// Copyright 2020-2021 Cloudflare, Inc. All rights reserved. Use of this source code // is governed by a BSD-style license that can be found in the LICENSE file. package tls // Delegated Credentials for TLS // (https://tools.ietf.org/html/draft-ietf-tls-subcerts) is an IETF Internet // draft and proposed TLS extension. If the client or server supports this // extension, then the server or client may use a "delegated credential" as the // signing key in the handshake. A delegated credential is a short lived // public/secret key pair delegated to the peer by an entity trusted by the // corresponding peer. This allows a reverse proxy to terminate a TLS connection // on behalf of the entity. Credentials can't be revoked; in order to // mitigate risk in case the reverse proxy is compromised, the credential is only // valid for a short time (days, hours, or even minutes). import ( "bytes" "crypto" "crypto/ecdsa" "crypto/ed25519" "crypto/elliptic" "crypto/rand" "crypto/rsa" "crypto/x509" "encoding/binary" "errors" "fmt" "io" "time" "golang.org/x/crypto/cryptobyte" ) const ( // In the absence of an application profile standard specifying otherwise, // the maximum validity period is set to 7 days. dcMaxTTLSeconds = 60 * 60 * 24 * 7 dcMaxTTL = time.Duration(dcMaxTTLSeconds * time.Second) dcMaxPubLen = (1 << 24) - 1 // Bytes dcMaxSignatureLen = (1 << 16) - 1 // Bytes ) const ( undefinedSignatureScheme SignatureScheme = 0x0000 ) var extensionDelegatedCredential = []int{1, 3, 6, 1, 4, 1, 44363, 44} // isValidForDelegation returns true if a certificate can be used for Delegated // Credentials. func isValidForDelegation(cert *x509.Certificate) bool { // Check that the digitalSignature key usage is set. // The certificate must contains the digitalSignature KeyUsage. if (cert.KeyUsage & x509.KeyUsageDigitalSignature) == 0 { return false } // Check that the certificate has the DelegationUsage extension and that // it's marked as non-critical (See Section 4.2 of RFC5280). for _, extension := range cert.Extensions { if extension.Id.Equal(extensionDelegatedCredential) { if extension.Critical { return false } return true } } return false } // isExpired returns true if the credential has expired. The end of the validity // interval is defined as the delegator certificate's notBefore field ('start') // plus dc.cred.validTime seconds. This function simply checks that the current time // ('now') is before the end of the validity interval. func (dc *DelegatedCredential) isExpired(start, now time.Time) bool { end := start.Add(dc.cred.validTime) return !now.Before(end) } // invalidTTL returns true if the credential's validity period is longer than the // maximum permitted. This is defined by the certificate's notBefore field // ('start') plus the dc.validTime, minus the current time ('now'). func (dc *DelegatedCredential) invalidTTL(start, now time.Time) bool { return dc.cred.validTime > (now.Sub(start) + dcMaxTTL).Round(time.Second) } // credential stores the public components of a Delegated Credential. type credential struct { // The amount of time for which the credential is valid. Specifically, the // the credential expires 'validTime' seconds after the 'notBefore' of the // delegation certificate. The delegator shall not issue Delegated // Credentials that are valid for more than 7 days from the current time. // // When this data structure is serialized, this value is converted to a // uint32 representing the duration in seconds. validTime time.Duration // The signature scheme associated with the credential public key. // This is expected to be the same as the CertificateVerify.algorithm // sent by the client or server. expCertVerfAlgo SignatureScheme // The credential's public key. publicKey crypto.PublicKey } // DelegatedCredential stores a Delegated Credential with the credential and its // signature. type DelegatedCredential struct { // The serialized form of the Delegated Credential. raw []byte // Cred stores the public components of a Delegated Credential. cred *credential // The signature scheme used to sign the Delegated Credential. algorithm SignatureScheme // The Credential's delegation: a signature that binds the credential to // the end-entity certificate's public key. signature []byte } // marshalPublicKeyInfo returns a DER encoded PublicKeyInfo // from a Delegated Credential (as defined in the X.509 standard). // The following key types are currently supported: *ecdsa.PublicKey // and ed25519.PublicKey. Unsupported key types result in an error. // rsa.PublicKey is not supported as defined by the draft. func (cred *credential) marshalPublicKeyInfo() ([]byte, error) { switch cred.expCertVerfAlgo { case ECDSAWithP256AndSHA256, ECDSAWithP384AndSHA384, ECDSAWithP521AndSHA512, Ed25519: rawPub, err := x509.MarshalPKIXPublicKey(cred.publicKey) if err != nil { return nil, err } return rawPub, nil default: return nil, fmt.Errorf("tls: unsupported signature scheme: 0x%04x", cred.expCertVerfAlgo) } } // marshal encodes the credential struct of the Delegated Credential. func (cred *credential) marshal() ([]byte, error) { var b cryptobyte.Builder b.AddUint32(uint32(cred.validTime / time.Second)) b.AddUint16(uint16(cred.expCertVerfAlgo)) // Encode the public key rawPub, err := cred.marshalPublicKeyInfo() if err != nil { return nil, err } // Assert that the public key encoding is no longer than 2^24-1 bytes. if len(rawPub) > dcMaxPubLen { return nil, errors.New("tls: public key length exceeds 2^24-1 limit") } b.AddUint24(uint32(len(rawPub))) b.AddBytes(rawPub) raw := b.BytesOrPanic() return raw, nil } // unmarshalCredential decodes serialized bytes and returns a credential, if possible. func unmarshalCredential(raw []byte) (*credential, error) { if len(raw) < 10 { return nil, errors.New("tls: Delegated Credential is not valid: invalid length") } s := cryptobyte.String(raw) var t uint32 if !s.ReadUint32(&t) { return nil, errors.New("tls: Delegated Credential is not valid") } validTime := time.Duration(t) * time.Second var pubAlgo uint16 if !s.ReadUint16(&pubAlgo) { return nil, errors.New("tls: Delegated Credential is not valid") } algo := SignatureScheme(pubAlgo) var pubLen uint32 s.ReadUint24(&pubLen) pubKey, err := x509.ParsePKIXPublicKey(s) if err != nil { return nil, err } return &credential{validTime, algo, pubKey}, nil } // getCredentialLen returns the number of bytes comprising the serialized // credential struct inside the Delegated Credential. func getCredentialLen(raw []byte) (int, error) { if len(raw) < 10 { return 0, errors.New("tls: Delegated Credential is not valid") } var read []byte s := cryptobyte.String(raw) s.ReadBytes(&read, 6) var pubLen uint32 s.ReadUint24(&pubLen) if !(pubLen > 0) { return 0, errors.New("tls: Delegated Credential is not valid") } raw = raw[6:] if len(raw) < int(pubLen) { return 0, errors.New("tls: Delegated Credential is not valid") } return 9 + int(pubLen), nil } // getHash maps the SignatureScheme to its corresponding hash function. func getHash(scheme SignatureScheme) crypto.Hash { switch scheme { case ECDSAWithP256AndSHA256: return crypto.SHA256 case ECDSAWithP384AndSHA384: return crypto.SHA384 case ECDSAWithP521AndSHA512: return crypto.SHA512 case Ed25519: return directSigning case PKCS1WithSHA256, PSSWithSHA256: return crypto.SHA256 case PSSWithSHA384: return crypto.SHA384 case PSSWithSHA512: return crypto.SHA512 default: return 0 // Unknown hash function } } // getECDSACurve maps the SignatureScheme to its corresponding ecdsa elliptic.Curve. func getECDSACurve(scheme SignatureScheme) elliptic.Curve { switch scheme { case ECDSAWithP256AndSHA256: return elliptic.P256() case ECDSAWithP384AndSHA384: return elliptic.P384() case ECDSAWithP521AndSHA512: return elliptic.P521() default: return nil } } // prepareDelegationSignatureInput returns the message that the delegator is going to sign. func prepareDelegationSignatureInput(hash crypto.Hash, cred *credential, dCert []byte, algo SignatureScheme, isClient bool) ([]byte, error) { header := make([]byte, 64) for i := range header { header[i] = 0x20 } var context string if !isClient { context = "TLS, server delegated credentials\x00" } else { context = "TLS, client delegated credentials\x00" } rawCred, err := cred.marshal() if err != nil { return nil, err } var rawAlgo [2]byte binary.BigEndian.PutUint16(rawAlgo[:], uint16(algo)) if hash == directSigning { b := &bytes.Buffer{} b.Write(header) io.WriteString(b, context) b.Write(dCert) b.Write(rawCred) b.Write(rawAlgo[:]) return b.Bytes(), nil } h := hash.New() h.Write(header) io.WriteString(h, context) h.Write(dCert) h.Write(rawCred) h.Write(rawAlgo[:]) return h.Sum(nil), nil } // Extract the algorithm used to sign the Delegated Credential from the // end-entity (leaf) certificate. func getSignatureAlgorithm(cert *Certificate) (SignatureScheme, error) { switch sk := cert.PrivateKey.(type) { case *ecdsa.PrivateKey: pk := sk.Public().(*ecdsa.PublicKey) curveName := pk.Curve.Params().Name certAlg := cert.Leaf.PublicKeyAlgorithm if certAlg == x509.ECDSA && curveName == "P-256" { return ECDSAWithP256AndSHA256, nil } else if certAlg == x509.ECDSA && curveName == "P-384" { return ECDSAWithP384AndSHA384, nil } else if certAlg == x509.ECDSA && curveName == "P-521" { return ECDSAWithP521AndSHA512, nil } else { return undefinedSignatureScheme, fmt.Errorf("using curve %s for %s is not supported", curveName, cert.Leaf.SignatureAlgorithm) } case ed25519.PrivateKey: return Ed25519, nil case *rsa.PrivateKey: // If the certificate has the RSAEncryption OID there are a number of valid signature schemes that may sign the DC. // In the absence of better information, we make a reasonable choice. return PSSWithSHA256, nil default: return undefinedSignatureScheme, fmt.Errorf("tls: unsupported algorithm for signing Delegated Credential") } } // NewDelegatedCredential creates a new Delegated Credential using 'cert' for // delegation, depending if the caller is the client or the server (defined by // 'isClient'). It generates a public/private key pair for the provided signature // algorithm ('pubAlgo') and it defines a validity interval (defined // by 'cert.Leaf.notBefore' and 'validTime'). It signs the Delegated Credential // using 'cert.PrivateKey'. func NewDelegatedCredential(cert *Certificate, pubAlgo SignatureScheme, validTime time.Duration, isClient bool) (*DelegatedCredential, crypto.PrivateKey, error) { // The granularity of DC validity is seconds. validTime = validTime.Round(time.Second) // Parse the leaf certificate if needed. var err error if cert.Leaf == nil { if len(cert.Certificate[0]) == 0 { return nil, nil, errors.New("tls: missing leaf certificate for Delegated Credential") } cert.Leaf, err = x509.ParseCertificate(cert.Certificate[0]) if err != nil { return nil, nil, err } } // Check that the leaf certificate can be used for delegation. if !isValidForDelegation(cert.Leaf) { return nil, nil, errors.New("tls: certificate not authorized for delegation") } sigAlgo, err := getSignatureAlgorithm(cert) if err != nil { return nil, nil, err } // Generate the Delegated Credential key pair based on the provided scheme var privK crypto.PrivateKey var pubK crypto.PublicKey switch pubAlgo { case ECDSAWithP256AndSHA256, ECDSAWithP384AndSHA384, ECDSAWithP521AndSHA512: privK, err = ecdsa.GenerateKey(getECDSACurve(pubAlgo), rand.Reader) if err != nil { return nil, nil, err } pubK = privK.(*ecdsa.PrivateKey).Public() case Ed25519: pubK, privK, err = ed25519.GenerateKey(rand.Reader) if err != nil { return nil, nil, err } default: return nil, nil, fmt.Errorf("tls: unsupported algorithm for Delegated Credential: %s", pubAlgo) } // Prepare the credential for signing hash := getHash(sigAlgo) credential := &credential{validTime, pubAlgo, pubK} values, err := prepareDelegationSignatureInput(hash, credential, cert.Leaf.Raw, sigAlgo, isClient) if err != nil { return nil, nil, err } var sig []byte switch sk := cert.PrivateKey.(type) { case *ecdsa.PrivateKey: opts := crypto.SignerOpts(hash) sig, err = sk.Sign(rand.Reader, values, opts) if err != nil { return nil, nil, err } case ed25519.PrivateKey: opts := crypto.SignerOpts(hash) sig, err = sk.Sign(rand.Reader, values, opts) if err != nil { return nil, nil, err } case *rsa.PrivateKey: opts := &rsa.PSSOptions{ SaltLength: rsa.PSSSaltLengthEqualsHash, Hash: hash, } sig, err = rsa.SignPSS(rand.Reader, sk, hash, values, opts) if err != nil { return nil, nil, err } default: return nil, nil, fmt.Errorf("tls: unsupported key type for Delegated Credential") } if len(sig) > dcMaxSignatureLen { return nil, nil, errors.New("tls: unable to create a Delegated Credential") } return &DelegatedCredential{ cred: credential, algorithm: sigAlgo, signature: sig, }, privK, nil } // Validate validates the Delegated Credential by checking that the signature is // valid, that it hasn't expired, and that the TTL is valid. It also checks that // certificate can be used for delegation. func (dc *DelegatedCredential) Validate(cert *x509.Certificate, isClient bool, now time.Time, certVerifyMsg *certificateVerifyMsg) bool { if dc.isExpired(cert.NotBefore, now) { return false } if dc.invalidTTL(cert.NotBefore, now) { return false } if dc.cred.expCertVerfAlgo != certVerifyMsg.signatureAlgorithm { return false } if !isValidForDelegation(cert) { return false } hash := getHash(dc.algorithm) in, err := prepareDelegationSignatureInput(hash, dc.cred, cert.Raw, dc.algorithm, isClient) if err != nil { return false } switch dc.algorithm { case ECDSAWithP256AndSHA256, ECDSAWithP384AndSHA384, ECDSAWithP521AndSHA512: pk, ok := cert.PublicKey.(*ecdsa.PublicKey) if !ok { return false } return ecdsa.VerifyASN1(pk, in, dc.signature) case Ed25519: pk, ok := cert.PublicKey.(ed25519.PublicKey) if !ok { return false } return ed25519.Verify(pk, in, dc.signature) case PSSWithSHA256, PSSWithSHA384, PSSWithSHA512: pk, ok := cert.PublicKey.(*rsa.PublicKey) if !ok { return false } hash := getHash(dc.algorithm) return rsa.VerifyPSS(pk, hash, in, dc.signature, nil) == nil default: return false } } // Marshal encodes a DelegatedCredential structure. It also sets dc.Raw to that // encoding. func (dc *DelegatedCredential) Marshal() ([]byte, error) { if len(dc.signature) > dcMaxSignatureLen { return nil, errors.New("tls: delegated credential is not valid") } if len(dc.signature) == 0 { return nil, errors.New("tls: delegated credential has no signature") } raw, err := dc.cred.marshal() if err != nil { return nil, err } var b cryptobyte.Builder b.AddBytes(raw) b.AddUint16(uint16(dc.algorithm)) b.AddUint16(uint16(len(dc.signature))) b.AddBytes(dc.signature) dc.raw = b.BytesOrPanic() return dc.raw, nil } // UnmarshalDelegatedCredential decodes a DelegatedCredential structure. func UnmarshalDelegatedCredential(raw []byte) (*DelegatedCredential, error) { rawCredentialLen, err := getCredentialLen(raw) if err != nil { return nil, err } credential, err := unmarshalCredential(raw[:rawCredentialLen]) if err != nil { return nil, err } raw = raw[rawCredentialLen:] if len(raw) < 4 { return nil, errors.New("tls: Delegated Credential is not valid") } s := cryptobyte.String(raw) var algo uint16 if !s.ReadUint16(&algo) { return nil, errors.New("tls: Delegated Credential is not valid") } var rawSignatureLen uint16 if !s.ReadUint16(&rawSignatureLen) { return nil, errors.New("tls: Delegated Credential is not valid") } var sig []byte if !s.ReadBytes(&sig, int(rawSignatureLen)) { return nil, errors.New("tls: Delegated Credential is not valid") } return &DelegatedCredential{ cred: credential, algorithm: SignatureScheme(algo), signature: sig, }, nil }