diff options
Diffstat (limited to 'chromium/components/certificate_transparency/chrome_ct_policy_enforcer.cc')
-rw-r--r-- | chromium/components/certificate_transparency/chrome_ct_policy_enforcer.cc | 307 |
1 files changed, 307 insertions, 0 deletions
diff --git a/chromium/components/certificate_transparency/chrome_ct_policy_enforcer.cc b/chromium/components/certificate_transparency/chrome_ct_policy_enforcer.cc new file mode 100644 index 00000000000..a2e2b493def --- /dev/null +++ b/chromium/components/certificate_transparency/chrome_ct_policy_enforcer.cc @@ -0,0 +1,307 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/certificate_transparency/chrome_ct_policy_enforcer.h" + +#include <stdint.h> + +#include <algorithm> +#include <memory> +#include <utility> + +#include "base/bind.h" +#include "base/build_time.h" +#include "base/callback_helpers.h" +#include "base/metrics/field_trial.h" +#include "base/metrics/histogram_macros.h" +#include "base/numerics/safe_conversions.h" +#include "base/strings/string_number_conversions.h" +#include "base/time/time.h" +#include "base/values.h" +#include "base/version.h" +#include "components/certificate_transparency/ct_known_logs.h" +#include "net/cert/ct_policy_status.h" +#include "net/cert/signed_certificate_timestamp.h" +#include "net/cert/x509_certificate.h" +#include "net/cert/x509_certificate_net_log_param.h" +#include "net/log/net_log_capture_mode.h" +#include "net/log/net_log_event_type.h" +#include "net/log/net_log_parameters_callback.h" +#include "net/log/net_log_with_source.h" + +using net::ct::CTPolicyCompliance; + +namespace certificate_transparency { + +namespace { + +// 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 +bool IsBuildTimely() { + const base::Time build_time = base::GetBuildTime(); + // We consider built-in information to be timely for 10 weeks. + return (base::Time::Now() - build_time).InDays() < 70 /* 10 weeks */; +} + +// 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 +// are not consistent in terms of including the range start value. +void RoundedDownMonthDifference(const base::Time& start, + const base::Time& end, + size_t* rounded_months_difference, + bool* has_partial_month) { + DCHECK(rounded_months_difference); + DCHECK(has_partial_month); + base::Time::Exploded exploded_start; + base::Time::Exploded exploded_expiry; + start.UTCExplode(&exploded_start); + end.UTCExplode(&exploded_expiry); + if (end < start) { + *rounded_months_difference = 0; + *has_partial_month = false; + return; + } + + *has_partial_month = true; + uint32_t month_diff = (exploded_expiry.year - exploded_start.year) * 12 + + (exploded_expiry.month - exploded_start.month); + if (exploded_expiry.day_of_month < exploded_start.day_of_month) + --month_diff; + else if (exploded_expiry.day_of_month == exploded_start.day_of_month) + *has_partial_month = false; + + *rounded_months_difference = month_diff; +} + +const char* CTPolicyComplianceToString(CTPolicyCompliance status) { + switch (status) { + case CTPolicyCompliance::CT_POLICY_COMPLIES_VIA_SCTS: + return "COMPLIES_VIA_SCTS"; + case CTPolicyCompliance::CT_POLICY_NOT_ENOUGH_SCTS: + return "NOT_ENOUGH_SCTS"; + case CTPolicyCompliance::CT_POLICY_NOT_DIVERSE_SCTS: + return "NOT_DIVERSE_SCTS"; + case CTPolicyCompliance::CT_POLICY_BUILD_NOT_TIMELY: + return "BUILD_NOT_TIMELY"; + case CTPolicyCompliance::CT_POLICY_COMPLIANCE_DETAILS_NOT_AVAILABLE: + case CTPolicyCompliance::CT_POLICY_MAX: + NOTREACHED(); + return "unknown"; + } + + NOTREACHED(); + return "unknown"; +} + +std::unique_ptr<base::Value> NetLogCertComplianceCheckResultCallback( + net::X509Certificate* cert, + bool build_timely, + CTPolicyCompliance compliance, + net::NetLogCaptureMode capture_mode) { + std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue()); + dict->Set("certificate", + net::NetLogX509CertificateCallback(cert, capture_mode)); + dict->SetBoolean("build_timely", build_timely); + dict->SetString("ct_compliance_status", + CTPolicyComplianceToString(compliance)); + return std::move(dict); +} + +// Evaluates against the policy specified at +// https://sites.google.com/a/chromium.org/dev/Home/chromium-security/root-ca-policy/EVCTPlanMay2015edition.pdf?attredirects=0 +CTPolicyCompliance CheckCTPolicyCompliance( + const net::X509Certificate& cert, + const net::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 CTPolicyCompliance::CT_POLICY_NOT_ENOUGH_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) { + base::Time unused; + if (IsLogDisqualified(sct->log_id, &unused)) + continue; + 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) { + base::Time disqualification_date; + bool is_disqualified = + IsLogDisqualified(sct->log_id, &disqualification_date); + if (is_disqualified && + sct->origin != net::ct::SignedCertificateTimestamp::SCT_EMBEDDED) { + // For OCSP and TLS delivered SCTs, only SCTs that are valid at the + // time of check are accepted. + continue; + } + + if (IsLogOperatedByGoogle(sct->log_id)) { + has_valid_google_sct |= !is_disqualified; + if (sct->origin == net::ct::SignedCertificateTimestamp::SCT_EMBEDDED) + has_embedded_google_sct = true; + } else { + has_valid_nongoogle_sct |= !is_disqualified; + if (sct->origin == net::ct::SignedCertificateTimestamp::SCT_EMBEDDED) + has_embedded_nongoogle_sct = true; + } + if (sct->origin != net::ct::SignedCertificateTimestamp::SCT_EMBEDDED) { + has_valid_nonembedded_sct = true; + } else { + has_valid_embedded_sct |= !is_disqualified; + // If the log is disqualified, it only counts towards quorum if + // the certificate was issued before the log was disqualified, and the + // SCT was obtained before the log was disqualified. + if (!is_disqualified || (issuance_date < disqualification_date && + sct->timestamp < disqualification_date)) { + 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 CTPolicyCompliance::CT_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 + ? CTPolicyCompliance::CT_POLICY_NOT_DIVERSE_SCTS + : CTPolicyCompliance::CT_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::UnixEpoch() + base::TimeDelta::FromSeconds(1435708800); + 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 CTPolicyCompliance::CT_POLICY_NOT_DIVERSE_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 CTPolicyCompliance::CT_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 + ? CTPolicyCompliance::CT_POLICY_NOT_DIVERSE_SCTS + : CTPolicyCompliance::CT_POLICY_NOT_ENOUGH_SCTS; +} + +} // namespace + +CTPolicyCompliance ChromeCTPolicyEnforcer::CheckCompliance( + net::X509Certificate* cert, + const net::ct::SCTList& verified_scts, + const net::NetLogWithSource& net_log) { + // If the build is not timely, no certificate is considered compliant + // with CT policy. The reasoning is that, for example, a log might + // have been pulled and is no longer considered valid; thus, a client + // needs up-to-date information about logs to consider certificates to + // be compliant with policy. + bool build_timely = IsBuildTimely(); + CTPolicyCompliance compliance; + if (!build_timely) { + compliance = CTPolicyCompliance::CT_POLICY_BUILD_NOT_TIMELY; + } else { + compliance = CheckCTPolicyCompliance(*cert, verified_scts); + } + + net::NetLogParametersCallback net_log_callback = + base::BindRepeating(&NetLogCertComplianceCheckResultCallback, + base::Unretained(cert), build_timely, compliance); + + net_log.AddEvent(net::NetLogEventType::CERT_CT_COMPLIANCE_CHECKED, + net_log_callback); + + return compliance; +} + +} // namespace certificate_transparency |