Source code
Revision control
Copy as Markdown
Other Tools
/* -*- 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
// 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 "VerifySSLServerCertChild.h"
#include "cert.h"
#include "mozilla/Assertions.h"
#include "mozilla/Casting.h"
#include "mozilla/RefPtr.h"
#include "mozilla/StaticPrefs_security.h"
#include "mozilla/Telemetry.h"
#include "mozilla/UniquePtr.h"
#include "mozilla/Unused.h"
#include "mozilla/glean/GleanMetrics.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->SetThreadLimit(5);
(void)gCertVerificationThreadPool->SetIdleThreadLimit(1);
(void)gCertVerificationThreadPool->SetIdleThreadMaximumTimeout(30 * 1000);
(void)gCertVerificationThreadPool->SetIdleThreadGraceTimeout(500);
(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;
case mozilla::pkix::
MOZILLA_PKIX_ERROR_INSUFFICIENT_CERTIFICATE_TRANSPARENCY:
return 21;
}
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_CA_CERT_INVALID:
case SEC_ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED:
case SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE:
case SEC_ERROR_UNKNOWN_ISSUER:
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_INSUFFICIENT_CERTIFICATE_TRANSPARENCY:
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_EXPIRED_CERTIFICATE:
case SEC_ERROR_INVALID_TIME:
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_VERIFICATION_STATUS in Histograms.json.
uint32_t verificationStatus = 0;
switch (verifiedSct.logState) {
case ct::CTLogState::Admissible:
verificationStatus = 1;
break;
case ct::CTLogState::Retired:
verificationStatus = 5;
break;
}
Telemetry::Accumulate(Telemetry::SSL_SCTS_VERIFICATION_STATUS,
verificationStatus);
}
void GatherCertificateTransparencyTelemetry(
const nsTArray<uint8_t>& rootCert,
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);
}
// See SSL_SCTS_VERIFICATION_STATUS in Histograms.json.
for (size_t i = 0; i < info.verifyResult.decodingErrors; ++i) {
Telemetry::Accumulate(Telemetry::SSL_SCTS_VERIFICATION_STATUS, 0);
}
for (size_t i = 0; i < info.verifyResult.sctsFromUnknownLogs; ++i) {
Telemetry::Accumulate(Telemetry::SSL_SCTS_VERIFICATION_STATUS, 2);
}
for (size_t i = 0; i < info.verifyResult.sctsWithInvalidSignatures; ++i) {
Telemetry::Accumulate(Telemetry::SSL_SCTS_VERIFICATION_STATUS, 3);
}
for (size_t i = 0; i < info.verifyResult.sctsWithInvalidTimestamps; ++i) {
Telemetry::Accumulate(Telemetry::SSL_SCTS_VERIFICATION_STATUS, 4);
}
// See SSL_SCTS_ORIGIN in Histograms.json.
for (size_t i = 0; i < info.verifyResult.embeddedSCTs; ++i) {
Telemetry::Accumulate(Telemetry::SSL_SCTS_ORIGIN, 1);
}
for (size_t i = 0; i < info.verifyResult.sctsFromTLSHandshake; ++i) {
Telemetry::Accumulate(Telemetry::SSL_SCTS_ORIGIN, 2);
}
for (size_t i = 0; i < info.verifyResult.sctsFromOCSP; ++i) {
Telemetry::Accumulate(Telemetry::SSL_SCTS_ORIGIN, 3);
}
// 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 by CA.
if (info.policyCompliance.isSome() &&
*info.policyCompliance != ct::CTPolicyCompliance::Compliant) {
AccumulateTelemetryForRootCA(
Telemetry::SSL_CT_POLICY_NON_COMPLIANT_CONNECTIONS_BY_CA_2, rootCert);
}
}
// 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,
const IssuerSources& issuerSources) {
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_2,
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_2,
rootCert);
mozilla::glean::tls::certificate_verifications.Add(1);
if (issuerSources.contains(IssuerSource::TLSHandshake)) {
mozilla::glean::verification_used_cert_from::tls_handshake.AddToNumerator(
1);
}
if (issuerSources.contains(IssuerSource::PreloadedIntermediates)) {
mozilla::glean::verification_used_cert_from::preloaded_intermediates
.AddToNumerator(1);
}
if (issuerSources.contains(IssuerSource::ThirdPartyCertificates)) {
mozilla::glean::verification_used_cert_from::third_party_certificates
.AddToNumerator(1);
}
if (issuerSources.contains(IssuerSource::NSSCertDB)) {
mozilla::glean::verification_used_cert_from::nss_cert_db.AddToNumerator(
1);
}
if (issuerSources.contains(IssuerSource::BuiltInRootsModule)) {
mozilla::glean::verification_used_cert_from::built_in_roots_module
.AddToNumerator(1);
}
}
if ((aCertVerificationResult == Success ||
aCertVerificationResult ==
Result::ERROR_INSUFFICIENT_CERTIFICATE_TRANSPARENCY) &&
aBuiltCertChain.Length() > 0) {
const nsTArray<uint8_t>& rootCert = aBuiltCertChain.LastElement();
GatherCertificateTransparencyTelemetry(rootCert,
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(); });
}
IssuerSources issuerSources;
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, &issuerSources);
CollectCertTelemetry(rv, evStatus, ocspStaplingStatus, keySizeStatus,
pinningTelemetryInfo, builtCertChain,
certificateTransparencyInfo, issuerSources);
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 UniqueSECItemArray& aCertChain) {
nsTArray<nsTArray<uint8_t>> certsBytes;
for (size_t i = 0; i < aCertChain->len; i++) {
nsTArray<uint8_t> certBytes;
certBytes.AppendElements(aCertChain->items[i].data,
aCertChain->items[i].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", mAddrForLogging));
RefPtr<SharedCertVerifier> certVerifier(GetDefaultCertVerifier());
if (!certVerifier) {
// We can't release this off the STS thread because some parts of it
// are not threadsafe. Just leak mResultTask.
Unused << mResultTask.forget();
return NS_ERROR_FAILURE;
}
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 result = AuthCertificate(
*certVerifier, mPinArg, certBytes, mPeerCertChain, mHostName,
mOriginAttributes, mStapledOCSPResponse, mSCTsFromTLSExtension, mDCInfo,
mProviderFlags, mTime, mCertVerifierFlags, builtChainBytesArray, evStatus,
certificateTransparencyInfo, isCertChainRootBuiltInRoot,
madeOCSPRequests);
TimeDuration elapsed = TimeStamp::Now() - jobStartTime;
if (result == Success) {
mozilla::glean::cert_verification_time::success.AccumulateRawDuration(
elapsed);
Telemetry::Accumulate(Telemetry::SSL_CERT_ERROR_OVERRIDES, 1);
nsresult rv = mResultTask->Dispatch(
std::move(builtChainBytesArray), std::move(mPeerCertChain),
TransportSecurityInfo::ConvertCertificateTransparencyInfoToStatus(
certificateTransparencyInfo),
evStatus, true, 0,
nsITransportSecurityInfo::OverridableErrorCategory::ERROR_UNSET,
isCertChainRootBuiltInRoot, mProviderFlags, madeOCSPRequests);
if (NS_FAILED(rv)) {
// We can't release this off the STS thread because some parts of it
// are not threadsafe. Just leak mResultTask.
Unused << mResultTask.forget();
}
return rv;
}
mozilla::glean::cert_verification_time::failure.AccumulateRawDuration(
elapsed);
PRErrorCode error = MapResultToPRErrorCode(result);
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.
nsresult rv = mResultTask->Dispatch(
std::move(builtChainBytesArray), std::move(mPeerCertChain),
TransportSecurityInfo::ConvertCertificateTransparencyInfoToStatus(
certificateTransparencyInfo),
EVStatus::NotEV, false, finalError, overridableErrorCategory,
// If the certificate verifier returned Result::ERROR_BAD_CERT_DOMAIN,
// a chain was built, so isCertChainRootBuiltInRoot is valid and
// potentially useful. Otherwise, assume no chain was built.
result == Result::ERROR_BAD_CERT_DOMAIN ? isCertChainRootBuiltInRoot
: false,
mProviderFlags, madeOCSPRequests);
if (NS_FAILED(rv)) {
// We can't release this off the STS thread because some parts of it
// are not threadsafe. Just leak mResultTask.
Unused << mResultTask.forget();
}
return rv;
}
// 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;
}
UniqueSECItemArray peerCertChain;
SECStatus rv =
SSL_PeerCertificateChainDER(fd, TempPtrToSetter(&peerCertChain));
if (rv != SECSuccess) {
PR_SetError(PR_INVALID_STATE_ERROR, 0);
return SECFailure;
}
MOZ_ASSERT(peerCertChain,
"AuthCertificateHook: peerCertChain unexpectedly null");
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 (!StaticPrefs::security_ssl_enable_ocsp_stapling() ||
!StaticPrefs::security_ssl_enable_ocsp_must_staple()) {
certVerifierFlags |= CertVerifier::FLAG_TLS_IGNORE_STATUS_REQUEST;
}
// Get DC information
Maybe<DelegatedCredentialInfo> dcInfo;
SSLPreliminaryChannelInfo channelPreInfo;
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;
if (!StaticPrefs::security_ssl_enable_ocsp_stapling() ||
!StaticPrefs::security_ssl_enable_ocsp_must_staple()) {
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) {}
nsresult 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() || mFinalError != 0 ||
mOverridableErrorCategory !=
nsITransportSecurityInfo::OverridableErrorCategory::ERROR_UNSET)) {
MOZ_ASSERT_UNREACHABLE(
"if certificate verification succeeded without overridden errors, the "
"built chain shouldn't be empty and any error bits should be unset");
mSucceeded = false;
mFinalError = SEC_ERROR_LIBRARY_FAILURE;
}
// Note that mSucceeded can be false while mFinalError is 0, in which case
// the connection will proceed.
if (!mSucceeded && mPeerCertChain.IsEmpty()) {
MOZ_ASSERT_UNREACHABLE(
"if certificate verification 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");
if (!stsTarget) {
// This has to be released on STS; just leak it
Unused << mSocketControl.forget();
return NS_ERROR_FAILURE;
}
rv = stsTarget->Dispatch(this, NS_DISPATCH_NORMAL);
MOZ_ASSERT(NS_SUCCEEDED(rv),
"Failed to dispatch SSLServerCertVerificationResult");
return rv;
}
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
mSocketControl->SetMadeOCSPRequests(mMadeOCSPRequests);
mSocketControl->SetIsBuiltCertChainRootBuiltInRoot(
mIsBuiltCertChainRootBuiltInRoot);
mSocketControl->SetCertificateTransparencyStatus(
mCertificateTransparencyStatus);
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));
} 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