Index: net/cert/ct_policy_enforcer.cc |
diff --git a/net/cert/ct_policy_enforcer.cc b/net/cert/ct_policy_enforcer.cc |
index 48ebbc2b2a29b9dc6f447d0c309f8af41d6588ab..beae34ac3c887e73cc4ed829c4ea5a09d5136935 100644 |
--- a/net/cert/ct_policy_enforcer.cc |
+++ b/net/cert/ct_policy_enforcer.cc |
@@ -4,6 +4,8 @@ |
#include "net/cert/ct_policy_enforcer.h" |
+#include <stdint.h> |
+ |
#include <algorithm> |
#include <memory> |
#include <utility> |
@@ -31,10 +33,6 @@ namespace net { |
namespace { |
-bool IsEmbeddedSCT(const scoped_refptr<ct::SignedCertificateTimestamp>& sct) { |
- return sct->origin == ct::SignedCertificateTimestamp::SCT_EMBEDDED; |
-} |
- |
// Returns true if the current build is recent enough to ensure that |
// built-in security information (e.g. CT Logs) is fresh enough. |
// TODO(eranm): Move to base or net/base |
@@ -44,11 +42,6 @@ bool IsBuildTimely() { |
return (base::Time::Now() - build_time).InDays() < 70 /* 10 weeks */; |
} |
-bool IsGoogleIssuedSCT( |
- const scoped_refptr<ct::SignedCertificateTimestamp>& sct) { |
- return ct::IsLogOperatedByGoogle(sct->log_id); |
-} |
- |
// Returns a rounded-down months difference of |start| and |end|, |
// together with an indication of whether the last month was |
// a full month, because the range starts specified in the policy |
@@ -79,58 +72,6 @@ void RoundedDownMonthDifference(const base::Time& start, |
*rounded_months_difference = month_diff; |
} |
-bool HasRequiredNumberOfSCTs(const X509Certificate& cert, |
- const ct::SCTList& verified_scts) { |
- size_t num_valid_scts = verified_scts.size(); |
- size_t num_embedded_scts = base::checked_cast<size_t>( |
- std::count_if(verified_scts.begin(), verified_scts.end(), IsEmbeddedSCT)); |
- |
- size_t num_non_embedded_scts = num_valid_scts - num_embedded_scts; |
- // If at least two valid SCTs were delivered by means other than embedding |
- // (i.e. in a TLS extension or OCSP), then the certificate conforms to bullet |
- // number 3 of the "Qualifying Certificate" section of the CT/EV policy. |
- if (num_non_embedded_scts >= 2) |
- return true; |
- |
- if (cert.valid_start().is_null() || cert.valid_expiry().is_null() || |
- cert.valid_start().is_max() || cert.valid_expiry().is_max()) { |
- // Will not be able to calculate the certificate's validity period. |
- return false; |
- } |
- |
- size_t lifetime; |
- bool has_partial_month; |
- RoundedDownMonthDifference(cert.valid_start(), cert.valid_expiry(), &lifetime, |
- &has_partial_month); |
- |
- // For embedded SCTs, if the certificate has the number of SCTs specified in |
- // table 1 of the "Qualifying Certificate" section of the CT/EV policy, then |
- // it qualifies. |
- size_t num_required_embedded_scts; |
- if (lifetime > 39 || (lifetime == 39 && has_partial_month)) { |
- num_required_embedded_scts = 5; |
- } else if (lifetime > 27 || (lifetime == 27 && has_partial_month)) { |
- num_required_embedded_scts = 4; |
- } else if (lifetime >= 15) { |
- num_required_embedded_scts = 3; |
- } else { |
- num_required_embedded_scts = 2; |
- } |
- |
- return num_embedded_scts >= num_required_embedded_scts; |
-} |
- |
-// Returns true if |verified_scts| contains SCTs from at least one log that is |
-// operated by Google and at least one log that is not operated by Google. This |
-// is required for SCTs after July 1st, 2015, as documented at |
-// http://dev.chromium.org/Home/chromium-security/root-ca-policy/EVCTPlanMay2015edition.pdf |
-bool HasEnoughDiverseSCTs(const ct::SCTList& verified_scts) { |
- size_t num_google_issued_scts = base::checked_cast<size_t>(std::count_if( |
- verified_scts.begin(), verified_scts.end(), IsGoogleIssuedSCT)); |
- return (num_google_issued_scts > 0) && |
- (verified_scts.size() != num_google_issued_scts); |
-} |
- |
const char* EVPolicyComplianceToString(ct::EVPolicyCompliance status) { |
switch (status) { |
case ct::EVPolicyCompliance::EV_POLICY_DOES_NOT_APPLY: |
@@ -238,52 +179,167 @@ std::unique_ptr<base::Value> NetLogCertComplianceCheckResultCallback( |
return std::move(dict); |
} |
-// Returns true if all SCTs in |verified_scts| were issued on, or after, the |
-// date specified in kDiverseSCTRequirementStartDate |
-bool AllSCTsPastDistinctSCTRequirementEnforcementDate( |
- const ct::SCTList& verified_scts) { |
- // The date when diverse SCTs requirement is effective from. |
- // 2015-07-01 00:00:00 UTC. |
- base::Time kDiverseSCTRequirementStartDate = |
- base::Time::FromInternalValue(13080182400000000); |
- |
- for (const auto& it : verified_scts) { |
- if (it->timestamp < kDiverseSCTRequirementStartDate) |
- return false; |
- } |
- |
- return true; |
-} |
- |
bool IsCertificateInWhitelist(const X509Certificate& cert, |
const ct::EVCertsWhitelist* ev_whitelist) { |
- bool cert_in_ev_whitelist = false; |
- if (ev_whitelist && ev_whitelist->IsValid()) { |
- const SHA256HashValue fingerprint( |
- X509Certificate::CalculateFingerprint256(cert.os_cert_handle())); |
+ if (!ev_whitelist || !ev_whitelist->IsValid()) |
+ return false; |
- std::string truncated_fp = |
- std::string(reinterpret_cast<const char*>(fingerprint.data), 8); |
- cert_in_ev_whitelist = ev_whitelist->ContainsCertificateHash(truncated_fp); |
+ const SHA256HashValue fingerprint( |
+ X509Certificate::CalculateFingerprint256(cert.os_cert_handle())); |
- UMA_HISTOGRAM_BOOLEAN("Net.SSL_EVCertificateInWhitelist", |
- cert_in_ev_whitelist); |
- } |
+ std::string truncated_fp = |
+ std::string(reinterpret_cast<const char*>(fingerprint.data), 8); |
+ bool cert_in_ev_whitelist = |
+ ev_whitelist->ContainsCertificateHash(truncated_fp); |
+ |
+ UMA_HISTOGRAM_BOOLEAN("Net.SSL_EVCertificateInWhitelist", |
+ cert_in_ev_whitelist); |
return cert_in_ev_whitelist; |
} |
+// Evaluates against the policy specified at |
+// https://sites.google.com/a/chromium.org/dev/Home/chromium-security/root-ca-policy/EVCTPlanMay2015edition.pdf?attredirects=0 |
ct::CertPolicyCompliance CheckCertPolicyCompliance( |
- X509Certificate* cert, |
- const ct::SCTList& verified_scts, |
- const BoundNetLog& net_log) { |
- if (!HasRequiredNumberOfSCTs(*cert, verified_scts)) |
+ const X509Certificate& cert, |
+ const ct::SCTList& verified_scts) { |
+ // Cert is outside the bounds of parsable; reject it. |
+ if (cert.valid_start().is_null() || cert.valid_expiry().is_null() || |
+ cert.valid_start().is_max() || cert.valid_expiry().is_max()) { |
return ct::CertPolicyCompliance::CERT_POLICY_NOT_ENOUGH_SCTS; |
- if (AllSCTsPastDistinctSCTRequirementEnforcementDate(verified_scts) && |
- !HasEnoughDiverseSCTs(verified_scts)) { |
+ } |
+ |
+ // Scan for the earliest SCT. This is used to determine whether to enforce |
+ // log diversity requirements, as well as whether to enforce whether or not |
+ // a log was qualified or pending qualification at time of issuance (in the |
+ // case of embedded SCTs). It's acceptable to ignore the origin of the SCT, |
+ // because SCTs 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. |
+ // Note: Here, issuance date is defined as the earliest of all SCTs, rather |
+ // than the latest of embedded SCTs, in order to give CAs the benefit of |
+ // the doubt in the event a log is revoked in the midst of processing |
+ // a precertificate and issuing the certificate. |
+ base::Time issuance_date = base::Time::Max(); |
+ for (const auto& sct : verified_scts) |
+ issuance_date = std::min(sct->timestamp, issuance_date); |
+ |
+ bool has_valid_google_sct = false; |
+ bool has_valid_nongoogle_sct = false; |
+ bool has_valid_embedded_sct = false; |
+ bool has_valid_nonembedded_sct = false; |
+ bool has_embedded_google_sct = false; |
+ bool has_embedded_nongoogle_sct = false; |
+ std::vector<base::StringPiece> embedded_log_ids; |
+ for (const auto& sct : verified_scts) { |
+ if (ct::IsLogOperatedByGoogle(sct->log_id)) { |
+ has_valid_google_sct = true; |
+ if (sct->origin == ct::SignedCertificateTimestamp::SCT_EMBEDDED) |
+ has_embedded_google_sct = true; |
+ } else { |
+ has_valid_nongoogle_sct = true; |
+ if (sct->origin == ct::SignedCertificateTimestamp::SCT_EMBEDDED) |
+ has_embedded_nongoogle_sct = true; |
+ } |
+ if (sct->origin != ct::SignedCertificateTimestamp::SCT_EMBEDDED) { |
+ has_valid_nonembedded_sct = true; |
+ } else { |
+ has_valid_embedded_sct = true; |
+ embedded_log_ids.push_back(sct->log_id); |
+ } |
+ } |
+ |
+ // Option 1: |
+ // An SCT presented via the TLS extension OR embedded within a stapled OCSP |
+ // response is from a log qualified at time of check; |
+ // AND there is at least one SCT from a Google Log that is qualified at |
+ // time of check, presented via any method; |
+ // AND there is at least one SCT from a non-Google Log that is qualified |
+ // at the time of check, presented via any method. |
+ // |
+ // Note: Because SCTs embedded via TLS or OCSP can be updated on the fly, |
+ // the issuance date is irrelevant, as any policy changes can be |
+ // accomodated. |
+ if (has_valid_nonembedded_sct && has_valid_google_sct && |
+ has_valid_nongoogle_sct) { |
+ return ct::CertPolicyCompliance::CERT_POLICY_COMPLIES_VIA_SCTS; |
+ } |
+ // Note: If has_valid_nonembedded_sct was true, but Option 2 isn't met, |
+ // then the result will be that there weren't diverse enough SCTs, as that |
+ // the only other way for the conditional above to fail). Because Option 1 |
+ // has the diversity requirement, it's implicitly a minimum number of SCTs |
+ // (specifically, 2), but that's not explicitly specified in the policy. |
+ |
+ // Option 2: |
+ // There is at least one embedded SCT from a log qualified at the time of |
+ // check ... |
+ if (!has_valid_embedded_sct) { |
+ // Under Option 2, there weren't enough SCTs, and potentially under |
+ // Option 1, there weren't diverse enough SCTs. Try to signal the error |
+ // that is most easily fixed. |
+ return has_valid_nonembedded_sct |
+ ? ct::CertPolicyCompliance::CERT_POLICY_NOT_DIVERSE_SCTS |
+ : ct::CertPolicyCompliance::CERT_POLICY_NOT_ENOUGH_SCTS; |
+ } |
+ |
+ // ... AND there is at least one embedded SCT from a Google Log once or |
+ // currently qualified; |
+ // AND there is at least one embedded SCT from a non-Google Log once or |
+ // currently qualified; |
+ // ... |
+ // |
+ // Note: This policy language is only enforced after the below issuance |
+ // date, as that's when the diversity policy first came into effect for |
+ // SCTs embedded in certificates. |
+ // The date when diverse SCTs requirement is effective from. |
+ // 2015-07-01 00:00:00 UTC. |
+ const base::Time kDiverseSCTRequirementStartDate = |
+ base::Time::FromInternalValue(INT64_C(13080182400000000)); |
+ if (issuance_date >= kDiverseSCTRequirementStartDate && |
+ !(has_embedded_google_sct && has_embedded_nongoogle_sct)) { |
+ // Note: This also covers the case for non-embedded SCTs, as it's only |
+ // possible to reach here if both sets are not diverse enough. |
return ct::CertPolicyCompliance::CERT_POLICY_NOT_DIVERSE_SCTS; |
} |
- return ct::CertPolicyCompliance::CERT_POLICY_COMPLIES_VIA_SCTS; |
+ size_t lifetime_in_months = 0; |
+ bool has_partial_month = false; |
+ RoundedDownMonthDifference(cert.valid_start(), cert.valid_expiry(), |
+ &lifetime_in_months, &has_partial_month); |
+ |
+ // ... AND the certificate embeds SCTs from AT LEAST the number of logs |
+ // once or currently qualified shown in Table 1 of the CT Policy. |
+ size_t num_required_embedded_scts = 5; |
+ if (lifetime_in_months > 39 || |
+ (lifetime_in_months == 39 && has_partial_month)) { |
+ num_required_embedded_scts = 5; |
+ } else if (lifetime_in_months > 27 || |
+ (lifetime_in_months == 27 && has_partial_month)) { |
+ num_required_embedded_scts = 4; |
+ } else if (lifetime_in_months >= 15) { |
+ num_required_embedded_scts = 3; |
+ } else { |
+ num_required_embedded_scts = 2; |
+ } |
+ |
+ // Sort the embedded log IDs and remove duplicates, so that only a single |
+ // SCT from each log is accepted. This is to handle the case where a given |
+ // log returns different SCTs for the same precertificate (which is |
+ // permitted, but advised against). |
+ std::sort(embedded_log_ids.begin(), embedded_log_ids.end()); |
+ auto sorted_end = |
+ std::unique(embedded_log_ids.begin(), embedded_log_ids.end()); |
+ size_t num_embedded_scts = |
+ std::distance(embedded_log_ids.begin(), sorted_end); |
+ |
+ if (num_embedded_scts >= num_required_embedded_scts) |
+ return ct::CertPolicyCompliance::CERT_POLICY_COMPLIES_VIA_SCTS; |
+ |
+ // Under Option 2, there weren't enough SCTs, and potentially under Option |
+ // 1, there weren't diverse enough SCTs. Try to signal the error that is |
+ // most easily fixed. |
+ return has_valid_nonembedded_sct |
+ ? ct::CertPolicyCompliance::CERT_POLICY_NOT_DIVERSE_SCTS |
+ : ct::CertPolicyCompliance::CERT_POLICY_NOT_ENOUGH_SCTS; |
} |
ct::EVPolicyCompliance CertPolicyComplianceToEVPolicyCompliance( |
@@ -307,7 +363,7 @@ void CheckCTEVPolicyCompliance(X509Certificate* cert, |
const BoundNetLog& net_log, |
EVComplianceDetails* result) { |
result->status = CertPolicyComplianceToEVPolicyCompliance( |
- CheckCertPolicyCompliance(cert, verified_scts, net_log)); |
+ CheckCertPolicyCompliance(*cert, verified_scts)); |
if (ev_whitelist && ev_whitelist->IsValid()) |
result->whitelist_version = ev_whitelist->Version(); |
@@ -333,7 +389,7 @@ ct::CertPolicyCompliance CTPolicyEnforcer::DoesConformToCertPolicy( |
if (!build_timely) { |
compliance = ct::CertPolicyCompliance::CERT_POLICY_BUILD_NOT_TIMELY; |
} else { |
- compliance = CheckCertPolicyCompliance(cert, verified_scts, net_log); |
+ compliance = CheckCertPolicyCompliance(*cert, verified_scts); |
} |
NetLog::ParametersCallback net_log_callback = |