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 01:29:21 +04:00
|
|
|
#include "pkix/pkix.h"
|
2014-02-03 09:21:00 +04:00
|
|
|
|
|
|
|
#include <limits>
|
|
|
|
|
|
|
|
#include "pkixcheck.h"
|
|
|
|
|
2014-03-21 01:29:21 +04:00
|
|
|
namespace mozilla { namespace pkix {
|
2014-02-03 09:21:00 +04:00
|
|
|
|
|
|
|
static Result BuildForward(TrustDomain& trustDomain,
|
2014-07-04 03:59:42 +04:00
|
|
|
const BackCert& subject,
|
2014-02-03 09:21:00 +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
|
|
|
/*optional*/ const SECItem* stapledOCSPResponse,
|
2014-02-03 09:21:00 +04:00
|
|
|
unsigned int subCACount,
|
|
|
|
/*out*/ ScopedCERTCertList& results);
|
|
|
|
|
2014-07-03 02:03:58 +04:00
|
|
|
class PathBuildingStep
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
PathBuildingStep(TrustDomain& trustDomain, const BackCert& subject,
|
|
|
|
PRTime time, EndEntityOrCA endEntityOrCA,
|
|
|
|
KeyPurposeId requiredEKUIfPresent,
|
|
|
|
const CertPolicyId& requiredPolicy,
|
|
|
|
/*optional*/ const SECItem* stapledOCSPResponse,
|
|
|
|
unsigned int subCACount,
|
|
|
|
/*out*/ ScopedCERTCertList& results)
|
|
|
|
: trustDomain(trustDomain)
|
|
|
|
, subject(subject)
|
|
|
|
, time(time)
|
|
|
|
, endEntityOrCA(endEntityOrCA)
|
|
|
|
, requiredEKUIfPresent(requiredEKUIfPresent)
|
|
|
|
, requiredPolicy(requiredPolicy)
|
|
|
|
, stapledOCSPResponse(stapledOCSPResponse)
|
|
|
|
, subCACount(subCACount)
|
|
|
|
, results(results)
|
|
|
|
, result(SEC_ERROR_LIBRARY_FAILURE)
|
|
|
|
, resultWasSet(false)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
SECStatus Build(const SECItem& potentialIssuerDER,
|
|
|
|
/*out*/ bool& keepGoing);
|
|
|
|
|
|
|
|
Result CheckResult() const;
|
|
|
|
|
|
|
|
private:
|
|
|
|
TrustDomain& trustDomain;
|
|
|
|
const BackCert& subject;
|
|
|
|
const PRTime time;
|
|
|
|
const EndEntityOrCA endEntityOrCA;
|
|
|
|
const KeyPurposeId requiredEKUIfPresent;
|
|
|
|
const CertPolicyId& requiredPolicy;
|
|
|
|
/*optional*/ SECItem const* const stapledOCSPResponse;
|
|
|
|
const unsigned int subCACount;
|
|
|
|
/*out*/ ScopedCERTCertList& results;
|
|
|
|
|
|
|
|
SECStatus RecordResult(PRErrorCode currentResult, /*out*/ bool& keepGoing);
|
|
|
|
PRErrorCode result;
|
|
|
|
bool resultWasSet;
|
|
|
|
|
|
|
|
PathBuildingStep(const PathBuildingStep&) /*= delete*/;
|
|
|
|
void operator=(const PathBuildingStep&) /*= delete*/;
|
|
|
|
};
|
|
|
|
|
|
|
|
SECStatus
|
|
|
|
PathBuildingStep::RecordResult(PRErrorCode newResult,
|
|
|
|
/*out*/ bool& keepGoing)
|
|
|
|
{
|
|
|
|
if (newResult == SEC_ERROR_UNTRUSTED_CERT) {
|
|
|
|
newResult = SEC_ERROR_UNTRUSTED_ISSUER;
|
|
|
|
}
|
|
|
|
if (resultWasSet) {
|
|
|
|
if (result == 0) {
|
|
|
|
PR_NOT_REACHED("RecordResult called after finding a chain");
|
|
|
|
PR_SetError(SEC_ERROR_LIBRARY_FAILURE, 0);
|
|
|
|
return SECFailure;
|
|
|
|
}
|
|
|
|
// If every potential issuer has the same problem (e.g. expired) and/or if
|
|
|
|
// there is only one bad potential issuer, then return a more specific
|
|
|
|
// error. Otherwise, punt on trying to decide which error should be
|
|
|
|
// returned by returning the generic SEC_ERROR_UNKNOWN_ISSUER error.
|
|
|
|
if (newResult != 0 && newResult != result) {
|
|
|
|
newResult = SEC_ERROR_UNKNOWN_ISSUER;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
result = newResult;
|
|
|
|
resultWasSet = true;
|
|
|
|
keepGoing = result != 0;
|
|
|
|
return SECSuccess;
|
|
|
|
}
|
|
|
|
|
|
|
|
Result
|
|
|
|
PathBuildingStep::CheckResult() const
|
|
|
|
{
|
|
|
|
if (!resultWasSet) {
|
|
|
|
return Fail(RecoverableError, SEC_ERROR_UNKNOWN_ISSUER);
|
|
|
|
}
|
|
|
|
if (result == 0) {
|
|
|
|
return Success;
|
|
|
|
}
|
|
|
|
PR_SetError(result, 0);
|
|
|
|
return MapSECStatus(SECFailure);
|
|
|
|
}
|
|
|
|
|
2014-02-03 09:21:00 +04:00
|
|
|
// The code that executes in the inner loop of BuildForward
|
2014-07-03 02:03:58 +04:00
|
|
|
SECStatus
|
|
|
|
PathBuildingStep::Build(const SECItem& potentialIssuerDER,
|
|
|
|
/*out*/ bool& keepGoing)
|
2014-02-03 09:21:00 +04:00
|
|
|
{
|
2014-07-04 03:59:42 +04:00
|
|
|
BackCert potentialIssuer(potentialIssuerDER, &subject,
|
|
|
|
BackCert::IncludeCN::No);
|
|
|
|
Result rv = potentialIssuer.Init();
|
2014-02-03 09:21:00 +04:00
|
|
|
if (rv != Success) {
|
2014-07-03 02:03:58 +04:00
|
|
|
return RecordResult(PR_GetError(), keepGoing);
|
2014-02-03 09:21:00 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
// RFC5280 4.2.1.1. Authority Key Identifier
|
|
|
|
// RFC5280 4.2.1.2. Subject Key Identifier
|
|
|
|
|
|
|
|
// Loop prevention, done as recommended by RFC4158 Section 5.2
|
|
|
|
// TODO: this doesn't account for subjectAltNames!
|
|
|
|
// TODO(perf): This probably can and should be optimized in some way.
|
|
|
|
bool loopDetected = false;
|
2014-07-04 03:59:42 +04:00
|
|
|
for (const BackCert* prev = potentialIssuer.childCert;
|
2014-02-03 09:21:00 +04:00
|
|
|
!loopDetected && prev != nullptr; prev = prev->childCert) {
|
2014-06-04 11:03:28 +04:00
|
|
|
if (SECITEM_ItemsAreEqual(&potentialIssuer.GetSubjectPublicKeyInfo(),
|
|
|
|
&prev->GetSubjectPublicKeyInfo()) &&
|
|
|
|
SECITEM_ItemsAreEqual(&potentialIssuer.GetSubject(),
|
|
|
|
&prev->GetSubject())) {
|
2014-07-03 02:03:58 +04:00
|
|
|
// XXX: error code
|
|
|
|
return RecordResult(SEC_ERROR_UNKNOWN_ISSUER, keepGoing);
|
2014-02-03 09:21:00 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-02-11 03:25:23 +04:00
|
|
|
rv = CheckNameConstraints(potentialIssuer);
|
|
|
|
if (rv != Success) {
|
2014-07-03 02:03:58 +04:00
|
|
|
return RecordResult(PR_GetError(), keepGoing);
|
2014-02-11 03:25:23 +04:00
|
|
|
}
|
|
|
|
|
2014-06-19 11:13:20 +04:00
|
|
|
// RFC 5280, Section 4.2.1.3: "If the keyUsage extension is present, then the
|
|
|
|
// subject public key MUST NOT be used to verify signatures on certificates
|
|
|
|
// or CRLs unless the corresponding keyCertSign or cRLSign bit is set."
|
2014-04-26 03:29:26 +04:00
|
|
|
rv = BuildForward(trustDomain, potentialIssuer, time, EndEntityOrCA::MustBeCA,
|
2014-06-19 11:13:20 +04:00
|
|
|
KeyUsage::keyCertSign, requiredEKUIfPresent,
|
|
|
|
requiredPolicy, nullptr, subCACount, results);
|
2014-02-03 09:21:00 +04:00
|
|
|
if (rv != Success) {
|
2014-07-03 02:03:58 +04:00
|
|
|
return RecordResult(PR_GetError(), keepGoing);
|
2014-02-03 09:21:00 +04:00
|
|
|
}
|
|
|
|
|
2014-07-04 03:59:42 +04:00
|
|
|
SECStatus srv = trustDomain.VerifySignedData(
|
|
|
|
subject.GetSignedData(),
|
|
|
|
potentialIssuer.GetSubjectPublicKeyInfo());
|
|
|
|
if (srv != SECSuccess) {
|
2014-07-03 02:03:58 +04:00
|
|
|
return RecordResult(PR_GetError(), keepGoing);
|
2014-07-04 03:59:42 +04:00
|
|
|
}
|
|
|
|
|
2014-07-02 23:21:41 +04:00
|
|
|
CertID certID(subject.GetIssuer(), potentialIssuer.GetSubjectPublicKeyInfo(),
|
|
|
|
subject.GetSerialNumber());
|
|
|
|
srv = trustDomain.CheckRevocation(endEntityOrCA, certID, time,
|
|
|
|
stapledOCSPResponse,
|
|
|
|
subject.GetAuthorityInfoAccess());
|
|
|
|
if (srv != SECSuccess) {
|
2014-07-03 02:03:58 +04:00
|
|
|
return RecordResult(PR_GetError(), keepGoing);
|
2014-07-02 23:21:41 +04:00
|
|
|
}
|
|
|
|
|
2014-07-03 02:03:58 +04:00
|
|
|
return RecordResult(0, keepGoing);
|
2014-02-03 09:21:00 +04:00
|
|
|
}
|
|
|
|
|
2014-02-11 12:46:05 +04:00
|
|
|
// Recursively build the path from the given subject certificate to the root.
|
|
|
|
//
|
|
|
|
// Be very careful about changing the order of checks. The order is significant
|
|
|
|
// because it affects which error we return when a certificate or certificate
|
|
|
|
// chain has multiple problems. See the error ranking documentation in
|
2014-03-21 01:29:21 +04:00
|
|
|
// pkix/pkix.h.
|
2014-02-03 09:21:00 +04:00
|
|
|
static Result
|
|
|
|
BuildForward(TrustDomain& trustDomain,
|
2014-07-04 03:59:42 +04:00
|
|
|
const BackCert& subject,
|
2014-02-03 09:21:00 +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
|
|
|
/*optional*/ const SECItem* stapledOCSPResponse,
|
2014-02-03 09:21:00 +04:00
|
|
|
unsigned int subCACount,
|
|
|
|
/*out*/ ScopedCERTCertList& results)
|
|
|
|
{
|
2014-02-17 06:09:06 +04:00
|
|
|
Result rv;
|
|
|
|
|
2014-04-26 03:29:26 +04:00
|
|
|
TrustLevel trustLevel;
|
2014-04-08 20:49:36 +04:00
|
|
|
// If this is an end-entity and not a trust anchor, we defer reporting
|
|
|
|
// any error found here until after attempting to find a valid chain.
|
|
|
|
// See the explanation of error prioritization in pkix.h.
|
2014-02-17 06:09:06 +04:00
|
|
|
rv = CheckIssuerIndependentProperties(trustDomain, subject, time,
|
|
|
|
endEntityOrCA,
|
2014-06-19 11:13:20 +04:00
|
|
|
requiredKeyUsageIfPresent,
|
2014-02-25 00:37:45 +04:00
|
|
|
requiredEKUIfPresent, requiredPolicy,
|
|
|
|
subCACount, &trustLevel);
|
2014-04-08 20:49:36 +04:00
|
|
|
PRErrorCode deferredEndEntityError = 0;
|
2014-02-07 06:13:20 +04:00
|
|
|
if (rv != Success) {
|
2014-04-26 03:29:26 +04:00
|
|
|
if (endEntityOrCA == EndEntityOrCA::MustBeEndEntity &&
|
|
|
|
trustLevel != TrustLevel::TrustAnchor) {
|
2014-04-08 20:49:36 +04:00
|
|
|
deferredEndEntityError = PR_GetError();
|
|
|
|
} else {
|
2014-02-11 12:46:05 +04:00
|
|
|
return rv;
|
|
|
|
}
|
2014-02-07 06:13:20 +04:00
|
|
|
}
|
|
|
|
|
2014-04-26 03:29:26 +04:00
|
|
|
if (trustLevel == TrustLevel::TrustAnchor) {
|
2014-06-04 12:28:44 +04:00
|
|
|
// End of the recursion.
|
2014-02-06 02:49:10 +04:00
|
|
|
|
2014-06-04 12:28:44 +04:00
|
|
|
// Construct the results cert chain.
|
|
|
|
results = CERT_NewCertList();
|
|
|
|
if (!results) {
|
|
|
|
return MapSECStatus(SECFailure);
|
2014-02-06 02:49:10 +04:00
|
|
|
}
|
2014-07-04 03:59:42 +04:00
|
|
|
for (const BackCert* cert = &subject; cert; cert = cert->childCert) {
|
|
|
|
ScopedCERTCertificate
|
|
|
|
nssCert(CERT_NewTempCertificate(CERT_GetDefaultCertDB(),
|
|
|
|
const_cast<SECItem*>(&cert->GetDER()),
|
|
|
|
nullptr, false, true));
|
|
|
|
if (CERT_AddCertToListHead(results.get(), nssCert.get()) != SECSuccess) {
|
2014-06-04 12:28:44 +04:00
|
|
|
return MapSECStatus(SECFailure);
|
2014-02-06 02:49:10 +04:00
|
|
|
}
|
2014-07-04 03:59:42 +04:00
|
|
|
nssCert.release(); // nssCert is now owned by results.
|
2014-02-06 02:49:10 +04:00
|
|
|
}
|
|
|
|
|
2014-06-04 12:28:44 +04:00
|
|
|
// This must be done here, after the chain is built but before any
|
|
|
|
// revocation checks have been done.
|
|
|
|
SECStatus srv = trustDomain.IsChainValid(results.get());
|
2014-02-06 02:49:10 +04:00
|
|
|
if (srv != SECSuccess) {
|
|
|
|
return MapSECStatus(srv);
|
|
|
|
}
|
|
|
|
|
2014-06-04 12:28:44 +04:00
|
|
|
return Success;
|
2014-02-03 09:21:00 +04:00
|
|
|
}
|
|
|
|
|
2014-06-01 03:32:58 +04:00
|
|
|
if (endEntityOrCA == EndEntityOrCA::MustBeCA) {
|
|
|
|
// Avoid stack overflows and poor performance by limiting cert chain
|
|
|
|
// length.
|
|
|
|
static const unsigned int MAX_SUBCA_COUNT = 6;
|
|
|
|
if (subCACount >= MAX_SUBCA_COUNT) {
|
|
|
|
return Fail(RecoverableError, SEC_ERROR_UNKNOWN_ISSUER);
|
|
|
|
}
|
|
|
|
++subCACount;
|
|
|
|
} else {
|
|
|
|
PR_ASSERT(subCACount == 0);
|
|
|
|
}
|
|
|
|
|
2014-02-03 09:21:00 +04:00
|
|
|
// Find a trusted issuer.
|
2014-07-03 02:03:58 +04:00
|
|
|
|
|
|
|
PathBuildingStep pathBuilder(trustDomain, subject, time, endEntityOrCA,
|
|
|
|
requiredEKUIfPresent, requiredPolicy,
|
|
|
|
stapledOCSPResponse, subCACount, results);
|
|
|
|
|
2014-02-03 09:21:00 +04:00
|
|
|
// TODO(bug 965136): Add SKI/AKI matching optimizations
|
|
|
|
ScopedCERTCertList candidates;
|
2014-06-20 21:10:51 +04:00
|
|
|
if (trustDomain.FindPotentialIssuers(&subject.GetIssuer(), time,
|
2014-02-03 09:21:00 +04:00
|
|
|
candidates) != SECSuccess) {
|
|
|
|
return MapSECStatus(SECFailure);
|
|
|
|
}
|
|
|
|
if (!candidates) {
|
|
|
|
return Fail(RecoverableError, SEC_ERROR_UNKNOWN_ISSUER);
|
|
|
|
}
|
|
|
|
for (CERTCertListNode* n = CERT_LIST_HEAD(candidates);
|
|
|
|
!CERT_LIST_END(n, candidates); n = CERT_LIST_NEXT(n)) {
|
2014-07-03 02:03:58 +04:00
|
|
|
bool keepGoing;
|
|
|
|
SECStatus srv = pathBuilder.Build(n->cert->derCert, keepGoing);
|
|
|
|
if (srv != SECSuccess) {
|
|
|
|
return MapSECStatus(SECFailure);
|
2014-02-03 09:21:00 +04:00
|
|
|
}
|
2014-07-03 02:03:58 +04:00
|
|
|
if (!keepGoing) {
|
|
|
|
break;
|
2014-02-03 09:21:00 +04:00
|
|
|
}
|
2014-07-03 02:03:58 +04:00
|
|
|
}
|
2014-02-11 12:46:05 +04:00
|
|
|
|
2014-07-03 02:03:58 +04:00
|
|
|
rv = pathBuilder.CheckResult();
|
|
|
|
if (rv != Success) {
|
|
|
|
return rv;
|
2014-02-11 12:46:05 +04:00
|
|
|
}
|
|
|
|
|
2014-07-03 02:03:58 +04:00
|
|
|
// We've built a valid chain from the subject cert up to a trusted root.
|
|
|
|
// At this point, we know the results contains the complete cert chain.
|
|
|
|
|
|
|
|
// If we found a valid chain but deferred reporting an error with the
|
|
|
|
// end-entity certificate, report it now.
|
|
|
|
if (deferredEndEntityError != 0) {
|
|
|
|
return Fail(RecoverableError, deferredEndEntityError);
|
2014-02-03 09:21:00 +04:00
|
|
|
}
|
|
|
|
|
2014-07-03 02:03:58 +04:00
|
|
|
// We've built a valid chain from the subject cert up to a trusted root.
|
|
|
|
return Success;
|
2014-02-03 09:21:00 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
SECStatus
|
2014-07-04 03:59:42 +04:00
|
|
|
BuildCertChain(TrustDomain& trustDomain, const SECItem& certDER,
|
|
|
|
PRTime time, EndEntityOrCA endEntityOrCA,
|
2014-06-19 11:13:20 +04:00
|
|
|
KeyUsage requiredKeyUsageIfPresent,
|
|
|
|
KeyPurposeId requiredEKUIfPresent,
|
2014-05-16 05:59:52 +04:00
|
|
|
const CertPolicyId& requiredPolicy,
|
2014-02-17 06:09:06 +04:00
|
|
|
/*optional*/ const SECItem* stapledOCSPResponse,
|
2014-02-03 09:21:00 +04:00
|
|
|
/*out*/ ScopedCERTCertList& results)
|
|
|
|
{
|
2014-02-11 03:25:23 +04:00
|
|
|
// XXX: Support the legacy use of the subject CN field for indicating the
|
|
|
|
// domain name the certificate is valid for.
|
2014-04-26 03:29:26 +04:00
|
|
|
BackCert::IncludeCN includeCN
|
|
|
|
= endEntityOrCA == EndEntityOrCA::MustBeEndEntity &&
|
2014-05-14 12:02:34 +04:00
|
|
|
requiredEKUIfPresent == KeyPurposeId::id_kp_serverAuth
|
2014-04-26 03:29:26 +04:00
|
|
|
? BackCert::IncludeCN::Yes
|
|
|
|
: BackCert::IncludeCN::No;
|
2014-02-11 03:25:23 +04:00
|
|
|
|
2014-07-04 03:59:42 +04:00
|
|
|
BackCert cert(certDER, nullptr, includeCN);
|
|
|
|
Result rv = cert.Init();
|
2014-02-03 09:21:00 +04:00
|
|
|
if (rv != Success) {
|
|
|
|
return SECFailure;
|
|
|
|
}
|
|
|
|
|
2013-09-13 11:09:08 +04:00
|
|
|
rv = BuildForward(trustDomain, cert, time, endEntityOrCA,
|
2014-06-19 11:13:20 +04:00
|
|
|
requiredKeyUsageIfPresent, requiredEKUIfPresent,
|
2014-02-25 00:37:45 +04:00
|
|
|
requiredPolicy, stapledOCSPResponse, 0, results);
|
2014-02-03 09:21:00 +04:00
|
|
|
if (rv != Success) {
|
|
|
|
results = nullptr;
|
|
|
|
return SECFailure;
|
|
|
|
}
|
|
|
|
|
|
|
|
return SECSuccess;
|
|
|
|
}
|
|
|
|
|
2014-03-21 01:29:21 +04:00
|
|
|
} } // namespace mozilla::pkix
|