mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-23 04:41:11 +00:00
Bug 1912120 - implement updated certificate transparency policy r=jschanck
This updates the certificate transparency policy based on Chrome's policy, found at https://googlechrome.github.io/CertificateTransparency/ct_policy.html. Both it and the Chrome policy are similar to the Apple policy, found at https://support.apple.com/en-us/103214. Essentially, the policy can be satisfied in two ways, depending on the source of the collected SCTs. For embedded SCTs, at least one must be from a log that was Admissible (Qualified, Usable, or ReadOnly) at the time of the check. There must be SCTs from N distinct logs that were Admissible or Retired at the time of the check, where N depends on the lifetime of the certificate. If the certificate lifetime is less than or equal to 180 days, N is 2. Otherwise, N is 3. Among these SCTs, at least two must be issued from distinct log operators. For SCTs delivered via the TLS handshake or an OCSP response, at least two must be from a log that was Admissible at the time of the check. Among these SCTs, at least two must be issued from distinct log operators. Differential Revision: https://phabricator.services.mozilla.com/D218800
This commit is contained in:
parent
7df2e8b66b
commit
49686e9766
@ -9,7 +9,6 @@
|
||||
#include <stdint.h>
|
||||
|
||||
#include "AppTrustDomain.h"
|
||||
#include "CTDiversityPolicy.h"
|
||||
#include "CTKnownLogs.h"
|
||||
#include "CTLogVerifier.h"
|
||||
#include "ExtendedValidation.h"
|
||||
@ -39,53 +38,6 @@ using namespace mozilla::psm;
|
||||
|
||||
mozilla::LazyLogModule gCertVerifierLog("certverifier");
|
||||
|
||||
// Returns the certificate validity period in calendar months (rounded down).
|
||||
// "extern" to allow unit tests in CTPolicyEnforcerTest.cpp.
|
||||
extern mozilla::pkix::Result GetCertLifetimeInFullMonths(Time certNotBefore,
|
||||
Time certNotAfter,
|
||||
size_t& months) {
|
||||
if (certNotBefore >= certNotAfter) {
|
||||
MOZ_ASSERT_UNREACHABLE("Expected notBefore < notAfter");
|
||||
return mozilla::pkix::Result::FATAL_ERROR_INVALID_ARGS;
|
||||
}
|
||||
uint64_t notBeforeSeconds;
|
||||
Result rv = SecondsSinceEpochFromTime(certNotBefore, ¬BeforeSeconds);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
uint64_t notAfterSeconds;
|
||||
rv = SecondsSinceEpochFromTime(certNotAfter, ¬AfterSeconds);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
// PRTime is microseconds
|
||||
PRTime notBeforePR = static_cast<PRTime>(notBeforeSeconds) * 1000000;
|
||||
PRTime notAfterPR = static_cast<PRTime>(notAfterSeconds) * 1000000;
|
||||
|
||||
PRExplodedTime explodedNotBefore;
|
||||
PRExplodedTime explodedNotAfter;
|
||||
|
||||
PR_ExplodeTime(notBeforePR, PR_LocalTimeParameters, &explodedNotBefore);
|
||||
PR_ExplodeTime(notAfterPR, 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;
|
||||
}
|
||||
|
||||
namespace mozilla {
|
||||
namespace psm {
|
||||
|
||||
@ -98,7 +50,7 @@ static const unsigned int MIN_RSA_BITS_WEAK = 1024;
|
||||
void CertificateTransparencyInfo::Reset() {
|
||||
enabled = false;
|
||||
verifyResult.Reset();
|
||||
policyCompliance = CTPolicyCompliance::Unknown;
|
||||
policyCompliance.reset();
|
||||
}
|
||||
|
||||
CertVerifier::CertVerifier(OcspDownloadConfig odc, OcspStrictConfig osc,
|
||||
@ -268,9 +220,6 @@ void CertVerifier::LoadKnownCTLogs() {
|
||||
|
||||
mCTVerifier->AddLog(std::move(logVerifier));
|
||||
}
|
||||
// TBD: Initialize mCTDiversityPolicy with the CA dependency map
|
||||
// of the known CT logs operators.
|
||||
mCTDiversityPolicy = MakeUnique<CTDiversityPolicy>();
|
||||
}
|
||||
|
||||
Result CertVerifier::VerifyCertificateTransparencyPolicy(
|
||||
@ -324,7 +273,7 @@ Result CertVerifier::VerifyCertificateTransparencyPolicy(
|
||||
if (ctInfo) {
|
||||
CTVerifyResult emptyResult;
|
||||
ctInfo->verifyResult = std::move(emptyResult);
|
||||
ctInfo->policyCompliance = CTPolicyCompliance::NotEnoughScts;
|
||||
ctInfo->policyCompliance.emplace(CTPolicyCompliance::NotEnoughScts);
|
||||
}
|
||||
return Success;
|
||||
}
|
||||
@ -396,30 +345,14 @@ Result CertVerifier::VerifyCertificateTransparencyPolicy(
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
size_t lifetimeInMonths;
|
||||
rv = GetCertLifetimeInFullMonths(notBefore, notAfter, lifetimeInMonths);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
Duration certLifetime(notBefore, notAfter);
|
||||
|
||||
CTLogOperatorList allOperators;
|
||||
GetCTLogOperatorsFromVerifiedSCTList(result.verifiedScts, allOperators);
|
||||
|
||||
CTLogOperatorList dependentOperators;
|
||||
rv = mCTDiversityPolicy->GetDependentOperators(builtChain, allOperators,
|
||||
dependentOperators);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
CTPolicyEnforcer ctPolicyEnforcer;
|
||||
CTPolicyCompliance ctPolicyCompliance;
|
||||
ctPolicyEnforcer.CheckCompliance(result.verifiedScts, lifetimeInMonths,
|
||||
dependentOperators, ctPolicyCompliance);
|
||||
CTPolicyCompliance ctPolicyCompliance =
|
||||
CheckCTPolicyCompliance(result.verifiedScts, certLifetime);
|
||||
|
||||
if (ctInfo) {
|
||||
ctInfo->verifyResult = std::move(result);
|
||||
ctInfo->policyCompliance = ctPolicyCompliance;
|
||||
ctInfo->policyCompliance.emplace(ctPolicyCompliance);
|
||||
}
|
||||
return Success;
|
||||
}
|
||||
|
@ -39,7 +39,6 @@ namespace ct {
|
||||
// dependent headers and force us to export them in moz.build.
|
||||
// Just forward-declare the classes here instead.
|
||||
class MultiLogCTVerifier;
|
||||
class CTDiversityPolicy;
|
||||
|
||||
} // namespace ct
|
||||
} // namespace mozilla
|
||||
@ -105,9 +104,7 @@ class PinningTelemetryInfo {
|
||||
|
||||
class CertificateTransparencyInfo {
|
||||
public:
|
||||
CertificateTransparencyInfo()
|
||||
: enabled(false),
|
||||
policyCompliance(mozilla::ct::CTPolicyCompliance::Unknown) {
|
||||
CertificateTransparencyInfo() : enabled(false), policyCompliance(Nothing()) {
|
||||
Reset();
|
||||
}
|
||||
|
||||
@ -116,7 +113,7 @@ class CertificateTransparencyInfo {
|
||||
// Verification result of the processed SCTs.
|
||||
mozilla::ct::CTVerifyResult verifyResult;
|
||||
// Connection compliance to the CT Policy.
|
||||
mozilla::ct::CTPolicyCompliance policyCompliance;
|
||||
Maybe<mozilla::ct::CTPolicyCompliance> policyCompliance;
|
||||
|
||||
void Reset();
|
||||
};
|
||||
@ -266,7 +263,6 @@ class CertVerifier {
|
||||
// We only have a forward declarations of these classes (see above)
|
||||
// so we must allocate dynamically.
|
||||
UniquePtr<mozilla::ct::MultiLogCTVerifier> mCTVerifier;
|
||||
UniquePtr<mozilla::ct::CTDiversityPolicy> mCTDiversityPolicy;
|
||||
|
||||
void LoadKnownCTLogs();
|
||||
mozilla::pkix::Result VerifyCertificateTransparencyPolicy(
|
||||
|
@ -1,39 +0,0 @@
|
||||
/* -*- 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 "CTDiversityPolicy.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace ct {
|
||||
|
||||
typedef pkix::Result Result;
|
||||
|
||||
void GetCTLogOperatorsFromVerifiedSCTList(const VerifiedSCTList& list,
|
||||
CTLogOperatorList& operators) {
|
||||
operators.clear();
|
||||
for (const VerifiedSCT& verifiedSct : list) {
|
||||
CTLogOperatorId sctLogOperatorId = verifiedSct.logOperatorId;
|
||||
bool alreadyAdded = false;
|
||||
for (CTLogOperatorId id : operators) {
|
||||
if (id == sctLogOperatorId) {
|
||||
alreadyAdded = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!alreadyAdded) {
|
||||
operators.push_back(sctLogOperatorId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Result CTDiversityPolicy::GetDependentOperators(
|
||||
const nsTArray<nsTArray<uint8_t>>& builtChain,
|
||||
const CTLogOperatorList& operators, CTLogOperatorList& dependentOperators) {
|
||||
return pkix::Success;
|
||||
}
|
||||
|
||||
} // namespace ct
|
||||
} // namespace mozilla
|
@ -1,43 +0,0 @@
|
||||
/* -*- 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 CTDiversityPolicy_h
|
||||
#define CTDiversityPolicy_h
|
||||
|
||||
#include "CTLog.h"
|
||||
#include "CTVerifyResult.h"
|
||||
#include "certt.h"
|
||||
#include "nsTArray.h"
|
||||
#include "mozpkix/Result.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace ct {
|
||||
|
||||
// Retuns the list of unique CT log operator IDs appearing in the provided
|
||||
// list of verified SCTs.
|
||||
void GetCTLogOperatorsFromVerifiedSCTList(const VerifiedSCTList& list,
|
||||
CTLogOperatorList& operators);
|
||||
|
||||
// Helper class used by CTPolicyEnforcer to check the CT log operators
|
||||
// diversity requirements of the CT Policy.
|
||||
// See CTPolicyEnforcer.h for more details.
|
||||
class CTDiversityPolicy {
|
||||
public:
|
||||
// Given a certificate chain and a set of CT log operators,
|
||||
// returns the subset of log operators that are dependent on the CA
|
||||
// issuing the certificate (as defined by the CT Policy).
|
||||
//
|
||||
// NOTE: TBD, PENDING FINALIZATION OF MOZILLA CT POLICY.
|
||||
pkix::Result GetDependentOperators(
|
||||
const nsTArray<nsTArray<uint8_t>>& builtChain,
|
||||
const CTLogOperatorList& operators,
|
||||
CTLogOperatorList& dependentOperators);
|
||||
};
|
||||
|
||||
} // namespace ct
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // CTDiversityPolicy_h
|
@ -7,115 +7,24 @@
|
||||
#include "CTPolicyEnforcer.h"
|
||||
|
||||
#include "mozilla/Assertions.h"
|
||||
#include <algorithm>
|
||||
#include "mozpkix/Time.h"
|
||||
#include <set>
|
||||
#include <stdint.h>
|
||||
#include <vector>
|
||||
|
||||
namespace mozilla {
|
||||
namespace ct {
|
||||
|
||||
using namespace mozilla::pkix;
|
||||
|
||||
// Returns the number of embedded SCTs required to be present on a
|
||||
// certificate for Qualification Case #2 (embedded SCTs).
|
||||
static size_t GetRequiredEmbeddedSctsCount(
|
||||
size_t certLifetimeInFullCalendarMonths) {
|
||||
// "there are Embedded SCTs from AT LEAST N+1 once or currently qualified
|
||||
// logs, where N is the lifetime of the certificate in years (normally
|
||||
// rounding up, but rounding down when up to 3 months over), and must be
|
||||
// at least 1"
|
||||
return 1 + (certLifetimeInFullCalendarMonths + 9) / 12;
|
||||
}
|
||||
|
||||
// Whether a valid embedded SCT is present in the list.
|
||||
static bool HasValidEmbeddedSct(const VerifiedSCTList& verifiedScts) {
|
||||
for (const VerifiedSCT& verifiedSct : verifiedScts) {
|
||||
if (verifiedSct.logState == CTLogState::Admissible &&
|
||||
verifiedSct.origin == SCTOrigin::Embedded) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Whether a valid non-embedded SCT is present in the list.
|
||||
static bool HasValidNonEmbeddedSct(const VerifiedSCTList& verifiedScts) {
|
||||
for (const VerifiedSCT& verifiedSct : verifiedScts) {
|
||||
if (verifiedSct.logState == CTLogState::Admissible &&
|
||||
(verifiedSct.origin == SCTOrigin::TLSExtension ||
|
||||
verifiedSct.origin == SCTOrigin::OCSPResponse)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Given a list of verified SCTs, counts the number of distinct CA-independent
|
||||
// log operators running the CT logs that issued the SCTs which satisfy
|
||||
// the provided boolean predicate.
|
||||
template <typename SelectFunc>
|
||||
void CountIndependentLogOperatorsForSelectedScts(
|
||||
const VerifiedSCTList& verifiedScts,
|
||||
const CTLogOperatorList& dependentOperators, size_t& count,
|
||||
SelectFunc selected) {
|
||||
CTLogOperatorList operatorIds;
|
||||
for (const VerifiedSCT& verifiedSct : verifiedScts) {
|
||||
CTLogOperatorId sctLogOperatorId = verifiedSct.logOperatorId;
|
||||
// Check if |sctLogOperatorId| is CA-dependent.
|
||||
bool isDependentOperator = false;
|
||||
for (CTLogOperatorId dependentOperator : dependentOperators) {
|
||||
if (sctLogOperatorId == dependentOperator) {
|
||||
isDependentOperator = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (isDependentOperator || !selected(verifiedSct)) {
|
||||
continue;
|
||||
}
|
||||
// Check if |sctLogOperatorId| is in |operatorIds|...
|
||||
bool alreadyAdded = false;
|
||||
for (CTLogOperatorId id : operatorIds) {
|
||||
if (id == sctLogOperatorId) {
|
||||
alreadyAdded = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// ...and if not, add it.
|
||||
if (!alreadyAdded) {
|
||||
operatorIds.push_back(sctLogOperatorId);
|
||||
}
|
||||
}
|
||||
count = operatorIds.size();
|
||||
}
|
||||
|
||||
// Given a list of verified SCTs, counts the number of distinct CT logs
|
||||
// that issued the SCTs that satisfy the |selected| predicate.
|
||||
template <typename SelectFunc>
|
||||
static void CountLogsForSelectedScts(const VerifiedSCTList& verifiedScts,
|
||||
size_t& count, SelectFunc selected) {
|
||||
// Keep pointers to log ids (of type Buffer) from |verifiedScts| to save on
|
||||
// memory allocations.
|
||||
std::vector<const Buffer*> logIds;
|
||||
for (const VerifiedSCT& verifiedSct : verifiedScts) {
|
||||
if (!selected(verifiedSct)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const Buffer* sctLogId = &verifiedSct.sct.logId;
|
||||
// Check if |sctLogId| points to data already in |logIds|...
|
||||
bool alreadyAdded = false;
|
||||
for (const Buffer* logId : logIds) {
|
||||
if (*logId == *sctLogId) {
|
||||
alreadyAdded = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// ...and if not, add it.
|
||||
if (!alreadyAdded) {
|
||||
logIds.push_back(sctLogId);
|
||||
}
|
||||
}
|
||||
count = logIds.size();
|
||||
// Returns the number of embedded SCTs required to be present in a certificate.
|
||||
// For certificates with a lifetime of less than or equal to 180 days, only 2
|
||||
// embedded SCTs are required. Otherwise 3 are required.
|
||||
const Duration ONE_HUNDRED_AND_EIGHTY_DAYS =
|
||||
Duration(180 * Time::ONE_DAY_IN_SECONDS);
|
||||
size_t GetRequiredEmbeddedSctsCount(Duration certLifetime) {
|
||||
// pkix::Duration doesn't define operator<=, hence phrasing this comparison
|
||||
// in an awkward way
|
||||
return ONE_HUNDRED_AND_EIGHTY_DAYS < certLifetime ? 3 : 2;
|
||||
}
|
||||
|
||||
// Calculates the effective issuance time of connection's certificate using
|
||||
@ -130,8 +39,7 @@ static void CountLogsForSelectedScts(const VerifiedSCTList& verifiedScts,
|
||||
// delivered via OCSP/TLS extension will cover the full certificate,
|
||||
// which necessarily will exist only after the precertificate
|
||||
// has been logged and the actual certificate issued.
|
||||
static uint64_t GetEffectiveCertIssuanceTime(
|
||||
const VerifiedSCTList& verifiedScts) {
|
||||
uint64_t GetEffectiveCertIssuanceTime(const VerifiedSCTList& verifiedScts) {
|
||||
uint64_t result = UINT64_MAX;
|
||||
for (const VerifiedSCT& verifiedSct : verifiedScts) {
|
||||
if (verifiedSct.logState == CTLogState::Admissible) {
|
||||
@ -144,8 +52,8 @@ static uint64_t GetEffectiveCertIssuanceTime(
|
||||
// Checks if the log that issued the given SCT is "once or currently qualified"
|
||||
// (i.e. was qualified at the time of the certificate issuance). In addition,
|
||||
// makes sure the SCT is before the retirement timestamp.
|
||||
static bool LogWasQualifiedForSct(const VerifiedSCT& verifiedSct,
|
||||
uint64_t certIssuanceTime) {
|
||||
bool LogWasQualifiedForSct(const VerifiedSCT& verifiedSct,
|
||||
uint64_t certIssuanceTime) {
|
||||
switch (verifiedSct.logState) {
|
||||
case CTLogState::Admissible:
|
||||
return true;
|
||||
@ -159,121 +67,91 @@ static bool LogWasQualifiedForSct(const VerifiedSCT& verifiedSct,
|
||||
return false;
|
||||
}
|
||||
|
||||
// "A certificate is CT Qualified if it is presented with at least two SCTs
|
||||
// from once or currently qualified logs run by a minimum of two entities
|
||||
// independent of the CA and of each other."
|
||||
// By the preexisting certificate exception provision (not currently
|
||||
// implemented), certificates "are CT Qualified if they are presented with SCTs
|
||||
// from once or currently qualified logs run by a minimum of one entity
|
||||
// independent of the CA."
|
||||
static void CheckOperatorDiversityCompliance(
|
||||
const VerifiedSCTList& verifiedScts, uint64_t certIssuanceTime,
|
||||
const CTLogOperatorList& dependentOperators, bool& compliant) {
|
||||
size_t independentOperatorsCount;
|
||||
CountIndependentLogOperatorsForSelectedScts(
|
||||
verifiedScts, dependentOperators, independentOperatorsCount,
|
||||
[certIssuanceTime](const VerifiedSCT& verifiedSct) -> bool {
|
||||
return LogWasQualifiedForSct(verifiedSct, certIssuanceTime);
|
||||
});
|
||||
// Having at least 2 operators implies we have at least 2 SCTs.
|
||||
// For the preexisting certificate exception provision (1 operator) we will
|
||||
// need to include an additional SCTs count check using
|
||||
// CountLogsForSelectedScts(verifiedScts, sctsCount,
|
||||
// [certIssuanceTime](const VerifiedSCT& verifiedSct)->bool {
|
||||
// return LogWasQualifiedForSct(verifiedSct, certIssuanceTime);
|
||||
// });
|
||||
compliant = independentOperatorsCount >= 2;
|
||||
}
|
||||
|
||||
// Qualification Case #1 (non-embedded SCTs) - the following must hold:
|
||||
// a. An SCT from a log qualified at the time of check is presented via the
|
||||
// TLS extension OR is embedded within a stapled OCSP response;
|
||||
// AND
|
||||
// b. There are at least two SCTs from logs qualified at the time of check,
|
||||
// presented via any method.
|
||||
static void CheckNonEmbeddedCompliance(const VerifiedSCTList& verifiedScts,
|
||||
bool& compliant) {
|
||||
if (!HasValidNonEmbeddedSct(verifiedScts)) {
|
||||
compliant = false;
|
||||
return;
|
||||
// Qualification for embedded SCTs:
|
||||
// There must be at least one embedded SCT from a log that was Admissible (i.e.
|
||||
// Qualified, Usable, or ReadOnly) at the time of the check.
|
||||
// There must be at least N embedded SCTs from distinct logs that were
|
||||
// Admissible or Retired at the time of the check, where N depends on the
|
||||
// lifetime of the certificate. If the certificate lifetime is less than or
|
||||
// equal to 180 days, N is 2. Otherwise, N is 3.
|
||||
// Among these SCTs, at least two must be issued from distinct log operators.
|
||||
CTPolicyCompliance EmbeddedSCTsCompliant(const VerifiedSCTList& verifiedScts,
|
||||
uint64_t certIssuanceTime,
|
||||
Duration certLifetime) {
|
||||
size_t admissibleCount = 0;
|
||||
size_t admissibleOrRetiredCount = 0;
|
||||
std::set<CTLogOperatorId> logOperators;
|
||||
std::set<Buffer> logIds;
|
||||
for (const auto& verifiedSct : verifiedScts) {
|
||||
if (verifiedSct.origin != SCTOrigin::Embedded) {
|
||||
continue;
|
||||
}
|
||||
if (verifiedSct.logState != CTLogState::Admissible &&
|
||||
!LogWasQualifiedForSct(verifiedSct, certIssuanceTime)) {
|
||||
continue;
|
||||
}
|
||||
// Note that a single SCT can count for both the "from a log that was
|
||||
// admissible" case and the "from a log that was admissible or retired"
|
||||
// case.
|
||||
if (verifiedSct.logState == CTLogState::Admissible) {
|
||||
admissibleCount++;
|
||||
}
|
||||
if (LogWasQualifiedForSct(verifiedSct, certIssuanceTime)) {
|
||||
admissibleOrRetiredCount++;
|
||||
logIds.insert(verifiedSct.sct.logId);
|
||||
}
|
||||
logOperators.insert(verifiedSct.logOperatorId);
|
||||
}
|
||||
|
||||
size_t validSctsCount;
|
||||
CountLogsForSelectedScts(
|
||||
verifiedScts, validSctsCount, [](const VerifiedSCT& verifiedSct) -> bool {
|
||||
return verifiedSct.logState == CTLogState::Admissible;
|
||||
});
|
||||
|
||||
compliant = validSctsCount >= 2;
|
||||
size_t requiredEmbeddedScts = GetRequiredEmbeddedSctsCount(certLifetime);
|
||||
if (admissibleCount < 1 || admissibleOrRetiredCount < requiredEmbeddedScts) {
|
||||
return CTPolicyCompliance::NotEnoughScts;
|
||||
}
|
||||
if (logIds.size() < requiredEmbeddedScts || logOperators.size() < 2) {
|
||||
return CTPolicyCompliance::NotDiverseScts;
|
||||
}
|
||||
return CTPolicyCompliance::Compliant;
|
||||
}
|
||||
|
||||
// Qualification Case #2 (embedded SCTs) - the following must hold:
|
||||
// a. An Embedded SCT from a log qualified at the time of check is presented;
|
||||
// AND
|
||||
// b. There are Embedded SCTs from AT LEAST N + 1 once or currently qualified
|
||||
// logs, where N is the lifetime of the certificate in years (normally
|
||||
// rounding up, but rounding down when up to 3 months over), and must be
|
||||
// at least 1.
|
||||
static void CheckEmbeddedCompliance(const VerifiedSCTList& verifiedScts,
|
||||
size_t certLifetimeInCalendarMonths,
|
||||
uint64_t certIssuanceTime,
|
||||
bool& compliant) {
|
||||
if (!HasValidEmbeddedSct(verifiedScts)) {
|
||||
compliant = false;
|
||||
return;
|
||||
// Qualification for non-embedded SCTs (i.e. SCTs delivered via TLS handshake
|
||||
// or OCSP response):
|
||||
// There must be at least two SCTs from logs that were Admissible (i.e.
|
||||
// Qualified, Usable, or ReadOnly) at the time of the check. Among these SCTs,
|
||||
// at least two must be issued from distinct log operators.
|
||||
CTPolicyCompliance NonEmbeddedSCTsCompliant(
|
||||
const VerifiedSCTList& verifiedScts) {
|
||||
size_t admissibleCount = 0;
|
||||
std::set<CTLogOperatorId> logOperators;
|
||||
std::set<Buffer> logIds;
|
||||
for (const auto& verifiedSct : verifiedScts) {
|
||||
if (verifiedSct.origin == SCTOrigin::Embedded) {
|
||||
continue;
|
||||
}
|
||||
if (verifiedSct.logState != CTLogState::Admissible) {
|
||||
continue;
|
||||
}
|
||||
admissibleCount++;
|
||||
logIds.insert(verifiedSct.sct.logId);
|
||||
logOperators.insert(verifiedSct.logOperatorId);
|
||||
}
|
||||
|
||||
// Count the compliant embedded SCTs. Only a single SCT from each log
|
||||
// is accepted. Note that a given log might return several different SCTs
|
||||
// for the same precertificate (it is permitted, but advised against).
|
||||
size_t embeddedSctsCount;
|
||||
CountLogsForSelectedScts(
|
||||
verifiedScts, embeddedSctsCount,
|
||||
[certIssuanceTime](const VerifiedSCT& verifiedSct) -> bool {
|
||||
return verifiedSct.origin == SCTOrigin::Embedded &&
|
||||
LogWasQualifiedForSct(verifiedSct, certIssuanceTime);
|
||||
});
|
||||
|
||||
size_t requiredSctsCount =
|
||||
GetRequiredEmbeddedSctsCount(certLifetimeInCalendarMonths);
|
||||
|
||||
compliant = embeddedSctsCount >= requiredSctsCount;
|
||||
if (admissibleCount < 2) {
|
||||
return CTPolicyCompliance::NotEnoughScts;
|
||||
}
|
||||
if (logIds.size() < 2 || logOperators.size() < 2) {
|
||||
return CTPolicyCompliance::NotDiverseScts;
|
||||
}
|
||||
return CTPolicyCompliance::Compliant;
|
||||
}
|
||||
|
||||
void CTPolicyEnforcer::CheckCompliance(
|
||||
const VerifiedSCTList& verifiedScts, size_t certLifetimeInCalendarMonths,
|
||||
const CTLogOperatorList& dependentOperators,
|
||||
CTPolicyCompliance& compliance) {
|
||||
CTPolicyCompliance CheckCTPolicyCompliance(const VerifiedSCTList& verifiedScts,
|
||||
Duration certLifetime) {
|
||||
if (NonEmbeddedSCTsCompliant(verifiedScts) == CTPolicyCompliance::Compliant) {
|
||||
return CTPolicyCompliance::Compliant;
|
||||
}
|
||||
|
||||
uint64_t certIssuanceTime = GetEffectiveCertIssuanceTime(verifiedScts);
|
||||
|
||||
bool diversityOK;
|
||||
CheckOperatorDiversityCompliance(verifiedScts, certIssuanceTime,
|
||||
dependentOperators, diversityOK);
|
||||
|
||||
bool nonEmbeddedCaseOK;
|
||||
CheckNonEmbeddedCompliance(verifiedScts, nonEmbeddedCaseOK);
|
||||
|
||||
bool embeddedCaseOK;
|
||||
CheckEmbeddedCompliance(verifiedScts, certLifetimeInCalendarMonths,
|
||||
certIssuanceTime, embeddedCaseOK);
|
||||
|
||||
if (nonEmbeddedCaseOK || embeddedCaseOK) {
|
||||
compliance = diversityOK ? CTPolicyCompliance::Compliant
|
||||
: CTPolicyCompliance::NotDiverseScts;
|
||||
} else {
|
||||
// Not enough SCTs are present to satisfy either case of the policy.
|
||||
compliance = CTPolicyCompliance::NotEnoughScts;
|
||||
}
|
||||
|
||||
switch (compliance) {
|
||||
case CTPolicyCompliance::Compliant:
|
||||
case CTPolicyCompliance::NotEnoughScts:
|
||||
case CTPolicyCompliance::NotDiverseScts:
|
||||
break;
|
||||
case CTPolicyCompliance::Unknown:
|
||||
default:
|
||||
assert(false);
|
||||
}
|
||||
return EmbeddedSCTsCompliant(verifiedScts, certIssuanceTime, certLifetime);
|
||||
}
|
||||
|
||||
} // namespace ct
|
||||
|
@ -10,15 +10,14 @@
|
||||
#include "CTLog.h"
|
||||
#include "CTVerifyResult.h"
|
||||
#include "mozpkix/Result.h"
|
||||
#include "mozpkix/Time.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace ct {
|
||||
|
||||
// Information about the compliance of the TLS connection with the
|
||||
// Certificate Transparency policy.
|
||||
// A helper enum to describe the result of running CheckCTPolicyCompliance on a
|
||||
// collection of verified SCTs.
|
||||
enum class CTPolicyCompliance {
|
||||
// Compliance not checked or not applicable.
|
||||
Unknown,
|
||||
// The connection complied with the certificate policy
|
||||
// by including SCTs that satisfy the policy.
|
||||
Compliant,
|
||||
@ -30,34 +29,30 @@ enum class CTPolicyCompliance {
|
||||
NotDiverseScts,
|
||||
};
|
||||
|
||||
// Checks whether a TLS connection complies with the current CT policy.
|
||||
// The implemented policy is based on the Mozilla CT Policy draft 0.1.0
|
||||
// (see https://docs.google.com/document/d/
|
||||
// 1rnqYYwscAx8WhS-MCdTiNzYQus9e37HuVyafQvEeNro/edit).
|
||||
// Checks the collected verified SCTs for compliance with the CT policy.
|
||||
// The policy is based on Chrome's policy as described here:
|
||||
// https://googlechrome.github.io/CertificateTransparency/ct_policy.html
|
||||
// This policy (as well as Chrome's), is very similar to Apple's:
|
||||
// https://support.apple.com/en-us/103214
|
||||
// Essentially, the policy can be satisfied in two ways, depending on the
|
||||
// source of the collected SCTs.
|
||||
// For embedded SCTs, at least one must be from a log that was Admissible
|
||||
// (Qualified, Usable, or ReadOnly) at the time of the check. There must be
|
||||
// SCTs from N distinct logs that were Admissible or Retired at the time of the
|
||||
// check, where N depends on the lifetime of the certificate. If the
|
||||
// certificate lifetime is less than or equal to 180 days, N is 2. Otherwise, N
|
||||
// is 3. Among these SCTs, at least two must be issued from distinct log
|
||||
// operators.
|
||||
// For SCTs delivered via the TLS handshake or an OCSP response, at least two
|
||||
// must be from a log that was Admissible at the time of the check. Among these
|
||||
// SCTs, at least two must be issued from distinct log operators.
|
||||
//
|
||||
// NOTE: CT DIVERSITY REQUIREMENT IS TBD, PENDING FINALIZATION
|
||||
// OF MOZILLA CT POLICY. Specifically:
|
||||
// 1. CT log operators being CA-dependent is not currently taken into account
|
||||
// (see CTDiversityPolicy.h).
|
||||
// 2. The preexisting certificate exception provision of the operator diversity
|
||||
// requirement is not implemented (see "CT Qualified" section of the policy and
|
||||
// CheckOperatorDiversityCompliance in CTPolicyEnforcer.cpp).
|
||||
class CTPolicyEnforcer {
|
||||
public:
|
||||
// |verifiedSct| - SCTs present on the connection along with their
|
||||
// verification status.
|
||||
// |certLifetimeInCalendarMonths| - certificate lifetime in full calendar
|
||||
// months (i.e. rounded down), based on the notBefore/notAfter fields.
|
||||
// |dependentOperators| - which CT log operators are dependent on the CA
|
||||
// that issued the certificate. SCTs issued by logs associated with such
|
||||
// operators are treated differenly when evaluating the policy.
|
||||
// See CTDiversityPolicy class.
|
||||
// |compliance| - the result of the compliance check.
|
||||
void CheckCompliance(const VerifiedSCTList& verifiedScts,
|
||||
size_t certLifetimeInCalendarMonths,
|
||||
const CTLogOperatorList& dependentOperators,
|
||||
CTPolicyCompliance& compliance);
|
||||
};
|
||||
// |verifiedSct| - SCTs present on the connection along with their verification
|
||||
// status.
|
||||
// |certLifetime| - certificate lifetime, based on the notBefore/notAfter
|
||||
// fields.
|
||||
CTPolicyCompliance CheckCTPolicyCompliance(const VerifiedSCTList& verifiedScts,
|
||||
pkix::Duration certLifetime);
|
||||
|
||||
} // namespace ct
|
||||
} // namespace mozilla
|
||||
|
@ -21,7 +21,6 @@ EXPORTS += [
|
||||
UNIFIED_SOURCES += [
|
||||
"BTVerifier.cpp",
|
||||
"Buffer.cpp",
|
||||
"CTDiversityPolicy.cpp",
|
||||
"CTLogVerifier.cpp",
|
||||
"CTObjectsExtractor.cpp",
|
||||
"CTPolicyEnforcer.cpp",
|
||||
|
@ -1,15 +0,0 @@
|
||||
/* -*- 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 "CTDiversityPolicy.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace ct {
|
||||
|
||||
// TBD, PENDING FINALIZATION OF MOZILLA CT POLICY.
|
||||
|
||||
}
|
||||
} // namespace mozilla
|
@ -18,11 +18,6 @@
|
||||
#include "hasht.h"
|
||||
#include "prtime.h"
|
||||
|
||||
// Implemented in CertVerifier.cpp.
|
||||
extern mozilla::pkix::Result GetCertLifetimeInFullMonths(
|
||||
mozilla::pkix::Time certNotBefore, mozilla::pkix::Time certNotAfter,
|
||||
size_t& months);
|
||||
|
||||
namespace mozilla {
|
||||
namespace ct {
|
||||
|
||||
@ -30,11 +25,6 @@ using namespace mozilla::pkix;
|
||||
|
||||
class CTPolicyEnforcerTest : public ::testing::Test {
|
||||
public:
|
||||
void SetUp() override {
|
||||
OPERATORS_1_AND_2.push_back(OPERATOR_1);
|
||||
OPERATORS_1_AND_2.push_back(OPERATOR_2);
|
||||
}
|
||||
|
||||
void GetLogId(Buffer& logId, size_t logNo) {
|
||||
logId.resize(SHA256_LENGTH);
|
||||
std::fill(logId.begin(), logId.end(), 0);
|
||||
@ -68,37 +58,28 @@ class CTPolicyEnforcerTest : public ::testing::Test {
|
||||
}
|
||||
|
||||
void CheckCompliance(const VerifiedSCTList& verifiedSct,
|
||||
size_t certLifetimeInCalendarMonths,
|
||||
const CTLogOperatorList& dependentLogOperators,
|
||||
Duration certLifetime,
|
||||
CTPolicyCompliance expectedCompliance) {
|
||||
CTPolicyCompliance compliance;
|
||||
mPolicyEnforcer.CheckCompliance(verifiedSct, certLifetimeInCalendarMonths,
|
||||
dependentLogOperators, compliance);
|
||||
CTPolicyCompliance compliance =
|
||||
CheckCTPolicyCompliance(verifiedSct, certLifetime);
|
||||
EXPECT_EQ(expectedCompliance, compliance);
|
||||
}
|
||||
|
||||
protected:
|
||||
CTPolicyEnforcer mPolicyEnforcer;
|
||||
|
||||
const size_t LOG_1 = 1;
|
||||
const size_t LOG_2 = 2;
|
||||
const size_t LOG_3 = 3;
|
||||
const size_t LOG_4 = 4;
|
||||
const size_t LOG_5 = 5;
|
||||
|
||||
const CTLogOperatorId OPERATOR_1 = 1;
|
||||
const CTLogOperatorId OPERATOR_2 = 2;
|
||||
const CTLogOperatorId OPERATOR_3 = 3;
|
||||
|
||||
CTLogOperatorList NO_OPERATORS;
|
||||
CTLogOperatorList OPERATORS_1_AND_2;
|
||||
|
||||
const SCTOrigin ORIGIN_EMBEDDED = SCTOrigin::Embedded;
|
||||
const SCTOrigin ORIGIN_TLS = SCTOrigin::TLSExtension;
|
||||
const SCTOrigin ORIGIN_OCSP = SCTOrigin::OCSPResponse;
|
||||
|
||||
// 4 years of cert lifetime requires 5 SCTs for the embedded case.
|
||||
const size_t DEFAULT_MONTHS = 4 * 12L;
|
||||
// 1 year of cert lifetime requires 3 SCTs for the embedded case.
|
||||
const Duration DEFAULT_LIFETIME = Duration(365 * Time::ONE_DAY_IN_SECONDS);
|
||||
|
||||
// Date.parse("2015-08-15T00:00:00Z")
|
||||
const uint64_t TIMESTAMP_1 = 1439596800000L;
|
||||
@ -110,7 +91,7 @@ class CTPolicyEnforcerTest : public ::testing::Test {
|
||||
const uint64_t BEFORE_RETIREMENT = 1459468800000L;
|
||||
|
||||
// Date.parse("2016-04-16T00:00:00Z")
|
||||
const uint64_t AFTER_DISQUALIFIED = 1460764800000L;
|
||||
const uint64_t AFTER_RETIREMENT = 1460764800000L;
|
||||
};
|
||||
|
||||
TEST_F(CTPolicyEnforcerTest, ConformsToCTPolicyWithNonEmbeddedSCTs) {
|
||||
@ -119,46 +100,42 @@ TEST_F(CTPolicyEnforcerTest, ConformsToCTPolicyWithNonEmbeddedSCTs) {
|
||||
AddSct(scts, LOG_1, OPERATOR_1, ORIGIN_TLS, TIMESTAMP_1);
|
||||
AddSct(scts, LOG_2, OPERATOR_2, ORIGIN_TLS, TIMESTAMP_1);
|
||||
|
||||
CheckCompliance(scts, DEFAULT_MONTHS, NO_OPERATORS,
|
||||
CTPolicyCompliance::Compliant);
|
||||
CheckCompliance(scts, DEFAULT_LIFETIME, CTPolicyCompliance::Compliant);
|
||||
}
|
||||
|
||||
TEST_F(CTPolicyEnforcerTest, DoesNotConformNotEnoughDiverseNonEmbeddedSCTs) {
|
||||
VerifiedSCTList scts;
|
||||
|
||||
AddSct(scts, LOG_1, OPERATOR_1, ORIGIN_TLS, TIMESTAMP_1);
|
||||
AddSct(scts, LOG_2, OPERATOR_2, ORIGIN_TLS, TIMESTAMP_1);
|
||||
AddSct(scts, LOG_2, OPERATOR_1, ORIGIN_TLS, TIMESTAMP_1);
|
||||
|
||||
CheckCompliance(scts, DEFAULT_MONTHS, OPERATORS_1_AND_2,
|
||||
CTPolicyCompliance::NotDiverseScts);
|
||||
// The implementation attempts to fulfill the non-embedded compliance case
|
||||
// first. Because the non-embedded SCTs do not have enough log diversity, the
|
||||
// implementation then attempts to fulfill the embedded compliance case.
|
||||
// Because there are no embedded SCTs, it returns a "not enough SCTs" error.
|
||||
CheckCompliance(scts, DEFAULT_LIFETIME, CTPolicyCompliance::NotEnoughScts);
|
||||
}
|
||||
|
||||
TEST_F(CTPolicyEnforcerTest, ConformsToCTPolicyWithEmbeddedSCTs) {
|
||||
VerifiedSCTList scts;
|
||||
|
||||
// 5 embedded SCTs required for DEFAULT_MONTHS.
|
||||
// 3 embedded SCTs required for DEFAULT_LIFETIME.
|
||||
AddSct(scts, LOG_1, OPERATOR_1, ORIGIN_EMBEDDED, TIMESTAMP_1);
|
||||
AddSct(scts, LOG_2, OPERATOR_1, ORIGIN_EMBEDDED, TIMESTAMP_1);
|
||||
AddSct(scts, LOG_3, OPERATOR_1, ORIGIN_EMBEDDED, TIMESTAMP_1);
|
||||
AddSct(scts, LOG_4, OPERATOR_1, ORIGIN_EMBEDDED, TIMESTAMP_1);
|
||||
AddSct(scts, LOG_5, OPERATOR_2, ORIGIN_EMBEDDED, TIMESTAMP_1);
|
||||
AddSct(scts, LOG_3, OPERATOR_2, ORIGIN_EMBEDDED, TIMESTAMP_1);
|
||||
|
||||
CheckCompliance(scts, DEFAULT_MONTHS, NO_OPERATORS,
|
||||
CTPolicyCompliance::Compliant);
|
||||
CheckCompliance(scts, DEFAULT_LIFETIME, CTPolicyCompliance::Compliant);
|
||||
}
|
||||
|
||||
TEST_F(CTPolicyEnforcerTest, DoesNotConformNotEnoughDiverseEmbeddedSCTs) {
|
||||
VerifiedSCTList scts;
|
||||
|
||||
// 5 embedded SCTs required for DEFAULT_MONTHS.
|
||||
// 3 embedded SCTs required for DEFAULT_LIFETIME.
|
||||
AddSct(scts, LOG_1, OPERATOR_1, ORIGIN_EMBEDDED, TIMESTAMP_1);
|
||||
AddSct(scts, LOG_2, OPERATOR_1, ORIGIN_EMBEDDED, TIMESTAMP_1);
|
||||
AddSct(scts, LOG_3, OPERATOR_1, ORIGIN_EMBEDDED, TIMESTAMP_1);
|
||||
AddSct(scts, LOG_4, OPERATOR_1, ORIGIN_EMBEDDED, TIMESTAMP_1);
|
||||
AddSct(scts, LOG_5, OPERATOR_2, ORIGIN_EMBEDDED, TIMESTAMP_1);
|
||||
|
||||
CheckCompliance(scts, DEFAULT_MONTHS, OPERATORS_1_AND_2,
|
||||
CTPolicyCompliance::NotDiverseScts);
|
||||
CheckCompliance(scts, DEFAULT_LIFETIME, CTPolicyCompliance::NotDiverseScts);
|
||||
}
|
||||
|
||||
TEST_F(CTPolicyEnforcerTest, ConformsToCTPolicyWithPooledNonEmbeddedSCTs) {
|
||||
@ -167,18 +144,16 @@ TEST_F(CTPolicyEnforcerTest, ConformsToCTPolicyWithPooledNonEmbeddedSCTs) {
|
||||
AddSct(scts, LOG_1, OPERATOR_1, ORIGIN_OCSP, TIMESTAMP_1);
|
||||
AddSct(scts, LOG_2, OPERATOR_2, ORIGIN_TLS, TIMESTAMP_1);
|
||||
|
||||
CheckCompliance(scts, DEFAULT_MONTHS, NO_OPERATORS,
|
||||
CTPolicyCompliance::Compliant);
|
||||
CheckCompliance(scts, DEFAULT_LIFETIME, CTPolicyCompliance::Compliant);
|
||||
}
|
||||
|
||||
TEST_F(CTPolicyEnforcerTest, ConformsToCTPolicyWithPooledEmbeddedSCTs) {
|
||||
TEST_F(CTPolicyEnforcerTest, DoesNotConformToCTPolicyWithPooledEmbeddedSCTs) {
|
||||
VerifiedSCTList scts;
|
||||
|
||||
AddSct(scts, LOG_1, OPERATOR_1, ORIGIN_EMBEDDED, TIMESTAMP_1);
|
||||
AddSct(scts, LOG_2, OPERATOR_2, ORIGIN_OCSP, TIMESTAMP_1);
|
||||
|
||||
CheckCompliance(scts, DEFAULT_MONTHS, NO_OPERATORS,
|
||||
CTPolicyCompliance::Compliant);
|
||||
CheckCompliance(scts, DEFAULT_LIFETIME, CTPolicyCompliance::NotEnoughScts);
|
||||
}
|
||||
|
||||
TEST_F(CTPolicyEnforcerTest, DoesNotConformToCTPolicyNotEnoughSCTs) {
|
||||
@ -187,8 +162,7 @@ TEST_F(CTPolicyEnforcerTest, DoesNotConformToCTPolicyNotEnoughSCTs) {
|
||||
AddSct(scts, LOG_1, OPERATOR_1, ORIGIN_EMBEDDED, TIMESTAMP_1);
|
||||
AddSct(scts, LOG_2, OPERATOR_2, ORIGIN_EMBEDDED, TIMESTAMP_1);
|
||||
|
||||
CheckCompliance(scts, DEFAULT_MONTHS, NO_OPERATORS,
|
||||
CTPolicyCompliance::NotEnoughScts);
|
||||
CheckCompliance(scts, DEFAULT_LIFETIME, CTPolicyCompliance::NotEnoughScts);
|
||||
}
|
||||
|
||||
TEST_F(CTPolicyEnforcerTest, DoesNotConformToCTPolicyNotEnoughFreshSCTs) {
|
||||
@ -202,113 +176,98 @@ TEST_F(CTPolicyEnforcerTest, DoesNotConformToCTPolicyNotEnoughFreshSCTs) {
|
||||
AddSct(scts, LOG_1, OPERATOR_1, ORIGIN_TLS, TIMESTAMP_1);
|
||||
AddSct(scts, LOG_2, OPERATOR_2, ORIGIN_TLS, BEFORE_RETIREMENT,
|
||||
CTLogState::Retired);
|
||||
CheckCompliance(scts, DEFAULT_MONTHS, NO_OPERATORS,
|
||||
CTPolicyCompliance::NotEnoughScts);
|
||||
CheckCompliance(scts, DEFAULT_LIFETIME, CTPolicyCompliance::NotEnoughScts);
|
||||
// SCT from after disqualification.
|
||||
scts.clear();
|
||||
AddSct(scts, LOG_1, OPERATOR_1, ORIGIN_TLS, TIMESTAMP_1);
|
||||
AddSct(scts, LOG_2, OPERATOR_2, ORIGIN_TLS, AFTER_DISQUALIFIED,
|
||||
AddSct(scts, LOG_2, OPERATOR_2, ORIGIN_TLS, AFTER_RETIREMENT,
|
||||
CTLogState::Retired);
|
||||
CheckCompliance(scts, DEFAULT_MONTHS, NO_OPERATORS,
|
||||
CTPolicyCompliance::NotEnoughScts);
|
||||
CheckCompliance(scts, DEFAULT_LIFETIME, CTPolicyCompliance::NotEnoughScts);
|
||||
|
||||
// Embedded SCT from before disqualification.
|
||||
scts.clear();
|
||||
AddSct(scts, LOG_1, OPERATOR_1, ORIGIN_TLS, TIMESTAMP_1);
|
||||
AddSct(scts, LOG_2, OPERATOR_2, ORIGIN_EMBEDDED, BEFORE_RETIREMENT,
|
||||
CTLogState::Retired);
|
||||
CheckCompliance(scts, DEFAULT_MONTHS, NO_OPERATORS,
|
||||
CTPolicyCompliance::NotEnoughScts);
|
||||
CheckCompliance(scts, DEFAULT_LIFETIME, CTPolicyCompliance::NotEnoughScts);
|
||||
|
||||
// Embedded SCT from after disqualification.
|
||||
scts.clear();
|
||||
AddSct(scts, LOG_1, OPERATOR_1, ORIGIN_TLS, TIMESTAMP_1);
|
||||
AddSct(scts, LOG_2, OPERATOR_2, ORIGIN_EMBEDDED, AFTER_DISQUALIFIED,
|
||||
AddSct(scts, LOG_2, OPERATOR_2, ORIGIN_EMBEDDED, AFTER_RETIREMENT,
|
||||
CTLogState::Retired);
|
||||
CheckCompliance(scts, DEFAULT_MONTHS, NO_OPERATORS,
|
||||
CTPolicyCompliance::NotEnoughScts);
|
||||
CheckCompliance(scts, DEFAULT_LIFETIME, CTPolicyCompliance::NotEnoughScts);
|
||||
}
|
||||
|
||||
TEST_F(CTPolicyEnforcerTest, ConformsWithRetiredLogBeforeDisqualificationDate) {
|
||||
VerifiedSCTList scts;
|
||||
|
||||
// 3 embedded SCTs required for DEFAULT_LIFETIME.
|
||||
AddSct(scts, LOG_1, OPERATOR_1, ORIGIN_EMBEDDED, TIMESTAMP_1);
|
||||
AddSct(scts, LOG_2, OPERATOR_1, ORIGIN_EMBEDDED, TIMESTAMP_1);
|
||||
AddSct(scts, LOG_3, OPERATOR_2, ORIGIN_EMBEDDED, BEFORE_RETIREMENT,
|
||||
CTLogState::Retired);
|
||||
|
||||
CheckCompliance(scts, DEFAULT_LIFETIME, CTPolicyCompliance::Compliant);
|
||||
}
|
||||
|
||||
TEST_F(CTPolicyEnforcerTest,
|
||||
ConformsWithDisqualifiedLogBeforeDisqualificationDate) {
|
||||
DoesNotConformWithRetiredLogAfterDisqualificationDate) {
|
||||
VerifiedSCTList scts;
|
||||
|
||||
// 5 embedded SCTs required for DEFAULT_MONTHS.
|
||||
// 3 embedded SCTs required for DEFAULT_LIFETIME.
|
||||
AddSct(scts, LOG_1, OPERATOR_1, ORIGIN_EMBEDDED, TIMESTAMP_1);
|
||||
AddSct(scts, LOG_2, OPERATOR_1, ORIGIN_EMBEDDED, TIMESTAMP_1);
|
||||
AddSct(scts, LOG_3, OPERATOR_1, ORIGIN_EMBEDDED, TIMESTAMP_1);
|
||||
AddSct(scts, LOG_4, OPERATOR_1, ORIGIN_EMBEDDED, TIMESTAMP_1);
|
||||
AddSct(scts, LOG_5, OPERATOR_2, ORIGIN_EMBEDDED, BEFORE_RETIREMENT,
|
||||
AddSct(scts, LOG_3, OPERATOR_2, ORIGIN_EMBEDDED, AFTER_RETIREMENT,
|
||||
CTLogState::Retired);
|
||||
|
||||
CheckCompliance(scts, DEFAULT_MONTHS, NO_OPERATORS,
|
||||
CTPolicyCompliance::Compliant);
|
||||
}
|
||||
|
||||
TEST_F(CTPolicyEnforcerTest,
|
||||
DoesNotConformWithDisqualifiedLogAfterDisqualificationDate) {
|
||||
VerifiedSCTList scts;
|
||||
|
||||
// 5 embedded SCTs required for DEFAULT_MONTHS.
|
||||
AddSct(scts, LOG_1, OPERATOR_1, ORIGIN_EMBEDDED, TIMESTAMP_1);
|
||||
AddSct(scts, LOG_2, OPERATOR_1, ORIGIN_EMBEDDED, TIMESTAMP_1);
|
||||
AddSct(scts, LOG_3, OPERATOR_1, ORIGIN_EMBEDDED, TIMESTAMP_1);
|
||||
AddSct(scts, LOG_4, OPERATOR_1, ORIGIN_EMBEDDED, TIMESTAMP_1);
|
||||
AddSct(scts, LOG_5, OPERATOR_2, ORIGIN_EMBEDDED, AFTER_DISQUALIFIED,
|
||||
CTLogState::Retired);
|
||||
|
||||
CheckCompliance(scts, DEFAULT_MONTHS, NO_OPERATORS,
|
||||
CTPolicyCompliance::NotEnoughScts);
|
||||
CheckCompliance(scts, DEFAULT_LIFETIME, CTPolicyCompliance::NotEnoughScts);
|
||||
}
|
||||
|
||||
TEST_F(CTPolicyEnforcerTest,
|
||||
DoesNotConformWithIssuanceDateAfterDisqualificationDate) {
|
||||
VerifiedSCTList scts;
|
||||
|
||||
// 5 embedded SCTs required for DEFAULT_MONTHS.
|
||||
AddSct(scts, LOG_1, OPERATOR_1, ORIGIN_EMBEDDED, AFTER_DISQUALIFIED,
|
||||
// 3 embedded SCTs required for DEFAULT_LIFETIME.
|
||||
AddSct(scts, LOG_1, OPERATOR_1, ORIGIN_EMBEDDED, AFTER_RETIREMENT,
|
||||
CTLogState::Retired);
|
||||
AddSct(scts, LOG_2, OPERATOR_1, ORIGIN_EMBEDDED, AFTER_DISQUALIFIED);
|
||||
AddSct(scts, LOG_3, OPERATOR_1, ORIGIN_EMBEDDED, AFTER_DISQUALIFIED);
|
||||
AddSct(scts, LOG_4, OPERATOR_1, ORIGIN_EMBEDDED, AFTER_DISQUALIFIED);
|
||||
AddSct(scts, LOG_5, OPERATOR_2, ORIGIN_EMBEDDED, AFTER_DISQUALIFIED);
|
||||
AddSct(scts, LOG_2, OPERATOR_1, ORIGIN_EMBEDDED, AFTER_RETIREMENT);
|
||||
AddSct(scts, LOG_3, OPERATOR_2, ORIGIN_EMBEDDED, AFTER_RETIREMENT);
|
||||
|
||||
CheckCompliance(scts, DEFAULT_MONTHS, NO_OPERATORS,
|
||||
CTPolicyCompliance::NotEnoughScts);
|
||||
CheckCompliance(scts, DEFAULT_LIFETIME, CTPolicyCompliance::NotEnoughScts);
|
||||
}
|
||||
|
||||
TEST_F(CTPolicyEnforcerTest,
|
||||
DoesNotConformToCTPolicyNotEnoughUniqueEmbeddedLogs) {
|
||||
DoesNotConformToCTPolicyNotEnoughUniqueEmbeddedRetiredLogs) {
|
||||
VerifiedSCTList scts;
|
||||
|
||||
// Operator #1
|
||||
AddSct(scts, LOG_1, OPERATOR_1, ORIGIN_EMBEDDED, TIMESTAMP_1);
|
||||
// Operator #2, different logs
|
||||
AddSct(scts, LOG_2, OPERATOR_2, ORIGIN_EMBEDDED, TIMESTAMP_1);
|
||||
AddSct(scts, LOG_3, OPERATOR_2, ORIGIN_EMBEDDED, TIMESTAMP_1);
|
||||
// Operator #3, same log
|
||||
AddSct(scts, LOG_4, OPERATOR_3, ORIGIN_EMBEDDED, TIMESTAMP_1);
|
||||
AddSct(scts, LOG_4, OPERATOR_3, ORIGIN_EMBEDDED, TIMESTAMP_1);
|
||||
// Operator #2, same retired logs
|
||||
AddSct(scts, LOG_2, OPERATOR_2, ORIGIN_EMBEDDED, BEFORE_RETIREMENT,
|
||||
CTLogState::Retired);
|
||||
AddSct(scts, LOG_2, OPERATOR_2, ORIGIN_EMBEDDED, BEFORE_RETIREMENT,
|
||||
CTLogState::Retired);
|
||||
|
||||
// 5 embedded SCTs required. However, only 4 are from distinct logs.
|
||||
CheckCompliance(scts, DEFAULT_MONTHS, NO_OPERATORS,
|
||||
CTPolicyCompliance::NotEnoughScts);
|
||||
// 3 embedded SCTs required. However, only 2 are from distinct logs.
|
||||
CheckCompliance(scts, DEFAULT_LIFETIME, CTPolicyCompliance::NotDiverseScts);
|
||||
}
|
||||
|
||||
TEST_F(CTPolicyEnforcerTest,
|
||||
ConformsToPolicyExactNumberOfSCTsForValidityPeriod) {
|
||||
// Test multiple validity periods.
|
||||
const struct TestData {
|
||||
size_t certLifetimeInCalendarMonths;
|
||||
Duration certLifetime;
|
||||
size_t sctsRequired;
|
||||
} kTestData[] = {{3, 2}, {12 + 2, 2}, {12 + 3, 3},
|
||||
{2 * 12 + 2, 3}, {2 * 12 + 3, 4}, {3 * 12 + 2, 4},
|
||||
{3 * 12 + 4, 5}};
|
||||
} kTestData[] = {{Duration(90 * Time::ONE_DAY_IN_SECONDS), 2},
|
||||
{Duration(180 * Time::ONE_DAY_IN_SECONDS), 2},
|
||||
{Duration(181 * Time::ONE_DAY_IN_SECONDS), 3},
|
||||
{Duration(365 * Time::ONE_DAY_IN_SECONDS), 3}};
|
||||
|
||||
for (size_t i = 0; i < MOZILLA_CT_ARRAY_LENGTH(kTestData); ++i) {
|
||||
SCOPED_TRACE(i);
|
||||
|
||||
size_t months = kTestData[i].certLifetimeInCalendarMonths;
|
||||
Duration certLifetime = kTestData[i].certLifetime;
|
||||
size_t sctsRequired = kTestData[i].sctsRequired;
|
||||
|
||||
// Less SCTs than required is not enough.
|
||||
@ -317,8 +276,8 @@ TEST_F(CTPolicyEnforcerTest,
|
||||
VerifiedSCTList scts;
|
||||
AddMultipleScts(scts, sctsAvailable, 1, ORIGIN_EMBEDDED, TIMESTAMP_1);
|
||||
|
||||
CTPolicyCompliance compliance;
|
||||
mPolicyEnforcer.CheckCompliance(scts, months, NO_OPERATORS, compliance);
|
||||
CTPolicyCompliance compliance =
|
||||
CheckCTPolicyCompliance(scts, certLifetime);
|
||||
EXPECT_EQ(CTPolicyCompliance::NotEnoughScts, compliance)
|
||||
<< "i=" << i << " sctsRequired=" << sctsRequired
|
||||
<< " sctsAvailable=" << sctsAvailable;
|
||||
@ -328,54 +287,10 @@ TEST_F(CTPolicyEnforcerTest,
|
||||
VerifiedSCTList scts;
|
||||
AddMultipleScts(scts, sctsRequired, 2, ORIGIN_EMBEDDED, TIMESTAMP_1);
|
||||
|
||||
CTPolicyCompliance compliance;
|
||||
mPolicyEnforcer.CheckCompliance(scts, months, NO_OPERATORS, compliance);
|
||||
CTPolicyCompliance compliance = CheckCTPolicyCompliance(scts, certLifetime);
|
||||
EXPECT_EQ(CTPolicyCompliance::Compliant, compliance) << "i=" << i;
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(CTPolicyEnforcerTest, TestEdgeCasesOfGetCertLifetimeInFullMonths) {
|
||||
const struct TestData {
|
||||
uint64_t notBefore;
|
||||
uint64_t notAfter;
|
||||
size_t expectedMonths;
|
||||
} kTestData[] = {
|
||||
{ // 1 second less than 1 month
|
||||
1424863500000000, // Date.parse("2015-02-25T11:25:00Z") * 1000
|
||||
1427196299000000, // Date.parse("2015-03-24T11:24:59Z") * 1000
|
||||
0},
|
||||
{ // exactly 1 month
|
||||
1424863500000000, // Date.parse("2015-02-25T11:25:00Z") * 1000
|
||||
1427282700000000, // Date.parse("2015-03-25T11:25:00Z") * 1000
|
||||
1},
|
||||
{ // 1 year, 1 month
|
||||
1427282700000000, // Date.parse("2015-03-25T11:25:00Z") * 1000
|
||||
1461583500000000, // Date.parse("2016-04-25T11:25:00Z") * 1000
|
||||
13},
|
||||
{// 1 year, 1 month, first day of notBefore month, last of notAfter
|
||||
1425209100000000, // Date.parse("2015-03-01T11:25:00Z") * 1000
|
||||
1462015500000000, // Date.parse("2016-04-30T11:25:00Z") * 1000
|
||||
13},
|
||||
{// 1 year, adjacent months, last day of notBefore month, first of
|
||||
// notAfter
|
||||
1427801100000000, // Date.parse("2015-03-31T11:25:00Z") * 1000
|
||||
1459509900000000, // Date.parse("2016-04-01T11:25:00Z") * 1000
|
||||
12}};
|
||||
|
||||
for (size_t i = 0; i < MOZILLA_CT_ARRAY_LENGTH(kTestData); ++i) {
|
||||
SCOPED_TRACE(i);
|
||||
|
||||
size_t months;
|
||||
ASSERT_EQ(Success,
|
||||
GetCertLifetimeInFullMonths(mozilla::pkix::TimeFromEpochInSeconds(
|
||||
kTestData[i].notBefore / 1000000),
|
||||
mozilla::pkix::TimeFromEpochInSeconds(
|
||||
kTestData[i].notAfter / 1000000),
|
||||
months))
|
||||
<< "i=" << i;
|
||||
EXPECT_EQ(kTestData[i].expectedMonths, months) << "i=" << i;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ct
|
||||
} // namespace mozilla
|
||||
|
@ -37,7 +37,6 @@ UNIFIED_SOURCES += [
|
||||
"BTSerializationTest.cpp",
|
||||
"BTSignedTreeHeadTest.cpp",
|
||||
"BTVerificationTest.cpp",
|
||||
"CTDiversityPolicyTest.cpp",
|
||||
"CTLogVerifierTest.cpp",
|
||||
"CTObjectsExtractorTest.cpp",
|
||||
"CTPolicyEnforcerTest.cpp",
|
||||
|
@ -497,17 +497,10 @@ void GatherCertificateTransparencyTelemetry(
|
||||
Telemetry::Accumulate(Telemetry::SSL_SCTS_PER_CONNECTION, sctsCount);
|
||||
|
||||
// Report CT Policy compliance by CA.
|
||||
switch (info.policyCompliance) {
|
||||
case ct::CTPolicyCompliance::Compliant:
|
||||
break;
|
||||
case ct::CTPolicyCompliance::NotEnoughScts:
|
||||
case ct::CTPolicyCompliance::NotDiverseScts:
|
||||
AccumulateTelemetryForRootCA(
|
||||
Telemetry::SSL_CT_POLICY_NON_COMPLIANT_CONNECTIONS_BY_CA_2, rootCert);
|
||||
break;
|
||||
case ct::CTPolicyCompliance::Unknown:
|
||||
default:
|
||||
MOZ_ASSERT_UNREACHABLE("Unexpected CTPolicyCompliance type");
|
||||
if (info.policyCompliance.isSome() &&
|
||||
*info.policyCompliance != ct::CTPolicyCompliance::Compliant) {
|
||||
AccumulateTelemetryForRootCA(
|
||||
Telemetry::SSL_CT_POLICY_NON_COMPLIANT_CONNECTIONS_BY_CA_2, rootCert);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1028,12 +1028,12 @@ uint16_t TransportSecurityInfo::ConvertCertificateTransparencyInfoToStatus(
|
||||
const mozilla::psm::CertificateTransparencyInfo& info) {
|
||||
using mozilla::ct::CTPolicyCompliance;
|
||||
|
||||
if (!info.enabled) {
|
||||
if (!info.enabled || info.policyCompliance.isNothing()) {
|
||||
// CT disabled.
|
||||
return nsITransportSecurityInfo::CERTIFICATE_TRANSPARENCY_NOT_APPLICABLE;
|
||||
}
|
||||
|
||||
switch (info.policyCompliance) {
|
||||
switch (*info.policyCompliance) {
|
||||
case CTPolicyCompliance::Compliant:
|
||||
return nsITransportSecurityInfo::
|
||||
CERTIFICATE_TRANSPARENCY_POLICY_COMPLIANT;
|
||||
@ -1043,7 +1043,6 @@ uint16_t TransportSecurityInfo::ConvertCertificateTransparencyInfoToStatus(
|
||||
case CTPolicyCompliance::NotDiverseScts:
|
||||
return nsITransportSecurityInfo ::
|
||||
CERTIFICATE_TRANSPARENCY_POLICY_NOT_DIVERSE_SCTS;
|
||||
case CTPolicyCompliance::Unknown:
|
||||
default:
|
||||
MOZ_ASSERT_UNREACHABLE("Unexpected CTPolicyCompliance type");
|
||||
}
|
||||
|
@ -26,11 +26,9 @@ registerCleanupFunction(() => {
|
||||
function run_test() {
|
||||
Services.prefs.setIntPref("security.pki.certificate_transparency.mode", 1);
|
||||
add_tls_server_setup("BadCertAndPinningServer", "test_ct");
|
||||
// These certificates have a validity period of 800 days, which is a little
|
||||
// over 2 years and 2 months. This gets rounded down to 2 years (since it's
|
||||
// less than 2 years and 3 months). Our policy requires N + 1 embedded SCTs,
|
||||
// where N is 2 in this case. So, a policy-compliant certificate would have at
|
||||
// least 3 SCTs.
|
||||
// These certificates have a validity period of 800 days, which is greater
|
||||
// than 180 days. Our policy requires 3 embedded SCTs for certificates with a
|
||||
// validity period greater than 180 days.
|
||||
add_connection_test(
|
||||
"ct-valid.example.com",
|
||||
PRErrorCodeSuccess,
|
||||
|
Loading…
Reference in New Issue
Block a user