Skip to content

Commit

Permalink
Bug 1471970, add support for post-handshake authentication, r=mt
Browse files Browse the repository at this point in the history
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

--HG--
extra : amend_source : 537d0155477340f2457fb63b88cdf1531c3005f9
  • Loading branch information
ueno committed Feb 20, 2019
1 parent c88db17 commit b73a7bd
Show file tree
Hide file tree
Showing 13 changed files with 583 additions and 22 deletions.
315 changes: 315 additions & 0 deletions gtests/ssl_gtest/ssl_auth_unittest.cc
Expand Up @@ -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
Expand Down
11 changes: 11 additions & 0 deletions lib/ssl/ssl.h
Expand Up @@ -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);
Expand Down
6 changes: 6 additions & 0 deletions lib/ssl/ssl3con.c
Expand Up @@ -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,
Expand Down Expand Up @@ -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) {
Expand Down
2 changes: 2 additions & 0 deletions lib/ssl/ssl3ext.c
Expand Up @@ -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 }
};
Expand Down Expand Up @@ -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 },
Expand Down
1 change: 1 addition & 0 deletions lib/ssl/sslerr.h
Expand Up @@ -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 */
Expand Down

0 comments on commit b73a7bd

Please sign in to comment.