// Package security provides shared-key and TLS certificate generation for // clip-sync peers. It uses only the Go standard library. package security import ( "crypto/ecdsa" "crypto/elliptic" "crypto/rand" "crypto/x509" "crypto/x509/pkix" "encoding/hex" "encoding/pem" "fmt" "math/big" "net" "os" "time" ) // GenerateKey returns a random 256-bit shared key, hex-encoded. It is suitable // for use as the security.shared_key bearer token shared by all peers. func GenerateKey() (string, error) { buf := make([]byte, 32) if _, err := rand.Read(buf); err != nil { return "", fmt.Errorf("generate key: %w", err) } return hex.EncodeToString(buf), nil } // GenerateSelfSignedCert creates a self-signed ECDSA certificate and writes it // (PEM-encoded) to certFile and keyFile. It is valid for the given hosts/IPs // and 10 years, which is acceptable for a LAN trust model where all peers // share the same certificate and key files. func GenerateSelfSignedCert(certFile, keyFile string, hosts []string) error { priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) if err != nil { return fmt.Errorf("generate cert: %w", err) } serial, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128)) if err != nil { return fmt.Errorf("generate cert serial: %w", err) } now := time.Now() tmpl := x509.Certificate{ SerialNumber: serial, Subject: pkix.Name{CommonName: "clip-sync"}, NotBefore: now.Add(-time.Hour), NotAfter: now.Add(10 * 365 * 24 * time.Hour), KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}, } for _, h := range hosts { if ip := net.ParseIP(h); ip != nil { tmpl.IPAddresses = append(tmpl.IPAddresses, ip) } else { tmpl.DNSNames = append(tmpl.DNSNames, h) } } if len(tmpl.IPAddresses) == 0 && len(tmpl.DNSNames) == 0 { tmpl.DNSNames = []string{"localhost"} } der, err := x509.CreateCertificate(rand.Reader, &tmpl, &tmpl, &priv.PublicKey, priv) if err != nil { return fmt.Errorf("generate cert: create: %w", err) } certOut, err := os.Create(certFile) if err != nil { return fmt.Errorf("generate cert: %w", err) } defer certOut.Close() if err := pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: der}); err != nil { return fmt.Errorf("generate cert: encode: %w", err) } keyDER, err := x509.MarshalECPrivateKey(priv) if err != nil { return fmt.Errorf("generate cert: marshal key: %w", err) } keyOut, err := os.OpenFile(keyFile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) if err != nil { return fmt.Errorf("generate cert: %w", err) } defer keyOut.Close() if err := pem.Encode(keyOut, &pem.Block{Type: "EC PRIVATE KEY", Bytes: keyDER}); err != nil { return fmt.Errorf("generate cert: encode key: %w", err) } return nil }