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