зеркало из https://github.com/mozilla/gecko-dev.git
Bug 878932, Part 1: Add OCSP response parsing & validation to insanity::pkix, r=keeler
--HG-- extra : rebase_source : 23771eaf97f67e5feb69d50a0c96dd4da31ae964 extra : source : b0511882e4c94c0960ef8533b381e8d72706172e
This commit is contained in:
Родитель
2cc043e6eb
Коммит
3a9c1abfb2
|
@ -9,6 +9,7 @@ UNIFIED_SOURCES += [
|
|||
'../insanity/lib/pkixcheck.cpp',
|
||||
'../insanity/lib/pkixder.cpp',
|
||||
'../insanity/lib/pkixkey.cpp',
|
||||
'../insanity/lib/pkixocsp.cpp',
|
||||
'CertVerifier.cpp',
|
||||
'NSSCertDBTrustDomain.cpp',
|
||||
]
|
||||
|
|
|
@ -29,6 +29,7 @@ SECStatus BuildCertChain(TrustDomain& trustDomain,
|
|||
EndEntityOrCA endEntityOrCA,
|
||||
/*optional*/ KeyUsages requiredKeyUsagesIfPresent,
|
||||
/*optional*/ SECOidTag requiredEKUIfPresent,
|
||||
/*optional*/ const SECItem* stapledOCSPResponse,
|
||||
/*out*/ ScopedCERTCertList& results);
|
||||
|
||||
// Verify the given signed data using the public key of the given certificate.
|
||||
|
@ -37,6 +38,16 @@ SECStatus VerifySignedData(const CERTSignedData* sd,
|
|||
const CERTCertificate* cert,
|
||||
void* pkcs11PinArg);
|
||||
|
||||
SECItem* CreateEncodedOCSPRequest(PLArenaPool* arena,
|
||||
const CERTCertificate* cert,
|
||||
const CERTCertificate* issuerCert);
|
||||
|
||||
SECStatus VerifyEncodedOCSPResponse(TrustDomain& trustDomain,
|
||||
const CERTCertificate* cert,
|
||||
CERTCertificate* issuerCert,
|
||||
PRTime time,
|
||||
const SECItem* encodedResponse);
|
||||
|
||||
} } // namespace insanity::pkix
|
||||
|
||||
#endif // insanity_pkix__pkix_h
|
||||
|
|
|
@ -79,6 +79,14 @@ public:
|
|||
virtual SECStatus VerifySignedData(const CERTSignedData* signedData,
|
||||
const CERTCertificate* cert) = 0;
|
||||
|
||||
// issuerCertToDup is only non-const so CERT_DupCertificate can be called on
|
||||
// it.
|
||||
virtual SECStatus CheckRevocation(EndEntityOrCA endEntityOrCA,
|
||||
const CERTCertificate* cert,
|
||||
/*const*/ CERTCertificate* issuerCertToDup,
|
||||
PRTime time,
|
||||
/*optional*/ const SECItem* stapledOCSPresponse) = 0;
|
||||
|
||||
protected:
|
||||
TrustDomain() { }
|
||||
|
||||
|
|
|
@ -96,6 +96,7 @@ static Result BuildForward(TrustDomain& trustDomain,
|
|||
EndEntityOrCA endEntityOrCA,
|
||||
KeyUsages requiredKeyUsagesIfPresent,
|
||||
SECOidTag requiredEKUIfPresent,
|
||||
/*optional*/ const SECItem* stapledOCSPResponse,
|
||||
unsigned int subCACount,
|
||||
/*out*/ ScopedCERTCertList& results);
|
||||
|
||||
|
@ -107,6 +108,7 @@ BuildForwardInner(TrustDomain& trustDomain,
|
|||
EndEntityOrCA endEntityOrCA,
|
||||
SECOidTag requiredEKUIfPresent,
|
||||
CERTCertificate* potentialIssuerCertToDup,
|
||||
/*optional*/ const SECItem* stapledOCSPResponse,
|
||||
unsigned int subCACount,
|
||||
ScopedCERTCertList& results)
|
||||
{
|
||||
|
@ -136,11 +138,6 @@ BuildForwardInner(TrustDomain& trustDomain,
|
|||
}
|
||||
}
|
||||
|
||||
rv = CheckTimes(potentialIssuer.GetNSSCert(), time);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
rv = CheckNameConstraints(potentialIssuer);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
|
@ -152,10 +149,9 @@ BuildForwardInner(TrustDomain& trustDomain,
|
|||
} else {
|
||||
PR_ASSERT(newSubCACount == 0);
|
||||
}
|
||||
|
||||
rv = BuildForward(trustDomain, potentialIssuer, time, MustBeCA,
|
||||
KU_KEY_CERT_SIGN, requiredEKUIfPresent,
|
||||
newSubCACount, results);
|
||||
nullptr, newSubCACount, results);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
|
@ -176,6 +172,7 @@ BuildForward(TrustDomain& trustDomain,
|
|||
EndEntityOrCA endEntityOrCA,
|
||||
KeyUsages requiredKeyUsagesIfPresent,
|
||||
SECOidTag requiredEKUIfPresent,
|
||||
/*optional*/ const SECItem* stapledOCSPResponse,
|
||||
unsigned int subCACount,
|
||||
/*out*/ ScopedCERTCertList& results)
|
||||
{
|
||||
|
@ -186,26 +183,15 @@ BuildForward(TrustDomain& trustDomain,
|
|||
return RecoverableError;
|
||||
}
|
||||
|
||||
TrustDomain::TrustLevel trustLevel;
|
||||
Result rv = MapSECStatus(trustDomain.GetCertTrust(endEntityOrCA,
|
||||
subject.GetNSSCert(),
|
||||
&trustLevel));
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
if (trustLevel == TrustDomain::ActivelyDistrusted) {
|
||||
return Fail(RecoverableError, SEC_ERROR_UNTRUSTED_CERT);
|
||||
}
|
||||
if (trustLevel != TrustDomain::TrustAnchor &&
|
||||
trustLevel != TrustDomain::InheritsTrust) {
|
||||
// The TrustDomain returned a trust level that we weren't expecting.
|
||||
return Fail(FatalError, PR_INVALID_STATE_ERROR);
|
||||
}
|
||||
Result rv;
|
||||
|
||||
rv = CheckExtensions(subject, endEntityOrCA,
|
||||
trustLevel == TrustDomain::TrustAnchor,
|
||||
requiredKeyUsagesIfPresent, requiredEKUIfPresent,
|
||||
subCACount);
|
||||
TrustDomain::TrustLevel trustLevel;
|
||||
|
||||
rv = CheckIssuerIndependentProperties(trustDomain, subject, time,
|
||||
endEntityOrCA,
|
||||
requiredKeyUsagesIfPresent,
|
||||
requiredEKUIfPresent, subCACount,
|
||||
&trustLevel);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
|
@ -237,8 +223,17 @@ BuildForward(TrustDomain& trustDomain,
|
|||
!CERT_LIST_END(n, candidates); n = CERT_LIST_NEXT(n)) {
|
||||
rv = BuildForwardInner(trustDomain, subject, time, endEntityOrCA,
|
||||
requiredEKUIfPresent,
|
||||
n->cert, subCACount, results);
|
||||
n->cert, stapledOCSPResponse, subCACount,
|
||||
results);
|
||||
if (rv == Success) {
|
||||
SECStatus srv = trustDomain.CheckRevocation(endEntityOrCA,
|
||||
subject.GetNSSCert(),
|
||||
n->cert, time,
|
||||
stapledOCSPResponse);
|
||||
if (srv != SECSuccess) {
|
||||
return MapSECStatus(SECFailure);
|
||||
}
|
||||
|
||||
// We found a trusted issuer. At this point, we know the cert is valid
|
||||
return subject.PrependNSSCertToList(results.get());
|
||||
}
|
||||
|
@ -257,6 +252,7 @@ BuildCertChain(TrustDomain& trustDomain,
|
|||
EndEntityOrCA endEntityOrCA,
|
||||
/*optional*/ KeyUsages requiredKeyUsagesIfPresent,
|
||||
/*optional*/ SECOidTag requiredEKUIfPresent,
|
||||
/*optional*/ const SECItem* stapledOCSPResponse,
|
||||
/*out*/ ScopedCERTCertList& results)
|
||||
{
|
||||
PORT_Assert(certToDup);
|
||||
|
@ -285,19 +281,12 @@ BuildCertChain(TrustDomain& trustDomain,
|
|||
|
||||
rv = BuildForward(trustDomain, cert, time, endEntityOrCA,
|
||||
requiredKeyUsagesIfPresent, requiredEKUIfPresent,
|
||||
0, results);
|
||||
stapledOCSPResponse, 0, results);
|
||||
if (rv != Success) {
|
||||
results = nullptr;
|
||||
return SECFailure;
|
||||
}
|
||||
|
||||
// Build the cert chain even if the cert is expired, because we would
|
||||
// rather report the untrusted issuer error than the expired error.
|
||||
if (CheckTimes(cert.GetNSSCert(), time) != Success) {
|
||||
PR_SetError(SEC_ERROR_EXPIRED_CERTIFICATE, 0);
|
||||
return SECFailure;
|
||||
}
|
||||
|
||||
return SECSuccess;
|
||||
}
|
||||
|
||||
|
|
|
@ -318,16 +318,47 @@ CheckExtendedKeyUsage(EndEntityOrCA endEntityOrCA, const SECItem* encodedEKUs,
|
|||
return Success;
|
||||
}
|
||||
|
||||
// Checks extensions that apply to both EE and intermediate certs,
|
||||
// except for AIA, CRL, and AKI/SKI, which are handled elsewhere.
|
||||
Result
|
||||
CheckExtensions(BackCert& cert,
|
||||
EndEntityOrCA endEntityOrCA,
|
||||
bool isTrustAnchor,
|
||||
KeyUsages requiredKeyUsagesIfPresent,
|
||||
SECOidTag requiredEKUIfPresent,
|
||||
unsigned int subCACount)
|
||||
CheckIssuerIndependentProperties(TrustDomain& trustDomain,
|
||||
BackCert& cert,
|
||||
PRTime time,
|
||||
EndEntityOrCA endEntityOrCA,
|
||||
KeyUsages requiredKeyUsagesIfPresent,
|
||||
SECOidTag requiredEKUIfPresent,
|
||||
unsigned int subCACount,
|
||||
/*optional out*/ TrustDomain::TrustLevel* trustLevelOut)
|
||||
{
|
||||
Result rv;
|
||||
|
||||
TrustDomain::TrustLevel trustLevel;
|
||||
rv = MapSECStatus(trustDomain.GetCertTrust(endEntityOrCA,
|
||||
cert.GetNSSCert(),
|
||||
&trustLevel));
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
if (trustLevel == TrustDomain::ActivelyDistrusted) {
|
||||
PORT_SetError(SEC_ERROR_UNTRUSTED_CERT);
|
||||
return RecoverableError;
|
||||
}
|
||||
if (trustLevel != TrustDomain::TrustAnchor &&
|
||||
trustLevel != TrustDomain::InheritsTrust) {
|
||||
// The TrustDomain returned a trust level that we weren't expecting.
|
||||
PORT_SetError(PR_INVALID_STATE_ERROR);
|
||||
return FatalError;
|
||||
}
|
||||
if (trustLevelOut) {
|
||||
*trustLevelOut = trustLevel;
|
||||
}
|
||||
|
||||
bool isTrustAnchor = endEntityOrCA == MustBeCA &&
|
||||
trustLevel == TrustDomain::TrustAnchor;
|
||||
|
||||
rv = CheckTimes(cert.GetNSSCert(), time);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
// 4.2.1.1. Authority Key Identifier dealt with as part of path building
|
||||
// 4.2.1.2. Subject Key Identifier dealt with as part of path building
|
||||
|
||||
|
@ -336,8 +367,6 @@ CheckExtensions(BackCert& cert,
|
|||
return FatalError;
|
||||
}
|
||||
|
||||
Result rv;
|
||||
|
||||
// 4.2.1.3. Key Usage
|
||||
|
||||
rv = CheckKeyUsage(endEntityOrCA, isTrustAnchor, cert.encodedKeyUsage,
|
||||
|
|
|
@ -23,14 +23,15 @@
|
|||
|
||||
namespace insanity { namespace pkix {
|
||||
|
||||
Result CheckTimes(const CERTCertificate* cert, PRTime time);
|
||||
|
||||
Result CheckExtensions(BackCert& certExt,
|
||||
EndEntityOrCA endEntityOrCA,
|
||||
bool isTrustAnchor,
|
||||
KeyUsages requiredKeyUsagesIfPresent,
|
||||
SECOidTag requiredEKUIfPresent,
|
||||
unsigned int depth);
|
||||
Result CheckIssuerIndependentProperties(
|
||||
TrustDomain& trustDomain,
|
||||
BackCert& cert,
|
||||
PRTime time,
|
||||
EndEntityOrCA endEntityOrCA,
|
||||
KeyUsages requiredKeyUsagesIfPresent,
|
||||
SECOidTag requiredEKUIfPresent,
|
||||
unsigned int subCACount,
|
||||
/*optional out*/ TrustDomain::TrustLevel* trustLevel = nullptr);
|
||||
|
||||
Result CheckNameConstraints(BackCert& cert);
|
||||
|
||||
|
|
|
@ -217,6 +217,13 @@ ExpectTagAndLength(Input& input, uint8_t expectedTag, uint8_t expectedLength)
|
|||
Result
|
||||
ExpectTagAndGetLength(Input& input, uint8_t expectedTag, uint16_t& length);
|
||||
|
||||
inline Result
|
||||
ExpectTagAndIgnoreLength(Input& input, uint8_t expectedTag)
|
||||
{
|
||||
uint16_t ignored;
|
||||
return ExpectTagAndGetLength(input, expectedTag, ignored);
|
||||
}
|
||||
|
||||
inline Result
|
||||
End(Input& input)
|
||||
{
|
||||
|
|
|
@ -0,0 +1,843 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* Copyright 2013 Mozilla Foundation
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <limits>
|
||||
|
||||
#include "insanity/bind.h"
|
||||
#include "insanity/pkix.h"
|
||||
#include "pkixcheck.h"
|
||||
#include "pkixder.h"
|
||||
|
||||
#include "hasht.h"
|
||||
#include "pk11pub.h"
|
||||
#include "secder.h"
|
||||
|
||||
#ifdef _MSC_VER
|
||||
// C4480: nonstandard extension used: specifying underlying type for enum
|
||||
#define ENUM_CLASS __pragma(warning(disable: 4480)) enum
|
||||
#else
|
||||
#define ENUM_CLASS enum class
|
||||
#endif
|
||||
|
||||
// TODO: use typed/qualified typedefs everywhere?
|
||||
// TODO: When should we return SEC_ERROR_OCSP_UNAUTHORIZED_RESPONSE?
|
||||
|
||||
namespace insanity { namespace pkix {
|
||||
|
||||
static const PRTime ONE_DAY
|
||||
= INT64_C(24) * INT64_C(60) * INT64_C(60) * PR_USEC_PER_SEC;
|
||||
static const PRTime SLOP = ONE_DAY;
|
||||
|
||||
// These values correspond to the tag values in the ASN.1 CertStatus
|
||||
ENUM_CLASS CertStatus : uint8_t {
|
||||
Good = der::CONTEXT_SPECIFIC | 0,
|
||||
Revoked = der::CONTEXT_SPECIFIC | der::CONSTRUCTED | 1,
|
||||
Unknown = der::CONTEXT_SPECIFIC | 2
|
||||
};
|
||||
|
||||
class Context
|
||||
{
|
||||
public:
|
||||
Context(TrustDomain& trustDomain,
|
||||
const CERTCertificate& cert,
|
||||
CERTCertificate& issuerCert,
|
||||
PRTime time)
|
||||
: trustDomain(trustDomain)
|
||||
, cert(cert)
|
||||
, issuerCert(issuerCert)
|
||||
, time(time)
|
||||
, certStatus(CertStatus::Unknown)
|
||||
{
|
||||
}
|
||||
|
||||
TrustDomain& trustDomain;
|
||||
const CERTCertificate& cert;
|
||||
CERTCertificate& issuerCert;
|
||||
const PRTime time;
|
||||
CertStatus certStatus;
|
||||
|
||||
private:
|
||||
Context(const Context&); // delete
|
||||
void operator=(const Context&); // delete
|
||||
};
|
||||
|
||||
// Verify that potentialSigner is a valid delegated OCSP response signing cert
|
||||
// according to RFC 6960 section 4.2.2.2.
|
||||
static Result
|
||||
CheckOCSPResponseSignerCert(TrustDomain& trustDomain,
|
||||
CERTCertificate& potentialSigner,
|
||||
const CERTCertificate& issuerCert, PRTime time)
|
||||
{
|
||||
Result rv;
|
||||
|
||||
BackCert cert(&potentialSigner, nullptr, BackCert::ExcludeCN);
|
||||
rv = cert.Init();
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
// We don't need to do a complete verification of the signer (i.e. we don't
|
||||
// have to call BuildCertChain to verify the entire chain) because we
|
||||
// already know that the issuerCert is valid, since revocation checking is
|
||||
// done from the root to the parent after we've built a complete chain that
|
||||
// we know is otherwise valid. Rather, we just need to do a one-step
|
||||
// validation from potentialSigner to issuerCert.
|
||||
//
|
||||
// It seems reasonable to require the KU_DIGITAL_SIGNATURE key usage on the
|
||||
// OCSP responder certificate if the OCSP responder certificate has a
|
||||
// key usage extension. However, according to bug 240456, some OCSP responder
|
||||
// certificates may have only the nonRepudiation bit set. Also, the OCSP
|
||||
// specification (RFC 6960) does not mandate any particular key usage to be
|
||||
// asserted for OCSP responde signers. Oddly, the CABForum Baseline
|
||||
// Requirements v.1.1.5 do say "If the Root CA Private Key is used for
|
||||
// signing OCSP responses, then the digitalSignature bit MUST be set."
|
||||
//
|
||||
// Note that CheckIssuerIndependentProperties processes
|
||||
// SEC_OID_OCSP_RESPONDER in the way that the OCSP specification requires us
|
||||
// to--in particular, it doesn't allow SEC_OID_OCSP_RESPONDER to be implied
|
||||
// by a missing EKU extension, unlike other EKUs.
|
||||
//
|
||||
// TODO(bug 926261): If we're validating for a policy then the policy OID we
|
||||
// are validating for should be passed to CheckIssuerIndependentProperties.
|
||||
rv = CheckIssuerIndependentProperties(trustDomain, cert, time,
|
||||
MustBeEndEntity, 0,
|
||||
SEC_OID_OCSP_RESPONDER, 0);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
// It is possible that there exists a certificate with the same key as the
|
||||
// issuer but with a different name, so we need to compare names
|
||||
// TODO: needs test
|
||||
if (!SECITEM_ItemsAreEqual(&cert.GetNSSCert()->derIssuer,
|
||||
&issuerCert.derSubject) &&
|
||||
CERT_CompareName(&cert.GetNSSCert()->issuer,
|
||||
&issuerCert.subject) != SECEqual) {
|
||||
return Fail(RecoverableError, SEC_ERROR_OCSP_RESPONDER_CERT_INVALID);
|
||||
}
|
||||
|
||||
// TODO(bug 926260): check name constraints
|
||||
|
||||
if (trustDomain.VerifySignedData(&potentialSigner.signatureWrap,
|
||||
&issuerCert) != SECSuccess) {
|
||||
return MapSECStatus(SECFailure);
|
||||
}
|
||||
|
||||
// TODO: check for revocation of the OCSP responder certificate unless no-check
|
||||
// or the caller forcing no-check. To properly support the no-check policy, we'd
|
||||
// need to enforce policy constraints from the issuerChain.
|
||||
|
||||
return Success;
|
||||
}
|
||||
|
||||
//typedef enum {
|
||||
// ocspResponderID_byName = 1,
|
||||
// ocspResponderID_byKey = 2
|
||||
//} ResponderIDType;
|
||||
|
||||
ENUM_CLASS ResponderIDType : uint8_t
|
||||
{
|
||||
byName = der::CONTEXT_SPECIFIC | der::CONSTRUCTED | 1,
|
||||
byKey = der::CONTEXT_SPECIFIC | der::CONSTRUCTED | 2
|
||||
};
|
||||
|
||||
static inline der::Result OCSPResponse(der::Input&, Context&);
|
||||
static inline der::Result ResponseBytes(der::Input&, Context&);
|
||||
static inline der::Result BasicResponse(der::Input&, Context&);
|
||||
static inline der::Result ResponseData(
|
||||
der::Input& tbsResponseData, Context& context,
|
||||
const CERTSignedData& signedResponseData,
|
||||
/*const*/ SECItem* certs, size_t numCerts);
|
||||
static inline der::Result SingleResponse(der::Input& input,
|
||||
Context& context);
|
||||
static inline der::Result CheckExtensionsForCriticality(der::Input&);
|
||||
static inline der::Result CertID(der::Input& input,
|
||||
const Context& context,
|
||||
/*out*/ bool& match);
|
||||
static der::Result MatchIssuerKey(const SECItem& issuerKeyHash,
|
||||
const CERTCertificate& issuer,
|
||||
/*out*/ bool& match);
|
||||
|
||||
// RFC 6960 section 4.2.2.2: The OCSP responder must either be the issuer of
|
||||
// the cert or it must be a delegated OCSP response signing cert directly
|
||||
// issued by the issuer. If the OCSP responder is a delegated OCSP response
|
||||
// signer, then its certificate is (probably) embedded within the OCSP
|
||||
// response and we'll need to verify that it is a valid certificate that chains
|
||||
// *directly* to issuerCert.
|
||||
static CERTCertificate*
|
||||
GetOCSPSignerCertificate(TrustDomain& trustDomain,
|
||||
ResponderIDType responderIDType,
|
||||
const SECItem& responderIDItem,
|
||||
const SECItem* certs, size_t numCerts,
|
||||
CERTCertificate& issuerCert, PRTime time)
|
||||
{
|
||||
bool isIssuer = true;
|
||||
size_t i = 0;
|
||||
for (;;) {
|
||||
ScopedCERTCertificate potentialSigner;
|
||||
if (isIssuer) {
|
||||
potentialSigner = CERT_DupCertificate(&issuerCert);
|
||||
} else if (i < numCerts) {
|
||||
potentialSigner = CERT_NewTempCertificate(
|
||||
CERT_GetDefaultCertDB(),
|
||||
/*TODO*/const_cast<SECItem*>(&certs[i]), nullptr,
|
||||
false, false);
|
||||
if (!potentialSigner) {
|
||||
return nullptr;
|
||||
}
|
||||
++i;
|
||||
} else {
|
||||
PR_SetError(SEC_ERROR_OCSP_INVALID_SIGNING_CERT, 0);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool match;
|
||||
switch (responderIDType) {
|
||||
case ResponderIDType::byName:
|
||||
// The CA is very likely to have encoded the name in the OCSP response
|
||||
// exactly the same as the name is encoded in the signing certificate.
|
||||
// Consequently, most of the time we will avoid parsing the name
|
||||
// completely. We're assuming here that the signer's subject name is
|
||||
// correctly formatted.
|
||||
// TODO: need test for exact name
|
||||
// TODO: need test for non-exact name match
|
||||
match = SECITEM_ItemsAreEqual(&responderIDItem,
|
||||
&potentialSigner->derSubject);
|
||||
if (!match) {
|
||||
ScopedPLArenaPool arena(PORT_NewArena(DER_DEFAULT_CHUNKSIZE));
|
||||
if (!arena) {
|
||||
return nullptr;
|
||||
}
|
||||
CERTName name;
|
||||
if (SEC_QuickDERDecodeItem(arena.get(), &name,
|
||||
SEC_ASN1_GET(CERT_NameTemplate),
|
||||
&responderIDItem) != SECSuccess) {
|
||||
return nullptr;
|
||||
}
|
||||
match = CERT_CompareName(&name, &potentialSigner->subject) == SECEqual;
|
||||
}
|
||||
break;
|
||||
|
||||
case ResponderIDType::byKey:
|
||||
{
|
||||
der::Input responderID;
|
||||
if (responderID.Init(responderIDItem.data, responderIDItem.len)
|
||||
!= der::Success) {
|
||||
return nullptr;
|
||||
}
|
||||
SECItem issuerKeyHash;
|
||||
if (der::Skip(responderID, der::OCTET_STRING, issuerKeyHash) != der::Success) {
|
||||
return nullptr;
|
||||
}
|
||||
if (MatchIssuerKey(issuerKeyHash, *potentialSigner.get(), match)
|
||||
!= der::Success) {
|
||||
return nullptr;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
PR_SetError(SEC_ERROR_OCSP_MALFORMED_RESPONSE, 0);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (match && !isIssuer) {
|
||||
Result rv = CheckOCSPResponseSignerCert(trustDomain,
|
||||
*potentialSigner.get(),
|
||||
issuerCert, time);
|
||||
if (rv == RecoverableError) {
|
||||
match = false;
|
||||
} else if (rv != Success) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
if (match) {
|
||||
return potentialSigner.release();
|
||||
}
|
||||
|
||||
isIssuer = false;
|
||||
}
|
||||
}
|
||||
|
||||
static SECStatus
|
||||
VerifySignature(Context& context, ResponderIDType responderIDType,
|
||||
const SECItem& responderID, const SECItem* certs,
|
||||
size_t numCerts, const CERTSignedData& signedResponseData)
|
||||
{
|
||||
ScopedCERTCertificate signer(
|
||||
GetOCSPSignerCertificate(context.trustDomain, responderIDType, responderID,
|
||||
certs, numCerts, context.issuerCert,
|
||||
context.time));
|
||||
if (!signer) {
|
||||
return SECFailure;
|
||||
}
|
||||
|
||||
if (context.trustDomain.VerifySignedData(&signedResponseData, signer.get())
|
||||
!= SECSuccess) {
|
||||
if (PR_GetError() == SEC_ERROR_BAD_SIGNATURE) {
|
||||
PR_SetError(SEC_ERROR_OCSP_BAD_SIGNATURE, 0);
|
||||
}
|
||||
return SECFailure;
|
||||
}
|
||||
|
||||
return SECSuccess;
|
||||
}
|
||||
|
||||
SECStatus
|
||||
VerifyEncodedOCSPResponse(TrustDomain& trustDomain,
|
||||
const CERTCertificate* cert,
|
||||
CERTCertificate* issuerCert, PRTime time,
|
||||
const SECItem* encodedResponse)
|
||||
{
|
||||
PR_ASSERT(cert);
|
||||
PR_ASSERT(issuerCert);
|
||||
// TODO: PR_Assert(pinArg)
|
||||
PR_ASSERT(encodedResponse);
|
||||
if (!cert || !issuerCert || !encodedResponse || !encodedResponse->data) {
|
||||
PR_SetError(SEC_ERROR_INVALID_ARGS, 0);
|
||||
return SECFailure;
|
||||
}
|
||||
|
||||
der::Input input;
|
||||
if (input.Init(encodedResponse->data, encodedResponse->len) != der::Success) {
|
||||
return SECFailure;
|
||||
}
|
||||
|
||||
Context context(trustDomain, *cert, *issuerCert, time);
|
||||
|
||||
if (der::Nested(input, der::SEQUENCE,
|
||||
bind(OCSPResponse, _1, ref(context))) != der::Success) {
|
||||
return SECFailure;
|
||||
}
|
||||
|
||||
if (der::End(input) != der::Success) {
|
||||
return SECFailure;
|
||||
}
|
||||
|
||||
switch (context.certStatus) {
|
||||
case CertStatus::Good:
|
||||
return SECSuccess;
|
||||
case CertStatus::Revoked:
|
||||
PR_SetError(SEC_ERROR_REVOKED_CERTIFICATE, 0);
|
||||
return SECFailure;
|
||||
case CertStatus::Unknown:
|
||||
PR_SetError(SEC_ERROR_OCSP_UNKNOWN_CERT, 0);
|
||||
return SECFailure;
|
||||
}
|
||||
|
||||
PR_NOT_REACHED("unknown CertStatus");
|
||||
PR_SetError(SEC_ERROR_OCSP_UNKNOWN_CERT, 0);
|
||||
return SECFailure;
|
||||
}
|
||||
|
||||
// OCSPResponse ::= SEQUENCE {
|
||||
// responseStatus OCSPResponseStatus,
|
||||
// responseBytes [0] EXPLICIT ResponseBytes OPTIONAL }
|
||||
//
|
||||
static inline der::Result
|
||||
OCSPResponse(der::Input& input, Context& context)
|
||||
{
|
||||
// OCSPResponseStatus ::= ENUMERATED {
|
||||
// successful (0), -- Response has valid confirmations
|
||||
// malformedRequest (1), -- Illegal confirmation request
|
||||
// internalError (2), -- Internal error in issuer
|
||||
// tryLater (3), -- Try again later
|
||||
// -- (4) is not used
|
||||
// sigRequired (5), -- Must sign the request
|
||||
// unauthorized (6) -- Request unauthorized
|
||||
// }
|
||||
uint8_t responseStatus;
|
||||
|
||||
if (der::Enumerated(input, responseStatus) != der::Success) {
|
||||
return der::Failure;
|
||||
}
|
||||
switch (responseStatus) {
|
||||
case 0: break; // successful
|
||||
case 1: return der::Fail(SEC_ERROR_OCSP_MALFORMED_REQUEST);
|
||||
case 2: return der::Fail(SEC_ERROR_OCSP_SERVER_ERROR);
|
||||
case 3: return der::Fail(SEC_ERROR_OCSP_TRY_SERVER_LATER);
|
||||
case 5: return der::Fail(SEC_ERROR_OCSP_REQUEST_NEEDS_SIG);
|
||||
case 6: return der::Fail(SEC_ERROR_OCSP_UNAUTHORIZED_REQUEST);
|
||||
default: return der::Fail(SEC_ERROR_OCSP_UNKNOWN_RESPONSE_STATUS);
|
||||
}
|
||||
|
||||
return der::Nested(input, der::CONTEXT_SPECIFIC | der::CONSTRUCTED | 0,
|
||||
der::SEQUENCE, bind(ResponseBytes, _1, ref(context)));
|
||||
}
|
||||
|
||||
// ResponseBytes ::= SEQUENCE {
|
||||
// responseType OBJECT IDENTIFIER,
|
||||
// response OCTET STRING }
|
||||
static inline der::Result
|
||||
ResponseBytes(der::Input& input, Context& context)
|
||||
{
|
||||
static const uint8_t id_pkix_ocsp_basic[] = {
|
||||
0x2B, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x01
|
||||
};
|
||||
|
||||
if (der::OID(input, id_pkix_ocsp_basic) != der::Success) {
|
||||
return der::Failure;
|
||||
}
|
||||
|
||||
return der::Nested(input, der::OCTET_STRING, der::SEQUENCE,
|
||||
bind(BasicResponse, _1, ref(context)));
|
||||
}
|
||||
|
||||
// BasicOCSPResponse ::= SEQUENCE {
|
||||
// tbsResponseData ResponseData,
|
||||
// signatureAlgorithm AlgorithmIdentifier,
|
||||
// signature BIT STRING,
|
||||
// certs [0] EXPLICIT SEQUENCE OF Certificate OPTIONAL }
|
||||
der::Result
|
||||
BasicResponse(der::Input& input, Context& context)
|
||||
{
|
||||
der::Input::Mark mark(input.GetMark());
|
||||
|
||||
uint16_t length;
|
||||
if (der::ExpectTagAndGetLength(input, der::SEQUENCE, length)
|
||||
!= der::Success) {
|
||||
return der::Failure;
|
||||
}
|
||||
|
||||
// The signature covers the entire DER encoding of tbsResponseData, including
|
||||
// the beginning tag and length. However, when we're parsing tbsResponseData,
|
||||
// we want to strip off the tag and length because we don't need it after
|
||||
// we've confirmed it's there and figured out what length it is.
|
||||
|
||||
der::Input tbsResponseData;
|
||||
|
||||
if (input.Skip(length, tbsResponseData) != der::Success) {
|
||||
return der::Failure;
|
||||
}
|
||||
|
||||
CERTSignedData signedData;
|
||||
|
||||
input.GetSECItem(siBuffer, mark, signedData.data);
|
||||
|
||||
if (der::Nested(input, der::SEQUENCE,
|
||||
bind(der::AlgorithmIdentifier, _1,
|
||||
ref(signedData.signatureAlgorithm))) != der::Success) {
|
||||
return der::Failure;
|
||||
}
|
||||
|
||||
if (der::Skip(input, der::BIT_STRING, signedData.signature) != der::Success) {
|
||||
return der::Failure;
|
||||
}
|
||||
if (signedData.signature.len == 0) {
|
||||
return der::Fail(SEC_ERROR_OCSP_BAD_SIGNATURE);
|
||||
}
|
||||
unsigned int unusedBitsAtEnd = signedData.signature.data[0];
|
||||
// XXX: Really the constraint should be that unusedBitsAtEnd must be less
|
||||
// than 7. But, we suspect there are no valid OCSP response signatures with
|
||||
// non-zero unused bits. It seems like NSS assumes this in various places, so
|
||||
// we enforce it. If we find compatibility issues, we'll know we're wrong.
|
||||
if (unusedBitsAtEnd != 0) {
|
||||
return der::Fail(SEC_ERROR_OCSP_BAD_SIGNATURE);
|
||||
}
|
||||
++signedData.signature.data;
|
||||
--signedData.signature.len;
|
||||
signedData.signature.len = (signedData.signature.len << 3); // Bytes to bits
|
||||
|
||||
// Parse certificates, if any
|
||||
|
||||
SECItem certs[8];
|
||||
size_t numCerts = 0;
|
||||
|
||||
if (!input.AtEnd()) {
|
||||
// We ignore the lengths of the wrappers because we'll detect bad lengths
|
||||
// during parsing--too short and we'll run out of input for parsing a cert,
|
||||
// and too long and we'll have leftover data that won't parse as a cert.
|
||||
|
||||
// [0] wrapper
|
||||
if (der::ExpectTagAndIgnoreLength(
|
||||
input, der::CONSTRUCTED | der::CONTEXT_SPECIFIC | 0)
|
||||
!= der::Success) {
|
||||
return der::Failure;
|
||||
}
|
||||
|
||||
// SEQUENCE wrapper
|
||||
if (der::ExpectTagAndIgnoreLength(input, der::SEQUENCE) != der::Success) {
|
||||
return der::Failure;
|
||||
}
|
||||
|
||||
// sequence of certificates
|
||||
while (!input.AtEnd()) {
|
||||
if (numCerts == PR_ARRAY_SIZE(certs)) {
|
||||
return der::Fail(SEC_ERROR_BAD_DER);
|
||||
}
|
||||
|
||||
// Unwrap the SEQUENCE that contains the certificate, which is itself a
|
||||
// SEQUENCE.
|
||||
der::Input::Mark mark(input.GetMark());
|
||||
if (der::Skip(input, der::SEQUENCE) != der::Success) {
|
||||
return der::Failure;
|
||||
}
|
||||
|
||||
input.GetSECItem(siBuffer, mark, certs[numCerts]);
|
||||
++numCerts;
|
||||
}
|
||||
}
|
||||
|
||||
return ResponseData(tbsResponseData, context, signedData, certs, numCerts);
|
||||
}
|
||||
|
||||
// ResponseData ::= SEQUENCE {
|
||||
// version [0] EXPLICIT Version DEFAULT v1,
|
||||
// responderID ResponderID,
|
||||
// producedAt GeneralizedTime,
|
||||
// responses SEQUENCE OF SingleResponse,
|
||||
// responseExtensions [1] EXPLICIT Extensions OPTIONAL }
|
||||
static inline der::Result
|
||||
ResponseData(der::Input& input, Context& context,
|
||||
const CERTSignedData& signedResponseData,
|
||||
/*const*/ SECItem* certs, size_t numCerts)
|
||||
{
|
||||
uint8_t version;
|
||||
if (der::OptionalVersion(input, version) != der::Success) {
|
||||
return der::Failure;
|
||||
}
|
||||
if (version != der::v1) {
|
||||
// TODO: more specific error code for bad version?
|
||||
return der::Fail(SEC_ERROR_BAD_DER);
|
||||
}
|
||||
|
||||
// ResponderID ::= CHOICE {
|
||||
// byName [1] Name,
|
||||
// byKey [2] KeyHash }
|
||||
SECItem responderID;
|
||||
uint16_t responderIDLength;
|
||||
ResponderIDType responderIDType
|
||||
= input.Peek(static_cast<uint8_t>(ResponderIDType::byName))
|
||||
? ResponderIDType::byName
|
||||
: ResponderIDType::byKey;
|
||||
if (ExpectTagAndGetLength(input, static_cast<uint8_t>(responderIDType),
|
||||
responderIDLength) != der::Success) {
|
||||
return der::Failure;
|
||||
}
|
||||
// TODO: responderID probably needs to have another level of ASN1 tag/length
|
||||
// checked and stripped.
|
||||
if (input.Skip(responderIDLength, responderID) != der::Success) {
|
||||
return der::Failure;
|
||||
}
|
||||
|
||||
// This is the soonest we can verify the signature. We verify the signature
|
||||
// right away to follow the principal of minimizing the processing of data
|
||||
// before verifying its signature.
|
||||
if (VerifySignature(context, responderIDType, responderID, certs, numCerts,
|
||||
signedResponseData) != SECSuccess) {
|
||||
return der::Failure;
|
||||
}
|
||||
|
||||
// TODO: Do we even need to parse this? Should we just skip it?
|
||||
PRTime producedAt;
|
||||
if (der::GeneralizedTime(input, producedAt) != der::Success) {
|
||||
return der::Failure;
|
||||
}
|
||||
|
||||
// We don't accept an empty sequence of responses. In practice, a legit OCSP
|
||||
// responder will never return an empty response, and handling the case of an
|
||||
// empty response makes things unnecessarily complicated.
|
||||
if (der::NestedOf(input, der::SEQUENCE, der::SEQUENCE,
|
||||
der::MustNotBeEmpty,
|
||||
bind(SingleResponse, _1, ref(context))) != der::Success) {
|
||||
return der::Failure;
|
||||
}
|
||||
|
||||
if (!input.AtEnd()) {
|
||||
if (CheckExtensionsForCriticality(input) != der::Success) {
|
||||
return der::Failure;
|
||||
}
|
||||
}
|
||||
|
||||
return der::Success;
|
||||
}
|
||||
|
||||
// SingleResponse ::= SEQUENCE {
|
||||
// certID CertID,
|
||||
// certStatus CertStatus,
|
||||
// thisUpdate GeneralizedTime,
|
||||
// nextUpdate [0] EXPLICIT GeneralizedTime OPTIONAL,
|
||||
// singleExtensions [1] EXPLICIT Extensions{{re-ocsp-crl |
|
||||
// re-ocsp-archive-cutoff |
|
||||
// CrlEntryExtensions, ...}
|
||||
// } OPTIONAL }
|
||||
static inline der::Result
|
||||
SingleResponse(der::Input& input, Context& context)
|
||||
{
|
||||
bool match = false;
|
||||
if (der::Nested(input, der::SEQUENCE,
|
||||
bind(CertID, _1, cref(context), ref(match)))
|
||||
!= der::Success) {
|
||||
return der::Failure;
|
||||
}
|
||||
|
||||
if (!match) {
|
||||
return der::Success;
|
||||
}
|
||||
|
||||
// CertStatus ::= CHOICE {
|
||||
// good [0] IMPLICIT NULL,
|
||||
// revoked [1] IMPLICIT RevokedInfo,
|
||||
// unknown [2] IMPLICIT UnknownInfo }
|
||||
//
|
||||
// In the event of multiple SingleResponses for a cert that have conflicting
|
||||
// statuses, we use the following precedence rules:
|
||||
//
|
||||
// * revoked overrides good and unknown
|
||||
// * good overrides unknown
|
||||
if (input.Peek(static_cast<uint8_t>(CertStatus::Good))) {
|
||||
if (ExpectTagAndLength(input, static_cast<uint8_t>(CertStatus::Good), 0)
|
||||
!= der::Success) {
|
||||
return der::Failure;
|
||||
}
|
||||
if (context.certStatus != CertStatus::Revoked) {
|
||||
context.certStatus = CertStatus::Good;
|
||||
}
|
||||
} else if (input.Peek(static_cast<uint8_t>(CertStatus::Revoked))) {
|
||||
// We don't need any info from the RevokedInfo structure, so we don't even
|
||||
// parse it. TODO: We should mention issues like this in the explanation of
|
||||
// why we treat invalid OCSP responses equivalently to revoked for OCSP
|
||||
// stapling.
|
||||
if (der::Skip(input, static_cast<uint8_t>(CertStatus::Revoked))
|
||||
!= der::Success) {
|
||||
return der::Failure;
|
||||
}
|
||||
context.certStatus = CertStatus::Revoked;
|
||||
} else if (ExpectTagAndLength(input,
|
||||
static_cast<uint8_t>(CertStatus::Unknown),
|
||||
0) != der::Success) {
|
||||
return der::Failure;
|
||||
}
|
||||
|
||||
// http://tools.ietf.org/html/rfc6960#section-3.2
|
||||
// 5. The time at which the status being indicated is known to be
|
||||
// correct (thisUpdate) is sufficiently recent;
|
||||
// 6. When available, the time at or before which newer information will
|
||||
// be available about the status of the certificate (nextUpdate) is
|
||||
// greater than the current time.
|
||||
|
||||
// We won't accept any OCSP responses that are more than 10 days old, even if
|
||||
// the nextUpdate time is further in the future.
|
||||
static const PRTime OLDEST_ACCEPTABLE = INT64_C(10) * ONE_DAY;
|
||||
|
||||
PRTime thisUpdate;
|
||||
if (der::GeneralizedTime(input, thisUpdate) != der::Success) {
|
||||
return der::Failure;
|
||||
}
|
||||
|
||||
if (thisUpdate > context.time + SLOP) {
|
||||
return der::Fail(SEC_ERROR_OCSP_FUTURE_RESPONSE);
|
||||
}
|
||||
|
||||
PRTime notAfter;
|
||||
static const uint8_t NEXT_UPDATE_TAG =
|
||||
der::CONTEXT_SPECIFIC | der::CONSTRUCTED | 0;
|
||||
if (input.Peek(NEXT_UPDATE_TAG)) {
|
||||
PRTime nextUpdate;
|
||||
if (der::Nested(input, NEXT_UPDATE_TAG,
|
||||
bind(der::GeneralizedTime, _1, ref(nextUpdate)))
|
||||
!= der::Success) {
|
||||
return der::Failure;
|
||||
}
|
||||
|
||||
if (nextUpdate < thisUpdate) {
|
||||
return der::Fail(SEC_ERROR_OCSP_MALFORMED_RESPONSE);
|
||||
}
|
||||
if (nextUpdate - thisUpdate <= OLDEST_ACCEPTABLE) {
|
||||
notAfter = nextUpdate;
|
||||
} else {
|
||||
notAfter = thisUpdate + OLDEST_ACCEPTABLE;
|
||||
}
|
||||
} else {
|
||||
// NSS requires all OCSP responses without a nextUpdate to be recent.
|
||||
// Match that stricter behavior.
|
||||
notAfter = thisUpdate + ONE_DAY;
|
||||
}
|
||||
|
||||
if (context.time < SLOP) { // prevent underflow
|
||||
return der::Fail(SEC_ERROR_INVALID_ARGS);
|
||||
}
|
||||
if (context.time - SLOP > notAfter) {
|
||||
return der::Fail(SEC_ERROR_OCSP_OLD_RESPONSE);
|
||||
}
|
||||
|
||||
|
||||
if (!input.AtEnd()) {
|
||||
if (CheckExtensionsForCriticality(input) != der::Success) {
|
||||
return der::Failure;
|
||||
}
|
||||
}
|
||||
|
||||
return der::Success;
|
||||
}
|
||||
|
||||
// CertID ::= SEQUENCE {
|
||||
// hashAlgorithm AlgorithmIdentifier,
|
||||
// issuerNameHash OCTET STRING, -- Hash of issuer's DN
|
||||
// issuerKeyHash OCTET STRING, -- Hash of issuer's public key
|
||||
// serialNumber CertificateSerialNumber }
|
||||
static inline der::Result
|
||||
CertID(der::Input& input, const Context& context, /*out*/ bool& match)
|
||||
{
|
||||
match = false;
|
||||
|
||||
SECAlgorithmID hashAlgorithm;
|
||||
if (der::Nested(input, der::SEQUENCE,
|
||||
bind(der::AlgorithmIdentifier, _1, ref(hashAlgorithm)))
|
||||
!= der::Success) {
|
||||
return der::Failure;
|
||||
}
|
||||
|
||||
SECItem issuerNameHash;
|
||||
if (der::Skip(input, der::OCTET_STRING, issuerNameHash) != der::Success) {
|
||||
return der::Failure;
|
||||
}
|
||||
|
||||
SECItem issuerKeyHash;
|
||||
if (der::Skip(input, der::OCTET_STRING, issuerKeyHash) != der::Success) {
|
||||
return der::Failure;
|
||||
}
|
||||
|
||||
SECItem serialNumber;
|
||||
if (der::CertificateSerialNumber(input, serialNumber) != der::Success) {
|
||||
return der::Failure;
|
||||
}
|
||||
|
||||
const CERTCertificate& cert = context.cert;
|
||||
const CERTCertificate& issuerCert = context.issuerCert;
|
||||
|
||||
if (!SECITEM_ItemsAreEqual(&serialNumber, &cert.serialNumber)) {
|
||||
return der::Success;
|
||||
}
|
||||
|
||||
// TODO: support SHA-2 hashes.
|
||||
|
||||
SECOidTag hashAlg = SECOID_GetAlgorithmTag(&hashAlgorithm);
|
||||
if (hashAlg != SEC_OID_SHA1) {
|
||||
return der::Success;
|
||||
}
|
||||
|
||||
if (issuerNameHash.len != SHA1_LENGTH) {
|
||||
return der::Fail(SEC_ERROR_OCSP_MALFORMED_RESPONSE);
|
||||
}
|
||||
|
||||
// From http://tools.ietf.org/html/rfc6960#section-4.1.1:
|
||||
// "The hash shall be calculated over the DER encoding of the
|
||||
// issuer's name field in the certificate being checked."
|
||||
uint8_t hashBuf[SHA1_LENGTH];
|
||||
if (PK11_HashBuf(SEC_OID_SHA1, hashBuf, cert.derIssuer.data,
|
||||
cert.derIssuer.len) != SECSuccess) {
|
||||
return der::Failure;
|
||||
}
|
||||
if (memcmp(hashBuf, issuerNameHash.data, issuerNameHash.len)) {
|
||||
return der::Success;
|
||||
}
|
||||
|
||||
return MatchIssuerKey(issuerKeyHash, issuerCert, match);
|
||||
}
|
||||
|
||||
// From http://tools.ietf.org/html/rfc6960#section-4.1.1:
|
||||
// "The hash shall be calculated over the value (excluding tag and length) of
|
||||
// the subject public key field in the issuer's certificate."
|
||||
static der::Result
|
||||
MatchIssuerKey(const SECItem& issuerKeyHash, const CERTCertificate& issuer,
|
||||
/*out*/ bool& match)
|
||||
{
|
||||
if (issuerKeyHash.len != SHA1_LENGTH) {
|
||||
return der::Fail(SEC_ERROR_OCSP_MALFORMED_RESPONSE);
|
||||
}
|
||||
|
||||
// TODO(bug 966856): support SHA-2 hashes
|
||||
|
||||
// Copy just the length and data pointer (nothing needs to be freed) of the
|
||||
// subject public key so we can convert the length from bits to bytes, which
|
||||
// is what the digest function expects.
|
||||
SECItem spk = issuer.subjectPublicKeyInfo.subjectPublicKey;
|
||||
DER_ConvertBitString(&spk);
|
||||
|
||||
static uint8_t hashBuf[SHA1_LENGTH];
|
||||
if (PK11_HashBuf(SEC_OID_SHA1, hashBuf, spk.data, spk.len) != SECSuccess) {
|
||||
return der::Failure;
|
||||
}
|
||||
|
||||
match = !memcmp(hashBuf, issuerKeyHash.data, issuerKeyHash.len);
|
||||
return der::Success;
|
||||
}
|
||||
|
||||
// Extension ::= SEQUENCE {
|
||||
// extnID OBJECT IDENTIFIER,
|
||||
// critical BOOLEAN DEFAULT FALSE,
|
||||
// extnValue OCTET STRING
|
||||
// }
|
||||
static der::Result
|
||||
CheckExtensionForCriticality(der::Input& input)
|
||||
{
|
||||
uint16_t toSkip;
|
||||
if (ExpectTagAndGetLength(input, der::OIDTag, toSkip) != der::Success) {
|
||||
return der::Failure;
|
||||
}
|
||||
|
||||
// TODO: maybe we should check the syntax of the OID value
|
||||
if (input.Skip(toSkip) != der::Success) {
|
||||
return der::Failure;
|
||||
}
|
||||
|
||||
// The only valid explicit encoding of the value is TRUE, so don't even
|
||||
// bother parsing it, since we're going to fail either way.
|
||||
if (input.Peek(der::BOOLEAN)) {
|
||||
return der::Fail(SEC_ERROR_UNKNOWN_CRITICAL_EXTENSION);
|
||||
}
|
||||
|
||||
if (ExpectTagAndGetLength(input, der::OCTET_STRING, toSkip)
|
||||
!= der::Success) {
|
||||
return der::Failure;
|
||||
}
|
||||
return input.Skip(toSkip);
|
||||
}
|
||||
|
||||
static der::Result
|
||||
CheckExtensionsForCriticality(der::Input& input)
|
||||
{
|
||||
return der::NestedOf(input, der::SEQUENCE | 1, der::SEQUENCE,
|
||||
der::MustNotBeEmpty, CheckExtensionForCriticality);
|
||||
}
|
||||
|
||||
// 1. The certificate identified in a received response corresponds to
|
||||
// the certificate that was identified in the corresponding request;
|
||||
// 2. The signature on the response is valid;
|
||||
// 3. The identity of the signer matches the intended recipient of the
|
||||
// request;
|
||||
// 4. The signer is currently authorized to provide a response for the
|
||||
// certificate in question;
|
||||
// 5. The time at which the status being indicated is known to be
|
||||
// correct (thisUpdate) is sufficiently recent;
|
||||
// 6. When available, the time at or before which newer information will
|
||||
// be available about the status of the certificate (nextUpdate) is
|
||||
// greater than the current time.
|
||||
//
|
||||
// Responses whose nextUpdate value is earlier than
|
||||
// the local system time value SHOULD be considered unreliable.
|
||||
// Responses whose thisUpdate time is later than the local system time
|
||||
// SHOULD be considered unreliable.
|
||||
//
|
||||
// If nextUpdate is not set, the responder is indicating that newer
|
||||
// revocation information is available all the time.
|
||||
//
|
||||
// http://tools.ietf.org/html/rfc5019#section-4
|
||||
|
||||
} } // namespace insanity::pkix
|
|
@ -109,7 +109,12 @@ public:
|
|||
|
||||
BackCert* const childCert;
|
||||
|
||||
const CERTCertificate* GetNSSCert() const { return nssCert; }
|
||||
// Only non-const so that we can pass this to TrustDomain::IsRevoked,
|
||||
// which only takes a non-const pointer because VerifyEncodedOCSPResponse
|
||||
// requires it, and that is only because the implementation of
|
||||
// VerifyEncodedOCSPResponse does a CERT_DupCertificate. TODO: get rid
|
||||
// of that CERT_DupCertificate so that we can make all these things const.
|
||||
/*const*/ CERTCertificate* GetNSSCert() const { return nssCert; }
|
||||
|
||||
// Returns the names that should be considered when evaluating name
|
||||
// constraints. The list is constructed lazily and cached. The result is a
|
||||
|
|
Загрузка…
Ссылка в новой задаче