Bug 1560353 - Extend SSLTokensCache to store the result of VerifySSLServerCert r=dragana,keeler
authorKershaw Chang <kershaw@mozilla.com>
Tue, 01 Oct 2019 12:10:58 +0000
changeset 495707 73a76edb175f07d1160d35627cd0ca8bf8bd6d53
parent 495706 b24092b7bd4989e85494022922d766d120536cf2
child 495708 f472f9a312c98519c9e7efe2a8633455798b2356
push id36637
push usershindli@mozilla.com
push dateTue, 01 Oct 2019 21:34:52 +0000
treeherdermozilla-central@0dfb6920936b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdragana, keeler
bugs1560353
milestone71.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 1560353 - Extend SSLTokensCache to store the result of VerifySSLServerCert r=dragana,keeler Differential Revision: https://phabricator.services.mozilla.com/D46159
netwerk/base/SSLTokensCache.cpp
netwerk/base/SSLTokensCache.h
netwerk/base/moz.build
netwerk/base/nsSocketTransport2.cpp
security/manager/ssl/TransportSecurityInfo.h
security/manager/ssl/moz.build
security/manager/ssl/nsNSSCallbacks.cpp
--- a/netwerk/base/SSLTokensCache.cpp
+++ b/netwerk/base/SSLTokensCache.cpp
@@ -1,15 +1,16 @@
 /* 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 "SSLTokensCache.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/Logging.h"
+#include "nsNSSIOLayer.h"
 #include "ssl.h"
 #include "sslexp.h"
 
 namespace mozilla {
 namespace net {
 
 static bool const kDefaultEnabled = false;
 Atomic<bool, Relaxed> SSLTokensCache::sEnabled(kDefaultEnabled);
@@ -31,16 +32,38 @@ class ExpirationComparator {
                 SSLTokensCache::TokenCacheRecord* b) const {
     return a->mExpirationTime < b->mExpirationTime;
   }
 };
 
 StaticRefPtr<SSLTokensCache> SSLTokensCache::gInstance;
 StaticMutex SSLTokensCache::sLock;
 
+uint32_t SSLTokensCache::TokenCacheRecord::Size() const {
+  uint32_t size = mToken.Length() + sizeof(mSessionCacheInfo.mEVStatus) +
+                  sizeof(mSessionCacheInfo.mCertificateTransparencyStatus) +
+                  mSessionCacheInfo.mServerCertBytes.Length();
+  if (mSessionCacheInfo.mSucceededCertChainBytes) {
+    for (const auto& cert : mSessionCacheInfo.mSucceededCertChainBytes.ref()) {
+      size += cert.Length();
+    }
+  }
+  return size;
+}
+
+void SSLTokensCache::TokenCacheRecord::Reset() {
+  mToken.Clear();
+  mExpirationTime = 0;
+  mSessionCacheInfo.mEVStatus = psm::EVStatus::NotEV;
+  mSessionCacheInfo.mCertificateTransparencyStatus =
+      nsITransportSecurityInfo::CERTIFICATE_TRANSPARENCY_NOT_APPLICABLE;
+  mSessionCacheInfo.mServerCertBytes.Clear();
+  mSessionCacheInfo.mSucceededCertChainBytes.reset();
+}
+
 NS_IMPL_ISUPPORTS(SSLTokensCache, nsIMemoryReporter)
 
 // static
 nsresult SSLTokensCache::Init() {
   StaticMutexAutoLock lock(sLock);
 
   if (XRE_GetProcessType() != GeckoProcessType_Default) {
     return NS_OK;
@@ -74,54 +97,119 @@ nsresult SSLTokensCache::Shutdown() {
 SSLTokensCache::SSLTokensCache() : mCacheSize(0) {
   LOG(("SSLTokensCache::SSLTokensCache"));
 }
 
 SSLTokensCache::~SSLTokensCache() { LOG(("SSLTokensCache::~SSLTokensCache")); }
 
 // static
 nsresult SSLTokensCache::Put(const nsACString& aKey, const uint8_t* aToken,
-                             uint32_t aTokenLen) {
+                             uint32_t aTokenLen,
+                             nsITransportSecurityInfo* aSecInfo) {
   StaticMutexAutoLock lock(sLock);
 
   LOG(("SSLTokensCache::Put [key=%s, tokenLen=%u]",
        PromiseFlatCString(aKey).get(), aTokenLen));
 
   if (!gInstance) {
     LOG(("  service not initialized"));
     return NS_ERROR_NOT_INITIALIZED;
   }
 
+  if (!aSecInfo) {
+    return NS_ERROR_FAILURE;
+  }
+
   PRUint32 expirationTime;
   SSLResumptionTokenInfo tokenInfo;
   if (SSL_GetResumptionTokenInfo(aToken, aTokenLen, &tokenInfo,
                                  sizeof(tokenInfo)) != SECSuccess) {
     LOG(("  cannot get expiration time from the token, NSS error %d",
          PORT_GetError()));
     return NS_ERROR_FAILURE;
   }
   expirationTime = tokenInfo.expirationTime;
   SSL_DestroyResumptionTokenInfo(&tokenInfo);
 
+  nsCOMPtr<nsIX509Cert> cert;
+  aSecInfo->GetServerCert(getter_AddRefs(cert));
+  if (!cert) {
+    return NS_ERROR_FAILURE;
+  }
+
+  nsTArray<uint8_t> certBytes;
+  nsresult rv = cert->GetRawDER(certBytes);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  Maybe<nsTArray<nsTArray<uint8_t>>> succeededCertChainBytes;
+  nsCOMPtr<nsIX509CertList> succeededCertChain;
+  Unused << aSecInfo->GetSucceededCertChain(getter_AddRefs(succeededCertChain));
+  if (succeededCertChain) {
+    succeededCertChainBytes.emplace();
+    rv = succeededCertChain->GetCertList()->ForEachCertificateInChain(
+        [&succeededCertChainBytes](nsCOMPtr<nsIX509Cert> aCert, bool /*unused*/,
+                                   /*out*/ bool& /*unused*/) {
+          nsTArray<uint8_t> cert;
+          nsresult rv = aCert->GetRawDER(cert);
+          if (NS_FAILED(rv)) {
+            return rv;
+          }
+
+          succeededCertChainBytes->AppendElement(std::move(cert));
+          return NS_OK;
+        });
+    if (NS_FAILED(rv)) {
+      return rv;
+    }
+  }
+
+  bool isEV;
+  rv = aSecInfo->GetIsExtendedValidation(&isEV);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  uint16_t certificateTransparencyStatus;
+  rv = aSecInfo->GetCertificateTransparencyStatus(
+      &certificateTransparencyStatus);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
   TokenCacheRecord* rec = nullptr;
 
   if (!gInstance->mTokenCacheRecords.Get(aKey, &rec)) {
     rec = new TokenCacheRecord();
     rec->mKey = aKey;
     gInstance->mTokenCacheRecords.Put(aKey, rec);
     gInstance->mExpirationArray.AppendElement(rec);
   } else {
-    gInstance->mCacheSize -= rec->mToken.Length();
-    rec->mToken.Clear();
+    gInstance->mCacheSize -= rec->Size();
+    rec->Reset();
   }
 
   rec->mExpirationTime = expirationTime;
   MOZ_ASSERT(rec->mToken.IsEmpty());
   rec->mToken.AppendElements(aToken, aTokenLen);
-  gInstance->mCacheSize += rec->mToken.Length();
+
+  rec->mSessionCacheInfo.mServerCertBytes = std::move(certBytes);
+
+  rec->mSessionCacheInfo.mSucceededCertChainBytes =
+      std::move(succeededCertChainBytes);
+
+  if (isEV) {
+    rec->mSessionCacheInfo.mEVStatus = psm::EVStatus::EV;
+  }
+
+  rec->mSessionCacheInfo.mCertificateTransparencyStatus =
+      certificateTransparencyStatus;
+
+  gInstance->mCacheSize += rec->Size();
 
   gInstance->LogStats();
 
   gInstance->EvictIfNecessary();
 
   return NS_OK;
 }
 
@@ -146,16 +234,40 @@ nsresult SSLTokensCache::Get(const nsACS
     }
   }
 
   LOG(("  token not found"));
   return NS_ERROR_NOT_AVAILABLE;
 }
 
 // static
+bool SSLTokensCache::GetSessionCacheInfo(const nsACString& aKey,
+                                         SessionCacheInfo& aResult) {
+  StaticMutexAutoLock lock(sLock);
+
+  LOG(("SSLTokensCache::GetSessionCacheInfo [key=%s]",
+       PromiseFlatCString(aKey).get()));
+
+  if (!gInstance) {
+    LOG(("  service not initialized"));
+    return false;
+  }
+
+  TokenCacheRecord* rec = nullptr;
+
+  if (gInstance->mTokenCacheRecords.Get(aKey, &rec)) {
+    aResult = rec->mSessionCacheInfo;
+    return true;
+  }
+
+  LOG(("  token not found"));
+  return false;
+}
+
+// static
 nsresult SSLTokensCache::Remove(const nsACString& aKey) {
   StaticMutexAutoLock lock(sLock);
 
   LOG(("SSLTokensCache::Remove [key=%s]", PromiseFlatCString(aKey).get()));
 
   if (!gInstance) {
     LOG(("  service not initialized"));
     return NS_ERROR_NOT_INITIALIZED;
@@ -172,17 +284,17 @@ nsresult SSLTokensCache::RemoveLocked(co
 
   nsAutoPtr<TokenCacheRecord> rec;
 
   if (!mTokenCacheRecords.Remove(aKey, &rec)) {
     LOG(("  token not found"));
     return NS_ERROR_NOT_AVAILABLE;
   }
 
-  mCacheSize -= rec->mToken.Length();
+  mCacheSize -= rec->Size();
 
   if (!mExpirationArray.RemoveElement(rec)) {
     MOZ_ASSERT(false, "token not found in mExpirationArray");
   }
 
   LogStats();
 
   return NS_OK;
--- a/netwerk/base/SSLTokensCache.h
+++ b/netwerk/base/SSLTokensCache.h
@@ -3,38 +3,50 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef SSLTokensCache_h_
 #define SSLTokensCache_h_
 
 #include "nsIMemoryReporter.h"
 #include "nsClassHashtable.h"
 #include "nsTArray.h"
+#include "mozilla/Maybe.h"
 #include "mozilla/StaticMutex.h"
 #include "mozilla/StaticPtr.h"
 #include "nsXULAppAPI.h"
+#include "TransportSecurityInfo.h"  // For EVStatus
 
 namespace mozilla {
 namespace net {
 
+struct SessionCacheInfo {
+  psm::EVStatus mEVStatus = psm::EVStatus::NotEV;
+  uint16_t mCertificateTransparencyStatus =
+      nsITransportSecurityInfo::CERTIFICATE_TRANSPARENCY_NOT_APPLICABLE;
+  nsTArray<uint8_t> mServerCertBytes;
+  Maybe<nsTArray<nsTArray<uint8_t>>> mSucceededCertChainBytes;
+};
+
 class SSLTokensCache : public nsIMemoryReporter {
  public:
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_NSIMEMORYREPORTER
 
   friend class ExpirationComparator;
 
   static nsresult Init();
   static nsresult Shutdown();
 
   static bool IsEnabled() { return sEnabled; }
 
   static nsresult Put(const nsACString& aKey, const uint8_t* aToken,
-                      uint32_t aTokenLen);
+                      uint32_t aTokenLen, nsITransportSecurityInfo* aSecInfo);
   static nsresult Get(const nsACString& aKey, nsTArray<uint8_t>& aToken);
+  static bool GetSessionCacheInfo(const nsACString& aKey,
+                                  SessionCacheInfo& aResult);
   static nsresult Remove(const nsACString& aKey);
 
  private:
   SSLTokensCache();
   virtual ~SSLTokensCache();
 
   nsresult RemoveLocked(const nsACString& aKey);
 
@@ -50,19 +62,23 @@ class SSLTokensCache : public nsIMemoryR
   static Atomic<bool, Relaxed> sEnabled;
   // Capacity of the cache in kilobytes
   static Atomic<uint32_t, Relaxed> sCapacity;
 
   uint32_t mCacheSize;  // Actual cache size in bytes
 
   class TokenCacheRecord {
    public:
+    uint32_t Size() const;
+    void Reset();
+
     nsCString mKey;
     PRUint32 mExpirationTime;
     nsTArray<uint8_t> mToken;
+    SessionCacheInfo mSessionCacheInfo;
   };
 
   nsClassHashtable<nsCStringHashKey, TokenCacheRecord> mTokenCacheRecords;
   nsTArray<TokenCacheRecord*> mExpirationArray;
 };
 
 }  // namespace net
 }  // namespace mozilla
--- a/netwerk/base/moz.build
+++ b/netwerk/base/moz.build
@@ -317,16 +317,17 @@ FINAL_LIBRARY = 'xul'
 
 LOCAL_INCLUDES += [
     '/docshell/base',
     '/dom/base',
     '/extensions/permissions',
     '/netwerk/protocol/http',
     '/netwerk/socket',
     '/netwerk/url-classifier',
+    '/security/manager/ssl',
 ]
 
 if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
     LOCAL_INCLUDES += [
         '/xpcom/base',
     ]
 
 if CONFIG['CC_TYPE'] in ('clang', 'gcc'):
--- a/netwerk/base/nsSocketTransport2.cpp
+++ b/netwerk/base/nsSocketTransport2.cpp
@@ -1238,20 +1238,27 @@ SECStatus nsSocketTransport::StoreResump
     return SECFailure;
   }
 
   nsCOMPtr<nsISSLSocketControl> secCtrl =
       do_QueryInterface(static_cast<nsSocketTransport*>(ctx)->mSecInfo);
   if (!secCtrl) {
     return SECFailure;
   }
-
   nsAutoCString peerId;
   secCtrl->GetPeerId(peerId);
-  SSLTokensCache::Put(peerId, resumptionToken, len);
+
+  nsCOMPtr<nsITransportSecurityInfo> secInfo = do_QueryInterface(secCtrl);
+  if (!secInfo) {
+    return SECFailure;
+  }
+
+  if (NS_FAILED(SSLTokensCache::Put(peerId, resumptionToken, len, secInfo))) {
+    return SECFailure;
+  }
 
   return SECSuccess;
 }
 
 nsresult nsSocketTransport::InitiateSocket() {
   SOCKET_LOG(("nsSocketTransport::InitiateSocket [this=%p]\n", this));
 
   nsresult rv;
--- a/security/manager/ssl/TransportSecurityInfo.h
+++ b/security/manager/ssl/TransportSecurityInfo.h
@@ -82,16 +82,21 @@ class TransportSecurityInfo : public nsI
   bool HasServerCert() { return mServerCert != nullptr; }
 
   void SetCertificateTransparencyInfo(
       const mozilla::psm::CertificateTransparencyInfo& info);
 
   // Use errorCode == 0 to indicate success;
   virtual void SetCertVerificationResult(PRErrorCode errorCode){};
 
+  void SetCertificateTransparencyStatus(
+      uint16_t aCertificateTransparencyStatus) {
+    mCertificateTransparencyStatus = aCertificateTransparencyStatus;
+  }
+
   uint16_t mCipherSuite;
   uint16_t mProtocolVersion;
   uint16_t mCertificateTransparencyStatus;
   nsCString mKeaGroup;
   nsCString mSignatureSchemeName;
 
   bool mIsDelegatedCredential;
   bool mIsDomainMismatch;
--- a/security/manager/ssl/moz.build
+++ b/security/manager/ssl/moz.build
@@ -206,16 +206,17 @@ else:
         'CertBlocklist.cpp',
     ]
 
 FINAL_LIBRARY = 'xul'
 
 LOCAL_INCLUDES += [
     '/dom/base',
     '/dom/crypto',
+    '/netwerk/base',
     '/security/certverifier',
 ]
 
 LOCAL_INCLUDES += [
     '!/dist/public/nss',
 ]
 
 GENERATED_FILES = [
--- a/security/manager/ssl/nsNSSCallbacks.cpp
+++ b/security/manager/ssl/nsNSSCallbacks.cpp
@@ -34,16 +34,17 @@
 #include "nsNSSIOLayer.h"
 #include "nsNetUtil.h"
 #include "nsProtectedAuthThread.h"
 #include "nsProxyRelease.h"
 #include "nsStringStream.h"
 #include "mozpkix/pkixtypes.h"
 #include "ssl.h"
 #include "sslproto.h"
+#include "SSLTokensCache.h"
 
 #include "TrustOverrideUtils.h"
 #include "TrustOverride-SymantecData.inc"
 #include "TrustOverride-AppleGoogleDigiCertData.inc"
 #include "TrustOverride-TestImminentDistrustData.inc"
 
 using namespace mozilla;
 using namespace mozilla::pkix;
@@ -1154,16 +1155,90 @@ nsresult IsCertificateDistrustImminent(n
                                   RootAppleAndGoogleSPKIs, isDistrusted);
     if (NS_FAILED(rv)) {
       return rv;
     }
   }
   return NS_OK;
 }
 
+static bool ConstructCERTCertListFromBytesArray(
+    nsTArray<nsTArray<uint8_t>>& aCertArray,
+    /*out*/ UniqueCERTCertList& aCertList) {
+  aCertList = UniqueCERTCertList(CERT_NewCertList());
+  if (!aCertList) {
+    return false;
+  }
+
+  CERTCertDBHandle* certDB(CERT_GetDefaultCertDB());  // non-owning
+  for (auto& cert : aCertArray) {
+    SECItem certDER = {siBuffer, cert.Elements(),
+                       static_cast<unsigned int>(cert.Length())};
+    UniqueCERTCertificate tmpCert(
+        CERT_NewTempCertificate(certDB, &certDER, nullptr, false, true));
+    if (!tmpCert) {
+      return false;
+    }
+
+    if (CERT_AddCertToListTail(aCertList.get(), tmpCert.get()) != SECSuccess) {
+      return false;
+    }
+    Unused << tmpCert.release();  // tmpCert is now owned by aCertList.
+  }
+
+  return true;
+}
+
+static void RebuildCertificateInfoFromSSLTokenCache(
+    nsNSSSocketInfo* aInfoObject) {
+  MOZ_ASSERT(aInfoObject);
+
+  if (!aInfoObject) {
+    return;
+  }
+
+  nsAutoCString key;
+  aInfoObject->GetPeerId(key);
+  mozilla::net::SessionCacheInfo info;
+  if (!mozilla::net::SSLTokensCache::GetSessionCacheInfo(key, info)) {
+    MOZ_LOG(
+        gPIPNSSLog, LogLevel::Debug,
+        ("RebuildCertificateInfoFromSSLTokenCache cannot find cached info."));
+    return;
+  }
+
+  RefPtr<nsNSSCertificate> nssc = nsNSSCertificate::ConstructFromDER(
+      BitwiseCast<char*, uint8_t*>(info.mServerCertBytes.Elements()),
+      info.mServerCertBytes.Length());
+  if (!nssc) {
+    MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
+            ("RebuildCertificateInfoFromSSLTokenCache failed to construct "
+             "server cert"));
+    return;
+  }
+
+  UniqueCERTCertList builtCertChain;
+  if (info.mSucceededCertChainBytes) {
+    if (!ConstructCERTCertListFromBytesArray(
+            info.mSucceededCertChainBytes.ref(), builtCertChain)) {
+      MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
+              ("RebuildCertificateInfoFromSSLTokenCache failed to construct "
+               "cert list"));
+      return;
+    }
+  }
+
+  aInfoObject->SetServerCert(nssc, info.mEVStatus);
+  aInfoObject->SetCertificateTransparencyStatus(
+      info.mCertificateTransparencyStatus);
+  if (builtCertChain) {
+    aInfoObject->SetSucceededCertChain(std::move(builtCertChain));
+  }
+}
+
 void HandshakeCallback(PRFileDesc* fd, void* client_data) {
   SECStatus rv;
 
   nsNSSSocketInfo* infoObject = (nsNSSSocketInfo*)fd->higher->secret;
 
   // Do the bookkeeping that needs to be done after the
   // server's ServerHello...ServerHelloDone have been processed, but that
   // doesn't need the handshake to be completed.
@@ -1291,17 +1366,21 @@ void HandshakeCallback(PRFileDesc* fd, v
                                                 infoObject->GetPort());
     }
   }
 
   if (infoObject->HasServerCert()) {
     MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
             ("HandshakeCallback KEEPING existing cert\n"));
   } else {
-    RebuildVerifiedCertificateInformation(fd, infoObject);
+    if (mozilla::net::SSLTokensCache::IsEnabled()) {
+      RebuildCertificateInfoFromSSLTokenCache(infoObject);
+    } else {
+      RebuildVerifiedCertificateInformation(fd, infoObject);
+    }
   }
 
   nsCOMPtr<nsIX509CertList> succeededCertChain;
   // This always returns NS_OK, but the list could be empty. This is a
   // best-effort check for now. Bug 731478 will reduce the incidence of empty
   // succeeded cert chains through better caching.
   Unused << infoObject->GetSucceededCertChain(
       getter_AddRefs(succeededCertChain));