From 94f99bd3c434e0306c721182ae57a9a6188d8d8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sof=C3=ADa=20Celi?= Date: Wed, 19 Aug 2020 08:39:59 -0500 Subject: [PATCH] crypto/tls: implement draft-ietf-tls-subcerts-10 * Define API for delegated credentials so they are fetched using the same mechanisms used to fetch certificates * Allow the usage of other keyUsage when checking for the dc extension. Fixes issues in earlier patch, addressing #127, #128, #129, #130, and #131. Add tool for generating delegated credentials. Co-authored-by: jhoyla --- src/crypto/tls/auth.go | 22 + src/crypto/tls/common.go | 66 ++ src/crypto/tls/conn.go | 6 + src/crypto/tls/delegated_credentials.go | 548 +++++++++++++++ src/crypto/tls/delegated_credentials_test.go | 653 ++++++++++++++++++ src/crypto/tls/generate_cert.go | 10 + .../tls/generate_delegated_credential.go | 125 ++++ src/crypto/tls/handshake_client.go | 5 + src/crypto/tls/handshake_client_tls13.go | 137 +++- src/crypto/tls/handshake_messages.go | 88 ++- src/crypto/tls/handshake_messages_test.go | 12 + src/crypto/tls/handshake_server.go | 22 +- src/crypto/tls/handshake_server_tls13.go | 117 +++- src/crypto/tls/tls.go | 14 + src/crypto/tls/tls_test.go | 2 + src/crypto/x509/parser.go | 9 + src/crypto/x509/verify_test.go | 15 +- src/crypto/x509/x509.go | 15 + src/crypto/x509/x509_test.go | 42 ++ 19 files changed, 1874 insertions(+), 34 deletions(-) create mode 100644 src/crypto/tls/delegated_credentials.go create mode 100644 src/crypto/tls/delegated_credentials_test.go create mode 100644 src/crypto/tls/generate_delegated_credential.go diff --git a/src/crypto/tls/auth.go b/src/crypto/tls/auth.go index 9d3efe2d71e..86811ab5b18 100644 --- a/src/crypto/tls/auth.go +++ b/src/crypto/tls/auth.go @@ -259,6 +259,28 @@ func signatureSchemesForCertificate(version uint16, cert *Certificate) []Signatu return sigAlgs } +// selectSignatureSchemeDC picks a SignatureScheme from the peer's preference list +// that works with the selected delegated credential. It's only called for protocol +// versions that support delegated credential, so TLS 1.3. +func selectSignatureSchemeDC(vers uint16, dc *DelegatedCredential, peerAlgs []SignatureScheme, peerAlgsDC []SignatureScheme) (SignatureScheme, error) { + if vers != VersionTLS13 { + return 0, errors.New("unsupported TLS version for dc") + } + + if !isSupportedSignatureAlgorithm(dc.algorithm, peerAlgs) { + return undefinedSignatureScheme, errors.New("tls: peer doesn't support the delegated credential's signature") + } + + // Pick signature scheme in the peer's preference order, as our + // preference order is not configurable. + for _, preferredAlg := range peerAlgsDC { + if preferredAlg == dc.cred.expCertVerfAlgo { + return preferredAlg, nil + } + } + return 0, errors.New("tls: peer doesn't support the delegated credential's signature algorithm") +} + // selectSignatureScheme picks a SignatureScheme from the peer's preference list // that works with the selected certificate. It's only called for protocol // versions that support signature algorithms, so TLS 1.2 and 1.3. diff --git a/src/crypto/tls/common.go b/src/crypto/tls/common.go index 81334010ce4..cb00adfcc99 100644 --- a/src/crypto/tls/common.go +++ b/src/crypto/tls/common.go @@ -91,6 +91,7 @@ const ( extensionSignatureAlgorithms uint16 = 13 extensionALPN uint16 = 16 extensionSCT uint16 = 18 + extensionDelegatedCredentials uint16 = 34 extensionSessionTicket uint16 = 35 extensionPreSharedKey uint16 = 41 extensionEarlyData uint16 = 42 @@ -192,6 +193,16 @@ var supportedSignatureAlgorithms = []SignatureScheme{ ECDSAWithSHA1, } +// supportedSignatureAlgorithmsDC contains the signature and hash algorithms that +// the code advertises as supported in a TLS 1.3 ClientHello and in a TLS 1.3 +// CertificateRequest. This excludes 'rsa_pss_rsae_' algorithms. +var supportedSignatureAlgorithmsDC = []SignatureScheme{ + ECDSAWithP256AndSHA256, + Ed25519, + ECDSAWithP384AndSHA384, + ECDSAWithP521AndSHA512, +} + // helloRetryRequestRandom is set as the Random value of a ServerHello // to signal that the message is actually a HelloRetryRequest. var helloRetryRequestRandom = []byte{ // See RFC 8446, Section 4.1.3. @@ -259,6 +270,11 @@ type ConnectionState struct { // (and the peer provided a certificate) or RequireAndVerifyClientCert. VerifiedChains [][]*x509.Certificate + // VerifiedDC indicates that the Delegated Credential sent by the peer (if advertised + // and correctly processed), which has been verified against the leaf certificate, + // has been used. + VerifiedDC bool + // SignedCertificateTimestamps is a list of SCTs provided by the peer // through the TLS handshake for the leaf certificate, if any. SignedCertificateTimestamps [][]byte @@ -428,6 +444,13 @@ type ClientHelloInfo struct { // Algorithms Extension is being used (see RFC 5246, Section 7.4.1.4.1). SignatureSchemes []SignatureScheme + // SignatureSchemesDC lists the signature schemes that the client + // is willing to verify when using Delegated Credentials. + // This is and can be different from SignatureSchemes. SignatureSchemesDC + // is set only if the DelegatedCredentials Extension is being used. + // If Delegated Credentials are supported, this list should not be nil. + SignatureSchemesDC []SignatureScheme + // SupportedProtos lists the application protocols supported by the client. // SupportedProtos is set only if the Application-Layer Protocol // Negotiation Extension is being used (see RFC 7301, Section 3.1). @@ -442,6 +465,10 @@ type ClientHelloInfo struct { // might be rejected if used. SupportedVersions []uint16 + // SupportDelegatedCredential is true if the client indicated willingness + // to negotiate the Delegated Credential extension. + SupportsDelegatedCredential bool + // Conn is the underlying net.Conn for the connection. Do not read // from, or write to, this connection; that will cause the TLS // connection to fail. @@ -472,10 +499,21 @@ type CertificateRequestInfo struct { // empty slice indicates that the server has no preference. AcceptableCAs [][]byte + // SupportDelegatedCredential is true if the server indicated willingness + // to negotiate the Delegated Credential extension. + SupportsDelegatedCredential bool + // SignatureSchemes lists the signature schemes that the server is // willing to verify. SignatureSchemes []SignatureScheme + // SignatureSchemesDC lists the signature schemes that the server + // is willing to verify when using Delegated Credentials. + // This is and can be different from SignatureSchemes. SignatureSchemesDC + // is set only if the DelegatedCredentials Extension is being used. + // If Delegated Credentials are supported, this list should not be nil. + SignatureSchemesDC []SignatureScheme + // Version is the TLS version that was negotiated for this connection. Version uint16 @@ -752,6 +790,13 @@ type Config struct { // This feature is unstable and applications MUST NOT depend on it. CFControl interface{} + // SupportDelegatedCredential is true if the client or server is willing + // to negotiate the delegated credential extension. + // This can only be used with TLS 1.3. + // + // See https://tools.ietf.org/html/draft-ietf-tls-subcerts. + SupportDelegatedCredential bool + // mutex protects sessionTicketKeys and autoSessionTicketKeys. mutex sync.RWMutex // sessionTicketKeys contains zero or more ticket keys. If set, it means the @@ -842,6 +887,7 @@ func (c *Config) Clone() *Config { DynamicRecordSizingDisabled: c.DynamicRecordSizingDisabled, Renegotiation: c.Renegotiation, KeyLogWriter: c.KeyLogWriter, + SupportDelegatedCredential: c.SupportDelegatedCredential, CFEventHandler: c.CFEventHandler, CFControl: c.CFControl, sessionTicketKeys: c.sessionTicketKeys, @@ -1377,6 +1423,16 @@ func (c *Config) writeKeyLog(label string, clientRandom, secret []byte) error { // and is only for debugging, so a global mutex saves space. var writerMutex sync.Mutex +// A DelegatedCredentialPair contains a Delegated Credential and its +// associated private key. +type DelegatedCredentialPair struct { + // DC is the delegated credential. + DC *DelegatedCredential + // PrivateKey is the private key used to derive the public key of + // contained in DC. PrivateKey must implement crypto.Signer. + PrivateKey crypto.PrivateKey +} + // A Certificate is a chain of one or more certificates, leaf first. type Certificate struct { Certificate [][]byte @@ -1394,6 +1450,16 @@ type Certificate struct { // SignedCertificateTimestamps contains an optional list of Signed // Certificate Timestamps which will be served to clients that request it. SignedCertificateTimestamps [][]byte + // DelegatedCredentials are a list of Delegated Credentials with their + // corresponding private keys, signed by the leaf certificate. + // If there are no delegated credentials, this field is nil. + DelegatedCredentials []DelegatedCredentialPair + // DelegatedCredential is the delegated credential to be used in the + // handshake. + // If there are no delegated credentials, this field is nil. + // NOTE: Do not fill this field, as it will be filled depending on + // the provided list of delegated credentials. + DelegatedCredential []byte // Leaf is the parsed form of the leaf certificate, which may be initialized // using x509.ParseCertificate to reduce per-handshake processing. If nil, // the leaf certificate will be parsed as needed. diff --git a/src/crypto/tls/conn.go b/src/crypto/tls/conn.go index 3be0fe641fd..cf2f31ab164 100644 --- a/src/crypto/tls/conn.go +++ b/src/crypto/tls/conn.go @@ -53,6 +53,9 @@ type Conn struct { // verifiedChains contains the certificate chains that we built, as // opposed to the ones presented by the server. verifiedChains [][]*x509.Certificate + // verifiedDC contains the Delegated Credential sent by the peer (if advertised + // and correctly processed), which has been verified against the leaf certificate. + verifiedDC *DelegatedCredential // serverName contains the server name indicated by the client, if any. serverName string // secureRenegotiation is true if the server echoed the secure @@ -1494,6 +1497,9 @@ func (c *Conn) connectionStateLocked() ConnectionState { state.CipherSuite = c.cipherSuite state.PeerCertificates = c.peerCertificates state.VerifiedChains = c.verifiedChains + if c.verifiedDC != nil { + state.VerifiedDC = true + } state.SignedCertificateTimestamps = c.scts state.OCSPResponse = c.ocspResponse state.CFControl = c.config.CFControl diff --git a/src/crypto/tls/delegated_credentials.go b/src/crypto/tls/delegated_credentials.go new file mode 100644 index 00000000000..2bf5bfd2a2d --- /dev/null +++ b/src/crypto/tls/delegated_credentials.go @@ -0,0 +1,548 @@ +// 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 +} diff --git a/src/crypto/tls/delegated_credentials_test.go b/src/crypto/tls/delegated_credentials_test.go new file mode 100644 index 00000000000..0f399768154 --- /dev/null +++ b/src/crypto/tls/delegated_credentials_test.go @@ -0,0 +1,653 @@ +// 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 + +import ( + "bytes" + "crypto" + "crypto/ecdsa" + "crypto/ed25519" + "crypto/elliptic" + "crypto/x509" + "errors" + "fmt" + "math/rand" + "testing" + "time" +) + +// These test keys were generated with the following program, available in the +// crypto/tls directory: +// +// go run generate_cert.go -ecdsa-curve P256 -host 127.0.0.1 -allowDC +// +var delegatorCertPEMP256 = `-----BEGIN CERTIFICATE----- +MIIBejCCAR+gAwIBAgIQKEg6iMq02QUu7QZSZJ/qjzAKBggqhkjOPQQDAjASMRAw +DgYDVQQKEwdBY21lIENvMB4XDTIxMDIyNzAwMTYwMVoXDTIyMDIyNzAwMTYwMVow +EjEQMA4GA1UEChMHQWNtZSBDbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABJTe +bU0Yny6aMvae3zlNj135l7XSzqPDZjYh1PqIqY/P2N5PPmD06fHQ2D7xZRUw/a5z +W7KMwRVXrvur+TVn4+GjVzBVMA4GA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggr +BgEFBQcDATAMBgNVHRMBAf8EAjAAMA8GCSsGAQQBgtpLLAQCBQAwDwYDVR0RBAgw +BocEfwAAATAKBggqhkjOPQQDAgNJADBGAiEAvkorBgZm6GidD0Z7tcAJWRq+2YOQ +GVclN1Z1CDljQIoCIQDUlTAqDyRpNJ9ntCHEdOQYe1LfAkJHasok5yCRHC1o8w== +-----END CERTIFICATE----- +` + +var delegatorKeyPEMP256 = `-----BEGIN EC PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg4OgO7q8sUUZaYjEp +JuLzlXH0qmTZ1k3UHgPYbAmRFOWhRANCAASU3m1NGJ8umjL2nt85TY9d+Ze10s6j +w2Y2IdT6iKmPz9jeTz5g9Onx0Ng+8WUVMP2uc1uyjMEVV677q/k1Z+Ph +-----END EC PRIVATE KEY----- +` + +// go run generate_cert.go -ecdsa-curve P384 -host 127.0.0.1 -allowDC + +var delegatorCertPEMP384 = `-----BEGIN CERTIFICATE----- +MIIBtzCCATygAwIBAgIQYhD6ucKVx53ZfdRCJkPy3DAKBggqhkjOPQQDAzASMRAw +DgYDVQQKEwdBY21lIENvMB4XDTIxMDIyNzAwMTYzOFoXDTIyMDIyNzAwMTYzOFow +EjEQMA4GA1UEChMHQWNtZSBDbzB2MBAGByqGSM49AgEGBSuBBAAiA2IABHNmyki5 +Xxfmxxrk4QRoXfU7hk0o2gJWTkCUAyzlVNcSaUTHub64v2cwn9/LbbooFBlhwz4n +n706yHtzmSQHTkCKmcG2LwS75U+ZajzPXKoSqazGhapBLQb7R7A+uRQGvqNXMFUw +DgYDVR0PAQH/BAQDAgeAMBMGA1UdJQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQC +MAAwDwYJKwYBBAGC2kssBAIFADAPBgNVHREECDAGhwR/AAABMAoGCCqGSM49BAMD +A2kAMGYCMQDIOr2c+CckkU48HqcFiyzPkYWUUeytqmzOg3QDOu6U0jfmi1Xb9dda +pytx77nIUucCMQDD9uVr1UeKGC3Iv0VIHw+tjBzTUg9iToG+PPIlnP+duIBjFQcl +FkeNmqTC8510USo= +-----END CERTIFICATE----- +` + +var delegatorKeyPEMP384 = `-----BEGIN EC PRIVATE KEY----- +MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDA1ouSiH174RBEvZBch +QQnl5iYWTpdCa+EHjexYzhQ9HHMcU7nKCk7OXRod3kAVcUahZANiAARzZspIuV8X +5sca5OEEaF31O4ZNKNoCVk5AlAMs5VTXEmlEx7m+uL9nMJ/fy226KBQZYcM+J5+9 +Osh7c5kkB05AipnBti8Eu+VPmWo8z1yqEqmsxoWqQS0G+0ewPrkUBr4= +-----END EC PRIVATE KEY----- +` + +// go run generate_cert.go -ecdsa-curve P521 -host 127.0.0.1 -allowDC + +var delegatorCertPEMP521 = `-----BEGIN CERTIFICATE----- +MIICATCCAWKgAwIBAgIQJq2J2jQNbTUbhfjk0PT8/TAKBggqhkjOPQQDBDASMRAw +DgYDVQQKEwdBY21lIENvMB4XDTIxMDIyNzAwMTcxN1oXDTIyMDIyNzAwMTcxN1ow +EjEQMA4GA1UEChMHQWNtZSBDbzCBmzAQBgcqhkjOPQIBBgUrgQQAIwOBhgAEAM3n +1xAxRLYhnDNRqc0onmNM9Ik0Jcja6e0bYa9mo0oV/y5DPeML3UJB1CNImFpAkx62 +wLiZmk/BhcPS0EstLAwXATBkb/q0fbKUZXFHd4gr5spRfAosXz5vg1VLeKHqpUku +tyJjgdFvuBZzmp2olqGKbBSKUElvDFkZWkZk5uGEnCsIo1cwVTAOBgNVHQ8BAf8E +BAMCB4AwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADAPBgkrBgEE +AYLaSywEAgUAMA8GA1UdEQQIMAaHBH8AAAEwCgYIKoZIzj0EAwQDgYwAMIGIAkIB +TVEJrlJkxqs0adMPKg5D1EQDGy4dUz4YSWc0VXFOV7TKFDhjo1Abs3SYNXPsgAgT +Ol8BhJ2gFUhgHBP8BiJqPUYCQgFWXEe6AfKPyAUcNH28pIavfhxeGc0DGE4Xux0w +/vWpDdT89YxJmQC1roSaXRwEW1GBXL41h5rMMklGqkkfnCW2SQ== +-----END CERTIFICATE----- +` + +var delegatorKeyPEMP521 = `-----BEGIN EC PRIVATE KEY----- +MIHuAgEAMBAGByqGSM49AgEGBSuBBAAjBIHWMIHTAgEBBEIA4X72HzMvgBj//dX/ +SLkA2+oQ93l2eB2jXVRFST/mQj5NSSt8TNcIqW+TaxSejst7+jAQgnH2Zrith8zK +r2/Gy/6hgYkDgYYABADN59cQMUS2IZwzUanNKJ5jTPSJNCXI2untG2GvZqNKFf8u +Qz3jC91CQdQjSJhaQJMetsC4mZpPwYXD0tBLLSwMFwEwZG/6tH2ylGVxR3eIK+bK +UXwKLF8+b4NVS3ih6qVJLrciY4HRb7gWc5qdqJahimwUilBJbwxZGVpGZObhhJwr +CA== +-----END EC PRIVATE KEY----- +` + +// go run generate_cert.go -ed25519 -host 127.0.0.1 -allowDC + +var delegatorCertPEMEd25519 = `-----BEGIN CERTIFICATE----- +MIIBOTCB7KADAgECAhEAzk3wRF7IPMF07CnnLbQEbDAFBgMrZXAwEjEQMA4GA1UE +ChMHQWNtZSBDbzAeFw0yMTAyMjcwMDE4MTVaFw0yMjAyMjcwMDE4MTVaMBIxEDAO +BgNVBAoTB0FjbWUgQ28wKjAFBgMrZXADIQD+aRKJTaCG+yEz/w3lLhglSTsxyPl4 +FepwdCUXDxj2oKNXMFUwDgYDVR0PAQH/BAQDAgeAMBMGA1UdJQQMMAoGCCsGAQUF +BwMBMAwGA1UdEwEB/wQCMAAwDwYJKwYBBAGC2kssBAIFADAPBgNVHREECDAGhwR/ +AAABMAUGAytlcANBAO0XGRvpMAdkI8SVheJmr+Oe+BBR3VWyhU9PdIxiWu+v+pjp +UQDJpmto6r3AsriHVw2EIdvONnL1FeNzMX2HRAw= +-----END CERTIFICATE----- +` + +var delegatorKeyPEMEd25519 = `-----BEGIN EC PRIVATE KEY----- +MC4CAQAwBQYDK2VwBCIEILsRn/g0To97rbKf+2zV+sr6ZmrqcEiLRK2/rD7r+xDZ +-----END EC PRIVATE KEY----- +` + +var nonDelegatorCertPEM = `-----BEGIN CERTIFICATE----- +MIIBaDCCAQ6gAwIBAgIQcMnAGu3NQYTGYf2HK+JodTAKBggqhkjOPQQDAjASMRAw +DgYDVQQKEwdBY21lIENvMB4XDTIwMDgxODA1NDg1NloXDTIxMDgxODA1NDg1Nlow +EjEQMA4GA1UEChMHQWNtZSBDbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABPAi +QzOthHUdwLTPo9P7Vk1I2W5RHW5nIkq9zYqqMZ5mHQ6vmmrpklvTNHtY93PlokjN +pnlhzEsxK/QrBoAQ8fajRjBEMA4GA1UdDwEB/wQEAwIFoDATBgNVHSUEDDAKBggr +BgEFBQcDATAMBgNVHRMBAf8EAjAAMA8GA1UdEQQIMAaHBH8AAAEwCgYIKoZIzj0E +AwIDSAAwRQIgbOxx7/KWTD47UTWIBcFB95BPrFp2SaFBUyjhzMDXsQkCIQDnwtye +V1OlcMigjCsQuGRacYFP3f1ASpYVv58t/ZeVCw== +-----END CERTIFICATE----- +` + +var nonDelegatorKeyPEM = `-----BEGIN EC PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgD9Q9131NamLDe4ud +dU9rg+gO0vv8lXYErf7P5GQlZD6hRANCAATwIkMzrYR1HcC0z6PT+1ZNSNluUR1u +ZyJKvc2KqjGeZh0Or5pq6ZJb0zR7WPdz5aJIzaZ5YcxLMSv0KwaAEPH2 +-----END EC PRIVATE KEY----- +` + +var ( + dcTestConfig *Config + dcTestCerts map[string]*Certificate + serverDC []DelegatedCredentialPair + clientDC []DelegatedCredentialPair + dcNow time.Time + dcTestDCSignatureScheme = []SignatureScheme{ECDSAWithP256AndSHA256, Ed25519, ECDSAWithP384AndSHA384, ECDSAWithP521AndSHA512} +) + +func init() { + dcTestConfig = &Config{ + Time: func() time.Time { + return dcNow + }, + Rand: zeroSource{}, + Certificates: nil, + MinVersion: VersionTLS10, + MaxVersion: VersionTLS13, + CipherSuites: allCipherSuites(), + } + +} + +func initDCTest() { + // Use a static time for testing at which time the test certificates are + // valid. + dcNow = time.Date(2021, time.March, 31, 11, 0, 0, 234234, time.UTC) + + // The certificates of the server. + dcTestCerts = make(map[string]*Certificate) + var err error + + // The delegation P256 certificate. + dcCertP256 := new(Certificate) + *dcCertP256, err = X509KeyPair([]byte(delegatorCertPEMP256), []byte(delegatorKeyPEMP256)) + if err != nil { + panic(err) + } + + dcCertP256.Leaf, err = x509.ParseCertificate(dcCertP256.Certificate[0]) + if err != nil { + panic(err) + } + dcTestCerts["dcP256"] = dcCertP256 + + // The delegation P384 certificate. + dcCertP384 := new(Certificate) + *dcCertP384, err = X509KeyPair([]byte(delegatorCertPEMP384), []byte(delegatorKeyPEMP384)) + if err != nil { + panic(err) + } + + dcCertP384.Leaf, err = x509.ParseCertificate(dcCertP384.Certificate[0]) + if err != nil { + panic(err) + } + dcTestCerts["dcP384"] = dcCertP384 + + // The delegation P521 certificate. + dcCertP521 := new(Certificate) + *dcCertP521, err = X509KeyPair([]byte(delegatorCertPEMP521), []byte(delegatorKeyPEMP521)) + if err != nil { + panic(err) + } + + dcCertP521.Leaf, err = x509.ParseCertificate(dcCertP521.Certificate[0]) + if err != nil { + panic(err) + } + dcTestCerts["dcP521"] = dcCertP521 + + // The delegation Ed25519 certificate. + dcCertEd25519 := new(Certificate) + *dcCertEd25519, err = X509KeyPair([]byte(delegatorCertPEMEd25519), []byte(delegatorKeyPEMEd25519)) + if err != nil { + panic(err) + } + + dcCertEd25519.Leaf, err = x509.ParseCertificate(dcCertEd25519.Certificate[0]) + if err != nil { + panic(err) + } + dcTestCerts["dcEd25519"] = dcCertEd25519 + + // The non-delegation certificate. + noDcCert := new(Certificate) + *noDcCert, err = X509KeyPair([]byte(nonDelegatorCertPEM), []byte(nonDelegatorKeyPEM)) + if err != nil { + panic(err) + } + noDcCert.Leaf, err = x509.ParseCertificate(noDcCert.Certificate[0]) + if err != nil { + panic(err) + } + dcTestCerts["no dc"] = noDcCert + + // The root certificates for the peer. + dcTestConfig.RootCAs = x509.NewCertPool() + + for _, c := range dcTestCerts { + dcRoot, err := x509.ParseCertificate(c.Certificate[len(c.Certificate)-1]) + if err != nil { + panic(err) + } + dcTestConfig.RootCAs.AddCert(dcRoot) + } + + for i := 0; i < len(dcTestDCSignatureScheme); i++ { + dc, priv, err := NewDelegatedCredential(dcCertP256, dcTestDCSignatureScheme[i], dcNow.Sub(dcCertP256.Leaf.NotBefore)+dcMaxTTL, false) + if err != nil { + panic(err) + } + serverDC = append(serverDC, DelegatedCredentialPair{dc, priv}) + + dc, priv, err = NewDelegatedCredential(dcCertP256, dcTestDCSignatureScheme[i], dcNow.Sub(dcCertP256.Leaf.NotBefore)+dcMaxTTL, true) + if err != nil { + panic(err) + } + clientDC = append(clientDC, DelegatedCredentialPair{dc, priv}) + } +} + +func publicKeysEqual(publicKey, publicKey2 crypto.PublicKey, algo SignatureScheme) error { + switch publicKey.(type) { + case *ecdsa.PublicKey: + curve := getECDSACurve(algo) + pk := publicKey.(*ecdsa.PublicKey) + pk2 := publicKey2.(*ecdsa.PublicKey) + + serPubKey := elliptic.Marshal(curve, pk.X, pk.Y) + serPubKey2 := elliptic.Marshal(curve, pk2.X, pk2.Y) + if !bytes.Equal(serPubKey2, serPubKey) { + return errors.New("ecdsa public Keys mismatch") + } + case ed25519.PublicKey: + pk := publicKey.(ed25519.PublicKey) + pk2 := publicKey2.(ed25519.PublicKey) + + if !bytes.Equal(pk, pk2) { + return errors.New("ed25519 Public Keys mismatch") + } + } + + return nil +} + +func delegagedCredentialsEqual(dc, dc2 *DelegatedCredential) error { + if dc2.cred.validTime != dc.cred.validTime { + return fmt.Errorf("ValidTime mismatch: got %d; want %d", dc2.cred.validTime, dc.cred.validTime) + } + + if dc2.cred.expCertVerfAlgo != dc.cred.expCertVerfAlgo { + return fmt.Errorf("scheme mismatch: got %04x; want %04x", dc2.cred.expCertVerfAlgo, dc.cred.expCertVerfAlgo) + } + + return publicKeysEqual(dc.cred.publicKey, dc2.cred.publicKey, dc.cred.expCertVerfAlgo) +} + +// Test delegation and validation of credentials. +func TestDelegateCredentialsValidate(t *testing.T) { + initDCTest() + cert := dcTestCerts["dcP384"] + validTime := dcNow.Sub(cert.Leaf.NotBefore) + dcMaxTTL + + delegatedCred, _, err := NewDelegatedCredential(cert, ECDSAWithP384AndSHA384, validTime, false) + if err != nil { + t.Fatal(err) + } else if delegatedCred == nil { + t.Fatal("unable to generate a Delegated Credential") + } + + rand := rand.New(rand.NewSource(time.Now().UnixNano())) + m := &certificateVerifyMsg{} + m.hasSignatureAlgorithm = true + m.signatureAlgorithm = ECDSAWithP384AndSHA384 + m.signature = randomBytes(rand.Intn(15)+1, rand) + + // Valid Delegated Credential + if !delegatedCred.Validate(cert.Leaf, false, dcNow, m) { + t.Error("generated valid Delegated Credential is rendered invalid") + } + + // Expired Delegated Credential + expired := dcNow.Add(dcMaxTTL).Add(time.Nanosecond) + if delegatedCred.Validate(cert.Leaf, false, expired, m) { + t.Error("expired delegated credential is valid; want invalid") + } + + // Test validation of Delegated Credential which TTL is too long + invalidDelegatedCred, _, err := NewDelegatedCredential(cert, ECDSAWithP384AndSHA384, validTime+time.Second, false) + if err != nil { + t.Fatal(err) + } + if invalidDelegatedCred.Validate(cert.Leaf, false, dcNow, m) { + t.Error("Delegated Credential validation with long TTL succeeded; want failure") + } + + shortValidTime := dcNow.Sub(cert.Leaf.NotBefore) + time.Second + + // Test validation of Delegated Credential which TTL is short + delegatedCred, _, err = NewDelegatedCredential(cert, ECDSAWithP384AndSHA384, shortValidTime, false) + if err != nil { + t.Fatal(err) + } + if !delegatedCred.Validate(cert.Leaf, false, dcNow, m) { + t.Error("valid Delegated Credential is invalid; want valid") + } + + delegatedCred.algorithm = ECDSAWithP521AndSHA512 + + // Test signature algorithm binding + if delegatedCred.Validate(cert.Leaf, false, dcNow, m) { + t.Error("Delegated Credential with wrong scheme is valid; want invalid") + } + + delegatedCred.algorithm = ECDSAWithP384AndSHA384 + + // Test delegation certificate binding + cert.Leaf.Raw[0] ^= byte(42) + if delegatedCred.Validate(cert.Leaf, false, dcNow, m) { + t.Error("Delegated Credential with wrong certificate is valid; want invalid") + } + + // Test validation of DC using a certificate that can't delegate. + if delegatedCred.Validate(dcTestCerts["no dc"].Leaf, false, dcNow, m) { + t.Error("Delegated Credential with non-delegation cert is valid; want invalid") + } + + // Test DC with another certificate + cert = dcTestCerts["dcP521"] + validTime = dcNow.Sub(cert.Leaf.NotBefore) + dcMaxTTL + delegatedCred, _, err = NewDelegatedCredential(cert, ECDSAWithP384AndSHA384, validTime, false) + if err != nil { + t.Fatal(err) + } else if delegatedCred == nil { + t.Fatal("unable to generate a Delegated Credential") + } + + // Valid Delegated Credential + if !delegatedCred.Validate(cert.Leaf, false, dcNow, m) { + t.Error("generated valid Delegated Credential is rendered invalid") + } +} + +// Test encoding/decoding of Delegated Credentials. +func TestDelegatedCredentialMarshal(t *testing.T) { + initDCTest() + cert := dcTestCerts["dcEd25519"] + time := dcNow.Sub(cert.Leaf.NotBefore) + dcMaxTTL + + for _, sig := range dcTestDCSignatureScheme { + delegatedCred, _, err := NewDelegatedCredential(cert, sig, time, false) + if err != nil { + t.Fatal(err) + } + + ser, err := delegatedCred.Marshal() + if err != nil { + t.Error(err) + } + + delegatedCred2, err := UnmarshalDelegatedCredential(ser) + if err != nil { + t.Error(err) + } + + err = delegagedCredentialsEqual(delegatedCred, delegatedCred2) + if err != nil { + t.Error(err) + } + + if delegatedCred.algorithm != delegatedCred2.algorithm { + t.Errorf("scheme mismatch: got %04x; want %04x", delegatedCred2.algorithm, delegatedCred.algorithm) + } + + if !bytes.Equal(delegatedCred2.signature, delegatedCred.signature) { + t.Error("Signature mismatch") + } + } +} + +var dcServerTests = []struct { + clientDCSupport bool + clientMaxVers uint16 + serverMaxVers uint16 + expectSuccess bool + expectDC bool + name string +}{ + {true, VersionTLS13, VersionTLS13, true, true, "tls13: DC client support"}, + {false, VersionTLS13, VersionTLS13, true, false, "DC not client support"}, + {true, VersionTLS12, VersionTLS13, true, false, "client using TLS 1.2. No DC is supported in that version."}, + {true, VersionTLS13, VersionTLS12, true, false, "server using TLS 1.2. No DC is supported in that version."}, + {true, VersionTLS11, VersionTLS13, true, false, "client using TLS 1.1. No DC is supported in that version."}, + {true, VersionTLS13, VersionTLS10, false, false, "server using TLS 1.0. No DC is supported in that version."}, +} + +var dcClientTests = []struct { + serverDCSupport bool + clientMaxVers uint16 + serverMaxVers uint16 + expectSuccess bool + expectDC bool + name string +}{ + {true, VersionTLS13, VersionTLS13, true, true, "tls13: DC server support"}, + {false, VersionTLS13, VersionTLS13, true, false, "DC not server support"}, + {true, VersionTLS12, VersionTLS13, true, false, "client using TLS 1.2. No DC is supported in that version."}, + {true, VersionTLS13, VersionTLS12, true, false, "server using TLS 1.2. No DC is supported in that version."}, + {true, VersionTLS11, VersionTLS13, true, false, "client using TLS 1.1. No DC is supported in that version."}, + {true, VersionTLS13, VersionTLS10, false, false, "server using TLS 1.0. No DC is supported in that version."}, +} + +// dcCount defines the delegated credential to be used as returned by the +// getCertificate or getClientCertificate callback. This allows to use +// delegated credentials with different algorithms at each run of the +// tests. +var dcCount int + +// Checks that the client suppports a version >= 1.3 and accepts Delegated +// Credentials. If so, it returns the delegation certificate; otherwise it +// returns a non-delegated certificate. +func testServerGetCertificate(ch *ClientHelloInfo) (*Certificate, error) { + versOk := false + for _, vers := range ch.SupportedVersions { + versOk = versOk || (vers >= uint16(VersionTLS13)) + } + + if versOk && ch.SupportsDelegatedCredential { + serverCert := dcTestCerts["dcP256"] + serverCert.DelegatedCredentials = serverDC[dcCount:] + return serverCert, nil + } + return dcTestCerts["no dc"], nil + +} + +// Used when the server doesn't support DCs. +// This function always returns a non-DC cert. +func testServerGetCertificateNoDC(ch *ClientHelloInfo) (*Certificate, error) { + return dcTestCerts["no dc"], nil +} + +// Checks that the client suppports a version >= 1.3 and accepts Delegated +// Credentials. If so, it returns the delegation certificate; otherwise it +// returns a non-Delegated certificate. +func testClientGetCertificate(cr *CertificateRequestInfo) (*Certificate, error) { + versOk := false + if cr.Version == VersionTLS13 { + versOk = true + } + + if versOk && cr.SupportsDelegatedCredential { + clientCert := dcTestCerts["dcP256"] + clientCert.DelegatedCredentials = clientDC[dcCount:] + return clientCert, nil + } + return dcTestCerts["no dc"], nil + +} + +// Tests the handshake and one round of application data. Returns true if the +// connection correctly used a Delegated Credential. +func testConnWithDC(t *testing.T, clientMsg, serverMsg string, clientConfig, serverConfig *Config, peer string) (bool, error) { + ln := newLocalListener(t) + defer ln.Close() + + serverCh := make(chan *Conn, 1) + var serverErr error + go func() { + serverConn, err := ln.Accept() + if err != nil { + serverErr = err + serverCh <- nil + return + } + server := Server(serverConn, serverConfig) + if err := server.Handshake(); err != nil { + serverErr = fmt.Errorf("handshake error: %v", err) + serverCh <- nil + return + } + serverCh <- server + }() + client, err := Dial("tcp", ln.Addr().String(), clientConfig) + + if err != nil { + return false, err + } + defer client.Close() + + server := <-serverCh + if server == nil { + return false, serverErr + } + + bufLen := len(clientMsg) + if len(serverMsg) > len(clientMsg) { + bufLen = len(serverMsg) + } + buf := make([]byte, bufLen) + + client.Write([]byte(clientMsg)) + n, err := server.Read(buf) + if err != nil || n != len(clientMsg) || string(buf[:n]) != clientMsg { + return false, fmt.Errorf("Server read = %d, buf= %q; want %d, %s", n, buf, len(clientMsg), clientMsg) + } + + server.Write([]byte(serverMsg)) + n, err = client.Read(buf) + if n != len(serverMsg) || err != nil || string(buf[:n]) != serverMsg { + return false, fmt.Errorf("Client read = %d, %v, data %q; want %d, nil, %s", n, err, buf, len(serverMsg), serverMsg) + } + + if peer == "server" { + return (server.verifiedDC != nil), nil + } else if peer == "client" { + return (client.verifiedDC != nil), nil + } else if peer == "both" { + return (client.verifiedDC != nil && server.verifiedDC != nil), nil + } + + return false, nil +} + +// Test the server authentication with the Delegated Credential extension. +func TestDCHandshakeServerAuth(t *testing.T) { + serverMsg := "hello, client" + clientMsg := "hello, server" + initDCTest() + clientConfig := dcTestConfig.Clone() + serverConfig := dcTestConfig.Clone() + + for i, test := range dcServerTests { + clientConfig.SupportDelegatedCredential = test.clientDCSupport + for dcCount = 0; dcCount < len(dcTestDCSignatureScheme); dcCount++ { + if test.serverMaxVers < VersionTLS13 { + t.Logf("Server doesn't support DCs, not offering. test %d", i) + serverConfig.GetCertificate = testServerGetCertificateNoDC + } else { + serverConfig.GetCertificate = testServerGetCertificate + } + + clientConfig.MaxVersion = test.clientMaxVers + serverConfig.MaxVersion = test.serverMaxVers + usedDC, err := testConnWithDC(t, clientMsg, serverMsg, clientConfig, serverConfig, "client") + + if err != nil && test.expectSuccess { + t.Errorf("test #%d (%s) with signature algorithm #%d fails: %s", i, test.name, dcCount, err.Error()) + } else if err == nil && !test.expectSuccess { + t.Errorf("test #%d (%s) with signature algorithm #%d succeeds; expected failure", i, test.name, dcCount) + } + + if usedDC != test.expectDC { + t.Errorf("test #%d (%s) with signature algorithm #%d usedDC = %v; expected %v", i, test.name, dcCount, usedDC, test.expectDC) + } + } + } +} + +// Test the client authentication with the Delegated Credential extension. +func TestDCHandshakeClientAuth(t *testing.T) { + clientMsg := "hello, server" + serverMsg := "hello, client" + + initDCTest() + serverConfig := dcTestConfig.Clone() + serverConfig.ClientAuth = RequestClientCert + serverConfig.GetCertificate = testServerGetCertificate + clientConfig := dcTestConfig.Clone() + clientConfig.GetClientCertificate = testClientGetCertificate + + for j, test := range dcClientTests { + serverConfig.SupportDelegatedCredential = test.serverDCSupport + + for dcCount = 0; dcCount < len(dcTestDCSignatureScheme); dcCount++ { + serverConfig.MaxVersion = test.serverMaxVers + clientConfig.MaxVersion = test.clientMaxVers + + usedDC, err := testConnWithDC(t, clientMsg, serverMsg, clientConfig, serverConfig, "server") + + if err != nil && test.expectSuccess { + t.Errorf("test #%d (%s) with signature algorithm #%d fails: %s", j, test.name, dcCount, err.Error()) + } else if err == nil && !test.expectSuccess { + t.Errorf("test #%d (%s) with signature algorithm #%d succeeds; expected failure", j, test.name, dcCount) + } + + if usedDC != test.expectDC { + t.Errorf("test #%d (%s) with signature algorithm #%d usedDC = %v; expected %v", j, test.name, dcCount, usedDC, test.expectDC) + } + } + } +} + +// Test server and client authentication with the Delegated Credential extension. +func TestDCHandshakeClientAndServerAuth(t *testing.T) { + clientMsg := "hello, server" + serverMsg := "hello, client" + + initDCTest() + serverConfig := dcTestConfig.Clone() + serverConfig.ClientAuth = RequestClientCert + serverConfig.GetCertificate = testServerGetCertificate + clientConfig := dcTestConfig.Clone() + clientConfig.GetClientCertificate = testClientGetCertificate + + serverConfig.SupportDelegatedCredential = true + clientConfig.SupportDelegatedCredential = true + + serverConfig.MaxVersion = VersionTLS13 + clientConfig.MaxVersion = VersionTLS13 + + usedDC, err := testConnWithDC(t, clientMsg, serverMsg, clientConfig, serverConfig, "both") + + if err != nil { + t.Errorf("test server and client auth fails: %s", err.Error()) + } + + if usedDC != true { + t.Errorf("test server and client auth does not succeed") + } +} diff --git a/src/crypto/tls/generate_cert.go b/src/crypto/tls/generate_cert.go index c5251ee52b8..ae43b57e108 100644 --- a/src/crypto/tls/generate_cert.go +++ b/src/crypto/tls/generate_cert.go @@ -34,6 +34,7 @@ var ( validFrom = flag.String("start-date", "", "Creation date formatted as Jan 1 15:04:05 2011") validFor = flag.Duration("duration", 365*24*time.Hour, "Duration that certificate is valid for") isCA = flag.Bool("ca", false, "whether this cert should be its own Certificate Authority") + allowDC = flag.Bool("allowDC", false, "whether this cert can be used with Delegated Credentials") rsaBits = flag.Int("rsa-bits", 2048, "Size of RSA key to generate. Ignored if --ecdsa-curve is set") ecdsaCurve = flag.String("ecdsa-curve", "", "ECDSA curve to use to generate a key. Valid values are P224, P256 (recommended), P384, P521") ed25519Key = flag.Bool("ed25519", false, "Generate an Ed25519 key") @@ -143,10 +144,19 @@ func main() { } if *isCA { + if *allowDC { + log.Fatal("Failed to create certificate: ca is not allowed with the dc flag") + } + template.IsCA = true template.KeyUsage |= x509.KeyUsageCertSign } + if *allowDC { + template.AllowDC = true + template.KeyUsage |= x509.KeyUsageDigitalSignature + } + derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, publicKey(priv), priv) if err != nil { log.Fatalf("Failed to create certificate: %v", err) diff --git a/src/crypto/tls/generate_delegated_credential.go b/src/crypto/tls/generate_delegated_credential.go new file mode 100644 index 00000000000..f2d41b02f9a --- /dev/null +++ b/src/crypto/tls/generate_delegated_credential.go @@ -0,0 +1,125 @@ +// Copyright 2022 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. + +//go:build ignore + +// Generate a delegated credential with the given signature scheme, signed with +// the given x.509 key pair. Outputs to 'dc.cred' and 'dckey.pem' and will +// overwrite existing files. + +// Example usage: +// generate_delegated_credential -cert-path cert.pem -key-path key.pem -signature-scheme Ed25519 -duration 24h + +package main + +import ( + circlSign "circl/sign" + "crypto" + "crypto/ecdsa" + "crypto/ed25519" + "crypto/rsa" + "crypto/tls" + "crypto/x509" + "encoding/pem" + "errors" + "flag" + "fmt" + "log" + "os" + "path/filepath" + "time" +) + +var ( + validFor = flag.Duration("duration", 5*24*time.Hour, "Duration that credential is valid for") + signatureScheme = flag.String("signature-scheme", "", "The signature scheme used by the DC") + certPath = flag.String("cert-path", "./cert.pem", "Path to signing cert") + keyPath = flag.String("key-path", "./key.pem", "Path to signing key") + isClient = flag.Bool("client-dc", false, "Create a client Delegated Credential") + outPath = flag.String("out-path", "./", "Path to output directory") +) + +var SigStringMap = map[string]tls.SignatureScheme{ + // ECDSA algorithms. Only constrained to a specific curve in TLS 1.3. + "ECDSAWithP256AndSHA256": tls.ECDSAWithP256AndSHA256, + "ECDSAWithP384AndSHA384": tls.ECDSAWithP384AndSHA384, + "ECDSAWithP521AndSHA512": tls.ECDSAWithP521AndSHA512, + + // EdDSA algorithms. + "Ed25519": tls.Ed25519, +} + +func main() { + flag.Parse() + sa := SigStringMap[*signatureScheme] + + cert, err := tls.LoadX509KeyPair(*certPath, *keyPath) + if err != nil { + log.Fatalf("Failed to load certificate and key: %v", err) + } + cert.Leaf, err = x509.ParseCertificate(cert.Certificate[0]) + if err != nil { + log.Fatalf("Failed to parse leaf certificate: %v", err) + } + + validTime := time.Since(cert.Leaf.NotBefore) + *validFor + dc, priv, err := tls.NewDelegatedCredential(&cert, sa, validTime, *isClient) + if err != nil { + log.Fatalf("Failed to create a DC: %v\n", err) + } + dcBytes, err := dc.Marshal() + if err != nil { + log.Fatalf("Failed to marshal DC: %v\n", err) + } + + DCOut, err := os.Create(filepath.Join(*outPath, "dc.cred")) + if err != nil { + log.Fatalf("Failed to open dc.cred for writing: %v", err) + } + + DCOut.Write(dcBytes) + if err := DCOut.Close(); err != nil { + log.Fatalf("Error closing dc.cred: %v", err) + } + log.Print("wrote dc.cred\n") + + derBytes, err := x509.MarshalPKCS8PrivateKey(priv) + if err != nil { + log.Fatalf("Failed to marshal DC private key: %v\n", err) + } + + DCKeyOut, err := os.Create(filepath.Join(*outPath, "dckey.pem")) + if err != nil { + log.Fatalf("Failed to open dckey.pem for writing: %v", err) + } + + if err := pem.Encode(DCKeyOut, &pem.Block{Type: "PRIVATE KEY", Bytes: derBytes}); err != nil { + log.Fatalf("Failed to write data to dckey.pem: %v\n", err) + } + if err := DCKeyOut.Close(); err != nil { + log.Fatalf("Error closing dckey.pem: %v\n", err) + } + log.Print("wrote dckey.pem\n") + + fmt.Println("Success") +} + +// Copied from tls.go, because it's private. +func parsePrivateKey(der []byte) (crypto.PrivateKey, error) { + if key, err := x509.ParsePKCS1PrivateKey(der); err == nil { + return key, nil + } + if key, err := x509.ParsePKCS8PrivateKey(der); err == nil { + switch key := key.(type) { + case *rsa.PrivateKey, *ecdsa.PrivateKey, ed25519.PrivateKey, circlSign.PrivateKey: + return key, nil + default: + return nil, errors.New("tls: found unknown private key type in PKCS#8 wrapping") + } + } + if key, err := x509.ParseECPrivateKey(der); err == nil { + return key, nil + } + + return nil, errors.New("tls: failed to parse private key") +} diff --git a/src/crypto/tls/handshake_client.go b/src/crypto/tls/handshake_client.go index e7467aee2b6..4f442401b83 100644 --- a/src/crypto/tls/handshake_client.go +++ b/src/crypto/tls/handshake_client.go @@ -139,6 +139,8 @@ func (c *Conn) makeClientHello() (*clientHelloMsg, ecdheParameters, error) { return nil, nil, err } hello.keyShares = []keyShare{{group: curveID, data: params.PublicKey()}} + hello.delegatedCredentialSupported = config.SupportDelegatedCredential + hello.supportedSignatureAlgorithmsDC = supportedSignatureAlgorithmsDC } return hello, params, nil @@ -913,6 +915,9 @@ func certificateRequestInfoFromMsg(ctx context.Context, vers uint16, certReq *ce AcceptableCAs: certReq.certificateAuthorities, Version: vers, ctx: ctx, + + SupportsDelegatedCredential: false, // Not supported in TLS <= 1.2 + SignatureSchemesDC: nil, // Not supported in TLS <= 1.2 } var rsaAvail, ecAvail bool diff --git a/src/crypto/tls/handshake_client_tls13.go b/src/crypto/tls/handshake_client_tls13.go index 25bbbc6e608..8f3f327b06d 100644 --- a/src/crypto/tls/handshake_client_tls13.go +++ b/src/crypto/tls/handshake_client_tls13.go @@ -11,6 +11,7 @@ import ( "crypto/hmac" "crypto/rsa" "errors" + "fmt" "hash" "sync/atomic" "time" @@ -38,6 +39,49 @@ type clientHandshakeStateTLS13 struct { hsTimings CFEventTLS13ClientHandshakeTimingInfo } +// processDelegatedCredentialFromServer unmarshals the DelegatedCredential +// offered by the server (if present) and validates it using the peer's +// certificate. +func (hs *clientHandshakeStateTLS13) processDelegatedCredentialFromServer(rawDC []byte, certVerifyMsg *certificateVerifyMsg) error { + c := hs.c + + var dc *DelegatedCredential + var err error + if rawDC != nil { + // Assert that support for the DC extension was indicated by the client. + if !hs.hello.delegatedCredentialSupported { + c.sendAlert(alertUnexpectedMessage) + return errors.New("tls: got Delegated Credential extension without indication") + } + + dc, err = UnmarshalDelegatedCredential(rawDC) + if err != nil { + c.sendAlert(alertDecodeError) + return fmt.Errorf("tls: Delegated Credential: %s", err) + } + + if !isSupportedSignatureAlgorithm(dc.cred.expCertVerfAlgo, supportedSignatureAlgorithmsDC) { + c.sendAlert(alertIllegalParameter) + return errors.New("tls: Delegated Credential used with invalid signature algorithm") + } + if !isSupportedSignatureAlgorithm(dc.algorithm, c.config.supportedSignatureAlgorithms()) { + c.sendAlert(alertIllegalParameter) + return errors.New("tls: Delegated Credential signed with unsupported signature algorithm") + } + } + + if dc != nil { + if !dc.Validate(c.peerCertificates[0], false, c.config.time(), certVerifyMsg) { + c.sendAlert(alertIllegalParameter) + return errors.New("tls: invalid Delegated Credential") + } + } + + c.verifiedDC = dc + + return nil +} + // handshake requires hs.c, hs.hello, hs.serverHello, hs.ecdheParams, and, // optionally, hs.session, hs.earlySecret and hs.binderKey to be set. func (hs *clientHandshakeStateTLS13) handshake() error { @@ -485,6 +529,7 @@ func (hs *clientHandshakeStateTLS13) readServerCertificate() error { c.sendAlert(alertIllegalParameter) return errors.New("tls: certificate used with invalid signature algorithm") } + sigType, sigHash, err := typeAndHashFromSignatureScheme(certVerify.signatureAlgorithm) if err != nil { return c.sendAlert(alertInternalError) @@ -493,8 +538,19 @@ func (hs *clientHandshakeStateTLS13) readServerCertificate() error { c.sendAlert(alertIllegalParameter) return errors.New("tls: certificate used with invalid signature algorithm") } + if certMsg.delegatedCredential { + if err := hs.processDelegatedCredentialFromServer(certMsg.certificate.DelegatedCredential, certVerify); err != nil { + return err // alert sent + } + } + + pk := c.peerCertificates[0].PublicKey + if c.verifiedDC != nil { + pk = c.verifiedDC.cred.publicKey + } + signed := signedMessage(sigHash, serverSignatureContext, hs.transcript) - if err := verifyHandshakeSignature(sigType, c.peerCertificates[0].PublicKey, + if err := verifyHandshakeSignature(sigType, pk, sigHash, signed, certVerify.signature); err != nil { c.sendAlert(alertDecryptError) return errors.New("tls: invalid signature by the server certificate: " + err.Error()) @@ -555,6 +611,44 @@ func (hs *clientHandshakeStateTLS13) readServerFinished() error { return nil } +func certificateRequestInfo(certReq *certificateRequestMsgTLS13, vers uint16, ctx context.Context) *CertificateRequestInfo { + cri := &CertificateRequestInfo{ + SupportsDelegatedCredential: certReq.supportDelegatedCredential, + SignatureSchemes: certReq.supportedSignatureAlgorithms, + SignatureSchemesDC: certReq.supportedSignatureAlgorithmsDC, + AcceptableCAs: certReq.certificateAuthorities, + Version: vers, + ctx: ctx, + } + + return cri +} + +// getClientDelegatedCredential will return a Delegated Credential pair (a +// Delegated Credential and its private key) for the given CertificateRequestInfo, +// defaulting to the first element of cert.DelegatedCredentialPair. +// The returned Delegated Credential could be invalid for usage in the handshake. +// Returns an error if there are no delegated credentials or if the one found +// cannot be used for the current connection. +func getClientDelegatedCredential(cri *CertificateRequestInfo, cert *Certificate) (*DelegatedCredentialPair, error) { + if len(cert.DelegatedCredentials) == 0 { + return nil, errors.New("no Delegated Credential found") + } + + for _, dcPair := range cert.DelegatedCredentials { + // If the client sent the signature_algorithms in the DC extension, ensure it supports + // schemes we can use with this delegated credential. + if len(cri.SignatureSchemesDC) > 0 { + if _, err := selectSignatureSchemeDC(VersionTLS13, dcPair.DC, cri.SignatureSchemes, cri.SignatureSchemesDC); err == nil { + return &dcPair, nil + } + } + } + + // No delegated credential can be returned. + return nil, errors.New("no valid Delegated Credential found") +} + func (hs *clientHandshakeStateTLS13) sendClientCertificate() error { c := hs.c @@ -562,21 +656,34 @@ func (hs *clientHandshakeStateTLS13) sendClientCertificate() error { return nil } - cert, err := c.getClientCertificate(&CertificateRequestInfo{ - AcceptableCAs: hs.certReq.certificateAuthorities, - SignatureSchemes: hs.certReq.supportedSignatureAlgorithms, - Version: c.vers, - ctx: hs.ctx, - }) + cri := certificateRequestInfo(hs.certReq, c.vers, hs.ctx) + + cert, err := c.getClientCertificate(cri) if err != nil { return err } + var dcPair *DelegatedCredentialPair + if hs.certReq.supportDelegatedCredential && len(hs.certReq.supportedSignatureAlgorithmsDC) > 0 { + // getClientDelegatedCredential selects a delegated credential that the server has advertised support for, if possible. + if delegatedCredentialPair, err := getClientDelegatedCredential(cri, cert); err == nil { + if delegatedCredentialPair.DC != nil && delegatedCredentialPair.PrivateKey != nil { + var err error + // Even if the Delegated Credential has already been marshalled, be sure it is the correct one. + if delegatedCredentialPair.DC.raw, err = delegatedCredentialPair.DC.Marshal(); err == nil { + dcPair = delegatedCredentialPair + cert.DelegatedCredential = dcPair.DC.raw + } + } + } + } + certMsg := new(certificateMsgTLS13) certMsg.certificate = *cert certMsg.scts = hs.certReq.scts && len(cert.SignedCertificateTimestamps) > 0 certMsg.ocspStapling = hs.certReq.ocspStapling && len(cert.OCSPStaple) > 0 + certMsg.delegatedCredential = hs.certReq.supportDelegatedCredential && len(cert.DelegatedCredential) > 0 hs.transcript.Write(certMsg.marshal()) if _, err := c.writeRecord(recordTypeHandshake, certMsg.marshal()); err != nil { @@ -593,7 +700,9 @@ func (hs *clientHandshakeStateTLS13) sendClientCertificate() error { certVerifyMsg := new(certificateVerifyMsg) certVerifyMsg.hasSignatureAlgorithm = true - certVerifyMsg.signatureAlgorithm, err = selectSignatureScheme(c.vers, cert, hs.certReq.supportedSignatureAlgorithms) + var sigAlgorithm SignatureScheme + suppSigAlgo := hs.certReq.supportedSignatureAlgorithms + sigAlgorithm, err = selectSignatureScheme(c.vers, cert, suppSigAlgo) if err != nil { // getClientCertificate returned a certificate incompatible with the // CertificateRequestInfo supported signature algorithms. @@ -601,6 +710,18 @@ func (hs *clientHandshakeStateTLS13) sendClientCertificate() error { return err } + if certMsg.delegatedCredential { + suppSigAlgo = hs.certReq.supportedSignatureAlgorithmsDC + if dcPair == nil || dcPair.DC == nil { + cert.DelegatedCredential = nil + } else { + sigAlgorithm = dcPair.DC.cred.expCertVerfAlgo + cert.PrivateKey = dcPair.PrivateKey + } + } + + certVerifyMsg.signatureAlgorithm = sigAlgorithm + sigType, sigHash, err := typeAndHashFromSignatureScheme(certVerifyMsg.signatureAlgorithm) if err != nil { return c.sendAlert(alertInternalError) diff --git a/src/crypto/tls/handshake_messages.go b/src/crypto/tls/handshake_messages.go index 17cf85910fa..2f501bfc0a7 100644 --- a/src/crypto/tls/handshake_messages.go +++ b/src/crypto/tls/handshake_messages.go @@ -81,8 +81,10 @@ type clientHelloMsg struct { sessionTicket []uint8 supportedSignatureAlgorithms []SignatureScheme supportedSignatureAlgorithmsCert []SignatureScheme + supportedSignatureAlgorithmsDC []SignatureScheme secureRenegotiationSupported bool secureRenegotiation []byte + delegatedCredentialSupported bool alpnProtocols []string scts bool supportedVersions []uint16 @@ -200,6 +202,19 @@ func (m *clientHelloMsg) marshal() []byte { }) }) } + if m.delegatedCredentialSupported { + if len(m.supportedSignatureAlgorithmsDC) > 0 { + // Draft: https://tools.ietf.org/html/draft-ietf-tls-subcerts-10 + b.AddUint16(extensionDelegatedCredentials) + b.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) { + b.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) { + for _, sigAlgo := range m.supportedSignatureAlgorithmsDC { + b.AddUint16(uint16(sigAlgo)) + } + }) + }) + } + } if len(m.alpnProtocols) > 0 { // RFC 7301, Section 3.1 b.AddUint16(extensionALPN) @@ -522,6 +537,20 @@ func (m *clientHelloMsg) unmarshal(data []byte) bool { len(m.cookie) == 0 { return false } + case extensionDelegatedCredentials: + var sigAndAlgs cryptobyte.String + if !extData.ReadUint16LengthPrefixed(&sigAndAlgs) || sigAndAlgs.Empty() { + return false + } + for !sigAndAlgs.Empty() { + var sigAndAlg uint16 + if !sigAndAlgs.ReadUint16(&sigAndAlg) { + return false + } + m.supportedSignatureAlgorithmsDC = append( + m.supportedSignatureAlgorithmsDC, SignatureScheme(sigAndAlg)) + } + m.delegatedCredentialSupported = true case extensionKeyShare: // RFC 8446, Section 4.2.8 var clientShares cryptobyte.String @@ -1053,7 +1082,9 @@ type certificateRequestMsgTLS13 struct { raw []byte ocspStapling bool scts bool + supportDelegatedCredential bool supportedSignatureAlgorithms []SignatureScheme + supportedSignatureAlgorithmsDC []SignatureScheme supportedSignatureAlgorithmsCert []SignatureScheme certificateAuthorities [][]byte } @@ -1084,6 +1115,19 @@ func (m *certificateRequestMsgTLS13) marshal() []byte { b.AddUint16(extensionSCT) b.AddUint16(0) // empty extension_data } + if m.supportDelegatedCredential { + if len(m.supportedSignatureAlgorithmsDC) > 0 { + // Draft: https://tools.ietf.org/html/draft-ietf-tls-subcerts-10 + b.AddUint16(extensionDelegatedCredentials) + b.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) { + b.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) { + for _, sigAlgo := range m.supportedSignatureAlgorithmsDC { + b.AddUint16(uint16(sigAlgo)) + } + }) + }) + } + } if len(m.supportedSignatureAlgorithms) > 0 { b.AddUint16(extensionSignatureAlgorithms) b.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) { @@ -1148,6 +1192,20 @@ func (m *certificateRequestMsgTLS13) unmarshal(data []byte) bool { m.ocspStapling = true case extensionSCT: m.scts = true + case extensionDelegatedCredentials: + var sigAndAlgs cryptobyte.String + if !extData.ReadUint16LengthPrefixed(&sigAndAlgs) || sigAndAlgs.Empty() { + return false + } + for !sigAndAlgs.Empty() { + var sigAndAlg uint16 + if !sigAndAlgs.ReadUint16(&sigAndAlg) { + return false + } + m.supportedSignatureAlgorithmsDC = append( + m.supportedSignatureAlgorithmsDC, SignatureScheme(sigAndAlg)) + } + m.supportDelegatedCredential = true case extensionSignatureAlgorithms: var sigAndAlgs cryptobyte.String if !extData.ReadUint16LengthPrefixed(&sigAndAlgs) || sigAndAlgs.Empty() { @@ -1277,10 +1335,11 @@ func (m *certificateMsg) unmarshal(data []byte) bool { } type certificateMsgTLS13 struct { - raw []byte - certificate Certificate - ocspStapling bool - scts bool + raw []byte + certificate Certificate + ocspStapling bool + scts bool + delegatedCredential bool } func (m *certificateMsgTLS13) marshal() []byte { @@ -1300,10 +1359,14 @@ func (m *certificateMsgTLS13) marshal() []byte { if !m.scts { certificate.SignedCertificateTimestamps = nil } + if !m.delegatedCredential { + certificate.DelegatedCredential = nil + } marshalCertificate(b, certificate) }) m.raw = b.BytesOrPanic() + return m.raw } @@ -1315,7 +1378,8 @@ func marshalCertificate(b *cryptobyte.Builder, certificate Certificate) { }) b.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) { if i > 0 { - // This library only supports OCSP and SCT for leaf certificates. + // This library only supports OCSP, SCT and Delegated Credentials for leaf certificates. + // Delegated Credentials are only supported on the leaf/end-entity certificate. return } if certificate.OCSPStaple != nil { @@ -1339,6 +1403,12 @@ func marshalCertificate(b *cryptobyte.Builder, certificate Certificate) { }) }) } + if certificate.DelegatedCredential != nil { + b.AddUint16(extensionDelegatedCredentials) + b.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) { + b.AddBytes(certificate.DelegatedCredential) + }) + } }) } }) @@ -1358,6 +1428,7 @@ func (m *certificateMsgTLS13) unmarshal(data []byte) bool { m.scts = m.certificate.SignedCertificateTimestamps != nil m.ocspStapling = m.certificate.OCSPStaple != nil + m.delegatedCredential = m.certificate.DelegatedCredential != nil return true } @@ -1409,6 +1480,13 @@ func unmarshalCertificate(s *cryptobyte.String, certificate *Certificate) bool { certificate.SignedCertificateTimestamps = append( certificate.SignedCertificateTimestamps, sct) } + case extensionDelegatedCredentials: + if !extData.ReadBytes(&certificate.DelegatedCredential, len(extData)) { + return false + } + if len(certificate.DelegatedCredential) == 0 { + return false + } default: // Ignore unknown extensions. continue diff --git a/src/crypto/tls/handshake_messages_test.go b/src/crypto/tls/handshake_messages_test.go index cc427bf72a0..d9e00e79b93 100644 --- a/src/crypto/tls/handshake_messages_test.go +++ b/src/crypto/tls/handshake_messages_test.go @@ -152,6 +152,10 @@ func (*clientHelloMsg) Generate(rand *rand.Rand, size int) reflect.Value { if rand.Intn(10) > 5 { m.supportedSignatureAlgorithmsCert = supportedSignatureAlgorithms } + if rand.Intn(10) > 5 { + m.delegatedCredentialSupported = true + m.supportedSignatureAlgorithmsDC = supportedSignatureAlgorithmsDC + } for i := 0; i < rand.Intn(5); i++ { m.alpnProtocols = append(m.alpnProtocols, randomString(rand.Intn(20)+1, rand)) } @@ -368,6 +372,10 @@ func (*certificateRequestMsgTLS13) Generate(rand *rand.Rand, size int) reflect.V if rand.Intn(10) > 5 { m.scts = true } + if rand.Intn(10) > 5 { + m.supportDelegatedCredential = true + m.supportedSignatureAlgorithmsDC = supportedSignatureAlgorithmsDC + } if rand.Intn(10) > 5 { m.supportedSignatureAlgorithms = supportedSignatureAlgorithms } @@ -393,6 +401,10 @@ func (*certificateMsgTLS13) Generate(rand *rand.Rand, size int) reflect.Value { m.ocspStapling = true m.certificate.OCSPStaple = randomBytes(rand.Intn(100)+1, rand) } + if rand.Intn(10) > 5 { + m.delegatedCredential = true + m.certificate.DelegatedCredential = randomBytes(rand.Intn(100)+1, rand) + } if rand.Intn(10) > 5 { m.scts = true for i := 0; i < rand.Intn(2)+1; i++ { diff --git a/src/crypto/tls/handshake_server.go b/src/crypto/tls/handshake_server.go index 0672fb7f014..22836842966 100644 --- a/src/crypto/tls/handshake_server.go +++ b/src/crypto/tls/handshake_server.go @@ -871,15 +871,17 @@ func clientHelloInfo(ctx context.Context, c *Conn, clientHello *clientHelloMsg) } return &ClientHelloInfo{ - CipherSuites: clientHello.cipherSuites, - ServerName: clientHello.serverName, - SupportedCurves: clientHello.supportedCurves, - SupportedPoints: clientHello.supportedPoints, - SignatureSchemes: clientHello.supportedSignatureAlgorithms, - SupportedProtos: clientHello.alpnProtocols, - SupportedVersions: supportedVersions, - Conn: c.conn, - config: c.config, - ctx: ctx, + CipherSuites: clientHello.cipherSuites, + ServerName: clientHello.serverName, + SupportedCurves: clientHello.supportedCurves, + SupportedPoints: clientHello.supportedPoints, + SignatureSchemes: clientHello.supportedSignatureAlgorithms, + SupportedProtos: clientHello.alpnProtocols, + SupportedVersions: supportedVersions, + SupportsDelegatedCredential: clientHello.delegatedCredentialSupported, + SignatureSchemesDC: clientHello.supportedSignatureAlgorithmsDC, + Conn: c.conn, + config: c.config, + ctx: ctx, } } diff --git a/src/crypto/tls/handshake_server_tls13.go b/src/crypto/tls/handshake_server_tls13.go index ae0ba1be7c0..82b3479afce 100644 --- a/src/crypto/tls/handshake_server_tls13.go +++ b/src/crypto/tls/handshake_server_tls13.go @@ -12,6 +12,7 @@ import ( "crypto/rsa" "encoding/binary" "errors" + "fmt" "hash" "io" "sync/atomic" @@ -40,10 +41,50 @@ type serverHandshakeStateTLS13 struct { trafficSecret []byte // client_application_traffic_secret_0 transcript hash.Hash clientFinished []byte + certReq *certificateRequestMsgTLS13 hsTimings CFEventTLS13ServerHandshakeTimingInfo } +// processDelegatedCredentialFromClient unmarshals the DelegatedCredential +// offered by the client (if present) and validates it using the peer's +// certificate. +func (hs *serverHandshakeStateTLS13) processDelegatedCredentialFromClient(rawDC []byte, certVerifyMsg *certificateVerifyMsg) error { + c := hs.c + + var dc *DelegatedCredential + var err error + if rawDC != nil { + // Assert that the DC extension was indicated by the client. + if !hs.certReq.supportDelegatedCredential { + c.sendAlert(alertUnexpectedMessage) + return errors.New("tls: got Delegated Credential extension without indication") + } + + dc, err = UnmarshalDelegatedCredential(rawDC) + if err != nil { + c.sendAlert(alertDecodeError) + return fmt.Errorf("tls: Delegated Credential: %s", err) + } + + if !isSupportedSignatureAlgorithm(dc.cred.expCertVerfAlgo, supportedSignatureAlgorithmsDC) { + c.sendAlert(alertIllegalParameter) + return errors.New("tls: Delegated Credential used with invalid signature algorithm") + } + } + + if dc != nil { + if !dc.Validate(c.peerCertificates[0], true, c.config.time(), certVerifyMsg) { + c.sendAlert(alertIllegalParameter) + return errors.New("tls: invalid Delegated Credential") + } + } + + c.verifiedDC = dc + + return nil +} + func (hs *serverHandshakeStateTLS13) handshake() error { c := hs.c @@ -353,6 +394,31 @@ func cloneHash(in hash.Hash, h crypto.Hash) hash.Hash { return out } +// getDelegatedCredential will return a Delegated Credential pair (a Delegated +// Credential and its private key) for the given ClientHelloInfo, defaulting to +// the first element of cert.DelegatedCredentialPair. +// The returned Delegated Credential could be invalid for usage in the handshake. +// Returns an error if there are no delegated credentials or if the one found +// cannot be used for the current connection. +func getDelegatedCredential(clientHello *ClientHelloInfo, cert *Certificate) (*DelegatedCredentialPair, error) { + if len(cert.DelegatedCredentials) == 0 { + return nil, errors.New("no Delegated Credential found") + } + + for _, dcPair := range cert.DelegatedCredentials { + // The client must have sent the signature_algorithms in the DC extension: ensure it supports + // schemes we can use with this delegated credential. + if len(clientHello.SignatureSchemesDC) > 0 { + if _, err := selectSignatureSchemeDC(VersionTLS13, dcPair.DC, clientHello.SignatureSchemes, clientHello.SignatureSchemesDC); err == nil { + return &dcPair, nil + } + } + } + + // No delegated credential can be returned. + return nil, errors.New("no valid Delegated Credential found") +} + func (hs *serverHandshakeStateTLS13) pickCertificate() error { c := hs.c @@ -375,6 +441,7 @@ func (hs *serverHandshakeStateTLS13) pickCertificate() error { } return err } + hs.sigAlg, err = selectSignatureScheme(c.vers, certificate, hs.clientHello.supportedSignatureAlgorithms) if err != nil { // getCertificate returned a certificate that is unsupported or @@ -382,8 +449,29 @@ func (hs *serverHandshakeStateTLS13) pickCertificate() error { c.sendAlert(alertHandshakeFailure) return err } + hs.cert = certificate + if hs.clientHello.delegatedCredentialSupported && len(hs.clientHello.supportedSignatureAlgorithmsDC) > 0 { + // getDelegatedCredential selects a delegated credential that the client has advertised support for, if possible. + delegatedCredentialPair, err := getDelegatedCredential(clientHelloInfo(hs.ctx, c, hs.clientHello), hs.cert) + if err != nil { + // a Delegated Credential was not found. Fallback to the certificate. + return nil + } + if delegatedCredentialPair.DC != nil && delegatedCredentialPair.PrivateKey != nil { + // Even if the Delegated Credential has already been marshalled, be sure it is the correct one. + delegatedCredentialPair.DC.raw, err = delegatedCredentialPair.DC.Marshal() + if err != nil { + // invalid Delegated Credential. Fallback to the certificate. + return nil + } + hs.sigAlg = delegatedCredentialPair.DC.cred.expCertVerfAlgo + + hs.cert.PrivateKey = delegatedCredentialPair.PrivateKey + hs.cert.DelegatedCredential = delegatedCredentialPair.DC.raw + } + } return nil } @@ -468,6 +556,7 @@ func illegalClientHelloChange(ch, ch1 *clientHelloMsg) bool { len(ch.supportedCurves) != len(ch1.supportedCurves) || len(ch.supportedSignatureAlgorithms) != len(ch1.supportedSignatureAlgorithms) || len(ch.supportedSignatureAlgorithmsCert) != len(ch1.supportedSignatureAlgorithmsCert) || + len(ch.supportedSignatureAlgorithmsDC) != len(ch1.supportedSignatureAlgorithmsDC) || len(ch.alpnProtocols) != len(ch1.alpnProtocols) { return true } @@ -496,6 +585,11 @@ func illegalClientHelloChange(ch, ch1 *clientHelloMsg) bool { return true } } + for i := range ch.supportedSignatureAlgorithmsDC { + if ch.supportedSignatureAlgorithmsDC[i] != ch1.supportedSignatureAlgorithmsDC[i] { + return true + } + } for i := range ch.alpnProtocols { if ch.alpnProtocols[i] != ch1.alpnProtocols[i] { return true @@ -512,6 +606,7 @@ func illegalClientHelloChange(ch, ch1 *clientHelloMsg) bool { !bytes.Equal(ch.sessionTicket, ch1.sessionTicket) || ch.secureRenegotiationSupported != ch1.secureRenegotiationSupported || !bytes.Equal(ch.secureRenegotiation, ch1.secureRenegotiation) || + ch.delegatedCredentialSupported != ch1.delegatedCredentialSupported || ch.scts != ch1.scts || !bytes.Equal(ch.cookie, ch1.cookie) || !bytes.Equal(ch.pskModes, ch1.pskModes) @@ -595,10 +690,13 @@ func (hs *serverHandshakeStateTLS13) sendServerCertificate() error { certReq.ocspStapling = true certReq.scts = true certReq.supportedSignatureAlgorithms = c.config.supportedSignatureAlgorithms() + certReq.supportDelegatedCredential = c.config.SupportDelegatedCredential + certReq.supportedSignatureAlgorithmsDC = supportedSignatureAlgorithmsDC if c.config.ClientCAs != nil { certReq.certificateAuthorities = c.config.ClientCAs.Subjects() } + hs.certReq = certReq hs.transcript.Write(certReq.marshal()) if _, err := c.writeRecord(recordTypeHandshake, certReq.marshal()); err != nil { return err @@ -610,6 +708,7 @@ func (hs *serverHandshakeStateTLS13) sendServerCertificate() error { certMsg.certificate = *hs.cert certMsg.scts = hs.clientHello.scts && len(hs.cert.SignedCertificateTimestamps) > 0 certMsg.ocspStapling = hs.clientHello.ocspStapling && len(hs.cert.OCSPStaple) > 0 + certMsg.delegatedCredential = hs.clientHello.delegatedCredentialSupported && len(hs.cert.DelegatedCredential) > 0 hs.transcript.Write(certMsg.marshal()) if _, err := c.writeRecord(recordTypeHandshake, certMsg.marshal()); err != nil { @@ -621,8 +720,7 @@ func (hs *serverHandshakeStateTLS13) sendServerCertificate() error { certVerifyMsg := new(certificateVerifyMsg) certVerifyMsg.hasSignatureAlgorithm = true certVerifyMsg.signatureAlgorithm = hs.sigAlg - - sigType, sigHash, err := typeAndHashFromSignatureScheme(hs.sigAlg) + sigType, sigHash, err := typeAndHashFromSignatureScheme(certVerifyMsg.signatureAlgorithm) if err != nil { return c.sendAlert(alertInternalError) } @@ -846,9 +944,20 @@ func (hs *serverHandshakeStateTLS13) readClientCertificate() error { c.sendAlert(alertIllegalParameter) return errors.New("tls: client certificate used with invalid signature algorithm") } + + if certMsg.delegatedCredential { + if err := hs.processDelegatedCredentialFromClient(certMsg.certificate.DelegatedCredential, certVerify); err != nil { + return err + } + } + + pk := c.peerCertificates[0].PublicKey + if c.verifiedDC != nil { + pk = c.verifiedDC.cred.publicKey + } + signed := signedMessage(sigHash, clientSignatureContext, hs.transcript) - if err := verifyHandshakeSignature(sigType, c.peerCertificates[0].PublicKey, - sigHash, signed, certVerify.signature); err != nil { + if err := verifyHandshakeSignature(sigType, pk, sigHash, signed, certVerify.signature); err != nil { c.sendAlert(alertDecryptError) return errors.New("tls: invalid signature by the client certificate: " + err.Error()) } diff --git a/src/crypto/tls/tls.go b/src/crypto/tls/tls.go index de7e81ebf58..37c961cd188 100644 --- a/src/crypto/tls/tls.go +++ b/src/crypto/tls/tls.go @@ -4,6 +4,20 @@ // Package tls partially implements TLS 1.2, as specified in RFC 5246, // and TLS 1.3, as specified in RFC 8446. +// +// This package implements the "Delegated Credentials" extension, as +// specified by draft-ietf-tls-subcerts-10. This extension allows the usage +// of a limited delegation mechanism that allows a TLS peer to issue its own +// credentials within the scope of a certificate issued by an external +// CA. These credentials only enable the recipient of the delegation to +// speak for names that the CA has authorized. 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). package tls // BUG(agl): The crypto/tls package only implements some countermeasures diff --git a/src/crypto/tls/tls_test.go b/src/crypto/tls/tls_test.go index 16ef45b0f85..3ce93846c1d 100644 --- a/src/crypto/tls/tls_test.go +++ b/src/crypto/tls/tls_test.go @@ -817,6 +817,8 @@ func TestCloneNonFuncFields(t *testing.T) { f.Set(reflect.ValueOf(true)) case "MinVersion", "MaxVersion": f.Set(reflect.ValueOf(uint16(VersionTLS12))) + case "SupportDelegatedCredential": + f.Set(reflect.ValueOf(true)) case "SessionTicketKey": f.Set(reflect.ValueOf([32]byte{})) case "CipherSuites": diff --git a/src/crypto/x509/parser.go b/src/crypto/x509/parser.go index 0a13b279840..887c098cb8c 100644 --- a/src/crypto/x509/parser.go +++ b/src/crypto/x509/parser.go @@ -810,6 +810,15 @@ func processExtensions(out *Certificate) error { out.IssuingCertificateURL = append(out.IssuingCertificateURL, string(aiaDER)) } } + } else if e.Id.Equal(oidExtensionDelegatedCredential) { + if !out.IsCA { + if out.KeyUsage == KeyUsageDigitalSignature { + if !bytes.Equal(e.Value, asn1.NullBytes) { + return errors.New("x509: invalid delegated credential extension") + } + out.AllowDC = true + } + } } else { // Unknown extensions are recorded if critical. unhandled = true diff --git a/src/crypto/x509/verify_test.go b/src/crypto/x509/verify_test.go index c3ef503b277..a79fe63f911 100644 --- a/src/crypto/x509/verify_test.go +++ b/src/crypto/x509/verify_test.go @@ -1725,7 +1725,7 @@ func TestValidHostname(t *testing.T) { } } -func generateCert(cn string, isCA bool, issuer *Certificate, issuerKey crypto.PrivateKey) (*Certificate, crypto.PrivateKey, error) { +func generateCert(cn string, isCA bool, isDC bool, issuer *Certificate, issuerKey crypto.PrivateKey) (*Certificate, crypto.PrivateKey, error) { priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) if err != nil { return nil, nil, err @@ -1744,6 +1744,7 @@ func generateCert(cn string, isCA bool, issuer *Certificate, issuerKey crypto.Pr ExtKeyUsage: []ExtKeyUsage{ExtKeyUsageServerAuth}, BasicConstraintsValid: true, IsCA: isCA, + AllowDC: isDC, } if issuer == nil { issuer = template @@ -1771,21 +1772,21 @@ func TestPathologicalChain(t *testing.T) { // path building worst behavior. roots, intermediates := NewCertPool(), NewCertPool() - parent, parentKey, err := generateCert("Root CA", true, nil, nil) + parent, parentKey, err := generateCert("Root CA", true, false, nil, nil) if err != nil { t.Fatal(err) } roots.AddCert(parent) for i := 1; i < 100; i++ { - parent, parentKey, err = generateCert("Intermediate CA", true, parent, parentKey) + parent, parentKey, err = generateCert("Intermediate CA", true, false, parent, parentKey) if err != nil { t.Fatal(err) } intermediates.AddCert(parent) } - leaf, _, err := generateCert("Leaf", false, parent, parentKey) + leaf, _, err := generateCert("Leaf", false, true, parent, parentKey) if err != nil { t.Fatal(err) } @@ -1809,7 +1810,7 @@ func TestLongChain(t *testing.T) { roots, intermediates := NewCertPool(), NewCertPool() - parent, parentKey, err := generateCert("Root CA", true, nil, nil) + parent, parentKey, err := generateCert("Root CA", true, false, nil, nil) if err != nil { t.Fatal(err) } @@ -1817,14 +1818,14 @@ func TestLongChain(t *testing.T) { for i := 1; i < 15; i++ { name := fmt.Sprintf("Intermediate CA #%d", i) - parent, parentKey, err = generateCert(name, true, parent, parentKey) + parent, parentKey, err = generateCert(name, true, false, parent, parentKey) if err != nil { t.Fatal(err) } intermediates.AddCert(parent) } - leaf, _, err := generateCert("Leaf", false, parent, parentKey) + leaf, _, err := generateCert("Leaf", false, true, parent, parentKey) if err != nil { t.Fatal(err) } diff --git a/src/crypto/x509/x509.go b/src/crypto/x509/x509.go index 7a33617a013..702d09d32cd 100644 --- a/src/crypto/x509/x509.go +++ b/src/crypto/x509/x509.go @@ -694,6 +694,9 @@ type Certificate struct { BasicConstraintsValid bool IsCA bool + // AllowDC indicates if the certificate can be used for delegated credentials. + AllowDC bool + // MaxPathLen and MaxPathLenZero indicate the presence and // value of the BasicConstraints' "pathLenConstraint". // @@ -999,6 +1002,7 @@ var ( oidExtensionCRLDistributionPoints = []int{2, 5, 29, 31} oidExtensionAuthorityInfoAccess = []int{1, 3, 6, 1, 5, 5, 7, 1, 1} oidExtensionCRLNumber = []int{2, 5, 29, 20} + oidExtensionDelegatedCredential = []int{1, 3, 6, 1, 4, 1, 44363, 44} ) var ( @@ -1101,6 +1105,16 @@ func buildCertExtensions(template *Certificate, subjectIsEmpty bool, authorityKe n++ } + // This extension is not critical + if template.AllowDC && !template.IsCA && !oidInExtensions(oidExtensionDelegatedCredential, template.ExtraExtensions) && (template.KeyUsage&KeyUsageDigitalSignature != 0) { + ret[n].Id = oidExtensionDelegatedCredential + ret[n].Value, err = asn1.Marshal(asn1.NullRawValue) + if err != nil { + return + } + n++ + } + if len(authorityKeyId) > 0 && !oidInExtensions(oidExtensionAuthorityKeyId, template.ExtraExtensions) { ret[n].Id = oidExtensionAuthorityKeyId ret[n].Value, err = asn1.Marshal(authKeyId{authorityKeyId}) @@ -1490,6 +1504,7 @@ var emptyASN1Subject = []byte{0x30, 0} // - ExtraExtensions // - IPAddresses // - IsCA +// - AllowDC // - IssuingCertificateURL // - KeyUsage // - MaxPathLen diff --git a/src/crypto/x509/x509_test.go b/src/crypto/x509/x509_test.go index f271b24fbeb..daa71152111 100644 --- a/src/crypto/x509/x509_test.go +++ b/src/crypto/x509/x509_test.go @@ -1939,6 +1939,48 @@ func TestISOOIDInCertificate(t *testing.T) { } } +const certIsDCOID = ` +-----BEGIN CERTIFICATE----- +MIIFRjCCBMugAwIBAgIQDGevB+lY0o/OecHFSJ6YnTAKBggqhkjOPQQDAzBMMQsw +CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMSYwJAYDVQQDEx1EaWdp +Q2VydCBFQ0MgU2VjdXJlIFNlcnZlciBDQTAeFw0xOTAzMjYwMDAwMDBaFw0yMTAz +MzAxMjAwMDBaMGoxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYw +FAYDVQQHEw1TYW4gRnJhbmNpc2NvMRkwFwYDVQQKExBDbG91ZGZsYXJlLCBJbmMu +MRMwEQYDVQQDEwprYzJrZG0uY29tMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE +d4azI83Bw0fcPgfoeiZpZZnwGuxjBjv++wzE0zAj8vNiUkKxOWSQiGNLn+xlWUpL +lw9djRN1rLmVmn2gb9GgdKOCA28wggNrMB8GA1UdIwQYMBaAFKOd5h/52jlPwG7o +kcuVpdox4gqfMB0GA1UdDgQWBBSfcb7fS3fUFAyB91fRcwoDPtgtJjAjBgNVHREE +HDAaggprYzJrZG0uY29tggwqLmtjMmtkbS5jb20wDgYDVR0PAQH/BAQDAgeAMB0G +A1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjBpBgNVHR8EYjBgMC6gLKAqhiho +dHRwOi8vY3JsMy5kaWdpY2VydC5jb20vc3NjYS1lY2MtZzEuY3JsMC6gLKAqhiho +dHRwOi8vY3JsNC5kaWdpY2VydC5jb20vc3NjYS1lY2MtZzEuY3JsMEwGA1UdIARF +MEMwNwYJYIZIAYb9bAEBMCowKAYIKwYBBQUHAgEWHGh0dHBzOi8vd3d3LmRpZ2lj +ZXJ0LmNvbS9DUFMwCAYGZ4EMAQICMHsGCCsGAQUFBwEBBG8wbTAkBggrBgEFBQcw +AYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29tMEUGCCsGAQUFBzAChjlodHRwOi8v +Y2FjZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRFQ0NTZWN1cmVTZXJ2ZXJDQS5j +cnQwDAYDVR0TAQH/BAIwADAPBgkrBgEEAYLaSywEAgUAMIIBfgYKKwYBBAHWeQIE +AgSCAW4EggFqAWgAdgC72d+8H4pxtZOUI5eqkntHOFeVCqtS6BqQlmQ2jh7RhQAA +AWm5hYJ5AAAEAwBHMEUCICiGfq+hSThRL2m8H0awoDR8OpnEHNkF0nI6nL5yYL/j +AiEAxwebGs/T6Es0YarPzoQJrVZqk+sHH/t+jrSrKd5TDjcAdgCHdb/nWXz4jEOZ +X73zbv9WjUdWNv9KtWDBtOr/XqCDDwAAAWm5hYNgAAAEAwBHMEUCIQD9OWA8KGL6 +bxDKfgIleHJWB0iWieRs88VgJyfAg/aFDgIgQ/OsdSF9XOy1foqge0DTDM2FExuw +0JR0AGZWXoNtJzMAdgBElGUusO7Or8RAB9io/ijA2uaCvtjLMbU/0zOWtbaBqAAA +AWm5hYHgAAAEAwBHMEUCIQC4vua1n3BqthEqpA/VBTcsNwMtAwpCuac2IhJ9wx6X +/AIgb+o00k28JQo9TMpP4vzJ3BD3HXWSNc2Zizbq7mkUQYMwCgYIKoZIzj0EAwMD +aQAwZgIxAJsX7d0SuA8ddf/m7IWfNfs3MQfJyGkEezMJX1t6sRso5z50SS12LpXe +muGa1FE2ZgIxAL+CDUF5pz7mhrAEIjQ1MqlpF9tH40dJGvYZZQ3W23cMzSkDfvlt +y5S4RfWHIIPjbw== +-----END CERTIFICATE-----` + +func TestIsDCOIDInCertificate(t *testing.T) { + block, _ := pem.Decode([]byte(certIsDCOID)) + if cert, err := ParseCertificate(block.Bytes); err != nil { + t.Errorf("certificate with DC OID failed to parse: %s", err) + } else if cert.SignatureAlgorithm == UnknownSignatureAlgorithm { + t.Errorf("DC OID not recognised in certificate") + } +} + // certMultipleRDN contains a RelativeDistinguishedName with two elements (the // common name and serial number). This particular certificate was the first // such certificate in the “Pilot” Certificate Transparency log.