Quellcodebibliothek Statistik Leitseite products/sources/formale Sprachen/C/Firefox/security/certverifier/   (Browser von der Mozilla Stiftung Version 136.0.1©)  Datei vom 10.2.2025 mit Größe 41 kB image not shown  

Quelle  CertVerifier.cpp   Sprache: C

 
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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/. */


#include "CertVerifier.h"

#include <stdint.h>

#include "AppTrustDomain.h"
#include "CTKnownLogs.h"
#include "CTLogVerifier.h"
#include "ExtendedValidation.h"
#include "MultiLogCTVerifier.h"
#include "NSSCertDBTrustDomain.h"
#include "NSSErrorsService.h"
#include "cert.h"
#include "mozilla/Assertions.h"
#include "mozilla/Casting.h"
#include "mozilla/IntegerPrintfMacros.h"
#include "mozilla/Logging.h"
#include "mozilla/StaticPrefs_security.h"
#include "mozilla/SyncRunnable.h"
#include "mozpkix/pkix.h"
#include "mozpkix/pkixcheck.h"
#include "mozpkix/pkixnss.h"
#include "mozpkix/pkixutil.h"
#include "nsNSSComponent.h"
#include "nsNetCID.h"
#include "nsPromiseFlatString.h"
#include "nsServiceManagerUtils.h"
#include "pk11pub.h"
#include "secmod.h"

using namespace mozilla::ct;
using namespace mozilla::pkix;
using namespace mozilla::psm;

mozilla::LazyLogModule gCertVerifierLog("certverifier");

namespace mozilla {
namespace psm {

const CertVerifier::Flags CertVerifier::FLAG_LOCAL_ONLY = 1;
const CertVerifier::Flags CertVerifier::FLAG_MUST_BE_EV = 2;
const CertVerifier::Flags CertVerifier::FLAG_TLS_IGNORE_STATUS_REQUEST = 4;
static const unsigned int MIN_RSA_BITS = 2048;
static const unsigned int MIN_RSA_BITS_WEAK = 1024;

void CertificateTransparencyInfo::Reset() {
  enabled = false;
  verifyResult.Reset();
  policyCompliance.reset();
}

CertVerifier::CertVerifier(OcspDownloadConfig odc, OcspStrictConfig osc,
                           mozilla::TimeDuration ocspTimeoutSoft,
                           mozilla::TimeDuration ocspTimeoutHard,
                           uint32_t certShortLifetimeInDays,
                           NetscapeStepUpPolicy netscapeStepUpPolicy,
                           CertificateTransparencyConfig&& ctConfig,
                           CRLiteMode crliteMode,
                           const nsTArray<EnterpriseCert>& thirdPartyCerts)
    : mOCSPDownloadConfig(odc),
      mOCSPStrict(osc == ocspStrict),
      mOCSPTimeoutSoft(ocspTimeoutSoft),
      mOCSPTimeoutHard(ocspTimeoutHard),
      mCertShortLifetimeInDays(certShortLifetimeInDays),
      mNetscapeStepUpPolicy(netscapeStepUpPolicy),
      mCTConfig(std::move(ctConfig)),
      mCRLiteMode(crliteMode),
      mSignatureCache(
          signature_cache_new(
              StaticPrefs::security_pki_cert_signature_cache_size()),
          signature_cache_free),
      mTrustCache(
          trust_cache_new(StaticPrefs::security_pki_cert_trust_cache_size()),
          trust_cache_free) {
  LoadKnownCTLogs();
  mThirdPartyCerts = thirdPartyCerts.Clone();
  for (const auto& root : mThirdPartyCerts) {
    Input input;
    if (root.GetInput(input) == Success) {
      // mThirdPartyCerts consists of roots and intermediates.
      if (root.GetIsRoot()) {
        mThirdPartyRootInputs.AppendElement(input);
      } else {
        mThirdPartyIntermediateInputs.AppendElement(input);
      }
    }
  }
}

CertVerifier::~CertVerifier() = default;

Result IsDelegatedCredentialAcceptable(const DelegatedCredentialInfo& dcInfo) {
  bool isEcdsa = dcInfo.scheme == ssl_sig_ecdsa_secp256r1_sha256 ||
                 dcInfo.scheme == ssl_sig_ecdsa_secp384r1_sha384 ||
                 dcInfo.scheme == ssl_sig_ecdsa_secp521r1_sha512;

  // Firefox currently does not advertise any RSA schemes for use
  // with Delegated Credentials. As a secondary (on top of NSS)
  // check, disallow any RSA SPKI here. When ssl_sig_rsa_pss_pss_*
  // schemes are supported, check the modulus size and allow RSA here.
  if (!isEcdsa) {
    return Result::ERROR_INVALID_KEY;
  }

  return Result::Success;
}

// The term "builtin root" traditionally refers to a root CA certificate that
// has been added to the NSS trust store, because it has been approved
// for inclusion according to the Mozilla CA policy, and might be accepted
// by Mozilla applications as an issuer for certificates seen on the public web.
Result IsCertBuiltInRoot(Input certInput, bool& result) {
  result = false;

  if (NS_FAILED(BlockUntilLoadableCertsLoaded())) {
    return Result::FATAL_ERROR_LIBRARY_FAILURE;
  }

#ifdef DEBUG
  nsCOMPtr<nsINSSComponent> component(do_GetService(PSM_COMPONENT_CONTRACTID));
  if (!component) {
    return Result::FATAL_ERROR_LIBRARY_FAILURE;
  }
  nsTArray<uint8_t> certBytes;
  certBytes.AppendElements(certInput.UnsafeGetData(), certInput.GetLength());
  if (NS_FAILED(component->IsCertTestBuiltInRoot(certBytes, &result))) {
    return Result::FATAL_ERROR_LIBRARY_FAILURE;
  }
  if (result) {
    return Success;
  }
#endif  // DEBUG
  SECItem certItem(UnsafeMapInputToSECItem(certInput));
  AutoSECMODListReadLock lock;
  for (SECMODModuleList* list = SECMOD_GetDefaultModuleList(); list;
       list = list->next) {
    for (int i = 0; i < list->module->slotCount; i++) {
      PK11SlotInfo* slot = list->module->slots[i];
      // We're searching for the "builtin root module", which is a module that
      // contains an object with a CKA_CLASS of CKO_NETSCAPE_BUILTIN_ROOT_LIST.
      // We use PK11_HasRootCerts() to identify a module with that property.
      // In the past, we exclusively used the PKCS#11 module named nssckbi,
      // which is provided by the NSS library.
      // Nowadays, some distributions use a replacement module, which contains
      // the builtin roots, but which also contains additional CA certificates,
      // such as CAs trusted in a local deployment.
      // We want to be able to distinguish between these two categories,
      // because a CA, which may issue certificates for the public web,
      // is expected to comply with additional requirements.
      // If the certificate has attribute CKA_NSS_MOZILLA_CA_POLICY set to true,
      // then we treat it as a "builtin root".
      if (!PK11_IsPresent(slot) || !PK11_HasRootCerts(slot)) {
        continue;
      }
      CK_OBJECT_HANDLE handle =
          PK11_FindEncodedCertInSlot(slot, &certItem, nullptr);
      if (handle == CK_INVALID_HANDLE) {
        continue;
      }
      if (PK11_HasAttributeSet(slot, handle, CKA_NSS_MOZILLA_CA_POLICY,
                               false)) {
        // Attribute was found, and is set to true
        result = true;
        break;
      }
    }
  }
  return Success;
}

static Result BuildCertChainForOneKeyUsage(
    NSSCertDBTrustDomain& trustDomain, Input certDER, Time time, KeyUsage ku1,
    KeyUsage ku2, KeyUsage ku3, KeyPurposeId eku,
    const CertPolicyId& requiredPolicy, const Input* stapledOCSPResponse,
    /*optional out*/ CertVerifier::OCSPStaplingStatus* ocspStaplingStatus) {
  trustDomain.ResetAccumulatedState();
  Result rv =
      BuildCertChain(trustDomain, certDER, time, EndEntityOrCA::MustBeEndEntity,
                     ku1, eku, requiredPolicy, stapledOCSPResponse);
  if (rv == Result::ERROR_INADEQUATE_KEY_USAGE) {
    trustDomain.ResetAccumulatedState();
    rv = BuildCertChain(trustDomain, certDER, time,
                        EndEntityOrCA::MustBeEndEntity, ku2, eku,
                        requiredPolicy, stapledOCSPResponse);
    if (rv == Result::ERROR_INADEQUATE_KEY_USAGE) {
      trustDomain.ResetAccumulatedState();
      rv = BuildCertChain(trustDomain, certDER, time,
                          EndEntityOrCA::MustBeEndEntity, ku3, eku,
                          requiredPolicy, stapledOCSPResponse);
      if (rv != Success) {
        rv = Result::ERROR_INADEQUATE_KEY_USAGE;
      }
    }
  }
  if (ocspStaplingStatus) {
    *ocspStaplingStatus = trustDomain.GetOCSPStaplingStatus();
  }
  return rv;
}

void CertVerifier::LoadKnownCTLogs() {
  if (mCTConfig.mMode == CertificateTransparencyMode::Disabled) {
    return;
  }
  mCTVerifier = MakeUnique<MultiLogCTVerifier>();
  for (const CTLogInfo& log : kCTLogList) {
    Input publicKey;
    Result rv = publicKey.Init(
        BitwiseCast<const uint8_t*, const char*>(log.key), log.keyLength);
    if (rv != Success) {
      MOZ_ASSERT_UNREACHABLE("Failed reading a log key for a known CT Log");
      continue;
    }

    const CTLogOperatorInfo& logOperator =
        kCTLogOperatorList[log.operatorIndex];
    CTLogVerifier logVerifier(logOperator.id, log.state, log.timestamp);
    rv = logVerifier.Init(publicKey);
    if (rv != Success) {
      MOZ_ASSERT_UNREACHABLE("Failed initializing a known CT Log");
      continue;
    }

    mCTVerifier->AddLog(std::move(logVerifier));
  }
}

bool HostnameMatchesPolicy(const char* hostname, const nsCString& policy) {
  // Some contexts don't have a hostname (mostly tests), in which case the
  // policy doesn't apply.
  if (!hostname) {
    return false;
  }
  nsDependentCString hostnameString(hostname);
  // The policy is a comma-separated list of entries of the form
  // '.example.com', 'example.com', or an IP address.
  for (const auto& entry : policy.Split(',')) {
    if (entry.IsEmpty()) {
      continue;
    }
    // For '.example.com' entries, exact matches match the policy.
    if (entry[0] == '.' &&
        Substring(entry, 1).EqualsIgnoreCase(hostnameString)) {
      MOZ_LOG(gCertVerifierLog, LogLevel::Debug,
              ("not enforcing CT for '%s' (matches policy '%s')", hostname,
               policy.get()));
      return true;
    }
    // For 'example.com' entries, exact matches or subdomains match the policy
    // (IP addresses match here too).
    if (StringEndsWith(hostnameString, entry) &&
        (hostnameString.Length() == entry.Length() ||
         hostnameString[hostnameString.Length() - entry.Length() - 1] == '.')) {
      MOZ_LOG(gCertVerifierLog, LogLevel::Debug,
              ("not enforcing CT for '%s' (matches policy '%s')", hostname,
               policy.get()));
      return true;
    }
  }
  return false;
}

bool CertificateListHasSPKIHashIn(
    const nsTArray<nsTArray<uint8_t>>& certificates,
    const nsTArray<CopyableTArray<uint8_t>>& spkiHashes) {
  if (spkiHashes.IsEmpty()) {
    return false;
  }
  for (const auto& certificate : certificates) {
    Input certificateInput;
    if (certificateInput.Init(certificate.Elements(), certificate.Length()) !=
        Success) {
      return false;
    }
    // No path building is happening here, so this parameter doesn't matter.
    EndEntityOrCA notUsedForPathBuilding = EndEntityOrCA::MustBeEndEntity;
    BackCert decodedCertificate(certificateInput, notUsedForPathBuilding,
                                nullptr);
    if (decodedCertificate.Init() != Success) {
      return false;
    }
    Input spki(decodedCertificate.GetSubjectPublicKeyInfo());
    uint8_t spkiHash[SHA256_LENGTH];
    if (DigestBufNSS(spki, DigestAlgorithm::sha256, spkiHash,
                     sizeof(spkiHash)) != Success) {
      return false;
    }
    Span spkiHashSpan(reinterpret_cast<const uint8_t*>(spkiHash),
                      sizeof(spkiHash));
    for (const auto& candidateSPKIHash : spkiHashes) {
      if (Span(candidateSPKIHash) == spkiHashSpan) {
        MOZ_LOG(gCertVerifierLog, LogLevel::Debug,
                ("found SPKI hash match - not enforcing CT"));
        return true;
      }
    }
  }
  return false;
}

Result CertVerifier::VerifyCertificateTransparencyPolicy(
    NSSCertDBTrustDomain& trustDomain,
    const nsTArray<nsTArray<uint8_t>>& builtChain, Input sctsFromTLS, Time time,
    const char* hostname,
    /*optional out*/ CertificateTransparencyInfo* ctInfo) {
  if (builtChain.IsEmpty()) {
    return Result::FATAL_ERROR_INVALID_ARGS;
  }
  if (ctInfo) {
    ctInfo->Reset();
  }
  if (mCTConfig.mMode == CertificateTransparencyMode::Disabled ||
      !trustDomain.GetIsBuiltChainRootBuiltInRoot()) {
    return Success;
  }
  if (time > TimeFromEpochInSeconds(kCTExpirationTime / PR_USEC_PER_SEC)) {
    MOZ_LOG(gCertVerifierLog, LogLevel::Warning,
            ("skipping CT - built-in information has expired"));
    return Success;
  }
  if (ctInfo) {
    ctInfo->enabled = true;
  }

  Result rv = VerifyCertificateTransparencyPolicyInner(
      trustDomain, builtChain, sctsFromTLS, time, ctInfo);
  if (rv == Result::ERROR_INSUFFICIENT_CERTIFICATE_TRANSPARENCY &&
      (mCTConfig.mMode != CertificateTransparencyMode::Enforce ||
       HostnameMatchesPolicy(hostname, mCTConfig.mSkipForHosts) ||
       CertificateListHasSPKIHashIn(builtChain,
                                    mCTConfig.mSkipForSPKIHashes))) {
    return Success;
  }

  return rv;
}

Result CertVerifier::VerifyCertificateTransparencyPolicyInner(
    NSSCertDBTrustDomain& trustDomain,
    const nsTArray<nsTArray<uint8_t>>& builtChain, Input sctsFromTLS, Time time,
    /*optional out*/ CertificateTransparencyInfo* ctInfo) {
  Input embeddedSCTs = trustDomain.GetSCTListFromCertificate();
  if (embeddedSCTs.GetLength() > 0) {
    MOZ_LOG(gCertVerifierLog, LogLevel::Debug,
            ("Got embedded SCT data of length %zu\n",
             static_cast<size_t>(embeddedSCTs.GetLength())));
  }
  Input sctsFromOCSP = trustDomain.GetSCTListFromOCSPStapling();
  if (sctsFromOCSP.GetLength() > 0) {
    MOZ_LOG(gCertVerifierLog, LogLevel::Debug,
            ("Got OCSP SCT data of length %zu\n",
             static_cast<size_t>(sctsFromOCSP.GetLength())));
  }
  if (sctsFromTLS.GetLength() > 0) {
    MOZ_LOG(gCertVerifierLog, LogLevel::Debug,
            ("Got TLS SCT data of length %zu\n",
             static_cast<size_t>(sctsFromTLS.GetLength())));
  }

  if (builtChain.Length() == 1) {
    // Issuer certificate is required for SCT verification.
    // If we've arrived here, we probably have a "trust chain" with only one
    // certificate (i.e. a self-signed end-entity that has been set as a trust
    // anchor either by a third party modifying our trust DB or via the
    // enterprise roots feature). If this is the case, certificate transparency
    // information will probably not be present, and it certainly won't verify
    // correctly. To simplify things, we return an empty CTVerifyResult and a
    // "not enough SCTs" CTPolicyCompliance result.
    if (ctInfo) {
      CTVerifyResult emptyResult;
      ctInfo->verifyResult = std::move(emptyResult);
      ctInfo->policyCompliance.emplace(CTPolicyCompliance::NotEnoughScts);
    }
    return Result::ERROR_INSUFFICIENT_CERTIFICATE_TRANSPARENCY;
  }

  const nsTArray<uint8_t>& endEntityBytes = builtChain.ElementAt(0);
  Input endEntityInput;
  Result rv =
      endEntityInput.Init(endEntityBytes.Elements(), endEntityBytes.Length());
  if (rv != Success) {
    return rv;
  }

  const nsTArray<uint8_t>& issuerBytes = builtChain.ElementAt(1);
  Input issuerInput;
  rv = issuerInput.Init(issuerBytes.Elements(), issuerBytes.Length());
  if (rv != Success) {
    return rv;
  }

  BackCert issuerBackCert(issuerInput, EndEntityOrCA::MustBeCA, nullptr);
  rv = issuerBackCert.Init();
  if (rv != Success) {
    return rv;
  }
  Input issuerPublicKeyInput = issuerBackCert.GetSubjectPublicKeyInfo();

  CTVerifyResult result;
  rv = mCTVerifier->Verify(endEntityInput, issuerPublicKeyInput, embeddedSCTs,
                           sctsFromOCSP, sctsFromTLS, time, result);
  if (rv != Success) {
    MOZ_LOG(gCertVerifierLog, LogLevel::Debug,
            ("SCT verification failed with fatal error %" PRId32 "\n",
             static_cast<uint32_t>(rv)));
    return rv;
  }

  if (MOZ_LOG_TEST(gCertVerifierLog, LogLevel::Debug)) {
    size_t validCount = 0;
    size_t retiredLogCount = 0;
    for (const VerifiedSCT& verifiedSct : result.verifiedScts) {
      switch (verifiedSct.logState) {
        case CTLogState::Admissible:
          validCount++;
          break;
        case CTLogState::Retired:
          retiredLogCount++;
          break;
      }
    }
    MOZ_LOG(gCertVerifierLog, LogLevel::Debug,
            ("SCT verification result: "
             "valid=%zu unknownLog=%zu retiredLog=%zu "
             "invalidSignature=%zu invalidTimestamp=%zu "
             "decodingErrors=%zu\n",
             validCount, result.sctsFromUnknownLogs, retiredLogCount,
             result.sctsWithInvalidSignatures, result.sctsWithInvalidTimestamps,
             result.decodingErrors));
  }

  BackCert endEntityBackCert(endEntityInput, EndEntityOrCA::MustBeEndEntity,
                             nullptr);
  rv = endEntityBackCert.Init();
  if (rv != Success) {
    return rv;
  }
  Time notBefore(Time::uninitialized);
  Time notAfter(Time::uninitialized);
  rv = ParseValidity(endEntityBackCert.GetValidity(), ¬Before, ¬After);
  if (rv != Success) {
    return rv;
  }
  Duration certLifetime(notBefore, notAfter);

  CTPolicyCompliance ctPolicyCompliance =
      CheckCTPolicyCompliance(result.verifiedScts, certLifetime);

  if (ctInfo) {
    ctInfo->verifyResult = std::move(result);
    ctInfo->policyCompliance.emplace(ctPolicyCompliance);
  }

  if (ctPolicyCompliance != CTPolicyCompliance::Compliant) {
    return Result::ERROR_INSUFFICIENT_CERTIFICATE_TRANSPARENCY;
  }

  return Success;
}

Result CertVerifier::VerifyCert(
    const nsTArray<uint8_t>& certBytes, SECCertificateUsage usage, Time time,
    void* pinArg, const char* hostname,
    /*out*/ nsTArray<nsTArray<uint8_t>>& builtChain,
    /*optional*/ const Flags flags,
    /*optional*/ const Maybe<nsTArray<nsTArray<uint8_t>>>& extraCertificates,
    /*optional*/ const Maybe<nsTArray<uint8_t>>& stapledOCSPResponseArg,
    /*optional*/ const Maybe<nsTArray<uint8_t>>& sctsFromTLS,
    /*optional*/ const OriginAttributes& originAttributes,
    /*optional out*/ EVStatus* evStatus,
    /*optional out*/ OCSPStaplingStatus* ocspStaplingStatus,
    /*optional out*/ KeySizeStatus* keySizeStatus,
    /*optional out*/ PinningTelemetryInfo* pinningTelemetryInfo,
    /*optional out*/ CertificateTransparencyInfo* ctInfo,
    /*optional out*/ bool* isBuiltChainRootBuiltInRoot,
    /*optional out*/ bool* madeOCSPRequests,
    /*optional out*/ IssuerSources* issuerSources) {
  MOZ_LOG(gCertVerifierLog, LogLevel::Debug, ("Top of VerifyCert\n"));

  MOZ_ASSERT(usage == certificateUsageSSLServer || !(flags & FLAG_MUST_BE_EV));
  MOZ_ASSERT(usage == certificateUsageSSLServer || !keySizeStatus);

  if (NS_FAILED(BlockUntilLoadableCertsLoaded())) {
    return Result::FATAL_ERROR_LIBRARY_FAILURE;
  }
  if (NS_FAILED(CheckForSmartCardChanges())) {
    return Result::FATAL_ERROR_LIBRARY_FAILURE;
  }

  if (evStatus) {
    *evStatus = EVStatus::NotEV;
  }
  if (ocspStaplingStatus) {
    if (usage != certificateUsageSSLServer) {
      return Result::FATAL_ERROR_INVALID_ARGS;
    }
    *ocspStaplingStatus = OCSP_STAPLING_NEVER_CHECKED;
  }

  if (keySizeStatus) {
    if (usage != certificateUsageSSLServer) {
      return Result::FATAL_ERROR_INVALID_ARGS;
    }
    *keySizeStatus = KeySizeStatus::NeverChecked;
  }

  if (usage != certificateUsageSSLServer && (flags & FLAG_MUST_BE_EV)) {
    return Result::FATAL_ERROR_INVALID_ARGS;
  }

  if (isBuiltChainRootBuiltInRoot) {
    *isBuiltChainRootBuiltInRoot = false;
  }

  if (madeOCSPRequests) {
    *madeOCSPRequests = false;
  }

  if (issuerSources) {
    issuerSources->clear();
  }

  Input certDER;
  Result rv = certDER.Init(certBytes.Elements(), certBytes.Length());
  if (rv != Success) {
    return rv;
  }

  // We configure the OCSP fetching modes separately for EV and non-EV
  // verifications.
  NSSCertDBTrustDomain::OCSPFetching defaultOCSPFetching =
      (mOCSPDownloadConfig == ocspOff) || (mOCSPDownloadConfig == ocspEVOnly) ||
              (flags & FLAG_LOCAL_ONLY)
          ? NSSCertDBTrustDomain::NeverFetchOCSP
      : !mOCSPStrict ? NSSCertDBTrustDomain::FetchOCSPForDVSoftFail
                     : NSSCertDBTrustDomain::FetchOCSPForDVHardFail;

  Input stapledOCSPResponseInput;
  const Input* stapledOCSPResponse = nullptr;
  if (stapledOCSPResponseArg) {
    rv = stapledOCSPResponseInput.Init(stapledOCSPResponseArg->Elements(),
                                       stapledOCSPResponseArg->Length());
    if (rv != Success) {
      // The stapled OCSP response was too big.
      return Result::ERROR_OCSP_MALFORMED_RESPONSE;
    }
    stapledOCSPResponse = &stapledOCSPResponseInput;
  }

  Input sctsFromTLSInput;
  if (sctsFromTLS) {
    rv = sctsFromTLSInput.Init(sctsFromTLS->Elements(), sctsFromTLS->Length());
    if (rv != Success && sctsFromTLSInput.GetLength() != 0) {
      return Result::FATAL_ERROR_LIBRARY_FAILURE;
    }
  }

  switch (usage) {
    case certificateUsageSSLClient: {
      // XXX: We don't really have a trust bit for SSL client authentication so
      // just use trustEmail as it is the closest alternative.
      NSSCertDBTrustDomain trustDomain(
          trustEmail, defaultOCSPFetching, mOCSPCache, mSignatureCache.get(),
          mTrustCache.get(), pinArg, mOCSPTimeoutSoft, mOCSPTimeoutHard,
          mCertShortLifetimeInDays, MIN_RSA_BITS_WEAK,
          ValidityCheckingMode::CheckingOff, NetscapeStepUpPolicy::NeverMatch,
          mCRLiteMode, originAttributes, mThirdPartyRootInputs,
          mThirdPartyIntermediateInputs, extraCertificates, builtChain, nullptr,
          nullptr);
      rv = BuildCertChain(
          trustDomain, certDER, time, EndEntityOrCA::MustBeEndEntity,
          KeyUsage::digitalSignature, KeyPurposeId::id_kp_clientAuth,
          CertPolicyId::anyPolicy, stapledOCSPResponse);
      if (madeOCSPRequests) {
        *madeOCSPRequests |=
            trustDomain.GetOCSPFetchStatus() == OCSPFetchStatus::Fetched;
      }
      break;
    }

    case certificateUsageSSLServer: {
      // TODO: When verifying a certificate in an SSL handshake, we should
      // restrict the acceptable key usage based on the key exchange method
      // chosen by the server.

      // Try to validate for EV first.
      NSSCertDBTrustDomain::OCSPFetching evOCSPFetching =
          (mOCSPDownloadConfig == ocspOff) || (flags & FLAG_LOCAL_ONLY)
              ? NSSCertDBTrustDomain::LocalOnlyOCSPForEV
              : NSSCertDBTrustDomain::FetchOCSPForEV;

      nsTArray<CertPolicyId> evPolicies;
      GetKnownEVPolicies(certBytes, evPolicies);
      rv = Result::ERROR_UNKNOWN_ERROR;
      for (const auto& evPolicy : evPolicies) {
        NSSCertDBTrustDomain trustDomain(
            trustSSL, evOCSPFetching, mOCSPCache, mSignatureCache.get(),
            mTrustCache.get(), pinArg, mOCSPTimeoutSoft, mOCSPTimeoutHard,
            mCertShortLifetimeInDays, MIN_RSA_BITS,
            ValidityCheckingMode::CheckForEV, mNetscapeStepUpPolicy,
            mCRLiteMode, originAttributes, mThirdPartyRootInputs,
            mThirdPartyIntermediateInputs, extraCertificates, builtChain,
            pinningTelemetryInfo, hostname);
        rv = BuildCertChainForOneKeyUsage(
            trustDomain, certDER, time,
            KeyUsage::digitalSignature,  // (EC)DHE
            KeyUsage::keyEncipherment,   // RSA
            KeyUsage::keyAgreement,      // (EC)DH
            KeyPurposeId::id_kp_serverAuth, evPolicy, stapledOCSPResponse,
            ocspStaplingStatus);
        if (madeOCSPRequests) {
          *madeOCSPRequests |=
              trustDomain.GetOCSPFetchStatus() == OCSPFetchStatus::Fetched;
        }
        if (issuerSources) {
          *issuerSources = trustDomain.GetIssuerSources();
        }
        if (rv == Success) {
          rv = VerifyCertificateTransparencyPolicy(trustDomain, builtChain,
                                                   sctsFromTLSInput, time,
                                                   hostname, ctInfo);
        }
        if (rv == Success) {
          if (evStatus) {
            *evStatus = EVStatus::EV;
          }
          if (isBuiltChainRootBuiltInRoot) {
            *isBuiltChainRootBuiltInRoot =
                trustDomain.GetIsBuiltChainRootBuiltInRoot();
          }
          break;
        }
      }
      if (rv == Success) {
        break;
      }
      if (flags & FLAG_MUST_BE_EV) {
        rv = Result::ERROR_POLICY_VALIDATION_FAILED;
        break;
      }

      // Now try non-EV.
      unsigned int keySizeOptions[] = {MIN_RSA_BITS, MIN_RSA_BITS_WEAK};

      KeySizeStatus keySizeStatuses[] = {KeySizeStatus::LargeMinimumSucceeded,
                                         KeySizeStatus::CompatibilityRisk};

      static_assert(std::size(keySizeOptions) == std::size(keySizeStatuses),
                    "keySize array lengths differ");

      size_t keySizeOptionsCount = std::size(keySizeStatuses);

      for (size_t i = 0; i < keySizeOptionsCount && rv != Success; i++) {
        // invalidate any telemetry info relating to failed chains
        if (pinningTelemetryInfo) {
          pinningTelemetryInfo->Reset();
        }

        NSSCertDBTrustDomain trustDomain(
            trustSSL, defaultOCSPFetching, mOCSPCache, mSignatureCache.get(),
            mTrustCache.get(), pinArg, mOCSPTimeoutSoft, mOCSPTimeoutHard,
            mCertShortLifetimeInDays, keySizeOptions[i],
            ValidityCheckingMode::CheckingOff, mNetscapeStepUpPolicy,
            mCRLiteMode, originAttributes, mThirdPartyRootInputs,
            mThirdPartyIntermediateInputs, extraCertificates, builtChain,
            pinningTelemetryInfo, hostname);
        rv = BuildCertChainForOneKeyUsage(
            trustDomain, certDER, time,
            KeyUsage::digitalSignature,  //(EC)DHE
            KeyUsage::keyEncipherment,   // RSA
            KeyUsage::keyAgreement,      //(EC)DH
            KeyPurposeId::id_kp_serverAuth, CertPolicyId::anyPolicy,
            stapledOCSPResponse, ocspStaplingStatus);
        if (madeOCSPRequests) {
          *madeOCSPRequests |=
              trustDomain.GetOCSPFetchStatus() == OCSPFetchStatus::Fetched;
        }
        if (issuerSources) {
          *issuerSources = trustDomain.GetIssuerSources();
        }
        if (rv != Success && !IsFatalError(rv) &&
            rv != Result::ERROR_REVOKED_CERTIFICATE &&
            trustDomain.GetIsErrorDueToDistrustedCAPolicy()) {
          // Bug 1444440 - If there are multiple paths, at least one to a CA
          // distrusted-by-policy, and none of them ending in a trusted root,
          // then we might show a different error (UNKNOWN_ISSUER) than we
          // intend, confusing users.
          rv = Result::ERROR_ADDITIONAL_POLICY_CONSTRAINT_FAILED;
        }
        if (rv == Success) {
          rv = VerifyCertificateTransparencyPolicy(trustDomain, builtChain,
                                                   sctsFromTLSInput, time,
                                                   hostname, ctInfo);
        }
        if (rv == Success) {
          if (keySizeStatus) {
            *keySizeStatus = keySizeStatuses[i];
          }
          if (isBuiltChainRootBuiltInRoot) {
            *isBuiltChainRootBuiltInRoot =
                trustDomain.GetIsBuiltChainRootBuiltInRoot();
          }
          break;
        }
      }

      if (rv != Success && keySizeStatus) {
        *keySizeStatus = KeySizeStatus::AlreadyBad;
      }

      break;
    }

    case certificateUsageSSLCA: {
      NSSCertDBTrustDomain trustDomain(
          trustSSL, defaultOCSPFetching, mOCSPCache, mSignatureCache.get(),
          mTrustCache.get(), pinArg, mOCSPTimeoutSoft, mOCSPTimeoutHard,
          mCertShortLifetimeInDays, MIN_RSA_BITS_WEAK,
          ValidityCheckingMode::CheckingOff, mNetscapeStepUpPolicy, mCRLiteMode,
          originAttributes, mThirdPartyRootInputs,
          mThirdPartyIntermediateInputs, extraCertificates, builtChain, nullptr,
          nullptr);
      rv = BuildCertChain(trustDomain, certDER, time, EndEntityOrCA::MustBeCA,
                          KeyUsage::keyCertSign, KeyPurposeId::id_kp_serverAuth,
                          CertPolicyId::anyPolicy, stapledOCSPResponse);
      if (madeOCSPRequests) {
        *madeOCSPRequests |=
            trustDomain.GetOCSPFetchStatus() == OCSPFetchStatus::Fetched;
      }
      break;
    }

    case certificateUsageEmailSigner: {
      NSSCertDBTrustDomain trustDomain(
          trustEmail, defaultOCSPFetching, mOCSPCache, mSignatureCache.get(),
          mTrustCache.get(), pinArg, mOCSPTimeoutSoft, mOCSPTimeoutHard,
          mCertShortLifetimeInDays, MIN_RSA_BITS_WEAK,
          ValidityCheckingMode::CheckingOff, NetscapeStepUpPolicy::NeverMatch,
          mCRLiteMode, originAttributes, mThirdPartyRootInputs,
          mThirdPartyIntermediateInputs, extraCertificates, builtChain, nullptr,
          nullptr);
      rv = BuildCertChain(
          trustDomain, certDER, time, EndEntityOrCA::MustBeEndEntity,
          KeyUsage::digitalSignature, KeyPurposeId::id_kp_emailProtection,
          CertPolicyId::anyPolicy, stapledOCSPResponse);
      if (rv == Result::ERROR_INADEQUATE_KEY_USAGE) {
        rv = BuildCertChain(
            trustDomain, certDER, time, EndEntityOrCA::MustBeEndEntity,
            KeyUsage::nonRepudiation, KeyPurposeId::id_kp_emailProtection,
            CertPolicyId::anyPolicy, stapledOCSPResponse);
      }
      if (madeOCSPRequests) {
        *madeOCSPRequests |=
            trustDomain.GetOCSPFetchStatus() == OCSPFetchStatus::Fetched;
      }
      break;
    }

    case certificateUsageEmailRecipient: {
      // TODO: The higher level S/MIME processing should pass in which key
      // usage it is trying to verify for, and base its algorithm choices
      // based on the result of the verification(s).
      NSSCertDBTrustDomain trustDomain(
          trustEmail, defaultOCSPFetching, mOCSPCache, mSignatureCache.get(),
          mTrustCache.get(), pinArg, mOCSPTimeoutSoft, mOCSPTimeoutHard,
          mCertShortLifetimeInDays, MIN_RSA_BITS_WEAK,
          ValidityCheckingMode::CheckingOff, NetscapeStepUpPolicy::NeverMatch,
          mCRLiteMode, originAttributes, mThirdPartyRootInputs,
          mThirdPartyIntermediateInputs, extraCertificates, builtChain, nullptr,
          nullptr);
      rv = BuildCertChain(trustDomain, certDER, time,
                          EndEntityOrCA::MustBeEndEntity,
                          KeyUsage::keyEncipherment,  // RSA
                          KeyPurposeId::id_kp_emailProtection,
                          CertPolicyId::anyPolicy, stapledOCSPResponse);
      if (rv == Result::ERROR_INADEQUATE_KEY_USAGE) {
        rv = BuildCertChain(trustDomain, certDER, time,
                            EndEntityOrCA::MustBeEndEntity,
                            KeyUsage::keyAgreement,  // ECDH/DH
                            KeyPurposeId::id_kp_emailProtection,
                            CertPolicyId::anyPolicy, stapledOCSPResponse);
      }
      if (madeOCSPRequests) {
        *madeOCSPRequests |=
            trustDomain.GetOCSPFetchStatus() == OCSPFetchStatus::Fetched;
      }
      break;
    }

    default:
      rv = Result::FATAL_ERROR_INVALID_ARGS;
  }

  if (rv != Success) {
    return rv;
  }

  return Success;
}

static bool CertIsSelfSigned(const BackCert& backCert, void* pinarg) {
  if (!InputsAreEqual(backCert.GetIssuer(), backCert.GetSubject())) {
    return false;
  }

  nsTArray<Span<const uint8_t>> emptyCertList;
  // AppTrustDomain is only used for its signature verification callbacks
  // (AppTrustDomain::Verify{ECDSA,RSAPKCS1,RSAPSS}SignedData).
  mozilla::psm::AppTrustDomain trustDomain(std::move(emptyCertList));
  Result rv = VerifySignedData(trustDomain, backCert.GetSignedData(),
                               backCert.GetSubjectPublicKeyInfo());
  return rv == Success;
}

static Result CheckCertHostnameHelper(Input peerCertInput,
                                      const nsACString& hostname,
                                      bool rootIsBuiltIn) {
  Input hostnameInput;
  Result rv = hostnameInput.Init(
      BitwiseCast<const uint8_t*, const char*>(hostname.BeginReading()),
      hostname.Length());
  if (rv != Success) {
    return Result::FATAL_ERROR_INVALID_ARGS;
  }

  SkipInvalidSANsForNonBuiltInRootsPolicy nameMatchingPolicy(rootIsBuiltIn);
  rv = CheckCertHostname(peerCertInput, hostnameInput, nameMatchingPolicy);
  // Treat malformed name information as a domain mismatch.
  if (rv == Result::ERROR_BAD_DER) {
    return Result::ERROR_BAD_CERT_DOMAIN;
  }
  return rv;
}

Result CertVerifier::VerifySSLServerCert(
    const nsTArray<uint8_t>& peerCertBytes, Time time,
    /*optional*/ void* pinarg, const nsACString& hostname,
    /*out*/ nsTArray<nsTArray<uint8_t>>& builtChain,
    /*optional*/ Flags flags,
    /*optional*/ const Maybe<nsTArray<nsTArray<uint8_t>>>& extraCertificates,
    /*optional*/ const Maybe<nsTArray<uint8_t>>& stapledOCSPResponse,
    /*optional*/ const Maybe<nsTArray<uint8_t>>& sctsFromTLS,
    /*optional*/ const Maybe<DelegatedCredentialInfo>& dcInfo,
    /*optional*/ const OriginAttributes& originAttributes,
    /*optional out*/ EVStatus* evStatus,
    /*optional out*/ OCSPStaplingStatus* ocspStaplingStatus,
    /*optional out*/ KeySizeStatus* keySizeStatus,
    /*optional out*/ PinningTelemetryInfo* pinningTelemetryInfo,
    /*optional out*/ CertificateTransparencyInfo* ctInfo,
    /*optional out*/ bool* isBuiltChainRootBuiltInRoot,
    /*optional out*/ bool* madeOCSPRequests,
    /*optional out*/ IssuerSources* issuerSources) {
  // XXX: MOZ_ASSERT(pinarg);
  MOZ_ASSERT(!hostname.IsEmpty());

  if (isBuiltChainRootBuiltInRoot) {
    *isBuiltChainRootBuiltInRoot = false;
  }

  if (evStatus) {
    *evStatus = EVStatus::NotEV;
  }

  if (hostname.IsEmpty()) {
    return Result::FATAL_ERROR_INVALID_ARGS;
  }

  // CreateCertErrorRunnable assumes that CheckCertHostname is only called
  // if VerifyCert succeeded.
  Input peerCertInput;
  Result rv =
      peerCertInput.Init(peerCertBytes.Elements(), peerCertBytes.Length());
  if (rv != Success) {
    return rv;
  }
  bool isBuiltChainRootBuiltInRootLocal;
  rv = VerifyCert(
      peerCertBytes, certificateUsageSSLServer, time, pinarg,
      PromiseFlatCString(hostname).get(), builtChain, flags, extraCertificates,
      stapledOCSPResponse, sctsFromTLS, originAttributes, evStatus,
      ocspStaplingStatus, keySizeStatus, pinningTelemetryInfo, ctInfo,
      &isBuiltChainRootBuiltInRootLocal, madeOCSPRequests, issuerSources);
  if (rv != Success) {
    // we don't use the certificate for path building, so this parameter doesn't
    // matter
    EndEntityOrCA notUsedForPaths = EndEntityOrCA::MustBeEndEntity;
    BackCert peerBackCert(peerCertInput, notUsedForPaths, nullptr);
    if (peerBackCert.Init() != Success) {
      return rv;
    }
    if ((rv == Result::ERROR_UNKNOWN_ISSUER ||
         rv == Result::ERROR_BAD_SIGNATURE ||
         rv == Result::ERROR_INADEQUATE_KEY_USAGE) &&
        CertIsSelfSigned(peerBackCert, pinarg)) {
      // In this case we didn't find any issuer for the certificate, or we did
      // find other certificates with the same subject but different keys, and
      // the certificate is self-signed.
      return Result::ERROR_SELF_SIGNED_CERT;
    }
    if (rv == Result::ERROR_UNKNOWN_ISSUER) {
      // In this case we didn't get any valid path for the cert. Let's see if
      // the issuer is the same as the issuer for our canary probe. If yes, this
      // connection is connecting via a misconfigured proxy.
      // Note: The MitM canary might not be set. In this case we consider this
      // an unknown issuer error.
      nsCOMPtr<nsINSSComponent> component(
          do_GetService(PSM_COMPONENT_CONTRACTID));
      if (!component) {
        return Result::FATAL_ERROR_LIBRARY_FAILURE;
      }
      // IssuerMatchesMitmCanary succeeds if the issuer matches the canary and
      // the feature is enabled.
      Input issuerNameInput = peerBackCert.GetIssuer();
      SECItem issuerNameItem = UnsafeMapInputToSECItem(issuerNameInput);
      UniquePORTString issuerName(CERT_DerNameToAscii(&issuerNameItem));
      if (!issuerName) {
        return Result::ERROR_BAD_DER;
      }
      nsresult rv = component->IssuerMatchesMitmCanary(issuerName.get());
      if (NS_SUCCEEDED(rv)) {
        return Result::ERROR_MITM_DETECTED;
      }
    }
    // If the certificate is expired or not yet valid, first check whether or
    // not it is valid for the indicated hostname, because that would be a more
    // serious error.
    if (rv == Result::ERROR_EXPIRED_CERTIFICATE ||
        rv == Result::ERROR_NOT_YET_VALID_CERTIFICATE ||
        rv == Result::ERROR_INVALID_DER_TIME) {
      Result hostnameResult =
          CheckCertHostnameHelper(peerCertInput, hostname, false);
      if (hostnameResult != Success) {
        return hostnameResult;
      }
    }
    return rv;
  }

  if (dcInfo) {
    rv = IsDelegatedCredentialAcceptable(*dcInfo);
    if (rv != Success) {
      return rv;
    }
  }

  Input stapledOCSPResponseInput;
  Input* responseInputPtr = nullptr;
  if (stapledOCSPResponse) {
    rv = stapledOCSPResponseInput.Init(stapledOCSPResponse->Elements(),
                                       stapledOCSPResponse->Length());
    if (rv != Success) {
      // The stapled OCSP response was too big.
      return Result::ERROR_OCSP_MALFORMED_RESPONSE;
    }
    responseInputPtr = &stapledOCSPResponseInput;
  }

  if (!(flags & FLAG_TLS_IGNORE_STATUS_REQUEST)) {
    rv = CheckTLSFeaturesAreSatisfied(peerCertInput, responseInputPtr);
    if (rv != Success) {
      return rv;
    }
  }

  rv = CheckCertHostnameHelper(peerCertInput, hostname,
                               isBuiltChainRootBuiltInRootLocal);
  if ((rv == Success || rv == Result::ERROR_BAD_CERT_DOMAIN) &&
      isBuiltChainRootBuiltInRoot) {
    *isBuiltChainRootBuiltInRoot = isBuiltChainRootBuiltInRootLocal;
  }
  if (rv != Success) {
    return rv;
  }

  return Success;
}

// Take the (data, signature, subjectPublicKeyInfo, publicKeyAlgorithm,
// digestAlgorithm) tuple that defines a signature and derive a hash that
// uniquely identifies it. This is done by prefixing each variable-length
// component (data, signature, and subjectPublicKeyInfo) with
// sizeof(pkix::Input::size_type) bytes (currently 2) indicating the length of
// that component and concatenating them together, followed by one byte for the
// digestAlgorithm. The concatenation is then hashed with sha512.
// It should be computationally infeasible to find two distinct sets of inputs
// that have the same sha512 hash (and if it were possible, then it would be
// possible to break the signature scheme itself).
void HashSignatureParams(pkix::Input data, pkix::Input signature,
                         pkix::Input subjectPublicKeyInfo,
                         pkix::der::PublicKeyAlgorithm publicKeyAlgorithm,
                         pkix::DigestAlgorithm digestAlgorithm,
                         /*out*/ Maybe<nsTArray<uint8_t>>& sha512Hash) {
  sha512Hash.reset();
  Digest digest;
  if (NS_FAILED(digest.Begin(SEC_OID_SHA512))) {
    return;
  }
  pkix::Input::size_type dataLength = data.GetLength();
  if (NS_FAILED(digest.Update(reinterpret_cast<const uint8_t*>(&dataLength),
                              sizeof(dataLength)))) {
    return;
  }
  if (NS_FAILED(digest.Update(data.UnsafeGetData(), dataLength))) {
    return;
  }
  pkix::Input::size_type signatureLength = signature.GetLength();
  if (NS_FAILED(
          digest.Update(reinterpret_cast<const uint8_t*>(&signatureLength),
                        sizeof(signatureLength)))) {
    return;
  }
  if (NS_FAILED(digest.Update(signature.UnsafeGetData(), signatureLength))) {
    return;
  }
  pkix::Input::size_type spkiLength = subjectPublicKeyInfo.GetLength();
  if (NS_FAILED(digest.Update(reinterpret_cast<const uint8_t*>(&spkiLength),
                              sizeof(spkiLength)))) {
    return;
  }
  if (NS_FAILED(
          digest.Update(subjectPublicKeyInfo.UnsafeGetData(), spkiLength))) {
    return;
  }
  if (NS_FAILED(
          digest.Update(reinterpret_cast<const uint8_t*>(&publicKeyAlgorithm),
                        sizeof(publicKeyAlgorithm)))) {
    return;
  }
  if (NS_FAILED(
          digest.Update(reinterpret_cast<const uint8_t*>(&digestAlgorithm),
                        sizeof(digestAlgorithm)))) {
    return;
  }
  nsTArray<uint8_t> result;
  if (NS_FAILED(digest.End(result))) {
    return;
  }
  sha512Hash.emplace(std::move(result));
}

Result VerifySignedDataWithCache(
    der::PublicKeyAlgorithm publicKeyAlg,
    mozilla::glean::impl::DenominatorMetric telemetryDenominator,
    mozilla::glean::impl::NumeratorMetric telemetryNumerator, Input data,
    DigestAlgorithm digestAlgorithm, Input signature,
    Input subjectPublicKeyInfo, SignatureCache* signatureCache, void* pinArg) {
  telemetryDenominator.Add(1);
  Maybe<nsTArray<uint8_t>> sha512Hash;
  HashSignatureParams(data, signature, subjectPublicKeyInfo, publicKeyAlg,
                      digestAlgorithm, sha512Hash);
  // If hashing the signature parameters succeeded, see if this signature is in
  // the signature cache.
  if (sha512Hash.isSome() &&
      signature_cache_get(signatureCache, sha512Hash.ref().Elements())) {
    telemetryNumerator.AddToNumerator(1);
    return Success;
  }
  Result result;
  switch (publicKeyAlg) {
    case der::PublicKeyAlgorithm::ECDSA:
      result = VerifyECDSASignedDataNSS(data, digestAlgorithm, signature,
                                        subjectPublicKeyInfo, pinArg);
      break;
    case der::PublicKeyAlgorithm::RSA_PKCS1:
      result = VerifyRSAPKCS1SignedDataNSS(data, digestAlgorithm, signature,
                                           subjectPublicKeyInfo, pinArg);
      break;
    case der::PublicKeyAlgorithm::RSA_PSS:
      result = VerifyRSAPSSSignedDataNSS(data, digestAlgorithm, signature,
                                         subjectPublicKeyInfo, pinArg);
      break;
    default:
      MOZ_ASSERT_UNREACHABLE("unhandled public key algorithm");
      return Result::FATAL_ERROR_LIBRARY_FAILURE;
  }
  // Add this signature to the signature cache.
  if (sha512Hash.isSome() && result == Success) {
    signature_cache_insert(signatureCache, sha512Hash.ref().Elements());
  }
  return result;
}

}  // namespace psm
}  // namespace mozilla

95%


¤ Dauer der Verarbeitung: 0.21 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung ist noch experimentell.