1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/security/pkix/lib/pkixbuild.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,399 @@ 1.4 +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ 1.6 +/* Copyright 2013 Mozilla Foundation 1.7 + * 1.8 + * Licensed under the Apache License, Version 2.0 (the "License"); 1.9 + * you may not use this file except in compliance with the License. 1.10 + * You may obtain a copy of the License at 1.11 + * 1.12 + * http://www.apache.org/licenses/LICENSE-2.0 1.13 + * 1.14 + * Unless required by applicable law or agreed to in writing, software 1.15 + * distributed under the License is distributed on an "AS IS" BASIS, 1.16 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1.17 + * See the License for the specific language governing permissions and 1.18 + * limitations under the License. 1.19 + */ 1.20 + 1.21 +#include "pkix/pkix.h" 1.22 + 1.23 +#include <limits> 1.24 + 1.25 +#include "pkixcheck.h" 1.26 +#include "pkixder.h" 1.27 + 1.28 +namespace mozilla { namespace pkix { 1.29 + 1.30 +// We assume ext has been zero-initialized by its constructor and otherwise 1.31 +// not modified. 1.32 +// 1.33 +// TODO(perf): This sorting of extensions should be be moved into the 1.34 +// certificate decoder so that the results are cached with the certificate, so 1.35 +// that the decoding doesn't have to happen more than once per cert. 1.36 +Result 1.37 +BackCert::Init() 1.38 +{ 1.39 + const CERTCertExtension* const* exts = nssCert->extensions; 1.40 + if (!exts) { 1.41 + return Success; 1.42 + } 1.43 + // We only decode v3 extensions for v3 certificates for two reasons. 1.44 + // 1. They make no sense in non-v3 certs 1.45 + // 2. An invalid cert can embed a basic constraints extension and the 1.46 + // check basic constrains will asume that this is valid. Making it 1.47 + // posible to create chains with v1 and v2 intermediates with is 1.48 + // not desirable. 1.49 + if (! (nssCert->version.len == 1 && 1.50 + nssCert->version.data[0] == mozilla::pkix::der::Version::v3)) { 1.51 + return Fail(RecoverableError, SEC_ERROR_EXTENSION_VALUE_INVALID); 1.52 + } 1.53 + 1.54 + const SECItem* dummyEncodedSubjectKeyIdentifier = nullptr; 1.55 + const SECItem* dummyEncodedAuthorityKeyIdentifier = nullptr; 1.56 + const SECItem* dummyEncodedAuthorityInfoAccess = nullptr; 1.57 + const SECItem* dummyEncodedSubjectAltName = nullptr; 1.58 + 1.59 + for (const CERTCertExtension* ext = *exts; ext; ext = *++exts) { 1.60 + const SECItem** out = nullptr; 1.61 + 1.62 + if (ext->id.len == 3 && 1.63 + ext->id.data[0] == 0x55 && ext->id.data[1] == 0x1d) { 1.64 + // { id-ce x } 1.65 + switch (ext->id.data[2]) { 1.66 + case 14: out = &dummyEncodedSubjectKeyIdentifier; break; // bug 965136 1.67 + case 15: out = &encodedKeyUsage; break; 1.68 + case 17: out = &dummyEncodedSubjectAltName; break; // bug 970542 1.69 + case 19: out = &encodedBasicConstraints; break; 1.70 + case 30: out = &encodedNameConstraints; break; 1.71 + case 32: out = &encodedCertificatePolicies; break; 1.72 + case 35: out = &dummyEncodedAuthorityKeyIdentifier; break; // bug 965136 1.73 + case 37: out = &encodedExtendedKeyUsage; break; 1.74 + case 54: out = &encodedInhibitAnyPolicy; break; // Bug 989051 1.75 + } 1.76 + } else if (ext->id.len == 9 && 1.77 + ext->id.data[0] == 0x2b && ext->id.data[1] == 0x06 && 1.78 + ext->id.data[2] == 0x06 && ext->id.data[3] == 0x01 && 1.79 + ext->id.data[4] == 0x05 && ext->id.data[5] == 0x05 && 1.80 + ext->id.data[6] == 0x07 && ext->id.data[7] == 0x01) { 1.81 + // { id-pe x } 1.82 + switch (ext->id.data[8]) { 1.83 + // We should remember the value of the encoded AIA extension here, but 1.84 + // since our TrustDomain implementations get the OCSP URI using 1.85 + // CERT_GetOCSPAuthorityInfoAccessLocation, we currently don't need to. 1.86 + case 1: out = &dummyEncodedAuthorityInfoAccess; break; 1.87 + } 1.88 + } else if (ext->critical.data && ext->critical.len > 0) { 1.89 + // The only valid explicit value of the critical flag is TRUE because 1.90 + // it is defined as BOOLEAN DEFAULT FALSE, so we just assume it is true. 1.91 + return Fail(RecoverableError, SEC_ERROR_UNKNOWN_CRITICAL_EXTENSION); 1.92 + } 1.93 + 1.94 + if (out) { 1.95 + // This is an extension we understand. Save it in results unless we've 1.96 + // already found the extension previously. 1.97 + if (*out) { 1.98 + // Duplicate extension 1.99 + return Fail(RecoverableError, SEC_ERROR_EXTENSION_VALUE_INVALID); 1.100 + } 1.101 + *out = &ext->value; 1.102 + } 1.103 + } 1.104 + 1.105 + return Success; 1.106 +} 1.107 + 1.108 +static Result BuildForward(TrustDomain& trustDomain, 1.109 + BackCert& subject, 1.110 + PRTime time, 1.111 + EndEntityOrCA endEntityOrCA, 1.112 + KeyUsage requiredKeyUsageIfPresent, 1.113 + SECOidTag requiredEKUIfPresent, 1.114 + SECOidTag requiredPolicy, 1.115 + /*optional*/ const SECItem* stapledOCSPResponse, 1.116 + unsigned int subCACount, 1.117 + /*out*/ ScopedCERTCertList& results); 1.118 + 1.119 +// The code that executes in the inner loop of BuildForward 1.120 +static Result 1.121 +BuildForwardInner(TrustDomain& trustDomain, 1.122 + BackCert& subject, 1.123 + PRTime time, 1.124 + EndEntityOrCA endEntityOrCA, 1.125 + SECOidTag requiredEKUIfPresent, 1.126 + SECOidTag requiredPolicy, 1.127 + CERTCertificate* potentialIssuerCertToDup, 1.128 + /*optional*/ const SECItem* stapledOCSPResponse, 1.129 + unsigned int subCACount, 1.130 + ScopedCERTCertList& results) 1.131 +{ 1.132 + PORT_Assert(potentialIssuerCertToDup); 1.133 + 1.134 + BackCert potentialIssuer(potentialIssuerCertToDup, &subject, 1.135 + BackCert::ExcludeCN); 1.136 + Result rv = potentialIssuer.Init(); 1.137 + if (rv != Success) { 1.138 + return rv; 1.139 + } 1.140 + 1.141 + // RFC5280 4.2.1.1. Authority Key Identifier 1.142 + // RFC5280 4.2.1.2. Subject Key Identifier 1.143 + 1.144 + // Loop prevention, done as recommended by RFC4158 Section 5.2 1.145 + // TODO: this doesn't account for subjectAltNames! 1.146 + // TODO(perf): This probably can and should be optimized in some way. 1.147 + bool loopDetected = false; 1.148 + for (BackCert* prev = potentialIssuer.childCert; 1.149 + !loopDetected && prev != nullptr; prev = prev->childCert) { 1.150 + if (SECITEM_ItemsAreEqual(&potentialIssuer.GetNSSCert()->derPublicKey, 1.151 + &prev->GetNSSCert()->derPublicKey) && 1.152 + SECITEM_ItemsAreEqual(&potentialIssuer.GetNSSCert()->derSubject, 1.153 + &prev->GetNSSCert()->derSubject)) { 1.154 + return Fail(RecoverableError, SEC_ERROR_UNKNOWN_ISSUER); // XXX: error code 1.155 + } 1.156 + } 1.157 + 1.158 + rv = CheckNameConstraints(potentialIssuer); 1.159 + if (rv != Success) { 1.160 + return rv; 1.161 + } 1.162 + 1.163 + unsigned int newSubCACount = subCACount; 1.164 + if (endEntityOrCA == MustBeCA) { 1.165 + newSubCACount = subCACount + 1; 1.166 + } else { 1.167 + PR_ASSERT(newSubCACount == 0); 1.168 + } 1.169 + rv = BuildForward(trustDomain, potentialIssuer, time, MustBeCA, 1.170 + KeyUsage::keyCertSign, requiredEKUIfPresent, 1.171 + requiredPolicy, nullptr, newSubCACount, results); 1.172 + if (rv != Success) { 1.173 + return rv; 1.174 + } 1.175 + 1.176 + if (trustDomain.VerifySignedData(&subject.GetNSSCert()->signatureWrap, 1.177 + potentialIssuer.GetNSSCert()) != SECSuccess) { 1.178 + return MapSECStatus(SECFailure); 1.179 + } 1.180 + 1.181 + return Success; 1.182 +} 1.183 + 1.184 +// Recursively build the path from the given subject certificate to the root. 1.185 +// 1.186 +// Be very careful about changing the order of checks. The order is significant 1.187 +// because it affects which error we return when a certificate or certificate 1.188 +// chain has multiple problems. See the error ranking documentation in 1.189 +// pkix/pkix.h. 1.190 +static Result 1.191 +BuildForward(TrustDomain& trustDomain, 1.192 + BackCert& subject, 1.193 + PRTime time, 1.194 + EndEntityOrCA endEntityOrCA, 1.195 + KeyUsage requiredKeyUsageIfPresent, 1.196 + SECOidTag requiredEKUIfPresent, 1.197 + SECOidTag requiredPolicy, 1.198 + /*optional*/ const SECItem* stapledOCSPResponse, 1.199 + unsigned int subCACount, 1.200 + /*out*/ ScopedCERTCertList& results) 1.201 +{ 1.202 + // Avoid stack overflows and poor performance by limiting cert length. 1.203 + // XXX: 6 is not enough for chains.sh anypolicywithlevel.cfg tests 1.204 + static const size_t MAX_DEPTH = 8; 1.205 + if (subCACount >= MAX_DEPTH - 1) { 1.206 + return Fail(RecoverableError, SEC_ERROR_UNKNOWN_ISSUER); 1.207 + } 1.208 + 1.209 + Result rv; 1.210 + 1.211 + TrustDomain::TrustLevel trustLevel; 1.212 + // If this is an end-entity and not a trust anchor, we defer reporting 1.213 + // any error found here until after attempting to find a valid chain. 1.214 + // See the explanation of error prioritization in pkix.h. 1.215 + rv = CheckIssuerIndependentProperties(trustDomain, subject, time, 1.216 + endEntityOrCA, 1.217 + requiredKeyUsageIfPresent, 1.218 + requiredEKUIfPresent, requiredPolicy, 1.219 + subCACount, &trustLevel); 1.220 + PRErrorCode deferredEndEntityError = 0; 1.221 + if (rv != Success) { 1.222 + if (endEntityOrCA == MustBeEndEntity && 1.223 + trustLevel != TrustDomain::TrustAnchor) { 1.224 + deferredEndEntityError = PR_GetError(); 1.225 + } else { 1.226 + return rv; 1.227 + } 1.228 + } 1.229 + 1.230 + if (trustLevel == TrustDomain::TrustAnchor) { 1.231 + ScopedCERTCertList certChain(CERT_NewCertList()); 1.232 + if (!certChain) { 1.233 + PR_SetError(SEC_ERROR_NO_MEMORY, 0); 1.234 + return MapSECStatus(SECFailure); 1.235 + } 1.236 + 1.237 + rv = subject.PrependNSSCertToList(certChain.get()); 1.238 + if (rv != Success) { 1.239 + return rv; 1.240 + } 1.241 + BackCert* child = subject.childCert; 1.242 + while (child) { 1.243 + rv = child->PrependNSSCertToList(certChain.get()); 1.244 + if (rv != Success) { 1.245 + return rv; 1.246 + } 1.247 + child = child->childCert; 1.248 + } 1.249 + 1.250 + SECStatus srv = trustDomain.IsChainValid(certChain.get()); 1.251 + if (srv != SECSuccess) { 1.252 + return MapSECStatus(srv); 1.253 + } 1.254 + 1.255 + // End of the recursion. Create the result list and add the trust anchor to 1.256 + // it. 1.257 + results = CERT_NewCertList(); 1.258 + if (!results) { 1.259 + return FatalError; 1.260 + } 1.261 + rv = subject.PrependNSSCertToList(results.get()); 1.262 + return rv; 1.263 + } 1.264 + 1.265 + // Find a trusted issuer. 1.266 + // TODO(bug 965136): Add SKI/AKI matching optimizations 1.267 + ScopedCERTCertList candidates; 1.268 + if (trustDomain.FindPotentialIssuers(&subject.GetNSSCert()->derIssuer, time, 1.269 + candidates) != SECSuccess) { 1.270 + return MapSECStatus(SECFailure); 1.271 + } 1.272 + if (!candidates) { 1.273 + return Fail(RecoverableError, SEC_ERROR_UNKNOWN_ISSUER); 1.274 + } 1.275 + 1.276 + PRErrorCode errorToReturn = 0; 1.277 + 1.278 + for (CERTCertListNode* n = CERT_LIST_HEAD(candidates); 1.279 + !CERT_LIST_END(n, candidates); n = CERT_LIST_NEXT(n)) { 1.280 + rv = BuildForwardInner(trustDomain, subject, time, endEntityOrCA, 1.281 + requiredEKUIfPresent, requiredPolicy, 1.282 + n->cert, stapledOCSPResponse, subCACount, 1.283 + results); 1.284 + if (rv == Success) { 1.285 + // If we found a valid chain but deferred reporting an error with the 1.286 + // end-entity certificate, report it now. 1.287 + if (deferredEndEntityError != 0) { 1.288 + PR_SetError(deferredEndEntityError, 0); 1.289 + return FatalError; 1.290 + } 1.291 + 1.292 + SECStatus srv = trustDomain.CheckRevocation(endEntityOrCA, 1.293 + subject.GetNSSCert(), 1.294 + n->cert, time, 1.295 + stapledOCSPResponse); 1.296 + if (srv != SECSuccess) { 1.297 + return MapSECStatus(SECFailure); 1.298 + } 1.299 + 1.300 + // We found a trusted issuer. At this point, we know the cert is valid 1.301 + return subject.PrependNSSCertToList(results.get()); 1.302 + } 1.303 + if (rv != RecoverableError) { 1.304 + return rv; 1.305 + } 1.306 + 1.307 + PRErrorCode currentError = PR_GetError(); 1.308 + switch (currentError) { 1.309 + case 0: 1.310 + PR_NOT_REACHED("Error code not set!"); 1.311 + PR_SetError(PR_INVALID_STATE_ERROR, 0); 1.312 + return FatalError; 1.313 + case SEC_ERROR_UNTRUSTED_CERT: 1.314 + currentError = SEC_ERROR_UNTRUSTED_ISSUER; 1.315 + break; 1.316 + default: 1.317 + break; 1.318 + } 1.319 + if (errorToReturn == 0) { 1.320 + errorToReturn = currentError; 1.321 + } else if (errorToReturn != currentError) { 1.322 + errorToReturn = SEC_ERROR_UNKNOWN_ISSUER; 1.323 + } 1.324 + } 1.325 + 1.326 + if (errorToReturn == 0) { 1.327 + errorToReturn = SEC_ERROR_UNKNOWN_ISSUER; 1.328 + } 1.329 + 1.330 + return Fail(RecoverableError, errorToReturn); 1.331 +} 1.332 + 1.333 +SECStatus 1.334 +BuildCertChain(TrustDomain& trustDomain, 1.335 + CERTCertificate* certToDup, 1.336 + PRTime time, 1.337 + EndEntityOrCA endEntityOrCA, 1.338 + /*optional*/ KeyUsage requiredKeyUsageIfPresent, 1.339 + /*optional*/ SECOidTag requiredEKUIfPresent, 1.340 + /*optional*/ SECOidTag requiredPolicy, 1.341 + /*optional*/ const SECItem* stapledOCSPResponse, 1.342 + /*out*/ ScopedCERTCertList& results) 1.343 +{ 1.344 + PORT_Assert(certToDup); 1.345 + 1.346 + if (!certToDup) { 1.347 + PR_SetError(SEC_ERROR_INVALID_ARGS, 0); 1.348 + return SECFailure; 1.349 + } 1.350 + 1.351 + // The only non-const operation on the cert we are allowed to do is 1.352 + // CERT_DupCertificate. 1.353 + 1.354 + // XXX: Support the legacy use of the subject CN field for indicating the 1.355 + // domain name the certificate is valid for. 1.356 + BackCert::ConstrainedNameOptions cnOptions 1.357 + = endEntityOrCA == MustBeEndEntity && 1.358 + requiredEKUIfPresent == SEC_OID_EXT_KEY_USAGE_SERVER_AUTH 1.359 + ? BackCert::IncludeCN 1.360 + : BackCert::ExcludeCN; 1.361 + 1.362 + BackCert cert(certToDup, nullptr, cnOptions); 1.363 + Result rv = cert.Init(); 1.364 + if (rv != Success) { 1.365 + return SECFailure; 1.366 + } 1.367 + 1.368 + rv = BuildForward(trustDomain, cert, time, endEntityOrCA, 1.369 + requiredKeyUsageIfPresent, requiredEKUIfPresent, 1.370 + requiredPolicy, stapledOCSPResponse, 0, results); 1.371 + if (rv != Success) { 1.372 + results = nullptr; 1.373 + return SECFailure; 1.374 + } 1.375 + 1.376 + return SECSuccess; 1.377 +} 1.378 + 1.379 +PLArenaPool* 1.380 +BackCert::GetArena() 1.381 +{ 1.382 + if (!arena) { 1.383 + arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); 1.384 + } 1.385 + return arena.get(); 1.386 +} 1.387 + 1.388 +Result 1.389 +BackCert::PrependNSSCertToList(CERTCertList* results) 1.390 +{ 1.391 + PORT_Assert(results); 1.392 + 1.393 + CERTCertificate* dup = CERT_DupCertificate(nssCert); 1.394 + if (CERT_AddCertToListHead(results, dup) != SECSuccess) { // takes ownership 1.395 + CERT_DestroyCertificate(dup); 1.396 + return FatalError; 1.397 + } 1.398 + 1.399 + return Success; 1.400 +} 1.401 + 1.402 +} } // namespace mozilla::pkix