Files
Local-Cursor/internal/certs/local.go

79 lines
2.4 KiB
Go
Raw Normal View History

package certs
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"errors"
"fmt"
"math/big"
"os"
"path/filepath"
"time"
"cursor/internal/securefile"
)
// LoadOrCreateLocalCA persists one private CA per application data directory.
// A single private bundle is the authority; ca.crt is a public export for clients.
func LoadOrCreateLocalCA(dir string) (*Manager, error) {
if err := securefile.PrivateDir(dir); err != nil {
return nil, err
}
unlock, err := securefile.Lock(filepath.Join(dir, "ca.lock"))
if err != nil {
return nil, err
}
defer unlock()
bundlePath := filepath.Join(dir, "local-ca.pem")
bundle, err := os.ReadFile(bundlePath)
if errors.Is(err, os.ErrNotExist) {
key, keyErr := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if keyErr != nil {
return nil, keyErr
}
serial, keyErr := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128))
if keyErr != nil {
return nil, keyErr
}
now := time.Now()
template := &x509.Certificate{SerialNumber: serial, Subject: pkix.Name{CommonName: "Local-Cursor Private CA", Organization: []string{"Local-Cursor"}}, NotBefore: now.Add(-time.Hour), NotAfter: now.AddDate(10, 0, 0), IsCA: true, BasicConstraintsValid: true, KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign}
der, keyErr := x509.CreateCertificate(rand.Reader, template, template, &key.PublicKey, key)
if keyErr != nil {
return nil, keyErr
}
keyPEM, keyErr := marshalPrivateKeyPEM(key)
if keyErr != nil {
return nil, keyErr
}
bundle = append(pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: der}), keyPEM...)
if err = securefile.Write(bundlePath, bundle); err != nil {
return nil, err
}
} else if err != nil {
return nil, err
}
if err = securefile.Protect(bundlePath); err != nil {
return nil, err
}
certBlock, rest := pem.Decode(bundle)
if certBlock == nil {
return nil, fmt.Errorf("本机 CA 文件损坏,请先备份并修复")
}
manager, err := NewManagerFromPEM(pem.EncodeToMemory(certBlock), rest)
if err != nil {
return nil, fmt.Errorf("本机 CA 校验失败(不会自动轮换): %w", err)
}
if err = securefile.Write(filepath.Join(dir, "ca.crt"), manager.CACertPEM()); err != nil {
return nil, err
}
return manager, nil
}
func (m *Manager) CACertPEM() []byte {
return pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: m.caCert.Raw})
}