diff options
author | Daiki Ueno <dueno@redhat.com> | 2019-02-20 10:25:50 +0100 |
---|---|---|
committer | Daiki Ueno <dueno@redhat.com> | 2019-02-20 10:25:50 +0100 |
commit | ae5cbca6d86cc61cd46b41457dee1d60b66a87dd (patch) | |
tree | 50fee50548af31aec758e19ec31ae7a8195c46ea | |
parent | a9b4d3dbe96c8889dc331fe39490402cb40e5f56 (diff) | |
download | nss-hg-ae5cbca6d86cc61cd46b41457dee1d60b66a87dd.tar.gz |
Bug 1471970, add support for post-handshake authentication, r=mt
Summary: This adds handling of the post_handshake_auth extension in CH and exposes tls13_SendCertificateRequest as an experimental API. For practical use, it might need another function that checks if the post_handshake_auth extension is received.
Reviewers: mt
Reviewed By: mt
Bug #: 1471970
Differential Revision: https://phabricator.services.mozilla.com/D14154
-rw-r--r-- | gtests/ssl_gtest/ssl_auth_unittest.cc | 315 | ||||
-rw-r--r-- | lib/ssl/ssl.h | 11 | ||||
-rw-r--r-- | lib/ssl/ssl3con.c | 6 | ||||
-rw-r--r-- | lib/ssl/ssl3ext.c | 2 | ||||
-rw-r--r-- | lib/ssl/sslerr.h | 1 | ||||
-rw-r--r-- | lib/ssl/sslexp.h | 21 | ||||
-rw-r--r-- | lib/ssl/sslimpl.h | 6 | ||||
-rw-r--r-- | lib/ssl/sslsock.c | 18 | ||||
-rw-r--r-- | lib/ssl/sslt.h | 1 | ||||
-rw-r--r-- | lib/ssl/tls13con.c | 186 | ||||
-rw-r--r-- | lib/ssl/tls13con.h | 1 | ||||
-rw-r--r-- | lib/ssl/tls13exthandle.c | 31 | ||||
-rw-r--r-- | lib/ssl/tls13exthandle.h | 6 |
13 files changed, 583 insertions, 22 deletions
diff --git a/gtests/ssl_gtest/ssl_auth_unittest.cc b/gtests/ssl_gtest/ssl_auth_unittest.cc index 7e6a1c661..8db23e74e 100644 --- a/gtests/ssl_gtest/ssl_auth_unittest.cc +++ b/gtests/ssl_gtest/ssl_auth_unittest.cc @@ -176,6 +176,321 @@ TEST_P(TlsConnectGeneric, ClientAuth) { CheckKeys(); } +class TlsCertificateRequestContextRecorder : public TlsHandshakeFilter { + public: + TlsCertificateRequestContextRecorder(const std::shared_ptr<TlsAgent>& a, + uint8_t handshake_type) + : TlsHandshakeFilter(a, {handshake_type}), buffer_(), filtered_(false) { + EnableDecryption(); + } + + bool filtered() const { return filtered_; } + const DataBuffer& buffer() const { return buffer_; } + + protected: + virtual PacketFilter::Action FilterHandshake(const HandshakeHeader& header, + const DataBuffer& input, + DataBuffer* output) { + assert(1 < input.len()); + size_t len = input.data()[0]; + assert(len + 1 < input.len()); + buffer_.Assign(input.data() + 1, len); + filtered_ = true; + return KEEP; + } + + private: + DataBuffer buffer_; + bool filtered_; +}; + +// All stream only tests; DTLS isn't supported yet. + +TEST_F(TlsConnectStreamTls13, PostHandshakeAuth) { + EnsureTlsSetup(); + auto capture_cert_req = MakeTlsFilter<TlsCertificateRequestContextRecorder>( + server_, kTlsHandshakeCertificateRequest); + auto capture_certificate = + MakeTlsFilter<TlsCertificateRequestContextRecorder>( + client_, kTlsHandshakeCertificate); + client_->SetupClientAuth(); + EXPECT_EQ(SECSuccess, SSL_OptionSet(client_->ssl_fd(), + SSL_ENABLE_POST_HANDSHAKE_AUTH, PR_TRUE)); + size_t called = 0; + server_->SetAuthCertificateCallback( + [&called](TlsAgent*, PRBool, PRBool) -> SECStatus { + called++; + return SECSuccess; + }); + Connect(); + EXPECT_EQ(0U, called); + EXPECT_FALSE(capture_cert_req->filtered()); + EXPECT_FALSE(capture_certificate->filtered()); + // Send CertificateRequest. + EXPECT_EQ(SECSuccess, SSL_SendCertificateRequest(server_->ssl_fd())) + << "Unexpected error: " << PORT_ErrorToName(PORT_GetError()); + // Need to do a round-trip so that the post-handshake message is + // handled on both client and server. + server_->SendData(50); + client_->ReadBytes(50); + client_->SendData(50); + server_->ReadBytes(50); + EXPECT_EQ(1U, called); + EXPECT_TRUE(capture_cert_req->filtered()); + EXPECT_TRUE(capture_certificate->filtered()); + // Check if a non-empty request context is generated and it is + // properly sent back. + EXPECT_LT(0U, capture_cert_req->buffer().len()); + EXPECT_EQ(capture_cert_req->buffer().len(), + capture_certificate->buffer().len()); + EXPECT_EQ(0, memcmp(capture_cert_req->buffer().data(), + capture_certificate->buffer().data(), + capture_cert_req->buffer().len())); + ScopedCERTCertificate cert1(SSL_PeerCertificate(server_->ssl_fd())); + ScopedCERTCertificate cert2(SSL_LocalCertificate(client_->ssl_fd())); + EXPECT_TRUE(SECITEM_ItemsAreEqual(&cert1->derCert, &cert2->derCert)); +} + +static SECStatus GetClientAuthDataHook(void* self, PRFileDesc* fd, + CERTDistNames* caNames, + CERTCertificate** clientCert, + SECKEYPrivateKey** clientKey) { + ScopedCERTCertificate cert; + ScopedSECKEYPrivateKey priv; + // use a different certificate than TlsAgent::kClient + if (!TlsAgent::LoadCertificate(TlsAgent::kRsa2048, &cert, &priv)) { + return SECFailure; + } + + *clientCert = cert.release(); + *clientKey = priv.release(); + return SECSuccess; +} + +TEST_F(TlsConnectStreamTls13, PostHandshakeAuthMultiple) { + client_->SetupClientAuth(); + EXPECT_EQ(SECSuccess, SSL_OptionSet(client_->ssl_fd(), + SSL_ENABLE_POST_HANDSHAKE_AUTH, PR_TRUE)); + size_t called = 0; + server_->SetAuthCertificateCallback( + [&called](TlsAgent*, PRBool, PRBool) -> SECStatus { + called++; + return SECSuccess; + }); + Connect(); + EXPECT_EQ(0U, called); + EXPECT_EQ(nullptr, SSL_PeerCertificate(server_->ssl_fd())); + // Send 1st CertificateRequest. + EXPECT_EQ(SECSuccess, SSL_SendCertificateRequest(server_->ssl_fd())) + << "Unexpected error: " << PORT_ErrorToName(PORT_GetError()); + server_->SendData(50); + client_->ReadBytes(50); + client_->SendData(50); + server_->ReadBytes(50); + EXPECT_EQ(1U, called); + ScopedCERTCertificate cert1(SSL_PeerCertificate(server_->ssl_fd())); + ScopedCERTCertificate cert2(SSL_LocalCertificate(client_->ssl_fd())); + EXPECT_TRUE(SECITEM_ItemsAreEqual(&cert1->derCert, &cert2->derCert)); + // Send 2nd CertificateRequest. + EXPECT_EQ(SECSuccess, SSL_GetClientAuthDataHook( + client_->ssl_fd(), GetClientAuthDataHook, nullptr)); + EXPECT_EQ(SECSuccess, SSL_SendCertificateRequest(server_->ssl_fd())) + << "Unexpected error: " << PORT_ErrorToName(PORT_GetError()); + server_->SendData(50); + client_->ReadBytes(50); + client_->SendData(50); + server_->ReadBytes(50); + EXPECT_EQ(2U, called); + ScopedCERTCertificate cert3(SSL_PeerCertificate(server_->ssl_fd())); + ScopedCERTCertificate cert4(SSL_LocalCertificate(client_->ssl_fd())); + EXPECT_TRUE(SECITEM_ItemsAreEqual(&cert3->derCert, &cert4->derCert)); + EXPECT_FALSE(SECITEM_ItemsAreEqual(&cert3->derCert, &cert1->derCert)); +} + +TEST_F(TlsConnectStreamTls13, PostHandshakeAuthConcurrent) { + client_->SetupClientAuth(); + EXPECT_EQ(SECSuccess, SSL_OptionSet(client_->ssl_fd(), + SSL_ENABLE_POST_HANDSHAKE_AUTH, PR_TRUE)); + Connect(); + // Send 1st CertificateRequest. + EXPECT_EQ(SECSuccess, SSL_SendCertificateRequest(server_->ssl_fd())) + << "Unexpected error: " << PORT_ErrorToName(PORT_GetError()); + // Send 2nd CertificateRequest. + EXPECT_EQ(SECFailure, SSL_SendCertificateRequest(server_->ssl_fd())); + EXPECT_EQ(PR_WOULD_BLOCK_ERROR, PORT_GetError()); +} + +TEST_F(TlsConnectStreamTls13, PostHandshakeAuthMissingExtension) { + client_->SetupClientAuth(); + Connect(); + // Send CertificateRequest, should fail due to missing + // post_handshake_auth extension. + EXPECT_EQ(SECFailure, SSL_SendCertificateRequest(server_->ssl_fd())); + EXPECT_EQ(SSL_ERROR_MISSING_POST_HANDSHAKE_AUTH_EXTENSION, PORT_GetError()); +} + +TEST_F(TlsConnectStreamTls13, PostHandshakeAuthAfterClientAuth) { + client_->SetupClientAuth(); + server_->RequestClientAuth(true); + EXPECT_EQ(SECSuccess, SSL_OptionSet(client_->ssl_fd(), + SSL_ENABLE_POST_HANDSHAKE_AUTH, PR_TRUE)); + size_t called = 0; + server_->SetAuthCertificateCallback( + [&called](TlsAgent*, PRBool, PRBool) -> SECStatus { + called++; + return SECSuccess; + }); + Connect(); + EXPECT_EQ(1U, called); + ScopedCERTCertificate cert1(SSL_PeerCertificate(server_->ssl_fd())); + ScopedCERTCertificate cert2(SSL_LocalCertificate(client_->ssl_fd())); + EXPECT_TRUE(SECITEM_ItemsAreEqual(&cert1->derCert, &cert2->derCert)); + // Send CertificateRequest. + EXPECT_EQ(SECSuccess, SSL_GetClientAuthDataHook( + client_->ssl_fd(), GetClientAuthDataHook, nullptr)); + EXPECT_EQ(SECSuccess, SSL_SendCertificateRequest(server_->ssl_fd())) + << "Unexpected error: " << PORT_ErrorToName(PORT_GetError()); + server_->SendData(50); + client_->ReadBytes(50); + client_->SendData(50); + server_->ReadBytes(50); + EXPECT_EQ(2U, called); + ScopedCERTCertificate cert3(SSL_PeerCertificate(server_->ssl_fd())); + ScopedCERTCertificate cert4(SSL_LocalCertificate(client_->ssl_fd())); + EXPECT_TRUE(SECITEM_ItemsAreEqual(&cert3->derCert, &cert4->derCert)); + EXPECT_FALSE(SECITEM_ItemsAreEqual(&cert3->derCert, &cert1->derCert)); +} + +// Damages the request context in a CertificateRequest message. +// We don't modify a Certificate message instead, so that the client +// can compute CertificateVerify correctly. +class TlsDamageCertificateRequestContextFilter : public TlsHandshakeFilter { + public: + TlsDamageCertificateRequestContextFilter(const std::shared_ptr<TlsAgent>& a) + : TlsHandshakeFilter(a, {kTlsHandshakeCertificateRequest}) { + EnableDecryption(); + } + + protected: + virtual PacketFilter::Action FilterHandshake(const HandshakeHeader& header, + const DataBuffer& input, + DataBuffer* output) { + *output = input; + assert(1 < output->len()); + // The request context has a 1 octet length. + output->data()[1] ^= 73; + return CHANGE; + } +}; + +TEST_F(TlsConnectStreamTls13, PostHandshakeAuthContextMismatch) { + EnsureTlsSetup(); + MakeTlsFilter<TlsDamageCertificateRequestContextFilter>(server_); + client_->SetupClientAuth(); + EXPECT_EQ(SECSuccess, SSL_OptionSet(client_->ssl_fd(), + SSL_ENABLE_POST_HANDSHAKE_AUTH, PR_TRUE)); + Connect(); + // Send CertificateRequest. + EXPECT_EQ(SECSuccess, SSL_SendCertificateRequest(server_->ssl_fd())) + << "Unexpected error: " << PORT_ErrorToName(PORT_GetError()); + server_->SendData(50); + client_->ReadBytes(50); + client_->SendData(50); + server_->ExpectSendAlert(kTlsAlertIllegalParameter); + server_->ReadBytes(50); + EXPECT_EQ(SSL_ERROR_RX_MALFORMED_CERTIFICATE, PORT_GetError()); + server_->ExpectReadWriteError(); + server_->SendData(50); + client_->ExpectReceiveAlert(kTlsAlertIllegalParameter); + client_->ReadBytes(50); + EXPECT_EQ(SSL_ERROR_ILLEGAL_PARAMETER_ALERT, PORT_GetError()); +} + +// Replaces signature in a CertificateVerify message. +class TlsDamageSignatureFilter : public TlsHandshakeFilter { + public: + TlsDamageSignatureFilter(const std::shared_ptr<TlsAgent>& a) + : TlsHandshakeFilter(a, {kTlsHandshakeCertificateVerify}) { + EnableDecryption(); + } + + protected: + virtual PacketFilter::Action FilterHandshake(const HandshakeHeader& header, + const DataBuffer& input, + DataBuffer* output) { + *output = input; + assert(2 < output->len()); + // The signature follows a 2-octet signature scheme. + output->data()[2] ^= 73; + return CHANGE; + } +}; + +TEST_F(TlsConnectStreamTls13, PostHandshakeAuthBadSignature) { + EnsureTlsSetup(); + MakeTlsFilter<TlsDamageSignatureFilter>(client_); + client_->SetupClientAuth(); + EXPECT_EQ(SECSuccess, SSL_OptionSet(client_->ssl_fd(), + SSL_ENABLE_POST_HANDSHAKE_AUTH, PR_TRUE)); + Connect(); + // Send CertificateRequest. + EXPECT_EQ(SECSuccess, SSL_SendCertificateRequest(server_->ssl_fd())) + << "Unexpected error: " << PORT_ErrorToName(PORT_GetError()); + server_->SendData(50); + client_->ReadBytes(50); + client_->SendData(50); + server_->ExpectSendAlert(kTlsAlertDecodeError); + server_->ReadBytes(50); + EXPECT_EQ(SSL_ERROR_RX_MALFORMED_CERT_VERIFY, PORT_GetError()); +} + +TEST_F(TlsConnectStreamTls13, PostHandshakeAuthDecline) { + EnsureTlsSetup(); + auto capture_cert_req = MakeTlsFilter<TlsCertificateRequestContextRecorder>( + server_, kTlsHandshakeCertificateRequest); + auto capture_certificate = + MakeTlsFilter<TlsCertificateRequestContextRecorder>( + client_, kTlsHandshakeCertificate); + client_->SetupClientAuth(); + EXPECT_EQ(SECSuccess, SSL_OptionSet(client_->ssl_fd(), + SSL_ENABLE_POST_HANDSHAKE_AUTH, PR_TRUE)); + // Client to decline the certificate request. + EXPECT_EQ(SECSuccess, + SSL_GetClientAuthDataHook( + client_->ssl_fd(), + [](void*, PRFileDesc*, CERTDistNames*, CERTCertificate**, + SECKEYPrivateKey**) -> SECStatus { return SECFailure; }, + nullptr)); + size_t called = 0; + server_->SetAuthCertificateCallback( + [&called](TlsAgent*, PRBool, PRBool) -> SECStatus { + called++; + return SECSuccess; + }); + Connect(); + EXPECT_EQ(0U, called); + // Send CertificateRequest. + EXPECT_EQ(SECSuccess, SSL_SendCertificateRequest(server_->ssl_fd())) + << "Unexpected error: " << PORT_ErrorToName(PORT_GetError()); + server_->SendData(50); + client_->ReadBytes(50); + client_->SendData(50); + server_->ReadBytes(50); + // AuthCertificateCallback is not called, because the client sends + // an empty certificate_list. + EXPECT_EQ(0U, called); + EXPECT_TRUE(capture_cert_req->filtered()); + EXPECT_TRUE(capture_certificate->filtered()); + // Check if a non-empty request context is generated and it is + // properly sent back. + EXPECT_LT(0U, capture_cert_req->buffer().len()); + EXPECT_EQ(capture_cert_req->buffer().len(), + capture_certificate->buffer().len()); + EXPECT_EQ(0, memcmp(capture_cert_req->buffer().data(), + capture_certificate->buffer().data(), + capture_cert_req->buffer().len())); +} + // In TLS 1.3, the client sends its cert rejection on the // second flight, and since it has already received the // server's Finished, it transitions to complete and diff --git a/lib/ssl/ssl.h b/lib/ssl/ssl.h index fc4a4a70c..bfeddeff8 100644 --- a/lib/ssl/ssl.h +++ b/lib/ssl/ssl.h @@ -299,6 +299,17 @@ SSL_IMPORT PRFileDesc *DTLS_ImportFD(PRFileDesc *model, PRFileDesc *fd); * This is disabled by default and will be removed in a future version. */ #define SSL_ENABLE_V2_COMPATIBLE_HELLO 38 +/* Enables the post-handshake authentication in TLS 1.3. If it is set + * to PR_TRUE, the client will send the "post_handshake_auth" + * extension to indicate that it will process CertificateRequest + * messages after handshake. + * + * This option applies only to clients. For a server, the + * SSL_SendCertificateRequest can be used to request post-handshake + * authentication. + */ +#define SSL_ENABLE_POST_HANDSHAKE_AUTH 39 + #ifdef SSL_DEPRECATED_FUNCTION /* Old deprecated function names */ SSL_IMPORT SECStatus SSL_Enable(PRFileDesc *fd, int option, PRIntn on); diff --git a/lib/ssl/ssl3con.c b/lib/ssl/ssl3con.c index 5ce729876..b5b047228 100644 --- a/lib/ssl/ssl3con.c +++ b/lib/ssl/ssl3con.c @@ -7393,6 +7393,9 @@ ssl3_CompleteHandleCertificateRequest(sslSocket *ss, if (ss->getClientAuthData != NULL) { PORT_Assert((ss->ssl3.hs.preliminaryInfo & ssl_preinfo_all) == ssl_preinfo_all); + PORT_Assert(ss->ssl3.clientPrivateKey == NULL); + PORT_Assert(ss->ssl3.clientCertificate == NULL); + PORT_Assert(ss->ssl3.clientCertChain == NULL); /* XXX Should pass cert_types and algorithms in this call!! */ rv = (SECStatus)(*ss->getClientAuthData)(ss->getClientAuthDataArg, ss->fd, ca_list, @@ -10749,6 +10752,9 @@ ssl3_AuthCertificate(sslSocket *ss) } } + if (ss->sec.ci.sid->peerCert) { + CERT_DestroyCertificate(ss->sec.ci.sid->peerCert); + } ss->sec.ci.sid->peerCert = CERT_DupCertificate(ss->sec.peerCert); if (!ss->sec.isServer) { diff --git a/lib/ssl/ssl3ext.c b/lib/ssl/ssl3ext.c index 60b5889e7..e9fc096b7 100644 --- a/lib/ssl/ssl3ext.c +++ b/lib/ssl/ssl3ext.c @@ -51,6 +51,7 @@ static const ssl3ExtensionHandler clientHelloHandlers[] = { { ssl_tls13_psk_key_exchange_modes_xtn, &tls13_ServerHandlePskModesXtn }, { ssl_tls13_cookie_xtn, &tls13_ServerHandleCookieXtn }, { ssl_tls13_encrypted_sni_xtn, &tls13_ServerHandleEsniXtn }, + { ssl_tls13_post_handshake_auth_xtn, &tls13_ServerHandlePostHandshakeAuthXtn }, { ssl_record_size_limit_xtn, &ssl_HandleRecordSizeLimitXtn }, { 0, NULL } }; @@ -138,6 +139,7 @@ static const sslExtensionBuilder clientHelloSendersTLS[] = { ssl_tls13_cookie_xtn, &tls13_ClientSendHrrCookieXtn }, { ssl_tls13_psk_key_exchange_modes_xtn, &tls13_ClientSendPskModesXtn }, { ssl_tls13_encrypted_sni_xtn, &tls13_ClientSendEsniXtn }, + { ssl_tls13_post_handshake_auth_xtn, &tls13_ClientSendPostHandshakeAuthXtn }, { ssl_record_size_limit_xtn, &ssl_SendRecordSizeLimitXtn }, /* The pre_shared_key extension MUST be last. */ { ssl_tls13_pre_shared_key_xtn, &tls13_ClientSendPreSharedKeyXtn }, diff --git a/lib/ssl/sslerr.h b/lib/ssl/sslerr.h index a4aa27657..b8f4e30ea 100644 --- a/lib/ssl/sslerr.h +++ b/lib/ssl/sslerr.h @@ -268,6 +268,7 @@ typedef enum { SSL_ERROR_RX_MALFORMED_ESNI_EXTENSION = (SSL_ERROR_BASE + 177), SSL_ERROR_MISSING_ESNI_EXTENSION = (SSL_ERROR_BASE + 178), SSL_ERROR_RX_UNEXPECTED_RECORD_TYPE = (SSL_ERROR_BASE + 179), + SSL_ERROR_MISSING_POST_HANDSHAKE_AUTH_EXTENSION = (SSL_ERROR_BASE + 180), SSL_ERROR_END_OF_LIST /* let the c compiler determine the value of this. */ } SSLErrorCodes; #endif /* NO_SECURITY_ERROR_ENUM */ diff --git a/lib/ssl/sslexp.h b/lib/ssl/sslexp.h index a342618e5..8f4720cdc 100644 --- a/lib/ssl/sslexp.h +++ b/lib/ssl/sslexp.h @@ -350,6 +350,27 @@ typedef SSLHelloRetryRequestAction(PR_CALLBACK *SSLHelloRetryRequestCallback)( (PRFileDesc * _fd, PRBool _requestUpdate), \ (fd, requestUpdate)) +/* This function allows a server application to trigger + * re-authentication (TLS 1.3 only) after handshake. + * + * This function will cause a CertificateRequest message to be sent by + * a server. This can be called once at a time, and is not allowed + * until an answer is received. + * + * The AuthCertificateCallback is called when the answer is received. + * If the answer is accepted by the server, the value returned by + * SSL_PeerCertificate() is replaced. If you need to remember all the + * certificates, you will need to call SSL_PeerCertificate() and save + * what you get before calling this. + * + * If the AuthCertificateCallback returns SECFailure, the connection + * is aborted. + */ +#define SSL_SendCertificateRequest(fd) \ + SSL_EXPERIMENTAL_API("SSL_SendCertificateRequest", \ + (PRFileDesc * _fd), \ + (fd)) + /* * Session cache API. */ diff --git a/lib/ssl/sslimpl.h b/lib/ssl/sslimpl.h index f3c63fa9f..d1bc69478 100644 --- a/lib/ssl/sslimpl.h +++ b/lib/ssl/sslimpl.h @@ -272,6 +272,7 @@ typedef struct sslOptionsStr { unsigned int enableDtlsShortHeader : 1; unsigned int enableHelloDowngradeCheck : 1; unsigned int enableV2CompatibleHello : 1; + unsigned int enablePostHandshakeAuth : 1; } sslOptions; typedef enum { sslHandshakingUndetermined = 0, @@ -742,6 +743,11 @@ struct ssl3StateStr { * update is initiated locally. */ PRBool peerRequestedKeyUpdate; + /* This is true after the server requests client certificate; + * false after the client certificate is received. Used by the + * server. */ + PRBool clientCertRequested; + CERTCertificate *clientCertificate; /* used by client */ SECKEYPrivateKey *clientPrivateKey; /* used by client */ CERTCertificateList *clientCertChain; /* used by client */ diff --git a/lib/ssl/sslsock.c b/lib/ssl/sslsock.c index 3c2a5c655..b8b75dc9c 100644 --- a/lib/ssl/sslsock.c +++ b/lib/ssl/sslsock.c @@ -86,7 +86,8 @@ static sslOptions ssl_defaults = { .enableTls13CompatMode = PR_FALSE, .enableDtlsShortHeader = PR_FALSE, .enableHelloDowngradeCheck = PR_FALSE, - .enableV2CompatibleHello = PR_FALSE + .enableV2CompatibleHello = PR_FALSE, + .enablePostHandshakeAuth = PR_FALSE }; /* @@ -842,6 +843,10 @@ SSL_OptionSet(PRFileDesc *fd, PRInt32 which, PRIntn val) ss->opt.enableV2CompatibleHello = val; break; + case SSL_ENABLE_POST_HANDSHAKE_AUTH: + ss->opt.enablePostHandshakeAuth = val; + break; + default: PORT_SetError(SEC_ERROR_INVALID_ARGS); rv = SECFailure; @@ -990,6 +995,9 @@ SSL_OptionGet(PRFileDesc *fd, PRInt32 which, PRIntn *pVal) case SSL_ENABLE_V2_COMPATIBLE_HELLO: val = ss->opt.enableV2CompatibleHello; break; + case SSL_ENABLE_POST_HANDSHAKE_AUTH: + val = ss->opt.enablePostHandshakeAuth; + break; default: PORT_SetError(SEC_ERROR_INVALID_ARGS); rv = SECFailure; @@ -1122,6 +1130,9 @@ SSL_OptionGetDefault(PRInt32 which, PRIntn *pVal) case SSL_ENABLE_V2_COMPATIBLE_HELLO: val = ssl_defaults.enableV2CompatibleHello; break; + case SSL_ENABLE_POST_HANDSHAKE_AUTH: + val = ssl_defaults.enablePostHandshakeAuth; + break; default: PORT_SetError(SEC_ERROR_INVALID_ARGS); rv = SECFailure; @@ -1325,6 +1336,10 @@ SSL_OptionSetDefault(PRInt32 which, PRIntn val) ssl_defaults.enableV2CompatibleHello = val; break; + case SSL_ENABLE_POST_HANDSHAKE_AUTH: + ssl_defaults.enablePostHandshakeAuth = val; + break; + default: PORT_SetError(SEC_ERROR_INVALID_ARGS); return SECFailure; @@ -4038,6 +4053,7 @@ struct { EXP(RecordLayerData), EXP(RecordLayerWriteCallback), EXP(SecretCallback), + EXP(SendCertificateRequest), EXP(SendSessionTicket), EXP(SetESNIKeyPair), EXP(SetMaxEarlyDataSize), diff --git a/lib/ssl/sslt.h b/lib/ssl/sslt.h index f489f882d..d389a6a2e 100644 --- a/lib/ssl/sslt.h +++ b/lib/ssl/sslt.h @@ -471,6 +471,7 @@ typedef enum { ssl_tls13_psk_key_exchange_modes_xtn = 45, ssl_tls13_ticket_early_data_info_xtn = 46, /* Deprecated. */ ssl_tls13_certificate_authorities_xtn = 47, + ssl_tls13_post_handshake_auth_xtn = 49, ssl_signature_algorithms_cert_xtn = 50, ssl_tls13_key_share_xtn = 51, ssl_next_proto_nego_xtn = 13172, /* Deprecated. */ diff --git a/lib/ssl/tls13con.c b/lib/ssl/tls13con.c index 898b36728..5daa48951 100644 --- a/lib/ssl/tls13con.c +++ b/lib/ssl/tls13con.c @@ -56,6 +56,7 @@ static SECStatus tls13_SendCertificate(sslSocket *ss); static SECStatus tls13_HandleCertificate( sslSocket *ss, PRUint8 *b, PRUint32 length); static SECStatus tls13_ReinjectHandshakeTranscript(sslSocket *ss); +static SECStatus tls13_SendCertificateRequest(sslSocket *ss); static SECStatus tls13_HandleCertificateRequest(sslSocket *ss, PRUint8 *b, PRUint32 length); static SECStatus @@ -104,6 +105,9 @@ static SECStatus tls13_ComputeFinished( PRBool sending, PRUint8 *output, unsigned int *outputLen, unsigned int maxOutputLen); static SECStatus tls13_SendClientSecondRound(sslSocket *ss); +static SECStatus tls13_SendClientSecondFlight(sslSocket *ss, + PRBool sendClientCert, + SSL3AlertDescription *sendAlert); static SECStatus tls13_FinishHandshake(sslSocket *ss); const char kHkdfLabelClient[] = "c"; @@ -826,6 +830,56 @@ tls13_HandleKeyUpdate(sslSocket *ss, PRUint8 *b, unsigned int length) } SECStatus +SSLExp_SendCertificateRequest(PRFileDesc *fd) +{ + SECStatus rv; + sslSocket *ss = ssl_FindSocket(fd); + if (!ss) { + return SECFailure; + } + + /* Not supported. */ + if (IS_DTLS(ss)) { + PORT_SetError(SSL_ERROR_FEATURE_NOT_SUPPORTED_FOR_VERSION); + return SECFailure; + } + + if (!ss->firstHsDone || ss->version < SSL_LIBRARY_VERSION_TLS_1_3) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + + if (ss->ssl3.clientCertRequested) { + PORT_SetError(PR_WOULD_BLOCK_ERROR); + return SECFailure; + } + + rv = TLS13_CHECK_HS_STATE(ss, SEC_ERROR_INVALID_ARGS, + idle_handshake); + if (rv != SECSuccess) { + return SECFailure; + } + + if (!ssl3_ExtensionNegotiated(ss, ssl_tls13_post_handshake_auth_xtn)) { + PORT_SetError(SSL_ERROR_MISSING_POST_HANDSHAKE_AUTH_EXTENSION); + return SECFailure; + } + + ssl_GetSSL3HandshakeLock(ss); + + rv = tls13_SendCertificateRequest(ss); + if (rv == SECSuccess) { + ssl_GetXmitBufLock(ss); + rv = ssl3_FlushHandshake(ss, 0); + ssl_ReleaseXmitBufLock(ss); + ss->ssl3.clientCertRequested = PR_TRUE; + } + + ssl_ReleaseSSL3HandshakeLock(ss); + return rv; +} + +SECStatus tls13_HandlePostHelloHandshakeMessage(sslSocket *ss, PRUint8 *b, PRUint32 length) { if (ss->sec.isServer && ss->ssl3.hs.zeroRttIgnore != ssl_0rtt_ignore_none) { @@ -2139,8 +2193,27 @@ tls13_SendCertificateRequest(sslSocket *ss) /* We should always have at least one of these. */ PORT_Assert(SSL_BUFFER_LEN(&extensionBuf) > 0); + /* Create a new request context for post-handshake authentication */ + if (ss->firstHsDone) { + PRUint8 context[16]; + SECItem contextItem = { siBuffer, context, sizeof(context) }; + + rv = PK11_GenerateRandom(context, sizeof(context)); + if (rv != SECSuccess) { + goto loser; + } + + SECITEM_FreeItem(&ss->xtnData.certReqContext, PR_FALSE); + rv = SECITEM_CopyItem(NULL, &ss->xtnData.certReqContext, &contextItem); + if (rv != SECSuccess) { + FATAL_ERROR(ss, SEC_ERROR_NO_MEMORY, internal_error); + goto loser; + } + } + rv = ssl3_AppendHandshakeHeader(ss, ssl_hs_certificate_request, - 1 + 0 + /* empty request context */ + 1 + /* request context length */ + ss->xtnData.certReqContext.len + 2 + /* extension length */ SSL_BUFFER_LEN(&extensionBuf)); if (rv != SECSuccess) { @@ -2148,7 +2221,8 @@ tls13_SendCertificateRequest(sslSocket *ss) } /* Context. */ - rv = ssl3_AppendHandshakeNumber(ss, 0, 1); + rv = ssl3_AppendHandshakeVariable(ss, ss->xtnData.certReqContext.data, + ss->xtnData.certReqContext.len, 1); if (rv != SECSuccess) { goto loser; /* err set by AppendHandshake. */ } @@ -2312,25 +2386,49 @@ tls13_HandleCertificateRequest(sslSocket *ss, PRUint8 *b, PRUint32 length) PORT_Assert(ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss)); /* Client */ - rv = TLS13_CHECK_HS_STATE(ss, SSL_ERROR_RX_UNEXPECTED_CERT_REQUEST, - wait_cert_request); + if (ss->opt.enablePostHandshakeAuth) { + rv = TLS13_CHECK_HS_STATE(ss, SSL_ERROR_RX_UNEXPECTED_CERT_REQUEST, + wait_cert_request, idle_handshake); + } else { + rv = TLS13_CHECK_HS_STATE(ss, SSL_ERROR_RX_UNEXPECTED_CERT_REQUEST, + wait_cert_request); + } if (rv != SECSuccess) { return SECFailure; } - PORT_Assert(ss->ssl3.clientCertChain == NULL); - PORT_Assert(ss->ssl3.clientCertificate == NULL); - PORT_Assert(ss->ssl3.clientPrivateKey == NULL); - PORT_Assert(!ss->ssl3.hs.clientCertRequested); + if (ss->firstHsDone) { + /* clean up anything left from previous handshake. */ + if (ss->ssl3.clientCertChain != NULL) { + CERT_DestroyCertificateList(ss->ssl3.clientCertChain); + ss->ssl3.clientCertChain = NULL; + } + if (ss->ssl3.clientCertificate != NULL) { + CERT_DestroyCertificate(ss->ssl3.clientCertificate); + ss->ssl3.clientCertificate = NULL; + } + if (ss->ssl3.clientPrivateKey != NULL) { + SECKEY_DestroyPrivateKey(ss->ssl3.clientPrivateKey); + ss->ssl3.clientPrivateKey = NULL; + } + SECITEM_FreeItem(&ss->xtnData.certReqContext, PR_FALSE); + ss->xtnData.certReqContext.data = NULL; + } else { + PORT_Assert(ss->ssl3.clientCertChain == NULL); + PORT_Assert(ss->ssl3.clientCertificate == NULL); + PORT_Assert(ss->ssl3.clientPrivateKey == NULL); + PORT_Assert(!ss->ssl3.hs.clientCertRequested); + PORT_Assert(ss->xtnData.certReqContext.data == NULL); + } rv = ssl3_ConsumeHandshakeVariable(ss, &context, 1, &b, &length); if (rv != SECSuccess) { return SECFailure; } - /* We don't support post-handshake client auth, the certificate request - * context must always be empty. */ - if (context.len > 0) { + /* Unless it is a post-handshake client auth, the certificate + * request context must be empty. */ + if (!ss->firstHsDone && context.len > 0) { FATAL_ERROR(ss, SSL_ERROR_RX_MALFORMED_CERT_REQUEST, illegal_parameter); return SECFailure; } @@ -2364,7 +2462,35 @@ tls13_HandleCertificateRequest(sslSocket *ss, PRUint8 *b, PRUint32 length) } ss->ssl3.hs.clientCertRequested = PR_TRUE; - TLS13_SET_HS_STATE(ss, wait_server_cert); + + if (ss->firstHsDone) { + SSL3AlertDescription sendAlert = no_alert; + + /* Request a client certificate. */ + rv = ssl3_CompleteHandleCertificateRequest( + ss, ss->xtnData.sigSchemes, ss->xtnData.numSigSchemes, + &ss->xtnData.certReqAuthorities); + if (rv != SECSuccess) { + FATAL_ERROR(ss, SEC_ERROR_LIBRARY_FAILURE, internal_error); + return rv; + } + + ssl_GetXmitBufLock(ss); + rv = tls13_SendClientSecondFlight(ss, !ss->ssl3.sendEmptyCert, + &sendAlert); + ssl_ReleaseXmitBufLock(ss); + if (rv != SECSuccess) { + if (sendAlert != no_alert) { + FATAL_ERROR(ss, PORT_GetError(), sendAlert); + } else { + LOG_ERROR(ss, PORT_GetError()); + } + return SECFailure; + } + PORT_Assert(ss->ssl3.hs.ws == idle_handshake); + } else { + TLS13_SET_HS_STATE(ss, wait_server_cert); + } return SECSuccess; } @@ -2900,8 +3026,13 @@ tls13_HandleCertificate(sslSocket *ss, PRUint8 *b, PRUint32 length) PORT_Assert(ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss)); if (ss->sec.isServer) { - rv = TLS13_CHECK_HS_STATE(ss, SSL_ERROR_RX_UNEXPECTED_CERTIFICATE, - wait_client_cert); + if (ss->ssl3.clientCertRequested) { + rv = TLS13_CHECK_HS_STATE(ss, SSL_ERROR_RX_UNEXPECTED_CERTIFICATE, + idle_handshake); + } else { + rv = TLS13_CHECK_HS_STATE(ss, SSL_ERROR_RX_UNEXPECTED_CERTIFICATE, + wait_client_cert); + } } else { rv = TLS13_CHECK_HS_STATE(ss, SSL_ERROR_RX_UNEXPECTED_CERTIFICATE, wait_cert_request, wait_server_cert); @@ -2919,10 +3050,12 @@ tls13_HandleCertificate(sslSocket *ss, PRUint8 *b, PRUint32 length) if (rv != SECSuccess) return SECFailure; - if (context.len) { - /* The context string MUST be empty */ - FATAL_ERROR(ss, SSL_ERROR_RX_MALFORMED_CERTIFICATE, illegal_parameter); - return SECFailure; + if (ss->ssl3.clientCertRequested) { + PORT_Assert(ss->sec.isServer); + if (SECITEM_CompareItem(&context, &ss->xtnData.certReqContext) != 0) { + FATAL_ERROR(ss, SSL_ERROR_RX_MALFORMED_CERTIFICATE, illegal_parameter); + return SECFailure; + } } rv = ssl3_ConsumeHandshakeVariable(ss, &certList, 3, &b, &length); @@ -3989,6 +4122,10 @@ tls13_HandleCertificateVerify(sslSocket *ss, PRUint8 *b, PRUint32 length) } } + if (ss->ssl3.clientCertRequested) { + PORT_Assert(ss->sec.isServer); + ss->ssl3.clientCertRequested = PR_FALSE; + } TLS13_SET_HS_STATE(ss, wait_finished); return SECSuccess; @@ -4290,12 +4427,18 @@ tls13_ServerHandleFinished(sslSocket *ss, PRUint8 *b, PRUint32 length) SSL_TRC(3, ("%d: TLS13[%d]: server handle finished handshake", SSL_GETPID(), ss->fd)); - rv = tls13_CommonHandleFinished(ss, ss->ssl3.hs.clientHsTrafficSecret, + rv = tls13_CommonHandleFinished(ss, + ss->firstHsDone ? ss->ssl3.hs.clientTrafficSecret : ss->ssl3.hs.clientHsTrafficSecret, b, length); if (rv != SECSuccess) { return SECFailure; } + if (ss->firstHsDone) { + TLS13_SET_HS_STATE(ss, idle_handshake); + return SECSuccess; + } + if (!tls13_ShouldRequestClientAuth(ss) && (ss->ssl3.hs.zeroRttState != ssl_0rtt_done)) { dtls_ReceivedFirstMessageInFlight(ss); @@ -4413,7 +4556,7 @@ tls13_SendClientSecondFlight(sslSocket *ss, PRBool sendClientCert, } } - rv = tls13_SendFinished(ss, ss->ssl3.hs.clientHsTrafficSecret); + rv = tls13_SendFinished(ss, ss->firstHsDone ? ss->ssl3.hs.clientTrafficSecret : ss->ssl3.hs.clientHsTrafficSecret); if (rv != SECSuccess) { return SECFailure; /* err code was set. */ } @@ -4868,7 +5011,8 @@ static const struct { { ssl_tls13_supported_versions_xtn, _M3(client_hello, server_hello, hello_retry_request) }, { ssl_record_size_limit_xtn, _M2(client_hello, encrypted_extensions) }, - { ssl_tls13_encrypted_sni_xtn, _M2(client_hello, encrypted_extensions) } + { ssl_tls13_encrypted_sni_xtn, _M2(client_hello, encrypted_extensions) }, + { ssl_tls13_post_handshake_auth_xtn, _M1(client_hello) } }; tls13ExtensionStatus diff --git a/lib/ssl/tls13con.h b/lib/ssl/tls13con.h index f3b2cb390..f6f52511d 100644 --- a/lib/ssl/tls13con.h +++ b/lib/ssl/tls13con.h @@ -131,6 +131,7 @@ SECStatus SSLExp_KeyUpdate(PRFileDesc *fd, PRBool requestUpdate); PRBool tls13_MaybeTls13(sslSocket *ss); SSLAEADCipher tls13_GetAead(const ssl3BulkCipherDef *cipherDef); void tls13_SetSpecRecordVersion(sslSocket *ss, ssl3CipherSpec *spec); +SECStatus SSLExp_SendCertificateRequest(PRFileDesc *fd); /* Use this instead of FATAL_ERROR when no alert shall be sent. */ #define LOG_ERROR(ss, prError) \ diff --git a/lib/ssl/tls13exthandle.c b/lib/ssl/tls13exthandle.c index 8ed18f69c..5ecc2e796 100644 --- a/lib/ssl/tls13exthandle.c +++ b/lib/ssl/tls13exthandle.c @@ -915,6 +915,37 @@ tls13_ServerHandleCookieXtn(const sslSocket *ss, TLSExtensionData *xtnData, return SECSuccess; } +SECStatus +tls13_ClientSendPostHandshakeAuthXtn(const sslSocket *ss, + TLSExtensionData *xtnData, + sslBuffer *buf, PRBool *added) +{ + SSL_TRC(3, ("%d: TLS13[%d]: send post_handshake_auth extension", + SSL_GETPID(), ss->fd)); + + *added = ss->opt.enablePostHandshakeAuth; + return SECSuccess; +} + +SECStatus +tls13_ServerHandlePostHandshakeAuthXtn(const sslSocket *ss, + TLSExtensionData *xtnData, + SECItem *data) +{ + SSL_TRC(3, ("%d: TLS13[%d]: handle post_handshake_auth extension", + SSL_GETPID(), ss->fd)); + + if (data->len) { + PORT_SetError(SSL_ERROR_RX_MALFORMED_CLIENT_HELLO); + return SECFailure; + } + + /* Keep track of negotiated extensions. */ + xtnData->negotiated[xtnData->numNegotiated++] = ssl_tls13_post_handshake_auth_xtn; + + return SECSuccess; +} + /* * enum { psk_ke(0), psk_dhe_ke(1), (255) } PskKeyExchangeMode; * diff --git a/lib/ssl/tls13exthandle.h b/lib/ssl/tls13exthandle.h index dd64b44ff..d36a4f935 100644 --- a/lib/ssl/tls13exthandle.h +++ b/lib/ssl/tls13exthandle.h @@ -93,5 +93,11 @@ SECStatus tls13_ClientSendEsniXtn(const sslSocket *ss, TLSExtensionData *xtnData SECStatus tls13_ServerHandleEsniXtn(const sslSocket *ss, TLSExtensionData *xtnData, SECItem *data); SECStatus tls13_ClientCheckEsniXtn(sslSocket *ss); +SECStatus tls13_ClientSendPostHandshakeAuthXtn(const sslSocket *ss, + TLSExtensionData *xtnData, + sslBuffer *buf, PRBool *added); +SECStatus tls13_ServerHandlePostHandshakeAuthXtn(const sslSocket *ss, + TLSExtensionData *xtnData, + SECItem *data); #endif |