bug 1245280 - add policy mechanism to optionally enforce BRs for falling back to subject CN r=Cykesiopka,mgoodwin

MozReview-Commit-ID: 7xT6JGpOH1g

--HG--
extra : rebase_source : 0def29e8be898a2d975ee4390b3bc6a193766b1b
This commit is contained in:
David Keeler 2016-02-09 10:14:27 -08:00
parent 5753e3da83
commit 6e4140d766
29 changed files with 549 additions and 59 deletions

View File

@ -49,6 +49,20 @@ pref("security.pki.cert_short_lifetime_in_days", 10);
// 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);

View File

@ -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;
}

View File

@ -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 FullyQualified
// Domain Name that is one of the values contained in the Certificates
// 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

View File

@ -8,6 +8,7 @@
#include <stdint.h>
#include "BRNameMatchingPolicy.h"
#include "ExtendedValidation.h"
#include "NSSCertDBTrustDomain.h"
#include "NSSErrorsService.h"
@ -38,13 +39,15 @@ CertVerifier::CertVerifier(OcspDownloadConfig odc,
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)
{
}
@ -716,7 +719,16 @@ CertVerifier::VerifySSLServerCert(CERTCertificate* peerCert,
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) {

View File

@ -7,10 +7,11 @@
#ifndef mozilla_psm__CertVerifier_h
#define mozilla_psm__CertVerifier_h
#include "mozilla/Telemetry.h"
#include "pkix/pkixtypes.h"
#include "BRNameMatchingPolicy.h"
#include "OCSPCache.h"
#include "ScopedNSSTypes.h"
#include "mozilla/Telemetry.h"
#include "pkix/pkixtypes.h"
namespace mozilla { namespace psm {
@ -121,7 +122,8 @@ public:
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(); }
@ -132,6 +134,7 @@ public:
const uint32_t mCertShortLifetimeInDays;
const PinningMode mPinningMode;
const SHA1Mode mSHA1Mode;
const BRNameMatchingPolicy::Mode mNameMatchingMode;
private:
OCSPCache mOCSPCache;

View File

@ -5,11 +5,13 @@
# 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',

View File

@ -96,41 +96,40 @@
#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 "mozilla/Assertions.h"
#include "mozilla/Mutex.h"
#include "mozilla/Telemetry.h"
#include "mozilla/net/DNS.h"
#include "mozilla/UniquePtr.h"
#include "mozilla/unused.h"
#include "nsIThreadPool.h"
#include "nsISocketProvider.h"
#include "nsXPCOMCIDInternal.h"
#include "nsComponentManagerUtils.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 "mozilla/Assertions.h"
#include "mozilla/Mutex.h"
#include "mozilla/Telemetry.h"
#include "mozilla/UniquePtr.h"
#include "mozilla/net/DNS.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 "nsNSSComponent.h"
#include "nsNSSIOLayer.h"
#include "nsNSSShutDown.h"
#include "nsServiceManagerUtils.h"
#include "nsURLHelper.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;
@ -432,13 +431,28 @@ DetermineCertOverrideErrors(CERTCertificate* cert, const char* hostName,
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;
}

View File

@ -21,9 +21,10 @@ public:
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)
{
}
};

View File

@ -872,6 +872,21 @@ void nsNSSComponent::setValidationOptions(bool isInitialSetting,
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;
@ -883,7 +898,8 @@ void nsNSSComponent::setValidationOptions(bool isInitialSetting,
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
@ -1321,7 +1337,8 @@ nsNSSComponent::Observe(nsISupports* aSubject, const char* aTopic,
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

View File

@ -12,6 +12,7 @@ if not CONFIG['MOZ_NO_SMART_CARDS']:
TEST_DIRS += [
'bad_certs',
'ocsp_certs',
'test_baseline_requirements',
'test_cert_eku',
'test_cert_embedded_null',
'test_cert_keyUsage',

View File

@ -29,7 +29,7 @@ keyUsage:[digitalSignature,nonRepudiation,keyEncipherment,
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>,...]
@ -531,14 +531,19 @@ class Certificate(object):
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)

View File

@ -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-----

View File

@ -0,0 +1,4 @@
issuer:ca
subject:ca
extension:basicConstraints:cA,
extension:keyUsage:cRLSign,keyCertSign

View File

@ -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)

View File

@ -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-----

View File

@ -0,0 +1,3 @@
issuer:ca
subject:example.com
validity:20160724-20160924

View File

@ -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-----

View File

@ -0,0 +1,3 @@
issuer:ca
subject:example.com
validity:20160824-20160924

View File

@ -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-----

View File

@ -0,0 +1,4 @@
issuer:ca
subject:example.com
validity:20160724-20160924
extension:subjectAlternativeName:/O=Example Organization

View File

@ -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-----

View File

@ -0,0 +1,4 @@
issuer:ca
subject:example.com
validity:20160824-20160924
extension:subjectAlternativeName:/O=Example Organization

View File

@ -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);
}
}

View File

@ -5,6 +5,7 @@ tags = psm
support-files =
bad_certs/**
ocsp_certs/**
test_baseline_requirements/**
test_cert_eku/**
test_cert_embedded_null/**
test_cert_keyUsage/**
@ -29,6 +30,7 @@ support-files =
tlsserver/**
[test_add_preexisting_cert.js]
[test_baseline_requirements_subject_common_name.js]
[test_cert_blocklist.js]
skip-if = buildapp == "b2g"
tags = addons psm

View File

@ -116,7 +116,8 @@ Result BuildCertChain(TrustDomain& trustDomain, Input cert,
// - 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.

View File

@ -349,6 +349,30 @@ protected:
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

View File

@ -114,8 +114,6 @@ ReadGeneralName(Reader& reader,
return Success;
}
enum class FallBackToSearchWithinSubject { No = 0, Yes = 1 };
enum class MatchResult
{
NoNamesOfGivenType = 0,
@ -219,7 +217,8 @@ MatchPresentedDNSIDWithReferenceDNSID(Input presentedDNSID,
// 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();
@ -227,10 +226,22 @@ CheckCertHostname(Input endEntityCertDER, Input hostname)
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
@ -244,13 +255,13 @@ CheckCertHostname(Input endEntityCertDER, Input hostname)
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;
}

View File

@ -217,6 +217,17 @@ class DefaultCryptoTrustDomain : public EverythingFailsByDefaultTrustDomain
}
};
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

View File

@ -902,6 +902,8 @@ class pkixnames_MatchPresentedDNSIDWithReferenceDNSID
: public ::testing::Test
, public ::testing::WithParamInterface<PresentedMatchesReference>
{
public:
DefaultNameMatchingPolicy mNameMatchingPolicy;
};
TEST_P(pkixnames_MatchPresentedDNSIDWithReferenceDNSID,
@ -937,6 +939,8 @@ class pkixnames_Turkish_I_Comparison
: public ::testing::Test
, public ::testing::WithParamInterface<InputValidity>
{
public:
DefaultNameMatchingPolicy mNameMatchingPolicy;
};
TEST_P(pkixnames_Turkish_I_Comparison, MatchPresentedDNSIDWithReferenceDNSID)
@ -982,6 +986,8 @@ class pkixnames_IsValidReferenceDNSID
: public ::testing::Test
, public ::testing::WithParamInterface<InputValidity>
{
public:
DefaultNameMatchingPolicy mNameMatchingPolicy;
};
TEST_P(pkixnames_IsValidReferenceDNSID, IsValidReferenceDNSID)
@ -1006,6 +1012,8 @@ class pkixnames_ParseIPv4Address
: public ::testing::Test
, public ::testing::WithParamInterface<IPAddressParams<4>>
{
public:
DefaultNameMatchingPolicy mNameMatchingPolicy;
};
TEST_P(pkixnames_ParseIPv4Address, ParseIPv4Address)
@ -1032,6 +1040,8 @@ class pkixnames_ParseIPv6Address
: public ::testing::Test
, public ::testing::WithParamInterface<IPAddressParams<16>>
{
public:
DefaultNameMatchingPolicy mNameMatchingPolicy;
};
TEST_P(pkixnames_ParseIPv6Address, ParseIPv6Address)
@ -1074,6 +1084,8 @@ class pkixnames_CheckCertHostname
: public ::testing::Test
, public ::testing::WithParamInterface<CheckCertHostnameParams>
{
public:
DefaultNameMatchingPolicy mNameMatchingPolicy;
};
#define WITH_SAN(r, ps, psan, result) \
@ -1578,7 +1590,8 @@ TEST_P(pkixnames_CheckCertHostname, CheckCertHostname)
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,
@ -1610,13 +1623,15 @@ TEST_F(pkixnames_CheckCertHostname, SANWithoutSequence)
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)
@ -1636,7 +1651,7 @@ TEST_P(pkixnames_CheckCertHostname_PresentedMatchesReference, CN_NoSAN)
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,
@ -1660,7 +1675,8 @@ TEST_P(pkixnames_CheckCertHostname_PresentedMatchesReference,
= 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,
@ -1689,8 +1705,10 @@ TEST_P(pkixnames_Turkish_I_Comparison, CheckCertHostname_CN_NoSAN)
? 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)
@ -1716,14 +1734,18 @@ TEST_P(pkixnames_Turkish_I_Comparison, CheckCertHostname_SAN)
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,
@ -1745,7 +1767,7 @@ TEST_P(pkixnames_CheckCertHostname_IPV4_Addresses,
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,
@ -1772,7 +1794,8 @@ TEST_P(pkixnames_CheckCertHostname_IPV4_Addresses,
? 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,
@ -2572,6 +2595,8 @@ class pkixnames_CheckNameConstraints
: public ::testing::Test
, public ::testing::WithParamInterface<NameConstraintParams>
{
public:
DefaultNameMatchingPolicy mNameMatchingPolicy;
};
TEST_P(pkixnames_CheckNameConstraints,