diff --git a/src/crypto/tls/auth.go b/src/crypto/tls/auth.go index 9d3efe2d71e..51113d88969 100644 --- a/src/crypto/tls/auth.go +++ b/src/crypto/tls/auth.go @@ -259,6 +259,74 @@ func signatureSchemesForCertificate(version uint16, cert *Certificate) []Signatu return sigAlgs } +// signatureSchemeForDelegatedCredential returns the list of supported +// SignatureSchemes for a given delegated credential, based on the public +// key, and optionally filtered by its explicit +// SupportedSignatureAlgorithmsDC. +// +// This function must be kept in sync with supportedSignatureAlgorithmsDC. +func signatureSchemeForDelegatedCredential(version uint16, dc *DelegatedCredential) []SignatureScheme { + pub := dc.cred.publicKey + + var sigAlgs []SignatureScheme + switch pub.(type) { + case *ecdsa.PublicKey: + pk, ok := pub.(*ecdsa.PublicKey) + if !ok { + return nil + } + switch pk.Curve { + case elliptic.P256(): + sigAlgs = []SignatureScheme{ECDSAWithP256AndSHA256} + case elliptic.P384(): + sigAlgs = []SignatureScheme{ECDSAWithP384AndSHA384} + case elliptic.P521(): + sigAlgs = []SignatureScheme{ECDSAWithP521AndSHA512} + default: + return nil + } + case ed25519.PublicKey: + sigAlgs = []SignatureScheme{Ed25519} + case circlSign.PublicKey: + pk, ok := pub.(circlSign.PublicKey) + if !ok { + return nil + } + scheme := pk.Scheme() + tlsScheme, ok := scheme.(circlPki.TLSScheme) + if !ok { + return nil + } + sigAlgs = []SignatureScheme{SignatureScheme(tlsScheme.TLSIdentifier())} + default: + return nil + } + + 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) (SignatureScheme, error) { + if vers != VersionTLS13 { + return 0, errors.New("unsupported TLS version for dc") + } + + supportedAlgs := signatureSchemeForDelegatedCredential(vers, dc) + if len(supportedAlgs) == 0 { + return 0, errors.New("unsupported scheme for dc") + } + // Pick signature scheme in the peer's preference order, as our + // preference order is not configurable. + for _, preferredAlg := range peerAlgs { + if isSupportedSignatureAlgorithm(preferredAlg, supportedAlgs) { + return preferredAlg, nil + } + } + return 0, errors.New("tls: peer doesn't support any of the delegated credential's signature algorithms") +} + // 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 0ac329ddb95..4450dc52d61 100644 --- a/src/crypto/tls/common.go +++ b/src/crypto/tls/common.go @@ -92,6 +92,7 @@ const ( extensionSignatureAlgorithms uint16 = 13 extensionALPN uint16 = 16 extensionSCT uint16 = 18 + extensionDelegatedCredentials uint16 = 34 extensionSessionTicket uint16 = 35 extensionPreSharedKey uint16 = 41 extensionEarlyData uint16 = 42 @@ -195,6 +196,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. @@ -301,6 +312,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 @@ -474,6 +490,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). @@ -488,6 +511,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. @@ -508,10 +535,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 } @@ -774,6 +812,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 @@ -863,6 +908,7 @@ func (c *Config) Clone() *Config { DynamicRecordSizingDisabled: c.DynamicRecordSizingDisabled, Renegotiation: c.Renegotiation, KeyLogWriter: c.KeyLogWriter, + SupportDelegatedCredential: c.SupportDelegatedCredential, ECHEnabled: c.ECHEnabled, ClientECHConfigs: c.ClientECHConfigs, ServerECHProvider: c.ServerECHProvider, @@ -1407,6 +1453,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 @@ -1424,6 +1480,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 859f3630d68..f3fc6d008b2 100644 --- a/src/crypto/tls/conn.go +++ b/src/crypto/tls/conn.go @@ -52,6 +52,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 @@ -1466,6 +1469,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.ECHAccepted = c.ech.accepted diff --git a/src/crypto/tls/delegated_credentials.go b/src/crypto/tls/delegated_credentials.go new file mode 100644 index 00000000000..1fae172d2eb --- /dev/null +++ b/src/crypto/tls/delegated_credentials.go @@ -0,0 +1,517 @@ +// 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/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 +) + +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 + 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) { + var sigAlgo SignatureScheme + switch sk := cert.PrivateKey.(type) { + case *ecdsa.PrivateKey: + pk := sk.Public().(*ecdsa.PublicKey) + curveName := pk.Curve.Params().Name + certAlg := cert.Leaf.SignatureAlgorithm + if certAlg == x509.ECDSAWithSHA256 && curveName == "P-256" { + sigAlgo = ECDSAWithP256AndSHA256 + } else if certAlg == x509.ECDSAWithSHA384 && curveName == "P-384" { + sigAlgo = ECDSAWithP384AndSHA384 + } else if certAlg == x509.ECDSAWithSHA512 && curveName == "P-521" { + sigAlgo = ECDSAWithP521AndSHA512 + } else { + return SignatureScheme(0x00), fmt.Errorf("using curve %s for %s is not supported", curveName, cert.Leaf.SignatureAlgorithm) + } + case ed25519.PrivateKey: + sigAlgo = Ed25519 + default: + return SignatureScheme(0x00), fmt.Errorf("tls: unsupported algorithm for Delegated Credential") + } + + return sigAlgo, nil +} + +// 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: %T", 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 + } + 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) + 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") + } + + 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..3e898562122 --- /dev/null +++ b/src/crypto/tls/delegated_credentials_test.go @@ -0,0 +1,646 @@ +// 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 + +} + +// 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" + + clientConfig := dcTestConfig.Clone() + serverConfig := dcTestConfig.Clone() + clientConfig.InsecureSkipVerify = true + + for i, test := range dcServerTests { + clientConfig.SupportDelegatedCredential = test.clientDCSupport + + initDCTest() + for dcCount = 0; dcCount < len(dcTestDCSignatureScheme); dcCount++ { + 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" + + serverConfig := dcTestConfig.Clone() + serverConfig.ClientAuth = RequestClientCert + serverConfig.GetCertificate = testServerGetCertificate + clientConfig := dcTestConfig.Clone() + clientConfig.GetClientCertificate = testClientGetCertificate + + for j, test := range dcClientTests { + serverConfig.SupportDelegatedCredential = test.serverDCSupport + + initDCTest() + 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" + + 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 + + initDCTest() + + 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 3fc35d0669f..22b746859c4 100644 --- a/src/crypto/tls/generate_cert.go +++ b/src/crypto/tls/generate_cert.go @@ -33,6 +33,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") @@ -140,10 +141,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/handshake_client.go b/src/crypto/tls/handshake_client.go index 8eb9583c173..816be289085 100644 --- a/src/crypto/tls/handshake_client.go +++ b/src/crypto/tls/handshake_client.go @@ -131,6 +131,8 @@ func (c *Conn) makeClientHello(minVersion uint16) (*clientHelloMsg, ecdheParamet return nil, nil, err } hello.keyShares = []keyShare{{group: curveID, data: params.PublicKey()}} + hello.delegatedCredentialSupported = config.SupportDelegatedCredential + hello.supportedSignatureAlgorithmsDC = supportedSignatureAlgorithmsDC } return hello, params, nil @@ -912,8 +914,10 @@ func (c *Conn) verifyServerCertificate(certificates [][]byte) error { // <= 1.2 CertificateRequest, making an effort to fill in missing information. func certificateRequestInfoFromMsg(vers uint16, certReq *certificateRequestMsg) *CertificateRequestInfo { cri := &CertificateRequestInfo{ - AcceptableCAs: certReq.certificateAuthorities, - Version: vers, + AcceptableCAs: certReq.certificateAuthorities, + Version: vers, + 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 5db1929d0e3..cc69bb1999a 100644 --- a/src/crypto/tls/handshake_client_tls13.go +++ b/src/crypto/tls/handshake_client_tls13.go @@ -40,6 +40,45 @@ type clientHandshakeStateTLS13 struct { handshakeTimings 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 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 { @@ -648,6 +687,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) @@ -656,8 +696,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()) @@ -718,6 +769,48 @@ func (hs *clientHandshakeStateTLS13) readServerFinished() error { return nil } +func certificateRequestInfo(certReq *certificateRequestMsgTLS13, vers uint16) *CertificateRequestInfo { + cri := &CertificateRequestInfo{ + SupportsDelegatedCredential: certReq.supportDelegatedCredential, + SignatureSchemes: certReq.supportedSignatureAlgorithms, + SignatureSchemesDC: certReq.supportedSignatureAlgorithmsDC, + AcceptableCAs: certReq.certificateAuthorities, + Version: vers, + } + + 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.") + } + + if len(cert.DelegatedCredentials) == 1 { + // There's only one choice, so no point doing any work. + return &cert.DelegatedCredentials[0], nil + } + + 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.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 @@ -725,20 +818,33 @@ func (hs *clientHandshakeStateTLS13) sendClientCertificate() error { return nil } - cert, err := c.getClientCertificate(&CertificateRequestInfo{ - AcceptableCAs: hs.certReq.certificateAuthorities, - SignatureSchemes: hs.certReq.supportedSignatureAlgorithms, - Version: c.vers, - }) + cri := certificateRequestInfo(hs.certReq, c.vers) + + cert, err := c.getClientCertificate(cri) if err != nil { return err } + var dcPair *DelegatedCredentialPair + if hs.certReq.supportDelegatedCredential && len(hs.certReq.supportedSignatureAlgorithmsDC) > 0 { + 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 { @@ -755,7 +861,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. @@ -763,6 +871,20 @@ func (hs *clientHandshakeStateTLS13) sendClientCertificate() error { return err } + if certMsg.delegatedCredential { + suppSigAlgo = hs.certReq.supportedSignatureAlgorithmsDC + sigAlgorithm, err = selectSignatureSchemeDC(c.vers, dcPair.DC, suppSigAlgo) + if err != nil { + // getDelegatedCredential returned a delegated credential incompatible with the + // CertificateRequestInfo supported signature algorithms. + cert.DelegatedCredential = nil + } else { + 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 5155866663c..a5550b8468d 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 @@ -208,6 +210,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) @@ -537,6 +552,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 @@ -1096,7 +1125,9 @@ type certificateRequestMsgTLS13 struct { raw []byte ocspStapling bool scts bool + supportDelegatedCredential bool supportedSignatureAlgorithms []SignatureScheme + supportedSignatureAlgorithmsDC []SignatureScheme supportedSignatureAlgorithmsCert []SignatureScheme certificateAuthorities [][]byte } @@ -1127,6 +1158,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) { @@ -1191,6 +1235,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() { @@ -1320,10 +1378,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 { @@ -1343,10 +1402,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 } @@ -1358,7 +1421,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 { @@ -1382,6 +1446,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) + }) + } }) } }) @@ -1401,6 +1471,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 } @@ -1452,6 +1523,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 bb8aea86700..de813efbefd 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 9485d7143c6..075fd9f5058 100644 --- a/src/crypto/tls/handshake_server.go +++ b/src/crypto/tls/handshake_server.go @@ -847,14 +847,16 @@ func clientHelloInfo(c *Conn, clientHello *clientHelloMsg) *ClientHelloInfo { } 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, + 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, } } diff --git a/src/crypto/tls/handshake_server_tls13.go b/src/crypto/tls/handshake_server_tls13.go index 1f846ba79eb..cb3b4cbf2c8 100644 --- a/src/crypto/tls/handshake_server_tls13.go +++ b/src/crypto/tls/handshake_server_tls13.go @@ -38,6 +38,7 @@ type serverHandshakeStateTLS13 struct { trafficSecret []byte // client_application_traffic_secret_0 transcript hash.Hash clientFinished []byte + certReq *certificateRequestMsgTLS13 handshakeTimings CFEventTLS13ServerHandshakeTimingInfo } @@ -46,6 +47,45 @@ func (hs *serverHandshakeStateTLS13) echIsInner() bool { return len(hs.clientHello.ech) == 1 && hs.clientHello.ech[0] == echClientHelloInnerVariant } +// 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 @@ -402,6 +442,36 @@ 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.") + } + + if len(cert.DelegatedCredentials) == 1 { + // There's only one choice, so no point doing any work. + return &cert.DelegatedCredentials[0], nil + } + + 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.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 @@ -424,6 +494,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 @@ -431,8 +502,36 @@ func (hs *serverHandshakeStateTLS13) pickCertificate() error { c.sendAlert(alertHandshakeFailure) return err } + hs.cert = certificate + if hs.clientHello.delegatedCredentialSupported && len(hs.clientHello.supportedSignatureAlgorithmsDC) > 0 { + delegatedCredentialPair, err := getDelegatedCredential(clientHelloInfo(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, err = selectSignatureSchemeDC(c.vers, delegatedCredentialPair.DC, hs.clientHello.supportedSignatureAlgorithmsDC) + if err != nil { + // the Delegated Credential is unsupported or + // incompatible with the client's signature algorithms. + // Fallback to the certificate. + return nil + } + + hs.cert.PrivateKey = delegatedCredentialPair.PrivateKey + hs.cert.DelegatedCredential = delegatedCredentialPair.DC.raw + } + } return nil } @@ -543,6 +642,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 } @@ -571,6 +671,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 @@ -587,6 +692,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) @@ -698,11 +804,14 @@ func (hs *serverHandshakeStateTLS13) sendServerCertificate() error { certReq := new(certificateRequestMsgTLS13) certReq.ocspStapling = true certReq.scts = true + certReq.supportDelegatedCredential = c.config.SupportDelegatedCredential certReq.supportedSignatureAlgorithms = supportedSignatureAlgorithms + 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 @@ -714,6 +823,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 { @@ -726,7 +836,7 @@ func (hs *serverHandshakeStateTLS13) sendServerCertificate() error { 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) } @@ -937,9 +1047,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 f7af420f810..a6f43561010 100644 --- a/src/crypto/tls/tls.go +++ b/src/crypto/tls/tls.go @@ -12,6 +12,20 @@ // forwards the decrypted ClientHello to the intended recipient, known as the // backend server. The goal of this mechanism is to ensure that connections made // to backend servers are indistinguishable from one another. +// +// 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(cjpatton): In order to achieve its security goal, the ECH extension diff --git a/src/crypto/tls/tls_test.go b/src/crypto/tls/tls_test.go index 4d855cefd84..1808ea4d407 100644 --- a/src/crypto/tls/tls_test.go +++ b/src/crypto/tls/tls_test.go @@ -816,6 +816,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/verify_test.go b/src/crypto/x509/verify_test.go index 8e0a7bef475..f7caf53d0ae 100644 --- a/src/crypto/x509/verify_test.go +++ b/src/crypto/x509/verify_test.go @@ -1883,7 +1883,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 @@ -1902,6 +1902,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 @@ -1929,21 +1930,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) } @@ -1967,7 +1968,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) } @@ -1975,14 +1976,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 1ba9508c29d..d02d9c91da4 100644 --- a/src/crypto/x509/x509.go +++ b/src/crypto/x509/x509.go @@ -695,6 +695,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". // @@ -1494,6 +1497,18 @@ func parseCertificate(in *certificate) (*Certificate, error) { out.IssuingCertificateURL = append(out.IssuingCertificateURL, string(v.Location.Bytes)) } } + } else if e.Id.Equal(oidExtensionDelegatedCredential) { + if !out.IsCA { + if out.KeyUsage == KeyUsageDigitalSignature { + val := asn1.RawValue{} + if rest, err := asn1.Unmarshal(e.Value, &val); err != nil { + return nil, err + } else if len(rest) != 0 { + return nil, errors.New("x509: trailing data after X.509 authority information") + } + out.AllowDC = true + } + } } else { // Unknown extensions are recorded if critical. unhandled = true @@ -1668,6 +1683,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 ( @@ -1770,6 +1786,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}) @@ -2159,6 +2185,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 3608eef7d4a..9bbd63147fb 100644 --- a/src/crypto/x509/x509_test.go +++ b/src/crypto/x509/x509_test.go @@ -1915,6 +1915,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.