linkerd2/controller/ca/ca.go

171 lines
5.2 KiB
Go

package ca
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"math/big"
"time"
)
// Issuing certificates concurrently is not supported.
type CA struct {
// validity is the duration for which issued certificates are valid. This
// is approximately cert.NotAfter - cert.NotBefore with some additional
// allowance for clock skew.
//
// Currently this is used for the CA's validity too, but nothing should
// assume that the CA's validity period is the same as issued certificates'
// validity.
validity time.Duration
// clockSkewAllocance is the maximum supported clock skew. Everything that
// processes the certificates must have a system clock that is off by no
// more than this allowance in either direction.
clockSkewAllocance time.Duration
// The CA's private key.
privateKey *ecdsa.PrivateKey
// The CA's certificate.
root *x509.Certificate
// The PEM X.509 encoding of `root`
rootPEM string
// nextSerialNumber is the serial number of the next certificate to issue.
// Serial numbers must not be reused.
//
// It is assumed there is only one instance of CA and it is assumed that a
// given CA object isn't requested to issue certificates concurrently.
//
// For now we do not attempt to meet CABForum requirements (e.g. regarding
// randomness).
nextSerialNumber uint64
}
type CertificateAndPrivateKey struct {
// The ASN.1 DER-encoded (binary, not PEM) certificate.
Certificate []byte
// The PKCS#8 DER-encoded (binary, not PEM) private key.
PrivateKey []byte
}
// NewCA is the only way to create a CA.
func NewCA() (*CA, error) {
// Initially all certificates will be valid for one year. TODO: Shorten the
// validity duration of CA and end-entity certificates downward.
validity := (24 * 365) * time.Hour
// Allow half a day of clock skew. TODO: decrease the default value of this
// and make it tunable. TODO: Reconsider how this interacts with the
// similar logic in the webpki verifier; since both are trying to account
// for clock skew, there is somewhat of an over-correction.
clockSkewAllocance := 12 * time.Hour
privateKey, err := generateKeyPair()
if err != nil {
return nil, err
}
ca := CA{
validity: validity,
clockSkewAllocance: clockSkewAllocance,
privateKey: privateKey,
nextSerialNumber: 1,
}
template := ca.createTemplate(&ca.privateKey.PublicKey)
template.Subject = pkix.Name{CommonName: "Cluster-local Managed Pod CA"}
// basicConstraints.cA = true
template.IsCA = true
template.MaxPathLen = -1
template.BasicConstraintsValid = true
// `parent == template` means "self-signed".
rootDer, err := x509.CreateCertificate(rand.Reader, &template, &template, privateKey.Public(), privateKey)
if err != nil {
return nil, err
}
ca.root, err = x509.ParseCertificate(rootDer)
if err != nil {
return nil, err
}
ca.rootPEM = string(pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: ca.root.Raw}))
return &ca, nil
}
// TrustAnchorDER returns the PEM-encoded X.509 certificate of the trust anchor
// (root CA).
func (ca *CA) TrustAnchorPEM() string {
return ca.rootPEM
}
// IssueEndEntityCertificate creates a new certificate that is valid for the
// given DNS name, generating a new keypair for it.
func (ca *CA) IssueEndEntityCertificate(dnsName string) (*CertificateAndPrivateKey, error) {
privateKey, err := generateKeyPair()
if err != nil {
return nil, err
}
p8, err := x509.MarshalPKCS8PrivateKey(privateKey)
if err != nil {
return nil, err
}
template := ca.createTemplate(&privateKey.PublicKey)
template.DNSNames = []string{dnsName}
crt, err := x509.CreateCertificate(rand.Reader, &template, ca.root, &privateKey.PublicKey, ca.privateKey)
if err != nil {
return nil, err
}
return &CertificateAndPrivateKey{
Certificate: crt,
PrivateKey: p8,
}, nil
}
// createTemplate returns a certificate template for a non-CA certificate with
// no subject name, no subjectAltNames. The template can then be modified into
// a (root) CA template or an end-entity template by the caller.
func (ca *CA) createTemplate(publicKey *ecdsa.PublicKey) x509.Certificate {
// ECDSA is used instead of RSA because ECDSA key generation is
// straightforward and fast whereas RSA key generation is extremely slow
// and error-prone.
//
// CA certificates are signed with the same algorithm as end-entity
// certificates because they are relatively short-lived, because using one
// algorithm minimizes exposure to implementation flaws, and to speed up
// signature verification time.
//
// SHA-256 is used because any larger digest would be truncated to 256 bits
// anyway since a P-256 scalar is only 256 bits long.
const SignatureAlgorithm = x509.ECDSAWithSHA256
serialNumber := big.NewInt(int64(ca.nextSerialNumber))
ca.nextSerialNumber += 1
notBefore := time.Now()
return x509.Certificate{
SerialNumber: serialNumber,
SignatureAlgorithm: SignatureAlgorithm,
NotBefore: notBefore.Add(-ca.clockSkewAllocance),
NotAfter: notBefore.Add(ca.validity).Add(ca.clockSkewAllocance),
PublicKey: publicKey,
}
}
func generateKeyPair() (*ecdsa.PrivateKey, error) {
return ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
}