зеркало из https://github.com/mozilla/gecko-dev.git
1426 строки
56 KiB
C++
1426 строки
56 KiB
C++
/* -*- 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 "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/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(uri, aProviderFlags, aOriginAttributes, nullptr,
|
|
nullptr, &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(
|
|
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;
|
|
Input rootInput;
|
|
Result rv = rootInput.Init(rootCert->derCert.data, rootCert->derCert.len);
|
|
if (rv != Result::Success) {
|
|
return;
|
|
}
|
|
rv = IsCertBuiltInRoot(rootInput, 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, EVStatus aEVStatus,
|
|
CertVerifier::OCSPStaplingStatus aOcspStaplingStatus,
|
|
KeySizeStatus aKeySizeStatus, SHA1ModeResult aSha1ModeResult,
|
|
const PinningTelemetryInfo& aPinningTelemetryInfo,
|
|
const nsTArray<nsTArray<uint8_t>>& aBuiltCertChain,
|
|
const CertificateTransparencyInfo& aCertificateTransparencyInfo) {
|
|
UniqueCERTCertList builtCertChainList(CERT_NewCertList());
|
|
if (!builtCertChainList) {
|
|
return;
|
|
}
|
|
CERTCertDBHandle* certDB(CERT_GetDefaultCertDB());
|
|
for (const auto& certBytes : aBuiltCertChain) {
|
|
SECItem certDERItem = {siBuffer, const_cast<uint8_t*>(certBytes.Elements()),
|
|
AssertedCast<unsigned int>(certBytes.Length())};
|
|
UniqueCERTCertificate cert(
|
|
CERT_NewTempCertificate(certDB, &certDERItem, nullptr, false, true));
|
|
if (!cert) {
|
|
return;
|
|
}
|
|
if (CERT_AddCertToListTail(builtCertChainList.get(), cert.get()) !=
|
|
SECSuccess) {
|
|
return;
|
|
}
|
|
Unused << cert.release(); // cert is now owned by certList.
|
|
}
|
|
|
|
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 (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(builtCertChainList);
|
|
GatherCertificateTransparencyTelemetry(builtCertChainList,
|
|
aEVStatus == EVStatus::EV,
|
|
aCertificateTransparencyInfo);
|
|
}
|
|
}
|
|
|
|
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 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& aIsCertChainRootBuiltInRoot) {
|
|
CertVerifier::OCSPStaplingStatus ocspStaplingStatus =
|
|
CertVerifier::OCSP_STAPLING_NEVER_CHECKED;
|
|
KeySizeStatus keySizeStatus = KeySizeStatus::NeverChecked;
|
|
SHA1ModeResult sha1ModeResult = SHA1ModeResult::NeverChecked;
|
|
PinningTelemetryInfo pinningTelemetryInfo;
|
|
|
|
nsTArray<nsTArray<uint8_t>> peerCertsBytes;
|
|
// Don't include the end-entity certificate.
|
|
if (!peerCertChain.IsEmpty()) {
|
|
std::transform(
|
|
peerCertChain.cbegin() + 1, peerCertChain.cend(),
|
|
MakeBackInserter(peerCertsBytes),
|
|
[](const auto& elementArray) { return elementArray.Clone(); });
|
|
}
|
|
|
|
Result rv = certVerifier.VerifySSLServerCert(
|
|
certBytes, time, aPinArg, aHostName, builtCertChain, certVerifierFlags,
|
|
Some(std::move(peerCertsBytes)), stapledOCSPResponse,
|
|
sctsFromTLSExtension, dcInfo, aOriginAttributes, &evStatus,
|
|
&ocspStaplingStatus, &keySizeStatus, &sha1ModeResult,
|
|
&pinningTelemetryInfo, &certificateTransparencyInfo,
|
|
&aIsCertChainRootBuiltInRoot);
|
|
|
|
CollectCertTelemetry(rv, evStatus, ocspStaplingStatus, keySizeStatus,
|
|
sha1ModeResult, pinningTelemetryInfo, builtCertChain,
|
|
certificateTransparencyInfo);
|
|
|
|
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, aOriginAttributes, 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 nsTArray<nsTArray<uint8_t>> CreateCertBytesArray(
|
|
const UniqueCERTCertList& aCertChain) {
|
|
nsTArray<nsTArray<uint8_t>> certsBytes;
|
|
for (CERTCertListNode* n = CERT_LIST_HEAD(aCertChain);
|
|
!CERT_LIST_END(n, aCertChain); n = CERT_LIST_NEXT(n)) {
|
|
nsTArray<uint8_t> certBytes;
|
|
certBytes.AppendElements(n->cert->derCert.data, n->cert->derCert.len);
|
|
certsBytes.AppendElement(std::move(certBytes));
|
|
}
|
|
return certsBytes;
|
|
}
|
|
|
|
/*static*/
|
|
SECStatus SSLServerCertVerificationJob::Dispatch(
|
|
uint64_t addrForLogging, void* aPinArg,
|
|
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();
|
|
EVStatus evStatus;
|
|
CertificateTransparencyInfo certificateTransparencyInfo;
|
|
bool isCertChainRootBuiltInRoot = false;
|
|
nsTArray<nsTArray<uint8_t>> builtChainBytesArray;
|
|
nsTArray<uint8_t> certBytes(mCert->derCert.data, mCert->derCert.len);
|
|
Result rv = AuthCertificate(
|
|
*certVerifier, mPinArg, certBytes, mPeerCertChain, mHostName,
|
|
mOriginAttributes, mStapledOCSPResponse, mSCTsFromTLSExtension, mDCInfo,
|
|
mProviderFlags, mTime, mCertVerifierFlags, builtChainBytesArray, evStatus,
|
|
certificateTransparencyInfo, isCertChainRootBuiltInRoot);
|
|
|
|
RefPtr<nsNSSCertificate> nsc = nsNSSCertificate::Create(mCert.get());
|
|
if (rv == Success) {
|
|
Telemetry::AccumulateTimeDelta(
|
|
Telemetry::SSL_SUCCESFUL_CERT_VALIDATION_TIME_MOZILLAPKIX, jobStartTime,
|
|
TimeStamp::Now());
|
|
Telemetry::Accumulate(Telemetry::SSL_CERT_ERROR_OVERRIDES, 1);
|
|
|
|
mResultTask->Dispatch(
|
|
nsc, std::move(builtChainBytesArray), std::move(mPeerCertChain),
|
|
TransportSecurityInfo::ConvertCertificateTransparencyInfoToStatus(
|
|
certificateTransparencyInfo),
|
|
evStatus, true, 0, 0, isCertChainRootBuiltInRoot, mProviderFlags);
|
|
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(builtChainBytesArray), std::move(mPeerCertChain),
|
|
nsITransportSecurityInfo::CERTIFICATE_TRANSPARENCY_NOT_APPLICABLE,
|
|
EVStatus::NotEV, false, finalError, collectedErrors, false,
|
|
mProviderFlags);
|
|
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 =
|
|
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 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;
|
|
}
|
|
|
|
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,
|
|
aHostName, 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),
|
|
mProviderFlags(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, uint32_t aProviderFlags) {
|
|
mCert = aCert;
|
|
mBuiltChain = std::move(aBuiltChain);
|
|
mPeerCertChain = std::move(aPeerCertChain);
|
|
mCertificateTransparencyStatus = aCertificateTransparencyStatus;
|
|
mEVStatus = aEVStatus;
|
|
mSucceeded = aSucceeded;
|
|
mFinalError = aFinalError;
|
|
mCollectedErrors = aCollectedErrors;
|
|
mIsBuiltCertChainRootBuiltInRoot = aIsCertChainRootBuiltInRoot;
|
|
mProviderFlags = aProviderFlags;
|
|
|
|
nsresult rv;
|
|
nsCOMPtr<nsIEventTarget> stsTarget =
|
|
do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv);
|
|
MOZ_ASSERT(stsTarget, "Failed to get socket transport service event target");
|
|
rv = stsTarget->Dispatch(this, NS_DISPATCH_NORMAL);
|
|
MOZ_ASSERT(NS_SUCCEEDED(rv),
|
|
"Failed to dispatch SSLServerCertVerificationResult");
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
SSLServerCertVerificationResult::Run() {
|
|
#ifdef DEBUG
|
|
bool onSTSThread = false;
|
|
nsresult nrv;
|
|
nsCOMPtr<nsIEventTarget> sts =
|
|
do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &nrv);
|
|
if (NS_SUCCEEDED(nrv)) {
|
|
nrv = sts->IsOnCurrentThread(&onSTSThread);
|
|
}
|
|
|
|
MOZ_ASSERT(onSTSThread);
|
|
#endif
|
|
|
|
if (mSucceeded && !XRE_IsSocketProcess() &&
|
|
!(mProviderFlags & nsISocketProvider::NO_PERMANENT_STORAGE)) {
|
|
// This dispatches an event that will run when the socket thread is idle.
|
|
SaveIntermediateCerts(mBuiltChain);
|
|
}
|
|
|
|
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
|