michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim: set ts=8 sts=2 et sw=2 tw=80: */ michael@0: /* Copyright 2013 Mozilla Foundation michael@0: * michael@0: * Licensed under the Apache License, Version 2.0 (the "License"); michael@0: * you may not use this file except in compliance with the License. michael@0: * You may obtain a copy of the License at michael@0: * michael@0: * http://www.apache.org/licenses/LICENSE-2.0 michael@0: * michael@0: * Unless required by applicable law or agreed to in writing, software michael@0: * distributed under the License is distributed on an "AS IS" BASIS, michael@0: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. michael@0: * See the License for the specific language governing permissions and michael@0: * limitations under the License. michael@0: */ michael@0: michael@0: #include "pkix/pkix.h" michael@0: michael@0: #include michael@0: michael@0: #include "pkixcheck.h" michael@0: #include "pkixder.h" michael@0: michael@0: namespace mozilla { namespace pkix { michael@0: michael@0: // We assume ext has been zero-initialized by its constructor and otherwise michael@0: // not modified. michael@0: // michael@0: // TODO(perf): This sorting of extensions should be be moved into the michael@0: // certificate decoder so that the results are cached with the certificate, so michael@0: // that the decoding doesn't have to happen more than once per cert. michael@0: Result michael@0: BackCert::Init() michael@0: { michael@0: const CERTCertExtension* const* exts = nssCert->extensions; michael@0: if (!exts) { michael@0: return Success; michael@0: } michael@0: // We only decode v3 extensions for v3 certificates for two reasons. michael@0: // 1. They make no sense in non-v3 certs michael@0: // 2. An invalid cert can embed a basic constraints extension and the michael@0: // check basic constrains will asume that this is valid. Making it michael@0: // posible to create chains with v1 and v2 intermediates with is michael@0: // not desirable. michael@0: if (! (nssCert->version.len == 1 && michael@0: nssCert->version.data[0] == mozilla::pkix::der::Version::v3)) { michael@0: return Fail(RecoverableError, SEC_ERROR_EXTENSION_VALUE_INVALID); michael@0: } michael@0: michael@0: const SECItem* dummyEncodedSubjectKeyIdentifier = nullptr; michael@0: const SECItem* dummyEncodedAuthorityKeyIdentifier = nullptr; michael@0: const SECItem* dummyEncodedAuthorityInfoAccess = nullptr; michael@0: const SECItem* dummyEncodedSubjectAltName = nullptr; michael@0: michael@0: for (const CERTCertExtension* ext = *exts; ext; ext = *++exts) { michael@0: const SECItem** out = nullptr; michael@0: michael@0: if (ext->id.len == 3 && michael@0: ext->id.data[0] == 0x55 && ext->id.data[1] == 0x1d) { michael@0: // { id-ce x } michael@0: switch (ext->id.data[2]) { michael@0: case 14: out = &dummyEncodedSubjectKeyIdentifier; break; // bug 965136 michael@0: case 15: out = &encodedKeyUsage; break; michael@0: case 17: out = &dummyEncodedSubjectAltName; break; // bug 970542 michael@0: case 19: out = &encodedBasicConstraints; break; michael@0: case 30: out = &encodedNameConstraints; break; michael@0: case 32: out = &encodedCertificatePolicies; break; michael@0: case 35: out = &dummyEncodedAuthorityKeyIdentifier; break; // bug 965136 michael@0: case 37: out = &encodedExtendedKeyUsage; break; michael@0: case 54: out = &encodedInhibitAnyPolicy; break; // Bug 989051 michael@0: } michael@0: } else if (ext->id.len == 9 && michael@0: ext->id.data[0] == 0x2b && ext->id.data[1] == 0x06 && michael@0: ext->id.data[2] == 0x06 && ext->id.data[3] == 0x01 && michael@0: ext->id.data[4] == 0x05 && ext->id.data[5] == 0x05 && michael@0: ext->id.data[6] == 0x07 && ext->id.data[7] == 0x01) { michael@0: // { id-pe x } michael@0: switch (ext->id.data[8]) { michael@0: // We should remember the value of the encoded AIA extension here, but michael@0: // since our TrustDomain implementations get the OCSP URI using michael@0: // CERT_GetOCSPAuthorityInfoAccessLocation, we currently don't need to. michael@0: case 1: out = &dummyEncodedAuthorityInfoAccess; break; michael@0: } michael@0: } else if (ext->critical.data && ext->critical.len > 0) { michael@0: // The only valid explicit value of the critical flag is TRUE because michael@0: // it is defined as BOOLEAN DEFAULT FALSE, so we just assume it is true. michael@0: return Fail(RecoverableError, SEC_ERROR_UNKNOWN_CRITICAL_EXTENSION); michael@0: } michael@0: michael@0: if (out) { michael@0: // This is an extension we understand. Save it in results unless we've michael@0: // already found the extension previously. michael@0: if (*out) { michael@0: // Duplicate extension michael@0: return Fail(RecoverableError, SEC_ERROR_EXTENSION_VALUE_INVALID); michael@0: } michael@0: *out = &ext->value; michael@0: } michael@0: } michael@0: michael@0: return Success; michael@0: } michael@0: michael@0: static Result BuildForward(TrustDomain& trustDomain, michael@0: BackCert& subject, michael@0: PRTime time, michael@0: EndEntityOrCA endEntityOrCA, michael@0: KeyUsage requiredKeyUsageIfPresent, michael@0: SECOidTag requiredEKUIfPresent, michael@0: SECOidTag requiredPolicy, michael@0: /*optional*/ const SECItem* stapledOCSPResponse, michael@0: unsigned int subCACount, michael@0: /*out*/ ScopedCERTCertList& results); michael@0: michael@0: // The code that executes in the inner loop of BuildForward michael@0: static Result michael@0: BuildForwardInner(TrustDomain& trustDomain, michael@0: BackCert& subject, michael@0: PRTime time, michael@0: EndEntityOrCA endEntityOrCA, michael@0: SECOidTag requiredEKUIfPresent, michael@0: SECOidTag requiredPolicy, michael@0: CERTCertificate* potentialIssuerCertToDup, michael@0: /*optional*/ const SECItem* stapledOCSPResponse, michael@0: unsigned int subCACount, michael@0: ScopedCERTCertList& results) michael@0: { michael@0: PORT_Assert(potentialIssuerCertToDup); michael@0: michael@0: BackCert potentialIssuer(potentialIssuerCertToDup, &subject, michael@0: BackCert::ExcludeCN); michael@0: Result rv = potentialIssuer.Init(); michael@0: if (rv != Success) { michael@0: return rv; michael@0: } michael@0: michael@0: // RFC5280 4.2.1.1. Authority Key Identifier michael@0: // RFC5280 4.2.1.2. Subject Key Identifier michael@0: michael@0: // Loop prevention, done as recommended by RFC4158 Section 5.2 michael@0: // TODO: this doesn't account for subjectAltNames! michael@0: // TODO(perf): This probably can and should be optimized in some way. michael@0: bool loopDetected = false; michael@0: for (BackCert* prev = potentialIssuer.childCert; michael@0: !loopDetected && prev != nullptr; prev = prev->childCert) { michael@0: if (SECITEM_ItemsAreEqual(&potentialIssuer.GetNSSCert()->derPublicKey, michael@0: &prev->GetNSSCert()->derPublicKey) && michael@0: SECITEM_ItemsAreEqual(&potentialIssuer.GetNSSCert()->derSubject, michael@0: &prev->GetNSSCert()->derSubject)) { michael@0: return Fail(RecoverableError, SEC_ERROR_UNKNOWN_ISSUER); // XXX: error code michael@0: } michael@0: } michael@0: michael@0: rv = CheckNameConstraints(potentialIssuer); michael@0: if (rv != Success) { michael@0: return rv; michael@0: } michael@0: michael@0: unsigned int newSubCACount = subCACount; michael@0: if (endEntityOrCA == MustBeCA) { michael@0: newSubCACount = subCACount + 1; michael@0: } else { michael@0: PR_ASSERT(newSubCACount == 0); michael@0: } michael@0: rv = BuildForward(trustDomain, potentialIssuer, time, MustBeCA, michael@0: KeyUsage::keyCertSign, requiredEKUIfPresent, michael@0: requiredPolicy, nullptr, newSubCACount, results); michael@0: if (rv != Success) { michael@0: return rv; michael@0: } michael@0: michael@0: if (trustDomain.VerifySignedData(&subject.GetNSSCert()->signatureWrap, michael@0: potentialIssuer.GetNSSCert()) != SECSuccess) { michael@0: return MapSECStatus(SECFailure); michael@0: } michael@0: michael@0: return Success; michael@0: } michael@0: michael@0: // Recursively build the path from the given subject certificate to the root. michael@0: // michael@0: // Be very careful about changing the order of checks. The order is significant michael@0: // because it affects which error we return when a certificate or certificate michael@0: // chain has multiple problems. See the error ranking documentation in michael@0: // pkix/pkix.h. michael@0: static Result michael@0: BuildForward(TrustDomain& trustDomain, michael@0: BackCert& subject, michael@0: PRTime time, michael@0: EndEntityOrCA endEntityOrCA, michael@0: KeyUsage requiredKeyUsageIfPresent, michael@0: SECOidTag requiredEKUIfPresent, michael@0: SECOidTag requiredPolicy, michael@0: /*optional*/ const SECItem* stapledOCSPResponse, michael@0: unsigned int subCACount, michael@0: /*out*/ ScopedCERTCertList& results) michael@0: { michael@0: // Avoid stack overflows and poor performance by limiting cert length. michael@0: // XXX: 6 is not enough for chains.sh anypolicywithlevel.cfg tests michael@0: static const size_t MAX_DEPTH = 8; michael@0: if (subCACount >= MAX_DEPTH - 1) { michael@0: return Fail(RecoverableError, SEC_ERROR_UNKNOWN_ISSUER); michael@0: } michael@0: michael@0: Result rv; michael@0: michael@0: TrustDomain::TrustLevel trustLevel; michael@0: // If this is an end-entity and not a trust anchor, we defer reporting michael@0: // any error found here until after attempting to find a valid chain. michael@0: // See the explanation of error prioritization in pkix.h. michael@0: rv = CheckIssuerIndependentProperties(trustDomain, subject, time, michael@0: endEntityOrCA, michael@0: requiredKeyUsageIfPresent, michael@0: requiredEKUIfPresent, requiredPolicy, michael@0: subCACount, &trustLevel); michael@0: PRErrorCode deferredEndEntityError = 0; michael@0: if (rv != Success) { michael@0: if (endEntityOrCA == MustBeEndEntity && michael@0: trustLevel != TrustDomain::TrustAnchor) { michael@0: deferredEndEntityError = PR_GetError(); michael@0: } else { michael@0: return rv; michael@0: } michael@0: } michael@0: michael@0: if (trustLevel == TrustDomain::TrustAnchor) { michael@0: ScopedCERTCertList certChain(CERT_NewCertList()); michael@0: if (!certChain) { michael@0: PR_SetError(SEC_ERROR_NO_MEMORY, 0); michael@0: return MapSECStatus(SECFailure); michael@0: } michael@0: michael@0: rv = subject.PrependNSSCertToList(certChain.get()); michael@0: if (rv != Success) { michael@0: return rv; michael@0: } michael@0: BackCert* child = subject.childCert; michael@0: while (child) { michael@0: rv = child->PrependNSSCertToList(certChain.get()); michael@0: if (rv != Success) { michael@0: return rv; michael@0: } michael@0: child = child->childCert; michael@0: } michael@0: michael@0: SECStatus srv = trustDomain.IsChainValid(certChain.get()); michael@0: if (srv != SECSuccess) { michael@0: return MapSECStatus(srv); michael@0: } michael@0: michael@0: // End of the recursion. Create the result list and add the trust anchor to michael@0: // it. michael@0: results = CERT_NewCertList(); michael@0: if (!results) { michael@0: return FatalError; michael@0: } michael@0: rv = subject.PrependNSSCertToList(results.get()); michael@0: return rv; michael@0: } michael@0: michael@0: // Find a trusted issuer. michael@0: // TODO(bug 965136): Add SKI/AKI matching optimizations michael@0: ScopedCERTCertList candidates; michael@0: if (trustDomain.FindPotentialIssuers(&subject.GetNSSCert()->derIssuer, time, michael@0: candidates) != SECSuccess) { michael@0: return MapSECStatus(SECFailure); michael@0: } michael@0: if (!candidates) { michael@0: return Fail(RecoverableError, SEC_ERROR_UNKNOWN_ISSUER); michael@0: } michael@0: michael@0: PRErrorCode errorToReturn = 0; michael@0: michael@0: for (CERTCertListNode* n = CERT_LIST_HEAD(candidates); michael@0: !CERT_LIST_END(n, candidates); n = CERT_LIST_NEXT(n)) { michael@0: rv = BuildForwardInner(trustDomain, subject, time, endEntityOrCA, michael@0: requiredEKUIfPresent, requiredPolicy, michael@0: n->cert, stapledOCSPResponse, subCACount, michael@0: results); michael@0: if (rv == Success) { michael@0: // If we found a valid chain but deferred reporting an error with the michael@0: // end-entity certificate, report it now. michael@0: if (deferredEndEntityError != 0) { michael@0: PR_SetError(deferredEndEntityError, 0); michael@0: return FatalError; michael@0: } michael@0: michael@0: SECStatus srv = trustDomain.CheckRevocation(endEntityOrCA, michael@0: subject.GetNSSCert(), michael@0: n->cert, time, michael@0: stapledOCSPResponse); michael@0: if (srv != SECSuccess) { michael@0: return MapSECStatus(SECFailure); michael@0: } michael@0: michael@0: // We found a trusted issuer. At this point, we know the cert is valid michael@0: return subject.PrependNSSCertToList(results.get()); michael@0: } michael@0: if (rv != RecoverableError) { michael@0: return rv; michael@0: } michael@0: michael@0: PRErrorCode currentError = PR_GetError(); michael@0: switch (currentError) { michael@0: case 0: michael@0: PR_NOT_REACHED("Error code not set!"); michael@0: PR_SetError(PR_INVALID_STATE_ERROR, 0); michael@0: return FatalError; michael@0: case SEC_ERROR_UNTRUSTED_CERT: michael@0: currentError = SEC_ERROR_UNTRUSTED_ISSUER; michael@0: break; michael@0: default: michael@0: break; michael@0: } michael@0: if (errorToReturn == 0) { michael@0: errorToReturn = currentError; michael@0: } else if (errorToReturn != currentError) { michael@0: errorToReturn = SEC_ERROR_UNKNOWN_ISSUER; michael@0: } michael@0: } michael@0: michael@0: if (errorToReturn == 0) { michael@0: errorToReturn = SEC_ERROR_UNKNOWN_ISSUER; michael@0: } michael@0: michael@0: return Fail(RecoverableError, errorToReturn); michael@0: } michael@0: michael@0: SECStatus michael@0: BuildCertChain(TrustDomain& trustDomain, michael@0: CERTCertificate* certToDup, michael@0: PRTime time, michael@0: EndEntityOrCA endEntityOrCA, michael@0: /*optional*/ KeyUsage requiredKeyUsageIfPresent, michael@0: /*optional*/ SECOidTag requiredEKUIfPresent, michael@0: /*optional*/ SECOidTag requiredPolicy, michael@0: /*optional*/ const SECItem* stapledOCSPResponse, michael@0: /*out*/ ScopedCERTCertList& results) michael@0: { michael@0: PORT_Assert(certToDup); michael@0: michael@0: if (!certToDup) { michael@0: PR_SetError(SEC_ERROR_INVALID_ARGS, 0); michael@0: return SECFailure; michael@0: } michael@0: michael@0: // The only non-const operation on the cert we are allowed to do is michael@0: // CERT_DupCertificate. michael@0: michael@0: // XXX: Support the legacy use of the subject CN field for indicating the michael@0: // domain name the certificate is valid for. michael@0: BackCert::ConstrainedNameOptions cnOptions michael@0: = endEntityOrCA == MustBeEndEntity && michael@0: requiredEKUIfPresent == SEC_OID_EXT_KEY_USAGE_SERVER_AUTH michael@0: ? BackCert::IncludeCN michael@0: : BackCert::ExcludeCN; michael@0: michael@0: BackCert cert(certToDup, nullptr, cnOptions); michael@0: Result rv = cert.Init(); michael@0: if (rv != Success) { michael@0: return SECFailure; michael@0: } michael@0: michael@0: rv = BuildForward(trustDomain, cert, time, endEntityOrCA, michael@0: requiredKeyUsageIfPresent, requiredEKUIfPresent, michael@0: requiredPolicy, stapledOCSPResponse, 0, results); michael@0: if (rv != Success) { michael@0: results = nullptr; michael@0: return SECFailure; michael@0: } michael@0: michael@0: return SECSuccess; michael@0: } michael@0: michael@0: PLArenaPool* michael@0: BackCert::GetArena() michael@0: { michael@0: if (!arena) { michael@0: arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); michael@0: } michael@0: return arena.get(); michael@0: } michael@0: michael@0: Result michael@0: BackCert::PrependNSSCertToList(CERTCertList* results) michael@0: { michael@0: PORT_Assert(results); michael@0: michael@0: CERTCertificate* dup = CERT_DupCertificate(nssCert); michael@0: if (CERT_AddCertToListHead(results, dup) != SECSuccess) { // takes ownership michael@0: CERT_DestroyCertificate(dup); michael@0: return FatalError; michael@0: } michael@0: michael@0: return Success; michael@0: } michael@0: michael@0: } } // namespace mozilla::pkix