Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Bug 1386191 - ClientHello callback for applications, r=ekr
--HG--
branch : NSS_TLS13_DRAFT19_BRANCH
extra : rebase_source : 76555bc5a80d7f5808ba1ca143611c7c0d1fd75c
extra : amend_source : 42a3fae051f45d3c50d6ee94f7041550ae22a9ab
  • Loading branch information
martinthomson committed Jul 16, 2017
1 parent 0115a13 commit bd277da
Show file tree
Hide file tree
Showing 19 changed files with 611 additions and 71 deletions.
1 change: 1 addition & 0 deletions cpputil/tls_parser.h
Expand Up @@ -49,6 +49,7 @@ const uint8_t kTlsAlertIllegalParameter = 47;
const uint8_t kTlsAlertDecodeError = 50;
const uint8_t kTlsAlertDecryptError = 51;
const uint8_t kTlsAlertProtocolVersion = 70;
const uint8_t kTlsAlertInternalError = 80;
const uint8_t kTlsAlertInappropriateFallback = 86;
const uint8_t kTlsAlertMissingExtension = 109;
const uint8_t kTlsAlertUnsupportedExtension = 110;
Expand Down
323 changes: 301 additions & 22 deletions gtests/ssl_gtest/ssl_hrr_unittest.cc
Expand Up @@ -187,6 +187,307 @@ TEST_P(TlsConnectTls13, RetryWithSameKeyShare) {
EXPECT_EQ(SSL_ERROR_ILLEGAL_PARAMETER_ALERT, client_->error_code());
}

TEST_P(TlsConnectTls13, RetryCallbackAccept) {
EnsureTlsSetup();

auto accept_hello = [](PRBool firstHello, const PRUint8* clientToken,
unsigned int clientTokenLen, PRUint8* appToken,
unsigned int* appTokenLen, unsigned int appTokenMax,
void* arg) {
auto* called = reinterpret_cast<bool*>(arg);
*called = true;

EXPECT_TRUE(firstHello);
EXPECT_EQ(0U, clientTokenLen);
return ssl_hello_retry_accept;
};

bool cb_run = false;
EXPECT_EQ(SECSuccess, SSL_HelloRetryRequestCallback(server_->ssl_fd(),
accept_hello, &cb_run));
Connect();
EXPECT_TRUE(cb_run);
}

TEST_P(TlsConnectTls13, RetryCallbackAcceptGroupMismatch) {
EnsureTlsSetup();

auto accept_hello_twice = [](PRBool firstHello, const PRUint8* clientToken,
unsigned int clientTokenLen, PRUint8* appToken,
unsigned int* appTokenLen,
unsigned int appTokenMax, void* arg) {
auto* called = reinterpret_cast<size_t*>(arg);
++*called;

EXPECT_EQ(0U, clientTokenLen);
return ssl_hello_retry_accept;
};

auto capture = std::make_shared<TlsExtensionCapture>(ssl_tls13_cookie_xtn);
capture->SetHandshakeTypes({kTlsHandshakeHelloRetryRequest});
server_->SetPacketFilter(capture);

static const std::vector<SSLNamedGroup> groups = {ssl_grp_ec_secp384r1};
server_->ConfigNamedGroups(groups);

size_t cb_run = 0;
EXPECT_EQ(SECSuccess, SSL_HelloRetryRequestCallback(
server_->ssl_fd(), accept_hello_twice, &cb_run));
Connect();
EXPECT_EQ(2U, cb_run);
EXPECT_TRUE(capture->captured()) << "expected a cookie in HelloRetryRequest";
}

TEST_P(TlsConnectTls13, RetryCallbackFail) {
EnsureTlsSetup();

auto fail_hello = [](PRBool firstHello, const PRUint8* clientToken,
unsigned int clientTokenLen, PRUint8* appToken,
unsigned int* appTokenLen, unsigned int appTokenMax,
void* arg) {
auto* called = reinterpret_cast<bool*>(arg);
*called = true;

EXPECT_TRUE(firstHello);
EXPECT_EQ(0U, clientTokenLen);
return ssl_hello_retry_fail;
};

bool cb_run = false;
EXPECT_EQ(SECSuccess, SSL_HelloRetryRequestCallback(server_->ssl_fd(),
fail_hello, &cb_run));
ConnectExpectAlert(server_, kTlsAlertHandshakeFailure);
server_->CheckErrorCode(SSL_ERROR_APPLICATION_ABORT);
EXPECT_TRUE(cb_run);
}

// Asking for retry twice isn't allowed.
TEST_P(TlsConnectTls13, RetryCallbackRequestHrrTwice) {
EnsureTlsSetup();

auto bad_callback = [](PRBool firstHello, const PRUint8* clientToken,
unsigned int clientTokenLen, PRUint8* appToken,
unsigned int* appTokenLen, unsigned int appTokenMax,
void* arg) -> SSLHelloRetryRequestAction {
return ssl_hello_retry_request;
};
EXPECT_EQ(SECSuccess, SSL_HelloRetryRequestCallback(server_->ssl_fd(),
bad_callback, NULL));
ConnectExpectAlert(server_, kTlsAlertInternalError);
server_->CheckErrorCode(SSL_ERROR_APP_CALLBACK_ERROR);
}

// Accepting the CH and modifying the token isn't allowed.
TEST_P(TlsConnectTls13, RetryCallbackAcceptAndSetToken) {
EnsureTlsSetup();

auto bad_callback = [](PRBool firstHello, const PRUint8* clientToken,
unsigned int clientTokenLen, PRUint8* appToken,
unsigned int* appTokenLen, unsigned int appTokenMax,
void* arg) -> SSLHelloRetryRequestAction {
*appTokenLen = 1;
return ssl_hello_retry_accept;
};
EXPECT_EQ(SECSuccess, SSL_HelloRetryRequestCallback(server_->ssl_fd(),
bad_callback, NULL));
ConnectExpectAlert(server_, kTlsAlertInternalError);
server_->CheckErrorCode(SSL_ERROR_APP_CALLBACK_ERROR);
}

// As above, but with reject.
TEST_P(TlsConnectTls13, RetryCallbackRejectAndSetToken) {
EnsureTlsSetup();

auto bad_callback = [](PRBool firstHello, const PRUint8* clientToken,
unsigned int clientTokenLen, PRUint8* appToken,
unsigned int* appTokenLen, unsigned int appTokenMax,
void* arg) -> SSLHelloRetryRequestAction {
*appTokenLen = 1;
return ssl_hello_retry_fail;
};
EXPECT_EQ(SECSuccess, SSL_HelloRetryRequestCallback(server_->ssl_fd(),
bad_callback, NULL));
ConnectExpectAlert(server_, kTlsAlertInternalError);
server_->CheckErrorCode(SSL_ERROR_APP_CALLBACK_ERROR);
}

// This is a (pretend) buffer overflow.
TEST_P(TlsConnectTls13, RetryCallbackSetTooLargeToken) {
EnsureTlsSetup();

auto bad_callback = [](PRBool firstHello, const PRUint8* clientToken,
unsigned int clientTokenLen, PRUint8* appToken,
unsigned int* appTokenLen, unsigned int appTokenMax,
void* arg) -> SSLHelloRetryRequestAction {
*appTokenLen = appTokenMax + 1;
return ssl_hello_retry_accept;
};
EXPECT_EQ(SECSuccess, SSL_HelloRetryRequestCallback(server_->ssl_fd(),
bad_callback, NULL));
ConnectExpectAlert(server_, kTlsAlertInternalError);
server_->CheckErrorCode(SSL_ERROR_APP_CALLBACK_ERROR);
}

SSLHelloRetryRequestAction RetryHello(PRBool firstHello,
const PRUint8* clientToken,
unsigned int clientTokenLen,
PRUint8* appToken,
unsigned int* appTokenLen,
unsigned int appTokenMax, void* arg) {
auto* called = reinterpret_cast<size_t*>(arg);
++*called;

EXPECT_EQ(0U, clientTokenLen);
return firstHello ? ssl_hello_retry_request : ssl_hello_retry_accept;
}

TEST_P(TlsConnectTls13, RetryCallbackRetry) {
EnsureTlsSetup();

auto capture_hrr = std::make_shared<TlsInspectorRecordHandshakeMessage>(
ssl_hs_hello_retry_request);
auto capture_key_share =
std::make_shared<TlsExtensionCapture>(ssl_tls13_key_share_xtn);
capture_key_share->SetHandshakeTypes({kTlsHandshakeHelloRetryRequest});
std::vector<std::shared_ptr<PacketFilter>> chain = {capture_hrr,
capture_key_share};
server_->SetPacketFilter(std::make_shared<ChainedPacketFilter>(chain));

size_t cb_called = 0;
EXPECT_EQ(SECSuccess, SSL_HelloRetryRequestCallback(server_->ssl_fd(),
RetryHello, &cb_called));

// Do the first message exchange.
client_->StartConnect();
server_->StartConnect();
client_->Handshake();
server_->Handshake();

EXPECT_EQ(1U, cb_called) << "callback should be called once here";
EXPECT_LT(0U, capture_hrr->buffer().len()) << "HelloRetryRequest expected";
EXPECT_FALSE(capture_key_share->captured())
<< "no key_share extension expected";

auto capture_cookie =
std::make_shared<TlsExtensionCapture>(ssl_tls13_cookie_xtn);
client_->SetPacketFilter(capture_cookie);

Connect();
EXPECT_EQ(2U, cb_called);
EXPECT_TRUE(capture_cookie->captured()) << "should have a cookie";
}

// The callback should be run even if we have another reason to send
// HelloRetryRequest. In this case, the server sends HRR because the server
// wants a P-384 key share and the client didn't offer one.
TEST_P(TlsConnectTls13, RetryCallbackRetryWithGroupMismatch) {
EnsureTlsSetup();

auto capture = std::make_shared<TlsExtensionCapture>(ssl_tls13_cookie_xtn);
capture->SetHandshakeTypes({kTlsHandshakeHelloRetryRequest});
server_->SetPacketFilter(capture);

static const std::vector<SSLNamedGroup> groups = {ssl_grp_ec_secp384r1};
server_->ConfigNamedGroups(groups);

size_t cb_called = 0;
EXPECT_EQ(SECSuccess, SSL_HelloRetryRequestCallback(server_->ssl_fd(),
RetryHello, &cb_called));
Connect();
EXPECT_EQ(2U, cb_called);
EXPECT_TRUE(capture->captured()) << "cookie expected";
}

static const uint8_t kApplicationToken[] = {0x92, 0x44, 0x00};

SSLHelloRetryRequestAction RetryHelloWithToken(
PRBool firstHello, const PRUint8* clientToken, unsigned int clientTokenLen,
PRUint8* appToken, unsigned int* appTokenLen, unsigned int appTokenMax,
void* arg) {
auto* called = reinterpret_cast<size_t*>(arg);
++*called;

if (firstHello) {
memcpy(appToken, kApplicationToken, sizeof(kApplicationToken));
*appTokenLen = sizeof(kApplicationToken);
return ssl_hello_retry_request;
}

EXPECT_EQ(DataBuffer(kApplicationToken, sizeof(kApplicationToken)),
DataBuffer(clientToken, static_cast<size_t>(clientTokenLen)));
return ssl_hello_retry_accept;
}

TEST_P(TlsConnectTls13, RetryCallbackRetryWithToken) {
EnsureTlsSetup();

auto capture_key_share =
std::make_shared<TlsExtensionCapture>(ssl_tls13_key_share_xtn);
capture_key_share->SetHandshakeTypes({kTlsHandshakeHelloRetryRequest});
server_->SetPacketFilter(capture_key_share);

size_t cb_called = 0;
EXPECT_EQ(SECSuccess,
SSL_HelloRetryRequestCallback(server_->ssl_fd(),
RetryHelloWithToken, &cb_called));
Connect();
EXPECT_EQ(2U, cb_called);
EXPECT_FALSE(capture_key_share->captured()) << "no key share expected";
}

TEST_P(TlsConnectTls13, RetryCallbackRetryWithTokenAndGroupMismatch) {
EnsureTlsSetup();

static const std::vector<SSLNamedGroup> groups = {ssl_grp_ec_secp384r1};
server_->ConfigNamedGroups(groups);

auto capture_key_share =
std::make_shared<TlsExtensionCapture>(ssl_tls13_key_share_xtn);
capture_key_share->SetHandshakeTypes({kTlsHandshakeHelloRetryRequest});
server_->SetPacketFilter(capture_key_share);

size_t cb_called = 0;
EXPECT_EQ(SECSuccess,
SSL_HelloRetryRequestCallback(server_->ssl_fd(),
RetryHelloWithToken, &cb_called));
Connect();
EXPECT_EQ(2U, cb_called);
EXPECT_TRUE(capture_key_share->captured()) << "key share expected";
}

SSLHelloRetryRequestAction CheckTicketToken(
PRBool firstHello, const PRUint8* clientToken, unsigned int clientTokenLen,
PRUint8* appToken, unsigned int* appTokenLen, unsigned int appTokenMax,
void* arg) {
auto* called = reinterpret_cast<bool*>(arg);
*called = true;

EXPECT_TRUE(firstHello);
EXPECT_EQ(DataBuffer(kApplicationToken, sizeof(kApplicationToken)),
DataBuffer(clientToken, static_cast<size_t>(clientTokenLen)));
return ssl_hello_retry_accept;
}

// Stream because SSL_SendSessionTicket only supports that.
TEST_F(TlsConnectStreamTls13, RetryCallbackWithSessionTicketToken) {
ConfigureSessionCache(RESUME_BOTH, RESUME_TICKET);
Connect();
EXPECT_EQ(SECSuccess,
SSL_SendSessionTicket(server_->ssl_fd(), kApplicationToken,
sizeof(kApplicationToken)));
SendReceive();

Reset();
ConfigureSessionCache(RESUME_BOTH, RESUME_TICKET);
ExpectResumption(RESUME_TICKET);

bool cb_run = false;
EXPECT_EQ(SECSuccess, SSL_HelloRetryRequestCallback(
server_->ssl_fd(), CheckTicketToken, &cb_run));
Connect();
EXPECT_TRUE(cb_run);
}

// Stream because the server doesn't consume the alert and terminate.
TEST_F(TlsConnectStreamTls13, RetryWithDifferentCipherSuite) {
EnsureTlsSetup();
Expand Down Expand Up @@ -352,28 +653,6 @@ TEST_P(HelloRetryRequestAgentTest, HandleNoopHelloRetryRequest) {
SSL_ERROR_RX_MALFORMED_HELLO_RETRY_REQUEST);
}

TEST_P(HelloRetryRequestAgentTest, HandleHelloRetryRequestCookie) {
const uint8_t canned_cookie_hrr[] = {
static_cast<uint8_t>(ssl_tls13_cookie_xtn >> 8),
static_cast<uint8_t>(ssl_tls13_cookie_xtn),
0,
5, // length of cookie extension
0,
3, // cookie value length
0xc0,
0x0c,
0x13};
DataBuffer hrr;
MakeCannedHrr(canned_cookie_hrr, sizeof(canned_cookie_hrr), &hrr);
auto capture = std::make_shared<TlsExtensionCapture>(ssl_tls13_cookie_xtn);
agent_->SetPacketFilter(capture);
ProcessMessage(hrr, TlsAgent::STATE_CONNECTING);
const size_t cookie_pos = 2 + 2; // cookie_xtn, extension len
DataBuffer cookie(canned_cookie_hrr + cookie_pos,
sizeof(canned_cookie_hrr) - cookie_pos);
EXPECT_EQ(cookie, capture->extension());
}

INSTANTIATE_TEST_CASE_P(HelloRetryRequestAgentTests, HelloRetryRequestAgentTest,
::testing::Combine(TlsConnectTestBase::kTlsVariantsAll,
TlsConnectTestBase::kTlsV13));
Expand Down
6 changes: 6 additions & 0 deletions lib/ssl/SSLerrs.h
Expand Up @@ -519,3 +519,9 @@ ER3(SSL_ERROR_RX_MALFORMED_END_OF_EARLY_DATA, (SSL_ERROR_BASE + 163),

ER3(SSL_ERROR_UNSUPPORTED_EXPERIMENTAL_API, (SSL_ERROR_BASE + 164),
"An experimental API was called, but not supported.")

ER3(SSL_ERROR_APPLICATION_ABORT, (SSL_ERROR_BASE + 165),
"SSL handshake aborted by the application.")

ER3(SSL_ERROR_APP_CALLBACK_ERROR, (SSL_ERROR_BASE + 166),
"An application callback produced an invalid response.")
4 changes: 4 additions & 0 deletions lib/ssl/ssl3con.c
Expand Up @@ -8444,6 +8444,10 @@ ssl3_HandleClientHello(sslSocket *ss, PRUint8 *b, PRUint32 length)
}
#endif

if (ssl3_FindExtension(ss, ssl_tls13_cookie_xtn)) {
ss->ssl3.hs.helloRetry = PR_TRUE;
}

/* Now parse the rest of the extensions. */
rv = ssl3_HandleParsedExtensions(ss, ssl_hs_client_hello);
if (rv != SECSuccess) {
Expand Down
3 changes: 2 additions & 1 deletion lib/ssl/ssl3ext.c
Expand Up @@ -178,7 +178,7 @@ static const struct {
{ ssl_tls13_pre_shared_key_xtn, ssl_ext_native_only },
{ ssl_tls13_early_data_xtn, ssl_ext_native_only },
{ ssl_tls13_supported_versions_xtn, ssl_ext_native_only },
{ ssl_tls13_cookie_xtn, ssl_ext_native },
{ ssl_tls13_cookie_xtn, ssl_ext_native_only },
{ ssl_tls13_psk_key_exchange_modes_xtn, ssl_ext_native_only },
{ ssl_tls13_ticket_early_data_info_xtn, ssl_ext_native_only },
{ ssl_next_proto_nego_xtn, ssl_ext_none },
Expand Down Expand Up @@ -946,6 +946,7 @@ ssl3_DestroyExtensionData(TLSExtensionData *xtnData)
SECITEM_FreeItem(&xtnData->nextProto, PR_FALSE);
tls13_DestroyKeyShares(&xtnData->remoteKeyShares);
SECITEM_FreeItem(&xtnData->certReqContext, PR_FALSE);
SECITEM_FreeItem(&xtnData->applicationToken, PR_FALSE);
if (xtnData->certReqAuthorities.arena) {
PORT_FreeArena(xtnData->certReqAuthorities.arena, PR_FALSE);
xtnData->certReqAuthorities.arena = NULL;
Expand Down
3 changes: 3 additions & 0 deletions lib/ssl/ssl3ext.h
Expand Up @@ -95,6 +95,9 @@ struct TLSExtensionDataStr {
PRUint32 ticketAge; /* Used to accept early data. */
SECItem cookie; /* HRR Cookie. */
const sslNamedGroupDef *selectedGroup; /* For HRR. */
/* The application token contains a value that was passed to the client via
* a session ticket, or the cookie in a HelloRetryRequest. */
SECItem applicationToken;
};

typedef struct TLSExtensionStr {
Expand Down

0 comments on commit bd277da

Please sign in to comment.