Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(2547)

Unified Diff: net/cert/ct_policy_enforcer.cc

Issue 1941973002: Align the CT Implementation with Policy (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Feedback Created 4 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « net/cert/ct_policy_enforcer.h ('k') | net/cert/ct_policy_enforcer_unittest.cc » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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 =
« no previous file with comments | « net/cert/ct_policy_enforcer.h ('k') | net/cert/ct_policy_enforcer_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698