95 lines
2.8 KiB
Go
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
|
|
}
|