зеркало из https://github.com/mozilla/gecko-dev.git
273 строки
8.9 KiB
C++
273 строки
8.9 KiB
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 "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<Span<const uint8_t>>&& 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<Input> 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<uint8_t> issuerBytes;
|
|
nsTArray<uint8_t> serialBytes;
|
|
nsTArray<uint8_t> subjectBytes;
|
|
nsTArray<uint8_t> 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<const uint8_t> 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
|