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 291431 dc40f46fae4832e4ff2e15e1034c40a96bf1851c
parent 291430 23b389d50a0b25455c79a8b25c0415a91197a0f3
child 291432 104e03d0e4082f82227e3e2c658510fa41346362
push id19656
push usergwagner@mozilla.com
push dateMon, 04 Apr 2016 13:43:23 +0000
treeherderb2g-inbound@e99061fde28a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersCykesiopka, mgoodwin
bugs1245280
milestone48.0a1
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.