Bug 1386191 - ClientHello callback for applications, r=ekr NSS_TLS13_DRAFT19_BRANCH
authorMartin Thomson <martin.thomson@gmail.com>
Sun, 16 Jul 2017 14:45:52 +0200
branchNSS_TLS13_DRAFT19_BRANCH
changeset 13506 867ef08a8ad7e2b590b008e65fe4ea78191d6224
parent 13503 b7d4f0321b9bea91dbed884f0b5521ebc41166ed
child 13516 f8e20a7a01b8b709f10e19660c20d2dab601e28a
push id2306
push usermartin.thomson@gmail.com
push dateMon, 07 Aug 2017 05:55:58 +0000
reviewersekr
bugs1386191
Bug 1386191 - ClientHello callback for applications, r=ekr
cpputil/tls_parser.h
gtests/ssl_gtest/ssl_hrr_unittest.cc
lib/ssl/SSLerrs.h
lib/ssl/ssl3con.c
lib/ssl/ssl3ext.c
lib/ssl/ssl3ext.h
lib/ssl/ssl3exthandle.c
lib/ssl/ssl3exthandle.h
lib/ssl/sslencode.c
lib/ssl/sslencode.h
lib/ssl/sslerr.h
lib/ssl/sslexp.h
lib/ssl/sslimpl.h
lib/ssl/sslsock.c
lib/ssl/tls13con.c
lib/ssl/tls13con.h
lib/ssl/tls13exthandle.c
lib/ssl/tls13hashstate.c
lib/ssl/tls13hashstate.h
--- a/cpputil/tls_parser.h
+++ b/cpputil/tls_parser.h
@@ -44,16 +44,17 @@ const uint8_t kTlsAlertCloseNotify = 0;
 const uint8_t kTlsAlertUnexpectedMessage = 10;
 const uint8_t kTlsAlertBadRecordMac = 20;
 const uint8_t kTlsAlertRecordOverflow = 22;
 const uint8_t kTlsAlertHandshakeFailure = 40;
 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;
 const uint8_t kTlsAlertUnrecognizedName = 112;
 const uint8_t kTlsAlertNoApplicationProtocol = 120;
 
 const uint8_t kTlsFakeChangeCipherSpec[] = {
     kTlsChangeCipherSpecType,  // Type
--- a/gtests/ssl_gtest/ssl_hrr_unittest.cc
+++ b/gtests/ssl_gtest/ssl_hrr_unittest.cc
@@ -182,16 +182,317 @@ TEST_P(TlsConnectTls13, RetryWithSameKey
   static const std::vector<SSLNamedGroup> groups = {ssl_grp_ec_secp384r1,
                                                     ssl_grp_ec_secp521r1};
   server_->ConfigNamedGroups(groups);
   ConnectExpectAlert(server_, kTlsAlertIllegalParameter);
   EXPECT_EQ(SSL_ERROR_BAD_2ND_CLIENT_HELLO, server_->error_code());
   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();
   // Force a HelloRetryRequest.
   static const std::vector<SSLNamedGroup> groups = {ssl_grp_ec_secp384r1};
   server_->ConfigNamedGroups(groups);
   // Then switch out the default suite (TLS_AES_128_GCM_SHA256).
   server_->SetPacketFilter(std::make_shared<SelectedCipherSuiteReplacer>(
@@ -347,38 +648,16 @@ TEST_P(HelloRetryRequestAgentTest, Handl
 TEST_P(HelloRetryRequestAgentTest, HandleNoopHelloRetryRequest) {
   DataBuffer hrr;
   MakeCannedHrr(nullptr, 0U, &hrr);
   ExpectAlert(kTlsAlertDecodeError);
   ProcessMessage(hrr, TlsAgent::STATE_ERROR,
                  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));
 #ifndef NSS_DISABLE_TLS_1_3
 INSTANTIATE_TEST_CASE_P(HelloRetryRequestKeyExchangeTests, TlsKeyExchange13,
                         ::testing::Combine(TlsConnectTestBase::kTlsVariantsAll,
                                            TlsConnectTestBase::kTlsV13));
 #endif
--- a/lib/ssl/SSLerrs.h
+++ b/lib/ssl/SSLerrs.h
@@ -514,8 +514,14 @@ ER3(SSL_ERROR_TOO_MUCH_EARLY_DATA, (SSL_
 ER3(SSL_ERROR_RX_UNEXPECTED_END_OF_EARLY_DATA, (SSL_ERROR_BASE + 162),
     "SSL received an unexpected End of Early Data message.")
 
 ER3(SSL_ERROR_RX_MALFORMED_END_OF_EARLY_DATA, (SSL_ERROR_BASE + 163),
     "SSL received a malformed End of Early Data message.")
 
 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.")
--- a/lib/ssl/ssl3con.c
+++ b/lib/ssl/ssl3con.c
@@ -8439,16 +8439,20 @@ ssl3_HandleClientHello(sslSocket *ss, PR
                 break;
             default:
                 /* Do not change random. */
                 break;
         }
     }
 #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) {
         goto loser; /* malformed */
     }
 
     /* If the ClientHello version is less than our maximum version, check for a
      * TLS_FALLBACK_SCSV and reject the connection if found. */
--- a/lib/ssl/ssl3ext.c
+++ b/lib/ssl/ssl3ext.c
@@ -173,17 +173,17 @@ static const struct {
     { ssl_signed_cert_timestamp_xtn, ssl_ext_native },
     { ssl_padding_xtn, ssl_ext_native },
     { ssl_extended_master_secret_xtn, ssl_ext_native_only },
     { ssl_session_ticket_xtn, ssl_ext_native_only },
     { ssl_tls13_key_share_xtn, ssl_ext_native_only },
     { 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 },
     { ssl_renegotiation_info_xtn, ssl_ext_native }
 };
 
 static SSLExtensionSupport
 ssl_GetExtensionSupport(PRUint16 type)
@@ -941,16 +941,17 @@ ssl3_InitExtensionData(TLSExtensionData 
 void
 ssl3_DestroyExtensionData(TLSExtensionData *xtnData)
 {
     ssl3_FreeSniNameArray(xtnData);
     PORT_Free(xtnData->sigSchemes);
     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;
     }
     PORT_Free(xtnData->advertised);
 }
 
 /* Free everything that has been allocated and then reset back to
--- a/lib/ssl/ssl3ext.h
+++ b/lib/ssl/ssl3ext.h
@@ -90,16 +90,19 @@ struct TLSExtensionDataStr {
     PRCList remoteKeyShares;    /* The other side's public keys (TLS 1.3) */
 
     /* The following are used by a TLS 1.3 server. */
     SECItem pskBinder;                     /* The binder for the first PSK. */
     unsigned int pskBindersLen;            /* The length of the binders. */
     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 {
     PRCList link;  /* The linked list link */
     PRUint16 type; /* Extension type */
     SECItem data;  /* Pointers into the handshake data. */
 } TLSExtension;
 
--- a/lib/ssl/ssl3exthandle.c
+++ b/lib/ssl/ssl3exthandle.c
@@ -1299,34 +1299,36 @@ ssl_CreateSIDFromTicket(sslSocket *ss, c
 
 loser:
     ssl_FreeSID(sid);
     return SECFailure;
 }
 
 /* Generic ticket processing code, common to all TLS versions. */
 SECStatus
-ssl3_ProcessSessionTicketCommon(sslSocket *ss, SECItem *data)
+ssl3_ProcessSessionTicketCommon(sslSocket *ss, const SECItem *ticket,
+                                SECItem *appToken)
 {
     SECItem decryptedTicket = { siBuffer, NULL, 0 };
     SessionTicket parsedTicket;
+    sslSessionID *sid = NULL;
     SECStatus rv;
 
     if (ss->sec.ci.sid != NULL) {
         ss->sec.uncache(ss->sec.ci.sid);
         ssl_FreeSID(ss->sec.ci.sid);
         ss->sec.ci.sid = NULL;
     }
 
-    if (!SECITEM_AllocItem(NULL, &decryptedTicket, data->len)) {
+    if (!SECITEM_AllocItem(NULL, &decryptedTicket, ticket->len)) {
         return SECFailure;
     }
 
     /* Decrypt the ticket. */
-    rv = ssl_SelfEncryptUnprotect(ss, data->data, data->len,
+    rv = ssl_SelfEncryptUnprotect(ss, ticket->data, ticket->len,
                                   decryptedTicket.data,
                                   &decryptedTicket.len,
                                   decryptedTicket.len);
     if (rv != SECSuccess) {
         SECITEM_ZfreeItem(&decryptedTicket, PR_FALSE);
 
         /* Fail with no ticket if we're not a recipient. Otherwise
          * it's a hard failure. */
@@ -1348,36 +1350,46 @@ ssl3_ProcessSessionTicketCommon(sslSocke
         ssl3stats = SSL_GetStatistics();
         SSL_AtomicIncrementLong(&ssl3stats->hch_sid_ticket_parse_failures);
         goto loser; /* code already set */
     }
 
     /* Use the ticket if it is valid and unexpired. */
     if (parsedTicket.timestamp + ssl_ticket_lifetime * PR_USEC_PER_SEC >
         ssl_TimeUsec()) {
-        sslSessionID *sid;
 
-        rv = ssl_CreateSIDFromTicket(ss, data, &parsedTicket, &sid);
+        rv = ssl_CreateSIDFromTicket(ss, ticket, &parsedTicket, &sid);
         if (rv != SECSuccess) {
             goto loser; /* code already set */
         }
+        if (appToken && parsedTicket.applicationToken.len) {
+            rv = SECITEM_CopyItem(NULL, appToken,
+                                  &parsedTicket.applicationToken);
+            if (rv != SECSuccess) {
+                goto loser; /* code already set */
+            }
+        }
+
         ss->statelessResume = PR_TRUE;
         ss->sec.ci.sid = sid;
 
         /* We have the baseline value for the obfuscated ticket age here.  Save
          * that in xtnData temporarily.  This value is updated in
          * tls13_ServerHandlePreSharedKeyXtn with the final estimate. */
         ss->xtnData.ticketAge = parsedTicket.ticketAgeBaseline;
     }
 
     SECITEM_ZfreeItem(&decryptedTicket, PR_FALSE);
     PORT_Memset(&parsedTicket, 0, sizeof(parsedTicket));
     return SECSuccess;
 
 loser:
+    if (sid) {
+        ssl_FreeSID(sid);
+    }
     SECITEM_ZfreeItem(&decryptedTicket, PR_FALSE);
     PORT_Memset(&parsedTicket, 0, sizeof(parsedTicket));
     return SECFailure;
 }
 
 SECStatus
 ssl3_ServerHandleSessionTicketXtn(const sslSocket *ss, TLSExtensionData *xtnData,
                                   SECItem *data)
@@ -1401,17 +1413,18 @@ ssl3_ServerHandleSessionTicketXtn(const 
      * lenient about some parse errors, falling back to a fullshake
      * instead of terminating the current connection.
      */
     if (data->len == 0) {
         xtnData->emptySessionTicket = PR_TRUE;
         return SECSuccess;
     }
 
-    return ssl3_ProcessSessionTicketCommon(CONST_CAST(sslSocket, ss), data);
+    return ssl3_ProcessSessionTicketCommon(CONST_CAST(sslSocket, ss), data,
+                                           NULL);
 }
 
 /* Extension format:
  * Extension number:   2 bytes
  * Extension length:   2 bytes
  * Verify Data Length: 1 byte
  * Verify Data (TLS): 12 bytes (client) or 24 bytes (server)
  * Verify Data (SSL): 36 bytes (client) or 72 bytes (server)
--- a/lib/ssl/ssl3exthandle.h
+++ b/lib/ssl/ssl3exthandle.h
@@ -84,17 +84,18 @@ SECStatus ssl3_ServerHandleSignedCertTim
                                                   TLSExtensionData *xtnData,
                                                   SECItem *data);
 SECStatus ssl3_SendExtendedMasterSecretXtn(const sslSocket *ss,
                                            TLSExtensionData *xtnData,
                                            sslBuffer *buf, PRBool *added);
 SECStatus ssl3_HandleExtendedMasterSecretXtn(const sslSocket *ss,
                                              TLSExtensionData *xtnData,
                                              SECItem *data);
-SECStatus ssl3_ProcessSessionTicketCommon(sslSocket *ss, SECItem *data);
+SECStatus ssl3_ProcessSessionTicketCommon(sslSocket *ss, const SECItem *ticket,
+                                          /* out */ SECItem *appToken);
 SECStatus ssl3_ClientSendServerNameXtn(const sslSocket *ss,
                                        TLSExtensionData *xtnData,
                                        sslBuffer *buf, PRBool *added);
 SECStatus ssl3_HandleServerNameXtn(const sslSocket *ss,
                                    TLSExtensionData *xtnData,
                                    SECItem *data);
 SECStatus ssl_HandleSupportedGroupsXtn(const sslSocket *ss,
                                        TLSExtensionData *xtnData,
--- a/lib/ssl/sslencode.c
+++ b/lib/ssl/sslencode.c
@@ -105,17 +105,17 @@ sslBuffer_Clear(sslBuffer *b)
         PORT_Free(b->buf);
         b->buf = NULL;
         b->len = 0;
         b->space = 0;
     }
 }
 
 SECStatus
-ssl3_AppendToItem(SECItem *item, const unsigned char *buf, unsigned int size)
+ssl3_AppendToItem(SECItem *item, const PRUint8 *buf, unsigned int size)
 {
     if (size > item->len) {
         PORT_SetError(SEC_ERROR_INVALID_ARGS);
         return SECFailure;
     }
 
     PORT_Memcpy(item->data, buf, size);
     item->data += size;
@@ -130,17 +130,17 @@ ssl3_AppendNumberToItem(SECItem *item, P
     PRUint8 b[sizeof(num)];
 
     ssl_EncodeUintX(num, size, b);
     rv = ssl3_AppendToItem(item, &b[0], size);
     return rv;
 }
 
 SECStatus
-ssl3_ConsumeFromItem(SECItem *item, unsigned char **buf, unsigned int size)
+ssl3_ConsumeFromItem(SECItem *item, PRUint8 **buf, unsigned int size)
 {
     if (size > item->len) {
         PORT_SetError(SEC_ERROR_BAD_DATA);
         return SECFailure;
     }
 
     *buf = item->data;
     item->data += size;
--- a/lib/ssl/sslencode.h
+++ b/lib/ssl/sslencode.h
@@ -28,17 +28,17 @@ SECStatus sslBuffer_AppendVariable(sslBu
 SECStatus sslBuffer_AppendBuffer(sslBuffer *b, const sslBuffer *append);
 SECStatus sslBuffer_AppendBufferVariable(sslBuffer *b, const sslBuffer *append,
                                          unsigned int size);
 void sslBuffer_Clear(sslBuffer *b);
 
 /* All of these functions modify the underlying SECItem, and so should
  * be performed on a shallow copy.*/
 SECStatus ssl3_AppendToItem(SECItem *item,
-                            const unsigned char *buf, PRUint32 bytes);
+                            const PRUint8 *buf, PRUint32 bytes);
 SECStatus ssl3_AppendNumberToItem(SECItem *item,
                                   PRUint64 num, unsigned int size);
 SECStatus ssl3_ConsumeFromItem(SECItem *item,
-                               unsigned char **buf, unsigned int size);
+                               PRUint8 **buf, unsigned int size);
 SECStatus ssl3_ConsumeNumberFromItem(SECItem *item,
                                      PRUint32 *num, unsigned int size);
 
 #endif /* __sslencode_h_ */
--- a/lib/ssl/sslerr.h
+++ b/lib/ssl/sslerr.h
@@ -247,15 +247,18 @@ typedef enum {
     SSL_ERROR_MISSING_PSK_KEY_EXCHANGE_MODES = (SSL_ERROR_BASE + 159),
     SSL_ERROR_DOWNGRADE_WITH_EARLY_DATA = (SSL_ERROR_BASE + 160),
     SSL_ERROR_TOO_MUCH_EARLY_DATA = (SSL_ERROR_BASE + 161),
     SSL_ERROR_RX_UNEXPECTED_END_OF_EARLY_DATA = (SSL_ERROR_BASE + 162),
     SSL_ERROR_RX_MALFORMED_END_OF_EARLY_DATA = (SSL_ERROR_BASE + 163),
 
     SSL_ERROR_UNSUPPORTED_EXPERIMENTAL_API = (SSL_ERROR_BASE + 164),
 
+    SSL_ERROR_APPLICATION_ABORT = (SSL_ERROR_BASE + 165),
+    SSL_ERROR_APP_CALLBACK_ERROR = (SSL_ERROR_BASE + 166),
+
     SSL_ERROR_END_OF_LIST   /* let the c compiler determine the value of this. */
 } SSLErrorCodes;
 #endif /* NO_SECURITY_ERROR_ENUM */
 
 /* clang-format on */
 
 #endif /* __SSL_ERR_H_ */
--- a/lib/ssl/sslexp.h
+++ b/lib/ssl/sslexp.h
@@ -237,11 +237,107 @@ typedef SECStatus(PR_CALLBACK *SSLExtens
  * NewSessionTicket message.
  */
 #define SSL_SendSessionTicket(fd, appToken, appTokenLen)              \
     SSL_EXPERIMENTAL_API("SSL_SendSessionTicket",                     \
                          (PRFileDesc * _fd, const PRUint8 *_appToken, \
                           unsigned int _appTokenLen),                 \
                          (fd, appToken, appTokenLen))
 
+/*
+ * A stateless retry handler gives an application some control over NSS handling
+ * of ClientHello messages.
+ *
+ * SSL_HelloRetryRequestCallback() installs a callback that allows an
+ * application to control how NSS sends HelloRetryRequest messages.  This
+ * handler is only used on servers and will only be called if the server selects
+ * TLS 1.3.  Support for older TLS versions could be added in other releases.
+ *
+ * The SSLHelloRetryRequestCallback is invoked during the processing of a
+ * TLS 1.3 ClientHello message.  It takes the following arguments:
+ *
+ * - |firstHello| indicates if the NSS believes that this is an initial
+ *   ClientHello.  An initial ClientHello will never include a cookie extension,
+ *   though it may contain a session ticket.
+ *
+ * - |clientToken| includes a token previously provided by the application.  If
+ *   |clientTokenLen| is 0, then |clientToken| may be NULL.
+ *
+ *   - If |firstHello| is PR_FALSE, the value that was provided in the
+ *     |retryToken| outparam of previous invocations of this callback will be
+ *     present here.
+ *
+ *   - If |firstHello| is PR_TRUE, and the handshake is resuming a session, then
+ *     this will contain any value that was passed in the |token| parameter of
+ *     SSL_SendNewSessionTicket() method (see below).  If this is not resuming a
+ *     session, then the token will be empty (and this value could be NULL).
+ *
+ * - |clientTokenLen| is the length of |clientToken|.
+ *
+ * - |retryToken| is an item that callback can write to.  This provides NSS with
+ *   a token.  This token is encrypted and integrity protected and embedded in
+ *   the cookie extension of a HelloRetryRequest.  The value of this field is
+ *   only used if the handler returns ssl_stateless_retry_check.  NSS allocates
+ *   space for this value.
+ *
+ * - |retryTokenLen| is an outparam for the length of the token. If this value
+ *   is not set, or set to 0, an empty token will be sent.
+ *
+ * - |retryTokenMax| is the size of the space allocated for retryToken. An
+ *   application cannot write more than this many bytes to retryToken.
+ *
+ * - |arg| is the same value that was passed to
+ *   SSL_InstallStatelessRetryHandler().
+ *
+ * The handler can validate any the value of |clientToken|, query the socket
+ * status (using SSL_GetPreliminaryChannelInfo() for example) and decide how to
+ * proceed:
+ *
+ * - Returning ssl_hello_retry_fail causes the handshake to fail.  This might be
+ *   used if the token is invalid or the application wishes to abort the
+ *   handshake.
+ *
+ * - Returning ssl_hello_retry_accept causes the handshake to proceed.
+ *
+ * - Returning ssl_hello_retry_request causes NSS to send a HelloRetryRequest
+ *   message and request a second ClientHello.  NSS generates a cookie extension
+ *   and embeds the value of |retryToken|.  The value of |retryToken| value may
+ *   be left empty if the application does not require any additional context to
+ *   validate a second ClientHello attempt.  This return code cannot be used to
+ *   reject a second ClientHello (i.e., when firstHello is PR_FALSE); NSS will
+ *   abort the handshake if this value is returned from a second call.
+ *
+ * An application that chooses to perform a stateless retry can discard the
+ * server socket.  All necessary state to continue the TLS handshake will be
+ * included in the cookie extension.  This makes it possible to use a new socket
+ * to handle the remainder of the handshake.  The existing socket can be safely
+ * discarded.  [TODO: see Bug 1386096]
+ *
+ * If the same socket is retained, the information in the cookie will be checked
+ * for consistency against the existing state of the socket.  Any discrepancy
+ * will result in the connection being closed.
+ *
+ * Tokens should be kept as small as possible.  NSS sets a limit on the size of
+ * tokens, which it passes in |retryTokenMax|.  Depending on circumstances,
+ * observing a smaller limit might be desirable or even necessary.  For
+ * instance, having HelloRetryRequest and ClientHello fit in a single packet has
+ * significant performance benefits.
+ */
+typedef enum {
+    ssl_hello_retry_fail,
+    ssl_hello_retry_accept,
+    ssl_hello_retry_request
+} SSLHelloRetryRequestAction;
+
+typedef SSLHelloRetryRequestAction(PR_CALLBACK *SSLHelloRetryRequestCallback)(
+    PRBool firstHello, const PRUint8 *clientToken, unsigned int clientTokenLen,
+    PRUint8 *retryToken, unsigned int *retryTokenLen, unsigned int retryTokMax,
+    void *arg);
+
+#define SSL_HelloRetryRequestCallback(fd, cb, arg)                       \
+    SSL_EXPERIMENTAL_API("SSL_HelloRetryRequestCallback",                \
+                         (PRFileDesc * _fd,                              \
+                          SSLHelloRetryRequestCallback _cb, void *_arg), \
+                         (fd, cb, arg))
+
 SEC_END_PROTOS
 
 #endif /* __sslexp_h_ */
--- a/lib/ssl/sslimpl.h
+++ b/lib/ssl/sslimpl.h
@@ -14,16 +14,17 @@
 #undef NDEBUG
 #else
 #undef NDEBUG
 #define NDEBUG
 #endif
 #include "secport.h"
 #include "secerr.h"
 #include "sslerr.h"
+#include "sslexp.h"
 #include "ssl3prot.h"
 #include "hasht.h"
 #include "nssilock.h"
 #include "pkcs11t.h"
 #if defined(XP_UNIX) || defined(XP_BEOS)
 #include "unistd.h"
 #endif
 #include "nssrwlk.h"
@@ -1139,16 +1140,18 @@ struct sslSocketStr {
     void *badCertArg;
     SSLHandshakeCallback handshakeCallback;
     void *handshakeCallbackData;
     SSLCanFalseStartCallback canFalseStartCallback;
     void *canFalseStartCallbackData;
     void *pkcs11PinArg;
     SSLNextProtoCallback nextProtoCallback;
     void *nextProtoArg;
+    SSLHelloRetryRequestCallback hrrCallback;
+    void *hrrCallbackArg;
     PRCList extensionHooks;
 
     PRIntervalTime rTimeout; /* timeout for NSPR I/O */
     PRIntervalTime wTimeout; /* timeout for NSPR I/O */
     PRIntervalTime cTimeout; /* timeout for NSPR I/O */
 
     PZLock *recvLock; /* lock against multiple reader threads. */
     PZLock *sendLock; /* lock against multiple sender threads. */
--- a/lib/ssl/sslsock.c
+++ b/lib/ssl/sslsock.c
@@ -3898,16 +3898,17 @@ SSL_CanBypass(CERTCertificate *cert, SEC
         "SSL_" #n, SSL_##n \
     }
 struct {
     const char *const name;
     void *function;
 } ssl_experimental_functions[] = {
 #ifndef SSL_DISABLE_EXPERIMENTAL_API
     EXP(GetExtensionSupport),
+    EXP(HelloRetryRequestCallback),
     EXP(InstallExtensionHooks),
     EXP(SendSessionTicket),
     EXP(SetupAntiReplay),
 #endif
     { "", NULL }
 };
 #undef EXP
 #undef PUB
--- a/lib/ssl/tls13con.c
+++ b/lib/ssl/tls13con.c
@@ -51,18 +51,19 @@ static SECStatus tls13_ChaCha20Poly1305(
     unsigned char *out, int *outlen, int maxout,
     const unsigned char *in, int inlen,
     const unsigned char *additionalData, int additionalDataLen);
 static SECStatus tls13_SendServerHelloSequence(sslSocket *ss);
 static SECStatus tls13_SendEncryptedExtensions(sslSocket *ss);
 static void tls13_SetKeyExchangeType(sslSocket *ss, const sslNamedGroupDef *group);
 static SECStatus tls13_HandleClientKeyShare(sslSocket *ss,
                                             TLS13KeyShareEntry *peerShare);
-static SECStatus tls13_SendHelloRetryRequest(sslSocket *ss,
-                                             const sslNamedGroupDef *selectedGroup);
+static SECStatus tls13_SendHelloRetryRequest(
+    sslSocket *ss, const sslNamedGroupDef *selectedGroup,
+    const PRUint8 *token, unsigned int tokenLen);
 
 static SECStatus tls13_HandleServerKeyShare(sslSocket *ss);
 static SECStatus tls13_HandleEncryptedExtensions(sslSocket *ss, PRUint8 *b,
                                                  PRUint32 length);
 static SECStatus tls13_SendCertificate(sslSocket *ss);
 static SECStatus tls13_HandleCertificate(
     sslSocket *ss, PRUint8 *b, PRUint32 length);
 static SECStatus tls13_ReinjectHandshakeTranscript(sslSocket *ss);
@@ -1069,17 +1070,19 @@ tls13_FindKeyShareEntry(sslSocket *ss, c
             return offer;
         }
         cur_p = PR_NEXT_LINK(cur_p);
     }
     return NULL;
 }
 
 static SECStatus
-tls13_NegotiateKeyExchange(sslSocket *ss, TLS13KeyShareEntry **clientShare)
+tls13_NegotiateKeyExchange(sslSocket *ss,
+                           const sslNamedGroupDef **requestedGroup,
+                           TLS13KeyShareEntry **clientShare)
 {
     unsigned int index;
     TLS13KeyShareEntry *entry = NULL;
     const sslNamedGroupDef *preferredGroup = NULL;
 
     /* We insist on DHE. */
     if (ss->statelessResume) {
         if (!ssl3_ExtensionNegotiated(ss, ssl_tls13_psk_key_exchange_modes_xtn)) {
@@ -1149,23 +1152,26 @@ tls13_NegotiateKeyExchange(sslSocket *ss
 
     if (!preferredGroup) {
         FATAL_ERROR(ss, SSL_ERROR_NO_CYPHER_OVERLAP, handshake_failure);
         return SECFailure;
     }
     SSL_TRC(3, ("%d: TLS13[%d]: group = %d", SSL_GETPID(), ss->fd,
                 preferredGroup->name));
 
-    if (!entry) {
-        return tls13_SendHelloRetryRequest(ss, preferredGroup);
-    }
-
-    PORT_Assert(preferredGroup == entry->group);
-    *clientShare = entry;
-
+    /* Either provide a share, or provide a group that should be requested in a
+     * HelloRetryRequest, but not both. */
+    if (entry) {
+        PORT_Assert(preferredGroup == entry->group);
+        *clientShare = entry;
+        *requestedGroup = NULL;
+    } else {
+        *clientShare = NULL;
+        *requestedGroup = preferredGroup;
+    }
     return SECSuccess;
 }
 
 SSLAuthType
 ssl_SignatureSchemeToAuthType(SSLSignatureScheme scheme)
 {
     switch (scheme) {
         case ssl_sig_rsa_pkcs1_sha1:
@@ -1231,16 +1237,78 @@ tls13_SelectServerCert(sslSocket *ss)
         }
     }
 
     FATAL_ERROR(ss, SSL_ERROR_UNSUPPORTED_SIGNATURE_ALGORITHM,
                 handshake_failure);
     return SECFailure;
 }
 
+/* Note: |requestedGroup| is non-NULL when we send a key_share extension. */
+static SECStatus
+tls13_MaybeSendHelloRetry(sslSocket *ss, const sslNamedGroupDef *requestedGroup,
+                          PRBool *hrrSent)
+{
+    SSLHelloRetryRequestAction action = ssl_hello_retry_accept;
+    PRUint8 token[256] = { 0 };
+    unsigned int tokenLen = 0;
+    SECStatus rv;
+
+    /* We asked already, but didn't get a share. */
+    if (requestedGroup && ss->ssl3.hs.helloRetry) {
+        FATAL_ERROR(ss, SSL_ERROR_BAD_2ND_CLIENT_HELLO, illegal_parameter);
+        return SECFailure;
+    }
+
+    if (ss->hrrCallback) {
+        action = ss->hrrCallback(!ss->ssl3.hs.helloRetry,
+                                 ss->xtnData.applicationToken.data,
+                                 ss->xtnData.applicationToken.len,
+                                 token, &tokenLen, sizeof(token),
+                                 ss->hrrCallbackArg);
+    }
+
+    /* These use SSL3_SendAlert directly to avoid an assertion in
+     * tls13_FatalError(), which is ordinarily OK. */
+    if (action == ssl_hello_retry_request && ss->ssl3.hs.helloRetry) {
+        (void)SSL3_SendAlert(ss, alert_fatal, internal_error);
+        PORT_SetError(SSL_ERROR_APP_CALLBACK_ERROR);
+        return SECFailure;
+    }
+
+    if (action != ssl_hello_retry_request && tokenLen) {
+        (void)SSL3_SendAlert(ss, alert_fatal, internal_error);
+        PORT_SetError(SSL_ERROR_APP_CALLBACK_ERROR);
+        return SECFailure;
+    }
+
+    if (tokenLen > sizeof(token)) {
+        (void)SSL3_SendAlert(ss, alert_fatal, internal_error);
+        PORT_SetError(SSL_ERROR_APP_CALLBACK_ERROR);
+        return SECFailure;
+    }
+
+    if (action == ssl_hello_retry_fail) {
+        FATAL_ERROR(ss, SSL_ERROR_APPLICATION_ABORT, handshake_failure);
+        return SECFailure;
+    }
+
+    if (!requestedGroup && action != ssl_hello_retry_request) {
+        return SECSuccess;
+    }
+
+    rv = tls13_SendHelloRetryRequest(ss, requestedGroup, token, tokenLen);
+    if (rv != SECSuccess) {
+        return SECFailure; /* Code already set. */
+    }
+
+    *hrrSent = PR_TRUE;
+    return SECSuccess;
+}
+
 static SECStatus
 tls13_NegotiateAuthentication(sslSocket *ss)
 {
     SECStatus rv;
 
     if (ss->statelessResume) {
         SSL_TRC(3, ("%d: TLS13[%d]: selected PSK authentication",
                     SSL_GETPID(), ss->fd));
@@ -1266,28 +1334,29 @@ SECStatus
 tls13_HandleClientHelloPart2(sslSocket *ss,
                              const SECItem *suites,
                              sslSessionID *sid,
                              const PRUint8 *msg,
                              unsigned int len)
 {
     SECStatus rv;
     SSL3Statistics *ssl3stats = SSL_GetStatistics();
+    const sslNamedGroupDef *requestedGroup = NULL;
     TLS13KeyShareEntry *clientShare = NULL;
-    int j;
+    PRBool hrr = PR_FALSE;
     ssl3CipherSuite previousCipherSuite;
 
     if (ssl3_ExtensionNegotiated(ss, ssl_tls13_early_data_xtn)) {
         ss->ssl3.hs.zeroRttState = ssl_0rtt_sent;
     }
 
 #ifndef PARANOID
     /* Look for a matching cipher suite. */
-    j = ssl3_config_match_init(ss);
-    if (j <= 0) { /* no ciphers are working/supported by PK11 */
+    if (ssl3_config_match_init(ss) <= 0) {
+        /* no ciphers are working/supported by PK11 */
         FATAL_ERROR(ss, PORT_GetError(), internal_error);
         goto loser;
     }
 #endif
 
     previousCipherSuite = ss->ssl3.hs.cipher_suite;
     rv = ssl3_NegotiateCipherSuite(ss, suites, PR_FALSE);
     if (rv != SECSuccess) {
@@ -1348,23 +1417,29 @@ tls13_HandleClientHelloPart2(sslSocket *
             return SECFailure;
         }
         if (!tls13_CanResume(ss, sid)) {
             ss->statelessResume = PR_FALSE;
         }
     }
 
     /* Select key exchange. */
-    rv = tls13_NegotiateKeyExchange(ss, &clientShare);
+    rv = tls13_NegotiateKeyExchange(ss, &requestedGroup, &clientShare);
     if (rv != SECSuccess) {
         goto loser;
     }
-
-    /* If we didn't find a client key share, we have to retry. */
-    if (!clientShare) {
+    /* We should get either one of these, but not both. */
+    PORT_Assert((requestedGroup && !clientShare) ||
+                (!requestedGroup && clientShare));
+
+    rv = tls13_MaybeSendHelloRetry(ss, requestedGroup, &hrr);
+    if (rv != SECSuccess) {
+        goto loser;
+    }
+    if (hrr) {
         if (sid) { /* Free the sid. */
             ss->sec.uncache(sid);
             ssl_FreeSID(sid);
         }
         PORT_Assert(ss->ssl3.hs.helloRetry);
         return SECSuccess;
     }
 
@@ -1512,16 +1587,30 @@ tls13_HandleClientHelloPart2(sslSocket *
 loser:
     if (sid) {
         ss->sec.uncache(sid);
         ssl_FreeSID(sid);
     }
     return SECFailure;
 }
 
+SECStatus
+SSLExp_HelloRetryRequestCallback(PRFileDesc *fd,
+                                 SSLHelloRetryRequestCallback cb, void *arg)
+{
+    sslSocket *ss = ssl_FindSocket(fd);
+    if (!ss) {
+        return SECFailure; /* Code already set. */
+    }
+
+    ss->hrrCallback = cb;
+    ss->hrrCallbackArg = arg;
+    return SECSuccess;
+}
+
 /*
  * struct {
  *     ProtocolVersion server_version;
  *     CipherSuite cipher_suite;
  *     Extension extensions<2..2^16-1>;
  * } HelloRetryRequest;
  *
  * Note: this function takes an empty buffer and returns
@@ -1574,44 +1663,41 @@ tls13_ConstructHelloRetryRequest(sslSock
     return SECSuccess;
 
 loser:
     sslBuffer_Clear(buffer);
     return SECFailure;
 }
 
 static SECStatus
-tls13_SendHelloRetryRequest(sslSocket *ss, const sslNamedGroupDef *selectedGroup)
+tls13_SendHelloRetryRequest(sslSocket *ss,
+                            const sslNamedGroupDef *requestedGroup,
+                            const PRUint8 *appToken, unsigned int appTokenLen)
 {
     SECStatus rv;
     unsigned int cookieLen;
     PRUint8 cookie[1024];
     sslBuffer messageBuf = { NULL, 0, 0 };
 
     SSL_TRC(3, ("%d: TLS13[%d]: send hello retry request handshake",
                 SSL_GETPID(), ss->fd));
 
     PORT_Assert(ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss));
 
-    /* We asked already, but made no progress. */
-    if (ss->ssl3.hs.helloRetry) {
-        FATAL_ERROR(ss, SSL_ERROR_BAD_2ND_CLIENT_HELLO, illegal_parameter);
-        return SECFailure;
-    }
-
     /* Compute the cookie we are going to need. */
-    rv = tls13_MakeHrrCookie(ss, selectedGroup,
+    rv = tls13_MakeHrrCookie(ss, requestedGroup,
+                             appToken, appTokenLen,
                              cookie, &cookieLen, sizeof(cookie));
     if (rv != SECSuccess) {
         FATAL_ERROR(ss, SEC_ERROR_LIBRARY_FAILURE, internal_error);
         return SECFailure;
     }
 
     /* Now build the body of the message. */
-    rv = tls13_ConstructHelloRetryRequest(ss, selectedGroup,
+    rv = tls13_ConstructHelloRetryRequest(ss, requestedGroup,
                                           cookie, cookieLen, &messageBuf);
     if (rv != SECSuccess) {
         FATAL_ERROR(ss, SEC_ERROR_LIBRARY_FAILURE, internal_error);
         return SECFailure;
     }
 
     /* And send it. */
     ssl_GetXmitBufLock(ss);
--- a/lib/ssl/tls13con.h
+++ b/lib/ssl/tls13con.h
@@ -4,16 +4,18 @@
  *
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef __tls13con_h_
 #define __tls13con_h_
 
+#include "sslexp.h"
+
 typedef enum {
     StaticSharedSecret,
     EphemeralSharedSecret
 } SharedSecretType;
 
 typedef enum {
     tls13_extension_allowed,
     tls13_extension_disallowed,
@@ -101,9 +103,13 @@ PRUint16 tls13_DecodeDraftVersion(PRUint
 SECStatus tls13_NegotiateVersion(sslSocket *ss,
                                  const TLSExtension *supported_versions);
 
 PRBool tls13_IsReplay(const sslSocket *ss, const sslSessionID *sid);
 void tls13_AntiReplayRollover(PRTime now);
 SECStatus SSLExp_SetupAntiReplay(PRTime window, unsigned int k,
                                  unsigned int bits);
 
+SECStatus SSLExp_HelloRetryRequestCallback(PRFileDesc *fd,
+                                           SSLHelloRetryRequestCallback cb,
+                                           void *arg);
+
 #endif /* __tls13con_h_ */
--- a/lib/ssl/tls13exthandle.c
+++ b/lib/ssl/tls13exthandle.c
@@ -453,28 +453,39 @@ loser:
 SECStatus
 tls13_ServerHandlePreSharedKeyXtn(const sslSocket *ss, TLSExtensionData *xtnData,
                                   SECItem *data)
 {
     SECItem inner;
     SECStatus rv;
     unsigned int numIdentities = 0;
     unsigned int numBinders = 0;
+    SECItem *appToken;
 
     SSL_TRC(3, ("%d: SSL3[%d]: handle pre_shared_key extension",
                 SSL_GETPID(), ss->fd));
 
     /* If we are doing < TLS 1.3, then ignore this. */
     if (ss->version < SSL_LIBRARY_VERSION_TLS_1_3) {
         return SECSuccess;
     }
 
+    /* The application token is set via the cookie extension if this is the
+     * second ClientHello.  Don't set it twice.  The cookie extension handler
+     * sets |helloRetry| and that will have been called already because this
+     * extension always comes last. */
+    if (!ss->ssl3.hs.helloRetry) {
+        appToken = &xtnData->applicationToken;
+    } else {
+        appToken = NULL;
+    }
+
     /* Parse the identities list. */
-    rv = ssl3_ExtConsumeHandshakeVariable(ss,
-                                          &inner, 2, &data->data, &data->len);
+    rv = ssl3_ExtConsumeHandshakeVariable(ss, &inner, 2,
+                                          &data->data, &data->len);
     if (rv != SECSuccess) {
         return SECFailure;
     }
 
     while (inner.len) {
         SECItem label;
         PRUint32 obfuscatedAge;
 
@@ -490,17 +501,17 @@ tls13_ServerHandlePreSharedKeyXtn(const 
                                             &inner.data, &inner.len);
         if (rv != SECSuccess)
             return rv;
 
         if (!numIdentities) {
             PRINT_BUF(50, (ss, "Handling PreSharedKey value",
                            label.data, label.len));
             rv = ssl3_ProcessSessionTicketCommon(
-                CONST_CAST(sslSocket, ss), &label);
+                CONST_CAST(sslSocket, ss), &label, appToken);
             /* This only happens if we have an internal error, not
              * a malformed ticket. Bogus tickets just don't resume
              * and return SECSuccess. */
             if (rv != SECSuccess)
                 return SECFailure;
 
             if (ss->sec.ci.sid) {
                 /* xtnData->ticketAge contains the baseline we use for
@@ -982,20 +993,16 @@ loser:
 SECStatus
 tls13_ServerSendHrrKeyShareXtn(const sslSocket *ss, TLSExtensionData *xtnData,
                                sslBuffer *buf, PRBool *added)
 {
     SECStatus rv;
 
     PORT_Assert(ss->version >= SSL_LIBRARY_VERSION_TLS_1_3);
 
-    /* In future, we may want to send HRRs w/o key_share but we don't
-     * currently do that. At that time, this assert can simply be
-     * removed. */
-    PORT_Assert(xtnData->selectedGroup != NULL);
     if (!xtnData->selectedGroup) {
         return SECSuccess;
     }
 
     rv = sslBuffer_AppendNumber(buf, xtnData->selectedGroup->name, 2);
     if (rv != SECSuccess) {
         return SECFailure;
     }
--- a/lib/ssl/tls13hashstate.c
+++ b/lib/ssl/tls13hashstate.c
@@ -16,24 +16,26 @@
 #include "tls13err.h"
 #include "tls13hashstate.h"
 
 /*
  * The cookie is structured as a self-encrypted structure with the
  * inner value being.
  *
  * struct {
- *     uint8 indicator = 0xff;          // To disambiguate from tickets.
- *     uint16 cipherSuite;              // Selected cipher suite.
- *     uint16 keyShare;                 // Key share we requested (0 if none)
- *     opaque ch_hash[rest_of_buffer]; // H(ClientHello)
+ *     uint8 indicator = 0xff;            // To disambiguate from tickets.
+ *     uint16 cipherSuite;                // Selected cipher suite.
+ *     uint16 keyShare;                   // Requested key share group (0=none)
+ *     opaque applicationToken<0..65535>; // Application token
+ *     opaque ch_hash[rest_of_buffer];    // H(ClientHello)
  * } CookieInner;
  */
 SECStatus
 tls13_MakeHrrCookie(sslSocket *ss, const sslNamedGroupDef *selectedGroup,
+                    const PRUint8 *appToken, unsigned int appTokenLen,
                     PRUint8 *buf, unsigned int *len, unsigned int maxlen)
 {
     SECStatus rv;
     SSL3Hashes hashes;
     PRUint8 encodedCookie[1024];
     SECItem cookieItem = { siBuffer, encodedCookie, sizeof(encodedCookie) };
 
     PORT_Assert(sizeof(encodedCookie) >= maxlen);
@@ -47,16 +49,26 @@ tls13_MakeHrrCookie(sslSocket *ss, const
     if (rv != SECSuccess) {
         return SECFailure;
     }
     rv = ssl3_AppendNumberToItem(&cookieItem, selectedGroup ? selectedGroup->name : 0, 2);
     if (rv != SECSuccess) {
         return SECFailure;
     }
 
+    /* Application token. */
+    rv = ssl3_AppendNumberToItem(&cookieItem, appTokenLen, 2);
+    if (rv != SECSuccess) {
+        return SECFailure;
+    }
+    rv = ssl3_AppendToItem(&cookieItem, appToken, appTokenLen);
+    if (rv != SECSuccess) {
+        return SECFailure;
+    }
+
     /* Encode the hash state */
     rv = tls13_ComputeHandshakeHashes(ss, &hashes);
     if (rv != SECSuccess) {
         return SECFailure;
     }
     rv = ssl3_AppendToItem(&cookieItem, hashes.u.raw, hashes.len);
     if (rv != SECSuccess) {
         return SECFailure;
@@ -82,16 +94,18 @@ tls13_RecoverHashState(sslSocket *ss,
     SECStatus rv;
     unsigned char plaintext[1024];
     SECItem ptItem = { siBuffer, plaintext, 0 };
     sslBuffer messageBuf = { NULL, 0, 0 };
     PRUint32 sentinel;
     PRUint32 cipherSuite;
     PRUint32 group;
     const sslNamedGroupDef *selectedGroup;
+    PRUint32 appTokenLen;
+    PRUint8 *appToken;
 
     rv = ssl_SelfEncryptUnprotect(ss, cookie, cookieLen,
                                   ptItem.data, &ptItem.len, sizeof(plaintext));
     if (rv != SECSuccess) {
         return SECFailure;
     }
 
     /* Should start with 0xff. */
@@ -109,21 +123,36 @@ tls13_RecoverHashState(sslSocket *ss,
 
     /* The named group, if any. */
     rv = ssl3_ConsumeNumberFromItem(&ptItem, &group, 2);
     if (rv != SECSuccess) {
         FATAL_ERROR(ss, SSL_ERROR_RX_MALFORMED_CLIENT_HELLO, illegal_parameter);
         return SECFailure;
     }
     selectedGroup = ssl_LookupNamedGroup(group);
-    PORT_Assert(selectedGroup);
-    if (selectedGroup == NULL) {
+
+    /* Application token. */
+    PORT_Assert(ss->xtnData.applicationToken.len == 0);
+    rv = ssl3_ConsumeNumberFromItem(&ptItem, &appTokenLen, 2);
+    if (rv != SECSuccess) {
         FATAL_ERROR(ss, SSL_ERROR_RX_MALFORMED_CLIENT_HELLO, illegal_parameter);
         return SECFailure;
     }
+    if (SECITEM_AllocItem(NULL, &ss->xtnData.applicationToken,
+                          appTokenLen) == NULL) {
+        FATAL_ERROR(ss, PORT_GetError(), internal_error);
+        return SECFailure;
+    }
+    ss->xtnData.applicationToken.len = appTokenLen;
+    rv = ssl3_ConsumeFromItem(&ptItem, &appToken, appTokenLen);
+    if (rv != SECSuccess) {
+        FATAL_ERROR(ss, SSL_ERROR_RX_MALFORMED_CLIENT_HELLO, illegal_parameter);
+        return SECFailure;
+    }
+    PORT_Memcpy(ss->xtnData.applicationToken.data, appToken, appTokenLen);
 
     /* The remainder is the hash. */
     if (ptItem.len != tls13_GetHashSize(ss)) {
         FATAL_ERROR(ss, SSL_ERROR_RX_MALFORMED_CLIENT_HELLO, illegal_parameter);
         return SECFailure;
     }
 
     /* Now reinject the message. */
--- a/lib/ssl/tls13hashstate.h
+++ b/lib/ssl/tls13hashstate.h
@@ -9,14 +9,15 @@
 #ifndef __tls13hashstate_h_
 #define __tls13hashstate_h_
 
 #include "ssl.h"
 #include "sslt.h"
 #include "sslimpl.h"
 
 SECStatus tls13_MakeHrrCookie(sslSocket *ss, const sslNamedGroupDef *selectedGroup,
+                              const PRUint8 *appToken, unsigned int appTokenLen,
                               PRUint8 *buf, unsigned int *len, unsigned int maxlen);
 SECStatus tls13_GetHrrCookieLength(sslSocket *ss, unsigned int *length);
 SECStatus tls13_RecoverHashState(sslSocket *ss,
                                  unsigned char *cookie,
                                  unsigned int cookieLen);
 #endif