diff --git a/13.go b/13.go index cc52510..99346be 100644 --- a/13.go +++ b/13.go @@ -408,7 +408,7 @@ func (hs *serverHandshakeState) sendCertificate13() error { // server is using the delegated credential extension. In TLS 1.3, the DC is // added as an extension to the end-entity certificate, i.e., the last // CertificateEntry of Certificate.certficate_list (see - // https://tools.ietf.org/html/draft-ietf-tls-subcerts). + // https://tools.ietf.org/html/draft-ietf-tls-subcerts-01). if len(certEntries) > 0 && hs.clientHello.delegatedCredential && hs.delegatedCredential != nil { certEntries[0].delegatedCredential = hs.delegatedCredential } @@ -1069,7 +1069,7 @@ func (hs *clientHandshakeState) doTLS13Handshake() error { // then the CertificateVerify signature will have been produced with the // DelegatedCredential's private key. if hs.c.verifiedDc != nil { - pk = hs.c.verifiedDc.PublicKey + pk = hs.c.verifiedDc.cred.publicKey } // Receive CertificateVerify message. diff --git a/common.go b/common.go index 9eea98e..93fb353 100644 --- a/common.go +++ b/common.go @@ -219,6 +219,7 @@ type ConnectionState struct { VerifiedChains [][]*x509.Certificate // verified chains built from PeerCertificates SignedCertificateTimestamps [][]byte // SCTs from the server, if any OCSPResponse []byte // stapled OCSP response from server, if any + DelegatedCredential []byte // Delegated credential sent by the server, if any // TLSUnique contains the "tls-unique" channel binding value (see RFC // 5929, section 3). For resumed sessions this value will be nil @@ -619,16 +620,20 @@ type Config struct { // // This value has no meaning for the server. // - // See https://tools.ietf.org/html/draft-ietf-tls-subcerts. + // See https://tools.ietf.org/html/draft-ietf-tls-subcerts-01. AcceptDelegatedCredential bool - // GetDelegatedCredential returns a DelegatedCredential for use with the - // delegated credential extension based on the ClientHello and TLS version - // selected for the session. If this is nil, then the server will not offer - // a DelegatedCredential. + // GetDelegatedCredential returns a DC and its private key for use in the + // delegated credential extension. The inputs to the callback are some + // information parsed from the ClientHello, as well as the protocol version + // selected by the server. This is necessary because the DC is bound to + // protocol version in which it's used. The return value is the raw DC + // encoded in the wire format specified in + // https://tools.ietf.org/html/draft-ietf-tls-subcerts-01. If the return + // value is nil, then the server will not offer negotiate the extension. // // This value has no meaning for the client. - GetDelegatedCredential func(*ClientHelloInfo, uint16) (*DelegatedCredential, crypto.PrivateKey, error) + GetDelegatedCredential func(*ClientHelloInfo, uint16) ([]byte, crypto.PrivateKey, error) serverInitOnce sync.Once // guards calling (*Config).serverInit diff --git a/conn.go b/conn.go index 402acdb..bdcfb58 100644 --- a/conn.go +++ b/conn.go @@ -58,7 +58,7 @@ type Conn struct { verifiedChains [][]*x509.Certificate // verifiedDc is set by a client who negotiates the use of a valid delegated // credential. - verifiedDc *DelegatedCredential + verifiedDc *delegatedCredential // serverName contains the server name indicated by the client, if any. serverName string // secureRenegotiation is true if the server echoed the secure @@ -1666,6 +1666,9 @@ func (c *Conn) ConnectionState() ConnectionState { state.VerifiedChains = c.verifiedChains state.SignedCertificateTimestamps = c.scts state.OCSPResponse = c.ocspResponse + if c.verifiedDc != nil { + state.DelegatedCredential = c.verifiedDc.raw + } state.HandshakeConfirmed = atomic.LoadInt32(&c.handshakeConfirmed) == 1 if !state.HandshakeConfirmed { state.Unique0RTTToken = c.binder diff --git a/generate_cert.go b/generate_cert.go index 235a545..8ee2b59 100644 --- a/generate_cert.go +++ b/generate_cert.go @@ -14,7 +14,6 @@ import ( "crypto/elliptic" "crypto/rand" "crypto/rsa" - "crypto/tls" "crypto/x509" "crypto/x509/pkix" "encoding/pem" @@ -35,7 +34,6 @@ var ( isCA = flag.Bool("ca", false, "whether this cert should be its own Certificate Authority") 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") - isDC = flag.Bool("dc", false, "whether this cert can be used with delegated credentials") ) func publicKey(priv interface{}) interface{} { @@ -139,11 +137,6 @@ func main() { template.KeyUsage |= x509.KeyUsageCertSign } - if *isDC { - template.ExtraExtensions = append( - template.ExtraExtensions, *tls.CreateDelegationUsagePKIXExtension()) - } - derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, publicKey(priv), priv) if err != nil { log.Fatalf("Failed to create certificate: %s", err) diff --git a/handshake_client.go b/handshake_client.go index 1b4950a..94024fa 100644 --- a/handshake_client.go +++ b/handshake_client.go @@ -413,15 +413,15 @@ func (hs *clientHandshakeState) processCertsFromServer(certificates [][]byte) er return nil } -// processDelegatedCredentialFromServer unmarshals the DelegatedCredential +// processDelegatedCredentialFromServer unmarshals the delegated credential // offered by the server (if present) and validates it using the peer // certificate. -func (hs *clientHandshakeState) processDelegatedCredentialFromServer(dc []byte) error { +func (hs *clientHandshakeState) processDelegatedCredentialFromServer(serialized []byte) error { c := hs.c - var cred *DelegatedCredential + var dc *delegatedCredential var err error - if dc != nil { + if serialized != nil { // Assert that the DC extension was indicated by the client. if !hs.hello.delegatedCredential { @@ -436,15 +436,15 @@ func (hs *clientHandshakeState) processDelegatedCredentialFromServer(dc []byte) return errors.New("tls: ServerHello with delegated credential extension in TLS != 1.2") } - cred, err = UnmarshalDelegatedCredential(dc) + dc, err = unmarshalDelegatedCredential(serialized) if err != nil { c.sendAlert(alertDecodeError) return fmt.Errorf("tls: delegated credential: %s", err) } } - if cred != nil && !c.config.InsecureSkipVerify { - if v, err := cred.Validate(c.peerCertificates[0], hs.c.vers, c.config.time()); err != nil { + if dc != nil && !c.config.InsecureSkipVerify { + if v, err := dc.validate(c.peerCertificates[0], hs.c.vers, c.config.time()); err != nil { c.sendAlert(alertIllegalParameter) return fmt.Errorf("delegated credential: %s", err) } else if !v { @@ -453,7 +453,7 @@ func (hs *clientHandshakeState) processDelegatedCredentialFromServer(dc []byte) } } - c.verifiedDc = cred + c.verifiedDc = dc return nil } @@ -533,9 +533,10 @@ func (hs *clientHandshakeState) doFullHandshake() error { pk := c.peerCertificates[0].PublicKey // If the delegated credential extension has successfully been negotiated, - // then the ServerKeyExchange DelegatedCredential's private key. + // then the ServerKeyExchange is signed with delegated credential private + // key. if c.verifiedDc != nil { - pk = c.verifiedDc.PublicKey + pk = c.verifiedDc.cred.publicKey } skx, ok := msg.(*serverKeyExchangeMsg) diff --git a/handshake_messages.go b/handshake_messages.go index ab3b006..eac6a69 100644 --- a/handshake_messages.go +++ b/handshake_messages.go @@ -759,7 +759,7 @@ func (m *clientHelloMsg) unmarshal(data []byte) alert { // https://tools.ietf.org/html/draft-ietf-tls-tls13-18#section-4.2.8 m.earlyData = true case extensionDelegatedCredential: - // https://tools.ietf.org/html/draft-ietf-tls-subcerts + // https://tools.ietf.org/html/draft-ietf-tls-subcerts-01 m.delegatedCredential = true } data = data[length:] diff --git a/handshake_server.go b/handshake_server.go index b781031..d625e3f 100644 --- a/handshake_server.go +++ b/handshake_server.go @@ -329,14 +329,7 @@ Curves: // Set the handshake private key. if dc != nil { hs.privateKey = sk - if dc.Raw == nil { - dc.Raw, err = dc.Marshal() - if err != nil { - c.sendAlert(alertInternalError) - return false, err - } - } - hs.delegatedCredential = dc.Raw + hs.delegatedCredential = dc // For TLS 1.2, the DC is an extension to the ServerHello. if c.vers == VersionTLS12 { diff --git a/subcerts.go b/subcerts.go index 7f025ec..6c8071f 100644 --- a/subcerts.go +++ b/subcerts.go @@ -5,29 +5,30 @@ 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 supports this extension, then -// the server 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 server by an entity trusted by the client. This allows a -// middlebox to terminate a TLS connection on behalf of the entity; for example, -// this can be used to delegate TLS termination to a reverse proxy. Credentials -// can't be revoked; in order to mitigate risk in case the middlebox is -// compromised, the credential is only valid for a short time (days, hours, or -// even minutes). +// (https://tools.ietf.org/html/draft-ietf-tls-subcerts-01) is an IETF Internet +// draft and proposed TLS extension. This allows a backend server to delegate +// TLS termination to a trusted frontend. If the client supports this extension, +// then the frontend may use a "delegated credential" as the signing key in the +// handshake. A delegated credential is a short lived key pair delegated to the +// server by an entity trusted by the client. Once issued, credentials can't be +// revoked; in order to mitigate risk in case the frontend is compromised, the +// credential is only valid for a short time (days, hours, or even minutes). // -// BUG(cjpatton) Subcerts: Need to add support for PKCS1, PSS, and EdDSA. -// Currently delegated credentials only support ECDSA. The delegator must also -// use an ECDSA key. +// This implements draft 01. This draft doesn't specify an object identifier for +// the X.509 extension; we use one assigned by Cloudflare. In addition, IANA has +// not assigned an extension ID for this extension; we picked up one that's not +// yet taken. +// +// TODO(cjpatton) Only ECDSA is supported with delegated credentials for now; +// we'd like to suppoort for EcDSA signatures once these have better support +// upstream. import ( "bytes" "crypto" "crypto/ecdsa" "crypto/elliptic" - "crypto/rand" "crypto/x509" - "crypto/x509/pkix" "encoding/asn1" "encoding/binary" "errors" @@ -49,22 +50,6 @@ var errNoDelegationUsage = errors.New("certificate not authorized for delegation // NOTE(cjpatton) This OID is a child of Cloudflare's IANA-assigned OID. var delegationUsageId = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 44363, 44} -// CreateDelegationUsagePKIXExtension returns a pkix.Extension that every delegation -// certificate must have. -// -// NOTE(cjpatton) Brendan McMillion suggests adding the delegationUsage -// extension as a flag `PermitsDelegationUsage` for the `x509.Certificate` -// structure. But we can't make this change unless tris includes crypto/x509, -// too. Once we upstream this code, we'll want to do modify x509.Certficate and -// do away with this function. -func CreateDelegationUsagePKIXExtension() *pkix.Extension { - return &pkix.Extension{ - Id: delegationUsageId, - Critical: false, - Value: nil, - } -} - // canDelegate returns true if a certificate can be used for delegated // credentials. func canDelegate(cert *x509.Certificate) bool { @@ -83,11 +68,41 @@ func canDelegate(cert *x509.Certificate) bool { return false } -// This structure stores the public components of a credential. +// credential stores the public components of a credential. type credential struct { + // The amount of time for which the credential is valid. Specifically, 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 credential public key. publicKey crypto.PublicKey - scheme SignatureScheme + + // The signature scheme associated with the credential public key. + // + // NOTE(cjpatton) This is used for bookkeeping and is not actually part of + // the credential structure specified in the standard. + scheme SignatureScheme +} + +// 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 ValidTime seconds. This function simply checks that the current time +// (`now`) is before the end of the valdity interval. +func (cred *credential) isExpired(start, now time.Time) bool { + end := start.Add(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 ValidTime, minus the current time (`now`). +func (cred *credential) invalidTTL(start, now time.Time) bool { + return cred.validTime > (now.Sub(start) + dcMaxTTL).Round(time.Second) } // marshalSubjectPublicKeyInfo returns a DER encoded SubjectPublicKeyInfo structure @@ -108,7 +123,8 @@ func (cred *credential) marshalSubjectPublicKeyInfo() ([]byte, error) { } } -// marshal encodes a credential as per the spec. +// marshal encodes a credential in the wire format specified in +// https://tools.ietf.org/html/draft-ietf-tls-subcerts-01. func (cred *credential) marshal() ([]byte, error) { // Write the valid_time field. serialized := make([]byte, 6) @@ -198,173 +214,62 @@ func getCredentialLen(serialized []byte) (int, error) { return 6 + serializedPublicKeyLen, nil } -// DelegatedCredential stores a credential and its delegation. -type DelegatedCredential struct { - // The serialized form of the credential. - Raw []byte +// delegatedCredential stores a credential and its delegation. +type delegatedCredential struct { + raw []byte - // 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 credential public key. - PublicKey crypto.PublicKey - - // The signature scheme associated with the credential public key. - publicKeyScheme SignatureScheme + // The credential, which contains a public and its validity time. + cred *credential // The signature scheme used to sign the credential. - Scheme SignatureScheme + scheme SignatureScheme // The credential's delegation. - Signature []byte + signature []byte } -// NewDelegatedCredential creates a new delegated credential using `cert` for -// delegation. It generates a public/private key pair for the provided signature -// algorithm (`scheme`), validity interval (defined by `cert.Leaf.notBefore` and -// `validTime`), and TLS version (`vers`), and signs it using `cert.PrivateKey`. -func NewDelegatedCredential(cert *Certificate, scheme SignatureScheme, validTime time.Duration, vers uint16) (*DelegatedCredential, crypto.PrivateKey, error) { - // The granularity of DC validity is seconds. - validTime = validTime.Round(time.Second) - - // Parse the leaf certificate if needed. +// ensureCertificateHasLeaf parses the leaf certificate if needed. +func ensureCertificateHasLeaf(cert *Certificate) error { var err error if cert.Leaf == nil { if len(cert.Certificate[0]) == 0 { - return nil, nil, errors.New("missing leaf certificate") + return errors.New("missing leaf certificate") } cert.Leaf, err = x509.ParseCertificate(cert.Certificate[0]) if err != nil { - return nil, nil, err + return err } } - - // Check that the leaf certificate can be used for delegation. - if !canDelegate(cert.Leaf) { - return nil, nil, errNoDelegationUsage - } - - // Extract the delegator signature scheme from the certificate. - var delegatorScheme SignatureScheme - switch sk := cert.PrivateKey.(type) { - case *ecdsa.PrivateKey: - // Set scheme. - pk := sk.Public().(*ecdsa.PublicKey) - curveName := pk.Curve.Params().Name - certAlg := cert.Leaf.SignatureAlgorithm - if certAlg == x509.ECDSAWithSHA256 && curveName == "P-256" { - delegatorScheme = ECDSAWithP256AndSHA256 - } else if certAlg == x509.ECDSAWithSHA384 && curveName == "P-384" { - delegatorScheme = ECDSAWithP384AndSHA384 - } else if certAlg == x509.ECDSAWithSHA512 && curveName == "P-521" { - delegatorScheme = ECDSAWithP521AndSHA512 - } else { - return nil, nil, fmt.Errorf( - "using curve %s for %s is not supported", - curveName, cert.Leaf.SignatureAlgorithm) - } - - default: - return nil, nil, fmt.Errorf("unsupported delgation key type: %T", sk) - } - - // Generate a new key pair. - var sk crypto.PrivateKey - var pk crypto.PublicKey - switch scheme { - case ECDSAWithP256AndSHA256, - ECDSAWithP384AndSHA384, - ECDSAWithP521AndSHA512: - sk, err = ecdsa.GenerateKey(getCurve(scheme), rand.Reader) - if err != nil { - return nil, nil, err - } - pk = sk.(*ecdsa.PrivateKey).Public() - - default: - return nil, nil, fmt.Errorf("unsupported signature scheme: 0x%04x", scheme) - } - - // Prepare the credential for digital signing. - hash := getHash(delegatorScheme) - cred := &credential{validTime, pk, scheme} - in, err := prepareDelegation(hash, cred, cert.Leaf.Raw, delegatorScheme, vers) - if err != nil { - return nil, nil, err - } - - // Sign the credential. - var sig []byte - switch sk := cert.PrivateKey.(type) { - case *ecdsa.PrivateKey: - opts := crypto.SignerOpts(hash) - sig, err = sk.Sign(rand.Reader, in, opts) - if err != nil { - return nil, nil, err - } - default: - return nil, nil, fmt.Errorf("unsupported delgation key type: %T", sk) - } - - return &DelegatedCredential{ - ValidTime: validTime, - PublicKey: pk, - publicKeyScheme: scheme, - Scheme: delegatorScheme, - Signature: sig, - }, sk, nil + return nil } -// 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 ValidTime seconds. This function simply checks that the current time -// (`now`) is before the end of the valdity interval. -func (dc *DelegatedCredential) IsExpired(start, now time.Time) bool { - end := start.Add(dc.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 ValidTime, minus the current time (`now`). -func (dc *DelegatedCredential) InvalidTTL(start, now time.Time) bool { - return dc.ValidTime > (now.Sub(start) + dcMaxTTL).Round(time.Second) -} - -// Validate checks that that the signature is valid, that the credential hasn't +// validate checks that that the signature is valid, that the credential 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, vers uint16, now time.Time) (bool, error) { +func (dc *delegatedCredential) validate(cert *x509.Certificate, vers uint16, now time.Time) (bool, error) { // Check that the cert can delegate. if !canDelegate(cert) { return false, errNoDelegationUsage } - if dc.IsExpired(cert.NotBefore, now) { + if dc.cred.isExpired(cert.NotBefore, now) { return false, errors.New("credential has expired") } - if dc.InvalidTTL(cert.NotBefore, now) { + if dc.cred.invalidTTL(cert.NotBefore, now) { return false, errors.New("credential TTL is invalid") } // Prepare the credential for verification. - hash := getHash(dc.Scheme) - cred := &credential{dc.ValidTime, dc.PublicKey, dc.publicKeyScheme} - in, err := prepareDelegation(hash, cred, cert.Raw, dc.Scheme, vers) + hash := getHash(dc.scheme) + in, err := prepareDelegation(hash, dc.cred, cert.Raw, dc.scheme, vers) if err != nil { return false, err } // TODO(any) This code overlaps signficantly with verifyHandshakeSignature() // in ../auth.go. This should be refactored. - switch dc.Scheme { + switch dc.scheme { case ECDSAWithP256AndSHA256, ECDSAWithP384AndSHA384, ECDSAWithP521AndSHA512: @@ -373,47 +278,19 @@ func (dc *DelegatedCredential) Validate(cert *x509.Certificate, vers uint16, now return false, errors.New("expected ECDSA public key") } sig := new(ecdsaSignature) - if _, err = asn1.Unmarshal(dc.Signature, sig); err != nil { + if _, err = asn1.Unmarshal(dc.signature, sig); err != nil { return false, err } return ecdsa.Verify(pk, in, sig.R, sig.S), nil default: return false, fmt.Errorf( - "unsupported signature scheme: 0x%04x", dc.Scheme) + "unsupported signature scheme: 0x%04x", dc.scheme) } } -// Marshal encodes a DelegatedCredential structure per the spec. It also sets -// dc.Raw to the output as a side effect. -func (dc *DelegatedCredential) Marshal() ([]byte, error) { - // The credential. - cred := &credential{dc.ValidTime, dc.PublicKey, dc.publicKeyScheme} - serialized, err := cred.marshal() - if err != nil { - return nil, err - } - - // The scheme. - serializedScheme := make([]byte, 2) - binary.BigEndian.PutUint16(serializedScheme, uint16(dc.Scheme)) - serialized = append(serialized, serializedScheme...) - - // The signature. - if len(dc.Signature) > dcMaxSignatureLen { - return nil, errors.New("signature is too long") - } - serializedSignature := make([]byte, 2) - binary.BigEndian.PutUint16(serializedSignature, uint16(len(dc.Signature))) - serializedSignature = append(serializedSignature, dc.Signature...) - serialized = append(serialized, serializedSignature...) - - dc.Raw = serialized - return serialized, nil -} - -// UnmarshalDelegatedCredential decodes a DelegatedCredential structure. -func UnmarshalDelegatedCredential(serialized []byte) (*DelegatedCredential, error) { +// unmarshalDelegatedCredential decodes a DelegatedCredential structure. +func unmarshalDelegatedCredential(serialized []byte) (*delegatedCredential, error) { // Get the length of the serialized credential that begins at the start of // the input slice. serializedCredentialLen, err := getCredentialLen(serialized) @@ -445,12 +322,11 @@ func UnmarshalDelegatedCredential(serialized []byte) (*DelegatedCredential, erro } sig := serialized[:serializedSignatureLen] - return &DelegatedCredential{ - ValidTime: cred.validTime, - PublicKey: cred.publicKey, - publicKeyScheme: cred.scheme, - Scheme: scheme, - Signature: sig, + return &delegatedCredential{ + raw: serialized, + cred: cred, + scheme: scheme, + signature: sig, }, nil } diff --git a/subcerts_test.go b/subcerts_test.go index bfd6bbc..d646a91 100644 --- a/subcerts_test.go +++ b/subcerts_test.go @@ -14,14 +14,10 @@ import ( "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 -dc -// -// To get a certificate without the DelegationUsage extension, remove the `-dc` -// parameter. -var delegatorCertPEM = `-----BEGIN CERTIFICATE----- +// A PEM-encoded "delegation certificate", an X.509 certificate with the +// DelegationUsage extension. The extension is defined in +// specified in https://tools.ietf.org/html/draft-ietf-tls-subcerts-01. +var dcDelegationCertPEM = `-----BEGIN CERTIFICATE----- MIIBdzCCAR2gAwIBAgIQLVIvEpo0/0TzRja4ImvB1TAKBggqhkjOPQQDAjASMRAw DgYDVQQKEwdBY21lIENvMB4XDTE4MDcwMzE2NTE1M1oXDTE5MDcwMzE2NTE1M1ow EjEQMA4GA1UEChMHQWNtZSBDbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABOhB @@ -33,14 +29,17 @@ OetDIvU4mdyoAiAGN97y3GJccYn9ZOJS4UOqhr9oO8PuZMLgdq4OrMRiiA== -----END CERTIFICATE----- ` -var delegatorKeyPEM = `-----BEGIN EC PRIVATE KEY----- +// The PEM-encoded "delegation key", the secret key associated with the +// delegation certificate. +var dcDelegationKeyPEM = `-----BEGIN EC PRIVATE KEY----- MHcCAQEEIJDVlo+sJolMcNjMkfCGDUjMJcE4UgclcXGCrOtbJAi2oAoGCCqGSM49 AwEHoUQDQgAE6EFTpp1oCCWItoVzU8Cj0cE7haKyHUbh1/cgrkRcvL6ihh+aR/NP UXGps0tm581jO97bl+alqX/VUkCOlXIqrQ== -----END EC PRIVATE KEY----- ` -var nonDelegatorCertPEM = `-----BEGIN CERTIFICATE----- +// A certificate without the DelegationUsage extension. +var dcCertPEM = `-----BEGIN CERTIFICATE----- MIIBaTCCAQ6gAwIBAgIQSUo+9uaip3qCW+1EPeHZgDAKBggqhkjOPQQDAjASMRAw DgYDVQQKEwdBY21lIENvMB4XDTE4MDYxMjIzNDAyNloXDTE5MDYxMjIzNDAyNlow EjEQMA4GA1UEChMHQWNtZSBDbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABLf7 @@ -52,13 +51,25 @@ AwIDSQAwRgIhANXG0zmrVtQBK0TNZZoEGMOtSwxmiZzXNe+IjdpxO3TiAiEA5VYx -----END CERTIFICATE----- ` -var nonDelegatorKeyPEM = `-----BEGIN EC PRIVATE KEY----- +// The secret key associatted with dcCertPEM. +var dcKeyPEM = `-----BEGIN EC PRIVATE KEY----- MHcCAQEEIMw9DiOfGI1E/XZrrW2huZSjYi0EKwvVjAe+dYtyFsSloAoGCCqGSM49 AwEHoUQDQgAEt/t+LOc9V1zdXmYzfKazBTb8iglqr914Dp2B2PnSjN1jJGIA+PHN zP3NGxnDVqlMX+HI1+IuFXgQTVWvBdxPkw== -----END EC PRIVATE KEY----- ` +// dcTestDC stores delegated credentials and their secret keys. +type dcTestDC struct { + Name string + Version int + Scheme int + DC []byte + PrivateKey []byte +} + +// Test data used for testing the TLS handshake with the delegated creedential +// extension. The PEM block encodes a DER encoded slice of dcTestDC's. var dcTestDCsPEM = `-----BEGIN DC TEST DATA----- MIIGQzCCAToTBXRsczEyAgIDAwICBAMEga0ACUp3AFswWTATBgcqhkjOPQIBBggq hkjOPQMBBwNCAAQ9z9RDrMvyRzPOkw9SK2S/O5DiwfRNjAwYcq7e/sKdN0ZcSP1K @@ -97,14 +108,6 @@ HJbn52hmB027JEIMPWsxKxPkr7udk7Q= -----END DC TEST DATA----- ` -type dcTestDC struct { - Name string - Version int - Scheme int - DC []byte - PrivateKey []byte -} - var dcTestDCs []dcTestDC var dcTestConfig *Config var dcTestDelegationCert Certificate @@ -112,8 +115,7 @@ var dcTestCert Certificate var dcTestNow time.Time func init() { - - // Parse the PEM-encoded DER block containing the test DCs. + // Parse the PEM block containing the test DCs. block, _ := pem.Decode([]byte(dcTestDCsPEM)) if block == nil { panic("failed to decode DC tests PEM block") @@ -142,7 +144,7 @@ func init() { } // The delegation certificate. - dcTestDelegationCert, err = X509KeyPair([]byte(delegatorCertPEM), []byte(delegatorKeyPEM)) + dcTestDelegationCert, err = X509KeyPair([]byte(dcDelegationCertPEM), []byte(dcDelegationKeyPEM)) if err != nil { panic(err) } @@ -152,7 +154,7 @@ func init() { } // A certificate without the the DelegationUsage extension for X.509. - dcTestCert, err = X509KeyPair([]byte(nonDelegatorCertPEM), []byte(nonDelegatorKeyPEM)) + dcTestCert, err = X509KeyPair([]byte(dcCertPEM), []byte(dcKeyPEM)) if err != nil { panic(err) } @@ -179,9 +181,9 @@ func init() { dcTestConfig.RootCAs.AddCert(root) } -// Tests the handshake and one round of application data. Returns true if the -// connection used a DC. -func testConnWithDC(t *testing.T, clientMsg, serverMsg string, clientConfig, serverConfig *Config) (bool, error) { +// Executes the handshake with the given configuration and returns true if the +// delegated credential extension was successfully negotiated. +func testConnWithDC(t *testing.T, clientConfig, serverConfig *Config) (bool, error) { ln := newLocalListener(t) defer ln.Close() @@ -216,28 +218,9 @@ func testConnWithDC(t *testing.T, clientMsg, serverMsg string, clientConfig, ser return false, serr } - bufLen := len(clientMsg) - if len(serverMsg) > len(clientMsg) { - bufLen = len(serverMsg) - } - buf := make([]byte, bufLen) - - // Client sends a message to the server, which is read by the server. - cli.Write([]byte(clientMsg)) - n, err := srv.Read(buf) - if 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 reads a message from the client, which is read by the client. - srv.Write([]byte(serverMsg)) - n, err = cli.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) - } - // Return true if the client's conn.dc structure was instantiated. - return (cli.verifiedDc != nil), nil + st := cli.ConnectionState() + return (st.DelegatedCredential != nil), nil } // Checks that the client suppports a version >= 1.2 and accepts delegated @@ -256,7 +239,7 @@ func testServerGetCertificate(ch *ClientHelloInfo) (*Certificate, error) { } // Various test cases for handshakes involving DCs. -var dcTests = []struct { +var dcTesters = []struct { clientDC bool serverDC bool clientSkipVerify bool @@ -284,53 +267,46 @@ var dcTests = []struct { // Tests the handshake with the delegated credential extension for each test // case in dcTests. func TestDCHandshake(t *testing.T) { - serverMsg := "hello" - clientMsg := "world" - clientConfig := dcTestConfig.Clone() serverConfig := dcTestConfig.Clone() serverConfig.GetCertificate = testServerGetCertificate - for i, test := range dcTests { - clientConfig.MaxVersion = test.clientMaxVers - serverConfig.MaxVersion = test.serverMaxVers - clientConfig.InsecureSkipVerify = test.clientSkipVerify - clientConfig.AcceptDelegatedCredential = test.clientDC + for i, tester := range dcTesters { + clientConfig.MaxVersion = tester.clientMaxVers + serverConfig.MaxVersion = tester.serverMaxVers + clientConfig.InsecureSkipVerify = tester.clientSkipVerify + clientConfig.AcceptDelegatedCredential = tester.clientDC clientConfig.Time = func() time.Time { - return dcTestNow.Add(time.Duration(test.nowOffset)) + return dcTestNow.Add(time.Duration(tester.nowOffset)) } - if test.serverDC { + if tester.serverDC { serverConfig.GetDelegatedCredential = func( - ch *ClientHelloInfo, vers uint16) (*DelegatedCredential, crypto.PrivateKey, error) { - for _, t := range dcTestDCs { - if t.Name == test.dcTestName { - dc, err := UnmarshalDelegatedCredential(t.DC) + ch *ClientHelloInfo, vers uint16) ([]byte, crypto.PrivateKey, error) { + for _, test := range dcTestDCs { + if test.Name == tester.dcTestName { + sk, err := x509.ParseECPrivateKey(test.PrivateKey) if err != nil { return nil, nil, err } - sk, err := x509.ParseECPrivateKey(t.PrivateKey) - if err != nil { - return nil, nil, err - } - return dc, sk, nil + return test.DC, sk, nil } } - return nil, nil, fmt.Errorf("Test DC with name '%s' not found", test.dcTestName) + return nil, nil, fmt.Errorf("Test DC with name '%s' not found", tester.dcTestName) } } else { serverConfig.GetDelegatedCredential = nil } - usedDC, err := testConnWithDC(t, clientMsg, serverMsg, clientConfig, serverConfig) - if err != nil && test.expectSuccess { - t.Errorf("test #%d (%s) fails: %s", i+1, test.name, err) - } else if err == nil && !test.expectSuccess { - t.Errorf("test #%d (%s) succeeds; expected failure", i+1, test.name) + usedDC, err := testConnWithDC(t, clientConfig, serverConfig) + if err != nil && tester.expectSuccess { + t.Errorf("test #%d (%s) fails: %s", i+1, tester.name, err) + } else if err == nil && !tester.expectSuccess { + t.Errorf("test #%d (%s) succeeds; expected failure", i+1, tester.name) } - if usedDC != test.expectDC { - t.Errorf("test #%d (%s) usedDC = %v; expected %v", i+1, test.name, usedDC, test.expectDC) + if usedDC != tester.expectDC { + t.Errorf("test #%d (%s) usedDC = %v; expected %v", i+1, tester.name, usedDC, tester.expectDC) } } }