blob: da71c11ad6928ab5be473fd3b024b83015ef76ef [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/compliance_checker.h"
#include "absl/base/nullability.h"
#include "absl/status/status.h"
#include "absl/status/status_macros.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_format.h"
#include "openssl/asn1.h"
#include "openssl/base.h"
#include "openssl/ec.h"
#include "openssl/ec_key.h"
#include "openssl/evp.h"
#include "openssl/nid.h"
#include "openssl/obj.h"
#include "openssl/rsa.h"
#include "openssl/x509.h"
namespace credentio {
namespace {
constexpr int kMinRsaModulusBits = 2048;
absl::Status IsValidCurve(const EVP_PKEY* absl_nonnull evp_key) {
EC_KEY* ec_key = EVP_PKEY_get0_EC_KEY(evp_key);
if (ec_key == nullptr) {
// This should never happen. Return an internal error to indicate a bug.
return absl::InternalError("EVP key is not an EC key");
}
const EC_GROUP* ec_group = EC_KEY_get0_group(ec_key);
if (ec_group == nullptr) {
// This should never happen. Return an internal error to indicate a bug.
return absl::InvalidArgumentError("EC key does not have an EC group");
}
int curve_name = EC_GROUP_get_curve_name(ec_group);
switch (curve_name) {
case NID_X9_62_prime256v1:
case NID_secp384r1:
case NID_secp521r1:
return absl::OkStatus();
default:
return absl::InvalidArgumentError(absl::StrCat(
"EC curve does not fulfill C2PA requirements: ", curve_name));
}
}
absl::Status IsValidModulus(const EVP_PKEY* absl_nonnull evp_key) {
RSA* rsa = EVP_PKEY_get0_RSA(evp_key);
if (rsa == nullptr) {
// This should never happen. Return an internal error to indicate a bug.
return absl::InternalError("EVP key is not an RSA key");
}
if (unsigned int length = RSA_bits(rsa); length < kMinRsaModulusBits) {
return absl::InvalidArgumentError(absl::StrFormat(
"RSA modulus does not fulfill C2PA requirements: The modulus length n "
"= %d bits (minimum required is %d bits)",
length, kMinRsaModulusBits));
}
return absl::OkStatus();
}
absl::Status IsValidTbsCertificate(const X509* absl_nonnull x509) {
const ASN1_BIT_STRING *issuer_uid, *subject_uid;
X509_get0_uids(x509, &issuer_uid, &subject_uid);
if (issuer_uid != nullptr || subject_uid != nullptr) {
return absl::InvalidArgumentError(
"Certificate contains issuerUniqueID or subjectUniqueID fields");
}
return absl::OkStatus();
}
} // namespace
// This method checks the requirements in
// https://spec.c2pa.org/specifications/specifications/2.4/specs/C2PA_Specification.html#_general_requirements.
absl::Status ComplianceChecker::IsValidC2paCertificate(const X509* x509,
bool is_leaf) {
if (x509 == nullptr) {
return absl::InvalidArgumentError("Certificate is null");
}
if (is_leaf) {
int bc_critical = -1;
bssl::UniquePtr<BASIC_CONSTRAINTS> basic_constraints(
static_cast<BASIC_CONSTRAINTS*>(X509_get_ext_d2i(
x509, NID_basic_constraints, &bc_critical, nullptr)));
if (basic_constraints == nullptr) {
if (bc_critical != -1) {
return absl::InvalidArgumentError(
"Malformed or invalid Basic Constraints extension in the "
"end-entity certificate.");
}
} else if (basic_constraints->ca) {
return absl::InvalidArgumentError(
"The cA boolean flag in the Basic Constraints extension of the "
"end-entity certificate MUST be set to FALSE.");
}
int ku_critical = -1;
bssl::UniquePtr<ASN1_BIT_STRING> key_usage(static_cast<ASN1_BIT_STRING*>(
X509_get_ext_d2i(x509, NID_key_usage, &ku_critical, nullptr)));
if (key_usage == nullptr) {
if (ku_critical != -1) {
return absl::InvalidArgumentError(
"Malformed or invalid Key Usage extension in the end-entity "
"certificate.");
}
} else if (ASN1_BIT_STRING_get_bit(key_usage.get(), 5) != 0) {
return absl::InvalidArgumentError(
"The keyCertSign bit in the Key Usage extension of the "
"end-entity certificate MUST be set to FALSE.");
}
}
EVP_PKEY* pubkey = X509_get0_pubkey(x509);
if (pubkey == nullptr) {
return absl::InvalidArgumentError("Certificate does not have a public key");
}
// * The algorithm requirements in the spec must be satisfied.
// A subset of these checks are performed when COSE verifier is constructing
// the `CoseSign1` structure so we do not need to repeat them here.
switch (EVP_PKEY_id(pubkey)) {
case EVP_PKEY_EC: // id_ecPublicKey
// Quoting
// https://spec.c2pa.org/specifications/specifications/2.4/specs/C2PA_Specification.html#_general_requirements,
// if the `algorithm` field of the `algorithm` field of the certificate’s
// `subjectPublicKeyInfo` is `id-ecPublicKey`, the `parameters` field
// shall be one of the following named curves from RFC 5480,
// section 2.1.1.1: "prime256v1", "secp384r1", "secp521r1".
ABSL_RETURN_IF_ERROR(IsValidCurve(pubkey));
break;
case EVP_PKEY_RSA: // rsaEncryption
case EVP_PKEY_RSA_PSS: // rsaPSS
// Quoting
// https://spec.c2pa.org/specifications/specifications/2.4/specs/C2PA_Specification.html#_general_requirements,
// if the `algorithm` field of the `algorithm` field of the certificate’s
// `subjectPublicKeyInfo` is `rsaEncryption` or `rsaPSS`, the modulus
// field of the parameters field shall have a length of at least 2048
// bits.
ABSL_RETURN_IF_ERROR(IsValidModulus(pubkey));
// BoringSSL does not support id-RSASSA-PSS key encoding, and
// `RSA_get0_pss_params` will always return nullptr. So we cannot perform
// the following checks required by the C2PA spec for RSA-PSS:
// * `hashAlgorithm` and `maskGenAlgorithm` are present in the PSS params
// and they have the same value.
// * The value is one of the following: `id-sha256`, id-sha385` or
// `id-sha512`.
//
// In the case of a malformed key where the `hashAlgorithm` and
// `maskGenAlgorithm` have different values, the signature verification
// will fail. `CoseSign1Verifier::Verify` will also return an error if the
// signing algorithm is RSA-PSS but the hash algorithm is not SHA-256,
// SHA-358 or SHA-512. So we do not need to check these two items here.
break;
}
// * The version must be equal to v3.
if (X509_get_version(x509) != X509_VERSION_3) {
return absl::InvalidArgumentError("Certificate version is not v3");
}
// * The `issuerUniqueID` and `subjectUniqueID` optional fields of the
// `TBSCertificate` sequence shall not be present, as per RFC 5280,
// section 4.1.2.8.
ABSL_RETURN_IF_ERROR(IsValidTbsCertificate(x509));
return absl::OkStatus();
}
} // namespace credentio