2014-01-21 10:10:33 +04:00
|
|
|
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
|
|
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
2012-10-27 11:11:35 +04:00
|
|
|
/* 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"
|
2014-01-23 05:13:19 +04:00
|
|
|
|
|
|
|
#include <stdint.h>
|
|
|
|
|
2017-01-09 09:22:28 +03:00
|
|
|
#include "CTDiversityPolicy.h"
|
2016-08-11 13:41:50 +03:00
|
|
|
#include "CTKnownLogs.h"
|
2016-11-23 16:37:31 +03:00
|
|
|
#include "CTLogVerifier.h"
|
2014-01-27 07:36:28 +04:00
|
|
|
#include "ExtendedValidation.h"
|
2016-08-11 13:41:50 +03:00
|
|
|
#include "MultiLogCTVerifier.h"
|
2013-07-09 03:30:59 +04:00
|
|
|
#include "NSSCertDBTrustDomain.h"
|
2014-05-29 02:28:03 +04:00
|
|
|
#include "NSSErrorsService.h"
|
2012-10-27 11:11:35 +04:00
|
|
|
#include "cert.h"
|
2016-08-11 13:41:50 +03:00
|
|
|
#include "mozilla/Assertions.h"
|
2016-09-08 15:46:26 +03:00
|
|
|
#include "mozilla/Casting.h"
|
2016-12-16 06:16:31 +03:00
|
|
|
#include "mozilla/IntegerPrintfMacros.h"
|
2016-03-05 04:06:33 +03:00
|
|
|
#include "nsNSSComponent.h"
|
2017-06-03 08:35:51 +03:00
|
|
|
#include "nsPromiseFlatString.h"
|
2016-03-05 04:06:33 +03:00
|
|
|
#include "nsServiceManagerUtils.h"
|
2014-02-06 02:49:10 +04:00
|
|
|
#include "pk11pub.h"
|
2014-05-29 02:28:03 +04:00
|
|
|
#include "pkix/pkix.h"
|
2014-07-18 22:48:49 +04:00
|
|
|
#include "pkix/pkixnss.h"
|
2016-03-05 04:06:33 +03:00
|
|
|
#include "secmod.h"
|
2012-10-27 11:11:35 +04:00
|
|
|
|
2016-08-11 13:41:50 +03:00
|
|
|
using namespace mozilla::ct;
|
2014-03-21 01:29:21 +04:00
|
|
|
using namespace mozilla::pkix;
|
2014-01-23 05:13:19 +04:00
|
|
|
using namespace mozilla::psm;
|
|
|
|
|
2016-01-28 21:36:00 +03:00
|
|
|
mozilla::LazyLogModule gCertVerifierLog("certverifier");
|
2012-10-27 11:11:35 +04:00
|
|
|
|
2017-01-09 09:22:28 +03:00
|
|
|
// Returns the certificate validity period in calendar months (rounded down).
|
|
|
|
// "extern" to allow unit tests in CTPolicyEnforcerTest.cpp.
|
|
|
|
extern mozilla::pkix::Result
|
|
|
|
GetCertLifetimeInFullMonths(PRTime certNotBefore,
|
|
|
|
PRTime certNotAfter,
|
|
|
|
size_t& months)
|
|
|
|
{
|
|
|
|
if (certNotBefore >= certNotAfter) {
|
|
|
|
MOZ_ASSERT_UNREACHABLE("Expected notBefore < notAfter");
|
|
|
|
return mozilla::pkix::Result::FATAL_ERROR_INVALID_ARGS;
|
|
|
|
}
|
|
|
|
|
|
|
|
PRExplodedTime explodedNotBefore;
|
|
|
|
PRExplodedTime explodedNotAfter;
|
|
|
|
|
|
|
|
PR_ExplodeTime(certNotBefore, PR_LocalTimeParameters, &explodedNotBefore);
|
|
|
|
PR_ExplodeTime(certNotAfter, PR_LocalTimeParameters, &explodedNotAfter);
|
|
|
|
|
|
|
|
PRInt32 signedMonths =
|
|
|
|
(explodedNotAfter.tm_year - explodedNotBefore.tm_year) * 12 +
|
|
|
|
(explodedNotAfter.tm_month - explodedNotBefore.tm_month);
|
|
|
|
if (explodedNotAfter.tm_mday < explodedNotBefore.tm_mday) {
|
|
|
|
--signedMonths;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Can't use `mozilla::AssertedCast<size_t>(signedMonths)` below
|
|
|
|
// since it currently generates a warning on Win x64 debug.
|
|
|
|
if (signedMonths < 0) {
|
|
|
|
MOZ_ASSERT_UNREACHABLE("Expected explodedNotBefore < explodedNotAfter");
|
|
|
|
return mozilla::pkix::Result::FATAL_ERROR_LIBRARY_FAILURE;
|
|
|
|
}
|
|
|
|
months = static_cast<size_t>(signedMonths);
|
|
|
|
|
|
|
|
return Success;
|
|
|
|
}
|
|
|
|
|
2012-10-27 11:11:35 +04:00
|
|
|
namespace mozilla { namespace psm {
|
|
|
|
|
|
|
|
const CertVerifier::Flags CertVerifier::FLAG_LOCAL_ONLY = 1;
|
2014-01-25 01:57:35 +04:00
|
|
|
const CertVerifier::Flags CertVerifier::FLAG_MUST_BE_EV = 2;
|
2015-11-13 19:49:08 +03:00
|
|
|
const CertVerifier::Flags CertVerifier::FLAG_TLS_IGNORE_STATUS_REQUEST = 4;
|
2012-10-27 11:11:35 +04:00
|
|
|
|
2017-01-09 09:22:28 +03:00
|
|
|
void
|
|
|
|
CertificateTransparencyInfo::Reset()
|
|
|
|
{
|
|
|
|
enabled = false;
|
|
|
|
verifyResult.Reset();
|
|
|
|
policyCompliance = CTPolicyCompliance::Unknown;
|
|
|
|
}
|
|
|
|
|
2015-01-23 08:17:00 +03:00
|
|
|
CertVerifier::CertVerifier(OcspDownloadConfig odc,
|
|
|
|
OcspStrictConfig osc,
|
|
|
|
OcspGetConfig ogc,
|
2017-04-01 01:21:40 +03:00
|
|
|
mozilla::TimeDuration ocspTimeoutSoft,
|
|
|
|
mozilla::TimeDuration ocspTimeoutHard,
|
2015-04-07 02:10:28 +03:00
|
|
|
uint32_t certShortLifetimeInDays,
|
2015-09-11 21:52:30 +03:00
|
|
|
PinningMode pinningMode,
|
2016-02-09 21:14:27 +03:00
|
|
|
SHA1Mode sha1Mode,
|
2016-05-06 02:11:11 +03:00
|
|
|
BRNameMatchingPolicy::Mode nameMatchingMode,
|
2016-08-11 13:41:50 +03:00
|
|
|
NetscapeStepUpPolicy netscapeStepUpPolicy,
|
|
|
|
CertificateTransparencyMode ctMode)
|
2015-05-28 23:29:13 +03:00
|
|
|
: mOCSPDownloadConfig(odc)
|
2015-01-23 08:17:00 +03:00
|
|
|
, mOCSPStrict(osc == ocspStrict)
|
|
|
|
, mOCSPGETEnabled(ogc == ocspGetEnabled)
|
2017-04-01 01:21:40 +03:00
|
|
|
, mOCSPTimeoutSoft(ocspTimeoutSoft)
|
|
|
|
, mOCSPTimeoutHard(ocspTimeoutHard)
|
2015-04-07 02:10:28 +03:00
|
|
|
, mCertShortLifetimeInDays(certShortLifetimeInDays)
|
2014-09-25 22:08:36 +04:00
|
|
|
, mPinningMode(pinningMode)
|
2015-09-11 21:52:30 +03:00
|
|
|
, mSHA1Mode(sha1Mode)
|
2016-02-09 21:14:27 +03:00
|
|
|
, mNameMatchingMode(nameMatchingMode)
|
2016-05-06 02:11:11 +03:00
|
|
|
, mNetscapeStepUpPolicy(netscapeStepUpPolicy)
|
2016-08-11 13:41:50 +03:00
|
|
|
, mCTMode(ctMode)
|
2012-10-27 11:11:35 +04:00
|
|
|
{
|
2016-08-11 13:41:50 +03:00
|
|
|
LoadKnownCTLogs();
|
2012-10-27 11:11:35 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
CertVerifier::~CertVerifier()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2016-01-13 23:50:42 +03:00
|
|
|
Result
|
2016-05-06 00:56:36 +03:00
|
|
|
IsCertChainRootBuiltInRoot(const UniqueCERTCertList& chain, bool& result)
|
2016-01-13 23:50:42 +03:00
|
|
|
{
|
|
|
|
if (!chain || CERT_LIST_EMPTY(chain)) {
|
|
|
|
return Result::FATAL_ERROR_LIBRARY_FAILURE;
|
|
|
|
}
|
|
|
|
CERTCertListNode* rootNode = CERT_LIST_TAIL(chain);
|
|
|
|
if (!rootNode) {
|
|
|
|
return Result::FATAL_ERROR_LIBRARY_FAILURE;
|
|
|
|
}
|
|
|
|
CERTCertificate* root = rootNode->cert;
|
|
|
|
if (!root) {
|
|
|
|
return Result::FATAL_ERROR_LIBRARY_FAILURE;
|
|
|
|
}
|
2016-03-05 04:06:33 +03:00
|
|
|
return IsCertBuiltInRoot(root, result);
|
2016-01-13 23:50:42 +03:00
|
|
|
}
|
|
|
|
|
2017-02-22 20:02:48 +03:00
|
|
|
// The term "builtin root" traditionally refers to a root CA certificate that
|
|
|
|
// has been added to the NSS trust store, because it has been approved
|
|
|
|
// for inclusion according to the Mozilla CA policy, and might be accepted
|
|
|
|
// by Mozilla applications as an issuer for certificates seen on the public web.
|
2016-03-05 04:06:33 +03:00
|
|
|
Result
|
2016-01-13 23:50:42 +03:00
|
|
|
IsCertBuiltInRoot(CERTCertificate* cert, bool& result)
|
|
|
|
{
|
2017-06-09 02:10:00 +03:00
|
|
|
if (NS_FAILED(BlockUntilLoadableRootsLoaded())) {
|
|
|
|
return Result::FATAL_ERROR_LIBRARY_FAILURE;
|
|
|
|
}
|
|
|
|
|
2014-02-06 02:49:10 +04:00
|
|
|
result = false;
|
2016-06-24 01:43:47 +03:00
|
|
|
#ifdef DEBUG
|
2016-03-05 04:06:33 +03:00
|
|
|
nsCOMPtr<nsINSSComponent> component(do_GetService(PSM_COMPONENT_CONTRACTID));
|
|
|
|
if (!component) {
|
|
|
|
return Result::FATAL_ERROR_LIBRARY_FAILURE;
|
2014-02-06 02:49:10 +04:00
|
|
|
}
|
2016-06-24 01:43:47 +03:00
|
|
|
nsresult rv = component->IsCertTestBuiltInRoot(cert, result);
|
2016-03-16 03:19:00 +03:00
|
|
|
if (NS_FAILED(rv)) {
|
|
|
|
return Result::FATAL_ERROR_LIBRARY_FAILURE;
|
|
|
|
}
|
|
|
|
if (result) {
|
|
|
|
return Success;
|
|
|
|
}
|
|
|
|
#endif // DEBUG
|
2016-06-24 01:43:47 +03:00
|
|
|
AutoSECMODListReadLock lock;
|
|
|
|
for (SECMODModuleList* list = SECMOD_GetDefaultModuleList(); list;
|
|
|
|
list = list->next) {
|
|
|
|
for (int i = 0; i < list->module->slotCount; i++) {
|
|
|
|
PK11SlotInfo* slot = list->module->slots[i];
|
2017-02-22 20:02:48 +03:00
|
|
|
// We're searching for the "builtin root module", which is a module that
|
|
|
|
// contains an object with a CKA_CLASS of CKO_NETSCAPE_BUILTIN_ROOT_LIST.
|
|
|
|
// We use PK11_HasRootCerts() to identify a module with that property.
|
|
|
|
// In the past, we exclusively used the PKCS#11 module named nssckbi,
|
|
|
|
// which is provided by the NSS library.
|
|
|
|
// Nowadays, some distributions use a replacement module, which contains
|
|
|
|
// the builtin roots, but which also contains additional CA certificates,
|
|
|
|
// such as CAs trusted in a local deployment.
|
|
|
|
// We want to be able to distinguish between these two categories,
|
|
|
|
// because a CA, which may issue certificates for the public web,
|
|
|
|
// is expected to comply with additional requirements.
|
|
|
|
// If the certificate has attribute CKA_NSS_MOZILLA_CA_POLICY set to true,
|
|
|
|
// then we treat it as a "builtin root".
|
|
|
|
if (PK11_IsPresent(slot) && PK11_HasRootCerts(slot)) {
|
|
|
|
CK_OBJECT_HANDLE handle = PK11_FindCertInSlot(slot, cert, nullptr);
|
|
|
|
if (handle != CK_INVALID_HANDLE &&
|
|
|
|
PK11_HasAttributeSet(slot, handle, CKA_NSS_MOZILLA_CA_POLICY,
|
|
|
|
false)) {
|
|
|
|
// Attribute was found, and is set to true
|
|
|
|
result = true;
|
|
|
|
break;
|
|
|
|
}
|
2016-06-24 01:43:47 +03:00
|
|
|
}
|
|
|
|
}
|
2016-03-05 04:06:33 +03:00
|
|
|
}
|
|
|
|
return Success;
|
2014-02-06 02:49:10 +04:00
|
|
|
}
|
|
|
|
|
2014-07-18 22:48:49 +04:00
|
|
|
static Result
|
2014-12-12 10:22:35 +03:00
|
|
|
BuildCertChainForOneKeyUsage(NSSCertDBTrustDomain& trustDomain, Input certDER,
|
2014-08-02 19:49:12 +04:00
|
|
|
Time time, KeyUsage ku1, KeyUsage ku2,
|
2014-06-19 11:13:20 +04:00
|
|
|
KeyUsage ku3, KeyPurposeId eku,
|
2014-05-16 05:59:52 +04:00
|
|
|
const CertPolicyId& requiredPolicy,
|
2014-12-12 10:22:35 +03:00
|
|
|
const Input* stapledOCSPResponse,
|
|
|
|
/*optional out*/ CertVerifier::OCSPStaplingStatus*
|
|
|
|
ocspStaplingStatus)
|
2014-02-10 23:41:12 +04:00
|
|
|
{
|
2016-08-11 13:41:50 +03:00
|
|
|
trustDomain.ResetAccumulatedState();
|
2014-07-19 09:30:51 +04:00
|
|
|
Result rv = BuildCertChain(trustDomain, certDER, time,
|
2014-07-18 22:48:49 +04:00
|
|
|
EndEntityOrCA::MustBeEndEntity, ku1,
|
|
|
|
eku, requiredPolicy, stapledOCSPResponse);
|
|
|
|
if (rv == Result::ERROR_INADEQUATE_KEY_USAGE) {
|
2016-08-11 13:41:50 +03:00
|
|
|
trustDomain.ResetAccumulatedState();
|
2014-07-19 09:30:51 +04:00
|
|
|
rv = BuildCertChain(trustDomain, certDER, time,
|
2014-04-26 03:29:26 +04:00
|
|
|
EndEntityOrCA::MustBeEndEntity, ku2,
|
2014-07-07 02:55:38 +04:00
|
|
|
eku, requiredPolicy, stapledOCSPResponse);
|
2014-07-18 22:48:49 +04:00
|
|
|
if (rv == Result::ERROR_INADEQUATE_KEY_USAGE) {
|
2016-08-11 13:41:50 +03:00
|
|
|
trustDomain.ResetAccumulatedState();
|
2014-07-19 09:30:51 +04:00
|
|
|
rv = BuildCertChain(trustDomain, certDER, time,
|
2014-04-26 03:29:26 +04:00
|
|
|
EndEntityOrCA::MustBeEndEntity, ku3,
|
2014-07-07 02:55:38 +04:00
|
|
|
eku, requiredPolicy, stapledOCSPResponse);
|
2014-07-18 22:48:49 +04:00
|
|
|
if (rv != Success) {
|
|
|
|
rv = Result::ERROR_INADEQUATE_KEY_USAGE;
|
2014-02-10 23:41:12 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2014-12-12 10:22:35 +03:00
|
|
|
if (ocspStaplingStatus) {
|
|
|
|
*ocspStaplingStatus = trustDomain.GetOCSPStaplingStatus();
|
|
|
|
}
|
2014-02-10 23:41:12 +04:00
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
|
2016-08-11 13:41:50 +03:00
|
|
|
void
|
|
|
|
CertVerifier::LoadKnownCTLogs()
|
|
|
|
{
|
|
|
|
mCTVerifier = MakeUnique<MultiLogCTVerifier>();
|
|
|
|
for (const CTLogInfo& log : kCTLogList) {
|
|
|
|
Input publicKey;
|
|
|
|
Result rv = publicKey.Init(
|
2016-11-29 23:51:46 +03:00
|
|
|
BitwiseCast<const uint8_t*, const char*>(log.key), log.keyLength);
|
2016-08-11 13:41:50 +03:00
|
|
|
if (rv != Success) {
|
|
|
|
MOZ_ASSERT_UNREACHABLE("Failed reading a log key for a known CT Log");
|
|
|
|
continue;
|
|
|
|
}
|
2016-11-23 16:37:31 +03:00
|
|
|
|
|
|
|
CTLogVerifier logVerifier;
|
2016-11-29 23:51:46 +03:00
|
|
|
const CTLogOperatorInfo& logOperator =
|
|
|
|
kCTLogOperatorList[log.operatorIndex];
|
|
|
|
rv = logVerifier.Init(publicKey, logOperator.id, log.status,
|
|
|
|
log.disqualificationTime);
|
2016-08-11 13:41:50 +03:00
|
|
|
if (rv != Success) {
|
|
|
|
MOZ_ASSERT_UNREACHABLE("Failed initializing a known CT Log");
|
|
|
|
continue;
|
|
|
|
}
|
2016-11-23 16:37:31 +03:00
|
|
|
|
|
|
|
rv = mCTVerifier->AddLog(Move(logVerifier));
|
|
|
|
if (rv != Success) {
|
|
|
|
MOZ_ASSERT_UNREACHABLE("Failed activating a known CT Log");
|
|
|
|
continue;
|
|
|
|
}
|
2016-08-11 13:41:50 +03:00
|
|
|
}
|
2017-01-09 09:22:28 +03:00
|
|
|
// TBD: Initialize mCTDiversityPolicy with the CA dependency map
|
|
|
|
// of the known CT logs operators.
|
|
|
|
mCTDiversityPolicy = MakeUnique<CTDiversityPolicy>();
|
2016-08-11 13:41:50 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
Result
|
2017-01-09 09:22:28 +03:00
|
|
|
CertVerifier::VerifyCertificateTransparencyPolicy(
|
2016-08-11 13:41:50 +03:00
|
|
|
NSSCertDBTrustDomain& trustDomain, const UniqueCERTCertList& builtChain,
|
|
|
|
Input sctsFromTLS, Time time,
|
|
|
|
/*optional out*/ CertificateTransparencyInfo* ctInfo)
|
|
|
|
{
|
|
|
|
if (ctInfo) {
|
|
|
|
ctInfo->Reset();
|
|
|
|
}
|
|
|
|
if (mCTMode == CertificateTransparencyMode::Disabled) {
|
|
|
|
return Success;
|
|
|
|
}
|
|
|
|
if (ctInfo) {
|
|
|
|
ctInfo->enabled = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!builtChain || CERT_LIST_EMPTY(builtChain)) {
|
|
|
|
return Result::FATAL_ERROR_INVALID_ARGS;
|
|
|
|
}
|
|
|
|
|
|
|
|
Input embeddedSCTs = trustDomain.GetSCTListFromCertificate();
|
|
|
|
if (embeddedSCTs.GetLength() > 0) {
|
|
|
|
MOZ_LOG(gCertVerifierLog, LogLevel::Debug,
|
|
|
|
("Got embedded SCT data of length %zu\n",
|
|
|
|
static_cast<size_t>(embeddedSCTs.GetLength())));
|
|
|
|
}
|
|
|
|
Input sctsFromOCSP = trustDomain.GetSCTListFromOCSPStapling();
|
|
|
|
if (sctsFromOCSP.GetLength() > 0) {
|
|
|
|
MOZ_LOG(gCertVerifierLog, LogLevel::Debug,
|
|
|
|
("Got OCSP SCT data of length %zu\n",
|
|
|
|
static_cast<size_t>(sctsFromOCSP.GetLength())));
|
|
|
|
}
|
|
|
|
if (sctsFromTLS.GetLength() > 0) {
|
|
|
|
MOZ_LOG(gCertVerifierLog, LogLevel::Debug,
|
|
|
|
("Got TLS SCT data of length %zu\n",
|
|
|
|
static_cast<size_t>(sctsFromTLS.GetLength())));
|
|
|
|
}
|
|
|
|
|
|
|
|
CERTCertListNode* endEntityNode = CERT_LIST_HEAD(builtChain);
|
2017-02-23 02:07:05 +03:00
|
|
|
if (!endEntityNode || CERT_LIST_END(endEntityNode, builtChain)) {
|
2016-08-11 13:41:50 +03:00
|
|
|
return Result::FATAL_ERROR_INVALID_ARGS;
|
|
|
|
}
|
|
|
|
CERTCertListNode* issuerNode = CERT_LIST_NEXT(endEntityNode);
|
2017-02-23 02:07:05 +03:00
|
|
|
if (!issuerNode || CERT_LIST_END(issuerNode, builtChain)) {
|
2016-08-11 13:41:50 +03:00
|
|
|
// Issuer certificate is required for SCT verification.
|
2017-02-24 23:32:41 +03:00
|
|
|
return Result::FATAL_ERROR_INVALID_ARGS;
|
2016-08-11 13:41:50 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
CERTCertificate* endEntity = endEntityNode->cert;
|
|
|
|
CERTCertificate* issuer = issuerNode->cert;
|
|
|
|
if (!endEntity || !issuer) {
|
|
|
|
return Result::FATAL_ERROR_INVALID_ARGS;
|
|
|
|
}
|
|
|
|
|
2017-01-09 09:22:28 +03:00
|
|
|
if (endEntity->subjectName) {
|
|
|
|
MOZ_LOG(gCertVerifierLog, LogLevel::Debug,
|
|
|
|
("Verifying CT Policy compliance of subject %s\n",
|
|
|
|
endEntity->subjectName));
|
|
|
|
}
|
|
|
|
|
2016-08-11 13:41:50 +03:00
|
|
|
Input endEntityDER;
|
|
|
|
Result rv = endEntityDER.Init(endEntity->derCert.data,
|
|
|
|
endEntity->derCert.len);
|
|
|
|
if (rv != Success) {
|
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
|
|
|
|
Input issuerPublicKeyDER;
|
|
|
|
rv = issuerPublicKeyDER.Init(issuer->derPublicKey.data,
|
|
|
|
issuer->derPublicKey.len);
|
|
|
|
if (rv != Success) {
|
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
|
|
|
|
CTVerifyResult result;
|
|
|
|
rv = mCTVerifier->Verify(endEntityDER, issuerPublicKeyDER,
|
|
|
|
embeddedSCTs, sctsFromOCSP, sctsFromTLS, time,
|
|
|
|
result);
|
|
|
|
if (rv != Success) {
|
|
|
|
MOZ_LOG(gCertVerifierLog, LogLevel::Debug,
|
2016-12-16 06:16:31 +03:00
|
|
|
("SCT verification failed with fatal error %" PRId32 "\n",
|
|
|
|
static_cast<uint32_t>(rv)));
|
2016-08-11 13:41:50 +03:00
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (MOZ_LOG_TEST(gCertVerifierLog, LogLevel::Debug)) {
|
2016-11-23 16:37:31 +03:00
|
|
|
size_t validCount = 0;
|
2016-08-11 13:41:50 +03:00
|
|
|
size_t unknownLogCount = 0;
|
2016-11-29 23:51:46 +03:00
|
|
|
size_t disqualifiedLogCount = 0;
|
2016-08-11 13:41:50 +03:00
|
|
|
size_t invalidSignatureCount = 0;
|
|
|
|
size_t invalidTimestampCount = 0;
|
2016-11-23 16:37:31 +03:00
|
|
|
for (const VerifiedSCT& verifiedSct : result.verifiedScts) {
|
|
|
|
switch (verifiedSct.status) {
|
|
|
|
case VerifiedSCT::Status::Valid:
|
|
|
|
validCount++;
|
2016-08-11 13:41:50 +03:00
|
|
|
break;
|
2016-11-29 23:51:46 +03:00
|
|
|
case VerifiedSCT::Status::ValidFromDisqualifiedLog:
|
|
|
|
disqualifiedLogCount++;
|
|
|
|
break;
|
2016-11-23 16:37:31 +03:00
|
|
|
case VerifiedSCT::Status::UnknownLog:
|
2016-08-11 13:41:50 +03:00
|
|
|
unknownLogCount++;
|
|
|
|
break;
|
2016-11-23 16:37:31 +03:00
|
|
|
case VerifiedSCT::Status::InvalidSignature:
|
2016-08-11 13:41:50 +03:00
|
|
|
invalidSignatureCount++;
|
|
|
|
break;
|
2016-11-23 16:37:31 +03:00
|
|
|
case VerifiedSCT::Status::InvalidTimestamp:
|
2016-08-11 13:41:50 +03:00
|
|
|
invalidTimestampCount++;
|
|
|
|
break;
|
2016-11-23 16:37:31 +03:00
|
|
|
case VerifiedSCT::Status::None:
|
2016-08-11 13:41:50 +03:00
|
|
|
default:
|
2016-11-23 16:37:31 +03:00
|
|
|
MOZ_ASSERT_UNREACHABLE("Unexpected SCT verification status");
|
2016-08-11 13:41:50 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
MOZ_LOG(gCertVerifierLog, LogLevel::Debug,
|
|
|
|
("SCT verification result: "
|
2016-11-29 23:51:46 +03:00
|
|
|
"valid=%zu unknownLog=%zu disqualifiedLog=%zu "
|
2016-08-11 13:41:50 +03:00
|
|
|
"invalidSignature=%zu invalidTimestamp=%zu "
|
|
|
|
"decodingErrors=%zu\n",
|
2016-11-29 23:51:46 +03:00
|
|
|
validCount, unknownLogCount, disqualifiedLogCount,
|
2016-08-11 13:41:50 +03:00
|
|
|
invalidSignatureCount, invalidTimestampCount,
|
|
|
|
result.decodingErrors));
|
|
|
|
}
|
|
|
|
|
2017-01-09 09:22:28 +03:00
|
|
|
PRTime notBefore;
|
|
|
|
PRTime notAfter;
|
|
|
|
if (CERT_GetCertTimes(endEntity, ¬Before, ¬After) != SECSuccess) {
|
|
|
|
return Result::FATAL_ERROR_LIBRARY_FAILURE;
|
|
|
|
}
|
|
|
|
size_t lifetimeInMonths;
|
|
|
|
rv = GetCertLifetimeInFullMonths(notBefore, notAfter, lifetimeInMonths);
|
|
|
|
if (rv != Success) {
|
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
|
|
|
|
CTLogOperatorList allOperators;
|
|
|
|
rv = GetCTLogOperatorsFromVerifiedSCTList(result.verifiedScts,
|
|
|
|
allOperators);
|
|
|
|
if (rv != Success) {
|
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
|
|
|
|
CTLogOperatorList dependentOperators;
|
|
|
|
rv = mCTDiversityPolicy->GetDependentOperators(builtChain, allOperators,
|
|
|
|
dependentOperators);
|
|
|
|
if (rv != Success) {
|
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
|
|
|
|
CTPolicyEnforcer ctPolicyEnforcer;
|
|
|
|
CTPolicyCompliance ctPolicyCompliance;
|
|
|
|
rv = ctPolicyEnforcer.CheckCompliance(result.verifiedScts, lifetimeInMonths,
|
|
|
|
dependentOperators, ctPolicyCompliance);
|
|
|
|
if (rv != Success) {
|
|
|
|
MOZ_LOG(gCertVerifierLog, LogLevel::Debug,
|
2016-12-16 06:16:31 +03:00
|
|
|
("CT policy check failed with fatal error %" PRIu32 "\n",
|
|
|
|
static_cast<uint32_t>(rv)));
|
2017-01-09 09:22:28 +03:00
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
|
2016-08-11 13:41:50 +03:00
|
|
|
if (ctInfo) {
|
|
|
|
ctInfo->verifyResult = Move(result);
|
2017-01-09 09:22:28 +03:00
|
|
|
ctInfo->policyCompliance = ctPolicyCompliance;
|
2016-08-11 13:41:50 +03:00
|
|
|
}
|
|
|
|
return Success;
|
|
|
|
}
|
|
|
|
|
2016-01-13 23:50:42 +03:00
|
|
|
bool
|
|
|
|
CertVerifier::SHA1ModeMoreRestrictiveThanGivenMode(SHA1Mode mode)
|
|
|
|
{
|
|
|
|
switch (mSHA1Mode) {
|
|
|
|
case SHA1Mode::Forbidden:
|
|
|
|
return mode != SHA1Mode::Forbidden;
|
|
|
|
case SHA1Mode::ImportedRoot:
|
2016-09-15 01:11:15 +03:00
|
|
|
return mode != SHA1Mode::Forbidden && mode != SHA1Mode::ImportedRoot;
|
|
|
|
case SHA1Mode::ImportedRootOrBefore2016:
|
2016-01-13 23:50:42 +03:00
|
|
|
return mode == SHA1Mode::Allowed;
|
|
|
|
case SHA1Mode::Allowed:
|
|
|
|
return false;
|
2016-09-15 01:11:15 +03:00
|
|
|
// MSVC warns unless we explicitly handle this now-unused option.
|
|
|
|
case SHA1Mode::UsedToBeBefore2016ButNowIsForbidden:
|
2016-01-13 23:50:42 +03:00
|
|
|
default:
|
|
|
|
MOZ_ASSERT(false, "unexpected SHA1Mode type");
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-03-05 18:41:00 +03:00
|
|
|
static const unsigned int MIN_RSA_BITS = 2048;
|
|
|
|
static const unsigned int MIN_RSA_BITS_WEAK = 1024;
|
2015-02-25 02:48:05 +03:00
|
|
|
|
2016-10-10 10:44:41 +03:00
|
|
|
Result
|
2014-06-17 10:13:29 +04:00
|
|
|
CertVerifier::VerifyCert(CERTCertificate* cert, SECCertificateUsage usage,
|
2014-08-02 19:49:12 +04:00
|
|
|
Time time, void* pinArg, const char* hostname,
|
2016-05-06 00:56:36 +03:00
|
|
|
/*out*/ UniqueCERTCertList& builtChain,
|
2016-01-13 23:50:42 +03:00
|
|
|
/*optional*/ const Flags flags,
|
2014-07-19 09:30:51 +04:00
|
|
|
/*optional*/ const SECItem* stapledOCSPResponseSECItem,
|
2016-08-11 13:41:50 +03:00
|
|
|
/*optional*/ const SECItem* sctsFromTLSSECItem,
|
2017-01-12 19:38:48 +03:00
|
|
|
/*optional*/ const OriginAttributes& originAttributes,
|
2014-12-12 10:22:35 +03:00
|
|
|
/*optional out*/ SECOidTag* evOidPolicy,
|
2015-02-25 02:48:05 +03:00
|
|
|
/*optional out*/ OCSPStaplingStatus* ocspStaplingStatus,
|
2015-07-09 09:22:29 +03:00
|
|
|
/*optional out*/ KeySizeStatus* keySizeStatus,
|
2016-01-13 23:50:42 +03:00
|
|
|
/*optional out*/ SHA1ModeResult* sha1ModeResult,
|
2016-08-11 13:41:50 +03:00
|
|
|
/*optional out*/ PinningTelemetryInfo* pinningTelemetryInfo,
|
|
|
|
/*optional out*/ CertificateTransparencyInfo* ctInfo)
|
2014-02-10 23:41:12 +04:00
|
|
|
{
|
2015-06-04 01:25:57 +03:00
|
|
|
MOZ_LOG(gCertVerifierLog, LogLevel::Debug, ("Top of VerifyCert\n"));
|
2014-02-24 10:15:53 +04:00
|
|
|
|
2017-01-02 09:11:30 +03:00
|
|
|
MOZ_ASSERT(cert);
|
|
|
|
MOZ_ASSERT(usage == certificateUsageSSLServer || !(flags & FLAG_MUST_BE_EV));
|
|
|
|
MOZ_ASSERT(usage == certificateUsageSSLServer || !keySizeStatus);
|
|
|
|
MOZ_ASSERT(usage == certificateUsageSSLServer || !sha1ModeResult);
|
2014-02-24 10:15:53 +04:00
|
|
|
|
2017-06-09 02:10:00 +03:00
|
|
|
if (NS_FAILED(BlockUntilLoadableRootsLoaded())) {
|
|
|
|
return Result::FATAL_ERROR_LIBRARY_FAILURE;
|
|
|
|
}
|
bug 1381154 - remove smartcard monitoring threads r=jcj,mgoodwin
Modified from bug 1248818 comment 11:
Before this patch, if a user had a smart card (PKCS#11 device) with removable
slots, Firefox would launch a thread for each module and loop, calling
SECMOD_WaitForAnyTokenEvent to be alerted to any insertions/removals. At
shutdown, we would call SECMOD_CancelWait, which would cancel any waiting
threads. However, since that involved calling 3rd party code, we really had no
idea if these modules were behaving correctly (and, indeed, they often weren't,
judging by the shutdown crashes we were getting).
The real solution is to stop relying on PKCS#11, but since that's unlikely in
the near future, the next best thing would be to load these modules in a child
process. That way, misbehaving modules don't cause Firefox to hang/crash/etc.
That's a lot of engineering work, though, so what this patch does is avoids the
issue by never calling SECMOD_WaitForAnyTokenEvent (and thus we never have to
call SECMOD_CancelWait, etc.). Instead, every time Firefox performs an operation
that may be affected by a newly added or removed smart card, it first has NSS
refresh its view of any removable slots. This is similar to how we ensure the
loadable roots module has been loaded (see bug 1372656).
MozReview-Commit-ID: JpmLdV7Vvor
--HG--
extra : rebase_source : d3503d19fa9297106d661a017a38c30969fa39b4
2017-09-29 00:27:21 +03:00
|
|
|
if (NS_FAILED(CheckForSmartCardChanges())) {
|
|
|
|
return Result::FATAL_ERROR_LIBRARY_FAILURE;
|
|
|
|
}
|
2017-06-09 02:10:00 +03:00
|
|
|
|
2014-02-24 10:15:53 +04:00
|
|
|
if (evOidPolicy) {
|
|
|
|
*evOidPolicy = SEC_OID_UNKNOWN;
|
|
|
|
}
|
2014-12-12 10:22:35 +03:00
|
|
|
if (ocspStaplingStatus) {
|
|
|
|
if (usage != certificateUsageSSLServer) {
|
2016-10-10 10:44:41 +03:00
|
|
|
return Result::FATAL_ERROR_INVALID_ARGS;
|
2014-12-12 10:22:35 +03:00
|
|
|
}
|
|
|
|
*ocspStaplingStatus = OCSP_STAPLING_NEVER_CHECKED;
|
|
|
|
}
|
2014-02-24 10:15:53 +04:00
|
|
|
|
2015-02-25 02:48:05 +03:00
|
|
|
if (keySizeStatus) {
|
|
|
|
if (usage != certificateUsageSSLServer) {
|
2016-10-10 10:44:41 +03:00
|
|
|
return Result::FATAL_ERROR_INVALID_ARGS;
|
2015-02-25 02:48:05 +03:00
|
|
|
}
|
|
|
|
*keySizeStatus = KeySizeStatus::NeverChecked;
|
|
|
|
}
|
|
|
|
|
2016-01-13 23:50:42 +03:00
|
|
|
if (sha1ModeResult) {
|
2015-07-09 09:22:29 +03:00
|
|
|
if (usage != certificateUsageSSLServer) {
|
2016-10-10 10:44:41 +03:00
|
|
|
return Result::FATAL_ERROR_INVALID_ARGS;
|
2015-07-09 09:22:29 +03:00
|
|
|
}
|
2016-01-13 23:50:42 +03:00
|
|
|
*sha1ModeResult = SHA1ModeResult::NeverChecked;
|
2015-07-09 09:22:29 +03:00
|
|
|
}
|
|
|
|
|
2014-02-24 10:15:53 +04:00
|
|
|
if (!cert ||
|
|
|
|
(usage != certificateUsageSSLServer && (flags & FLAG_MUST_BE_EV))) {
|
2016-10-10 10:44:41 +03:00
|
|
|
return Result::FATAL_ERROR_INVALID_ARGS;
|
2014-02-24 10:15:53 +04:00
|
|
|
}
|
|
|
|
|
2014-07-31 23:17:31 +04:00
|
|
|
Input certDER;
|
2016-10-10 10:44:41 +03:00
|
|
|
Result rv = certDER.Init(cert->derCert.data, cert->derCert.len);
|
2014-07-19 09:30:51 +04:00
|
|
|
if (rv != Success) {
|
2016-10-10 10:44:41 +03:00
|
|
|
return rv;
|
2014-07-19 09:30:51 +04:00
|
|
|
}
|
|
|
|
|
2015-05-28 23:29:13 +03:00
|
|
|
// We configure the OCSP fetching modes separately for EV and non-EV
|
|
|
|
// verifications.
|
|
|
|
NSSCertDBTrustDomain::OCSPFetching defaultOCSPFetching
|
|
|
|
= (mOCSPDownloadConfig == ocspOff) ||
|
|
|
|
(mOCSPDownloadConfig == ocspEVOnly) ||
|
2014-02-24 10:15:53 +04:00
|
|
|
(flags & FLAG_LOCAL_ONLY) ? NSSCertDBTrustDomain::NeverFetchOCSP
|
|
|
|
: !mOCSPStrict ? NSSCertDBTrustDomain::FetchOCSPForDVSoftFail
|
|
|
|
: NSSCertDBTrustDomain::FetchOCSPForDVHardFail;
|
|
|
|
|
2015-01-23 08:17:00 +03:00
|
|
|
OcspGetConfig ocspGETConfig = mOCSPGETEnabled ? ocspGetEnabled
|
|
|
|
: ocspGetDisabled;
|
2014-02-10 23:41:12 +04:00
|
|
|
|
2014-07-31 23:17:31 +04:00
|
|
|
Input stapledOCSPResponseInput;
|
|
|
|
const Input* stapledOCSPResponse = nullptr;
|
2014-07-19 09:30:51 +04:00
|
|
|
if (stapledOCSPResponseSECItem) {
|
2014-07-31 23:17:31 +04:00
|
|
|
rv = stapledOCSPResponseInput.Init(stapledOCSPResponseSECItem->data,
|
|
|
|
stapledOCSPResponseSECItem->len);
|
2014-07-19 09:30:51 +04:00
|
|
|
if (rv != Success) {
|
|
|
|
// The stapled OCSP response was too big.
|
2016-10-10 10:44:41 +03:00
|
|
|
return Result::ERROR_OCSP_MALFORMED_RESPONSE;
|
2014-07-19 09:30:51 +04:00
|
|
|
}
|
2014-07-31 23:17:31 +04:00
|
|
|
stapledOCSPResponse = &stapledOCSPResponseInput;
|
2014-07-19 09:30:51 +04:00
|
|
|
}
|
2014-02-10 23:41:12 +04:00
|
|
|
|
2016-08-11 13:41:50 +03:00
|
|
|
Input sctsFromTLSInput;
|
|
|
|
if (sctsFromTLSSECItem) {
|
|
|
|
rv = sctsFromTLSInput.Init(sctsFromTLSSECItem->data,
|
|
|
|
sctsFromTLSSECItem->len);
|
|
|
|
// Silently discard the error of the extension being too big,
|
|
|
|
// do not fail the verification.
|
|
|
|
MOZ_ASSERT(rv == Success);
|
|
|
|
}
|
|
|
|
|
2014-02-10 23:41:12 +04:00
|
|
|
switch (usage) {
|
|
|
|
case certificateUsageSSLClient: {
|
|
|
|
// XXX: We don't really have a trust bit for SSL client authentication so
|
|
|
|
// just use trustEmail as it is the closest alternative.
|
2015-07-09 09:22:29 +03:00
|
|
|
NSSCertDBTrustDomain trustDomain(trustEmail, defaultOCSPFetching,
|
|
|
|
mOCSPCache, pinArg, ocspGETConfig,
|
2017-04-01 01:21:40 +03:00
|
|
|
mOCSPTimeoutSoft, mOCSPTimeoutHard,
|
2015-04-07 02:10:28 +03:00
|
|
|
mCertShortLifetimeInDays,
|
2015-07-09 09:22:29 +03:00
|
|
|
pinningDisabled, MIN_RSA_BITS_WEAK,
|
2015-06-29 23:19:00 +03:00
|
|
|
ValidityCheckingMode::CheckingOff,
|
2016-05-06 02:11:11 +03:00
|
|
|
SHA1Mode::Allowed,
|
|
|
|
NetscapeStepUpPolicy::NeverMatch,
|
2016-11-14 13:26:15 +03:00
|
|
|
originAttributes,
|
2017-09-18 20:28:58 +03:00
|
|
|
builtChain, nullptr, nullptr);
|
2014-07-19 09:30:51 +04:00
|
|
|
rv = BuildCertChain(trustDomain, certDER, time,
|
2014-06-19 11:13:20 +04:00
|
|
|
EndEntityOrCA::MustBeEndEntity,
|
|
|
|
KeyUsage::digitalSignature,
|
2014-05-14 12:02:34 +04:00
|
|
|
KeyPurposeId::id_kp_clientAuth,
|
2014-07-07 02:55:38 +04:00
|
|
|
CertPolicyId::anyPolicy, stapledOCSPResponse);
|
2014-02-10 23:41:12 +04:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case certificateUsageSSLServer: {
|
|
|
|
// TODO: When verifying a certificate in an SSL handshake, we should
|
|
|
|
// restrict the acceptable key usage based on the key exchange method
|
|
|
|
// chosen by the server.
|
2014-02-24 10:15:53 +04:00
|
|
|
|
2016-01-13 23:50:42 +03:00
|
|
|
// These configurations are in order of most restrictive to least
|
|
|
|
// restrictive. This enables us to gather telemetry on the expected
|
|
|
|
// results of setting the default policy to a particular configuration.
|
|
|
|
SHA1Mode sha1ModeConfigurations[] = {
|
|
|
|
SHA1Mode::Forbidden,
|
|
|
|
SHA1Mode::ImportedRoot,
|
2016-09-15 01:11:15 +03:00
|
|
|
SHA1Mode::ImportedRootOrBefore2016,
|
2016-01-13 23:50:42 +03:00
|
|
|
SHA1Mode::Allowed,
|
2015-07-09 09:22:29 +03:00
|
|
|
};
|
|
|
|
|
2016-01-13 23:50:42 +03:00
|
|
|
SHA1ModeResult sha1ModeResults[] = {
|
|
|
|
SHA1ModeResult::SucceededWithoutSHA1,
|
|
|
|
SHA1ModeResult::SucceededWithImportedRoot,
|
2016-09-15 01:11:15 +03:00
|
|
|
SHA1ModeResult::SucceededWithImportedRootOrSHA1Before2016,
|
2016-01-13 23:50:42 +03:00
|
|
|
SHA1ModeResult::SucceededWithSHA1,
|
2015-07-09 09:22:29 +03:00
|
|
|
};
|
|
|
|
|
2016-01-13 23:50:42 +03:00
|
|
|
size_t sha1ModeConfigurationsCount = MOZ_ARRAY_LENGTH(sha1ModeConfigurations);
|
2015-07-09 09:22:29 +03:00
|
|
|
|
2016-01-13 23:50:42 +03:00
|
|
|
static_assert(MOZ_ARRAY_LENGTH(sha1ModeConfigurations) ==
|
|
|
|
MOZ_ARRAY_LENGTH(sha1ModeResults),
|
2015-07-09 09:22:29 +03:00
|
|
|
"digestAlgorithm array lengths differ");
|
|
|
|
|
|
|
|
rv = Result::ERROR_UNKNOWN_ERROR;
|
|
|
|
|
2014-02-24 10:15:53 +04:00
|
|
|
// Try to validate for EV first.
|
2015-05-28 23:29:13 +03:00
|
|
|
NSSCertDBTrustDomain::OCSPFetching evOCSPFetching
|
|
|
|
= (mOCSPDownloadConfig == ocspOff) ||
|
|
|
|
(flags & FLAG_LOCAL_ONLY) ? NSSCertDBTrustDomain::LocalOnlyOCSPForEV
|
|
|
|
: NSSCertDBTrustDomain::FetchOCSPForEV;
|
|
|
|
|
2014-05-16 05:59:52 +04:00
|
|
|
CertPolicyId evPolicy;
|
|
|
|
SECOidTag evPolicyOidTag;
|
2016-12-14 15:10:25 +03:00
|
|
|
bool foundEVPolicy = GetFirstEVPolicy(*cert, evPolicy, evPolicyOidTag);
|
2016-01-13 23:50:42 +03:00
|
|
|
for (size_t i = 0;
|
2016-12-14 15:10:25 +03:00
|
|
|
i < sha1ModeConfigurationsCount && rv != Success && foundEVPolicy;
|
2015-07-09 09:22:29 +03:00
|
|
|
i++) {
|
2016-01-13 23:50:42 +03:00
|
|
|
// Don't attempt verification if the SHA1 mode set by preferences
|
|
|
|
// (mSHA1Mode) is more restrictive than the SHA1 mode option we're on.
|
|
|
|
// (To put it another way, only attempt verification if the SHA1 mode
|
|
|
|
// option we're on is as restrictive or more restrictive than
|
|
|
|
// mSHA1Mode.) This allows us to gather telemetry information while
|
|
|
|
// still enforcing the mode set by preferences.
|
|
|
|
if (SHA1ModeMoreRestrictiveThanGivenMode(sha1ModeConfigurations[i])) {
|
|
|
|
continue;
|
|
|
|
}
|
2016-03-28 22:52:40 +03:00
|
|
|
|
|
|
|
// Because of the try-strict and fallback approach, we have to clear any
|
|
|
|
// previously noted telemetry information
|
|
|
|
if (pinningTelemetryInfo) {
|
|
|
|
pinningTelemetryInfo->Reset();
|
|
|
|
}
|
|
|
|
|
2014-02-24 10:15:53 +04:00
|
|
|
NSSCertDBTrustDomain
|
2015-05-28 23:29:13 +03:00
|
|
|
trustDomain(trustSSL, evOCSPFetching,
|
2015-04-07 02:10:28 +03:00
|
|
|
mOCSPCache, pinArg, ocspGETConfig,
|
2017-04-01 01:21:40 +03:00
|
|
|
mOCSPTimeoutSoft, mOCSPTimeoutHard,
|
2015-04-07 02:10:28 +03:00
|
|
|
mCertShortLifetimeInDays, mPinningMode, MIN_RSA_BITS,
|
2015-07-09 09:22:29 +03:00
|
|
|
ValidityCheckingMode::CheckForEV,
|
2016-05-06 02:11:11 +03:00
|
|
|
sha1ModeConfigurations[i], mNetscapeStepUpPolicy,
|
2017-09-18 20:28:58 +03:00
|
|
|
originAttributes, builtChain, pinningTelemetryInfo,
|
|
|
|
hostname);
|
2014-07-19 09:30:51 +04:00
|
|
|
rv = BuildCertChainForOneKeyUsage(trustDomain, certDER, time,
|
2014-06-19 11:13:20 +04:00
|
|
|
KeyUsage::digitalSignature,// (EC)DHE
|
|
|
|
KeyUsage::keyEncipherment, // RSA
|
|
|
|
KeyUsage::keyAgreement, // (EC)DH
|
2014-05-14 12:02:34 +04:00
|
|
|
KeyPurposeId::id_kp_serverAuth,
|
2014-12-12 10:22:35 +03:00
|
|
|
evPolicy, stapledOCSPResponse,
|
|
|
|
ocspStaplingStatus);
|
2016-01-13 23:50:42 +03:00
|
|
|
if (rv == Success &&
|
|
|
|
sha1ModeConfigurations[i] == SHA1Mode::ImportedRoot) {
|
|
|
|
bool isBuiltInRoot = false;
|
|
|
|
rv = IsCertChainRootBuiltInRoot(builtChain, isBuiltInRoot);
|
|
|
|
if (rv != Success) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (isBuiltInRoot) {
|
|
|
|
rv = Result::ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED;
|
|
|
|
}
|
|
|
|
}
|
2014-07-18 22:48:49 +04:00
|
|
|
if (rv == Success) {
|
2015-07-09 09:22:29 +03:00
|
|
|
MOZ_LOG(gCertVerifierLog, LogLevel::Debug,
|
2016-12-16 06:16:31 +03:00
|
|
|
("cert is EV with status %i\n", static_cast<int>(sha1ModeResults[i])));
|
2014-02-24 10:15:53 +04:00
|
|
|
if (evOidPolicy) {
|
2014-05-16 05:59:52 +04:00
|
|
|
*evOidPolicy = evPolicyOidTag;
|
2014-02-24 10:15:53 +04:00
|
|
|
}
|
2016-01-13 23:50:42 +03:00
|
|
|
if (sha1ModeResult) {
|
|
|
|
*sha1ModeResult = sha1ModeResults[i];
|
2015-07-09 09:22:29 +03:00
|
|
|
}
|
2017-01-09 09:22:28 +03:00
|
|
|
rv = VerifyCertificateTransparencyPolicy(trustDomain, builtChain,
|
|
|
|
sctsFromTLSInput, time,
|
|
|
|
ctInfo);
|
2016-08-11 13:41:50 +03:00
|
|
|
if (rv != Success) {
|
|
|
|
break;
|
|
|
|
}
|
2014-02-24 10:15:53 +04:00
|
|
|
}
|
|
|
|
}
|
2015-07-09 09:22:29 +03:00
|
|
|
if (rv == Success) {
|
|
|
|
break;
|
|
|
|
}
|
2014-02-24 10:15:53 +04:00
|
|
|
|
|
|
|
if (flags & FLAG_MUST_BE_EV) {
|
2014-07-18 22:48:49 +04:00
|
|
|
rv = Result::ERROR_POLICY_VALIDATION_FAILED;
|
2014-02-24 10:15:53 +04:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Now try non-EV.
|
2015-07-09 09:22:29 +03:00
|
|
|
unsigned int keySizeOptions[] = {
|
|
|
|
MIN_RSA_BITS,
|
|
|
|
MIN_RSA_BITS_WEAK
|
|
|
|
};
|
2015-02-25 02:48:05 +03:00
|
|
|
|
2015-07-09 09:22:29 +03:00
|
|
|
KeySizeStatus keySizeStatuses[] = {
|
|
|
|
KeySizeStatus::LargeMinimumSucceeded,
|
|
|
|
KeySizeStatus::CompatibilityRisk
|
|
|
|
};
|
|
|
|
|
|
|
|
static_assert(MOZ_ARRAY_LENGTH(keySizeOptions) ==
|
|
|
|
MOZ_ARRAY_LENGTH(keySizeStatuses),
|
|
|
|
"keySize array lengths differ");
|
|
|
|
|
|
|
|
size_t keySizeOptionsCount = MOZ_ARRAY_LENGTH(keySizeStatuses);
|
|
|
|
|
2016-01-13 23:50:42 +03:00
|
|
|
for (size_t i = 0; i < keySizeOptionsCount && rv != Success; i++) {
|
|
|
|
for (size_t j = 0; j < sha1ModeConfigurationsCount && rv != Success;
|
|
|
|
j++) {
|
|
|
|
// Don't attempt verification if the SHA1 mode set by preferences
|
|
|
|
// (mSHA1Mode) is more restrictive than the SHA1 mode option we're on.
|
|
|
|
// (To put it another way, only attempt verification if the SHA1 mode
|
|
|
|
// option we're on is as restrictive or more restrictive than
|
|
|
|
// mSHA1Mode.) This allows us to gather telemetry information while
|
|
|
|
// still enforcing the mode set by preferences.
|
|
|
|
if (SHA1ModeMoreRestrictiveThanGivenMode(sha1ModeConfigurations[j])) {
|
2015-09-11 21:52:30 +03:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2016-03-28 22:52:40 +03:00
|
|
|
// invalidate any telemetry info relating to failed chains
|
|
|
|
if (pinningTelemetryInfo) {
|
|
|
|
pinningTelemetryInfo->Reset();
|
|
|
|
}
|
|
|
|
|
2015-07-09 09:22:29 +03:00
|
|
|
NSSCertDBTrustDomain trustDomain(trustSSL, defaultOCSPFetching,
|
2015-05-28 23:29:13 +03:00
|
|
|
mOCSPCache, pinArg, ocspGETConfig,
|
2017-04-01 01:21:40 +03:00
|
|
|
mOCSPTimeoutSoft, mOCSPTimeoutHard,
|
2015-04-07 02:10:28 +03:00
|
|
|
mCertShortLifetimeInDays,
|
2015-07-09 09:22:29 +03:00
|
|
|
mPinningMode, keySizeOptions[i],
|
2015-06-29 23:19:00 +03:00
|
|
|
ValidityCheckingMode::CheckingOff,
|
2016-01-13 23:50:42 +03:00
|
|
|
sha1ModeConfigurations[j],
|
2016-10-04 11:49:55 +03:00
|
|
|
mNetscapeStepUpPolicy,
|
2016-11-14 13:26:15 +03:00
|
|
|
originAttributes, builtChain,
|
2017-09-18 20:28:58 +03:00
|
|
|
pinningTelemetryInfo, hostname);
|
2015-07-09 09:22:29 +03:00
|
|
|
rv = BuildCertChainForOneKeyUsage(trustDomain, certDER, time,
|
|
|
|
KeyUsage::digitalSignature,//(EC)DHE
|
|
|
|
KeyUsage::keyEncipherment,//RSA
|
|
|
|
KeyUsage::keyAgreement,//(EC)DH
|
|
|
|
KeyPurposeId::id_kp_serverAuth,
|
|
|
|
CertPolicyId::anyPolicy,
|
|
|
|
stapledOCSPResponse,
|
|
|
|
ocspStaplingStatus);
|
2016-01-13 23:50:42 +03:00
|
|
|
if (rv == Success &&
|
|
|
|
sha1ModeConfigurations[j] == SHA1Mode::ImportedRoot) {
|
|
|
|
bool isBuiltInRoot = false;
|
|
|
|
rv = IsCertChainRootBuiltInRoot(builtChain, isBuiltInRoot);
|
|
|
|
if (rv != Success) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (isBuiltInRoot) {
|
|
|
|
rv = Result::ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED;
|
|
|
|
}
|
|
|
|
}
|
2015-07-09 09:22:29 +03:00
|
|
|
if (rv == Success) {
|
|
|
|
if (keySizeStatus) {
|
|
|
|
*keySizeStatus = keySizeStatuses[i];
|
|
|
|
}
|
2016-01-13 23:50:42 +03:00
|
|
|
if (sha1ModeResult) {
|
|
|
|
*sha1ModeResult = sha1ModeResults[j];
|
2015-07-09 09:22:29 +03:00
|
|
|
}
|
2017-01-09 09:22:28 +03:00
|
|
|
rv = VerifyCertificateTransparencyPolicy(trustDomain, builtChain,
|
|
|
|
sctsFromTLSInput, time,
|
|
|
|
ctInfo);
|
2016-08-11 13:41:50 +03:00
|
|
|
if (rv != Success) {
|
|
|
|
break;
|
|
|
|
}
|
2015-07-09 09:22:29 +03:00
|
|
|
}
|
2015-02-25 02:48:05 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-07-09 09:22:29 +03:00
|
|
|
if (rv == Success) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (keySizeStatus) {
|
|
|
|
*keySizeStatus = KeySizeStatus::AlreadyBad;
|
|
|
|
}
|
2016-09-15 01:11:15 +03:00
|
|
|
// The telemetry probe CERT_CHAIN_SHA1_POLICY_STATUS gives us feedback on
|
|
|
|
// the result of setting a specific policy. However, we don't want noise
|
2017-01-11 01:48:30 +03:00
|
|
|
// from users who have manually set the policy to something other than the
|
|
|
|
// default, so we only collect for ImportedRoot (which is the default).
|
|
|
|
if (sha1ModeResult && mSHA1Mode == SHA1Mode::ImportedRoot) {
|
2016-01-13 23:50:42 +03:00
|
|
|
*sha1ModeResult = SHA1ModeResult::Failed;
|
2015-07-09 09:22:29 +03:00
|
|
|
}
|
|
|
|
|
2014-02-10 23:41:12 +04:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case certificateUsageSSLCA: {
|
2015-05-28 23:29:13 +03:00
|
|
|
NSSCertDBTrustDomain trustDomain(trustSSL, defaultOCSPFetching,
|
|
|
|
mOCSPCache, pinArg, ocspGETConfig,
|
2017-04-01 01:21:40 +03:00
|
|
|
mOCSPTimeoutSoft, mOCSPTimeoutHard,
|
2015-04-07 02:10:28 +03:00
|
|
|
mCertShortLifetimeInDays,
|
|
|
|
pinningDisabled, MIN_RSA_BITS_WEAK,
|
2015-06-29 23:19:00 +03:00
|
|
|
ValidityCheckingMode::CheckingOff,
|
2016-09-15 01:11:15 +03:00
|
|
|
SHA1Mode::Allowed, mNetscapeStepUpPolicy,
|
2017-09-18 20:28:58 +03:00
|
|
|
originAttributes, builtChain, nullptr,
|
|
|
|
nullptr);
|
2014-07-19 09:30:51 +04:00
|
|
|
rv = BuildCertChain(trustDomain, certDER, time,
|
2014-07-07 02:55:38 +04:00
|
|
|
EndEntityOrCA::MustBeCA, KeyUsage::keyCertSign,
|
2014-06-19 11:13:20 +04:00
|
|
|
KeyPurposeId::id_kp_serverAuth,
|
2014-07-07 02:55:38 +04:00
|
|
|
CertPolicyId::anyPolicy, stapledOCSPResponse);
|
2014-02-10 23:41:12 +04:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case certificateUsageEmailSigner: {
|
2015-05-28 23:29:13 +03:00
|
|
|
NSSCertDBTrustDomain trustDomain(trustEmail, defaultOCSPFetching,
|
|
|
|
mOCSPCache, pinArg, ocspGETConfig,
|
2017-04-01 01:21:40 +03:00
|
|
|
mOCSPTimeoutSoft, mOCSPTimeoutHard,
|
2015-04-07 02:10:28 +03:00
|
|
|
mCertShortLifetimeInDays,
|
|
|
|
pinningDisabled, MIN_RSA_BITS_WEAK,
|
2015-06-29 23:19:00 +03:00
|
|
|
ValidityCheckingMode::CheckingOff,
|
2016-05-06 02:11:11 +03:00
|
|
|
SHA1Mode::Allowed,
|
|
|
|
NetscapeStepUpPolicy::NeverMatch,
|
2017-09-18 20:28:58 +03:00
|
|
|
originAttributes, builtChain, nullptr,
|
|
|
|
nullptr);
|
2014-07-19 09:30:51 +04:00
|
|
|
rv = BuildCertChain(trustDomain, certDER, time,
|
2014-06-19 11:13:20 +04:00
|
|
|
EndEntityOrCA::MustBeEndEntity,
|
|
|
|
KeyUsage::digitalSignature,
|
2014-05-16 05:59:52 +04:00
|
|
|
KeyPurposeId::id_kp_emailProtection,
|
2014-07-07 02:55:38 +04:00
|
|
|
CertPolicyId::anyPolicy, stapledOCSPResponse);
|
2014-12-18 05:31:00 +03:00
|
|
|
if (rv == Result::ERROR_INADEQUATE_KEY_USAGE) {
|
|
|
|
rv = BuildCertChain(trustDomain, certDER, time,
|
|
|
|
EndEntityOrCA::MustBeEndEntity,
|
|
|
|
KeyUsage::nonRepudiation,
|
|
|
|
KeyPurposeId::id_kp_emailProtection,
|
|
|
|
CertPolicyId::anyPolicy, stapledOCSPResponse);
|
|
|
|
}
|
2014-02-10 23:41:12 +04:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case certificateUsageEmailRecipient: {
|
|
|
|
// TODO: The higher level S/MIME processing should pass in which key
|
|
|
|
// usage it is trying to verify for, and base its algorithm choices
|
|
|
|
// based on the result of the verification(s).
|
2015-05-28 23:29:13 +03:00
|
|
|
NSSCertDBTrustDomain trustDomain(trustEmail, defaultOCSPFetching,
|
|
|
|
mOCSPCache, pinArg, ocspGETConfig,
|
2017-04-01 01:21:40 +03:00
|
|
|
mOCSPTimeoutSoft, mOCSPTimeoutHard,
|
2015-04-07 02:10:28 +03:00
|
|
|
mCertShortLifetimeInDays,
|
|
|
|
pinningDisabled, MIN_RSA_BITS_WEAK,
|
2015-06-29 23:19:00 +03:00
|
|
|
ValidityCheckingMode::CheckingOff,
|
2016-05-06 02:11:11 +03:00
|
|
|
SHA1Mode::Allowed,
|
|
|
|
NetscapeStepUpPolicy::NeverMatch,
|
2017-09-18 20:28:58 +03:00
|
|
|
originAttributes, builtChain, nullptr,
|
|
|
|
nullptr);
|
2014-07-19 09:30:51 +04:00
|
|
|
rv = BuildCertChain(trustDomain, certDER, time,
|
2014-06-19 11:13:20 +04:00
|
|
|
EndEntityOrCA::MustBeEndEntity,
|
|
|
|
KeyUsage::keyEncipherment, // RSA
|
|
|
|
KeyPurposeId::id_kp_emailProtection,
|
2014-07-07 02:55:38 +04:00
|
|
|
CertPolicyId::anyPolicy, stapledOCSPResponse);
|
2014-07-18 22:48:49 +04:00
|
|
|
if (rv == Result::ERROR_INADEQUATE_KEY_USAGE) {
|
2014-07-19 09:30:51 +04:00
|
|
|
rv = BuildCertChain(trustDomain, certDER, time,
|
2014-06-19 11:13:20 +04:00
|
|
|
EndEntityOrCA::MustBeEndEntity,
|
|
|
|
KeyUsage::keyAgreement, // ECDH/DH
|
|
|
|
KeyPurposeId::id_kp_emailProtection,
|
2014-07-07 02:55:38 +04:00
|
|
|
CertPolicyId::anyPolicy, stapledOCSPResponse);
|
2014-06-19 11:13:20 +04:00
|
|
|
}
|
2014-02-10 23:41:12 +04:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
default:
|
2014-07-18 22:48:49 +04:00
|
|
|
rv = Result::FATAL_ERROR_INVALID_ARGS;
|
2014-02-10 23:41:12 +04:00
|
|
|
}
|
|
|
|
|
2014-07-18 22:48:49 +04:00
|
|
|
if (rv != Success) {
|
2016-10-10 10:44:41 +03:00
|
|
|
return rv;
|
2014-07-18 22:48:49 +04:00
|
|
|
}
|
|
|
|
|
2016-10-10 10:44:41 +03:00
|
|
|
return Success;
|
2014-02-10 23:41:12 +04:00
|
|
|
}
|
|
|
|
|
2016-10-10 10:44:41 +03:00
|
|
|
Result
|
2016-04-20 11:14:22 +03:00
|
|
|
CertVerifier::VerifySSLServerCert(const UniqueCERTCertificate& peerCert,
|
2013-09-28 06:53:36 +04:00
|
|
|
/*optional*/ const SECItem* stapledOCSPResponse,
|
2016-08-11 13:41:50 +03:00
|
|
|
/*optional*/ const SECItem* sctsFromTLS,
|
2014-08-02 19:49:12 +04:00
|
|
|
Time time,
|
2013-07-09 03:30:59 +04:00
|
|
|
/*optional*/ void* pinarg,
|
2017-06-03 08:35:51 +03:00
|
|
|
const nsACString& hostname,
|
2016-05-06 00:56:36 +03:00
|
|
|
/*out*/ UniqueCERTCertList& builtChain,
|
2016-01-13 23:50:42 +03:00
|
|
|
/*optional*/ bool saveIntermediatesInPermanentDatabase,
|
|
|
|
/*optional*/ Flags flags,
|
2017-01-12 19:38:48 +03:00
|
|
|
/*optional*/ const OriginAttributes& originAttributes,
|
2014-12-12 10:22:35 +03:00
|
|
|
/*optional out*/ SECOidTag* evOidPolicy,
|
2015-02-25 02:48:05 +03:00
|
|
|
/*optional out*/ OCSPStaplingStatus* ocspStaplingStatus,
|
2015-07-09 09:22:29 +03:00
|
|
|
/*optional out*/ KeySizeStatus* keySizeStatus,
|
2016-01-13 23:50:42 +03:00
|
|
|
/*optional out*/ SHA1ModeResult* sha1ModeResult,
|
2016-08-11 13:41:50 +03:00
|
|
|
/*optional out*/ PinningTelemetryInfo* pinningTelemetryInfo,
|
|
|
|
/*optional out*/ CertificateTransparencyInfo* ctInfo)
|
2013-07-09 03:30:59 +04:00
|
|
|
{
|
2017-01-02 09:11:30 +03:00
|
|
|
MOZ_ASSERT(peerCert);
|
|
|
|
// XXX: MOZ_ASSERT(pinarg);
|
2017-06-03 08:35:51 +03:00
|
|
|
MOZ_ASSERT(!hostname.IsEmpty());
|
2013-07-09 03:30:59 +04:00
|
|
|
|
|
|
|
if (evOidPolicy) {
|
|
|
|
*evOidPolicy = SEC_OID_UNKNOWN;
|
|
|
|
}
|
|
|
|
|
2017-06-03 08:35:51 +03:00
|
|
|
if (hostname.IsEmpty()) {
|
2016-10-10 10:44:41 +03:00
|
|
|
return Result::ERROR_BAD_CERT_DOMAIN;
|
2013-07-09 03:30:59 +04:00
|
|
|
}
|
|
|
|
|
2014-10-29 01:28:38 +03:00
|
|
|
// CreateCertErrorRunnable assumes that CheckCertHostname is only called
|
2014-02-23 07:08:06 +04:00
|
|
|
// if VerifyCert succeeded.
|
2016-10-10 10:44:41 +03:00
|
|
|
Result rv = VerifyCert(peerCert.get(), certificateUsageSSLServer, time,
|
2017-06-03 08:35:51 +03:00
|
|
|
pinarg, PromiseFlatCString(hostname).get(), builtChain,
|
2017-09-18 20:28:58 +03:00
|
|
|
flags, stapledOCSPResponse, sctsFromTLS,
|
|
|
|
originAttributes, evOidPolicy, ocspStaplingStatus,
|
|
|
|
keySizeStatus, sha1ModeResult, pinningTelemetryInfo,
|
|
|
|
ctInfo);
|
2016-10-10 10:44:41 +03:00
|
|
|
if (rv != Success) {
|
2013-07-09 03:30:59 +04:00
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
|
2014-10-29 01:28:38 +03:00
|
|
|
Input peerCertInput;
|
2016-10-10 10:44:41 +03:00
|
|
|
rv = peerCertInput.Init(peerCert->derCert.data, peerCert->derCert.len);
|
|
|
|
if (rv != Success) {
|
|
|
|
return rv;
|
2014-10-29 01:28:38 +03:00
|
|
|
}
|
2015-11-13 19:49:08 +03:00
|
|
|
|
|
|
|
Input stapledOCSPResponseInput;
|
|
|
|
Input* responseInputPtr = nullptr;
|
|
|
|
if (stapledOCSPResponse) {
|
2016-10-10 10:44:41 +03:00
|
|
|
rv = stapledOCSPResponseInput.Init(stapledOCSPResponse->data,
|
|
|
|
stapledOCSPResponse->len);
|
|
|
|
if (rv != Success) {
|
2015-11-13 19:49:08 +03:00
|
|
|
// The stapled OCSP response was too big.
|
2016-10-10 10:44:41 +03:00
|
|
|
return Result::ERROR_OCSP_MALFORMED_RESPONSE;
|
2015-11-13 19:49:08 +03:00
|
|
|
}
|
|
|
|
responseInputPtr = &stapledOCSPResponseInput;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!(flags & FLAG_TLS_IGNORE_STATUS_REQUEST)) {
|
2016-10-10 10:44:41 +03:00
|
|
|
rv = CheckTLSFeaturesAreSatisfied(peerCertInput, responseInputPtr);
|
|
|
|
if (rv != Success) {
|
|
|
|
return rv;
|
2015-11-13 19:49:08 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-10-29 01:28:38 +03:00
|
|
|
Input hostnameInput;
|
2017-06-03 08:35:51 +03:00
|
|
|
rv = hostnameInput.Init(
|
|
|
|
BitwiseCast<const uint8_t*, const char*>(hostname.BeginReading()),
|
|
|
|
hostname.Length());
|
2016-10-10 10:44:41 +03:00
|
|
|
if (rv != Success) {
|
|
|
|
return Result::FATAL_ERROR_INVALID_ARGS;
|
2014-10-29 01:28:38 +03:00
|
|
|
}
|
2016-02-09 21:14:27 +03:00
|
|
|
bool isBuiltInRoot;
|
2016-10-10 10:44:41 +03:00
|
|
|
rv = IsCertChainRootBuiltInRoot(builtChain, isBuiltInRoot);
|
|
|
|
if (rv != Success) {
|
|
|
|
return rv;
|
2016-02-09 21:14:27 +03:00
|
|
|
}
|
|
|
|
BRNameMatchingPolicy nameMatchingPolicy(
|
|
|
|
isBuiltInRoot ? mNameMatchingMode
|
|
|
|
: BRNameMatchingPolicy::Mode::DoNotEnforce);
|
2016-10-10 10:44:41 +03:00
|
|
|
rv = CheckCertHostname(peerCertInput, hostnameInput, nameMatchingPolicy);
|
|
|
|
if (rv != Success) {
|
2015-06-01 23:55:23 +03:00
|
|
|
// Treat malformed name information as a domain mismatch.
|
2016-10-10 10:44:41 +03:00
|
|
|
if (rv == Result::ERROR_BAD_DER) {
|
|
|
|
return Result::ERROR_BAD_CERT_DOMAIN;
|
2015-06-01 23:55:23 +03:00
|
|
|
}
|
2016-10-10 10:44:41 +03:00
|
|
|
|
|
|
|
return rv;
|
2013-07-09 03:30:59 +04:00
|
|
|
}
|
|
|
|
|
2014-07-15 03:43:33 +04:00
|
|
|
if (saveIntermediatesInPermanentDatabase) {
|
2016-01-13 23:50:42 +03:00
|
|
|
SaveIntermediateCerts(builtChain);
|
2013-07-09 03:30:59 +04:00
|
|
|
}
|
|
|
|
|
2016-10-10 10:44:41 +03:00
|
|
|
return Success;
|
2013-07-09 03:30:59 +04:00
|
|
|
}
|
|
|
|
|
2012-10-27 11:11:35 +04:00
|
|
|
} } // namespace mozilla::psm
|