зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1006812: Use mozilla::pkix::der to decode the key usage extension, r=keeler
--HG-- extra : rebase_source : e445c913994dc027e1179543d7b6cab2505e734d
This commit is contained in:
Родитель
8d7ccbd531
Коммит
b76e937c55
|
@ -604,7 +604,8 @@ VerifySignature(AppTrustedRoot trustedRoot,
|
|||
return MapSECStatus(SECFailure);
|
||||
}
|
||||
if (BuildCertChain(trustDomain, signerCert, PR_Now(),
|
||||
EndEntityOrCA::MustBeEndEntity, KU_DIGITAL_SIGNATURE,
|
||||
EndEntityOrCA::MustBeEndEntity,
|
||||
KeyUsage::digitalSignature,
|
||||
KeyPurposeId::id_kp_codeSigning,
|
||||
CertPolicyId::anyPolicy,
|
||||
nullptr, builtChain)
|
||||
|
|
|
@ -302,27 +302,22 @@ destroyCertListThatShouldNotExist(CERTCertList** certChain)
|
|||
|
||||
static SECStatus
|
||||
BuildCertChainForOneKeyUsage(TrustDomain& trustDomain, CERTCertificate* cert,
|
||||
PRTime time, KeyUsages ku1, KeyUsages ku2,
|
||||
KeyUsages ku3, KeyPurposeId eku,
|
||||
PRTime time, KeyUsage ku1, KeyUsage ku2,
|
||||
KeyUsage ku3, KeyPurposeId eku,
|
||||
const CertPolicyId& requiredPolicy,
|
||||
const SECItem* stapledOCSPResponse,
|
||||
ScopedCERTCertList& builtChain)
|
||||
{
|
||||
PR_ASSERT(ku1);
|
||||
PR_ASSERT(ku2);
|
||||
|
||||
SECStatus rv = BuildCertChain(trustDomain, cert, time,
|
||||
EndEntityOrCA::MustBeEndEntity, ku1,
|
||||
eku, requiredPolicy,
|
||||
stapledOCSPResponse, builtChain);
|
||||
if (rv != SECSuccess && ku2 &&
|
||||
PR_GetError() == SEC_ERROR_INADEQUATE_KEY_USAGE) {
|
||||
if (rv != SECSuccess && PR_GetError() == SEC_ERROR_INADEQUATE_KEY_USAGE) {
|
||||
rv = BuildCertChain(trustDomain, cert, time,
|
||||
EndEntityOrCA::MustBeEndEntity, ku2,
|
||||
eku, requiredPolicy,
|
||||
stapledOCSPResponse, builtChain);
|
||||
if (rv != SECSuccess && ku3 &&
|
||||
PR_GetError() == SEC_ERROR_INADEQUATE_KEY_USAGE) {
|
||||
if (rv != SECSuccess && PR_GetError() == SEC_ERROR_INADEQUATE_KEY_USAGE) {
|
||||
rv = BuildCertChain(trustDomain, cert, time,
|
||||
EndEntityOrCA::MustBeEndEntity, ku3,
|
||||
eku, requiredPolicy,
|
||||
|
@ -388,7 +383,8 @@ CertVerifier::MozillaPKIXVerifyCert(
|
|||
NSSCertDBTrustDomain trustDomain(trustEmail, ocspFetching, mOCSPCache,
|
||||
pinArg, ocspGETConfig);
|
||||
rv = BuildCertChain(trustDomain, cert, time,
|
||||
EndEntityOrCA::MustBeEndEntity, KU_DIGITAL_SIGNATURE,
|
||||
EndEntityOrCA::MustBeEndEntity,
|
||||
KeyUsage::digitalSignature,
|
||||
KeyPurposeId::id_kp_clientAuth,
|
||||
CertPolicyId::anyPolicy, stapledOCSPResponse,
|
||||
builtChain);
|
||||
|
@ -413,9 +409,9 @@ CertVerifier::MozillaPKIXVerifyCert(
|
|||
: NSSCertDBTrustDomain::FetchOCSPForEV,
|
||||
mOCSPCache, pinArg, ocspGETConfig, &callbackContainer);
|
||||
rv = BuildCertChainForOneKeyUsage(trustDomain, cert, time,
|
||||
KU_DIGITAL_SIGNATURE, // ECDHE/DHE
|
||||
KU_KEY_ENCIPHERMENT, // RSA
|
||||
KU_KEY_AGREEMENT, // ECDH/DH
|
||||
KeyUsage::digitalSignature,// (EC)DHE
|
||||
KeyUsage::keyEncipherment, // RSA
|
||||
KeyUsage::keyAgreement, // (EC)DH
|
||||
KeyPurposeId::id_kp_serverAuth,
|
||||
evPolicy, stapledOCSPResponse,
|
||||
builtChain);
|
||||
|
@ -440,9 +436,9 @@ CertVerifier::MozillaPKIXVerifyCert(
|
|||
pinArg, ocspGETConfig,
|
||||
&callbackContainer);
|
||||
rv = BuildCertChainForOneKeyUsage(trustDomain, cert, time,
|
||||
KU_DIGITAL_SIGNATURE, // ECDHE/DHE
|
||||
KU_KEY_ENCIPHERMENT, // RSA
|
||||
KU_KEY_AGREEMENT, // ECDH/DH
|
||||
KeyUsage::digitalSignature, // (EC)DHE
|
||||
KeyUsage::keyEncipherment, // RSA
|
||||
KeyUsage::keyAgreement, // (EC)DH
|
||||
KeyPurposeId::id_kp_serverAuth,
|
||||
CertPolicyId::anyPolicy,
|
||||
stapledOCSPResponse, builtChain);
|
||||
|
@ -453,7 +449,8 @@ CertVerifier::MozillaPKIXVerifyCert(
|
|||
NSSCertDBTrustDomain trustDomain(trustSSL, ocspFetching, mOCSPCache,
|
||||
pinArg, ocspGETConfig);
|
||||
rv = BuildCertChain(trustDomain, cert, time, EndEntityOrCA::MustBeCA,
|
||||
KU_KEY_CERT_SIGN, KeyPurposeId::id_kp_serverAuth,
|
||||
KeyUsage::keyCertSign,
|
||||
KeyPurposeId::id_kp_serverAuth,
|
||||
CertPolicyId::anyPolicy,
|
||||
stapledOCSPResponse, builtChain);
|
||||
break;
|
||||
|
@ -463,7 +460,8 @@ CertVerifier::MozillaPKIXVerifyCert(
|
|||
NSSCertDBTrustDomain trustDomain(trustEmail, ocspFetching, mOCSPCache,
|
||||
pinArg, ocspGETConfig);
|
||||
rv = BuildCertChain(trustDomain, cert, time,
|
||||
EndEntityOrCA::MustBeEndEntity, KU_DIGITAL_SIGNATURE,
|
||||
EndEntityOrCA::MustBeEndEntity,
|
||||
KeyUsage::digitalSignature,
|
||||
KeyPurposeId::id_kp_emailProtection,
|
||||
CertPolicyId::anyPolicy,
|
||||
stapledOCSPResponse, builtChain);
|
||||
|
@ -476,13 +474,20 @@ CertVerifier::MozillaPKIXVerifyCert(
|
|||
// based on the result of the verification(s).
|
||||
NSSCertDBTrustDomain trustDomain(trustEmail, ocspFetching, mOCSPCache,
|
||||
pinArg, ocspGETConfig);
|
||||
rv = BuildCertChainForOneKeyUsage(trustDomain, cert, time,
|
||||
KU_KEY_ENCIPHERMENT, // RSA
|
||||
KU_KEY_AGREEMENT, // ECDH/DH
|
||||
0,
|
||||
KeyPurposeId::id_kp_emailProtection,
|
||||
CertPolicyId::anyPolicy,
|
||||
stapledOCSPResponse, builtChain);
|
||||
rv = BuildCertChain(trustDomain, cert, time,
|
||||
EndEntityOrCA::MustBeEndEntity,
|
||||
KeyUsage::keyEncipherment, // RSA
|
||||
KeyPurposeId::id_kp_emailProtection,
|
||||
CertPolicyId::anyPolicy,
|
||||
stapledOCSPResponse, builtChain);
|
||||
if (rv != SECSuccess && PR_GetError() == SEC_ERROR_INADEQUATE_KEY_USAGE) {
|
||||
rv = BuildCertChain(trustDomain, cert, time,
|
||||
EndEntityOrCA::MustBeEndEntity,
|
||||
KeyUsage::keyAgreement, // ECDH/DH
|
||||
KeyPurposeId::id_kp_emailProtection,
|
||||
CertPolicyId::anyPolicy,
|
||||
stapledOCSPResponse, builtChain);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -490,7 +495,8 @@ CertVerifier::MozillaPKIXVerifyCert(
|
|||
NSSCertDBTrustDomain trustDomain(trustObjectSigning, ocspFetching,
|
||||
mOCSPCache, pinArg, ocspGETConfig);
|
||||
rv = BuildCertChain(trustDomain, cert, time,
|
||||
EndEntityOrCA::MustBeEndEntity, KU_DIGITAL_SIGNATURE,
|
||||
EndEntityOrCA::MustBeEndEntity,
|
||||
KeyUsage::digitalSignature,
|
||||
KeyPurposeId::id_kp_codeSigning,
|
||||
CertPolicyId::anyPolicy,
|
||||
stapledOCSPResponse, builtChain);
|
||||
|
@ -504,15 +510,15 @@ CertVerifier::MozillaPKIXVerifyCert(
|
|||
// certificate viewer UI. Because we don't know what trust bit is
|
||||
// interesting, we just try them all.
|
||||
mozilla::pkix::EndEntityOrCA endEntityOrCA;
|
||||
mozilla::pkix::KeyUsages keyUsage;
|
||||
mozilla::pkix::KeyUsage keyUsage;
|
||||
KeyPurposeId eku;
|
||||
if (usage == certificateUsageVerifyCA) {
|
||||
endEntityOrCA = EndEntityOrCA::MustBeCA;
|
||||
keyUsage = KU_KEY_CERT_SIGN;
|
||||
keyUsage = KeyUsage::keyCertSign;
|
||||
eku = KeyPurposeId::anyExtendedKeyUsage;
|
||||
} else {
|
||||
endEntityOrCA = EndEntityOrCA::MustBeEndEntity;
|
||||
keyUsage = KU_DIGITAL_SIGNATURE;
|
||||
keyUsage = KeyUsage::digitalSignature;
|
||||
eku = KeyPurposeId::id_kp_OCSPSigning;
|
||||
}
|
||||
|
||||
|
|
|
@ -93,7 +93,7 @@ SECStatus BuildCertChain(TrustDomain& trustDomain,
|
|||
const CERTCertificate* cert,
|
||||
PRTime time,
|
||||
EndEntityOrCA endEntityOrCA,
|
||||
/*optional*/ KeyUsages requiredKeyUsagesIfPresent,
|
||||
KeyUsage requiredKeyUsageIfPresent,
|
||||
KeyPurposeId requiredEKUIfPresent,
|
||||
const CertPolicyId& requiredPolicy,
|
||||
/*optional*/ const SECItem* stapledOCSPResponse,
|
||||
|
|
|
@ -49,7 +49,18 @@ typedef ScopedPtr<CERTCertList, CERT_DestroyCertList> ScopedCERTCertList;
|
|||
|
||||
MOZILLA_PKIX_ENUM_CLASS EndEntityOrCA { MustBeEndEntity = 0, MustBeCA = 1 };
|
||||
|
||||
typedef unsigned int KeyUsages;
|
||||
MOZILLA_PKIX_ENUM_CLASS KeyUsage : uint8_t {
|
||||
digitalSignature = 0,
|
||||
nonRepudiation = 1,
|
||||
keyEncipherment = 2,
|
||||
dataEncipherment = 3,
|
||||
keyAgreement = 4,
|
||||
keyCertSign = 5,
|
||||
// cRLSign = 6,
|
||||
// encipherOnly = 7,
|
||||
// decipherOnly = 8,
|
||||
noParticularKeyUsageRequired = 0xff,
|
||||
};
|
||||
|
||||
MOZILLA_PKIX_ENUM_CLASS KeyPurposeId {
|
||||
anyExtendedKeyUsage = 0,
|
||||
|
|
|
@ -142,7 +142,7 @@ static Result BuildForward(TrustDomain& trustDomain,
|
|||
BackCert& subject,
|
||||
PRTime time,
|
||||
EndEntityOrCA endEntityOrCA,
|
||||
KeyUsages requiredKeyUsagesIfPresent,
|
||||
KeyUsage requiredKeyUsageIfPresent,
|
||||
KeyPurposeId requiredEKUIfPresent,
|
||||
const CertPolicyId& requiredPolicy,
|
||||
/*optional*/ const SECItem* stapledOCSPResponse,
|
||||
|
@ -158,7 +158,7 @@ BuildForwardInner(TrustDomain& trustDomain,
|
|||
const CertPolicyId& requiredPolicy,
|
||||
const SECItem& potentialIssuerDER,
|
||||
unsigned int subCACount,
|
||||
ScopedCERTCertList& results)
|
||||
/*out*/ ScopedCERTCertList& results)
|
||||
{
|
||||
BackCert potentialIssuer(&subject, BackCert::IncludeCN::No);
|
||||
Result rv = potentialIssuer.Init(potentialIssuerDER);
|
||||
|
@ -188,9 +188,12 @@ BuildForwardInner(TrustDomain& trustDomain,
|
|||
return rv;
|
||||
}
|
||||
|
||||
// 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."
|
||||
rv = BuildForward(trustDomain, potentialIssuer, time, EndEntityOrCA::MustBeCA,
|
||||
KU_KEY_CERT_SIGN, requiredEKUIfPresent, requiredPolicy,
|
||||
nullptr, subCACount, results);
|
||||
KeyUsage::keyCertSign, requiredEKUIfPresent,
|
||||
requiredPolicy, nullptr, subCACount, results);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
|
@ -210,7 +213,7 @@ BuildForward(TrustDomain& trustDomain,
|
|||
BackCert& subject,
|
||||
PRTime time,
|
||||
EndEntityOrCA endEntityOrCA,
|
||||
KeyUsages requiredKeyUsagesIfPresent,
|
||||
KeyUsage requiredKeyUsageIfPresent,
|
||||
KeyPurposeId requiredEKUIfPresent,
|
||||
const CertPolicyId& requiredPolicy,
|
||||
/*optional*/ const SECItem* stapledOCSPResponse,
|
||||
|
@ -225,7 +228,7 @@ BuildForward(TrustDomain& trustDomain,
|
|||
// See the explanation of error prioritization in pkix.h.
|
||||
rv = CheckIssuerIndependentProperties(trustDomain, subject, time,
|
||||
endEntityOrCA,
|
||||
requiredKeyUsagesIfPresent,
|
||||
requiredKeyUsageIfPresent,
|
||||
requiredEKUIfPresent, requiredPolicy,
|
||||
subCACount, &trustLevel);
|
||||
PRErrorCode deferredEndEntityError = 0;
|
||||
|
@ -348,8 +351,8 @@ BuildCertChain(TrustDomain& trustDomain,
|
|||
const CERTCertificate* nssCert,
|
||||
PRTime time,
|
||||
EndEntityOrCA endEntityOrCA,
|
||||
/*optional*/ KeyUsages requiredKeyUsagesIfPresent,
|
||||
/*optional*/ KeyPurposeId requiredEKUIfPresent,
|
||||
KeyUsage requiredKeyUsageIfPresent,
|
||||
KeyPurposeId requiredEKUIfPresent,
|
||||
const CertPolicyId& requiredPolicy,
|
||||
/*optional*/ const SECItem* stapledOCSPResponse,
|
||||
/*out*/ ScopedCERTCertList& results)
|
||||
|
@ -375,7 +378,7 @@ BuildCertChain(TrustDomain& trustDomain,
|
|||
}
|
||||
|
||||
rv = BuildForward(trustDomain, cert, time, endEntityOrCA,
|
||||
requiredKeyUsagesIfPresent, requiredEKUIfPresent,
|
||||
requiredKeyUsageIfPresent, requiredEKUIfPresent,
|
||||
requiredPolicy, stapledOCSPResponse, 0, results);
|
||||
if (rv != Success) {
|
||||
results = nullptr;
|
||||
|
|
|
@ -46,12 +46,18 @@ CheckTimes(const CERTCertificate* cert, PRTime time)
|
|||
}
|
||||
|
||||
// 4.2.1.3. Key Usage (id-ce-keyUsage)
|
||||
// Modeled after GetKeyUsage in certdb.c
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
Result
|
||||
CheckKeyUsage(EndEntityOrCA endEntityOrCA,
|
||||
const SECItem* encodedKeyUsage,
|
||||
KeyUsages requiredKeyUsagesIfPresent,
|
||||
PLArenaPool* arena)
|
||||
CheckKeyUsage(EndEntityOrCA endEntityOrCA, const SECItem* encodedKeyUsage,
|
||||
KeyUsage requiredKeyUsageIfPresent)
|
||||
{
|
||||
if (!encodedKeyUsage) {
|
||||
// TODO(bug 970196): Reject certificates that are being used to verify
|
||||
|
@ -68,39 +74,92 @@ CheckKeyUsage(EndEntityOrCA endEntityOrCA,
|
|||
return Success;
|
||||
}
|
||||
|
||||
SECItem tmpItem;
|
||||
Result rv = MapSECStatus(SEC_QuickDERDecodeItem(arena, &tmpItem,
|
||||
SEC_ASN1_GET(SEC_BitStringTemplate),
|
||||
encodedKeyUsage));
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
der::Input input;
|
||||
if (input.Init(encodedKeyUsage->data, encodedKeyUsage->len) != der::Success) {
|
||||
return Fail(RecoverableError, SEC_ERROR_INADEQUATE_KEY_USAGE);
|
||||
}
|
||||
|
||||
// TODO XXX: Why is tmpItem.len > 1?
|
||||
|
||||
KeyUsages allowedKeyUsages = tmpItem.data[0];
|
||||
if ((allowedKeyUsages & requiredKeyUsagesIfPresent)
|
||||
!= requiredKeyUsagesIfPresent) {
|
||||
der::Input value;
|
||||
if (der::ExpectTagAndGetValue(input, der::BIT_STRING, value) != der::Success) {
|
||||
return Fail(RecoverableError, SEC_ERROR_INADEQUATE_KEY_USAGE);
|
||||
}
|
||||
|
||||
if (endEntityOrCA == EndEntityOrCA::MustBeCA) {
|
||||
// "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."
|
||||
if ((allowedKeyUsages & KU_KEY_CERT_SIGN) == 0) {
|
||||
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);
|
||||
}
|
||||
|
||||
uint8_t bits;
|
||||
if (value.Read(bits) != der::Success) {
|
||||
// Reject empty bit masks.
|
||||
return Fail(RecoverableError, SEC_ERROR_INADEQUATE_KEY_USAGE);
|
||||
}
|
||||
|
||||
// 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) {
|
||||
return Fail(RecoverableError, SEC_ERROR_INADEQUATE_KEY_USAGE);
|
||||
}
|
||||
} else {
|
||||
// "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."
|
||||
// TODO XXX: commented out to match classic NSS behavior.
|
||||
//if ((allowedKeyUsages & KU_KEY_CERT_SIGN) != 0) {
|
||||
// // XXX: better error code.
|
||||
// return Fail(RecoverableError, SEC_ERROR_INADEQUATE_CERT_TYPE);
|
||||
//}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
return Success;
|
||||
|
@ -549,7 +608,7 @@ CheckIssuerIndependentProperties(TrustDomain& trustDomain,
|
|||
BackCert& cert,
|
||||
PRTime time,
|
||||
EndEntityOrCA endEntityOrCA,
|
||||
KeyUsages requiredKeyUsagesIfPresent,
|
||||
KeyUsage requiredKeyUsageIfPresent,
|
||||
KeyPurposeId requiredEKUIfPresent,
|
||||
const CertPolicyId& requiredPolicy,
|
||||
unsigned int subCACount,
|
||||
|
@ -584,18 +643,13 @@ CheckIssuerIndependentProperties(TrustDomain& trustDomain,
|
|||
!cert.GetNSSCert()->version.len) ? der::Version::v1
|
||||
: der::Version::v3;
|
||||
|
||||
PLArenaPool* arena = cert.GetArena();
|
||||
if (!arena) {
|
||||
return FatalError;
|
||||
}
|
||||
|
||||
// 4.2.1.1. Authority Key Identifier is ignored (see bug 965136).
|
||||
|
||||
// 4.2.1.2. Subject Key Identifier is ignored (see bug 965136).
|
||||
|
||||
// 4.2.1.3. Key Usage
|
||||
rv = CheckKeyUsage(endEntityOrCA, cert.encodedKeyUsage,
|
||||
requiredKeyUsagesIfPresent, arena);
|
||||
requiredKeyUsageIfPresent);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
|
|
|
@ -36,7 +36,7 @@ Result CheckIssuerIndependentProperties(
|
|||
BackCert& cert,
|
||||
PRTime time,
|
||||
EndEntityOrCA endEntityOrCA,
|
||||
KeyUsages requiredKeyUsagesIfPresent,
|
||||
KeyUsage requiredKeyUsageIfPresent,
|
||||
KeyPurposeId requiredEKUIfPresent,
|
||||
const CertPolicyId& requiredPolicy,
|
||||
unsigned int subCACount,
|
||||
|
|
|
@ -147,7 +147,8 @@ CheckOCSPResponseSignerCert(TrustDomain& trustDomain,
|
|||
// 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, potentialSigner, time,
|
||||
EndEntityOrCA::MustBeEndEntity, 0,
|
||||
EndEntityOrCA::MustBeEndEntity,
|
||||
KeyUsage::noParticularKeyUsageRequired,
|
||||
KeyPurposeId::id_kp_OCSPSigning,
|
||||
CertPolicyId::anyPolicy, 0);
|
||||
if (rv != Success) {
|
||||
|
|
|
@ -11,6 +11,7 @@ SOURCES += [
|
|||
'pkix_cert_chain_length_tests.cpp',
|
||||
'pkix_cert_extension_tests.cpp',
|
||||
'pkix_ocsp_request_tests.cpp',
|
||||
'pkixcheck_CheckKeyUsage_tests.cpp',
|
||||
'pkixder_input_tests.cpp',
|
||||
'pkixder_pki_types_tests.cpp',
|
||||
'pkixder_universal_types_tests.cpp',
|
||||
|
|
|
@ -190,7 +190,7 @@ TEST_F(pkix_cert_chain_length, MaxAcceptableCertChainLength)
|
|||
ScopedCERTCertList results;
|
||||
ASSERT_SECSuccess(BuildCertChain(trustDomain, trustDomain.GetLeafeCACert(),
|
||||
now, EndEntityOrCA::MustBeCA,
|
||||
0, // key usage
|
||||
KeyUsage::noParticularKeyUsageRequired,
|
||||
KeyPurposeId::id_kp_serverAuth,
|
||||
CertPolicyId::anyPolicy,
|
||||
nullptr, // stapled OCSP response
|
||||
|
@ -205,7 +205,7 @@ TEST_F(pkix_cert_chain_length, MaxAcceptableCertChainLength)
|
|||
trustDomain.leafCAKey.get(), privateKey, cert));
|
||||
ASSERT_SECSuccess(BuildCertChain(trustDomain, cert.get(), now,
|
||||
EndEntityOrCA::MustBeEndEntity,
|
||||
0, // key usage
|
||||
KeyUsage::noParticularKeyUsageRequired,
|
||||
KeyPurposeId::id_kp_serverAuth,
|
||||
CertPolicyId::anyPolicy,
|
||||
nullptr, // stapled OCSP response
|
||||
|
@ -227,7 +227,7 @@ TEST_F(pkix_cert_chain_length, BeyondMaxAcceptableCertChainLength)
|
|||
ASSERT_SECFailure(SEC_ERROR_UNKNOWN_ISSUER,
|
||||
BuildCertChain(trustDomain, caCert.get(), now,
|
||||
EndEntityOrCA::MustBeCA,
|
||||
0, // key usage
|
||||
KeyUsage::noParticularKeyUsageRequired,
|
||||
KeyPurposeId::id_kp_serverAuth,
|
||||
CertPolicyId::anyPolicy,
|
||||
nullptr, // stapled OCSP response
|
||||
|
@ -243,7 +243,7 @@ TEST_F(pkix_cert_chain_length, BeyondMaxAcceptableCertChainLength)
|
|||
ASSERT_SECFailure(SEC_ERROR_UNKNOWN_ISSUER,
|
||||
BuildCertChain(trustDomain, cert.get(), now,
|
||||
EndEntityOrCA::MustBeEndEntity,
|
||||
0, // key usage
|
||||
KeyUsage::noParticularKeyUsageRequired,
|
||||
KeyPurposeId::id_kp_serverAuth,
|
||||
CertPolicyId::anyPolicy,
|
||||
nullptr, // stapled OCSP response
|
||||
|
|
|
@ -150,7 +150,7 @@ TEST_F(pkix_cert_extensions, UnknownCriticalExtension)
|
|||
ASSERT_SECFailure(SEC_ERROR_UNKNOWN_CRITICAL_EXTENSION,
|
||||
BuildCertChain(trustDomain, cert.get(),
|
||||
now, EndEntityOrCA::MustBeEndEntity,
|
||||
0, // key usage
|
||||
KeyUsage::noParticularKeyUsageRequired,
|
||||
KeyPurposeId::anyExtendedKeyUsage,
|
||||
CertPolicyId::anyPolicy,
|
||||
nullptr, // stapled OCSP response
|
||||
|
@ -182,7 +182,7 @@ TEST_F(pkix_cert_extensions, UnknownNonCriticalExtension)
|
|||
ScopedCERTCertList results;
|
||||
ASSERT_SECSuccess(BuildCertChain(trustDomain, cert.get(),
|
||||
now, EndEntityOrCA::MustBeEndEntity,
|
||||
0, // key usage
|
||||
KeyUsage::noParticularKeyUsageRequired,
|
||||
KeyPurposeId::anyExtendedKeyUsage,
|
||||
CertPolicyId::anyPolicy,
|
||||
nullptr, // stapled OCSP response
|
||||
|
@ -216,7 +216,7 @@ TEST_F(pkix_cert_extensions, WrongOIDCriticalExtension)
|
|||
ASSERT_SECFailure(SEC_ERROR_UNKNOWN_CRITICAL_EXTENSION,
|
||||
BuildCertChain(trustDomain, cert.get(),
|
||||
now, EndEntityOrCA::MustBeEndEntity,
|
||||
0, // key usage
|
||||
KeyUsage::noParticularKeyUsageRequired,
|
||||
KeyPurposeId::anyExtendedKeyUsage,
|
||||
CertPolicyId::anyPolicy,
|
||||
nullptr, // stapled OCSP response
|
||||
|
@ -248,7 +248,7 @@ TEST_F(pkix_cert_extensions, CriticalAIAExtension)
|
|||
ScopedCERTCertList results;
|
||||
ASSERT_SECSuccess(BuildCertChain(trustDomain, cert.get(),
|
||||
now, EndEntityOrCA::MustBeEndEntity,
|
||||
0, // key usage
|
||||
KeyUsage::noParticularKeyUsageRequired,
|
||||
KeyPurposeId::anyExtendedKeyUsage,
|
||||
CertPolicyId::anyPolicy,
|
||||
nullptr, // stapled OCSP response
|
||||
|
@ -281,7 +281,7 @@ TEST_F(pkix_cert_extensions, UnknownCriticalCEExtension)
|
|||
ASSERT_SECFailure(SEC_ERROR_UNKNOWN_CRITICAL_EXTENSION,
|
||||
BuildCertChain(trustDomain, cert.get(),
|
||||
now, EndEntityOrCA::MustBeEndEntity,
|
||||
0, // key usage
|
||||
KeyUsage::noParticularKeyUsageRequired,
|
||||
KeyPurposeId::anyExtendedKeyUsage,
|
||||
CertPolicyId::anyPolicy,
|
||||
nullptr, // stapled OCSP response
|
||||
|
@ -311,7 +311,7 @@ TEST_F(pkix_cert_extensions, KnownCriticalCEExtension)
|
|||
ScopedCERTCertList results;
|
||||
ASSERT_SECSuccess(BuildCertChain(trustDomain, cert.get(),
|
||||
now, EndEntityOrCA::MustBeEndEntity,
|
||||
0, // key usage
|
||||
KeyUsage::noParticularKeyUsageRequired,
|
||||
KeyPurposeId::anyExtendedKeyUsage,
|
||||
CertPolicyId::anyPolicy,
|
||||
nullptr, // stapled OCSP response
|
||||
|
|
|
@ -0,0 +1,272 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* 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
|
||||
*
|
||||
* 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 "pkixgtest.h"
|
||||
|
||||
using namespace mozilla::pkix;
|
||||
using namespace mozilla::pkix::test;
|
||||
|
||||
namespace mozilla { namespace pkix {
|
||||
|
||||
extern Result CheckKeyUsage(EndEntityOrCA endEntityOrCA,
|
||||
const SECItem* encodedKeyUsage,
|
||||
KeyUsage requiredKeyUsageIfPresent);
|
||||
|
||||
} } // namespace mozilla::pkix
|
||||
|
||||
#define ASSERT_BAD(x) \
|
||||
ASSERT_RecoverableError(SEC_ERROR_INADEQUATE_KEY_USAGE, x)
|
||||
|
||||
// Make it easy to define test data for the common, simplest cases.
|
||||
#define NAMED_SIMPLE_KU(name, unusedBits, bits) \
|
||||
const uint8_t name##_bytes[4] = { \
|
||||
0x03/*BIT STRING*/, 0x02/*LENGTH=2*/, unusedBits, bits \
|
||||
}; \
|
||||
const SECItem name = { \
|
||||
siBuffer, \
|
||||
const_cast<uint8_t*>(name##_bytes), \
|
||||
4 \
|
||||
}
|
||||
|
||||
static uint8_t dummy;
|
||||
static const SECItem empty_null = { siBuffer, nullptr, 0 };
|
||||
static const SECItem empty_nonnull = { siBuffer, &dummy, 0 };
|
||||
|
||||
// Note that keyCertSign is really the only interesting case for CA
|
||||
// certificates since we don't support cRLSign.
|
||||
|
||||
TEST(pkixcheck_CheckKeyUsage, EE_none)
|
||||
{
|
||||
// The input SECItem is nullptr. This means the cert had no keyUsage
|
||||
// extension. This is always valid because no key usage in an end-entity
|
||||
// means that there are no key usage restrictions.
|
||||
|
||||
ASSERT_Success(CheckKeyUsage(EndEntityOrCA::MustBeEndEntity, nullptr,
|
||||
KeyUsage::noParticularKeyUsageRequired));
|
||||
ASSERT_Success(CheckKeyUsage(EndEntityOrCA::MustBeEndEntity, nullptr,
|
||||
KeyUsage::digitalSignature));
|
||||
ASSERT_Success(CheckKeyUsage(EndEntityOrCA::MustBeEndEntity, nullptr,
|
||||
KeyUsage::nonRepudiation));
|
||||
ASSERT_Success(CheckKeyUsage(EndEntityOrCA::MustBeEndEntity, nullptr,
|
||||
KeyUsage::keyEncipherment));
|
||||
ASSERT_Success(CheckKeyUsage(EndEntityOrCA::MustBeEndEntity, nullptr,
|
||||
KeyUsage::dataEncipherment));
|
||||
ASSERT_Success(CheckKeyUsage(EndEntityOrCA::MustBeEndEntity, nullptr,
|
||||
KeyUsage::keyAgreement));
|
||||
}
|
||||
|
||||
TEST(pkixcheck_CheckKeyUsage, EE_empty)
|
||||
{
|
||||
// The input SECItem is empty. The cert had an empty keyUsage extension,
|
||||
// which is syntactically invalid.
|
||||
ASSERT_BAD(CheckKeyUsage(EndEntityOrCA::MustBeEndEntity, &empty_null,
|
||||
KeyUsage::digitalSignature));
|
||||
ASSERT_BAD(CheckKeyUsage(EndEntityOrCA::MustBeEndEntity, &empty_nonnull,
|
||||
KeyUsage::digitalSignature));
|
||||
}
|
||||
|
||||
TEST(pkixcheck_CheckKeyUsage, CA_none)
|
||||
{
|
||||
// A CA certificate does not have a KU extension.
|
||||
ASSERT_Success(CheckKeyUsage(EndEntityOrCA::MustBeCA, nullptr,
|
||||
KeyUsage::keyCertSign));
|
||||
}
|
||||
|
||||
TEST(pkixcheck_CheckKeyUsage, CA_empty)
|
||||
{
|
||||
// A CA certificate has an empty KU extension.
|
||||
ASSERT_BAD(CheckKeyUsage(EndEntityOrCA::MustBeCA, &empty_null,
|
||||
KeyUsage::keyCertSign));
|
||||
ASSERT_BAD(CheckKeyUsage(EndEntityOrCA::MustBeCA, &empty_nonnull,
|
||||
KeyUsage::keyCertSign));
|
||||
}
|
||||
|
||||
TEST(pkixchekc_CheckKeyusage, maxUnusedBits)
|
||||
{
|
||||
NAMED_SIMPLE_KU(encoded, 7, 0x80);
|
||||
ASSERT_Success(CheckKeyUsage(EndEntityOrCA::MustBeEndEntity, &encoded,
|
||||
KeyUsage::digitalSignature));
|
||||
}
|
||||
|
||||
TEST(pkixchekc_CheckKeyusage, tooManyUnusedBits)
|
||||
{
|
||||
static uint8_t oneValueByteData[] = {
|
||||
0x03/*BIT STRING*/, 0x02/*LENGTH=2*/, 8/*unused bits*/, 0x80
|
||||
};
|
||||
const SECItem oneValueByte = {
|
||||
siBuffer,
|
||||
oneValueByteData,
|
||||
sizeof(oneValueByteData)
|
||||
};
|
||||
ASSERT_BAD(CheckKeyUsage(EndEntityOrCA::MustBeEndEntity, &oneValueByte,
|
||||
KeyUsage::digitalSignature));
|
||||
|
||||
static uint8_t twoValueBytesData[] = {
|
||||
0x03/*BIT STRING*/, 0x03/*LENGTH=3*/, 8/*unused bits*/, 0x01, 0x00
|
||||
};
|
||||
const SECItem twoValueBytes = {
|
||||
siBuffer,
|
||||
twoValueBytesData,
|
||||
sizeof(twoValueBytesData)
|
||||
};
|
||||
ASSERT_BAD(CheckKeyUsage(EndEntityOrCA::MustBeEndEntity, &twoValueBytes,
|
||||
KeyUsage::digitalSignature));
|
||||
}
|
||||
|
||||
void ASSERT_SimpleCase(uint8_t unusedBits, uint8_t bits, KeyUsage usage)
|
||||
{
|
||||
// Test that only the right bit is accepted for the usage for both EE and CA
|
||||
// certs.
|
||||
NAMED_SIMPLE_KU(good, unusedBits, bits);
|
||||
ASSERT_Success(CheckKeyUsage(EndEntityOrCA::MustBeEndEntity, &good, usage));
|
||||
ASSERT_Success(CheckKeyUsage(EndEntityOrCA::MustBeCA, &good, usage));
|
||||
|
||||
// We use (~bits >> unusedBits) << unusedBits) instead of using the same
|
||||
// calculation that is in CheckKeyUsage to validate that the calculation in
|
||||
// CheckKeyUsage is correct.
|
||||
|
||||
// Test that none of the other non-padding bits are mistaken for the given
|
||||
// key usage in the single-byte value case.
|
||||
NAMED_SIMPLE_KU(notGood, unusedBits,
|
||||
static_cast<uint8_t>((~bits >> unusedBits) << unusedBits));
|
||||
ASSERT_BAD(CheckKeyUsage(EndEntityOrCA::MustBeEndEntity, ¬Good, usage));
|
||||
ASSERT_BAD(CheckKeyUsage(EndEntityOrCA::MustBeCA, ¬Good, usage));
|
||||
|
||||
// Test that none of the other non-padding bits are mistaken for the given
|
||||
// key usage in the two-byte value case.
|
||||
uint8_t twoByteNotGoodData[] = {
|
||||
0x03/*BIT STRING*/, 0x03/*LENGTH=3*/, unusedBits,
|
||||
static_cast<uint8_t>(~bits),
|
||||
static_cast<uint8_t>((0xFFu >> unusedBits) << unusedBits)
|
||||
};
|
||||
const SECItem twoByteNotGood = {
|
||||
siBuffer, twoByteNotGoodData, sizeof(twoByteNotGoodData)
|
||||
};
|
||||
ASSERT_BAD(CheckKeyUsage(EndEntityOrCA::MustBeEndEntity, &twoByteNotGood,
|
||||
usage));
|
||||
ASSERT_BAD(CheckKeyUsage(EndEntityOrCA::MustBeCA, &twoByteNotGood, usage));
|
||||
}
|
||||
|
||||
TEST(pkixcheck_CheckKeyUsage, simpleCases)
|
||||
{
|
||||
ASSERT_SimpleCase(7, 0x80, KeyUsage::digitalSignature);
|
||||
ASSERT_SimpleCase(6, 0x40, KeyUsage::nonRepudiation);
|
||||
ASSERT_SimpleCase(5, 0x20, KeyUsage::keyEncipherment);
|
||||
ASSERT_SimpleCase(4, 0x10, KeyUsage::dataEncipherment);
|
||||
ASSERT_SimpleCase(3, 0x08, KeyUsage::keyAgreement);
|
||||
}
|
||||
|
||||
// Only CAs are allowed to assert keyCertSign
|
||||
TEST(pkixcheck_CheckKeyUsage, keyCertSign)
|
||||
{
|
||||
NAMED_SIMPLE_KU(good, 2, 0x04);
|
||||
ASSERT_BAD(CheckKeyUsage(EndEntityOrCA::MustBeEndEntity, &good,
|
||||
KeyUsage::keyCertSign));
|
||||
ASSERT_Success(CheckKeyUsage(EndEntityOrCA::MustBeCA, &good,
|
||||
KeyUsage::keyCertSign));
|
||||
|
||||
// Test that none of the other non-padding bits are mistaken for the given
|
||||
// key usage in the one-byte value case.
|
||||
NAMED_SIMPLE_KU(notGood, 2, 0xFB);
|
||||
ASSERT_BAD(CheckKeyUsage(EndEntityOrCA::MustBeEndEntity, ¬Good,
|
||||
KeyUsage::keyCertSign));
|
||||
ASSERT_BAD(CheckKeyUsage(EndEntityOrCA::MustBeCA, ¬Good,
|
||||
KeyUsage::keyCertSign));
|
||||
|
||||
// Test that none of the other non-padding bits are mistaken for the given
|
||||
// key usage in the two-byte value case.
|
||||
static uint8_t twoByteNotGoodData[] = {
|
||||
0x03/*BIT STRING*/, 0x03/*LENGTH=3*/, 2/*unused bits*/, 0xFBu, 0xFCu
|
||||
};
|
||||
static const SECItem twoByteNotGood = {
|
||||
siBuffer, twoByteNotGoodData, sizeof(twoByteNotGoodData)
|
||||
};
|
||||
ASSERT_BAD(CheckKeyUsage(EndEntityOrCA::MustBeEndEntity, &twoByteNotGood,
|
||||
KeyUsage::keyCertSign));
|
||||
ASSERT_BAD(CheckKeyUsage(EndEntityOrCA::MustBeCA, &twoByteNotGood,
|
||||
KeyUsage::keyCertSign));
|
||||
}
|
||||
|
||||
TEST(pkixcheck_CheckKeyUsage, unusedBitNotZero)
|
||||
{
|
||||
// single byte control case
|
||||
static uint8_t controlOneValueByteData[] = {
|
||||
0x03/*BIT STRING*/, 0x02/*LENGTH=2*/, 7/*unused bits*/, 0x80
|
||||
};
|
||||
const SECItem controlOneValueByte = {
|
||||
siBuffer,
|
||||
controlOneValueByteData,
|
||||
sizeof(controlOneValueByteData)
|
||||
};
|
||||
ASSERT_Success(CheckKeyUsage(EndEntityOrCA::MustBeEndEntity,
|
||||
&controlOneValueByte,
|
||||
KeyUsage::digitalSignature));
|
||||
ASSERT_Success(CheckKeyUsage(EndEntityOrCA::MustBeCA, &controlOneValueByte,
|
||||
KeyUsage::digitalSignature));
|
||||
|
||||
// single-byte test case
|
||||
static uint8_t oneValueByteData[] = {
|
||||
0x03/*BIT STRING*/, 0x02/*LENGTH=2*/, 7/*unused bits*/, 0x80 | 0x01
|
||||
};
|
||||
const SECItem oneValueByte = {
|
||||
siBuffer,
|
||||
oneValueByteData,
|
||||
sizeof(oneValueByteData)
|
||||
};
|
||||
ASSERT_BAD(CheckKeyUsage(EndEntityOrCA::MustBeEndEntity, &oneValueByte,
|
||||
KeyUsage::digitalSignature));
|
||||
ASSERT_BAD(CheckKeyUsage(EndEntityOrCA::MustBeCA, &oneValueByte,
|
||||
KeyUsage::digitalSignature));
|
||||
|
||||
// two-byte control case
|
||||
static uint8_t controlTwoValueBytesData[] = {
|
||||
0x03/*BIT STRING*/, 0x03/*LENGTH=3*/, 7/*unused bits*/,
|
||||
0x80 | 0x01, 0x80
|
||||
};
|
||||
const SECItem controlTwoValueBytes = {
|
||||
siBuffer,
|
||||
controlTwoValueBytesData,
|
||||
sizeof(controlTwoValueBytesData)
|
||||
};
|
||||
ASSERT_Success(CheckKeyUsage(EndEntityOrCA::MustBeEndEntity,
|
||||
&controlTwoValueBytes,
|
||||
KeyUsage::digitalSignature));
|
||||
ASSERT_Success(CheckKeyUsage(EndEntityOrCA::MustBeCA, &controlTwoValueBytes,
|
||||
KeyUsage::digitalSignature));
|
||||
|
||||
// two-byte test case
|
||||
static uint8_t twoValueBytesData[] = {
|
||||
0x03/*BIT STRING*/, 0x03/*LENGTH=3*/, 7/*unused bits*/,
|
||||
0x80 | 0x01, 0x80 | 0x01
|
||||
};
|
||||
const SECItem twoValueBytes = {
|
||||
siBuffer,
|
||||
twoValueBytesData,
|
||||
sizeof(twoValueBytesData)
|
||||
};
|
||||
ASSERT_BAD(CheckKeyUsage(EndEntityOrCA::MustBeEndEntity, &twoValueBytes,
|
||||
KeyUsage::digitalSignature));
|
||||
ASSERT_BAD(CheckKeyUsage(EndEntityOrCA::MustBeCA, &twoValueBytes,
|
||||
KeyUsage::digitalSignature));
|
||||
}
|
Загрузка…
Ссылка в новой задаче