Bug 1769150 - try all known EV policy OIDs found in a certificate when verifying for EV r=jschanck
Before this patch, the certificate verifier would only attempt to build a trusted path to a root with the first recognized EV OID in the end-entity certificate. Thus, if an end-entity certificate had more than one EV OID, it could fail to verify as EV if an intermediate or root had the "wrong" EV OID. This patch addresses this shortcoming by trying to build a path with each recognized EV OID in the end-entity certificate until it finds one that works. Differential Revision: https://phabricator.services.mozilla.com/D149319
This commit is contained in:
@@ -561,10 +561,10 @@ Result CertVerifier::VerifyCert(
|
|||||||
? NSSCertDBTrustDomain::LocalOnlyOCSPForEV
|
? NSSCertDBTrustDomain::LocalOnlyOCSPForEV
|
||||||
: NSSCertDBTrustDomain::FetchOCSPForEV;
|
: NSSCertDBTrustDomain::FetchOCSPForEV;
|
||||||
|
|
||||||
CertPolicyId evPolicy;
|
nsTArray<CertPolicyId> evPolicies;
|
||||||
bool foundEVPolicy = GetFirstEVPolicy(certBytes, evPolicy);
|
GetKnownEVPolicies(certBytes, evPolicies);
|
||||||
rv = Result::ERROR_UNKNOWN_ERROR;
|
rv = Result::ERROR_UNKNOWN_ERROR;
|
||||||
if (foundEVPolicy) {
|
for (const auto& evPolicy : evPolicies) {
|
||||||
NSSCertDBTrustDomain trustDomain(
|
NSSCertDBTrustDomain trustDomain(
|
||||||
trustSSL, evOCSPFetching, mOCSPCache, pinArg, mOCSPTimeoutSoft,
|
trustSSL, evOCSPFetching, mOCSPCache, pinArg, mOCSPTimeoutSoft,
|
||||||
mOCSPTimeoutHard, mCertShortLifetimeInDays, MIN_RSA_BITS,
|
mOCSPTimeoutHard, mCertShortLifetimeInDays, MIN_RSA_BITS,
|
||||||
@@ -594,6 +594,9 @@ Result CertVerifier::VerifyCert(
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (rv == Success) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
if (flags & FLAG_MUST_BE_EV) {
|
if (flags & FLAG_MUST_BE_EV) {
|
||||||
rv = Result::ERROR_POLICY_VALIDATION_FAILED;
|
rv = Result::ERROR_POLICY_VALIDATION_FAILED;
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -1276,34 +1276,35 @@ nsresult LoadExtendedValidationInfo() {
|
|||||||
return NS_OK;
|
return NS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper function for GetFirstEVPolicy(): reads an EV Policy if there is one
|
// Helper function for GetKnownEVPolicies(): reads an EV Policy if there is one,
|
||||||
bool FindMatchingEVPolicy(Reader& idReader,
|
// and appends it to the given list of CertPolicyIds.
|
||||||
mozilla::pkix::CertPolicyId& policy) {
|
void FindMatchingEVPolicy(Reader& idReader,
|
||||||
|
nsTArray<mozilla::pkix::CertPolicyId>& policies) {
|
||||||
Input cabForumEVIdBytes;
|
Input cabForumEVIdBytes;
|
||||||
Result rv =
|
Result rv =
|
||||||
cabForumEVIdBytes.Init(sCABForumEVId.bytes, sCABForumEVId.numBytes);
|
cabForumEVIdBytes.Init(sCABForumEVId.bytes, sCABForumEVId.numBytes);
|
||||||
if (rv == Success && idReader.MatchRest(cabForumEVIdBytes)) {
|
if (rv == Success && idReader.MatchRest(cabForumEVIdBytes)) {
|
||||||
policy = sCABForumEVId;
|
policies.AppendElement(sCABForumEVId);
|
||||||
return true;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const CertPolicyId& id : sEVInfoIds) {
|
for (const CertPolicyId& id : sEVInfoIds) {
|
||||||
Input idBytes;
|
Input idBytes;
|
||||||
rv = idBytes.Init(id.bytes, id.numBytes);
|
rv = idBytes.Init(id.bytes, id.numBytes);
|
||||||
if (rv == Success && idReader.MatchRest(idBytes)) {
|
if (rv == Success && idReader.MatchRest(idBytes)) {
|
||||||
policy = id;
|
policies.AppendElement(id);
|
||||||
return true;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GetFirstEVPolicy(const nsTArray<uint8_t>& certBytes,
|
void GetKnownEVPolicies(
|
||||||
/*out*/ mozilla::pkix::CertPolicyId& policy) {
|
const nsTArray<uint8_t>& certBytes,
|
||||||
|
/*out*/ nsTArray<mozilla::pkix::CertPolicyId>& policies) {
|
||||||
Input certInput;
|
Input certInput;
|
||||||
Result rv = certInput.Init(certBytes.Elements(), certBytes.Length());
|
Result rv = certInput.Init(certBytes.Elements(), certBytes.Length());
|
||||||
if (rv != Success) {
|
if (rv != Success) {
|
||||||
return false;
|
return;
|
||||||
}
|
}
|
||||||
// we don't use the certificate for path building, so this parameter
|
// we don't use the certificate for path building, so this parameter
|
||||||
// doesn't matter
|
// doesn't matter
|
||||||
@@ -1311,15 +1312,15 @@ bool GetFirstEVPolicy(const nsTArray<uint8_t>& certBytes,
|
|||||||
BackCert cert(certInput, notUsedForPaths, nullptr);
|
BackCert cert(certInput, notUsedForPaths, nullptr);
|
||||||
rv = cert.Init();
|
rv = cert.Init();
|
||||||
if (rv != Success) {
|
if (rv != Success) {
|
||||||
return false;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Input* policies = cert.GetCertificatePolicies();
|
const Input* extensionInput = cert.GetCertificatePolicies();
|
||||||
if (!policies) {
|
if (!extensionInput) {
|
||||||
return false;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Reader extension(*policies);
|
Reader extension(*extensionInput);
|
||||||
Reader certificatePolicies;
|
Reader certificatePolicies;
|
||||||
// certificatePolicies ::= SEQUENCE SIZE (1..MAX) OF PolicyInformation
|
// certificatePolicies ::= SEQUENCE SIZE (1..MAX) OF PolicyInformation
|
||||||
// PolicyInformation ::= SEQUENCE {
|
// PolicyInformation ::= SEQUENCE {
|
||||||
@@ -1329,7 +1330,7 @@ bool GetFirstEVPolicy(const nsTArray<uint8_t>& certBytes,
|
|||||||
// CertPolicyId ::= OBJECT IDENTIFIER
|
// CertPolicyId ::= OBJECT IDENTIFIER
|
||||||
rv = der::ExpectTagAndGetValue(extension, der::SEQUENCE, certificatePolicies);
|
rv = der::ExpectTagAndGetValue(extension, der::SEQUENCE, certificatePolicies);
|
||||||
if (rv != Success || !extension.AtEnd()) {
|
if (rv != Success || !extension.AtEnd()) {
|
||||||
return false;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
do {
|
do {
|
||||||
@@ -1337,22 +1338,18 @@ bool GetFirstEVPolicy(const nsTArray<uint8_t>& certBytes,
|
|||||||
rv = der::ExpectTagAndGetValue(certificatePolicies, der::SEQUENCE,
|
rv = der::ExpectTagAndGetValue(certificatePolicies, der::SEQUENCE,
|
||||||
policyInformation);
|
policyInformation);
|
||||||
if (rv != Success) {
|
if (rv != Success) {
|
||||||
return false;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Reader policyOid;
|
Reader policyOid;
|
||||||
rv = der::ExpectTagAndGetValue(policyInformation, der::OIDTag, policyOid);
|
rv = der::ExpectTagAndGetValue(policyInformation, der::OIDTag, policyOid);
|
||||||
if (rv != Success) {
|
if (rv != Success) {
|
||||||
return false;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// we don't validate policy qualifiers here
|
// we don't validate policy qualifiers here
|
||||||
if (FindMatchingEVPolicy(policyOid, policy)) {
|
FindMatchingEVPolicy(policyOid, policies);
|
||||||
return true;
|
|
||||||
}
|
|
||||||
} while (!certificatePolicies.AtEnd());
|
} while (!certificatePolicies.AtEnd());
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace psm
|
} // namespace psm
|
||||||
|
|||||||
@@ -21,19 +21,16 @@ namespace psm {
|
|||||||
nsresult LoadExtendedValidationInfo();
|
nsresult LoadExtendedValidationInfo();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Finds the first policy OID in the given cert that is known to be an EV policy
|
* Finds all policy OIDs in the given cert that are known to be EV policy OIDs.
|
||||||
* OID.
|
|
||||||
*
|
*
|
||||||
* @param cert
|
* @param cert
|
||||||
* The cert to find the first EV policy of.
|
* The bytes of the cert to find the EV policies of.
|
||||||
* @param policy
|
* @param policies
|
||||||
* The found policy.
|
* The found policies.
|
||||||
* @param policyOidTag
|
|
||||||
* The OID tag of the found policy.
|
|
||||||
* @return true if a suitable policy was found, false otherwise.
|
|
||||||
*/
|
*/
|
||||||
bool GetFirstEVPolicy(const nsTArray<uint8_t>& cert,
|
void GetKnownEVPolicies(
|
||||||
/*out*/ mozilla::pkix::CertPolicyId& policy);
|
const nsTArray<uint8_t>& cert,
|
||||||
|
/*out*/ nsTArray<mozilla::pkix::CertPolicyId>& policies);
|
||||||
|
|
||||||
// CertIsAuthoritativeForEVPolicy does NOT evaluate whether the cert is trusted
|
// CertIsAuthoritativeForEVPolicy does NOT evaluate whether the cert is trusted
|
||||||
// or distrusted.
|
// or distrusted.
|
||||||
|
|||||||
@@ -220,9 +220,14 @@ add_task(async function plainExpectSuccessEVTests() {
|
|||||||
await ensureVerifiesAsEV("reverse-order-oids-path");
|
await ensureVerifiesAsEV("reverse-order-oids-path");
|
||||||
// In this case, the end-entity has both the CA/B Forum OID and the test OID
|
// In this case, the end-entity has both the CA/B Forum OID and the test OID
|
||||||
// (in that order). The intermediate has the CA/B Forum OID. Since the
|
// (in that order). The intermediate has the CA/B Forum OID. Since the
|
||||||
// implementation uses the first EV policy it encounters in the end-entity as
|
// implementation tries all EV policies it encounters, this successfully
|
||||||
// the required one, this successfully verifies as EV.
|
// verifies as EV.
|
||||||
await ensureVerifiesAsEV("cabforum-and-test-oid-ee-cabforum-oid-int-path");
|
await ensureVerifiesAsEV("cabforum-and-test-oid-ee-cabforum-oid-int-path");
|
||||||
|
// In this case, the end-entity has both the test OID and the CA/B Forum OID
|
||||||
|
// (in that order). The intermediate has only the CA/B Forum OID. Since the
|
||||||
|
// implementation tries all EV policies it encounters, this successfully
|
||||||
|
// verifies as EV.
|
||||||
|
await ensureVerifiesAsEV("test-and-cabforum-oid-ee-cabforum-oid-int-path");
|
||||||
});
|
});
|
||||||
|
|
||||||
// These fail for various reasons to verify as EV, but fallback to DV should
|
// These fail for various reasons to verify as EV, but fallback to DV should
|
||||||
@@ -236,11 +241,6 @@ add_task(async function expectDVFallbackTests() {
|
|||||||
// CA/B Forum OID. Since the CA/B Forum OID is not treated the same as the
|
// CA/B Forum OID. Since the CA/B Forum OID is not treated the same as the
|
||||||
// anyPolicy OID, this will not verify as EV.
|
// anyPolicy OID, this will not verify as EV.
|
||||||
await ensureVerifiesAsDV("test-oid-ee-cabforum-oid-int-path");
|
await ensureVerifiesAsDV("test-oid-ee-cabforum-oid-int-path");
|
||||||
// In this case, the end-entity has both the test OID and the CA/B Forum OID
|
|
||||||
// (in that order). The intermediate has only the CA/B Forum OID. Since the
|
|
||||||
// implementation uses the first EV policy it encounters in the end-entity as
|
|
||||||
// the required one, this fails to verify as EV.
|
|
||||||
await ensureVerifiesAsDV("test-and-cabforum-oid-ee-cabforum-oid-int-path");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Test that removing the trust bits from an EV root causes verifications
|
// Test that removing the trust bits from an EV root causes verifications
|
||||||
|
|||||||
Reference in New Issue
Block a user