Files

95 lines
2.8 KiB
Go

// 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
}