summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaiki Ueno <dueno@redhat.com>2019-02-20 10:25:50 +0100
committerDaiki Ueno <dueno@redhat.com>2019-02-20 10:25:50 +0100
commitae5cbca6d86cc61cd46b41457dee1d60b66a87dd (patch)
tree50fee50548af31aec758e19ec31ae7a8195c46ea
parenta9b4d3dbe96c8889dc331fe39490402cb40e5f56 (diff)
downloadnss-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.cc315
-rw-r--r--lib/ssl/ssl.h11
-rw-r--r--lib/ssl/ssl3con.c6
-rw-r--r--lib/ssl/ssl3ext.c2
-rw-r--r--lib/ssl/sslerr.h1
-rw-r--r--lib/ssl/sslexp.h21
-rw-r--r--lib/ssl/sslimpl.h6
-rw-r--r--lib/ssl/sslsock.c18
-rw-r--r--lib/ssl/sslt.h1
-rw-r--r--lib/ssl/tls13con.c186
-rw-r--r--lib/ssl/tls13con.h1
-rw-r--r--lib/ssl/tls13exthandle.c31
-rw-r--r--lib/ssl/tls13exthandle.h6
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