2014-02-03 09:21:00 +04:00
|
|
|
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
|
|
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
2014-05-14 17:37:25 +04:00
|
|
|
/* This code is made available to you under your choice of the following sets
|
|
|
|
* of licensing terms:
|
|
|
|
*/
|
|
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
|
|
*/
|
|
|
|
/* Copyright 2013 Mozilla Contributors
|
2014-02-03 09:21:00 +04:00
|
|
|
*
|
|
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
* you may not use this file except in compliance with the License.
|
|
|
|
* You may obtain a copy of the License at
|
|
|
|
*
|
|
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
*
|
|
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
* See the License for the specific language governing permissions and
|
|
|
|
* limitations under the License.
|
|
|
|
*/
|
|
|
|
|
2014-03-21 22:52:01 +04:00
|
|
|
#include <limits>
|
|
|
|
|
2014-05-05 20:55:57 +04:00
|
|
|
#include "pkix/bind.h"
|
2014-03-21 01:29:21 +04:00
|
|
|
#include "pkix/pkix.h"
|
2014-02-03 09:21:00 +04:00
|
|
|
#include "pkixcheck.h"
|
2014-02-07 06:13:20 +04:00
|
|
|
#include "pkixder.h"
|
2014-02-03 09:21:00 +04:00
|
|
|
#include "pkixutil.h"
|
|
|
|
|
2014-03-21 01:29:21 +04:00
|
|
|
namespace mozilla { namespace pkix {
|
2014-02-03 09:21:00 +04:00
|
|
|
|
|
|
|
Result
|
2014-07-04 03:59:42 +04:00
|
|
|
CheckValidity(const SECItem& encodedValidity, PRTime time)
|
2014-02-03 09:21:00 +04:00
|
|
|
{
|
2014-07-04 03:59:42 +04:00
|
|
|
der::Input validity;
|
|
|
|
if (validity.Init(encodedValidity.data, encodedValidity.len)
|
2014-06-25 08:48:12 +04:00
|
|
|
!= der::Success) {
|
|
|
|
return Fail(RecoverableError, SEC_ERROR_EXPIRED_CERTIFICATE);
|
|
|
|
}
|
|
|
|
PRTime notBefore;
|
2014-07-04 03:59:42 +04:00
|
|
|
if (der::TimeChoice(validity, notBefore) != der::Success) {
|
2014-06-25 08:48:12 +04:00
|
|
|
return Fail(RecoverableError, SEC_ERROR_EXPIRED_CERTIFICATE);
|
|
|
|
}
|
|
|
|
if (time < notBefore) {
|
|
|
|
return Fail(RecoverableError, SEC_ERROR_EXPIRED_CERTIFICATE);
|
|
|
|
}
|
2014-02-03 09:21:00 +04:00
|
|
|
|
2014-06-25 08:48:12 +04:00
|
|
|
PRTime notAfter;
|
2014-07-04 03:59:42 +04:00
|
|
|
if (der::TimeChoice(validity, notAfter) != der::Success) {
|
2014-06-25 08:48:12 +04:00
|
|
|
return Fail(RecoverableError, SEC_ERROR_EXPIRED_CERTIFICATE);
|
|
|
|
}
|
|
|
|
if (time > notAfter) {
|
2014-02-03 09:21:00 +04:00
|
|
|
return Fail(RecoverableError, SEC_ERROR_EXPIRED_CERTIFICATE);
|
|
|
|
}
|
|
|
|
|
2014-07-04 03:59:42 +04:00
|
|
|
if (der::End(validity) != der::Success) {
|
|
|
|
return MapSECStatus(SECFailure);
|
|
|
|
}
|
|
|
|
|
2014-02-03 09:21:00 +04:00
|
|
|
return Success;
|
|
|
|
}
|
|
|
|
|
2014-01-29 11:20:11 +04:00
|
|
|
// 4.2.1.3. Key Usage (id-ce-keyUsage)
|
2014-06-19 11:13:20 +04:00
|
|
|
|
|
|
|
// As explained in the comment in CheckKeyUsage, bit 0 is the most significant
|
|
|
|
// bit and bit 7 is the least significant bit.
|
|
|
|
inline uint8_t KeyUsageToBitMask(KeyUsage keyUsage)
|
|
|
|
{
|
|
|
|
PR_ASSERT(keyUsage != KeyUsage::noParticularKeyUsageRequired);
|
|
|
|
return 0x80u >> static_cast<uint8_t>(keyUsage);
|
|
|
|
}
|
|
|
|
|
2014-01-29 11:20:11 +04:00
|
|
|
Result
|
2014-06-19 11:13:20 +04:00
|
|
|
CheckKeyUsage(EndEntityOrCA endEntityOrCA, const SECItem* encodedKeyUsage,
|
|
|
|
KeyUsage requiredKeyUsageIfPresent)
|
2014-01-29 11:20:11 +04:00
|
|
|
{
|
|
|
|
if (!encodedKeyUsage) {
|
2014-04-24 00:38:19 +04:00
|
|
|
// TODO(bug 970196): Reject certificates that are being used to verify
|
|
|
|
// certificate signatures unless the certificate is a trust anchor, to
|
|
|
|
// reduce the chances of an end-entity certificate being abused as a CA
|
|
|
|
// certificate.
|
2014-04-26 03:29:26 +04:00
|
|
|
// if (endEntityOrCA == EndEntityOrCA::MustBeCA && !isTrustAnchor) {
|
2014-01-29 11:20:11 +04:00
|
|
|
// return Fail(RecoverableError, SEC_ERROR_INADEQUATE_KEY_USAGE);
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// TODO: Users may configure arbitrary certificates as trust anchors, not
|
|
|
|
// just roots. We should only allow a certificate without a key usage to be
|
|
|
|
// used as a CA when it is self-issued and self-signed.
|
|
|
|
return Success;
|
|
|
|
}
|
|
|
|
|
2014-06-19 11:13:20 +04:00
|
|
|
der::Input input;
|
|
|
|
if (input.Init(encodedKeyUsage->data, encodedKeyUsage->len) != der::Success) {
|
|
|
|
return Fail(RecoverableError, SEC_ERROR_INADEQUATE_KEY_USAGE);
|
|
|
|
}
|
|
|
|
der::Input value;
|
|
|
|
if (der::ExpectTagAndGetValue(input, der::BIT_STRING, value) != der::Success) {
|
|
|
|
return Fail(RecoverableError, SEC_ERROR_INADEQUATE_KEY_USAGE);
|
2014-01-29 11:20:11 +04:00
|
|
|
}
|
|
|
|
|
2014-06-19 11:13:20 +04:00
|
|
|
uint8_t numberOfPaddingBits;
|
|
|
|
if (value.Read(numberOfPaddingBits) != der::Success) {
|
|
|
|
return Fail(RecoverableError, SEC_ERROR_INADEQUATE_KEY_USAGE);
|
|
|
|
}
|
|
|
|
if (numberOfPaddingBits > 7) {
|
|
|
|
return Fail(RecoverableError, SEC_ERROR_INADEQUATE_KEY_USAGE);
|
|
|
|
}
|
2014-01-29 11:20:11 +04:00
|
|
|
|
2014-06-19 11:13:20 +04:00
|
|
|
uint8_t bits;
|
|
|
|
if (value.Read(bits) != der::Success) {
|
|
|
|
// Reject empty bit masks.
|
2014-01-29 11:20:11 +04:00
|
|
|
return Fail(RecoverableError, SEC_ERROR_INADEQUATE_KEY_USAGE);
|
|
|
|
}
|
|
|
|
|
2014-06-19 11:13:20 +04:00
|
|
|
// The most significant bit is numbered 0 (digitalSignature) and the least
|
|
|
|
// significant bit is numbered 7 (encipherOnly), and the padding is in the
|
|
|
|
// least significant bits of the last byte. The numbering of bits in a byte
|
|
|
|
// is backwards from how we usually interpret them.
|
|
|
|
//
|
|
|
|
// For example, let's say bits is encoded in one byte with of value 0xB0 and
|
|
|
|
// numberOfPaddingBits == 4. Then, bits is 10110000 in binary:
|
|
|
|
//
|
|
|
|
// bit 0 bit 3
|
|
|
|
// | |
|
|
|
|
// v v
|
|
|
|
// 10110000
|
|
|
|
// ^^^^
|
|
|
|
// |
|
|
|
|
// 4 padding bits
|
|
|
|
//
|
|
|
|
// Since bits is the last byte, we have to consider the padding by ensuring
|
|
|
|
// that the least significant 4 bits are all zero, since DER rules require
|
|
|
|
// all padding bits to be zero. Then we have to look at the bit N bits to the
|
|
|
|
// right of the most significant bit, where N is a value from the KeyUsage
|
|
|
|
// enumeration.
|
|
|
|
//
|
|
|
|
// Let's say we're interested in the keyCertSign (5) bit. We'd need to look
|
|
|
|
// at bit 5, which is zero, so keyCertSign is not asserted. (Since we check
|
|
|
|
// that the padding is all zeros, it is OK to read from the padding bits.)
|
|
|
|
//
|
|
|
|
// Let's say we're interested in the digitalSignature (0) bit. We'd need to
|
|
|
|
// look at the bit 0 (the most significant bit), which is set, so that means
|
|
|
|
// digitalSignature is asserted. Similarly, keyEncipherment (2) and
|
|
|
|
// dataEncipherment (3) are asserted.
|
|
|
|
//
|
|
|
|
// Note that since the KeyUsage enumeration is limited to values 0-7, we
|
|
|
|
// only ever need to examine the first byte test for
|
|
|
|
// requiredKeyUsageIfPresent.
|
|
|
|
|
|
|
|
if (requiredKeyUsageIfPresent != KeyUsage::noParticularKeyUsageRequired) {
|
|
|
|
// Check that the required key usage bit is set.
|
|
|
|
if ((bits & KeyUsageToBitMask(requiredKeyUsageIfPresent)) == 0) {
|
2014-01-29 11:20:11 +04:00
|
|
|
return Fail(RecoverableError, SEC_ERROR_INADEQUATE_KEY_USAGE);
|
|
|
|
}
|
2014-06-19 11:13:20 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
if (endEntityOrCA != EndEntityOrCA::MustBeCA) {
|
|
|
|
// RFC 5280 says "The keyCertSign bit is asserted when the subject public
|
|
|
|
// key is used for verifying signatures on public key certificates. If the
|
|
|
|
// keyCertSign bit is asserted, then the cA bit in the basic constraints
|
|
|
|
// extension (Section 4.2.1.9) MUST also be asserted."
|
|
|
|
if ((bits & KeyUsageToBitMask(KeyUsage::keyCertSign)) != 0) {
|
|
|
|
return Fail(RecoverableError, SEC_ERROR_INADEQUATE_KEY_USAGE);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// The padding applies to the last byte, so skip to the last byte.
|
|
|
|
while (!value.AtEnd()) {
|
|
|
|
if (value.Read(bits) != der::Success) {
|
|
|
|
return Fail(RecoverableError, SEC_ERROR_INADEQUATE_KEY_USAGE);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// All of the padding bits must be zero, according to DER rules.
|
|
|
|
uint8_t paddingMask = static_cast<uint8_t>((1 << numberOfPaddingBits) - 1);
|
|
|
|
if ((bits & paddingMask) != 0) {
|
|
|
|
return Fail(RecoverableError, SEC_ERROR_INADEQUATE_KEY_USAGE);
|
2014-01-29 11:20:11 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
return Success;
|
|
|
|
}
|
|
|
|
|
2014-02-25 00:37:45 +04:00
|
|
|
// RFC5820 4.2.1.4. Certificate Policies
|
2014-05-16 05:59:52 +04:00
|
|
|
|
2014-02-25 00:37:45 +04:00
|
|
|
// "The user-initial-policy-set contains the special value any-policy if the
|
|
|
|
// user is not concerned about certificate policy."
|
2014-05-16 05:59:52 +04:00
|
|
|
//
|
|
|
|
// id-ce OBJECT IDENTIFIER ::= {joint-iso-ccitt(2) ds(5) 29}
|
|
|
|
// id-ce-certificatePolicies OBJECT IDENTIFIER ::= { id-ce 32 }
|
|
|
|
// anyPolicy OBJECT IDENTIFIER ::= { id-ce-certificatePolicies 0 }
|
|
|
|
|
|
|
|
/*static*/ const CertPolicyId CertPolicyId::anyPolicy = {
|
|
|
|
4, { (40*2)+5, 29, 32, 0 }
|
|
|
|
};
|
|
|
|
|
|
|
|
bool CertPolicyId::IsAnyPolicy() const
|
|
|
|
{
|
|
|
|
return this == &anyPolicy ||
|
|
|
|
(numBytes == anyPolicy.numBytes &&
|
|
|
|
!memcmp(bytes, anyPolicy.bytes, anyPolicy.numBytes));
|
|
|
|
}
|
|
|
|
|
|
|
|
// PolicyInformation ::= SEQUENCE {
|
|
|
|
// policyIdentifier CertPolicyId,
|
|
|
|
// policyQualifiers SEQUENCE SIZE (1..MAX) OF
|
|
|
|
// PolicyQualifierInfo OPTIONAL }
|
|
|
|
inline der::Result
|
|
|
|
CheckPolicyInformation(der::Input& input, EndEntityOrCA endEntityOrCA,
|
|
|
|
const CertPolicyId& requiredPolicy,
|
|
|
|
/*in/out*/ bool& found)
|
|
|
|
{
|
|
|
|
if (input.MatchTLV(der::OIDTag, requiredPolicy.numBytes,
|
|
|
|
requiredPolicy.bytes)) {
|
|
|
|
found = true;
|
|
|
|
} else if (endEntityOrCA == EndEntityOrCA::MustBeCA &&
|
|
|
|
input.MatchTLV(der::OIDTag, CertPolicyId::anyPolicy.numBytes,
|
|
|
|
CertPolicyId::anyPolicy.bytes)) {
|
|
|
|
found = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// RFC 5280 Section 4.2.1.4 says "Optional qualifiers, which MAY be present,
|
|
|
|
// are not expected to change the definition of the policy." Also, it seems
|
|
|
|
// that Section 6, which defines validation, does not require any matching of
|
|
|
|
// qualifiers. Thus, doing anything with the policy qualifiers would be a
|
|
|
|
// waste of time and a source of potential incompatibilities, so we just
|
|
|
|
// ignore them.
|
|
|
|
|
|
|
|
// Skip unmatched OID and/or policyQualifiers
|
|
|
|
input.SkipToEnd();
|
|
|
|
|
|
|
|
return der::Success;
|
|
|
|
}
|
|
|
|
|
|
|
|
// certificatePolicies ::= SEQUENCE SIZE (1..MAX) OF PolicyInformation
|
2014-02-25 00:37:45 +04:00
|
|
|
Result
|
2014-05-16 05:59:52 +04:00
|
|
|
CheckCertificatePolicies(EndEntityOrCA endEntityOrCA,
|
|
|
|
const SECItem* encodedCertificatePolicies,
|
|
|
|
const SECItem* encodedInhibitAnyPolicy,
|
|
|
|
TrustLevel trustLevel,
|
|
|
|
const CertPolicyId& requiredPolicy)
|
2014-02-25 00:37:45 +04:00
|
|
|
{
|
2014-05-16 05:59:52 +04:00
|
|
|
if (requiredPolicy.numBytes == 0 ||
|
|
|
|
requiredPolicy.numBytes > sizeof requiredPolicy.bytes) {
|
|
|
|
return Fail(FatalError, SEC_ERROR_INVALID_ARGS);
|
2014-02-25 00:37:45 +04:00
|
|
|
}
|
|
|
|
|
2014-05-16 05:59:52 +04:00
|
|
|
// Ignore all policy information if the caller indicates any policy is
|
|
|
|
// acceptable. See TrustDomain::GetCertTrust and the policy part of
|
|
|
|
// BuildCertChain's documentation.
|
|
|
|
if (requiredPolicy.IsAnyPolicy()) {
|
|
|
|
return Success;
|
2014-02-25 00:37:45 +04:00
|
|
|
}
|
|
|
|
|
2014-03-28 21:00:29 +04:00
|
|
|
// Bug 989051. Until we handle inhibitAnyPolicy we will fail close when
|
|
|
|
// inhibitAnyPolicy extension is present and we need to evaluate certificate
|
|
|
|
// policies.
|
2014-05-16 05:59:52 +04:00
|
|
|
if (encodedInhibitAnyPolicy) {
|
2014-04-24 01:13:32 +04:00
|
|
|
return Fail(RecoverableError, SEC_ERROR_POLICY_VALIDATION_FAILED);
|
2014-03-28 21:00:29 +04:00
|
|
|
}
|
|
|
|
|
2014-02-25 00:37:45 +04:00
|
|
|
// The root CA certificate may omit the policies that it has been
|
|
|
|
// trusted for, so we cannot require the policies to be present in those
|
|
|
|
// certificates. Instead, the determination of which roots are trusted for
|
|
|
|
// which policies is made by the TrustDomain's GetCertTrust method.
|
2014-05-16 05:59:52 +04:00
|
|
|
if (trustLevel == TrustLevel::TrustAnchor &&
|
|
|
|
endEntityOrCA == EndEntityOrCA::MustBeCA) {
|
2014-02-25 00:37:45 +04:00
|
|
|
return Success;
|
|
|
|
}
|
|
|
|
|
2014-05-16 05:59:52 +04:00
|
|
|
if (!encodedCertificatePolicies) {
|
2014-04-24 01:13:32 +04:00
|
|
|
return Fail(RecoverableError, SEC_ERROR_POLICY_VALIDATION_FAILED);
|
2014-02-25 00:37:45 +04:00
|
|
|
}
|
|
|
|
|
2014-05-16 05:59:52 +04:00
|
|
|
bool found = false;
|
2014-02-25 00:37:45 +04:00
|
|
|
|
2014-05-16 05:59:52 +04:00
|
|
|
der::Input input;
|
|
|
|
if (input.Init(encodedCertificatePolicies->data,
|
|
|
|
encodedCertificatePolicies->len) != der::Success) {
|
|
|
|
return Fail(RecoverableError, SEC_ERROR_POLICY_VALIDATION_FAILED);
|
|
|
|
}
|
|
|
|
if (der::NestedOf(input, der::SEQUENCE, der::SEQUENCE, der::EmptyAllowed::No,
|
|
|
|
bind(CheckPolicyInformation, _1, endEntityOrCA,
|
|
|
|
requiredPolicy, ref(found))) != der::Success) {
|
|
|
|
return Fail(RecoverableError, SEC_ERROR_POLICY_VALIDATION_FAILED);
|
|
|
|
}
|
|
|
|
if (der::End(input) != der::Success) {
|
|
|
|
return Fail(RecoverableError, SEC_ERROR_POLICY_VALIDATION_FAILED);
|
|
|
|
}
|
|
|
|
if (!found) {
|
|
|
|
return Fail(RecoverableError, SEC_ERROR_POLICY_VALIDATION_FAILED);
|
2014-02-25 00:37:45 +04:00
|
|
|
}
|
|
|
|
|
2014-05-16 05:59:52 +04:00
|
|
|
return Success;
|
2014-02-25 00:37:45 +04:00
|
|
|
}
|
|
|
|
|
2014-05-05 20:55:57 +04:00
|
|
|
static const long UNLIMITED_PATH_LEN = -1; // must be less than zero
|
|
|
|
|
2014-03-21 22:52:01 +04:00
|
|
|
// BasicConstraints ::= SEQUENCE {
|
|
|
|
// cA BOOLEAN DEFAULT FALSE,
|
|
|
|
// pathLenConstraint INTEGER (0..MAX) OPTIONAL }
|
2014-05-05 20:55:57 +04:00
|
|
|
static der::Result
|
|
|
|
DecodeBasicConstraints(der::Input& input, /*out*/ bool& isCA,
|
|
|
|
/*out*/ long& pathLenConstraint)
|
2014-03-21 22:52:01 +04:00
|
|
|
{
|
2014-03-31 22:06:43 +04:00
|
|
|
// TODO(bug 989518): cA is by default false. According to DER, default
|
|
|
|
// values must not be explicitly encoded in a SEQUENCE. So, if this
|
|
|
|
// value is present and false, it is an encoding error. However, Go Daddy
|
|
|
|
// has issued many certificates with this improper encoding, so we can't
|
|
|
|
// enforce this yet (hence passing true for allowInvalidExplicitEncoding
|
|
|
|
// to der::OptionalBoolean).
|
|
|
|
if (der::OptionalBoolean(input, true, isCA) != der::Success) {
|
2014-03-21 22:52:01 +04:00
|
|
|
return der::Fail(SEC_ERROR_EXTENSION_VALUE_INVALID);
|
|
|
|
}
|
|
|
|
|
2014-05-05 20:55:57 +04:00
|
|
|
// TODO(bug 985025): If isCA is false, pathLenConstraint MUST NOT
|
|
|
|
// be included (as per RFC 5280 section 4.2.1.9), but for compatibility
|
|
|
|
// reasons, we don't check this for now.
|
|
|
|
if (OptionalInteger(input, UNLIMITED_PATH_LEN, pathLenConstraint)
|
|
|
|
!= der::Success) {
|
2014-03-21 22:52:01 +04:00
|
|
|
return der::Fail(SEC_ERROR_EXTENSION_VALUE_INVALID);
|
|
|
|
}
|
2014-05-05 20:55:57 +04:00
|
|
|
|
2014-03-21 22:52:01 +04:00
|
|
|
return der::Success;
|
|
|
|
}
|
|
|
|
|
2014-02-07 06:13:20 +04:00
|
|
|
// RFC5280 4.2.1.9. Basic Constraints (id-ce-basicConstraints)
|
|
|
|
Result
|
2014-04-26 07:27:27 +04:00
|
|
|
CheckBasicConstraints(EndEntityOrCA endEntityOrCA,
|
|
|
|
const SECItem* encodedBasicConstraints,
|
2014-06-25 12:32:06 +04:00
|
|
|
der::Version version, TrustLevel trustLevel,
|
2014-02-07 06:13:20 +04:00
|
|
|
unsigned int subCACount)
|
|
|
|
{
|
2014-05-05 20:55:57 +04:00
|
|
|
bool isCA = false;
|
|
|
|
long pathLenConstraint = UNLIMITED_PATH_LEN;
|
|
|
|
|
2014-04-26 07:27:27 +04:00
|
|
|
if (encodedBasicConstraints) {
|
2014-05-05 20:55:57 +04:00
|
|
|
der::Input input;
|
2014-04-26 07:27:27 +04:00
|
|
|
if (input.Init(encodedBasicConstraints->data,
|
|
|
|
encodedBasicConstraints->len) != der::Success) {
|
2014-05-05 20:55:57 +04:00
|
|
|
return Fail(RecoverableError, SEC_ERROR_EXTENSION_VALUE_INVALID);
|
|
|
|
}
|
|
|
|
if (der::Nested(input, der::SEQUENCE,
|
|
|
|
bind(DecodeBasicConstraints, _1, ref(isCA),
|
|
|
|
ref(pathLenConstraint))) != der::Success) {
|
|
|
|
return Fail(RecoverableError, SEC_ERROR_EXTENSION_VALUE_INVALID);
|
|
|
|
}
|
|
|
|
if (der::End(input) != der::Success) {
|
|
|
|
return Fail(RecoverableError, SEC_ERROR_EXTENSION_VALUE_INVALID);
|
2014-02-07 06:13:20 +04:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// "If the basic constraints extension is not present in a version 3
|
|
|
|
// certificate, or the extension is present but the cA boolean is not
|
|
|
|
// asserted, then the certified public key MUST NOT be used to verify
|
|
|
|
// certificate signatures."
|
|
|
|
//
|
|
|
|
// For compatibility, we must accept v1 trust anchors without basic
|
|
|
|
// constraints as CAs.
|
|
|
|
//
|
|
|
|
// TODO: add check for self-signedness?
|
2014-04-26 07:27:27 +04:00
|
|
|
if (endEntityOrCA == EndEntityOrCA::MustBeCA &&
|
|
|
|
trustLevel == TrustLevel::TrustAnchor && version == der::Version::v1) {
|
|
|
|
isCA = true;
|
2014-02-07 06:13:20 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-04-26 03:29:26 +04:00
|
|
|
if (endEntityOrCA == EndEntityOrCA::MustBeEndEntity) {
|
2014-02-07 06:13:20 +04:00
|
|
|
// CA certificates are not trusted as EE certs.
|
|
|
|
|
2014-05-05 20:55:57 +04:00
|
|
|
if (isCA) {
|
2014-02-09 03:00:32 +04:00
|
|
|
// XXX: We use SEC_ERROR_CA_CERT_INVALID here so we can distinguish
|
2014-02-07 06:13:20 +04:00
|
|
|
// this error from other errors, given that NSS does not have a "CA cert
|
|
|
|
// used as end-entity" error code since it doesn't have such a
|
|
|
|
// prohibition. We should add such an error code and stop abusing
|
2014-02-09 03:00:32 +04:00
|
|
|
// SEC_ERROR_CA_CERT_INVALID this way.
|
|
|
|
//
|
|
|
|
// Note, in particular, that this check prevents a delegated OCSP
|
|
|
|
// response signing certificate with the CA bit from successfully
|
|
|
|
// validating when we check it from pkixocsp.cpp, which is a good thing.
|
|
|
|
//
|
|
|
|
return Fail(RecoverableError, SEC_ERROR_CA_CERT_INVALID);
|
2014-02-07 06:13:20 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
return Success;
|
|
|
|
}
|
|
|
|
|
2014-04-26 03:29:26 +04:00
|
|
|
PORT_Assert(endEntityOrCA == EndEntityOrCA::MustBeCA);
|
2014-02-07 06:13:20 +04:00
|
|
|
|
|
|
|
// End-entity certificates are not allowed to act as CA certs.
|
2014-05-05 20:55:57 +04:00
|
|
|
if (!isCA) {
|
2014-02-07 06:13:20 +04:00
|
|
|
return Fail(RecoverableError, SEC_ERROR_CA_CERT_INVALID);
|
|
|
|
}
|
|
|
|
|
2014-05-05 20:55:57 +04:00
|
|
|
if (pathLenConstraint >= 0 &&
|
|
|
|
static_cast<long>(subCACount) > pathLenConstraint) {
|
|
|
|
return Fail(RecoverableError, SEC_ERROR_PATH_LEN_CONSTRAINT_INVALID);
|
2014-02-07 06:13:20 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
return Success;
|
|
|
|
}
|
|
|
|
|
2014-02-11 03:25:23 +04:00
|
|
|
// 4.2.1.10. Name Constraints
|
|
|
|
Result
|
2014-06-27 01:22:20 +04:00
|
|
|
CheckNameConstraints(const BackCert& cert)
|
2014-02-11 03:25:23 +04:00
|
|
|
{
|
2014-07-04 03:59:42 +04:00
|
|
|
if (!cert.GetNameConstraints()) {
|
2014-02-11 03:25:23 +04:00
|
|
|
return Success;
|
|
|
|
}
|
|
|
|
|
2014-06-27 01:22:20 +04:00
|
|
|
ScopedPLArenaPool arena(PORT_NewArena(DER_DEFAULT_CHUNKSIZE));
|
2014-02-11 03:25:23 +04:00
|
|
|
if (!arena) {
|
2014-06-27 01:22:20 +04:00
|
|
|
return MapSECStatus(SECFailure);
|
2014-02-11 03:25:23 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
// Owned by arena
|
|
|
|
const CERTNameConstraints* constraints =
|
2014-07-04 03:59:42 +04:00
|
|
|
CERT_DecodeNameConstraintsExtension(arena.get(), cert.GetNameConstraints());
|
2014-02-11 03:25:23 +04:00
|
|
|
if (!constraints) {
|
|
|
|
return MapSECStatus(SECFailure);
|
|
|
|
}
|
|
|
|
|
2014-06-27 01:22:20 +04:00
|
|
|
for (const BackCert* child = cert.childCert; child;
|
|
|
|
child = child->childCert) {
|
|
|
|
ScopedCERTCertificate
|
|
|
|
nssCert(CERT_NewTempCertificate(CERT_GetDefaultCertDB(),
|
|
|
|
const_cast<SECItem*>(&child->GetDER()),
|
|
|
|
nullptr, false, true));
|
|
|
|
if (!nssCert) {
|
|
|
|
return MapSECStatus(SECFailure);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool includeCN = child->includeCN == BackCert::IncludeCN::Yes;
|
|
|
|
// owned by arena
|
|
|
|
const CERTGeneralName*
|
|
|
|
names(CERT_GetConstrainedCertificateNames(nssCert.get(), arena.get(),
|
|
|
|
includeCN));
|
|
|
|
if (!names) {
|
|
|
|
return MapSECStatus(SECFailure);
|
2014-02-11 03:25:23 +04:00
|
|
|
}
|
2014-06-27 01:22:20 +04:00
|
|
|
|
2014-02-11 03:25:23 +04:00
|
|
|
CERTGeneralName* currentName = const_cast<CERTGeneralName*>(names);
|
|
|
|
do {
|
2014-06-27 01:22:20 +04:00
|
|
|
if (CERT_CheckNameSpace(arena.get(), constraints, currentName)
|
|
|
|
!= SECSuccess) {
|
2014-02-11 12:46:05 +04:00
|
|
|
// XXX: It seems like CERT_CheckNameSpace doesn't always call
|
|
|
|
// PR_SetError when it fails. We set the error code here, though this
|
|
|
|
// may be papering over some fatal errors. NSS's
|
|
|
|
// cert_VerifyCertChainOld does something similar.
|
2014-04-24 01:13:32 +04:00
|
|
|
return Fail(RecoverableError, SEC_ERROR_CERT_NOT_IN_NAME_SPACE);
|
2014-02-11 03:25:23 +04:00
|
|
|
}
|
|
|
|
currentName = CERT_GetNextGeneralName(currentName);
|
|
|
|
} while (currentName != names);
|
|
|
|
}
|
|
|
|
|
|
|
|
return Success;
|
|
|
|
}
|
|
|
|
|
2014-02-09 03:00:32 +04:00
|
|
|
// 4.2.1.12. Extended Key Usage (id-ce-extKeyUsage)
|
2014-05-14 12:02:34 +04:00
|
|
|
|
|
|
|
static der::Result
|
|
|
|
MatchEKU(der::Input& value, KeyPurposeId requiredEKU,
|
|
|
|
EndEntityOrCA endEntityOrCA, /*in/out*/ bool& found,
|
|
|
|
/*in/out*/ bool& foundOCSPSigning)
|
2014-02-09 03:00:32 +04:00
|
|
|
{
|
2014-05-14 12:02:34 +04:00
|
|
|
// See Section 5.9 of "A Layman's Guide to a Subset of ASN.1, BER, and DER"
|
|
|
|
// for a description of ASN.1 DER encoding of OIDs.
|
|
|
|
|
|
|
|
// id-pkix OBJECT IDENTIFIER ::=
|
|
|
|
// { iso(1) identified-organization(3) dod(6) internet(1)
|
|
|
|
// security(5) mechanisms(5) pkix(7) }
|
|
|
|
// id-kp OBJECT IDENTIFIER ::= { id-pkix 3 }
|
|
|
|
// id-kp-serverAuth OBJECT IDENTIFIER ::= { id-kp 1 }
|
|
|
|
// id-kp-clientAuth OBJECT IDENTIFIER ::= { id-kp 2 }
|
|
|
|
// id-kp-codeSigning OBJECT IDENTIFIER ::= { id-kp 3 }
|
|
|
|
// id-kp-emailProtection OBJECT IDENTIFIER ::= { id-kp 4 }
|
|
|
|
// id-kp-OCSPSigning OBJECT IDENTIFIER ::= { id-kp 9 }
|
|
|
|
static const uint8_t server[] = { (40*1)+3, 6, 1, 5, 5, 7, 3, 1 };
|
|
|
|
static const uint8_t client[] = { (40*1)+3, 6, 1, 5, 5, 7, 3, 2 };
|
|
|
|
static const uint8_t code [] = { (40*1)+3, 6, 1, 5, 5, 7, 3, 3 };
|
|
|
|
static const uint8_t email [] = { (40*1)+3, 6, 1, 5, 5, 7, 3, 4 };
|
|
|
|
static const uint8_t ocsp [] = { (40*1)+3, 6, 1, 5, 5, 7, 3, 9 };
|
|
|
|
|
|
|
|
// id-Netscape OBJECT IDENTIFIER ::= { 2 16 840 1 113730 }
|
|
|
|
// id-Netscape-policy OBJECT IDENTIFIER ::= { id-Netscape 4 }
|
|
|
|
// id-Netscape-stepUp OBJECT IDENTIFIER ::= { id-Netscape-policy 1 }
|
|
|
|
static const uint8_t serverStepUp[] =
|
|
|
|
{ (40*2)+16, 128+6,72, 1, 128+6,128+120,66, 4, 1 };
|
|
|
|
|
|
|
|
bool match = false;
|
|
|
|
|
|
|
|
if (!found) {
|
|
|
|
switch (requiredEKU) {
|
|
|
|
case KeyPurposeId::id_kp_serverAuth:
|
|
|
|
// Treat CA certs with step-up OID as also having SSL server type.
|
|
|
|
// Comodo has issued certificates that require this behavior that don't
|
|
|
|
// expire until June 2020! TODO(bug 982932): Limit this exception to
|
|
|
|
// old certificates.
|
2014-05-30 09:09:45 +04:00
|
|
|
match = value.MatchRest(server) ||
|
2014-05-14 12:02:34 +04:00
|
|
|
(endEntityOrCA == EndEntityOrCA::MustBeCA &&
|
2014-05-30 09:09:45 +04:00
|
|
|
value.MatchRest(serverStepUp));
|
2014-05-14 12:02:34 +04:00
|
|
|
break;
|
|
|
|
|
|
|
|
case KeyPurposeId::id_kp_clientAuth:
|
2014-05-30 09:09:45 +04:00
|
|
|
match = value.MatchRest(client);
|
2014-05-14 12:02:34 +04:00
|
|
|
break;
|
|
|
|
|
|
|
|
case KeyPurposeId::id_kp_codeSigning:
|
2014-05-30 09:09:45 +04:00
|
|
|
match = value.MatchRest(code);
|
2014-05-14 12:02:34 +04:00
|
|
|
break;
|
|
|
|
|
|
|
|
case KeyPurposeId::id_kp_emailProtection:
|
2014-05-30 09:09:45 +04:00
|
|
|
match = value.MatchRest(email);
|
2014-05-14 12:02:34 +04:00
|
|
|
break;
|
|
|
|
|
|
|
|
case KeyPurposeId::id_kp_OCSPSigning:
|
2014-05-30 09:09:45 +04:00
|
|
|
match = value.MatchRest(ocsp);
|
2014-05-14 12:02:34 +04:00
|
|
|
break;
|
|
|
|
|
|
|
|
case KeyPurposeId::anyExtendedKeyUsage:
|
|
|
|
PR_NOT_REACHED("anyExtendedKeyUsage should start with found==true");
|
|
|
|
return der::Fail(SEC_ERROR_LIBRARY_FAILURE);
|
|
|
|
|
|
|
|
default:
|
|
|
|
PR_NOT_REACHED("unrecognized EKU");
|
|
|
|
return der::Fail(SEC_ERROR_LIBRARY_FAILURE);
|
|
|
|
}
|
|
|
|
}
|
2014-02-09 03:00:32 +04:00
|
|
|
|
2014-05-14 12:02:34 +04:00
|
|
|
if (match) {
|
2014-05-30 09:09:45 +04:00
|
|
|
found = true;
|
|
|
|
if (requiredEKU == KeyPurposeId::id_kp_OCSPSigning) {
|
|
|
|
foundOCSPSigning = true;
|
2014-05-14 12:02:34 +04:00
|
|
|
}
|
2014-05-30 09:09:45 +04:00
|
|
|
} else if (value.MatchRest(ocsp)) {
|
2014-05-14 12:02:34 +04:00
|
|
|
foundOCSPSigning = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
value.SkipToEnd(); // ignore unmatched OIDs.
|
|
|
|
|
|
|
|
return der::Success;
|
|
|
|
}
|
|
|
|
|
|
|
|
Result
|
|
|
|
CheckExtendedKeyUsage(EndEntityOrCA endEntityOrCA,
|
|
|
|
const SECItem* encodedExtendedKeyUsage,
|
|
|
|
KeyPurposeId requiredEKU)
|
|
|
|
{
|
2014-02-09 03:00:32 +04:00
|
|
|
// XXX: We're using SEC_ERROR_INADEQUATE_CERT_TYPE here so that callers can
|
|
|
|
// distinguish EKU mismatch from KU mismatch from basic constraints mismatch.
|
|
|
|
// We should probably add a new error code that is more clear for this type
|
|
|
|
// of problem.
|
|
|
|
|
|
|
|
bool foundOCSPSigning = false;
|
|
|
|
|
2014-05-14 12:02:34 +04:00
|
|
|
if (encodedExtendedKeyUsage) {
|
|
|
|
bool found = requiredEKU == KeyPurposeId::anyExtendedKeyUsage;
|
2014-02-09 03:00:32 +04:00
|
|
|
|
2014-05-14 12:02:34 +04:00
|
|
|
der::Input input;
|
|
|
|
if (input.Init(encodedExtendedKeyUsage->data,
|
|
|
|
encodedExtendedKeyUsage->len) != der::Success) {
|
|
|
|
return Fail(RecoverableError, SEC_ERROR_INADEQUATE_CERT_TYPE);
|
|
|
|
}
|
|
|
|
if (der::NestedOf(input, der::SEQUENCE, der::OIDTag, der::EmptyAllowed::No,
|
|
|
|
bind(MatchEKU, _1, requiredEKU, endEntityOrCA,
|
|
|
|
ref(found), ref(foundOCSPSigning)))
|
|
|
|
!= der::Success) {
|
|
|
|
return Fail(RecoverableError, SEC_ERROR_INADEQUATE_CERT_TYPE);
|
|
|
|
}
|
|
|
|
if (der::End(input) != der::Success) {
|
|
|
|
return Fail(RecoverableError, SEC_ERROR_INADEQUATE_CERT_TYPE);
|
2014-02-09 03:00:32 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
// If the EKU extension was included, then the required EKU must be in the
|
|
|
|
// list.
|
|
|
|
if (!found) {
|
2014-04-24 01:13:32 +04:00
|
|
|
return Fail(RecoverableError, SEC_ERROR_INADEQUATE_CERT_TYPE);
|
2014-02-09 03:00:32 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// pkixocsp.cpp depends on the following additional checks.
|
|
|
|
|
2014-04-26 03:29:26 +04:00
|
|
|
if (endEntityOrCA == EndEntityOrCA::MustBeEndEntity) {
|
2014-02-09 03:00:32 +04:00
|
|
|
// When validating anything other than an delegated OCSP signing cert,
|
|
|
|
// reject any cert that also claims to be an OCSP responder, because such
|
|
|
|
// a cert does not make sense. For example, if an SSL certificate were to
|
|
|
|
// assert id-kp-OCSPSigning then it could sign OCSP responses for itself,
|
|
|
|
// if not for this check.
|
2014-04-10 21:15:02 +04:00
|
|
|
// That said, we accept CA certificates with id-kp-OCSPSigning because
|
|
|
|
// some CAs in Mozilla's CA program have issued such intermediate
|
|
|
|
// certificates, and because some CAs have reported some Microsoft server
|
|
|
|
// software wrongly requires CA certificates to have id-kp-OCSPSigning.
|
|
|
|
// Allowing this exception does not cause any security issues because we
|
|
|
|
// require delegated OCSP response signing certificates to be end-entity
|
|
|
|
// certificates.
|
2014-05-14 12:02:34 +04:00
|
|
|
if (foundOCSPSigning && requiredEKU != KeyPurposeId::id_kp_OCSPSigning) {
|
|
|
|
return Fail(RecoverableError, SEC_ERROR_INADEQUATE_CERT_TYPE);
|
2014-02-09 03:00:32 +04:00
|
|
|
}
|
|
|
|
// http://tools.ietf.org/html/rfc6960#section-4.2.2.2:
|
|
|
|
// "OCSP signing delegation SHALL be designated by the inclusion of
|
|
|
|
// id-kp-OCSPSigning in an extended key usage certificate extension
|
|
|
|
// included in the OCSP response signer's certificate."
|
|
|
|
//
|
|
|
|
// id-kp-OCSPSigning is the only EKU that isn't implicitly assumed when the
|
|
|
|
// EKU extension is missing from an end-entity certificate. However, any CA
|
|
|
|
// certificate can issue a delegated OCSP response signing certificate, so
|
|
|
|
// we can't require the EKU be explicitly included for CA certificates.
|
2014-05-14 12:02:34 +04:00
|
|
|
if (!foundOCSPSigning && requiredEKU == KeyPurposeId::id_kp_OCSPSigning) {
|
|
|
|
return Fail(RecoverableError, SEC_ERROR_INADEQUATE_CERT_TYPE);
|
2014-04-10 21:15:02 +04:00
|
|
|
}
|
2014-02-09 03:00:32 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
return Success;
|
|
|
|
}
|
|
|
|
|
2014-02-07 06:13:20 +04:00
|
|
|
Result
|
2014-02-17 06:09:06 +04:00
|
|
|
CheckIssuerIndependentProperties(TrustDomain& trustDomain,
|
2014-07-04 03:59:42 +04:00
|
|
|
const BackCert& cert,
|
2014-02-17 06:09:06 +04:00
|
|
|
PRTime time,
|
|
|
|
EndEntityOrCA endEntityOrCA,
|
2014-06-19 11:13:20 +04:00
|
|
|
KeyUsage requiredKeyUsageIfPresent,
|
2014-05-14 12:02:34 +04:00
|
|
|
KeyPurposeId requiredEKUIfPresent,
|
2014-05-16 05:59:52 +04:00
|
|
|
const CertPolicyId& requiredPolicy,
|
2014-02-17 06:09:06 +04:00
|
|
|
unsigned int subCACount,
|
2014-04-26 03:29:26 +04:00
|
|
|
/*optional out*/ TrustLevel* trustLevelOut)
|
2014-02-07 06:13:20 +04:00
|
|
|
{
|
2014-02-17 06:09:06 +04:00
|
|
|
Result rv;
|
|
|
|
|
2014-04-26 03:29:26 +04:00
|
|
|
TrustLevel trustLevel;
|
2014-06-03 21:47:25 +04:00
|
|
|
rv = MapSECStatus(trustDomain.GetCertTrust(endEntityOrCA, requiredPolicy,
|
|
|
|
cert.GetDER(), &trustLevel));
|
2014-02-17 06:09:06 +04:00
|
|
|
if (rv != Success) {
|
|
|
|
return rv;
|
|
|
|
}
|
2014-04-26 03:29:26 +04:00
|
|
|
if (trustLevel == TrustLevel::ActivelyDistrusted) {
|
2014-04-24 01:13:32 +04:00
|
|
|
return Fail(RecoverableError, SEC_ERROR_UNTRUSTED_CERT);
|
2014-02-17 06:09:06 +04:00
|
|
|
}
|
2014-04-26 03:29:26 +04:00
|
|
|
if (trustLevel != TrustLevel::TrustAnchor &&
|
|
|
|
trustLevel != TrustLevel::InheritsTrust) {
|
2014-02-17 06:09:06 +04:00
|
|
|
// The TrustDomain returned a trust level that we weren't expecting.
|
|
|
|
PORT_SetError(PR_INVALID_STATE_ERROR);
|
|
|
|
return FatalError;
|
|
|
|
}
|
|
|
|
if (trustLevelOut) {
|
|
|
|
*trustLevelOut = trustLevel;
|
|
|
|
}
|
|
|
|
|
2014-02-25 00:37:45 +04:00
|
|
|
// 4.2.1.1. Authority Key Identifier is ignored (see bug 965136).
|
|
|
|
|
|
|
|
// 4.2.1.2. Subject Key Identifier is ignored (see bug 965136).
|
2014-01-29 11:20:11 +04:00
|
|
|
|
2014-02-25 00:37:45 +04:00
|
|
|
// 4.2.1.3. Key Usage
|
2014-07-04 03:59:42 +04:00
|
|
|
rv = CheckKeyUsage(endEntityOrCA, cert.GetKeyUsage(),
|
2014-06-19 11:13:20 +04:00
|
|
|
requiredKeyUsageIfPresent);
|
2014-01-29 11:20:11 +04:00
|
|
|
if (rv != Success) {
|
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
|
2014-02-07 06:13:20 +04:00
|
|
|
// 4.2.1.4. Certificate Policies
|
2014-07-04 03:59:42 +04:00
|
|
|
rv = CheckCertificatePolicies(endEntityOrCA, cert.GetCertificatePolicies(),
|
|
|
|
cert.GetInhibitAnyPolicy(), trustLevel,
|
2014-02-25 00:37:45 +04:00
|
|
|
requiredPolicy);
|
|
|
|
if (rv != Success) {
|
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
|
|
|
|
// 4.2.1.5. Policy Mappings are not supported; see the documentation about
|
|
|
|
// policy enforcement in pkix.h.
|
|
|
|
|
|
|
|
// 4.2.1.6. Subject Alternative Name dealt with during name constraint
|
|
|
|
// checking and during name verification (CERT_VerifyCertName).
|
|
|
|
|
|
|
|
// 4.2.1.7. Issuer Alternative Name is not something that needs checking.
|
|
|
|
|
|
|
|
// 4.2.1.8. Subject Directory Attributes is not something that needs
|
|
|
|
// checking.
|
|
|
|
|
|
|
|
// 4.2.1.9. Basic Constraints.
|
2014-07-04 03:59:42 +04:00
|
|
|
rv = CheckBasicConstraints(endEntityOrCA, cert.GetBasicConstraints(),
|
|
|
|
cert.GetVersion(), trustLevel, subCACount);
|
2014-02-07 06:13:20 +04:00
|
|
|
if (rv != Success) {
|
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
|
2014-02-25 00:37:45 +04:00
|
|
|
// 4.2.1.10. Name Constraints is dealt with in during path building.
|
|
|
|
|
|
|
|
// 4.2.1.11. Policy Constraints are implicitly supported; see the
|
|
|
|
// documentation about policy enforcement in pkix.h.
|
2013-09-13 11:09:08 +04:00
|
|
|
|
2014-02-07 06:13:20 +04:00
|
|
|
// 4.2.1.12. Extended Key Usage
|
2014-07-04 03:59:42 +04:00
|
|
|
rv = CheckExtendedKeyUsage(endEntityOrCA, cert.GetExtKeyUsage(),
|
2014-02-09 03:00:32 +04:00
|
|
|
requiredEKUIfPresent);
|
|
|
|
if (rv != Success) {
|
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
|
2014-02-25 00:37:45 +04:00
|
|
|
// 4.2.1.13. CRL Distribution Points is not supported, though the
|
|
|
|
// TrustDomain's CheckRevocation method may parse it and process it
|
|
|
|
// on its own.
|
|
|
|
|
|
|
|
// 4.2.1.14. Inhibit anyPolicy is implicitly supported; see the documentation
|
|
|
|
// about policy enforcement in pkix.h.
|
2014-02-07 06:13:20 +04:00
|
|
|
|
2014-02-11 12:46:05 +04:00
|
|
|
// IMPORTANT: This check must come after the other checks in order for error
|
|
|
|
// ranking to work correctly.
|
2014-07-04 03:59:42 +04:00
|
|
|
rv = CheckValidity(cert.GetValidity(), time);
|
2014-02-11 12:46:05 +04:00
|
|
|
if (rv != Success) {
|
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
|
2014-02-07 06:13:20 +04:00
|
|
|
return Success;
|
|
|
|
}
|
|
|
|
|
2014-03-21 01:29:21 +04:00
|
|
|
} } // namespace mozilla::pkix
|