summaryrefslogtreecommitdiffstats
path: root/security/manager/ssl/SSLServerCertVerification.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'security/manager/ssl/SSLServerCertVerification.cpp')
-rw-r--r--security/manager/ssl/SSLServerCertVerification.cpp1130
1 files changed, 1130 insertions, 0 deletions
diff --git a/security/manager/ssl/SSLServerCertVerification.cpp b/security/manager/ssl/SSLServerCertVerification.cpp
new file mode 100644
index 0000000000..f22ddb5917
--- /dev/null
+++ b/security/manager/ssl/SSLServerCertVerification.cpp
@@ -0,0 +1,1130 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// During certificate authentication, we call CertVerifier::VerifySSLServerCert.
+// This function may make zero or more HTTP requests (e.g. to gather revocation
+// information). Our fetching logic for these requests processes them on the
+// socket transport service thread.
+//
+// Because the connection for which we are verifying the certificate is
+// happening on the socket transport thread, if our cert auth hook were to call
+// VerifySSLServerCert directly, there would be a deadlock: VerifySSLServerCert
+// would cause an event to be asynchronously posted to the socket transport
+// thread, and then it would block the socket transport thread waiting to be
+// notified of the HTTP response. However, the HTTP request would never actually
+// be processed because the socket transport thread would be blocked and so it
+// wouldn't be able process HTTP requests.
+//
+// Consequently, when we are asked to verify a certificate, we must always call
+// VerifySSLServerCert on another thread. To accomplish this, our auth cert hook
+// dispatches a SSLServerCertVerificationJob to a pool of background threads,
+// and then immediately returns SECWouldBlock to libssl. These jobs are where
+// VerifySSLServerCert is actually called.
+//
+// When our auth cert hook returns SECWouldBlock, libssl will carry on the
+// handshake while we validate the certificate. This will free up the socket
+// transport thread so that HTTP requests--including the OCSP requests needed
+// for cert verification as mentioned above--can be processed.
+//
+// Once VerifySSLServerCert returns, the cert verification job dispatches a
+// SSLServerCertVerificationResult to the socket transport thread; the
+// SSLServerCertVerificationResult will notify libssl that the certificate
+// authentication is complete. Once libssl is notified that the authentication
+// is complete, it will continue the TLS handshake (if it hasn't already
+// finished) and it will begin allowing us to send/receive data on the
+// connection.
+//
+// Timeline of events (for connections managed by the socket transport service):
+//
+// * libssl calls SSLServerCertVerificationJob::Dispatch on the socket
+// transport thread.
+// * SSLServerCertVerificationJob::Dispatch queues a job
+// (instance of SSLServerCertVerificationJob) to its background thread
+// pool and returns.
+// * One of the background threads calls CertVerifier::VerifySSLServerCert,
+// which may enqueue some HTTP request(s) onto the socket transport thread,
+// and then blocks that background thread waiting for the responses and/or
+// timeouts or errors for those requests.
+// * Once those HTTP responses have all come back or failed, the
+// CertVerifier::VerifySSLServerCert function returns a result indicating
+// that the validation succeeded or failed.
+// * If the validation succeeded, then a SSLServerCertVerificationResult
+// event is posted to the socket transport thread, and the cert
+// verification thread becomes free to verify other certificates.
+// * Otherwise, we do cert override processing to see if the validation
+// error can be convered by override rules. The result of this processing
+// is similarly dispatched in a SSLServerCertVerificationResult.
+// * The SSLServerCertVerificationResult event will either wake up the
+// socket (using SSL_AuthCertificateComplete) if validation succeeded or
+// there was an error override, or it will set an error flag so that the
+// next I/O operation on the socket will fail, causing the socket transport
+// thread to close the connection.
+//
+// SSLServerCertVerificationResult must be dispatched to the socket transport
+// thread because we must only call SSL_* functions on the socket transport
+// thread since they may do I/O, because many parts of NSSSocketControl and the
+// PSM NSS I/O layer are not thread-safe, and because we need the event to
+// interrupt the PR_Poll that may waiting for I/O on the socket for which we
+// are validating the cert.
+//
+// When socket process is enabled, libssl is running on socket process. To
+// perform certificate authentication with CertVerifier, we have to send all
+// needed information to parent process and send the result back to socket
+// process via IPC. The workflow is described below.
+// 1. In AuthCertificateHookInternal(), we call RemoteProcessCertVerification()
+// instead of SSLServerCertVerificationJob::Dispatch when we are on socket
+// process.
+// 2. In RemoteProcessCertVerification(), PVerifySSLServerCert actors will be
+// created on IPDL background thread for carrying needed information via IPC.
+// 3. On parent process, VerifySSLServerCertParent is created and it calls
+// SSLServerCertVerificationJob::Dispatch for doing certificate verification
+// on one of CertVerificationThreads.
+// 4. When validation is done, OnVerifiedSSLServerCertSuccess IPC message is
+// sent through the IPDL background thread when
+// CertVerifier::VerifySSLServerCert returns Success. Otherwise,
+// OnVerifiedSSLServerCertFailure is sent.
+// 5. After setp 4, PVerifySSLServerCert actors will be released. The
+// verification result will be dispatched via
+// SSLServerCertVerificationResult.
+
+#include "SSLServerCertVerification.h"
+
+#include <cstring>
+
+#include "CertVerifier.h"
+#include "CryptoTask.h"
+#include "ExtendedValidation.h"
+#include "NSSCertDBTrustDomain.h"
+#include "NSSSocketControl.h"
+#include "PSMRunnable.h"
+#include "RootCertificateTelemetryUtils.h"
+#include "ScopedNSSTypes.h"
+#include "SharedCertVerifier.h"
+#include "SharedSSLState.h"
+#include "VerifySSLServerCertChild.h"
+#include "cert.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/Casting.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/Unused.h"
+#include "nsComponentManagerUtils.h"
+#include "nsContentUtils.h"
+#include "nsICertOverrideService.h"
+#include "nsIPublicKeyPinningService.h"
+#include "nsISiteSecurityService.h"
+#include "nsISocketProvider.h"
+#include "nsThreadPool.h"
+#include "nsNetUtil.h"
+#include "nsNSSCertificate.h"
+#include "nsNSSComponent.h"
+#include "nsNSSIOLayer.h"
+#include "nsServiceManagerUtils.h"
+#include "nsString.h"
+#include "nsURLHelper.h"
+#include "nsXPCOMCIDInternal.h"
+#include "mozpkix/pkix.h"
+#include "mozpkix/pkixcheck.h"
+#include "mozpkix/pkixnss.h"
+#include "mozpkix/pkixutil.h"
+#include "secerr.h"
+#include "secport.h"
+#include "ssl.h"
+#include "sslerr.h"
+#include "sslexp.h"
+
+extern mozilla::LazyLogModule gPIPNSSLog;
+
+using namespace mozilla::pkix;
+
+namespace mozilla {
+namespace psm {
+
+// do not use a nsCOMPtr to avoid static initializer/destructor
+nsIThreadPool* gCertVerificationThreadPool = nullptr;
+
+// Called when the socket transport thread starts, to initialize the SSL cert
+// verification thread pool. By tying the thread pool startup/shutdown directly
+// to the STS thread's lifetime, we ensure that they are *always* available for
+// SSL connections and that there are no races during startup and especially
+// shutdown. (Previously, we have had multiple problems with races in PSM
+// background threads, and the race-prevention/shutdown logic used there is
+// brittle. Since this service is critical to things like downloading updates,
+// we take no chances.) Also, by doing things this way, we avoid the need for
+// locks, since gCertVerificationThreadPool is only ever accessed on the socket
+// transport thread.
+void InitializeSSLServerCertVerificationThreads() {
+ // TODO: tuning, make parameters preferences
+ gCertVerificationThreadPool = new nsThreadPool();
+ NS_ADDREF(gCertVerificationThreadPool);
+
+ (void)gCertVerificationThreadPool->SetIdleThreadLimit(5);
+ (void)gCertVerificationThreadPool->SetIdleThreadTimeout(30 * 1000);
+ (void)gCertVerificationThreadPool->SetThreadLimit(5);
+ (void)gCertVerificationThreadPool->SetName("SSL Cert"_ns);
+}
+
+// Called when the socket transport thread finishes, to destroy the thread
+// pool. Since the socket transport service has stopped processing events, it
+// will not attempt any more SSL I/O operations, so it is clearly safe to shut
+// down the SSL cert verification infrastructure. Also, the STS will not
+// dispatch many SSL verification result events at this point, so any pending
+// cert verifications will (correctly) fail at the point they are dispatched.
+//
+// The other shutdown race condition that is possible is a race condition with
+// shutdown of the nsNSSComponent service. We use the
+// nsNSSShutdownPreventionLock where needed (not here) to prevent that.
+void StopSSLServerCertVerificationThreads() {
+ if (gCertVerificationThreadPool) {
+ gCertVerificationThreadPool->Shutdown();
+ NS_RELEASE(gCertVerificationThreadPool);
+ }
+}
+
+// A probe value of 1 means "no error".
+uint32_t MapOverridableErrorToProbeValue(PRErrorCode errorCode) {
+ switch (errorCode) {
+ case SEC_ERROR_UNKNOWN_ISSUER:
+ return 2;
+ case SEC_ERROR_CA_CERT_INVALID:
+ return 3;
+ case SEC_ERROR_UNTRUSTED_ISSUER:
+ return 4;
+ case SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE:
+ return 5;
+ case SEC_ERROR_UNTRUSTED_CERT:
+ return 6;
+ case SEC_ERROR_INADEQUATE_KEY_USAGE:
+ return 7;
+ case SEC_ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED:
+ return 8;
+ case SSL_ERROR_BAD_CERT_DOMAIN:
+ return 9;
+ case SEC_ERROR_EXPIRED_CERTIFICATE:
+ return 10;
+ case mozilla::pkix::MOZILLA_PKIX_ERROR_CA_CERT_USED_AS_END_ENTITY:
+ return 11;
+ case mozilla::pkix::MOZILLA_PKIX_ERROR_V1_CERT_USED_AS_CA:
+ return 12;
+ case mozilla::pkix::MOZILLA_PKIX_ERROR_INADEQUATE_KEY_SIZE:
+ return 13;
+ case mozilla::pkix::MOZILLA_PKIX_ERROR_NOT_YET_VALID_CERTIFICATE:
+ return 14;
+ case mozilla::pkix::MOZILLA_PKIX_ERROR_NOT_YET_VALID_ISSUER_CERTIFICATE:
+ return 15;
+ case SEC_ERROR_INVALID_TIME:
+ return 16;
+ case mozilla::pkix::MOZILLA_PKIX_ERROR_EMPTY_ISSUER_NAME:
+ return 17;
+ case mozilla::pkix::MOZILLA_PKIX_ERROR_ADDITIONAL_POLICY_CONSTRAINT_FAILED:
+ return 18;
+ case mozilla::pkix::MOZILLA_PKIX_ERROR_SELF_SIGNED_CERT:
+ return 19;
+ case mozilla::pkix::MOZILLA_PKIX_ERROR_MITM_DETECTED:
+ return 20;
+ }
+ NS_WARNING(
+ "Unknown certificate error code. Does MapOverridableErrorToProbeValue "
+ "handle everything in CategorizeCertificateError?");
+ return 0;
+}
+
+static uint32_t MapCertErrorToProbeValue(PRErrorCode errorCode) {
+ uint32_t probeValue;
+ switch (errorCode) {
+ // see security/pkix/include/pkix/Result.h
+#define MOZILLA_PKIX_MAP(name, value, nss_name) \
+ case nss_name: \
+ probeValue = value; \
+ break;
+ MOZILLA_PKIX_MAP_LIST
+#undef MOZILLA_PKIX_MAP
+ default:
+ return 0;
+ }
+
+ // Since FATAL_ERROR_FLAG is 0x800, fatal error values are much larger than
+ // non-fatal error values. To conserve space, we remap these so they start at
+ // (decimal) 90 instead of 0x800. Currently there are ~50 non-fatal errors
+ // mozilla::pkix might return, so saving space for 90 should be sufficient
+ // (similarly, there are 4 fatal errors, so saving space for 10 should also
+ // be sufficient).
+ static_assert(
+ FATAL_ERROR_FLAG == 0x800,
+ "mozilla::pkix::FATAL_ERROR_FLAG is not what we were expecting");
+ if (probeValue & FATAL_ERROR_FLAG) {
+ probeValue ^= FATAL_ERROR_FLAG;
+ probeValue += 90;
+ }
+ return probeValue;
+}
+
+// If the given PRErrorCode is an overridable certificate error, return which
+// category (trust, time, domain mismatch) it falls in. If it is not
+// overridable, return Nothing.
+Maybe<nsITransportSecurityInfo::OverridableErrorCategory>
+CategorizeCertificateError(PRErrorCode certificateError) {
+ switch (certificateError) {
+ case SEC_ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED:
+ case SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE:
+ case SEC_ERROR_UNKNOWN_ISSUER:
+ case SEC_ERROR_CA_CERT_INVALID:
+ case mozilla::pkix::MOZILLA_PKIX_ERROR_ADDITIONAL_POLICY_CONSTRAINT_FAILED:
+ case mozilla::pkix::MOZILLA_PKIX_ERROR_CA_CERT_USED_AS_END_ENTITY:
+ case mozilla::pkix::MOZILLA_PKIX_ERROR_EMPTY_ISSUER_NAME:
+ case mozilla::pkix::MOZILLA_PKIX_ERROR_INADEQUATE_KEY_SIZE:
+ case mozilla::pkix::MOZILLA_PKIX_ERROR_MITM_DETECTED:
+ case mozilla::pkix::MOZILLA_PKIX_ERROR_NOT_YET_VALID_ISSUER_CERTIFICATE:
+ case mozilla::pkix::MOZILLA_PKIX_ERROR_SELF_SIGNED_CERT:
+ case mozilla::pkix::MOZILLA_PKIX_ERROR_V1_CERT_USED_AS_CA:
+ return Some(
+ nsITransportSecurityInfo::OverridableErrorCategory::ERROR_TRUST);
+
+ case SSL_ERROR_BAD_CERT_DOMAIN:
+ return Some(
+ nsITransportSecurityInfo::OverridableErrorCategory::ERROR_DOMAIN);
+
+ case SEC_ERROR_INVALID_TIME:
+ case SEC_ERROR_EXPIRED_CERTIFICATE:
+ case mozilla::pkix::MOZILLA_PKIX_ERROR_NOT_YET_VALID_CERTIFICATE:
+ return Some(
+ nsITransportSecurityInfo::OverridableErrorCategory::ERROR_TIME);
+
+ default:
+ break;
+ }
+ return Nothing();
+}
+
+// Helper function to determine if overrides are allowed for this host.
+// Overrides are not allowed for known HSTS hosts or hosts with pinning
+// information. However, IP addresses can never be HSTS hosts and don't have
+// pinning information.
+static nsresult OverrideAllowedForHost(
+ uint64_t aPtrForLog, const nsACString& aHostname,
+ const OriginAttributes& aOriginAttributes, /*out*/ bool& aOverrideAllowed) {
+ aOverrideAllowed = false;
+
+ // If this is an IP address, overrides are allowed, because an IP address is
+ // never an HSTS host. nsISiteSecurityService takes this into account
+ // already, but the real problem here is that calling NS_NewURI with an IPv6
+ // address fails. We do this to avoid that. A more comprehensive fix would be
+ // to have Necko provide an nsIURI to PSM and to use that here (and
+ // everywhere). However, that would be a wide-spanning change.
+ if (net_IsValidIPv6Addr(aHostname)) {
+ aOverrideAllowed = true;
+ return NS_OK;
+ }
+
+ // If this is an HTTP Strict Transport Security host or a pinned host and the
+ // certificate is bad, don't allow overrides (RFC 6797 section 12.1).
+ bool strictTransportSecurityEnabled = false;
+ bool isStaticallyPinned = false;
+ nsCOMPtr<nsISiteSecurityService> sss(do_GetService(NS_SSSERVICE_CONTRACTID));
+ if (!sss) {
+ MOZ_LOG(
+ gPIPNSSLog, LogLevel::Debug,
+ ("[0x%" PRIx64 "] Couldn't get nsISiteSecurityService to check HSTS",
+ aPtrForLog));
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = NS_NewURI(getter_AddRefs(uri), "https://"_ns + aHostname);
+ if (NS_FAILED(rv)) {
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
+ ("[0x%" PRIx64 "] Creating new URI failed", aPtrForLog));
+ return rv;
+ }
+
+ rv =
+ sss->IsSecureURI(uri, aOriginAttributes, &strictTransportSecurityEnabled);
+ if (NS_FAILED(rv)) {
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
+ ("[0x%" PRIx64 "] checking for HSTS failed", aPtrForLog));
+ return rv;
+ }
+
+ nsCOMPtr<nsIPublicKeyPinningService> pkps =
+ do_GetService(NS_PKPSERVICE_CONTRACTID, &rv);
+ if (!pkps) {
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
+ ("[0x%" PRIx64
+ "] Couldn't get nsIPublicKeyPinningService to check pinning",
+ aPtrForLog));
+ return NS_ERROR_FAILURE;
+ }
+ rv = pkps->HostHasPins(uri, &isStaticallyPinned);
+ if (NS_FAILED(rv)) {
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
+ ("[0x%" PRIx64 "] checking for static pin failed", aPtrForLog));
+ return rv;
+ }
+
+ aOverrideAllowed = !strictTransportSecurityEnabled && !isStaticallyPinned;
+ return NS_OK;
+}
+
+// This function assumes that we will only use the SPDY connection coalescing
+// feature on connections where we have negotiated SPDY using NPN. If we ever
+// talk SPDY without having negotiated it with SPDY, this code will give wrong
+// and perhaps unsafe results.
+//
+// Returns SECSuccess on the initial handshake of all connections, on
+// renegotiations for any connections where we did not negotiate SPDY, or on any
+// SPDY connection where the server's certificate did not change.
+//
+// Prohibit changing the server cert only if we negotiated SPDY,
+// in order to support SPDY's cross-origin connection pooling.
+static SECStatus BlockServerCertChangeForSpdy(
+ NSSSocketControl* socketControl, const UniqueCERTCertificate& serverCert) {
+ if (!socketControl->IsHandshakeCompleted()) {
+ // first handshake on this connection, not a
+ // renegotiation.
+ return SECSuccess;
+ }
+
+ // Filter out sockets that did not neogtiate SPDY via NPN
+ nsCOMPtr<nsITransportSecurityInfo> securityInfo;
+ nsresult rv = socketControl->GetSecurityInfo(getter_AddRefs(securityInfo));
+ MOZ_ASSERT(NS_SUCCEEDED(rv), "GetSecurityInfo() failed during renegotiation");
+ if (NS_FAILED(rv) || !securityInfo) {
+ PR_SetError(SEC_ERROR_LIBRARY_FAILURE, 0);
+ return SECFailure;
+ }
+ nsAutoCString negotiatedNPN;
+ rv = securityInfo->GetNegotiatedNPN(negotiatedNPN);
+ MOZ_ASSERT(NS_SUCCEEDED(rv),
+ "GetNegotiatedNPN() failed during renegotiation");
+
+ if (NS_SUCCEEDED(rv) && !StringBeginsWith(negotiatedNPN, "spdy/"_ns)) {
+ return SECSuccess;
+ }
+ // If GetNegotiatedNPN() failed we will assume spdy for safety's safe
+ if (NS_FAILED(rv)) {
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
+ ("BlockServerCertChangeForSpdy failed GetNegotiatedNPN() call."
+ " Assuming spdy."));
+ }
+
+ // Check to see if the cert has actually changed
+ nsCOMPtr<nsIX509Cert> cert(socketControl->GetServerCert());
+ if (!cert) {
+ PR_SetError(SEC_ERROR_LIBRARY_FAILURE, 0);
+ return SECFailure;
+ }
+ nsTArray<uint8_t> certDER;
+ if (NS_FAILED(cert->GetRawDER(certDER))) {
+ PR_SetError(SEC_ERROR_LIBRARY_FAILURE, 0);
+ return SECFailure;
+ }
+ if (certDER.Length() == serverCert->derCert.len &&
+ memcmp(certDER.Elements(), serverCert->derCert.data, certDER.Length()) ==
+ 0) {
+ return SECSuccess;
+ }
+
+ // Report an error - changed cert is confirmed
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
+ ("SPDY refused to allow new cert during renegotiation"));
+ PR_SetError(SSL_ERROR_RENEGOTIATION_NOT_ALLOWED, 0);
+ return SECFailure;
+}
+
+void GatherTelemetryForSingleSCT(const ct::VerifiedSCT& verifiedSct) {
+ // See SSL_SCTS_ORIGIN in Histograms.json.
+ uint32_t origin = 0;
+ switch (verifiedSct.origin) {
+ case ct::VerifiedSCT::Origin::Embedded:
+ origin = 1;
+ break;
+ case ct::VerifiedSCT::Origin::TLSExtension:
+ origin = 2;
+ break;
+ case ct::VerifiedSCT::Origin::OCSPResponse:
+ origin = 3;
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unexpected VerifiedSCT::Origin type");
+ }
+ Telemetry::Accumulate(Telemetry::SSL_SCTS_ORIGIN, origin);
+
+ // See SSL_SCTS_VERIFICATION_STATUS in Histograms.json.
+ uint32_t verificationStatus = 0;
+ switch (verifiedSct.status) {
+ case ct::VerifiedSCT::Status::Valid:
+ verificationStatus = 1;
+ break;
+ case ct::VerifiedSCT::Status::UnknownLog:
+ verificationStatus = 2;
+ break;
+ case ct::VerifiedSCT::Status::InvalidSignature:
+ verificationStatus = 3;
+ break;
+ case ct::VerifiedSCT::Status::InvalidTimestamp:
+ verificationStatus = 4;
+ break;
+ case ct::VerifiedSCT::Status::ValidFromDisqualifiedLog:
+ verificationStatus = 5;
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unexpected VerifiedSCT::Status type");
+ }
+ Telemetry::Accumulate(Telemetry::SSL_SCTS_VERIFICATION_STATUS,
+ verificationStatus);
+}
+
+void GatherCertificateTransparencyTelemetry(
+ const nsTArray<uint8_t>& rootCert, bool isEV,
+ const CertificateTransparencyInfo& info) {
+ if (!info.enabled) {
+ // No telemetry is gathered when CT is disabled.
+ return;
+ }
+
+ for (const ct::VerifiedSCT& sct : info.verifyResult.verifiedScts) {
+ GatherTelemetryForSingleSCT(sct);
+ }
+
+ // Decoding errors are reported to the 0th bucket
+ // of the SSL_SCTS_VERIFICATION_STATUS enumerated probe.
+ for (size_t i = 0; i < info.verifyResult.decodingErrors; ++i) {
+ Telemetry::Accumulate(Telemetry::SSL_SCTS_VERIFICATION_STATUS, 0);
+ }
+
+ // Handle the histogram of SCTs counts.
+ uint32_t sctsCount =
+ static_cast<uint32_t>(info.verifyResult.verifiedScts.size());
+ // Note that sctsCount can also be 0 in case we've received SCT binary data,
+ // but it failed to parse (e.g. due to unsupported CT protocol version).
+ Telemetry::Accumulate(Telemetry::SSL_SCTS_PER_CONNECTION, sctsCount);
+
+ // Report CT Policy compliance of EV certificates.
+ if (isEV) {
+ uint32_t evCompliance = 0;
+ switch (info.policyCompliance) {
+ case ct::CTPolicyCompliance::Compliant:
+ evCompliance = 1;
+ break;
+ case ct::CTPolicyCompliance::NotEnoughScts:
+ evCompliance = 2;
+ break;
+ case ct::CTPolicyCompliance::NotDiverseScts:
+ evCompliance = 3;
+ break;
+ case ct::CTPolicyCompliance::Unknown:
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unexpected CTPolicyCompliance type");
+ }
+ Telemetry::Accumulate(Telemetry::SSL_CT_POLICY_COMPLIANCE_OF_EV_CERTS,
+ evCompliance);
+ }
+
+ // Report CT Policy compliance by CA.
+ switch (info.policyCompliance) {
+ case ct::CTPolicyCompliance::Compliant:
+ AccumulateTelemetryForRootCA(
+ Telemetry::SSL_CT_POLICY_COMPLIANT_CONNECTIONS_BY_CA, rootCert);
+ break;
+ case ct::CTPolicyCompliance::NotEnoughScts:
+ case ct::CTPolicyCompliance::NotDiverseScts:
+ AccumulateTelemetryForRootCA(
+ Telemetry::SSL_CT_POLICY_NON_COMPLIANT_CONNECTIONS_BY_CA, rootCert);
+ break;
+ case ct::CTPolicyCompliance::Unknown:
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unexpected CTPolicyCompliance type");
+ }
+}
+
+// This function collects telemetry about certs. It will be called on one of
+// CertVerificationThread. When the socket process is used this will be called
+// on the parent process.
+static void CollectCertTelemetry(
+ mozilla::pkix::Result aCertVerificationResult, EVStatus aEVStatus,
+ CertVerifier::OCSPStaplingStatus aOcspStaplingStatus,
+ KeySizeStatus aKeySizeStatus,
+ const PinningTelemetryInfo& aPinningTelemetryInfo,
+ const nsTArray<nsTArray<uint8_t>>& aBuiltCertChain,
+ const CertificateTransparencyInfo& aCertificateTransparencyInfo) {
+ uint32_t evStatus = (aCertVerificationResult != Success) ? 0 // 0 = Failure
+ : (aEVStatus != EVStatus::EV) ? 1 // 1 = DV
+ : 2; // 2 = EV
+ Telemetry::Accumulate(Telemetry::CERT_EV_STATUS, evStatus);
+
+ if (aOcspStaplingStatus != CertVerifier::OCSP_STAPLING_NEVER_CHECKED) {
+ Telemetry::Accumulate(Telemetry::SSL_OCSP_STAPLING, aOcspStaplingStatus);
+ }
+
+ if (aKeySizeStatus != KeySizeStatus::NeverChecked) {
+ Telemetry::Accumulate(Telemetry::CERT_CHAIN_KEY_SIZE_STATUS,
+ static_cast<uint32_t>(aKeySizeStatus));
+ }
+
+ if (aPinningTelemetryInfo.accumulateForRoot) {
+ Telemetry::Accumulate(Telemetry::CERT_PINNING_FAILURES_BY_CA,
+ aPinningTelemetryInfo.rootBucket);
+ }
+
+ if (aPinningTelemetryInfo.accumulateResult) {
+ MOZ_ASSERT(aPinningTelemetryInfo.certPinningResultHistogram.isSome());
+ Telemetry::Accumulate(
+ aPinningTelemetryInfo.certPinningResultHistogram.value(),
+ aPinningTelemetryInfo.certPinningResultBucket);
+ }
+
+ if (aCertVerificationResult == Success && aBuiltCertChain.Length() > 0) {
+ const nsTArray<uint8_t>& rootCert = aBuiltCertChain.LastElement();
+ AccumulateTelemetryForRootCA(Telemetry::CERT_VALIDATION_SUCCESS_BY_CA,
+ rootCert);
+ GatherCertificateTransparencyTelemetry(rootCert, aEVStatus == EVStatus::EV,
+ aCertificateTransparencyInfo);
+ }
+}
+
+// Note: Takes ownership of |peerCertChain| if SECSuccess is not returned.
+Result AuthCertificate(
+ CertVerifier& certVerifier, void* aPinArg,
+ const nsTArray<uint8_t>& certBytes,
+ const nsTArray<nsTArray<uint8_t>>& peerCertChain,
+ const nsACString& aHostName, const OriginAttributes& aOriginAttributes,
+ const Maybe<nsTArray<uint8_t>>& stapledOCSPResponse,
+ const Maybe<nsTArray<uint8_t>>& sctsFromTLSExtension,
+ const Maybe<DelegatedCredentialInfo>& dcInfo, uint32_t providerFlags,
+ Time time, uint32_t certVerifierFlags,
+ /*out*/ nsTArray<nsTArray<uint8_t>>& builtCertChain,
+ /*out*/ EVStatus& evStatus,
+ /*out*/ CertificateTransparencyInfo& certificateTransparencyInfo,
+ /*out*/ bool& aIsBuiltCertChainRootBuiltInRoot,
+ /*out*/ bool& aMadeOCSPRequests) {
+ CertVerifier::OCSPStaplingStatus ocspStaplingStatus =
+ CertVerifier::OCSP_STAPLING_NEVER_CHECKED;
+ KeySizeStatus keySizeStatus = KeySizeStatus::NeverChecked;
+ PinningTelemetryInfo pinningTelemetryInfo;
+
+ nsTArray<nsTArray<uint8_t>> peerCertsBytes;
+ // Don't include the end-entity certificate.
+ if (!peerCertChain.IsEmpty()) {
+ std::transform(
+ peerCertChain.cbegin() + 1, peerCertChain.cend(),
+ MakeBackInserter(peerCertsBytes),
+ [](const auto& elementArray) { return elementArray.Clone(); });
+ }
+
+ Result rv = certVerifier.VerifySSLServerCert(
+ certBytes, time, aPinArg, aHostName, builtCertChain, certVerifierFlags,
+ Some(std::move(peerCertsBytes)), stapledOCSPResponse,
+ sctsFromTLSExtension, dcInfo, aOriginAttributes, &evStatus,
+ &ocspStaplingStatus, &keySizeStatus, &pinningTelemetryInfo,
+ &certificateTransparencyInfo, &aIsBuiltCertChainRootBuiltInRoot,
+ &aMadeOCSPRequests);
+
+ CollectCertTelemetry(rv, evStatus, ocspStaplingStatus, keySizeStatus,
+ pinningTelemetryInfo, builtCertChain,
+ certificateTransparencyInfo);
+
+ return rv;
+}
+
+PRErrorCode AuthCertificateParseResults(
+ uint64_t aPtrForLog, const nsACString& aHostName, int32_t aPort,
+ const OriginAttributes& aOriginAttributes,
+ const nsCOMPtr<nsIX509Cert>& aCert, mozilla::pkix::Time aTime,
+ PRErrorCode aCertVerificationError,
+ /* out */
+ nsITransportSecurityInfo::OverridableErrorCategory&
+ aOverridableErrorCategory) {
+ uint32_t probeValue = MapCertErrorToProbeValue(aCertVerificationError);
+ Telemetry::Accumulate(Telemetry::SSL_CERT_VERIFICATION_ERRORS, probeValue);
+
+ Maybe<nsITransportSecurityInfo::OverridableErrorCategory>
+ maybeOverridableErrorCategory =
+ CategorizeCertificateError(aCertVerificationError);
+ // If this isn't an overridable error, return it now. This will stop the
+ // connection and report the given error.
+ if (!maybeOverridableErrorCategory.isSome()) {
+ return aCertVerificationError;
+ }
+ aOverridableErrorCategory = *maybeOverridableErrorCategory;
+
+ bool overrideAllowed = false;
+ nsresult rv = OverrideAllowedForHost(aPtrForLog, aHostName, aOriginAttributes,
+ overrideAllowed);
+ if (NS_FAILED(rv)) {
+ return aCertVerificationError;
+ }
+
+ if (!overrideAllowed) {
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
+ ("[0x%" PRIx64 "] HSTS or pinned host - no overrides allowed",
+ aPtrForLog));
+ return aCertVerificationError;
+ }
+
+ nsCOMPtr<nsICertOverrideService> overrideService =
+ do_GetService(NS_CERTOVERRIDE_CONTRACTID);
+ if (!overrideService) {
+ return aCertVerificationError;
+ }
+ bool haveOverride;
+ bool isTemporaryOverride;
+ rv = overrideService->HasMatchingOverride(aHostName, aPort, aOriginAttributes,
+ aCert, &isTemporaryOverride,
+ &haveOverride);
+ if (NS_FAILED(rv)) {
+ return aCertVerificationError;
+ }
+ Unused << isTemporaryOverride;
+ if (haveOverride) {
+ uint32_t probeValue =
+ MapOverridableErrorToProbeValue(aCertVerificationError);
+ Telemetry::Accumulate(Telemetry::SSL_CERT_ERROR_OVERRIDES, probeValue);
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
+ ("[0x%" PRIx64 "] certificate error overridden", aPtrForLog));
+ return 0;
+ }
+
+ return aCertVerificationError;
+}
+
+static nsTArray<nsTArray<uint8_t>> CreateCertBytesArray(
+ const UniqueCERTCertList& aCertChain) {
+ nsTArray<nsTArray<uint8_t>> certsBytes;
+ for (CERTCertListNode* n = CERT_LIST_HEAD(aCertChain);
+ !CERT_LIST_END(n, aCertChain); n = CERT_LIST_NEXT(n)) {
+ nsTArray<uint8_t> certBytes;
+ certBytes.AppendElements(n->cert->derCert.data, n->cert->derCert.len);
+ certsBytes.AppendElement(std::move(certBytes));
+ }
+ return certsBytes;
+}
+
+/*static*/
+SECStatus SSLServerCertVerificationJob::Dispatch(
+ uint64_t addrForLogging, void* aPinArg,
+ nsTArray<nsTArray<uint8_t>>&& peerCertChain, const nsACString& aHostName,
+ int32_t aPort, const OriginAttributes& aOriginAttributes,
+ Maybe<nsTArray<uint8_t>>& stapledOCSPResponse,
+ Maybe<nsTArray<uint8_t>>& sctsFromTLSExtension,
+ Maybe<DelegatedCredentialInfo>& dcInfo, uint32_t providerFlags, Time time,
+ uint32_t certVerifierFlags,
+ BaseSSLServerCertVerificationResult* aResultTask) {
+ // Runs on the socket transport thread
+ if (!aResultTask || peerCertChain.IsEmpty()) {
+ MOZ_ASSERT_UNREACHABLE(
+ "must have result task and non-empty peer cert chain");
+ PR_SetError(SEC_ERROR_LIBRARY_FAILURE, 0);
+ return SECFailure;
+ }
+
+ if (!gCertVerificationThreadPool) {
+ PR_SetError(PR_INVALID_STATE_ERROR, 0);
+ return SECFailure;
+ }
+
+ RefPtr<SSLServerCertVerificationJob> job(new SSLServerCertVerificationJob(
+ addrForLogging, aPinArg, std::move(peerCertChain), aHostName, aPort,
+ aOriginAttributes, stapledOCSPResponse, sctsFromTLSExtension, dcInfo,
+ providerFlags, time, certVerifierFlags, aResultTask));
+
+ nsresult nrv = gCertVerificationThreadPool->Dispatch(job, NS_DISPATCH_NORMAL);
+ if (NS_FAILED(nrv)) {
+ // We can't call SetCertVerificationResult here to change
+ // mCertVerificationState because SetCertVerificationResult will call
+ // libssl functions that acquire SSL locks that are already being held at
+ // this point. However, we can set an error with PR_SetError and return
+ // SECFailure, and the correct thing will happen (the error will be
+ // propagated and this connection will be terminated).
+ PRErrorCode error = nrv == NS_ERROR_OUT_OF_MEMORY ? PR_OUT_OF_MEMORY_ERROR
+ : PR_INVALID_STATE_ERROR;
+ PR_SetError(error, 0);
+ return SECFailure;
+ }
+
+ PR_SetError(PR_WOULD_BLOCK_ERROR, 0);
+ return SECWouldBlock;
+}
+
+NS_IMETHODIMP
+SSLServerCertVerificationJob::Run() {
+ // Runs on a cert verification thread and only on parent process.
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ MOZ_LOG(
+ gPIPNSSLog, LogLevel::Debug,
+ ("[%" PRIx64 "] SSLServerCertVerificationJob::Run\n", mAddrForLogging));
+
+ RefPtr<SharedCertVerifier> certVerifier(GetDefaultCertVerifier());
+ if (!certVerifier) {
+ PR_SetError(SEC_ERROR_NOT_INITIALIZED, 0);
+ return NS_OK;
+ }
+
+ TimeStamp jobStartTime = TimeStamp::Now();
+ EVStatus evStatus;
+ CertificateTransparencyInfo certificateTransparencyInfo;
+ bool isCertChainRootBuiltInRoot = false;
+ bool madeOCSPRequests = false;
+ nsTArray<nsTArray<uint8_t>> builtChainBytesArray;
+ nsTArray<uint8_t> certBytes(mPeerCertChain.ElementAt(0).Clone());
+ Result rv = AuthCertificate(
+ *certVerifier, mPinArg, certBytes, mPeerCertChain, mHostName,
+ mOriginAttributes, mStapledOCSPResponse, mSCTsFromTLSExtension, mDCInfo,
+ mProviderFlags, mTime, mCertVerifierFlags, builtChainBytesArray, evStatus,
+ certificateTransparencyInfo, isCertChainRootBuiltInRoot,
+ madeOCSPRequests);
+
+ if (rv == Success) {
+ Telemetry::AccumulateTimeDelta(
+ Telemetry::SSL_SUCCESFUL_CERT_VALIDATION_TIME_MOZILLAPKIX, jobStartTime,
+ TimeStamp::Now());
+ Telemetry::Accumulate(Telemetry::SSL_CERT_ERROR_OVERRIDES, 1);
+
+ mResultTask->Dispatch(
+ std::move(builtChainBytesArray), std::move(mPeerCertChain),
+ TransportSecurityInfo::ConvertCertificateTransparencyInfoToStatus(
+ certificateTransparencyInfo),
+ evStatus, true, 0,
+ nsITransportSecurityInfo::OverridableErrorCategory::ERROR_UNSET,
+ isCertChainRootBuiltInRoot, mProviderFlags, madeOCSPRequests);
+ return NS_OK;
+ }
+
+ Telemetry::AccumulateTimeDelta(
+ Telemetry::SSL_INITIAL_FAILED_CERT_VALIDATION_TIME_MOZILLAPKIX,
+ jobStartTime, TimeStamp::Now());
+
+ PRErrorCode error = MapResultToPRErrorCode(rv);
+ nsITransportSecurityInfo::OverridableErrorCategory overridableErrorCategory =
+ nsITransportSecurityInfo::OverridableErrorCategory::ERROR_UNSET;
+ nsCOMPtr<nsIX509Cert> cert(new nsNSSCertificate(std::move(certBytes)));
+ PRErrorCode finalError = AuthCertificateParseResults(
+ mAddrForLogging, mHostName, mPort, mOriginAttributes, cert, mTime, error,
+ overridableErrorCategory);
+
+ // NB: finalError may be 0 here, in which the connection will continue.
+ mResultTask->Dispatch(
+ std::move(builtChainBytesArray), std::move(mPeerCertChain),
+ nsITransportSecurityInfo::CERTIFICATE_TRANSPARENCY_NOT_APPLICABLE,
+ EVStatus::NotEV, false, finalError, overridableErrorCategory, false,
+ mProviderFlags, madeOCSPRequests);
+ return NS_OK;
+}
+
+// Takes information needed for cert verification, does some consistency
+// checks and calls SSLServerCertVerificationJob::Dispatch.
+SECStatus AuthCertificateHookInternal(
+ CommonSocketControl* socketControl, const void* aPtrForLogging,
+ const nsACString& hostName, nsTArray<nsTArray<uint8_t>>&& peerCertChain,
+ Maybe<nsTArray<uint8_t>>& stapledOCSPResponse,
+ Maybe<nsTArray<uint8_t>>& sctsFromTLSExtension,
+ Maybe<DelegatedCredentialInfo>& dcInfo, uint32_t providerFlags,
+ uint32_t certVerifierFlags) {
+ // Runs on the socket transport thread
+
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
+ ("[%p] starting AuthCertificateHookInternal\n", aPtrForLogging));
+
+ if (!socketControl || peerCertChain.IsEmpty()) {
+ PR_SetError(PR_INVALID_STATE_ERROR, 0);
+ return SECFailure;
+ }
+
+ bool onSTSThread;
+ nsresult nrv;
+ nsCOMPtr<nsIEventTarget> sts =
+ do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &nrv);
+ if (NS_SUCCEEDED(nrv)) {
+ nrv = sts->IsOnCurrentThread(&onSTSThread);
+ }
+
+ if (NS_FAILED(nrv)) {
+ NS_ERROR("Could not get STS service or IsOnCurrentThread failed");
+ PR_SetError(PR_UNKNOWN_ERROR, 0);
+ return SECFailure;
+ }
+
+ MOZ_ASSERT(onSTSThread);
+
+ if (!onSTSThread) {
+ PR_SetError(PR_INVALID_STATE_ERROR, 0);
+ return SECFailure;
+ }
+
+ uint64_t addr = reinterpret_cast<uintptr_t>(aPtrForLogging);
+ RefPtr<SSLServerCertVerificationResult> resultTask =
+ new SSLServerCertVerificationResult(socketControl);
+
+ if (XRE_IsSocketProcess()) {
+ return RemoteProcessCertVerification(
+ std::move(peerCertChain), hostName, socketControl->GetPort(),
+ socketControl->GetOriginAttributes(), stapledOCSPResponse,
+ sctsFromTLSExtension, dcInfo, providerFlags, certVerifierFlags,
+ resultTask);
+ }
+
+ // We *must* do certificate verification on a background thread because
+ // we need the socket transport thread to be free for our OCSP requests,
+ // and we *want* to do certificate verification on a background thread
+ // because of the performance benefits of doing so.
+ return SSLServerCertVerificationJob::Dispatch(
+ addr, socketControl, std::move(peerCertChain), hostName,
+ socketControl->GetPort(), socketControl->GetOriginAttributes(),
+ stapledOCSPResponse, sctsFromTLSExtension, dcInfo, providerFlags, Now(),
+ certVerifierFlags, resultTask);
+}
+
+// Extracts whatever information we need out of fd (using SSL_*) and passes it
+// to AuthCertificateHookInternal. AuthCertificateHookInternal will call
+// SSLServerCertVerificationJob::Dispatch. SSLServerCertVerificationJob
+// should never do anything with fd except logging.
+SECStatus AuthCertificateHook(void* arg, PRFileDesc* fd, PRBool checkSig,
+ PRBool isServer) {
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
+ ("[%p] starting AuthCertificateHook\n", fd));
+
+ // Modern libssl always passes PR_TRUE for checkSig, and we have no means of
+ // doing verification without checking signatures.
+ MOZ_ASSERT(checkSig, "AuthCertificateHook: checkSig unexpectedly false");
+
+ // PSM never causes libssl to call this function with PR_TRUE for isServer,
+ // and many things in PSM assume that we are a client.
+ MOZ_ASSERT(!isServer, "AuthCertificateHook: isServer unexpectedly true");
+
+ NSSSocketControl* socketInfo = static_cast<NSSSocketControl*>(arg);
+
+ UniqueCERTCertificate serverCert(SSL_PeerCertificate(fd));
+
+ if (!checkSig || isServer || !socketInfo || !serverCert) {
+ PR_SetError(PR_INVALID_STATE_ERROR, 0);
+ return SECFailure;
+ }
+ socketInfo->SetFullHandshake();
+
+ if (BlockServerCertChangeForSpdy(socketInfo, serverCert) != SECSuccess) {
+ return SECFailure;
+ }
+
+ UniqueCERTCertList peerCertChain(SSL_PeerCertificateChain(fd));
+ if (!peerCertChain) {
+ PR_SetError(PR_INVALID_STATE_ERROR, 0);
+ return SECFailure;
+ }
+
+ nsTArray<nsTArray<uint8_t>> peerCertsBytes =
+ CreateCertBytesArray(peerCertChain);
+
+ // SSL_PeerStapledOCSPResponses will never return a non-empty response if
+ // OCSP stapling wasn't enabled because libssl wouldn't have let the server
+ // return a stapled OCSP response.
+ // We don't own these pointers.
+ const SECItemArray* csa = SSL_PeerStapledOCSPResponses(fd);
+ Maybe<nsTArray<uint8_t>> stapledOCSPResponse;
+ // we currently only support single stapled responses
+ if (csa && csa->len == 1) {
+ stapledOCSPResponse.emplace();
+ stapledOCSPResponse->SetCapacity(csa->items[0].len);
+ stapledOCSPResponse->AppendElements(csa->items[0].data, csa->items[0].len);
+ }
+
+ Maybe<nsTArray<uint8_t>> sctsFromTLSExtension;
+ const SECItem* sctsFromTLSExtensionSECItem = SSL_PeerSignedCertTimestamps(fd);
+ if (sctsFromTLSExtensionSECItem) {
+ sctsFromTLSExtension.emplace();
+ sctsFromTLSExtension->SetCapacity(sctsFromTLSExtensionSECItem->len);
+ sctsFromTLSExtension->AppendElements(sctsFromTLSExtensionSECItem->data,
+ sctsFromTLSExtensionSECItem->len);
+ }
+
+ uint32_t providerFlags = 0;
+ socketInfo->GetProviderFlags(&providerFlags);
+
+ uint32_t certVerifierFlags = 0;
+ if (!socketInfo->SharedState().IsOCSPStaplingEnabled() ||
+ !socketInfo->SharedState().IsOCSPMustStapleEnabled()) {
+ certVerifierFlags |= CertVerifier::FLAG_TLS_IGNORE_STATUS_REQUEST;
+ }
+
+ // Get DC information
+ Maybe<DelegatedCredentialInfo> dcInfo;
+ SSLPreliminaryChannelInfo channelPreInfo;
+ SECStatus rv = SSL_GetPreliminaryChannelInfo(fd, &channelPreInfo,
+ sizeof(channelPreInfo));
+ if (rv != SECSuccess) {
+ PR_SetError(PR_INVALID_STATE_ERROR, 0);
+ return SECFailure;
+ }
+ if (channelPreInfo.peerDelegCred) {
+ dcInfo.emplace(DelegatedCredentialInfo(channelPreInfo.signatureScheme,
+ channelPreInfo.authKeyBits));
+ }
+
+ // If we configured an ECHConfig and NSS returned the public name
+ // for verification, ECH was rejected. Proceed, verifying to the
+ // public name. The result determines how NSS will fail (i.e. with
+ // any provided retry_configs if successful). See draft-ietf-tls-esni-08.
+ nsCString echConfig;
+ nsresult nsrv = socketInfo->GetEchConfig(echConfig);
+ bool verifyToEchPublicName =
+ NS_SUCCEEDED(nsrv) && echConfig.Length() && channelPreInfo.echPublicName;
+
+ const nsCString echPublicName(channelPreInfo.echPublicName);
+ const nsACString& hostname =
+ verifyToEchPublicName ? echPublicName : socketInfo->GetHostName();
+ socketInfo->SetCertVerificationWaiting();
+ rv = AuthCertificateHookInternal(socketInfo, static_cast<const void*>(fd),
+ hostname, std::move(peerCertsBytes),
+ stapledOCSPResponse, sctsFromTLSExtension,
+ dcInfo, providerFlags, certVerifierFlags);
+ return rv;
+}
+
+// Takes information needed for cert verification, does some consistency
+// checks and calls SSLServerCertVerificationJob::Dispatch.
+// This function is used for Quic.
+SECStatus AuthCertificateHookWithInfo(
+ CommonSocketControl* socketControl, const nsACString& aHostName,
+ const void* aPtrForLogging, nsTArray<nsTArray<uint8_t>>&& peerCertChain,
+ Maybe<nsTArray<nsTArray<uint8_t>>>& stapledOCSPResponses,
+ Maybe<nsTArray<uint8_t>>& sctsFromTLSExtension, uint32_t providerFlags) {
+ if (peerCertChain.IsEmpty()) {
+ PR_SetError(PR_INVALID_STATE_ERROR, 0);
+ return SECFailure;
+ }
+
+ // we currently only support single stapled responses
+ Maybe<nsTArray<uint8_t>> stapledOCSPResponse;
+ if (stapledOCSPResponses && (stapledOCSPResponses->Length() == 1)) {
+ stapledOCSPResponse.emplace(stapledOCSPResponses->ElementAt(0).Clone());
+ }
+
+ uint32_t certVerifierFlags = 0;
+ // QuicSocketControl does not have a SharedState as NSSSocketControl.
+ // Here we need prefs for ocsp. This are prefs they are the same for
+ // PublicSSLState and PrivateSSLState, just take them from one of them.
+ if (!PublicSSLState()->IsOCSPStaplingEnabled() ||
+ !PublicSSLState()->IsOCSPMustStapleEnabled()) {
+ certVerifierFlags |= CertVerifier::FLAG_TLS_IGNORE_STATUS_REQUEST;
+ }
+
+ // Need to update Quic stack to reflect the PreliminaryInfo fields
+ // for Delegated Credentials.
+ Maybe<DelegatedCredentialInfo> dcInfo;
+
+ return AuthCertificateHookInternal(socketControl, aPtrForLogging, aHostName,
+ std::move(peerCertChain),
+ stapledOCSPResponse, sctsFromTLSExtension,
+ dcInfo, providerFlags, certVerifierFlags);
+}
+
+NS_IMPL_ISUPPORTS_INHERITED0(SSLServerCertVerificationResult, Runnable)
+
+SSLServerCertVerificationResult::SSLServerCertVerificationResult(
+ CommonSocketControl* socketControl)
+ : Runnable("psm::SSLServerCertVerificationResult"),
+ mSocketControl(socketControl),
+ mCertificateTransparencyStatus(0),
+ mEVStatus(EVStatus::NotEV),
+ mSucceeded(false),
+ mFinalError(0),
+ mOverridableErrorCategory(
+ nsITransportSecurityInfo::OverridableErrorCategory::ERROR_UNSET),
+ mProviderFlags(0) {}
+
+void SSLServerCertVerificationResult::Dispatch(
+ nsTArray<nsTArray<uint8_t>>&& aBuiltChain,
+ nsTArray<nsTArray<uint8_t>>&& aPeerCertChain,
+ uint16_t aCertificateTransparencyStatus, EVStatus aEVStatus,
+ bool aSucceeded, PRErrorCode aFinalError,
+ nsITransportSecurityInfo::OverridableErrorCategory
+ aOverridableErrorCategory,
+ bool aIsBuiltCertChainRootBuiltInRoot, uint32_t aProviderFlags,
+ bool aMadeOCSPRequests) {
+ mBuiltChain = std::move(aBuiltChain);
+ mPeerCertChain = std::move(aPeerCertChain);
+ mCertificateTransparencyStatus = aCertificateTransparencyStatus;
+ mEVStatus = aEVStatus;
+ mSucceeded = aSucceeded;
+ mFinalError = aFinalError;
+ mOverridableErrorCategory = aOverridableErrorCategory;
+ mIsBuiltCertChainRootBuiltInRoot = aIsBuiltCertChainRootBuiltInRoot;
+ mProviderFlags = aProviderFlags;
+ mMadeOCSPRequests = aMadeOCSPRequests;
+
+ if (mSucceeded && mBuiltChain.IsEmpty()) {
+ MOZ_ASSERT_UNREACHABLE(
+ "if the handshake succeeded, the built chain shouldn't be empty");
+ mSucceeded = false;
+ mFinalError = SEC_ERROR_LIBRARY_FAILURE;
+ }
+ if (!mSucceeded && mPeerCertChain.IsEmpty()) {
+ MOZ_ASSERT_UNREACHABLE(
+ "if the handshake failed, the peer chain shouldn't be empty");
+ mFinalError = SEC_ERROR_LIBRARY_FAILURE;
+ }
+
+ nsresult rv;
+ nsCOMPtr<nsIEventTarget> stsTarget =
+ do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv);
+ MOZ_ASSERT(stsTarget, "Failed to get socket transport service event target");
+ rv = stsTarget->Dispatch(this, NS_DISPATCH_NORMAL);
+ MOZ_ASSERT(NS_SUCCEEDED(rv),
+ "Failed to dispatch SSLServerCertVerificationResult");
+}
+
+NS_IMETHODIMP
+SSLServerCertVerificationResult::Run() {
+#ifdef DEBUG
+ bool onSTSThread = false;
+ nsresult nrv;
+ nsCOMPtr<nsIEventTarget> sts =
+ do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &nrv);
+ if (NS_SUCCEEDED(nrv)) {
+ nrv = sts->IsOnCurrentThread(&onSTSThread);
+ }
+
+ MOZ_ASSERT(onSTSThread);
+#endif
+
+ if (mSucceeded && !XRE_IsSocketProcess() &&
+ !(mProviderFlags & nsISocketProvider::NO_PERMANENT_STORAGE)) {
+ // This dispatches an event that will run when the socket thread is idle.
+ SaveIntermediateCerts(mBuiltChain);
+ }
+
+ mSocketControl->SetMadeOCSPRequests(mMadeOCSPRequests);
+
+ if (mSucceeded) {
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
+ ("SSLServerCertVerificationResult::Run setting NEW cert"));
+ nsTArray<uint8_t> certBytes(mBuiltChain.ElementAt(0).Clone());
+ nsCOMPtr<nsIX509Cert> cert(new nsNSSCertificate(std::move(certBytes)));
+ mSocketControl->SetServerCert(cert, mEVStatus);
+ mSocketControl->SetSucceededCertChain(std::move(mBuiltChain));
+
+ mSocketControl->SetIsBuiltCertChainRootBuiltInRoot(
+ mIsBuiltCertChainRootBuiltInRoot);
+ mSocketControl->SetCertificateTransparencyStatus(
+ mCertificateTransparencyStatus);
+ } else {
+ nsTArray<uint8_t> certBytes(mPeerCertChain.ElementAt(0).Clone());
+ nsCOMPtr<nsIX509Cert> cert(new nsNSSCertificate(std::move(certBytes)));
+ // Certificate validation failed; store the peer certificate chain on
+ // mSocketControl so it can be used for error reporting.
+ mSocketControl->SetFailedCertChain(std::move(mPeerCertChain));
+ if (mOverridableErrorCategory !=
+ nsITransportSecurityInfo::OverridableErrorCategory::ERROR_UNSET) {
+ mSocketControl->SetStatusErrorBits(cert, mOverridableErrorCategory);
+ }
+ }
+
+ mSocketControl->SetCertVerificationResult(mFinalError);
+ return NS_OK;
+}
+
+} // namespace psm
+} // namespace mozilla