blob: 67e95c70c33cd7251112d19f64830fe6300328d7 [file] [log] [blame]
// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "net/cert/caching_cert_verifier.h"
#include <utility>
#include "base/functional/bind.h"
#include "base/time/time.h"
#include "net/base/net_errors.h"
namespace net {
namespace {
// The maximum number of cache entries to use for the ExpiringCache.
const unsigned kMaxCacheEntries = 256;
// The number of seconds to cache entries.
const unsigned kTTLSecs = 1800; // 30 minutes.
} // namespace
CachingCertVerifier::CachingCertVerifier(std::unique_ptr<CertVerifier> verifier)
: verifier_(std::move(verifier)), cache_(kMaxCacheEntries) {
verifier_->AddObserver(this);
CertDatabase::GetInstance()->AddObserver(this);
}
CachingCertVerifier::~CachingCertVerifier() {
CertDatabase::GetInstance()->RemoveObserver(this);
verifier_->RemoveObserver(this);
}
int CachingCertVerifier::Verify(const CertVerifier::RequestParams& params,
CertVerifyResult* verify_result,
CompletionOnceCallback callback,
std::unique_ptr<Request>* out_req,
const NetLogWithSource& net_log) {
out_req->reset();
requests_++;
const CertVerificationCache::value_type* cached_entry =
cache_.Get(params, CacheValidityPeriod(base::Time::Now()));
if (cached_entry) {
++cache_hits_;
*verify_result = cached_entry->result;
return cached_entry->error;
}
base::Time start_time = base::Time::Now();
CompletionOnceCallback caching_callback = base::BindOnce(
&CachingCertVerifier::OnRequestFinished, base::Unretained(this),
config_id_, params, start_time, std::move(callback), verify_result);
int result = verifier_->Verify(params, verify_result,
std::move(caching_callback), out_req, net_log);
if (result != ERR_IO_PENDING) {
// Synchronous completion; add directly to cache.
AddResultToCache(config_id_, params, start_time, *verify_result, result);
}
return result;
}
void CachingCertVerifier::SetConfig(const CertVerifier::Config& config) {
verifier_->SetConfig(config);
config_id_++;
ClearCache();
}
void CachingCertVerifier::AddObserver(CertVerifier::Observer* observer) {
verifier_->AddObserver(observer);
}
void CachingCertVerifier::RemoveObserver(CertVerifier::Observer* observer) {
verifier_->RemoveObserver(observer);
}
CachingCertVerifier::CachedResult::CachedResult() = default;
CachingCertVerifier::CachedResult::~CachedResult() = default;
CachingCertVerifier::CacheValidityPeriod::CacheValidityPeriod(base::Time now)
: verification_time(now), expiration_time(now) {}
CachingCertVerifier::CacheValidityPeriod::CacheValidityPeriod(
base::Time now,
base::Time expiration)
: verification_time(now), expiration_time(expiration) {}
bool CachingCertVerifier::CacheExpirationFunctor::operator()(
const CacheValidityPeriod& now,
const CacheValidityPeriod& expiration) const {
// Ensure this functor is being used for expiration only, and not strict
// weak ordering/sorting. |now| should only ever contain a single
// base::Time.
// Note: DCHECK_EQ is not used due to operator<< overloading requirements.
DCHECK(now.verification_time == now.expiration_time);
// |now| contains only a single time (verification_time), while |expiration|
// contains the validity range - both when the certificate was verified and
// when the verification result should expire.
//
// If the user receives a "not yet valid" message, and adjusts their clock
// foward to the correct time, this will (typically) cause
// now.verification_time to advance past expiration.expiration_time, thus
// treating the cached result as an expired entry and re-verifying.
// If the user receives a "expired" message, and adjusts their clock
// backwards to the correct time, this will cause now.verification_time to
// be less than expiration_verification_time, thus treating the cached
// result as an expired entry and re-verifying.
// If the user receives either of those messages, and does not adjust their
// clock, then the result will be (typically) be cached until the expiration
// TTL.
//
// This algorithm is only problematic if the user consistently keeps
// adjusting their clock backwards in increments smaller than the expiration
// TTL, in which case, cached elements continue to be added. However,
// because the cache has a fixed upper bound, if no entries are expired, a
// 'random' entry will be, thus keeping the memory constraints bounded over
// time.
return now.verification_time >= expiration.verification_time &&
now.verification_time < expiration.expiration_time;
}
void CachingCertVerifier::OnRequestFinished(uint32_t config_id,
const RequestParams& params,
base::Time start_time,
CompletionOnceCallback callback,
CertVerifyResult* verify_result,
int error) {
AddResultToCache(config_id, params, start_time, *verify_result, error);
// Now chain to the user's callback, which may delete |this|.
std::move(callback).Run(error);
}
void CachingCertVerifier::AddResultToCache(
uint32_t config_id,
const RequestParams& params,
base::Time start_time,
const CertVerifyResult& verify_result,
int error) {
// If the configuration has changed since this verification was started,
// don't add it to the cache.
if (config_id != config_id_)
return;
// When caching, this uses the time that validation started as the
// beginning of the validity, rather than the time that it ended (aka
// base::Time::Now()), to account for the fact that during validation,
// the clock may have changed.
//
// If the clock has changed significantly, then this result will ideally
// be evicted and the next time the certificate is encountered, it will
// be revalidated.
//
// Because of this, it's possible for situations to arise where the
// clock was correct at the start of validation, changed to an
// incorrect time during validation (such as too far in the past or
// future), and then was reset to the correct time. If this happens,
// it's likely that the result will not be a valid/correct result,
// but will still be used from the cache because the clock was reset
// to the correct time after the (bad) validation result completed.
//
// However, this solution optimizes for the case where the clock is
// bad at the start of validation, and subsequently is corrected. In
// that situation, the result is also incorrect, but because the clock
// was corrected after validation, if the cache validity period was
// computed at the end of validation, it would continue to serve an
// invalid result for kTTLSecs.
CachedResult cached_result;
cached_result.error = error;
cached_result.result = verify_result;
cache_.Put(
params, cached_result, CacheValidityPeriod(start_time),
CacheValidityPeriod(start_time, start_time + base::Seconds(kTTLSecs)));
}
void CachingCertVerifier::OnCertVerifierChanged() {
config_id_++;
ClearCache();
}
void CachingCertVerifier::OnCertDBChanged() {
config_id_++;
ClearCache();
}
void CachingCertVerifier::ClearCache() {
cache_.Clear();
}
size_t CachingCertVerifier::GetCacheSize() const {
return cache_.size();
}
} // namespace net