| // 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 |