88 lines
2.2 KiB
Go
88 lines
2.2 KiB
Go
/*
|
|
Package ca implements a disk file-backed certificate authority with a built-in
|
|
CRL signer. The certificate authority can issue new certificates (including the
|
|
creation of intermediate CAs) and revoke existing certificates, resulting in a
|
|
fresh CRL.
|
|
*/
|
|
package ca
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"crypto/x509"
|
|
"crypto/x509/pkix"
|
|
"fmt"
|
|
"math/big"
|
|
"path/filepath"
|
|
"time"
|
|
|
|
"src.lwithers.me.uk/go/rsa/pkg/pemfile"
|
|
)
|
|
|
|
const (
|
|
crlFilename = "crl.pem"
|
|
crlKeyFilename = "crl-signer-key.pem"
|
|
crlCertFilename = "crl-signer-cert.pem"
|
|
)
|
|
|
|
func (ca *CA) loadCRLState() error {
|
|
var err error
|
|
ca.crl, err = pemfile.ReadCRL(filepath.Join(ca.dir, crlFilename))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
ca.crlKey, err = pemfile.ReadKey(filepath.Join(ca.dir, crlKeyFilename))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
ca.crlCert, err = pemfile.ReadCert(filepath.Join(ca.dir, crlCertFilename))
|
|
return err
|
|
}
|
|
|
|
func (ca *CA) Revoke(serial *big.Int) (*x509.RevocationList, error) {
|
|
// if the certificate is already revoked, update its revocation time
|
|
var found bool
|
|
for i := range ca.crl.RevokedCertificates {
|
|
if ca.crl.RevokedCertificates[i].SerialNumber.Cmp(serial) == 0 {
|
|
ca.crl.RevokedCertificates[i].RevocationTime = time.Now()
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
|
|
// otherwise, add a new entry
|
|
if !found {
|
|
n := big.NewInt(0)
|
|
n.SetBytes(serial.Bytes())
|
|
ca.crl.RevokedCertificates = append(ca.crl.RevokedCertificates,
|
|
pkix.RevokedCertificate{
|
|
SerialNumber: n,
|
|
RevocationTime: time.Now(),
|
|
})
|
|
}
|
|
|
|
// increment issue number
|
|
ca.crl.Number.Add(ca.crl.Number, big.NewInt(1))
|
|
ca.crl.ThisUpdate = time.Now()
|
|
|
|
// create new DER-form certificate
|
|
der, err := x509.CreateRevocationList(rand.Reader, ca.crl, ca.crlCert, ca.crlKey)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to sign new CRL: %w", err)
|
|
}
|
|
|
|
// ensure we can parse it
|
|
newCRL, err := x509.ParseRevocationList(der)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to parse newly-signed CRL: %w", err)
|
|
}
|
|
|
|
// save to disk
|
|
if err := pemfile.Write(filepath.Join(ca.dir, crlFilename), pemfile.TypeX509CRL, der); err != nil {
|
|
return nil, fmt.Errorf("failed to save CRL: %w", err)
|
|
}
|
|
|
|
// update in-memory copy
|
|
ca.crl = newCRL
|
|
return ca.crl, nil
|
|
}
|