bug 1245280 - add policy mechanism to optionally enforce BRs for falling back to subject CN r=Cykesiopka,mgoodwin
authorDavid Keeler <dkeeler@mozilla.com>
Tue, 09 Feb 2016 10:14:27 -0800
changeset 329243 dc40f46fae4832e4ff2e15e1034c40a96bf1851c
parent 329242 23b389d50a0b25455c79a8b25c0415a91197a0f3
child 329244 104e03d0e4082f82227e3e2c658510fa41346362
push id6048
push userkmoir@mozilla.com
push dateMon, 06 Jun 2016 19:02:08 +0000
treeherdermozilla-beta@46d72a56c57d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersCykesiopka, mgoodwin
bugs1245280
milestone48.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 1245280 - add policy mechanism to optionally enforce BRs for falling back to subject CN r=Cykesiopka,mgoodwin MozReview-Commit-ID: 7xT6JGpOH1g
netwerk/base/security-prefs.js
security/certverifier/BRNameMatchingPolicy.cpp
security/certverifier/BRNameMatchingPolicy.h
security/certverifier/CertVerifier.cpp
security/certverifier/CertVerifier.h
security/certverifier/moz.build
security/manager/ssl/SSLServerCertVerification.cpp
security/manager/ssl/SharedCertVerifier.h
security/manager/ssl/nsNSSComponent.cpp
security/manager/ssl/tests/unit/moz.build
security/manager/ssl/tests/unit/pycert.py
security/manager/ssl/tests/unit/test_baseline_requirements/ca.pem
security/manager/ssl/tests/unit/test_baseline_requirements/ca.pem.certspec
security/manager/ssl/tests/unit/test_baseline_requirements/moz.build
security/manager/ssl/tests/unit/test_baseline_requirements/no-san-old.pem
security/manager/ssl/tests/unit/test_baseline_requirements/no-san-old.pem.certspec
security/manager/ssl/tests/unit/test_baseline_requirements/no-san-recent.pem
security/manager/ssl/tests/unit/test_baseline_requirements/no-san-recent.pem.certspec
security/manager/ssl/tests/unit/test_baseline_requirements/san-contains-no-hostnames-old.pem
security/manager/ssl/tests/unit/test_baseline_requirements/san-contains-no-hostnames-old.pem.certspec
security/manager/ssl/tests/unit/test_baseline_requirements/san-contains-no-hostnames-recent.pem
security/manager/ssl/tests/unit/test_baseline_requirements/san-contains-no-hostnames-recent.pem.certspec
security/manager/ssl/tests/unit/test_baseline_requirements_subject_common_name.js
security/manager/ssl/tests/unit/xpcshell.ini
security/pkix/include/pkix/pkix.h
security/pkix/include/pkix/pkixtypes.h
security/pkix/lib/pkixnames.cpp
security/pkix/test/gtest/pkixgtest.h
security/pkix/test/gtest/pkixnames_tests.cpp
--- a/netwerk/base/security-prefs.js
+++ b/netwerk/base/security-prefs.js
@@ -44,15 +44,29 @@ pref("security.OCSP.require", false);
 pref("security.OCSP.GET.enabled", false);
 
 pref("security.pki.cert_short_lifetime_in_days", 10);
 // NB: Changes to this pref affect CERT_CHAIN_SHA1_POLICY_STATUS telemetry.
 // See the comment in CertVerifier.cpp.
 // 3 = allow SHA-1 for certificates issued before 2016 or by an imported root.
 pref("security.pki.sha1_enforcement_level", 3);
 
+// security.pki.name_matching_mode controls how the platform matches hostnames
+// to name information in TLS certificates. The possible values are:
+// 0: always fall back to the subject common name if necessary (as in, if the
+//    subject alternative name extension is either not present or does not
+//    contain any DNS names or IP addresses)
+// 1: fall back to the subject common name for certificates valid before 23
+//    August 2016 if necessary
+// 2: only use name information from the subject alternative name extension
+#ifdef RELEASE_BUILD
+pref("security.pki.name_matching_mode", 1);
+#else
+pref("security.pki.name_matching_mode", 2);
+#endif
+
 pref("security.webauth.u2f", false);
 pref("security.webauth.u2f.softtoken", false);
 pref("security.webauth.u2f.usbtoken", false);
 
 pref("security.ssl.errorReporting.enabled", true);
 pref("security.ssl.errorReporting.url", "https://data.mozilla.com/submit/sslreports");
 pref("security.ssl.errorReporting.automatic", false);
new file mode 100644
--- /dev/null
+++ b/security/certverifier/BRNameMatchingPolicy.cpp
@@ -0,0 +1,38 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=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 "BRNameMatchingPolicy.h"
+
+#include "mozilla/Assertions.h"
+
+using namespace mozilla::psm;
+using namespace mozilla::pkix;
+
+Result
+BRNameMatchingPolicy::FallBackToCommonName(
+  Time notBefore,
+  /*out*/ FallBackToSearchWithinSubject& fallBackToCommonName)
+{
+  // (new Date("2016-08-23T00:00:00Z")).getTime() / 1000
+  static const Time AUGUST_23_2016 = TimeFromEpochInSeconds(1471910400);
+  switch (mMode)
+  {
+    case Mode::Enforce:
+      fallBackToCommonName = FallBackToSearchWithinSubject::No;
+      break;
+    case Mode::EnforceAfter23August2016:
+      fallBackToCommonName = notBefore > AUGUST_23_2016
+                           ? FallBackToSearchWithinSubject::No
+                           : FallBackToSearchWithinSubject::Yes;
+      break;
+    case Mode::DoNotEnforce:
+      fallBackToCommonName = FallBackToSearchWithinSubject::Yes;
+      break;
+    default:
+      MOZ_CRASH("Unexpected Mode");
+  }
+  return Success;
+}
new file mode 100644
--- /dev/null
+++ b/security/certverifier/BRNameMatchingPolicy.h
@@ -0,0 +1,56 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=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/. */
+
+#ifndef BRNameMatchingPolicy_h
+#define BRNameMatchingPolicy_h
+
+#include "pkix/pkixtypes.h"
+
+namespace mozilla { namespace psm {
+
+// According to the Baseline Requirements version 1.3.3 section 7.1.4.2.2.a,
+// the requirements of the subject common name field are as follows:
+// "If present, this field MUST contain a single IP address or Fully‐Qualified
+// Domain Name that is one of the values contained in the Certificate’s
+// subjectAltName extension". Consequently, since any name information present
+// in the common name must be present in the subject alternative name extension,
+// when performing name matching, it should not be necessary to fall back to the
+// common name. Because this consequence has not commonly been enforced, this
+// implementation provides a mechanism to start enforcing it gradually while
+// maintaining some backwards compatibility. If configured with the mode
+// "EnforceAfter23August2016", name matching will only fall back to using the
+// subject common name for certificates where the notBefore field is before 23
+// August 2016.
+// Note that this implementation does not actually directly enforce that if the
+// subject common name is present, its value corresponds to a dNSName or
+// iPAddress entry in the subject alternative name extension.
+
+class BRNameMatchingPolicy : public mozilla::pkix::NameMatchingPolicy
+{
+public:
+  enum class Mode {
+    DoNotEnforce = 0,
+    EnforceAfter23August2016 = 1,
+    Enforce = 2,
+  };
+
+  explicit BRNameMatchingPolicy(Mode mode)
+    : mMode(mode)
+  {
+  }
+
+  virtual mozilla::pkix::Result FallBackToCommonName(
+    mozilla::pkix::Time notBefore,
+    /*out*/ mozilla::pkix::FallBackToSearchWithinSubject& fallBacktoCommonName)
+    override;
+
+private:
+  Mode mMode;
+};
+
+} } // namespace mozilla::psm
+
+#endif // BRNameMatchingPolicy_h
--- a/security/certverifier/CertVerifier.cpp
+++ b/security/certverifier/CertVerifier.cpp
@@ -3,16 +3,17 @@
 /* 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 "CertVerifier.h"
 
 #include <stdint.h>
 
+#include "BRNameMatchingPolicy.h"
 #include "ExtendedValidation.h"
 #include "NSSCertDBTrustDomain.h"
 #include "NSSErrorsService.h"
 #include "cert.h"
 #include "nsNSSComponent.h"
 #include "nsServiceManagerUtils.h"
 #include "pk11pub.h"
 #include "pkix/pkix.h"
@@ -33,23 +34,25 @@ const CertVerifier::Flags CertVerifier::
 const CertVerifier::Flags CertVerifier::FLAG_MUST_BE_EV = 2;
 const CertVerifier::Flags CertVerifier::FLAG_TLS_IGNORE_STATUS_REQUEST = 4;
 
 CertVerifier::CertVerifier(OcspDownloadConfig odc,
                            OcspStrictConfig osc,
                            OcspGetConfig ogc,
                            uint32_t certShortLifetimeInDays,
                            PinningMode pinningMode,
-                           SHA1Mode sha1Mode)
+                           SHA1Mode sha1Mode,
+                           BRNameMatchingPolicy::Mode nameMatchingMode)
   : mOCSPDownloadConfig(odc)
   , mOCSPStrict(osc == ocspStrict)
   , mOCSPGETEnabled(ogc == ocspGetEnabled)
   , mCertShortLifetimeInDays(certShortLifetimeInDays)
   , mPinningMode(pinningMode)
   , mSHA1Mode(sha1Mode)
+  , mNameMatchingMode(nameMatchingMode)
 {
 }
 
 CertVerifier::~CertVerifier()
 {
 }
 
 void
@@ -711,17 +714,26 @@ CertVerifier::VerifySSLServerCert(CERTCe
   }
 
   Input hostnameInput;
   result = hostnameInput.Init(uint8_t_ptr_cast(hostname), strlen(hostname));
   if (result != Success) {
     PR_SetError(SEC_ERROR_INVALID_ARGS, 0);
     return SECFailure;
   }
-  result = CheckCertHostname(peerCertInput, hostnameInput);
+  bool isBuiltInRoot;
+  result = IsCertChainRootBuiltInRoot(builtChain, isBuiltInRoot);
+  if (result != Success) {
+    PR_SetError(MapResultToPRErrorCode(result), 0);
+    return SECFailure;
+  }
+  BRNameMatchingPolicy nameMatchingPolicy(
+    isBuiltInRoot ? mNameMatchingMode
+                  : BRNameMatchingPolicy::Mode::DoNotEnforce);
+  result = CheckCertHostname(peerCertInput, hostnameInput, nameMatchingPolicy);
   if (result != Success) {
     // Treat malformed name information as a domain mismatch.
     if (result == Result::ERROR_BAD_DER) {
       PR_SetError(SSL_ERROR_BAD_CERT_DOMAIN, 0);
     } else {
       PR_SetError(MapResultToPRErrorCode(result), 0);
     }
     return SECFailure;
--- a/security/certverifier/CertVerifier.h
+++ b/security/certverifier/CertVerifier.h
@@ -2,20 +2,21 @@
 /* vim: set ts=8 sts=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/. */
 
 #ifndef mozilla_psm__CertVerifier_h
 #define mozilla_psm__CertVerifier_h
 
+#include "BRNameMatchingPolicy.h"
+#include "OCSPCache.h"
+#include "ScopedNSSTypes.h"
 #include "mozilla/Telemetry.h"
 #include "pkix/pkixtypes.h"
-#include "OCSPCache.h"
-#include "ScopedNSSTypes.h"
 
 namespace mozilla { namespace psm {
 
 // These values correspond to the CERT_CHAIN_KEY_SIZE_STATUS telemetry.
 enum class KeySizeStatus {
   NeverChecked = 0,
   LargeMinimumSucceeded = 1,
   CompatibilityRisk = 2,
@@ -116,27 +117,29 @@ public:
     ocspOn = 1,
     ocspEVOnly = 2
   };
   enum OcspStrictConfig { ocspRelaxed = 0, ocspStrict };
   enum OcspGetConfig { ocspGetDisabled = 0, ocspGetEnabled = 1 };
 
   CertVerifier(OcspDownloadConfig odc, OcspStrictConfig osc,
                OcspGetConfig ogc, uint32_t certShortLifetimeInDays,
-               PinningMode pinningMode, SHA1Mode sha1Mode);
+               PinningMode pinningMode, SHA1Mode sha1Mode,
+               BRNameMatchingPolicy::Mode nameMatchingMode);
   ~CertVerifier();
 
   void ClearOCSPCache() { mOCSPCache.Clear(); }
 
   const OcspDownloadConfig mOCSPDownloadConfig;
   const bool mOCSPStrict;
   const bool mOCSPGETEnabled;
   const uint32_t mCertShortLifetimeInDays;
   const PinningMode mPinningMode;
   const SHA1Mode mSHA1Mode;
+  const BRNameMatchingPolicy::Mode mNameMatchingMode;
 
 private:
   OCSPCache mOCSPCache;
 
   // Returns true if the configured SHA1 mode is more restrictive than the given
   // mode. SHA1Mode::Forbidden is more restrictive than any other mode except
   // Forbidden. Next is Before2016, then ImportedRoot, then Allowed.
   // (A mode is never more restrictive than itself.)
--- a/security/certverifier/moz.build
+++ b/security/certverifier/moz.build
@@ -1,20 +1,22 @@
 # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # 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/.
 
 EXPORTS += [
+    'BRNameMatchingPolicy.h',
     'CertVerifier.h',
     'OCSPCache.h',
 ]
 
 UNIFIED_SOURCES += [
+    'BRNameMatchingPolicy.cpp',
     'CertVerifier.cpp',
     'NSSCertDBTrustDomain.cpp',
     'OCSPCache.cpp',
     'OCSPRequestor.cpp',
     'OCSPVerificationTrustDomain.cpp',
 ]
 
 if not CONFIG['NSS_NO_EV_CERTS']:
--- a/security/manager/ssl/SSLServerCertVerification.cpp
+++ b/security/manager/ssl/SSLServerCertVerification.cpp
@@ -91,51 +91,50 @@
 // an SSL handshake) and the PSM NSS I/O layer are not thread-safe, and because
 // we need the event to interrupt the PR_Poll that may waiting for I/O on the
 // socket for which we are validating the cert.
 
 #include "SSLServerCertVerification.h"
 
 #include <cstring>
 
-#include "pkix/pkix.h"
-#include "pkix/pkixnss.h"
+#include "BRNameMatchingPolicy.h"
 #include "CertVerifier.h"
 #include "CryptoTask.h"
 #include "ExtendedValidation.h"
 #include "NSSCertDBTrustDomain.h"
-#include "nsIBadCertListener2.h"
-#include "nsICertOverrideService.h"
-#include "nsISiteSecurityService.h"
-#include "nsNSSComponent.h"
-#include "nsNSSIOLayer.h"
-#include "nsNSSShutDown.h"
-
+#include "PSMRunnable.h"
+#include "RootCertificateTelemetryUtils.h"
+#include "SharedSSLState.h"
+#include "cert.h"
 #include "mozilla/Assertions.h"
 #include "mozilla/Mutex.h"
 #include "mozilla/Telemetry.h"
+#include "mozilla/UniquePtr.h"
 #include "mozilla/net/DNS.h"
-#include "mozilla/UniquePtr.h"
 #include "mozilla/unused.h"
+#include "nsComponentManagerUtils.h"
+#include "nsContentUtils.h"
+#include "nsIBadCertListener2.h"
+#include "nsICertOverrideService.h"
+#include "nsISiteSecurityService.h"
+#include "nsISocketProvider.h"
 #include "nsIThreadPool.h"
-#include "nsISocketProvider.h"
-#include "nsXPCOMCIDInternal.h"
-#include "nsComponentManagerUtils.h"
+#include "nsNSSComponent.h"
+#include "nsNSSIOLayer.h"
+#include "nsNSSShutDown.h"
 #include "nsServiceManagerUtils.h"
-#include "PSMRunnable.h"
-#include "RootCertificateTelemetryUtils.h"
-#include "SharedSSLState.h"
-#include "nsContentUtils.h"
 #include "nsURLHelper.h"
-
-#include "ssl.h"
-#include "cert.h"
+#include "nsXPCOMCIDInternal.h"
+#include "pkix/pkix.h"
+#include "pkix/pkixnss.h"
 #include "secerr.h"
 #include "secoidt.h"
 #include "secport.h"
+#include "ssl.h"
 #include "sslerr.h"
 
 extern mozilla::LazyLogModule gPIPNSSLog;
 
 using namespace mozilla::pkix;
 
 namespace mozilla { namespace psm {
 
@@ -427,23 +426,38 @@ DetermineCertOverrideErrors(CERTCertific
     }
     Input hostnameInput;
     Result result = hostnameInput.Init(uint8_t_ptr_cast(hostName),
                                        strlen(hostName));
     if (result != Success) {
       PR_SetError(SEC_ERROR_INVALID_ARGS, 0);
       return SECFailure;
     }
-    result = CheckCertHostname(certInput, hostnameInput);
+    // Use a lax policy so as to not generate potentially spurious name
+    // mismatch "hints".
+    BRNameMatchingPolicy nameMatchingPolicy(
+      BRNameMatchingPolicy::Mode::DoNotEnforce);
+    // CheckCertHostname expects that its input represents a certificate that
+    // has already been successfully validated by BuildCertChain. This is
+    // obviously not the case, however, because we're in the error path of
+    // certificate verification. Thus, this is problematic. In the future, it
+    // would be nice to remove this optimistic additional error checking and
+    // simply punt to the front-end, which can more easily (and safely) perform
+    // extra checks to give the user hints as to why verification failed.
+    result = CheckCertHostname(certInput, hostnameInput, nameMatchingPolicy);
     // Treat malformed name information as a domain mismatch.
     if (result == Result::ERROR_BAD_DER ||
         result == Result::ERROR_BAD_CERT_DOMAIN) {
       collectedErrors |= nsICertOverrideService::ERROR_MISMATCH;
       errorCodeMismatch = SSL_ERROR_BAD_CERT_DOMAIN;
-    } else if (result != Success) {
+    } else if (IsFatalError(result)) {
+      // Because its input has not been validated by BuildCertChain,
+      // CheckCertHostname can return an error that is less important than the
+      // original certificate verification error. Only return an error result
+      // from this function if we've encountered a fatal error.
       PR_SetError(MapResultToPRErrorCode(result), 0);
       return SECFailure;
     }
   }
 
   return SECSuccess;
 }
 
--- a/security/manager/ssl/SharedCertVerifier.h
+++ b/security/manager/ssl/SharedCertVerifier.h
@@ -16,18 +16,19 @@ class SharedCertVerifier : public mozill
 protected:
   ~SharedCertVerifier();
 
 public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SharedCertVerifier)
 
   SharedCertVerifier(OcspDownloadConfig odc, OcspStrictConfig osc,
                      OcspGetConfig ogc, uint32_t certShortLifetimeInDays,
-                     PinningMode pinningMode, SHA1Mode sha1Mode)
+                     PinningMode pinningMode, SHA1Mode sha1Mode,
+                     BRNameMatchingPolicy::Mode nameMatchingMode)
     : mozilla::psm::CertVerifier(odc, osc, ogc, certShortLifetimeInDays,
-                                 pinningMode, sha1Mode)
+                                 pinningMode, sha1Mode, nameMatchingMode)
   {
   }
 };
 
 } } // namespace mozilla::psm
 
 #endif // mozilla_psm__SharedCertVerifier_h
--- a/security/manager/ssl/nsNSSComponent.cpp
+++ b/security/manager/ssl/nsNSSComponent.cpp
@@ -867,28 +867,44 @@ void nsNSSComponent::setValidationOption
   switch (sha1Mode) {
     case CertVerifier::SHA1Mode::Allowed:
     case CertVerifier::SHA1Mode::Forbidden:
     case CertVerifier::SHA1Mode::Before2016:
     case CertVerifier::SHA1Mode::ImportedRoot:
       break;
     default:
       sha1Mode = CertVerifier::SHA1Mode::Allowed;
+      break;
+  }
+
+  BRNameMatchingPolicy::Mode nameMatchingMode =
+    static_cast<BRNameMatchingPolicy::Mode>
+      (Preferences::GetInt("security.pki.name_matching_mode",
+                           static_cast<int32_t>(BRNameMatchingPolicy::Mode::DoNotEnforce)));
+  switch (nameMatchingMode) {
+    case BRNameMatchingPolicy::Mode::Enforce:
+    case BRNameMatchingPolicy::Mode::EnforceAfter23August2016:
+    case BRNameMatchingPolicy::Mode::DoNotEnforce:
+      break;
+    default:
+      nameMatchingMode = BRNameMatchingPolicy::Mode::DoNotEnforce;
+      break;
   }
 
   CertVerifier::OcspDownloadConfig odc;
   CertVerifier::OcspStrictConfig osc;
   CertVerifier::OcspGetConfig ogc;
   uint32_t certShortLifetimeInDays;
 
   GetRevocationBehaviorFromPrefs(&odc, &osc, &ogc, &certShortLifetimeInDays,
                                  lock);
   mDefaultCertVerifier = new SharedCertVerifier(odc, osc, ogc,
                                                 certShortLifetimeInDays,
-                                                pinningMode, sha1Mode);
+                                                pinningMode, sha1Mode,
+                                                nameMatchingMode);
 }
 
 // Enable the TLS versions given in the prefs, defaulting to TLS 1.0 (min) and
 // TLS 1.2 (max) when the prefs aren't set or set to invalid values.
 nsresult
 nsNSSComponent::setEnabledTLSVersions()
 {
   // keep these values in sync with security-prefs.js
@@ -1316,17 +1332,18 @@ nsNSSComponent::Observe(nsISupports* aSu
       ConfigureTLSSessionIdentifiers();
     } else if (prefName.EqualsLiteral("security.OCSP.enabled") ||
                prefName.EqualsLiteral("security.OCSP.require") ||
                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")) {
+               prefName.EqualsLiteral("security.pki.sha1_enforcement_level") ||
+               prefName.EqualsLiteral("security.pki.name_matching_mode")) {
       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 {
--- a/security/manager/ssl/tests/unit/moz.build
+++ b/security/manager/ssl/tests/unit/moz.build
@@ -7,16 +7,17 @@
 DIRS += ['tlsserver']
 
 if not CONFIG['MOZ_NO_SMART_CARDS']:
     DIRS += ['pkcs11testmodule']
 
 TEST_DIRS += [
     'bad_certs',
     'ocsp_certs',
+    'test_baseline_requirements',
     'test_cert_eku',
     'test_cert_embedded_null',
     'test_cert_keyUsage',
     'test_cert_sha1',
     'test_cert_signatures',
     'test_cert_trust',
     'test_cert_version',
     'test_certDB_import',
--- a/security/manager/ssl/tests/unit/pycert.py
+++ b/security/manager/ssl/tests/unit/pycert.py
@@ -24,17 +24,17 @@ subject:<subject distinguished name spec
 
 Known extensions are:
 basicConstraints:[cA],[pathLenConstraint]
 keyUsage:[digitalSignature,nonRepudiation,keyEncipherment,
           dataEncipherment,keyAgreement,keyCertSign,cRLSign]
 extKeyUsage:[serverAuth,clientAuth,codeSigning,emailProtection
              nsSGC, # Netscape Server Gated Crypto
              OCSPSigning,timeStamping]
-subjectAlternativeName:[<dNSName>,...]
+subjectAlternativeName:[<dNSName|directoryName>,...]
 authorityInformationAccess:<OCSP URI>
 certificatePolicies:<policy OID>
 nameConstraints:{permitted,excluded}:[<dNSName|directoryName>,...]
 nsCertType:sslServer
 TLSFeature:[<TLSFeature>,...]
 
 Where:
   [] indicates an optional field or component of a field
@@ -526,24 +526,29 @@ class Certificate(object):
         raise UnknownKeyPurposeTypeError(keyPurpose)
 
     def addExtKeyUsage(self, extKeyUsage, critical):
         extKeyUsageExtension = rfc2459.ExtKeyUsageSyntax()
         for count, keyPurpose in enumerate(extKeyUsage.split(',')):
             extKeyUsageExtension.setComponentByPosition(count, self.keyPurposeToOID(keyPurpose))
         self.addExtension(rfc2459.id_ce_extKeyUsage, extKeyUsageExtension, critical)
 
-    def addSubjectAlternativeName(self, dNSNames, critical):
+    def addSubjectAlternativeName(self, names, critical):
         subjectAlternativeName = rfc2459.SubjectAltName()
-        for count, dNSName in enumerate(dNSNames.split(',')):
+        for count, name in enumerate(names.split(',')):
             generalName = rfc2459.GeneralName()
-            # The string may have things like '\0' (i.e. a slash
-            # followed by the number zero) that have to be decoded into
-            # the resulting '\x00' (i.e. a byte with value zero).
-            generalName.setComponentByName('dNSName', dNSName.decode(encoding='string_escape'))
+            if '/' in name:
+                directoryName = stringToDN(name,
+                                           tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 4))
+                generalName.setComponentByName('directoryName', directoryName)
+            else:
+                # The string may have things like '\0' (i.e. a slash
+                # followed by the number zero) that have to be decoded into
+                # the resulting '\x00' (i.e. a byte with value zero).
+                generalName.setComponentByName('dNSName', name.decode(encoding='string_escape'))
             subjectAlternativeName.setComponentByPosition(count, generalName)
         self.addExtension(rfc2459.id_ce_subjectAltName, subjectAlternativeName, critical)
 
     def addAuthorityInformationAccess(self, ocspURI, critical):
         sequence = univ.Sequence()
         accessDescription = stringToAccessDescription(ocspURI)
         sequence.setComponentByPosition(0, accessDescription)
         self.addExtension(rfc2459.id_pe_authorityInfoAccess, sequence, critical)
new file mode 100644
--- /dev/null
+++ b/security/manager/ssl/tests/unit/test_baseline_requirements/ca.pem
@@ -0,0 +1,17 @@
+-----BEGIN CERTIFICATE-----
+MIICxTCCAa+gAwIBAgIUQy+m6w0ZtMTfbmtELQQz8zwqCAowCwYJKoZIhvcNAQEL
+MA0xCzAJBgNVBAMMAmNhMCIYDzIwMTQxMTI3MDAwMDAwWhgPMjAxNzAyMDQwMDAw
+MDBaMA0xCzAJBgNVBAMMAmNhMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC
+AQEAuohRqESOFtZB/W62iAY2ED08E9nq5DVKtOz1aFdsJHvBxyWo4NgfvbGcBptu
+Gobya+KvWnVramRxCHqlWqdFh/cc1SScAn7NQ/weadA4ICmTqyDDSeTbuUzCa2wO
+7RWCD/F+rWkasdMCOosqQe6ncOAPDY39ZgsrsCSSpH25iGF5kLFXkD3SO8XguEgf
+qDfTiEPvJxbYVbdmWqp+ApAvOnsQgAYkzBxsl62WYVu34pYSwHUxowyR3bTK9/yt
+HSXTCe+5Fw6naOGzey8ib2njtIqVYR3uJtYlnauRCE42yxwkBCy/Fosv5fGPmRcx
+uLP+SSP6clHEMdUDrNoYCjXtjQIDAQABox0wGzAMBgNVHRMEBTADAQH/MAsGA1Ud
+DwQEAwIBBjALBgkqhkiG9w0BAQsDggEBAJQcekrdR+S6U0I3owUQxVOoUJMzHdTj
+u562Ra7cOiJQwe1OQZbvo6rQkQWPrpuDOGpwwr1+HBMGb8mjUqeFo5wIinU003TC
+UYYEpDCbPwXOKDkDUukKd1aO4wpJc/v8YIiCz7aCRj9HQ3L5YO5JsgMNSCXKKoUm
+ILcz2V+IQZ6lePzFfd2aO3zLMDPwEOyujYYtQnBVZIT4F/x/6nU8E6bkbDSGPjQW
+CSVhwa0YQ9lCRSM6e//wGry4i8X8718t1V+Nqh7y6u7UlOrXbNEA4pR6mvJsqPhF
+Mj82We4OGNBxXbyuGJObQgLBfmRuwKQT9SNtKWEifiaTw8apT/fBagc=
+-----END CERTIFICATE-----
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/security/manager/ssl/tests/unit/test_baseline_requirements/ca.pem.certspec
@@ -0,0 +1,4 @@
+issuer:ca
+subject:ca
+extension:basicConstraints:cA,
+extension:keyUsage:cRLSign,keyCertSign
new file mode 100644
--- /dev/null
+++ b/security/manager/ssl/tests/unit/test_baseline_requirements/moz.build
@@ -0,0 +1,17 @@
+# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+# Temporarily disabled. See bug 1256495.
+#test_certificates = (
+#    'ca.pem',
+#    'no-san-old.pem',
+#    'no-san-recent.pem',
+#    'san-contains-no-hostnames-old.pem',
+#    'san-contains-no-hostnames-recent.pem',
+#)
+#
+#for test_certificate in test_certificates:
+#    GeneratedTestCertificate(test_certificate)
new file mode 100644
--- /dev/null
+++ b/security/manager/ssl/tests/unit/test_baseline_requirements/no-san-old.pem
@@ -0,0 +1,17 @@
+-----BEGIN CERTIFICATE-----
+MIICrzCCAZmgAwIBAgIUS00fexo4Y4FagP1oiKQiGCJKd/swCwYJKoZIhvcNAQEL
+MA0xCzAJBgNVBAMMAmNhMCIYDzIwMTYwNzI0MDAwMDAwWhgPMjAxNjA5MjQwMDAw
+MDBaMBYxFDASBgNVBAMMC2V4YW1wbGUuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOC
+AQ8AMIIBCgKCAQEAuohRqESOFtZB/W62iAY2ED08E9nq5DVKtOz1aFdsJHvBxyWo
+4NgfvbGcBptuGobya+KvWnVramRxCHqlWqdFh/cc1SScAn7NQ/weadA4ICmTqyDD
+SeTbuUzCa2wO7RWCD/F+rWkasdMCOosqQe6ncOAPDY39ZgsrsCSSpH25iGF5kLFX
+kD3SO8XguEgfqDfTiEPvJxbYVbdmWqp+ApAvOnsQgAYkzBxsl62WYVu34pYSwHUx
+owyR3bTK9/ytHSXTCe+5Fw6naOGzey8ib2njtIqVYR3uJtYlnauRCE42yxwkBCy/
+Fosv5fGPmRcxuLP+SSP6clHEMdUDrNoYCjXtjQIDAQABMAsGCSqGSIb3DQEBCwOC
+AQEAnooFCIG4D5EQxpe6EcB3gXE2Cj5kMBvO5H+OobaVRd4kIxET+MChwsN2nuwy
+xooA/P2M+9W/S3KP/K8L1tlyf/8J2xlr349MhULhCKG7XxpEQZl6sVjaeDLwWrEK
+2Ts1jPgCTufdfPYBcO3rb4HSdHNfFlhB98dfgSakXqXzUZ9sz2VxmAeL6tLvspG9
+tH5viWFc+lv1J3/Jtzk79hleDsiIdcjTjC/bM/Y49jkNOBxru4Qrw5AZuveoVy/G
+2axz89wBpjLYjI0KtVxCf8dutcVW7UwFKQywmo7QDqha61f0f8I6oCYXsDPK9FPf
+WLIrK1TT3YxdhN7QIRu6J8ttBQ==
+-----END CERTIFICATE-----
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/security/manager/ssl/tests/unit/test_baseline_requirements/no-san-old.pem.certspec
@@ -0,0 +1,3 @@
+issuer:ca
+subject:example.com
+validity:20160724-20160924
new file mode 100644
--- /dev/null
+++ b/security/manager/ssl/tests/unit/test_baseline_requirements/no-san-recent.pem
@@ -0,0 +1,17 @@
+-----BEGIN CERTIFICATE-----
+MIICrzCCAZmgAwIBAgIUWxGwhSb8roUQoLNpJajl0X8jk10wCwYJKoZIhvcNAQEL
+MA0xCzAJBgNVBAMMAmNhMCIYDzIwMTYwODI0MDAwMDAwWhgPMjAxNjA5MjQwMDAw
+MDBaMBYxFDASBgNVBAMMC2V4YW1wbGUuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOC
+AQ8AMIIBCgKCAQEAuohRqESOFtZB/W62iAY2ED08E9nq5DVKtOz1aFdsJHvBxyWo
+4NgfvbGcBptuGobya+KvWnVramRxCHqlWqdFh/cc1SScAn7NQ/weadA4ICmTqyDD
+SeTbuUzCa2wO7RWCD/F+rWkasdMCOosqQe6ncOAPDY39ZgsrsCSSpH25iGF5kLFX
+kD3SO8XguEgfqDfTiEPvJxbYVbdmWqp+ApAvOnsQgAYkzBxsl62WYVu34pYSwHUx
+owyR3bTK9/ytHSXTCe+5Fw6naOGzey8ib2njtIqVYR3uJtYlnauRCE42yxwkBCy/
+Fosv5fGPmRcxuLP+SSP6clHEMdUDrNoYCjXtjQIDAQABMAsGCSqGSIb3DQEBCwOC
+AQEADs0POeb1wJthEcg1nRYRcPgcNHsV1yVCkHMyfmssA1rgWXsp93PRVaCyZYgI
+Dq+8QJtAYpdcChFcEYeGjcjLv49Dh+yiiZPPbRWKJB0y3v+13A74M1q+IQFoQWAh
+L5GzAEjApsB1j/csRfDNjIwcP1WApN1iZ2NPxFU+PAIFhcmm+uD2doDtQfpMN9vi
+HEg5H1x7JOufOZlN+zbnPK+Ob7N13pFd/P/IO8XhA/X8by4G45oh0deyELf9zVcW
+4flslHPYthp4LDEyPvTP52jHn/yTO/m8cxKmCZOuwiw4DWfSVUy6irUs8W5SlfS8
+gI2Ctb+G5YvSffsPYwkUg3Hg7g==
+-----END CERTIFICATE-----
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/security/manager/ssl/tests/unit/test_baseline_requirements/no-san-recent.pem.certspec
@@ -0,0 +1,3 @@
+issuer:ca
+subject:example.com
+validity:20160824-20160924
new file mode 100644
--- /dev/null
+++ b/security/manager/ssl/tests/unit/test_baseline_requirements/san-contains-no-hostnames-old.pem
@@ -0,0 +1,18 @@
+-----BEGIN CERTIFICATE-----
+MIIC4TCCAcugAwIBAgIUL/Gibj3iILbh9idTh3u9XTSwzqIwCwYJKoZIhvcNAQEL
+MA0xCzAJBgNVBAMMAmNhMCIYDzIwMTYwNzI0MDAwMDAwWhgPMjAxNjA5MjQwMDAw
+MDBaMBYxFDASBgNVBAMMC2V4YW1wbGUuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOC
+AQ8AMIIBCgKCAQEAuohRqESOFtZB/W62iAY2ED08E9nq5DVKtOz1aFdsJHvBxyWo
+4NgfvbGcBptuGobya+KvWnVramRxCHqlWqdFh/cc1SScAn7NQ/weadA4ICmTqyDD
+SeTbuUzCa2wO7RWCD/F+rWkasdMCOosqQe6ncOAPDY39ZgsrsCSSpH25iGF5kLFX
+kD3SO8XguEgfqDfTiEPvJxbYVbdmWqp+ApAvOnsQgAYkzBxsl62WYVu34pYSwHUx
+owyR3bTK9/ytHSXTCe+5Fw6naOGzey8ib2njtIqVYR3uJtYlnauRCE42yxwkBCy/
+Fosv5fGPmRcxuLP+SSP6clHEMdUDrNoYCjXtjQIDAQABozAwLjAsBgNVHREEJTAj
+pCEwHzEdMBsGA1UECgwURXhhbXBsZSBPcmdhbml6YXRpb24wCwYJKoZIhvcNAQEL
+A4IBAQAtCjzHSdXdAyaC5Qyw77gFwQKECHDAimgze1Nvgkyiv4LJLSuFZ84jnLIL
+PM+iRxrxeBOdNy8PNIaDadFb5NoovmdLTG08ZjNjJoXOt5JufIHQrUzrcZy1aP7z
+rWXED1QcwyKkoOAOqr5hOZ3pmu67a1vJgjZ8H4dVhfFkmSVGPG/6mTvn9H4N/AEo
+K+M7BW1WMnNexsV5mMvlUUdfZP0J3o9oI9msH6uH92xU6jIHpgcm6plXfpOBGQfB
+g6EUGD4znDe24ljbaohFATWw5c09kkOQNg/H6DQpb1Vi6k+X62Zgj5UR79zx53e+
+3Wo3Iu+hJUfNwyNk7KVF+r1wsUdA
+-----END CERTIFICATE-----
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/security/manager/ssl/tests/unit/test_baseline_requirements/san-contains-no-hostnames-old.pem.certspec
@@ -0,0 +1,4 @@
+issuer:ca
+subject:example.com
+validity:20160724-20160924
+extension:subjectAlternativeName:/O=Example Organization
new file mode 100644
--- /dev/null
+++ b/security/manager/ssl/tests/unit/test_baseline_requirements/san-contains-no-hostnames-recent.pem
@@ -0,0 +1,18 @@
+-----BEGIN CERTIFICATE-----
+MIIC4TCCAcugAwIBAgIUUwz+d++GVy8L6Lxi9GVnzG6yUCEwCwYJKoZIhvcNAQEL
+MA0xCzAJBgNVBAMMAmNhMCIYDzIwMTYwODI0MDAwMDAwWhgPMjAxNjA5MjQwMDAw
+MDBaMBYxFDASBgNVBAMMC2V4YW1wbGUuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOC
+AQ8AMIIBCgKCAQEAuohRqESOFtZB/W62iAY2ED08E9nq5DVKtOz1aFdsJHvBxyWo
+4NgfvbGcBptuGobya+KvWnVramRxCHqlWqdFh/cc1SScAn7NQ/weadA4ICmTqyDD
+SeTbuUzCa2wO7RWCD/F+rWkasdMCOosqQe6ncOAPDY39ZgsrsCSSpH25iGF5kLFX
+kD3SO8XguEgfqDfTiEPvJxbYVbdmWqp+ApAvOnsQgAYkzBxsl62WYVu34pYSwHUx
+owyR3bTK9/ytHSXTCe+5Fw6naOGzey8ib2njtIqVYR3uJtYlnauRCE42yxwkBCy/
+Fosv5fGPmRcxuLP+SSP6clHEMdUDrNoYCjXtjQIDAQABozAwLjAsBgNVHREEJTAj
+pCEwHzEdMBsGA1UECgwURXhhbXBsZSBPcmdhbml6YXRpb24wCwYJKoZIhvcNAQEL
+A4IBAQCdKXA+1XhcpKdyjeJjEEUm9ptoS8tKJt/vdGUPCByzlD71OJGsiXorTcGY
+V2sgbGCmxA8dnxG8bPQMFdAZ2hRjWjZ/Hs18SDbMAONjzgiPlwUWRZldb2Th7WX3
+7a+1uMsT1rEsgmip7FuJjqW0qEyuHFRTt47aK0GJRX42VC5kJVMX8ujl8ucqSSNa
+PRh6IPQgIxSchu79weP+YIxMz3GDvNuu6z4QWdkzQrnYqSJpLgNGPAdAxFCgsok3
+5rFNhadGNv6RqrG00HmXPBTTq6T8rQEsbQZQZ4eensb0HxdiQyX4XGnMeEpElTwO
+LHziGIW2jBA9v5FJVBCC9QQysNby
+-----END CERTIFICATE-----
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/security/manager/ssl/tests/unit/test_baseline_requirements/san-contains-no-hostnames-recent.pem.certspec
@@ -0,0 +1,4 @@
+issuer:ca
+subject:example.com
+validity:20160824-20160924
+extension:subjectAlternativeName:/O=Example Organization
new file mode 100644
--- /dev/null
+++ b/security/manager/ssl/tests/unit/test_baseline_requirements_subject_common_name.js
@@ -0,0 +1,131 @@
+// -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
+// 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/.
+
+// The preference security.pki.name_matching_mode controls whether or not
+// mozilla::pkix will fall back to using a certificate's subject common name
+// during name matching. If the Baseline Requirements are followed, fallback
+// should not be necessary (because any name information in the subject common
+// name should be present in the subject alternative name extension). Due to
+// compatibility concerns, the platform can be configured to fall back for
+// certificates that are valid before 23 August 2016. Note that for certificates
+// issued by an imported root, the platform will fall back if necessary,
+// regardless of the value of the preference.
+
+"use strict";
+
+do_get_profile(); // must be called before getting nsIX509CertDB
+const gCertDB = Cc["@mozilla.org/security/x509certdb;1"]
+                  .getService(Ci.nsIX509CertDB);
+
+function certFromFile(certName) {
+  return constructCertFromFile(`test_baseline_requirements/${certName}.pem`);
+}
+
+function loadCertWithTrust(certName, trustString) {
+  addCertFromFile(gCertDB, `test_baseline_requirements/${certName}.pem`,
+                  trustString);
+}
+
+function checkCertOn25August2016(cert, expectedResult) {
+  // (new Date("2016-08-25T00:00:00Z")).getTime() / 1000
+  const VALIDATION_TIME = 1472083200;
+  checkCertErrorGenericAtTime(gCertDB, cert, expectedResult,
+                              certificateUsageSSLServer, VALIDATION_TIME, {},
+                              "example.com");
+}
+
+function run_test() {
+  do_register_cleanup(() => {
+    Services.prefs.clearUserPref("security.pki.name_matching_mode");
+    Services.prefs.clearUserPref("security.test.built_in_root_hash");
+  });
+
+  loadCertWithTrust("ca", "CTu,,");
+
+  // When verifying a certificate, if the trust anchor is not a built-in root,
+  // name matching will fall back to using the subject common name if necessary
+  // (i.e. if there is no subject alternative name extension or it does not
+  // contain any dNSName or iPAddress entries). Thus, since imported roots are
+  // not in general treated as built-ins, these should all successfully verify
+  // regardless of the value of the pref.
+  Services.prefs.setIntPref("security.pki.name_matching_mode", 0);
+  do_print("current mode: always fall back, root not built-in");
+  checkCertOn25August2016(certFromFile("no-san-recent"),
+                          PRErrorCodeSuccess);
+  checkCertOn25August2016(certFromFile("no-san-old"),
+                          PRErrorCodeSuccess);
+  checkCertOn25August2016(certFromFile("san-contains-no-hostnames-recent"),
+                          PRErrorCodeSuccess);
+  checkCertOn25August2016(certFromFile("san-contains-no-hostnames-old"),
+                          PRErrorCodeSuccess);
+
+  Services.prefs.setIntPref("security.pki.name_matching_mode", 1);
+  do_print("current mode: fall back for notBefore < August 23, 2016, root " +
+           "not built-in");
+  checkCertOn25August2016(certFromFile("no-san-recent"),
+                          PRErrorCodeSuccess);
+  checkCertOn25August2016(certFromFile("no-san-old"),
+                          PRErrorCodeSuccess);
+  checkCertOn25August2016(certFromFile("san-contains-no-hostnames-recent"),
+                          PRErrorCodeSuccess);
+  checkCertOn25August2016(certFromFile("san-contains-no-hostnames-old"),
+                          PRErrorCodeSuccess);
+
+  Services.prefs.setIntPref("security.pki.name_matching_mode", 2);
+  do_print("current mode: never fall back, root not built-in");
+  checkCertOn25August2016(certFromFile("no-san-recent"),
+                          PRErrorCodeSuccess);
+  checkCertOn25August2016(certFromFile("no-san-old"),
+                          PRErrorCodeSuccess);
+  checkCertOn25August2016(certFromFile("san-contains-no-hostnames-recent"),
+                          PRErrorCodeSuccess);
+  checkCertOn25August2016(certFromFile("san-contains-no-hostnames-old"),
+                          PRErrorCodeSuccess);
+
+  // In debug builds, we can treat an imported root as a built-in, and thus we
+  // can actually test the different values of the pref.
+  if (isDebugBuild) {
+    let root = certFromFile("ca");
+    Services.prefs.setCharPref("security.test.built_in_root_hash",
+                               root.sha256Fingerprint);
+
+    // Always fall back if necessary.
+    Services.prefs.setIntPref("security.pki.name_matching_mode", 0);
+    do_print("current mode: always fall back, root built-in");
+    checkCertOn25August2016(certFromFile("no-san-recent"),
+                            PRErrorCodeSuccess);
+    checkCertOn25August2016(certFromFile("no-san-old"),
+                            PRErrorCodeSuccess);
+    checkCertOn25August2016(certFromFile("san-contains-no-hostnames-recent"),
+                            PRErrorCodeSuccess);
+    checkCertOn25August2016(certFromFile("san-contains-no-hostnames-old"),
+                            PRErrorCodeSuccess);
+
+    // Only fall back if notBefore < 23 August 2016
+    Services.prefs.setIntPref("security.pki.name_matching_mode", 1);
+    do_print("current mode: fall back for notBefore < August 23, 2016, root " +
+             "built-in");
+    checkCertOn25August2016(certFromFile("no-san-recent"),
+                            SSL_ERROR_BAD_CERT_DOMAIN);
+    checkCertOn25August2016(certFromFile("no-san-old"),
+                            PRErrorCodeSuccess);
+    checkCertOn25August2016(certFromFile("san-contains-no-hostnames-recent"),
+                            SSL_ERROR_BAD_CERT_DOMAIN);
+    checkCertOn25August2016(certFromFile("san-contains-no-hostnames-old"),
+                            PRErrorCodeSuccess);
+
+    // Never fall back.
+    Services.prefs.setIntPref("security.pki.name_matching_mode", 2);
+    do_print("current mode: never fall back, root built-in");
+    checkCertOn25August2016(certFromFile("no-san-recent"),
+                            SSL_ERROR_BAD_CERT_DOMAIN);
+    checkCertOn25August2016(certFromFile("no-san-old"),
+                            SSL_ERROR_BAD_CERT_DOMAIN);
+    checkCertOn25August2016(certFromFile("san-contains-no-hostnames-recent"),
+                            SSL_ERROR_BAD_CERT_DOMAIN);
+    checkCertOn25August2016(certFromFile("san-contains-no-hostnames-old"),
+                            SSL_ERROR_BAD_CERT_DOMAIN);
+  }
+}
--- a/security/manager/ssl/tests/unit/xpcshell.ini
+++ b/security/manager/ssl/tests/unit/xpcshell.ini
@@ -1,15 +1,16 @@
 [DEFAULT]
 head = head_psm.js
 tail =
 tags = psm
 support-files =
   bad_certs/**
   ocsp_certs/**
+  test_baseline_requirements/**
   test_cert_eku/**
   test_cert_embedded_null/**
   test_cert_keyUsage/**
   test_cert_sha1/**
   test_cert_signatures/**
   test_cert_trust/**
   test_cert_version/**
   test_certDB_import/**
@@ -24,16 +25,17 @@ support-files =
   test_ocsp_url/**
   test_onecrl/**
   test_pinning_dynamic/**
   test_signed_apps/**
   test_validity/**
   tlsserver/**
 
 [test_add_preexisting_cert.js]
+[test_baseline_requirements_subject_common_name.js]
 [test_cert_blocklist.js]
 skip-if = buildapp == "b2g"
 tags = addons psm
 [test_cert_chains.js]
 run-sequentially = hardcoded ports
 [test_cert_dbKey.js]
 [test_cert_eku.js]
 [test_cert_embedded_null.js]
--- a/security/pkix/include/pkix/pkix.h
+++ b/security/pkix/include/pkix/pkix.h
@@ -111,17 +111,18 @@ Result BuildCertChain(TrustDomain& trust
                       /*optional*/ const Input* stapledOCSPResponse);
 
 // Verify that the given end-entity cert, which is assumed to have been already
 // validated with BuildCertChain, is valid for the given hostname. The matching
 // function attempts to implement RFC 6125 with a couple of differences:
 // - IP addresses are out of scope of RFC 6125, but this method accepts them for
 //   backward compatibility (see SearchNames in pkixnames.cpp)
 // - A wildcard in a DNS-ID may only appear as the entirety of the first label.
-Result CheckCertHostname(Input cert, Input hostname);
+Result CheckCertHostname(Input cert, Input hostname,
+                         NameMatchingPolicy& nameMatchingPolicy);
 
 // Construct an RFC-6960-encoded OCSP request, ready for submission to a
 // responder, for the provided CertID. The request has no extensions.
 static const size_t OCSP_REQUEST_MAX_LENGTH = 127;
 Result CreateEncodedOCSPRequest(TrustDomain& trustDomain,
                                 const CertID& certID,
                                 /*out*/ uint8_t (&out)[OCSP_REQUEST_MAX_LENGTH],
                                 /*out*/ size_t& outLen);
--- a/security/pkix/include/pkix/pkixtypes.h
+++ b/security/pkix/include/pkix/pkixtypes.h
@@ -344,11 +344,35 @@ public:
                            size_t digestBufLen) = 0;
 protected:
   TrustDomain() { }
 
   TrustDomain(const TrustDomain&) = delete;
   void operator=(const TrustDomain&) = delete;
 };
 
+enum class FallBackToSearchWithinSubject { No = 0, Yes = 1 };
+
+// Applications control the behavior of matching presented name information from
+// a certificate against a reference hostname by implementing the
+// NameMatchingPolicy interface. Used in concert with CheckCertHostname.
+class NameMatchingPolicy
+{
+public:
+  virtual ~NameMatchingPolicy() { }
+
+  // Given that the certificate in question has a notBefore field with the given
+  // value, should name matching fall back to searching within the subject
+  // common name field?
+  virtual Result FallBackToCommonName(
+    Time notBefore,
+    /*out*/ FallBackToSearchWithinSubject& fallBackToCommonName) = 0;
+
+protected:
+  NameMatchingPolicy() { }
+
+  NameMatchingPolicy(const NameMatchingPolicy&) = delete;
+  void operator=(const NameMatchingPolicy&) = delete;
+};
+
 } } // namespace mozilla::pkix
 
 #endif // mozilla_pkix_pkixtypes_h
--- a/security/pkix/lib/pkixnames.cpp
+++ b/security/pkix/lib/pkixnames.cpp
@@ -109,18 +109,16 @@ ReadGeneralName(Reader& reader,
       generalNameType = GeneralNameType::registeredID;
       break;
     default:
       return Result::ERROR_BAD_DER;
   }
   return Success;
 }
 
-enum class FallBackToSearchWithinSubject { No = 0, Yes = 1 };
-
 enum class MatchResult
 {
   NoNamesOfGivenType = 0,
   Mismatch = 1,
   Match = 2
 };
 
 Result SearchNames(const Input* subjectAltName, Input subject,
@@ -214,48 +212,61 @@ MatchPresentedDNSIDWithReferenceDNSID(In
            referenceDNSID, matches);
 }
 
 // Verify that the given end-entity cert, which is assumed to have been already
 // validated with BuildCertChain, is valid for the given hostname. hostname is
 // assumed to be a string representation of an IPv4 address, an IPv6 addresss,
 // or a normalized ASCII (possibly punycode) DNS name.
 Result
-CheckCertHostname(Input endEntityCertDER, Input hostname)
+CheckCertHostname(Input endEntityCertDER, Input hostname,
+                  NameMatchingPolicy& nameMatchingPolicy)
 {
   BackCert cert(endEntityCertDER, EndEntityOrCA::MustBeEndEntity, nullptr);
   Result rv = cert.Init();
   if (rv != Success) {
     return rv;
   }
 
+  Time notBefore(Time::uninitialized);
+  rv = ParseValidity(cert.GetValidity(), &notBefore);
+  if (rv != Success) {
+    return rv;
+  }
+  FallBackToSearchWithinSubject fallBackToSearchWithinSubject;
+  rv = nameMatchingPolicy.FallBackToCommonName(notBefore,
+                                               fallBackToSearchWithinSubject);
+  if (rv != Success) {
+    return rv;
+  }
+
   const Input* subjectAltName(cert.GetSubjectAltName());
   Input subject(cert.GetSubject());
 
-  // For backward compatibility with legacy certificates, we fall back to
+  // For backward compatibility with legacy certificates, we may fall back to
   // searching for a name match in the subject common name for DNS names and
   // IPv4 addresses. We don't do so for IPv6 addresses because we do not think
   // there are many certificates that would need such fallback, and because
   // comparisons of string representations of IPv6 addresses are particularly
   // error prone due to the syntactic flexibility that IPv6 addresses have.
   //
   // IPv4 and IPv6 addresses are represented using the same type of GeneralName
   // (iPAddress); they are differentiated by the lengths of the values.
   MatchResult match;
   uint8_t ipv6[16];
   uint8_t ipv4[4];
   if (IsValidReferenceDNSID(hostname)) {
     rv = SearchNames(subjectAltName, subject, GeneralNameType::dNSName,
-                     hostname, FallBackToSearchWithinSubject::Yes, match);
+                     hostname, fallBackToSearchWithinSubject, match);
   } else if (ParseIPv6Address(hostname, ipv6)) {
     rv = SearchNames(subjectAltName, subject, GeneralNameType::iPAddress,
                      Input(ipv6), FallBackToSearchWithinSubject::No, match);
   } else if (ParseIPv4Address(hostname, ipv4)) {
     rv = SearchNames(subjectAltName, subject, GeneralNameType::iPAddress,
-                     Input(ipv4), FallBackToSearchWithinSubject::Yes, match);
+                     Input(ipv4), fallBackToSearchWithinSubject, match);
   } else {
     return Result::ERROR_BAD_CERT_DOMAIN;
   }
   if (rv != Success) {
     return rv;
   }
   switch (match) {
     case MatchResult::NoNamesOfGivenType: // fall through
--- a/security/pkix/test/gtest/pkixgtest.h
+++ b/security/pkix/test/gtest/pkixgtest.h
@@ -212,11 +212,22 @@ class DefaultCryptoTrustDomain : public 
 
   Result CheckValidityIsAcceptable(Time, Time, EndEntityOrCA, KeyPurposeId)
                                    override
   {
     return Success;
   }
 };
 
+class DefaultNameMatchingPolicy : public NameMatchingPolicy
+{
+public:
+  virtual Result FallBackToCommonName(
+    Time, /*out*/ FallBackToSearchWithinSubject& fallBackToCommonName) override
+  {
+    fallBackToCommonName = FallBackToSearchWithinSubject::Yes;
+    return Success;
+  }
+};
+
 } } } // namespace mozilla::pkix::test
 
 #endif // mozilla_pkix_pkixgtest_h
--- a/security/pkix/test/gtest/pkixnames_tests.cpp
+++ b/security/pkix/test/gtest/pkixnames_tests.cpp
@@ -897,16 +897,18 @@ static const IPAddressParams<16> IPV6_AD
   IPV6_INVALID("::1.2.3.4\0"),
   IPV6_INVALID("::1.2\02.3.4"),
 };
 
 class pkixnames_MatchPresentedDNSIDWithReferenceDNSID
   : public ::testing::Test
   , public ::testing::WithParamInterface<PresentedMatchesReference>
 {
+public:
+  DefaultNameMatchingPolicy mNameMatchingPolicy;
 };
 
 TEST_P(pkixnames_MatchPresentedDNSIDWithReferenceDNSID,
        MatchPresentedDNSIDWithReferenceDNSID)
 {
   const PresentedMatchesReference& param(GetParam());
   SCOPED_TRACE(param.presentedDNSID.c_str());
   SCOPED_TRACE(param.referenceDNSID.c_str());
@@ -932,16 +934,18 @@ TEST_P(pkixnames_MatchPresentedDNSIDWith
 INSTANTIATE_TEST_CASE_P(pkixnames_MatchPresentedDNSIDWithReferenceDNSID,
                         pkixnames_MatchPresentedDNSIDWithReferenceDNSID,
                         testing::ValuesIn(DNSID_MATCH_PARAMS));
 
 class pkixnames_Turkish_I_Comparison
   : public ::testing::Test
   , public ::testing::WithParamInterface<InputValidity>
 {
+public:
+  DefaultNameMatchingPolicy mNameMatchingPolicy;
 };
 
 TEST_P(pkixnames_Turkish_I_Comparison, MatchPresentedDNSIDWithReferenceDNSID)
 {
   // Make sure we don't have the similar problems that strcasecmp and others
   // have with the other kinds of "i" and "I" commonly used in Turkish locales.
 
   const InputValidity& inputValidity(GetParam());
@@ -977,16 +981,18 @@ TEST_P(pkixnames_Turkish_I_Comparison, M
 INSTANTIATE_TEST_CASE_P(pkixnames_Turkish_I_Comparison,
                         pkixnames_Turkish_I_Comparison,
                         testing::ValuesIn(DNSNAMES_VALIDITY_TURKISH_I));
 
 class pkixnames_IsValidReferenceDNSID
   : public ::testing::Test
   , public ::testing::WithParamInterface<InputValidity>
 {
+public:
+  DefaultNameMatchingPolicy mNameMatchingPolicy;
 };
 
 TEST_P(pkixnames_IsValidReferenceDNSID, IsValidReferenceDNSID)
 {
   const InputValidity& inputValidity(GetParam());
   SCOPED_TRACE(inputValidity.input.c_str());
   Input input;
   ASSERT_EQ(Success, input.Init(inputValidity.input.data(),
@@ -1001,16 +1007,18 @@ INSTANTIATE_TEST_CASE_P(pkixnames_IsVali
 INSTANTIATE_TEST_CASE_P(pkixnames_IsValidReferenceDNSID_Turkish_I,
                         pkixnames_IsValidReferenceDNSID,
                         testing::ValuesIn(DNSNAMES_VALIDITY_TURKISH_I));
 
 class pkixnames_ParseIPv4Address
   : public ::testing::Test
   , public ::testing::WithParamInterface<IPAddressParams<4>>
 {
+public:
+  DefaultNameMatchingPolicy mNameMatchingPolicy;
 };
 
 TEST_P(pkixnames_ParseIPv4Address, ParseIPv4Address)
 {
   const IPAddressParams<4>& param(GetParam());
   SCOPED_TRACE(param.input.c_str());
   Input input;
   ASSERT_EQ(Success, input.Init(param.input.data(),
@@ -1027,16 +1035,18 @@ TEST_P(pkixnames_ParseIPv4Address, Parse
 INSTANTIATE_TEST_CASE_P(pkixnames_ParseIPv4Address,
                         pkixnames_ParseIPv4Address,
                         testing::ValuesIn(IPV4_ADDRESSES));
 
 class pkixnames_ParseIPv6Address
   : public ::testing::Test
   , public ::testing::WithParamInterface<IPAddressParams<16>>
 {
+public:
+  DefaultNameMatchingPolicy mNameMatchingPolicy;
 };
 
 TEST_P(pkixnames_ParseIPv6Address, ParseIPv6Address)
 {
   const IPAddressParams<16>& param(GetParam());
   SCOPED_TRACE(param.input.c_str());
   Input input;
   ASSERT_EQ(Success, input.Init(param.input.data(),
@@ -1069,16 +1079,18 @@ struct CheckCertHostnameParams
   ByteString subjectAltName;
   Result result;
 };
 
 class pkixnames_CheckCertHostname
   : public ::testing::Test
   , public ::testing::WithParamInterface<CheckCertHostnameParams>
 {
+public:
+  DefaultNameMatchingPolicy mNameMatchingPolicy;
 };
 
 #define WITH_SAN(r, ps, psan, result) \
   { \
     ByteString(reinterpret_cast<const uint8_t*>(r), sizeof(r) - 1), \
     ps, \
     psan, \
     result \
@@ -1573,17 +1585,18 @@ TEST_P(pkixnames_CheckCertHostname, Chec
   ASSERT_FALSE(ENCODING_FAILED(cert));
   Input certInput;
   ASSERT_EQ(Success, certInput.Init(cert.data(), cert.length()));
 
   Input hostnameInput;
   ASSERT_EQ(Success, hostnameInput.Init(param.hostname.data(),
                                         param.hostname.length()));
 
-  ASSERT_EQ(param.result, CheckCertHostname(certInput, hostnameInput));
+  ASSERT_EQ(param.result, CheckCertHostname(certInput, hostnameInput,
+                                            mNameMatchingPolicy));
 }
 
 INSTANTIATE_TEST_CASE_P(pkixnames_CheckCertHostname,
                         pkixnames_CheckCertHostname,
                         testing::ValuesIn(CHECK_CERT_HOSTNAME_PARAMS));
 
 TEST_F(pkixnames_CheckCertHostname, SANWithoutSequence)
 {
@@ -1605,23 +1618,25 @@ TEST_F(pkixnames_CheckCertHostname, SANW
                        Name(RDN(CN("a"))), *keyPair, extensions,
                        *keyPair, sha256WithRSAEncryption()));
   ASSERT_FALSE(ENCODING_FAILED(certDER));
   Input certInput;
   ASSERT_EQ(Success, certInput.Init(certDER.data(), certDER.length()));
 
   static const uint8_t a[] = { 'a' };
   ASSERT_EQ(Result::ERROR_EXTENSION_VALUE_INVALID,
-            CheckCertHostname(certInput, Input(a)));
+            CheckCertHostname(certInput, Input(a), mNameMatchingPolicy));
 }
 
 class pkixnames_CheckCertHostname_PresentedMatchesReference
   : public ::testing::Test
   , public ::testing::WithParamInterface<PresentedMatchesReference>
 {
+public:
+  DefaultNameMatchingPolicy mNameMatchingPolicy;
 };
 
 TEST_P(pkixnames_CheckCertHostname_PresentedMatchesReference, CN_NoSAN)
 {
   // Since there is no SAN, a valid presented DNS ID in the subject CN field
   // should result in a match.
 
   const PresentedMatchesReference& param(GetParam());
@@ -1631,17 +1646,17 @@ TEST_P(pkixnames_CheckCertHostname_Prese
   Input certInput;
   ASSERT_EQ(Success, certInput.Init(cert.data(), cert.length()));
 
   Input hostnameInput;
   ASSERT_EQ(Success, hostnameInput.Init(param.referenceDNSID.data(),
                                         param.referenceDNSID.length()));
 
   ASSERT_EQ(param.expectedMatches ? Success : Result::ERROR_BAD_CERT_DOMAIN,
-            CheckCertHostname(certInput, hostnameInput));
+            CheckCertHostname(certInput, hostnameInput, mNameMatchingPolicy));
 }
 
 TEST_P(pkixnames_CheckCertHostname_PresentedMatchesReference,
        SubjectAltName_CNNotDNSName)
 {
   // A DNSName SAN entry should match, regardless of the contents of the
   // subject CN.
 
@@ -1655,17 +1670,18 @@ TEST_P(pkixnames_CheckCertHostname_Prese
 
   Input hostnameInput;
   ASSERT_EQ(Success, hostnameInput.Init(param.referenceDNSID.data(),
                                         param.referenceDNSID.length()));
   Result expectedResult
     = param.expectedResult != Success ? param.expectedResult
     : param.expectedMatches ? Success
     : Result::ERROR_BAD_CERT_DOMAIN;
-  ASSERT_EQ(expectedResult, CheckCertHostname(certInput, hostnameInput));
+  ASSERT_EQ(expectedResult, CheckCertHostname(certInput, hostnameInput,
+                                              mNameMatchingPolicy));
 }
 
 INSTANTIATE_TEST_CASE_P(pkixnames_CheckCertHostname_DNSID_MATCH_PARAMS,
                         pkixnames_CheckCertHostname_PresentedMatchesReference,
                         testing::ValuesIn(DNSID_MATCH_PARAMS));
 
 TEST_P(pkixnames_Turkish_I_Comparison, CheckCertHostname_CN_NoSAN)
 {
@@ -1684,18 +1700,20 @@ TEST_P(pkixnames_Turkish_I_Comparison, C
   Input certInput;
   ASSERT_EQ(Success, certInput.Init(cert.data(), cert.length()));
 
   Result expectedResult = (InputsAreEqual(LOWERCASE_I, input) ||
                            InputsAreEqual(UPPERCASE_I, input))
                         ? Success
                         : Result::ERROR_BAD_CERT_DOMAIN;
 
-  ASSERT_EQ(expectedResult, CheckCertHostname(certInput, UPPERCASE_I));
-  ASSERT_EQ(expectedResult, CheckCertHostname(certInput, LOWERCASE_I));
+  ASSERT_EQ(expectedResult, CheckCertHostname(certInput, UPPERCASE_I,
+                                              mNameMatchingPolicy));
+  ASSERT_EQ(expectedResult, CheckCertHostname(certInput, LOWERCASE_I,
+                                              mNameMatchingPolicy));
 }
 
 TEST_P(pkixnames_Turkish_I_Comparison, CheckCertHostname_SAN)
 {
   // Make sure we don't have the similar problems that strcasecmp and others
   // have with the other kinds of "i" and "I" commonly used in Turkish locales,
   // when we're matching a dNSName in the SAN.
 
@@ -1711,24 +1729,28 @@ TEST_P(pkixnames_Turkish_I_Comparison, C
   ASSERT_EQ(Success, certInput.Init(cert.data(), cert.length()));
 
   Result expectedResult
     = (!param.isValidPresentedID) ? Result::ERROR_BAD_DER
     : (InputsAreEqual(LOWERCASE_I, input) ||
        InputsAreEqual(UPPERCASE_I, input)) ? Success
     : Result::ERROR_BAD_CERT_DOMAIN;
 
-  ASSERT_EQ(expectedResult, CheckCertHostname(certInput, UPPERCASE_I));
-  ASSERT_EQ(expectedResult, CheckCertHostname(certInput, LOWERCASE_I));
+  ASSERT_EQ(expectedResult, CheckCertHostname(certInput, UPPERCASE_I,
+                                              mNameMatchingPolicy));
+  ASSERT_EQ(expectedResult, CheckCertHostname(certInput, LOWERCASE_I,
+                                              mNameMatchingPolicy));
 }
 
 class pkixnames_CheckCertHostname_IPV4_Addresses
   : public ::testing::Test
   , public ::testing::WithParamInterface<IPAddressParams<4>>
 {
+public:
+  DefaultNameMatchingPolicy mNameMatchingPolicy;
 };
 
 TEST_P(pkixnames_CheckCertHostname_IPV4_Addresses,
        ValidIPv4AddressInIPAddressSAN)
 {
   // When the reference hostname is a valid IPv4 address, a correctly-formed
   // IPv4 Address SAN matches it.
 
@@ -1740,17 +1762,17 @@ TEST_P(pkixnames_CheckCertHostname_IPV4_
   Input certInput;
   ASSERT_EQ(Success, certInput.Init(cert.data(), cert.length()));
 
   Input hostnameInput;
   ASSERT_EQ(Success, hostnameInput.Init(param.input.data(),
                                         param.input.length()));
 
   ASSERT_EQ(param.isValid ? Success : Result::ERROR_BAD_CERT_DOMAIN,
-            CheckCertHostname(certInput, hostnameInput));
+            CheckCertHostname(certInput, hostnameInput, mNameMatchingPolicy));
 }
 
 TEST_P(pkixnames_CheckCertHostname_IPV4_Addresses,
        ValidIPv4AddressInCN_NoSAN)
 {
   // When the reference hostname is a valid IPv4 address, a correctly-formed
   // IPv4 Address in the CN matches it when there is no SAN.
 
@@ -1767,17 +1789,18 @@ TEST_P(pkixnames_CheckCertHostname_IPV4_
   ASSERT_EQ(Success, hostnameInput.Init(param.input.data(),
                                         param.input.length()));
 
   // Some of the invalid IPv4 addresses are valid DNS names!
   Result expectedResult = (param.isValid || IsValidReferenceDNSID(hostnameInput))
                         ? Success
                         : Result::ERROR_BAD_CERT_DOMAIN;
 
-  ASSERT_EQ(expectedResult, CheckCertHostname(certInput, hostnameInput));
+  ASSERT_EQ(expectedResult, CheckCertHostname(certInput, hostnameInput,
+                                              mNameMatchingPolicy));
 }
 
 INSTANTIATE_TEST_CASE_P(pkixnames_CheckCertHostname_IPV4_ADDRESSES,
                         pkixnames_CheckCertHostname_IPV4_Addresses,
                         testing::ValuesIn(IPV4_ADDRESSES));
 
 struct NameConstraintParams
 {
@@ -2567,16 +2590,18 @@ static const NameConstraintParams NAME_C
     Result::ERROR_CERT_NOT_IN_NAME_SPACE, Result::ERROR_CERT_NOT_IN_NAME_SPACE
   },
 };
 
 class pkixnames_CheckNameConstraints
   : public ::testing::Test
   , public ::testing::WithParamInterface<NameConstraintParams>
 {
+public:
+  DefaultNameMatchingPolicy mNameMatchingPolicy;
 };
 
 TEST_P(pkixnames_CheckNameConstraints,
        NameConstraintsEnforcedForDirectlyIssuedEndEntity)
 {
   // Test that name constraints are enforced on a certificate directly issued by
   // a certificate with the given name constraints.