// Copyright 2012 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/multi_threaded_cert_verifier.h" #include "base/check_op.h" #include "base/functional/bind.h" #include "base/functional/callback_helpers.h" #include "base/memory/raw_ptr.h" #include "base/memory/weak_ptr.h" #include "base/task/thread_pool.h" #include "base/threading/thread_restrictions.h" #include "crypto/crypto_buildflags.h" #include "net/base/net_errors.h" #include "net/base/trace_constants.h" #include "net/base/tracing.h" #include "net/cert/cert_verify_proc.h" #include "net/cert/cert_verify_result.h" #include "net/cert/crl_set.h" #include "net/cert/x509_certificate.h" #include "net/log/net_log_event_type.h" #include "net/log/net_log_source_type.h" #include "net/log/net_log_with_source.h" #if BUILDFLAG(USE_NSS_CERTS) #include "net/cert/x509_util_nss.h" #endif namespace net { // Allows DoVerifyOnWorkerThread to wait on a base::WaitableEvent. // DoVerifyOnWorkerThread may wait on network operations done on a separate // sequence. For instance when using the NSS-based implementation of certificate // verification, the library requires a blocking callback for fetching OCSP and // AIA responses. class [[maybe_unused, nodiscard]] MultiThreadedCertVerifierScopedAllowBaseSyncPrimitives : public base::ScopedAllowBaseSyncPrimitives{}; namespace { // Used to pass the result of DoVerifyOnWorkerThread() to // MultiThreadedCertVerifier::InternalRequest::OnJobComplete(). struct ResultHelper { int error; CertVerifyResult result; NetLogWithSource net_log; }; int GetFlagsForConfig(const CertVerifier::Config& config) { int flags = 0; if (config.enable_rev_checking) flags |= CertVerifyProc::VERIFY_REV_CHECKING_ENABLED; if (config.require_rev_checking_local_anchors) flags |= CertVerifyProc::VERIFY_REV_CHECKING_REQUIRED_LOCAL_ANCHORS; if (config.enable_sha1_local_anchors) flags |= CertVerifyProc::VERIFY_ENABLE_SHA1_LOCAL_ANCHORS; if (config.disable_symantec_enforcement) flags |= CertVerifyProc::VERIFY_DISABLE_SYMANTEC_ENFORCEMENT; return flags; } // Runs the verification synchronously on a worker thread. std::unique_ptr DoVerifyOnWorkerThread( const scoped_refptr& verify_proc, const scoped_refptr& cert, const std::string& hostname, const std::string& ocsp_response, const std::string& sct_list, int flags, const CertificateList& additional_trust_anchors, const NetLogWithSource& net_log) { TRACE_EVENT0(NetTracingCategory(), "DoVerifyOnWorkerThread"); auto verify_result = std::make_unique(); verify_result->net_log = net_log; MultiThreadedCertVerifierScopedAllowBaseSyncPrimitives allow_base_sync_primitives; verify_result->error = verify_proc->Verify( cert.get(), hostname, ocsp_response, sct_list, flags, additional_trust_anchors, &verify_result->result, net_log); // The CertVerifyResult is created and populated on the worker thread and // then returned to the network thread. Detach now before returning the // result, since any further access will be on the network thread. verify_result->result.DetachFromSequence(); return verify_result; } } // namespace // Helper to allow callers to cancel pending CertVerifier::Verify requests. // Note that because the CertVerifyProc is blocking, it's not actually // possible to cancel the in-progress request; instead, this simply guarantees // that the provided callback will not be invoked if the Request is deleted. class MultiThreadedCertVerifier::InternalRequest : public CertVerifier::Request, public base::LinkNode { public: InternalRequest(CompletionOnceCallback callback, CertVerifyResult* caller_result); ~InternalRequest() override; void Start(const scoped_refptr& verify_proc, const CertVerifier::Config& config, const CertVerifier::RequestParams& params, const NetLogWithSource& caller_net_log); void ResetCallback() { callback_.Reset(); } private: // This is a static method with a |self| weak pointer instead of a regular // method, so that PostTask will still run it even if the weakptr is no // longer valid. static void OnJobComplete(base::WeakPtr self, std::unique_ptr verify_result); CompletionOnceCallback callback_; raw_ptr caller_result_; base::WeakPtrFactory weak_factory_{this}; }; MultiThreadedCertVerifier::InternalRequest::InternalRequest( CompletionOnceCallback callback, CertVerifyResult* caller_result) : callback_(std::move(callback)), caller_result_(caller_result) {} MultiThreadedCertVerifier::InternalRequest::~InternalRequest() { if (callback_) { // This InternalRequest was eagerly cancelled as the callback is still // valid, so |this| needs to be removed from MultiThreadedCertVerifier's // list. RemoveFromList(); } } void MultiThreadedCertVerifier::InternalRequest::Start( const scoped_refptr& verify_proc, const CertVerifier::Config& config, const CertVerifier::RequestParams& params, const NetLogWithSource& caller_net_log) { const NetLogWithSource net_log(NetLogWithSource::Make( caller_net_log.net_log(), NetLogSourceType::CERT_VERIFIER_TASK)); net_log.BeginEvent(NetLogEventType::CERT_VERIFIER_TASK); caller_net_log.AddEventReferencingSource( NetLogEventType::CERT_VERIFIER_TASK_BOUND, net_log.source()); int flags = GetFlagsForConfig(config); if (params.flags() & CertVerifier::VERIFY_DISABLE_NETWORK_FETCHES) { flags |= CertVerifyProc::VERIFY_DISABLE_NETWORK_FETCHES; } base::ThreadPool::PostTaskAndReplyWithResult( FROM_HERE, {base::MayBlock(), base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN}, base::BindOnce(&DoVerifyOnWorkerThread, verify_proc, params.certificate(), params.hostname(), params.ocsp_response(), params.sct_list(), flags, config.additional_trust_anchors, net_log), base::BindOnce(&MultiThreadedCertVerifier::InternalRequest::OnJobComplete, weak_factory_.GetWeakPtr())); } // static void MultiThreadedCertVerifier::InternalRequest::OnJobComplete( base::WeakPtr self, std::unique_ptr verify_result) { // Always log the EndEvent, even if the Request has been destroyed. verify_result->net_log.EndEvent(NetLogEventType::CERT_VERIFIER_TASK); // Check |self| weakptr and don't continue if the Request was destroyed. if (!self) return; DCHECK(verify_result); // If the MultiThreadedCertVerifier has been deleted, the callback will have // been reset to null. if (!self->callback_) return; // If ~MultiThreadedCertVerifier has not Reset() our callback, then this // InternalRequest will not have been removed from MultiThreadedCertVerifier's // list yet. self->RemoveFromList(); *self->caller_result_ = verify_result->result; // Note: May delete |self|. std::move(self->callback_).Run(verify_result->error); } MultiThreadedCertVerifier::MultiThreadedCertVerifier( scoped_refptr verify_proc, scoped_refptr verify_proc_factory) : verify_proc_(std::move(verify_proc)), verify_proc_factory_(std::move(verify_proc_factory)) { CHECK(verify_proc_); CHECK(verify_proc_factory_); } MultiThreadedCertVerifier::~MultiThreadedCertVerifier() { DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); // Reset the callbacks for each InternalRequest to fulfill the respective // net::CertVerifier contract. for (base::LinkNode* node = request_list_.head(); node != request_list_.end();) { // Resetting the callback may delete the request, so save a pointer to the // next node first. base::LinkNode* next_node = node->next(); node->value()->ResetCallback(); node = next_node; } } int MultiThreadedCertVerifier::Verify(const RequestParams& params, CertVerifyResult* verify_result, CompletionOnceCallback callback, std::unique_ptr* out_req, const NetLogWithSource& net_log) { out_req->reset(); DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); if (callback.is_null() || !verify_result || params.hostname().empty()) return ERR_INVALID_ARGUMENT; std::unique_ptr request = std::make_unique(std::move(callback), verify_result); request->Start(verify_proc_, config_, params, net_log); request_list_.Append(request.get()); *out_req = std::move(request); return ERR_IO_PENDING; } void MultiThreadedCertVerifier::UpdateVerifyProcData( scoped_refptr cert_net_fetcher, const net::CertVerifyProcFactory::ImplParams& impl_params) { DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); verify_proc_ = verify_proc_factory_->CreateCertVerifyProc( std::move(cert_net_fetcher), impl_params); NotifyCertVerifierChanged(); } void MultiThreadedCertVerifier::SetConfig(const CertVerifier::Config& config) { DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); LOG_IF(DFATAL, verify_proc_ && !verify_proc_->SupportsAdditionalTrustAnchors() && !config.additional_trust_anchors.empty()) << "Attempted to set a CertVerifier::Config with additional trust " "anchors, but |verify_proc_| does not support additional trust " "anchors."; // TODO(https://crbug.com/978854): Pass these into the actual CertVerifyProc // rather than relying on global side-effects. #if !BUILDFLAG(USE_NSS_CERTS) // Not yet implemented. DCHECK(config.additional_untrusted_authorities.empty()); #else // Construct a temporary list and then swap that into the member variable, to // be polite to any verifications that might be in progress in a background // thread. This ensures that, at least for certs that are present in both the // old and new config, there will not be a time when the refcount drops to // zero. For the case where a cert was in the old config and is not in the // new config, it might be removed while a verification is still going on // that might be able to use it. Oh well. Ideally the list should be passed // into CertVerifyProc as noted by the TODO(https://crbug.com/978854), since // the workers could then keep a reference to the appropriate certs as long // as they need. net::ScopedCERTCertificateList temp_certs; for (const auto& cert : config.additional_untrusted_authorities) { ScopedCERTCertificate nss_cert = x509_util::CreateCERTCertificateFromX509Certificate(cert.get()); if (nss_cert) temp_certs.push_back(std::move(nss_cert)); } temp_certs_ = std::move(temp_certs); #endif config_ = config; } void MultiThreadedCertVerifier::AddObserver(Observer* observer) { DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); observers_.AddObserver(observer); } void MultiThreadedCertVerifier::RemoveObserver(Observer* observer) { DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); observers_.RemoveObserver(observer); } void MultiThreadedCertVerifier::NotifyCertVerifierChanged() { DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); for (Observer& observer : observers_) { observer.OnCertVerifierChanged(); } } } // namespace net