blob: b92dd8df6f423e5318c064a8dd7ac7a7a3cc76b5 [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/default_crypto_read_handler.h"
#include <cstddef>
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include <vector>
#include "absl/base/nullability.h"
#include "absl/log/absl_check.h"
#include "absl/log/absl_log.h"
#include "absl/log/check.h"
#include "absl/log/log.h"
#include "absl/status/status.h"
#include "absl/status/status_macros.h"
#include "absl/status/statusor.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"
#include "absl/time/time.h"
#include "absl/types/span.h"
#include "cose/simple_cms_parser.h"
#include "crypto/algorithms.h"
#include "crypto/crypto_read_handler.h"
#include "crypto/default/eku_verifier.h"
#include "crypto/default/trust_store.h"
#include "crypto/default/x509_certificate.h"
#include "openssl/pki/ocsp.h"
#include "openssl/pki/verify.h"
#include "openssl/pki/verify_error.h"
#include "tsp/timestamp_verifier.h"
#include "tsp/verified_timestamp.h"
namespace credentio {
namespace {
class DefaultParsedCertificates : public ParsedCertificates {
public:
DefaultParsedCertificates() = default;
~DefaultParsedCertificates() override = default;
// If `all_claim_signer_roots_` is nullptr, VerifyClaimSignerTrust skips trust
// checks. If `legacy_claim_signer_roots_` is nullptr, VerifyClaimSignerTrust
// accepts only the C2PA EKU.
explicit DefaultParsedCertificates(
std::vector<std::unique_ptr<X509Certificate>> certificate_chain_x509,
std::vector<std::string> certificate_chain_der,
bssl::VerifyTrustStore* absl_nullable all_claim_signer_roots,
bssl::VerifyTrustStore* absl_nullable legacy_claim_signer_roots)
: certificate_chain_x509_(std::move(certificate_chain_x509)),
certificate_chain_der_(std::move(certificate_chain_der)),
all_claim_signer_roots_(all_claim_signer_roots),
legacy_claim_signer_roots_(legacy_claim_signer_roots) {
ABSL_CHECK(!certificate_chain_x509_.empty());
ABSL_CHECK_EQ(certificate_chain_der_.size(),
certificate_chain_x509_.size());
}
absl::StatusOr<std::vector<std::string>> VerifyClaimSignerTrust(
absl::Time content_time) const override {
if (all_claim_signer_roots_ == nullptr) {
ABSL_LOG_EVERY_N_SEC(INFO, 60)
<< "Bypassing C2PA claim signer certificate trust check.";
return std::vector<std::string>();
}
if (certificate_chain_der_.size() > 3) {
return absl::UnauthenticatedError(
absl::StrCat("Excessive certificate chain length: ",
certificate_chain_der_.size()));
}
bssl::CertificateVerifyOptions opts;
opts.leaf_cert = certificate_chain_der_.at(0);
if (EkuVerifier::CheckLeafCertEku(opts.leaf_cert)) {
// `EkuVerifier::CheckLeafCertEku` returns true iff the version 2.2
// requirements are met:
// 1. The leaf certificate asserts the `digitalSignature` KU bit.
// 2. The leaf certificate has the C2PA claim signing EKU.
// 3. The leaf certificate does not have the `anyExtendedKeyUsage` EKU.
// So, we can skip the EKU checks in BoringSSL.
opts.key_purpose = bssl::CertificateVerifyOptions::KeyPurpose::ANY_EKU;
opts.trust_store = all_claim_signer_roots_;
} else {
// Version 2.2 requirements are not met. Fall back to version 2.1 checks
// in BoringSSL.
opts.key_purpose =
bssl::CertificateVerifyOptions::KeyPurpose::C2PA_MANIFEST;
if (legacy_claim_signer_roots_ == nullptr) {
return absl::InvalidArgumentError(
"Extended Key Usage c2pa-kp-claimSigning not present");
}
opts.trust_store = legacy_claim_signer_roots_;
}
for (int i = 1; i < certificate_chain_der_.size(); ++i) {
opts.intermediates.push_back(certificate_chain_der_.at(i));
}
opts.time = absl::ToTimeT(content_time);
bssl::VerifyError error;
// This check only verifies that there is a valid chain from the leaf
// certificate to one of the trusted root certificates. Note that this is
// a relaxed version of the requirement in
// https://spec.c2pa.org/specifications/specifications/2.4/specs/C2PA_Specification.html#x509_certificates
// which requires the certificates in the chain to be ordered starting with
// the certificate containing the end-entity key (i.e., the leaf
// certificate) followed by the certificate that signed it, and so on.
auto chain = bssl::CertificateVerify(opts, &error);
if (!chain) {
switch (error.Code()) {
case bssl::VerifyError::StatusCode::CERTIFICATE_NOT_YET_VALID:
case bssl::VerifyError::StatusCode::CERTIFICATE_EXPIRED:
return absl::OutOfRangeError(
absl::StrCat("certificate outside validity period: ",
error.DiagnosticString()));
case bssl::VerifyError::StatusCode::PATH_NOT_FOUND:
return absl::UnauthenticatedError(absl::StrCat(
"untrusted claim signer: ", error.DiagnosticString()));
default:
return absl::InvalidArgumentError(absl::StrCat(
"Failed to verify the trust chain: ", error.DiagnosticString()));
}
}
return *chain;
}
absl::Status VerifySignature(absl::string_view signature,
absl::string_view data,
SigningAlgorithm algorithm) const override {
return leaf().VerifySignature(signature, data, algorithm);
}
size_t GetCertificateCount() const override {
return certificate_chain_x509_.size();
}
absl::StatusOr<std::string> GetSubject(size_t index) const override {
if (index >= certificate_chain_x509_.size()) {
return absl::OutOfRangeError("Certificate index out of bounds");
}
return certificate_chain_x509_[index]->GetSubject();
}
absl::StatusOr<std::string> GetIssuer(size_t index) const override {
if (index >= certificate_chain_x509_.size()) {
return absl::OutOfRangeError("Certificate index out of bounds");
}
return certificate_chain_x509_[index]->GetIssuer();
}
absl::StatusOr<absl::Time> GetStartTime(size_t index) const override {
if (index >= certificate_chain_x509_.size()) {
return absl::OutOfRangeError("Certificate index out of bounds");
}
return certificate_chain_x509_[index]->StartTime();
}
absl::StatusOr<absl::Time> GetEndTime(size_t index) const override {
if (index >= certificate_chain_x509_.size()) {
return absl::OutOfRangeError("Certificate index out of bounds");
}
return certificate_chain_x509_[index]->EndTime();
}
absl::StatusOr<std::string> GetSerialNumberHex(size_t index) const override {
if (index >= certificate_chain_x509_.size()) {
return absl::OutOfRangeError("Certificate index out of bounds");
}
return certificate_chain_x509_[index]->GetSerialNumberHex();
}
absl::StatusOr<std::string> GetAssuranceLevel(size_t index) const override {
if (index >= certificate_chain_x509_.size()) {
return absl::OutOfRangeError("Certificate index out of bounds");
}
return certificate_chain_x509_[index]->GetAssuranceLevel();
}
absl::StatusOr<std::string> GetConformingProductId(
size_t index) const override {
if (index >= certificate_chain_x509_.size()) {
return absl::OutOfRangeError("Certificate index out of bounds");
}
return certificate_chain_x509_[index]->GetConformingProductId();
}
const X509Certificate& leaf() const {
return *certificate_chain_x509_.front();
}
#ifndef NDEBUG
[[maybe_unused]] std::string CertChainDebugString() const {
std::string s;
for (const auto& cert : certificate_chain_x509_) {
absl::StrAppend(&s, cert->DebugString());
}
return s;
}
#endif
private:
std::vector<std::unique_ptr<X509Certificate>> certificate_chain_x509_;
std::vector<std::string> certificate_chain_der_;
bssl::VerifyTrustStore* absl_nullable all_claim_signer_roots_;
bssl::VerifyTrustStore* absl_nullable legacy_claim_signer_roots_;
};
// An implementation of CryptoReadHandler intended for use in production.
// It uses Tink, BoringSSL, and SimpleCMS.
class DefaultCryptoReadHandler : public CryptoReadHandler {
public:
DefaultCryptoReadHandler(
absl_nullable std::unique_ptr<bssl::VerifyTrustStore>
all_claim_signer_roots,
absl_nullable std::unique_ptr<bssl::VerifyTrustStore>
legacy_claim_signer_roots,
absl_nullable std::unique_ptr<bssl::VerifyTrustStore> tsa_roots,
TrustEnvironment trust_environment)
: all_claim_signer_roots_(std::move(all_claim_signer_roots)),
legacy_claim_signer_roots_(std::move(legacy_claim_signer_roots)),
tsa_roots_(std::move(tsa_roots)),
timestamp_verifier_(
std::make_unique<TimestampVerifier>(tsa_roots_.get())),
cms_parser_(std::make_unique<SimpleCmsParser>()),
trust_environment_(trust_environment) {}
~DefaultCryptoReadHandler() override = default;
absl::StatusOr<std::unique_ptr<ParsedCertificates>> ParseCertificatesDer(
absl::Span<const absl::string_view> certificates) const override;
absl::StatusOr<OCSPRevocationStatus> VerifyOcspResponse(
absl::string_view ocsp_response_der, absl::string_view certificate_der,
absl::string_view issuer_certificate_der,
absl::Time verify_time) const override {
bssl::OCSPVerifyResult::ResponseStatus response_status;
bssl::OCSPRevocationStatus revocation_status = bssl::CheckOCSP(
ocsp_response_der, certificate_der, issuer_certificate_der,
absl::ToUnixSeconds(verify_time), std::nullopt, &response_status);
switch (response_status) {
case bssl::OCSPVerifyResult::PROVIDED:
switch (revocation_status) {
case bssl::OCSPRevocationStatus::GOOD:
return OCSPRevocationStatus::kGood;
case bssl::OCSPRevocationStatus::REVOKED:
return OCSPRevocationStatus::kRevoked;
case bssl::OCSPRevocationStatus::UNKNOWN:
return OCSPRevocationStatus::kUnknown;
default:
return absl::InternalError("Unknown revocation status");
}
case bssl::OCSPVerifyResult::NO_MATCHING_RESPONSE:
return absl::UnauthenticatedError(
"OCSP response signature failed verification or no matching "
"response found.");
case bssl::OCSPVerifyResult::INVALID_DATE:
case bssl::OCSPVerifyResult::BAD_PRODUCED_AT:
return absl::OutOfRangeError(
"verify_time is outside the valid time window of the OCSP "
"response.");
default:
return absl::InvalidArgumentError(
"OCSP response is malformed or invalid.");
}
}
absl::StatusOr<VerifiedTimestamp> VerifyTimestamp(
absl::string_view cms) const override {
ABSL_ASSIGN_OR_RETURN(auto parsed_token,
cms_parser_->ParseTimestampToken(cms));
return timestamp_verifier_->VerifyTimestampToken(*parsed_token);
}
TrustEnvironment trust_environment() const override {
return trust_environment_;
}
private:
absl_nullable std::unique_ptr<bssl::VerifyTrustStore> all_claim_signer_roots_;
absl_nullable std::unique_ptr<bssl::VerifyTrustStore>
legacy_claim_signer_roots_;
absl_nullable std::unique_ptr<bssl::VerifyTrustStore> tsa_roots_;
std::unique_ptr<TimestampVerifier> timestamp_verifier_;
std::unique_ptr<SimpleCmsParser> cms_parser_;
TrustEnvironment trust_environment_;
};
} // namespace
absl::StatusOr<std::unique_ptr<ParsedCertificates>>
DefaultCryptoReadHandler::ParseCertificatesDer(
absl::Span<const absl::string_view> certificates) const {
if (certificates.empty()) {
return absl::InvalidArgumentError("No leaf certificate provided");
}
// Construct the X.509 public key certificate from DER bytes.
// The first element of the certificate chain is the signer's public key DER
// bytes. The certificate chain starts with the signer's certificate and the
// following elements constitute the rest of the chain.
std::vector<std::unique_ptr<X509Certificate>> certificates_x509;
std::vector<std::string> certificates_der;
certificates_der.reserve(certificates.size());
for (int i = 0; i < certificates.size(); ++i) {
const auto& cert_der = certificates[i];
certificates_der.push_back(std::string(cert_der));
ABSL_ASSIGN_OR_RETURN(auto x509, X509Certificate::Create(/*der=*/cert_der));
ABSL_DVLOG(1) << "Cert validity start time = " << x509->StartTime();
ABSL_DVLOG(1) << "Cert validity end time = " << x509->EndTime();
if (x509 == nullptr) {
return absl::InternalError("X509Certificate::Create returned nullptr");
}
ABSL_RETURN_IF_ERROR(x509->IsValidC2paCertificate(/*is_leaf=*/(i == 0)));
certificates_x509.push_back(std::move(x509));
}
return std::make_unique<DefaultParsedCertificates>(
std::move(certificates_x509), std::move(certificates_der),
all_claim_signer_roots_.get(), legacy_claim_signer_roots_.get());
}
absl::StatusOr<std::unique_ptr<CryptoReadHandler>>
CreateDefaultCryptoReadHandler(const DefaultCryptoReadHandlerOptions& options) {
std::unique_ptr<bssl::VerifyTrustStore> all_claim_signer_roots;
std::unique_ptr<bssl::VerifyTrustStore> legacy_claim_signer_roots;
if (!options.skip_claim_signer_trust_checks_for_test) {
if (!options.legacy_claim_signer_trust_anchors_pem.empty()) {
// If the string is nonempty, it must contain at least one cert.
ABSL_ASSIGN_OR_RETURN(
legacy_claim_signer_roots,
LoadTrustStore(options.legacy_claim_signer_trust_anchors_pem));
}
ABSL_ASSIGN_OR_RETURN(all_claim_signer_roots,
LoadTrustStore(absl::StrCat(
options.claim_signer_trust_anchors_pem,
options.legacy_claim_signer_trust_anchors_pem)));
}
std::unique_ptr<bssl::VerifyTrustStore> tsa_roots;
if (!options.skip_tsa_trust_checks_for_test) {
ABSL_ASSIGN_OR_RETURN(tsa_roots,
LoadTrustStore(options.tsa_trust_anchors_pem));
}
return std::make_unique<DefaultCryptoReadHandler>(
/*all_claim_signer_roots=*/std::move(all_claim_signer_roots),
/*legacy_claim_signer_roots=*/std::move(legacy_claim_signer_roots),
std::move(tsa_roots), options.trust_environment);
}
} // namespace credentio