blob: 7cccf70cbc61e0105ec3f1c65e0fec5316523fd2 [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/compliance_checker.h"
#include <vector>
#include "absl/status/status.h"
#include "absl/status/status_matchers.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "openssl/asn1.h"
#include "openssl/base.h"
#include "openssl/nid.h"
#include "openssl/obj.h"
#include "openssl/x509.h"
#include "testing/crypto_testing.h"
namespace credentio {
namespace {
using ::absl_testing::IsOk;
using ::absl_testing::StatusIs;
using ::testing::HasSubstr;
TEST(ComplianceCheckerTest, NullCertificate) {
EXPECT_THAT(
ComplianceChecker::IsValidC2paCertificate(nullptr, /*is_leaf=*/true),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("Certificate is null")));
EXPECT_THAT(
ComplianceChecker::IsValidC2paCertificate(nullptr, /*is_leaf=*/false),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("Certificate is null")));
}
TEST(ComplianceCheckerTest, NoPublicKey) {
bssl::UniquePtr<X509> cert(X509_new());
X509_set_version(cert.get(), X509_VERSION_1);
EXPECT_THAT(ComplianceChecker::IsValidC2paCertificate(cert.get(),
/*is_leaf=*/true),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("Certificate does not have a public key")));
EXPECT_THAT(ComplianceChecker::IsValidC2paCertificate(cert.get(),
/*is_leaf=*/false),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("Certificate does not have a public key")));
}
TEST(ComplianceCheckerTest, VersionNotV3) {
bssl::UniquePtr<X509> cert(X509_new());
X509_set_version(cert.get(), X509_VERSION_1);
bssl::UniquePtr<EVP_PKEY> evp_key = CreateTestEcKey(NID_secp384r1);
X509_set_pubkey(cert.get(), evp_key.get());
EXPECT_THAT(
ComplianceChecker::IsValidC2paCertificate(cert.get(), /*is_leaf=*/true),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("Certificate version is not v3")));
EXPECT_THAT(
ComplianceChecker::IsValidC2paCertificate(cert.get(), /*is_leaf=*/false),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("Certificate version is not v3")));
}
TEST(ComplianceCheckerTest, EcdsaGoodCurve) {
bssl::UniquePtr<X509> cert(X509_new());
X509_set_version(cert.get(), X509_VERSION_3);
bssl::UniquePtr<EVP_PKEY> evp_key = CreateTestEcKey(NID_secp384r1);
X509_set_pubkey(cert.get(), evp_key.get());
EXPECT_THAT(
ComplianceChecker::IsValidC2paCertificate(cert.get(), /*is_leaf=*/true),
IsOk());
EXPECT_THAT(
ComplianceChecker::IsValidC2paCertificate(cert.get(), /*is_leaf=*/false),
IsOk());
}
TEST(ComplianceCheckerTest, EcdsaBadCurve) {
bssl::UniquePtr<X509> cert(X509_new());
X509_set_version(cert.get(), X509_VERSION_3);
bssl::UniquePtr<EVP_PKEY> evp_key = CreateTestEcKey(NID_secp224r1);
X509_set_pubkey(cert.get(), evp_key.get());
EXPECT_THAT(
ComplianceChecker::IsValidC2paCertificate(cert.get(), /*is_leaf=*/true),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("EC curve does not fulfill C2PA requirements")));
EXPECT_THAT(
ComplianceChecker::IsValidC2paCertificate(cert.get(), /*is_leaf=*/false),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("EC curve does not fulfill C2PA requirements")));
}
TEST(ComplianceCheckerTest, Rsa) {
bssl::UniquePtr<X509> cert(X509_new());
X509_set_version(cert.get(), X509_VERSION_3);
bssl::UniquePtr<EVP_PKEY> evp_key = CreateTestRsaKey(2048);
X509_set_pubkey(cert.get(), evp_key.get());
EXPECT_THAT(
ComplianceChecker::IsValidC2paCertificate(cert.get(), /*is_leaf=*/true),
IsOk());
EXPECT_THAT(
ComplianceChecker::IsValidC2paCertificate(cert.get(), /*is_leaf=*/false),
IsOk());
}
TEST(ComplianceCheckerTest, RsaBadModulus) {
bssl::UniquePtr<X509> cert(X509_new());
X509_set_version(cert.get(), X509_VERSION_3);
bssl::UniquePtr<EVP_PKEY> evp_key = CreateTestRsaKey(1024);
X509_set_pubkey(cert.get(), evp_key.get());
EXPECT_THAT(
ComplianceChecker::IsValidC2paCertificate(cert.get(), /*is_leaf=*/true),
StatusIs(
absl::StatusCode::kInvalidArgument,
HasSubstr(
"RSA modulus does not fulfill C2PA requirements: The modulus "
"length n = 1024 bits (minimum required is 2048 bits)")));
EXPECT_THAT(
ComplianceChecker::IsValidC2paCertificate(cert.get(), /*is_leaf=*/false),
StatusIs(
absl::StatusCode::kInvalidArgument,
HasSubstr(
"RSA modulus does not fulfill C2PA requirements: The modulus "
"length n = 1024 bits (minimum required is 2048 bits)")));
}
TEST(ComplianceCheckerTest, Ed) {
bssl::UniquePtr<X509> cert(X509_new());
X509_set_version(cert.get(), X509_VERSION_3);
bssl::UniquePtr<EVP_PKEY> evp_key = CreateTestEdKey();
X509_set_pubkey(cert.get(), evp_key.get());
EXPECT_THAT(
ComplianceChecker::IsValidC2paCertificate(cert.get(), /*is_leaf=*/true),
IsOk());
EXPECT_THAT(
ComplianceChecker::IsValidC2paCertificate(cert.get(), /*is_leaf=*/false),
IsOk());
}
TEST(ComplianceCheckerTest, CALeafCertificate) {
bssl::UniquePtr<X509> cert(X509_new());
X509_set_version(cert.get(), X509_VERSION_3);
bssl::UniquePtr<EVP_PKEY> evp_key = CreateTestEdKey();
X509_set_pubkey(cert.get(), evp_key.get());
bssl::UniquePtr<BASIC_CONSTRAINTS> bc(BASIC_CONSTRAINTS_new());
bc->ca = 1;
ASSERT_TRUE(X509_add1_ext_i2d(cert.get(), NID_basic_constraints, bc.get(),
/*crit=*/1, /*flags=*/0));
// Valid as non-leaf.
EXPECT_THAT(
ComplianceChecker::IsValidC2paCertificate(cert.get(), /*is_leaf=*/false),
IsOk());
// Invalid as leaf.
EXPECT_THAT(
ComplianceChecker::IsValidC2paCertificate(cert.get(), /*is_leaf=*/true),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("end-entity certificate MUST be set to FALSE")));
}
TEST(ComplianceCheckerTest, KeyCertSignLeafCertificate) {
bssl::UniquePtr<X509> cert(X509_new());
X509_set_version(cert.get(), X509_VERSION_3);
bssl::UniquePtr<EVP_PKEY> evp_key = CreateTestEdKey();
X509_set_pubkey(cert.get(), evp_key.get());
bssl::UniquePtr<ASN1_BIT_STRING> ku(ASN1_BIT_STRING_new());
std::vector<uint8_t> bits = {0b00000100}; // Bit 5 is keyCertSign
ASSERT_TRUE(ASN1_BIT_STRING_set(ku.get(), bits.data(), bits.size()));
ASSERT_TRUE(X509_add1_ext_i2d(cert.get(), NID_key_usage, ku.get(),
/*crit=*/1, /*flags=*/0));
// Valid as non-leaf.
EXPECT_THAT(
ComplianceChecker::IsValidC2paCertificate(cert.get(), /*is_leaf=*/false),
IsOk());
// Invalid as leaf.
EXPECT_THAT(
ComplianceChecker::IsValidC2paCertificate(cert.get(), /*is_leaf=*/true),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("keyCertSign bit in the Key Usage extension")));
}
TEST(ComplianceCheckerTest, MalformedExtensionsLeafCertificate) {
// Malformed Basic Constraints in leaf certificate.
{
bssl::UniquePtr<X509> cert(X509_new());
X509_set_version(cert.get(), X509_VERSION_3);
bssl::UniquePtr<EVP_PKEY> evp_key = CreateTestEdKey();
X509_set_pubkey(cert.get(), evp_key.get());
bssl::UniquePtr<ASN1_OCTET_STRING> malformed_data(ASN1_OCTET_STRING_new());
std::vector<uint8_t> invalid_der = {
0x05, 0x00}; // Invalid DER for BASIC_CONSTRAINTS
ASSERT_TRUE(ASN1_OCTET_STRING_set(malformed_data.get(), invalid_der.data(),
invalid_der.size()));
bssl::UniquePtr<X509_EXTENSION> ext(X509_EXTENSION_new());
ASSERT_TRUE(X509_EXTENSION_set_object(ext.get(),
OBJ_nid2obj(NID_basic_constraints)));
ASSERT_TRUE(X509_EXTENSION_set_data(ext.get(), malformed_data.get()));
ASSERT_TRUE(X509_add_ext(cert.get(), ext.get(), -1));
EXPECT_THAT(
ComplianceChecker::IsValidC2paCertificate(cert.get(), /*is_leaf=*/true),
StatusIs(
absl::StatusCode::kInvalidArgument,
HasSubstr("Malformed or invalid Basic Constraints extension")));
}
// Malformed Key Usage in leaf certificate.
{
bssl::UniquePtr<X509> cert(X509_new());
X509_set_version(cert.get(), X509_VERSION_3);
bssl::UniquePtr<EVP_PKEY> evp_key = CreateTestEdKey();
X509_set_pubkey(cert.get(), evp_key.get());
bssl::UniquePtr<ASN1_OCTET_STRING> malformed_data(ASN1_OCTET_STRING_new());
std::vector<uint8_t> invalid_der = {0x05,
0x00}; // Invalid DER for KeyUsage
ASSERT_TRUE(ASN1_OCTET_STRING_set(malformed_data.get(), invalid_der.data(),
invalid_der.size()));
bssl::UniquePtr<X509_EXTENSION> ext(X509_EXTENSION_new());
ASSERT_TRUE(
X509_EXTENSION_set_object(ext.get(), OBJ_nid2obj(NID_key_usage)));
ASSERT_TRUE(X509_EXTENSION_set_data(ext.get(), malformed_data.get()));
ASSERT_TRUE(X509_add_ext(cert.get(), ext.get(), -1));
EXPECT_THAT(
ComplianceChecker::IsValidC2paCertificate(cert.get(), /*is_leaf=*/true),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("Malformed or invalid Key Usage extension")));
}
}
} // namespace
} // namespace credentio