From 963d5877be2a28045989911c3b92a5a382ec0353 Mon Sep 17 00:00:00 2001 From: Christopher Patton Date: Thu, 21 Jun 2018 09:34:19 -0700 Subject: [PATCH 1/2] Refactor the keyAgreement interface It's sufficient to pass in the *tls.Certificate (resp. *x509.Certificate) to the server functions (resp. client funcctions), but not necessary; the existing keyAgreement implementations only makes use of the private key (resp. public key). Moreover, this change is necessary for implementing the delegated credentials extension, which replaces the private key (resp. public key) used in the handshake. --- cipher_suites.go | 10 +++++----- handshake_client.go | 4 ++-- handshake_server.go | 4 ++-- key_agreement.go | 27 +++++++++++++-------------- 4 files changed, 22 insertions(+), 23 deletions(-) diff --git a/cipher_suites.go b/cipher_suites.go index 26dc92d..cfd73a9 100644 --- a/cipher_suites.go +++ b/cipher_suites.go @@ -5,6 +5,7 @@ package tls import ( + "crypto" "crypto/aes" "crypto/cipher" "crypto/des" @@ -12,7 +13,6 @@ import ( "crypto/rc4" "crypto/sha1" "crypto/sha256" - "crypto/x509" "hash" "golang_org/x/crypto/chacha20poly1305" @@ -26,15 +26,15 @@ type keyAgreement interface { // In the case that the key agreement protocol doesn't use a // ServerKeyExchange message, generateServerKeyExchange can return nil, // nil. - generateServerKeyExchange(*Config, *Certificate, *clientHelloMsg, *serverHelloMsg) (*serverKeyExchangeMsg, error) - processClientKeyExchange(*Config, *Certificate, *clientKeyExchangeMsg, uint16) ([]byte, error) + generateServerKeyExchange(*Config, crypto.PrivateKey, *clientHelloMsg, *serverHelloMsg) (*serverKeyExchangeMsg, error) + processClientKeyExchange(*Config, crypto.PrivateKey, *clientKeyExchangeMsg, uint16) ([]byte, error) // On the client side, the next two methods are called in order. // This method may not be called if the server doesn't send a // ServerKeyExchange message. - processServerKeyExchange(*Config, *clientHelloMsg, *serverHelloMsg, *x509.Certificate, *serverKeyExchangeMsg) error - generateClientKeyExchange(*Config, *clientHelloMsg, *x509.Certificate) ([]byte, *clientKeyExchangeMsg, error) + processServerKeyExchange(*Config, *clientHelloMsg, *serverHelloMsg, crypto.PublicKey, *serverKeyExchangeMsg) error + generateClientKeyExchange(*Config, *clientHelloMsg, crypto.PublicKey) ([]byte, *clientKeyExchangeMsg, error) } const ( diff --git a/handshake_client.go b/handshake_client.go index ca1921f..8311dbc 100644 --- a/handshake_client.go +++ b/handshake_client.go @@ -480,7 +480,7 @@ func (hs *clientHandshakeState) doFullHandshake() error { skx, ok := msg.(*serverKeyExchangeMsg) if ok { hs.finishedHash.Write(skx.marshal()) - err = keyAgreement.processServerKeyExchange(c.config, hs.hello, hs.serverHello, c.peerCertificates[0], skx) + err = keyAgreement.processServerKeyExchange(c.config, hs.hello, hs.serverHello, c.peerCertificates[0].PublicKey, skx) if err != nil { c.sendAlert(alertUnexpectedMessage) return err @@ -529,7 +529,7 @@ func (hs *clientHandshakeState) doFullHandshake() error { } } - preMasterSecret, ckx, err := keyAgreement.generateClientKeyExchange(c.config, hs.hello, c.peerCertificates[0]) + preMasterSecret, ckx, err := keyAgreement.generateClientKeyExchange(c.config, hs.hello, c.peerCertificates[0].PublicKey) if err != nil { c.sendAlert(alertInternalError) return err diff --git a/handshake_server.go b/handshake_server.go index 48d4389..d466fdb 100644 --- a/handshake_server.go +++ b/handshake_server.go @@ -479,7 +479,7 @@ func (hs *serverHandshakeState) doFullHandshake() error { } keyAgreement := hs.suite.ka(c.vers) - skx, err := keyAgreement.generateServerKeyExchange(c.config, hs.cert, hs.clientHello, hs.hello) + skx, err := keyAgreement.generateServerKeyExchange(c.config, hs.cert.PrivateKey, hs.clientHello, hs.hello) if err != nil { c.sendAlert(alertHandshakeFailure) return err @@ -572,7 +572,7 @@ func (hs *serverHandshakeState) doFullHandshake() error { } hs.finishedHash.Write(ckx.marshal()) - preMasterSecret, err := keyAgreement.processClientKeyExchange(c.config, hs.cert, ckx, c.vers) + preMasterSecret, err := keyAgreement.processClientKeyExchange(c.config, hs.cert.PrivateKey, ckx, c.vers) if err != nil { if err == errClientKeyExchange { c.sendAlert(alertDecodeError) diff --git a/key_agreement.go b/key_agreement.go index 1c5660d..d0078ed 100644 --- a/key_agreement.go +++ b/key_agreement.go @@ -10,7 +10,6 @@ import ( "crypto/md5" "crypto/rsa" "crypto/sha1" - "crypto/x509" "errors" "io" "math/big" @@ -25,11 +24,11 @@ var errServerKeyExchange = errors.New("tls: invalid ServerKeyExchange message") // encrypts the pre-master secret to the server's public key. type rsaKeyAgreement struct{} -func (ka rsaKeyAgreement) generateServerKeyExchange(config *Config, cert *Certificate, clientHello *clientHelloMsg, hello *serverHelloMsg) (*serverKeyExchangeMsg, error) { +func (ka rsaKeyAgreement) generateServerKeyExchange(config *Config, sk crypto.PrivateKey, clientHello *clientHelloMsg, hello *serverHelloMsg) (*serverKeyExchangeMsg, error) { return nil, nil } -func (ka rsaKeyAgreement) processClientKeyExchange(config *Config, cert *Certificate, ckx *clientKeyExchangeMsg, version uint16) ([]byte, error) { +func (ka rsaKeyAgreement) processClientKeyExchange(config *Config, sk crypto.PrivateKey, ckx *clientKeyExchangeMsg, version uint16) ([]byte, error) { if len(ckx.ciphertext) < 2 { return nil, errClientKeyExchange } @@ -42,7 +41,7 @@ func (ka rsaKeyAgreement) processClientKeyExchange(config *Config, cert *Certifi } ciphertext = ckx.ciphertext[2:] } - priv, ok := cert.PrivateKey.(crypto.Decrypter) + priv, ok := sk.(crypto.Decrypter) if !ok { return nil, errors.New("tls: certificate private key does not implement crypto.Decrypter") } @@ -60,11 +59,11 @@ func (ka rsaKeyAgreement) processClientKeyExchange(config *Config, cert *Certifi return preMasterSecret, nil } -func (ka rsaKeyAgreement) processServerKeyExchange(config *Config, clientHello *clientHelloMsg, serverHello *serverHelloMsg, cert *x509.Certificate, skx *serverKeyExchangeMsg) error { +func (ka rsaKeyAgreement) processServerKeyExchange(config *Config, clientHello *clientHelloMsg, serverHello *serverHelloMsg, pk crypto.PublicKey, skx *serverKeyExchangeMsg) error { return errors.New("tls: unexpected ServerKeyExchange") } -func (ka rsaKeyAgreement) generateClientKeyExchange(config *Config, clientHello *clientHelloMsg, cert *x509.Certificate) ([]byte, *clientKeyExchangeMsg, error) { +func (ka rsaKeyAgreement) generateClientKeyExchange(config *Config, clientHello *clientHelloMsg, pk crypto.PublicKey) ([]byte, *clientKeyExchangeMsg, error) { preMasterSecret := make([]byte, 48) preMasterSecret[0] = byte(clientHello.vers >> 8) preMasterSecret[1] = byte(clientHello.vers) @@ -73,7 +72,7 @@ func (ka rsaKeyAgreement) generateClientKeyExchange(config *Config, clientHello return nil, nil, err } - encrypted, err := rsa.EncryptPKCS1v15(config.rand(), cert.PublicKey.(*rsa.PublicKey), preMasterSecret) + encrypted, err := rsa.EncryptPKCS1v15(config.rand(), pk.(*rsa.PublicKey), preMasterSecret) if err != nil { return nil, nil, err } @@ -156,7 +155,7 @@ type ecdheKeyAgreement struct { x, y *big.Int } -func (ka *ecdheKeyAgreement) generateServerKeyExchange(config *Config, cert *Certificate, clientHello *clientHelloMsg, hello *serverHelloMsg) (*serverKeyExchangeMsg, error) { +func (ka *ecdheKeyAgreement) generateServerKeyExchange(config *Config, sk crypto.PrivateKey, clientHello *clientHelloMsg, hello *serverHelloMsg) (*serverKeyExchangeMsg, error) { preferredCurves := config.curvePreferences() NextCandidate: @@ -207,7 +206,7 @@ NextCandidate: serverECDHParams[3] = byte(len(ecdhePublic)) copy(serverECDHParams[4:], ecdhePublic) - priv, ok := cert.PrivateKey.(crypto.Signer) + priv, ok := sk.(crypto.Signer) if !ok { return nil, errors.New("tls: certificate private key does not implement crypto.Signer") } @@ -255,7 +254,7 @@ NextCandidate: return skx, nil } -func (ka *ecdheKeyAgreement) processClientKeyExchange(config *Config, cert *Certificate, ckx *clientKeyExchangeMsg, version uint16) ([]byte, error) { +func (ka *ecdheKeyAgreement) processClientKeyExchange(config *Config, sk crypto.PrivateKey, ckx *clientKeyExchangeMsg, version uint16) ([]byte, error) { if len(ckx.ciphertext) == 0 || int(ckx.ciphertext[0]) != len(ckx.ciphertext)-1 { return nil, errClientKeyExchange } @@ -291,7 +290,7 @@ func (ka *ecdheKeyAgreement) processClientKeyExchange(config *Config, cert *Cert return preMasterSecret, nil } -func (ka *ecdheKeyAgreement) processServerKeyExchange(config *Config, clientHello *clientHelloMsg, serverHello *serverHelloMsg, cert *x509.Certificate, skx *serverKeyExchangeMsg) error { +func (ka *ecdheKeyAgreement) processServerKeyExchange(config *Config, clientHello *clientHelloMsg, serverHello *serverHelloMsg, pk crypto.PublicKey, skx *serverKeyExchangeMsg) error { if len(skx.key) < 4 { return errServerKeyExchange } @@ -337,7 +336,7 @@ func (ka *ecdheKeyAgreement) processServerKeyExchange(config *Config, clientHell return errServerKeyExchange } } - _, sigType, hashFunc, err := pickSignatureAlgorithm(cert.PublicKey, []SignatureScheme{signatureAlgorithm}, clientHello.supportedSignatureAlgorithms, ka.version) + _, sigType, hashFunc, err := pickSignatureAlgorithm(pk, []SignatureScheme{signatureAlgorithm}, clientHello.supportedSignatureAlgorithms, ka.version) if err != nil { return err } @@ -355,10 +354,10 @@ func (ka *ecdheKeyAgreement) processServerKeyExchange(config *Config, clientHell if err != nil { return err } - return verifyHandshakeSignature(sigType, cert.PublicKey, hashFunc, digest, sig) + return verifyHandshakeSignature(sigType, pk, hashFunc, digest, sig) } -func (ka *ecdheKeyAgreement) generateClientKeyExchange(config *Config, clientHello *clientHelloMsg, cert *x509.Certificate) ([]byte, *clientKeyExchangeMsg, error) { +func (ka *ecdheKeyAgreement) generateClientKeyExchange(config *Config, clientHello *clientHelloMsg, pk crypto.PublicKey) ([]byte, *clientKeyExchangeMsg, error) { if ka.curveid == 0 { return nil, nil, errors.New("tls: missing ServerKeyExchange message") } From 84fe9084cd2a405ff0a04820d903f4482ca802c3 Mon Sep 17 00:00:00 2001 From: Christopher Patton Date: Tue, 3 Jul 2018 09:57:54 -0700 Subject: [PATCH 2/2] Implement the delegated_credential extension for TLS This complies with the standard, currently an Internet draft: https://tlswg.github.io/tls-subcerts/draft-ietf-tls-subcerts.html It also adds a minimal interface for generating new delegated credentials. --- 13.go | 40 +++- common.go | 23 ++ conn.go | 5 +- generate_cert.go | 7 + handshake_client.go | 66 +++++- handshake_messages.go | 68 +++++- handshake_server.go | 73 ++++-- subcerts.go | 521 ++++++++++++++++++++++++++++++++++++++++++ subcerts_test.go | 500 ++++++++++++++++++++++++++++++++++++++++ tls_test.go | 4 +- 10 files changed, 1275 insertions(+), 32 deletions(-) create mode 100644 subcerts.go create mode 100644 subcerts_test.go diff --git a/13.go b/13.go index be4abcc..cc52510 100644 --- a/13.go +++ b/13.go @@ -403,6 +403,16 @@ func (hs *serverHandshakeState) sendCertificate13() error { if len(certEntries) > 0 && hs.clientHello.scts { certEntries[0].sctList = hs.cert.SignedCertificateTimestamps } + + // If hs.delegatedCredential is set (see hs.readClientHello()) then the + // 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). + if len(certEntries) > 0 && hs.clientHello.delegatedCredential && hs.delegatedCredential != nil { + certEntries[0].delegatedCredential = hs.delegatedCredential + } + certMsg := &certificateMsg13{certificates: certEntries} hs.keySchedule.write(certMsg.marshal()) @@ -423,7 +433,7 @@ func (hs *serverHandshakeState) sendCertificate13() error { } toSign := prepareDigitallySigned(sigHash, "TLS 1.3, server CertificateVerify", hs.keySchedule.transcriptHash.Sum(nil)) - signature, err := hs.cert.PrivateKey.(crypto.Signer).Sign(c.config.rand(), toSign[:], opts) + signature, err := hs.privateKey.(crypto.Signer).Sign(c.config.rand(), toSign[:], opts) if err != nil { c.sendAlert(alertInternalError) return err @@ -468,9 +478,9 @@ func (c *Conn) handleEndOfEarlyData() error { // See https://tools.ietf.org/html/draft-ietf-tls-tls13-18#section-4.4.1.2 func (hs *serverHandshakeState) selectTLS13SignatureScheme() (sigScheme SignatureScheme, err error) { var supportedSchemes []SignatureScheme - signer, ok := hs.cert.PrivateKey.(crypto.Signer) + signer, ok := hs.privateKey.(crypto.Signer) if !ok { - return 0, errors.New("tls: certificate private key does not implement crypto.Signer") + return 0, errors.New("tls: private key does not implement crypto.Signer") } pk := signer.Public() if _, ok := pk.(*rsa.PublicKey); ok { @@ -1034,12 +1044,34 @@ func (hs *clientHandshakeState) doTLS13Handshake() error { return unexpectedMessageError(certMsg, msg) } hs.keySchedule.write(certMsg.marshal()) + // Validate certificates. certs := getCertsFromEntries(certMsg.certificates) if err := hs.processCertsFromServer(certs); err != nil { return err } + // Validate the DC if present. The DC is only processed if the extension was + // indicated by the ClientHello; otherwise this call will result in an + // "illegal_parameter" alert. The call also asserts that the DC extension + // did not appear in the ServerHello. + if len(certMsg.certificates) > 0 { + if err := hs.processDelegatedCredentialFromServer( + certMsg.certificates[0].delegatedCredential); err != nil { + return err + } + } + + // Set the public key used to verify the handshake. + pk := hs.c.peerCertificates[0].PublicKey + + // If the delegated credential extension has successfully been negotiated, + // then the CertificateVerify signature will have been produced with the + // DelegatedCredential's private key. + if hs.c.verifiedDc != nil { + pk = hs.c.verifiedDc.PublicKey + } + // Receive CertificateVerify message. msg, err = c.readHandshake() if err != nil { @@ -1052,7 +1084,7 @@ func (hs *clientHandshakeState) doTLS13Handshake() error { } err, alertCode := verifyPeerHandshakeSignature( certVerifyMsg, - hs.c.peerCertificates[0].PublicKey, + pk, hs.hello.supportedSignatureAlgorithms, hs.keySchedule.transcriptHash.Sum(nil), "TLS 1.3, server CertificateVerify") diff --git a/common.go b/common.go index 5fbe17a..9eea98e 100644 --- a/common.go +++ b/common.go @@ -97,6 +97,7 @@ const ( extensionKeyShare uint16 = 51 extensionNextProtoNeg uint16 = 13172 // not IANA assigned extensionRenegotiationInfo uint16 = 0xff01 + extensionDelegatedCredential uint16 = 0xff02 // TODO(any) Get IANA assignment ) // TLS signaling cipher suite values @@ -356,6 +357,10 @@ type ClientHelloInfo struct { // immediately available for Read. Offered0RTTData bool + // AcceptsDelegatedCredential is true if the client indicated willingness + // to negotiate the delegated credential extension. + AcceptsDelegatedCredential bool + // The Fingerprint is an sequence of bytes unique to this Client Hello. // It can be used to prevent or mitigate 0-RTT data replays as it's // guaranteed that a replayed connection will have the same Fingerprint. @@ -609,6 +614,22 @@ type Config struct { // session tickets, instead of SessionTicketKey. SessionTicketSealer SessionTicketSealer + // AcceptDelegatedCredential is true if the client is willing to negotiate + // the delegated credential extension. + // + // This value has no meaning for the server. + // + // See https://tools.ietf.org/html/draft-ietf-tls-subcerts. + 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. + // + // This value has no meaning for the client. + GetDelegatedCredential func(*ClientHelloInfo, uint16) (*DelegatedCredential, crypto.PrivateKey, error) + serverInitOnce sync.Once // guards calling (*Config).serverInit // mutex protects sessionTicketKeys. @@ -685,6 +706,8 @@ func (c *Config) Clone() *Config { Accept0RTTData: c.Accept0RTTData, Max0RTTDataSize: c.Max0RTTDataSize, SessionTicketSealer: c.SessionTicketSealer, + AcceptDelegatedCredential: c.AcceptDelegatedCredential, + GetDelegatedCredential: c.GetDelegatedCredential, sessionTicketKeys: sessionTicketKeys, } } diff --git a/conn.go b/conn.go index b2b913b..402acdb 100644 --- a/conn.go +++ b/conn.go @@ -51,11 +51,14 @@ type Conn struct { didResume bool // whether this connection was a session resumption cipherSuite uint16 ocspResponse []byte // stapled OCSP response - scts [][]byte // signed certificate timestamps from server + scts [][]byte // Signed certificate timestamps from server peerCertificates []*x509.Certificate // verifiedChains contains the certificate chains that we built, as // opposed to the ones presented by the server. verifiedChains [][]*x509.Certificate + // verifiedDc is set by a client who negotiates the use of a valid delegated + // credential. + verifiedDc *DelegatedCredential // serverName contains the server name indicated by the client, if any. serverName string // secureRenegotiation is true if the server echoed the secure diff --git a/generate_cert.go b/generate_cert.go index 8ee2b59..235a545 100644 --- a/generate_cert.go +++ b/generate_cert.go @@ -14,6 +14,7 @@ import ( "crypto/elliptic" "crypto/rand" "crypto/rsa" + "crypto/tls" "crypto/x509" "crypto/x509/pkix" "encoding/pem" @@ -34,6 +35,7 @@ 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{} { @@ -137,6 +139,11 @@ 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 8311dbc..1b4950a 100644 --- a/handshake_client.go +++ b/handshake_client.go @@ -65,6 +65,7 @@ func makeClientHello(config *Config) (*clientHelloMsg, error) { supportedPoints: []uint8{pointFormatUncompressed}, nextProtoNeg: len(config.NextProtos) > 0, secureRenegotiationSupported: true, + delegatedCredential: config.AcceptDelegatedCredential, alpnProtocols: config.NextProtos, } possibleCipherSuites := config.cipherSuites() @@ -412,6 +413,50 @@ func (hs *clientHandshakeState) processCertsFromServer(certificates [][]byte) er return nil } +// processDelegatedCredentialFromServer unmarshals the DelegatedCredential +// offered by the server (if present) and validates it using the peer +// certificate. +func (hs *clientHandshakeState) processDelegatedCredentialFromServer(dc []byte) error { + c := hs.c + + var cred *DelegatedCredential + var err error + if dc != nil { + + // Assert that the DC extension was indicated by the client. + if !hs.hello.delegatedCredential { + c.sendAlert(alertUnexpectedMessage) + return errors.New("tls: got delegated credential extension without indication") + } + + // Assert that the DC was sent in the ServerHello in (and only in) + // version 1.2. + if hs.serverHello.delegatedCredential != nil && hs.serverHello.vers != VersionTLS12 { + c.sendAlert(alertIllegalParameter) + return errors.New("tls: ServerHello with delegated credential extension in TLS != 1.2") + } + + cred, err = UnmarshalDelegatedCredential(dc) + 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 { + c.sendAlert(alertIllegalParameter) + return fmt.Errorf("delegated credential: %s", err) + } else if !v { + c.sendAlert(alertIllegalParameter) + return errors.New("delegated credential: signature invalid") + } + } + + c.verifiedDc = cred + return nil +} + func (hs *clientHandshakeState) doFullHandshake() error { c := hs.c @@ -432,6 +477,13 @@ func (hs *clientHandshakeState) doFullHandshake() error { if err := hs.processCertsFromServer(certMsg.certificates); err != nil { return err } + // Validate the DC if present. The DC is only processed if the + // extension was indicated by the ClientHello; otherwise this call will + // result in an "illegal_parameter" alert. It also asserts that the DC + // was sent in the ServerHello if and only if TLS 1.2 is in use. + if err := hs.processDelegatedCredentialFromServer(hs.serverHello.delegatedCredential); err != nil { + return err + } } else { // This is a renegotiation handshake. We require that the // server's identity (i.e. leaf certificate) is unchanged and @@ -477,10 +529,20 @@ func (hs *clientHandshakeState) doFullHandshake() error { keyAgreement := hs.suite.ka(c.vers) + // Set the public key used to verify the handshake. + pk := c.peerCertificates[0].PublicKey + + // If the delegated credential extension has successfully been negotiated, + // then the ServerKeyExchange DelegatedCredential's private key. + if c.verifiedDc != nil { + pk = c.verifiedDc.PublicKey + } + skx, ok := msg.(*serverKeyExchangeMsg) if ok { hs.finishedHash.Write(skx.marshal()) - err = keyAgreement.processServerKeyExchange(c.config, hs.hello, hs.serverHello, c.peerCertificates[0].PublicKey, skx) + + err = keyAgreement.processServerKeyExchange(c.config, hs.hello, hs.serverHello, pk, skx) if err != nil { c.sendAlert(alertUnexpectedMessage) return err @@ -529,7 +591,7 @@ func (hs *clientHandshakeState) doFullHandshake() error { } } - preMasterSecret, ckx, err := keyAgreement.generateClientKeyExchange(c.config, hs.hello, c.peerCertificates[0].PublicKey) + preMasterSecret, ckx, err := keyAgreement.generateClientKeyExchange(c.config, hs.hello, pk) if err != nil { c.sendAlert(alertInternalError) return err diff --git a/handshake_messages.go b/handshake_messages.go index 9da37af..ab3b006 100644 --- a/handshake_messages.go +++ b/handshake_messages.go @@ -49,12 +49,14 @@ type clientHelloMsg struct { psks []psk pskKeyExchangeModes []uint8 earlyData bool + delegatedCredential bool } -// Helpers -// Function used for signature_algorithms and signature_algorithrms_cert extensions only -// (for more details, see TLS 1.3 draft 28, 4.2.3) -// Function advances data slice and returns it, so that it can be used for further processing +// Function used for signature_algorithms and signature_algorithrms_cert +// extensions only (for more details, see TLS 1.3 draft 28, 4.2.3). +// +// It advances data slice and returns it, so that it can be used for further +// processing func marshalExtensionSignatureAlgorithms(extension uint16, data []byte, schemes []SignatureScheme) []byte { algNum := uint16(len(schemes)) if algNum == 0 { @@ -126,7 +128,8 @@ func (m *clientHelloMsg) equal(i interface{}) bool { eqStrings(m.alpnProtocols, m1.alpnProtocols) && eqKeyShares(m.keyShares, m1.keyShares) && eqUint16s(m.supportedVersions, m1.supportedVersions) && - m.earlyData == m1.earlyData + m.earlyData == m1.earlyData && + m.delegatedCredential == m1.delegatedCredential } func (m *clientHelloMsg) marshal() []byte { @@ -202,6 +205,9 @@ func (m *clientHelloMsg) marshal() []byte { if m.earlyData { numExtensions++ } + if m.delegatedCredential { + numExtensions++ + } if numExtensions > 0 { extensionsLength += 4 * numExtensions length += 2 + extensionsLength @@ -419,6 +425,10 @@ func (m *clientHelloMsg) marshal() []byte { z[1] = byte(extensionEarlyData) z = z[4:] } + if m.delegatedCredential { + binary.BigEndian.PutUint16(z, extensionDelegatedCredential) + z = z[4:] + } m.raw = x @@ -483,6 +493,7 @@ func (m *clientHelloMsg) unmarshal(data []byte) alert { m.psks = nil m.pskKeyExchangeModes = nil m.earlyData = false + m.delegatedCredential = false if len(data) == 0 { // ClientHello is optionally followed by extension data @@ -747,6 +758,9 @@ func (m *clientHelloMsg) unmarshal(data []byte) alert { case extensionEarlyData: // 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 + m.delegatedCredential = true } data = data[length:] bindersOffset += length @@ -775,6 +789,10 @@ type serverHelloMsg struct { secureRenegotiationSupported bool alpnProtocol string + // TLS 1.2. In TLS 1.3, the DC extension is included in of the end-entity + // certificate in the Certificate message. + delegatedCredential []byte + // TLS 1.3 keyShare keyShare psk bool @@ -810,6 +828,7 @@ func (m *serverHelloMsg) equal(i interface{}) bool { bytes.Equal(m.secureRenegotiation, m1.secureRenegotiation) && m.alpnProtocol == m1.alpnProtocol && m.keyShare.group == m1.keyShare.group && + bytes.Equal(m.delegatedCredential, m1.delegatedCredential) && bytes.Equal(m.keyShare.data, m1.keyShare.data) && m.psk == m1.psk && m.pskIdentity == m1.pskIdentity @@ -863,6 +882,10 @@ func (m *serverHelloMsg) marshal() []byte { extensionsLength += 2 + sctLen numExtensions++ } + if dcLen := len(m.delegatedCredential); dcLen > 0 && m.vers == VersionTLS12 { + extensionsLength += 4 + dcLen + numExtensions++ + } if m.keyShare.group != 0 { extensionsLength += 4 + len(m.keyShare.data) numExtensions++ @@ -992,7 +1015,13 @@ func (m *serverHelloMsg) marshal() []byte { z = z[len(sct)+2:] } } - + if dcLen := len(m.delegatedCredential); dcLen > 0 && m.vers == VersionTLS12 { + binary.BigEndian.PutUint16(z, extensionDelegatedCredential) + binary.BigEndian.PutUint16(z[2:], uint16(dcLen)) + z = z[4:] + copy(z, m.delegatedCredential) + z = z[dcLen:] + } if m.keyShare.group != 0 { z[0] = uint8(extensionKeyShare >> 8) z[1] = uint8(extensionKeyShare) @@ -1180,6 +1209,11 @@ func (m *serverHelloMsg) unmarshal(data []byte) alert { m.scts = append(m.scts, d[:sctLen]) d = d[sctLen:] } + case extensionDelegatedCredential: + if m.vers != VersionTLS12 { + return alertUnexpectedMessage + } + m.delegatedCredential = data[:length] case extensionKeyShare: d := data[:length] @@ -1422,9 +1456,10 @@ func (m *certificateMsg) unmarshal(data []byte) alert { } type certificateEntry struct { - data []byte - ocspStaple []byte - sctList [][]byte + data []byte + ocspStaple []byte + sctList [][]byte + delegatedCredential []byte } type certificateMsg13 struct { @@ -1446,6 +1481,7 @@ func (m *certificateMsg13) equal(i interface{}) bool { ok := bytes.Equal(m.certificates[i].data, m1.certificates[i].data) ok = ok && bytes.Equal(m.certificates[i].ocspStaple, m1.certificates[i].ocspStaple) ok = ok && eqByteSlices(m.certificates[i].sctList, m1.certificates[i].sctList) + ok = ok && bytes.Equal(m.certificates[i].delegatedCredential, m1.certificates[i].delegatedCredential) if !ok { return false } @@ -1472,6 +1508,9 @@ func (m *certificateMsg13) marshal() (x []byte) { i += 2 + len(sct) } } + if len(cert.delegatedCredential) != 0 { + i += 4 + len(cert.delegatedCredential) + } } length := 3 + 3*len(m.certificates) + i @@ -1546,6 +1585,15 @@ func (m *certificateMsg13) marshal() (x []byte) { sctLenPos[2] = uint8(sctLen >> 8) sctLenPos[3] = uint8(sctLen) } + if len(cert.delegatedCredential) != 0 { + binary.BigEndian.PutUint16(z, extensionDelegatedCredential) + binary.BigEndian.PutUint16(z[2:], uint16(len(cert.delegatedCredential))) + z = z[4:] + copy(z, cert.delegatedCredential) + z = z[len(cert.delegatedCredential):] + extensionLen += 4 + len(cert.delegatedCredential) + } + extLenPos[0] = uint8(extensionLen >> 8) extLenPos[1] = uint8(extensionLen) } @@ -1651,6 +1699,8 @@ func (m *certificateMsg13) unmarshal(data []byte) alert { m.certificates[i].sctList = append(m.certificates[i].sctList, body[2:2+sctLen]) body = body[2+sctLen:] } + case extensionDelegatedCredential: + m.certificates[i].delegatedCredential = body } } } diff --git a/handshake_server.go b/handshake_server.go index d466fdb..b781031 100644 --- a/handshake_server.go +++ b/handshake_server.go @@ -30,6 +30,11 @@ type serverHandshakeState struct { clientHello *clientHelloMsg hello *serverHelloMsg cert *Certificate + privateKey crypto.PrivateKey + + // A marshalled DelegatedCredential to be sent to the client in the + // handshake. + delegatedCredential []byte // TLS 1.0-1.2 fields ellipticOk bool @@ -299,11 +304,48 @@ Curves: c.sendAlert(alertInternalError) return false, err } - if hs.clientHello.scts && hs.hello != nil { + + // Set the private key for this handshake to the certificate's secret key. + hs.privateKey = hs.cert.PrivateKey + + if hs.clientHello.scts { hs.hello.scts = hs.cert.SignedCertificateTimestamps } - if priv, ok := hs.cert.PrivateKey.(crypto.Signer); ok { + // Set the private key to the DC private key if the client and server are + // willing to negotiate the delegated credential extension. + // + // Check to see if a DelegatedCredential is available and should be used. + // If one is available, the session is using TLS >= 1.2, and the client + // accepts the delegated credential extension, then set the handshake + // private key to the DC private key. + if c.config.GetDelegatedCredential != nil && hs.clientHello.delegatedCredential && c.vers >= VersionTLS12 { + dc, sk, err := c.config.GetDelegatedCredential(hs.clientHelloInfo(), c.vers) + if err != nil { + c.sendAlert(alertInternalError) + return false, err + } + + // 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 + + // For TLS 1.2, the DC is an extension to the ServerHello. + if c.vers == VersionTLS12 { + hs.hello.delegatedCredential = hs.delegatedCredential + } + } + } + + if priv, ok := hs.privateKey.(crypto.Signer); ok { switch priv.Public().(type) { case *ecdsa.PublicKey: hs.ecdsaOk = true @@ -314,7 +356,7 @@ Curves: return false, fmt.Errorf("tls: unsupported signing key type (%T)", priv.Public()) } } - if priv, ok := hs.cert.PrivateKey.(crypto.Decrypter); ok { + if priv, ok := hs.privateKey.(crypto.Decrypter); ok { switch priv.Public().(type) { case *rsa.PublicKey: hs.rsaDecryptOk = true @@ -479,7 +521,7 @@ func (hs *serverHandshakeState) doFullHandshake() error { } keyAgreement := hs.suite.ka(c.vers) - skx, err := keyAgreement.generateServerKeyExchange(c.config, hs.cert.PrivateKey, hs.clientHello, hs.hello) + skx, err := keyAgreement.generateServerKeyExchange(c.config, hs.privateKey, hs.clientHello, hs.hello) if err != nil { c.sendAlert(alertHandshakeFailure) return err @@ -572,7 +614,7 @@ func (hs *serverHandshakeState) doFullHandshake() error { } hs.finishedHash.Write(ckx.marshal()) - preMasterSecret, err := keyAgreement.processClientKeyExchange(c.config, hs.cert.PrivateKey, ckx, c.vers) + preMasterSecret, err := keyAgreement.processClientKeyExchange(c.config, hs.privateKey, ckx, c.vers) if err != nil { if err == errClientKeyExchange { c.sendAlert(alertDecodeError) @@ -880,16 +922,17 @@ func (hs *serverHandshakeState) clientHelloInfo() *ClientHelloInfo { } hs.cachedClientHelloInfo = &ClientHelloInfo{ - CipherSuites: hs.clientHello.cipherSuites, - ServerName: hs.clientHello.serverName, - SupportedCurves: hs.clientHello.supportedCurves, - SupportedPoints: hs.clientHello.supportedPoints, - SignatureSchemes: hs.clientHello.supportedSignatureAlgorithms, - SupportedProtos: hs.clientHello.alpnProtocols, - SupportedVersions: supportedVersions, - Conn: hs.c.conn, - Offered0RTTData: hs.clientHello.earlyData, - Fingerprint: pskBinder, + CipherSuites: hs.clientHello.cipherSuites, + ServerName: hs.clientHello.serverName, + SupportedCurves: hs.clientHello.supportedCurves, + SupportedPoints: hs.clientHello.supportedPoints, + SignatureSchemes: hs.clientHello.supportedSignatureAlgorithms, + SupportedProtos: hs.clientHello.alpnProtocols, + SupportedVersions: supportedVersions, + Conn: hs.c.conn, + Offered0RTTData: hs.clientHello.earlyData, + AcceptsDelegatedCredential: hs.clientHello.delegatedCredential, + Fingerprint: pskBinder, } return hs.cachedClientHelloInfo diff --git a/subcerts.go b/subcerts.go new file mode 100644 index 0000000..7f025ec --- /dev/null +++ b/subcerts.go @@ -0,0 +1,521 @@ +// Copyright 2018 The Go Authors. 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 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). +// +// 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. + +import ( + "bytes" + "crypto" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/x509" + "crypto/x509/pkix" + "encoding/asn1" + "encoding/binary" + "errors" + "fmt" + "time" +) + +const ( + dcMaxTTLSeconds = 60 * 60 * 24 * 7 // 7 days + dcMaxTTL = time.Duration(dcMaxTTLSeconds * time.Second) + dcMaxPublicKeyLen = 1 << 16 // Bytes + dcMaxSignatureLen = 1 << 16 // Bytes +) + +var errNoDelegationUsage = errors.New("certificate not authorized for delegation") + +// delegationUsageId is the DelegationUsage X.509 extension OID +// +// 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 { + // Check that the digitalSignature key usage is set. + if (cert.KeyUsage & x509.KeyUsageDigitalSignature) == 0 { + return false + } + + // Check that the certificate has the DelegationUsage extension and that + // it's non-critical (per the spec). + for _, extension := range cert.Extensions { + if extension.Id.Equal(delegationUsageId) { + return true + } + } + return false +} + +// This structure stores the public components of a credential. +type credential struct { + validTime time.Duration + publicKey crypto.PublicKey + scheme SignatureScheme +} + +// marshalSubjectPublicKeyInfo returns a DER encoded SubjectPublicKeyInfo structure +// (as defined in the X.509 standard) for the credential. +func (cred *credential) marshalSubjectPublicKeyInfo() ([]byte, error) { + switch cred.scheme { + case ECDSAWithP256AndSHA256, + ECDSAWithP384AndSHA384, + ECDSAWithP521AndSHA512: + serializedPublicKey, err := x509.MarshalPKIXPublicKey(cred.publicKey) + if err != nil { + return nil, err + } + return serializedPublicKey, nil + + default: + return nil, fmt.Errorf("unsupported signature scheme: 0x%04x", cred.scheme) + } +} + +// marshal encodes a credential as per the spec. +func (cred *credential) marshal() ([]byte, error) { + // Write the valid_time field. + serialized := make([]byte, 6) + binary.BigEndian.PutUint32(serialized, uint32(cred.validTime/time.Second)) + + // Encode the public key and assert that the encoding is no longer than 2^16 + // bytes (per the spect). + serializedPublicKey, err := cred.marshalSubjectPublicKeyInfo() + if err != nil { + return nil, err + } + if len(serializedPublicKey) > dcMaxPublicKeyLen { + return nil, errors.New("public key is too long") + } + + // Write the length of the public_key field. + binary.BigEndian.PutUint16(serialized[4:], uint16(len(serializedPublicKey))) + + // Write the public key. + return append(serialized, serializedPublicKey...), nil +} + +// unmarshalCredential decodes a credential and returns it. +func unmarshalCredential(serialized []byte) (*credential, error) { + // Bytes 0-3 are the validity time field; bytes 4-6 are the length of the + // serialized SubjectPublicKeyInfo. + if len(serialized) < 6 { + return nil, errors.New("credential is too short") + } + + // Parse the validity time. + validTime := time.Duration(binary.BigEndian.Uint32(serialized)) * time.Second + + // Parse the SubjectPublicKeyInfo. + pk, scheme, err := unmarshalSubjectPublicKeyInfo(serialized[6:]) + if err != nil { + return nil, err + } + + return &credential{validTime, pk, scheme}, nil +} + +// unmarshalSubjectPublicKeyInfo parses a DER encoded SubjectPublicKeyInfo +// structure into a public key and its corresponding algorithm. +func unmarshalSubjectPublicKeyInfo(serialized []byte) (crypto.PublicKey, SignatureScheme, error) { + publicKey, err := x509.ParsePKIXPublicKey(serialized) + if err != nil { + return nil, 0, err + } + + switch pk := publicKey.(type) { + case *ecdsa.PublicKey: + curveName := pk.Curve.Params().Name + if curveName == "P-256" { + return pk, ECDSAWithP256AndSHA256, nil + } else if curveName == "P-384" { + return pk, ECDSAWithP384AndSHA384, nil + } else if curveName == "P-521" { + return pk, ECDSAWithP521AndSHA512, nil + } else { + return nil, 0, fmt.Errorf("curve %s s not supported", curveName) + } + + default: + return nil, 0, fmt.Errorf("unsupported delgation key type: %T", pk) + } +} + +// getCredentialLen returns the number of bytes comprising the serialized +// credential that starts at the beginning of the input slice. It returns an +// error if the input is too short to contain a credential. +func getCredentialLen(serialized []byte) (int, error) { + if len(serialized) < 6 { + return 0, errors.New("credential is too short") + } + // First 4 bytes is the validity time. + serialized = serialized[4:] + + // The next 2 bytes are the length of the serialized public key. + serializedPublicKeyLen := int(binary.BigEndian.Uint16(serialized)) + serialized = serialized[2:] + + if len(serialized) < serializedPublicKeyLen { + return 0, errors.New("public key of credential is too short") + } + + return 6 + serializedPublicKeyLen, nil +} + +// DelegatedCredential stores a credential and its delegation. +type DelegatedCredential struct { + // The serialized form of the credential. + 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 signature scheme used to sign the credential. + Scheme SignatureScheme + + // The credential's delegation. + 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. + var err error + if cert.Leaf == nil { + if len(cert.Certificate[0]) == 0 { + return nil, nil, errors.New("missing leaf certificate") + } + 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 !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 +} + +// 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 +// 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) { + // Check that the cert can delegate. + if !canDelegate(cert) { + return false, errNoDelegationUsage + } + + if dc.IsExpired(cert.NotBefore, now) { + return false, errors.New("credential has expired") + } + + if dc.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) + if err != nil { + return false, err + } + + // TODO(any) This code overlaps signficantly with verifyHandshakeSignature() + // in ../auth.go. This should be refactored. + switch dc.Scheme { + case ECDSAWithP256AndSHA256, + ECDSAWithP384AndSHA384, + ECDSAWithP521AndSHA512: + pk, ok := cert.PublicKey.(*ecdsa.PublicKey) + if !ok { + return false, errors.New("expected ECDSA public key") + } + sig := new(ecdsaSignature) + 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) + } +} + +// 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) { + // Get the length of the serialized credential that begins at the start of + // the input slice. + serializedCredentialLen, err := getCredentialLen(serialized) + if err != nil { + return nil, err + } + + // Parse the credential. + cred, err := unmarshalCredential(serialized[:serializedCredentialLen]) + if err != nil { + return nil, err + } + + // Parse the signature scheme. + serialized = serialized[serializedCredentialLen:] + if len(serialized) < 4 { + return nil, errors.New("delegated credential is too short") + } + scheme := SignatureScheme(binary.BigEndian.Uint16(serialized)) + + // Parse the signature length. + serialized = serialized[2:] + serializedSignatureLen := binary.BigEndian.Uint16(serialized) + + // Prase the signature. + serialized = serialized[2:] + if len(serialized) < int(serializedSignatureLen) { + return nil, errors.New("signature of delegated credential is too short") + } + sig := serialized[:serializedSignatureLen] + + return &DelegatedCredential{ + ValidTime: cred.validTime, + PublicKey: cred.publicKey, + publicKeyScheme: cred.scheme, + Scheme: scheme, + Signature: sig, + }, nil +} + +// getCurve maps the SignatureScheme to its corresponding elliptic.Curve. +func getCurve(scheme SignatureScheme) elliptic.Curve { + switch scheme { + case ECDSAWithP256AndSHA256: + return elliptic.P256() + case ECDSAWithP384AndSHA384: + return elliptic.P384() + case ECDSAWithP521AndSHA512: + return elliptic.P521() + default: + return nil + } +} + +// getHash maps the SignatureScheme to its corresponding hash function. +// +// TODO(any) This function overlaps with hashForSignatureScheme in 13.go. +func getHash(scheme SignatureScheme) crypto.Hash { + switch scheme { + case ECDSAWithP256AndSHA256: + return crypto.SHA256 + case ECDSAWithP384AndSHA384: + return crypto.SHA384 + case ECDSAWithP521AndSHA512: + return crypto.SHA512 + default: + return 0 // Unknown hash function + } +} + +// prepareDelegation returns a hash of the message that the delegator is to +// sign. The inputs are the credential (cred), the DER-encoded delegator +// certificate (`delegatorCert`), the signature scheme of the delegator +// (`delegatorScheme`), and the protocol version (`vers`) in which the credential +// is to be used. +func prepareDelegation(hash crypto.Hash, cred *credential, delegatorCert []byte, delegatorScheme SignatureScheme, vers uint16) ([]byte, error) { + h := hash.New() + + // The header. + h.Write(bytes.Repeat([]byte{0x20}, 64)) + h.Write([]byte("TLS, server delegated credentials")) + h.Write([]byte{0x00}) + + // The protocol version. + var serializedVers [2]byte + binary.BigEndian.PutUint16(serializedVers[:], uint16(vers)) + h.Write(serializedVers[:]) + + // The delegation certificate. + h.Write(delegatorCert) + + // The delegator signature scheme. + var serializedScheme [2]byte + binary.BigEndian.PutUint16(serializedScheme[:], uint16(delegatorScheme)) + h.Write(serializedScheme[:]) + + // The credential. + serializedCred, err := cred.marshal() + if err != nil { + return nil, err + } + h.Write(serializedCred) + + return h.Sum(nil), nil +} diff --git a/subcerts_test.go b/subcerts_test.go new file mode 100644 index 0000000..fe4478e --- /dev/null +++ b/subcerts_test.go @@ -0,0 +1,500 @@ +// Copyright 2018 The Go Authors. 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/elliptic" + "crypto/x509" + "errors" + "fmt" + "testing" + "time" +) + +// dcWithPrivateKey stores a delegated credential and its corresponding private +// key. +type dcWithPrivateKey struct { + *DelegatedCredential + privateKey crypto.PrivateKey +} + +// 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----- +MIIBdzCCAR2gAwIBAgIQLVIvEpo0/0TzRja4ImvB1TAKBggqhkjOPQQDAjASMRAw +DgYDVQQKEwdBY21lIENvMB4XDTE4MDcwMzE2NTE1M1oXDTE5MDcwMzE2NTE1M1ow +EjEQMA4GA1UEChMHQWNtZSBDbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABOhB +U6adaAgliLaFc1PAo9HBO4Wish1G4df3IK5EXLy+ooYfmkfzT1FxqbNLZufNYzve +25fmpal/1VJAjpVyKq2jVTBTMA4GA1UdDwEB/wQEAwIFoDATBgNVHSUEDDAKBggr +BgEFBQcDATAMBgNVHRMBAf8EAjAAMA8GA1UdEQQIMAaHBH8AAAEwDQYJKwYBBAGC +2kssBAAwCgYIKoZIzj0EAwIDSAAwRQIhAPNwRk6cygm6zO5rjOzohKYWS+1KuWCM +OetDIvU4mdyoAiAGN97y3GJccYn9ZOJS4UOqhr9oO8PuZMLgdq4OrMRiiA== +-----END CERTIFICATE----- +` + +var delegatorKeyPEM = `-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIJDVlo+sJolMcNjMkfCGDUjMJcE4UgclcXGCrOtbJAi2oAoGCCqGSM49 +AwEHoUQDQgAE6EFTpp1oCCWItoVzU8Cj0cE7haKyHUbh1/cgrkRcvL6ihh+aR/NP +UXGps0tm581jO97bl+alqX/VUkCOlXIqrQ== +-----END EC PRIVATE KEY----- +` + +var nonDelegatorCertPEM = `-----BEGIN CERTIFICATE----- +MIIBaTCCAQ6gAwIBAgIQSUo+9uaip3qCW+1EPeHZgDAKBggqhkjOPQQDAjASMRAw +DgYDVQQKEwdBY21lIENvMB4XDTE4MDYxMjIzNDAyNloXDTE5MDYxMjIzNDAyNlow +EjEQMA4GA1UEChMHQWNtZSBDbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABLf7 +fiznPVdc3V5mM3ymswU2/IoJaq/deA6dgdj50ozdYyRiAPjxzcz9zRsZw1apTF/h +yNfiLhV4EE1VrwXcT5OjRjBEMA4GA1UdDwEB/wQEAwIFoDATBgNVHSUEDDAKBggr +BgEFBQcDATAMBgNVHRMBAf8EAjAAMA8GA1UdEQQIMAaHBH8AAAEwCgYIKoZIzj0E +AwIDSQAwRgIhANXG0zmrVtQBK0TNZZoEGMOtSwxmiZzXNe+IjdpxO3TiAiEA5VYx +0CWJq5zqpVXbJMeKVMASo2nrXZoA6NhJvFQ97hw= +-----END CERTIFICATE----- +` + +var nonDelegatorKeyPEM = `-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIMw9DiOfGI1E/XZrrW2huZSjYi0EKwvVjAe+dYtyFsSloAoGCCqGSM49 +AwEHoUQDQgAEt/t+LOc9V1zdXmYzfKazBTb8iglqr914Dp2B2PnSjN1jJGIA+PHN +zP3NGxnDVqlMX+HI1+IuFXgQTVWvBdxPkw== +-----END EC PRIVATE KEY----- +` + +// Invalid TLS versions used for testing purposes. +const ( + versionInvalidDC uint16 = 0xff00 + versionMalformedDC12 uint16 = 0xff12 + versionMalformedDC13 uint16 = 0xff13 +) + +var dcTestConfig *Config +var dcTestCerts map[string]*Certificate +var dcTestDCs map[uint16]dcWithPrivateKey +var dcNow time.Time +var dcTestDCScheme = ECDSAWithP521AndSHA512 +var dcTestDCVersions = []uint16{ + VersionTLS12, + VersionTLS13, + VersionTLS13Draft23, + versionInvalidDC, +} + +func init() { + + // Use a static time for testing at whcih time the test certificates are + // valid. + dcNow = time.Date(2018, 07, 03, 18, 0, 0, 234234, time.UTC) + + dcTestConfig = &Config{ + Time: func() time.Time { + return dcNow + }, + Rand: zeroSource{}, + Certificates: nil, + MinVersion: VersionTLS10, + MaxVersion: VersionTLS13Draft22, + CipherSuites: allCipherSuites(), + } + + // The certificates of the server. + dcTestCerts = make(map[string]*Certificate) + var err error + + // The delegation certificate. + dcCert := new(Certificate) + *dcCert, err = X509KeyPair([]byte(delegatorCertPEM), []byte(delegatorKeyPEM)) + if err != nil { + panic(err) + } + dcCert.Leaf, err = x509.ParseCertificate(dcCert.Certificate[0]) + if err != nil { + panic(err) + } + dcTestCerts["dc"] = dcCert + + // The standard certificate. + ndcCert := new(Certificate) + *ndcCert, err = X509KeyPair([]byte(nonDelegatorCertPEM), []byte(nonDelegatorKeyPEM)) + if err != nil { + panic(err) + } + ndcCert.Leaf, err = x509.ParseCertificate(ndcCert.Certificate[0]) + if err != nil { + panic(err) + } + dcTestCerts["no dc"] = ndcCert + + // The root certificates for the client. + dcTestConfig.RootCAs = x509.NewCertPool() + + dcRoot, err := x509.ParseCertificate(dcCert.Certificate[len(dcCert.Certificate)-1]) + if err != nil { + panic(err) + } + dcTestConfig.RootCAs.AddCert(dcRoot) + + ndcRoot, err := x509.ParseCertificate(ndcCert.Certificate[len(ndcCert.Certificate)-1]) + if err != nil { + panic(err) + } + dcTestConfig.RootCAs.AddCert(ndcRoot) + + // A pool of DCs. + dcTestDCs = make(map[uint16]dcWithPrivateKey) + for _, vers := range dcTestDCVersions { + dc, sk, err := NewDelegatedCredential(dcCert, dcTestDCScheme, dcNow.Sub(dcCert.Leaf.NotBefore)+dcMaxTTL, vers) + if err != nil { + panic(err) + } + dcTestDCs[vers] = dcWithPrivateKey{dc, sk} + } + // Add two DCs with invalid private keys, one for TLS 1.2 and another for + // 1.3. + malformedDC12 := new(DelegatedCredential) + *malformedDC12 = *dcTestDCs[VersionTLS12].DelegatedCredential + dcTestDCs[versionMalformedDC12] = dcWithPrivateKey{ + malformedDC12, + dcTestDCs[versionInvalidDC].privateKey, + } + malformedDC13 := new(DelegatedCredential) + *malformedDC13 = *dcTestDCs[VersionTLS13].DelegatedCredential + dcTestDCs[versionMalformedDC13] = dcWithPrivateKey{ + malformedDC13, + dcTestDCs[versionInvalidDC].privateKey, + } +} + +func checkECDSAPublicKeysEqual( + publicKey, publicKey2 crypto.PublicKey, scheme SignatureScheme) error { + + curve := getCurve(scheme) + pk := publicKey.(*ecdsa.PublicKey) + pk2 := publicKey2.(*ecdsa.PublicKey) + serializedPublicKey := elliptic.Marshal(curve, pk.X, pk.Y) + serializedPublicKey2 := elliptic.Marshal(curve, pk2.X, pk2.Y) + if !bytes.Equal(serializedPublicKey2, serializedPublicKey) { + return errors.New("PublicKey mismatch") + } + + return nil +} + +// Test that cred and cred2 are equal. +func checkCredentialsEqual(dc, dc2 *DelegatedCredential) error { + if dc2.ValidTime != dc.ValidTime { + return fmt.Errorf("ValidTime mismatch: got %d; want %d", dc2.ValidTime, dc.ValidTime) + } + if dc2.publicKeyScheme != dc.publicKeyScheme { + return fmt.Errorf("scheme mismatch: got %04x; want %04x", dc2.publicKeyScheme, dc.publicKeyScheme) + } + + return checkECDSAPublicKeysEqual(dc.PublicKey, dc2.PublicKey, dc.publicKeyScheme) +} + +// Test delegation and validation of credentials. +func TestDelegateValidate(t *testing.T) { + ver := uint16(VersionTLS12) + cert := dcTestCerts["dc"] + + validTime := dcNow.Sub(cert.Leaf.NotBefore) + dcMaxTTL + shortValidTime := dcNow.Sub(cert.Leaf.NotBefore) + time.Second + + delegatedCred, _, err := NewDelegatedCredential(cert, ECDSAWithP256AndSHA256, validTime, ver) + if err != nil { + t.Fatal(err) + } + + // Test validation of good DC. + if v, err := delegatedCred.Validate(cert.Leaf, ver, dcNow); err != nil { + t.Error(err) + } else if !v { + t.Error("good DC is invalid; want valid") + } + + // Test validation of expired DC. + tooLate := dcNow.Add(dcMaxTTL).Add(time.Nanosecond) + if v, err := delegatedCred.Validate(cert.Leaf, ver, tooLate); err == nil { + t.Error("expired DC validation succeeded; want failure") + } else if v { + t.Error("expired DC is valid; want invalid") + } + + // Test protocol binding. + if v, err := delegatedCred.Validate(cert.Leaf, VersionSSL30, dcNow); err != nil { + t.Fatal(err) + } else if v { + t.Error("DC with wrong version is valid; want invalid") + } + + // Test signature algorithm binding. + delegatedCred.Scheme = ECDSAWithP521AndSHA512 + if v, err := delegatedCred.Validate(cert.Leaf, ver, dcNow); err != nil { + t.Fatal(err) + } else if v { + t.Error("DC with wrong scheme is valid; want invalid") + } + delegatedCred.Scheme = ECDSAWithP256AndSHA256 + + // Test delegation cedrtificate binding. + cert.Leaf.Raw[0] ^= byte(42) + if v, err := delegatedCred.Validate(cert.Leaf, ver, dcNow); err != nil { + t.Fatal(err) + } else if v { + t.Error("DC with wrong cert is valid; want invalid") + } + cert.Leaf.Raw[0] ^= byte(42) + + // Test validation of DC who's TTL is too long. + delegatedCred2, _, err := NewDelegatedCredential(cert, ECDSAWithP256AndSHA256, validTime+time.Second, ver) + if err != nil { + t.Fatal(err) + } + if v, err := delegatedCred2.Validate(cert.Leaf, ver, dcNow); err == nil { + t.Error("DC validation with long TTL succeeded; want failure") + } else if v { + t.Error("DC with long TTL is valid; want invalid") + } + + // Test validation of DC who's TTL is short. + delegatedCred3, _, err := NewDelegatedCredential(cert, ECDSAWithP256AndSHA256, shortValidTime, ver) + if err != nil { + t.Fatal(err) + } + if v, err := delegatedCred3.Validate(cert.Leaf, ver, dcNow); err != nil { + t.Error(err) + } else if !v { + t.Error("good DC is invalid; want valid") + } + + // Test validation of DC using a certificate that can't delegate. + if v, err := delegatedCred.Validate( + dcTestCerts["no dc"].Leaf, ver, dcNow); err != errNoDelegationUsage { + t.Error("DC validation with non-delegation cert succeeded; want failure") + } else if v { + t.Error("DC with non-delegation cert is valid; want invalid") + } +} + +// Test encoding/decoding of delegated credentials. +func TestDelegatedCredentialMarshalUnmarshal(t *testing.T) { + cert := dcTestCerts["dc"] + delegatedCred, _, err := NewDelegatedCredential(cert, + ECDSAWithP256AndSHA256, + dcNow.Sub(cert.Leaf.NotBefore)+dcMaxTTL, + VersionTLS12) + if err != nil { + t.Fatal(err) + } + + serialized, err := delegatedCred.Marshal() + if err != nil { + t.Error(err) + } + + delegatedCred2, err := UnmarshalDelegatedCredential(serialized) + if err != nil { + t.Error(err) + } + + err = checkCredentialsEqual(delegatedCred, delegatedCred2) + if err != nil { + t.Error(err) + } + + if delegatedCred.Scheme != delegatedCred2.Scheme { + t.Errorf("scheme mismatch: got %04x; want %04x", + delegatedCred2.Scheme, delegatedCred.Scheme) + } + + if !bytes.Equal(delegatedCred2.Signature, delegatedCred.Signature) { + t.Error("Signature mismatch") + } +} + +// 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) { + + ln := newLocalListener(t) + defer ln.Close() + + srvCh := make(chan *Conn, 1) + var serr error + go func() { + sconn, err := ln.Accept() + if err != nil { + serr = err + srvCh <- nil + return + } + srv := Server(sconn, serverConfig) + if err := srv.Handshake(); err != nil { + serr = fmt.Errorf("handshake: %v", err) + srvCh <- nil + return + } + srvCh <- srv + }() + + cli, err := Dial("tcp", ln.Addr().String(), clientConfig) + if err != nil { + return false, err + } + defer cli.Close() + + srv := <-srvCh + if srv == nil { + return false, serr + } + + bufLen := len(clientMsg) + if len(serverMsg) > len(clientMsg) { + bufLen = len(serverMsg) + } + buf := make([]byte, bufLen) + + 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) + } + + 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 +} + +// Checks that the client suppports a version >= 1.2 and accepts delegated +// credentials. If so, it returns the delegation certificate; otherwise it +// returns a plain certificate. +func testServerGetCertificate(ch *ClientHelloInfo) (*Certificate, error) { + versOk := false + for _, vers := range ch.SupportedVersions { + versOk = versOk || (vers >= uint16(VersionTLS12)) + } + + if versOk && ch.AcceptsDelegatedCredential { + return dcTestCerts["dc"], nil + } + return dcTestCerts["no dc"], nil +} + +// Checks that the ciient supports the signature algorithm supported by the test +// server, and that the server has a DC for the selected protocol version. +func testServerGetDC(ch *ClientHelloInfo, vers uint16) (*DelegatedCredential, crypto.PrivateKey, error) { + schemeOk := false + for _, scheme := range ch.SignatureSchemes { + schemeOk = schemeOk || (scheme == dcTestDCScheme) + } + + versOk := false + for _, testVers := range dcTestDCVersions { + versOk = versOk || (vers == testVers) + } + + if schemeOk && versOk && ch.AcceptsDelegatedCredential { + d := dcTestDCs[vers] + return d.DelegatedCredential, d.privateKey, nil + } + return nil, nil, nil +} + +// Returns a DC signed with a bad version number. +func testServerGetInvalidDC(ch *ClientHelloInfo, vers uint16) (*DelegatedCredential, crypto.PrivateKey, error) { + d := dcTestDCs[versionInvalidDC] + return d.DelegatedCredential, d.privateKey, nil +} + +// Returns a DC with the wrong private key. +func testServerGetMalformedDC(ch *ClientHelloInfo, vers uint16) (*DelegatedCredential, crypto.PrivateKey, error) { + if vers == VersionTLS12 { + d := dcTestDCs[versionMalformedDC12] + return d.DelegatedCredential, d.privateKey, nil + } else if vers == VersionTLS13 { + d := dcTestDCs[versionMalformedDC13] + return d.DelegatedCredential, d.privateKey, nil + } else { + return nil, nil, fmt.Errorf("testServerGetMalformedDC: unsupported version %x", vers) + } + +} + +var dcTests = []struct { + clientDC bool + serverDC bool + clientSkipVerify bool + clientMaxVers uint16 + serverMaxVers uint16 + useMalformedDC bool + useInvalidDC bool + expectSuccess bool + expectDC bool + name string +}{ + {true, true, false, VersionTLS12, VersionTLS12, false, false, true, true, "tls12"}, + {true, true, false, VersionTLS13, VersionTLS13, false, false, true, true, "tls13"}, + {true, true, false, VersionTLS12, VersionTLS12, true, false, false, false, "tls12, malformed dc"}, + {true, true, false, VersionTLS13, VersionTLS13, true, false, false, false, "tls13, malformed dc"}, + {true, true, true, VersionTLS12, VersionTLS12, false, true, true, true, "tls12, invalid dc, skip verify"}, + {true, true, true, VersionTLS13, VersionTLS13, false, true, true, true, "tls13, invalid dc, skip verify"}, + {false, true, false, VersionTLS12, VersionTLS12, false, false, true, false, "client no dc"}, + {true, false, false, VersionTLS12, VersionTLS12, false, false, true, false, "server no dc"}, + {true, true, false, VersionTLS11, VersionTLS12, false, false, true, false, "client old"}, + {true, true, false, VersionTLS12, VersionTLS11, false, false, true, false, "server old"}, +} + +// Tests the handshake with the delegated credential extension. +func TestDCHandshake(t *testing.T) { + serverMsg := "hello" + clientMsg := "world" + + clientConfig := dcTestConfig.Clone() + serverConfig := dcTestConfig.Clone() + serverConfig.GetCertificate = testServerGetCertificate + + for i, test := range dcTests { + clientConfig.AcceptDelegatedCredential = test.clientDC + clientConfig.InsecureSkipVerify = test.clientSkipVerify + + if test.serverDC { + if test.useInvalidDC { + serverConfig.GetDelegatedCredential = testServerGetInvalidDC + } else if test.useMalformedDC { + serverConfig.GetDelegatedCredential = testServerGetMalformedDC + } else { + serverConfig.GetDelegatedCredential = testServerGetDC + } + } else { + serverConfig.GetDelegatedCredential = nil + } + + clientConfig.MaxVersion = test.clientMaxVers + serverConfig.MaxVersion = test.serverMaxVers + + 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) + } + + if usedDC != test.expectDC { + t.Errorf("test #%d (%s) usedDC = %v; expected %v", i+1, test.name, usedDC, test.expectDC) + } + } +} diff --git a/tls_test.go b/tls_test.go index 13482d8..119b386 100644 --- a/tls_test.go +++ b/tls_test.go @@ -674,7 +674,7 @@ func TestCloneNonFuncFields(t *testing.T) { switch fn := typ.Field(i).Name; fn { case "Rand": f.Set(reflect.ValueOf(io.Reader(os.Stdin))) - case "Time", "GetCertificate", "GetConfigForClient", "VerifyPeerCertificate", "GetClientCertificate": + case "Time", "GetCertificate", "GetConfigForClient", "VerifyPeerCertificate", "GetClientCertificate", "GetDelegatedCredential": // DeepEqual can't compare functions. If you add a // function field to this list, you must also change // TestCloneFuncFields to ensure that the func field is @@ -713,6 +713,8 @@ func TestCloneNonFuncFields(t *testing.T) { f.Set(reflect.ValueOf(uint32(0))) case "SessionTicketSealer": // TODO + case "AcceptDelegatedCredential": + f.Set(reflect.ValueOf(false)) default: t.Errorf("all fields must be accounted for, but saw unknown field %q", fn) }