Line data Source code
1 : /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 : /* This Source Code Form is subject to the terms of the Mozilla Public
4 : * License, v. 2.0. If a copy of the MPL was not distributed with this
5 : * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 :
7 : #include "AppTrustDomain.h"
8 :
9 : #include "MainThreadUtils.h"
10 : #include "certdb.h"
11 : #include "mozilla/ArrayUtils.h"
12 : #include "mozilla/Casting.h"
13 : #include "mozilla/Preferences.h"
14 : #include "nsComponentManagerUtils.h"
15 : #include "nsIFile.h"
16 : #include "nsIFileStreams.h"
17 : #include "nsIX509CertDB.h"
18 : #include "nsNSSCertificate.h"
19 : #include "nsNetUtil.h"
20 : #include "pkix/pkixnss.h"
21 : #include "prerror.h"
22 :
23 : // Generated by gen_cert_header.py, which gets called by the build system.
24 : #include "xpcshell.inc"
25 : // Add-on signing Certificates
26 : #include "addons-public.inc"
27 : #include "addons-stage.inc"
28 : // Privileged Package Certificates
29 : #include "privileged-package-root.inc"
30 :
31 : using namespace mozilla::pkix;
32 :
33 : extern mozilla::LazyLogModule gPIPNSSLog;
34 :
35 : static char kDevImportedDER[] =
36 : "network.http.signed-packages.developer-root";
37 :
38 : namespace mozilla { namespace psm {
39 :
40 3 : StaticMutex AppTrustDomain::sMutex;
41 3 : UniquePtr<unsigned char[]> AppTrustDomain::sDevImportedDERData;
42 : unsigned int AppTrustDomain::sDevImportedDERLen = 0;
43 :
44 0 : AppTrustDomain::AppTrustDomain(UniqueCERTCertList& certChain, void* pinArg)
45 : : mCertChain(certChain)
46 0 : , mPinArg(pinArg)
47 : {
48 0 : }
49 :
50 : nsresult
51 0 : AppTrustDomain::SetTrustedRoot(AppTrustedRoot trustedRoot)
52 : {
53 : SECItem trustedDER;
54 :
55 : // Load the trusted certificate into the in-memory NSS database so that
56 : // CERT_CreateSubjectCertList can find it.
57 :
58 0 : switch (trustedRoot)
59 : {
60 : case nsIX509CertDB::AppXPCShellRoot:
61 0 : trustedDER.data = const_cast<uint8_t*>(xpcshellRoot);
62 0 : trustedDER.len = mozilla::ArrayLength(xpcshellRoot);
63 0 : break;
64 :
65 : case nsIX509CertDB::AddonsPublicRoot:
66 0 : trustedDER.data = const_cast<uint8_t*>(addonsPublicRoot);
67 0 : trustedDER.len = mozilla::ArrayLength(addonsPublicRoot);
68 0 : break;
69 :
70 : case nsIX509CertDB::AddonsStageRoot:
71 0 : trustedDER.data = const_cast<uint8_t*>(addonsStageRoot);
72 0 : trustedDER.len = mozilla::ArrayLength(addonsStageRoot);
73 0 : break;
74 :
75 : case nsIX509CertDB::PrivilegedPackageRoot:
76 0 : trustedDER.data = const_cast<uint8_t*>(privilegedPackageRoot);
77 0 : trustedDER.len = mozilla::ArrayLength(privilegedPackageRoot);
78 0 : break;
79 :
80 : case nsIX509CertDB::DeveloperImportedRoot: {
81 0 : StaticMutexAutoLock lock(sMutex);
82 0 : if (!sDevImportedDERData) {
83 0 : MOZ_ASSERT(!NS_IsMainThread());
84 0 : nsCOMPtr<nsIFile> file(do_CreateInstance("@mozilla.org/file/local;1"));
85 0 : if (!file) {
86 0 : return NS_ERROR_FAILURE;
87 : }
88 0 : nsresult rv = file->InitWithNativePath(
89 0 : Preferences::GetCString(kDevImportedDER));
90 0 : if (NS_FAILED(rv)) {
91 0 : return rv;
92 : }
93 :
94 0 : nsCOMPtr<nsIInputStream> inputStream;
95 0 : rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream), file, -1,
96 0 : -1, nsIFileInputStream::CLOSE_ON_EOF);
97 0 : if (NS_FAILED(rv)) {
98 0 : return rv;
99 : }
100 :
101 : uint64_t length;
102 0 : rv = inputStream->Available(&length);
103 0 : if (NS_FAILED(rv)) {
104 0 : return rv;
105 : }
106 :
107 0 : auto data = MakeUnique<char[]>(length);
108 0 : rv = inputStream->Read(data.get(), length, &sDevImportedDERLen);
109 0 : if (NS_FAILED(rv)) {
110 0 : return rv;
111 : }
112 :
113 0 : MOZ_ASSERT(length == sDevImportedDERLen);
114 0 : sDevImportedDERData.reset(
115 0 : BitwiseCast<unsigned char*, char*>(data.release()));
116 : }
117 :
118 0 : trustedDER.data = sDevImportedDERData.get();
119 0 : trustedDER.len = sDevImportedDERLen;
120 0 : break;
121 : }
122 :
123 : default:
124 0 : return NS_ERROR_INVALID_ARG;
125 : }
126 :
127 0 : mTrustedRoot.reset(CERT_NewTempCertificate(CERT_GetDefaultCertDB(),
128 0 : &trustedDER, nullptr, false, true));
129 0 : if (!mTrustedRoot) {
130 0 : return mozilla::psm::GetXPCOMFromNSSError(PR_GetError());
131 : }
132 :
133 0 : return NS_OK;
134 : }
135 :
136 : Result
137 0 : AppTrustDomain::FindIssuer(Input encodedIssuerName, IssuerChecker& checker,
138 : Time)
139 :
140 : {
141 0 : MOZ_ASSERT(mTrustedRoot);
142 0 : if (!mTrustedRoot) {
143 0 : return Result::FATAL_ERROR_INVALID_STATE;
144 : }
145 :
146 : // TODO(bug 1035418): If/when mozilla::pkix relaxes the restriction that
147 : // FindIssuer must only pass certificates with a matching subject name to
148 : // checker.Check, we can stop using CERT_CreateSubjectCertList and instead
149 : // use logic like this:
150 : //
151 : // 1. First, try the trusted trust anchor.
152 : // 2. Secondly, iterate through the certificates that were stored in the CMS
153 : // message, passing each one to checker.Check.
154 : SECItem encodedIssuerNameSECItem =
155 0 : UnsafeMapInputToSECItem(encodedIssuerName);
156 : UniqueCERTCertList
157 : candidates(CERT_CreateSubjectCertList(nullptr, CERT_GetDefaultCertDB(),
158 : &encodedIssuerNameSECItem, 0,
159 0 : false));
160 0 : if (candidates) {
161 0 : for (CERTCertListNode* n = CERT_LIST_HEAD(candidates);
162 0 : !CERT_LIST_END(n, candidates); n = CERT_LIST_NEXT(n)) {
163 0 : Input certDER;
164 0 : Result rv = certDER.Init(n->cert->derCert.data, n->cert->derCert.len);
165 0 : if (rv != Success) {
166 0 : continue; // probably too big
167 : }
168 :
169 : bool keepGoing;
170 : rv = checker.Check(certDER, nullptr/*additionalNameConstraints*/,
171 0 : keepGoing);
172 0 : if (rv != Success) {
173 0 : return rv;
174 : }
175 0 : if (!keepGoing) {
176 0 : break;
177 : }
178 : }
179 : }
180 :
181 0 : return Success;
182 : }
183 :
184 : Result
185 0 : AppTrustDomain::GetCertTrust(EndEntityOrCA endEntityOrCA,
186 : const CertPolicyId& policy,
187 : Input candidateCertDER,
188 : /*out*/ TrustLevel& trustLevel)
189 : {
190 0 : MOZ_ASSERT(policy.IsAnyPolicy());
191 0 : MOZ_ASSERT(mTrustedRoot);
192 0 : if (!policy.IsAnyPolicy()) {
193 0 : return Result::FATAL_ERROR_INVALID_ARGS;
194 : }
195 0 : if (!mTrustedRoot) {
196 0 : return Result::FATAL_ERROR_INVALID_STATE;
197 : }
198 :
199 : // Handle active distrust of the certificate.
200 :
201 : // XXX: This would be cleaner and more efficient if we could get the trust
202 : // information without constructing a CERTCertificate here, but NSS doesn't
203 : // expose it in any other easy-to-use fashion.
204 : SECItem candidateCertDERSECItem =
205 0 : UnsafeMapInputToSECItem(candidateCertDER);
206 : UniqueCERTCertificate candidateCert(
207 : CERT_NewTempCertificate(CERT_GetDefaultCertDB(), &candidateCertDERSECItem,
208 0 : nullptr, false, true));
209 0 : if (!candidateCert) {
210 0 : return MapPRErrorCodeToResult(PR_GetError());
211 : }
212 :
213 : CERTCertTrust trust;
214 0 : if (CERT_GetCertTrust(candidateCert.get(), &trust) == SECSuccess) {
215 0 : uint32_t flags = SEC_GET_TRUST_FLAGS(&trust, trustObjectSigning);
216 :
217 : // For DISTRUST, we use the CERTDB_TRUSTED or CERTDB_TRUSTED_CA bit,
218 : // because we can have active distrust for either type of cert. Note that
219 : // CERTDB_TERMINAL_RECORD means "stop trying to inherit trust" so if the
220 : // relevant trust bit isn't set then that means the cert must be considered
221 : // distrusted.
222 : uint32_t relevantTrustBit = endEntityOrCA == EndEntityOrCA::MustBeCA
223 0 : ? CERTDB_TRUSTED_CA
224 0 : : CERTDB_TRUSTED;
225 0 : if (((flags & (relevantTrustBit | CERTDB_TERMINAL_RECORD)))
226 : == CERTDB_TERMINAL_RECORD) {
227 0 : trustLevel = TrustLevel::ActivelyDistrusted;
228 0 : return Success;
229 : }
230 : }
231 :
232 : // mTrustedRoot is the only trust anchor for this validation.
233 0 : if (CERT_CompareCerts(mTrustedRoot.get(), candidateCert.get())) {
234 0 : trustLevel = TrustLevel::TrustAnchor;
235 0 : return Success;
236 : }
237 :
238 0 : trustLevel = TrustLevel::InheritsTrust;
239 0 : return Success;
240 : }
241 :
242 : Result
243 0 : AppTrustDomain::DigestBuf(Input item,
244 : DigestAlgorithm digestAlg,
245 : /*out*/ uint8_t* digestBuf,
246 : size_t digestBufLen)
247 : {
248 0 : return DigestBufNSS(item, digestAlg, digestBuf, digestBufLen);
249 : }
250 :
251 : Result
252 0 : AppTrustDomain::CheckRevocation(EndEntityOrCA, const CertID&, Time, Duration,
253 : /*optional*/ const Input*,
254 : /*optional*/ const Input*)
255 : {
256 : // We don't currently do revocation checking. If we need to distrust an Apps
257 : // certificate, we will use the active distrust mechanism.
258 0 : return Success;
259 : }
260 :
261 : Result
262 0 : AppTrustDomain::IsChainValid(const DERArray& certChain, Time time,
263 : const CertPolicyId& requiredPolicy)
264 : {
265 0 : MOZ_ASSERT(requiredPolicy.IsAnyPolicy());
266 0 : SECStatus srv = ConstructCERTCertListFromReversedDERArray(certChain,
267 0 : mCertChain);
268 0 : if (srv != SECSuccess) {
269 0 : return MapPRErrorCodeToResult(PR_GetError());
270 : }
271 0 : return Success;
272 : }
273 :
274 : Result
275 0 : AppTrustDomain::CheckSignatureDigestAlgorithm(DigestAlgorithm,
276 : EndEntityOrCA,
277 : Time)
278 : {
279 : // TODO: We should restrict signatures to SHA-256 or better.
280 0 : return Success;
281 : }
282 :
283 : Result
284 0 : AppTrustDomain::CheckRSAPublicKeyModulusSizeInBits(
285 : EndEntityOrCA /*endEntityOrCA*/, unsigned int modulusSizeInBits)
286 : {
287 0 : if (modulusSizeInBits < 2048u) {
288 0 : return Result::ERROR_INADEQUATE_KEY_SIZE;
289 : }
290 0 : return Success;
291 : }
292 :
293 : Result
294 0 : AppTrustDomain::VerifyRSAPKCS1SignedDigest(const SignedDigest& signedDigest,
295 : Input subjectPublicKeyInfo)
296 : {
297 : // TODO: We should restrict signatures to SHA-256 or better.
298 0 : return VerifyRSAPKCS1SignedDigestNSS(signedDigest, subjectPublicKeyInfo,
299 0 : mPinArg);
300 : }
301 :
302 : Result
303 0 : AppTrustDomain::CheckECDSACurveIsAcceptable(EndEntityOrCA /*endEntityOrCA*/,
304 : NamedCurve curve)
305 : {
306 0 : switch (curve) {
307 : case NamedCurve::secp256r1: // fall through
308 : case NamedCurve::secp384r1: // fall through
309 : case NamedCurve::secp521r1:
310 0 : return Success;
311 : }
312 :
313 0 : return Result::ERROR_UNSUPPORTED_ELLIPTIC_CURVE;
314 : }
315 :
316 : Result
317 0 : AppTrustDomain::VerifyECDSASignedDigest(const SignedDigest& signedDigest,
318 : Input subjectPublicKeyInfo)
319 : {
320 0 : return VerifyECDSASignedDigestNSS(signedDigest, subjectPublicKeyInfo,
321 0 : mPinArg);
322 : }
323 :
324 : Result
325 0 : AppTrustDomain::CheckValidityIsAcceptable(Time /*notBefore*/, Time /*notAfter*/,
326 : EndEntityOrCA /*endEntityOrCA*/,
327 : KeyPurposeId /*keyPurpose*/)
328 : {
329 0 : return Success;
330 : }
331 :
332 : Result
333 0 : AppTrustDomain::NetscapeStepUpMatchesServerAuth(Time /*notBefore*/,
334 : /*out*/ bool& matches)
335 : {
336 0 : matches = false;
337 0 : return Success;
338 : }
339 :
340 : void
341 0 : AppTrustDomain::NoteAuxiliaryExtension(AuxiliaryExtension /*extension*/,
342 : Input /*extensionData*/)
343 : {
344 0 : }
345 :
346 9 : } } // namespace mozilla::psm
|