Bug 1391506 - Creating max version, fallback limit, and alt server hello flag values for the tlsFlags r=keeler
authorSajjad Arshad <sarshad@mozilla.com>
Tue, 29 Aug 2017 09:04:08 -0400
changeset 429727 537aeafd49ec63a6aeb1221759667f4313238a03
parent 429726 d87bd54dc409c481e1c28a16be50e76ea91cf6c3
child 429728 4fdf0a1290c141eb970a269d7d2bf79330f32ce1
push id1567
push userjlorenzo@mozilla.com
push dateThu, 02 Nov 2017 12:36:05 +0000
treeherdermozilla-release@e512c14a0406 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskeeler
bugs1391506
milestone57.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1391506 - Creating max version, fallback limit, and alt server hello flag values for the tlsFlags r=keeler Additional-Author: Patrick McManus <mcmanus@ducksong.com> MozReview-Commit-ID: AmsvDtvDGPt
netwerk/test/unit/test_tls_flags.js
netwerk/test/unit/xpcshell.ini
security/manager/ssl/SharedSSLState.cpp
security/manager/ssl/SharedSSLState.h
security/manager/ssl/nsClientAuthRemember.cpp
security/manager/ssl/nsNSSIOLayer.cpp
security/manager/ssl/nsNSSIOLayer.h
security/nss.symbols
new file mode 100644
--- /dev/null
+++ b/netwerk/test/unit/test_tls_flags.js
@@ -0,0 +1,233 @@
+// -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
+// Any copyright is dedicated to the Public Domain.
+// http://creativecommons.org/publicdomain/zero/1.0/
+
+"use strict";
+
+// a fork of test_be_conservative
+
+// Tests that nsIHttpChannelInternal.tlsFlags can be used to set the
+// client max version level. Flags can also be used to set the
+// level of intolerance rollback and to test out an experimental 1.3
+// hello, though they are not tested here.
+
+const { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
+const { NetUtil } = Cu.import("resource://gre/modules/NetUtil.jsm", {});
+
+// Get a profile directory and ensure PSM initializes NSS.
+do_get_profile();
+Cc["@mozilla.org/psm;1"].getService(Ci.nsISupports);
+
+function getCert() {
+  return new Promise((resolve, reject) => {
+    let certService = Cc["@mozilla.org/security/local-cert-service;1"]
+                        .getService(Ci.nsILocalCertService);
+    certService.getOrCreateCert("tlsflags-test", {
+      handleCert: function(c, rv) {
+        if (rv) {
+          reject(rv);
+          return;
+        }
+        resolve(c);
+      }
+    });
+  });
+}
+
+class InputStreamCallback {
+  constructor(output) {
+    this.output = output;
+    this.stopped = false;
+  }
+
+  onInputStreamReady(stream) {
+    do_print("input stream ready");
+    if (this.stopped) {
+      do_print("input stream callback stopped - bailing");
+      return;
+    }
+    let available = 0;
+    try {
+      available = stream.available();
+    } catch (e) {
+      // onInputStreamReady may fire when the stream has been closed.
+      equal(e.result, Cr.NS_BASE_STREAM_CLOSED,
+            "error should be NS_BASE_STREAM_CLOSED");
+    }
+    if (available > 0) {
+      let request = NetUtil.readInputStreamToString(stream, available,
+                                                    { charset: "utf8"});
+      ok(request.startsWith("GET / HTTP/1.1\r\n"),
+         "Should get a simple GET / HTTP/1.1 request");
+      let response = "HTTP/1.1 200 OK\r\n" +
+                     "Content-Length: 2\r\n" +
+                     "Content-Type: text/plain\r\n" +
+                     "\r\nOK";
+      let written = this.output.write(response, response.length);
+      equal(written, response.length,
+            "should have been able to write entire response");
+    }
+    this.output.close();
+    do_print("done with input stream ready");
+  }
+
+  stop() {
+    this.stopped = true;
+    this.output.close();
+  }
+}
+
+class TLSServerSecurityObserver {
+  constructor(input, output, expectedVersion) {
+    this.input = input;
+    this.output = output;
+    this.expectedVersion = expectedVersion;
+    this.callbacks = [];
+    this.stopped = false;
+  }
+
+  onHandshakeDone(socket, status) {
+    do_print("TLS handshake done");
+    do_print(`TLS version used: ${status.tlsVersionUsed}`);
+    do_print(this.expectedVersion);
+    equal(status.tlsVersionUsed, this.expectedVersion, "expected version check");
+    if (this.stopped) {
+      do_print("handshake done callback stopped - bailing");
+      return;
+    }
+
+    let callback = new InputStreamCallback(this.output);
+    this.callbacks.push(callback);
+    this.input.asyncWait(callback, 0, 0, Services.tm.currentThread);
+  }
+
+  stop() {
+    this.stopped = true;
+    this.input.close();
+    this.output.close();
+    this.callbacks.forEach((callback) => {
+      callback.stop();
+    });
+  }
+}
+
+
+function startServer(cert, minServerVersion, maxServerVersion, expectedVersion) {
+  let tlsServer = Cc["@mozilla.org/network/tls-server-socket;1"]
+                    .createInstance(Ci.nsITLSServerSocket);
+  tlsServer.init(-1, true, -1);
+  tlsServer.serverCert = cert;
+  tlsServer.setVersionRange(minServerVersion, maxServerVersion);
+  tlsServer.setSessionCache(false);
+  tlsServer.setSessionTickets(false);
+
+  let listener = {
+    securityObservers : [],
+
+    onSocketAccepted: function(socket, transport) {
+      do_print("accepted TLS client connection");
+      let connectionInfo = transport.securityInfo
+          .QueryInterface(Ci.nsITLSServerConnectionInfo);
+      let input = transport.openInputStream(0, 0, 0);
+      let output = transport.openOutputStream(0, 0, 0);
+      let securityObserver = new TLSServerSecurityObserver(input, output, expectedVersion);
+      this.securityObservers.push(securityObserver);
+      connectionInfo.setSecurityObserver(securityObserver);
+    },
+
+    // For some reason we get input stream callback events after we've stopped
+    // listening, so this ensures we just drop those events.
+    onStopListening: function() {
+      do_print("onStopListening");
+      this.securityObservers.forEach((observer) => {
+        observer.stop();
+      });
+    }
+  }
+  tlsServer.asyncListen(listener);
+  return tlsServer;
+}
+
+const hostname = "example.com"
+
+function storeCertOverride(port, cert) {
+  let certOverrideService = Cc["@mozilla.org/security/certoverride;1"]
+                              .getService(Ci.nsICertOverrideService);
+  let overrideBits = Ci.nsICertOverrideService.ERROR_UNTRUSTED |
+                     Ci.nsICertOverrideService.ERROR_MISMATCH;
+  certOverrideService.rememberValidityOverride(hostname, port, cert,
+                                               overrideBits, true);
+}
+
+function startClient(port, tlsFlags, expectSuccess) {
+  let req = new XMLHttpRequest();
+  req.open("GET", `https://${hostname}:${port}`);
+  let internalChannel = req.channel.QueryInterface(Ci.nsIHttpChannelInternal);
+  internalChannel.tlsFlags = tlsFlags;
+  return new Promise((resolve, reject) => {
+    req.onload = () => {
+      ok(expectSuccess,
+         `should ${expectSuccess ? "" : "not "}have gotten load event`);
+      equal(req.responseText, "OK", "response text should be 'OK'");
+      resolve();
+    };
+    req.onerror = () => {
+      ok(!expectSuccess,
+         `should ${!expectSuccess ? "" : "not "}have gotten an error`);
+      resolve();
+    };
+
+    req.send();
+  });
+}
+
+add_task(async function() {
+  Services.prefs.setIntPref("security.tls.version.max", 4);
+  Services.prefs.setCharPref("network.dns.localDomains", hostname);
+  let cert = await getCert();
+
+  // server that accepts 1.1->1.3 and a client max 1.3. expect 1.3
+  do_print("TEST 1");
+  let server = startServer(cert,
+                           Ci.nsITLSClientStatus.TLS_VERSION_1_1,
+                           Ci.nsITLSClientStatus.TLS_VERSION_1_3,
+                           Ci.nsITLSClientStatus.TLS_VERSION_1_3);
+  storeCertOverride(server.port, cert);
+  await startClient(server.port, 4, true /*should succeed*/);
+  server.close();
+
+  // server that accepts 1.1->1.3 and a client max 1.1. expect 1.1
+  do_print("TEST 2");
+  server = startServer(cert,
+                       Ci.nsITLSClientStatus.TLS_VERSION_1_1,
+                       Ci.nsITLSClientStatus.TLS_VERSION_1_3,
+                       Ci.nsITLSClientStatus.TLS_VERSION_1_1);
+  storeCertOverride(server.port, cert);
+  await startClient(server.port, 2, true);
+  server.close();
+
+  // server that accepts 1.2->1.2 and a client max 1.3. expect 1.2
+  do_print("TEST 3");
+  server = startServer(cert,
+                       Ci.nsITLSClientStatus.TLS_VERSION_1_2,
+                       Ci.nsITLSClientStatus.TLS_VERSION_1_2,
+                       Ci.nsITLSClientStatus.TLS_VERSION_1_2);
+  storeCertOverride(server.port, cert);
+  await startClient(server.port, 4, true);
+  server.close();
+
+  // server that accepts 1.2->1.2 and a client max 1.1. expect fail
+  do_print("TEST 4");
+  server = startServer(cert,
+                       Ci.nsITLSClientStatus.TLS_VERSION_1_2,
+                       Ci.nsITLSClientStatus.TLS_VERSION_1_2, 0);
+  storeCertOverride(server.port, cert);
+  await startClient(server.port, 2, false);
+
+  server.close();
+});
+
+do_register_cleanup(function() {
+  Services.prefs.clearUserPref("security.tls.version.max");
+  Services.prefs.clearUserPref("network.dns.localDomains");
+});
--- a/netwerk/test/unit/xpcshell.ini
+++ b/netwerk/test/unit/xpcshell.ini
@@ -392,8 +392,9 @@ skip-if = os == "android"
 [test_race_cache_with_network.js]
 [test_channel_priority.js]
 [test_bug1312774_http1.js]
 [test_1351443-missing-NewChannel2.js]
 [test_bug1312782_http1.js]
 [test_bug1355539_http1.js]
 [test_bug1378385_http1.js]
 [test_tls_flags_separate_connections.js]
+[test_tls_flags.js]
--- a/security/manager/ssl/SharedSSLState.cpp
+++ b/security/manager/ssl/SharedSSLState.cpp
@@ -112,25 +112,28 @@ PrivateBrowsingObserver::Observe(nsISupp
                                  const char16_t *aData)
 {
   if (!nsCRT::strcmp(aTopic, "last-pb-context-exited")) {
     mOwner->ResetStoredData();
   }
   return NS_OK;
 }
 
-SharedSSLState::SharedSSLState()
-: mClientAuthRemember(new nsClientAuthRememberService)
+SharedSSLState::SharedSSLState(uint32_t aTlsFlags)
+: mIOLayerHelpers(aTlsFlags)
 , mMutex("SharedSSLState::mMutex")
 , mSocketCreated(false)
 , mOCSPStaplingEnabled(false)
 , mOCSPMustStapleEnabled(false)
 {
   mIOLayerHelpers.Init();
-  mClientAuthRemember->Init();
+  if (!aTlsFlags) { // the per socket flags don't need memory
+    mClientAuthRemember = new nsClientAuthRememberService();
+    mClientAuthRemember->Init();
+  }
 }
 
 SharedSSLState::~SharedSSLState()
 {
 }
 
 void
 SharedSSLState::NotePrivateBrowsingStatus()
@@ -139,16 +142,19 @@ SharedSSLState::NotePrivateBrowsingStatu
   mObserver = new PrivateBrowsingObserver(this);
   nsCOMPtr<nsIObserverService> obsSvc = mozilla::services::GetObserverService();
   obsSvc->AddObserver(mObserver, "last-pb-context-exited", false);
 }
 
 void
 SharedSSLState::ResetStoredData()
 {
+  if (!mClientAuthRemember) {
+    return;
+  }
   MOZ_ASSERT(NS_IsMainThread(), "Not on main thread");
   mClientAuthRemember->ClearRememberedDecisions();
   mIOLayerHelpers.clearStoredData();
 }
 
 void
 SharedSSLState::NoteSocketCreated()
 {
--- a/security/manager/ssl/SharedSSLState.h
+++ b/security/manager/ssl/SharedSSLState.h
@@ -14,17 +14,17 @@ class nsClientAuthRememberService;
 class nsIObserver;
 
 namespace mozilla {
 namespace psm {
 
 class SharedSSLState {
 public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SharedSSLState)
-  SharedSSLState();
+  explicit SharedSSLState(uint32_t aTlsFlags = 0);
 
   static void GlobalInit();
   static void GlobalCleanup();
 
   nsClientAuthRememberService* GetClientAuthRememberService() {
     return mClientAuthRemember;
   }
 
--- a/security/manager/ssl/nsClientAuthRemember.cpp
+++ b/security/manager/ssl/nsClientAuthRemember.cpp
@@ -80,20 +80,26 @@ void nsClientAuthRememberService::ClearR
   ReentrantMonitorAutoEnter lock(monitor);
   RemoveAllFromMemory();
 }
 
 void nsClientAuthRememberService::ClearAllRememberedDecisions()
 {
   RefPtr<nsClientAuthRememberService> svc =
     PublicSSLState()->GetClientAuthRememberService();
-  svc->ClearRememberedDecisions();
+  MOZ_ASSERT(svc);
+  if (svc) {
+    svc->ClearRememberedDecisions();
+  }
 
   svc = PrivateSSLState()->GetClientAuthRememberService();
-  svc->ClearRememberedDecisions();
+  MOZ_ASSERT(svc);
+  if (svc) {
+    svc->ClearRememberedDecisions();
+  }
 }
 
 void
 nsClientAuthRememberService::RemoveAllFromMemory()
 {
   mSettingsTable.Clear();
 }
 
--- a/security/manager/ssl/nsNSSIOLayer.cpp
+++ b/security/manager/ssl/nsNSSIOLayer.cpp
@@ -39,32 +39,71 @@
 #include "pkix/pkixtypes.h"
 #include "prmem.h"
 #include "prnetdb.h"
 #include "secder.h"
 #include "secerr.h"
 #include "ssl.h"
 #include "sslerr.h"
 #include "sslproto.h"
+#include "sslexp.h"
 
 using namespace mozilla;
 using namespace mozilla::psm;
 
 //#define DEBUG_SSL_VERBOSE //Enable this define to get minimal
                             //reports when doing SSL read/write
 
 //#define DUMP_BUFFER  //Enable this define along with
                        //DEBUG_SSL_VERBOSE to dump SSL
                        //read/write buffer to a log.
                        //Uses PR_LOG except on Mac where
                        //we always write out to our own
                        //file.
 
 namespace {
 
+// The NSSSocketInfo tls flags are meant to be opaque to most calling applications
+// but provide a mechanism for direct TLS manipulation when experimenting with new
+// features in the scope of a single socket. They do not create a persistent ABI.
+//
+// Use of these flags creates a new 'sharedSSLState' so existing states for intolerance
+// are not carried to sockets that use these flags (and intolerance they discover
+// does not impact other normal sockets not using the flags.)
+//
+// Their current definitions are:
+//
+// bits 0-2 (mask 0x07) specify the max tls version
+//          0 means no override 1->4 are 1.0, 1.1, 1.2, 1.3, 4->7 unused
+// bits 3-5 (mask 0x38) specify the tls fallback limit
+//          0 means no override, values 1->4 match prefs
+// bit    6 (mask 0x40) specifies use of SSL_AltServerHelloType on handshake
+
+enum {
+  kTLSProviderFlagMaxVersion10   = 0x01,
+  kTLSProviderFlagMaxVersion11   = 0x02,
+  kTLSProviderFlagMaxVersion12   = 0x03,
+  kTLSProviderFlagMaxVersion13   = 0x04,
+};
+
+static uint32_t getTLSProviderFlagMaxVersion(uint32_t flags)
+{
+  return (flags & 0x07);
+}
+
+static uint32_t getTLSProviderFlagFallbackLimit(uint32_t flags)
+{
+  return (flags & 0x38) >> 3;
+}
+
+static bool getTLSProviderFlagAltServerHello(uint32_t flags)
+{
+  return (flags & 0x40);
+}
+
 #define MAX_ALPN_LENGTH 255
 
 void
 getSiteKey(const nsACString& hostName, uint16_t port,
            /*out*/ nsACString& key)
 {
   key = hostName;
   key.AppendASCII(":");
@@ -658,16 +697,22 @@ nsNSSSocketInfo::SetCertVerificationResu
 }
 
 SharedSSLState&
 nsNSSSocketInfo::SharedState()
 {
   return mSharedState;
 }
 
+void
+nsNSSSocketInfo::SetSharedOwningReference(SharedSSLState* aRef)
+{
+  mOwningSharedRef = aRef;
+}
+
 void nsSSLIOLayerHelpers::Cleanup()
 {
   MutexAutoLock lock(mutex);
   mTLSIntoleranceInfo.Clear();
   mInsecureFallbackSites.Clear();
 }
 
 static void
@@ -1352,21 +1397,22 @@ nsSSLIOLayerPoll(PRFileDesc* fd, int16_t
   // it reaches any point that would be unsafe to send/receive something before
   // cert validation is complete.
   int16_t result = fd->lower->methods->poll(fd->lower, in_flags, out_flags);
   MOZ_LOG(gPIPNSSLog, LogLevel::Verbose,
           ("[%p] poll SSL socket returned %d\n", (void*) fd, (int) result));
   return result;
 }
 
-nsSSLIOLayerHelpers::nsSSLIOLayerHelpers()
+nsSSLIOLayerHelpers::nsSSLIOLayerHelpers(uint32_t aTlsFlags)
   : mTreatUnsafeNegotiationAsBroken(false)
   , mTLSIntoleranceInfo()
   , mVersionFallbackLimit(SSL_LIBRARY_VERSION_TLS_1_0)
   , mutex("nsSSLIOLayerHelpers.mutex")
+  , mTlsFlags(aTlsFlags)
 {
 }
 
 static int
 _PSM_InvalidInt(void)
 {
   MOZ_ASSERT_UNREACHABLE("I/O method is invalid");
   PR_SetError(PR_INVALID_METHOD_ERROR, 0);
@@ -1673,16 +1719,17 @@ nsSSLIOLayerHelpers::~nsSSLIOLayerHelper
         "security.tls.insecure_fallback_hosts");
   }
 }
 
 nsresult
 nsSSLIOLayerHelpers::Init()
 {
   if (!nsSSLIOLayerInitialized) {
+    MOZ_ASSERT(NS_IsMainThread());
     nsSSLIOLayerInitialized = true;
     nsSSLIOLayerIdentity = PR_GetUniqueIdentity("NSS layer");
     nsSSLIOLayerMethods  = *PR_GetDefaultIOMethods();
 
     nsSSLIOLayerMethods.available = (PRAvailableFN) PSMAvailable;
     nsSSLIOLayerMethods.available64 = (PRAvailable64FN) PSMAvailable64;
     nsSSLIOLayerMethods.fsync = (PRFsyncFN) _PSM_InvalidStatus;
     nsSSLIOLayerMethods.seek = (PRSeekFN) _PSM_InvalidInt;
@@ -1714,39 +1761,60 @@ nsSSLIOLayerHelpers::Init()
     nsSSLIOLayerMethods.read = nsSSLIOLayerRead;
     nsSSLIOLayerMethods.poll = nsSSLIOLayerPoll;
 
     nsSSLPlaintextLayerIdentity = PR_GetUniqueIdentity("Plaintxext PSM layer");
     nsSSLPlaintextLayerMethods  = *PR_GetDefaultIOMethods();
     nsSSLPlaintextLayerMethods.recv = PlaintextRecv;
   }
 
-  bool enabled = false;
-  Preferences::GetBool("security.ssl.treat_unsafe_negotiation_as_broken", &enabled);
-  setTreatUnsafeNegotiationAsBroken(enabled);
-
   loadVersionFallbackLimit();
-  initInsecureFallbackSites();
-
-  mPrefObserver = new PrefObserver(this);
-  Preferences::AddStrongObserver(mPrefObserver,
-                                 "security.ssl.treat_unsafe_negotiation_as_broken");
-  Preferences::AddStrongObserver(mPrefObserver,
-                                 "security.tls.version.fallback-limit");
-  Preferences::AddStrongObserver(mPrefObserver,
-                                 "security.tls.insecure_fallback_hosts");
+
+  // non main thread helpers will need to use defaults
+  if (NS_IsMainThread()) {
+    bool enabled = false;
+    Preferences::GetBool("security.ssl.treat_unsafe_negotiation_as_broken", &enabled);
+    setTreatUnsafeNegotiationAsBroken(enabled);
+
+    initInsecureFallbackSites();
+
+    mPrefObserver = new PrefObserver(this);
+    Preferences::AddStrongObserver(mPrefObserver,
+                                   "security.ssl.treat_unsafe_negotiation_as_broken");
+    Preferences::AddStrongObserver(mPrefObserver,
+                                   "security.tls.version.fallback-limit");
+    Preferences::AddStrongObserver(mPrefObserver,
+                                   "security.tls.insecure_fallback_hosts");
+  } else {
+    MOZ_ASSERT(mTlsFlags, "Only per socket version can ignore prefs");
+  }
+
   return NS_OK;
 }
 
 void
 nsSSLIOLayerHelpers::loadVersionFallbackLimit()
 {
   // see nsNSSComponent::setEnabledTLSVersions for pref handling rules
-  uint32_t limit = Preferences::GetUint("security.tls.version.fallback-limit",
-                                        3); // 3 = TLS 1.2
+  uint32_t limit = 3; // TLS 1.2
+
+  if (NS_IsMainThread()) {
+    limit = Preferences::GetUint("security.tls.version.fallback-limit",
+                                 3); // 3 = TLS 1.2
+  }
+
+  // set fallback limit if it is set in the tls flags
+  uint32_t tlsFlagsFallbackLimit = getTLSProviderFlagFallbackLimit(mTlsFlags);
+
+  if (tlsFlagsFallbackLimit) {
+    limit = tlsFlagsFallbackLimit;
+    MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
+            ("loadVersionFallbackLimit overriden by tlsFlags %d\n", limit));
+  }
+
   SSLVersionRange defaults = { SSL_LIBRARY_VERSION_TLS_1_2,
                                SSL_LIBRARY_VERSION_TLS_1_2 };
   SSLVersionRange filledInRange;
   nsNSSComponent::FillTLSVersionRange(filledInRange, limit, limit, defaults);
   if (filledInRange.max < SSL_LIBRARY_VERSION_TLS_1_2) {
     filledInRange.max = SSL_LIBRARY_VERSION_TLS_1_2;
   }
 
@@ -2493,17 +2561,47 @@ nsSSLIOLayerSetOptions(PRFileDesc* fd, b
     }
   }
 
   SSLVersionRange range;
   if (SSL_VersionRangeGet(fd, &range) != SECSuccess) {
     return NS_ERROR_FAILURE;
   }
 
-  // Use infoObject->GetProviderTlsFlags() to get the TLS flags
+  // setting TLS max version
+  uint32_t versionFlags =
+    getTLSProviderFlagMaxVersion(infoObject->GetProviderTlsFlags());
+  if (versionFlags) {
+    MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
+            ("[%p] nsSSLIOLayerSetOptions: version flags %d\n", fd, versionFlags));
+    if (versionFlags == kTLSProviderFlagMaxVersion10) {
+      range.max = SSL_LIBRARY_VERSION_TLS_1_0;
+    } else if (versionFlags == kTLSProviderFlagMaxVersion11) {
+      range.max = SSL_LIBRARY_VERSION_TLS_1_1;
+    } else if (versionFlags == kTLSProviderFlagMaxVersion12) {
+      range.max = SSL_LIBRARY_VERSION_TLS_1_2;
+    } else if (versionFlags == kTLSProviderFlagMaxVersion13) {
+      range.max = SSL_LIBRARY_VERSION_TLS_1_3;
+    } else {
+      MOZ_LOG(gPIPNSSLog, LogLevel::Error,
+              ("[%p] nsSSLIOLayerSetOptions: unknown version flags %d\n",
+               fd, versionFlags));
+    }
+  }
+
+  // enabling alternative server hello
+  if (getTLSProviderFlagAltServerHello(infoObject->GetProviderTlsFlags())) {
+    MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
+            ("[%p] nsSSLIOLayerSetOptions: Use AltServerHello\n", fd));
+    if (SECSuccess != SSL_UseAltServerHelloType(fd, PR_TRUE)) {
+          MOZ_LOG(gPIPNSSLog, LogLevel::Error,
+                  ("[%p] nsSSLIOLayerSetOptions: Use AltServerHello failed\n", fd));
+          // continue on default path
+    }
+  }
 
   if ((infoObject->GetProviderFlags() & nsISocketProvider::BE_CONSERVATIVE) &&
       (range.max > SSL_LIBRARY_VERSION_TLS_1_2)) {
     MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
             ("[%p] nsSSLIOLayerSetOptions: range.max limited to 1.2 due to BE_CONSERVATIVE flag\n",
              fd));
     range.max = SSL_LIBRARY_VERSION_TLS_1_2;
   }
@@ -2626,26 +2724,36 @@ nsSSLIOLayerAddToSocket(int32_t family,
                         uint32_t providerTlsFlags)
 {
   nsNSSShutDownPreventionLock locker;
   PRFileDesc* layer = nullptr;
   PRFileDesc* plaintextLayer = nullptr;
   nsresult rv;
   PRStatus stat;
 
-  SharedSSLState* sharedState =
-    providerFlags & nsISocketProvider::NO_PERMANENT_STORAGE ? PrivateSSLState() : PublicSSLState();
+  SharedSSLState* sharedState = nullptr;
+  RefPtr<SharedSSLState> allocatedState;
+  if (providerTlsFlags) {
+    allocatedState = new SharedSSLState(providerTlsFlags);
+    sharedState = allocatedState.get();
+  } else {
+    sharedState = (providerFlags & nsISocketProvider::NO_PERMANENT_STORAGE) ? PrivateSSLState() : PublicSSLState();
+  }
+
   nsNSSSocketInfo* infoObject = new nsNSSSocketInfo(*sharedState, providerFlags, providerTlsFlags);
   if (!infoObject) return NS_ERROR_FAILURE;
 
   NS_ADDREF(infoObject);
   infoObject->SetForSTARTTLS(forSTARTTLS);
   infoObject->SetHostName(host);
   infoObject->SetPort(port);
   infoObject->SetOriginAttributes(originAttributes);
+  if (allocatedState) {
+    infoObject->SetSharedOwningReference(allocatedState);
+  }
 
   bool haveProxy = false;
   if (proxy) {
     nsCString proxyHost;
     proxy->GetHost(proxyHost);
     haveProxy = !proxyHost.IsEmpty();
   }
 
--- a/security/manager/ssl/nsNSSIOLayer.h
+++ b/security/manager/ssl/nsNSSIOLayer.h
@@ -159,16 +159,18 @@ public:
     MOZ_ASSERT(amount >= mShortWriteOriginalAmount,
                "unexpected amount length after short write");
     MOZ_ASSERT(!memcmp(mShortWriteBufferCheck.get(), data, mShortWriteOriginalAmount),
                "unexpected buffer content after short write");
     mShortWriteBufferCheck = nullptr;
   }
 #endif
 
+  void SetSharedOwningReference(mozilla::psm::SharedSSLState* ref);
+
 protected:
   virtual ~nsNSSSocketInfo();
 
 private:
   PRFileDesc* mFd;
 
   CertVerificationState mCertVerificationState;
 
@@ -220,22 +222,29 @@ private:
   bool    mBypassAuthentication;
 
   uint32_t mProviderFlags;
   uint32_t mProviderTlsFlags;
   mozilla::TimeStamp mSocketCreationTimestamp;
   uint64_t mPlaintextBytesRead;
 
   nsCOMPtr<nsIX509Cert> mClientCert;
+
+  // if non-null this is a reference to the mSharedState (which is
+  // not an owning reference). If this is used, the info has a private
+  // state that does not share things like intolerance lists with the
+  // rest of the session. This is normally used when you have per
+  // socket tls flags overriding session wide defaults.
+  RefPtr<mozilla::psm::SharedSSLState> mOwningSharedRef;
 };
 
 class nsSSLIOLayerHelpers
 {
 public:
-  nsSSLIOLayerHelpers();
+  explicit nsSSLIOLayerHelpers(uint32_t aTlsFlags = 0);
   ~nsSSLIOLayerHelpers();
 
   nsresult Init();
   void Cleanup();
 
   static bool nsSSLIOLayerInitialized;
   static PRDescIdentity nsSSLIOLayerIdentity;
   static PRDescIdentity nsSSLPlaintextLayerIdentity;
@@ -283,16 +292,17 @@ public:
   bool isPublic() const;
   void removeInsecureFallbackSite(const nsACString& hostname, uint16_t port);
   bool isInsecureFallbackSite(const nsACString& hostname);
 
   uint16_t mVersionFallbackLimit;
 private:
   mozilla::Mutex mutex;
   nsCOMPtr<nsIObserver> mPrefObserver;
+  uint32_t mTlsFlags;
 };
 
 nsresult nsSSLIOLayerNewSocket(int32_t family,
                                const char* host,
                                int32_t port,
                                nsIProxyInfo *proxy,
                                const OriginAttributes& originAttributes,
                                PRFileDesc** fd,
--- a/security/nss.symbols
+++ b/security/nss.symbols
@@ -659,16 +659,17 @@ SSL_ClearSessionCache
 SSL_ConfigSecureServer
 SSL_ConfigSecureServerWithCertChain
 SSL_ConfigServerSessionIDCache
 SSL_ExportKeyingMaterial
 SSL_ForceHandshake
 SSL_GetChannelInfo
 SSL_GetCipherSuiteInfo
 SSL_GetClientAuthDataHook
+SSL_GetExperimentalAPI
 SSL_GetImplementedCiphers
 SSL_GetNextProto
 SSL_GetNumImplementedCiphers
 SSL_GetPreliminaryChannelInfo
 SSL_GetSRTPCipher
 SSL_GetStatistics
 SSL_HandshakeCallback
 SSL_HandshakeNegotiatedExtension