security/pkix/lib/pkixbuild.cpp

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

     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  */
    18 #include "pkix/pkix.h"
    20 #include <limits>
    22 #include "pkixcheck.h"
    23 #include "pkixder.h"
    25 namespace mozilla { namespace pkix {
    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   }
    51   const SECItem* dummyEncodedSubjectKeyIdentifier = nullptr;
    52   const SECItem* dummyEncodedAuthorityKeyIdentifier = nullptr;
    53   const SECItem* dummyEncodedAuthorityInfoAccess = nullptr;
    54   const SECItem* dummyEncodedSubjectAltName = nullptr;
    56   for (const CERTCertExtension* ext = *exts; ext; ext = *++exts) {
    57     const SECItem** out = nullptr;
    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     }
    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   }
   102   return Success;
   103 }
   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);
   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);
   131   BackCert potentialIssuer(potentialIssuerCertToDup, &subject,
   132                            BackCert::ExcludeCN);
   133   Result rv = potentialIssuer.Init();
   134   if (rv != Success) {
   135     return rv;
   136   }
   138   // RFC5280 4.2.1.1. Authority Key Identifier
   139   // RFC5280 4.2.1.2. Subject Key Identifier
   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   }
   155   rv = CheckNameConstraints(potentialIssuer);
   156   if (rv != Success) {
   157     return rv;
   158   }
   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   }
   173   if (trustDomain.VerifySignedData(&subject.GetNSSCert()->signatureWrap,
   174                                    potentialIssuer.GetNSSCert()) != SECSuccess) {
   175     return MapSECStatus(SECFailure);
   176   }
   178   return Success;
   179 }
   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   }
   206   Result rv;
   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   }
   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     }
   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     }
   247     SECStatus srv = trustDomain.IsChainValid(certChain.get());
   248     if (srv != SECSuccess) {
   249       return MapSECStatus(srv);
   250     }
   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   }
   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   }
   273   PRErrorCode errorToReturn = 0;
   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       }
   289       SECStatus srv = trustDomain.CheckRevocation(endEntityOrCA,
   290                                                   subject.GetNSSCert(),
   291                                                   n->cert, time,
   292                                                   stapledOCSPResponse);
   293       if (srv != SECSuccess) {
   294         return MapSECStatus(SECFailure);
   295       }
   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     }
   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   }
   323   if (errorToReturn == 0) {
   324     errorToReturn = SEC_ERROR_UNKNOWN_ISSUER;
   325   }
   327   return Fail(RecoverableError, errorToReturn);
   328 }
   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);
   343   if (!certToDup) {
   344     PR_SetError(SEC_ERROR_INVALID_ARGS, 0);
   345     return SECFailure;
   346   }
   348   // The only non-const operation on the cert we are allowed to do is
   349   // CERT_DupCertificate.
   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;
   359   BackCert cert(certToDup, nullptr, cnOptions);
   360   Result rv = cert.Init();
   361   if (rv != Success) {
   362     return SECFailure;
   363   }
   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   }
   373   return SECSuccess;
   374 }
   376 PLArenaPool*
   377 BackCert::GetArena()
   378 {
   379   if (!arena) {
   380     arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
   381   }
   382   return arena.get();
   383 }
   385 Result
   386 BackCert::PrependNSSCertToList(CERTCertList* results)
   387 {
   388   PORT_Assert(results);
   390   CERTCertificate* dup = CERT_DupCertificate(nssCert);
   391   if (CERT_AddCertToListHead(results, dup) != SECSuccess) { // takes ownership
   392     CERT_DestroyCertificate(dup);
   393     return FatalError;
   394   }
   396   return Success;
   397 }
   399 } } // namespace mozilla::pkix

mercurial