/* -*- 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 "certdb.h" #include "mozilla/ArrayUtils.h" #include "mozilla/Casting.h" #include "mozilla/Preferences.h" #include "nsComponentManagerUtils.h" #include "nsIX509CertDB.h" #include "nsNSSCertificate.h" #include "nsNetUtil.h" #include "mozpkix/pkixnss.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(UniqueCERTCertList& certChain, void* pinArg) : mCertChain(certChain), mPinArg(pinArg) {} nsresult AppTrustDomain::SetTrustedRoot(AppTrustedRoot trustedRoot) { SECItem trustedDER; // Load the trusted certificate into the in-memory NSS database so that // CERT_CreateSubjectCertList can find it. switch (trustedRoot) { case nsIX509CertDB::AppXPCShellRoot: trustedDER.data = const_cast(xpcshellRoot); trustedDER.len = mozilla::ArrayLength(xpcshellRoot); break; case nsIX509CertDB::AddonsPublicRoot: trustedDER.data = const_cast(addonsPublicRoot); trustedDER.len = mozilla::ArrayLength(addonsPublicRoot); break; case nsIX509CertDB::AddonsStageRoot: trustedDER.data = const_cast(addonsStageRoot); trustedDER.len = mozilla::ArrayLength(addonsStageRoot); break; default: return NS_ERROR_INVALID_ARG; } mTrustedRoot.reset(CERT_NewTempCertificate( CERT_GetDefaultCertDB(), &trustedDER, nullptr, false, true)); if (!mTrustedRoot) { return mozilla::psm::GetXPCOMFromNSSError(PR_GetError()); } // 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. // Merely holding this alive in memory makes it available for NSS to find in // AppTrustDomain::FindIssuer. if (trustedRoot == nsIX509CertDB::AddonsPublicRoot) { SECItem intermediateDER = { siBuffer, const_cast(addonsPublicIntermediate), static_cast( mozilla::ArrayLength(addonsPublicIntermediate)), }; mAddonsIntermediate.reset(CERT_NewTempCertificate( CERT_GetDefaultCertDB(), &intermediateDER, nullptr, false, true)); if (!mAddonsIntermediate) { return mozilla::psm::GetXPCOMFromNSSError(PR_GetError()); } } return NS_OK; } Result AppTrustDomain::FindIssuer(Input encodedIssuerName, IssuerChecker& checker, Time) { MOZ_ASSERT(mTrustedRoot); if (!mTrustedRoot) { return Result::FATAL_ERROR_INVALID_STATE; } // TODO(bug 1035418): If/when mozilla::pkix relaxes the restriction that // FindIssuer must only pass certificates with a matching subject name to // checker.Check, we can stop using CERT_CreateSubjectCertList and instead // use logic like this: // // 1. First, try the trusted trust anchor. // 2. Secondly, iterate through the certificates that were stored in the CMS // message, passing each one to checker.Check. SECItem encodedIssuerNameSECItem = UnsafeMapInputToSECItem(encodedIssuerName); UniqueCERTCertList candidates(CERT_CreateSubjectCertList( nullptr, CERT_GetDefaultCertDB(), &encodedIssuerNameSECItem, 0, false)); if (candidates) { for (CERTCertListNode* n = CERT_LIST_HEAD(candidates); !CERT_LIST_END(n, candidates); 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); if (!policy.IsAnyPolicy()) { return Result::FATAL_ERROR_INVALID_ARGS; } if (!mTrustedRoot) { return Result::FATAL_ERROR_INVALID_STATE; } // Handle active distrust of the certificate. // XXX: This would be cleaner and more efficient if we could get the trust // information without constructing a CERTCertificate here, but NSS doesn't // expose it in any other easy-to-use fashion. SECItem candidateCertDERSECItem = UnsafeMapInputToSECItem(candidateCertDER); UniqueCERTCertificate candidateCert(CERT_NewTempCertificate( CERT_GetDefaultCertDB(), &candidateCertDERSECItem, nullptr, false, true)); if (!candidateCert) { return MapPRErrorCodeToResult(PR_GetError()); } CERTCertTrust trust; if (CERT_GetCertTrust(candidateCert.get(), &trust) == SECSuccess) { uint32_t flags = SEC_GET_TRUST_FLAGS(&trust, trustObjectSigning); // For DISTRUST, we use the CERTDB_TRUSTED or CERTDB_TRUSTED_CA bit, // because we can have active distrust for either type of cert. Note that // CERTDB_TERMINAL_RECORD means "stop trying to inherit trust" so if the // relevant trust bit isn't set then that means the cert must be considered // distrusted. uint32_t relevantTrustBit = endEntityOrCA == EndEntityOrCA::MustBeCA ? CERTDB_TRUSTED_CA : CERTDB_TRUSTED; if (((flags & (relevantTrustBit | CERTDB_TERMINAL_RECORD))) == CERTDB_TERMINAL_RECORD) { trustLevel = TrustLevel::ActivelyDistrusted; return Success; } } // mTrustedRoot is the only trust anchor for this validation. if (CERT_CompareCerts(mTrustedRoot.get(), candidateCert.get())) { 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, Time, Duration, /*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()); SECStatus srv = ConstructCERTCertListFromReversedDERArray(certChain, mCertChain); if (srv != SECSuccess) { return MapPRErrorCodeToResult(PR_GetError()); } 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, mPinArg); } 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, mPinArg); } 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