security/pkix/lib/pkixbuild.cpp

changeset 0
6474c204b198
equal deleted inserted replaced
-1:000000000000 0:4bec4e0461b6
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

mercurial