blob: 1acac2eb2d1d062e596ea76552904276577123cb [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 "components/trusted_vault/recovery_key_store_certificate.h"
#include <string>
#include <vector>
#include "base/containers/span.h"
#include "base/time/time.h"
#include "components/trusted_vault/securebox.h"
#include "components/trusted_vault/test/recovery_key_store_certificate_test_util.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace trusted_vault {
namespace {
using test_certs::GetCertificate;
using test_certs::GetEndpoint1;
using test_certs::GetEndpoint2;
using test_certs::GetEndpoint3;
using test_certs::GetIntermediate1;
using test_certs::GetIntermediate2;
using test_certs::GetSignature;
using test_certs::kCertXml;
using test_certs::kEndpoint1PublicKey;
using test_certs::kEndpoint2PublicKey;
using test_certs::kEndpoint3PublicKey;
using test_certs::kSigXml;
using test_certs::kValidCertificateDate;
class RecoveryKeyStoreCertificateTest : public testing::Test {};
TEST_F(RecoveryKeyStoreCertificateTest, Internals_ParseValidCertXml) {
auto result = internal::ParseRecoveryKeyStoreCertXML(kCertXml);
ASSERT_TRUE(result);
EXPECT_THAT(result->endpoints,
testing::UnorderedElementsAre(GetEndpoint1(), GetEndpoint2(),
GetEndpoint3()));
EXPECT_THAT(
result->intermediates,
testing::UnorderedElementsAre(GetIntermediate1(), GetIntermediate2()));
}
TEST_F(RecoveryKeyStoreCertificateTest, Internals_CertXmlWithWrongRootTag) {
EXPECT_FALSE(internal::ParseRecoveryKeyStoreCertXML("<not-cert></not-cert>"));
}
TEST_F(RecoveryKeyStoreCertificateTest, Internals_CertXmlIgnoresEmptyCerts) {
static constexpr std::string_view kTestXml =
R"(<certificate>
<intermediates>
<cert></cert>
<cert>intermediate</cert>
</intermediates>
<endpoints>
<cert>endpoint</cert>
<cert></cert>
</endpoints>
</certificate>)";
auto result = internal::ParseRecoveryKeyStoreCertXML(kTestXml);
ASSERT_TRUE(result);
EXPECT_THAT(result->endpoints, testing::ElementsAre("endpoint"));
EXPECT_THAT(result->intermediates, testing::ElementsAre("intermediate"));
}
TEST_F(RecoveryKeyStoreCertificateTest,
Internals_CertXmlIgnoresUnrecognizedTags) {
static constexpr std::string_view kTestXml =
R"(<certificate>
<something-else></something-else>
<another-thing/>
<intermediates>
<not-cert></not-cert>
<cert>intermediate</cert>
<also-not-cert/>
</intermediates>
<endpoints>
<not-cert></not-cert>
<cert>endpoint</cert>
<also-not-cert/>
</endpoints>
<something-else></something-else>
<another-thing/>
</certificate>)";
auto result = internal::ParseRecoveryKeyStoreCertXML(kTestXml);
ASSERT_TRUE(result);
EXPECT_THAT(result->endpoints, testing::ElementsAre("endpoint"));
EXPECT_THAT(result->intermediates, testing::ElementsAre("intermediate"));
}
TEST_F(RecoveryKeyStoreCertificateTest, Internals_CertXmlIgnoresNestedCerts) {
static constexpr std::string_view kTestXml =
R"(<certificate>
<intermediates>
<should-ignore-this>
<cert>intermediate-should-be-ignored</cert>
</should-ignore-this>
<cert>intermediate</cert>
<also-not-cert/>
</intermediates>
<endpoints>
<should-ignore-this>
<cert>endpoint-should-be-ignored</cert>
</should-ignore-this>
<cert>endpoint</cert>
</endpoints>
</certificate>)";
auto result = internal::ParseRecoveryKeyStoreCertXML(kTestXml);
ASSERT_TRUE(result);
EXPECT_THAT(result->endpoints, testing::ElementsAre("endpoint"));
EXPECT_THAT(result->intermediates, testing::ElementsAre("intermediate"));
}
TEST_F(RecoveryKeyStoreCertificateTest, Internals_CertXmlNoEndpoints) {
static constexpr std::string_view kTestXml =
R"(<certificate>
<intermediates>
<cert>intermediate</cert>
</intermediates>
</certificate>)";
EXPECT_FALSE(internal::ParseRecoveryKeyStoreCertXML(kTestXml));
}
TEST_F(RecoveryKeyStoreCertificateTest, Internals_CertXmlNoIntermediates) {
static constexpr std::string_view kTestXml =
R"(<certificate>
<intermediates>
</intermediates>
<endpoints>
<cert>endpoint</cert>
</endpoints>
</certificate>)";
EXPECT_FALSE(internal::ParseRecoveryKeyStoreCertXML(kTestXml));
}
TEST_F(RecoveryKeyStoreCertificateTest, Internals_CertXmlEmptyString) {
EXPECT_FALSE(internal::ParseRecoveryKeyStoreCertXML(""));
}
TEST_F(RecoveryKeyStoreCertificateTest, Internals_ParseValidSigXml) {
auto result = internal::ParseRecoveryKeyStoreSigXML(kSigXml);
ASSERT_TRUE(result);
EXPECT_THAT(
result->intermediates,
testing::UnorderedElementsAre(GetIntermediate1(), GetIntermediate2()));
EXPECT_EQ(result->certificate, GetCertificate());
EXPECT_EQ(result->signature, GetSignature());
}
TEST_F(RecoveryKeyStoreCertificateTest, Internals_SigXmlWithWrongRootTag) {
EXPECT_FALSE(internal::ParseRecoveryKeyStoreSigXML("<not-sig></not-sig>"));
}
TEST_F(RecoveryKeyStoreCertificateTest, Internals_SigXmlIgnoresEmptyCerts) {
static constexpr std::string_view kTestXml =
R"(<signature>
<intermediates>
<cert></cert>
<cert>intermediate</cert>
</intermediates>
<certificate>certificate</certificate>
<value>signature</value>
</signature>)";
auto result = internal::ParseRecoveryKeyStoreSigXML(kTestXml);
ASSERT_TRUE(result);
EXPECT_THAT(result->intermediates, testing::ElementsAre("intermediate"));
EXPECT_EQ(result->certificate, "certificate");
EXPECT_EQ(result->signature, "signature");
}
TEST_F(RecoveryKeyStoreCertificateTest,
Internals_SigXmlIgnoresUnrecognizedTags) {
static constexpr std::string_view kTestXml =
R"(<signature>
<intermediates>
<something-else></something-else>
<cert>intermediate</cert>
<something-else/>
</intermediates>
<unexpected></unexpected>
<certificate>certificate</certificate>
<value>signature</value>
<unexpected/>
</signature>)";
auto result = internal::ParseRecoveryKeyStoreSigXML(kTestXml);
ASSERT_TRUE(result);
EXPECT_THAT(result->intermediates, testing::ElementsAre("intermediate"));
EXPECT_EQ(result->certificate, "certificate");
EXPECT_EQ(result->signature, "signature");
}
TEST_F(RecoveryKeyStoreCertificateTest, Internals_SigXmlIgnoresNestedCerts) {
static constexpr std::string_view kTestXml =
R"(<signature>
<intermediates>
<nested>
<cert>should ignore this</cert>
</nested>
<cert>intermediate</cert>
</intermediates>
<unexpected></unexpected>
<certificate>certificate</certificate>
<value>signature</value>
<unexpected/>
</signature>)";
auto result = internal::ParseRecoveryKeyStoreSigXML(kTestXml);
ASSERT_TRUE(result);
EXPECT_THAT(result->intermediates, testing::ElementsAre("intermediate"));
EXPECT_EQ(result->certificate, "certificate");
EXPECT_EQ(result->signature, "signature");
}
TEST_F(RecoveryKeyStoreCertificateTest, Internals_SigXmlNoIntermediates) {
static constexpr std::string_view kTestXml =
R"(<signature>
<certificate>certificate</certificate>
<value>signature</value>
</signature>)";
EXPECT_FALSE(internal::ParseRecoveryKeyStoreSigXML(kTestXml));
}
TEST_F(RecoveryKeyStoreCertificateTest, Internals_SigXmlNoCertificate) {
static constexpr std::string_view kTestXml =
R"(<signature>
<intermediates>
<cert>intermediate</cert>
</intermediates>
<value>signature</value>
</signature>)";
EXPECT_FALSE(internal::ParseRecoveryKeyStoreSigXML(kTestXml));
}
TEST_F(RecoveryKeyStoreCertificateTest, Internals_SigXmlNoSignature) {
static constexpr std::string_view kTestXml =
R"(<signature>
<intermediates>
<cert>intermediate</cert>
</intermediates>
<certificate>certificate</certificate>
</signature>)";
EXPECT_FALSE(internal::ParseRecoveryKeyStoreSigXML(kTestXml));
}
TEST_F(RecoveryKeyStoreCertificateTest, Internals_SigXmlEmptyString) {
EXPECT_FALSE(internal::ParseRecoveryKeyStoreSigXML(""));
}
TEST_F(RecoveryKeyStoreCertificateTest, Internals_VerifySignatureChainSuccess) {
std::vector<std::string> intermediates{std::string(GetIntermediate1()),
std::string(GetIntermediate2())};
std::shared_ptr<const bssl::ParsedCertificate> certificate =
internal::VerifySignatureChain(GetCertificate(), intermediates,
kValidCertificateDate);
EXPECT_TRUE(certificate);
}
TEST_F(RecoveryKeyStoreCertificateTest,
Internals_VerifySignatureChainCertificateNotB64) {
std::vector<std::string> intermediates{std::string(GetIntermediate1()),
std::string(GetIntermediate2())};
std::shared_ptr<const bssl::ParsedCertificate> certificate =
internal::VerifySignatureChain("not base 64", intermediates,
kValidCertificateDate);
EXPECT_FALSE(certificate);
}
TEST_F(RecoveryKeyStoreCertificateTest,
Internals_VerifySignatureChainSkipsNotB64Intermediates) {
std::vector<std::string> intermediates{"not base 64",
std::string(GetIntermediate1()),
std::string(GetIntermediate2())};
std::shared_ptr<const bssl::ParsedCertificate> certificate =
internal::VerifySignatureChain(GetCertificate(), intermediates,
kValidCertificateDate);
EXPECT_TRUE(certificate);
}
TEST_F(RecoveryKeyStoreCertificateTest, Internals_VerifySignatureChainExpired) {
std::vector<std::string> intermediates{std::string(GetIntermediate1()),
std::string(GetIntermediate2())};
std::shared_ptr<const bssl::ParsedCertificate> certificate =
internal::VerifySignatureChain(GetCertificate(), intermediates,
kValidCertificateDate + base::Days(10000));
EXPECT_FALSE(certificate);
}
TEST_F(RecoveryKeyStoreCertificateTest,
Internals_VerifySignatureChainNotYetValid) {
std::vector<std::string> intermediates = {std::string(GetIntermediate1()),
std::string(GetIntermediate2())};
std::shared_ptr<const bssl::ParsedCertificate> certificate =
internal::VerifySignatureChain(GetCertificate(), intermediates,
kValidCertificateDate - base::Days(10000));
EXPECT_FALSE(certificate);
}
TEST_F(RecoveryKeyStoreCertificateTest, Internals_VerifySignature) {
std::vector<std::string> intermediates = {std::string(GetIntermediate1()),
std::string(GetIntermediate2())};
auto certificate = internal::VerifySignatureChain(
GetCertificate(), intermediates, kValidCertificateDate);
ASSERT_TRUE(certificate);
EXPECT_TRUE(internal::VerifySignature(std::move(certificate), kCertXml,
GetSignature()));
}
TEST_F(RecoveryKeyStoreCertificateTest, Internals_VerifySignatureNotValid) {
std::vector<std::string> intermediates = {std::string(GetIntermediate1()),
std::string(GetIntermediate2())};
auto certificate = internal::VerifySignatureChain(
GetCertificate(), intermediates, kValidCertificateDate);
ASSERT_TRUE(certificate);
std::string invalid_signature(GetSignature());
invalid_signature[0] = 'X';
EXPECT_FALSE(internal::VerifySignature(std::move(certificate), kCertXml,
invalid_signature));
}
TEST_F(RecoveryKeyStoreCertificateTest, Internals_VerifySignatureNotBase64) {
std::vector<std::string> intermediates = {std::string(GetIntermediate1()),
std::string(GetIntermediate2())};
auto certificate = internal::VerifySignatureChain(
GetCertificate(), intermediates, kValidCertificateDate);
ASSERT_TRUE(certificate);
EXPECT_FALSE(internal::VerifySignature(std::move(certificate), kCertXml,
"not base 64"));
}
TEST_F(RecoveryKeyStoreCertificateTest, Internals_ExtractEndpointPKs) {
internal::ParsedRecoveryKeyStoreCertXML cert_xml =
*internal::ParseRecoveryKeyStoreCertXML(kCertXml);
std::vector<std::unique_ptr<SecureBoxPublicKey>> pks =
internal::ExtractEndpointPublicKeys(std::move(cert_xml),
kValidCertificateDate);
ASSERT_EQ(pks.size(), 3u);
EXPECT_THAT(pks.at(0)->ExportToBytes(),
testing::ElementsAreArray(kEndpoint1PublicKey));
EXPECT_THAT(pks.at(1)->ExportToBytes(),
testing::ElementsAreArray(kEndpoint2PublicKey));
EXPECT_THAT(pks.at(2)->ExportToBytes(),
testing::ElementsAreArray(kEndpoint3PublicKey));
}
TEST_F(RecoveryKeyStoreCertificateTest,
Internals_ExtractEndpointIgnoresBadCerts) {
internal::ParsedRecoveryKeyStoreCertXML cert_xml =
*internal::ParseRecoveryKeyStoreCertXML(kCertXml);
cert_xml.endpoints.emplace_back("aabbcc");
std::vector<std::unique_ptr<SecureBoxPublicKey>> pks =
internal::ExtractEndpointPublicKeys(std::move(cert_xml),
kValidCertificateDate);
EXPECT_EQ(pks.size(), 3u);
}
TEST_F(RecoveryKeyStoreCertificateTest,
Internals_ExtractEndpointIgnoresBadBase64) {
internal::ParsedRecoveryKeyStoreCertXML cert_xml =
*internal::ParseRecoveryKeyStoreCertXML(kCertXml);
cert_xml.endpoints.emplace_back("not base 64");
std::vector<std::unique_ptr<SecureBoxPublicKey>> pks =
internal::ExtractEndpointPublicKeys(std::move(cert_xml),
kValidCertificateDate);
EXPECT_EQ(pks.size(), 3u);
}
TEST_F(RecoveryKeyStoreCertificateTest, ParseSuccess) {
std::optional<RecoveryKeyStoreCertificate> recovery_key_store_cert =
RecoveryKeyStoreCertificate::Parse(kCertXml, kSigXml,
kValidCertificateDate);
ASSERT_TRUE(recovery_key_store_cert.has_value());
ASSERT_EQ(recovery_key_store_cert->endpoint_public_keys().size(), 3u);
EXPECT_THAT(
recovery_key_store_cert->endpoint_public_keys().at(0)->ExportToBytes(),
testing::ElementsAreArray(kEndpoint1PublicKey));
EXPECT_THAT(
recovery_key_store_cert->endpoint_public_keys().at(1)->ExportToBytes(),
testing::ElementsAreArray(kEndpoint2PublicKey));
EXPECT_THAT(
recovery_key_store_cert->endpoint_public_keys().at(2)->ExportToBytes(),
testing::ElementsAreArray(kEndpoint3PublicKey));
}
TEST_F(RecoveryKeyStoreCertificateTest, Parse_BadCertXml) {
EXPECT_FALSE(RecoveryKeyStoreCertificate::Parse("not cert xml", kSigXml,
kValidCertificateDate));
}
TEST_F(RecoveryKeyStoreCertificateTest, Parse_BadSigXml) {
EXPECT_FALSE(RecoveryKeyStoreCertificate::Parse(kCertXml, "not sig xml",
kValidCertificateDate));
}
TEST_F(RecoveryKeyStoreCertificateTest, Parse_BadChain) {
static constexpr std::string_view kBadSigXml =
R"(<?xml version="1.0" encoding="UTF-8"?>
<signature>
<intermediates>
<cert>intermediate</cert>
</intermediates>
<certificate>certificate</certificate>
<value>signature</value>
</signature>)";
EXPECT_FALSE(RecoveryKeyStoreCertificate::Parse(kCertXml, kBadSigXml,
kValidCertificateDate));
}
TEST_F(RecoveryKeyStoreCertificateTest, Parse_BadSignature) {
std::string sig_xml_bad_signature(kSigXml);
size_t signature_index = sig_xml_bad_signature.find(GetSignature());
sig_xml_bad_signature[signature_index] = 'X';
EXPECT_FALSE(RecoveryKeyStoreCertificate::Parse(
kCertXml, sig_xml_bad_signature, kValidCertificateDate));
}
} // namespace
} // namespace trusted_vault