Refactor trial cert comparison verifier to extract logic for comparing
results; reuse that logic in cert_verify_comparision_tool. Bug: 1248209 Change-Id: Ic99a5c4eaf66753fc7effffbf80ee6d523e798a4 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3243523 Commit-Queue: Hubert Chao <hchao@chromium.org> Reviewed-by: Matt Mueller <mattm@chromium.org> Cr-Commit-Position: refs/heads/main@{#936138}
This commit is contained in:

committed by
Chromium LUCI CQ

parent
7d27c1ec05
commit
99aa7f45b6
@ -379,6 +379,8 @@ component("net") {
|
||||
"cert/symantec_certs.h",
|
||||
"cert/test_root_certs.cc",
|
||||
"cert/test_root_certs.h",
|
||||
"cert/trial_comparison_cert_verifier_util.cc",
|
||||
"cert/trial_comparison_cert_verifier_util.h",
|
||||
"cert/x509_cert_types.cc",
|
||||
"cert/x509_cert_types.h",
|
||||
"cert/x509_certificate.cc",
|
||||
|
@ -13,14 +13,11 @@
|
||||
#include "base/task/post_task.h"
|
||||
#include "base/values.h"
|
||||
#include "build/build_config.h"
|
||||
#include "crypto/sha2.h"
|
||||
#include "net/base/net_errors.h"
|
||||
#include "net/cert/cert_verify_proc.h"
|
||||
#include "net/cert/cert_verify_result.h"
|
||||
#include "net/cert/ev_root_ca_metadata.h"
|
||||
#include "net/cert/internal/cert_errors.h"
|
||||
#include "net/cert/internal/parsed_certificate.h"
|
||||
#include "net/cert/multi_threaded_cert_verifier.h"
|
||||
#include "net/cert/trial_comparison_cert_verifier_util.h"
|
||||
#include "net/cert/x509_util.h"
|
||||
#include "net/log/net_log.h"
|
||||
#include "net/log/net_log_event_type.h"
|
||||
@ -37,91 +34,6 @@ base::Value JobResultParams(bool trial_success) {
|
||||
return results;
|
||||
}
|
||||
|
||||
// Note: This ignores the result of stapled OCSP (which is the same for both
|
||||
// verifiers) and informational statuses about the certificate algorithms and
|
||||
// the hashes, since they will be the same if the certificate chains are the
|
||||
// same.
|
||||
bool CertVerifyResultEqual(const CertVerifyResult& a,
|
||||
const CertVerifyResult& b) {
|
||||
return std::tie(a.cert_status, a.is_issued_by_known_root) ==
|
||||
std::tie(b.cert_status, b.is_issued_by_known_root) &&
|
||||
(!!a.verified_cert == !!b.verified_cert) &&
|
||||
(!a.verified_cert ||
|
||||
a.verified_cert->EqualsIncludingChain(b.verified_cert.get()));
|
||||
}
|
||||
|
||||
scoped_refptr<ParsedCertificate> ParsedCertificateFromBuffer(
|
||||
CRYPTO_BUFFER* cert_handle,
|
||||
CertErrors* errors) {
|
||||
return ParsedCertificate::Create(bssl::UpRef(cert_handle),
|
||||
x509_util::DefaultParseCertificateOptions(),
|
||||
errors);
|
||||
}
|
||||
|
||||
ParsedCertificateList ParsedCertificateListFromX509Certificate(
|
||||
const X509Certificate* cert) {
|
||||
CertErrors parsing_errors;
|
||||
|
||||
ParsedCertificateList certs;
|
||||
scoped_refptr<ParsedCertificate> target =
|
||||
ParsedCertificateFromBuffer(cert->cert_buffer(), &parsing_errors);
|
||||
if (!target)
|
||||
return {};
|
||||
certs.push_back(target);
|
||||
|
||||
for (const auto& buf : cert->intermediate_buffers()) {
|
||||
scoped_refptr<ParsedCertificate> intermediate =
|
||||
ParsedCertificateFromBuffer(buf.get(), &parsing_errors);
|
||||
if (!intermediate)
|
||||
return {};
|
||||
certs.push_back(intermediate);
|
||||
}
|
||||
|
||||
return certs;
|
||||
}
|
||||
|
||||
// Tests whether cert has multiple EV policies, and at least one matches the
|
||||
// root. This is not a complete test of EV, but just enough to give a possible
|
||||
// explanation as to why the platform verifier did not validate as EV while
|
||||
// builtin did. (Since only the builtin verifier correctly handles multiple
|
||||
// candidate EV policies.)
|
||||
bool CertHasMultipleEVPoliciesAndOneMatchesRoot(const X509Certificate* cert) {
|
||||
if (cert->intermediate_buffers().empty())
|
||||
return false;
|
||||
|
||||
ParsedCertificateList certs = ParsedCertificateListFromX509Certificate(cert);
|
||||
if (certs.empty())
|
||||
return false;
|
||||
|
||||
ParsedCertificate* leaf = certs.front().get();
|
||||
ParsedCertificate* root = certs.back().get();
|
||||
|
||||
if (!leaf->has_policy_oids())
|
||||
return false;
|
||||
|
||||
const EVRootCAMetadata* ev_metadata = EVRootCAMetadata::GetInstance();
|
||||
std::set<der::Input> candidate_oids;
|
||||
for (const der::Input& oid : leaf->policy_oids()) {
|
||||
if (ev_metadata->IsEVPolicyOIDGivenBytes(oid))
|
||||
candidate_oids.insert(oid);
|
||||
}
|
||||
|
||||
if (candidate_oids.size() <= 1)
|
||||
return false;
|
||||
|
||||
SHA256HashValue root_fingerprint;
|
||||
crypto::SHA256HashString(root->der_cert().AsStringPiece(),
|
||||
root_fingerprint.data,
|
||||
sizeof(root_fingerprint.data));
|
||||
|
||||
for (const der::Input& oid : candidate_oids) {
|
||||
if (ev_metadata->HasEVPolicyOIDGivenBytes(root_fingerprint, oid))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// The Job represents the state machine for a trial cert verification.
|
||||
@ -190,16 +102,6 @@ class TrialComparisonCertVerifier::Job {
|
||||
// that re-verification completes.
|
||||
void OnPrimaryReverifyWithSecondaryChainCompleted(int result);
|
||||
|
||||
// Check if the differences between the primary and trial verifiers can be
|
||||
// ignored. This only handles differences that can be checked synchronously.
|
||||
// If the difference is ignorable, returns the relevant TrialComparisonResult,
|
||||
// otherwise returns kInvalid.
|
||||
TrialComparisonResult IsSynchronouslyIgnorableDifference(
|
||||
int primary_error,
|
||||
const CertVerifyResult& primary_result,
|
||||
int trial_error,
|
||||
const CertVerifyResult& trial_result);
|
||||
|
||||
const CertVerifier::Config config_;
|
||||
bool config_changed_ = false;
|
||||
const CertVerifier::RequestParams params_;
|
||||
@ -386,16 +288,16 @@ void TrialComparisonCertVerifier::Job::FinishWithError() {
|
||||
DCHECK(trial_error_ != primary_error_ ||
|
||||
!CertVerifyResultEqual(trial_result_, primary_result_));
|
||||
|
||||
TrialComparisonResult result_code = kInvalid;
|
||||
TrialComparisonResult result_code = TrialComparisonResult::kInvalid;
|
||||
|
||||
if (primary_error_ == OK && trial_error_ == OK) {
|
||||
result_code = kBothValidDifferentDetails;
|
||||
result_code = TrialComparisonResult::kBothValidDifferentDetails;
|
||||
} else if (primary_error_ == OK) {
|
||||
result_code = kPrimaryValidSecondaryError;
|
||||
result_code = TrialComparisonResult::kPrimaryValidSecondaryError;
|
||||
} else if (trial_error_ == OK) {
|
||||
result_code = kPrimaryErrorSecondaryValid;
|
||||
result_code = TrialComparisonResult::kPrimaryErrorSecondaryValid;
|
||||
} else {
|
||||
result_code = kBothErrorDifferentDetails;
|
||||
result_code = TrialComparisonResult::kBothErrorDifferentDetails;
|
||||
}
|
||||
Finish(/*is_success=*/false, result_code);
|
||||
}
|
||||
@ -461,7 +363,7 @@ void TrialComparisonCertVerifier::Job::OnTrialJobCompleted(int result) {
|
||||
|
||||
if (trial_success) {
|
||||
// Note: Will delete |this|.
|
||||
FinishSuccess(kEqual);
|
||||
FinishSuccess(TrialComparisonResult::kEqual);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -472,7 +374,7 @@ void TrialComparisonCertVerifier::Job::OnTrialJobCompleted(int result) {
|
||||
(CERT_STATUS_REVOKED | CERT_STATUS_REV_CHECKING_ENABLED))) {
|
||||
if (config_changed_) {
|
||||
// Note: Will delete |this|.
|
||||
FinishSuccess(kIgnoredConfigurationChanged);
|
||||
FinishSuccess(TrialComparisonResult::kIgnoredConfigurationChanged);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -498,7 +400,7 @@ void TrialComparisonCertVerifier::Job::OnTrialJobCompleted(int result) {
|
||||
if (!chains_equal && (trial_error_ == OK || primary_error_ != OK)) {
|
||||
if (config_changed_) {
|
||||
// Note: Will delete |this|.
|
||||
FinishSuccess(kIgnoredConfigurationChanged);
|
||||
FinishSuccess(TrialComparisonResult::kIgnoredConfigurationChanged);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -523,7 +425,7 @@ void TrialComparisonCertVerifier::Job::OnTrialJobCompleted(int result) {
|
||||
TrialComparisonResult ignorable_difference =
|
||||
IsSynchronouslyIgnorableDifference(primary_error_, primary_result_,
|
||||
trial_error_, trial_result_);
|
||||
if (ignorable_difference != kInvalid) {
|
||||
if (ignorable_difference != TrialComparisonResult::kInvalid) {
|
||||
FinishSuccess(ignorable_difference); // Note: Will delete |this|.
|
||||
return;
|
||||
}
|
||||
@ -536,7 +438,8 @@ void TrialComparisonCertVerifier::Job::
|
||||
OnMacRevCheckingReverificationJobCompleted(int result) {
|
||||
if (result == ERR_CERT_REVOKED) {
|
||||
// Will delete |this|.
|
||||
FinishSuccess(kIgnoredMacUndesiredRevocationChecking);
|
||||
FinishSuccess(
|
||||
TrialComparisonResult::kIgnoredMacUndesiredRevocationChecking);
|
||||
return;
|
||||
}
|
||||
FinishWithError(); // Note: Will delete |this|.
|
||||
@ -552,20 +455,22 @@ void TrialComparisonCertVerifier::Job::
|
||||
// Ignore the difference.
|
||||
//
|
||||
// Note: Will delete |this|.
|
||||
FinishSuccess(kIgnoredDifferentPathReVerifiesEquivalent);
|
||||
FinishSuccess(
|
||||
TrialComparisonResult::kIgnoredDifferentPathReVerifiesEquivalent);
|
||||
return;
|
||||
}
|
||||
|
||||
if (IsSynchronouslyIgnorableDifference(result, reverification_result_,
|
||||
trial_error_,
|
||||
trial_result_) != kInvalid) {
|
||||
trial_error_, trial_result_) !=
|
||||
TrialComparisonResult::kInvalid) {
|
||||
// The new result matches if ignoring differences. Still use the
|
||||
// |kIgnoredDifferentPathReVerifiesEquivalent| code rather than the result
|
||||
// of IsSynchronouslyIgnorableDifference, since it's the higher level
|
||||
// description of what the difference is in this case.
|
||||
//
|
||||
// Note: Will delete |this|.
|
||||
FinishSuccess(kIgnoredDifferentPathReVerifiesEquivalent);
|
||||
FinishSuccess(
|
||||
TrialComparisonResult::kIgnoredDifferentPathReVerifiesEquivalent);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -573,39 +478,6 @@ void TrialComparisonCertVerifier::Job::
|
||||
FinishWithError();
|
||||
}
|
||||
|
||||
TrialComparisonCertVerifier::TrialComparisonResult
|
||||
TrialComparisonCertVerifier::Job::IsSynchronouslyIgnorableDifference(
|
||||
int primary_error,
|
||||
const CertVerifyResult& primary_result,
|
||||
int trial_error,
|
||||
const CertVerifyResult& trial_result) {
|
||||
DCHECK(primary_result.verified_cert);
|
||||
DCHECK(trial_result.verified_cert);
|
||||
|
||||
if (primary_error == OK &&
|
||||
primary_result.verified_cert->intermediate_buffers().empty()) {
|
||||
// Platform may support trusting a leaf certificate directly. Builtin
|
||||
// verifier does not. See https://crbug.com/814994.
|
||||
return kIgnoredLocallyTrustedLeaf;
|
||||
}
|
||||
|
||||
const bool chains_equal = primary_result.verified_cert->EqualsIncludingChain(
|
||||
trial_result.verified_cert.get());
|
||||
|
||||
if (chains_equal && (trial_result.cert_status & CERT_STATUS_IS_EV) &&
|
||||
!(primary_result.cert_status & CERT_STATUS_IS_EV) &&
|
||||
(primary_error == trial_error)) {
|
||||
// The platform CertVerifyProc impls only check a single potential EV
|
||||
// policy from the leaf. If the leaf had multiple policies, builtin
|
||||
// verifier may verify it as EV when the platform verifier did not.
|
||||
if (CertHasMultipleEVPoliciesAndOneMatchesRoot(
|
||||
trial_result.verified_cert.get())) {
|
||||
return kIgnoredMultipleEVPoliciesAndOneMatchesRoot;
|
||||
}
|
||||
}
|
||||
return kInvalid;
|
||||
}
|
||||
|
||||
TrialComparisonCertVerifier::Job::Request::Request(
|
||||
TrialComparisonCertVerifier::Job* parent,
|
||||
CertVerifyResult* client_result,
|
||||
|
@ -28,23 +28,6 @@ class CertVerifyProc;
|
||||
// examine the differences.
|
||||
class NET_EXPORT TrialComparisonCertVerifier : public CertVerifier {
|
||||
public:
|
||||
// These values are persisted to logs. Entries should not be renumbered and
|
||||
// numeric values should never be reused.
|
||||
enum TrialComparisonResult {
|
||||
kInvalid = 0,
|
||||
kEqual = 1,
|
||||
kPrimaryValidSecondaryError = 2,
|
||||
kPrimaryErrorSecondaryValid = 3,
|
||||
kBothValidDifferentDetails = 4,
|
||||
kBothErrorDifferentDetails = 5,
|
||||
kIgnoredMacUndesiredRevocationChecking = 6,
|
||||
kIgnoredMultipleEVPoliciesAndOneMatchesRoot = 7,
|
||||
kIgnoredDifferentPathReVerifiesEquivalent = 8,
|
||||
kIgnoredLocallyTrustedLeaf = 9,
|
||||
kIgnoredConfigurationChanged = 10,
|
||||
kMaxValue = kIgnoredConfigurationChanged
|
||||
};
|
||||
|
||||
using ReportCallback = base::RepeatingCallback<void(
|
||||
const std::string& hostname,
|
||||
const scoped_refptr<X509Certificate>& unverified_cert,
|
||||
|
@ -18,6 +18,7 @@
|
||||
#include "net/cert/cert_verify_proc.h"
|
||||
#include "net/cert/cert_verify_result.h"
|
||||
#include "net/cert/ev_root_ca_metadata.h"
|
||||
#include "net/cert/trial_comparison_cert_verifier_util.h"
|
||||
#include "net/cert/x509_certificate.h"
|
||||
#include "net/cert/x509_util.h"
|
||||
#include "net/log/net_log_with_source.h"
|
||||
@ -421,7 +422,7 @@ TEST_F(TrialComparisonCertVerifierTest, InitiallyDisallowedThenAllowed) {
|
||||
1);
|
||||
histograms_.ExpectUniqueSample(
|
||||
"Net.CertVerifier_TrialComparisonResult",
|
||||
TrialComparisonCertVerifier::kPrimaryValidSecondaryError, 1);
|
||||
TrialComparisonResult::kPrimaryValidSecondaryError, 1);
|
||||
|
||||
// Expect a report from the second verification.
|
||||
ASSERT_EQ(1U, reports.size());
|
||||
@ -505,7 +506,7 @@ TEST_F(TrialComparisonCertVerifierTest, InitiallyAllowedThenDisallowed) {
|
||||
1);
|
||||
histograms_.ExpectUniqueSample(
|
||||
"Net.CertVerifier_TrialComparisonResult",
|
||||
TrialComparisonCertVerifier::kPrimaryValidSecondaryError, 1);
|
||||
TrialComparisonResult::kPrimaryValidSecondaryError, 1);
|
||||
|
||||
// Expect a report from the first verification.
|
||||
ASSERT_EQ(1U, reports.size());
|
||||
@ -608,7 +609,7 @@ TEST_F(TrialComparisonCertVerifierTest, ConfigChangedDuringTrialVerification) {
|
||||
1);
|
||||
histograms_.ExpectUniqueSample(
|
||||
"Net.CertVerifier_TrialComparisonResult",
|
||||
TrialComparisonCertVerifier::kPrimaryValidSecondaryError, 1);
|
||||
TrialComparisonResult::kPrimaryValidSecondaryError, 1);
|
||||
|
||||
// Expect a report.
|
||||
ASSERT_EQ(1U, reports.size());
|
||||
@ -658,7 +659,7 @@ TEST_F(TrialComparisonCertVerifierTest, SameResult) {
|
||||
histograms_.ExpectTotalCount("Net.CertVerifier_Job_Latency_TrialSecondary",
|
||||
1);
|
||||
histograms_.ExpectUniqueSample("Net.CertVerifier_TrialComparisonResult",
|
||||
TrialComparisonCertVerifier::kEqual, 1);
|
||||
TrialComparisonResult::kEqual, 1);
|
||||
}
|
||||
|
||||
TEST_F(TrialComparisonCertVerifierTest, PrimaryVerifierErrorSecondaryOk) {
|
||||
@ -723,7 +724,7 @@ TEST_F(TrialComparisonCertVerifierTest, PrimaryVerifierErrorSecondaryOk) {
|
||||
1);
|
||||
histograms_.ExpectUniqueSample(
|
||||
"Net.CertVerifier_TrialComparisonResult",
|
||||
TrialComparisonCertVerifier::kPrimaryErrorSecondaryValid, 1);
|
||||
TrialComparisonResult::kPrimaryErrorSecondaryValid, 1);
|
||||
}
|
||||
|
||||
TEST_F(TrialComparisonCertVerifierTest, PrimaryVerifierOkSecondaryError) {
|
||||
@ -784,7 +785,7 @@ TEST_F(TrialComparisonCertVerifierTest, PrimaryVerifierOkSecondaryError) {
|
||||
1);
|
||||
histograms_.ExpectUniqueSample(
|
||||
"Net.CertVerifier_TrialComparisonResult",
|
||||
TrialComparisonCertVerifier::kPrimaryValidSecondaryError, 1);
|
||||
TrialComparisonResult::kPrimaryValidSecondaryError, 1);
|
||||
}
|
||||
|
||||
TEST_F(TrialComparisonCertVerifierTest, BothVerifiersDifferentErrors) {
|
||||
@ -847,7 +848,7 @@ TEST_F(TrialComparisonCertVerifierTest, BothVerifiersDifferentErrors) {
|
||||
1);
|
||||
histograms_.ExpectUniqueSample(
|
||||
"Net.CertVerifier_TrialComparisonResult",
|
||||
TrialComparisonCertVerifier::kBothErrorDifferentDetails, 1);
|
||||
TrialComparisonResult::kBothErrorDifferentDetails, 1);
|
||||
}
|
||||
|
||||
TEST_F(TrialComparisonCertVerifierTest,
|
||||
@ -909,7 +910,7 @@ TEST_F(TrialComparisonCertVerifierTest,
|
||||
1);
|
||||
histograms_.ExpectUniqueSample(
|
||||
"Net.CertVerifier_TrialComparisonResult",
|
||||
TrialComparisonCertVerifier::kBothValidDifferentDetails, 1);
|
||||
TrialComparisonResult::kBothValidDifferentDetails, 1);
|
||||
}
|
||||
|
||||
TEST_F(TrialComparisonCertVerifierTest,
|
||||
@ -969,7 +970,7 @@ TEST_F(TrialComparisonCertVerifierTest,
|
||||
1);
|
||||
histograms_.ExpectUniqueSample(
|
||||
"Net.CertVerifier_TrialComparisonResult",
|
||||
TrialComparisonCertVerifier::kIgnoredConfigurationChanged, 1);
|
||||
TrialComparisonResult::kIgnoredConfigurationChanged, 1);
|
||||
}
|
||||
|
||||
TEST_F(TrialComparisonCertVerifierTest,
|
||||
@ -1027,8 +1028,7 @@ TEST_F(TrialComparisonCertVerifierTest,
|
||||
1);
|
||||
histograms_.ExpectUniqueSample(
|
||||
"Net.CertVerifier_TrialComparisonResult",
|
||||
TrialComparisonCertVerifier::kIgnoredDifferentPathReVerifiesEquivalent,
|
||||
1);
|
||||
TrialComparisonResult::kIgnoredDifferentPathReVerifiesEquivalent, 1);
|
||||
}
|
||||
|
||||
TEST_F(TrialComparisonCertVerifierTest,
|
||||
@ -1130,8 +1130,7 @@ TEST_F(TrialComparisonCertVerifierTest,
|
||||
1);
|
||||
histograms_.ExpectUniqueSample(
|
||||
"Net.CertVerifier_TrialComparisonResult",
|
||||
TrialComparisonCertVerifier::kIgnoredDifferentPathReVerifiesEquivalent,
|
||||
1);
|
||||
TrialComparisonResult::kIgnoredDifferentPathReVerifiesEquivalent, 1);
|
||||
}
|
||||
|
||||
TEST_F(TrialComparisonCertVerifierTest, BothVerifiersOkDifferentCertStatus) {
|
||||
@ -1202,7 +1201,7 @@ TEST_F(TrialComparisonCertVerifierTest, BothVerifiersOkDifferentCertStatus) {
|
||||
1);
|
||||
histograms_.ExpectUniqueSample(
|
||||
"Net.CertVerifier_TrialComparisonResult",
|
||||
TrialComparisonCertVerifier::kBothValidDifferentDetails, 1);
|
||||
TrialComparisonResult::kBothValidDifferentDetails, 1);
|
||||
}
|
||||
|
||||
TEST_F(TrialComparisonCertVerifierTest, CancelledDuringPrimaryVerification) {
|
||||
@ -1267,7 +1266,7 @@ TEST_F(TrialComparisonCertVerifierTest, CancelledDuringPrimaryVerification) {
|
||||
1);
|
||||
histograms_.ExpectUniqueSample(
|
||||
"Net.CertVerifier_TrialComparisonResult",
|
||||
TrialComparisonCertVerifier::kPrimaryErrorSecondaryValid, 1);
|
||||
TrialComparisonResult::kPrimaryErrorSecondaryValid, 1);
|
||||
}
|
||||
|
||||
TEST_F(TrialComparisonCertVerifierTest, DeletedDuringPrimaryVerification) {
|
||||
@ -1436,7 +1435,7 @@ TEST_F(TrialComparisonCertVerifierTest, DeletedDuringTrialReport) {
|
||||
1);
|
||||
histograms_.ExpectUniqueSample(
|
||||
"Net.CertVerifier_TrialComparisonResult",
|
||||
TrialComparisonCertVerifier::kPrimaryErrorSecondaryValid, 1);
|
||||
TrialComparisonResult::kPrimaryErrorSecondaryValid, 1);
|
||||
}
|
||||
|
||||
TEST_F(TrialComparisonCertVerifierTest, DeletedAfterTrialVerificationStarted) {
|
||||
@ -1562,14 +1561,14 @@ TEST_F(TrialComparisonCertVerifierTest, MacUndesiredRevocationChecking) {
|
||||
|
||||
histograms_.ExpectUniqueSample(
|
||||
"Net.CertVerifier_TrialComparisonResult",
|
||||
TrialComparisonCertVerifier::kIgnoredMacUndesiredRevocationChecking, 1);
|
||||
TrialComparisonResult::kIgnoredMacUndesiredRevocationChecking, 1);
|
||||
#else
|
||||
// Expect a report.
|
||||
EXPECT_EQ(1U, reports.size());
|
||||
|
||||
histograms_.ExpectUniqueSample(
|
||||
"Net.CertVerifier_TrialComparisonResult",
|
||||
TrialComparisonCertVerifier::kPrimaryErrorSecondaryValid, 1);
|
||||
TrialComparisonResult::kPrimaryErrorSecondaryValid, 1);
|
||||
#endif
|
||||
}
|
||||
|
||||
@ -1629,7 +1628,7 @@ TEST_F(TrialComparisonCertVerifierTest, PrimaryRevokedSecondaryOk) {
|
||||
1);
|
||||
histograms_.ExpectUniqueSample(
|
||||
"Net.CertVerifier_TrialComparisonResult",
|
||||
TrialComparisonCertVerifier::kPrimaryErrorSecondaryValid, 1);
|
||||
TrialComparisonResult::kPrimaryErrorSecondaryValid, 1);
|
||||
|
||||
// Expect a report.
|
||||
EXPECT_EQ(1U, reports.size());
|
||||
@ -1704,8 +1703,7 @@ TEST_F(TrialComparisonCertVerifierTest, MultipleEVPolicies) {
|
||||
1);
|
||||
histograms_.ExpectUniqueSample(
|
||||
"Net.CertVerifier_TrialComparisonResult",
|
||||
TrialComparisonCertVerifier::kIgnoredMultipleEVPoliciesAndOneMatchesRoot,
|
||||
1);
|
||||
TrialComparisonResult::kIgnoredMultipleEVPoliciesAndOneMatchesRoot, 1);
|
||||
}
|
||||
|
||||
TEST_F(TrialComparisonCertVerifierTest, MultipleEVPoliciesNoneValidForRoot) {
|
||||
@ -1770,7 +1768,7 @@ TEST_F(TrialComparisonCertVerifierTest, MultipleEVPoliciesNoneValidForRoot) {
|
||||
1);
|
||||
histograms_.ExpectUniqueSample(
|
||||
"Net.CertVerifier_TrialComparisonResult",
|
||||
TrialComparisonCertVerifier::kBothValidDifferentDetails, 1);
|
||||
TrialComparisonResult::kBothValidDifferentDetails, 1);
|
||||
}
|
||||
|
||||
TEST_F(TrialComparisonCertVerifierTest, MultiplePoliciesOnlyOneIsEV) {
|
||||
@ -1839,7 +1837,7 @@ TEST_F(TrialComparisonCertVerifierTest, MultiplePoliciesOnlyOneIsEV) {
|
||||
1);
|
||||
histograms_.ExpectUniqueSample(
|
||||
"Net.CertVerifier_TrialComparisonResult",
|
||||
TrialComparisonCertVerifier::kBothValidDifferentDetails, 1);
|
||||
TrialComparisonResult::kBothValidDifferentDetails, 1);
|
||||
}
|
||||
|
||||
TEST_F(TrialComparisonCertVerifierTest, LocallyTrustedLeaf) {
|
||||
@ -1890,7 +1888,7 @@ TEST_F(TrialComparisonCertVerifierTest, LocallyTrustedLeaf) {
|
||||
1);
|
||||
histograms_.ExpectUniqueSample(
|
||||
"Net.CertVerifier_TrialComparisonResult",
|
||||
TrialComparisonCertVerifier::kIgnoredLocallyTrustedLeaf, 1);
|
||||
TrialComparisonResult::kIgnoredLocallyTrustedLeaf, 1);
|
||||
}
|
||||
|
||||
} // namespace net
|
||||
|
138
net/cert/trial_comparison_cert_verifier_util.cc
Normal file
138
net/cert/trial_comparison_cert_verifier_util.cc
Normal file
@ -0,0 +1,138 @@
|
||||
// Copyright 2021 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "net/cert/trial_comparison_cert_verifier_util.h"
|
||||
|
||||
#include "crypto/sha2.h"
|
||||
#include "net/base/net_errors.h"
|
||||
#include "net/cert/cert_status_flags.h"
|
||||
#include "net/cert/ev_root_ca_metadata.h"
|
||||
#include "net/cert/internal/cert_errors.h"
|
||||
#include "net/cert/internal/parsed_certificate.h"
|
||||
#include "net/cert/x509_util.h"
|
||||
|
||||
namespace net {
|
||||
|
||||
namespace {
|
||||
|
||||
scoped_refptr<ParsedCertificate> ParsedCertificateFromBuffer(
|
||||
CRYPTO_BUFFER* cert_handle,
|
||||
CertErrors* errors) {
|
||||
return ParsedCertificate::Create(bssl::UpRef(cert_handle),
|
||||
x509_util::DefaultParseCertificateOptions(),
|
||||
errors);
|
||||
}
|
||||
|
||||
ParsedCertificateList ParsedCertificateListFromX509Certificate(
|
||||
const X509Certificate* cert) {
|
||||
CertErrors parsing_errors;
|
||||
|
||||
ParsedCertificateList certs;
|
||||
scoped_refptr<ParsedCertificate> target =
|
||||
ParsedCertificateFromBuffer(cert->cert_buffer(), &parsing_errors);
|
||||
if (!target)
|
||||
return {};
|
||||
certs.push_back(target);
|
||||
|
||||
for (const auto& buf : cert->intermediate_buffers()) {
|
||||
scoped_refptr<ParsedCertificate> intermediate =
|
||||
ParsedCertificateFromBuffer(buf.get(), &parsing_errors);
|
||||
if (!intermediate)
|
||||
return {};
|
||||
certs.push_back(intermediate);
|
||||
}
|
||||
|
||||
return certs;
|
||||
}
|
||||
|
||||
// Tests whether cert has multiple EV policies, and at least one matches the
|
||||
// root. This is not a complete test of EV, but just enough to give a possible
|
||||
// explanation as to why the platform verifier did not validate as EV while
|
||||
// builtin did. (Since only the builtin verifier correctly handles multiple
|
||||
// candidate EV policies.)
|
||||
bool CertHasMultipleEVPoliciesAndOneMatchesRoot(const X509Certificate* cert) {
|
||||
if (cert->intermediate_buffers().empty())
|
||||
return false;
|
||||
|
||||
ParsedCertificateList certs = ParsedCertificateListFromX509Certificate(cert);
|
||||
if (certs.empty())
|
||||
return false;
|
||||
|
||||
ParsedCertificate* leaf = certs.front().get();
|
||||
ParsedCertificate* root = certs.back().get();
|
||||
|
||||
if (!leaf->has_policy_oids())
|
||||
return false;
|
||||
|
||||
const EVRootCAMetadata* ev_metadata = EVRootCAMetadata::GetInstance();
|
||||
std::set<der::Input> candidate_oids;
|
||||
for (const der::Input& oid : leaf->policy_oids()) {
|
||||
if (ev_metadata->IsEVPolicyOIDGivenBytes(oid))
|
||||
candidate_oids.insert(oid);
|
||||
}
|
||||
|
||||
if (candidate_oids.size() <= 1)
|
||||
return false;
|
||||
|
||||
SHA256HashValue root_fingerprint;
|
||||
crypto::SHA256HashString(root->der_cert().AsStringPiece(),
|
||||
root_fingerprint.data,
|
||||
sizeof(root_fingerprint.data));
|
||||
|
||||
for (const der::Input& oid : candidate_oids) {
|
||||
if (ev_metadata->HasEVPolicyOIDGivenBytes(root_fingerprint, oid))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// Note: This ignores the result of stapled OCSP (which is the same for both
|
||||
// verifiers) and informational statuses about the certificate algorithms and
|
||||
// the hashes, since they will be the same if the certificate chains are the
|
||||
// same.
|
||||
bool CertVerifyResultEqual(const CertVerifyResult& a,
|
||||
const CertVerifyResult& b) {
|
||||
return std::tie(a.cert_status, a.is_issued_by_known_root) ==
|
||||
std::tie(b.cert_status, b.is_issued_by_known_root) &&
|
||||
(!!a.verified_cert == !!b.verified_cert) &&
|
||||
(!a.verified_cert ||
|
||||
a.verified_cert->EqualsIncludingChain(b.verified_cert.get()));
|
||||
}
|
||||
|
||||
TrialComparisonResult IsSynchronouslyIgnorableDifference(
|
||||
int primary_error,
|
||||
const CertVerifyResult& primary_result,
|
||||
int trial_error,
|
||||
const CertVerifyResult& trial_result) {
|
||||
DCHECK(primary_result.verified_cert);
|
||||
DCHECK(trial_result.verified_cert);
|
||||
|
||||
if (primary_error == OK &&
|
||||
primary_result.verified_cert->intermediate_buffers().empty()) {
|
||||
// Platform may support trusting a leaf certificate directly. Builtin
|
||||
// verifier does not. See https://crbug.com/814994.
|
||||
return TrialComparisonResult::kIgnoredLocallyTrustedLeaf;
|
||||
}
|
||||
|
||||
const bool chains_equal = primary_result.verified_cert->EqualsIncludingChain(
|
||||
trial_result.verified_cert.get());
|
||||
|
||||
if (chains_equal && (trial_result.cert_status & CERT_STATUS_IS_EV) &&
|
||||
!(primary_result.cert_status & CERT_STATUS_IS_EV) &&
|
||||
(primary_error == trial_error)) {
|
||||
// The platform CertVerifyProc impls only check a single potential EV
|
||||
// policy from the leaf. If the leaf had multiple policies, builtin
|
||||
// verifier may verify it as EV when the platform verifier did not.
|
||||
if (CertHasMultipleEVPoliciesAndOneMatchesRoot(
|
||||
trial_result.verified_cert.get())) {
|
||||
return TrialComparisonResult::kIgnoredMultipleEVPoliciesAndOneMatchesRoot;
|
||||
}
|
||||
}
|
||||
return TrialComparisonResult::kInvalid;
|
||||
}
|
||||
|
||||
} // namespace net
|
41
net/cert/trial_comparison_cert_verifier_util.h
Normal file
41
net/cert/trial_comparison_cert_verifier_util.h
Normal file
@ -0,0 +1,41 @@
|
||||
// Copyright 2021 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef NET_CERT_TRIAL_COMPARISON_CERT_VERIFIER_UTIL_H_
|
||||
#define NET_CERT_TRIAL_COMPARISON_CERT_VERIFIER_UTIL_H_
|
||||
|
||||
#include "net/base/net_export.h"
|
||||
#include "net/cert/cert_verify_result.h"
|
||||
|
||||
namespace net {
|
||||
|
||||
// These values are persisted to logs. Entries should not be renumbered and
|
||||
// numeric values should never be reused.
|
||||
enum class TrialComparisonResult {
|
||||
kInvalid = 0,
|
||||
kEqual = 1,
|
||||
kPrimaryValidSecondaryError = 2,
|
||||
kPrimaryErrorSecondaryValid = 3,
|
||||
kBothValidDifferentDetails = 4,
|
||||
kBothErrorDifferentDetails = 5,
|
||||
kIgnoredMacUndesiredRevocationChecking = 6,
|
||||
kIgnoredMultipleEVPoliciesAndOneMatchesRoot = 7,
|
||||
kIgnoredDifferentPathReVerifiesEquivalent = 8,
|
||||
kIgnoredLocallyTrustedLeaf = 9,
|
||||
kIgnoredConfigurationChanged = 10,
|
||||
kMaxValue = kIgnoredConfigurationChanged
|
||||
};
|
||||
|
||||
NET_EXPORT_PRIVATE bool CertVerifyResultEqual(const CertVerifyResult& a,
|
||||
const CertVerifyResult& b);
|
||||
|
||||
NET_EXPORT_PRIVATE TrialComparisonResult
|
||||
IsSynchronouslyIgnorableDifference(int primary_error,
|
||||
const CertVerifyResult& primary_result,
|
||||
int trial_error,
|
||||
const CertVerifyResult& trial_result);
|
||||
|
||||
} // namespace net
|
||||
|
||||
#endif // NET_CERT_TRIAL_COMPARISON_CERT_VERIFIER_UTIL_H_
|
@ -25,6 +25,7 @@
|
||||
#include "net/cert/cert_verify_proc_builtin.h"
|
||||
#include "net/cert/crl_set.h"
|
||||
#include "net/cert/internal/system_trust_store.h"
|
||||
#include "net/cert/trial_comparison_cert_verifier_util.h"
|
||||
#include "net/cert/x509_certificate.h"
|
||||
#include "net/cert_net/cert_net_fetcher_url_request.h"
|
||||
#include "net/tools/cert_verify_tool/cert_verify_tool_util.h"
|
||||
@ -142,15 +143,6 @@ std::unique_ptr<CertVerifyImpl> CreateCertVerifyImplFromName(
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool CertVerifyResultEqual(const net::CertVerifyResult& a,
|
||||
const net::CertVerifyResult& b) {
|
||||
return std::tie(a.cert_status, a.is_issued_by_known_root) ==
|
||||
std::tie(b.cert_status, b.is_issued_by_known_root) &&
|
||||
(!!a.verified_cert == !!b.verified_cert) &&
|
||||
(!a.verified_cert ||
|
||||
a.verified_cert->EqualsIncludingChain(b.verified_cert.get()));
|
||||
}
|
||||
|
||||
const char kUsage[] =
|
||||
" --input=<file>\n"
|
||||
"\n"
|
||||
@ -225,20 +217,58 @@ int RunCert(base::File* input_file,
|
||||
net::CertVerifyResult builtin_result;
|
||||
int builtin_error;
|
||||
|
||||
// TODO(mattm,hchao): Explore calling TrialComparisonCertVerifier directly
|
||||
// to have the extra comparison logic built into both places directly.
|
||||
platform_proc->VerifyCert(*x509_target_and_intermediates, cert_chain.host(),
|
||||
&platform_result, &platform_error);
|
||||
builtin_proc->VerifyCert(*x509_target_and_intermediates, cert_chain.host(),
|
||||
&builtin_result, &builtin_error);
|
||||
|
||||
if (CertVerifyResultEqual(platform_result, builtin_result) &&
|
||||
if (net::CertVerifyResultEqual(platform_result, builtin_result) &&
|
||||
platform_error == builtin_error) {
|
||||
std::cerr << "Host " << cert_chain.host() << " has equal verify results!\n";
|
||||
} else {
|
||||
// Much of the below code is lifted from
|
||||
// TrialComparisonCertVerifier::Job::OnTrialJobCompleted as it wasn't
|
||||
// obvious how to easily refactor the code here to prevent copying this
|
||||
// section of code.
|
||||
const bool chains_equal =
|
||||
platform_result.verified_cert->EqualsIncludingChain(
|
||||
builtin_result.verified_cert.get());
|
||||
|
||||
// Chains built were different with either builtin being OK or both not OK.
|
||||
// Pass builtin chain to platform, see if platform comes back the same.
|
||||
if (!chains_equal &&
|
||||
(builtin_error == net::OK || platform_error != net::OK)) {
|
||||
net::CertVerifyResult platform_reverification_result;
|
||||
int platform_reverification_error;
|
||||
|
||||
platform_proc->VerifyCert(
|
||||
*builtin_result.verified_cert, cert_chain.host(),
|
||||
&platform_reverification_result, &platform_reverification_error);
|
||||
if (net::CertVerifyResultEqual(platform_reverification_result,
|
||||
builtin_result) &&
|
||||
platform_reverification_error == builtin_error) {
|
||||
std::cerr << "Host " << cert_chain.host()
|
||||
<< " has equal reverify results!\n";
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
net::TrialComparisonResult result = net::IsSynchronouslyIgnorableDifference(
|
||||
platform_error, platform_result, builtin_error, builtin_result);
|
||||
|
||||
// TODO(hchao): gather stats on result values.
|
||||
|
||||
if (result != net::TrialComparisonResult::kInvalid) {
|
||||
std::cerr << "Host " << cert_chain.host()
|
||||
<< " has ignorable verify results!";
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::cerr << "Host " << cert_chain.host()
|
||||
<< " has different verify results!\n";
|
||||
|
||||
// TODO(hchao): print input chain.
|
||||
|
||||
std::cerr << "Platform: (error = "
|
||||
<< net::ErrorToShortString(platform_error) << ")\n";
|
||||
PrintCertVerifyResult(platform_result);
|
||||
|
Reference in New Issue
Block a user