chrome_ct_policy_enforcer.cc 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441
  1. // Copyright 2014 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. #include "components/certificate_transparency/chrome_ct_policy_enforcer.h"
  5. #include <stdint.h>
  6. #include <algorithm>
  7. #include <memory>
  8. #include <utility>
  9. #include "base/bind.h"
  10. #include "base/callback_helpers.h"
  11. #include "base/feature_list.h"
  12. #include "base/metrics/field_trial.h"
  13. #include "base/metrics/histogram_macros.h"
  14. #include "base/numerics/safe_conversions.h"
  15. #include "base/strings/string_number_conversions.h"
  16. #include "base/time/default_clock.h"
  17. #include "base/time/time.h"
  18. #include "base/values.h"
  19. #include "base/version.h"
  20. #include "components/certificate_transparency/ct_known_logs.h"
  21. #include "crypto/sha2.h"
  22. #include "net/cert/ct_policy_status.h"
  23. #include "net/cert/signed_certificate_timestamp.h"
  24. #include "net/cert/x509_certificate.h"
  25. #include "net/cert/x509_certificate_net_log_param.h"
  26. #include "net/log/net_log_capture_mode.h"
  27. #include "net/log/net_log_event_type.h"
  28. #include "net/log/net_log_with_source.h"
  29. using net::ct::CTPolicyCompliance;
  30. namespace certificate_transparency {
  31. namespace {
  32. // Returns a rounded-down months difference of |start| and |end|,
  33. // together with an indication of whether the last month was
  34. // a full month, because the range starts specified in the policy
  35. // are not consistent in terms of including the range start value.
  36. void RoundedDownMonthDifference(const base::Time& start,
  37. const base::Time& end,
  38. size_t* rounded_months_difference,
  39. bool* has_partial_month) {
  40. DCHECK(rounded_months_difference);
  41. DCHECK(has_partial_month);
  42. base::Time::Exploded exploded_start;
  43. base::Time::Exploded exploded_expiry;
  44. start.UTCExplode(&exploded_start);
  45. end.UTCExplode(&exploded_expiry);
  46. if (end < start) {
  47. *rounded_months_difference = 0;
  48. *has_partial_month = false;
  49. return;
  50. }
  51. *has_partial_month = true;
  52. uint32_t month_diff = (exploded_expiry.year - exploded_start.year) * 12 +
  53. (exploded_expiry.month - exploded_start.month);
  54. if (exploded_expiry.day_of_month < exploded_start.day_of_month)
  55. --month_diff;
  56. else if (exploded_expiry.day_of_month == exploded_start.day_of_month)
  57. *has_partial_month = false;
  58. *rounded_months_difference = month_diff;
  59. }
  60. const char* CTPolicyComplianceToString(CTPolicyCompliance status) {
  61. switch (status) {
  62. case CTPolicyCompliance::CT_POLICY_COMPLIES_VIA_SCTS:
  63. return "COMPLIES_VIA_SCTS";
  64. case CTPolicyCompliance::CT_POLICY_NOT_ENOUGH_SCTS:
  65. return "NOT_ENOUGH_SCTS";
  66. case CTPolicyCompliance::CT_POLICY_NOT_DIVERSE_SCTS:
  67. return "NOT_DIVERSE_SCTS";
  68. case CTPolicyCompliance::CT_POLICY_BUILD_NOT_TIMELY:
  69. return "BUILD_NOT_TIMELY";
  70. case CTPolicyCompliance::CT_POLICY_COMPLIANCE_DETAILS_NOT_AVAILABLE:
  71. case CTPolicyCompliance::CT_POLICY_COUNT:
  72. NOTREACHED();
  73. return "unknown";
  74. }
  75. NOTREACHED();
  76. return "unknown";
  77. }
  78. base::Value NetLogCertComplianceCheckResultParams(
  79. net::X509Certificate* cert,
  80. bool build_timely,
  81. CTPolicyCompliance compliance) {
  82. base::Value dict(base::Value::Type::DICTIONARY);
  83. // TODO(mattm): This double-wrapping of the certificate list is weird. Remove
  84. // this (probably requires updates to netlog-viewer).
  85. base::Value certificate_dict(base::Value::Type::DICTIONARY);
  86. certificate_dict.SetKey("certificates", net::NetLogX509CertificateList(cert));
  87. dict.SetKey("certificate", std::move(certificate_dict));
  88. dict.SetBoolKey("build_timely", build_timely);
  89. dict.SetStringKey("ct_compliance_status",
  90. CTPolicyComplianceToString(compliance));
  91. return dict;
  92. }
  93. } // namespace
  94. OperatorHistoryEntry::OperatorHistoryEntry() = default;
  95. OperatorHistoryEntry::~OperatorHistoryEntry() = default;
  96. OperatorHistoryEntry::OperatorHistoryEntry(const OperatorHistoryEntry& other) =
  97. default;
  98. ChromeCTPolicyEnforcer::ChromeCTPolicyEnforcer(
  99. base::Time log_list_date,
  100. std::vector<std::pair<std::string, base::Time>> disqualified_logs,
  101. std::vector<std::string> operated_by_google_logs,
  102. std::map<std::string, OperatorHistoryEntry> log_operator_history)
  103. : disqualified_logs_(std::move(disqualified_logs)),
  104. operated_by_google_logs_(std::move(operated_by_google_logs)),
  105. log_operator_history_(std::move(log_operator_history)),
  106. clock_(base::DefaultClock::GetInstance()),
  107. log_list_date_(log_list_date) {}
  108. ChromeCTPolicyEnforcer::~ChromeCTPolicyEnforcer() {}
  109. CTPolicyCompliance ChromeCTPolicyEnforcer::CheckCompliance(
  110. net::X509Certificate* cert,
  111. const net::ct::SCTList& verified_scts,
  112. const net::NetLogWithSource& net_log) {
  113. // If the build is not timely, no certificate is considered compliant
  114. // with CT policy. The reasoning is that, for example, a log might
  115. // have been pulled and is no longer considered valid; thus, a client
  116. // needs up-to-date information about logs to consider certificates to
  117. // be compliant with policy.
  118. bool build_timely = IsLogDataTimely();
  119. CTPolicyCompliance compliance;
  120. if (!build_timely) {
  121. compliance = CTPolicyCompliance::CT_POLICY_BUILD_NOT_TIMELY;
  122. } else {
  123. compliance = CheckCTPolicyCompliance(*cert, verified_scts);
  124. }
  125. net_log.AddEvent(net::NetLogEventType::CERT_CT_COMPLIANCE_CHECKED, [&] {
  126. return NetLogCertComplianceCheckResultParams(cert, build_timely,
  127. compliance);
  128. });
  129. return compliance;
  130. }
  131. void ChromeCTPolicyEnforcer::UpdateCTLogList(
  132. base::Time update_time,
  133. std::vector<std::pair<std::string, base::Time>> disqualified_logs,
  134. std::vector<std::string> operated_by_google_logs,
  135. std::map<std::string, OperatorHistoryEntry> log_operator_history) {
  136. log_list_date_ = update_time;
  137. disqualified_logs_ = std::move(disqualified_logs);
  138. operated_by_google_logs_ = std::move(operated_by_google_logs);
  139. log_operator_history_ = std::move(log_operator_history);
  140. if (valid_google_log_for_testing_.has_value()) {
  141. valid_google_log_for_testing_ = absl::nullopt;
  142. }
  143. if (disqualified_log_for_testing_.has_value()) {
  144. disqualified_log_for_testing_ = absl::nullopt;
  145. }
  146. }
  147. bool ChromeCTPolicyEnforcer::IsLogDisqualified(
  148. base::StringPiece log_id,
  149. base::Time* disqualification_date) const {
  150. CHECK_EQ(log_id.size(), crypto::kSHA256Length);
  151. if (disqualified_log_for_testing_.has_value() &&
  152. log_id == disqualified_log_for_testing_.value().first) {
  153. *disqualification_date = disqualified_log_for_testing_.value().second;
  154. return *disqualification_date < base::Time::Now();
  155. }
  156. auto p = std::lower_bound(
  157. std::begin(disqualified_logs_), std::end(disqualified_logs_), log_id,
  158. [](const auto& a, base::StringPiece b) { return a.first < b; });
  159. if (p == std::end(disqualified_logs_) || p->first != log_id) {
  160. return false;
  161. }
  162. *disqualification_date = p->second;
  163. if (base::Time::Now() < *disqualification_date) {
  164. return false;
  165. }
  166. return true;
  167. }
  168. bool ChromeCTPolicyEnforcer::IsLogOperatedByGoogle(
  169. base::StringPiece log_id) const {
  170. if (valid_google_log_for_testing_.has_value() &&
  171. log_id == valid_google_log_for_testing_.value()) {
  172. return true;
  173. }
  174. return std::binary_search(std::begin(operated_by_google_logs_),
  175. std::end(operated_by_google_logs_), log_id);
  176. }
  177. bool ChromeCTPolicyEnforcer::IsLogDataTimely() const {
  178. if (ct_log_list_always_timely_for_testing_)
  179. return true;
  180. // We consider built-in information to be timely for 10 weeks.
  181. return (clock_->Now() - log_list_date_).InDays() < 70 /* 10 weeks */;
  182. }
  183. // Evaluates against the policy specified at
  184. // https://sites.google.com/a/chromium.org/dev/Home/chromium-security/root-ca-policy/EVCTPlanMay2015edition.pdf?attredirects=0
  185. CTPolicyCompliance ChromeCTPolicyEnforcer::CheckCTPolicyCompliance(
  186. const net::X509Certificate& cert,
  187. const net::ct::SCTList& verified_scts) const {
  188. // Cert is outside the bounds of parsable; reject it.
  189. if (cert.valid_start().is_null() || cert.valid_expiry().is_null() ||
  190. cert.valid_start().is_max() || cert.valid_expiry().is_max()) {
  191. return CTPolicyCompliance::CT_POLICY_NOT_ENOUGH_SCTS;
  192. }
  193. // Scan for the earliest SCT. This is used to determine whether to enforce
  194. // log diversity requirements, as well as whether to enforce whether or not
  195. // a log was qualified or pending qualification at time of issuance (in the
  196. // case of embedded SCTs). It's acceptable to ignore the origin of the SCT,
  197. // because SCTs delivered via OCSP/TLS extension will cover the full
  198. // certificate, which necessarily will exist only after the precertificate
  199. // has been logged and the actual certificate issued.
  200. // Note: Here, issuance date is defined as the earliest of all SCTs, rather
  201. // than the latest of embedded SCTs, in order to give CAs the benefit of
  202. // the doubt in the event a log is revoked in the midst of processing
  203. // a precertificate and issuing the certificate.
  204. base::Time issuance_date = base::Time::Max();
  205. for (const auto& sct : verified_scts) {
  206. base::Time unused;
  207. if (IsLogDisqualified(sct->log_id, &unused))
  208. continue;
  209. issuance_date = std::min(sct->timestamp, issuance_date);
  210. }
  211. // Certificates issued after this date (April 15, 2022, OO:OO:OO GMT)
  212. // will be subject to the new CT policy, which:
  213. // -Removes the One Google log requirement.
  214. // -Introduces a log operator diversity (at least 2 SCTs that come from
  215. // different operators are required).
  216. // -Uses days for certificate lifetime calculations instead of rounding to
  217. // months.
  218. // Increases the SCT requirements for certificates with a lifetime between
  219. // 180 days and 15 months, from 2 to 3.
  220. // This conditional, and the pre-2022 policy logic can be removed after June
  221. // 1, 2023, since all publicly trusted certificates issued prior to the
  222. // policy change date will have expired by then.
  223. const base::Time kPolicyUpdateDate =
  224. base::Time::UnixEpoch() + base::Seconds(1649980800);
  225. bool use_2022_policy = issuance_date >= kPolicyUpdateDate;
  226. bool has_valid_google_sct = false;
  227. bool has_valid_nongoogle_sct = false;
  228. bool has_valid_embedded_sct = false;
  229. bool has_valid_nonembedded_sct = false;
  230. bool has_embedded_google_sct = false;
  231. bool has_embedded_nongoogle_sct = false;
  232. bool has_diverse_log_operators = false;
  233. std::vector<base::StringPiece> embedded_log_ids;
  234. std::string first_seen_operator;
  235. for (const auto& sct : verified_scts) {
  236. base::Time disqualification_date;
  237. bool is_disqualified =
  238. IsLogDisqualified(sct->log_id, &disqualification_date);
  239. if (is_disqualified &&
  240. sct->origin != net::ct::SignedCertificateTimestamp::SCT_EMBEDDED) {
  241. // For OCSP and TLS delivered SCTs, only SCTs that are valid at the
  242. // time of check are accepted.
  243. continue;
  244. }
  245. if (!use_2022_policy) {
  246. if (IsLogOperatedByGoogle(sct->log_id)) {
  247. has_valid_google_sct |= !is_disqualified;
  248. if (sct->origin == net::ct::SignedCertificateTimestamp::SCT_EMBEDDED)
  249. has_embedded_google_sct = true;
  250. } else {
  251. has_valid_nongoogle_sct |= !is_disqualified;
  252. if (sct->origin == net::ct::SignedCertificateTimestamp::SCT_EMBEDDED)
  253. has_embedded_nongoogle_sct = true;
  254. }
  255. }
  256. if (sct->origin != net::ct::SignedCertificateTimestamp::SCT_EMBEDDED) {
  257. has_valid_nonembedded_sct = true;
  258. } else {
  259. has_valid_embedded_sct |= !is_disqualified;
  260. // If the log is disqualified, it only counts towards quorum if
  261. // the certificate was issued before the log was disqualified, and the
  262. // SCT was obtained before the log was disqualified.
  263. if (!is_disqualified || (issuance_date < disqualification_date &&
  264. sct->timestamp < disqualification_date)) {
  265. embedded_log_ids.push_back(sct->log_id);
  266. }
  267. }
  268. if (use_2022_policy && !has_diverse_log_operators) {
  269. std::string sct_operator = GetOperatorForLog(sct->log_id, sct->timestamp);
  270. if (first_seen_operator.empty()) {
  271. first_seen_operator = sct_operator;
  272. } else {
  273. has_diverse_log_operators |= first_seen_operator != sct_operator;
  274. }
  275. }
  276. }
  277. // Option 1:
  278. // An SCT presented via the TLS extension OR embedded within a stapled OCSP
  279. // response is from a log qualified at time of check;
  280. // With previous policy:
  281. // AND there is at least one SCT from a Google Log that is qualified at
  282. // time of check, presented via any method;
  283. // AND there is at least one SCT from a non-Google Log that is qualified
  284. // at the time of check, presented via any method.
  285. // With new policy:
  286. // AND there are at least two SCTs from logs with different operators,
  287. // presented by any method.
  288. //
  289. // Note: Because SCTs embedded via TLS or OCSP can be updated on the fly,
  290. // the issuance date is irrelevant, as any policy changes can be
  291. // accommodated.
  292. if (has_valid_nonembedded_sct) {
  293. if (use_2022_policy) {
  294. if (has_diverse_log_operators) {
  295. return CTPolicyCompliance::CT_POLICY_COMPLIES_VIA_SCTS;
  296. }
  297. } else {
  298. if (has_valid_google_sct && has_valid_nongoogle_sct) {
  299. return CTPolicyCompliance::CT_POLICY_COMPLIES_VIA_SCTS;
  300. }
  301. }
  302. }
  303. // Note: If has_valid_nonembedded_sct was true, but Option 2 isn't met,
  304. // then the result will be that there weren't diverse enough SCTs, as that
  305. // the only other way for the conditional above to fail). Because Option 1
  306. // has the diversity requirement, it's implicitly a minimum number of SCTs
  307. // (specifically, 2), but that's not explicitly specified in the policy.
  308. // Option 2:
  309. // There is at least one embedded SCT from a log qualified at the time of
  310. // check ...
  311. if (!has_valid_embedded_sct) {
  312. // Under Option 2, there weren't enough SCTs, and potentially under
  313. // Option 1, there weren't diverse enough SCTs. Try to signal the error
  314. // that is most easily fixed.
  315. return has_valid_nonembedded_sct
  316. ? CTPolicyCompliance::CT_POLICY_NOT_DIVERSE_SCTS
  317. : CTPolicyCompliance::CT_POLICY_NOT_ENOUGH_SCTS;
  318. }
  319. size_t num_required_embedded_scts = 5;
  320. if (use_2022_policy) {
  321. // ... AND there are at least two SCTs from logs with different
  322. // operators ...
  323. if (!has_diverse_log_operators) {
  324. return CTPolicyCompliance::CT_POLICY_NOT_DIVERSE_SCTS;
  325. }
  326. // ... AND the certificate embeds SCTs from AT LEAST the number of logs
  327. // once or currently qualified shown in Table 1 of the CT Policy.
  328. base::TimeDelta lifetime = cert.valid_expiry() - cert.valid_start();
  329. if (lifetime > base::Days(180)) {
  330. num_required_embedded_scts = 3;
  331. } else {
  332. num_required_embedded_scts = 2;
  333. }
  334. } else {
  335. // ... AND there is at least one embedded SCT from a Google Log once or
  336. // currently qualified;
  337. // AND there is at least one embedded SCT from a non-Google Log once or
  338. // currently qualified;
  339. // ...
  340. if (!(has_embedded_google_sct && has_embedded_nongoogle_sct)) {
  341. // Note: This also covers the case for non-embedded SCTs, as it's only
  342. // possible to reach here if both sets are not diverse enough.
  343. return CTPolicyCompliance::CT_POLICY_NOT_DIVERSE_SCTS;
  344. }
  345. size_t lifetime_in_months = 0;
  346. bool has_partial_month = false;
  347. RoundedDownMonthDifference(cert.valid_start(), cert.valid_expiry(),
  348. &lifetime_in_months, &has_partial_month);
  349. // ... AND the certificate embeds SCTs from AT LEAST the number of logs
  350. // once or currently qualified shown in Table 1 of the CT Policy.
  351. if (lifetime_in_months > 39 ||
  352. (lifetime_in_months == 39 && has_partial_month)) {
  353. num_required_embedded_scts = 5;
  354. } else if (lifetime_in_months > 27 ||
  355. (lifetime_in_months == 27 && has_partial_month)) {
  356. num_required_embedded_scts = 4;
  357. } else if (lifetime_in_months >= 15) {
  358. num_required_embedded_scts = 3;
  359. } else {
  360. num_required_embedded_scts = 2;
  361. }
  362. }
  363. // Sort the embedded log IDs and remove duplicates, so that only a single
  364. // SCT from each log is accepted. This is to handle the case where a given
  365. // log returns different SCTs for the same precertificate (which is
  366. // permitted, but advised against).
  367. std::sort(embedded_log_ids.begin(), embedded_log_ids.end());
  368. auto sorted_end =
  369. std::unique(embedded_log_ids.begin(), embedded_log_ids.end());
  370. size_t num_embedded_scts =
  371. std::distance(embedded_log_ids.begin(), sorted_end);
  372. if (num_embedded_scts >= num_required_embedded_scts)
  373. return CTPolicyCompliance::CT_POLICY_COMPLIES_VIA_SCTS;
  374. // Under Option 2, there weren't enough SCTs, and potentially under Option
  375. // 1, there weren't diverse enough SCTs. Try to signal the error that is
  376. // most easily fixed.
  377. return has_valid_nonembedded_sct
  378. ? CTPolicyCompliance::CT_POLICY_NOT_DIVERSE_SCTS
  379. : CTPolicyCompliance::CT_POLICY_NOT_ENOUGH_SCTS;
  380. }
  381. std::string ChromeCTPolicyEnforcer::GetOperatorForLog(
  382. std::string log_id,
  383. base::Time timestamp) const {
  384. if (valid_google_log_for_testing_.has_value() &&
  385. log_id == valid_google_log_for_testing_.value()) {
  386. return "Google";
  387. }
  388. DCHECK(log_operator_history_.find(log_id) != log_operator_history_.end());
  389. OperatorHistoryEntry log_history = log_operator_history_.at(log_id);
  390. for (auto operator_entry : log_history.previous_operators_) {
  391. if (timestamp < operator_entry.second)
  392. return operator_entry.first;
  393. }
  394. // Either the log has only ever had one operator, or the timestamp is after
  395. // the last operator change.
  396. return log_history.current_operator_;
  397. }
  398. } // namespace certificate_transparency