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 = AUGUST_23_2016 < notBefore
+ ? 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,39 @@
+/* -*- 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 mozilla_psm__BRNameMatchingPolicy_h
+#define mozilla_psm__BRNameMatchingPolicy_h
+
+#include "pkix/pkixtypes.h"
+
+namespace mozilla { namespace psm {
+
+class BRNameMatchingPolicy : public mozilla::pkix::NameMatchingPolicy
+{
+public:
+ enum class Mode {
+ Enforce = 0,
+ EnforceAfter23August2016 = 1,
+ DoNotEnforce = 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 // mozilla_psm__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 "pk11pub.h"
#include "pkix/pkix.h"
#include "pkix/pkixnss.h"
#include "prerror.h"
@@ -30,23 +31,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
@@ -691,17 +694,18 @@ 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);
+ BRNameMatchingPolicy nameMatchingPolicy(mNameMatchingMode);
+ 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,16 +2,17 @@
/* 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 "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.
@@ -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 PRLogModuleInfo* 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 spurrious 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
@@ -871,26 +871,40 @@ void nsNSSComponent::setValidationOption
case CertVerifier::SHA1Mode::Forbidden:
case CertVerifier::SHA1Mode::Before2016:
case CertVerifier::SHA1Mode::ImportedRoot:
break;
default:
sha1Mode = CertVerifier::SHA1Mode::Allowed;
}
+ BRNameMatchingPolicy::Mode nameMatchingMode =
+ static_cast<BRNameMatchingPolicy::Mode>
+ (Preferences::GetInt("security.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;
+ }
+
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
@@ -1320,17 +1334,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.name_matching_mode")) {
MutexAutoLock lock(mutex);
setValidationOptions(false, lock);
} else {
clearSessionCache = false;
}
if (clearSessionCache)
SSL_ClearSessionCache();
}
--- 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_ev_certs',
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/cn-contains-extra-entry-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/cn-contains-extra-entry-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/moz.build
@@ -0,0 +1,14 @@
+# -*- 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/.
+
+test_certificates = (
+ 'ca.pem',
+ 'cn-contains-extra-entry-old.pem',
+ 'cn-contains-extra-entry-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_subject_common_name.js
@@ -0,0 +1,51 @@
+// -*- 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/.
+
+// Tests that the baseline requirements for the subject common name field of
+// certificates are appropriately enforced based on the preference
+// security.name_matching_mode.
+
+"use strict";
+
+do_get_profile();
+
+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 checkCert(cert, expectedResult) {
+ // (new Date("25 August 2016")).getTime() / 1000
+ const VALIDATION_TIME = 1472108400;
+ checkCertErrorGenericAtTime(gCertDB, cert, expectedResult,
+ certificateUsageSSLServer, VALIDATION_TIME, {},
+ "example.com");
+}
+
+function run_test() {
+ loadCertWithTrust("ca", "CTu,,");
+
+ // Enforce BRv1.3.3 section 7.1.4.2.2.a
+ Services.prefs.setIntPref("security.name_matching_mode", 0);
+ checkCert(certFromFile("cn-contains-extra-entry-recent"), SSL_ERROR_BAD_CERT_DOMAIN);
+ checkCert(certFromFile("cn-contains-extra-entry-old"), SSL_ERROR_BAD_CERT_DOMAIN);
+
+ // Enforce BRv1.3.3 section 7.1.4.2.2.a if notBefore > 23 August 2016
+ Services.prefs.setIntPref("security.name_matching_mode", 1);
+ checkCert(certFromFile("cn-contains-extra-entry-recent"), SSL_ERROR_BAD_CERT_DOMAIN);
+ checkCert(certFromFile("cn-contains-extra-entry-old"), PRErrorCodeSuccess);
+
+ // Do not enforce BRv1.3.3 section 7.1.4.2.2.a
+ Services.prefs.setIntPref("security.name_matching_mode", 2);
+ checkCert(certFromFile("cn-contains-extra-entry-recent"), PRErrorCodeSuccess);
+ checkCert(certFromFile("cn-contains-extra-entry-old"), PRErrorCodeSuccess);
+}
--- a/security/manager/ssl/tests/unit/test_pinning_dynamic/cn-a.pinning2.example.com-badca.pem.certspec
+++ b/security/manager/ssl/tests/unit/test_pinning_dynamic/cn-a.pinning2.example.com-badca.pem.certspec
@@ -1,4 +1,5 @@
issuer:badca
subject:a.pinning2.example.com
issuerKey:alternate
subjectKey:alternate
+extension:subjectAlternativeName:a.pinning2.example.com
--- a/security/manager/ssl/tests/unit/test_pinning_dynamic/cn-a.pinning2.example.com-pinningroot.pem.certspec
+++ b/security/manager/ssl/tests/unit/test_pinning_dynamic/cn-a.pinning2.example.com-pinningroot.pem.certspec
@@ -1,3 +1,4 @@
issuer:pinningroot
subject:a.pinning2.example.com
subjectKey:alternate
+extension:subjectAlternativeName:a.pinning2.example.com
--- a/security/manager/ssl/tests/unit/test_pinning_dynamic/cn-b.pinning2.example.com-badca.pem.certspec
+++ b/security/manager/ssl/tests/unit/test_pinning_dynamic/cn-b.pinning2.example.com-badca.pem.certspec
@@ -1,4 +1,5 @@
issuer:badca
subject:b.pinning2.example.com
issuerKey:alternate
subjectKey:alternate
+extension:subjectAlternativeName:b.pinning2.example.com
--- a/security/manager/ssl/tests/unit/test_pinning_dynamic/cn-b.pinning2.example.com-pinningroot.pem.certspec
+++ b/security/manager/ssl/tests/unit/test_pinning_dynamic/cn-b.pinning2.example.com-pinningroot.pem.certspec
@@ -1,3 +1,4 @@
issuer:pinningroot
subject:b.pinning2.example.com
subjectKey:alternate
+extension:subjectAlternativeName:b.pinning2.example.com
--- a/security/manager/ssl/tests/unit/test_pinning_dynamic/cn-x.a.pinning2.example.com-badca.pem.certspec
+++ b/security/manager/ssl/tests/unit/test_pinning_dynamic/cn-x.a.pinning2.example.com-badca.pem.certspec
@@ -1,4 +1,5 @@
issuer:badca
subject:x.a.pinning2.example.com
issuerKey:alternate
subjectKey:alternate
+extension:subjectAlternativeName:x.a.pinning2.example.com
--- a/security/manager/ssl/tests/unit/test_pinning_dynamic/cn-x.a.pinning2.example.com-pinningroot.pem.certspec
+++ b/security/manager/ssl/tests/unit/test_pinning_dynamic/cn-x.a.pinning2.example.com-pinningroot.pem.certspec
@@ -1,3 +1,4 @@
issuer:pinningroot
subject:x.a.pinning2.example.com
subjectKey:alternate
+extension:subjectAlternativeName:x.a.pinning2.example.com
--- a/security/manager/ssl/tests/unit/test_pinning_dynamic/cn-x.b.pinning2.example.com-badca.pem.certspec
+++ b/security/manager/ssl/tests/unit/test_pinning_dynamic/cn-x.b.pinning2.example.com-badca.pem.certspec
@@ -1,4 +1,5 @@
issuer:badca
subject:x.b.pinning2.example.com
issuerKey:alternate
subjectKey:alternate
+extension:subjectAlternativeName:x.b.pinning2.example.com
--- a/security/manager/ssl/tests/unit/test_pinning_dynamic/cn-x.b.pinning2.example.com-pinningroot.pem.certspec
+++ b/security/manager/ssl/tests/unit/test_pinning_dynamic/cn-x.b.pinning2.example.com-pinningroot.pem.certspec
@@ -1,3 +1,4 @@
issuer:pinningroot
subject:x.b.pinning2.example.com
subjectKey:alternate
+extension:subjectAlternativeName:x.b.pinning2.example.com
--- a/security/manager/ssl/tests/unit/xpcshell.ini
+++ b/security/manager/ssl/tests/unit/xpcshell.ini
@@ -1,14 +1,15 @@
[DEFAULT]
head = head_psm.js
tail =
tags = psm
support-files =
ocsp_common/**
+ 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_certviewer_invalid_oids/**
@@ -22,16 +23,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,24 +212,37 @@ 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(), ¬Before);
+ 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
// 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
@@ -239,23 +250,23 @@ CheckCertHostname(Input endEntityCertDER
//
// 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
@@ -211,11 +211,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 \
@@ -1561,17 +1573,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)
{
@@ -1593,23 +1606,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());
@@ -1619,17 +1634,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.
@@ -1643,17 +1658,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)
{
@@ -1672,18 +1688,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.
@@ -1699,24 +1717,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.
@@ -1728,17 +1750,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.
@@ -1755,17 +1777,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
{
@@ -2505,16 +2528,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
// this certificate.