linkerd2/pkg/tls/ca.go

297 lines
8.8 KiB
Go

package tls
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/x509"
"crypto/x509/pkix"
"fmt"
"math/big"
"time"
)
type (
// CA provides a certificate authority for TLS-enabled installs.
// Issuing certificates concurrently is not supported.
CA struct {
// Cred contains the CA's credentials.
Cred Cred
// Validity configures the NotBefore and NotAfter parameters for certificates
// issued by this CA.
//
// 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 Validity
// 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
// firstCrtExpiration is the time when the first expiration of a certificate
// in the trust chain occurs
firstCrtExpiration time.Time
}
// Validity configures the expiry times of issued certificates.
Validity 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.
Lifetime time.Duration
// ClockSkewAllowance 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.
ClockSkewAllowance time.Duration
// ValidFrom is the point in time from which the certificate is valid.
// This is cert.NotBefore with some clock skew allowance.
ValidFrom *time.Time
}
// Issuer implementors signs certificate requests.
Issuer interface {
IssueEndEntityCrt(*x509.CertificateRequest) (Crt, error)
}
)
const (
// DefaultLifetime configures certificate validity.
//
// Initially all certificates will be valid for one year.
//
// TODO: Shorten the validity duration of CA and end-entity certificates downward.
DefaultLifetime = (24 * 365) * time.Hour
// DefaultClockSkewAllowance indicates the maximum allowed difference in clocks
// in the network.
//
// TODO: 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.
DefaultClockSkewAllowance = 10 * time.Second
)
// Finds the time at which the first certificate
// from the chain will expire
func findFirstExpiration(cred *Cred) time.Time {
firstExpiration := cred.Certificate.NotAfter
for _, c := range cred.TrustChain {
if c.NotAfter.Before(firstExpiration) {
firstExpiration = c.NotAfter
}
}
return firstExpiration
}
// NewCA initializes a new CA with default settings.
func NewCA(cred Cred, validity Validity) *CA {
return &CA{cred, validity, uint64(1), findFirstExpiration(&cred)}
}
func init() {
// Assert that the struct implements the interface.
var _ Issuer = &CA{}
}
// CreateRootCA configures a new root CA with the given settings
func CreateRootCA(
name string,
key *ecdsa.PrivateKey,
validity Validity,
) (*CA, error) {
// Configure the root certificate.
t := createTemplate(1, &key.PublicKey, validity)
t.Subject = pkix.Name{CommonName: name}
t.IsCA = true
t.MaxPathLen = -1
t.BasicConstraintsValid = true
t.KeyUsage = x509.KeyUsageCertSign | x509.KeyUsageCRLSign
// Self-sign the root certificate.
crtb, err := x509.CreateCertificate(rand.Reader, t, t, key.Public(), key)
if err != nil {
return nil, err
}
c, err := x509.ParseCertificate(crtb)
if err != nil {
return nil, err
}
// The Crt has an empty TrustChain because it's at the root.
cred := validCredOrPanic(key, Crt{Certificate: c})
ca := NewCA(cred, validity)
ca.nextSerialNumber++ // Because we've already created the root cert.
return ca, nil
}
// GenerateKey creates a new P-256 ECDSA private key from the default random
// source.
func GenerateKey() (*ecdsa.PrivateKey, error) {
return ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
}
// GenerateRootCAWithDefaults generates a new root CA with default settings.
func GenerateRootCAWithDefaults(name string) (*CA, error) {
// Generate a new root key.
key, err := GenerateKey()
if err != nil {
return nil, err
}
return CreateRootCA(name, key, Validity{})
}
// GenerateCA generates a new intermediate CA.
func (ca *CA) GenerateCA(name string, maxPathLen int) (*CA, error) {
key, err := GenerateKey()
if err != nil {
return nil, err
}
t := ca.createTemplate(&key.PublicKey)
t.Subject = pkix.Name{CommonName: name}
t.IsCA = true
t.MaxPathLen = maxPathLen
t.MaxPathLenZero = true // 0-values are actually 0
t.BasicConstraintsValid = true
t.KeyUsage = x509.KeyUsageCertSign | x509.KeyUsageCRLSign
crt, err := ca.Cred.SignCrt(t)
if err != nil {
return nil, err
}
return NewCA(validCredOrPanic(key, crt), ca.Validity), nil
}
// GenerateEndEntityCred creates a new certificate that is valid for the
// given DNS name, generating a new keypair for it.
func (ca *CA) GenerateEndEntityCred(dnsName string) (*Cred, error) {
key, err := GenerateKey()
if err != nil {
return nil, err
}
csr := x509.CertificateRequest{
Subject: pkix.Name{CommonName: dnsName},
DNSNames: []string{dnsName},
PublicKey: &key.PublicKey,
}
crt, err := ca.IssueEndEntityCrt(&csr)
if err != nil {
return nil, err
}
c := validCredOrPanic(key, crt)
return &c, nil
}
// IssueEndEntityCrt creates a new certificate that is valid for the
// given DNS name, generating a new keypair for it.
func (ca *CA) IssueEndEntityCrt(csr *x509.CertificateRequest) (Crt, error) {
pubkey, ok := csr.PublicKey.(*ecdsa.PublicKey)
if !ok {
return Crt{}, fmt.Errorf("CSR must contain an ECDSA public key: %+v", csr.PublicKey)
}
t := ca.createTemplate(pubkey)
t.Issuer = ca.Cred.Crt.Certificate.Subject
t.Subject = csr.Subject
t.Extensions = csr.Extensions
t.ExtraExtensions = csr.ExtraExtensions
t.DNSNames = csr.DNSNames
t.EmailAddresses = csr.EmailAddresses
t.IPAddresses = csr.IPAddresses
t.URIs = csr.URIs
return ca.Cred.SignCrt(t)
}
// createTemplate returns a certificate t for a non-CA certificate with
// no subject name, no subjectAltNames. The t can then be modified into
// a (root) CA t or an end-entity t by the caller.
func (ca *CA) createTemplate(pubkey *ecdsa.PublicKey) *x509.Certificate {
c := createTemplate(ca.nextSerialNumber, pubkey, ca.Validity)
ca.nextSerialNumber++
// if our trust chain contains a certificate that expires
// sooner than the one we intend to issue, we clamp the
// NotAfter time of our newly issued certificate. That ensures
// the proxy will request a new cert before any of the
// certs in the chain are expired.
if ca.firstCrtExpiration.Before(c.NotAfter) {
c.NotAfter = ca.firstCrtExpiration
}
return c
}
// createTemplate returns a certificate t for a non-CA certificate with
// no subject name, no subjectAltNames. The t can then be modified into
// a (root) CA t or an end-entity t by the caller.
func createTemplate(
serialNumber uint64,
k *ecdsa.PublicKey,
v Validity,
) *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
if v.ValidFrom == nil {
now := time.Now()
v.ValidFrom = &now
}
notBefore, notAfter := v.Window(*v.ValidFrom)
return &x509.Certificate{
SerialNumber: big.NewInt(int64(serialNumber)),
SignatureAlgorithm: SignatureAlgorithm,
NotBefore: notBefore,
NotAfter: notAfter,
PublicKey: k,
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{
x509.ExtKeyUsageServerAuth,
x509.ExtKeyUsageClientAuth,
},
}
}
// Window returns the time window for which a certificate should be valid.
func (v *Validity) Window(t time.Time) (time.Time, time.Time) {
life := v.Lifetime
if life == 0 {
life = DefaultLifetime
}
skew := v.ClockSkewAllowance
if skew == 0 {
skew = DefaultClockSkewAllowance
}
start := t.Add(-skew)
end := t.Add(life).Add(skew)
return start, end
}