/* -*- 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 "AppTrustDomain.h" #include "MainThreadUtils.h" #include "cert_storage/src/cert_storage.h" #include "certdb.h" #include "mozilla/ArrayUtils.h" #include "mozilla/Casting.h" #include "mozilla/Preferences.h" #include "mozpkix/pkixnss.h" #include "nsComponentManagerUtils.h" #include "nsDirectoryServiceUtils.h" #include "nsIX509CertDB.h" #include "nsNSSCertificate.h" #include "nsNetUtil.h" #include "prerror.h" // Generated by gen_cert_header.py, which gets called by the build system. #include "xpcshell.inc" // Add-on signing Certificates #include "addons-public.inc" #include "addons-public-intermediate.inc" #include "addons-stage.inc" using namespace mozilla::pkix; extern mozilla::LazyLogModule gPIPNSSLog; namespace mozilla { namespace psm { AppTrustDomain::AppTrustDomain(nsTArray>&& collectedCerts) : mIntermediates(std::move(collectedCerts)), mCertBlocklist(do_GetService(NS_CERT_STORAGE_CID)) {} nsresult AppTrustDomain::SetTrustedRoot(AppTrustedRoot trustedRoot) { switch (trustedRoot) { case nsIX509CertDB::AppXPCShellRoot: mTrustedRoot = {xpcshellRoot}; break; case nsIX509CertDB::AddonsPublicRoot: mTrustedRoot = {addonsPublicRoot}; break; case nsIX509CertDB::AddonsStageRoot: mTrustedRoot = {addonsStageRoot}; break; default: return NS_ERROR_INVALID_ARG; } // If we're verifying add-ons signed by our production root, we want to make // sure a valid intermediate certificate is available for path building. if (trustedRoot == nsIX509CertDB::AddonsPublicRoot) { mAddonsIntermediate = {addonsPublicIntermediate}; } return NS_OK; } Result AppTrustDomain::FindIssuer(Input encodedIssuerName, IssuerChecker& checker, Time) { MOZ_ASSERT(!mTrustedRoot.IsEmpty()); if (mTrustedRoot.IsEmpty()) { return Result::FATAL_ERROR_INVALID_STATE; } nsTArray candidates; Input rootInput; Result rv = rootInput.Init(mTrustedRoot.Elements(), mTrustedRoot.Length()); // This should never fail, since the possible roots are all hard-coded and // they should never be too long. if (rv != Success) { return rv; } candidates.AppendElement(std::move(rootInput)); if (!mAddonsIntermediate.IsEmpty()) { Input intermediateInput; rv = intermediateInput.Init(mAddonsIntermediate.Elements(), mAddonsIntermediate.Length()); // Again, this should never fail for the same reason as above. if (rv != Success) { return rv; } candidates.AppendElement(std::move(intermediateInput)); } for (const auto& intermediate : mIntermediates) { Input intermediateInput; rv = intermediateInput.Init(intermediate.Elements(), intermediate.Length()); // This is untrusted input, so skip any intermediates that are too large. if (rv != Success) { continue; } candidates.AppendElement(std::move(intermediateInput)); } for (const auto& candidate : candidates) { bool keepGoing; rv = checker.Check(candidate, nullptr /*additionalNameConstraints*/, keepGoing); if (rv != Success) { return rv; } if (!keepGoing) { return Success; } } // If the above did not succeed in building a verified certificate chain, // fall back to searching for candidates in NSS. This is important in case an // intermediate involved in add-on signing expires before it is replaced. See // bug 1548973. SECItem encodedIssuerNameSECItem = UnsafeMapInputToSECItem(encodedIssuerName); UniqueCERTCertList nssCandidates(CERT_CreateSubjectCertList( nullptr, CERT_GetDefaultCertDB(), &encodedIssuerNameSECItem, 0, false)); if (nssCandidates) { for (CERTCertListNode* n = CERT_LIST_HEAD(nssCandidates); !CERT_LIST_END(n, nssCandidates); n = CERT_LIST_NEXT(n)) { Input certDER; Result rv = certDER.Init(n->cert->derCert.data, n->cert->derCert.len); if (rv != Success) { continue; // probably too big } bool keepGoing; rv = checker.Check(certDER, nullptr /*additionalNameConstraints*/, keepGoing); if (rv != Success) { return rv; } if (!keepGoing) { break; } } } return Success; } Result AppTrustDomain::GetCertTrust(EndEntityOrCA endEntityOrCA, const CertPolicyId& policy, Input candidateCertDER, /*out*/ TrustLevel& trustLevel) { MOZ_ASSERT(policy.IsAnyPolicy()); MOZ_ASSERT(!mTrustedRoot.IsEmpty()); if (!policy.IsAnyPolicy()) { return Result::FATAL_ERROR_INVALID_ARGS; } if (mTrustedRoot.IsEmpty()) { return Result::FATAL_ERROR_INVALID_STATE; } nsTArray issuerBytes; nsTArray serialBytes; nsTArray subjectBytes; nsTArray pubKeyBytes; Result result = BuildRevocationCheckArrays(candidateCertDER, endEntityOrCA, issuerBytes, serialBytes, subjectBytes, pubKeyBytes); if (result != Success) { return result; } int16_t revocationState; nsresult nsrv = mCertBlocklist->GetRevocationState( issuerBytes, serialBytes, subjectBytes, pubKeyBytes, &revocationState); if (NS_FAILED(nsrv)) { return Result::FATAL_ERROR_LIBRARY_FAILURE; } if (revocationState == nsICertStorage::STATE_ENFORCE) { return Result::ERROR_REVOKED_CERTIFICATE; } // mTrustedRoot is the only trust anchor for this validation. Span candidateCertDERSpan = {candidateCertDER.UnsafeGetData(), candidateCertDER.GetLength()}; if (mTrustedRoot == candidateCertDERSpan) { trustLevel = TrustLevel::TrustAnchor; return Success; } trustLevel = TrustLevel::InheritsTrust; return Success; } Result AppTrustDomain::DigestBuf(Input item, DigestAlgorithm digestAlg, /*out*/ uint8_t* digestBuf, size_t digestBufLen) { return DigestBufNSS(item, digestAlg, digestBuf, digestBufLen); } Result AppTrustDomain::CheckRevocation(EndEntityOrCA, const CertID&, Time, Duration, /*optional*/ const Input*, /*optional*/ const Input*, /*optional*/ const Input*) { // We don't currently do revocation checking. If we need to distrust an Apps // certificate, we will use the active distrust mechanism. return Success; } Result AppTrustDomain::IsChainValid(const DERArray& certChain, Time time, const CertPolicyId& requiredPolicy) { MOZ_ASSERT(requiredPolicy.IsAnyPolicy()); return Success; } Result AppTrustDomain::CheckSignatureDigestAlgorithm(DigestAlgorithm, EndEntityOrCA, Time) { // TODO: We should restrict signatures to SHA-256 or better. return Success; } Result AppTrustDomain::CheckRSAPublicKeyModulusSizeInBits( EndEntityOrCA /*endEntityOrCA*/, unsigned int modulusSizeInBits) { if (modulusSizeInBits < 2048u) { return Result::ERROR_INADEQUATE_KEY_SIZE; } return Success; } Result AppTrustDomain::VerifyRSAPKCS1SignedDigest( const SignedDigest& signedDigest, Input subjectPublicKeyInfo) { // TODO: We should restrict signatures to SHA-256 or better. return VerifyRSAPKCS1SignedDigestNSS(signedDigest, subjectPublicKeyInfo, nullptr); } Result AppTrustDomain::CheckECDSACurveIsAcceptable( EndEntityOrCA /*endEntityOrCA*/, NamedCurve curve) { switch (curve) { case NamedCurve::secp256r1: // fall through case NamedCurve::secp384r1: // fall through case NamedCurve::secp521r1: return Success; } return Result::ERROR_UNSUPPORTED_ELLIPTIC_CURVE; } Result AppTrustDomain::VerifyECDSASignedDigest(const SignedDigest& signedDigest, Input subjectPublicKeyInfo) { return VerifyECDSASignedDigestNSS(signedDigest, subjectPublicKeyInfo, nullptr); } Result AppTrustDomain::CheckValidityIsAcceptable( Time /*notBefore*/, Time /*notAfter*/, EndEntityOrCA /*endEntityOrCA*/, KeyPurposeId /*keyPurpose*/) { return Success; } Result AppTrustDomain::NetscapeStepUpMatchesServerAuth(Time /*notBefore*/, /*out*/ bool& matches) { matches = false; return Success; } void AppTrustDomain::NoteAuxiliaryExtension(AuxiliaryExtension /*extension*/, Input /*extensionData*/) {} } // namespace psm } // namespace mozilla