1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138
|
// Copyright 2016 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/single_tree_tracker.h"
#include <utility>
#include "base/metrics/histogram_macros.h"
#include "net/cert/ct_log_verifier.h"
#include "net/cert/signed_certificate_timestamp.h"
#include "net/cert/x509_certificate.h"
using net::ct::SignedTreeHead;
namespace {
// Enum indicating whether an SCT can be checked for inclusion and if not,
// the reason it cannot.
//
// Note: The numeric values are used within a histogram and should not change
// or be re-assigned.
enum SCTCanBeCheckedForInclusion {
// If the SingleTreeTracker does not have a valid STH, then a valid STH is
// first required to evaluate whether the SCT can be checked for inclusion
// or not.
VALID_STH_REQUIRED = 0,
// If the STH does not cover the SCT (the timestamp in the SCT is greater than
// MMD + timestamp in the STH), then a newer STH is needed.
NEWER_STH_REQUIRED = 1,
// When an SCT is observed, if the SingleTreeTracker instance has a valid STH
// and the STH covers the SCT (the timestamp in the SCT is less than MMD +
// timestamp in the STH), then it can be checked for inclusion.
CAN_BE_CHECKED = 2,
SCT_CAN_BE_CHECKED_MAX
};
// Measure how often clients encounter very new SCTs, by measuring whether an
// SCT can be checked for inclusion upon first observation.
void LogCanBeCheckedForInclusionToUMA(
SCTCanBeCheckedForInclusion can_be_checked) {
UMA_HISTOGRAM_ENUMERATION("Net.CertificateTransparency.CanInclusionCheckSCT",
can_be_checked, SCT_CAN_BE_CHECKED_MAX);
}
} // namespace
namespace certificate_transparency {
SingleTreeTracker::SingleTreeTracker(
scoped_refptr<const net::CTLogVerifier> ct_log)
: ct_log_(std::move(ct_log)) {}
SingleTreeTracker::~SingleTreeTracker() {}
void SingleTreeTracker::OnSCTVerified(
net::X509Certificate* cert,
const net::ct::SignedCertificateTimestamp* sct) {
DCHECK_EQ(ct_log_->key_id(), sct->log_id);
// SCT was previously observed, so its status should not be changed.
if (entries_status_.find(sct->timestamp) != entries_status_.end())
return;
// If there isn't a valid STH or the STH is not fresh enough to check
// inclusion against, store the SCT for future checking and return.
if (verified_sth_.timestamp.is_null() ||
(verified_sth_.timestamp <
(sct->timestamp + base::TimeDelta::FromHours(24)))) {
entries_status_.insert(
std::make_pair(sct->timestamp, SCT_PENDING_NEWER_STH));
if (!verified_sth_.timestamp.is_null()) {
LogCanBeCheckedForInclusionToUMA(NEWER_STH_REQUIRED);
} else {
LogCanBeCheckedForInclusionToUMA(VALID_STH_REQUIRED);
}
return;
}
LogCanBeCheckedForInclusionToUMA(CAN_BE_CHECKED);
// TODO(eranm): Check inclusion here.
entries_status_.insert(
std::make_pair(sct->timestamp, SCT_PENDING_INCLUSION_CHECK));
}
void SingleTreeTracker::NewSTHObserved(const SignedTreeHead& sth) {
DCHECK_EQ(ct_log_->key_id(), sth.log_id);
if (!ct_log_->VerifySignedTreeHead(sth)) {
// Sanity check the STH; the caller should have done this
// already, but being paranoid here.
// NOTE(eranm): Right now there's no way to get rid of this check here
// as this is the first object in the chain that has an instance of
// a CTLogVerifier to verify the STH.
return;
}
// In order to avoid updating |verified_sth_| to an older STH in case
// an older STH is observed, check that either the observed STH is for
// a larger tree size or that it is for the same tree size but has
// a newer timestamp.
const bool sths_for_same_tree = verified_sth_.tree_size == sth.tree_size;
const bool received_sth_is_for_larger_tree =
(verified_sth_.tree_size > sth.tree_size);
const bool received_sth_is_newer = (sth.timestamp > verified_sth_.timestamp);
if (verified_sth_.timestamp.is_null() || received_sth_is_for_larger_tree ||
(sths_for_same_tree && received_sth_is_newer)) {
verified_sth_ = sth;
}
// Find out which SCTs can now be checked for inclusion.
// TODO(eranm): Keep two maps of MerkleTreeLeaf instances, one for leaves
// pending inclusion checks and one for leaves pending a new STH.
// The comparison function between MerkleTreeLeaf instances should use the
// timestamp to determine sorting order, so that bulk moving from one
// map to the other can happen.
auto entry = entries_status_.begin();
while (entry != entries_status_.end() &&
entry->first < verified_sth_.timestamp) {
entry->second = SCT_PENDING_INCLUSION_CHECK;
++entry;
// TODO(eranm): Check inclusion here.
}
}
SingleTreeTracker::SCTInclusionStatus
SingleTreeTracker::GetLogEntryInclusionStatus(
net::X509Certificate* cert,
const net::ct::SignedCertificateTimestamp* sct) {
auto it = entries_status_.find(sct->timestamp);
return it == entries_status_.end() ? SCT_NOT_OBSERVED : it->second;
}
} // namespace certificate_transparency
|