blob: b3d14c3070e1c1e6e377bcad04d57b17889ce938 [file] [log] [blame]
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "net/cert/qwac.h"
#include <stdint.h>
#include "net/test/cert_builder.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/boringssl/src/pki/extended_key_usage.h"
#include "third_party/boringssl/src/pki/parsed_certificate.h"
namespace net {
namespace {
TEST(ParseQcStatements, InvalidSequence) {
EXPECT_EQ(std::nullopt, ParseQcStatements(bssl::der::Input("invalid")));
}
TEST(ParseQcStatements, EmptySequence) {
constexpr uint8_t kEmptySequence[] = {0x30, 0x0};
// An empty QCStatements sequence doesn't really make sense, but RFC 3739
// does not specify that the sequence must be non-empty, so we allow it.
auto r = ParseQcStatements(bssl::der::Input(kEmptySequence));
ASSERT_TRUE(r.has_value());
EXPECT_THAT(r.value(), testing::IsEmpty());
}
TEST(ParseQcStatements, InvalidStatementSequence) {
// SEQUENCE { OBJECT_IDENTIFIER { 1.2.3 } }
constexpr uint8_t kInvalid[] = {0x30, 0x04, 0x06, 0x02, 0x2a, 0x03};
EXPECT_EQ(std::nullopt, ParseQcStatements(bssl::der::Input(kInvalid)));
}
TEST(ParseQcStatements, 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, ParseQcStatements(bssl::der::Input(kInvalid)));
}
TEST(ParseQcStatements, MultipleStatementsSomeWithInfo) {
// 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 }
// }
// }
// }
constexpr uint8_t kQcStatementsValue[] = {
0x30, 0x36, 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,
0x13, 0x06, 0x06, 0x04, 0x00, 0x8e, 0x46, 0x01, 0x06, 0x30, 0x09, 0x06,
0x07, 0x04, 0x00, 0x8e, 0x46, 0x01, 0x06, 0x03};
auto r = ParseQcStatements(bssl::der::Input(kQcStatementsValue));
ASSERT_TRUE(r.has_value());
ASSERT_EQ(3u, r->size());
constexpr uint8_t id_0[] = {0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x0b, 0x02};
EXPECT_EQ(bssl::der::Input(id_0), r.value()[0].id);
constexpr uint8_t info_0[] = {0x30, 0x09, 0x06, 0x07, 0x04, 0x00,
0x8b, 0xec, 0x49, 0x01, 0x02};
EXPECT_EQ(bssl::der::Input(info_0), r.value()[0].info);
EXPECT_EQ(bssl::der::Input(kEtsiQcsQcComplianceOid), r.value()[1].id);
EXPECT_TRUE(r.value()[1].info.empty());
EXPECT_EQ(bssl::der::Input(kEtsiQcsQcTypeOid), r.value()[2].id);
constexpr uint8_t info_2[] = {0x30, 0x09, 0x06, 0x07, 0x04, 0x00,
0x8e, 0x46, 0x01, 0x06, 0x03};
EXPECT_EQ(bssl::der::Input(info_2), r.value()[2].info);
}
TEST(HasQwacQcStatements, Empty) {
EXPECT_EQ(QwacQcStatementsStatus::kNotQwac, 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.
TEST(HasQwacQcStatements, Valid) {
std::vector<QcStatement> statements;
statements.emplace_back(bssl::der::Input(kEtsiQcsQcComplianceOid),
bssl::der::Input());
// SEQUENCE { OBJECT_IDENTIFIER { 0.4.0.1862.1.6.3 } }
constexpr uint8_t kQctWebOidSequence[] = {0x30, 0x09, 0x06, 0x07, 0x04, 0x00,
0x8e, 0x46, 0x01, 0x06, 0x03};
statements.emplace_back(bssl::der::Input(kEtsiQcsQcTypeOid),
bssl::der::Input(kQctWebOidSequence));
EXPECT_EQ(QwacQcStatementsStatus::kHasQwacStatements,
HasQwacQcStatements(statements));
}
// A QcStatement which has a id-etsi-qcs-QcCompliance statement but does not
// have a id-etsi-qcs-QcType statement. Is not a QwacQcStatement.
TEST(HasQwacQcStatements, NoQcType) {
std::vector<QcStatement> statements;
statements.emplace_back(bssl::der::Input(kEtsiQcsQcComplianceOid),
bssl::der::Input());
EXPECT_EQ(QwacQcStatementsStatus::kInconsistent,
HasQwacQcStatements(statements));
}
// A QcStatement which has a id-etsi-qcs-QcCompliance statement and a
// id-etsi-qcs-QcType statement, but the QcType info does not contain
// id-etsi-qct-web. Is not a QwacQcStatement.
TEST(HasQwacQcStatements, WrongQcType) {
std::vector<QcStatement> statements;
statements.emplace_back(bssl::der::Input(kEtsiQcsQcComplianceOid),
bssl::der::Input());
// SEQUENCE { OBJECT_IDENTIFIER { 0.4.0.1862.1.6.2 } }
constexpr uint8_t kQctEsealOidSequence[] = {
0x30, 0x09, 0x06, 0x07, 0x04, 0x00, 0x8e, 0x46, 0x01, 0x06, 0x02};
statements.emplace_back(bssl::der::Input(kEtsiQcsQcTypeOid),
bssl::der::Input(kQctEsealOidSequence));
EXPECT_EQ(QwacQcStatementsStatus::kInconsistent,
HasQwacQcStatements(statements));
}
// A QcStatement which has a id-etsi-qcs-QcType statement of type
// id-etsi-qct-web but does not contain a a id-etsi-qcs-QcCompliance statement.
// Is not a QwacQcStatement.
TEST(HasQwacQcStatements, NoQcCompliance) {
std::vector<QcStatement> statements;
// SEQUENCE { OBJECT_IDENTIFIER { 0.4.0.1862.1.6.3 } }
constexpr uint8_t kQctWebOidSequence[] = {0x30, 0x09, 0x06, 0x07, 0x04, 0x00,
0x8e, 0x46, 0x01, 0x06, 0x03};
statements.emplace_back(bssl::der::Input(kEtsiQcsQcTypeOid),
bssl::der::Input(kQctWebOidSequence));
EXPECT_EQ(QwacQcStatementsStatus::kInconsistent,
HasQwacQcStatements(statements));
}
TEST(Has1QwacPolicies, TestPolicyCases) {
struct TestCase {
QwacPoliciesStatus expected_qwacness;
std::vector<bssl::der::Input> policies;
} kTestCases[] = {
// Expected QWAC cases:
{QwacPoliciesStatus::kHasQwacPolicies,
{bssl::der::Input(kCabfBrEvOid), bssl::der::Input(kQevcpwOid)}},
{QwacPoliciesStatus::kHasQwacPolicies,
{bssl::der::Input(kCabfBrIvOid), bssl::der::Input(kQncpwOid)}},
{QwacPoliciesStatus::kHasQwacPolicies,
{bssl::der::Input(kCabfBrOvOid), bssl::der::Input(kQncpwOid)}},
// Mismatch between EV/non-EV policies:
{QwacPoliciesStatus::kInconsistent,
{bssl::der::Input(kCabfBrEvOid), bssl::der::Input(kQncpwOid)}},
{QwacPoliciesStatus::kInconsistent,
{bssl::der::Input(kCabfBrIvOid), bssl::der::Input(kQevcpwOid)}},
{QwacPoliciesStatus::kInconsistent,
{bssl::der::Input(kCabfBrOvOid), bssl::der::Input(kQevcpwOid)}},
// Trying to use 2-QWAC policy on a 1-QWAC:
{QwacPoliciesStatus::kNotQwac,
{bssl::der::Input(kCabfBrEvOid), bssl::der::Input(kQncpwgenOid)}},
{QwacPoliciesStatus::kNotQwac,
{bssl::der::Input(kCabfBrIvOid), bssl::der::Input(kQncpwgenOid)}},
{QwacPoliciesStatus::kNotQwac,
{bssl::der::Input(kCabfBrOvOid), bssl::der::Input(kQncpwgenOid)}},
// Only has EU policies but doesn't have any CABF policy:
{QwacPoliciesStatus::kInconsistent, {bssl::der::Input(kQevcpwOid)}},
{QwacPoliciesStatus::kInconsistent, {bssl::der::Input(kQncpwOid)}},
// Only has CABF policies:
{QwacPoliciesStatus::kNotQwac, {bssl::der::Input(kCabfBrEvOid)}},
{QwacPoliciesStatus::kNotQwac, {bssl::der::Input(kCabfBrIvOid)}},
{QwacPoliciesStatus::kNotQwac, {bssl::der::Input(kCabfBrOvOid)}},
// No policies:
{QwacPoliciesStatus::kNotQwac, {}},
};
constexpr uint8_t kUnrelated[] = {0x01, 0x02, 0x03};
for (const auto& [expected_qwacness, policies] : kTestCases) {
std::set<bssl::der::Input> policy_set(policies.begin(), policies.end());
EXPECT_EQ(expected_qwacness, Has1QwacPolicies(policy_set));
// Adding additional unrelated policies to the policy set should not change
// the result.
policy_set.insert(bssl::der::Input(kUnrelated));
EXPECT_EQ(expected_qwacness, Has1QwacPolicies(policy_set));
}
}
TEST(Has2QwacPolicies, TestPolicyCases) {
struct TestCase {
QwacPoliciesStatus expected_qwacness;
std::vector<bssl::der::Input> policies;
} kTestCases[] = {
// Expected QWAC cases:
{QwacPoliciesStatus::kHasQwacPolicies, {bssl::der::Input(kQncpwgenOid)}},
// Trying to use 1-QWAC policies on a 2-QWAC:
{QwacPoliciesStatus::kNotQwac,
{bssl::der::Input(kCabfBrEvOid), bssl::der::Input(kQevcpwOid)}},
{QwacPoliciesStatus::kNotQwac,
{bssl::der::Input(kCabfBrIvOid), bssl::der::Input(kQncpwOid)}},
{QwacPoliciesStatus::kNotQwac,
{bssl::der::Input(kCabfBrOvOid), bssl::der::Input(kQncpwOid)}},
// No policies:
{QwacPoliciesStatus::kNotQwac, {}},
};
constexpr uint8_t kUnrelated[] = {0x01, 0x02, 0x03};
for (const auto& [expected_qwacness, policies] : kTestCases) {
std::set<bssl::der::Input> policy_set(policies.begin(), policies.end());
EXPECT_EQ(expected_qwacness, Has2QwacPolicies(policy_set));
// Adding additional unrelated policies to the policy set should not change
// the result.
policy_set.insert(bssl::der::Input(kUnrelated));
EXPECT_EQ(expected_qwacness, Has2QwacPolicies(policy_set));
}
}
TEST(Has2QwacEku, TestEkuCases) {
struct TestCase {
QwacEkuStatus expected_qwacness;
std::vector<bssl::der::Input> ekus;
} kTestCases[] = {
// Expected QWAC case:
{QwacEkuStatus::kHasQwacEku, {bssl::der::Input(kIdKpTlsBinding)}},
// Contains additional EKUs:
{QwacEkuStatus::kInconsistent,
{bssl::der::Input(kIdKpTlsBinding),
bssl::der::Input(bssl::kServerAuth)}},
// Wrong eku:
{QwacEkuStatus::kNotQwac, {bssl::der::Input(bssl::kServerAuth)}},
{QwacEkuStatus::kNotQwac,
{bssl::der::Input(bssl::kServerAuth),
bssl::der::Input(bssl::kClientAuth)}},
// No eku:
{QwacEkuStatus::kNotQwac, {}},
};
auto [leaf, root] = CertBuilder::CreateSimpleChain2();
for (const auto& [expected_qwacness, ekus] : kTestCases) {
if (ekus.empty()) {
leaf->EraseExtension(bssl::der::Input(bssl::kExtKeyUsageOid));
} else {
leaf->SetExtendedKeyUsages(ekus);
}
auto parsed_cert =
bssl::ParsedCertificate::Create(leaf->DupCertBuffer(), {}, nullptr);
ASSERT_TRUE(parsed_cert);
EXPECT_EQ(expected_qwacness, Has2QwacEku(parsed_cert.get()));
}
}
} // namespace
} // namespace net