Implement client certificates for TLS 1.3 in Go. Tested by having client and server talk to each other. This adds the certificate_extensions field to CertificateRequest which I'd previously missed. (We completely ignore the field, with the expectation that the C code won't have anything useful to do with it either.) Change-Id: I74f96acd36747d4b6a6f533535e36ea8e94d2be8 Reviewed-on: https://boringssl-review.googlesource.com/8710 Reviewed-by: David Benjamin <davidben@google.com>
diff --git a/ssl/test/runner/handshake_client.go b/ssl/test/runner/handshake_client.go index a67b242..4ff7012 100644 --- a/ssl/test/runner/handshake_client.go +++ b/ssl/test/runner/handshake_client.go
@@ -510,8 +510,7 @@ } var chainToSend *Certificate - var certRequested bool - var certRequestContext []byte + var certReq *certificateRequestMsg if hs.suite.flags&suitePSK != 0 { if encryptedExtensions.extensions.ocspResponse != nil { c.sendAlert(alertUnsupportedExtension) @@ -530,11 +529,10 @@ return err } - certReq, ok := msg.(*certificateRequestMsg) + var ok bool + certReq, ok = msg.(*certificateRequestMsg) if ok { hs.writeServerHash(certReq.marshal()) - certRequested = true - certRequestContext = certReq.requestContext chainToSend, err = selectClientCertificate(c, certReq) if err != nil { @@ -602,10 +600,42 @@ masterSecret := hs.finishedHash.extractKey(handshakeSecret, zeroSecret) trafficSecret := hs.finishedHash.deriveSecret(masterSecret, applicationTrafficLabel) - if certRequested { - _ = chainToSend - _ = certRequestContext - return errors.New("tls: client auth not implemented.") + if certReq != nil { + certMsg := &certificateMsg{ + hasRequestContext: true, + requestContext: certReq.requestContext, + } + if chainToSend != nil { + certMsg.certificates = chainToSend.Certificate + } + hs.writeClientHash(certMsg.marshal()) + c.writeRecord(recordTypeHandshake, certMsg.marshal()) + + if chainToSend != nil { + certVerify := &certificateVerifyMsg{ + hasSignatureAlgorithm: true, + } + + // Determine the hash to sign. + privKey := chainToSend.PrivateKey + + var err error + certVerify.signatureAlgorithm, err = selectSignatureAlgorithm(c.vers, privKey, c.config, certReq.signatureAlgorithms) + if err != nil { + c.sendAlert(alertInternalError) + return err + } + + input := hs.finishedHash.certificateVerifyInput(clientCertificateVerifyContextTLS13) + certVerify.signature, err = signMessage(c.vers, privKey, c.config, certVerify.signatureAlgorithm, input) + if err != nil { + c.sendAlert(alertInternalError) + return err + } + + hs.writeClientHash(certVerify.marshal()) + c.writeRecord(recordTypeHandshake, certVerify.marshal()) + } } // Send a client Finished message.
diff --git a/ssl/test/runner/handshake_messages.go b/ssl/test/runner/handshake_messages.go index baa07bd..0df3be3 100644 --- a/ssl/test/runner/handshake_messages.go +++ b/ssl/test/runner/handshake_messages.go
@@ -1461,6 +1461,11 @@ caEntry.addBytes(ca) } + if m.hasRequestContext { + // Emit no certificate extensions. + body.addU16(0) + } + m.raw = builder.finish() return m.raw } @@ -1538,6 +1543,19 @@ m.certificateAuthorities = append(m.certificateAuthorities, cas[:caLen]) cas = cas[caLen:] } + + if m.hasRequestContext { + // Ignore certificate extensions. + if len(data) < 2 { + return false + } + extsLength := int(data[0])<<8 | int(data[1]) + if len(data) < 2+extsLength { + return false + } + data = data[2+extsLength:] + } + if len(data) > 0 { return false }
diff --git a/ssl/test/runner/handshake_server.go b/ssl/test/runner/handshake_server.go index db0b358..06823ed 100644 --- a/ssl/test/runner/handshake_server.go +++ b/ssl/test/runner/handshake_server.go
@@ -406,8 +406,25 @@ if hs.suite.flags&suitePSK == 0 { if config.ClientAuth >= RequestClientCert { - // TODO(davidben): Implement client auth. - return errors.New("tls: client auth not implemented") + // Request a client certificate + certReq := &certificateRequestMsg{ + hasSignatureAlgorithm: true, + hasRequestContext: true, + } + if !config.Bugs.NoSignatureAlgorithms { + certReq.signatureAlgorithms = config.signSignatureAlgorithms() + } + + // An empty list of certificateAuthorities signals to + // the client that it may send any certificate in response + // to our request. When we know the CAs we trust, then + // we can send them down, so that the client can choose + // an appropriate certificate to give to us. + if config.ClientCAs != nil { + certReq.certificateAuthorities = config.ClientCAs.Subjects() + } + hs.writeServerHash(certReq.marshal()) + c.writeRecord(recordTypeHandshake, certReq.marshal()) } certMsg := &certificateMsg{ @@ -461,7 +478,51 @@ // If we requested a client certificate, then the client must send a // certificate message, even if it's empty. if config.ClientAuth >= RequestClientCert { - return errors.New("tls: client certificates not implemented") + msg, err := c.readHandshake() + if err != nil { + return err + } + + certMsg, ok := msg.(*certificateMsg) + if !ok { + c.sendAlert(alertUnexpectedMessage) + return unexpectedMessageError(certMsg, msg) + } + hs.writeClientHash(certMsg.marshal()) + + if len(certMsg.certificates) == 0 { + // The client didn't actually send a certificate + switch config.ClientAuth { + case RequireAnyClientCert, RequireAndVerifyClientCert: + c.sendAlert(alertBadCertificate) + return errors.New("tls: client didn't provide a certificate") + } + } + + pub, err := hs.processCertsFromClient(certMsg.certificates) + if err != nil { + return err + } + + if len(c.peerCertificates) > 0 { + msg, err = c.readHandshake() + if err != nil { + return err + } + + certVerify, ok := msg.(*certificateVerifyMsg) + if !ok { + c.sendAlert(alertUnexpectedMessage) + return unexpectedMessageError(certVerify, msg) + } + + input := hs.finishedHash.certificateVerifyInput(clientCertificateVerifyContextTLS13) + if err := verifyMessage(c.vers, pub, config, certVerify.signatureAlgorithm, input, certVerify.signature); err != nil { + c.sendAlert(alertBadCertificate) + return err + } + hs.writeClientHash(certVerify.marshal()) + } } // Read the client Finished message.