ca/ca.go
Ref: Size: 4.1 KiB
package ca
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"errors"
"math/big"
"net"
"os"
"path/filepath"
"time"
)
// LoadOrCreate loads an existing CA cert and key from dir, or generates a new one and saves it.
func LoadOrCreate(dir string) (*x509.Certificate, *ecdsa.PrivateKey, error) {
certPath := filepath.Join(dir, "ca.pem")
keyPath := filepath.Join(dir, "ca.key")
_, certErr := os.Stat(certPath)
_, keyErr := os.Stat(keyPath)
if certErr == nil && keyErr == nil {
return load(certPath, keyPath)
}
return generate(certPath, keyPath)
}
func load(certPath, keyPath string) (*x509.Certificate, *ecdsa.PrivateKey, error) {
certPEM, err := os.ReadFile(certPath)
if err != nil {
return nil, nil, err
}
keyPEM, err := os.ReadFile(keyPath)
if err != nil {
return nil, nil, err
}
block, _ := pem.Decode(certPEM)
if block == nil {
return nil, nil, errors.New("failed to decode ca.pem")
}
cert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
return nil, nil, err
}
block, _ = pem.Decode(keyPEM)
if block == nil {
return nil, nil, errors.New("failed to decode ca.key")
}
key, err := x509.ParseECPrivateKey(block.Bytes)
if err != nil {
return nil, nil, err
}
return cert, key, nil
}
func generate(certPath, keyPath string) (*x509.Certificate, *ecdsa.PrivateKey, error) {
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
return nil, nil, err
}
serial, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128))
if err != nil {
return nil, nil, err
}
template := &x509.Certificate{
SerialNumber: serial,
Subject: pkix.Name{
CommonName: "Nono Proxy CA",
},
NotBefore: time.Now().Add(-time.Minute),
NotAfter: time.Now().Add(10 * 365 * 24 * time.Hour),
IsCA: true,
BasicConstraintsValid: true,
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
}
certDER, err := x509.CreateCertificate(rand.Reader, template, template, &key.PublicKey, key)
if err != nil {
return nil, nil, err
}
cert, err := x509.ParseCertificate(certDER)
if err != nil {
return nil, nil, err
}
// Save cert
certFile, err := os.Create(certPath)
if err != nil {
return nil, nil, err
}
defer certFile.Close()
if err := pem.Encode(certFile, &pem.Block{Type: "CERTIFICATE", Bytes: certDER}); err != nil {
return nil, nil, err
}
// Save key
keyDER, err := x509.MarshalECPrivateKey(key)
if err != nil {
return nil, nil, err
}
keyFile, err := os.OpenFile(keyPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
if err != nil {
return nil, nil, err
}
defer keyFile.Close()
if err := pem.Encode(keyFile, &pem.Block{Type: "EC PRIVATE KEY", Bytes: keyDER}); err != nil {
return nil, nil, err
}
return cert, key, nil
}
// GenerateLeaf generates a leaf TLS certificate for the given host, signed by the CA.
func GenerateLeaf(host string, caCert *x509.Certificate, caKey *ecdsa.PrivateKey) (tls.Certificate, error) {
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
return tls.Certificate{}, err
}
serial, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128))
if err != nil {
return tls.Certificate{}, err
}
template := &x509.Certificate{
SerialNumber: serial,
Subject: pkix.Name{
CommonName: host,
},
NotBefore: time.Now().Add(-time.Minute),
NotAfter: time.Now().Add(24 * time.Hour),
KeyUsage: x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{
x509.ExtKeyUsageServerAuth,
},
}
if ip := net.ParseIP(host); ip != nil {
template.IPAddresses = []net.IP{ip}
} else {
template.DNSNames = []string{host}
}
certDER, err := x509.CreateCertificate(rand.Reader, template, caCert, &key.PublicKey, caKey)
if err != nil {
return tls.Certificate{}, err
}
certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER})
keyDER, err := x509.MarshalECPrivateKey(key)
if err != nil {
return tls.Certificate{}, err
}
keyPEM := pem.EncodeToMemory(&pem.Block{Type: "EC PRIVATE KEY", Bytes: keyDER})
return tls.X509KeyPair(certPEM, keyPEM)
}