security/pkix/lib/pkixbuild.cpp

Sat, 03 Jan 2015 20:18:00 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Sat, 03 Jan 2015 20:18:00 +0100
branch
TOR_BUG_3246
changeset 7
129ffea94266
permissions
-rw-r--r--

Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.

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

mercurial