summaryrefslogtreecommitdiffstats
path: root/security/manager/ssl/SSLServerCertVerification.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
commit2aa4a82499d4becd2284cdb482213d541b8804dd (patch)
treeb80bf8bf13c3766139fbacc530efd0dd9d54394c /security/manager/ssl/SSLServerCertVerification.cpp
parentInitial commit. (diff)
downloadfirefox-2aa4a82499d4becd2284cdb482213d541b8804dd.tar.xz
firefox-2aa4a82499d4becd2284cdb482213d541b8804dd.zip
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'security/manager/ssl/SSLServerCertVerification.cpp')
-rw-r--r--security/manager/ssl/SSLServerCertVerification.cpp1422
1 files changed, 1422 insertions, 0 deletions
diff --git a/security/manager/ssl/SSLServerCertVerification.cpp b/security/manager/ssl/SSLServerCertVerification.cpp
new file mode 100644
index 0000000000..3c5512c95e
--- /dev/null
+++ b/security/manager/ssl/SSLServerCertVerification.cpp
@@ -0,0 +1,1422 @@
+/* -*- 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 nsNSSSocketInfo (the
+// subclass of TransportSecurityInfo used when validating certificates during
+// an SSL handshake) 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 "BRNameMatchingPolicy.h"
+#include "CertVerifier.h"
+#include "CryptoTask.h"
+#include "ExtendedValidation.h"
+#include "NSSCertDBTrustDomain.h"
+#include "PSMRunnable.h"
+#include "RootCertificateTelemetryUtils.h"
+#include "ScopedNSSTypes.h"
+#include "SharedCertVerifier.h"
+#include "SharedSSLState.h"
+#include "TransportSecurityInfo.h" // For RememberCertErrorsTable
+#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 "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/pkixnss.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 {
+
+namespace {
+
+// do not use a nsCOMPtr to avoid static initializer/destructor
+nsIThreadPool* gCertVerificationThreadPool = nullptr;
+
+} // unnamed namespace
+
+// 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);
+ }
+}
+
+namespace {
+
+// 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 DetermineCertOverrideErrors?");
+ 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;
+}
+
+SECStatus DetermineCertOverrideErrors(const UniqueCERTCertificate& cert,
+ const nsACString& hostName, PRTime now,
+ PRErrorCode defaultErrorCodeToReport,
+ /*out*/ uint32_t& collectedErrors,
+ /*out*/ PRErrorCode& errorCodeTrust,
+ /*out*/ PRErrorCode& errorCodeMismatch,
+ /*out*/ PRErrorCode& errorCodeTime) {
+ MOZ_ASSERT(cert);
+ MOZ_ASSERT(collectedErrors == 0);
+ MOZ_ASSERT(errorCodeTrust == 0);
+ MOZ_ASSERT(errorCodeMismatch == 0);
+ MOZ_ASSERT(errorCodeTime == 0);
+
+ // Assumes the error prioritization described in mozilla::pkix's
+ // BuildForward function. Also assumes that CheckCertHostname was only
+ // called if CertVerifier::VerifyCert succeeded.
+ switch (defaultErrorCodeToReport) {
+ 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: {
+ collectedErrors = nsICertOverrideService::ERROR_UNTRUSTED;
+ errorCodeTrust = defaultErrorCodeToReport;
+
+ SECCertTimeValidity validity =
+ CERT_CheckCertValidTimes(cert.get(), now, false);
+ if (validity == secCertTimeUndetermined) {
+ // This only happens if cert is null. CERT_CheckCertValidTimes will
+ // have set the error code to SEC_ERROR_INVALID_ARGS. We should really
+ // be using mozilla::pkix here anyway.
+ MOZ_ASSERT(PR_GetError() == SEC_ERROR_INVALID_ARGS);
+ return SECFailure;
+ }
+ if (validity == secCertTimeExpired) {
+ collectedErrors |= nsICertOverrideService::ERROR_TIME;
+ errorCodeTime = SEC_ERROR_EXPIRED_CERTIFICATE;
+ } else if (validity == secCertTimeNotValidYet) {
+ collectedErrors |= nsICertOverrideService::ERROR_TIME;
+ errorCodeTime =
+ mozilla::pkix::MOZILLA_PKIX_ERROR_NOT_YET_VALID_CERTIFICATE;
+ }
+ break;
+ }
+
+ case SEC_ERROR_INVALID_TIME:
+ case SEC_ERROR_EXPIRED_CERTIFICATE:
+ case mozilla::pkix::MOZILLA_PKIX_ERROR_NOT_YET_VALID_CERTIFICATE:
+ collectedErrors = nsICertOverrideService::ERROR_TIME;
+ errorCodeTime = defaultErrorCodeToReport;
+ break;
+
+ case SSL_ERROR_BAD_CERT_DOMAIN:
+ collectedErrors = nsICertOverrideService::ERROR_MISMATCH;
+ errorCodeMismatch = SSL_ERROR_BAD_CERT_DOMAIN;
+ break;
+
+ case 0:
+ NS_ERROR("No error code set during certificate validation failure.");
+ PR_SetError(PR_INVALID_STATE_ERROR, 0);
+ return SECFailure;
+
+ default:
+ PR_SetError(defaultErrorCodeToReport, 0);
+ return SECFailure;
+ }
+
+ if (defaultErrorCodeToReport != SSL_ERROR_BAD_CERT_DOMAIN) {
+ Input certInput;
+ if (certInput.Init(cert->derCert.data, cert->derCert.len) != Success) {
+ PR_SetError(SEC_ERROR_BAD_DER, 0);
+ return SECFailure;
+ }
+ Input hostnameInput;
+ Result result = hostnameInput.Init(
+ BitwiseCast<const uint8_t*, const char*>(hostName.BeginReading()),
+ hostName.Length());
+ if (result != Success) {
+ PR_SetError(SEC_ERROR_INVALID_ARGS, 0);
+ return SECFailure;
+ }
+ // Use a lax policy so as to not generate potentially spurious name
+ // mismatch "hints".
+ BRNameMatchingPolicy nameMatchingPolicy(
+ BRNameMatchingPolicy::Mode::DoNotEnforce);
+ // CheckCertHostname expects that its input represents a certificate that
+ // has already been successfully validated by BuildCertChain. This is
+ // obviously not the case, however, because we're in the error path of
+ // certificate verification. Thus, this is problematic. In the future, it
+ // would be nice to remove this optimistic additional error checking and
+ // simply punt to the front-end, which can more easily (and safely) perform
+ // extra checks to give the user hints as to why verification failed.
+ result = CheckCertHostname(certInput, hostnameInput, nameMatchingPolicy);
+ // Treat malformed name information as a domain mismatch.
+ if (result == Result::ERROR_BAD_DER ||
+ result == Result::ERROR_BAD_CERT_DOMAIN) {
+ collectedErrors |= nsICertOverrideService::ERROR_MISMATCH;
+ errorCodeMismatch = SSL_ERROR_BAD_CERT_DOMAIN;
+ } else if (IsFatalError(result)) {
+ // Because its input has not been validated by BuildCertChain,
+ // CheckCertHostname can return an error that is less important than the
+ // original certificate verification error. Only return an error result
+ // from this function if we've encountered a fatal error.
+ PR_SetError(MapResultToPRErrorCode(result), 0);
+ return SECFailure;
+ }
+ }
+
+ return SECSuccess;
+}
+
+// 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, uint32_t aProviderFlags,
+ /*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(nsISiteSecurityService::HEADER_HSTS, uri,
+ aProviderFlags, aOriginAttributes, nullptr, nullptr,
+ &strictTransportSecurityEnabled);
+ if (NS_FAILED(rv)) {
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
+ ("[0x%" PRIx64 "] checking for HSTS failed", aPtrForLog));
+ return rv;
+ }
+
+ rv = sss->IsSecureURI(nsISiteSecurityService::STATIC_PINNING, uri,
+ aProviderFlags, aOriginAttributes, nullptr, nullptr,
+ &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(
+ nsNSSSocketInfo* infoObject, const UniqueCERTCertificate& serverCert) {
+ // Get the existing cert. If there isn't one, then there is
+ // no cert change to worry about.
+ nsCOMPtr<nsIX509Cert> cert;
+
+ if (!infoObject->IsHandshakeCompleted()) {
+ // first handshake on this connection, not a
+ // renegotiation.
+ return SECSuccess;
+ }
+
+ infoObject->GetServerCert(getter_AddRefs(cert));
+ if (!cert) {
+ MOZ_ASSERT_UNREACHABLE(
+ "TransportSecurityInfo must have a cert implementing nsIX509Cert");
+ PR_SetError(SEC_ERROR_LIBRARY_FAILURE, 0);
+ return SECFailure;
+ }
+
+ // Filter out sockets that did not neogtiate SPDY via NPN
+ nsAutoCString negotiatedNPN;
+ nsresult rv = infoObject->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.\n"));
+ }
+
+ // Check to see if the cert has actually changed
+ UniqueCERTCertificate c(cert->GetCert());
+ MOZ_ASSERT(c, "Somehow couldn't get underlying cert from nsIX509Cert");
+ bool sameCert = CERT_CompareCerts(c.get(), serverCert.get());
+ if (sameCert) {
+ return SECSuccess;
+ }
+
+ // Report an error - changed cert is confirmed
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
+ ("SPDY Refused to allow new cert during renegotiation\n"));
+ PR_SetError(SSL_ERROR_RENEGOTIATION_NOT_ALLOWED, 0);
+ return SECFailure;
+}
+
+// Gather telemetry on whether the end-entity cert for a server has the
+// required TLS Server Authentication EKU, or any others
+void GatherEKUTelemetry(const UniqueCERTCertList& certList) {
+ CERTCertListNode* endEntityNode = CERT_LIST_HEAD(certList);
+ CERTCertListNode* rootNode = CERT_LIST_TAIL(certList);
+ MOZ_ASSERT(!(CERT_LIST_END(endEntityNode, certList) ||
+ CERT_LIST_END(rootNode, certList)));
+ if (CERT_LIST_END(endEntityNode, certList) ||
+ CERT_LIST_END(rootNode, certList)) {
+ return;
+ }
+ CERTCertificate* endEntityCert = endEntityNode->cert;
+ MOZ_ASSERT(endEntityCert);
+ if (!endEntityCert) {
+ return;
+ }
+
+ // Only log telemetry if the root CA is built-in
+ CERTCertificate* rootCert = rootNode->cert;
+ MOZ_ASSERT(rootCert);
+ if (!rootCert) {
+ return;
+ }
+ bool isBuiltIn = false;
+ Result rv = IsCertBuiltInRoot(rootCert, isBuiltIn);
+ if (rv != Success || !isBuiltIn) {
+ return;
+ }
+
+ // Find the EKU extension, if present
+ bool foundEKU = false;
+ SECOidTag oidTag;
+ CERTCertExtension* ekuExtension = nullptr;
+ for (size_t i = 0; endEntityCert->extensions && endEntityCert->extensions[i];
+ i++) {
+ oidTag = SECOID_FindOIDTag(&endEntityCert->extensions[i]->id);
+ if (oidTag == SEC_OID_X509_EXT_KEY_USAGE) {
+ foundEKU = true;
+ ekuExtension = endEntityCert->extensions[i];
+ }
+ }
+
+ if (!foundEKU) {
+ Telemetry::Accumulate(Telemetry::SSL_SERVER_AUTH_EKU, 0);
+ return;
+ }
+
+ // Parse the EKU extension
+ UniqueCERTOidSequence ekuSequence(
+ CERT_DecodeOidSequence(&ekuExtension->value));
+ if (!ekuSequence) {
+ return;
+ }
+
+ // Search through the available EKUs
+ bool foundServerAuth = false;
+ bool foundOther = false;
+ for (SECItem** oids = ekuSequence->oids; oids && *oids; oids++) {
+ oidTag = SECOID_FindOIDTag(*oids);
+ if (oidTag == SEC_OID_EXT_KEY_USAGE_SERVER_AUTH) {
+ foundServerAuth = true;
+ } else {
+ foundOther = true;
+ }
+ }
+
+ // Cases 3 is included only for completeness. It should never
+ // appear in these statistics, because CheckExtendedKeyUsage()
+ // should require the EKU extension, if present, to contain the
+ // value id_kp_serverAuth.
+ if (foundServerAuth && !foundOther) {
+ Telemetry::Accumulate(Telemetry::SSL_SERVER_AUTH_EKU, 1);
+ } else if (foundServerAuth && foundOther) {
+ Telemetry::Accumulate(Telemetry::SSL_SERVER_AUTH_EKU, 2);
+ } else if (!foundServerAuth) {
+ Telemetry::Accumulate(Telemetry::SSL_SERVER_AUTH_EKU, 3);
+ }
+}
+
+// Gathers telemetry on which CA is the root of a given cert chain.
+// If the root is a built-in root, then the telemetry makes a count
+// by root. Roots that are not built-in are counted in one bin.
+void GatherRootCATelemetry(const UniqueCERTCertList& certList) {
+ CERTCertListNode* rootNode = CERT_LIST_TAIL(certList);
+ MOZ_ASSERT(rootNode);
+ if (!rootNode) {
+ return;
+ }
+ MOZ_ASSERT(!CERT_LIST_END(rootNode, certList));
+ if (CERT_LIST_END(rootNode, certList)) {
+ return;
+ }
+ CERTCertificate* rootCert = rootNode->cert;
+ MOZ_ASSERT(rootCert);
+ if (!rootCert) {
+ return;
+ }
+ Span<uint8_t> certSpan = {rootCert->derCert.data, rootCert->derCert.len};
+ AccumulateTelemetryForRootCA(Telemetry::CERT_VALIDATION_SUCCESS_BY_CA,
+ certSpan);
+}
+
+// There are various things that we want to measure about certificate
+// chains that we accept. This is a single entry point for all of them.
+void GatherSuccessfulValidationTelemetry(const UniqueCERTCertList& certList) {
+ GatherEKUTelemetry(certList);
+ GatherRootCATelemetry(certList);
+}
+
+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 UniqueCERTCertList& certList, 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);
+ }
+
+ // Get the root cert.
+ CERTCertListNode* rootNode = CERT_LIST_TAIL(certList);
+ MOZ_ASSERT(rootNode);
+ if (!rootNode) {
+ return;
+ }
+ MOZ_ASSERT(!CERT_LIST_END(rootNode, certList));
+ if (CERT_LIST_END(rootNode, certList)) {
+ return;
+ }
+ CERTCertificate* rootCert = rootNode->cert;
+ MOZ_ASSERT(rootCert);
+ if (!rootCert) {
+ return;
+ }
+
+ // Report CT Policy compliance by CA.
+ Span<uint8_t> certSpan = {rootCert->derCert.data, rootCert->derCert.len};
+ switch (info.policyCompliance) {
+ case ct::CTPolicyCompliance::Compliant:
+ AccumulateTelemetryForRootCA(
+ Telemetry::SSL_CT_POLICY_COMPLIANT_CONNECTIONS_BY_CA, certSpan);
+ break;
+ case ct::CTPolicyCompliance::NotEnoughScts:
+ case ct::CTPolicyCompliance::NotDiverseScts:
+ AccumulateTelemetryForRootCA(
+ Telemetry::SSL_CT_POLICY_NON_COMPLIANT_CONNECTIONS_BY_CA, certSpan);
+ 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, SECOidTag aEvOidPolicy,
+ CertVerifier::OCSPStaplingStatus aOcspStaplingStatus,
+ KeySizeStatus aKeySizeStatus, SHA1ModeResult aSha1ModeResult,
+ const PinningTelemetryInfo& aPinningTelemetryInfo,
+ const UniqueCERTCertList& aBuiltCertChain,
+ const CertificateTransparencyInfo& aCertificateTransparencyInfo,
+ const CRLiteLookupResult& aCRLiteLookupResult) {
+ uint32_t evStatus = (aCertVerificationResult != Success) ? 0 // 0 = Failure
+ : (aEvOidPolicy == SEC_OID_UNKNOWN) ? 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 (aSha1ModeResult != SHA1ModeResult::NeverChecked) {
+ Telemetry::Accumulate(Telemetry::CERT_CHAIN_SHA1_POLICY_STATUS,
+ static_cast<uint32_t>(aSha1ModeResult));
+ }
+
+ 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) {
+ GatherSuccessfulValidationTelemetry(aBuiltCertChain);
+ GatherCertificateTransparencyTelemetry(
+ aBuiltCertChain,
+ /*isEV*/ aEvOidPolicy != SEC_OID_UNKNOWN, aCertificateTransparencyInfo);
+ }
+
+ switch (aCRLiteLookupResult) {
+ case CRLiteLookupResult::FilterNotAvailable:
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_CRLITE_RESULT::FilterNotAvailable);
+ break;
+ case CRLiteLookupResult::IssuerNotEnrolled:
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_CRLITE_RESULT::IssuerNotEnrolled);
+ break;
+ case CRLiteLookupResult::CertificateTooNew:
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_CRLITE_RESULT::CertificateTooNew);
+ break;
+ case CRLiteLookupResult::CertificateValid:
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_CRLITE_RESULT::CertificateValid);
+ break;
+ case CRLiteLookupResult::CertificateRevoked:
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_CRLITE_RESULT::CertificateRevoked);
+ break;
+ case CRLiteLookupResult::LibraryFailure:
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_CRLITE_RESULT::LibraryFailure);
+ break;
+ case CRLiteLookupResult::CertRevokedByStash:
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_CRLITE_RESULT::CertRevokedByStash);
+ break;
+ case CRLiteLookupResult::NeverChecked:
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unhandled CRLiteLookupResult value?");
+ break;
+ }
+}
+
+static void AuthCertificateSetResults(
+ TransportSecurityInfo* aInfoObject, nsNSSCertificate* aCert,
+ nsTArray<nsTArray<uint8_t>>&& aBuiltCertChain,
+ nsTArray<nsTArray<uint8_t>>&& aPeerCertChain,
+ uint16_t aCertificateTransparencyStatus, EVStatus aEvStatus,
+ bool aSucceeded, bool aIsCertChainRootBuiltInRoot) {
+ MOZ_ASSERT(aInfoObject);
+
+ if (aSucceeded) {
+ // Certificate verification succeeded. Delete any potential record of
+ // certificate error bits.
+ RememberCertErrorsTable::GetInstance().RememberCertHasError(aInfoObject,
+ SECSuccess);
+
+ aInfoObject->SetServerCert(aCert, aEvStatus);
+ aInfoObject->SetSucceededCertChain(std::move(aBuiltCertChain));
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
+ ("AuthCertificate setting NEW cert %p", aCert));
+
+ aInfoObject->SetIsBuiltCertChainRootBuiltInRoot(
+ aIsCertChainRootBuiltInRoot);
+ aInfoObject->SetCertificateTransparencyStatus(
+ aCertificateTransparencyStatus);
+ } else {
+ // Certificate validation failed; store the peer certificate chain on
+ // infoObject so it can be used for error reporting.
+ aInfoObject->SetFailedCertChain(std::move(aPeerCertChain));
+ }
+}
+
+// Note: Takes ownership of |peerCertChain| if SECSuccess is not returned.
+Result AuthCertificate(
+ CertVerifier& certVerifier, void* aPinArg,
+ const UniqueCERTCertificate& cert,
+ 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*/ UniqueCERTCertList& builtCertChain,
+ /*out*/ SECOidTag& evOidPolicy,
+ /*out*/ CertificateTransparencyInfo& certificateTransparencyInfo,
+ /*out*/ bool& aIsCertChainRootBuiltInRoot) {
+ MOZ_ASSERT(cert);
+
+ // We want to avoid storing any intermediate cert information when browsing
+ // in private, transient contexts.
+ bool saveIntermediates =
+ !(providerFlags & nsISocketProvider::NO_PERMANENT_STORAGE);
+
+ CertVerifier::OCSPStaplingStatus ocspStaplingStatus =
+ CertVerifier::OCSP_STAPLING_NEVER_CHECKED;
+ KeySizeStatus keySizeStatus = KeySizeStatus::NeverChecked;
+ SHA1ModeResult sha1ModeResult = SHA1ModeResult::NeverChecked;
+ PinningTelemetryInfo pinningTelemetryInfo;
+ CRLiteLookupResult crliteTelemetryInfo;
+
+ 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(
+ cert, time, aPinArg, aHostName, builtCertChain, certVerifierFlags,
+ Some(std::move(peerCertsBytes)), stapledOCSPResponse,
+ sctsFromTLSExtension, dcInfo, aOriginAttributes, saveIntermediates,
+ &evOidPolicy, &ocspStaplingStatus, &keySizeStatus, &sha1ModeResult,
+ &pinningTelemetryInfo, &certificateTransparencyInfo, &crliteTelemetryInfo,
+ &aIsCertChainRootBuiltInRoot);
+
+ CollectCertTelemetry(rv, evOidPolicy, ocspStaplingStatus, keySizeStatus,
+ sha1ModeResult, pinningTelemetryInfo, builtCertChain,
+ certificateTransparencyInfo, crliteTelemetryInfo);
+
+ return rv;
+}
+
+PRErrorCode AuthCertificateParseResults(
+ uint64_t aPtrForLog, const nsACString& aHostName, int32_t aPort,
+ const OriginAttributes& aOriginAttributes,
+ const UniqueCERTCertificate& aCert, uint32_t aProviderFlags, PRTime aPRTime,
+ PRErrorCode aDefaultErrorCodeToReport,
+ /* out */ uint32_t& aCollectedErrors) {
+ if (aDefaultErrorCodeToReport == 0) {
+ MOZ_ASSERT_UNREACHABLE(
+ "No error set during certificate validation failure");
+ return SEC_ERROR_LIBRARY_FAILURE;
+ }
+
+ uint32_t probeValue = MapCertErrorToProbeValue(aDefaultErrorCodeToReport);
+ Telemetry::Accumulate(Telemetry::SSL_CERT_VERIFICATION_ERRORS, probeValue);
+
+ aCollectedErrors = 0;
+ PRErrorCode errorCodeTrust = 0;
+ PRErrorCode errorCodeMismatch = 0;
+ PRErrorCode errorCodeTime = 0;
+ if (DetermineCertOverrideErrors(aCert, aHostName, aPRTime,
+ aDefaultErrorCodeToReport, aCollectedErrors,
+ errorCodeTrust, errorCodeMismatch,
+ errorCodeTime) != SECSuccess) {
+ PRErrorCode errorCode = PR_GetError();
+ MOZ_ASSERT(!ErrorIsOverridable(errorCode));
+ if (errorCode == 0) {
+ MOZ_ASSERT_UNREACHABLE(
+ "No error set during DetermineCertOverrideErrors failure");
+ return SEC_ERROR_LIBRARY_FAILURE;
+ }
+ return errorCode;
+ }
+
+ if (!aCollectedErrors) {
+ MOZ_ASSERT_UNREACHABLE("aCollectedErrors should not be 0");
+ return SEC_ERROR_LIBRARY_FAILURE;
+ }
+
+ bool overrideAllowed = false;
+ if (NS_FAILED(OverrideAllowedForHost(aPtrForLog, aHostName, aOriginAttributes,
+ aProviderFlags, overrideAllowed))) {
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
+ ("[0x%" PRIx64 "] AuthCertificateParseResults - "
+ "OverrideAllowedForHost failed\n",
+ aPtrForLog));
+ return aDefaultErrorCodeToReport;
+ }
+
+ if (overrideAllowed) {
+ nsCOMPtr<nsICertOverrideService> overrideService =
+ do_GetService(NS_CERTOVERRIDE_CONTRACTID);
+
+ uint32_t overrideBits = 0;
+ uint32_t remainingDisplayErrors = aCollectedErrors;
+
+ // it is fine to continue without the nsICertOverrideService
+ if (overrideService) {
+ bool haveOverride;
+ bool isTemporaryOverride; // we don't care
+ RefPtr<nsIX509Cert> nssCert(nsNSSCertificate::Create(aCert.get()));
+ if (!nssCert) {
+ MOZ_ASSERT(false, "nsNSSCertificate::Create failed");
+ return SEC_ERROR_NO_MEMORY;
+ }
+ nsresult rv = overrideService->HasMatchingOverride(
+ aHostName, aPort, nssCert, &overrideBits, &isTemporaryOverride,
+ &haveOverride);
+ if (NS_SUCCEEDED(rv) && haveOverride) {
+ // remove the errors that are already overriden
+ remainingDisplayErrors &= ~overrideBits;
+ }
+ }
+
+ if (!remainingDisplayErrors) {
+ // This can double- or triple-count one certificate with multiple
+ // different types of errors. Since this is telemetry and we just
+ // want a ballpark answer, we don't care.
+ if (errorCodeTrust != 0) {
+ uint32_t probeValue = MapOverridableErrorToProbeValue(errorCodeTrust);
+ Telemetry::Accumulate(Telemetry::SSL_CERT_ERROR_OVERRIDES, probeValue);
+ }
+ if (errorCodeMismatch != 0) {
+ uint32_t probeValue =
+ MapOverridableErrorToProbeValue(errorCodeMismatch);
+ Telemetry::Accumulate(Telemetry::SSL_CERT_ERROR_OVERRIDES, probeValue);
+ }
+ if (errorCodeTime != 0) {
+ uint32_t probeValue = MapOverridableErrorToProbeValue(errorCodeTime);
+ Telemetry::Accumulate(Telemetry::SSL_CERT_ERROR_OVERRIDES, probeValue);
+ }
+
+ // all errors are covered by override rules, so let's accept the cert
+ MOZ_LOG(
+ gPIPNSSLog, LogLevel::Debug,
+ ("[0x%" PRIx64 "] All errors covered by override rules", aPtrForLog));
+ return 0;
+ }
+ } else {
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
+ ("[0x%" PRIx64 "] HSTS or pinned host - no overrides allowed\n",
+ aPtrForLog));
+ }
+
+ MOZ_LOG(
+ gPIPNSSLog, LogLevel::Debug,
+ ("[0x%" PRIx64 "] Certificate error was not overridden\n", aPtrForLog));
+
+ // pick the error code to report by priority
+ return errorCodeTrust ? errorCodeTrust
+ : errorCodeMismatch ? errorCodeMismatch
+ : errorCodeTime ? errorCodeTime
+ : aDefaultErrorCodeToReport;
+}
+
+} // unnamed namespace
+
+/*static*/
+SECStatus SSLServerCertVerificationJob::Dispatch(
+ uint64_t addrForLogging, void* aPinArg,
+ const UniqueCERTCertificate& serverCert,
+ 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,
+ PRTime prtime, uint32_t certVerifierFlags,
+ BaseSSLServerCertVerificationResult* aResultTask) {
+ // Runs on the socket transport thread
+ if (!aResultTask || !serverCert) {
+ NS_ERROR("Invalid parameters for SSL server cert validation");
+ PR_SetError(PR_INVALID_ARGUMENT_ERROR, 0);
+ return SECFailure;
+ }
+
+ if (!gCertVerificationThreadPool) {
+ PR_SetError(PR_INVALID_STATE_ERROR, 0);
+ return SECFailure;
+ }
+
+ RefPtr<SSLServerCertVerificationJob> job(new SSLServerCertVerificationJob(
+ addrForLogging, aPinArg, serverCert, std::move(peerCertChain), aHostName,
+ aPort, aOriginAttributes, stapledOCSPResponse, sctsFromTLSExtension,
+ dcInfo, providerFlags, time, prtime, 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();
+ UniqueCERTCertList builtCertChain;
+ SECOidTag evOidPolicy;
+ CertificateTransparencyInfo certificateTransparencyInfo;
+ bool isCertChainRootBuiltInRoot = false;
+ Result rv = AuthCertificate(
+ *certVerifier, mPinArg, mCert, mPeerCertChain, mHostName,
+ mOriginAttributes, mStapledOCSPResponse, mSCTsFromTLSExtension, mDCInfo,
+ mProviderFlags, mTime, mCertVerifierFlags, builtCertChain, evOidPolicy,
+ certificateTransparencyInfo, isCertChainRootBuiltInRoot);
+
+ RefPtr<nsNSSCertificate> nsc = nsNSSCertificate::Create(mCert.get());
+ nsTArray<nsTArray<uint8_t>> certBytesArray;
+ if (rv == Success) {
+ Telemetry::AccumulateTimeDelta(
+ Telemetry::SSL_SUCCESFUL_CERT_VALIDATION_TIME_MOZILLAPKIX, jobStartTime,
+ TimeStamp::Now());
+ Telemetry::Accumulate(Telemetry::SSL_CERT_ERROR_OVERRIDES, 1);
+
+ certBytesArray =
+ TransportSecurityInfo::CreateCertBytesArray(builtCertChain);
+ EVStatus evStatus =
+ evOidPolicy == SEC_OID_UNKNOWN ? EVStatus::NotEV : EVStatus::EV;
+ mResultTask->Dispatch(
+ nsc, std::move(certBytesArray), std::move(mPeerCertChain),
+ TransportSecurityInfo::ConvertCertificateTransparencyInfoToStatus(
+ certificateTransparencyInfo),
+ evStatus, true, 0, 0, isCertChainRootBuiltInRoot);
+ return NS_OK;
+ }
+
+ Telemetry::AccumulateTimeDelta(
+ Telemetry::SSL_INITIAL_FAILED_CERT_VALIDATION_TIME_MOZILLAPKIX,
+ jobStartTime, TimeStamp::Now());
+
+ PRErrorCode error = MapResultToPRErrorCode(rv);
+ uint32_t collectedErrors = 0;
+ PRErrorCode finalError = AuthCertificateParseResults(
+ mAddrForLogging, mHostName, mPort, mOriginAttributes, mCert,
+ mProviderFlags, mPRTime, error, collectedErrors);
+
+ // NB: finalError may be 0 here, in which the connection will continue.
+ mResultTask->Dispatch(
+ nsc, std::move(certBytesArray), std::move(mPeerCertChain),
+ nsITransportSecurityInfo::CERTIFICATE_TRANSPARENCY_NOT_APPLICABLE,
+ EVStatus::NotEV, false, finalError, collectedErrors, false);
+ return NS_OK;
+}
+
+// Takes information needed for cert verification, does some consistency
+// checks and calls SSLServerCertVerificationJob::Dispatch.
+SECStatus AuthCertificateHookInternal(
+ TransportSecurityInfo* infoObject, const void* aPtrForLogging,
+ const UniqueCERTCertificate& serverCert, 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 (!infoObject || !serverCert) {
+ 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(infoObject);
+
+ if (XRE_IsSocketProcess()) {
+ return RemoteProcessCertVerification(
+ serverCert, std::move(peerCertChain), hostName, infoObject->GetPort(),
+ infoObject->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, infoObject, serverCert, std::move(peerCertChain), hostName,
+ infoObject->GetPort(), infoObject->GetOriginAttributes(),
+ stapledOCSPResponse, sctsFromTLSExtension, dcInfo, providerFlags, Now(),
+ PR_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");
+
+ nsNSSSocketInfo* socketInfo = static_cast<nsNSSSocketInfo*>(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 =
+ TransportSecurityInfo::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), serverCert, 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(
+ TransportSecurityInfo* infoObject, 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;
+ }
+
+ SECItem der = {SECItemType::siBuffer, peerCertChain[0].Elements(),
+ (uint32_t)peerCertChain[0].Length()};
+ UniqueCERTCertificate cert(CERT_NewTempCertificate(
+ CERT_GetDefaultCertDB(), &der, nullptr, false, true));
+ if (!cert) {
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
+ ("AuthCertificateHookWithInfo: cert failed"));
+ 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;
+ // QuicTransportSecInfo does not have a SharedState as nsNSSSocketInfo.
+ // 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(
+ infoObject, aPtrForLogging, cert, infoObject->GetHostName(),
+ std::move(peerCertChain), stapledOCSPResponse, sctsFromTLSExtension,
+ dcInfo, providerFlags, certVerifierFlags);
+}
+
+NS_IMPL_ISUPPORTS_INHERITED0(SSLServerCertVerificationResult, Runnable)
+
+SSLServerCertVerificationResult::SSLServerCertVerificationResult(
+ TransportSecurityInfo* infoObject)
+ : Runnable("psm::SSLServerCertVerificationResult"),
+ mInfoObject(infoObject),
+ mCertificateTransparencyStatus(0),
+ mEVStatus(EVStatus::NotEV),
+ mSucceeded(false),
+ mFinalError(0),
+ mCollectedErrors(0) {}
+
+void SSLServerCertVerificationResult::Dispatch(
+ nsNSSCertificate* aCert, nsTArray<nsTArray<uint8_t>>&& aBuiltChain,
+ nsTArray<nsTArray<uint8_t>>&& aPeerCertChain,
+ uint16_t aCertificateTransparencyStatus, EVStatus aEVStatus,
+ bool aSucceeded, PRErrorCode aFinalError, uint32_t aCollectedErrors,
+ bool aIsCertChainRootBuiltInRoot) {
+ mCert = aCert;
+ mBuiltChain = std::move(aBuiltChain);
+ mPeerCertChain = std::move(aPeerCertChain);
+ mCertificateTransparencyStatus = aCertificateTransparencyStatus;
+ mEVStatus = aEVStatus;
+ mSucceeded = aSucceeded;
+ mFinalError = aFinalError;
+ mCollectedErrors = aCollectedErrors;
+ mIsBuiltCertChainRootBuiltInRoot = aIsCertChainRootBuiltInRoot;
+
+ 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
+
+ AuthCertificateSetResults(mInfoObject, mCert, std::move(mBuiltChain),
+ std::move(mPeerCertChain),
+ mCertificateTransparencyStatus, mEVStatus,
+ mSucceeded, mIsBuiltCertChainRootBuiltInRoot);
+
+ if (!mSucceeded && mCollectedErrors != 0) {
+ mInfoObject->SetStatusErrorBits(mCert, mCollectedErrors);
+ }
+ mInfoObject->SetCertVerificationResult(mFinalError);
+ return NS_OK;
+}
+
+} // namespace psm
+} // namespace mozilla