Reland "show QcStatements and QWAC policies in certificate viewer"
This is a reland of commit 84ec1e29f6
There are no changes, the original was incorrectly reverted due to a
bot issue.
Original change's description:
> show QcStatements and QWAC policies in certificate viewer
>
> Bug: 392934324
> Change-Id: Icaf6195c093bcb13ef80473887d373afc9824442
> Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6341247
> Commit-Queue: Matt Mueller <mattm@chromium.org>
> Reviewed-by: Nick Harper <nharper@chromium.org>
> Cr-Commit-Position: refs/heads/main@{#1432499}
Bug: 392934324
Change-Id: I80e64121c7320ee35e251d3637e4545b695cee41
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6357157
Reviewed-by: Nick Harper <nharper@chromium.org>
Commit-Queue: Nick Harper <nharper@chromium.org>
Auto-Submit: Matt Mueller <mattm@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1432942}
This commit is contained in:

committed by
Chromium LUCI CQ

parent
30d65af624
commit
986b636ef1
chrome
app
common
net
@ -4434,6 +4434,9 @@ are declared in tools/grit/grit_args.gni.
|
||||
<message name="IDS_CERT_X509_SCT_LIST" desc="description of extension Signed Certificate Timestamp List">
|
||||
Signed Certificate Timestamp List
|
||||
</message>
|
||||
<message name="IDS_CERT_QC_STATEMENTS" desc="description of extension Qualified Certificate Statements">
|
||||
Qualified Certificate Statements
|
||||
</message>
|
||||
<message name="IDS_CERT_X509_POLICY_MAPPINGS" desc="description of extension Certificate Policy Mappings">
|
||||
Certificate Policy Mappings
|
||||
</message>
|
||||
@ -4593,6 +4596,24 @@ are declared in tools/grit/grit_args.gni.
|
||||
<message name="IDS_CERT_EKU_OCSP_SIGNING" desc="description of extended key usage Signing OCSP Responses">
|
||||
Signing OCSP Responses
|
||||
</message>
|
||||
|
||||
<message name="IDS_CERT_POLICY_ETSI_QEVCP_W" desc="description of ETSI qualified website certificates that offer the Extended Validated level of assurance" translateable="false">
|
||||
ETSI QEVCP-w
|
||||
</message>
|
||||
<message name="IDS_CERT_POLICY_ETSI_QNCP_W" desc="description of ETSI qualified website certificates that offer the Organization Validated or Individual Validated level of assurance" translateable="false">
|
||||
ETSI QNCP-w
|
||||
</message>
|
||||
|
||||
<message name="IDS_CERT_QC_ETSI_QCS_QCCOMPLIANCE" desc="description of ETSI qualified certificate statement compliance" translateable="false">
|
||||
ETSI QcCompliance
|
||||
</message>
|
||||
<message name="IDS_CERT_QC_ETSI_QCS_QCTYPE" desc="description of ETSI qualified certificate statement certificate type" translateable="false">
|
||||
ETSI QcType
|
||||
</message>
|
||||
<message name="IDS_CERT_QC_ETSI_QCT_WEB" desc="description of ETSI qualified certificate statement certificate type for web site authentication" translateable="false">
|
||||
ETSI qct-web
|
||||
</message>
|
||||
|
||||
<message name="IDS_CERT_EKU_MS_INDIVIDUAL_CODE_SIGNING" desc="description of extended key usage Microsoft Individual Code Signing">
|
||||
Microsoft Individual Code Signing
|
||||
</message>
|
||||
|
@ -0,0 +1 @@
|
||||
7e5e937003b674ac37a653e32833421af64419c4
|
@ -20,6 +20,7 @@
|
||||
#include "crypto/sha2.h"
|
||||
#include "net/base/ip_address.h"
|
||||
#include "net/cert/ct_objects_extractor.h"
|
||||
#include "net/cert/qwac.h"
|
||||
#include "net/cert/time_conversions.h"
|
||||
#include "net/cert/x509_util.h"
|
||||
#include "third_party/boringssl/src/include/openssl/bn.h"
|
||||
@ -478,6 +479,7 @@ constexpr auto kOidStringMap = base::MakeFixedFlatMap<bssl::der::Input, int>({
|
||||
{bssl::der::Input(bssl::kUserNoticeId),
|
||||
IDS_CERT_PKIX_USER_NOTICE_QUALIFIER},
|
||||
{bssl::der::Input(net::ct::kEmbeddedSCTOid), IDS_CERT_X509_SCT_LIST},
|
||||
{bssl::der::Input(net::kQcStatementsOid), IDS_CERT_QC_STATEMENTS},
|
||||
|
||||
// Extended Key Usages:
|
||||
{bssl::der::Input(bssl::kAnyEKU), IDS_CERT_EKU_ANY_EKU},
|
||||
@ -492,6 +494,16 @@ constexpr auto kOidStringMap = base::MakeFixedFlatMap<bssl::der::Input, int>({
|
||||
{bssl::der::Input(kNetscapeServerGatedCrypto),
|
||||
IDS_CERT_EKU_NETSCAPE_INTERNATIONAL_STEP_UP},
|
||||
|
||||
// Policies:
|
||||
{bssl::der::Input(net::kQevcpwOid), IDS_CERT_POLICY_ETSI_QEVCP_W},
|
||||
{bssl::der::Input(net::kQncpwOid), IDS_CERT_POLICY_ETSI_QNCP_W},
|
||||
|
||||
// QcStatements:
|
||||
{bssl::der::Input(net::kEtsiQcsQcComplianceOid),
|
||||
IDS_CERT_QC_ETSI_QCS_QCCOMPLIANCE},
|
||||
{bssl::der::Input(net::kEtsiQcsQcTypeOid), IDS_CERT_QC_ETSI_QCS_QCTYPE},
|
||||
{bssl::der::Input(net::kEtsiQctWebOid), IDS_CERT_QC_ETSI_QCT_WEB},
|
||||
|
||||
// Microsoft oids:
|
||||
{bssl::der::Input(kMsCertExtCerttype), IDS_CERT_EXT_MS_CERT_TYPE},
|
||||
{bssl::der::Input(kMsCertsrvCaVersion), IDS_CERT_EXT_MS_CA_VERSION},
|
||||
@ -1234,6 +1246,42 @@ std::vector<uint8_t> BIGNUMBytes(const BIGNUM* bn) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::optional<std::string> ProcessQcStatements(
|
||||
bssl::der::Input extension_data) {
|
||||
std::optional<std::vector<net::QcStatement>> qc_statements =
|
||||
net::ParseQcStatements(extension_data);
|
||||
if (!qc_statements.has_value()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::string rv;
|
||||
for (const auto& statement : qc_statements.value()) {
|
||||
rv += GetOidTextOrNumeric(statement.id);
|
||||
if (!statement.info.empty()) {
|
||||
rv += " = ";
|
||||
// The `statement.info` is dependent on the `id`, so `info` can only be
|
||||
// processed for known ids. Otherwise the raw bytes of `info` are shown.
|
||||
if (statement.id == bssl::der::Input(net::kEtsiQcsQcTypeOid)) {
|
||||
std::optional<std::vector<bssl::der::Input>> qc_types =
|
||||
net::ParseQcTypeInfo(statement.info);
|
||||
if (!qc_types.has_value()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
std::string sep;
|
||||
for (const auto& qc_type_id : qc_types.value()) {
|
||||
rv += sep;
|
||||
rv += GetOidTextOrNumeric(qc_type_id);
|
||||
sep = ", ";
|
||||
}
|
||||
} else {
|
||||
rv += ProcessRawBytes(statement.info);
|
||||
}
|
||||
}
|
||||
rv += "\n";
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
X509CertificateModel::X509CertificateModel(
|
||||
@ -1508,6 +1556,9 @@ std::optional<std::string> X509CertificateModel::ProcessExtensionData(
|
||||
extension.oid == bssl::der::Input(kNetscapeLostPasswordURLOid)) {
|
||||
return ProcessIA5String(extension.value);
|
||||
}
|
||||
if (extension.oid == bssl::der::Input(net::kQcStatementsOid)) {
|
||||
return ProcessQcStatements(extension.value);
|
||||
}
|
||||
// TODO(crbug.com/41395047): SCT
|
||||
// TODO(mattm): name constraints
|
||||
// TODO(mattm): policy mappings
|
||||
|
@ -11,6 +11,7 @@
|
||||
|
||||
#include <string_view>
|
||||
|
||||
#include "net/cert/qwac.h"
|
||||
#include "net/cert/x509_util.h"
|
||||
#include "net/test/cert_builder.h"
|
||||
#include "net/test/cert_test_util.h"
|
||||
@ -732,6 +733,55 @@ TEST_P(X509CertificateModel, SubjectEmptySequence) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(X509CertificateModel, QcStatements) {
|
||||
base::FilePath certs_dir = net::GetTestCertsDirectory();
|
||||
std::unique_ptr<net::CertBuilder> builder =
|
||||
net::CertBuilder::FromFile(certs_dir.AppendASCII("ok_cert.pem"), nullptr);
|
||||
ASSERT_TRUE(builder);
|
||||
|
||||
// SEQUENCE {
|
||||
// SEQUENCE {
|
||||
// OBJECT_IDENTIFIER { 1.3.6.1.5.5.7.11.2 }
|
||||
// SEQUENCE {
|
||||
// OBJECT_IDENTIFIER { 0.4.0.194121.1.2 }
|
||||
// }
|
||||
// }
|
||||
// SEQUENCE {
|
||||
// OBJECT_IDENTIFIER { 0.4.0.1862.1.1 }
|
||||
// }
|
||||
// SEQUENCE {
|
||||
// OBJECT_IDENTIFIER { 0.4.0.1862.1.6 }
|
||||
// SEQUENCE {
|
||||
// OBJECT_IDENTIFIER { 0.4.0.1862.1.6.3 }
|
||||
// OBJECT_IDENTIFIER { 0.4.0.1862.1.6.2 }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
constexpr uint8_t kQcStatementsValue[] = {
|
||||
0x30, 0x3f, 0x30, 0x15, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05,
|
||||
0x07, 0x0b, 0x02, 0x30, 0x09, 0x06, 0x07, 0x04, 0x00, 0x8b, 0xec,
|
||||
0x49, 0x01, 0x02, 0x30, 0x08, 0x06, 0x06, 0x04, 0x00, 0x8e, 0x46,
|
||||
0x01, 0x01, 0x30, 0x1c, 0x06, 0x06, 0x04, 0x00, 0x8e, 0x46, 0x01,
|
||||
0x06, 0x30, 0x12, 0x06, 0x07, 0x04, 0x00, 0x8e, 0x46, 0x01, 0x06,
|
||||
0x03, 0x06, 0x07, 0x04, 0x00, 0x8e, 0x46, 0x01, 0x06, 0x02};
|
||||
builder->SetExtension(bssl::der::Input(net::kQcStatementsOid),
|
||||
std::string(base::as_string_view(kQcStatementsValue)));
|
||||
|
||||
x509_certificate_model::X509CertificateModel model(
|
||||
bssl::UpRef(builder->GetCertBuffer()), GetParam());
|
||||
ASSERT_TRUE(model.is_valid());
|
||||
auto extensions = model.GetExtensions("critical", "notcrit");
|
||||
auto extension_value =
|
||||
FindExtension(extensions, "Qualified Certificate Statements");
|
||||
ASSERT_TRUE(extension_value);
|
||||
EXPECT_EQ(
|
||||
"notcrit\n"
|
||||
"OID.1.3.6.1.5.5.7.11.2 = 30 09 06 07 04 00 8B EC 49 01 02\n"
|
||||
"ETSI QcCompliance\n"
|
||||
"ETSI QcType = ETSI qct-web, OID.0.4.0.1862.1.6.2\n",
|
||||
*extension_value);
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(All,
|
||||
X509CertificateModel,
|
||||
testing::Values(std::string(),
|
||||
|
@ -2620,8 +2620,8 @@ if (!is_cronet_build) {
|
||||
|
||||
fuzztests = [
|
||||
"CookieIndicesFuzzTest.FuzzParseFromHeader",
|
||||
"QwacFuzzTest.FuzzHasQwacQcStatements",
|
||||
"QwacFuzzTest.FuzzParseQcStatements",
|
||||
"QwacFuzzTest.FuzzParseQcTypeInfo",
|
||||
"X509CertificateFuzzTest.FuzzCreateFromDERCertChain",
|
||||
]
|
||||
|
||||
|
@ -50,6 +50,29 @@ std::optional<std::vector<QcStatement>> ParseQcStatements(
|
||||
return results;
|
||||
}
|
||||
|
||||
std::optional<std::vector<bssl::der::Input>> ParseQcTypeInfo(
|
||||
bssl::der::Input statement_info) {
|
||||
// QcType::= SEQUENCE OF OBJECT IDENTIFIER (id-etsi-qct-esign |
|
||||
// id-etsi-qct-eseal | id-etsi-qct-web, ...)
|
||||
bssl::der::Parser info_parser(statement_info);
|
||||
bssl::der::Parser qctype_parser;
|
||||
if (!info_parser.ReadSequence(&qctype_parser)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
std::vector<bssl::der::Input> results;
|
||||
while (qctype_parser.HasMore()) {
|
||||
bssl::der::Input qctype_id;
|
||||
if (!qctype_parser.ReadTag(CBS_ASN1_OBJECT, &qctype_id)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
results.push_back(qctype_id);
|
||||
}
|
||||
if (info_parser.HasMore()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
bool HasQwacQcStatements(const std::vector<QcStatement>& qc_statements) {
|
||||
// ETSI TS 119 411-5 - V2.1.1 - section 6.1.2:
|
||||
// the QWAC includes QCStatements as specified in clause 4.2 of ETSI EN 319
|
||||
@ -84,19 +107,13 @@ bool HasQwacQcStatements(const std::vector<QcStatement>& qc_statements) {
|
||||
if (statement.id == bssl::der::Input(kEtsiQcsQcComplianceOid)) {
|
||||
has_qc_compliance = true;
|
||||
} else if (statement.id == bssl::der::Input(kEtsiQcsQcTypeOid)) {
|
||||
// QcType::= SEQUENCE OF OBJECT IDENTIFIER (id-etsi-qct-esign |
|
||||
// id-etsi-qct-eseal | id-etsi-qct-web, ...)
|
||||
bssl::der::Parser info_parser(statement.info);
|
||||
bssl::der::Parser qctype_parser;
|
||||
if (!info_parser.ReadSequence(&qctype_parser)) {
|
||||
std::optional<std::vector<bssl::der::Input>> qc_types =
|
||||
ParseQcTypeInfo(statement.info);
|
||||
if (!qc_types.has_value()) {
|
||||
return false;
|
||||
}
|
||||
while (qctype_parser.HasMore()) {
|
||||
bssl::der::Input qctype_id;
|
||||
if (!qctype_parser.ReadTag(CBS_ASN1_OBJECT, &qctype_id)) {
|
||||
return false;
|
||||
}
|
||||
if (qctype_id == bssl::der::Input(kEtsiQctWebOid)) {
|
||||
for (const auto& qc_type_id : qc_types.value()) {
|
||||
if (qc_type_id == bssl::der::Input(kEtsiQctWebOid)) {
|
||||
has_qctype_web = true;
|
||||
}
|
||||
}
|
||||
|
@ -94,6 +94,11 @@ NET_EXPORT
|
||||
std::optional<std::vector<QcStatement>> ParseQcStatements(
|
||||
bssl::der::Input extension_value);
|
||||
|
||||
// Parses the statementInfo of a etsi-qcs-QcType statement. Returns a vector of
|
||||
// the OID values, or nullopt on error.
|
||||
NET_EXPORT std::optional<std::vector<bssl::der::Input>> ParseQcTypeInfo(
|
||||
bssl::der::Input statement_info);
|
||||
|
||||
// Returns true if the given QcStatements extension (as returned by
|
||||
// ParseQcStatements) indicates the certificate is a QWAC.
|
||||
NET_EXPORT_PRIVATE bool HasQwacQcStatements(
|
||||
|
@ -80,22 +80,32 @@ FUZZ_TEST(QwacFuzzTest, FuzzParseQcStatements)
|
||||
base::ToVector(kQcStatementsValue),
|
||||
});
|
||||
|
||||
void FuzzHasQwacQcStatements(
|
||||
const std::vector<std::pair<std::vector<uint8_t>, std::vector<uint8_t>>>&
|
||||
qc_statements_input) {
|
||||
std::vector<QcStatement> qc_statements;
|
||||
for (const auto& statement : qc_statements_input) {
|
||||
qc_statements.emplace_back(bssl::der::Input(statement.first),
|
||||
bssl::der::Input(statement.second));
|
||||
void FuzzParseQcTypeInfo(std::vector<uint8_t> statement_info_value) {
|
||||
const auto parsed = ParseQcTypeInfo(bssl::der::Input(statement_info_value));
|
||||
if (!parsed.has_value()) {
|
||||
return;
|
||||
}
|
||||
for (const auto& qc_type : parsed.value()) {
|
||||
ASSERT_TRUE(IsSubSpan(qc_type, statement_info_value));
|
||||
}
|
||||
std::ignore = HasQwacQcStatements(qc_statements);
|
||||
}
|
||||
|
||||
// TODO(crbug.com/392931068): Use initial seeds and/or seeded domains?
|
||||
// TODO(crbug.com/392931068): not sure how useful this fuzzer actually is.
|
||||
// Maybe refactor things (extract the QcType parsing into a separate function?)
|
||||
// so that the fuzzer can be more focused?
|
||||
FUZZ_TEST(QwacFuzzTest, FuzzHasQwacQcStatements);
|
||||
// SEQUENCE {
|
||||
// OBJECT_IDENTIFIER { 1.2.3 }
|
||||
// OBJECT_IDENTIFIER { 2.1.6 }
|
||||
// OBJECT_IDENTIFIER { 1.2.4 }
|
||||
// }
|
||||
constexpr uint8_t kQcTypeInfoMultiple[] = {0x30, 0x0c, 0x06, 0x02, 0x2a,
|
||||
0x03, 0x06, 0x02, 0x51, 0x06,
|
||||
0x06, 0x02, 0x2a, 0x04};
|
||||
|
||||
FUZZ_TEST(QwacFuzzTest, FuzzParseQcTypeInfo)
|
||||
.WithSeeds({
|
||||
base::ToVector(kEmptySequence),
|
||||
base::ToVector(kInvalidStatementSequence),
|
||||
base::ToVector(kInvalidStatementOid),
|
||||
base::ToVector(kQcTypeInfoMultiple),
|
||||
});
|
||||
|
||||
} // namespace
|
||||
|
||||
|
@ -85,6 +85,60 @@ TEST(HasQwacQcStatements, Empty) {
|
||||
EXPECT_FALSE(HasQwacQcStatements({}));
|
||||
}
|
||||
|
||||
TEST(ParseQcTypeInfo, InvalidSequence) {
|
||||
EXPECT_EQ(std::nullopt, ParseQcTypeInfo(bssl::der::Input("invalid")));
|
||||
}
|
||||
|
||||
TEST(ParseQcTypeInfo, EmptySequence) {
|
||||
constexpr uint8_t kEmptySequence[] = {0x30, 0x0};
|
||||
// An empty QCTypeInfo sequence doesn't really make sense, but the spec does
|
||||
// not specify that the sequence must be non-empty, so we allow it.
|
||||
auto r = ParseQcTypeInfo(bssl::der::Input(kEmptySequence));
|
||||
ASSERT_TRUE(r.has_value());
|
||||
EXPECT_THAT(r.value(), testing::IsEmpty());
|
||||
}
|
||||
|
||||
TEST(ParseQcTypeInfo, SingleValue) {
|
||||
// SEQUENCE { OBJECT_IDENTIFIER { 1.2.3 } }
|
||||
constexpr uint8_t kQcTypeInfo[] = {0x30, 0x04, 0x06, 0x02, 0x2a, 0x03};
|
||||
constexpr uint8_t kOid[] = {0x2a, 0x03};
|
||||
auto r = ParseQcTypeInfo(bssl::der::Input(kQcTypeInfo));
|
||||
ASSERT_TRUE(r.has_value());
|
||||
EXPECT_THAT(r.value(), testing::ElementsAre(bssl::der::Input(kOid)));
|
||||
}
|
||||
|
||||
TEST(ParseQcTypeInfo, MultipleValues) {
|
||||
// SEQUENCE {
|
||||
// OBJECT_IDENTIFIER { 1.2.3 }
|
||||
// OBJECT_IDENTIFIER { 2.1.6 }
|
||||
// OBJECT_IDENTIFIER { 1.2.4 }
|
||||
// }
|
||||
constexpr uint8_t kQcTypeInfo[] = {0x30, 0x0c, 0x06, 0x02, 0x2a, 0x03, 0x06,
|
||||
0x02, 0x51, 0x06, 0x06, 0x02, 0x2a, 0x04};
|
||||
constexpr uint8_t kOid1[] = {0x2a, 0x03};
|
||||
constexpr uint8_t kOid2[] = {0x51, 0x06};
|
||||
constexpr uint8_t kOid3[] = {0x2a, 0x04};
|
||||
auto r = ParseQcTypeInfo(bssl::der::Input(kQcTypeInfo));
|
||||
ASSERT_TRUE(r.has_value());
|
||||
EXPECT_THAT(r.value(), testing::ElementsAre(bssl::der::Input(kOid1),
|
||||
bssl::der::Input(kOid2),
|
||||
bssl::der::Input(kOid3)));
|
||||
}
|
||||
|
||||
TEST(ParseQcTypeInfo, InvalidTrailingData) {
|
||||
// SEQUENCE { OBJECT_IDENTIFIER { 1.2.3 } } SEQUENCE { }
|
||||
constexpr uint8_t kInvalid[] = {0x30, 0x04, 0x06, 0x02,
|
||||
0x2a, 0x03, 0x30, 0x0};
|
||||
EXPECT_EQ(std::nullopt, ParseQcTypeInfo(bssl::der::Input(kInvalid)));
|
||||
}
|
||||
|
||||
TEST(ParseQcTypeInfo, InvalidStatementOid) {
|
||||
// SEQUENCE { SEQUENCE { SEQUENCE { OBJECT_IDENTIFIER { 1.2.4 } } } }
|
||||
constexpr uint8_t kInvalid[] = {0x30, 0x08, 0x30, 0x06, 0x30,
|
||||
0x04, 0x06, 0x02, 0x2a, 0x04};
|
||||
EXPECT_EQ(std::nullopt, ParseQcTypeInfo(bssl::der::Input(kInvalid)));
|
||||
}
|
||||
|
||||
// A valid QcStatement which has a id-etsi-qcs-QcCompliance statement and a
|
||||
// id-etsi-qcs-QcType statement which contains id-etsi-qct-web. Is a
|
||||
// QwacQcStatement.
|
||||
|
Reference in New Issue
Block a user