blob: 7f42d857d23f44ea9a82228e05003190999f9835 [file] [edit]
// 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/x509_certificate.h"
#include <cstdint>
#include <ctime>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "absl/base/no_destructor.h"
#include "absl/status/status.h"
#include "absl/status/status_macros.h"
#include "absl/status/statusor.h"
#include "absl/strings/ascii.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"
#include "absl/strings/substitute.h"
#include "absl/time/time.h"
#include "crypto/algorithms.h"
#include "crypto/default/compliance_checker.h"
#include "openssl/asn1.h"
#include "openssl/base.h"
#include "openssl/bio.h"
#include "openssl/bn.h"
#include "openssl/evp.h"
#include "openssl/obj.h"
#include "openssl/pem.h"
#include "openssl/pool.h"
#include "openssl/rsa.h"
#include "openssl/x509.h"
#include "proto/common.pb.h"
#include "tink/cleartext_keyset_handle.h"
#include "tink/public_key_verify.h"
#include "tink/signature/config_v0.h"
#include "tink/signature/signature_pem_keyset_reader.h"
namespace credentio {
namespace {
using ::crypto::tink::PemKeyParams;
using ::crypto::tink::SignaturePemKeysetReaderBuilder;
// c2pa-al from
// https://github.com/c2pa-org/conformance-public/blob/main/docs/current/schemas/mib/oid.txt
constexpr char kAssuranceLevelOid[] = "1.3.6.1.4.1.62558.3";
// c2pa-cpl-record from
// https://github.com/c2pa-org/conformance-public/blob/main/docs/current/schemas/mib/oid.txt
constexpr char kConformanceRecordOid[] = "1.3.6.1.4.1.62558.4";
absl::Status CheckCompatibleWithAlgorithm(EVP_PKEY* pub_key,
SigningAlgorithm algorithm) {
int key_type = EVP_PKEY_id(pub_key);
switch (algorithm) {
case SigningAlgorithm::kPs256:
case SigningAlgorithm::kPs384:
case SigningAlgorithm::kPs512:
if (key_type == EVP_PKEY_RSA || key_type == EVP_PKEY_RSA_PSS) {
return absl::OkStatus();
}
break;
case SigningAlgorithm::kEs256:
case SigningAlgorithm::kEs384:
case SigningAlgorithm::kEs512:
if (key_type == EVP_PKEY_EC) {
return absl::OkStatus();
}
break;
case SigningAlgorithm::kEdDsa:
if (key_type == EVP_PKEY_ED25519) {
return absl::OkStatus();
}
break;
}
return absl::InvalidArgumentError(absl::Substitute(
"Wrong key type ($0) for algorithm $1", key_type, algorithm));
}
// Returns a list of C2PA supported algorithms.
// See
// https://spec.c2pa.org/specifications/specifications/2.4/specs/C2PA_Specification.html#certificate-requirements.
// Note: bssl::GetDefaultEVPAlgorithms() does not include RSA_PSS.
const std::vector<const EVP_PKEY_ALG*>& GetC2paEVPAlgorithms() {
static const absl::NoDestructor<std::vector<const EVP_PKEY_ALG*>> kAlgorithms(
{
EVP_pkey_ec_p256(),
EVP_pkey_ec_p384(),
EVP_pkey_ec_p521(),
EVP_pkey_rsa(),
EVP_pkey_rsa_pss_sha256(),
EVP_pkey_rsa_pss_sha384(),
EVP_pkey_rsa_pss_sha512(),
EVP_pkey_ed25519(),
});
return *kAlgorithms;
}
absl::StatusOr<bssl::UniquePtr<X509>> GetCertificateFromDER(
absl::string_view der_data) {
auto crypto_buffer = bssl::UniquePtr<CRYPTO_BUFFER>(
CRYPTO_BUFFER_new(reinterpret_cast<const uint8_t*>(der_data.data()),
der_data.length(), /*pool=*/nullptr));
const auto& algorithms = GetC2paEVPAlgorithms();
bssl::UniquePtr<X509> cert(X509_parse_with_algorithms(
crypto_buffer.get(), algorithms.data(), algorithms.size()));
if (cert == nullptr) {
return absl::InvalidArgumentError(
"Failed to parse the X.509 certificate from DER bytes");
}
return cert;
}
absl::StatusOr<std::string> PemEncode(EVP_PKEY* pub_key) {
bssl::UniquePtr<BIO> bio(BIO_new(BIO_s_mem()));
if (PEM_write_bio_PUBKEY(bio.get(), pub_key) != 1) {
return absl::InternalError("Failed to write pub key to BIO");
}
const uint8_t* pem;
size_t pem_length = 0;
if (BIO_mem_contents(bio.get(), &pem, &pem_length) != 1) {
return absl::InternalError("Failed to get PEM string from BIO");
}
std::string pem_str(reinterpret_cast<const char*>(pem), pem_length);
return pem_str;
}
bssl::UniquePtr<EVP_PKEY> MaybeConvertPssToRsa(
bssl::UniquePtr<EVP_PKEY> pub_key) {
// Tink uses BoringSSL to parse public keys from PEM, which does not support
// id-RSASSA-PSS by default. Since Tink uses key templates, it doesn't care if
// the original public key had the "restricted" RSA-PSS OID or the "standard"
// RSA OID. It only cares about the modulus and exponent, then applies the PSS
// logic defined in the GetPemKeyParams. Therefore, for compatibility, we turn
// the "restricted" RSA-PSS public key to a "generic" RSA public key.
RSA* rsa_params = EVP_PKEY_get1_RSA(pub_key.get());
if (rsa_params != nullptr) {
// Ideally, we should check if it is a RSA_PSS key. However, BoringSSL does
// not implement RSA_get0_pss_params yet.
bssl::UniquePtr<EVP_PKEY> rsa_key(EVP_PKEY_new());
EVP_PKEY_assign_RSA(rsa_key.get(), rsa_params);
return rsa_key;
}
return pub_key;
}
std::string X509NamePrintEx(const X509_NAME* name, uint64_t flags) {
if (!name) return "";
bssl::UniquePtr<BIO> bio(BIO_new(BIO_s_mem()));
X509_NAME_print_ex(bio.get(), name, 0, flags);
int size = BIO_pending(bio.get());
std::string buffer(size, '\0');
int bytes_read = BIO_read(bio.get(), buffer.data(), size);
if (bytes_read != size) {
return "";
}
return buffer;
}
} // namespace
std::string X509Certificate::GetSubject() const {
return X509NamePrintEx(X509_get_subject_name(cert_.get()), XN_FLAG_RFC2253);
}
std::string X509Certificate::GetIssuer() const {
return X509NamePrintEx(X509_get_issuer_name(cert_.get()), XN_FLAG_RFC2253);
}
absl::StatusOr<std::unique_ptr<X509Certificate>> X509Certificate::Create(
absl::string_view der) {
ABSL_ASSIGN_OR_RETURN(auto x509, GetCertificateFromDER(der));
return std::make_unique<X509Certificate>(std::move(x509));
}
absl::Status X509Certificate::IsValidC2paCertificate(bool is_leaf) const {
return ComplianceChecker::IsValidC2paCertificate(cert_.get(), is_leaf);
}
absl::Status X509Certificate::VerifySignature(
absl::string_view signature, absl::string_view data,
SigningAlgorithm algorithm) const {
bssl::UniquePtr<EVP_PKEY> pub_key(X509_get_pubkey(cert_.get()));
if (pub_key == nullptr) {
return absl::InvalidArgumentError(
"Certificate does not have a valid public key");
}
pub_key = MaybeConvertPssToRsa(std::move(pub_key));
ABSL_ASSIGN_OR_RETURN(auto pem_str, PemEncode(pub_key.get()));
auto builder = SignaturePemKeysetReaderBuilder(
SignaturePemKeysetReaderBuilder::PemReaderType::PUBLIC_KEY_VERIFY);
auto params = GetPemKeyParams(algorithm);
if (!params.ok()) {
return absl::Status(params.status().code(),
absl::StrCat(params.status().message(),
"; Failed to get PEM key params"));
}
for (const auto& param : *params) {
builder.Add({.serialized_key = pem_str, .parameters = param});
}
auto reader = builder.Build();
if (!reader.ok()) {
return absl::Status(
reader.status().code(),
absl::StrCat(reader.status().message(),
"; Failed to construct the PEM KeysetReader"));
}
auto handle = crypto::tink::CleartextKeysetHandle::Read(*std::move(reader));
if (!handle.ok()) {
return absl::Status(handle.status().code(),
absl::StrCat(handle.status().message(),
"; Failed to construct the KeysetHandle"));
}
auto verifier = (*handle)->GetPrimitive<crypto::tink::PublicKeyVerify>(
crypto::tink::ConfigSignatureV0());
if (!verifier.ok()) {
return absl::Status(
verifier.status().code(),
absl::StrCat(verifier.status().message(),
"; Failed to construct the PublicKeyVerify primitive"));
}
auto status = (*verifier)->Verify(signature, data);
if (!status.ok()) {
return absl::InvalidArgumentError(absl::StrCat(
"Tink verification failed with the status error: ", status.message()));
}
return absl::OkStatus();
}
std::string X509Certificate::DebugString() const {
bssl::UniquePtr<BIO> bio(BIO_new(BIO_s_mem()));
if (!X509_print(bio.get(), cert_.get())) {
return "<X509_print failed>";
}
const uint8_t* data;
size_t len;
if (!BIO_mem_contents(bio.get(), &data, &len) || len == 0) {
return "BIO_mem_contents failed";
}
return std::string(reinterpret_cast<const char*>(data), len);
}
absl::StatusOr<absl::Time> X509Certificate::StartTime() const {
const ASN1_TIME* not_before = X509_get0_notBefore(cert_.get());
if (not_before == nullptr) {
return absl::InternalError("failed to get certificate start time");
}
int64_t posix_time;
if (ASN1_TIME_to_posix(not_before, &posix_time) == 0) {
return absl::InternalError("Invalid certificate start time");
}
return absl::FromUnixSeconds(posix_time);
}
absl::StatusOr<absl::Time> X509Certificate::EndTime() const {
int64_t posix_time;
const ASN1_TIME* not_after = X509_get0_notAfter(cert_.get());
if (not_after == nullptr) {
return absl::InternalError("failed to get certificate end time");
}
if (ASN1_TIME_to_posix(not_after, &posix_time) == 0) {
return absl::InternalError("Invalid certificate end time");
}
return absl::FromUnixSeconds(posix_time);
}
absl::StatusOr<std::vector<PemKeyParams>> X509Certificate::GetPemKeyParams(
SigningAlgorithm algorithm) const {
bssl::UniquePtr<EVP_PKEY> pub_key(X509_get_pubkey(cert_.get()));
if (pub_key == nullptr) {
return absl::InvalidArgumentError(
"Certificate does not have a valid public key");
}
ABSL_RETURN_IF_ERROR(CheckCompatibleWithAlgorithm(pub_key.get(), algorithm));
size_t key_size = EVP_PKEY_bits(pub_key.get());
switch (algorithm) {
case SigningAlgorithm::kPs256:
return std::vector<PemKeyParams>{PemKeyParams{
.key_type = crypto::tink::PemKeyType::PEM_RSA,
.algorithm = crypto::tink::PemAlgorithm::RSASSA_PSS,
.key_size_in_bits = key_size,
.hash_type = google::crypto::tink::HashType::SHA256,
}};
case SigningAlgorithm::kPs384:
return std::vector<PemKeyParams>{PemKeyParams{
.key_type = crypto::tink::PemKeyType::PEM_RSA,
.algorithm = crypto::tink::PemAlgorithm::RSASSA_PSS,
.key_size_in_bits = key_size,
.hash_type = google::crypto::tink::HashType::SHA384,
}};
case SigningAlgorithm::kPs512:
return std::vector<PemKeyParams>{PemKeyParams{
.key_type = crypto::tink::PemKeyType::PEM_RSA,
.algorithm = crypto::tink::PemAlgorithm::RSASSA_PSS,
.key_size_in_bits = key_size,
.hash_type = google::crypto::tink::HashType::SHA512,
}};
case SigningAlgorithm::kEs256:
return std::vector<PemKeyParams>{
PemKeyParams{
.key_type = crypto::tink::PemKeyType::PEM_EC,
.algorithm = crypto::tink::PemAlgorithm::ECDSA_IEEE,
.key_size_in_bits = key_size,
.hash_type = google::crypto::tink::HashType::SHA256,
},
PemKeyParams{
.key_type = crypto::tink::PemKeyType::PEM_EC,
.algorithm = crypto::tink::PemAlgorithm::ECDSA_DER,
.key_size_in_bits = key_size,
.hash_type = google::crypto::tink::HashType::SHA256,
}};
case SigningAlgorithm::kEs384:
return std::vector<PemKeyParams>{
PemKeyParams{
.key_type = crypto::tink::PemKeyType::PEM_EC,
.algorithm = crypto::tink::PemAlgorithm::ECDSA_IEEE,
.key_size_in_bits = key_size,
.hash_type = google::crypto::tink::HashType::SHA384,
},
PemKeyParams{
.key_type = crypto::tink::PemKeyType::PEM_EC,
.algorithm = crypto::tink::PemAlgorithm::ECDSA_DER,
.key_size_in_bits = key_size,
.hash_type = google::crypto::tink::HashType::SHA384,
}};
case SigningAlgorithm::kEs512:
return std::vector<PemKeyParams>{
PemKeyParams{
.key_type = crypto::tink::PemKeyType::PEM_EC,
.algorithm = crypto::tink::PemAlgorithm::ECDSA_IEEE,
.key_size_in_bits = key_size,
.hash_type = google::crypto::tink::HashType::SHA512,
},
PemKeyParams{
.key_type = crypto::tink::PemKeyType::PEM_EC,
.algorithm = crypto::tink::PemAlgorithm::ECDSA_DER,
.key_size_in_bits = key_size,
.hash_type = google::crypto::tink::HashType::SHA512,
},
};
case SigningAlgorithm::kEdDsa:
return std::vector<PemKeyParams>{PemKeyParams{
.key_type = crypto::tink::PemKeyType::PEM_EC,
.algorithm = crypto::tink::PemAlgorithm::ED25519,
.key_size_in_bits = key_size,
.hash_type = google::crypto::tink::HashType::SHA512,
}};
}
}
absl::StatusOr<std::string> X509Certificate::GetSerialNumberHex() const {
const ASN1_INTEGER* serial = X509_get0_serialNumber(cert_.get());
if (serial == nullptr) {
return absl::InternalError("failed to get certificate serial number");
}
bssl::UniquePtr<BIGNUM> bn(ASN1_INTEGER_to_BN(serial, nullptr));
if (!bn) {
return absl::InternalError("failed to convert serial number to BIGNUM");
}
bssl::UniquePtr<char> hex(BN_bn2hex(bn.get()));
if (!hex) {
return absl::InternalError("failed to convert BIGNUM to hex");
}
return absl::AsciiStrToLower(hex.get());
}
absl::StatusOr<std::string> X509Certificate::GetAssuranceLevel() const {
static const ASN1_OBJECT* const assurance_level_oid = []() {
return OBJ_txt2obj(kAssuranceLevelOid, /*dont_search_names=*/1);
}();
if (assurance_level_oid == nullptr) {
return absl::InternalError("Failed to parse assurance level OID");
}
int index = X509_get_ext_by_OBJ(cert_.get(), assurance_level_oid, -1);
if (index == -1) {
return absl::NotFoundError("assurance level extension not found");
}
const X509_EXTENSION* extension = X509_get_ext(cert_.get(), index);
if (extension == nullptr) {
return absl::NotFoundError("cannot extract assurance level extension");
}
const ASN1_OCTET_STRING* octet_str = X509_EXTENSION_get_data(extension);
if (octet_str == nullptr) {
return absl::NotFoundError("cannot extract assurance level extension data");
}
const unsigned char* p = ASN1_STRING_get0_data(octet_str);
int64_t len = ASN1_STRING_length(octet_str);
bssl::UniquePtr<ASN1_OBJECT> val_obj(d2i_ASN1_OBJECT(nullptr, &p, len));
if (!val_obj) {
return absl::NotFoundError("cannot parse assurance level value");
}
char buf[128];
int res = OBJ_obj2txt(buf, sizeof(buf), val_obj.get(), /*no_name=*/1);
if (res <= 0) {
return absl::NotFoundError(
"cannot convert assurance level value to string");
}
return std::string(buf);
}
absl::StatusOr<std::string> X509Certificate::GetConformingProductId() const {
static const ASN1_OBJECT* const conformance_record_oid = []() {
return OBJ_txt2obj(kConformanceRecordOid, /*dont_search_names=*/1);
}();
if (conformance_record_oid == nullptr) {
return absl::InternalError("Failed to parse conformance record OID");
}
int index = X509_get_ext_by_OBJ(cert_.get(), conformance_record_oid, -1);
if (index == -1) {
return absl::NotFoundError("Conformance record extension not found");
}
const X509_EXTENSION* extension = X509_get_ext(cert_.get(), index);
if (extension == nullptr) {
return absl::NotFoundError(
"Failed to retrieve conformance record extension");
}
const ASN1_OCTET_STRING* octet_str = X509_EXTENSION_get_data(extension);
if (octet_str == nullptr) {
return absl::NotFoundError("Conformance record extension data is null");
}
const unsigned char* p = ASN1_STRING_get0_data(octet_str);
int64_t len = ASN1_STRING_length(octet_str);
bssl::UniquePtr<ASN1_UTF8STRING> utf8_str(
d2i_ASN1_UTF8STRING(nullptr, &p, len));
if (!utf8_str) {
return absl::NotFoundError("Failed to parse UUID as UTF8String");
}
return std::string(
reinterpret_cast<const char*>(ASN1_STRING_get0_data(utf8_str.get())),
ASN1_STRING_length(utf8_str.get()));
}
} // namespace credentio