bug 1050063 - consider tls client hello version in alpn/npn offer list r=hurley r=keeler
authorPatrick McManus <mcmanus@ducksong.com>
Fri, 15 Aug 2014 09:39:53 -0400
changeset 200269 bd50eac73b51507aeec849988f115960d75d27fa
parent 200268 241f31c1ad6286179e4f86eb8480f5457eb5ae37
child 200270 cc392a569cf433b77c3d1c74595e0b5fe00f2243
push id47862
push usermcmanus@ducksong.com
push dateTue, 19 Aug 2014 12:49:23 +0000
treeherdermozilla-inbound@bd50eac73b51 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewershurley, keeler
bugs1050063
milestone34.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 1050063 - consider tls client hello version in alpn/npn offer list r=hurley r=keeler
netwerk/protocol/http/ASpdySession.cpp
netwerk/protocol/http/ASpdySession.h
netwerk/protocol/http/Http2Session.cpp
netwerk/protocol/http/Http2Session.h
netwerk/protocol/http/nsHttpConnection.cpp
netwerk/socket/nsISSLSocketControl.idl
security/manager/ssl/src/nsNSSIOLayer.cpp
--- a/netwerk/protocol/http/ASpdySession.cpp
+++ b/netwerk/protocol/http/ASpdySession.cpp
@@ -59,57 +59,64 @@ ASpdySession::NewSpdySession(uint32_t ve
   } else  if (version == SPDY_VERSION_31) {
     return new SpdySession31(aTransport);
   } else  if (version == NS_HTTP2_DRAFT_VERSION) {
     return new Http2Session(aTransport);
   }
 
   return nullptr;
 }
+static bool SpdySessionTrue(nsISupports *securityInfo)
+{
+  return true;
+}
 
 SpdyInformation::SpdyInformation()
 {
   // highest index of enabled protocols is the
   // most preferred for ALPN negotiaton
   Version[0] = SPDY_VERSION_3;
   VersionString[0] = NS_LITERAL_CSTRING("spdy/3");
+  ALPNCallbacks[0] = SpdySessionTrue;
 
   Version[1] = SPDY_VERSION_31;
   VersionString[1] = NS_LITERAL_CSTRING("spdy/3.1");
+  ALPNCallbacks[1] = SpdySessionTrue;
 
   Version[2] = NS_HTTP2_DRAFT_VERSION;
   VersionString[2] = NS_LITERAL_CSTRING(NS_HTTP2_DRAFT_TOKEN);
+  ALPNCallbacks[2] = Http2Session::ALPNCallback;
 }
 
 bool
-SpdyInformation::ProtocolEnabled(uint32_t index)
+SpdyInformation::ProtocolEnabled(uint32_t index) const
 {
   MOZ_ASSERT(index < kCount, "index out of range");
 
   switch (index) {
   case 0:
     return gHttpHandler->IsSpdyV3Enabled();
   case 1:
     return gHttpHandler->IsSpdyV31Enabled();
   case 2:
     return gHttpHandler->IsHttp2DraftEnabled();
   }
   return false;
 }
 
 nsresult
-SpdyInformation::GetNPNVersionIndex(const nsACString &npnString,
-                                    uint8_t *result)
+SpdyInformation::GetNPNIndex(const nsACString &npnString,
+                             uint32_t *result) const
 {
   if (npnString.IsEmpty())
     return NS_ERROR_FAILURE;
 
   for (uint32_t index = 0; index < kCount; ++index) {
     if (npnString.Equals(VersionString[index])) {
-      *result = Version[index];
+      *result = index;
       return NS_OK;
     }
   }
 
   return NS_ERROR_FAILURE;
 }
 
 //////////////////////////////////////////
--- a/netwerk/protocol/http/ASpdySession.h
+++ b/netwerk/protocol/http/ASpdySession.h
@@ -56,33 +56,42 @@ public:
 
     return (code == NS_BASE_STREAM_CLOSED || code == NS_BINDING_FAILED ||
             code == NS_BINDING_ABORTED || code == NS_BINDING_REDIRECTED ||
             code == NS_ERROR_INVALID_CONTENT_ENCODING ||
             code == NS_BINDING_RETARGETED || code == NS_ERROR_CORRUPTED_CONTENT);
   }
 };
 
+typedef bool (*ALPNCallback) (nsISupports *); // nsISSLSocketControl is typical
+
 // this is essentially a single instantiation as a member of nsHttpHandler.
 // It could be all static except using static ctors of XPCOM objects is a
 // bad idea.
 class SpdyInformation
 {
 public:
   SpdyInformation();
   ~SpdyInformation() {}
 
   static const uint32_t kCount = 3;
 
-  // determine if a version of the protocol is enabled for index <= kCount
-  bool ProtocolEnabled(uint32_t index);
+  // determine the index (0..kCount-1) of the spdy information that
+  // correlates to the npn string. NS_FAILED() if no match is found.
+  nsresult GetNPNIndex(const nsACString &npnString, uint32_t *result) const;
+
+  // determine if a version of the protocol is enabled for index < kCount
+  bool ProtocolEnabled(uint32_t index) const;
 
-  // lookup a version enum based on an npn string. returns NS_OK if
-  // string was known.
-  nsresult GetNPNVersionIndex(const nsACString &npnString, uint8_t *result);
+  uint8_t   Version[kCount]; // telemetry enum e.g. SPDY_VERSION_31
+  nsCString VersionString[kCount]; // npn string e.g. "spdy/3.1"
 
-  uint8_t   Version[kCount];
-  nsCString VersionString[kCount];
+  // the ALPNCallback function allows the protocol stack to decide whether or
+  // not to offer a particular protocol based on the known TLS information
+  // that we will offer in the client hello (such as version). There has
+  // not been a Server Hello received yet, so not much else can be considered.
+  // Stacks without restrictions can just use SpdySessionTrue()
+  ALPNCallback ALPNCallbacks[kCount];
 };
 
 }} // namespace mozilla::net
 
 #endif // mozilla_net_ASpdySession_h
--- a/netwerk/protocol/http/Http2Session.cpp
+++ b/netwerk/protocol/http/Http2Session.cpp
@@ -2942,16 +2942,31 @@ Http2Session::BufferOutput(const char *b
 {
   nsAHttpSegmentReader *old = mSegmentReader;
   mSegmentReader = nullptr;
   nsresult rv = OnReadSegment(buf, count, countRead);
   mSegmentReader = old;
   return rv;
 }
 
+bool // static
+Http2Session::ALPNCallback(nsISupports *securityInfo)
+{
+  nsCOMPtr<nsISSLSocketControl> ssl = do_QueryInterface(securityInfo);
+  LOG3(("Http2Session::ALPNCallback sslsocketcontrol=%p\n", ssl.get()));
+  if (ssl) {
+    int16_t version = ssl->GetSSLVersionOffered();
+    LOG3(("Http2Session::ALPNCallback version=%x\n", version));
+    if (version >= nsISSLSocketControl::TLS_VERSION_1_2) {
+      return true;
+    }
+  }
+  return false;
+}
+
 nsresult
 Http2Session::ConfirmTLSProfile()
 {
   if (mTLSProfileConfirmed)
     return NS_OK;
 
   LOG3(("Http2Session::ConfirmTLSProfile %p mConnection=%p\n",
         this, mConnection.get()));
--- a/netwerk/protocol/http/Http2Session.h
+++ b/netwerk/protocol/http/Http2Session.h
@@ -193,16 +193,17 @@ public:
   uint32_t AmountOfOutputBuffered() { return mOutputQueueUsed - mOutputQueueSent; }
 
   uint32_t GetServerInitialStreamWindow() { return mServerInitialStreamWindow; }
 
   void ConnectPushedStream(Http2Stream *stream);
   void MaybeDecrementConcurrent(Http2Stream *stream);
 
   nsresult ConfirmTLSProfile();
+  static bool ALPNCallback(nsISupports *securityInfo);
 
   uint64_t Serial() { return mSerial; }
 
   void PrintDiagnostics (nsCString &log);
 
   // Streams need access to these
   uint32_t SendingChunkSize() { return mSendingChunkSize; }
   uint32_t PushAllowance() { return mPushAllowance; }
--- a/netwerk/protocol/http/nsHttpConnection.cpp
+++ b/netwerk/protocol/http/nsHttpConnection.cpp
@@ -305,32 +305,30 @@ nsHttpConnection::EnsureNPNComplete()
         rv = mSocketOut->Write("", 0, &count);
         if (NS_FAILED(rv) && rv != NS_BASE_STREAM_WOULD_BLOCK) {
             goto npnComplete;
         }
 
         return false;
     }
 
-    if (NS_FAILED(rv)) {
-        goto npnComplete;
-    }
-    LOG(("nsHttpConnection::EnsureNPNComplete %p [%s] negotiated to '%s'%s\n",
-         this, mConnInfo->HashKey().get(), negotiatedNPN.get(),
-         mTLSFilter ? " [Double Tunnel]" : ""));
+    if (NS_SUCCEEDED(rv)) {
+        LOG(("nsHttpConnection::EnsureNPNComplete %p [%s] negotiated to '%s'%s\n",
+             this, mConnInfo->HashKey().get(), negotiatedNPN.get(),
+             mTLSFilter ? " [Double Tunnel]" : ""));
 
-    uint8_t spdyVersion;
-    rv = gHttpHandler->SpdyInfo()->GetNPNVersionIndex(negotiatedNPN,
-                                                      &spdyVersion);
-    if (NS_SUCCEEDED(rv)) {
-        StartSpdy(spdyVersion);
+        uint32_t infoIndex;
+        const SpdyInformation *info = gHttpHandler->SpdyInfo();
+        if (NS_SUCCEEDED(info->GetNPNIndex(negotiatedNPN, &infoIndex))) {
+            StartSpdy(info->Version[infoIndex]);
+        }
+
+        Telemetry::Accumulate(Telemetry::SPDY_NPN_CONNECT, UsingSpdy());
     }
 
-    Telemetry::Accumulate(Telemetry::SPDY_NPN_CONNECT, UsingSpdy());
-
 npnComplete:
     LOG(("nsHttpConnection::EnsureNPNComplete setting complete to true"));
     mNPNComplete = true;
     return true;
 }
 
 void
 nsHttpConnection::OnTunnelNudged(TLSFilterTransaction *trans)
@@ -470,16 +468,20 @@ nsHttpConnection::SetupSSL()
     if (mInSpdyTunnel) {
         InitSSLParams(false, true);
     } else {
         bool usingHttpsProxy = mConnInfo->UsingHttpsProxy();
         InitSSLParams(usingHttpsProxy, usingHttpsProxy);
     }
 }
 
+// The naming of NPN is historical - this function creates the basic
+// offer list for both NPN and ALPN. ALPN validation callbacks are made
+// now before the handshake is complete, and NPN validation callbacks
+// are made during the handshake.
 nsresult
 nsHttpConnection::SetupNPNList(nsISSLSocketControl *ssl, uint32_t caps)
 {
     nsTArray<nsCString> protocolArray;
 
     // The first protocol is used as the fallback if none of the
     // protocols supported overlap with the server's list.
     // When using ALPN the advertised preferences are protocolArray indicies
@@ -487,20 +489,22 @@ nsHttpConnection::SetupNPNList(nsISSLSoc
     // For NPN, In the case of overlap, matching priority is driven by
     // the order of the server's advertisement - with index 0 used when
     // there is no match.
     protocolArray.AppendElement(NS_LITERAL_CSTRING("http/1.1"));
 
     if (gHttpHandler->IsSpdyEnabled() &&
         !(caps & NS_HTTP_DISALLOW_SPDY)) {
         LOG(("nsHttpConnection::SetupSSL Allow SPDY NPN selection"));
+        const SpdyInformation *info = gHttpHandler->SpdyInfo();
         for (uint32_t index = SpdyInformation::kCount; index > 0; --index) {
-            if (gHttpHandler->SpdyInfo()->ProtocolEnabled(index - 1))
-                protocolArray.AppendElement(
-                    gHttpHandler->SpdyInfo()->VersionString[index - 1]);
+            if (info->ProtocolEnabled(index - 1) &&
+                info->ALPNCallbacks[index - 1](ssl)) {
+                protocolArray.AppendElement(info->VersionString[index - 1]);
+            }
         }
     }
 
     nsresult rv = ssl->SetNPNList(protocolArray);
     LOG(("nsHttpConnection::SetupNPNList %p %x\n",this, rv));
     return rv;
 }
 
--- a/netwerk/socket/nsISSLSocketControl.idl
+++ b/netwerk/socket/nsISSLSocketControl.idl
@@ -10,17 +10,17 @@ interface nsIInterfaceRequestor;
 interface nsIX509Cert;
 
 %{C++
 template<class T> class nsTArray;
 class nsCString;
 %}
 [ref] native nsCStringTArrayRef(nsTArray<nsCString>);
 
-[scriptable, builtinclass, uuid(7836a872-e50c-4e43-8224-fd08a8d09699)]
+[scriptable, builtinclass, uuid(89b819dc-31b0-4d09-915a-66f8a3703483)]
 interface nsISSLSocketControl : nsISupports {
     attribute nsIInterfaceRequestor     notificationCallbacks;
 
     void proxyStartSSL();
     void StartTLS();
 
     /* NPN (Next Protocol Negotiation) is a mechanism for
        negotiating the protocol to be spoken inside the SSL
@@ -78,16 +78,17 @@ interface nsISSLSocketControl : nsISuppo
     /* These values are defined by TLS. */
     const short SSL_VERSION_3   = 0x0300;
     const short TLS_VERSION_1   = 0x0301;
     const short TLS_VERSION_1_1 = 0x0302;
     const short TLS_VERSION_1_2 = 0x0303;
     const short SSL_VERSION_UNKNOWN = -1;
 
     [infallible] readonly attribute short SSLVersionUsed;
+    [infallible] readonly attribute short SSLVersionOffered;
 
     /* These values match the NSS defined values in sslt.h */
     const short SSL_MAC_UNKNOWN = -1;
     const short SSL_MAC_NULL    = 0;
     const short SSL_MAC_MD5     = 1;
     const short SSL_MAC_SHA     = 2;
     const short SSL_HMAC_MD5    = 3;
     const short SSL_HMAC_SHA    = 4;
--- a/security/manager/ssl/src/nsNSSIOLayer.cpp
+++ b/security/manager/ssl/src/nsNSSIOLayer.cpp
@@ -193,16 +193,23 @@ nsNSSSocketInfo::GetKEAKeyBits(uint32_t*
 NS_IMETHODIMP
 nsNSSSocketInfo::GetSSLVersionUsed(int16_t* aSSLVersionUsed)
 {
   *aSSLVersionUsed = mSSLVersionUsed;
   return NS_OK;
 }
 
 NS_IMETHODIMP
+nsNSSSocketInfo::GetSSLVersionOffered(int16_t* aSSLVersionOffered)
+{
+  *aSSLVersionOffered = mTLSVersionRange.max;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 nsNSSSocketInfo::GetMACAlgorithmUsed(int16_t* aMac)
 {
   *aMac = mMACAlgorithmUsed;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsNSSSocketInfo::GetClientCert(nsIX509Cert** aClientCert)