150 lines
3.5 KiB
Go
150 lines
3.5 KiB
Go
package ca
|
|
|
|
import (
|
|
"crypto"
|
|
"crypto/ed25519"
|
|
"crypto/rand"
|
|
"crypto/x509"
|
|
"crypto/x509/pkix"
|
|
"encoding/pem"
|
|
"fmt"
|
|
"math/big"
|
|
"os"
|
|
"path/filepath"
|
|
"time"
|
|
)
|
|
|
|
type CA struct {
|
|
key crypto.Signer
|
|
cert *x509.Certificate
|
|
}
|
|
|
|
func NewOrLoad(dataDir string, keyType string, validity time.Duration) (*CA, error) {
|
|
keyPath := filepath.Join(dataDir, "ca.key")
|
|
certPath := filepath.Join(dataDir, "ca.crt")
|
|
|
|
caKey, err := loadOrGenKey(keyPath, keyType)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("ca key: %w", err)
|
|
}
|
|
|
|
caCert, err := loadOrGenCert(certPath, caKey, validity)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("ca cert: %w", err)
|
|
}
|
|
|
|
return &CA{key: caKey, cert: caCert}, nil
|
|
}
|
|
|
|
func loadOrGenKey(path, keyType string) (crypto.Signer, error) {
|
|
if data, err := os.ReadFile(path); err == nil {
|
|
block, _ := pem.Decode(data)
|
|
if block == nil {
|
|
return nil, fmt.Errorf("invalid CA key PEM")
|
|
}
|
|
key, err := x509.ParsePKCS8PrivateKey(block.Bytes)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return key.(crypto.Signer), nil
|
|
}
|
|
|
|
var signer crypto.Signer
|
|
switch keyType {
|
|
case "ed25519":
|
|
_, priv, err := ed25519.GenerateKey(rand.Reader)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
signer = priv
|
|
default:
|
|
return nil, fmt.Errorf("unsupported key type: %s", keyType)
|
|
}
|
|
|
|
b, err := x509.MarshalPKCS8PrivateKey(signer)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
pemData := pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: b})
|
|
if err := os.WriteFile(path, pemData, 0600); err != nil {
|
|
return nil, err
|
|
}
|
|
return signer, nil
|
|
}
|
|
|
|
func loadOrGenCert(path string, key crypto.Signer, validity time.Duration) (*x509.Certificate, error) {
|
|
if data, err := os.ReadFile(path); err == nil {
|
|
block, _ := pem.Decode(data)
|
|
if block == nil {
|
|
return nil, fmt.Errorf("invalid CA cert PEM")
|
|
}
|
|
return x509.ParseCertificate(block.Bytes)
|
|
}
|
|
|
|
pub := key.Public()
|
|
serial, _ := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128))
|
|
now := time.Now()
|
|
|
|
template := &x509.Certificate{
|
|
SerialNumber: serial,
|
|
Subject: pkix.Name{
|
|
CommonName: "QuiC Call Root CA",
|
|
},
|
|
NotBefore: now,
|
|
NotAfter: now.Add(validity),
|
|
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
|
|
BasicConstraintsValid: true,
|
|
IsCA: true,
|
|
MaxPathLenZero: true,
|
|
}
|
|
|
|
certDER, err := x509.CreateCertificate(rand.Reader, template, template, pub, key)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
pemData := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER})
|
|
if err := os.WriteFile(path, pemData, 0644); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return x509.ParseCertificate(certDER)
|
|
}
|
|
|
|
func (ca *CA) IssueCert(commonName string, pub crypto.PublicKey, validity time.Duration) ([]byte, error) {
|
|
serial, _ := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128))
|
|
now := time.Now()
|
|
|
|
template := &x509.Certificate{
|
|
SerialNumber: serial,
|
|
Subject: pkix.Name{
|
|
CommonName: commonName,
|
|
Organization: []string{"QuiC Call"},
|
|
},
|
|
NotBefore: now,
|
|
NotAfter: now.Add(validity),
|
|
KeyUsage: x509.KeyUsageDigitalSignature,
|
|
ExtKeyUsage: []x509.ExtKeyUsage{
|
|
x509.ExtKeyUsageClientAuth,
|
|
},
|
|
}
|
|
|
|
certDER, err := x509.CreateCertificate(rand.Reader, template, ca.cert, pub, ca.key)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("create cert: %w", err)
|
|
}
|
|
|
|
return certDER, nil
|
|
}
|
|
|
|
func (ca *CA) CAKey() crypto.Signer {
|
|
return ca.key
|
|
}
|
|
|
|
func (ca *CA) CACert() *x509.Certificate {
|
|
return ca.cert
|
|
}
|
|
|
|
func (ca *CA) CACertPEM() []byte {
|
|
return pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: ca.cert.Raw})
|
|
}
|