|
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 |