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