security/pkix/lib/pkixbuild.cpp

changeset 0
6474c204b198
     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

mercurial