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 michael@0: michael@0: #include "pkix/pkix.h" michael@0: #include "pkixcheck.h" michael@0: #include "pkixder.h" michael@0: #include "pkixutil.h" michael@0: #include "secder.h" michael@0: michael@0: namespace mozilla { namespace pkix { michael@0: michael@0: Result michael@0: CheckTimes(const CERTCertificate* cert, PRTime time) michael@0: { michael@0: PR_ASSERT(cert); michael@0: michael@0: SECCertTimeValidity validity = CERT_CheckCertValidTimes(cert, time, false); michael@0: if (validity != secCertTimeValid) { michael@0: return Fail(RecoverableError, SEC_ERROR_EXPIRED_CERTIFICATE); michael@0: } michael@0: michael@0: return Success; michael@0: } michael@0: michael@0: // 4.2.1.3. Key Usage (id-ce-keyUsage) michael@0: michael@0: // As explained in the comment in CheckKeyUsage, bit 0 is the most significant michael@0: // bit and bit 7 is the least significant bit. michael@0: inline uint8_t KeyUsageToBitMask(KeyUsage keyUsage) michael@0: { michael@0: PR_ASSERT(keyUsage != KeyUsage::noParticularKeyUsageRequired); michael@0: return 0x80u >> static_cast(keyUsage); michael@0: } michael@0: michael@0: Result michael@0: CheckKeyUsage(EndEntityOrCA endEntityOrCA, const SECItem* encodedKeyUsage, michael@0: KeyUsage requiredKeyUsageIfPresent) michael@0: { michael@0: if (!encodedKeyUsage) { michael@0: // TODO(bug 970196): Reject certificates that are being used to verify michael@0: // certificate signatures unless the certificate is a trust anchor, to michael@0: // reduce the chances of an end-entity certificate being abused as a CA michael@0: // certificate. michael@0: // if (endEntityOrCA == EndEntityOrCA::MustBeCA && !isTrustAnchor) { michael@0: // return Fail(RecoverableError, SEC_ERROR_INADEQUATE_KEY_USAGE); michael@0: // } michael@0: // michael@0: // TODO: Users may configure arbitrary certificates as trust anchors, not michael@0: // just roots. We should only allow a certificate without a key usage to be michael@0: // used as a CA when it is self-issued and self-signed. michael@0: return Success; michael@0: } michael@0: michael@0: der::Input input; michael@0: if (input.Init(encodedKeyUsage->data, encodedKeyUsage->len) != der::Success) { michael@0: return Fail(RecoverableError, SEC_ERROR_INADEQUATE_KEY_USAGE); michael@0: } michael@0: der::Input value; michael@0: if (der::ExpectTagAndGetValue(input, der::BIT_STRING, value) != der::Success) { michael@0: return Fail(RecoverableError, SEC_ERROR_INADEQUATE_KEY_USAGE); michael@0: } michael@0: michael@0: uint8_t numberOfPaddingBits; michael@0: if (value.Read(numberOfPaddingBits) != der::Success) { michael@0: return Fail(RecoverableError, SEC_ERROR_INADEQUATE_KEY_USAGE); michael@0: } michael@0: if (numberOfPaddingBits > 7) { michael@0: return Fail(RecoverableError, SEC_ERROR_INADEQUATE_KEY_USAGE); michael@0: } michael@0: michael@0: uint8_t bits; michael@0: if (value.Read(bits) != der::Success) { michael@0: // Reject empty bit masks. michael@0: return Fail(RecoverableError, SEC_ERROR_INADEQUATE_KEY_USAGE); michael@0: } michael@0: michael@0: // The most significant bit is numbered 0 (digitalSignature) and the least michael@0: // significant bit is numbered 7 (encipherOnly), and the padding is in the michael@0: // least significant bits of the last byte. The numbering of bits in a byte michael@0: // is backwards from how we usually interpret them. michael@0: // michael@0: // For example, let's say bits is encoded in one byte with of value 0xB0 and michael@0: // numberOfPaddingBits == 4. Then, bits is 10110000 in binary: michael@0: // michael@0: // bit 0 bit 3 michael@0: // | | michael@0: // v v michael@0: // 10110000 michael@0: // ^^^^ michael@0: // | michael@0: // 4 padding bits michael@0: // michael@0: // Since bits is the last byte, we have to consider the padding by ensuring michael@0: // that the least significant 4 bits are all zero, since DER rules require michael@0: // all padding bits to be zero. Then we have to look at the bit N bits to the michael@0: // right of the most significant bit, where N is a value from the KeyUsage michael@0: // enumeration. michael@0: // michael@0: // Let's say we're interested in the keyCertSign (5) bit. We'd need to look michael@0: // at bit 5, which is zero, so keyCertSign is not asserted. (Since we check michael@0: // that the padding is all zeros, it is OK to read from the padding bits.) michael@0: // michael@0: // Let's say we're interested in the digitalSignature (0) bit. We'd need to michael@0: // look at the bit 0 (the most significant bit), which is set, so that means michael@0: // digitalSignature is asserted. Similarly, keyEncipherment (2) and michael@0: // dataEncipherment (3) are asserted. michael@0: // michael@0: // Note that since the KeyUsage enumeration is limited to values 0-7, we michael@0: // only ever need to examine the first byte test for michael@0: // requiredKeyUsageIfPresent. michael@0: michael@0: if (requiredKeyUsageIfPresent != KeyUsage::noParticularKeyUsageRequired) { michael@0: // Check that the required key usage bit is set. michael@0: if ((bits & KeyUsageToBitMask(requiredKeyUsageIfPresent)) == 0) { michael@0: return Fail(RecoverableError, SEC_ERROR_INADEQUATE_KEY_USAGE); michael@0: } michael@0: } michael@0: michael@0: if (endEntityOrCA != EndEntityOrCA::MustBeCA) { michael@0: // RFC 5280 says "The keyCertSign bit is asserted when the subject public michael@0: // key is used for verifying signatures on public key certificates. If the michael@0: // keyCertSign bit is asserted, then the cA bit in the basic constraints michael@0: // extension (Section 4.2.1.9) MUST also be asserted." michael@0: if ((bits & KeyUsageToBitMask(KeyUsage::keyCertSign)) != 0) { michael@0: return Fail(RecoverableError, SEC_ERROR_INADEQUATE_KEY_USAGE); michael@0: } michael@0: } michael@0: michael@0: // The padding applies to the last byte, so skip to the last byte. michael@0: while (!value.AtEnd()) { michael@0: if (value.Read(bits) != der::Success) { michael@0: return Fail(RecoverableError, SEC_ERROR_INADEQUATE_KEY_USAGE); michael@0: } michael@0: } michael@0: michael@0: // All of the padding bits must be zero, according to DER rules. michael@0: uint8_t paddingMask = static_cast((1 << numberOfPaddingBits) - 1); michael@0: if ((bits & paddingMask) != 0) { michael@0: return Fail(RecoverableError, SEC_ERROR_INADEQUATE_KEY_USAGE); michael@0: } michael@0: michael@0: return Success; michael@0: } michael@0: michael@0: // RFC5820 4.2.1.4. Certificate Policies michael@0: // michael@0: // "The user-initial-policy-set contains the special value any-policy if the michael@0: // user is not concerned about certificate policy." michael@0: Result michael@0: CheckCertificatePolicies(BackCert& cert, EndEntityOrCA endEntityOrCA, michael@0: bool isTrustAnchor, SECOidTag requiredPolicy) michael@0: { michael@0: if (requiredPolicy == SEC_OID_X509_ANY_POLICY) { michael@0: return Success; michael@0: } michael@0: michael@0: // It is likely some callers will pass SEC_OID_UNKNOWN when they don't care, michael@0: // instead of passing SEC_OID_X509_ANY_POLICY. Help them out by failing hard. michael@0: if (requiredPolicy == SEC_OID_UNKNOWN) { michael@0: PR_SetError(SEC_ERROR_INVALID_ARGS, 0); michael@0: return FatalError; michael@0: } michael@0: michael@0: // Bug 989051. Until we handle inhibitAnyPolicy we will fail close when michael@0: // inhibitAnyPolicy extension is present and we need to evaluate certificate michael@0: // policies. michael@0: if (cert.encodedInhibitAnyPolicy) { michael@0: PR_SetError(SEC_ERROR_POLICY_VALIDATION_FAILED, 0); michael@0: return RecoverableError; michael@0: } michael@0: michael@0: // The root CA certificate may omit the policies that it has been michael@0: // trusted for, so we cannot require the policies to be present in those michael@0: // certificates. Instead, the determination of which roots are trusted for michael@0: // which policies is made by the TrustDomain's GetCertTrust method. michael@0: if (isTrustAnchor && endEntityOrCA == MustBeCA) { michael@0: return Success; michael@0: } michael@0: michael@0: if (!cert.encodedCertificatePolicies) { michael@0: PR_SetError(SEC_ERROR_POLICY_VALIDATION_FAILED, 0); michael@0: return RecoverableError; michael@0: } michael@0: michael@0: ScopedPtr michael@0: policies(CERT_DecodeCertificatePoliciesExtension( michael@0: cert.encodedCertificatePolicies)); michael@0: if (!policies) { michael@0: return MapSECStatus(SECFailure); michael@0: } michael@0: michael@0: for (const CERTPolicyInfo* const* policyInfos = policies->policyInfos; michael@0: *policyInfos; ++policyInfos) { michael@0: if ((*policyInfos)->oid == requiredPolicy) { michael@0: return Success; michael@0: } michael@0: // Intermediate certs are allowed to have the anyPolicy OID michael@0: if (endEntityOrCA == MustBeCA && michael@0: (*policyInfos)->oid == SEC_OID_X509_ANY_POLICY) { michael@0: return Success; michael@0: } michael@0: } michael@0: michael@0: PR_SetError(SEC_ERROR_POLICY_VALIDATION_FAILED, 0); michael@0: return RecoverableError; michael@0: } michael@0: michael@0: // BasicConstraints ::= SEQUENCE { michael@0: // cA BOOLEAN DEFAULT FALSE, michael@0: // pathLenConstraint INTEGER (0..MAX) OPTIONAL } michael@0: der::Result michael@0: DecodeBasicConstraints(const SECItem* encodedBasicConstraints, michael@0: CERTBasicConstraints& basicConstraints) michael@0: { michael@0: PR_ASSERT(encodedBasicConstraints); michael@0: if (!encodedBasicConstraints) { michael@0: return der::Fail(SEC_ERROR_INVALID_ARGS); michael@0: } michael@0: michael@0: basicConstraints.isCA = false; michael@0: basicConstraints.pathLenConstraint = 0; michael@0: michael@0: der::Input input; michael@0: if (input.Init(encodedBasicConstraints->data, encodedBasicConstraints->len) michael@0: != der::Success) { michael@0: return der::Fail(SEC_ERROR_EXTENSION_VALUE_INVALID); michael@0: } michael@0: michael@0: if (der::ExpectTagAndIgnoreLength(input, der::SEQUENCE) != der::Success) { michael@0: return der::Fail(SEC_ERROR_EXTENSION_VALUE_INVALID); michael@0: } michael@0: michael@0: bool isCA = false; michael@0: // TODO(bug 989518): cA is by default false. According to DER, default michael@0: // values must not be explicitly encoded in a SEQUENCE. So, if this michael@0: // value is present and false, it is an encoding error. However, Go Daddy michael@0: // has issued many certificates with this improper encoding, so we can't michael@0: // enforce this yet (hence passing true for allowInvalidExplicitEncoding michael@0: // to der::OptionalBoolean). michael@0: if (der::OptionalBoolean(input, true, isCA) != der::Success) { michael@0: return der::Fail(SEC_ERROR_EXTENSION_VALUE_INVALID); michael@0: } michael@0: basicConstraints.isCA = isCA; michael@0: michael@0: if (input.Peek(der::INTEGER)) { michael@0: SECItem pathLenConstraintEncoded; michael@0: if (der::Integer(input, pathLenConstraintEncoded) != der::Success) { michael@0: return der::Fail(SEC_ERROR_EXTENSION_VALUE_INVALID); michael@0: } michael@0: long pathLenConstraint = DER_GetInteger(&pathLenConstraintEncoded); michael@0: if (pathLenConstraint >= std::numeric_limits::max() || michael@0: pathLenConstraint < 0) { michael@0: return der::Fail(SEC_ERROR_EXTENSION_VALUE_INVALID); michael@0: } michael@0: basicConstraints.pathLenConstraint = static_cast(pathLenConstraint); michael@0: // TODO(bug 985025): If isCA is false, pathLenConstraint MUST NOT michael@0: // be included (as per RFC 5280 section 4.2.1.9), but for compatibility michael@0: // reasons, we don't check this for now. michael@0: } else if (basicConstraints.isCA) { michael@0: // If this is a CA but the path length is omitted, it is unlimited. michael@0: basicConstraints.pathLenConstraint = CERT_UNLIMITED_PATH_CONSTRAINT; michael@0: } michael@0: michael@0: if (der::End(input) != der::Success) { michael@0: return der::Fail(SEC_ERROR_EXTENSION_VALUE_INVALID); michael@0: } michael@0: return der::Success; michael@0: } michael@0: michael@0: // RFC5280 4.2.1.9. Basic Constraints (id-ce-basicConstraints) michael@0: Result michael@0: CheckBasicConstraints(const BackCert& cert, michael@0: EndEntityOrCA endEntityOrCA, michael@0: bool isTrustAnchor, michael@0: unsigned int subCACount) michael@0: { michael@0: CERTBasicConstraints basicConstraints; michael@0: if (cert.encodedBasicConstraints) { michael@0: if (DecodeBasicConstraints(cert.encodedBasicConstraints, michael@0: basicConstraints) != der::Success) { michael@0: return RecoverableError; michael@0: } michael@0: } else { michael@0: // Synthesize a non-CA basic constraints by default michael@0: basicConstraints.isCA = false; michael@0: basicConstraints.pathLenConstraint = 0; michael@0: michael@0: // "If the basic constraints extension is not present in a version 3 michael@0: // certificate, or the extension is present but the cA boolean is not michael@0: // asserted, then the certified public key MUST NOT be used to verify michael@0: // certificate signatures." michael@0: // michael@0: // For compatibility, we must accept v1 trust anchors without basic michael@0: // constraints as CAs. michael@0: // michael@0: // TODO: add check for self-signedness? michael@0: if (endEntityOrCA == MustBeCA && isTrustAnchor) { michael@0: const CERTCertificate* nssCert = cert.GetNSSCert(); michael@0: // We only allow trust anchor CA certs to omit the michael@0: // basicConstraints extension if they are v1. v1 is encoded michael@0: // implicitly. michael@0: if (!nssCert->version.data && !nssCert->version.len) { michael@0: basicConstraints.isCA = true; michael@0: basicConstraints.pathLenConstraint = CERT_UNLIMITED_PATH_CONSTRAINT; michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (endEntityOrCA == MustBeEndEntity) { michael@0: // CA certificates are not trusted as EE certs. michael@0: michael@0: if (basicConstraints.isCA) { michael@0: // XXX: We use SEC_ERROR_CA_CERT_INVALID here so we can distinguish michael@0: // this error from other errors, given that NSS does not have a "CA cert michael@0: // used as end-entity" error code since it doesn't have such a michael@0: // prohibition. We should add such an error code and stop abusing michael@0: // SEC_ERROR_CA_CERT_INVALID this way. michael@0: // michael@0: // Note, in particular, that this check prevents a delegated OCSP michael@0: // response signing certificate with the CA bit from successfully michael@0: // validating when we check it from pkixocsp.cpp, which is a good thing. michael@0: // michael@0: return Fail(RecoverableError, SEC_ERROR_CA_CERT_INVALID); michael@0: } michael@0: michael@0: return Success; michael@0: } michael@0: michael@0: PORT_Assert(endEntityOrCA == MustBeCA); michael@0: michael@0: // End-entity certificates are not allowed to act as CA certs. michael@0: if (!basicConstraints.isCA) { michael@0: return Fail(RecoverableError, SEC_ERROR_CA_CERT_INVALID); michael@0: } michael@0: michael@0: if (basicConstraints.pathLenConstraint >= 0) { michael@0: if (subCACount > michael@0: static_cast(basicConstraints.pathLenConstraint)) { michael@0: return Fail(RecoverableError, SEC_ERROR_PATH_LEN_CONSTRAINT_INVALID); michael@0: } michael@0: } michael@0: michael@0: return Success; michael@0: } michael@0: michael@0: Result michael@0: BackCert::GetConstrainedNames(/*out*/ const CERTGeneralName** result) michael@0: { michael@0: if (!constrainedNames) { michael@0: if (!GetArena()) { michael@0: return FatalError; michael@0: } michael@0: michael@0: constrainedNames = michael@0: CERT_GetConstrainedCertificateNames(nssCert, arena.get(), michael@0: cnOptions == IncludeCN); michael@0: if (!constrainedNames) { michael@0: return MapSECStatus(SECFailure); michael@0: } michael@0: } michael@0: michael@0: *result = constrainedNames; michael@0: return Success; michael@0: } michael@0: michael@0: // 4.2.1.10. Name Constraints michael@0: Result michael@0: CheckNameConstraints(BackCert& cert) michael@0: { michael@0: static const char constraintFranceGov[] = michael@0: "\x30\x5D" /* sequence len 93*/ michael@0: "\xA0\x5B" /* element len 91 */ michael@0: "\x30\x05" /* sequence len 5 */ michael@0: "\x82\x03" /* entry len 3 */ michael@0: ".fr" michael@0: "\x30\x05\x82\x03" /* sequence len 5, entry len 3 */ michael@0: ".gp" michael@0: "\x30\x05\x82\x03" michael@0: ".gf" michael@0: "\x30\x05\x82\x03" michael@0: ".mq" michael@0: "\x30\x05\x82\x03" michael@0: ".re" michael@0: "\x30\x05\x82\x03" michael@0: ".yt" michael@0: "\x30\x05\x82\x03" michael@0: ".pm" michael@0: "\x30\x05\x82\x03" michael@0: ".bl" michael@0: "\x30\x05\x82\x03" michael@0: ".mf" michael@0: "\x30\x05\x82\x03" michael@0: ".wf" michael@0: "\x30\x05\x82\x03" michael@0: ".pf" michael@0: "\x30\x05\x82\x03" michael@0: ".nc" michael@0: "\x30\x05\x82\x03" michael@0: ".tf"; michael@0: michael@0: /* The stringified value for the subject is: michael@0: E=igca@sgdn.pm.gouv.fr,CN=IGC/A,OU=DCSSI,O=PM/SGDN,L=Paris,ST=France,C=FR michael@0: */ michael@0: static const char rawANSSISubject[] = michael@0: "\x30\x81\x85\x31\x0B\x30\x09\x06\x03\x55\x04" michael@0: "\x06\x13\x02\x46\x52\x31\x0F\x30\x0D\x06\x03" michael@0: "\x55\x04\x08\x13\x06\x46\x72\x61\x6E\x63\x65" michael@0: "\x31\x0E\x30\x0C\x06\x03\x55\x04\x07\x13\x05" michael@0: "\x50\x61\x72\x69\x73\x31\x10\x30\x0E\x06\x03" michael@0: "\x55\x04\x0A\x13\x07\x50\x4D\x2F\x53\x47\x44" michael@0: "\x4E\x31\x0E\x30\x0C\x06\x03\x55\x04\x0B\x13" michael@0: "\x05\x44\x43\x53\x53\x49\x31\x0E\x30\x0C\x06" michael@0: "\x03\x55\x04\x03\x13\x05\x49\x47\x43\x2F\x41" michael@0: "\x31\x23\x30\x21\x06\x09\x2A\x86\x48\x86\xF7" michael@0: "\x0D\x01\x09\x01\x16\x14\x69\x67\x63\x61\x40" michael@0: "\x73\x67\x64\x6E\x2E\x70\x6D\x2E\x67\x6F\x75" michael@0: "\x76\x2E\x66\x72"; michael@0: michael@0: const SECItem ANSSI_SUBJECT = { michael@0: siBuffer, michael@0: reinterpret_cast(const_cast(rawANSSISubject)), michael@0: sizeof(rawANSSISubject) - 1 michael@0: }; michael@0: michael@0: const SECItem PERMIT_FRANCE_GOV_NC = { michael@0: siBuffer, michael@0: reinterpret_cast(const_cast(constraintFranceGov)), michael@0: sizeof(constraintFranceGov) - 1 michael@0: }; michael@0: michael@0: const SECItem* nameConstraintsToUse = cert.encodedNameConstraints; michael@0: michael@0: if (!nameConstraintsToUse) { michael@0: if (SECITEM_ItemsAreEqual(&cert.GetNSSCert()->derSubject, &ANSSI_SUBJECT)) { michael@0: nameConstraintsToUse = &PERMIT_FRANCE_GOV_NC; michael@0: } else { michael@0: return Success; michael@0: } michael@0: } michael@0: michael@0: PLArenaPool* arena = cert.GetArena(); michael@0: if (!arena) { michael@0: return FatalError; michael@0: } michael@0: michael@0: // Owned by arena michael@0: const CERTNameConstraints* constraints = michael@0: CERT_DecodeNameConstraintsExtension(arena, nameConstraintsToUse); michael@0: if (!constraints) { michael@0: return MapSECStatus(SECFailure); michael@0: } michael@0: michael@0: for (BackCert* prev = cert.childCert; prev; prev = prev->childCert) { michael@0: const CERTGeneralName* names = nullptr; michael@0: Result rv = prev->GetConstrainedNames(&names); michael@0: if (rv != Success) { michael@0: return rv; michael@0: } michael@0: PORT_Assert(names); michael@0: CERTGeneralName* currentName = const_cast(names); michael@0: do { michael@0: if (CERT_CheckNameSpace(arena, constraints, currentName) != SECSuccess) { michael@0: // XXX: It seems like CERT_CheckNameSpace doesn't always call michael@0: // PR_SetError when it fails. We set the error code here, though this michael@0: // may be papering over some fatal errors. NSS's michael@0: // cert_VerifyCertChainOld does something similar. michael@0: PR_SetError(SEC_ERROR_CERT_NOT_IN_NAME_SPACE, 0); michael@0: return RecoverableError; michael@0: } michael@0: currentName = CERT_GetNextGeneralName(currentName); michael@0: } while (currentName != names); michael@0: } michael@0: michael@0: return Success; michael@0: } michael@0: michael@0: // 4.2.1.12. Extended Key Usage (id-ce-extKeyUsage) michael@0: // 4.2.1.12. Extended Key Usage (id-ce-extKeyUsage) michael@0: Result michael@0: CheckExtendedKeyUsage(EndEntityOrCA endEntityOrCA, const SECItem* encodedEKUs, michael@0: SECOidTag requiredEKU) michael@0: { michael@0: // TODO: Either do not allow anyExtendedKeyUsage to be passed as requiredEKU, michael@0: // or require that callers pass anyExtendedKeyUsage instead of michael@0: // SEC_OID_UNKNWON and disallow SEC_OID_UNKNWON. michael@0: michael@0: // XXX: We're using SEC_ERROR_INADEQUATE_CERT_TYPE here so that callers can michael@0: // distinguish EKU mismatch from KU mismatch from basic constraints mismatch. michael@0: // We should probably add a new error code that is more clear for this type michael@0: // of problem. michael@0: michael@0: bool foundOCSPSigning = false; michael@0: michael@0: if (encodedEKUs) { michael@0: ScopedPtr michael@0: seq(CERT_DecodeOidSequence(encodedEKUs)); michael@0: if (!seq) { michael@0: PR_SetError(SEC_ERROR_INADEQUATE_CERT_TYPE, 0); michael@0: return RecoverableError; michael@0: } michael@0: michael@0: bool found = false; michael@0: michael@0: // XXX: We allow duplicate entries. michael@0: for (const SECItem* const* oids = seq->oids; oids && *oids; ++oids) { michael@0: SECOidTag oidTag = SECOID_FindOIDTag(*oids); michael@0: if (requiredEKU != SEC_OID_UNKNOWN && oidTag == requiredEKU) { michael@0: found = true; michael@0: } else { michael@0: // Treat CA certs with step-up OID as also having SSL server type. michael@0: // COMODO has issued certificates that require this behavior michael@0: // that don't expire until June 2020! michael@0: // TODO 982932: Limit this expection to old certificates michael@0: if (endEntityOrCA == MustBeCA && michael@0: requiredEKU == SEC_OID_EXT_KEY_USAGE_SERVER_AUTH && michael@0: oidTag == SEC_OID_NS_KEY_USAGE_GOVT_APPROVED) { michael@0: found = true; michael@0: } michael@0: } michael@0: if (oidTag == SEC_OID_OCSP_RESPONDER) { michael@0: foundOCSPSigning = true; michael@0: } michael@0: } michael@0: michael@0: // If the EKU extension was included, then the required EKU must be in the michael@0: // list. michael@0: if (!found) { michael@0: PR_SetError(SEC_ERROR_INADEQUATE_CERT_TYPE, 0); michael@0: return RecoverableError; michael@0: } michael@0: } michael@0: michael@0: // pkixocsp.cpp depends on the following additional checks. michael@0: michael@0: if (endEntityOrCA == MustBeEndEntity) { michael@0: // When validating anything other than an delegated OCSP signing cert, michael@0: // reject any cert that also claims to be an OCSP responder, because such michael@0: // a cert does not make sense. For example, if an SSL certificate were to michael@0: // assert id-kp-OCSPSigning then it could sign OCSP responses for itself, michael@0: // if not for this check. michael@0: // That said, we accept CA certificates with id-kp-OCSPSigning because michael@0: // some CAs in Mozilla's CA program have issued such intermediate michael@0: // certificates, and because some CAs have reported some Microsoft server michael@0: // software wrongly requires CA certificates to have id-kp-OCSPSigning. michael@0: // Allowing this exception does not cause any security issues because we michael@0: // require delegated OCSP response signing certificates to be end-entity michael@0: // certificates. michael@0: if (foundOCSPSigning && requiredEKU != SEC_OID_OCSP_RESPONDER) { michael@0: PR_SetError(SEC_ERROR_INADEQUATE_CERT_TYPE, 0); michael@0: return RecoverableError; michael@0: } michael@0: // http://tools.ietf.org/html/rfc6960#section-4.2.2.2: michael@0: // "OCSP signing delegation SHALL be designated by the inclusion of michael@0: // id-kp-OCSPSigning in an extended key usage certificate extension michael@0: // included in the OCSP response signer's certificate." michael@0: // michael@0: // id-kp-OCSPSigning is the only EKU that isn't implicitly assumed when the michael@0: // EKU extension is missing from an end-entity certificate. However, any CA michael@0: // certificate can issue a delegated OCSP response signing certificate, so michael@0: // we can't require the EKU be explicitly included for CA certificates. michael@0: if (!foundOCSPSigning && requiredEKU == SEC_OID_OCSP_RESPONDER) { michael@0: PR_SetError(SEC_ERROR_INADEQUATE_CERT_TYPE, 0); michael@0: return RecoverableError; michael@0: } michael@0: } michael@0: michael@0: return Success; michael@0: } michael@0: michael@0: Result michael@0: CheckIssuerIndependentProperties(TrustDomain& trustDomain, michael@0: BackCert& cert, michael@0: PRTime time, michael@0: EndEntityOrCA endEntityOrCA, michael@0: KeyUsage requiredKeyUsageIfPresent, michael@0: SECOidTag requiredEKUIfPresent, michael@0: SECOidTag requiredPolicy, michael@0: unsigned int subCACount, michael@0: /*optional out*/ TrustDomain::TrustLevel* trustLevelOut) michael@0: { michael@0: Result rv; michael@0: michael@0: TrustDomain::TrustLevel trustLevel; michael@0: rv = MapSECStatus(trustDomain.GetCertTrust(endEntityOrCA, michael@0: requiredPolicy, michael@0: cert.GetNSSCert(), michael@0: &trustLevel)); michael@0: if (rv != Success) { michael@0: return rv; michael@0: } michael@0: if (trustLevel == TrustDomain::ActivelyDistrusted) { michael@0: PORT_SetError(SEC_ERROR_UNTRUSTED_CERT); michael@0: return RecoverableError; michael@0: } michael@0: if (trustLevel != TrustDomain::TrustAnchor && michael@0: trustLevel != TrustDomain::InheritsTrust) { michael@0: // The TrustDomain returned a trust level that we weren't expecting. michael@0: PORT_SetError(PR_INVALID_STATE_ERROR); michael@0: return FatalError; michael@0: } michael@0: if (trustLevelOut) { michael@0: *trustLevelOut = trustLevel; michael@0: } michael@0: michael@0: bool isTrustAnchor = endEntityOrCA == MustBeCA && michael@0: trustLevel == TrustDomain::TrustAnchor; michael@0: michael@0: PLArenaPool* arena = cert.GetArena(); michael@0: if (!arena) { michael@0: return FatalError; michael@0: } michael@0: michael@0: // 4.2.1.1. Authority Key Identifier is ignored (see bug 965136). michael@0: michael@0: // 4.2.1.2. Subject Key Identifier is ignored (see bug 965136). michael@0: michael@0: // 4.2.1.3. Key Usage michael@0: rv = CheckKeyUsage(endEntityOrCA, cert.encodedKeyUsage, michael@0: requiredKeyUsageIfPresent); michael@0: if (rv != Success) { michael@0: return rv; michael@0: } michael@0: michael@0: // 4.2.1.4. Certificate Policies michael@0: rv = CheckCertificatePolicies(cert, endEntityOrCA, isTrustAnchor, michael@0: requiredPolicy); michael@0: if (rv != Success) { michael@0: return rv; michael@0: } michael@0: michael@0: // 4.2.1.5. Policy Mappings are not supported; see the documentation about michael@0: // policy enforcement in pkix.h. michael@0: michael@0: // 4.2.1.6. Subject Alternative Name dealt with during name constraint michael@0: // checking and during name verification (CERT_VerifyCertName). michael@0: michael@0: // 4.2.1.7. Issuer Alternative Name is not something that needs checking. michael@0: michael@0: // 4.2.1.8. Subject Directory Attributes is not something that needs michael@0: // checking. michael@0: michael@0: // 4.2.1.9. Basic Constraints. michael@0: rv = CheckBasicConstraints(cert, endEntityOrCA, isTrustAnchor, subCACount); michael@0: if (rv != Success) { michael@0: return rv; michael@0: } michael@0: michael@0: // 4.2.1.10. Name Constraints is dealt with in during path building. michael@0: michael@0: // 4.2.1.11. Policy Constraints are implicitly supported; see the michael@0: // documentation about policy enforcement in pkix.h. michael@0: michael@0: // 4.2.1.12. Extended Key Usage michael@0: rv = CheckExtendedKeyUsage(endEntityOrCA, cert.encodedExtendedKeyUsage, michael@0: requiredEKUIfPresent); michael@0: if (rv != Success) { michael@0: return rv; michael@0: } michael@0: michael@0: // 4.2.1.13. CRL Distribution Points is not supported, though the michael@0: // TrustDomain's CheckRevocation method may parse it and process it michael@0: // on its own. michael@0: michael@0: // 4.2.1.14. Inhibit anyPolicy is implicitly supported; see the documentation michael@0: // about policy enforcement in pkix.h. michael@0: michael@0: // IMPORTANT: This check must come after the other checks in order for error michael@0: // ranking to work correctly. michael@0: rv = CheckTimes(cert.GetNSSCert(), time); michael@0: if (rv != Success) { michael@0: return rv; michael@0: } michael@0: michael@0: return Success; michael@0: } michael@0: michael@0: } } // namespace mozilla::pkix