bug 1240118 - add functionality to treat a test certificate as a built-in root r?mgoodwin draft
authorDavid Keeler <dkeeler@mozilla.com>
Tue, 15 Mar 2016 17:19:00 -0700
changeset 341864 e4376c3097d4a963edbf5b9f2e570b328b2bb845
parent 341653 3e04659fdf6aef792f7cf9840189c6c38d08d1e8
child 516484 a6d472be0f645b00ecea418ed9a25fa5c037924c
push id13312
push userdkeeler@mozilla.com
push dateThu, 17 Mar 2016 23:42:48 +0000
reviewersmgoodwin
bugs1240118
milestone48.0a1
bug 1240118 - add functionality to treat a test certificate as a built-in root r?mgoodwin MozReview-Commit-ID: GJMd2zEAcmX
security/certverifier/CertVerifier.cpp
security/manager/ssl/nsNSSComponent.cpp
security/manager/ssl/nsNSSComponent.h
security/manager/ssl/tests/unit/test_cert_sha1.js
--- a/security/certverifier/CertVerifier.cpp
+++ b/security/certverifier/CertVerifier.cpp
@@ -77,18 +77,28 @@ IsCertChainRootBuiltInRoot(CERTCertList*
 Result
 IsCertBuiltInRoot(CERTCertificate* cert, bool& result)
 {
   result = false;
   nsCOMPtr<nsINSSComponent> component(do_GetService(PSM_COMPONENT_CONTRACTID));
   if (!component) {
     return Result::FATAL_ERROR_LIBRARY_FAILURE;
   }
+  nsresult rv;
+#ifdef DEBUG
+  rv = component->IsCertTestBuiltInRoot(cert, result);
+  if (NS_FAILED(rv)) {
+    return Result::FATAL_ERROR_LIBRARY_FAILURE;
+  }
+  if (result) {
+    return Success;
+  }
+#endif // DEBUG
   nsAutoString modName;
-  nsresult rv = component->GetPIPNSSBundleString("RootCertModuleName", modName);
+  rv = component->GetPIPNSSBundleString("RootCertModuleName", modName);
   if (NS_FAILED(rv)) {
     return Result::FATAL_ERROR_LIBRARY_FAILURE;
   }
   NS_ConvertUTF16toUTF8 modNameUTF8(modName);
   UniqueSECMODModule builtinRootsModule(SECMOD_FindModule(modNameUTF8.get()));
   // If the built-in roots module isn't loaded, nothing is a built-in root.
   if (!builtinRootsModule) {
     return Success;
--- a/security/manager/ssl/nsNSSComponent.cpp
+++ b/security/manager/ssl/nsNSSComponent.cpp
@@ -1321,16 +1321,21 @@ nsNSSComponent::Observe(nsISupports* aSu
                prefName.EqualsLiteral("security.OCSP.GET.enabled") ||
                prefName.EqualsLiteral("security.pki.cert_short_lifetime_in_days") ||
                prefName.EqualsLiteral("security.ssl.enable_ocsp_stapling") ||
                prefName.EqualsLiteral("security.ssl.enable_ocsp_must_staple") ||
                prefName.EqualsLiteral("security.cert_pinning.enforcement_level") ||
                prefName.EqualsLiteral("security.pki.sha1_enforcement_level")) {
       MutexAutoLock lock(mutex);
       setValidationOptions(false, lock);
+#ifdef DEBUG
+    } else if (prefName.EqualsLiteral("security.test.built_in_root_hash")) {
+      MutexAutoLock lock(mutex);
+      mTestBuiltInRootHash = Preferences::GetString("security.test.built_in_root_hash");
+#endif // DEBUG
     } else {
       clearSessionCache = false;
     }
     if (clearSessionCache)
       SSL_ClearSessionCache();
   }
 
   return NS_OK;
@@ -1445,16 +1450,44 @@ nsNSSComponent::DoProfileBeforeChange()
 NS_IMETHODIMP
 nsNSSComponent::IsNSSInitialized(bool* initialized)
 {
   MutexAutoLock lock(mutex);
   *initialized = mNSSInitialized;
   return NS_OK;
 }
 
+#ifdef DEBUG
+NS_IMETHODIMP
+nsNSSComponent::IsCertTestBuiltInRoot(CERTCertificate* cert, bool& result)
+{
+  MutexAutoLock lock(mutex);
+  MOZ_ASSERT(mNSSInitialized);
+
+  result = false;
+
+  if (mTestBuiltInRootHash.IsEmpty()) {
+    return NS_OK;
+  }
+
+  RefPtr<nsNSSCertificate> nsc = nsNSSCertificate::Create(cert);
+  if (!nsc) {
+    return NS_ERROR_FAILURE;
+  }
+  nsAutoString certHash;
+  nsresult rv = nsc->GetSha256Fingerprint(certHash);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  result = mTestBuiltInRootHash.Equals(certHash);
+  return NS_OK;
+}
+#endif // DEBUG
+
 SharedCertVerifier::~SharedCertVerifier() { }
 
 already_AddRefed<SharedCertVerifier>
 nsNSSComponent::GetDefaultCertVerifier()
 {
   MutexAutoLock lock(mutex);
   MOZ_ASSERT(mNSSInitialized);
   RefPtr<SharedCertVerifier> certVerifier(mDefaultCertVerifier);
--- a/security/manager/ssl/nsNSSComponent.h
+++ b/security/manager/ssl/nsNSSComponent.h
@@ -77,16 +77,20 @@ public:
 #ifndef MOZ_NO_SMART_CARDS
   NS_IMETHOD LaunchSmartCardThread(SECMODModule* module) = 0;
 
   NS_IMETHOD ShutdownSmartCardThread(SECMODModule* module) = 0;
 #endif
 
   NS_IMETHOD IsNSSInitialized(bool* initialized) = 0;
 
+#ifdef DEBUG
+  NS_IMETHOD IsCertTestBuiltInRoot(CERTCertificate* cert, bool& result) = 0;
+#endif
+
   virtual ::already_AddRefed<mozilla::psm::SharedCertVerifier>
     GetDefaultCertVerifier() = 0;
 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(nsINSSComponent, NS_INSSCOMPONENT_IID)
 
 class nsNSSShutDownList;
 class nsCertVerificationThread;
@@ -127,16 +131,20 @@ public:
   void ShutdownSmartCardThreads();
   nsresult DispatchEventToWindow(nsIDOMWindow* domWin,
                                  const nsAString& eventType,
                                  const nsAString& token);
 #endif
 
   NS_IMETHOD IsNSSInitialized(bool* initialized) override;
 
+#ifdef DEBUG
+  NS_IMETHOD IsCertTestBuiltInRoot(CERTCertificate* cert, bool& result) override;
+#endif
+
   ::already_AddRefed<mozilla::psm::SharedCertVerifier>
     GetDefaultCertVerifier() override;
 
   // The following two methods are thread-safe.
   static bool AreAnyWeakCiphersEnabled();
   static void UseWeakCiphersOnSocket(PRFileDesc* fd);
 
   static void FillTLSVersionRange(SSLVersionRange& rangeOut,
@@ -168,16 +176,20 @@ private:
   nsCOMPtr<nsIStringBundle> mNSSErrorsBundle;
   bool mNSSInitialized;
   static int mInstanceCount;
   nsNSSShutDownList* mShutdownObjectList;
 #ifndef MOZ_NO_SMART_CARDS
   SmartCardThreadList* mThreadList;
 #endif
 
+#ifdef DEBUG
+  nsAutoString mTestBuiltInRootHash;
+#endif
+
   void deleteBackgroundThreads();
   void createBackgroundThreads();
   nsCertVerificationThread* mCertVerificationThread;
 
   nsNSSHttpInterface mHttpForNSS;
   RefPtr<mozilla::psm::SharedCertVerifier> mDefaultCertVerifier;
 
   static PRStatus IdentityInfoInit(void);
--- a/security/manager/ssl/tests/unit/test_cert_sha1.js
+++ b/security/manager/ssl/tests/unit/test_cert_sha1.js
@@ -58,18 +58,22 @@ function run_test() {
   //
   // Expected outcomes (accept / reject):
   //
   //                     a   b   c   d   e
   // Allowed=0          Acc Acc Acc Acc Acc
   // Forbidden=1        Rej Rej Rej Rej Rej
   // Before2016=2       Acc Acc Rej Rej Rej
   //
-  // The pref setting of ImportedRoot (3) accepts everything because the
-  // testing root is an imported one. This will be addressed in bug 1240118.
+  // The pref setting of ImportedRoot (3) accepts usage of SHA-1 for
+  // certificates valid before 2016 issued by built-in roots or SHA-1 for
+  // certificates issued any time by non-built-in roots. By default, the testing
+  // certificates are all considered issued by a non-built-in root. However, we
+  // now have the ability to treat a given non-built-in root as built-in. We
+  // test both of these situations below.
 
   // SHA-1 allowed
   Services.prefs.setIntPref("security.pki.sha1_enforcement_level", 0);
   checkIntermediate(certFromFile("int-pre"), PRErrorCodeSuccess);
   checkEndEntity(certFromFile("ee-pre_int-pre"), PRErrorCodeSuccess);
   checkEndEntity(certFromFile("ee-post_int-pre"), PRErrorCodeSuccess);
   checkIntermediate(certFromFile("int-post"), PRErrorCodeSuccess);
   checkEndEntity(certFromFile("ee-post_int-post"), PRErrorCodeSuccess);
@@ -85,17 +89,39 @@ function run_test() {
   // SHA-1 allowed only before 2016
   Services.prefs.setIntPref("security.pki.sha1_enforcement_level", 2);
   checkIntermediate(certFromFile("int-pre"), PRErrorCodeSuccess);
   checkEndEntity(certFromFile("ee-pre_int-pre"), PRErrorCodeSuccess);
   checkEndEntity(certFromFile("ee-post_int-pre"), SEC_ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED);
   checkIntermediate(certFromFile("int-post"), SEC_ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED);
   checkEndEntity(certFromFile("ee-post_int-post"), SEC_ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED);
 
-  // SHA-1 allowed only before 2016 or when issued by an imported root (which
-  // happens to be all of our test certificates).
+  // SHA-1 allowed only before 2016 or when issued by an imported root. First
+  // test with the test root considered a built-in (on debug only - this
+  // functionality is disabled on non-debug builds).
   Services.prefs.setIntPref("security.pki.sha1_enforcement_level", 3);
+  if (isDebugBuild) {
+    let root = certFromFile("ca");
+    Services.prefs.setCharPref("security.test.built_in_root_hash", root.sha256Fingerprint);
+    checkIntermediate(certFromFile("int-pre"), PRErrorCodeSuccess);
+    checkEndEntity(certFromFile("ee-pre_int-pre"), PRErrorCodeSuccess);
+    checkEndEntity(certFromFile("ee-post_int-pre"), SEC_ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED);
+    // This should fail but it doesn't, because the implementation makes no
+    // effort to enforce that when verifying a certificate for the capability
+    // of issuing TLS server auth certificates (i.e. the
+    // "certificateUsageSSLCA" usage), if SHA-1 was necessary, then the root of
+    // trust is an imported certificate. We don't really care, though, because
+    // the platform doesn't actually make trust decisions in this way and the
+    // ability to even verify a certificate for this purpose is intended to go
+    // away in bug 1257362.
+    // checkIntermediate(certFromFile("int-post"), SEC_ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED);
+    checkEndEntity(certFromFile("ee-post_int-post"), SEC_ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED);
+    Services.prefs.clearUserPref("security.test.built_in_root_hash");
+  }
+
+  // SHA-1 still allowed only before 2016 or when issued by an imported root.
+  // Now test with the test root considered a non-built-in.
   checkIntermediate(certFromFile("int-pre"), PRErrorCodeSuccess);
   checkEndEntity(certFromFile("ee-pre_int-pre"), PRErrorCodeSuccess);
   checkEndEntity(certFromFile("ee-post_int-pre"), PRErrorCodeSuccess);
   checkIntermediate(certFromFile("int-post"), PRErrorCodeSuccess);
   checkEndEntity(certFromFile("ee-post_int-post"), PRErrorCodeSuccess);
 }