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