coalescing_cert_verifier.cc 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462
  1. // Copyright 2019 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 "net/cert/coalescing_cert_verifier.h"
  5. #include <algorithm>
  6. #include "base/bind.h"
  7. #include "base/containers/linked_list.h"
  8. #include "base/containers/unique_ptr_adapters.h"
  9. #include "base/memory/raw_ptr.h"
  10. #include "base/memory/weak_ptr.h"
  11. #include "base/metrics/histogram_macros.h"
  12. #include "base/strings/string_number_conversions.h"
  13. #include "base/time/time.h"
  14. #include "net/base/net_errors.h"
  15. #include "net/cert/cert_verify_result.h"
  16. #include "net/cert/crl_set.h"
  17. #include "net/cert/pem.h"
  18. #include "net/cert/x509_certificate_net_log_param.h"
  19. #include "net/log/net_log_event_type.h"
  20. #include "net/log/net_log_source.h"
  21. #include "net/log/net_log_source_type.h"
  22. #include "net/log/net_log_values.h"
  23. #include "net/log/net_log_with_source.h"
  24. namespace net {
  25. // DESIGN OVERVIEW:
  26. //
  27. // The CoalescingCertVerifier implements an algorithm to group multiple calls
  28. // to Verify() into a single Job. This avoids overloading the underlying
  29. // CertVerifier, particularly those that are expensive to talk to (e.g.
  30. // talking to the system verifier or across processes), batching multiple
  31. // requests to CoaleacingCertVerifier::Verify() into a single underlying call.
  32. //
  33. // However, this makes lifetime management a bit more complex.
  34. // - The Job object represents all of the state for a single verification to
  35. // the CoalescingCertVerifier's underlying CertVerifier.
  36. // * It keeps the CertVerifyResult alive, which is required as long as
  37. // there is a pending verification.
  38. // * It keeps the CertVerify::Request to the underlying verifier alive,
  39. // as long as there is a pending Request attached to the Job.
  40. // * It keeps track of every CoalescingCertVerifier::Request that is
  41. // interested in receiving notification. However, it does NOT own
  42. // these objects, and thus needs to coordinate with the Request (via
  43. // AddRequest/AbortRequest) to make sure it never has a stale
  44. // pointer.
  45. // NB: It would have also been possible for the Job to only
  46. // hold WeakPtr<Request>s, rather than Request*, but that seemed less
  47. // clear as to the lifetime invariants, even if it was more clear
  48. // about how the pointers are used.
  49. // - The Job object is always owned by the CoalescingCertVerifier. If the
  50. // CoalescingCertVerifier is deleted, all in-flight requests to the
  51. // underlying verifier should be cancelled. When the Job goes away, all the
  52. // Requests will be orphaned.
  53. // - The Request object is always owned by the CALLER. It is a handle to
  54. // allow a caller to cancel a request, per the CertVerifier interface. If
  55. // the Request goes away, no caller callbacks should be invoked if the Job
  56. // it was (previously) attached to completes.
  57. // - Per the CertVerifier interface, when the CoalescingCertVerifier is
  58. // deleted, then regardless of there being any live Requests, none of those
  59. // caller callbacks should be invoked.
  60. //
  61. // Finally, to add to the complexity, it's possible that, during the handling
  62. // of a result from the underlying CertVerifier, a Job may begin dispatching
  63. // to its Requests. The Request may delete the CoalescingCertVerifier. If that
  64. // happens, then the Job being processed is also deleted, and none of the
  65. // other Requests should be notified.
  66. namespace {
  67. base::Value CertVerifierParams(const CertVerifier::RequestParams& params) {
  68. base::Value::Dict dict;
  69. dict.Set("certificates",
  70. NetLogX509CertificateList(params.certificate().get()));
  71. if (!params.ocsp_response().empty()) {
  72. dict.Set("ocsp_response",
  73. PEMEncode(params.ocsp_response(), "NETLOG OCSP RESPONSE"));
  74. }
  75. if (!params.sct_list().empty()) {
  76. dict.Set("sct_list", PEMEncode(params.sct_list(), "NETLOG SCT LIST"));
  77. }
  78. dict.Set("host", NetLogStringValue(params.hostname()));
  79. dict.Set("verifier_flags", params.flags());
  80. return base::Value(std::move(dict));
  81. }
  82. } // namespace
  83. // Job contains all the state for a single verification using the underlying
  84. // verifier.
  85. class CoalescingCertVerifier::Job {
  86. public:
  87. Job(CoalescingCertVerifier* parent,
  88. const CertVerifier::RequestParams& params,
  89. NetLog* net_log,
  90. bool is_first_job);
  91. ~Job();
  92. const CertVerifier::RequestParams& params() const { return params_; }
  93. const CertVerifyResult& verify_result() const { return verify_result_; }
  94. // Attaches |request|, causing it to be notified once this Job completes.
  95. void AddRequest(CoalescingCertVerifier::Request* request);
  96. // Stops |request| from being notified. If there are no Requests remaining,
  97. // the Job will be cancelled.
  98. // NOTE: It's only necessary to call this if the Job has not yet completed.
  99. // If the Request has been notified of completion, this should not be called.
  100. void AbortRequest(CoalescingCertVerifier::Request* request);
  101. // Starts a verification using |underlying_verifier|. If this completes
  102. // synchronously, returns the result code, with the associated result being
  103. // available via |verify_result()|. Otherwise, it will complete
  104. // asynchronously, notifying any Requests associated via |AttachRequest|.
  105. int Start(CertVerifier* underlying_verifier);
  106. private:
  107. void OnVerifyComplete(int result);
  108. void LogMetrics();
  109. raw_ptr<CoalescingCertVerifier> parent_verifier_;
  110. const CertVerifier::RequestParams params_;
  111. const NetLogWithSource net_log_;
  112. bool is_first_job_ = false;
  113. CertVerifyResult verify_result_;
  114. base::TimeTicks start_time_;
  115. std::unique_ptr<CertVerifier::Request> pending_request_;
  116. base::LinkedList<CoalescingCertVerifier::Request> attached_requests_;
  117. base::WeakPtrFactory<Job> weak_ptr_factory_{this};
  118. };
  119. // Tracks the state associated with a single CoalescingCertVerifier::Verify
  120. // request.
  121. //
  122. // There are two ways for requests to be cancelled:
  123. // - The caller of Verify() can delete the Request object, indicating
  124. // they are no longer interested in this particular request.
  125. // - The caller can delete the CoalescingCertVerifier, which should cause
  126. // all in-process Jobs to be aborted and deleted. Any Requests attached to
  127. // Jobs should be orphaned, and do nothing when the Request is (eventually)
  128. // deleted.
  129. class CoalescingCertVerifier::Request
  130. : public base::LinkNode<CoalescingCertVerifier::Request>,
  131. public CertVerifier::Request {
  132. public:
  133. // Create a request that will be attached to |job|, and will notify
  134. // |callback| and fill |verify_result| if the Job completes successfully.
  135. // If the Request is deleted, or the Job is deleted, |callback| will not
  136. // be notified.
  137. Request(CoalescingCertVerifier::Job* job,
  138. CertVerifyResult* verify_result,
  139. CompletionOnceCallback callback,
  140. const NetLogWithSource& net_log);
  141. ~Request() override;
  142. const NetLogWithSource& net_log() const { return net_log_; }
  143. // Called by Job to complete the requests (either successfully or as a sign
  144. // that the underlying Job is going away).
  145. void Complete(int result);
  146. // Called when |job_| is being deleted, to ensure that the Request does not
  147. // attempt to access the Job further. No callbacks will be invoked,
  148. // consistent with the CoalescingCertVerifier's contract.
  149. void OnJobAbort();
  150. private:
  151. raw_ptr<CoalescingCertVerifier::Job> job_;
  152. raw_ptr<CertVerifyResult> verify_result_;
  153. CompletionOnceCallback callback_;
  154. const NetLogWithSource net_log_;
  155. };
  156. CoalescingCertVerifier::Job::Job(CoalescingCertVerifier* parent,
  157. const CertVerifier::RequestParams& params,
  158. NetLog* net_log,
  159. bool is_first_job)
  160. : parent_verifier_(parent),
  161. params_(params),
  162. net_log_(
  163. NetLogWithSource::Make(net_log, NetLogSourceType::CERT_VERIFIER_JOB)),
  164. is_first_job_(is_first_job) {}
  165. CoalescingCertVerifier::Job::~Job() {
  166. // If there was at least one outstanding Request still pending, then this
  167. // Job was aborted, rather than being completed normally and cleaned up.
  168. if (!attached_requests_.empty() && pending_request_) {
  169. net_log_.AddEvent(NetLogEventType::CANCELLED);
  170. net_log_.EndEvent(NetLogEventType::CERT_VERIFIER_JOB);
  171. }
  172. while (!attached_requests_.empty()) {
  173. auto* link_node = attached_requests_.head();
  174. link_node->RemoveFromList();
  175. link_node->value()->OnJobAbort();
  176. }
  177. }
  178. void CoalescingCertVerifier::Job::AddRequest(
  179. CoalescingCertVerifier::Request* request) {
  180. // There must be a pending asynchronous verification in process.
  181. DCHECK(pending_request_);
  182. request->net_log().AddEventReferencingSource(
  183. NetLogEventType::CERT_VERIFIER_REQUEST_BOUND_TO_JOB, net_log_.source());
  184. attached_requests_.Append(request);
  185. }
  186. void CoalescingCertVerifier::Job::AbortRequest(
  187. CoalescingCertVerifier::Request* request) {
  188. // Check to make sure |request| hasn't already been removed.
  189. DCHECK(request->previous() || request->next());
  190. request->RemoveFromList();
  191. // If there are no more pending requests, abort. This isn't strictly
  192. // necessary; the request could be allowed to run to completion (and
  193. // potentially to allow later Requests to join in), but in keeping with the
  194. // idea of providing more stable guarantees about resources, clean up early.
  195. if (attached_requests_.empty()) {
  196. // If this was the last Request, then the Job had not yet completed; this
  197. // matches the logic in the dtor, which handles when it's the Job that is
  198. // deleted first, rather than the last Request.
  199. net_log_.AddEvent(NetLogEventType::CANCELLED);
  200. net_log_.EndEvent(NetLogEventType::CERT_VERIFIER_JOB);
  201. // DANGER: This will cause |this_| to be deleted!
  202. parent_verifier_->RemoveJob(this);
  203. return;
  204. }
  205. }
  206. int CoalescingCertVerifier::Job::Start(CertVerifier* underlying_verifier) {
  207. // Requests are only attached for asynchronous completion, so they must
  208. // always be attached after Start() has been called.
  209. DCHECK(attached_requests_.empty());
  210. // There should not be a pending request already started (e.g. Start called
  211. // multiple times).
  212. DCHECK(!pending_request_);
  213. net_log_.BeginEvent(NetLogEventType::CERT_VERIFIER_JOB,
  214. [&] { return CertVerifierParams(params_); });
  215. verify_result_.Reset();
  216. start_time_ = base::TimeTicks::Now();
  217. int result = underlying_verifier->Verify(
  218. params_, &verify_result_,
  219. // Safe, because |verify_request_| is self-owned and guarantees the
  220. // callback won't be called if |this| is deleted.
  221. base::BindOnce(&CoalescingCertVerifier::Job::OnVerifyComplete,
  222. base::Unretained(this)),
  223. &pending_request_, net_log_);
  224. if (result != ERR_IO_PENDING) {
  225. LogMetrics();
  226. net_log_.EndEvent(NetLogEventType::CERT_VERIFIER_JOB,
  227. [&] { return verify_result_.NetLogParams(result); });
  228. }
  229. return result;
  230. }
  231. void CoalescingCertVerifier::Job::OnVerifyComplete(int result) {
  232. LogMetrics();
  233. pending_request_.reset(); // Reset to signal clean completion.
  234. net_log_.EndEvent(NetLogEventType::CERT_VERIFIER_JOB,
  235. [&] { return verify_result_.NetLogParams(result); });
  236. // It's possible that during the process of invoking a callback for a
  237. // Request, |this| may get deleted (along with the associated parent). If
  238. // that happens, it's important to ensure that processing of the Job is
  239. // stopped - i.e. no other callbacks are invoked for other Requests, nor is
  240. // |this| accessed.
  241. //
  242. // To help detect and protect against this, a WeakPtr to |this| is taken. If
  243. // |this| is deleted, the destructor will have invalidated the WeakPtr.
  244. //
  245. // Note that if a Job had already been deleted, this method would not have
  246. // been invoked in the first place, as the Job (via |pending_request_|) owns
  247. // the underlying CertVerifier::Request that this method was bound to as a
  248. // callback. This is why it's OK to grab the WeakPtr from |this| initially.
  249. base::WeakPtr<Job> weak_this = weak_ptr_factory_.GetWeakPtr();
  250. while (!attached_requests_.empty()) {
  251. // Note: It's also possible for additional Requests to be attached to the
  252. // current Job while processing a Request.
  253. auto* link_node = attached_requests_.head();
  254. link_node->RemoveFromList();
  255. // Note: |this| MAY be deleted here.
  256. // - If the CoalescingCertVerifier is deleted, it will delete the
  257. // Jobs (including |this|)
  258. // - If this is the second-to-last Request, and the completion of this
  259. // event causes the other Request to be deleted, detaching that Request
  260. // from this Job will lead to this Job being deleted (via
  261. // Job::AbortRequest())
  262. link_node->value()->Complete(result);
  263. // Check if |this| has been deleted (which implicitly includes
  264. // |parent_verifier_|), and abort if so, since no further cleanup is
  265. // needed.
  266. if (!weak_this)
  267. return;
  268. }
  269. // DANGER: |this| will be invalidated (deleted) after this point.
  270. return parent_verifier_->RemoveJob(this);
  271. }
  272. void CoalescingCertVerifier::Job::LogMetrics() {
  273. base::TimeDelta latency = base::TimeTicks::Now() - start_time_;
  274. UMA_HISTOGRAM_CUSTOM_TIMES("Net.CertVerifier_Job_Latency", latency,
  275. base::Milliseconds(1), base::Minutes(10), 100);
  276. if (is_first_job_) {
  277. UMA_HISTOGRAM_CUSTOM_TIMES("Net.CertVerifier_First_Job_Latency", latency,
  278. base::Milliseconds(1), base::Minutes(10), 100);
  279. }
  280. }
  281. CoalescingCertVerifier::Request::Request(CoalescingCertVerifier::Job* job,
  282. CertVerifyResult* verify_result,
  283. CompletionOnceCallback callback,
  284. const NetLogWithSource& net_log)
  285. : job_(job),
  286. verify_result_(verify_result),
  287. callback_(std::move(callback)),
  288. net_log_(net_log) {
  289. net_log_.BeginEvent(NetLogEventType::CERT_VERIFIER_REQUEST);
  290. }
  291. CoalescingCertVerifier::Request::~Request() {
  292. if (job_) {
  293. net_log_.AddEvent(NetLogEventType::CANCELLED);
  294. net_log_.EndEvent(NetLogEventType::CERT_VERIFIER_REQUEST);
  295. // If the Request is deleted before the Job, then detach from the Job.
  296. // Note: This may cause |job_| to be deleted.
  297. job_->AbortRequest(this);
  298. job_ = nullptr;
  299. }
  300. }
  301. void CoalescingCertVerifier::Request::Complete(int result) {
  302. DCHECK(job_); // There must be a pending/non-aborted job to complete.
  303. *verify_result_ = job_->verify_result();
  304. // On successful completion, the Job removes the Request from its set;
  305. // similarly, break the association here so that when the Request is
  306. // deleted, it does not try to abort the (now-completed) Job.
  307. job_ = nullptr;
  308. net_log_.EndEvent(NetLogEventType::CERT_VERIFIER_REQUEST);
  309. // Run |callback_|, which may delete |this|.
  310. std::move(callback_).Run(result);
  311. }
  312. void CoalescingCertVerifier::Request::OnJobAbort() {
  313. DCHECK(job_); // There must be a pending job to abort.
  314. // If the Job is deleted before the Request, just clean up. The Request will
  315. // eventually be deleted by the caller.
  316. net_log_.AddEvent(NetLogEventType::CANCELLED);
  317. net_log_.EndEvent(NetLogEventType::CERT_VERIFIER_REQUEST);
  318. job_ = nullptr;
  319. // Note: May delete |this|, if the caller made |callback_| own the Request.
  320. callback_.Reset();
  321. }
  322. CoalescingCertVerifier::CoalescingCertVerifier(
  323. std::unique_ptr<CertVerifier> verifier)
  324. : verifier_(std::move(verifier)) {}
  325. CoalescingCertVerifier::~CoalescingCertVerifier() = default;
  326. int CoalescingCertVerifier::Verify(
  327. const RequestParams& params,
  328. CertVerifyResult* verify_result,
  329. CompletionOnceCallback callback,
  330. std::unique_ptr<CertVerifier::Request>* out_req,
  331. const NetLogWithSource& net_log) {
  332. DCHECK(verify_result);
  333. DCHECK(!callback.is_null());
  334. out_req->reset();
  335. ++requests_;
  336. Job* job = FindJob(params);
  337. if (job) {
  338. // An identical request is in-flight and joinable, so just attach the
  339. // callback.
  340. ++inflight_joins_;
  341. } else {
  342. // No existing Jobs can be used. Create and start a new one.
  343. std::unique_ptr<Job> new_job =
  344. std::make_unique<Job>(this, params, net_log.net_log(), requests_ == 1);
  345. int result = new_job->Start(verifier_.get());
  346. if (result != ERR_IO_PENDING) {
  347. *verify_result = new_job->verify_result();
  348. return result;
  349. }
  350. job = new_job.get();
  351. joinable_jobs_[params] = std::move(new_job);
  352. }
  353. std::unique_ptr<CoalescingCertVerifier::Request> request =
  354. std::make_unique<CoalescingCertVerifier::Request>(
  355. job, verify_result, std::move(callback), net_log);
  356. job->AddRequest(request.get());
  357. *out_req = std::move(request);
  358. return ERR_IO_PENDING;
  359. }
  360. void CoalescingCertVerifier::SetConfig(const CertVerifier::Config& config) {
  361. ++config_id_;
  362. verifier_->SetConfig(config);
  363. for (auto& job : joinable_jobs_) {
  364. inflight_jobs_.emplace_back(std::move(job.second));
  365. }
  366. joinable_jobs_.clear();
  367. }
  368. CoalescingCertVerifier::Job* CoalescingCertVerifier::FindJob(
  369. const RequestParams& params) {
  370. auto it = joinable_jobs_.find(params);
  371. if (it != joinable_jobs_.end())
  372. return it->second.get();
  373. return nullptr;
  374. }
  375. void CoalescingCertVerifier::RemoveJob(Job* job) {
  376. // See if this was a job from the current configuration generation.
  377. // Note: It's also necessary to compare that the underlying pointer is the
  378. // same, and not merely a Job with the same parameters.
  379. auto joinable_it = joinable_jobs_.find(job->params());
  380. if (joinable_it != joinable_jobs_.end() && joinable_it->second.get() == job) {
  381. joinable_jobs_.erase(joinable_it);
  382. return;
  383. }
  384. // Otherwise, it MUST have been a job from a previous generation.
  385. auto inflight_it = std::find_if(inflight_jobs_.begin(), inflight_jobs_.end(),
  386. base::MatchesUniquePtr(job));
  387. DCHECK(inflight_it != inflight_jobs_.end());
  388. inflight_jobs_.erase(inflight_it);
  389. return;
  390. }
  391. } // namespace net