Bug 1368980 - Custom extension writers/handlers, r=ekr NSS_TLS13_DRAFT19_BRANCH
authorMartin Thomson <martin.thomson@gmail.com>
Wed, 31 May 2017 18:28:52 +1000
branchNSS_TLS13_DRAFT19_BRANCH
changeset 13457 2d24546baff061a8c73e06e683678b5dcae7a143
parent 13456 c50689be15fc86fd1461f09cefd02c6c88a6f333
child 13459 eb70c6c1a13ede0908dffd9750c537822011d063
push id2267
push usermartin.thomson@gmail.com
push dateWed, 12 Jul 2017 23:34:11 +0000
reviewersekr
bugs1368980
Bug 1368980 - Custom extension writers/handlers, r=ekr
gtests/ssl_gtest/manifest.mn
gtests/ssl_gtest/ssl_custext_unittest.cc
gtests/ssl_gtest/ssl_gtest.gyp
gtests/ssl_gtest/tls_filter.h
lib/ssl/ssl.def
lib/ssl/ssl.h
lib/ssl/ssl3con.c
lib/ssl/ssl3ext.c
lib/ssl/ssl3ext.h
lib/ssl/ssl3exthandle.c
lib/ssl/sslimpl.h
lib/ssl/sslsecur.c
lib/ssl/sslsock.c
lib/ssl/sslt.h
lib/ssl/tls13con.c
lib/ssl/tls13exthandle.c
--- a/gtests/ssl_gtest/manifest.mn
+++ b/gtests/ssl_gtest/manifest.mn
@@ -12,16 +12,17 @@ CSRCS = \
       $(NULL)
 
 CPPSRCS = \
       ssl_0rtt_unittest.cc \
       ssl_agent_unittest.cc \
       ssl_auth_unittest.cc \
       ssl_cert_ext_unittest.cc \
       ssl_ciphersuite_unittest.cc \
+      ssl_custext_unittest.cc \
       ssl_damage_unittest.cc \
       ssl_dhe_unittest.cc \
       ssl_drop_unittest.cc \
       ssl_ecdh_unittest.cc \
       ssl_ems_unittest.cc \
       ssl_exporter_unittest.cc \
       ssl_extension_unittest.cc \
       ssl_fragment_unittest.cc \
new file mode 100644
--- /dev/null
+++ b/gtests/ssl_gtest/ssl_custext_unittest.cc
@@ -0,0 +1,498 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#include "ssl.h"
+#include "ssl3prot.h"
+#include "sslerr.h"
+#include "sslproto.h"
+
+#include <memory>
+
+#include "tls_connect.h"
+
+namespace nss_test {
+
+static void IncrementCounterArg(void *arg) {
+  if (arg) {
+    auto *called = reinterpret_cast<size_t *>(arg);
+    ++*called;
+  }
+}
+
+PRBool NoopExtensionWriter(PRFileDesc *fd, SSLHandshakeType message,
+                           PRUint8 *data, unsigned int *len,
+                           unsigned int maxLen, void *arg) {
+  IncrementCounterArg(arg);
+  return PR_FALSE;
+}
+
+PRBool EmptyExtensionWriter(PRFileDesc *fd, SSLHandshakeType message,
+                            PRUint8 *data, unsigned int *len,
+                            unsigned int maxLen, void *arg) {
+  IncrementCounterArg(arg);
+  return PR_TRUE;
+}
+
+SECStatus NoopExtensionHandler(PRFileDesc *fd, SSLHandshakeType message,
+                               const PRUint8 *data, unsigned int len,
+                               SSLAlertDescription *alert, void *arg) {
+  return SECSuccess;
+}
+
+// All of the (current) set of supported extensions, plus a few extra.
+static const uint16_t kManyExtensions[] = {ssl_server_name_xtn,
+                                           ssl_cert_status_xtn,
+                                           ssl_supported_groups_xtn,
+                                           ssl_ec_point_formats_xtn,
+                                           ssl_signature_algorithms_xtn,
+                                           ssl_use_srtp_xtn,
+                                           ssl_app_layer_protocol_xtn,
+                                           ssl_signed_cert_timestamp_xtn,
+                                           ssl_padding_xtn,
+                                           ssl_extended_master_secret_xtn,
+                                           ssl_session_ticket_xtn,
+                                           ssl_tls13_key_share_xtn,
+                                           ssl_tls13_pre_shared_key_xtn,
+                                           ssl_tls13_early_data_xtn,
+                                           ssl_tls13_supported_versions_xtn,
+                                           ssl_tls13_cookie_xtn,
+                                           ssl_tls13_psk_key_exchange_modes_xtn,
+                                           ssl_tls13_ticket_early_data_info_xtn,
+                                           ssl_renegotiation_info_xtn,
+                                           ssl_next_proto_nego_xtn,
+                                           ssl_tls13_ticket_early_data_info_xtn,
+                                           ssl_tls13_short_header_xtn,
+                                           1,
+                                           0xffff};
+// The list here includes all extensions we expect to use (SSL_MAX_EXTENSIONS),
+// plus the deprecated values (see sslt.h), and two extra dummy values.
+PR_STATIC_ASSERT((SSL_MAX_EXTENSIONS + 5) == PR_ARRAY_SIZE(kManyExtensions));
+
+void InstallManyWriters(std::shared_ptr<TlsAgent> agent,
+                        SSLExtensionWriter writer, size_t *installed = nullptr,
+                        size_t *called = nullptr) {
+  for (size_t i = 0; i < PR_ARRAY_SIZE(kManyExtensions); ++i) {
+    SSLExtensionSupport support = SSL_GetExtensionSupport(kManyExtensions[i]);
+    SECStatus rv =
+        SSL_InstallExtensionHooks(agent->ssl_fd(), kManyExtensions[i], writer,
+                                  called, NoopExtensionHandler, nullptr);
+    if (support == ssl_ext_native_only) {
+      EXPECT_EQ(SECFailure, rv);
+      EXPECT_EQ(SEC_ERROR_INVALID_ARGS, PORT_GetError());
+    } else {
+      if (installed) {
+        ++*installed;
+      }
+      EXPECT_EQ(SECSuccess, rv);
+    }
+  }
+}
+
+TEST_F(TlsConnectStreamTls13, CustomExtensionAllNoopClient) {
+  EnsureTlsSetup();
+  size_t installed = 0;
+  size_t called = 0;
+  InstallManyWriters(client_, NoopExtensionWriter, &installed, &called);
+  EXPECT_LT(0U, installed);
+  Connect();
+  EXPECT_EQ(installed, called);
+}
+
+TEST_F(TlsConnectStreamTls13, CustomExtensionAllNoopServer) {
+  EnsureTlsSetup();
+  size_t installed = 0;
+  size_t called = 0;
+  InstallManyWriters(server_, NoopExtensionWriter, &installed, &called);
+  EXPECT_LT(0U, installed);
+  Connect();
+  // Extension writers are all called for each of ServerHello,
+  // EncryptedExtensions, and Certificate.
+  EXPECT_EQ(installed * 3, called);
+}
+
+TEST_F(TlsConnectStreamTls13, CustomExtensionEmptyWriterClient) {
+  EnsureTlsSetup();
+  InstallManyWriters(client_, EmptyExtensionWriter);
+  Connect();
+}
+
+TEST_F(TlsConnectStreamTls13, CustomExtensionEmptyWriterServer) {
+  EnsureTlsSetup();
+  InstallManyWriters(server_, EmptyExtensionWriter);
+  // Sending extensions that the client doesn't expect leads to extensions
+  // appearing even if the client didn't send one, or in the wrong messages.
+  client_->ExpectSendAlert(kTlsAlertUnsupportedExtension);
+  server_->ExpectSendAlert(kTlsAlertBadRecordMac);
+  ConnectExpectFail();
+}
+
+// Install an writer to disable sending of a natively-supported extension.
+TEST_F(TlsConnectStreamTls13, CustomExtensionWriterDisable) {
+  EnsureTlsSetup();
+
+  // This option enables sending the extension via the native support.
+  SECStatus rv = SSL_OptionSet(client_->ssl_fd(),
+                               SSL_ENABLE_SIGNED_CERT_TIMESTAMPS, PR_TRUE);
+  EXPECT_EQ(SECSuccess, rv);
+
+  // This installs an override that doesn't do anything.  You have to specify
+  // something; passing all nullptr values removes an existing handler.
+  rv = SSL_InstallExtensionHooks(
+      client_->ssl_fd(), ssl_signed_cert_timestamp_xtn, NoopExtensionWriter,
+      nullptr, NoopExtensionHandler, nullptr);
+  EXPECT_EQ(SECSuccess, rv);
+  auto capture =
+      std::make_shared<TlsExtensionCapture>(ssl_signed_cert_timestamp_xtn);
+  client_->SetPacketFilter(capture);
+
+  Connect();
+  // So nothing will be sent.
+  EXPECT_FALSE(capture->captured());
+}
+
+// An extension that is unlikely to be parsed as valid.
+static uint8_t kNonsenseExtension[] = {91, 82, 73, 64, 55, 46, 37, 28, 19};
+
+static PRBool NonsenseExtensionWriter(PRFileDesc *fd, SSLHandshakeType message,
+                                      PRUint8 *data, unsigned int *len,
+                                      unsigned int maxLen, void *arg) {
+  TlsAgent *agent = reinterpret_cast<TlsAgent *>(arg);
+  EXPECT_NE(nullptr, agent);
+  EXPECT_NE(nullptr, data);
+  EXPECT_NE(nullptr, len);
+  EXPECT_EQ(0U, *len);
+  EXPECT_LT(0U, maxLen);
+  EXPECT_EQ(agent->ssl_fd(), fd);
+
+  if (message != ssl_hs_client_hello && message != ssl_hs_server_hello &&
+      message != ssl_hs_encrypted_extensions) {
+    return PR_FALSE;
+  }
+
+  *len = static_cast<unsigned int>(sizeof(kNonsenseExtension));
+  EXPECT_GE(maxLen, *len);
+  if (maxLen < *len) {
+    return PR_FALSE;
+  }
+  PORT_Memcpy(data, kNonsenseExtension, *len);
+  return PR_TRUE;
+}
+
+// Override the extension handler for an natively-supported and produce
+// nonsense, which results in a handshake failure.
+TEST_F(TlsConnectStreamTls13, CustomExtensionOverride) {
+  EnsureTlsSetup();
+
+  // This option enables sending the extension via the native support.
+  SECStatus rv = SSL_OptionSet(client_->ssl_fd(),
+                               SSL_ENABLE_SIGNED_CERT_TIMESTAMPS, PR_TRUE);
+  EXPECT_EQ(SECSuccess, rv);
+
+  // This installs an override that sends nonsense.
+  rv = SSL_InstallExtensionHooks(
+      client_->ssl_fd(), ssl_signed_cert_timestamp_xtn, NonsenseExtensionWriter,
+      client_.get(), NoopExtensionHandler, nullptr);
+  EXPECT_EQ(SECSuccess, rv);
+
+  // Capture it to see what we got.
+  auto capture =
+      std::make_shared<TlsExtensionCapture>(ssl_signed_cert_timestamp_xtn);
+  client_->SetPacketFilter(capture);
+
+  ConnectExpectAlert(server_, kTlsAlertDecodeError);
+
+  EXPECT_TRUE(capture->captured());
+  EXPECT_EQ(DataBuffer(kNonsenseExtension, sizeof(kNonsenseExtension)),
+            capture->extension());
+}
+
+static SECStatus NonsenseExtensionHandler(PRFileDesc *fd,
+                                          SSLHandshakeType message,
+                                          const PRUint8 *data, unsigned int len,
+                                          SSLAlertDescription *alert,
+                                          void *arg) {
+  TlsAgent *agent = reinterpret_cast<TlsAgent *>(arg);
+  EXPECT_EQ(agent->ssl_fd(), fd);
+  if (agent->role() == TlsAgent::SERVER) {
+    EXPECT_EQ(ssl_hs_client_hello, message);
+  } else {
+    EXPECT_TRUE(message == ssl_hs_server_hello ||
+                message == ssl_hs_encrypted_extensions);
+  }
+  EXPECT_EQ(DataBuffer(kNonsenseExtension, sizeof(kNonsenseExtension)),
+            DataBuffer(data, len));
+  EXPECT_NE(nullptr, alert);
+  return SECSuccess;
+}
+
+// Send nonsense in an extension from client to server.
+TEST_F(TlsConnectStreamTls13, CustomExtensionClientToServer) {
+  EnsureTlsSetup();
+
+  // This installs an override that sends nonsense.
+  const uint16_t extension_code = 0xffe5;
+  SECStatus rv = SSL_InstallExtensionHooks(
+      client_->ssl_fd(), extension_code, NonsenseExtensionWriter, client_.get(),
+      NoopExtensionHandler, nullptr);
+  EXPECT_EQ(SECSuccess, rv);
+
+  // Capture it to see what we got.
+  auto capture = std::make_shared<TlsExtensionCapture>(extension_code);
+  client_->SetPacketFilter(capture);
+
+  // Handle it so that the handshake completes.
+  rv = SSL_InstallExtensionHooks(server_->ssl_fd(), extension_code,
+                                 NoopExtensionWriter, nullptr,
+                                 NonsenseExtensionHandler, server_.get());
+  EXPECT_EQ(SECSuccess, rv);
+
+  Connect();
+
+  EXPECT_TRUE(capture->captured());
+  EXPECT_EQ(DataBuffer(kNonsenseExtension, sizeof(kNonsenseExtension)),
+            capture->extension());
+}
+
+static PRBool NonsenseExtensionWriterSH(PRFileDesc *fd,
+                                        SSLHandshakeType message, PRUint8 *data,
+                                        unsigned int *len, unsigned int maxLen,
+                                        void *arg) {
+  if (message == ssl_hs_server_hello) {
+    return NonsenseExtensionWriter(fd, message, data, len, maxLen, arg);
+  }
+  return PR_FALSE;
+}
+
+// Send nonsense in an extension from server to client, in ServerHello.
+TEST_F(TlsConnectStreamTls13, CustomExtensionServerToClientSH) {
+  EnsureTlsSetup();
+
+  // This installs an override that sends nothing but expects nonsense.
+  const uint16_t extension_code = 0xff5e;
+  SECStatus rv = SSL_InstallExtensionHooks(
+      client_->ssl_fd(), extension_code, EmptyExtensionWriter, nullptr,
+      NonsenseExtensionHandler, client_.get());
+  EXPECT_EQ(SECSuccess, rv);
+
+  // Have the server send nonsense.
+  rv = SSL_InstallExtensionHooks(server_->ssl_fd(), extension_code,
+                                 NonsenseExtensionWriterSH, server_.get(),
+                                 NoopExtensionHandler, nullptr);
+  EXPECT_EQ(SECSuccess, rv);
+
+  // Capture the extension from the ServerHello only and check it.
+  auto capture = std::make_shared<TlsExtensionCapture>(extension_code);
+  capture->SetHandshakeTypes({kTlsHandshakeServerHello});
+  server_->SetPacketFilter(capture);
+
+  Connect();
+
+  EXPECT_TRUE(capture->captured());
+  EXPECT_EQ(DataBuffer(kNonsenseExtension, sizeof(kNonsenseExtension)),
+            capture->extension());
+}
+
+static PRBool NonsenseExtensionWriterEE(PRFileDesc *fd,
+                                        SSLHandshakeType message, PRUint8 *data,
+                                        unsigned int *len, unsigned int maxLen,
+                                        void *arg) {
+  if (message == ssl_hs_encrypted_extensions) {
+    return NonsenseExtensionWriter(fd, message, data, len, maxLen, arg);
+  }
+  return PR_FALSE;
+}
+
+// Send nonsense in an extension from server to client, in EncryptedExtensions.
+TEST_F(TlsConnectStreamTls13, CustomExtensionServerToClientEE) {
+  EnsureTlsSetup();
+
+  // This installs an override that sends nothing but expects nonsense.
+  const uint16_t extension_code = 0xff5e;
+  SECStatus rv = SSL_InstallExtensionHooks(
+      client_->ssl_fd(), extension_code, EmptyExtensionWriter, nullptr,
+      NonsenseExtensionHandler, client_.get());
+  EXPECT_EQ(SECSuccess, rv);
+
+  // Have the server send nonsense.
+  rv = SSL_InstallExtensionHooks(server_->ssl_fd(), extension_code,
+                                 NonsenseExtensionWriterEE, server_.get(),
+                                 NoopExtensionHandler, nullptr);
+  EXPECT_EQ(SECSuccess, rv);
+
+  // Capture the extension from the EncryptedExtensions only and check it.
+  auto capture = std::make_shared<TlsExtensionCapture>(extension_code);
+  capture->SetHandshakeTypes({kTlsHandshakeEncryptedExtensions});
+  server_->SetTlsRecordFilter(capture);
+  capture->EnableDecryption();
+
+  Connect();
+
+  EXPECT_TRUE(capture->captured());
+  EXPECT_EQ(DataBuffer(kNonsenseExtension, sizeof(kNonsenseExtension)),
+            capture->extension());
+}
+
+TEST_F(TlsConnectStreamTls13, CustomExtensionUnsolicitedServer) {
+  EnsureTlsSetup();
+
+  const uint16_t extension_code = 0xff5e;
+  SECStatus rv = SSL_InstallExtensionHooks(
+      server_->ssl_fd(), extension_code, NonsenseExtensionWriter, server_.get(),
+      NoopExtensionHandler, nullptr);
+  EXPECT_EQ(SECSuccess, rv);
+
+  // Capture it to see what we got.
+  auto capture = std::make_shared<TlsExtensionCapture>(extension_code);
+  server_->SetPacketFilter(capture);
+
+  client_->ExpectSendAlert(kTlsAlertUnsupportedExtension);
+  server_->ExpectSendAlert(kTlsAlertBadRecordMac);
+  ConnectExpectFail();
+
+  EXPECT_TRUE(capture->captured());
+  EXPECT_EQ(DataBuffer(kNonsenseExtension, sizeof(kNonsenseExtension)),
+            capture->extension());
+}
+
+SECStatus RejectExtensionHandler(PRFileDesc *fd, SSLHandshakeType message,
+                                 const PRUint8 *data, unsigned int len,
+                                 SSLAlertDescription *alert, void *arg) {
+  return SECFailure;
+}
+
+TEST_F(TlsConnectStreamTls13, CustomExtensionServerReject) {
+  EnsureTlsSetup();
+
+  // This installs an override that sends nonsense.
+  const uint16_t extension_code = 0xffe7;
+  SECStatus rv = SSL_InstallExtensionHooks(client_->ssl_fd(), extension_code,
+                                           EmptyExtensionWriter, nullptr,
+                                           NoopExtensionHandler, nullptr);
+  EXPECT_EQ(SECSuccess, rv);
+
+  // Reject the extension for no good reason.
+  rv = SSL_InstallExtensionHooks(server_->ssl_fd(), extension_code,
+                                 NoopExtensionWriter, nullptr,
+                                 RejectExtensionHandler, nullptr);
+  EXPECT_EQ(SECSuccess, rv);
+
+  ConnectExpectAlert(server_, kTlsAlertHandshakeFailure);
+}
+
+// Send nonsense in an extension from client to server.
+TEST_F(TlsConnectStreamTls13, CustomExtensionClientReject) {
+  EnsureTlsSetup();
+
+  // This installs an override that sends nothing but expects nonsense.
+  const uint16_t extension_code = 0xff58;
+  SECStatus rv = SSL_InstallExtensionHooks(client_->ssl_fd(), extension_code,
+                                           EmptyExtensionWriter, nullptr,
+                                           RejectExtensionHandler, nullptr);
+  EXPECT_EQ(SECSuccess, rv);
+
+  // Have the server send nonsense.
+  rv = SSL_InstallExtensionHooks(server_->ssl_fd(), extension_code,
+                                 EmptyExtensionWriter, nullptr,
+                                 NoopExtensionHandler, nullptr);
+  EXPECT_EQ(SECSuccess, rv);
+
+  client_->ExpectSendAlert(kTlsAlertHandshakeFailure);
+  server_->ExpectSendAlert(kTlsAlertBadRecordMac);
+  ConnectExpectFail();
+}
+
+static const uint8_t kCustomAlert = 0xf6;
+
+SECStatus AlertExtensionHandler(PRFileDesc *fd, SSLHandshakeType message,
+                                const PRUint8 *data, unsigned int len,
+                                SSLAlertDescription *alert, void *arg) {
+  *alert = kCustomAlert;
+  return SECFailure;
+}
+
+TEST_F(TlsConnectStreamTls13, CustomExtensionServerRejectAlert) {
+  EnsureTlsSetup();
+
+  // This installs an override that sends nonsense.
+  const uint16_t extension_code = 0xffea;
+  SECStatus rv = SSL_InstallExtensionHooks(client_->ssl_fd(), extension_code,
+                                           EmptyExtensionWriter, nullptr,
+                                           NoopExtensionHandler, nullptr);
+  EXPECT_EQ(SECSuccess, rv);
+
+  // Reject the extension for no good reason.
+  rv = SSL_InstallExtensionHooks(server_->ssl_fd(), extension_code,
+                                 NoopExtensionWriter, nullptr,
+                                 AlertExtensionHandler, nullptr);
+  EXPECT_EQ(SECSuccess, rv);
+
+  ConnectExpectAlert(server_, kCustomAlert);
+}
+
+// Send nonsense in an extension from client to server.
+TEST_F(TlsConnectStreamTls13, CustomExtensionClientRejectAlert) {
+  EnsureTlsSetup();
+
+  // This installs an override that sends nothing but expects nonsense.
+  const uint16_t extension_code = 0xff5a;
+  SECStatus rv = SSL_InstallExtensionHooks(client_->ssl_fd(), extension_code,
+                                           EmptyExtensionWriter, nullptr,
+                                           AlertExtensionHandler, nullptr);
+  EXPECT_EQ(SECSuccess, rv);
+
+  // Have the server send nonsense.
+  rv = SSL_InstallExtensionHooks(server_->ssl_fd(), extension_code,
+                                 EmptyExtensionWriter, nullptr,
+                                 NoopExtensionHandler, nullptr);
+  EXPECT_EQ(SECSuccess, rv);
+
+  client_->ExpectSendAlert(kCustomAlert);
+  server_->ExpectSendAlert(kTlsAlertBadRecordMac);
+  ConnectExpectFail();
+}
+
+// Configure a custom extension hook badly.
+TEST_F(TlsConnectStreamTls13, CustomExtensionOnlyWriter) {
+  EnsureTlsSetup();
+
+  // This installs an override that sends nothing but expects nonsense.
+  SECStatus rv =
+      SSL_InstallExtensionHooks(client_->ssl_fd(), 0xff6c, EmptyExtensionWriter,
+                                nullptr, nullptr, nullptr);
+  EXPECT_EQ(SECFailure, rv);
+  EXPECT_EQ(SEC_ERROR_INVALID_ARGS, PORT_GetError());
+}
+
+TEST_F(TlsConnectStreamTls13, CustomExtensionOnlyHandler) {
+  EnsureTlsSetup();
+
+  // This installs an override that sends nothing but expects nonsense.
+  SECStatus rv =
+      SSL_InstallExtensionHooks(client_->ssl_fd(), 0xff6d, nullptr, nullptr,
+                                NoopExtensionHandler, nullptr);
+  EXPECT_EQ(SECFailure, rv);
+  EXPECT_EQ(SEC_ERROR_INVALID_ARGS, PORT_GetError());
+}
+
+TEST_F(TlsConnectStreamTls13, CustomExtensionOverrunBuffer) {
+  EnsureTlsSetup();
+  // This doesn't actually overrun the buffer, but it says that it does.
+  auto overrun_writer = [](PRFileDesc *fd, SSLHandshakeType message,
+                           PRUint8 *data, unsigned int *len,
+                           unsigned int maxLen, void *arg) -> PRBool {
+    *len = maxLen + 1;
+    return PR_TRUE;
+  };
+  SECStatus rv =
+      SSL_InstallExtensionHooks(client_->ssl_fd(), 0xff71, overrun_writer,
+                                nullptr, NoopExtensionHandler, nullptr);
+  EXPECT_EQ(SECSuccess, rv);
+  client_->StartConnect();
+  client_->Handshake();
+  client_->CheckErrorCode(SEC_ERROR_APPLICATION_CALLBACK_ERROR);
+}
+
+}  // namespace "nss_test"
--- a/gtests/ssl_gtest/ssl_gtest.gyp
+++ b/gtests/ssl_gtest/ssl_gtest.gyp
@@ -13,16 +13,17 @@
       'sources': [
         'libssl_internals.c',
         'selfencrypt_unittest.cc',
         'ssl_0rtt_unittest.cc',
         'ssl_agent_unittest.cc',
         'ssl_auth_unittest.cc',
         'ssl_cert_ext_unittest.cc',
         'ssl_ciphersuite_unittest.cc',
+        'ssl_custext_unittest.cc',
         'ssl_damage_unittest.cc',
         'ssl_dhe_unittest.cc',
         'ssl_drop_unittest.cc',
         'ssl_ecdh_unittest.cc',
         'ssl_ems_unittest.cc',
         'ssl_exporter_unittest.cc',
         'ssl_extension_unittest.cc',
         'ssl_fuzz_unittest.cc',
--- a/gtests/ssl_gtest/tls_filter.h
+++ b/gtests/ssl_gtest/tls_filter.h
@@ -254,21 +254,26 @@ class ChainedPacketFilter : public Packe
 typedef std::function<bool(TlsParser* parser, const TlsVersioned& header)>
     TlsExtensionFinder;
 
 class TlsExtensionFilter : public TlsHandshakeFilter {
  public:
   TlsExtensionFilter() : handshake_types_() {
     handshake_types_.insert(kTlsHandshakeClientHello);
     handshake_types_.insert(kTlsHandshakeServerHello);
+    handshake_types_.insert(kTlsHandshakeEncryptedExtensions);
   }
 
   TlsExtensionFilter(const std::set<uint8_t>& types)
       : handshake_types_(types) {}
 
+  void SetHandshakeTypes(const std::set<uint8_t>& types) {
+    handshake_types_ = types;
+  }
+
   static bool FindExtensions(TlsParser* parser, const HandshakeHeader& header);
 
  protected:
   PacketFilter::Action FilterHandshake(const HandshakeHeader& header,
                                        const DataBuffer& input,
                                        DataBuffer* output) override;
 
   virtual PacketFilter::Action FilterExtension(uint16_t extension_type,
--- a/lib/ssl/ssl.def
+++ b/lib/ssl/ssl.def
@@ -229,8 +229,15 @@ SSL_SetSessionTicketKeyPair;
 ;+};
 ;+NSS_3.30.0.1 { # Additional symbols for NSS 3.30 release
 ;+    global:
 SSL_AlertReceivedCallback;
 SSL_AlertSentCallback;
 ;+    local:
 ;+*;
 ;+};
+;+NSS_3.32 {    # NSS 3.32 release
+;+    global:
+SSL_GetExtensionSupport;
+SSL_InstallExtensionHooks;
+;+    local:
+;+*;
+;+};
--- a/lib/ssl/ssl.h
+++ b/lib/ssl/ssl.h
@@ -1369,11 +1369,138 @@ extern const char *NSSSSL_GetVersion(voi
  * return SECSuccess (normally), but that does not mean that the application
  * should continue using the connection. If the application passes a non-zero
  * value for second argument (error), or if SSL_AuthCertificateComplete returns
  * anything other than SECSuccess, then the application should close the
  * connection.
  */
 SSL_IMPORT SECStatus SSL_AuthCertificateComplete(PRFileDesc *fd,
                                                  PRErrorCode error);
+
+/*
+ * SSL_GetExtensionSupport() returns whether NSS supports a particular TLS extension.
+ *
+ * - ssl_ext_none indicates that NSS does not support the extension and
+ *   extension hooks can be installed.
+ *
+ * - ssl_ext_native indicates that NSS supports the extension natively, but
+ *   allows an application to override that support and install its own
+ *   extension hooks.
+ *
+ * - ssl_ext_native_only indicates that NSS supports the extension natively
+ *   and does not permit custom extension hooks to be installed.  These
+ *   extensions are critical to the functioning of NSS.
+ */
+typedef enum {
+    ssl_ext_none,
+    ssl_ext_native,
+    ssl_ext_native_only
+} SSLExtensionSupport;
+
+SSL_IMPORT SSLExtensionSupport
+SSL_GetExtensionSupport(PRUint16 extension);
+
+/*
+ * Custom extension hooks.
+ *
+ * The SSL_InstallExtensionHooks() registers two callback functions for use
+ * with the identified extension type.
+ *
+ * Installing extension hooks disables the checks in TLS 1.3 that ensure that
+ * extensions are only added to the correct messages.  The application is
+ * responsible for ensuring that extensions are only sent with the right message
+ * or messages.
+ *
+ * Installing an extension handler does not disable checks for whether an
+ * extension can be used in a message that is a response to an extension in
+ * another message.  Extensions in ServerHello, EncryptedExtensions and the
+ * server Certificate messages are rejected unless the client sends an extension
+ * in the ClientHello.  Similarly, a client Certificate message cannot contain
+ * extensions that don't appear in a CertificateRequest (in TLS 1.3).
+ *
+ * Setting both |writer| and |handler| to NULL removes any existing hooks for
+ * that extension.
+ *
+ * == SSLExtensionWriter
+ *
+ * An SSLExtensionWriter function is responsible for constructing the contents
+ * of an extension.  This function is called during the construction of all
+ * handshake messages where an extension might be included.
+ *
+ * - The |fd| argument is the socket file descriptor.
+ *
+ * - The |message| argument is the TLS handshake message type.  The writer will
+ *   be called for every handshake message that NSS sends.  Most extensions
+ *   should only be sent in a subset of messages.  NSS doesn’t check that
+ *   extension writers don’t violate protocol rules regarding which message an
+ *   extension can be sent in.
+ *
+ * - The |data| argument is a pointer to a buffer that should be written to with
+ *   any data for the extension.
+ *
+ * - The |len| argument is an outparam indicating how many bytes were written to
+ *   |data|.  The value referenced by |len| is initialized to zero, so an
+ *   extension that is empty does not need to write to this value.
+ *
+ * - The |maxLen| indicates the maximum number of bytes that can be written to
+ *   |data|.
+ *
+ * - The |arg| argument is the value of the writerArg that was passed during
+ *   installation.
+ *
+ * An SSLExtensionWriter function returns PR_TRUE if an extension should be
+ * written, and PR_FALSE otherwise.
+ *
+ * If there is an error, return PR_FALSE; if the error is truly fatal, the
+ * application can mark the connection as failed. However, recursively calling
+ * functions that alter the file descriptor in the callback - such as PR_Close()
+ * - should be avoided.
+ *
+ * Note: The ClientHello message can be sent twice in TLS 1.3.  An
+ * SSLExtensionWriter will be called twice with the same arguments in that case;
+ * NSS does not distinguish between a first and second ClientHello.  It is up to
+ * the application to track this if it needs to act differently each time.  In
+ * most cases the correct behaviour is to provide an identical extension on each
+ * invocation.
+ *
+ * == SSLExtensionHandler
+ *
+ * An SSLExtensionHandler function consumes a handshake message.  This function
+ * is called when an extension is present.
+ *
+ * - The |fd| argument is the socket file descriptor.
+ *
+ * - The |message| argument is the TLS handshake message type. This can be used
+ *   to validate that the extension was included in the correct handshake
+ *   message.
+ *
+ * - The |data| argument points to the contents of the extension.
+ *
+ * - The |len| argument contains the length of the extension.
+ *
+ * - The |alert| argument is an outparam that allows an application to choose
+ *   which alert is sent in the case of a fatal error.
+ *
+ * - The |arg| argument is the value of the handlerArg that was passed during
+ *   installation.
+ *
+ * An SSLExtensionHandler function returns SECSuccess when the extension is
+ * process successfully.  It can return SECFailure to cause the handshake to
+ * fail.  If the value of alert is written to, NSS will generate a fatal alert
+ * using the provided alert code.  The value of |alert| is otherwise not used.
+ */
+typedef PRBool(PR_CALLBACK *SSLExtensionWriter)(
+    PRFileDesc *fd, SSLHandshakeType message,
+    PRUint8 *data, unsigned int *len, unsigned int maxLen, void *arg);
+
+typedef SECStatus(PR_CALLBACK *SSLExtensionHandler)(
+    PRFileDesc *fd, SSLHandshakeType message,
+    const PRUint8 *data, unsigned int len,
+    SSLAlertDescription *alert, void *arg);
+
+SSL_IMPORT SECStatus
+SSL_InstallExtensionHooks(PRFileDesc *fd, PRUint16 extension,
+                          SSLExtensionWriter writer, void *writerArg,
+                          SSLExtensionHandler handler, void *handlerArg);
+
 SEC_END_PROTOS
 
 #endif /* __ssl_h_ */
--- a/lib/ssl/ssl3con.c
+++ b/lib/ssl/ssl3con.c
@@ -4953,22 +4953,22 @@ ssl3_SendClientHello(sslSocket *ss, sslC
     if (ss->ssl3.hs.helloRetry) {
         PORT_Assert(type == client_hello_retry);
     } else {
         ssl3_InitState(ss);
         ssl3_RestartHandshakeHashes(ss);
     }
 
     /* These must be reset every handshake. */
+    ssl3_ResetExtensionData(&ss->xtnData, ss);
     ss->ssl3.hs.sendingSCSV = PR_FALSE;
     ss->ssl3.hs.preliminaryInfo = 0;
     PORT_Assert(IS_DTLS(ss) || type != client_hello_retransmit);
     SECITEM_FreeItem(&ss->ssl3.hs.newSessionTicket.ticket, PR_FALSE);
     ss->ssl3.hs.receivedNewSessionTicket = PR_FALSE;
-    ssl3_ResetExtensionData(&ss->xtnData);
 
     /* How many suites does our PKCS11 support (regardless of policy)? */
     num_suites = ssl3_config_match_init(ss);
     if (!num_suites) {
         return SECFailure; /* ssl3_config_match_init has set error code. */
     }
 
     /*
@@ -5346,18 +5346,18 @@ ssl3_SendClientHello(sslSocket *ss, sslC
             rv = ssl3_AppendBufferToHandshakeVariable(ss, &extensionBuf, 2);
         }
         if (rv != SECSuccess) {
             goto loser; /* err set by AppendHandshake. */
         }
     }
 
     sslBuffer_Clear(&extensionBuf);
-    if (sid->u.ssl3.lock) {
-        unlockNeeded = PR_FALSE;
+    if (unlockNeeded) {
+        /* Note: goto loser can't be used past this point. */
         PR_RWLock_Unlock(sid->u.ssl3.lock);
     }
 
     if (ss->xtnData.sentSessionTicketInClientHello) {
         SSL_AtomicIncrementLong(&ssl3stats.sch_sid_stateless_resumes);
     }
 
     if (ss->ssl3.hs.sendingSCSV) {
@@ -8272,17 +8272,17 @@ ssl3_HandleClientHello(sslSocket *ss, PR
     rv = ssl_GetPeerInfo(ss);
     if (rv != SECSuccess) {
         return rv; /* error code is set. */
     }
 
     /* We might be starting session renegotiation in which case we should
      * clear previous state.
      */
-    ssl3_ResetExtensionData(&ss->xtnData);
+    ssl3_ResetExtensionData(&ss->xtnData, ss);
     ss->statelessResume = PR_FALSE;
 
     if (IS_DTLS(ss)) {
         dtls_RehandshakeCleanup(ss);
     }
 
     rv = ssl3_ConsumeHandshakeNumber(ss, &tmp, 2, &b, &length);
     if (rv != SECSuccess)
@@ -9027,18 +9027,16 @@ ssl3_HandleV2ClientHello(sslSocket *ss, 
     unsigned int total = SSL_HL_CLIENT_HELLO_HBYTES;
 
     SSL_TRC(3, ("%d: SSL3[%d]: handle v2 client_hello", SSL_GETPID(), ss->fd));
 
     PORT_Assert(ss->opt.noLocks || ssl_HaveRecvBufLock(ss));
 
     ssl_GetSSL3HandshakeLock(ss);
 
-    ssl3_ResetExtensionData(&ss->xtnData);
-
     version = (buffer[1] << 8) | buffer[2];
     if (version < SSL_LIBRARY_VERSION_3_0) {
         goto loser;
     }
 
     ssl3_InitState(ss);
     ssl3_RestartHandshakeHashes(ss);
 
@@ -12795,17 +12793,17 @@ ssl3_InitState(sslSocket *ss)
     ssl3_InitCipherSpec(ss->ssl3.prSpec);
     ss->ssl3.crSpec->version = ss->ssl3.prSpec->version = ss->vrange.max;
     ssl_ReleaseSpecWriteLock(ss);
 
     ss->ssl3.hs.sendingSCSV = PR_FALSE;
     ss->ssl3.hs.preliminaryInfo = 0;
     ss->ssl3.hs.ws = (ss->sec.isServer) ? wait_client_hello : wait_server_hello;
 
-    ssl3_ResetExtensionData(&ss->xtnData);
+    ssl3_ResetExtensionData(&ss->xtnData, ss);
     PR_INIT_CLIST(&ss->ssl3.hs.remoteExtensions);
     if (IS_DTLS(ss)) {
         ss->ssl3.hs.sendMessageSeq = 0;
         ss->ssl3.hs.recvMessageSeq = 0;
         ss->ssl3.hs.rtTimeoutMs = DTLS_RETRANSMIT_INITIAL_MS;
         ss->ssl3.hs.rtRetries = 0;
         ss->ssl3.hs.recvdHighWater = -1;
         PR_INIT_CLIST(&ss->ssl3.hs.lastMessageFlight);
@@ -13171,17 +13169,17 @@ ssl3_DestroySSL3Info(sslSocket *ss)
         dtls_FreeHandshakeMessages(&ss->ssl3.hs.lastMessageFlight);
         if (ss->ssl3.hs.recvdFragments.buf) {
             PORT_Free(ss->ssl3.hs.recvdFragments.buf);
         }
     }
 
     /* Destroy remote extensions */
     ssl3_DestroyRemoteExtensions(&ss->ssl3.hs.remoteExtensions);
-    ssl3_ResetExtensionData(&ss->xtnData);
+    ssl3_DestroyExtensionData(&ss->xtnData);
 
     /* Destroy TLS 1.3 cipher specs */
     tls13_DestroyCipherSpecs(&ss->ssl3.hs.cipherSpecs);
 
     /* Destroy TLS 1.3 keys */
     if (ss->ssl3.hs.currentSecret)
         PK11_FreeSymKey(ss->ssl3.hs.currentSecret);
     if (ss->ssl3.hs.resumptionMasterSecret)
--- a/lib/ssl/ssl3ext.c
+++ b/lib/ssl/ssl3ext.c
@@ -146,16 +146,130 @@ static const sslExtensionBuilder clientH
 };
 
 static const sslExtensionBuilder tls13_cert_req_senders[] = {
     { ssl_signature_algorithms_xtn, &ssl3_SendSigAlgsXtn },
     { ssl_tls13_certificate_authorities_xtn, &tls13_SendCertAuthoritiesXtn },
     { 0, NULL }
 };
 
+static const struct {
+    SSLExtensionType type;
+    SSLExtensionSupport support;
+} ssl_supported_extensions[] = {
+    { ssl_server_name_xtn, ssl_ext_native_only },
+    { ssl_cert_status_xtn, ssl_ext_native },
+    { ssl_supported_groups_xtn, ssl_ext_native_only },
+    { ssl_ec_point_formats_xtn, ssl_ext_native },
+    { ssl_signature_algorithms_xtn, ssl_ext_native_only },
+    { ssl_use_srtp_xtn, ssl_ext_native },
+    { ssl_app_layer_protocol_xtn, ssl_ext_native_only },
+    { 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_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 }
+};
+
+SSLExtensionSupport
+SSL_GetExtensionSupport(PRUint16 type)
+{
+    unsigned int i;
+    for (i = 0; i < PR_ARRAY_SIZE(ssl_supported_extensions); ++i) {
+        if (type == ssl_supported_extensions[i].type) {
+            return ssl_supported_extensions[i].support;
+        }
+    }
+    return ssl_ext_none;
+}
+
+SECStatus
+SSL_InstallExtensionHooks(PRFileDesc *fd, PRUint16 extension,
+                          SSLExtensionWriter writer, void *writerArg,
+                          SSLExtensionHandler handler, void *handlerArg)
+{
+    sslSocket *ss = ssl_FindSocket(fd);
+    PRCList *cursor;
+    sslCustomExtensionHooks *hook;
+
+    if (!ss) {
+        return SECFailure; /* Code already set. */
+    }
+
+    /* Need to specify both or neither, but not just one. */
+    if ((writer && !handler) || (!writer && handler)) {
+        PORT_SetError(SEC_ERROR_INVALID_ARGS);
+        return SECFailure;
+    }
+
+    if (SSL_GetExtensionSupport(extension) == ssl_ext_native_only) {
+        PORT_SetError(SEC_ERROR_INVALID_ARGS);
+        return SECFailure;
+    }
+
+    if (ss->ssl3.hs.ws != idle_handshake || ss->firstHsDone) {
+        PORT_SetError(PR_INVALID_STATE_ERROR);
+        return SECFailure;
+    }
+
+    /* Remove any old handler. */
+    for (cursor = PR_NEXT_LINK(&ss->extensionHooks);
+         cursor != &ss->extensionHooks;
+         cursor = PR_NEXT_LINK(cursor)) {
+        hook = (sslCustomExtensionHooks *)cursor;
+        if (hook->type == extension) {
+            PR_REMOVE_LINK(&hook->link);
+            PORT_Free(hook);
+            break;
+        }
+    }
+
+    if (!writer && !handler) {
+        return SECSuccess;
+    }
+
+    hook = PORT_ZNew(sslCustomExtensionHooks);
+    if (!hook) {
+        return SECFailure; /* This removed the old one, oh well. */
+    }
+
+    hook->type = extension;
+    hook->writer = writer;
+    hook->writerArg = writerArg;
+    hook->handler = handler;
+    hook->handlerArg = handlerArg;
+    PR_APPEND_LINK(&hook->link, &ss->extensionHooks);
+    return SECSuccess;
+}
+
+static sslCustomExtensionHooks *
+ssl_FindCustomExtensionHooks(sslSocket *ss, PRUint16 extension)
+{
+    PRCList *cursor;
+
+    for (cursor = PR_NEXT_LINK(&ss->extensionHooks);
+         cursor != &ss->extensionHooks;
+         cursor = PR_NEXT_LINK(cursor)) {
+        sslCustomExtensionHooks *hook = (sslCustomExtensionHooks *)cursor;
+        if (hook->type == extension) {
+            return hook;
+        }
+    }
+
+    return NULL;
+}
+
 static PRBool
 arrayContainsExtension(const PRUint16 *array, PRUint32 len, PRUint16 ex_type)
 {
     unsigned int i;
     for (i = 0; i < len; i++) {
         if (ex_type == array[i])
             return PR_TRUE;
     }
@@ -165,18 +279,21 @@ arrayContainsExtension(const PRUint16 *a
 PRBool
 ssl3_ExtensionNegotiated(const sslSocket *ss, PRUint16 ex_type)
 {
     const TLSExtensionData *xtnData = &ss->xtnData;
     return arrayContainsExtension(xtnData->negotiated,
                                   xtnData->numNegotiated, ex_type);
 }
 
+/* This checks for whether an extension was advertised.  On the client, this
+ * covers extensions that are sent in ClientHello; on the server, extensions
+ * sent in CertificateRequest (TLS 1.3 only). */
 PRBool
-ssl3_ClientExtensionAdvertised(const sslSocket *ss, PRUint16 ex_type)
+ssl3_ExtensionAdvertised(const sslSocket *ss, PRUint16 ex_type)
 {
     const TLSExtensionData *xtnData = &ss->xtnData;
     return arrayContainsExtension(xtnData->advertised,
                                   xtnData->numAdvertised, ex_type);
 }
 
 /* Go through hello extensions in |b| and deserialize
  * them into the list in |ss->ssl3.hs.remoteExtensions|.
@@ -249,43 +366,79 @@ ssl3_FindExtension(sslSocket *ss, SSLExt
         if (extension->type == extension_type) {
             return extension;
         }
     }
 
     return NULL;
 }
 
+static SECStatus
+ssl_CallExtensionHandler(sslSocket *ss, SSLHandshakeType handshakeMessage,
+                         TLSExtension *extension,
+                         const ssl3ExtensionHandler *handler)
+{
+    SECStatus rv = SECSuccess;
+    SSLAlertDescription alert = handshake_failure;
+    sslCustomExtensionHooks *customHooks;
+
+    customHooks = ssl_FindCustomExtensionHooks(ss, extension->type);
+    if (customHooks) {
+        if (customHooks->handler) {
+            rv = customHooks->handler(ss->fd, handshakeMessage,
+                                      extension->data.data,
+                                      extension->data.len,
+                                      &alert, customHooks->handlerArg);
+        }
+    } else {
+        /* Find extension_type in table of Hello Extension Handlers. */
+        for (; handler->ex_handler != NULL; ++handler) {
+            if (handler->ex_type == extension->type) {
+                rv = (*handler->ex_handler)(ss, &ss->xtnData, &extension->data);
+                break;
+            }
+        }
+    }
+
+    if (rv != SECSuccess) {
+        if (!ss->ssl3.fatalAlertSent) {
+            /* Send an alert if the handler didn't already. */
+            (void)SSL3_SendAlert(ss, alert_fatal, alert);
+        }
+        return SECFailure;
+    }
+
+    return SECSuccess;
+}
+
 /* Go through the hello extensions in |ss->ssl3.hs.remoteExtensions|.
  * For each one, find the extension handler in the table, and
  * if present, invoke that handler.
  * Servers ignore any extensions with unknown extension types.
  * Clients reject any extensions with unadvertised extension types
  *
  * In TLS >= 1.3, the client checks that extensions appear in the
  * right phase.
  */
 SECStatus
-ssl3_HandleParsedExtensions(sslSocket *ss,
-                            SSLHandshakeType handshakeMessage)
+ssl3_HandleParsedExtensions(sslSocket *ss, SSLHandshakeType message)
 {
     const ssl3ExtensionHandler *handlers;
-    const ssl3ExtensionHandler *handler;
     /* HelloRetryRequest doesn't set ss->version. It might be safe to
      * do so, but we weren't entirely sure. TODO(ekr@rtfm.com). */
     PRBool isTLS13 = (ss->version >= SSL_LIBRARY_VERSION_TLS_1_3) ||
-                     (handshakeMessage == ssl_hs_hello_retry_request);
+                     (message == ssl_hs_hello_retry_request);
     /* The following messages can include extensions that were not included in
      * the original ClientHello. */
-    PRBool allowNotOffered = (handshakeMessage == ssl_hs_client_hello) ||
-                             (handshakeMessage == ssl_hs_certificate_request) ||
-                             (handshakeMessage == ssl_hs_new_session_ticket);
+    PRBool allowNotOffered = (message == ssl_hs_client_hello) ||
+                             (message == ssl_hs_certificate_request) ||
+                             (message == ssl_hs_new_session_ticket);
     PRCList *cursor;
 
-    switch (handshakeMessage) {
+    switch (message) {
         case ssl_hs_client_hello:
             handlers = clientHelloHandlers;
             break;
         case ssl_hs_new_session_ticket:
             PORT_Assert(ss->version >= SSL_LIBRARY_VERSION_TLS_1_3);
             handlers = newSessionTicketHandlers;
             break;
         case ssl_hs_hello_retry_request:
@@ -314,35 +467,37 @@ ssl3_HandleParsedExtensions(sslSocket *s
             PORT_Assert(0);
             return SECFailure;
     }
 
     for (cursor = PR_NEXT_LINK(&ss->ssl3.hs.remoteExtensions);
          cursor != &ss->ssl3.hs.remoteExtensions;
          cursor = PR_NEXT_LINK(cursor)) {
         TLSExtension *extension = (TLSExtension *)cursor;
+        SECStatus rv;
 
         /* Check whether the server sent an extension which was not advertised
          * in the ClientHello.
          *
          * Note that a TLS 1.3 server should check if CertificateRequest
          * extensions were sent.  But the extensions used for CertificateRequest
          * do not have any response, so we rely on
-         * ssl3_ClientExtensionAdvertised to return false on the server.  That
+         * ssl3_ExtensionAdvertised to return false on the server.  That
          * results in the server only rejecting any extension. */
         if (!allowNotOffered && (extension->type != ssl_tls13_cookie_xtn) &&
-            !ssl3_ClientExtensionAdvertised(ss, extension->type)) {
+            !ssl3_ExtensionAdvertised(ss, extension->type)) {
             (void)SSL3_SendAlert(ss, alert_fatal, unsupported_extension);
             PORT_SetError(SSL_ERROR_RX_UNEXPECTED_EXTENSION);
             return SECFailure;
         }
 
         /* Check that this is a legal extension in TLS 1.3 */
-        if (isTLS13) {
-            switch (tls13_ExtensionStatus(extension->type, handshakeMessage)) {
+        if (isTLS13 &&
+            !ssl_FindCustomExtensionHooks(ss, extension->type)) {
+            switch (tls13_ExtensionStatus(extension->type, message)) {
                 case tls13_extension_allowed:
                     break;
                 case tls13_extension_unknown:
                     if (allowNotOffered) {
                         continue; /* Skip over unknown extensions. */
                     }
                 /* Fall through. */
                 case tls13_extension_disallowed:
@@ -358,33 +513,19 @@ ssl3_HandleParsedExtensions(sslSocket *s
             (extension->type == ssl_tls13_pre_shared_key_xtn) &&
             (PR_NEXT_LINK(cursor) != &ss->ssl3.hs.remoteExtensions)) {
             tls13_FatalError(ss,
                              SSL_ERROR_RX_MALFORMED_CLIENT_HELLO,
                              illegal_parameter);
             return SECFailure;
         }
 
-        /* find extension_type in table of Hello Extension Handlers */
-        for (handler = handlers; handler->ex_handler; ++handler) {
-            /* if found, call this handler */
-            if (handler->ex_type == extension->type) {
-                SECStatus rv;
-
-                rv = (*handler->ex_handler)(ss, &ss->xtnData,
-                                            &extension->data);
-                if (rv != SECSuccess) {
-                    if (!ss->ssl3.fatalAlertSent) {
-                        /* send a generic alert if the handler didn't already */
-                        (void)SSL3_SendAlert(ss, alert_fatal, handshake_failure);
-                    }
-                    return SECFailure;
-                }
-                break;
-            }
+        rv = ssl_CallExtensionHandler(ss, message, extension, handlers);
+        if (rv != SECSuccess) {
+            return SECFailure;
         }
     }
     return SECSuccess;
 }
 
 /* Syntactic sugar around ssl3_ParseExtensions and
  * ssl3_HandleParsedExtensions. */
 SECStatus
@@ -451,16 +592,89 @@ ssl3_RegisterExtensionSender(const sslSo
             break;
         }
     }
     PORT_Assert(i < SSL_MAX_EXTENSIONS); /* table needs to grow */
     PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
     return SECFailure;
 }
 
+static SECStatus
+ssl_CallCustomExtensionSenders(sslSocket *ss, sslBuffer *buf,
+                               SSLHandshakeType message)
+{
+    sslBuffer tail = { NULL, 0, 0 };
+    SECStatus rv;
+    PRCList *cursor;
+
+    /* Save any extensions that want to be last. */
+    if (ss->xtnData.lastXtnOffset) {
+        rv = sslBuffer_Append(&tail, buf->buf + ss->xtnData.lastXtnOffset,
+                              buf->len - ss->xtnData.lastXtnOffset);
+        if (rv != SECSuccess) {
+            return SECFailure;
+        }
+        buf->len = ss->xtnData.lastXtnOffset;
+    }
+
+    /* Reserve the maximum amount of space possible. */
+    rv = sslBuffer_Grow(buf, 65535);
+    if (rv != SECSuccess) {
+        return SECFailure;
+    }
+
+    for (cursor = PR_NEXT_LINK(&ss->extensionHooks);
+         cursor != &ss->extensionHooks;
+         cursor = PR_NEXT_LINK(cursor)) {
+        sslCustomExtensionHooks *hook =
+            (sslCustomExtensionHooks *)cursor;
+        PRBool append = PR_FALSE;
+        unsigned int len = 0;
+
+        if (hook->writer) {
+            /* The writer writes directly into |buf|.  Provide space that allows
+             * for the existing extensions, any tail, plus type and length. */
+            unsigned int space = buf->space - (buf->len + tail.len + 4);
+            append = (*hook->writer)(ss->fd, message,
+                                     buf->buf + buf->len + 4, &len, space,
+                                     hook->writerArg);
+            if (len > space) {
+                PORT_SetError(SEC_ERROR_APPLICATION_CALLBACK_ERROR);
+                goto loser;
+            }
+        }
+        if (!append) {
+            continue;
+        }
+
+        rv = sslBuffer_AppendNumber(buf, hook->type, 2);
+        if (rv != SECSuccess) {
+            goto loser; /* Code already set. */
+        }
+        rv = sslBuffer_AppendNumber(buf, len, 2);
+        if (rv != SECSuccess) {
+            goto loser; /* Code already set. */
+        }
+        buf->len += len;
+
+        if (message == ssl_hs_client_hello ||
+            message == ssl_hs_certificate_request) {
+            ss->xtnData.advertised[ss->xtnData.numAdvertised++] = hook->type;
+        }
+    }
+
+    sslBuffer_Append(buf, tail.buf, tail.len);
+    sslBuffer_Clear(&tail);
+    return SECSuccess;
+
+loser:
+    sslBuffer_Clear(&tail);
+    return SECFailure;
+}
+
 /* Call extension handlers for the given message. */
 SECStatus
 ssl_ConstructExtensions(sslSocket *ss, sslBuffer *buf, SSLHandshakeType message)
 {
     const sslExtensionBuilder *sender;
     SECStatus rv;
 
     PORT_Assert(buf->len == 0);
@@ -494,21 +708,25 @@ ssl_ConstructExtensions(sslSocket *ss, s
             break;
 
         default:
             PORT_Assert(0);
             PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
             return SECFailure;
     }
 
-    for (; sender->ex_sender; ++sender) {
+    for (; sender->ex_sender != NULL; ++sender) {
         PRBool append = PR_FALSE;
         unsigned int start = buf->len;
         unsigned int length;
 
+        if (ssl_FindCustomExtensionHooks(ss, sender->ex_type)) {
+            continue;
+        }
+
         /* Save space for the extension type and length. Note that we don't grow
          * the buffer now; rely on sslBuffer_Append* to do that. */
         buf->len += 4;
         rv = (*sender->ex_sender)(ss, &ss->xtnData, buf, &append);
         if (rv != SECSuccess) {
             goto loser;
         }
 
@@ -526,22 +744,30 @@ ssl_ConstructExtensions(sslSocket *ss, s
         }
         rv = sslBuffer_AppendNumber(buf, length, 2);
         if (rv != SECSuccess) {
             goto loser; /* Code already set. */
         }
         /* Skip over the extension body. */
         buf->len += length;
 
-        if (message == ssl_hs_client_hello) {
+        if (message == ssl_hs_client_hello ||
+            message == ssl_hs_certificate_request) {
             ss->xtnData.advertised[ss->xtnData.numAdvertised++] =
                 sender->ex_type;
         }
     }
 
+    if (!PR_CLIST_IS_EMPTY(&ss->extensionHooks)) {
+        rv = ssl_CallCustomExtensionSenders(ss, buf, message);
+        if (rv != SECSuccess) {
+            goto loser;
+        }
+    }
+
     if (buf->len > 0xffff) {
         PORT_SetError(SSL_ERROR_TX_RECORD_TOO_LONG);
         goto loser;
     }
 
     return SECSuccess;
 
 loser:
@@ -617,27 +843,27 @@ ssl_InsertPaddingExtension(const sslSock
      * block and the size of the existing extensions. */
     paddingLen = ssl_CalculatePaddingExtLen(ss, prefixLen + 2 + buf->len);
     if (!paddingLen) {
         return SECSuccess;
     }
 
     /* Move the tail if there is one. This only happens if we are sending the
      * TLS 1.3 PSK extension, which needs to be at the end. */
-    if (ss->xtnData.paddingOffset) {
-        PORT_Assert(buf->len > ss->xtnData.paddingOffset);
-        tailLen = buf->len - ss->xtnData.paddingOffset;
+    if (ss->xtnData.lastXtnOffset) {
+        PORT_Assert(buf->len > ss->xtnData.lastXtnOffset);
+        tailLen = buf->len - ss->xtnData.lastXtnOffset;
         rv = sslBuffer_Grow(buf, buf->len + 4 + paddingLen);
         if (rv != SECSuccess) {
             return SECFailure;
         }
-        PORT_Memmove(buf->buf + ss->xtnData.paddingOffset + 4 + paddingLen,
-                     buf->buf + ss->xtnData.paddingOffset,
+        PORT_Memmove(buf->buf + ss->xtnData.lastXtnOffset + 4 + paddingLen,
+                     buf->buf + ss->xtnData.lastXtnOffset,
                      tailLen);
-        buf->len = ss->xtnData.paddingOffset;
+        buf->len = ss->xtnData.lastXtnOffset;
     } else {
         tailLen = 0;
     }
 
     rv = sslBuffer_AppendNumber(buf, ssl_padding_xtn, 2);
     if (rv != SECSuccess) {
         return SECFailure; /* Code already set. */
     }
@@ -660,42 +886,65 @@ ssl3_DestroyRemoteExtensions(PRCList *li
         cur_p = PR_LIST_TAIL(list);
         PR_REMOVE_LINK(cur_p);
         PORT_Free(cur_p);
     }
 }
 
 /* Initialize the extension data block. */
 void
-ssl3_InitExtensionData(TLSExtensionData *xtnData)
+ssl3_InitExtensionData(TLSExtensionData *xtnData, const sslSocket *ss)
 {
+    unsigned int advertisedMax;
+    PRCList *cursor;
+
     /* Set things up to the right starting state. */
     PORT_Memset(xtnData, 0, sizeof(*xtnData));
     xtnData->peerSupportsFfdheGroups = PR_FALSE;
     PR_INIT_CLIST(&xtnData->remoteKeyShares);
+
+    /* Allocate enough to allow for native extensions, plus any custom ones. */
+    if (ss->sec.isServer) {
+        advertisedMax = PR_MAX(PR_ARRAY_SIZE(certificateRequestHandlers),
+                               PR_ARRAY_SIZE(tls13_cert_req_senders));
+    } else {
+        advertisedMax = PR_MAX(PR_ARRAY_SIZE(clientHelloHandlers),
+                               PR_ARRAY_SIZE(clientHelloSendersTLS));
+        ++advertisedMax; /* For the RI SCSV, which we also track. */
+    }
+    for (cursor = PR_NEXT_LINK(&ss->extensionHooks);
+         cursor != &ss->extensionHooks;
+         cursor = PR_NEXT_LINK(cursor)) {
+        ++advertisedMax;
+    }
+    xtnData->advertised = PORT_ZNewArray(PRUint16, advertisedMax);
 }
 
-/* Free everything that has been allocated and then reset back to
- * the starting state. */
 void
-ssl3_ResetExtensionData(TLSExtensionData *xtnData)
+ssl3_DestroyExtensionData(TLSExtensionData *xtnData)
 {
-    /* Clean up. */
     ssl3_FreeSniNameArray(xtnData);
     PORT_Free(xtnData->sigSchemes);
     SECITEM_FreeItem(&xtnData->nextProto, PR_FALSE);
     tls13_DestroyKeyShares(&xtnData->remoteKeyShares);
     SECITEM_FreeItem(&xtnData->certReqContext, PR_FALSE);
     if (xtnData->certReqAuthorities.arena) {
         PORT_FreeArena(xtnData->certReqAuthorities.arena, PR_FALSE);
         xtnData->certReqAuthorities.arena = NULL;
     }
+    PORT_Free(xtnData->advertised);
+}
 
-    /* Now reinit. */
-    ssl3_InitExtensionData(xtnData);
+/* Free everything that has been allocated and then reset back to
+ * the starting state. */
+void
+ssl3_ResetExtensionData(TLSExtensionData *xtnData, const sslSocket *ss)
+{
+    ssl3_DestroyExtensionData(xtnData);
+    ssl3_InitExtensionData(xtnData, ss);
 }
 
 /* Thunks to let extension handlers operate on const sslSocket* objects. */
 void
 ssl3_ExtSendAlert(const sslSocket *ss, SSL3AlertLevel level,
                   SSL3AlertDescription desc)
 {
     (void)SSL3_SendAlert((sslSocket *)ss, level, desc);
--- a/lib/ssl/ssl3ext.h
+++ b/lib/ssl/ssl3ext.h
@@ -30,20 +30,20 @@ typedef struct {
 } sslExtensionBuilder;
 
 struct TLSExtensionDataStr {
     /* registered callbacks that send server hello extensions */
     sslExtensionBuilder serverHelloSenders[SSL_MAX_EXTENSIONS];
     sslExtensionBuilder encryptedExtensionsSenders[SSL_MAX_EXTENSIONS];
     sslExtensionBuilder certificateSenders[SSL_MAX_EXTENSIONS];
 
-    /* Keep track of the extensions that are negotiated. */
+    /* Keep track of the extensions that are advertised or negotiated. */
     PRUint16 numAdvertised;
+    PRUint16 *advertised; /* Allocated dynamically. */
     PRUint16 numNegotiated;
-    PRUint16 advertised[SSL_MAX_EXTENSIONS];
     PRUint16 negotiated[SSL_MAX_EXTENSIONS];
 
     /* SessionTicket Extension related data. */
     PRBool ticketTimestampVerified;
     PRBool emptySessionTicket;
     PRBool sentSessionTicketInClientHello;
     SECItem psk_ke_modes;
     PRUint32 max_early_data_size;
@@ -81,43 +81,53 @@ struct TLSExtensionDataStr {
     /* In a client: if the server supports Next Protocol Negotiation, then
      * this is the protocol that was negotiated.
      */
     SECItem nextProto;
     SSLNextProtoState nextProtoState;
 
     PRUint16 dtlsSRTPCipherSuite; /* 0 if not selected */
 
-    unsigned int paddingOffset;      /* Where to insert padding. 0 = end. */
+    unsigned int lastXtnOffset;      /* Where to insert padding. 0 = end. */
     SECItem pskBinder;               /* The PSK binder for the first PSK (TLS 1.3) */
     unsigned int pskBinderPrefixLen; /* The length of the binder input. */
     PRCList remoteKeyShares;         /* The other side's public keys (TLS 1.3) */
 };
 
 typedef struct TLSExtensionStr {
     PRCList link;  /* The linked list link */
     PRUint16 type; /* Extension type */
     SECItem data;  /* Pointers into the handshake data. */
 } TLSExtension;
 
+typedef struct sslCustomExtensionHooks {
+    PRCList link;
+    PRUint16 type;
+    SSLExtensionWriter writer;
+    void *writerArg;
+    SSLExtensionHandler handler;
+    void *handlerArg;
+} sslCustomExtensionHooks;
+
 SECStatus ssl3_HandleExtensions(sslSocket *ss,
                                 PRUint8 **b, PRUint32 *length,
                                 SSLHandshakeType handshakeMessage);
 SECStatus ssl3_ParseExtensions(sslSocket *ss,
                                PRUint8 **b, PRUint32 *length);
 SECStatus ssl3_HandleParsedExtensions(sslSocket *ss,
                                       SSLHandshakeType handshakeMessage);
 TLSExtension *ssl3_FindExtension(sslSocket *ss,
                                  SSLExtensionType extension_type);
 void ssl3_DestroyRemoteExtensions(PRCList *list);
-void ssl3_InitExtensionData(TLSExtensionData *xtnData);
-void ssl3_ResetExtensionData(TLSExtensionData *xtnData);
+void ssl3_InitExtensionData(TLSExtensionData *xtnData, const sslSocket *ss);
+void ssl3_DestroyExtensionData(TLSExtensionData *xtnData);
+void ssl3_ResetExtensionData(TLSExtensionData *xtnData, const sslSocket *ss);
 
 PRBool ssl3_ExtensionNegotiated(const sslSocket *ss, PRUint16 ex_type);
-PRBool ssl3_ClientExtensionAdvertised(const sslSocket *ss, PRUint16 ex_type);
+PRBool ssl3_ExtensionAdvertised(const sslSocket *ss, PRUint16 ex_type);
 
 SECStatus ssl3_RegisterExtensionSender(const sslSocket *ss,
                                        TLSExtensionData *xtnData,
                                        PRUint16 ex_type,
                                        sslExtensionBuilderFunc cb);
 SECStatus ssl_ConstructExtensions(sslSocket *ss, sslBuffer *buf,
                                   SSLHandshakeType message);
 SECStatus ssl_SendEmptyExtension(const sslSocket *ss, TLSExtensionData *xtnData,
--- a/lib/ssl/ssl3exthandle.c
+++ b/lib/ssl/ssl3exthandle.c
@@ -1808,17 +1808,18 @@ ssl3_ServerHandleSignedCertTimestampXtn(
                                         ssl_signed_cert_timestamp_xtn,
                                         ssl3_ServerSendSignedCertTimestampXtn);
 }
 
 /* Just make sure that the remote client supports uncompressed points,
  * Since that is all we support.  Disable ECC cipher suites if it doesn't.
  */
 SECStatus
-ssl3_HandleSupportedPointFormatsXtn(const sslSocket *ss, TLSExtensionData *xtnData,
+ssl3_HandleSupportedPointFormatsXtn(const sslSocket *ss,
+                                    TLSExtensionData *xtnData,
                                     SECItem *data)
 {
     int i;
 
     PORT_Assert(ss->version < SSL_LIBRARY_VERSION_TLS_1_3);
 
     if (data->len < 2 || data->len > 255 || !data->data ||
         data->len != (unsigned int)data->data[0] + 1) {
--- a/lib/ssl/sslimpl.h
+++ b/lib/ssl/sslimpl.h
@@ -1127,17 +1127,17 @@ struct sslSocketStr {
     void *badCertArg;
     SSLHandshakeCallback handshakeCallback;
     void *handshakeCallbackData;
     SSLCanFalseStartCallback canFalseStartCallback;
     void *canFalseStartCallbackData;
     void *pkcs11PinArg;
     SSLNextProtoCallback nextProtoCallback;
     void *nextProtoArg;
-    PRCList extensionHandlers;
+    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/sslsecur.c
+++ b/lib/ssl/sslsecur.c
@@ -196,17 +196,17 @@ SSL_ResetHandshake(PRFileDesc *s, PRBool
     ssl_ResetSecurityInfo(&ss->sec, PR_TRUE);
     status = ssl_CreateSecurityInfo(ss);
     ssl_ReleaseXmitBufLock(ss);
 
     ssl_ReleaseSSL3HandshakeLock(ss);
     ssl_Release1stHandshakeLock(ss);
 
     ssl3_DestroyRemoteExtensions(&ss->ssl3.hs.remoteExtensions);
-    ssl3_ResetExtensionData(&ss->xtnData);
+    ssl3_ResetExtensionData(&ss->xtnData, ss);
 
     if (!ss->TCPconnected)
         ss->TCPconnected = (PR_SUCCESS == ssl_DefGetpeername(ss, &addr));
 
 loser:
     SSL_UNLOCK_WRITER(ss);
     SSL_UNLOCK_READER(ss);
 
--- a/lib/ssl/sslsock.c
+++ b/lib/ssl/sslsock.c
@@ -290,36 +290,48 @@ ssl_DupSocket(sslSocket *os)
                     os->ssl3.signatureSchemeCount);
     ss->ssl3.signatureSchemeCount = os->ssl3.signatureSchemeCount;
     ss->ssl3.downgradeCheckVersion = os->ssl3.downgradeCheckVersion;
 
     ss->ssl3.dheWeakGroupEnabled = os->ssl3.dheWeakGroupEnabled;
 
     if (ss->opt.useSecurity) {
         PRCList *cursor;
+
         for (cursor = PR_NEXT_LINK(&os->serverCerts);
              cursor != &os->serverCerts;
              cursor = PR_NEXT_LINK(cursor)) {
             sslServerCert *sc = ssl_CopyServerCert((sslServerCert *)cursor);
             if (!sc)
                 goto loser;
             PR_APPEND_LINK(&sc->link, &ss->serverCerts);
         }
 
-        PR_INIT_CLIST(&ss->ephemeralKeyPairs);
         for (cursor = PR_NEXT_LINK(&os->ephemeralKeyPairs);
              cursor != &os->ephemeralKeyPairs;
              cursor = PR_NEXT_LINK(cursor)) {
             sslEphemeralKeyPair *okp = (sslEphemeralKeyPair *)cursor;
             sslEphemeralKeyPair *skp = ssl_CopyEphemeralKeyPair(okp);
             if (!skp)
                 goto loser;
             PR_APPEND_LINK(&skp->link, &ss->ephemeralKeyPairs);
         }
 
+        for (cursor = PR_NEXT_LINK(&os->extensionHooks);
+             cursor != &os->extensionHooks;
+             cursor = PR_NEXT_LINK(cursor)) {
+            sslCustomExtensionHooks *oh = (sslCustomExtensionHooks *)cursor;
+            sslCustomExtensionHooks *sh = PORT_ZNew(sslCustomExtensionHooks);
+            if (!sh) {
+                goto loser;
+            }
+            *sh = *oh;
+            PR_APPEND_LINK(&sh->link, &ss->extensionHooks);
+        }
+
         /*
          * XXX the preceding CERT_ and SECKEY_ functions can fail and return NULL.
          * XXX We should detect this, and not just march on with NULL pointers.
          */
         ss->authCertificate = os->authCertificate;
         ss->authCertificateArg = os->authCertificateArg;
         ss->getClientAuthData = os->getClientAuthData;
         ss->getClientAuthDataArg = os->getClientAuthDataArg;
@@ -412,16 +424,24 @@ ssl_DestroySocketContents(sslSocket *ss)
         PORT_Free((void *)ss->url); /* CONST */
 
     /* Clean up server certificates and sundries. */
     while (!PR_CLIST_IS_EMPTY(&ss->serverCerts)) {
         cursor = PR_LIST_TAIL(&ss->serverCerts);
         PR_REMOVE_LINK(cursor);
         ssl_FreeServerCert((sslServerCert *)cursor);
     }
+
+    /* Remove extension handlers. */
+    while (!PR_CLIST_IS_EMPTY(&ss->extensionHooks)) {
+        cursor = PR_LIST_TAIL(&ss->extensionHooks);
+        PR_REMOVE_LINK(cursor);
+        PORT_Free(cursor);
+    }
+
     ssl_FreeEphemeralKeyPairs(ss);
     SECITEM_FreeItem(&ss->opt.nextProtoNego, PR_FALSE);
     ssl3_FreeSniNameArray(&ss->xtnData);
 }
 
 /*
  * free an sslSocket struct, and all the stuff that hangs off of it
  */
@@ -2114,16 +2134,35 @@ SSL_ReconfigFD(PRFileDesc *model, PRFile
          cursor != &sm->ephemeralKeyPairs;
          cursor = PR_NEXT_LINK(cursor)) {
         sslEphemeralKeyPair *mkp = (sslEphemeralKeyPair *)cursor;
         sslEphemeralKeyPair *skp = ssl_CopyEphemeralKeyPair(mkp);
         if (!skp)
             return NULL;
         PR_APPEND_LINK(&skp->link, &ss->ephemeralKeyPairs);
     }
+
+    while (!PR_CLIST_IS_EMPTY(&ss->extensionHooks)) {
+        cursor = PR_LIST_TAIL(&ss->extensionHooks);
+        PR_REMOVE_LINK(cursor);
+        PORT_Free(cursor);
+    }
+    for (cursor = PR_NEXT_LINK(&sm->extensionHooks);
+         cursor != &sm->extensionHooks;
+         cursor = PR_NEXT_LINK(cursor)) {
+        SECStatus rv;
+        sslCustomExtensionHooks *hook = (sslCustomExtensionHooks *)cursor;
+        rv = SSL_InstallExtensionHooks(ss->fd, hook->type,
+                                       hook->writer, hook->writerArg,
+                                       hook->handler, hook->handlerArg);
+        if (rv != SECSuccess) {
+            return NULL;
+        }
+    }
+
     PORT_Memcpy((void *)ss->namedGroupPreferences,
                 sm->namedGroupPreferences,
                 sizeof(ss->namedGroupPreferences));
     ss->additionalShares = sm->additionalShares;
 
     /* copy trust anchor names */
     if (sm->ssl3.ca_list) {
         if (ss->ssl3.ca_list) {
@@ -2204,17 +2243,17 @@ ssl3_GetEffectiveVersionPolicy(SSLProtoc
         minPolicy > maxPolicy) {
         return SECFailure;
     }
     effectivePolicy->min = PR_MAX(effectivePolicy->min, minPolicy);
     effectivePolicy->max = PR_MIN(effectivePolicy->max, maxPolicy);
     return SECSuccess;
 }
 
-/* 
+/*
  * Assumes that rangeParam values are within the supported boundaries,
  * but should contain all potentially allowed versions, even if they contain
  * conflicting versions.
  * Will return the overlap, or a NONE range if system policy is invalid.
  */
 static SECStatus
 ssl3_CreateOverlapWithPolicy(SSLProtocolVariant protocolVariant,
                              SSLVersionRange *input,
@@ -3767,16 +3806,17 @@ ssl_NewSocket(PRBool makeLocks, SSLProto
     ss->peerID = NULL;
     ss->rTimeout = PR_INTERVAL_NO_TIMEOUT;
     ss->wTimeout = PR_INTERVAL_NO_TIMEOUT;
     ss->cTimeout = PR_INTERVAL_NO_TIMEOUT;
     ss->url = NULL;
 
     PR_INIT_CLIST(&ss->serverCerts);
     PR_INIT_CLIST(&ss->ephemeralKeyPairs);
+    PR_INIT_CLIST(&ss->extensionHooks);
 
     ss->dbHandle = CERT_GetDefaultCertDB();
 
     /* Provide default implementation of hooks */
     ss->authCertificate = SSL_AuthCertificate;
     ss->authCertificateArg = (void *)ss->dbHandle;
     ss->sniSocketConfig = NULL;
     ss->sniSocketConfigArg = NULL;
@@ -3794,17 +3834,17 @@ ssl_NewSocket(PRBool makeLocks, SSLProto
     for (i = 0; i < SSL_NAMED_GROUP_COUNT; ++i) {
         ss->namedGroupPreferences[i] = &ssl_named_groups[i];
     }
     ss->additionalShares = 0;
     PR_INIT_CLIST(&ss->ssl3.hs.remoteExtensions);
     PR_INIT_CLIST(&ss->ssl3.hs.lastMessageFlight);
     PR_INIT_CLIST(&ss->ssl3.hs.cipherSpecs);
     PR_INIT_CLIST(&ss->ssl3.hs.bufferedEarlyData);
-    ssl3_InitExtensionData(&ss->xtnData);
+    ssl3_InitExtensionData(&ss->xtnData, ss);
     if (makeLocks) {
         rv = ssl_MakeLocks(ss);
         if (rv != SECSuccess)
             goto loser;
     }
     rv = ssl_CreateSecurityInfo(ss);
     if (rv != SECSuccess)
         goto loser;
--- a/lib/ssl/sslt.h
+++ b/lib/ssl/sslt.h
@@ -418,28 +418,28 @@ typedef enum {
     ssl_tls13_key_share_xtn = 40,
     ssl_tls13_pre_shared_key_xtn = 41,
     ssl_tls13_early_data_xtn = 42,
     ssl_tls13_supported_versions_xtn = 43,
     ssl_tls13_cookie_xtn = 44,
     ssl_tls13_psk_key_exchange_modes_xtn = 45,
     ssl_tls13_ticket_early_data_info_xtn = 46, /* Deprecated. */
     ssl_tls13_certificate_authorities_xtn = 47,
-    ssl_next_proto_nego_xtn = 13172,
+    ssl_next_proto_nego_xtn = 13172, /* Deprecated. */
     ssl_renegotiation_info_xtn = 0xff01,
-    ssl_tls13_short_header_xtn = 0xff03
+    ssl_tls13_short_header_xtn = 0xff03 /* Deprecated. */
 } SSLExtensionType;
 
 /* This is the old name for the supported_groups extensions. */
 #define ssl_elliptic_curves_xtn ssl_supported_groups_xtn
 
 /* SSL_MAX_EXTENSIONS includes the maximum number of extensions that are
  * supported for any single message type.  That is, a ClientHello; ServerHello
  * and TLS 1.3 NewSessionTicket and HelloRetryRequest extensions have fewer. */
-#define SSL_MAX_EXTENSIONS 20
+#define SSL_MAX_EXTENSIONS 19
 
 /* Deprecated */
 typedef enum {
     ssl_dhe_group_none = 0,
     ssl_ff_dhe_2048_group = 1,
     ssl_ff_dhe_3072_group = 2,
     ssl_ff_dhe_4096_group = 3,
     ssl_ff_dhe_6144_group = 4,
--- a/lib/ssl/tls13con.c
+++ b/lib/ssl/tls13con.c
@@ -2031,17 +2031,17 @@ tls13_HandleServerHelloPart2(sslSocket *
         if (sid->peerCert) {
             ss->sec.peerCert = CERT_DupCertificate(sid->peerCert);
         }
 
         SSL_AtomicIncrementLong(&ssl3stats->hsh_sid_cache_hits);
         SSL_AtomicIncrementLong(&ssl3stats->hsh_sid_stateless_resumes);
     } else {
         /* !PSK */
-        if (ssl3_ClientExtensionAdvertised(ss, ssl_tls13_pre_shared_key_xtn)) {
+        if (ssl3_ExtensionAdvertised(ss, ssl_tls13_pre_shared_key_xtn)) {
             SSL_AtomicIncrementLong(&ssl3stats->hsh_sid_cache_misses);
         }
         if (sid->cached == in_client_cache) {
             /* If we tried to resume and failed, let's not try again. */
             ss->sec.uncache(sid);
         }
     }
 
@@ -4077,17 +4077,16 @@ static const struct {
     { ssl_tls13_psk_key_exchange_modes_xtn, _M1(client_hello) },
     { ssl_tls13_early_data_xtn, _M3(client_hello, encrypted_extensions,
                                     new_session_ticket) },
     { ssl_signed_cert_timestamp_xtn, _M3(client_hello, certificate_request,
                                          certificate) },
     { ssl_cert_status_xtn, _M3(client_hello, certificate_request,
                                certificate) },
     { ssl_tls13_cookie_xtn, _M2(client_hello, hello_retry_request) },
-    { ssl_tls13_short_header_xtn, _M2(client_hello, server_hello) },
     { ssl_tls13_certificate_authorities_xtn, _M1(certificate_request) },
     { ssl_tls13_supported_versions_xtn, _M1(client_hello) }
 };
 
 tls13ExtensionStatus
 tls13_ExtensionStatus(PRUint16 extension, SSLHandshakeType message)
 {
     unsigned int i;
@@ -4367,17 +4366,17 @@ tls13_ClientAllow0Rtt(const sslSocket *s
 
 SECStatus
 tls13_MaybeDo0RTTHandshake(sslSocket *ss)
 {
     SECStatus rv;
 
     /* Don't do anything if there is no early_data xtn, which means we're
      * not doing early data. */
-    if (!ssl3_ClientExtensionAdvertised(ss, ssl_tls13_early_data_xtn)) {
+    if (!ssl3_ExtensionAdvertised(ss, ssl_tls13_early_data_xtn)) {
         return SECSuccess;
     }
 
     ss->ssl3.hs.zeroRttState = ssl_0rtt_sent;
     ss->ssl3.hs.zeroRttSuite = ss->ssl3.hs.cipher_suite;
 
     SSL_TRC(3, ("%d: TLS13[%d]: in 0-RTT mode", SSL_GETPID(), ss->fd));
 
--- a/lib/ssl/tls13exthandle.c
+++ b/lib/ssl/tls13exthandle.c
@@ -395,17 +395,17 @@ tls13_ClientSendPreSharedKeyXtn(const ss
     /* We only set statelessResume on the client in TLS 1.3 code. */
     if (!ss->statelessResume) {
         return SECSuccess;
     }
 
     /* Save where this extension starts so that if we have to add padding, it
      * can be inserted before this extension. */
     PORT_Assert(buf->len >= 4);
-    xtnData->paddingOffset = buf->len - 4;
+    xtnData->lastXtnOffset = buf->len - 4;
 
     PORT_Assert(ss->vrange.max >= SSL_LIBRARY_VERSION_TLS_1_3);
 
     /* Send a single ticket identity. */
     session_ticket = &ss->sec.ci.sid->u.ssl3.locked.sessionTicket;
     rv = sslBuffer_AppendNumber(buf, 2 +                              /* identity length */
                                          session_ticket->ticket.len + /* ticket */
                                          4 /* obfuscated_ticket_age */,