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/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/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 ca1921f..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], 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]) + 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 48d4389..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, 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, 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/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") } 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) }