blob: 7cba807cae62a1bfaa7669de47d0e5f52f31d467 [file]
// Copyright 2026 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#include "crypto/default/timestamp_verifier.h"
#include <cstddef>
#include <cstdint>
#include <iterator>
#include <memory>
#include <optional>
#include <string>
#include <vector>
#include "absl/algorithm/container.h"
#include "absl/base/no_destructor.h"
#include "absl/log/check.h"
#include "absl/status/status.h"
#include "absl/status/status_matchers.h"
#include "absl/status/statusor.h"
#include "absl/strings/string_view.h"
#include "absl/time/time.h"
#include "absl/types/span.h"
#include "crypto/algorithms.h"
#include "crypto/cbs_utils.h"
#include "crypto/default/trust_store.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "openssl/base.h"
#include "openssl/bio.h"
#include "openssl/bytestring.h"
#include "openssl/digest.h"
#include "openssl/mem.h"
#include "openssl/nid.h"
#include "openssl/obj.h"
#include "openssl/pem.h"
#include "openssl/pki/verify.h"
#include "openssl/x509.h"
#include "proto/validation_result.pb.h"
#include "tsp/constants.h"
#include "tsp/parsed_timestamp_token.h"
#include "tsp/test_helpers.h"
#include "tsp/verified_timestamp.h"
namespace credentio {
namespace {
using ::absl_testing::StatusIs;
using ::testing::ElementsAre;
using ::testing::Eq;
using ::testing::HasSubstr;
using ::testing::Return;
constexpr absl::string_view kCaPem = R"(-----BEGIN CERTIFICATE-----
MIIB3zCCAWagAwIBAgIUXJz+6VKGMVOVs+xupRS3Cne3or0wCgYIKoZIzj0EAwIw
JjEkMCIGA1UEAwwbR29vZ2xlIEMyUEEgVGVzdGluZyBSb290IENBMCAXDTI1MDIw
NDE1MTMyMFoYDzIxMjUwMTExMTUxMzIwWjAmMSQwIgYDVQQDDBtHb29nbGUgQzJQ
QSBUZXN0aW5nIFJvb3QgQ0EwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAATWa/wOwQMD
5zNmXVNGbPJuptbvXrNBhgKZI6SHRzngJ4bV+SShgLDqFdtnDfwAO/nxHyACTNl6
sJ9ue407+HW6W1WEODD9a/jVYizAVkzUaEDElDEA5PPkegHqtVJuf8CjUzBRMB0G
A1UdDgQWBBTTMOrQr2ny9gYyywPGUZGx/KENtzAfBgNVHSMEGDAWgBTTMOrQr2ny
9gYyywPGUZGx/KENtzAPBgNVHRMBAf8EBTADAQH/MAoGCCqGSM49BAMCA2cAMGQC
MCsAxaLyEkp2nyQ2BtBoSuu52k3qfWLNAOHiaifoaxhQzDo1juNYuITM8I88PwVO
QAIwJBRVRNysxvouMf7cQ2VSYTPc32BY/m38wnWS3K94l7eS3sANM9U++e8dgEtG
U2Tv
-----END CERTIFICATE-----)";
constexpr absl::string_view kTsaPem = R"(-----BEGIN CERTIFICATE-----
MIIB1zCCAVygAwIBAgIUaUM3tWVucTzvVWUNY0HjFKBAEkwwCgYIKoZIzj0EAwIw
JjEkMCIGA1UEAwwbR29vZ2xlIEMyUEEgVGVzdGluZyBSb290IENBMCAXDTI1MDIw
NDIxNDIxNFoYDzIxMjUwMTExMjE0MjE0WjAiMSAwHgYDVQQDDBdHb29nbGUgQzJQ
QSBUZXN0aW5nIFRTQTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABLlLssu8MJdD
CS3UV4CPcI1l4lnQ7Y6V7jAaAXTt7wbRnN8lbjcMmfzY4mqhc6HMtFYqYj4UeO7t
tjkvzI2PfGmjajBoMA4GA1UdDwEB/wQEAwIHgDAWBgNVHSUBAf8EDDAKBggrBgEF
BQcDCDAdBgNVHQ4EFgQUeWySrF5tKc2Cwe7AzkxALXVXyIwwHwYDVR0jBBgwFoAU
0zDq0K9p8vYGMssDxlGRsfyhDbcwCgYIKoZIzj0EAwIDaQAwZgIxAPwCKKGMymYV
8p2gXDDyJSMbHJnfHu/FvfeaWYF6a6twCCt7IFehJWbyg/f1p/3Q7QIxAK0yLI6L
/FTQPRaB3nZBm4Ocw9QZ171qisDKd1wW83mjmFAp9aNFlFv2yQt9A1NeEQ==
-----END CERTIFICATE-----)";
constexpr absl::string_view kTsaRsaPem = R"(-----BEGIN CERTIFICATE-----
MIIDoDCCAyegAwIBAgIUDDONK2J0GuK8OSkZYI4sN4GGJaYwCgYIKoZIzj0EAwIw
JjEkMCIGA1UEAwwbR29vZ2xlIEMyUEEgVGVzdGluZyBSb290IENBMCAXDTI1MDIw
NTAwNDU1N1oYDzIxMjUwMTEyMDA0NTU3WjAiMSAwHgYDVQQDDBdHb29nbGUgQzJQ
QSBUZXN0aW5nIFRTQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAO5N
FYAVihiznVBVhnpoOIvz790xxqdpZXRYrCXkj9L2o07sjUza8cotpvDzd9W8EkPg
CJSl0dJ4VeZ7wq4PH0azLgpWDppTVtUtzbYGWacvgWsgLQ96SqAmPYnuHwD+b+Wv
q/p35JB3csFDpg2F9SKFHZH50KQJ4z+WLAKf9Gexfs0y8DsN3DtQgzaVF2vS7/SL
jfFbnS1aHXoeKvPiu475pltkuw0velbXi8/cHIq3VuQOwxgyCIE4FuWJYYPMMu5j
HJ0+bBtDI+Nn8rWmN7hpOV1Lo268gzwR2HtGmdM1LABj+RY8nBdyXJmfAY9kqm/D
VuEVrsGdIOYiAw7iHLvjc4CfNaKs6l9bhTppsVydLZE1fKuMFvmSs5Gc9yIermeu
jmoPB6QSVJh76mKOug7BmEzLHipFRxfDOmdr+eP/5w3L1ZOA+nEG2XWVICbIH/ND
rteeFCGJsDV7wDx6Yhpfvjh8A5qP9Z41MW8CtqpzNakivtVaDKmULZ/dyKeI8BZ5
DvEg/6mpwN6Eu06czep0tO4LQ/l3935w3d/lkpn1yKDu9nm02uvVBEqwZW2lR5BT
MeqxQTMrrbmoi0mjsyX+5XmsUzAzBFlZ9HJi14p6vd70FJOlRXttGxCxiLC/QkmN
krFnFH7v1bhbRGGtKqttUK4yogdnxaOhZ6gr/s45AgMBAAGjajBoMA4GA1UdDwEB
/wQEAwIHgDAWBgNVHSUBAf8EDDAKBggrBgEFBQcDCDAdBgNVHQ4EFgQU+rL+AIZ5
EsQVhIN6SfX34LSTS7YwHwYDVR0jBBgwFoAU0zDq0K9p8vYGMssDxlGRsfyhDbcw
CgYIKoZIzj0EAwIDZwAwZAIwDrCJbyS2Z4yA+82o7slKap3VpBwqBW5xrYjIJyV3
IGzPI3i55bQb0wKRf6SMBHmCAjAfrVYauA+rpNxEYEIoM/ziaJ0xEbF/tw/JQyVE
0JW905o/hzXHO5LV3z/x0SYRPiY=
-----END CERTIFICATE-----)";
constexpr absl::string_view kTsaWithIntermediatePem =
R"(-----BEGIN CERTIFICATE-----
MIIBvzCCAWSgAwIBAgIUImopbS3q7uF8Wz88f39/wfxAIYowCgYIKoZIzj0EAwIw
LjEsMCoGA1UEAwwjR29vZ2xlIEMyUEEgVGVzdGluZyBJbnRlcm1lZGlhdGUgQ0Ew
IBcNMjUwMzEzMTM1NzI2WhgPMjEyNTAyMTcxMzU3MjZaMCIxIDAeBgNVBAMMF0dv
b2dsZSBDMlBBIFRlc3RpbmcgVFNBMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE
Z9YEmeJodxdGQdhJbSjIeccIbxjIQY5rPp/c6zqF4/mRr8VOdMur3r/r0Gyxju8k
7R+6/j5IjQwzci01Qs7W06NqMGgwDgYDVR0PAQH/BAQDAgeAMBYGA1UdJQEB/wQM
MAoGCCsGAQUFBwMIMB0GA1UdDgQWBBQGI2mf9lUe+oNSZE/WRQlwVGMgoTAfBgNV
HSMEGDAWgBRmRE2zkZe58xIqg//9Iw76XyOLKjAKBggqhkjOPQQDAgNJADBGAiEA
onFfvfflN96uF0ECnej2aZ7ikuyz+OsDEQVDQZNBTHMCIQCo5YiNm7EeeMexhDzM
vVdDSKzUFmzJXGZAO/kkEQyNXw==
-----END CERTIFICATE-----)";
constexpr absl::string_view kIntermediatePem = R"(-----BEGIN CERTIFICATE-----
MIIByjCCAVGgAwIBAgIUVhW73Ry+Zn7eKwhnxTiyLDJ3cKowCgYIKoZIzj0EAwIw
JjEkMCIGA1UEAwwbR29vZ2xlIEMyUEEgVGVzdGluZyBSb290IENBMCAXDTI1MDMx
MzEzMzc0MVoYDzIxMjUwMjE3MTMzNzQxWjAuMSwwKgYDVQQDDCNHb29nbGUgQzJQ
QSBUZXN0aW5nIEludGVybWVkaWF0ZSBDQTBZMBMGByqGSM49AgEGCCqGSM49AwEH
A0IABJKYZuxD1F5Eqkgl+fqnubgtLr4MHlAh2p/7n8E/BOZuemrAAXVw7p27D1Oy
n6RNyobJVRZHcm61i09hfTKw/NajUzBRMB0GA1UdDgQWBBRmRE2zkZe58xIqg//9
Iw76XyOLKjAfBgNVHSMEGDAWgBTTMOrQr2ny9gYyywPGUZGx/KENtzAPBgNVHRMB
Af8EBTADAQH/MAoGCCqGSM49BAMCA2cAMGQCMBqHAcfcOv21PmRMWchgZ37ZRoor
4+XlKPnh+L0gBBRykqI49VvvpgxtCC2jVA2BnQIwZf0DOnk6c4lUvSzrEJ7+ToR1
dJMoNuufOTVoMQdojCLwnH4GrwID0qsdXJm8X0XY
-----END CERTIFICATE-----)";
constexpr absl::string_view kClaimSignerPem = R"(-----BEGIN CERTIFICATE-----
MIICDzCCAZWgAwIBAgIUR8DAqLjMOExo0KJS3DGC4Qsy79kwCgYIKoZIzj0EAwIw
JjEkMCIGA1UEAwwbR29vZ2xlIEMyUEEgVGVzdGluZyBSb290IENBMCAXDTI1MDIy
MzA1MDMwMFoYDzIxMjUwMTMwMDUwMzAwWjBbMSYwJAYDVQQDDB1Hb29nbGUgVGVz
dCBDMlBBIENsYWltIFNpZ25lcjEdMBsGA1UECgwUR29vZ2xlIEMyUEEgVGVzdCBP
cmcxEjAQBgNVBAsMCVRlc3QgVW5pdDBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IA
BKw1/4qPpbSgEWNUy7B5R48dxnsx92d5lqh7HY204TpvmxQgukcwavpW8XBxSdYr
F+XEgNStkprWpnkoKWNpShyjajBoMA4GA1UdDwEB/wQEAwIHgDAWBgNVHSUBAf8E
DDAKBggrBgEFBQcDBDAdBgNVHQ4EFgQU6wle6tElLcBSIwsoBICb7gInPiQwHwYD
VR0jBBgwFoAU0zDq0K9p8vYGMssDxlGRsfyhDbcwCgYIKoZIzj0EAwIDaAAwZQIw
YNgGpaaZxI2km45t/Wd+njvQlF0JJ4+9GvZ0bDQWH7kjAQTmKrkxuNMT77cA2IMW
AjEArZqFNmE/5yv8tzHbPP73c3n8BPUmjIpZR5A2nzjfhPzpClFp70lkO+mTu82d
ULnv
-----END CERTIFICATE-----)";
class MockParsedTimestampToken : public ParsedTimestampToken {
public:
MOCK_METHOD(absl::Status, VerifySignature, (), (const, override));
MOCK_METHOD(absl::StatusOr<HashAlgorithm>, GetMessageImprintHashAlgorithm, (),
(const, override));
MOCK_METHOD(absl::StatusOr<std::vector<std::string>>, GetCertificateChain, (),
(const, override));
MOCK_METHOD(absl::StatusOr<absl::string_view>, GetSigningCertificateV2Bytes,
(), (const, override));
MOCK_METHOD(absl::StatusOr<absl::string_view>, GetTstInfoBytes, (),
(const, override));
};
std::string GetSigningCertificateV2Value(absl::string_view attribute_bytes) {
CBS cbs = FromStringView(attribute_bytes);
CBS seq, oid, set;
CHECK(CBS_get_asn1(&cbs, &seq, CBS_ASN1_SEQUENCE));
CHECK(CBS_get_asn1(&seq, &oid, CBS_ASN1_OBJECT));
CHECK(CBS_get_asn1(&seq, &set, CBS_ASN1_SET));
CBS val_elem;
CHECK(CBS_get_any_asn1_element(&set, &val_elem, nullptr, nullptr));
return std::string(ToStringView(val_elem));
}
constexpr absl::string_view kSha384OidTxt = "2.16.840.1.101.3.4.2.2";
constexpr absl::string_view kMessageImprintHash = "fake message imprint";
constexpr absl::string_view kValidTimestamp = "20240101000000Z";
absl::Time ValidTimestampTime() {
static absl::Time time = []() {
absl::Time time;
std::string err;
CHECK(absl::ParseTime(kGenTimeFormat, kValidTimestamp, &time, &err)) << err;
return time;
}();
return time;
}
bssl::UniquePtr<X509> ParseCert(absl::string_view pem) {
CBS cbs = FromStringView(pem);
bssl::UniquePtr<BIO> bio(BIO_new_mem_buf(CBS_data(&cbs), CBS_len(&cbs)));
CHECK(bio != nullptr);
bssl::UniquePtr<X509> x509(
PEM_read_bio_X509(bio.get(), nullptr, nullptr, nullptr));
CHECK(x509 != nullptr);
return x509;
}
std::string RawDer(X509* x509) {
uint8_t* der_ptr = nullptr;
size_t der_len = i2d_X509(x509, &der_ptr);
bssl::UniquePtr<uint8_t> der(der_ptr);
return std::string(reinterpret_cast<char*>(der_ptr), der_len);
}
std::string RawDer(absl::string_view pem) {
return RawDer(ParseCert(pem).get());
}
absl::Time ParseGenTime(absl::string_view timestamp) {
absl::Time time;
std::string parse_error;
CHECK(absl::ParseTime(kGenTimeFormat, timestamp, &time, &parse_error))
<< "ParseTime(\"" << kGenTimeFormat << "\", \"" << timestamp
<< "\"): " << parse_error;
return time;
}
absl::string_view ValidNonce() {
static absl::NoDestructor<std::string> nonce(EncodeDerInteger(0xf00dbabe));
return *nonce;
}
std::string SigningCertificateV2AttributeFromPem(
absl::Span<const absl::string_view> pems) {
std::vector<bssl::UniquePtr<X509>> certs;
for (absl::string_view pem : pems) {
certs.push_back(ParseCert(pem));
}
std::vector<X509*> ptr_vec;
absl::c_transform(certs, std::back_inserter(ptr_vec),
[](const auto& cert) { return cert.get(); });
return CreateSigningCertificateV2Attribute(absl::MakeSpan(ptr_vec),
HashAlgorithm::kSha256);
}
class TimestampVerifierTest : public testing::Test {
protected:
TimestampVerifierTest() : no_trust_checks_(/*tsa_roots=*/nullptr) {}
void SetupMockDefaults(MockParsedTimestampToken& mock_token,
const std::vector<std::string>& cert_chain,
absl::string_view v2_val, absl::string_view tst_info) {
ON_CALL(mock_token, GetCertificateChain())
.WillByDefault(Return(cert_chain));
ON_CALL(mock_token, GetMessageImprintHashAlgorithm())
.WillByDefault(Return(HashAlgorithm::kSha384));
ON_CALL(mock_token, VerifySignature())
.WillByDefault(Return(absl::OkStatus()));
ON_CALL(mock_token, GetSigningCertificateV2Bytes())
.WillByDefault(Return(v2_val));
ON_CALL(mock_token, GetTstInfoBytes()).WillByDefault(Return(tst_info));
}
TimestampVerifier no_trust_checks_;
};
TEST_F(TimestampVerifierTest, AcceptsRsaTimestamp) {
std::string tst_info = CreateTstInfo(kValidTimestamp, kMessageImprintHash,
kSha384OidTxt, ValidNonce());
std::string v2_attr = SigningCertificateV2AttributeFromPem({kTsaRsaPem});
std::string v2_val = GetSigningCertificateV2Value(v2_attr);
std::string cert_der = RawDer(kTsaRsaPem);
MockParsedTimestampToken mock_token;
SetupMockDefaults(mock_token, {cert_der}, v2_val, tst_info);
auto timestamp = no_trust_checks_.VerifyTimestampToken(mock_token);
ASSERT_TRUE(timestamp.ok());
EXPECT_EQ(timestamp->asserted_time(), ValidTimestampTime());
EXPECT_THAT(timestamp->message_imprint_hash(), Eq(kMessageImprintHash));
EXPECT_THAT(timestamp->message_imprint_hash_algorithm(),
Eq(HashAlgorithm::kSha384));
EXPECT_THAT(timestamp->nonce(), Eq(ValidNonce()));
EXPECT_THAT(timestamp->tsa_certificate(), Eq(RawDer(kTsaRsaPem)));
EXPECT_THAT(timestamp->certificate_chain(), ElementsAre(RawDer(kTsaRsaPem)));
}
TEST_F(TimestampVerifierTest, AcceptsEcdsaTimestamp) {
std::string tst_info = CreateTstInfo(kValidTimestamp, kMessageImprintHash,
kSha384OidTxt, ValidNonce());
std::string v2_attr = SigningCertificateV2AttributeFromPem({kTsaPem});
std::string v2_val = GetSigningCertificateV2Value(v2_attr);
std::string cert_der = RawDer(kTsaPem);
MockParsedTimestampToken mock_token;
SetupMockDefaults(mock_token, {cert_der}, v2_val, tst_info);
auto timestamp = no_trust_checks_.VerifyTimestampToken(mock_token);
ASSERT_TRUE(timestamp.ok());
EXPECT_EQ(timestamp->asserted_time(), ValidTimestampTime());
EXPECT_THAT(timestamp->message_imprint_hash(), Eq(kMessageImprintHash));
EXPECT_THAT(timestamp->message_imprint_hash_algorithm(),
Eq(HashAlgorithm::kSha384));
EXPECT_THAT(timestamp->nonce(), Eq(ValidNonce()));
EXPECT_THAT(timestamp->tsa_certificate(), Eq(cert_der));
EXPECT_THAT(timestamp->certificate_chain(), ElementsAre(cert_der));
}
TEST_F(TimestampVerifierTest, AcceptsTimestampWithIntermediateCertificate) {
std::string tst_info = CreateTstInfo(kValidTimestamp, kMessageImprintHash,
kSha384OidTxt, ValidNonce());
std::string v2_attr =
SigningCertificateV2AttributeFromPem({kTsaWithIntermediatePem});
std::string v2_val = GetSigningCertificateV2Value(v2_attr);
std::string cert_der = RawDer(kTsaWithIntermediatePem);
std::string intermediate_der = RawDer(kIntermediatePem);
MockParsedTimestampToken mock_token;
SetupMockDefaults(mock_token, {cert_der, intermediate_der}, v2_val, tst_info);
auto timestamp = no_trust_checks_.VerifyTimestampToken(mock_token);
ASSERT_TRUE(timestamp.ok());
EXPECT_EQ(timestamp->asserted_time(), ValidTimestampTime());
EXPECT_THAT(timestamp->message_imprint_hash(), Eq(kMessageImprintHash));
EXPECT_THAT(timestamp->message_imprint_hash_algorithm(),
Eq(HashAlgorithm::kSha384));
EXPECT_THAT(timestamp->nonce(), Eq(ValidNonce()));
EXPECT_THAT(timestamp->tsa_certificate(), Eq(cert_der));
EXPECT_THAT(timestamp->certificate_chain(),
ElementsAre(cert_der, intermediate_der));
}
TEST_F(TimestampVerifierTest, GetSigningCertificateV2BytesFails) {
MockParsedTimestampToken mock_token;
EXPECT_CALL(mock_token, GetCertificateChain())
.WillOnce(Return(std::vector<std::string>{RawDer(kTsaRsaPem)}));
EXPECT_CALL(mock_token, GetMessageImprintHashAlgorithm())
.WillOnce(Return(HashAlgorithm::kSha384));
EXPECT_CALL(mock_token, VerifySignature()).WillOnce(Return(absl::OkStatus()));
EXPECT_CALL(mock_token, GetSigningCertificateV2Bytes())
.WillOnce(Return(absl::InvalidArgumentError("Missing attribute")));
EXPECT_THAT(
no_trust_checks_.VerifyTimestampToken(mock_token),
StatusIs(absl::StatusCode::kInvalidArgument, "Missing attribute"));
}
TEST_F(TimestampVerifierTest, InvalidTstInfoFails) {
std::string v2_attr = SigningCertificateV2AttributeFromPem({kTsaRsaPem});
std::string v2_val = GetSigningCertificateV2Value(v2_attr);
MockParsedTimestampToken mock_token;
SetupMockDefaults(mock_token, {RawDer(kTsaRsaPem)}, v2_val,
"THIS IS NOT BER OR DER OR CER OR AER OR ...");
EXPECT_THAT(no_trust_checks_.VerifyTimestampToken(mock_token),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("could not read top-level sequence")));
}
TEST_F(TimestampVerifierTest, SignatureVerificationFails) {
MockParsedTimestampToken mock_token;
EXPECT_CALL(mock_token, GetCertificateChain())
.WillOnce(Return(std::vector<std::string>{RawDer(kTsaPem)}));
EXPECT_CALL(mock_token, GetMessageImprintHashAlgorithm())
.WillOnce(Return(HashAlgorithm::kSha384));
EXPECT_CALL(mock_token, VerifySignature())
.WillOnce(Return(absl::UnauthenticatedError("Signature failed")));
EXPECT_THAT(no_trust_checks_.VerifyTimestampToken(mock_token),
StatusIs(absl::StatusCode::kUnauthenticated,
HasSubstr("Signature failed")));
}
TEST_F(TimestampVerifierTest, EmptyChainFails) {
MockParsedTimestampToken mock_token;
EXPECT_CALL(mock_token, GetCertificateChain())
.WillOnce(Return(std::vector<std::string>{}));
EXPECT_CALL(mock_token, GetMessageImprintHashAlgorithm())
.WillOnce(Return(HashAlgorithm::kSha384));
EXPECT_THAT(no_trust_checks_.VerifyTimestampToken(mock_token),
StatusIs(absl::StatusCode::kInternal,
HasSubstr("TSA leaf cert missing")));
}
TEST_F(TimestampVerifierTest, InvalidSigningCertificateAttribute) {
MockParsedTimestampToken mock_token;
EXPECT_CALL(mock_token, GetCertificateChain())
.WillOnce(Return(std::vector<std::string>{RawDer(kTsaRsaPem)}));
EXPECT_CALL(mock_token, GetMessageImprintHashAlgorithm())
.WillOnce(Return(HashAlgorithm::kSha384));
EXPECT_CALL(mock_token, VerifySignature()).WillOnce(Return(absl::OkStatus()));
EXPECT_CALL(mock_token, GetSigningCertificateV2Bytes())
.WillOnce(Return("THIS IS NOT A DER SEQUENCE"));
EXPECT_THAT(no_trust_checks_.VerifyTimestampToken(mock_token),
StatusIs(absl::StatusCode::kInvalidArgument,
"could not parse DER SEQUENCE for SigningCertificateV2 "
"attribute value"));
}
TEST_F(TimestampVerifierTest, UnrecognizedSiginingCertificateHashAlgorithm) {
CBB cbb;
CHECK(CBB_init(&cbb, 1000));
CBB value_seq_cbb;
CHECK(CBB_add_asn1(&cbb, &value_seq_cbb, CBS_ASN1_SEQUENCE));
CBB certs_cbb;
CHECK(CBB_add_asn1(&value_seq_cbb, &certs_cbb, CBS_ASN1_SEQUENCE));
constexpr absl::string_view kFakeOid = "1.2.840.113549.1.800.888.8888";
CBS hash_cbs = FromStringView(
"thisisnotarealhashbutthetestwontcarebecauseitwillfailduetothebadoidanywa"
"ys");
CBB certs_entry_cbb;
CHECK(CBB_add_asn1(&certs_cbb, &certs_entry_cbb, CBS_ASN1_SEQUENCE));
CBB obj_cbb;
CHECK(CBB_add_asn1(&certs_entry_cbb, &obj_cbb, CBS_ASN1_OBJECT));
CHECK(
CBB_add_asn1_oid_from_text(&obj_cbb, kFakeOid.data(), kFakeOid.length()));
CHECK(CBB_flush(&certs_entry_cbb));
CHECK(CBB_add_asn1_octet_string(&certs_entry_cbb, CBS_data(&hash_cbs),
CBS_len(&hash_cbs)));
auto value_bytes = FinishToString(&cbb);
ASSERT_TRUE(value_bytes.ok());
MockParsedTimestampToken mock_token;
EXPECT_CALL(mock_token, GetCertificateChain())
.WillOnce(Return(std::vector<std::string>{RawDer(kTsaRsaPem)}));
EXPECT_CALL(mock_token, GetMessageImprintHashAlgorithm())
.WillOnce(Return(HashAlgorithm::kSha384));
EXPECT_CALL(mock_token, VerifySignature()).WillOnce(Return(absl::OkStatus()));
EXPECT_CALL(mock_token, GetSigningCertificateV2Bytes())
.WillOnce(Return(*value_bytes));
EXPECT_THAT(no_trust_checks_.VerifyTimestampToken(mock_token),
StatusIs(absl::StatusCode::kInvalidArgument,
"unrecognized OID in `certs.hashAlgorithm` field of "
"SigningCertificateV2 attribute value; OID value: "
"1.2.840.113549.1.800.888.8888"));
}
TEST_F(TimestampVerifierTest, NoSigningCertificateHash) {
CBB cbb;
CHECK(CBB_init(&cbb, 1000));
CBB value_seq_cbb;
CHECK(CBB_add_asn1(&cbb, &value_seq_cbb, CBS_ASN1_SEQUENCE));
CBB certs_cbb;
CHECK(CBB_add_asn1(&value_seq_cbb, &certs_cbb, CBS_ASN1_SEQUENCE));
CBB certs_entry_cbb;
CHECK(CBB_add_asn1(&certs_cbb, &certs_entry_cbb, CBS_ASN1_SEQUENCE));
CHECK(OBJ_nid2cbb(&certs_entry_cbb, NID_sha384));
CHECK(CBB_flush(&certs_entry_cbb));
auto value_bytes = FinishToString(&cbb);
ASSERT_TRUE(value_bytes.ok());
MockParsedTimestampToken mock_token;
EXPECT_CALL(mock_token, GetCertificateChain())
.WillOnce(Return(std::vector<std::string>{RawDer(kTsaRsaPem)}));
EXPECT_CALL(mock_token, GetMessageImprintHashAlgorithm())
.WillOnce(Return(HashAlgorithm::kSha384));
EXPECT_CALL(mock_token, VerifySignature()).WillOnce(Return(absl::OkStatus()));
EXPECT_CALL(mock_token, GetSigningCertificateV2Bytes())
.WillOnce(Return(*value_bytes));
EXPECT_THAT(
no_trust_checks_.VerifyTimestampToken(mock_token),
StatusIs(absl::StatusCode::kInvalidArgument,
"could not parse `certs.hash` field (DER OCTETSTRING) of "
"SigningCertificateV2 attribute value"));
}
TEST_F(TimestampVerifierTest, UnsupportedSigningCertificateHashAlgorithm) {
CBB cbb;
CHECK(CBB_init(&cbb, 1000));
CBB value_seq_cbb;
CHECK(CBB_add_asn1(&cbb, &value_seq_cbb, CBS_ASN1_SEQUENCE));
CBB certs_cbb;
CHECK(CBB_add_asn1(&value_seq_cbb, &certs_cbb, CBS_ASN1_SEQUENCE));
CBS hash_cbs = FromStringView("Not a real hash");
CBB certs_entry_cbb;
CHECK(CBB_add_asn1(&certs_cbb, &certs_entry_cbb, CBS_ASN1_SEQUENCE));
CHECK(OBJ_nid2cbb(&certs_entry_cbb, NID_sha1));
CHECK(CBB_flush(&certs_entry_cbb));
CHECK(CBB_add_asn1_octet_string(&certs_entry_cbb, CBS_data(&hash_cbs),
CBS_len(&hash_cbs)));
auto value_bytes = FinishToString(&cbb);
ASSERT_TRUE(value_bytes.ok());
MockParsedTimestampToken mock_token;
EXPECT_CALL(mock_token, GetCertificateChain())
.WillOnce(Return(std::vector<std::string>{RawDer(kTsaRsaPem)}));
EXPECT_CALL(mock_token, GetMessageImprintHashAlgorithm())
.WillOnce(Return(HashAlgorithm::kSha384));
EXPECT_CALL(mock_token, VerifySignature()).WillOnce(Return(absl::OkStatus()));
EXPECT_CALL(mock_token, GetSigningCertificateV2Bytes())
.WillOnce(Return(*value_bytes));
EXPECT_THAT(no_trust_checks_.VerifyTimestampToken(mock_token),
StatusIs(absl::StatusCode::kInvalidArgument,
"unsupported hash algorithm in `certs.hashAlgorithm` "
"field of SigningCertificateV2 attribute value: hash "
"algorithm NID is not on C2PA allowlist: 64"));
}
class TrustChecksTest : public ::testing::Test {
protected:
TrustChecksTest()
: tsa_roots_(LoadTestRoots().value()), verifier_(tsa_roots_.get()) {}
absl::StatusOr<std::unique_ptr<bssl::VerifyTrustStore>> LoadTestRoots() {
return LoadTrustStore(kCaPem);
}
void SetupMockDefaults(MockParsedTimestampToken& mock_token,
const std::vector<std::string>& cert_chain,
absl::string_view v2_val, absl::string_view tst_info) {
ON_CALL(mock_token, GetCertificateChain())
.WillByDefault(Return(cert_chain));
ON_CALL(mock_token, GetMessageImprintHashAlgorithm())
.WillByDefault(Return(HashAlgorithm::kSha384));
ON_CALL(mock_token, VerifySignature())
.WillByDefault(Return(absl::OkStatus()));
ON_CALL(mock_token, GetSigningCertificateV2Bytes())
.WillByDefault(Return(v2_val));
ON_CALL(mock_token, GetTstInfoBytes()).WillByDefault(Return(tst_info));
}
const std::unique_ptr<bssl::VerifyTrustStore> tsa_roots_;
TimestampVerifier verifier_;
};
TEST_F(TrustChecksTest, Trusted) {
constexpr absl::string_view kTime = "20250205000000Z";
std::string tst_info =
CreateTstInfo(kTime, kMessageImprintHash, kSha384OidTxt);
std::string v2_attr = SigningCertificateV2AttributeFromPem({kTsaPem});
std::string v2_val = GetSigningCertificateV2Value(v2_attr);
std::string cert_der = RawDer(kTsaPem);
MockParsedTimestampToken mock_token;
SetupMockDefaults(mock_token, {cert_der}, v2_val, tst_info);
auto verified_timestamp = verifier_.VerifyTimestampToken(mock_token);
ASSERT_TRUE(verified_timestamp.ok());
EXPECT_THAT(verified_timestamp->asserted_time(), Eq(ParseGenTime(kTime)));
}
TEST_F(TrustChecksTest, TrustedWithIntermediate) {
constexpr absl::string_view kTime = "20250514000000Z";
std::string tst_info =
CreateTstInfo(kTime, kMessageImprintHash, kSha384OidTxt);
std::string v2_attr =
SigningCertificateV2AttributeFromPem({kTsaWithIntermediatePem});
std::string v2_val = GetSigningCertificateV2Value(v2_attr);
std::string cert_der = RawDer(kTsaWithIntermediatePem);
std::string intermediate_der = RawDer(kIntermediatePem);
MockParsedTimestampToken mock_token;
SetupMockDefaults(mock_token, {cert_der, intermediate_der}, v2_val, tst_info);
auto verified_timestamp = verifier_.VerifyTimestampToken(mock_token);
ASSERT_TRUE(verified_timestamp.ok());
EXPECT_THAT(verified_timestamp->asserted_time(), Eq(ParseGenTime(kTime)));
}
TEST_F(TrustChecksTest, TooManyIntermediateCerts) {
constexpr absl::string_view kTime = "20250514000000Z";
std::string tst_info =
CreateTstInfo(kTime, kMessageImprintHash, kSha384OidTxt);
std::string v2_attr =
SigningCertificateV2AttributeFromPem({kTsaWithIntermediatePem});
std::string v2_val = GetSigningCertificateV2Value(v2_attr);
std::string cert_der = RawDer(kTsaWithIntermediatePem);
std::string intermediate_der = RawDer(kIntermediatePem);
MockParsedTimestampToken mock_token;
// Chain length 4 (1 leaf + 3 intermediates)
SetupMockDefaults(
mock_token,
{cert_der, intermediate_der, intermediate_der, intermediate_der}, v2_val,
tst_info);
EXPECT_THAT(
verifier_.VerifyTimestampToken(mock_token),
StatusIs(absl::StatusCode::kUnauthenticated,
HasSubstr("excessive timestamp certificate chain length")));
}
TEST_F(TrustChecksTest, TrustedMissingIntermediate) {
constexpr absl::string_view kTime = "20250514000000Z";
std::string tst_info =
CreateTstInfo(kTime, kMessageImprintHash, kSha384OidTxt);
std::string v2_attr =
SigningCertificateV2AttributeFromPem({kTsaWithIntermediatePem});
std::string v2_val = GetSigningCertificateV2Value(v2_attr);
std::string cert_der = RawDer(kTsaWithIntermediatePem);
MockParsedTimestampToken mock_token;
// Missing intermediate!
SetupMockDefaults(mock_token, {cert_der}, v2_val, tst_info);
EXPECT_THAT(verifier_.VerifyTimestampToken(mock_token),
StatusIs(absl::StatusCode::kUnauthenticated,
HasSubstr("No matching issuer found")));
}
TEST_F(TrustChecksTest, Untrusted) {
constexpr absl::string_view kTime = "20250205000000Z";
std::string tst_info =
CreateTstInfo(kTime, kMessageImprintHash, kSha384OidTxt);
std::string dummy_cert = "DUMMY_CERT_DER";
// Compute SHA256 of dummy_cert using internal OpenSSL without hasher.h
uint8_t hash[32];
unsigned int hash_len;
CHECK(EVP_Digest(dummy_cert.data(), dummy_cert.length(), hash, &hash_len,
EVP_sha256(), nullptr));
std::string digest(reinterpret_cast<char*>(hash), hash_len);
CBB cbb;
CHECK(CBB_init(&cbb, 1000));
CBB value_seq_cbb;
CHECK(CBB_add_asn1(&cbb, &value_seq_cbb, CBS_ASN1_SEQUENCE));
CBB certs_cbb;
CHECK(CBB_add_asn1(&value_seq_cbb, &certs_cbb, CBS_ASN1_SEQUENCE));
CBB certs_entry_cbb;
CHECK(CBB_add_asn1(&certs_cbb, &certs_entry_cbb, CBS_ASN1_SEQUENCE));
CHECK(OBJ_nid2cbb(&certs_entry_cbb, NID_sha256));
CHECK(CBB_flush(&certs_entry_cbb));
CBS hash_cbs = FromStringView(digest);
CHECK(CBB_add_asn1_octet_string(&certs_entry_cbb, CBS_data(&hash_cbs),
CBS_len(&hash_cbs)));
auto v2_val = FinishToString(&cbb);
ASSERT_TRUE(v2_val.ok());
MockParsedTimestampToken mock_token;
SetupMockDefaults(mock_token, {dummy_cert}, *v2_val, tst_info);
EXPECT_THAT(verifier_.VerifyTimestampToken(mock_token),
StatusIs(absl::StatusCode::kUnauthenticated,
HasSubstr("timestamp certificate chain")));
}
TEST_F(TrustChecksTest, InvalidEku) {
constexpr absl::string_view kTime = "20250205000000Z";
std::string tst_info =
CreateTstInfo(kTime, kMessageImprintHash, kSha384OidTxt);
std::string v2_attr = SigningCertificateV2AttributeFromPem({kClaimSignerPem});
std::string v2_val = GetSigningCertificateV2Value(v2_attr);
std::string cert_der = RawDer(kClaimSignerPem);
MockParsedTimestampToken mock_token;
SetupMockDefaults(mock_token, {cert_der}, v2_val, tst_info);
// The certificate has Email Protection EKU, which is not allowed for TSAs.
EXPECT_THAT(verifier_.VerifyTimestampToken(mock_token),
StatusIs(absl::StatusCode::kUnauthenticated,
HasSubstr("prohibited key usage")));
}
TEST_F(TrustChecksTest, SigningCertificateV2AttributeLeafMismatch) {
constexpr absl::string_view kTime = "21250204161300Z";
std::string tst_info =
CreateTstInfo(kTime, kMessageImprintHash, kSha384OidTxt);
std::string v2_attr = SigningCertificateV2AttributeFromPem({kTsaRsaPem});
std::string v2_val = GetSigningCertificateV2Value(v2_attr);
std::string cert_der = RawDer(kTsaPem);
MockParsedTimestampToken mock_token;
SetupMockDefaults(mock_token, {cert_der}, v2_val, tst_info);
EXPECT_THAT(
verifier_.VerifyTimestampToken(mock_token),
StatusIs(
absl::StatusCode::kUnauthenticated,
HasSubstr("TSA cert does not match SigningCertificateV2 attribute: "
"certificate does not match EssCertIDV2 value")));
}
TEST_F(TrustChecksTest, EmptySigningCertificateAttribute) {
CBB cbb;
CHECK(CBB_init(&cbb, 1000));
CBB value_seq_cbb;
CHECK(CBB_add_asn1(&cbb, &value_seq_cbb, CBS_ASN1_SEQUENCE));
CBB certs_cbb;
CHECK(CBB_add_asn1(&value_seq_cbb, &certs_cbb, CBS_ASN1_SEQUENCE));
auto value_bytes = FinishToString(&cbb);
ASSERT_TRUE(value_bytes.ok());
constexpr absl::string_view kTime = "20250205000000Z";
std::string tst_info =
CreateTstInfo(kTime, kMessageImprintHash, kSha384OidTxt);
std::string cert_der = RawDer(kTsaRsaPem);
MockParsedTimestampToken mock_token;
SetupMockDefaults(mock_token, {cert_der}, *value_bytes, tst_info);
EXPECT_THAT(
verifier_.VerifyTimestampToken(mock_token),
StatusIs(
absl::StatusCode::kInvalidArgument,
"SigningCertificateV2 signed attribute is empty; at minimum, "
"TSAs must record their own leaf certificate in this attribute"));
}
TEST_F(TrustChecksTest, OutsideTsaCertValidityWindow) {
// This time is outside the validity window of the test TSA certificate,
// but inside the validity window of the test CA certificate.
constexpr absl::string_view kTime = "20250204161300Z";
std::string tst_info =
CreateTstInfo(kTime, kMessageImprintHash, kSha384OidTxt);
std::string v2_attr = SigningCertificateV2AttributeFromPem({kTsaPem});
std::string v2_val = GetSigningCertificateV2Value(v2_attr);
std::string cert_der = RawDer(kTsaPem);
MockParsedTimestampToken mock_token;
SetupMockDefaults(mock_token, {cert_der}, v2_val, tst_info);
EXPECT_THAT(
verifier_.VerifyTimestampToken(mock_token),
StatusIs(absl::StatusCode::kOutOfRange, HasSubstr("before notBefore")));
}
TEST_F(TrustChecksTest, OutsideCaCertValidityWindow) {
// This time is inside the validity window of the test TSA certificate,
// but outside the validity window of the test CA certificate.
constexpr absl::string_view kTime = "21250204161300Z";
std::string tst_info =
CreateTstInfo(kTime, kMessageImprintHash, kSha384OidTxt);
std::string v2_attr = SigningCertificateV2AttributeFromPem({kTsaPem});
std::string v2_val = GetSigningCertificateV2Value(v2_attr);
std::string cert_der = RawDer(kTsaPem);
MockParsedTimestampToken mock_token;
SetupMockDefaults(mock_token, {cert_der}, v2_val, tst_info);
EXPECT_THAT(
verifier_.VerifyTimestampToken(mock_token),
StatusIs(absl::StatusCode::kOutOfRange, HasSubstr("after notAfter")));
}
} // namespace
} // namespace credentio