| |
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 /* Copyright 2013 Mozilla Foundation |
| |
4 * |
| |
5 * Licensed under the Apache License, Version 2.0 (the "License"); |
| |
6 * you may not use this file except in compliance with the License. |
| |
7 * You may obtain a copy of the License at |
| |
8 * |
| |
9 * http://www.apache.org/licenses/LICENSE-2.0 |
| |
10 * |
| |
11 * Unless required by applicable law or agreed to in writing, software |
| |
12 * distributed under the License is distributed on an "AS IS" BASIS, |
| |
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| |
14 * See the License for the specific language governing permissions and |
| |
15 * limitations under the License. |
| |
16 */ |
| |
17 |
| |
18 #include "pkix/pkix.h" |
| |
19 |
| |
20 #include <limits> |
| |
21 |
| |
22 #include "pkixcheck.h" |
| |
23 #include "pkixder.h" |
| |
24 |
| |
25 namespace mozilla { namespace pkix { |
| |
26 |
| |
27 // We assume ext has been zero-initialized by its constructor and otherwise |
| |
28 // not modified. |
| |
29 // |
| |
30 // TODO(perf): This sorting of extensions should be be moved into the |
| |
31 // certificate decoder so that the results are cached with the certificate, so |
| |
32 // that the decoding doesn't have to happen more than once per cert. |
| |
33 Result |
| |
34 BackCert::Init() |
| |
35 { |
| |
36 const CERTCertExtension* const* exts = nssCert->extensions; |
| |
37 if (!exts) { |
| |
38 return Success; |
| |
39 } |
| |
40 // We only decode v3 extensions for v3 certificates for two reasons. |
| |
41 // 1. They make no sense in non-v3 certs |
| |
42 // 2. An invalid cert can embed a basic constraints extension and the |
| |
43 // check basic constrains will asume that this is valid. Making it |
| |
44 // posible to create chains with v1 and v2 intermediates with is |
| |
45 // not desirable. |
| |
46 if (! (nssCert->version.len == 1 && |
| |
47 nssCert->version.data[0] == mozilla::pkix::der::Version::v3)) { |
| |
48 return Fail(RecoverableError, SEC_ERROR_EXTENSION_VALUE_INVALID); |
| |
49 } |
| |
50 |
| |
51 const SECItem* dummyEncodedSubjectKeyIdentifier = nullptr; |
| |
52 const SECItem* dummyEncodedAuthorityKeyIdentifier = nullptr; |
| |
53 const SECItem* dummyEncodedAuthorityInfoAccess = nullptr; |
| |
54 const SECItem* dummyEncodedSubjectAltName = nullptr; |
| |
55 |
| |
56 for (const CERTCertExtension* ext = *exts; ext; ext = *++exts) { |
| |
57 const SECItem** out = nullptr; |
| |
58 |
| |
59 if (ext->id.len == 3 && |
| |
60 ext->id.data[0] == 0x55 && ext->id.data[1] == 0x1d) { |
| |
61 // { id-ce x } |
| |
62 switch (ext->id.data[2]) { |
| |
63 case 14: out = &dummyEncodedSubjectKeyIdentifier; break; // bug 965136 |
| |
64 case 15: out = &encodedKeyUsage; break; |
| |
65 case 17: out = &dummyEncodedSubjectAltName; break; // bug 970542 |
| |
66 case 19: out = &encodedBasicConstraints; break; |
| |
67 case 30: out = &encodedNameConstraints; break; |
| |
68 case 32: out = &encodedCertificatePolicies; break; |
| |
69 case 35: out = &dummyEncodedAuthorityKeyIdentifier; break; // bug 965136 |
| |
70 case 37: out = &encodedExtendedKeyUsage; break; |
| |
71 case 54: out = &encodedInhibitAnyPolicy; break; // Bug 989051 |
| |
72 } |
| |
73 } else if (ext->id.len == 9 && |
| |
74 ext->id.data[0] == 0x2b && ext->id.data[1] == 0x06 && |
| |
75 ext->id.data[2] == 0x06 && ext->id.data[3] == 0x01 && |
| |
76 ext->id.data[4] == 0x05 && ext->id.data[5] == 0x05 && |
| |
77 ext->id.data[6] == 0x07 && ext->id.data[7] == 0x01) { |
| |
78 // { id-pe x } |
| |
79 switch (ext->id.data[8]) { |
| |
80 // We should remember the value of the encoded AIA extension here, but |
| |
81 // since our TrustDomain implementations get the OCSP URI using |
| |
82 // CERT_GetOCSPAuthorityInfoAccessLocation, we currently don't need to. |
| |
83 case 1: out = &dummyEncodedAuthorityInfoAccess; break; |
| |
84 } |
| |
85 } else if (ext->critical.data && ext->critical.len > 0) { |
| |
86 // The only valid explicit value of the critical flag is TRUE because |
| |
87 // it is defined as BOOLEAN DEFAULT FALSE, so we just assume it is true. |
| |
88 return Fail(RecoverableError, SEC_ERROR_UNKNOWN_CRITICAL_EXTENSION); |
| |
89 } |
| |
90 |
| |
91 if (out) { |
| |
92 // This is an extension we understand. Save it in results unless we've |
| |
93 // already found the extension previously. |
| |
94 if (*out) { |
| |
95 // Duplicate extension |
| |
96 return Fail(RecoverableError, SEC_ERROR_EXTENSION_VALUE_INVALID); |
| |
97 } |
| |
98 *out = &ext->value; |
| |
99 } |
| |
100 } |
| |
101 |
| |
102 return Success; |
| |
103 } |
| |
104 |
| |
105 static Result BuildForward(TrustDomain& trustDomain, |
| |
106 BackCert& subject, |
| |
107 PRTime time, |
| |
108 EndEntityOrCA endEntityOrCA, |
| |
109 KeyUsage requiredKeyUsageIfPresent, |
| |
110 SECOidTag requiredEKUIfPresent, |
| |
111 SECOidTag requiredPolicy, |
| |
112 /*optional*/ const SECItem* stapledOCSPResponse, |
| |
113 unsigned int subCACount, |
| |
114 /*out*/ ScopedCERTCertList& results); |
| |
115 |
| |
116 // The code that executes in the inner loop of BuildForward |
| |
117 static Result |
| |
118 BuildForwardInner(TrustDomain& trustDomain, |
| |
119 BackCert& subject, |
| |
120 PRTime time, |
| |
121 EndEntityOrCA endEntityOrCA, |
| |
122 SECOidTag requiredEKUIfPresent, |
| |
123 SECOidTag requiredPolicy, |
| |
124 CERTCertificate* potentialIssuerCertToDup, |
| |
125 /*optional*/ const SECItem* stapledOCSPResponse, |
| |
126 unsigned int subCACount, |
| |
127 ScopedCERTCertList& results) |
| |
128 { |
| |
129 PORT_Assert(potentialIssuerCertToDup); |
| |
130 |
| |
131 BackCert potentialIssuer(potentialIssuerCertToDup, &subject, |
| |
132 BackCert::ExcludeCN); |
| |
133 Result rv = potentialIssuer.Init(); |
| |
134 if (rv != Success) { |
| |
135 return rv; |
| |
136 } |
| |
137 |
| |
138 // RFC5280 4.2.1.1. Authority Key Identifier |
| |
139 // RFC5280 4.2.1.2. Subject Key Identifier |
| |
140 |
| |
141 // Loop prevention, done as recommended by RFC4158 Section 5.2 |
| |
142 // TODO: this doesn't account for subjectAltNames! |
| |
143 // TODO(perf): This probably can and should be optimized in some way. |
| |
144 bool loopDetected = false; |
| |
145 for (BackCert* prev = potentialIssuer.childCert; |
| |
146 !loopDetected && prev != nullptr; prev = prev->childCert) { |
| |
147 if (SECITEM_ItemsAreEqual(&potentialIssuer.GetNSSCert()->derPublicKey, |
| |
148 &prev->GetNSSCert()->derPublicKey) && |
| |
149 SECITEM_ItemsAreEqual(&potentialIssuer.GetNSSCert()->derSubject, |
| |
150 &prev->GetNSSCert()->derSubject)) { |
| |
151 return Fail(RecoverableError, SEC_ERROR_UNKNOWN_ISSUER); // XXX: error code |
| |
152 } |
| |
153 } |
| |
154 |
| |
155 rv = CheckNameConstraints(potentialIssuer); |
| |
156 if (rv != Success) { |
| |
157 return rv; |
| |
158 } |
| |
159 |
| |
160 unsigned int newSubCACount = subCACount; |
| |
161 if (endEntityOrCA == MustBeCA) { |
| |
162 newSubCACount = subCACount + 1; |
| |
163 } else { |
| |
164 PR_ASSERT(newSubCACount == 0); |
| |
165 } |
| |
166 rv = BuildForward(trustDomain, potentialIssuer, time, MustBeCA, |
| |
167 KeyUsage::keyCertSign, requiredEKUIfPresent, |
| |
168 requiredPolicy, nullptr, newSubCACount, results); |
| |
169 if (rv != Success) { |
| |
170 return rv; |
| |
171 } |
| |
172 |
| |
173 if (trustDomain.VerifySignedData(&subject.GetNSSCert()->signatureWrap, |
| |
174 potentialIssuer.GetNSSCert()) != SECSuccess) { |
| |
175 return MapSECStatus(SECFailure); |
| |
176 } |
| |
177 |
| |
178 return Success; |
| |
179 } |
| |
180 |
| |
181 // Recursively build the path from the given subject certificate to the root. |
| |
182 // |
| |
183 // Be very careful about changing the order of checks. The order is significant |
| |
184 // because it affects which error we return when a certificate or certificate |
| |
185 // chain has multiple problems. See the error ranking documentation in |
| |
186 // pkix/pkix.h. |
| |
187 static Result |
| |
188 BuildForward(TrustDomain& trustDomain, |
| |
189 BackCert& subject, |
| |
190 PRTime time, |
| |
191 EndEntityOrCA endEntityOrCA, |
| |
192 KeyUsage requiredKeyUsageIfPresent, |
| |
193 SECOidTag requiredEKUIfPresent, |
| |
194 SECOidTag requiredPolicy, |
| |
195 /*optional*/ const SECItem* stapledOCSPResponse, |
| |
196 unsigned int subCACount, |
| |
197 /*out*/ ScopedCERTCertList& results) |
| |
198 { |
| |
199 // Avoid stack overflows and poor performance by limiting cert length. |
| |
200 // XXX: 6 is not enough for chains.sh anypolicywithlevel.cfg tests |
| |
201 static const size_t MAX_DEPTH = 8; |
| |
202 if (subCACount >= MAX_DEPTH - 1) { |
| |
203 return Fail(RecoverableError, SEC_ERROR_UNKNOWN_ISSUER); |
| |
204 } |
| |
205 |
| |
206 Result rv; |
| |
207 |
| |
208 TrustDomain::TrustLevel trustLevel; |
| |
209 // If this is an end-entity and not a trust anchor, we defer reporting |
| |
210 // any error found here until after attempting to find a valid chain. |
| |
211 // See the explanation of error prioritization in pkix.h. |
| |
212 rv = CheckIssuerIndependentProperties(trustDomain, subject, time, |
| |
213 endEntityOrCA, |
| |
214 requiredKeyUsageIfPresent, |
| |
215 requiredEKUIfPresent, requiredPolicy, |
| |
216 subCACount, &trustLevel); |
| |
217 PRErrorCode deferredEndEntityError = 0; |
| |
218 if (rv != Success) { |
| |
219 if (endEntityOrCA == MustBeEndEntity && |
| |
220 trustLevel != TrustDomain::TrustAnchor) { |
| |
221 deferredEndEntityError = PR_GetError(); |
| |
222 } else { |
| |
223 return rv; |
| |
224 } |
| |
225 } |
| |
226 |
| |
227 if (trustLevel == TrustDomain::TrustAnchor) { |
| |
228 ScopedCERTCertList certChain(CERT_NewCertList()); |
| |
229 if (!certChain) { |
| |
230 PR_SetError(SEC_ERROR_NO_MEMORY, 0); |
| |
231 return MapSECStatus(SECFailure); |
| |
232 } |
| |
233 |
| |
234 rv = subject.PrependNSSCertToList(certChain.get()); |
| |
235 if (rv != Success) { |
| |
236 return rv; |
| |
237 } |
| |
238 BackCert* child = subject.childCert; |
| |
239 while (child) { |
| |
240 rv = child->PrependNSSCertToList(certChain.get()); |
| |
241 if (rv != Success) { |
| |
242 return rv; |
| |
243 } |
| |
244 child = child->childCert; |
| |
245 } |
| |
246 |
| |
247 SECStatus srv = trustDomain.IsChainValid(certChain.get()); |
| |
248 if (srv != SECSuccess) { |
| |
249 return MapSECStatus(srv); |
| |
250 } |
| |
251 |
| |
252 // End of the recursion. Create the result list and add the trust anchor to |
| |
253 // it. |
| |
254 results = CERT_NewCertList(); |
| |
255 if (!results) { |
| |
256 return FatalError; |
| |
257 } |
| |
258 rv = subject.PrependNSSCertToList(results.get()); |
| |
259 return rv; |
| |
260 } |
| |
261 |
| |
262 // Find a trusted issuer. |
| |
263 // TODO(bug 965136): Add SKI/AKI matching optimizations |
| |
264 ScopedCERTCertList candidates; |
| |
265 if (trustDomain.FindPotentialIssuers(&subject.GetNSSCert()->derIssuer, time, |
| |
266 candidates) != SECSuccess) { |
| |
267 return MapSECStatus(SECFailure); |
| |
268 } |
| |
269 if (!candidates) { |
| |
270 return Fail(RecoverableError, SEC_ERROR_UNKNOWN_ISSUER); |
| |
271 } |
| |
272 |
| |
273 PRErrorCode errorToReturn = 0; |
| |
274 |
| |
275 for (CERTCertListNode* n = CERT_LIST_HEAD(candidates); |
| |
276 !CERT_LIST_END(n, candidates); n = CERT_LIST_NEXT(n)) { |
| |
277 rv = BuildForwardInner(trustDomain, subject, time, endEntityOrCA, |
| |
278 requiredEKUIfPresent, requiredPolicy, |
| |
279 n->cert, stapledOCSPResponse, subCACount, |
| |
280 results); |
| |
281 if (rv == Success) { |
| |
282 // If we found a valid chain but deferred reporting an error with the |
| |
283 // end-entity certificate, report it now. |
| |
284 if (deferredEndEntityError != 0) { |
| |
285 PR_SetError(deferredEndEntityError, 0); |
| |
286 return FatalError; |
| |
287 } |
| |
288 |
| |
289 SECStatus srv = trustDomain.CheckRevocation(endEntityOrCA, |
| |
290 subject.GetNSSCert(), |
| |
291 n->cert, time, |
| |
292 stapledOCSPResponse); |
| |
293 if (srv != SECSuccess) { |
| |
294 return MapSECStatus(SECFailure); |
| |
295 } |
| |
296 |
| |
297 // We found a trusted issuer. At this point, we know the cert is valid |
| |
298 return subject.PrependNSSCertToList(results.get()); |
| |
299 } |
| |
300 if (rv != RecoverableError) { |
| |
301 return rv; |
| |
302 } |
| |
303 |
| |
304 PRErrorCode currentError = PR_GetError(); |
| |
305 switch (currentError) { |
| |
306 case 0: |
| |
307 PR_NOT_REACHED("Error code not set!"); |
| |
308 PR_SetError(PR_INVALID_STATE_ERROR, 0); |
| |
309 return FatalError; |
| |
310 case SEC_ERROR_UNTRUSTED_CERT: |
| |
311 currentError = SEC_ERROR_UNTRUSTED_ISSUER; |
| |
312 break; |
| |
313 default: |
| |
314 break; |
| |
315 } |
| |
316 if (errorToReturn == 0) { |
| |
317 errorToReturn = currentError; |
| |
318 } else if (errorToReturn != currentError) { |
| |
319 errorToReturn = SEC_ERROR_UNKNOWN_ISSUER; |
| |
320 } |
| |
321 } |
| |
322 |
| |
323 if (errorToReturn == 0) { |
| |
324 errorToReturn = SEC_ERROR_UNKNOWN_ISSUER; |
| |
325 } |
| |
326 |
| |
327 return Fail(RecoverableError, errorToReturn); |
| |
328 } |
| |
329 |
| |
330 SECStatus |
| |
331 BuildCertChain(TrustDomain& trustDomain, |
| |
332 CERTCertificate* certToDup, |
| |
333 PRTime time, |
| |
334 EndEntityOrCA endEntityOrCA, |
| |
335 /*optional*/ KeyUsage requiredKeyUsageIfPresent, |
| |
336 /*optional*/ SECOidTag requiredEKUIfPresent, |
| |
337 /*optional*/ SECOidTag requiredPolicy, |
| |
338 /*optional*/ const SECItem* stapledOCSPResponse, |
| |
339 /*out*/ ScopedCERTCertList& results) |
| |
340 { |
| |
341 PORT_Assert(certToDup); |
| |
342 |
| |
343 if (!certToDup) { |
| |
344 PR_SetError(SEC_ERROR_INVALID_ARGS, 0); |
| |
345 return SECFailure; |
| |
346 } |
| |
347 |
| |
348 // The only non-const operation on the cert we are allowed to do is |
| |
349 // CERT_DupCertificate. |
| |
350 |
| |
351 // XXX: Support the legacy use of the subject CN field for indicating the |
| |
352 // domain name the certificate is valid for. |
| |
353 BackCert::ConstrainedNameOptions cnOptions |
| |
354 = endEntityOrCA == MustBeEndEntity && |
| |
355 requiredEKUIfPresent == SEC_OID_EXT_KEY_USAGE_SERVER_AUTH |
| |
356 ? BackCert::IncludeCN |
| |
357 : BackCert::ExcludeCN; |
| |
358 |
| |
359 BackCert cert(certToDup, nullptr, cnOptions); |
| |
360 Result rv = cert.Init(); |
| |
361 if (rv != Success) { |
| |
362 return SECFailure; |
| |
363 } |
| |
364 |
| |
365 rv = BuildForward(trustDomain, cert, time, endEntityOrCA, |
| |
366 requiredKeyUsageIfPresent, requiredEKUIfPresent, |
| |
367 requiredPolicy, stapledOCSPResponse, 0, results); |
| |
368 if (rv != Success) { |
| |
369 results = nullptr; |
| |
370 return SECFailure; |
| |
371 } |
| |
372 |
| |
373 return SECSuccess; |
| |
374 } |
| |
375 |
| |
376 PLArenaPool* |
| |
377 BackCert::GetArena() |
| |
378 { |
| |
379 if (!arena) { |
| |
380 arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); |
| |
381 } |
| |
382 return arena.get(); |
| |
383 } |
| |
384 |
| |
385 Result |
| |
386 BackCert::PrependNSSCertToList(CERTCertList* results) |
| |
387 { |
| |
388 PORT_Assert(results); |
| |
389 |
| |
390 CERTCertificate* dup = CERT_DupCertificate(nssCert); |
| |
391 if (CERT_AddCertToListHead(results, dup) != SECSuccess) { // takes ownership |
| |
392 CERT_DestroyCertificate(dup); |
| |
393 return FatalError; |
| |
394 } |
| |
395 |
| |
396 return Success; |
| |
397 } |
| |
398 |
| |
399 } } // namespace mozilla::pkix |