| // 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 "testing/crypto_testing.h" | |
| #include <cstddef> | |
| #include <cstdint> | |
| #include <string> | |
| #include <utility> | |
| #include "absl/log/absl_check.h" | |
| #include "absl/strings/string_view.h" | |
| #include "absl/time/time.h" | |
| #include "openssl/asn1.h" | |
| #include "openssl/base.h" | |
| #include "openssl/bio.h" | |
| #include "openssl/bn.h" | |
| #include "openssl/digest.h" | |
| #include "openssl/ec_key.h" | |
| #include "openssl/evp.h" | |
| #include "openssl/mem.h" | |
| #include "openssl/nid.h" | |
| #include "openssl/obj.h" | |
| #include "openssl/pem.h" | |
| #include "openssl/rsa.h" | |
| #include "openssl/stack.h" | |
| #include "openssl/x509.h" | |
| namespace credentio { | |
| bssl::UniquePtr<EVP_PKEY> CreateTestEcKey(int nid) { | |
| bssl::UniquePtr<EC_KEY> key(EC_KEY_new_by_curve_name(nid)); | |
| ABSL_CHECK_NE(key, nullptr); | |
| ABSL_CHECK(EC_KEY_generate_key(key.get())); | |
| bssl::UniquePtr<EVP_PKEY> pkey(EVP_PKEY_new()); | |
| ABSL_CHECK_NE(pkey, nullptr); | |
| ABSL_CHECK(EVP_PKEY_set1_EC_KEY(pkey.get(), key.get())); | |
| return pkey; | |
| } | |
| bssl::UniquePtr<EVP_PKEY> CreateTestRsaKey(size_t num_bits) { | |
| bssl::UniquePtr<BIGNUM> rsa_f4(BN_new()); | |
| ABSL_CHECK_NE(rsa_f4, nullptr); | |
| ABSL_CHECK(BN_set_word(rsa_f4.get(), RSA_F4)); | |
| bssl::UniquePtr<RSA> rsa(RSA_new()); | |
| ABSL_CHECK_NE(rsa, nullptr); | |
| ABSL_CHECK(RSA_generate_key_ex(rsa.get(), num_bits, rsa_f4.get(), nullptr)); | |
| bssl::UniquePtr<EVP_PKEY> pkey(EVP_PKEY_new()); | |
| ABSL_CHECK_NE(pkey, nullptr); | |
| ABSL_CHECK(EVP_PKEY_set1_RSA(pkey.get(), rsa.get())); | |
| return pkey; | |
| } | |
| bssl::UniquePtr<EVP_PKEY> CreateTestEdKey() { | |
| bssl::UniquePtr<EVP_PKEY_CTX> ctx( | |
| EVP_PKEY_CTX_new_id(EVP_PKEY_ED25519, nullptr)); | |
| ABSL_CHECK_NE(ctx, nullptr); | |
| ABSL_CHECK(EVP_PKEY_keygen_init(ctx.get())); | |
| EVP_PKEY* pkey = nullptr; | |
| ABSL_CHECK(EVP_PKEY_keygen(ctx.get(), &pkey)); | |
| return bssl::UniquePtr<EVP_PKEY>(pkey); | |
| } | |
| bssl::UniquePtr<X509> CreateTestCertificate( | |
| const TestCertificateOptions& options) { | |
| // Options validation. | |
| ABSL_CHECK(!options.subject_common_name.empty()); | |
| ABSL_CHECK(!options.issuer_common_name.empty()); | |
| ABSL_CHECK(options.signing_key != nullptr); | |
| ABSL_CHECK(options.private_key != nullptr); | |
| // Create the X509 object. | |
| bssl::UniquePtr<X509> x509(X509_new()); | |
| ABSL_CHECK(X509_set_version(x509.get(), 2)); | |
| // Set the not_before/not_after fields. | |
| bssl::UniquePtr<ASN1_TIME> not_before(ASN1_TIME_new()); | |
| ABSL_CHECK(ASN1_TIME_set_posix(not_before.get(), | |
| absl::ToUnixSeconds(options.not_before))); | |
| ABSL_CHECK(X509_set1_notBefore(x509.get(), not_before.get())); | |
| bssl::UniquePtr<ASN1_TIME> not_after(ASN1_TIME_new()); | |
| ABSL_CHECK(ASN1_TIME_set_posix(not_after.get(), | |
| absl::ToUnixSeconds(options.not_after))); | |
| ABSL_CHECK(X509_set1_notAfter(x509.get(), not_after.get())); | |
| // Set the subject DN. | |
| bssl::UniquePtr<X509_NAME> subject_name(X509_NAME_new()); | |
| ABSL_CHECK(X509_NAME_add_entry_by_txt( | |
| subject_name.get(), /*field=*/"CN", MBSTRING_ASC, | |
| reinterpret_cast<const unsigned char*>( | |
| options.subject_common_name.c_str()), | |
| /*len=*/-1, /*loc=*/-1, | |
| /*set=*/0)); | |
| ABSL_CHECK(X509_NAME_add_entry_by_txt( | |
| subject_name.get(), /*field=*/"O", MBSTRING_ASC, | |
| reinterpret_cast<const unsigned char*>("Test Org"), | |
| /*len=*/-1, /*loc=*/-1, | |
| /*set=*/0)); | |
| ABSL_CHECK(X509_NAME_add_entry_by_txt( | |
| subject_name.get(), /*field=*/"OU", MBSTRING_ASC, | |
| reinterpret_cast<const unsigned char*>("Test Unit"), | |
| /*len=*/-1, /*loc=*/-1, | |
| /*set=*/0)); | |
| ABSL_CHECK(X509_set_subject_name(x509.get(), subject_name.get())); | |
| // Set the issuer DN. | |
| bssl::UniquePtr<X509_NAME> issuer_name(X509_NAME_new()); | |
| ABSL_CHECK(X509_NAME_add_entry_by_txt(issuer_name.get(), /*field=*/"CN", | |
| MBSTRING_ASC, | |
| reinterpret_cast<const unsigned char*>( | |
| options.issuer_common_name.c_str()), | |
| /*len=*/-1, /*loc=*/-1, | |
| /*set=*/0)); | |
| ABSL_CHECK(X509_NAME_add_entry_by_txt( | |
| issuer_name.get(), /*field=*/"O", MBSTRING_ASC, | |
| reinterpret_cast<const unsigned char*>("Test Org"), | |
| /*len=*/-1, /*loc=*/-1, | |
| /*set=*/0)); | |
| ABSL_CHECK(X509_NAME_add_entry_by_txt( | |
| issuer_name.get(), /*field=*/"OU", MBSTRING_ASC, | |
| reinterpret_cast<const unsigned char*>("Test Unit"), | |
| /*len=*/-1, /*loc=*/-1, | |
| /*set=*/0)); | |
| ABSL_CHECK(X509_set_issuer_name(x509.get(), issuer_name.get())); | |
| if (options.serial_number.has_value()) { | |
| ABSL_CHECK(ASN1_INTEGER_set_int64(X509_get_serialNumber(x509.get()), | |
| *options.serial_number)); | |
| } | |
| if (options.is_ca.has_value()) { | |
| bssl::UniquePtr<BASIC_CONSTRAINTS> basic_constraints( | |
| BASIC_CONSTRAINTS_new()); | |
| basic_constraints->ca = *options.is_ca ? 1 : 0; | |
| ABSL_CHECK(X509_add1_ext_i2d(x509.get(), NID_basic_constraints, | |
| basic_constraints.get(), /*crit=*/1, | |
| /*flags=*/0)); | |
| bssl::UniquePtr<ASN1_BIT_STRING> key_usage(ASN1_BIT_STRING_new()); | |
| if (*options.is_ca) { | |
| ABSL_CHECK( | |
| ASN1_BIT_STRING_set_bit(key_usage.get(), /*n=*/5, /*value=*/1)); | |
| ABSL_CHECK( | |
| ASN1_BIT_STRING_set_bit(key_usage.get(), /*n=*/6, /*value=*/1)); | |
| } else { | |
| ABSL_CHECK( | |
| ASN1_BIT_STRING_set_bit(key_usage.get(), /*n=*/0, /*value=*/1)); | |
| } | |
| ABSL_CHECK(X509_add1_ext_i2d(x509.get(), NID_key_usage, key_usage.get(), | |
| /*crit=*/1, /*flags=*/0)); | |
| } | |
| if (!options.extended_key_usage_oids.empty()) { | |
| bssl::UniquePtr<EXTENDED_KEY_USAGE> eku(EXTENDED_KEY_USAGE_new()); | |
| for (const std::string& oid : options.extended_key_usage_oids) { | |
| bssl::UniquePtr<ASN1_OBJECT> obj( | |
| OBJ_txt2obj(oid.c_str(), /*dont_search_names=*/1)); | |
| ABSL_CHECK(obj != nullptr); | |
| ABSL_CHECK(bssl::PushToStack(eku.get(), std::move(obj))); | |
| } | |
| ABSL_CHECK(X509_add1_ext_i2d(x509.get(), NID_ext_key_usage, eku.get(), | |
| options.extended_key_usage_critical ? 1 : 0, | |
| /*flags=*/0)); | |
| } | |
| // Set the public key and sign the certificate. | |
| ABSL_CHECK(X509_set_pubkey(x509.get(), options.private_key)); | |
| ABSL_CHECK(X509_sign(x509.get(), options.signing_key, EVP_sha256())); | |
| return x509; | |
| } | |
| std::string KeyToPem(EVP_PKEY* key) { | |
| bssl::UniquePtr<BIO> bio(BIO_new(BIO_s_mem())); | |
| ABSL_CHECK(bio != nullptr); | |
| ABSL_CHECK(PEM_write_bio_PrivateKey(bio.get(), key, /*enc=*/nullptr, | |
| /*pass=*/nullptr, /*pass_len=*/0, | |
| /*cb=*/nullptr, /*userdata=*/nullptr)); | |
| const uint8_t* data = nullptr; | |
| size_t len = 0; | |
| ABSL_CHECK(BIO_mem_contents(bio.get(), &data, &len)); | |
| return std::string(reinterpret_cast<const char*>(data), len); | |
| } | |
| std::string CertToPem(X509* cert) { | |
| bssl::UniquePtr<BIO> bio(BIO_new(BIO_s_mem())); | |
| ABSL_CHECK(bio != nullptr); | |
| ABSL_CHECK(PEM_write_bio_X509(bio.get(), cert)); | |
| const uint8_t* data = nullptr; | |
| size_t len = 0; | |
| ABSL_CHECK(BIO_mem_contents(bio.get(), &data, &len)); | |
| return std::string(reinterpret_cast<const char*>(data), len); | |
| } | |
| std::string CertToDer(X509* cert) { | |
| int der_len = i2d_X509(cert, nullptr); | |
| ABSL_CHECK_GT(der_len, 0); | |
| std::string der(der_len, '\0'); | |
| unsigned char* p = reinterpret_cast<unsigned char*>(&der[0]); | |
| ABSL_CHECK_EQ(i2d_X509(cert, &p), der_len); | |
| return der; | |
| } | |
| TestPki CreateTestPki() { | |
| constexpr absl::string_view kRootCaCommonName = "Test Root CA"; | |
| bssl::UniquePtr<EVP_PKEY> ca_key = CreateTestEcKey(NID_X9_62_prime256v1); | |
| bssl::UniquePtr<X509> ca_cert = CreateTestCertificate({ | |
| .subject_common_name = std::string(kRootCaCommonName), | |
| .issuer_common_name = std::string(kRootCaCommonName), | |
| .signing_key = ca_key.get(), | |
| .private_key = ca_key.get(), | |
| .serial_number = 1, | |
| .is_ca = true, | |
| }); | |
| bssl::UniquePtr<EVP_PKEY> claim_signer_key = | |
| CreateTestEcKey(NID_X9_62_prime256v1); | |
| bssl::UniquePtr<X509> claim_signer_cert = CreateTestCertificate({ | |
| .subject_common_name = "Test Claim Signer", | |
| .issuer_common_name = std::string(kRootCaCommonName), | |
| .signing_key = ca_key.get(), | |
| .private_key = claim_signer_key.get(), | |
| .serial_number = 2, | |
| .is_ca = false, | |
| .extended_key_usage_oids = {std::string(kC2paClaimSigningEkuOid)}, | |
| }); | |
| bssl::UniquePtr<EVP_PKEY> tsa_key = CreateTestEcKey(NID_X9_62_prime256v1); | |
| bssl::UniquePtr<X509> tsa_cert = CreateTestCertificate({ | |
| .subject_common_name = "Test TSA", | |
| .issuer_common_name = std::string(kRootCaCommonName), | |
| .signing_key = ca_key.get(), | |
| .private_key = tsa_key.get(), | |
| .serial_number = 3, | |
| .is_ca = false, | |
| // The time stamping EKU must be marked critical per RFC 3161. | |
| .extended_key_usage_oids = {std::string(kTimeStampingEkuOid)}, | |
| .extended_key_usage_critical = true, | |
| }); | |
| return TestPki{ | |
| .root_ca = | |
| { | |
| .key_pem = KeyToPem(ca_key.get()), | |
| .cert_pem = CertToPem(ca_cert.get()), | |
| .cert_der = CertToDer(ca_cert.get()), | |
| }, | |
| .claim_signer = | |
| { | |
| .key_pem = KeyToPem(claim_signer_key.get()), | |
| .cert_pem = CertToPem(claim_signer_cert.get()), | |
| .cert_der = CertToDer(claim_signer_cert.get()), | |
| }, | |
| .tsa = | |
| { | |
| .key_pem = KeyToPem(tsa_key.get()), | |
| .cert_pem = CertToPem(tsa_cert.get()), | |
| .cert_der = CertToDer(tsa_cert.get()), | |
| }, | |
| }; | |
| } | |
| } // namespace credentio |