From 7fcac527764ae06781bb22836ec541c1987cc4c5 Mon Sep 17 00:00:00 2001 From: Hailey Ho Date: Mon, 15 Sep 2025 20:16:55 +0000 Subject: [PATCH 01/14] Add more options to x509 cli --- tool-openssl/CMakeLists.txt | 3 +- tool-openssl/internal.h | 8 +- tool-openssl/rehash_test.cc | 59 ++- tool-openssl/req_test.cc | 214 +------- tool-openssl/test_util.cc | 308 +++++++++++ tool-openssl/test_util.h | 31 +- tool-openssl/verify_test.cc | 195 +++---- tool-openssl/x509.cc | 476 ++++++++++++++--- tool-openssl/x509_test.cc | 983 +++++++++++++++++++++++++++--------- 9 files changed, 1648 insertions(+), 629 deletions(-) create mode 100644 tool-openssl/test_util.cc diff --git a/tool-openssl/CMakeLists.txt b/tool-openssl/CMakeLists.txt index e831809a29..30e7b713ae 100644 --- a/tool-openssl/CMakeLists.txt +++ b/tool-openssl/CMakeLists.txt @@ -6,7 +6,7 @@ add_executable( ../tool/fd.cc ../tool/client.cc ../tool/transport_common.cc - + crl.cc dgst.cc ec.cc @@ -86,6 +86,7 @@ if(BUILD_TESTING) ../tool/client.cc ../tool/transport_common.cc + test_util.cc crl.cc crl_test.cc dgst.cc diff --git a/tool-openssl/internal.h b/tool-openssl/internal.h index 058cabfbfd..56ee835fb8 100644 --- a/tool-openssl/internal.h +++ b/tool-openssl/internal.h @@ -24,7 +24,7 @@ struct Tool { bool IsNumeric(const std::string &str); -X509 *CreateAndSignX509Certificate(); + X509_CRL *createTestCRL(); bool isStringUpperCaseEqual(const std::string &a, const std::string &b); @@ -38,7 +38,7 @@ enum class Source : uint8_t { kEnv, // Password from environment with env: prefix kStdin, // Password from stdin #ifndef _WIN32 - kFd, // Password from file descriptor with fd: prefix (Unix only) + kFd, // Password from file descriptor with fd: prefix (Unix only) #endif }; @@ -82,8 +82,8 @@ BSSL_NAMESPACE_BEGIN BORINGSSL_MAKE_DELETER(std::string, pass_util::SensitiveStringDeleter) BSSL_NAMESPACE_END -bool LoadPrivateKeyAndSignCertificate(X509 *x509, - const std::string &signkey_path); +// bool LoadPrivateKeyAndSignCertificate(X509 *x509, +// const std::string &signkey_path); EVP_PKEY *CreateTestKey(int key_bits); tool_func_t FindTool(const std::string &name); diff --git a/tool-openssl/rehash_test.cc b/tool-openssl/rehash_test.cc index 2c0fe12531..f00328efe7 100644 --- a/tool-openssl/rehash_test.cc +++ b/tool-openssl/rehash_test.cc @@ -1,10 +1,10 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 OR ISC -#include "internal.h" +#include #include "../crypto/test/test_util.h" +#include "internal.h" #include "test_util.h" -#include #if !defined(OPENSSL_WINDOWS) #include @@ -17,13 +17,14 @@ using ScopedCharBuffer = std::unique_ptr; // Test fixture class class RehashTest : public ::testing::Test { -protected: - BUCKET** hash_table = get_table(); + protected: + BUCKET **hash_table = get_table(); - void makePathInDir(ScopedCharBuffer &full_path, const char *dir, const char *filename) { + void makePathInDir(ScopedCharBuffer &full_path, const char *dir, + const char *filename) { size_t buffer_len = strlen(dir) + strlen(filename) + 2; - char *buffer = (char *)OPENSSL_zalloc(sizeof(char)*buffer_len); - if(buffer == nullptr) { + char *buffer = (char *)OPENSSL_zalloc(sizeof(char) * buffer_len); + if (buffer == nullptr) { abort(); } full_path.reset(buffer); @@ -38,13 +39,14 @@ class RehashTest : public ::testing::Test { makePathInDir(crl2_path, test_dir, "crl2.pem"); ScopedFILE in_file(fopen(cert1_path.get(), "wb")); - bssl::UniquePtr x509(CreateAndSignX509Certificate()); + bssl::UniquePtr x509; + CreateAndSignX509Certificate(x509, nullptr); ASSERT_TRUE(x509); ASSERT_TRUE(in_file); ASSERT_TRUE(PEM_write_X509(in_file.get(), x509.get())); ScopedFILE in_file2(fopen(cert2_path.get(), "wb")); - x509.reset(CreateAndSignX509Certificate()); + CreateAndSignX509Certificate(x509, nullptr); ASSERT_TRUE(x509); ASSERT_TRUE(in_file2); ASSERT_TRUE(PEM_write_X509(in_file2.get(), x509.get())); @@ -71,15 +73,15 @@ class RehashTest : public ::testing::Test { } // Helper function to create test entries - void CreateTestEntry(Type type, uint32_t hash, const char* filename, - uint8_t* digest) { + void CreateTestEntry(Type type, uint32_t hash, const char *filename, + uint8_t *digest) { add_entry(type, hash, filename, digest); } // Helper to count entries in a bucket - size_t CountEntriesInBucket(BUCKET* bucket) { + size_t CountEntriesInBucket(BUCKET *bucket) { size_t count = 0; - HASH_ENTRY* entry = bucket ? bucket->first_entry : nullptr; + HASH_ENTRY *entry = bucket ? bucket->first_entry : nullptr; while (entry) { count++; entry = entry->next; @@ -119,7 +121,7 @@ TEST_F(RehashTest, BucketCollision) { CreateTestEntry(TYPE_CRL, hash2, "crl.pem", digest2); CreateTestEntry(TYPE_CERT, hash3, "cert2.pem", digest3); - BUCKET* bucket = hash_table[idx1]; + BUCKET *bucket = hash_table[idx1]; ASSERT_NE(bucket, nullptr); // First bucket should be the most recently added @@ -164,15 +166,15 @@ TEST_F(RehashTest, EntryCollision) { CreateTestEntry(TYPE_CERT, 0x12345678, "cert4.pem", digest1); uint32_t expected_idx = (TYPE_CERT + 0x12345678) % 257; - BUCKET* bucket = hash_table[expected_idx]; + BUCKET *bucket = hash_table[expected_idx]; ASSERT_NE(bucket, nullptr); EXPECT_EQ(bucket->num_entries, 3u); EXPECT_EQ(CountEntriesInBucket(bucket), 3u); // Verify entries are in correct order - HASH_ENTRY* entry = bucket->first_entry; - HASH_ENTRY* last = bucket->last_entry; + HASH_ENTRY *entry = bucket->first_entry; + HASH_ENTRY *last = bucket->last_entry; EXPECT_STREQ(entry->filename, "cert1.pem"); entry = entry->next; EXPECT_STREQ(entry->filename, "cert2.pem"); @@ -219,19 +221,20 @@ TEST_F(RehashTest, ValidDirectory) { // Get hashes for certs and CRLs ScopedFILE cert_file(fopen(cert1_path.get(), "rb")); - bssl::UniquePtr cert(PEM_read_X509( - cert_file.get(), nullptr, nullptr, nullptr)); + bssl::UniquePtr cert( + PEM_read_X509(cert_file.get(), nullptr, nullptr, nullptr)); ASSERT_TRUE(cert); uint32_t cert_hash = X509_subject_name_hash(cert.get()); ScopedFILE crl_file(fopen(crl1_path.get(), "rb")); - bssl::UniquePtr crl(PEM_read_X509_CRL( - crl_file.get(), nullptr, nullptr, nullptr)); + bssl::UniquePtr crl( + PEM_read_X509_CRL(crl_file.get(), nullptr, nullptr, nullptr)); ASSERT_TRUE(crl); uint32_t crl_hash = X509_NAME_hash(X509_CRL_get_issuer(crl.get())); // Check that symlinks exist with correct format and targets - size_t link_path_len = strlen(test_dir) + 13; // 13 = slash + hex + decimal + char + int + nul + size_t link_path_len = + strlen(test_dir) + 13; // 13 = slash + hex + decimal + char + int + nul ScopedCharBuffer link_path( (char *)OPENSSL_zalloc(sizeof(char) * link_path_len)); char link_target[PATH_MAX]; @@ -246,7 +249,8 @@ TEST_F(RehashTest, ValidDirectory) { ASSERT_EQ(0, lstat(link_path.get(), &st)); ASSERT_TRUE(S_ISLNK(st.st_mode)); - ssize_t len = readlink(link_path.get(), link_target, sizeof(link_target) - 1); + ssize_t len = + readlink(link_path.get(), link_target, sizeof(link_target) - 1); ASSERT_GT(len, 0); link_target[len] = '\0'; ASSERT_TRUE(strstr(link_target, "cert") != nullptr); @@ -260,10 +264,11 @@ TEST_F(RehashTest, ValidDirectory) { ASSERT_EQ(0, lstat(link_path.get(), &st)); ASSERT_TRUE(S_ISLNK(st.st_mode)); - ssize_t len = readlink(link_path.get(), link_target, sizeof(link_target) - 1); + ssize_t len = + readlink(link_path.get(), link_target, sizeof(link_target) - 1); ASSERT_GT(len, 0); link_target[len] = '\0'; - ASSERT_TRUE(strstr(link_target, "crl") != nullptr);\ + ASSERT_TRUE(strstr(link_target, "crl") != nullptr); unlink(link_path.get()); } } @@ -284,13 +289,13 @@ TEST(TmpDir, CreateTmpDir) { // Test we can create a file in the directory char testfile[PATH_MAX]; snprintf(testfile, PATH_MAX, "%s\\test.txt", tempdir); - FILE* f = fopen(testfile, "w"); + FILE *f = fopen(testfile, "w"); ASSERT_TRUE(f != nullptr); fprintf(f, "test"); fclose(f); // Cleanup - DeleteFileA(testfile); // Delete test file + DeleteFileA(testfile); // Delete test file EXPECT_TRUE(RemoveDirectoryA(tempdir)); // Delete directory } diff --git a/tool-openssl/req_test.cc b/tool-openssl/req_test.cc index 560523c6d4..99bfe2a790 100644 --- a/tool-openssl/req_test.cc +++ b/tool-openssl/req_test.cc @@ -87,213 +87,6 @@ class ReqComparisonTest : public ::testing::Test { } } - public: - int ExecuteCommand(const std::string &command) { - return system(command.c_str()); - } - - // Load a CSR from a PEM file - bssl::UniquePtr LoadCSR(const char *path) { - bssl::UniquePtr bio(BIO_new_file(path, "r")); - if (!bio) { - return NULL; - } - - bssl::UniquePtr csr( - PEM_read_bio_X509_REQ(bio.get(), nullptr, nullptr, nullptr)); - return csr; - } - - // Load an X509 certificate from a PEM file - bssl::UniquePtr LoadCertificate(const char *path) { - bssl::UniquePtr bio(BIO_new_file(path, "r")); - if (!bio) { - return NULL; - } - - bssl::UniquePtr cert( - PEM_read_bio_X509(bio.get(), nullptr, nullptr, nullptr)); - return cert; - } - - bool CompareCSRs(X509_REQ *csr1, X509_REQ *csr2) { - if (!csr1 || !csr2) - return false; - - // 1. Compare subjects - X509_NAME *name1 = X509_REQ_get_subject_name(csr1); - X509_NAME *name2 = X509_REQ_get_subject_name(csr2); - if (X509_NAME_cmp(name1, name2) != 0) - return false; - - // 2. Compare signature algorithms - int sig_nid1 = X509_REQ_get_signature_nid(csr1); - int sig_nid2 = X509_REQ_get_signature_nid(csr2); - if (sig_nid1 != sig_nid2) - return false; - - // 3. Compare public key type and parameters - EVP_PKEY *pkey1 = X509_REQ_get0_pubkey(csr1); - EVP_PKEY *pkey2 = X509_REQ_get0_pubkey(csr2); - if (!pkey1 || !pkey2) { - return false; - } - if (EVP_PKEY_id(pkey1) != EVP_PKEY_id(pkey2)) { - return false; - } - - // For RSA keys, check key size - if (EVP_PKEY_id(pkey1) == EVP_PKEY_RSA) { - RSA *rsa1 = EVP_PKEY_get0_RSA(pkey1); - RSA *rsa2 = EVP_PKEY_get0_RSA(pkey2); - if (!rsa1 || !rsa2) { - return false; - } - if (RSA_size(rsa1) != RSA_size(rsa2)) { - return false; - } - } - - // 4. Verify that both CSRs have valid signatures - if (X509_REQ_verify(csr1, pkey1) != 1) { - return false; - } - if (X509_REQ_verify(csr2, pkey2) != 1) { - return false; - } - - return true; - } - - bool CheckCertificateValidityPeriod(X509 *cert, int expected_days) { - if (!cert) - return false; - - const ASN1_TIME *not_before = X509_get0_notBefore(cert); - const ASN1_TIME *not_after = X509_get0_notAfter(cert); - if (!not_before || !not_after) - return false; - - // Get the difference in days between not_before and not_after - int days, seconds; - if (!ASN1_TIME_diff(&days, &seconds, not_before, not_after)) { - return false; - } - - return (days == expected_days); - } - - // Improved certificate comparison function - bool CompareCertificates(X509 *cert1, X509 *cert2, int expected_days) { - if (!cert1 || !cert2) { - return false; - } - - // 1. Compare subjects - X509_NAME *subj1 = X509_get_subject_name(cert1); - X509_NAME *subj2 = X509_get_subject_name(cert2); - if (X509_NAME_cmp(subj1, subj2) != 0) { - return false; - } - - // 2. Compare issuers - X509_NAME *issuer1 = X509_get_issuer_name(cert1); - X509_NAME *issuer2 = X509_get_issuer_name(cert2); - if (X509_NAME_cmp(issuer1, issuer2) != 0) { - return false; - } - - // 3. Both certificates should be self-signed - if (X509_NAME_cmp(subj1, issuer1) != 0) { - return false; - } - if (X509_NAME_cmp(subj2, issuer2) != 0) { - return false; - } - - // 4. Compare signature algorithms - int sig_nid1 = X509_get_signature_nid(cert1); - int sig_nid2 = X509_get_signature_nid(cert2); - if (sig_nid1 != sig_nid2) { - return false; - } - - // 5. Check validity periods - if (!CheckCertificateValidityPeriod(cert1, expected_days)) { - return false; - } - if (!CheckCertificateValidityPeriod(cert2, expected_days)) { - return false; - } - - // 6. Check public key type and parameters - EVP_PKEY *pkey1 = X509_get0_pubkey(cert1); - EVP_PKEY *pkey2 = X509_get0_pubkey(cert2); - if (!pkey1 || !pkey2) { - return false; - } - - if (EVP_PKEY_id(pkey1) != EVP_PKEY_id(pkey2)) { - return false; - } - - // For RSA keys, check key size - if (EVP_PKEY_id(pkey1) == EVP_PKEY_RSA) { - RSA *rsa1 = EVP_PKEY_get0_RSA(pkey1); - RSA *rsa2 = EVP_PKEY_get0_RSA(pkey2); - if (!rsa1 || !rsa2) { - return false; - } - - if (RSA_size(rsa1) != RSA_size(rsa2)) { - return false; - } - } - - // 7. Verify signatures (self-signed) - if (X509_verify(cert1, pkey1) != 1) { - return false; - } - if (X509_verify(cert2, pkey2) != 1) { - return false; - } - - // 8. Compare extensions - simplified approach - int ext_count1 = X509_get_ext_count(cert1); - int ext_count2 = X509_get_ext_count(cert2); - if (ext_count1 != ext_count2) { - return false; - } - - // Compare each extension by index (assuming same order) - for (int i = 0; i < ext_count1; i++) { - X509_EXTENSION *ext1 = X509_get_ext(cert1, i); - X509_EXTENSION *ext2 = X509_get_ext(cert2, i); - if (!ext1 || !ext2) { - return false; - } - - // Compare extension OIDs - ASN1_OBJECT *obj1 = X509_EXTENSION_get_object(ext1); - ASN1_OBJECT *obj2 = X509_EXTENSION_get_object(ext2); - if (!obj1 || !obj2) { - return false; - } - - if (OBJ_cmp(obj1, obj2) != 0) { - return false; - } - - // Compare critical flags - if (X509_EXTENSION_get_critical(ext1) != - X509_EXTENSION_get_critical(ext2)) { - return false; - } - } - - return true; - } - char cert_path_openssl[PATH_MAX]; char cert_path_awslc[PATH_MAX]; char csr_path_openssl[PATH_MAX]; @@ -361,8 +154,8 @@ TEST_F(ReqComparisonTest, GenerateSelfSignedCertificate) { ExecuteCommand(openssl_command); // Load certificates - auto cert_tool = LoadCertificate(cert_path_awslc); - auto cert_openssl = LoadCertificate(cert_path_openssl); + auto cert_tool = LoadPEMCertificate(cert_path_awslc); + auto cert_openssl = LoadPEMCertificate(cert_path_openssl); ASSERT_TRUE(cert_tool != nullptr) << "Failed to load certificate generated by tool"; @@ -370,7 +163,8 @@ TEST_F(ReqComparisonTest, GenerateSelfSignedCertificate) { << "Failed to load certificate generated by OpenSSL"; // Compare certificates in detail with 365 days validity period - ASSERT_TRUE(CompareCertificates(cert_tool.get(), cert_openssl.get(), 365)) + ASSERT_TRUE(CompareCertificates(cert_tool.get(), cert_openssl.get(), + cert_tool.get(), 365)) << "Certificates generated by tool and OpenSSL have different attributes"; } diff --git a/tool-openssl/test_util.cc b/tool-openssl/test_util.cc new file mode 100644 index 0000000000..da270d6679 --- /dev/null +++ b/tool-openssl/test_util.cc @@ -0,0 +1,308 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 OR ISC + +#include "test_util.h" +#include + +void CreateAndSignX509Certificate(bssl::UniquePtr &x509, + bssl::UniquePtr *pkey_p) { + x509.reset(X509_new()); + if (!x509) { + fprintf(stderr, "Error creating new X509 certificate\n"); + return; + } + + // Set version to X509v3 + X509_set_version(x509.get(), X509_VERSION_3); + + // Set validity period for 30 days + if (!X509_gmtime_adj(X509_getm_notBefore(x509.get()), 0) || + !X509_gmtime_adj(X509_getm_notAfter(x509.get()), 60 * 60 * 24 * 30L)) { + fprintf(stderr, "Error setting expiration date\n"); + return; + } + + EVP_PKEY *pkey = EVP_PKEY_new(); + + if (!pkey) { + fprintf(stderr, "Error creating new private key\n"); + return; + } + + bssl::UniquePtr rsa(RSA_new()); + bssl::UniquePtr bn(BN_new()); + if (!bn || !BN_set_word(bn.get(), RSA_F4) || + !RSA_generate_key_ex(rsa.get(), 2048, bn.get(), nullptr) || + !EVP_PKEY_assign_RSA(pkey, rsa.release())) { + fprintf(stderr, "Error generating new key\n"); + return; + } + if (!X509_set_pubkey(x509.get(), pkey)) { + fprintf(stderr, "Error setting public key\n"); + return; + } + + X509_NAME *subject_name = X509_NAME_new(); + if (!X509_NAME_add_entry_by_NID( + subject_name, NID_organizationName, MBSTRING_UTF8, + reinterpret_cast("Org"), /*len=*/-1, + /*loc=*/-1, + /*set=*/0) || + !X509_NAME_add_entry_by_NID( + subject_name, NID_commonName, MBSTRING_UTF8, + reinterpret_cast("Name"), /*len=*/-1, + /*loc=*/-1, + /*set=*/0)) { + fprintf(stderr, "Error creating a subject\n"); + return; + } + + // self-signed + if (!X509_set_subject_name(x509.get(), subject_name) || + !X509_set_issuer_name(x509.get(), subject_name)) { + fprintf(stderr, "Error setting certificate subject and issuer\n"); + return; + }; + X509_NAME_free(subject_name); + + // Add X509v3 extensions + X509V3_CTX ctx; + X509V3_set_ctx_nodb(&ctx); + X509V3_set_ctx(&ctx, x509.get(), x509.get(), nullptr, nullptr, 0); + + X509_EXTENSION *ext; + if (!(ext = X509V3_EXT_conf_nid(nullptr, &ctx, NID_basic_constraints, + const_cast("critical,CA:TRUE"))) || + !X509_add_ext(x509.get(), ext, -1)) { + fprintf(stderr, "Error setting extension\n"); + return; + } + + if (!(ext = X509V3_EXT_conf_nid(nullptr, &ctx, NID_subject_key_identifier, + const_cast("hash"))) || + !X509_add_ext(x509.get(), ext, -1)) { + fprintf(stderr, "Error setting subject key identifier extension\n"); + return; + } + X509_EXTENSION_free(ext); + + if (X509_sign(x509.get(), pkey, EVP_sha256()) <= 0) { + fprintf(stderr, "Error signing certificate\n"); + return; + } + + if (pkey_p != nullptr) { + pkey_p->reset(pkey); + } else { + EVP_PKEY_free(pkey); + } +} + +// Load a CSR from a PEM file +bssl::UniquePtr LoadCSR(const char *path) { + bssl::UniquePtr bio(BIO_new_file(path, "r")); + if (!bio) { + return NULL; + } + + bssl::UniquePtr csr( + PEM_read_bio_X509_REQ(bio.get(), nullptr, nullptr, nullptr)); + return csr; +} + +// Load an X509 certificate from a PEM file +bssl::UniquePtr LoadPEMCertificate(const char *path) { + bssl::UniquePtr bio(BIO_new_file(path, "r")); + if (!bio) { + return NULL; + } + + bssl::UniquePtr cert( + PEM_read_bio_X509(bio.get(), nullptr, nullptr, nullptr)); + return cert; +} + +bool CompareCSRs(X509_REQ *csr1, X509_REQ *csr2) { + if (!csr1 || !csr2) + return false; + + // 1. Compare subjects + X509_NAME *name1 = X509_REQ_get_subject_name(csr1); + X509_NAME *name2 = X509_REQ_get_subject_name(csr2); + if (X509_NAME_cmp(name1, name2) != 0) + return false; + + // 2. Compare signature algorithms + int sig_nid1 = X509_REQ_get_signature_nid(csr1); + int sig_nid2 = X509_REQ_get_signature_nid(csr2); + if (sig_nid1 != sig_nid2) + return false; + + // 3. Compare public key type and parameters + EVP_PKEY *pkey1 = X509_REQ_get0_pubkey(csr1); + EVP_PKEY *pkey2 = X509_REQ_get0_pubkey(csr2); + if (!pkey1 || !pkey2) { + return false; + } + if (EVP_PKEY_id(pkey1) != EVP_PKEY_id(pkey2)) { + return false; + } + + // For RSA keys, check key size + if (EVP_PKEY_id(pkey1) == EVP_PKEY_RSA) { + RSA *rsa1 = EVP_PKEY_get0_RSA(pkey1); + RSA *rsa2 = EVP_PKEY_get0_RSA(pkey2); + if (!rsa1 || !rsa2) { + return false; + } + if (RSA_size(rsa1) != RSA_size(rsa2)) { + return false; + } + } + + // 4. Verify that both CSRs have valid signatures + if (X509_REQ_verify(csr1, pkey1) != 1) { + return false; + } + if (X509_REQ_verify(csr2, pkey2) != 1) { + return false; + } + + return true; +} + +bool CheckCertificateValidityPeriod(X509 *cert, int expected_days) { + if (!cert) + return false; + + const ASN1_TIME *not_before = X509_get0_notBefore(cert); + const ASN1_TIME *not_after = X509_get0_notAfter(cert); + if (!not_before || !not_after) + return false; + + // Get the difference in days between not_before and not_after + int days, seconds; + if (!ASN1_TIME_diff(&days, &seconds, not_before, not_after)) { + return false; + } + + return (days == expected_days); +} + +// Improved certificate comparison function +bool CompareCertificates(X509 *cert1, X509 *cert2, X509 *ca_cert, + int expected_days) { + if (!cert1 || !cert2 || !ca_cert) { + return false; + } + + // 1. Compare subjects + X509_NAME *subj1 = X509_get_subject_name(cert1); + X509_NAME *subj2 = X509_get_subject_name(cert2); + if (X509_NAME_cmp(subj1, subj2) != 0) { + return false; + } + + // 2. Compare issuers + X509_NAME *issuer1 = X509_get_issuer_name(cert1); + X509_NAME *issuer2 = X509_get_issuer_name(cert2); + if (X509_NAME_cmp(issuer1, issuer2) != 0) { + return false; + } + + // 3. Both certificates should be self-signed + X509_NAME *ca_name = X509_get_issuer_name(ca_cert); + if (X509_NAME_cmp(issuer1, ca_name) != 0) { + return false; + } + if (X509_NAME_cmp(issuer2, ca_name) != 0) { + return false; + } + + // 4. Compare signature algorithms + int sig_nid1 = X509_get_signature_nid(cert1); + int sig_nid2 = X509_get_signature_nid(cert2); + if (sig_nid1 != sig_nid2) { + return false; + } + + // 5. Check validity periods + if (!CheckCertificateValidityPeriod(cert1, expected_days)) { + return false; + } + if (!CheckCertificateValidityPeriod(cert2, expected_days)) { + return false; + } + + // 6. Check public key type and parameters + EVP_PKEY *pkey1 = X509_get0_pubkey(cert1); + EVP_PKEY *pkey2 = X509_get0_pubkey(cert2); + if (!pkey1 || !pkey2) { + return false; + } + + if (EVP_PKEY_id(pkey1) != EVP_PKEY_id(pkey2)) { + return false; + } + + // For RSA keys, check key size + if (EVP_PKEY_id(pkey1) == EVP_PKEY_RSA) { + RSA *rsa1 = EVP_PKEY_get0_RSA(pkey1); + RSA *rsa2 = EVP_PKEY_get0_RSA(pkey2); + if (!rsa1 || !rsa2) { + return false; + } + + if (RSA_size(rsa1) != RSA_size(rsa2)) { + return false; + } + } + + // 7. Verify signatures + EVP_PKEY *ca_pkey = X509_get0_pubkey(ca_cert); + if (X509_verify(cert1, ca_pkey) != 1) { + return false; + } + + if (X509_verify(cert2, ca_pkey) != 1) { + return false; + } + // if (X509_verify(cert1, pkey1) != X509_verify(cert2, pkey2)) { + // return false; + // } + + // 8. Compare extensions - simplified approach + int ext_count1 = X509_get_ext_count(cert1); + int ext_count2 = X509_get_ext_count(cert2); + if (ext_count1 != ext_count2) { + return false; + } + + // Compare each extension by index (assuming same order) + for (int i = 0; i < ext_count1; i++) { + X509_EXTENSION *ext1 = X509_get_ext(cert1, i); + X509_EXTENSION *ext2 = X509_get_ext(cert2, i); + if (!ext1 || !ext2) { + return false; + } + + // Compare extension OIDs + ASN1_OBJECT *obj1 = X509_EXTENSION_get_object(ext1); + ASN1_OBJECT *obj2 = X509_EXTENSION_get_object(ext2); + if (!obj1 || !obj2) { + return false; + } + + if (OBJ_cmp(obj1, obj2) != 0) { + return false; + } + + // Compare critical flags + if (X509_EXTENSION_get_critical(ext1) != + X509_EXTENSION_get_critical(ext2)) { + return false; + } + } + + return true; +} \ No newline at end of file diff --git a/tool-openssl/test_util.h b/tool-openssl/test_util.h index a96f78f4ec..2a58e33991 100644 --- a/tool-openssl/test_util.h +++ b/tool-openssl/test_util.h @@ -22,10 +22,13 @@ static inline std::string &trim(std::string &s) { return !std::isspace(static_cast(ch)); })); s.erase(std::find_if(s.rbegin(), s.rend(), + [](unsigned char ch) { return !std::isspace(static_cast(ch)); }) + .base(), + s.end()); return s; } @@ -36,6 +39,7 @@ inline std::string ReadFileToString(const std::string &file_path) { return ""; } + // Check if file exists first struct stat stat_buffer; if (stat(file_path.c_str(), &stat_buffer) != 0) { @@ -47,9 +51,11 @@ inline std::string ReadFileToString(const std::string &file_path) { return ""; } + std::ostringstream output_buffer; output_buffer << file_stream.rdbuf(); + return output_buffer.str(); } @@ -66,17 +72,26 @@ inline void RunCommandsAndCompareOutput(const std::string &tool_command, ASSERT_EQ(openssl_result, 0) << "OpenSSL command failed: " << openssl_command; std::ifstream tool_output(out_path_tool); + tool_output_str = std::string((std::istreambuf_iterator(tool_output)), + std::istreambuf_iterator()); tool_output_str = std::string((std::istreambuf_iterator(tool_output)), std::istreambuf_iterator()); std::ifstream openssl_output(out_path_openssl); openssl_output_str = std::string((std::istreambuf_iterator(openssl_output)), std::istreambuf_iterator()); + openssl_output_str = + std::string((std::istreambuf_iterator(openssl_output)), + std::istreambuf_iterator()); std::cout << "AWS-LC tool output:" << std::endl << tool_output_str << std::endl; std::cout << "OpenSSL output:" << std::endl << openssl_output_str << std::endl; + std::cout << "AWS-LC tool output:" << std::endl + << tool_output_str << std::endl; + std::cout << "OpenSSL output:" << std::endl + << openssl_output_str << std::endl; } inline void RemoveFile(const char *path) { @@ -88,7 +103,21 @@ inline void RemoveFile(const char *path) { } } -// OpenSSL versions 3.1.0 and later change from "(stdin)= " to "MD5(stdin) =" +inline int ExecuteCommand(const std::string &command) { + return system(command.c_str()); +} + +// OpenSSL versions 3.1.0 and later change from "(stdin)= " to "MD5(stdin) +// =" std::string GetHash(const std::string &str); +void CreateAndSignX509Certificate(bssl::UniquePtr &x509, + bssl::UniquePtr *pkey); +bssl::UniquePtr LoadCSR(const char *path); +bssl::UniquePtr LoadPEMCertificate(const char *path); +bssl::UniquePtr LoadDERCertificate(const char *path); +bool CompareCSRs(X509_REQ *csr1, X509_REQ *csr2); +bool CheckCertificateValidityPeriod(X509 *cert, int expected_days); +bool CompareCertificates(X509 *cert1, X509 *cert2, X509 *ca_cert, + int expected_days); #endif // TEST_UTIL_H diff --git a/tool-openssl/verify_test.cc b/tool-openssl/verify_test.cc index 3c905aefdf..cde5c6342f 100644 --- a/tool-openssl/verify_test.cc +++ b/tool-openssl/verify_test.cc @@ -1,48 +1,50 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 OR ISC -#include "openssl/x509.h" #include #include +#include "../crypto/test/test_util.h" #include "internal.h" +#include "openssl/x509.h" #include "test_util.h" -#include "../crypto/test/test_util.h" class VerifyTest : public ::testing::Test { -protected: - void SetUp() override { - ASSERT_GT(createTempFILEpath(ca_path), 0u); - ASSERT_GT(createTempFILEpath(chain_path), 0u); - ASSERT_GT(createTempFILEpath(in_path), 0u); - - bssl::UniquePtr x509(CreateAndSignX509Certificate()); - ASSERT_TRUE(x509); - - ScopedFILE in_file(fopen(in_path, "wb")); - ASSERT_TRUE(in_file); - ASSERT_TRUE(PEM_write_X509(in_file.get(), x509.get())); - - ScopedFILE ca_file(fopen(ca_path, "wb")); - ASSERT_TRUE(ca_file); - ASSERT_TRUE(PEM_write_X509(ca_file.get(), x509.get())); - - ScopedFILE chain_file(fopen(chain_path, "wb")); - ASSERT_TRUE(chain_file); - ASSERT_TRUE(PEM_write_X509(chain_file.get(), x509.get())); - } - void TearDown() override { - RemoveFile(ca_path); - RemoveFile(chain_path); - RemoveFile(in_path); - } - char ca_path[PATH_MAX]; - char chain_path[PATH_MAX]; - char in_path[PATH_MAX]; + protected: + void SetUp() override { + ASSERT_GT(createTempFILEpath(ca_path), 0u); + ASSERT_GT(createTempFILEpath(chain_path), 0u); + ASSERT_GT(createTempFILEpath(in_path), 0u); + + bssl::UniquePtr x509; + CreateAndSignX509Certificate(x509, nullptr); + ASSERT_TRUE(x509); + + ScopedFILE in_file(fopen(in_path, "wb")); + ASSERT_TRUE(in_file); + ASSERT_TRUE(PEM_write_X509(in_file.get(), x509.get())); + + ScopedFILE ca_file(fopen(ca_path, "wb")); + ASSERT_TRUE(ca_file); + ASSERT_TRUE(PEM_write_X509(ca_file.get(), x509.get())); + + ScopedFILE chain_file(fopen(chain_path, "wb")); + ASSERT_TRUE(chain_file); + ASSERT_TRUE(PEM_write_X509(chain_file.get(), x509.get())); + } + void TearDown() override { + RemoveFile(ca_path); + RemoveFile(chain_path); + RemoveFile(in_path); + } + char ca_path[PATH_MAX]; + char chain_path[PATH_MAX]; + char in_path[PATH_MAX]; }; -// ----------------------------- Verify Option Tests ----------------------------- +// ----------------------------- Verify Option Tests +// ----------------------------- // Test -CAfile with self-signed certificate TEST_F(VerifyTest, VerifyTestSelfSignedCertWithCAfileTest) { @@ -51,7 +53,7 @@ TEST_F(VerifyTest, VerifyTestSelfSignedCertWithCAfileTest) { ASSERT_TRUE(result); } -// Test certificate without -CAfile +// Test certificate without -CAfile TEST_F(VerifyTest, VerifyTestSelfSignedCertWithoutCAfile) { args_list_t args = {in_path}; bool result = VerifyTool(args); @@ -72,66 +74,73 @@ TEST_F(VerifyTest, VerifyTestSelfSignedCertWithCAFileAndUntrustedChain) { ASSERT_TRUE(result); } -// -------------------- Verify OpenSSL Comparison Tests -------------------------- +// -------------------- Verify OpenSSL Comparison Tests +// -------------------------- // Comparison tests cannot run without set up of environment variables: // AWSLC_TOOL_PATH and OPENSSL_TOOL_PATH. class VerifyComparisonTest : public ::testing::Test { -protected: - void SetUp() override { - - // Skip gtests if env variables not set - tool_executable_path = getenv("AWSLC_TOOL_PATH"); - openssl_executable_path = getenv("OPENSSL_TOOL_PATH"); - if (tool_executable_path == nullptr || openssl_executable_path == nullptr) { - GTEST_SKIP() << "Skipping test: AWSLC_TOOL_PATH and/or OPENSSL_TOOL_PATH environment variables are not set"; - } - - ASSERT_GT(createTempFILEpath(in_path), 0u); - ASSERT_GT(createTempFILEpath(ca_path), 0u); - ASSERT_GT(createTempFILEpath(out_path_tool), 0u); - ASSERT_GT(createTempFILEpath(out_path_openssl), 0u); - - x509.reset(CreateAndSignX509Certificate()); - ASSERT_TRUE(x509); - - ScopedFILE in_file(fopen(in_path, "wb")); - ASSERT_TRUE(in_file); - ASSERT_TRUE(PEM_write_X509(in_file.get(), x509.get())); - - ScopedFILE ca_file(fopen(ca_path, "wb")); - ASSERT_TRUE(ca_file); - ASSERT_TRUE(PEM_write_X509(ca_file.get(), x509.get())); + protected: + void SetUp() override { + // Skip gtests if env variables not set + tool_executable_path = getenv("AWSLC_TOOL_PATH"); + openssl_executable_path = getenv("OPENSSL_TOOL_PATH"); + if (tool_executable_path == nullptr || openssl_executable_path == nullptr) { + GTEST_SKIP() << "Skipping test: AWSLC_TOOL_PATH and/or OPENSSL_TOOL_PATH " + "environment variables are not set"; } - void TearDown() override { - if (tool_executable_path != nullptr && openssl_executable_path != nullptr) { - RemoveFile(in_path); - RemoveFile(out_path_tool); - RemoveFile(out_path_openssl); - RemoveFile(ca_path); - } - } + ASSERT_GT(createTempFILEpath(in_path), 0u); + ASSERT_GT(createTempFILEpath(ca_path), 0u); + ASSERT_GT(createTempFILEpath(out_path_tool), 0u); + ASSERT_GT(createTempFILEpath(out_path_openssl), 0u); - char in_path[PATH_MAX]; - char ca_path[PATH_MAX]; - char out_path_tool[PATH_MAX]; - char out_path_openssl[PATH_MAX]; - bssl::UniquePtr x509; - const char* tool_executable_path; - const char* openssl_executable_path; - std::string tool_output_str; - std::string openssl_output_str; + CreateAndSignX509Certificate(x509, nullptr); + ASSERT_TRUE(x509); + + ScopedFILE in_file(fopen(in_path, "wb")); + ASSERT_TRUE(in_file); + ASSERT_TRUE(PEM_write_X509(in_file.get(), x509.get())); + + ScopedFILE ca_file(fopen(ca_path, "wb")); + ASSERT_TRUE(ca_file); + ASSERT_TRUE(PEM_write_X509(ca_file.get(), x509.get())); + } + + void TearDown() override { + if (tool_executable_path != nullptr && openssl_executable_path != nullptr) { + RemoveFile(in_path); + RemoveFile(out_path_tool); + RemoveFile(out_path_openssl); + RemoveFile(ca_path); + } + } + + char in_path[PATH_MAX]; + char ca_path[PATH_MAX]; + char out_path_tool[PATH_MAX]; + char out_path_openssl[PATH_MAX]; + bssl::UniquePtr x509; + const char *tool_executable_path; + const char *openssl_executable_path; + std::string tool_output_str; + std::string openssl_output_str; }; // Test against OpenSSL with -CAfile & self-signed cert fed in as a file // "openssl verify -CAfile cert.pem cert.pem" TEST_F(VerifyComparisonTest, VerifyToolOpenSSLCAFileSelfSignedComparison) { - std::string tool_command = std::string(tool_executable_path) + " verify -CAfile " + ca_path + " " + in_path + " &> " + out_path_tool; - std::string openssl_command = std::string(openssl_executable_path) + " verify -CAfile " + ca_path + " " + in_path + " &> " + out_path_openssl; + std::string tool_command = std::string(tool_executable_path) + + " verify -CAfile " + ca_path + " " + in_path + + " &> " + out_path_tool; + std::string openssl_command = std::string(openssl_executable_path) + + " verify -CAfile " + ca_path + " " + in_path + + " &> " + out_path_openssl; - RunCommandsAndCompareOutput(tool_command, openssl_command, out_path_tool, out_path_openssl, tool_output_str, openssl_output_str); + RunCommandsAndCompareOutput(tool_command, openssl_command, out_path_tool, + out_path_openssl, tool_output_str, + openssl_output_str); ASSERT_EQ(tool_output_str, openssl_output_str); } @@ -139,10 +148,16 @@ TEST_F(VerifyComparisonTest, VerifyToolOpenSSLCAFileSelfSignedComparison) { // Test against OpenSSL with -CAfile & 2 self-signed cert fed in as files // "openssl verify -CAfile cert.pem cert.pem cert.pem" TEST_F(VerifyComparisonTest, VerifyToolOpenSSLCAFileMultipleFilesComparison) { - std::string tool_command = std::string(tool_executable_path) + " verify -CAfile " + ca_path + " " + in_path + " " + in_path + " &> " + out_path_tool; - std::string openssl_command = std::string(openssl_executable_path) + " verify -CAfile " + ca_path + " " + in_path + " " + in_path + " &> " + out_path_openssl; + std::string tool_command = std::string(tool_executable_path) + + " verify -CAfile " + ca_path + " " + in_path + + " " + in_path + " &> " + out_path_tool; + std::string openssl_command = std::string(openssl_executable_path) + + " verify -CAfile " + ca_path + " " + in_path + + " " + in_path + " &> " + out_path_openssl; - RunCommandsAndCompareOutput(tool_command, openssl_command, out_path_tool, out_path_openssl, tool_output_str, openssl_output_str); + RunCommandsAndCompareOutput(tool_command, openssl_command, out_path_tool, + out_path_openssl, tool_output_str, + openssl_output_str); ASSERT_EQ(tool_output_str, openssl_output_str); } @@ -150,10 +165,18 @@ TEST_F(VerifyComparisonTest, VerifyToolOpenSSLCAFileMultipleFilesComparison) { // Test against OpenSSL with -CAfile & self-signed cert fed through stdin // "cat cert.pem | openssl verify -CAfile cert.pem" TEST_F(VerifyComparisonTest, VerifyToolOpenSSLCAFileSelfSignedStdinComparison) { - std::string tool_command = "cat " + std::string(ca_path) + " | " + std::string(tool_executable_path) + " verify -CAfile " + ca_path + " &> " + out_path_tool; - std::string openssl_command = "cat " + std::string(ca_path) + " | " + std::string(openssl_executable_path) + " verify -CAfile " + ca_path + " &> " + out_path_openssl; - - RunCommandsAndCompareOutput(tool_command, openssl_command, out_path_tool, out_path_openssl, tool_output_str, openssl_output_str); + std::string tool_command = "cat " + std::string(ca_path) + " | " + + std::string(tool_executable_path) + + " verify -CAfile " + ca_path + " &> " + + out_path_tool; + std::string openssl_command = "cat " + std::string(ca_path) + " | " + + std::string(openssl_executable_path) + + " verify -CAfile " + ca_path + " &> " + + out_path_openssl; + + RunCommandsAndCompareOutput(tool_command, openssl_command, out_path_tool, + out_path_openssl, tool_output_str, + openssl_output_str); ASSERT_EQ(tool_output_str, openssl_output_str); } diff --git a/tool-openssl/x509.cc b/tool-openssl/x509.cc index 8d7fb7927a..0517b3aa93 100644 --- a/tool-openssl/x509.cc +++ b/tool-openssl/x509.cc @@ -11,69 +11,278 @@ #include "internal.h" static const argument_t kArguments[] = { - { "-help", kBooleanArgument, "Display option summary" }, - { "-in", kOptionalArgument, "Certificate input, or CSR input file with -req" }, - { "-req", kBooleanArgument, "Input is a CSR file (rather than a certificate)" }, - { "-signkey", kOptionalArgument, "Causes input file to be self signed using supplied private key" }, - { "-out", kOptionalArgument, "Filepath to write all output to, if not set write to stdout" }, - { "-noout", kBooleanArgument, "Prevents output of the encoded version of the certificate" }, - { "-dates", kBooleanArgument, "Print the start and expiry dates of a certificate" }, - { "-modulus", kBooleanArgument, "Prints out value of the modulus of the public key contained in the certificate" }, - { "-subject", kBooleanArgument, "Prints the subject name"}, - { "-subject_hash", kBooleanArgument, "Prints subject hash value"}, - { "-subject_hash_old", kBooleanArgument, "Prints old OpenSSL style (MD5) subject hash value"}, - { "-fingerprint", kBooleanArgument, "Prints the certificate fingerprint"}, - { "-checkend", kOptionalArgument, "Check whether cert expires in the next arg seconds" }, - { "-days", kOptionalArgument, "Number of days until newly generated certificate expires - default 30" }, - { "-text", kBooleanArgument, "Pretty print the contents of the certificate"}, - { "-inform", kOptionalArgument, "This specifies the input format normally the command will expect an X509 " - "certificate but this can change if other options such as -req are present. " - "The DER format is the DER encoding of the certificate and PEM is the base64 " - "encoding of the DER encoding with header and footer lines added. The default " - "format is PEM."}, - { "-enddate", kBooleanArgument, "Prints out the expiry date of the certificate, that is the notAfter date."}, - { "", kOptionalArgument, "" } -}; - -static bool WriteSignedCertificate(X509 *x509, bssl::UniquePtr &output_bio, const std::string &out_path) { - if (!PEM_write_bio_X509(output_bio.get(), x509)) { - fprintf(stderr, "Error: error writing certificate to '%s'\n", out_path.c_str()); - ERR_print_errors_fp(stderr); - return false; + // General options + {"-help", kBooleanArgument, "Display option summary"}, + {"-in", kOptionalArgument, + "Certificate input, or CSR input file with -req"}, + {"-req", kBooleanArgument, + "Input is a CSR file (rather than a certificate)"}, + {"-signkey", kOptionalArgument, + "Causes input file to be self signed using supplied private key"}, + {"-inform", kOptionalArgument, + "This specifies the input format normally the command will expect an X509 " + "certificate but this can change if other options such as -req are " + "present. " + "The DER format is the DER encoding of the certificate and PEM is the " + "base64 " + "encoding of the DER encoding with header and footer lines added. The " + "default " + "format is PEM."}, + {"-out", kOptionalArgument, + "Filepath to write all output to, if not set write to stdout"}, + {"-outform", kOptionalArgument, "Output format (DER or PEM) - default PEM"}, + // Micro-CA + {"-CA", kOptionalArgument, + "Use the given CA certificate to sign certificates. Cannot be used with " + "-signkey"}, + {"-CAkey", kOptionalArgument, + "The corresponding CA key. If not specified, the private key is assumed " + "to be in the CA file"}, + // Certificate printing + {"-noout", kBooleanArgument, + "Prevents output of the encoded version of the certificate"}, + {"-dates", kBooleanArgument, + "Print the start and expiry dates of a certificate"}, + {"-modulus", kBooleanArgument, + "Prints out value of the modulus of the public key contained in the " + "certificate"}, + {"-subject", kBooleanArgument, "Prints the subject name"}, + {"-subject_hash", kBooleanArgument, "Prints subject hash value"}, + {"-subject_hash_old", kBooleanArgument, + "Prints old OpenSSL style (MD5) subject hash value"}, + {"-fingerprint", kBooleanArgument, "Prints the certificate fingerprint"}, + {"-pubkey", kBooleanArgument, "Print the public key in PEM format"}, + {"-text", kBooleanArgument, "Pretty print the contents of the certificate"}, + {"-enddate", kBooleanArgument, + "Prints out the expiry date of the certificate, that is the notAfter " + "date."}, + // Certificate checking + {"-checkend", kOptionalArgument, + "Check whether cert expires in the next arg seconds"}, + // Certificate output + {"-days", kOptionalArgument, + "Number of days until newly generated certificate expires - default 30"}, + {"-extfile", kOptionalArgument, + "Config file with X509V3 extensions to add"}, + {"-extensions", kOptionalArgument, + "Section of extfile to use - default: unnamed section"}, + {"", kOptionalArgument, ""}}; + +static bool WriteSignedCertificate(X509 *x509, bssl::UniquePtr &output_bio, + const std::string &out_path, + const std::string &outform) { + if (!outform.empty() && isStringUpperCaseEqual(outform, "DER")) { + if (!i2d_X509_bio(output_bio.get(), x509)) { + fprintf(stderr, "Error: error writing certificate to '%s'\n", + out_path.c_str()); + ERR_print_errors_fp(stderr); + return false; + } + } else { + if (!PEM_write_bio_X509(output_bio.get(), x509)) { + fprintf(stderr, "Error: error writing certificate to '%s'\n", + out_path.c_str()); + ERR_print_errors_fp(stderr); + return false; + } } + return true; } static bool isCharUpperCaseEqual(char a, char b) { - return ::toupper(a) == ::toupper(b); + return ::toupper(a) == ::toupper(b); } bool isStringUpperCaseEqual(const std::string &a, const std::string &b) { - return a.size() == b.size() && std::equal(a.begin(), a.end(), b.begin(), isCharUpperCaseEqual); + return a.size() == b.size() && + std::equal(a.begin(), a.end(), b.begin(), isCharUpperCaseEqual); } -bool LoadPrivateKeyAndSignCertificate(X509 *x509, const std::string &signkey_path) { +static int AdaptExtension(X509 *cert, X509V3_CTX *ext_ctx, const char *name, + const char *value, int add_default) { + bssl::UniquePtr new_ext( + X509V3_EXT_nconf(NULL, ext_ctx, name, value)); + + if (new_ext == NULL) { + return 0; + } + + const ASN1_OBJECT *ext_obj = X509_EXTENSION_get_object(new_ext.get()); + if (ext_obj == NULL) { + return 0; + } + + int idx = X509_get_ext_by_OBJ(cert, ext_obj, -1); + if (idx >= 0) { + X509_EXTENSION *found_ext = X509_get_ext(cert, idx); + if (ASN1_STRING_length(X509_EXTENSION_get_data(found_ext)) <= 2) { + X509_delete_ext(cert, idx); + } + return 1; + } + + return !add_default || X509_add_ext(cert, new_ext.get(), -1); +} + +static bool LoadExtensionsAndSignCertificate(const X509 *issuer, X509 *subject, + EVP_PKEY *pkey, + const std::string pkey_path, + const std::string &ext_file_path, + std::string &ext_section) { + X509V3_CTX ext_ctx; + bssl::UniquePtr ext_conf(nullptr); + + X509V3_set_ctx(&ext_ctx, issuer, subject, NULL, NULL, 0); + + if (ext_file_path.empty()) { + if (!ext_section.empty()) { + fprintf(stderr, + "Warning: ignoring -extensions option without -extfile\n"); + } + } else { + X509V3_CTX temp_ctx; + bssl::UniquePtr ext_bio(BIO_new_file(ext_file_path.c_str(), "r")); + if (!ext_bio) { + fprintf(stderr, "Error: unable to load extension file from '%s'\n", + ext_file_path.c_str()); + return false; + } + + ext_conf.reset(NCONF_new(NULL)); + if (!ext_conf) { + fprintf(stderr, "Failed to create extension config\n"); + return false; + } + + if (NCONF_load_bio(ext_conf.get(), ext_bio.get(), NULL) <= 0) { + fprintf(stderr, "Error: Failed to load config from BIO\n"); + return false; + } + + if (ext_section.empty()) { + const char *res = + NCONF_get_string(ext_conf.get(), "default", "extensions"); + ext_section = res ? res : "default"; + } + + // validate extension config + X509V3_set_ctx_test(&temp_ctx); + X509V3_set_nconf(&temp_ctx, ext_conf.get()); + if (!X509V3_EXT_add_nconf(ext_conf.get(), &temp_ctx, ext_section.c_str(), + NULL)) { + fprintf(stderr, "Error: Failed to check extension section\n"); + return false; + } + + // initialize actual extension context + X509V3_set_nconf(&ext_ctx, ext_conf.get()); + + if (!X509V3_EXT_add_nconf(ext_conf.get(), &ext_ctx, ext_section.c_str(), + subject)) { + fprintf(stderr, "Error: Failed to add extensions from section %s\n", + ext_section.c_str()); + return false; + } + } + + if (!X509_set_version(subject, X509_VERSION_3)) { + fprintf(stderr, "Error: unable to set certificate version\n"); + return false; + } + + /* Prevent X509_V_ERR_MISSING_SUBJECT_KEY_IDENTIFIER */ + if (!AdaptExtension(subject, &ext_ctx, "subjectKeyIdentifier", "hash", 1)) { + fprintf(stderr, "Error: Failed to handle subject key identifier\n"); + return false; + } + /* Prevent X509_V_ERR_MISSING_AUTHORITY_KEY_IDENTIFIER */ + int self_sign = X509_check_private_key(subject, pkey); + if (!AdaptExtension(subject, &ext_ctx, "authorityKeyIdentifier", + "keyid, issuer", !self_sign)) { + fprintf(stderr, "Error: Failed to handle authority key identifier\n"); + return false; + } + + // TODO: make customizable with -digest option + if (!X509_sign(subject, pkey, EVP_sha256())) { + fprintf(stderr, "Error: error signing certificate with key from '%s'\n", + pkey_path.c_str()); + ERR_print_errors_fp(stderr); + return false; + } + + return true; +} + +static bool LoadPrivateKey(bssl::UniquePtr &pkey, + const std::string &signkey_path) { ScopedFILE signkey_file(fopen(signkey_path.c_str(), "rb")); if (!signkey_file) { - fprintf(stderr, "Error: unable to load private key from '%s'\n", signkey_path.c_str()); + fprintf(stderr, "Error: unable to load private key from '%s'\n", + signkey_path.c_str()); return false; } - bssl::UniquePtr pkey(PEM_read_PrivateKey(signkey_file.get(), nullptr, nullptr, nullptr)); + pkey.reset( + PEM_read_PrivateKey(signkey_file.get(), nullptr, nullptr, nullptr)); if (!pkey) { - fprintf(stderr, "Error: error reading private key from '%s'\n", signkey_path.c_str()); + fprintf(stderr, "Error: error reading private key from '%s'\n", + signkey_path.c_str()); ERR_print_errors_fp(stderr); return false; } - // TODO: make customizable with -digest option - if (!X509_sign(x509, pkey.get(), EVP_sha256())) { - fprintf(stderr, "Error: error signing certificate with key from '%s'\n", signkey_path.c_str()); - ERR_print_errors_fp(stderr); + + return true; +} + +static bool LoadCA(bssl::UniquePtr &ca, bssl::UniquePtr &ca_key, + const std::string &ca_file_path, + const std::string &ca_key_path) { + ScopedFILE ca_file(fopen(ca_file_path.c_str(), "rb")); + + if (!ca_file) { + fprintf(stderr, "Error: "); + return false; + } + + ca.reset(PEM_read_X509(ca_file.get(), nullptr, nullptr, + nullptr)); // TODO: add password obtained from -passin + + if (!ca) { + fprintf(stderr, "Error: "); + return false; + } + + if (!ca_key_path.empty()) { + ScopedFILE ca_key_file(fopen(ca_key_path.c_str(), "rb")); + + if (!ca_key_file) { + fprintf(stderr, "Error: "); + return false; + } + + ca_key.reset( + PEM_read_PrivateKey(ca_key_file.get(), nullptr, nullptr, nullptr)); + } else { + ca_key.reset(PEM_read_PrivateKey(ca_file.get(), nullptr, nullptr, nullptr)); + } + + if (!ca_key) { + fprintf(stderr, "Error: "); return false; } + + // Do validation + if (!X509_check_private_key(ca.get(), ca_key.get())) { + fprintf(stderr, + "Error: CA certificate and CA private key does not match\n"); + return false; + } + return true; } -bool IsNumeric(const std::string& str) { +bool IsNumeric(const std::string &str) { return !str.empty() && std::all_of(str.begin(), str.end(), ::isdigit); } @@ -115,6 +324,21 @@ static bool handleModulus(X509 *x509, BIO *output_bio) { return true; } +static bool handlePubkey(X509 *x509, BIO *output_bio) { + bssl::UniquePtr pkey(X509_get_pubkey(x509)); + + if (!pkey) { + fprintf(stderr, "Error: unable to load public key from certificate\n"); + return false; + } + + if (!PEM_write_bio_PUBKEY(output_bio, pkey.get())) { + fprintf(stderr, "Error: failed to write public key in PEM format\n"); + return false; + } + return true; +} + static bool handleSubject(X509 *x509, BIO *output_bio) { X509_NAME *subject_name = X509_get_subject_name(x509); if (!subject_name) { @@ -189,6 +413,9 @@ static bool ProcessArgument(const std::string &arg_name, if (arg_name == "-modulus") { return handleModulus(x509, output_bio.get()); } + if (arg_name == "-pubkey") { + return handlePubkey(x509, output_bio.get()); + } if (arg_name == "-text") { X509_print(output_bio.get(), x509); return true; @@ -230,13 +457,15 @@ bool X509Tool(const args_list_t &args) { // Use the ordered argument list instead of the standard map ordered_args::ordered_args_map_t parsed_args; args_list_t extra_args; - if (!ordered_args::ParseOrderedKeyValueArguments(parsed_args, extra_args, args, kArguments) || + if (!ordered_args::ParseOrderedKeyValueArguments(parsed_args, extra_args, + args, kArguments) || extra_args.size() > 0) { PrintUsage(kArguments); return false; } - std::string in_path, out_path, signkey_path, days_str, inform; + std::string in_path, out_path, signkey_path, days_str, inform, outform, + ca_file_path, ca_key_path, ext_file_path, ext_section; bool noout = false, dates = false, req = false, help = false; std::unique_ptr days; @@ -248,6 +477,11 @@ bool X509Tool(const args_list_t &args) { ordered_args::GetBoolArgument(&noout, "-noout", parsed_args); ordered_args::GetBoolArgument(&dates, "-dates", parsed_args); ordered_args::GetString(&inform, "-inform", "", parsed_args); + ordered_args::GetString(&outform, "-outform", "", parsed_args); + ordered_args::GetString(&ca_file_path, "-CA", "", parsed_args); + ordered_args::GetString(&ca_key_path, "-CAkey", "", parsed_args); + ordered_args::GetString(&ext_file_path, "-extfile", "", parsed_args); + ordered_args::GetString(&ext_section, "-extensions", "", parsed_args); // Display x509 tool option summary if (help) { @@ -265,23 +499,46 @@ bool X509Tool(const args_list_t &args) { } } - // -req must include -signkey - if (req && signkey_path.empty()) { - fprintf(stderr, "Error: '-req' option must be used with '-signkey' option\n"); + // -req must include a private key + if (req && signkey_path.empty() && ca_file_path.empty()) { + fprintf( + stderr, + "Error: '-req' option must be used with '-signkey' or '-CA' option\n"); + return false; + } + + // -CAkey must include -CA + if (!ca_key_path.empty() && ca_file_path.empty()) { + fprintf(stderr, "Error: '-CAkey' option must be used with '-CA' option\n"); return false; } // Check for mutually exclusive options if (req && (dates || ordered_args::HasArgument(parsed_args, "-checkend"))) { - fprintf(stderr, "Error: '-req' option cannot be used with '-dates' and '-checkend' options\n"); + fprintf(stderr, + "Error: '-req' option cannot be used with '-dates' and '-checkend' " + "options\n"); return false; } - if (!signkey_path.empty() && (dates || ordered_args::HasArgument(parsed_args, "-checkend"))) { - fprintf(stderr, "Error: '-signkey' option cannot be used with '-dates' and '-checkend' options\n"); + if (!signkey_path.empty() && + (dates || ordered_args::HasArgument(parsed_args, "-checkend"))) { + fprintf(stderr, + "Error: '-signkey' option cannot be used with '-dates' and " + "'-checkend' options\n"); return false; } - if (ordered_args::HasArgument(parsed_args, "-days") && (dates || ordered_args::HasArgument(parsed_args, "-checkend"))) { - fprintf(stderr, "Error: '-days' option cannot be used with '-dates' and '-checkend' options\n"); + if (!signkey_path.empty() && + (!ca_file_path.empty() || !ca_key_path.empty())) { + fprintf(stderr, + "Error: '-signkey' option cannot be used with '-CA' and " + "'-CAkey'options\n"); + return false; + } + if (ordered_args::HasArgument(parsed_args, "-days") && + (dates || ordered_args::HasArgument(parsed_args, "-checkend"))) { + fprintf(stderr, + "Error: '-days' option cannot be used with '-dates' and " + "'-checkend' options\n"); return false; } @@ -289,16 +546,31 @@ bool X509Tool(const args_list_t &args) { if (ordered_args::HasArgument(parsed_args, "-days")) { ordered_args::GetString(&days_str, "-days", "", parsed_args); if (!IsNumeric(days_str) || std::stoul(days_str) == 0) { - fprintf(stderr, "Error: '-days' option must include a positive integer\n"); + fprintf(stderr, + "Error: '-days' option must include a positive integer\n"); return false; } days.reset(new unsigned(std::stoul(days_str))); } // Check -inform has a valid value - if(!inform.empty()) { - if (!isStringUpperCaseEqual(inform, "DER") && !isStringUpperCaseEqual(inform, "PEM")) { - fprintf(stderr, "Error: '-inform' option must specify a valid encoding DER|PEM\n"); + if (!inform.empty()) { + if (!isStringUpperCaseEqual(inform, "DER") && + !isStringUpperCaseEqual(inform, "PEM")) { + fprintf( + stderr, + "Error: '-inform' option must specify a valid encoding DER|PEM\n"); + return false; + } + } + + // Check -outform has a valid value + if (!outform.empty()) { + if (!isStringUpperCaseEqual(outform, "DER") && + !isStringUpperCaseEqual(outform, "PEM")) { + fprintf( + stderr, + "Error: '-outform' option must specify a valid encoding DER|PEM\n"); return false; } } @@ -310,7 +582,21 @@ bool X509Tool(const args_list_t &args) { } else { in_file.reset(fopen(in_path.c_str(), "rb")); if (!in_file) { - fprintf(stderr, "Error: unable to load certificate from '%s'\n", in_path.c_str()); + fprintf(stderr, "Error: unable to load certificate from '%s'\n", + in_path.c_str()); + return false; + } + } + + // Load CA and CA key + bssl::UniquePtr ca; + bssl::UniquePtr pkey; + if (!ca_file_path.empty()) { + if (!LoadCA(ca, pkey, ca_file_path, ca_key_path)) { + return false; + } + } else if (!signkey_path.empty()) { + if (!LoadPrivateKey(pkey, signkey_path)) { return false; } } @@ -337,20 +623,29 @@ bool X509Tool(const args_list_t &args) { } // Set the subject from CSR - if (!X509_set_subject_name(x509.get(), X509_REQ_get_subject_name(csr.get()))) { + if (!X509_set_subject_name(x509.get(), + X509_REQ_get_subject_name(csr.get()))) { fprintf(stderr, "Error: unable to set subject name from CSR\n"); return false; } // Set the public key from CSR bssl::UniquePtr csr_pkey(X509_REQ_get_pubkey(csr.get())); - if (!csr_pkey || !X509_set_pubkey(x509.get(), csr_pkey.get())) { - fprintf(stderr, "Error: unable to set public key from CSR\n"); + if ((!signkey_path.empty() && !csr_pkey) || + !X509_set_pubkey(x509.get(), + !signkey_path.empty() ? pkey.get() : csr_pkey.get())) { + fprintf(stderr, + "Error: unable to set public key from either CSR or a provided " + "key\n"); return false; } // Set issuer name - if (!X509_set_issuer_name(x509.get(), X509_REQ_get_subject_name(csr.get()))) { + X509_NAME *issuer = ca_file_path.empty() + ? X509_REQ_get_subject_name(csr.get()) + : X509_get_subject_name(ca.get()); + + if (!X509_set_issuer_name(x509.get(), issuer)) { fprintf(stderr, "Error: unable to set issuer name\n"); return false; } @@ -358,19 +653,28 @@ bool X509Tool(const args_list_t &args) { // Set validity period, default 30 days if not specified unsigned valid_days = days ? *days : 30; if (!X509_gmtime_adj(X509_getm_notBefore(x509.get()), 0) || - !X509_gmtime_adj(X509_getm_notAfter(x509.get()), 60 * 60 * 24 * valid_days)) { + !X509_gmtime_adj(X509_getm_notAfter(x509.get()), + 60 * 60 * 24 * valid_days)) { fprintf(stderr, "Error: unable to set validity period\n"); return false; } // Sign the certificate with the provided key - if (!signkey_path.empty()) { - if (!LoadPrivateKeyAndSignCertificate(x509.get(), signkey_path)) { + if (!ca_file_path.empty()) { + if (!LoadExtensionsAndSignCertificate(ca.get(), x509.get(), pkey.get(), + ca_file_path, ext_file_path, + ext_section)) { + return false; + } + } else if (!signkey_path.empty()) { + if (!LoadExtensionsAndSignCertificate(x509.get(), x509.get(), pkey.get(), + signkey_path, ext_file_path, + ext_section)) { return false; } } - if (!WriteSignedCertificate(x509.get(), output_bio, out_path)) { + if (!WriteSignedCertificate(x509.get(), output_bio, out_path, outform)) { return false; } } else { @@ -383,11 +687,39 @@ bool X509Tool(const args_list_t &args) { } if (!x509) { - fprintf(stderr, "Error: error parsing certificate from '%s'\n", in_path.c_str()); + fprintf(stderr, "Error: error parsing certificate from '%s'\n", + in_path.c_str()); ERR_print_errors_fp(stderr); return false; } + if (!signkey_path.empty() && !X509_set_pubkey(x509.get(), pkey.get())) { + fprintf(stderr, "Error: unable to set public key using a provided key\n"); + return false; + } + + if (!ca_file_path.empty()) { + if (!X509_set_issuer_name(x509.get(), X509_get_subject_name(ca.get()))) { + fprintf(stderr, "Error: unable to set issuer name\n"); + return false; + } + } + + // Sign the certificate with the provided key + if (!ca_file_path.empty()) { + if (!LoadExtensionsAndSignCertificate(ca.get(), x509.get(), pkey.get(), + ca_file_path, ext_file_path, + ext_section)) { + return false; + } + } else if (!signkey_path.empty()) { + if (!LoadExtensionsAndSignCertificate(x509.get(), x509.get(), pkey.get(), + signkey_path, ext_file_path, + ext_section)) { + return false; + } + } + // Process arguments in the order they were provided bool dates_processed = false; for (const auto &arg_pair : parsed_args) { @@ -395,25 +727,21 @@ bool X509Tool(const args_list_t &args) { const std::string &arg_value = arg_pair.second; // Skip non-output arguments - if (arg_name == "-in" || arg_name == "-out" || arg_name == "-inform" || + if (arg_name == "-in" || arg_name == "-out" || arg_name == "-inform" || arg_name == "-signkey" || arg_name == "-days" || arg_name == "-req" || - arg_name == "-noout" || arg_name == "-help") { + arg_name == "-noout" || arg_name == "-help" || arg_name == "-CA" || + arg_name == "-CAkey") { continue; } - if (!ProcessArgument(arg_name, arg_value, x509.get(), output_bio, &dates_processed)) { - return false; - } - } - - if (!signkey_path.empty()) { - if (!LoadPrivateKeyAndSignCertificate(x509.get(), signkey_path)) { + if (!ProcessArgument(arg_name, arg_value, x509.get(), output_bio, + &dates_processed)) { return false; } } if (!noout && !ordered_args::HasArgument(parsed_args, "-checkend")) { - if (!WriteSignedCertificate(x509.get(), output_bio, out_path)) { + if (!WriteSignedCertificate(x509.get(), output_bio, out_path, outform)) { return false; } } diff --git a/tool-openssl/x509_test.cc b/tool-openssl/x509_test.cc index cfef362dba..7aa5acc3ea 100644 --- a/tool-openssl/x509_test.cc +++ b/tool-openssl/x509_test.cc @@ -4,100 +4,39 @@ #include "openssl/x509.h" #include #include +#include +#include "../crypto/test/test_util.h" #include "internal.h" #include "test_util.h" -#include "../crypto/test/test_util.h" -#include - - X509* CreateAndSignX509Certificate() { - bssl::UniquePtr x509(X509_new()); - if (!x509) return nullptr; - - // Set version to X509v3 - X509_set_version(x509.get(), X509_VERSION_3); - - // Set validity period for 30 days - if (!X509_gmtime_adj(X509_getm_notBefore(x509.get()), 0) || - !X509_gmtime_adj(X509_getm_notAfter(x509.get()), 60 * 60 * 24 * 30L)) { - return nullptr; - } - - bssl::UniquePtr pkey(EVP_PKEY_new()); - if (!pkey) { - return nullptr; - } - bssl::UniquePtr rsa(RSA_new()); - bssl::UniquePtr bn(BN_new()); - if (!bn || !BN_set_word(bn.get(), RSA_F4) || - !RSA_generate_key_ex(rsa.get(), 2048, bn.get(), nullptr) || - !EVP_PKEY_assign_RSA(pkey.get(), rsa.release())) { - return nullptr; - } - if (!X509_set_pubkey(x509.get(), pkey.get())) { - return nullptr; - } - - X509_NAME *subject_name = X509_NAME_new(); - if (!X509_NAME_add_entry_by_NID( - subject_name, NID_organizationName, MBSTRING_UTF8, - reinterpret_cast("Org"), /*len=*/-1, /*loc=*/-1, - /*set=*/0) || - !X509_NAME_add_entry_by_NID( - subject_name, NID_commonName, MBSTRING_UTF8, - reinterpret_cast("Name"), /*len=*/-1, /*loc=*/-1, - /*set=*/0)) { - return nullptr; - } - - // self-signed - if (!X509_set_subject_name(x509.get(), subject_name) || - !X509_set_issuer_name(x509.get(), subject_name)) { - return nullptr; - }; - X509_NAME_free(subject_name); - - // Add X509v3 extensions - X509V3_CTX ctx; - X509V3_set_ctx_nodb(&ctx); - X509V3_set_ctx(&ctx, x509.get(), x509.get(), nullptr, nullptr, 0); - - X509_EXTENSION *ext; - if (!(ext = X509V3_EXT_conf_nid(nullptr, &ctx, NID_basic_constraints, const_cast("critical,CA:TRUE"))) || - !X509_add_ext(x509.get(), ext, -1)) { - return nullptr; - } - X509_EXTENSION_free(ext); - - if (X509_sign(x509.get(), pkey.get(), EVP_sha256()) <= 0) { - return nullptr; - } - - return x509.release(); - } class X509Test : public ::testing::Test { -protected: + protected: void SetUp() override { ASSERT_GT(createTempFILEpath(in_path), 0u); ASSERT_GT(createTempFILEpath(csr_path), 0u); ASSERT_GT(createTempFILEpath(out_path), 0u); ASSERT_GT(createTempFILEpath(signkey_path), 0u); ASSERT_GT(createTempFILEpath(der_cert_path), 0u); + ASSERT_GT(createTempFILEpath(ca_cert_path), 0u); + ASSERT_GT(createTempFILEpath(ca_key_path), 0u); + + bssl::UniquePtr x509; + CreateAndSignX509Certificate(x509, nullptr); + ASSERT_TRUE(x509); bssl::UniquePtr pkey(EVP_PKEY_new()); ASSERT_TRUE(pkey); bssl::UniquePtr rsa(RSA_new()); ASSERT_TRUE(rsa); bssl::UniquePtr bn(BN_new()); - ASSERT_TRUE(bn && rsa && BN_set_word(bn.get(), RSA_F4) && RSA_generate_key_ex(rsa.get(), 2048, bn.get(), nullptr)); + ASSERT_TRUE(bn && rsa && BN_set_word(bn.get(), RSA_F4) && + RSA_generate_key_ex(rsa.get(), 2048, bn.get(), nullptr)); ASSERT_TRUE(EVP_PKEY_assign_RSA(pkey.get(), rsa.release())); ScopedFILE signkey_file(fopen(signkey_path, "wb")); ASSERT_TRUE(signkey_file); - ASSERT_TRUE(PEM_write_PrivateKey(signkey_file.get(), pkey.get(), nullptr, nullptr, 0, nullptr, nullptr)); - - bssl::UniquePtr x509(CreateAndSignX509Certificate()); - ASSERT_TRUE(x509); + ASSERT_TRUE(PEM_write_PrivateKey(signkey_file.get(), pkey.get(), nullptr, + nullptr, 0, nullptr, nullptr)); ScopedFILE in_file(fopen(in_path, "wb")); ASSERT_TRUE(in_file); @@ -117,6 +56,23 @@ class X509Test : public ::testing::Test { ASSERT_TRUE(csr_file); ASSERT_TRUE(PEM_write_X509_REQ(csr_file.get(), csr.get())); + // Set up mini-CA + bssl::UniquePtr ca; + bssl::UniquePtr ca_pkey; + CreateAndSignX509Certificate(ca, &ca_pkey); + ASSERT_TRUE(ca); + ASSERT_TRUE(ca_pkey); + + ScopedFILE ca_cert_file(fopen(ca_cert_path, "wb")); + ASSERT_TRUE(ca_cert_file); + ASSERT_TRUE(PEM_write_X509(ca_cert_file.get(), ca.get())); + ASSERT_TRUE(PEM_write_PrivateKey(ca_cert_file.get(), ca_pkey.get(), nullptr, + nullptr, 0, nullptr, nullptr)); + + ScopedFILE ca_key_file(fopen(ca_key_path, "wb")); + ASSERT_TRUE(ca_key_file); + ASSERT_TRUE(PEM_write_PrivateKey(ca_key_file.get(), ca_pkey.get(), nullptr, + nullptr, 0, nullptr, nullptr)); } void TearDown() override { RemoveFile(in_path); @@ -124,15 +80,18 @@ class X509Test : public ::testing::Test { RemoveFile(out_path); RemoveFile(signkey_path); RemoveFile(der_cert_path); + RemoveFile(ca_cert_path); + RemoveFile(ca_key_path); } char in_path[PATH_MAX]; char csr_path[PATH_MAX]; char out_path[PATH_MAX]; char signkey_path[PATH_MAX]; char der_cert_path[PATH_MAX]; + char ca_cert_path[PATH_MAX]; + char ca_key_path[PATH_MAX]; }; - // ----------------------------- X509 Option Tests ----------------------------- // Test -in and -out @@ -143,7 +102,8 @@ TEST_F(X509Test, X509ToolInOutTest) { { ScopedFILE out_file(fopen(out_path, "rb")); ASSERT_TRUE(out_file); - bssl::UniquePtr parsed_x509(PEM_read_X509(out_file.get(), nullptr, nullptr, nullptr)); + bssl::UniquePtr parsed_x509( + PEM_read_X509(out_file.get(), nullptr, nullptr, nullptr)); ASSERT_TRUE(parsed_x509); } } @@ -189,7 +149,8 @@ TEST_F(X509Test, X509ToolSignkeyTest) { // Test -days TEST_F(X509Test, X509ToolDaysTest) { - args_list_t args = {"-in", in_path, "-out", out_path, "-signkey", signkey_path, "-days", "365"}; + args_list_t args = {"-in", in_path, "-out", out_path, + "-signkey", signkey_path, "-days", "365"}; bool result = X509Tool(args); ASSERT_TRUE(result); } @@ -219,6 +180,27 @@ TEST_F(X509Test, X509ToolInformTest) { ASSERT_TRUE(result); } +// Test -outform +TEST_F(X509Test, X509ToolOutformTest) { + args_list_t args = {"-in", in_path, "-out", out_path, "-outform", "DER"}; + bool result = X509Tool(args); + ASSERT_TRUE(result); + + ScopedFILE out_file(fopen(out_path, "rb")); + ASSERT_TRUE(out_file); + bssl::UniquePtr parsed_x509(d2i_X509_fp(out_file.get(), nullptr)); + ASSERT_TRUE(parsed_x509); + + args = {"-in", in_path, "-out", out_path, "-outform", "PEM"}; + result = X509Tool(args); + ASSERT_TRUE(result); + + out_file.reset(fopen(out_path, "rb")); + ASSERT_TRUE(out_file); + parsed_x509.reset(PEM_read_X509(out_file.get(), nullptr, nullptr, nullptr)); + ASSERT_TRUE(result); +} + // Test -checkend TEST_F(X509Test, X509ToolCheckendTest) { args_list_t args = {"-in", in_path, "-checkend", "3600"}; @@ -228,18 +210,86 @@ TEST_F(X509Test, X509ToolCheckendTest) { // Test -req TEST_F(X509Test, X509ToolReqTest) { - args_list_t args = {"-in", csr_path, "-req", "-signkey", signkey_path, "-out", out_path}; + args_list_t args = {"-in", csr_path, "-req", "-signkey", + signkey_path, "-out", out_path}; + bool result = X509Tool(args); + ASSERT_TRUE(result); +} + +// Test -pubkey +TEST_F(X509Test, X509ToolPubkeyTest) { + args_list_t args = {"-in", in_path, "-pubkey"}; + bool result = X509Tool(args); + ASSERT_TRUE(result); +} + +// Test -CA and -CAkey +TEST_F(X509Test, X509ToolCATest) { + args_list_t args = {"-in", in_path, "-CA", + ca_cert_path, "-CAkey", ca_key_path}; + bool result = X509Tool(args); + ASSERT_TRUE(result); + + args = {"-in", in_path, "-CA", ca_cert_path}; // use key in CA file + result = X509Tool(args); + ASSERT_TRUE(result); + + args = {"-in", csr_path, "-req", "-CA", ca_cert_path, "-CAkey", ca_key_path}; + result = X509Tool(args); + ASSERT_TRUE(result); +} + +// Test -extfile and -extensions +TEST_F(X509Test, X509ToolExtensionTest) { + char ext_path[PATH_MAX]; + ASSERT_GT(createTempFILEpath(ext_path), 0u); + + ScopedFILE ext_file(fopen(ext_path, "w")); + ASSERT_TRUE(ext_file); + fprintf(ext_file.get(), "[test_ext]\nbasicConstraints=CA:FALSE\n"); + fclose(ext_file.release()); + + args_list_t args = {"-in", csr_path, "-req", + "-signkey", signkey_path, "-extfile", + ext_path, "-extensions", "test_ext"}; bool result = X509Tool(args); ASSERT_TRUE(result); + + // Test extfile without -extensions (default section) + ext_file.reset(fopen(ext_path, "w")); + ASSERT_TRUE(ext_file); + fprintf( + ext_file.get(), + "basicConstraints=CA:FALSE\nkeyUsage=digitalSignature,keyEncipherment\n"); + fclose(ext_file.release()); + + args = {"-in", csr_path, "-req", "-signkey", + signkey_path, "-extfile", ext_path}; + result = X509Tool(args); + ASSERT_TRUE(result); + + // Test extfile with extensions variable pointing to section + ext_file.reset(fopen(ext_path, "w")); + ASSERT_TRUE(ext_file); + fprintf(ext_file.get(), + "extensions=v3_req\n\n[v3_req]\nbasicConstraints=CA:FALSE\nkeyUsage=" + "digitalSignature,keyEncipherment\n"); + fclose(ext_file.release()); + + args = {"-in", in_path, "-signkey", signkey_path, "-extfile", ext_path}; + result = X509Tool(args); + ASSERT_TRUE(result); + + RemoveFile(ext_path); } // -------------------- X590 Option Usage Error Tests -------------------------- class X509OptionUsageErrorsTest : public X509Test { -protected: - void TestOptionUsageErrors(const std::vector& args) { + protected: + void TestOptionUsageErrors(const std::vector &args) { args_list_t c_args; - for (const auto& arg : args) { + for (const auto &arg : args) { c_args.push_back(arg.c_str()); } bool result = X509Tool(c_args); @@ -250,43 +300,53 @@ class X509OptionUsageErrorsTest : public X509Test { // Test mutually exclusive options TEST_F(X509OptionUsageErrorsTest, MutuallyExclusiveOptionsTests) { std::vector> testparams = { - {"-in", in_path, "-req", "-signkey", signkey_path, "-dates"}, - {"-in", in_path, "-req", "-signkey", signkey_path, "-checkend", "3600"}, - {"-in", in_path, "-signkey", signkey_path, "-dates"}, - {"-in", in_path, "-signkey", signkey_path, "-checkend", "3600"}, - {"-in", in_path, "-days", "365", "-dates"}, - {"-in", in_path, "-days", "365", "-checkend", "3600"}, + {"-in", in_path, "-req", "-signkey", signkey_path, "-dates"}, + {"-in", in_path, "-req", "-signkey", signkey_path, "-checkend", "3600"}, + {"-in", in_path, "-signkey", signkey_path, "-dates"}, + {"-in", in_path, "-signkey", signkey_path, "-checkend", "3600"}, + {"-in", in_path, "-days", "365", "-dates"}, + {"-in", in_path, "-days", "365", "-checkend", "3600"}, + {"-in", in_path, "-signkey", signkey_path, "-CA", ca_cert_path}, + }; - for (const auto& args : testparams) { + for (const auto &args : testparams) { TestOptionUsageErrors(args); } } -// Test -req without -signkey TEST_F(X509OptionUsageErrorsTest, RequiredOptionTests) { std::vector> testparams = { - {"-in", in_path, "-req"}, + {"-in", in_path, "-req"}, // Test -req without -signkey + {"-in", in_path, "-CAkey", ca_key_path} // Test -CAkey without -CA }; - for (const auto& args : testparams) { + for (const auto &args : testparams) { TestOptionUsageErrors(args); } } -// Test argument errors for -days: !<0 || non-integer, -checkend: !<=0 || non-integer, -inform != {DER, PEM} +// Test argument errors for -days: !<0 || non-integer, -checkend: !<=0 || +// non-integer, -inform != {DER, PEM} TEST_F(X509OptionUsageErrorsTest, DaysAndCheckendArgTests) { std::vector> testparams = { - {"-in", in_path, "-checkend", "abc"}, - {"-in", in_path, "-checkend", "-1"}, - {"-in", in_path, "-signkey", signkey_path, "-days", "abc"}, - {"-in", in_path, "-signkey", signkey_path, "-days", "0"}, - {"-in", in_path, "-signkey", signkey_path, "-days", "-1.7"}, - {"-in", in_path, "-inform", "RANDOM"} + {"-in", in_path, "-checkend", "abc"}, + {"-in", in_path, "-checkend", "-1"}, + {"-in", in_path, "-signkey", signkey_path, "-days", "abc"}, + {"-in", in_path, "-signkey", signkey_path, "-days", "0"}, + {"-in", in_path, "-signkey", signkey_path, "-days", "-1.7"}, }; - for (const auto& args : testparams) { + for (const auto &args : testparams) { TestOptionUsageErrors(args); } } +TEST_F(X509OptionUsageErrorsTest, InvalidArgTests) { + std::vector> testparams = { + {"-in", in_path, "-inform", "RANDOM"}, + {"-in", in_path, "-out", out_path, "-outform", "RANDOM"}}; + for (const auto &args : testparams) { + TestOptionUsageErrors(args); + } +} // -------------------- X509 OpenSSL Comparison Tests -------------------------- @@ -294,14 +354,14 @@ TEST_F(X509OptionUsageErrorsTest, DaysAndCheckendArgTests) { // AWSLC_TOOL_PATH and OPENSSL_TOOL_PATH. class X509ComparisonTest : public ::testing::Test { -protected: + protected: void SetUp() override { - // Skip gtests if env variables not set tool_executable_path = getenv("AWSLC_TOOL_PATH"); openssl_executable_path = getenv("OPENSSL_TOOL_PATH"); if (tool_executable_path == nullptr || openssl_executable_path == nullptr) { - GTEST_SKIP() << "Skipping test: AWSLC_TOOL_PATH and/or OPENSSL_TOOL_PATH environment variables are not set"; + GTEST_SKIP() << "Skipping test: AWSLC_TOOL_PATH and/or OPENSSL_TOOL_PATH " + "environment variables are not set"; } ASSERT_GT(createTempFILEpath(in_path), 0u); @@ -310,8 +370,10 @@ class X509ComparisonTest : public ::testing::Test { ASSERT_GT(createTempFILEpath(out_path_openssl), 0u); ASSERT_GT(createTempFILEpath(signkey_path), 0u); ASSERT_GT(createTempFILEpath(der_cert_path), 0u); + ASSERT_GT(createTempFILEpath(ca_cert_path), 0u); + ASSERT_GT(createTempFILEpath(ca_key_path), 0u); - x509.reset(CreateAndSignX509Certificate()); + CreateAndSignX509Certificate(x509, nullptr); ASSERT_TRUE(x509); ScopedFILE in_file(fopen(in_path, "wb")); @@ -323,15 +385,16 @@ class X509ComparisonTest : public ::testing::Test { ASSERT_TRUE(i2d_X509_fp(der_file.get(), x509.get())); bssl::UniquePtr pkey(EVP_PKEY_new()); - ASSERT_TRUE(pkey); bssl::UniquePtr rsa(RSA_new()); bssl::UniquePtr bn(BN_new()); - ASSERT_TRUE(bn && BN_set_word(bn.get(), RSA_F4) && RSA_generate_key_ex(rsa.get(), 2048, bn.get(), nullptr)); + ASSERT_TRUE(bn && BN_set_word(bn.get(), RSA_F4) && + RSA_generate_key_ex(rsa.get(), 2048, bn.get(), nullptr)); ASSERT_TRUE(EVP_PKEY_assign_RSA(pkey.get(), rsa.release())); ScopedFILE signkey_file(fopen(signkey_path, "wb")); ASSERT_TRUE(signkey_file); - ASSERT_TRUE(PEM_write_PrivateKey(signkey_file.get(), pkey.get(), nullptr, nullptr, 0, nullptr, nullptr)); + ASSERT_TRUE(PEM_write_PrivateKey(signkey_file.get(), pkey.get(), nullptr, + nullptr, 0, nullptr, nullptr)); csr.reset(X509_REQ_new()); ASSERT_TRUE(csr); @@ -341,6 +404,24 @@ class X509ComparisonTest : public ::testing::Test { ScopedFILE csr_file(fopen(csr_path, "wb")); ASSERT_TRUE(csr_file); ASSERT_TRUE(PEM_write_X509_REQ(csr_file.get(), csr.get())); + + // Set up mini-CA + bssl::UniquePtr ca; + bssl::UniquePtr ca_pkey; + CreateAndSignX509Certificate(ca, &ca_pkey); + ASSERT_TRUE(ca); + ASSERT_TRUE(ca_pkey); + + ScopedFILE ca_cert_file(fopen(ca_cert_path, "wb")); + ASSERT_TRUE(ca_cert_file); + ASSERT_TRUE(PEM_write_X509(ca_cert_file.get(), ca.get())); + ASSERT_TRUE(PEM_write_PrivateKey(ca_cert_file.get(), ca_pkey.get(), nullptr, + nullptr, 0, nullptr, nullptr)); + + ScopedFILE ca_key_file(fopen(ca_key_path, "wb")); + ASSERT_TRUE(ca_key_file); + ASSERT_TRUE(PEM_write_PrivateKey(ca_key_file.get(), ca_pkey.get(), nullptr, + nullptr, 0, nullptr, nullptr)); } void TearDown() override { @@ -351,6 +432,8 @@ class X509ComparisonTest : public ::testing::Test { RemoveFile(out_path_openssl); RemoveFile(signkey_path); RemoveFile(der_cert_path); + RemoveFile(ca_cert_path); + RemoveFile(ca_key_path); } } @@ -360,10 +443,12 @@ class X509ComparisonTest : public ::testing::Test { char out_path_openssl[PATH_MAX]; char signkey_path[PATH_MAX]; char der_cert_path[PATH_MAX]; + char ca_cert_path[PATH_MAX]; + char ca_key_path[PATH_MAX]; bssl::UniquePtr x509; bssl::UniquePtr csr; - const char* tool_executable_path; - const char* openssl_executable_path; + const char *tool_executable_path; + const char *openssl_executable_path; std::string tool_output_str; std::string openssl_output_str; }; @@ -375,26 +460,47 @@ static std::string normalize_subject(std::string input) { if (subject_start != std::string::npos) { size_t line_end = input.find('\n', subject_start); if (line_end != std::string::npos) { - std::string subject_line = input.substr(subject_start, line_end - subject_start); - subject_line.erase(remove(subject_line.begin(), subject_line.end(), ' '), subject_line.end()); + std::string subject_line = + input.substr(subject_start, line_end - subject_start); + subject_line.erase(remove(subject_line.begin(), subject_line.end(), ' '), + subject_line.end()); input.replace(subject_start, line_end - subject_start, subject_line); } } return input; } -// Certificate boundaries -const std::string CERT_BEGIN = "-----BEGIN CERTIFICATE-----"; -const std::string CERT_END = "-----END CERTIFICATE-----"; +// Test against OpenSSL output "openssl x509 -req -in csr_file -signkey +// private_key_file" +// TEST_F(X509ComparisonTest, X509ToolCompareReqOpenSSL) { +// std::string tool_command = std::string(tool_executable_path) + +// " x509 -req -in " + csr_path + " > " + +// out_path_tool; +// std::string openssl_command = std::string(openssl_executable_path) + +// " x509 -req -in " + csr_path + " > " + +// out_path_openssl; -// Test against OpenSSL output "openssl x509 -in file -text -noout" -TEST_F(X509ComparisonTest, X509ToolCompareTextOpenSSL) { - std::string tool_command = std::string(tool_executable_path) + " x509 -in " + in_path + " -text -noout> " + out_path_tool; - std::string openssl_command = std::string(openssl_executable_path) + " x509 -in " + in_path + " -text -noout > " + out_path_openssl; +// RunCommandsAndCompareOutput(tool_command, openssl_command, out_path_tool, +// out_path_openssl, tool_output_str, +// openssl_output_str); - RunCommandsAndCompareOutput(tool_command, openssl_command, out_path_tool, out_path_openssl, tool_output_str, openssl_output_str); +// ASSERT_EQ(tool_output_str, openssl_output_str); +// } - // OpenSSL 3.0+ include an additional "Signature Value" header before printing the signature +// Test against OpenSSL output "openssl x509 -in file -text -noout" +TEST_F(X509ComparisonTest, X509ToolCompareTextOpenSSL) { + std::string tool_command = std::string(tool_executable_path) + " x509 -in " + + in_path + " -text -noout> " + out_path_tool; + std::string openssl_command = std::string(openssl_executable_path) + + " x509 -in " + in_path + " -text -noout > " + + out_path_openssl; + + RunCommandsAndCompareOutput(tool_command, openssl_command, out_path_tool, + out_path_openssl, tool_output_str, + openssl_output_str); + + // OpenSSL 3.0+ include an additional "Signature Value" header before printing + // the signature const char *signature_string = "Signature Value:"; size_t index = openssl_output_str.find(signature_string); if (index != std::string::npos) { @@ -402,149 +508,249 @@ TEST_F(X509ComparisonTest, X509ToolCompareTextOpenSSL) { } // OpenSSL disagrees on what the Subject Public Key Info headers should be - const char* rsa_public_key = "RSA Public-Key:"; + const char *rsa_public_key = "RSA Public-Key:"; index = openssl_output_str.find(rsa_public_key); if (index != std::string::npos) { openssl_output_str.replace(index, strlen(rsa_public_key), "Public-Key:"); } // OpenSSL versions disagree on the amount of indentation of certain fields - tool_output_str.erase(remove_if(tool_output_str.begin(), tool_output_str.end(), isspace), tool_output_str.end()); - openssl_output_str.erase(remove_if(openssl_output_str.begin(), openssl_output_str.end(), isspace), openssl_output_str.end()); + tool_output_str.erase( + remove_if(tool_output_str.begin(), tool_output_str.end(), isspace), + tool_output_str.end()); + openssl_output_str.erase( + remove_if(openssl_output_str.begin(), openssl_output_str.end(), isspace), + openssl_output_str.end()); ASSERT_EQ(tool_output_str, openssl_output_str); } // Test against OpenSSL output "openssl x509 -in file -modulus" TEST_F(X509ComparisonTest, X509ToolCompareModulusOpenSSL) { - std::string tool_command = std::string(tool_executable_path) + " x509 -in " + in_path + " -modulus > " + out_path_tool; - std::string openssl_command = std::string(openssl_executable_path) + " x509 -in " + in_path + " -modulus > " + out_path_openssl; + std::string tool_command = std::string(tool_executable_path) + " x509 -in " + + in_path + " -modulus > " + out_path_tool; + std::string openssl_command = std::string(openssl_executable_path) + + " x509 -in " + in_path + " -modulus > " + + out_path_openssl; - RunCommandsAndCompareOutput(tool_command, openssl_command, out_path_tool, out_path_openssl, tool_output_str, openssl_output_str); + RunCommandsAndCompareOutput(tool_command, openssl_command, out_path_tool, + out_path_openssl, tool_output_str, + openssl_output_str); ASSERT_EQ(tool_output_str, openssl_output_str); - tool_command = std::string(tool_executable_path) + " x509 -in " + in_path + " -modulus -out " + out_path_tool; - openssl_command = std::string(openssl_executable_path) + " x509 -in " + in_path + " -modulus -out " + out_path_openssl; + tool_command = std::string(tool_executable_path) + " x509 -in " + in_path + + " -modulus -out " + out_path_tool; + openssl_command = std::string(openssl_executable_path) + " x509 -in " + + in_path + " -modulus -out " + out_path_openssl; - RunCommandsAndCompareOutput(tool_command, openssl_command, out_path_tool, out_path_openssl, tool_output_str, openssl_output_str); + RunCommandsAndCompareOutput(tool_command, openssl_command, out_path_tool, + out_path_openssl, tool_output_str, + openssl_output_str); ASSERT_EQ(tool_output_str, openssl_output_str); } // Test against OpenSSL output "openssl x509 -in file -subject" TEST_F(X509ComparisonTest, X509ToolCompareSubjectOpenSSL) { - std::string tool_command = std::string(tool_executable_path) + " x509 -in " + in_path + " -subject > " + out_path_tool; - std::string openssl_command = std::string(openssl_executable_path) + " x509 -in " + in_path + " -subject > " + out_path_openssl; - - RunCommandsAndCompareOutput(tool_command, openssl_command, out_path_tool, out_path_openssl, tool_output_str, openssl_output_str); - - // OpenSSL master and versions <= 3.2 have differences in spacing for the subject field + std::string tool_command = std::string(tool_executable_path) + " x509 -in " + + in_path + " -subject > " + out_path_tool; + std::string openssl_command = std::string(openssl_executable_path) + + " x509 -in " + in_path + " -subject > " + + out_path_openssl; + + RunCommandsAndCompareOutput(tool_command, openssl_command, out_path_tool, + out_path_openssl, tool_output_str, + openssl_output_str); + + // OpenSSL master and versions <= 3.2 have differences in spacing for the + // subject field tool_output_str = normalize_subject(tool_output_str); openssl_output_str = normalize_subject(openssl_output_str); ASSERT_EQ(tool_output_str, openssl_output_str); - tool_command = std::string(tool_executable_path) + " x509 -in " + in_path + " -subject -out " + out_path_tool; - openssl_command = std::string(openssl_executable_path) + " x509 -in " + in_path + " -subject -out " + out_path_openssl; + tool_command = std::string(tool_executable_path) + " x509 -in " + in_path + + " -subject -out " + out_path_tool; + openssl_command = std::string(openssl_executable_path) + " x509 -in " + + in_path + " -subject -out " + out_path_openssl; - RunCommandsAndCompareOutput(tool_command, openssl_command, out_path_tool, out_path_openssl, tool_output_str, openssl_output_str); + RunCommandsAndCompareOutput(tool_command, openssl_command, out_path_tool, + out_path_openssl, tool_output_str, + openssl_output_str); - // OpenSSL master and versions <= 3.2 have differences in spacing for the subject field + // OpenSSL master and versions <= 3.2 have differences in spacing for the + // subject field tool_output_str = normalize_subject(tool_output_str); openssl_output_str = normalize_subject(openssl_output_str); ASSERT_EQ(tool_output_str, openssl_output_str); } -// Test against OpenSSL output "openssl x509 -in file -fingerprint -subject_hash -subject_hash_old" +// Test against OpenSSL output "openssl x509 -in file -fingerprint -subject_hash +// -subject_hash_old" TEST_F(X509ComparisonTest, X509ToolCompareFingerprintOpenSSL) { - std::string tool_command = std::string(tool_executable_path) + " x509 -in " + in_path + " -subject_hash -subject_hash_old -fingerprint > " + out_path_tool; - std::string openssl_command = std::string(openssl_executable_path) + " x509 -in " + in_path + " -subject_hash -subject_hash_old -fingerprint > " + out_path_openssl; + std::string tool_command = + std::string(tool_executable_path) + " x509 -in " + in_path + + " -subject_hash -subject_hash_old -fingerprint > " + out_path_tool; + std::string openssl_command = + std::string(openssl_executable_path) + " x509 -in " + in_path + + " -subject_hash -subject_hash_old -fingerprint > " + out_path_openssl; - RunCommandsAndCompareOutput(tool_command, openssl_command, out_path_tool, out_path_openssl, tool_output_str, openssl_output_str); + RunCommandsAndCompareOutput(tool_command, openssl_command, out_path_tool, + out_path_openssl, tool_output_str, + openssl_output_str); ASSERT_EQ(tool_output_str, openssl_output_str); - tool_command = std::string(tool_executable_path) + " x509 -in " + in_path + " -subject_hash -subject_hash_old -fingerprint -out " + out_path_tool; - openssl_command = std::string(openssl_executable_path) + " x509 -in " + in_path + " -subject_hash -subject_hash_old -fingerprint -out " + out_path_openssl; + tool_command = std::string(tool_executable_path) + " x509 -in " + in_path + + " -subject_hash -subject_hash_old -fingerprint -out " + + out_path_tool; + openssl_command = + std::string(openssl_executable_path) + " x509 -in " + in_path + + " -subject_hash -subject_hash_old -fingerprint -out " + out_path_openssl; - RunCommandsAndCompareOutput(tool_command, openssl_command, out_path_tool, out_path_openssl, tool_output_str, openssl_output_str); + RunCommandsAndCompareOutput(tool_command, openssl_command, out_path_tool, + out_path_openssl, tool_output_str, + openssl_output_str); ASSERT_EQ(tool_output_str, openssl_output_str); } -// Test against OpenSSL output "openssl x509 -in file -fingerprint -subject_hash -subject_hash_old" +// Test against OpenSSL output "openssl x509 -in file -fingerprint -subject_hash +// -subject_hash_old" TEST_F(X509ComparisonTest, X509ToolCompareReorderedFingerprintOpenSSL) { - std::string tool_command = std::string(tool_executable_path) + " x509 -in " + in_path + " -subject_hash -fingerprint -subject_hash_old > " + out_path_tool; - std::string openssl_command = std::string(openssl_executable_path) + " x509 -in " + in_path + " -subject_hash -fingerprint -subject_hash_old > " + out_path_openssl; + std::string tool_command = + std::string(tool_executable_path) + " x509 -in " + in_path + + " -subject_hash -fingerprint -subject_hash_old > " + out_path_tool; + std::string openssl_command = + std::string(openssl_executable_path) + " x509 -in " + in_path + + " -subject_hash -fingerprint -subject_hash_old > " + out_path_openssl; - RunCommandsAndCompareOutput(tool_command, openssl_command, out_path_tool, out_path_openssl, tool_output_str, openssl_output_str); + RunCommandsAndCompareOutput(tool_command, openssl_command, out_path_tool, + out_path_openssl, tool_output_str, + openssl_output_str); ASSERT_EQ(tool_output_str, openssl_output_str); - tool_command = std::string(tool_executable_path) + " x509 -in " + in_path + " -fingerprint -subject_hash_old -subject_hash -out " + out_path_tool; - openssl_command = std::string(openssl_executable_path) + " x509 -in " + in_path + " -fingerprint -subject_hash_old -subject_hash -out " + out_path_openssl; + tool_command = std::string(tool_executable_path) + " x509 -in " + in_path + + " -fingerprint -subject_hash_old -subject_hash -out " + + out_path_tool; + openssl_command = + std::string(openssl_executable_path) + " x509 -in " + in_path + + " -fingerprint -subject_hash_old -subject_hash -out " + out_path_openssl; - RunCommandsAndCompareOutput(tool_command, openssl_command, out_path_tool, out_path_openssl, tool_output_str, openssl_output_str); + RunCommandsAndCompareOutput(tool_command, openssl_command, out_path_tool, + out_path_openssl, tool_output_str, + openssl_output_str); ASSERT_EQ(tool_output_str, openssl_output_str); } -// Test against OpenSSL output "openssl x509 -in file -fingerprint -subject_hash -subject_hash_old" +// Test against OpenSSL output "openssl x509 -in file -fingerprint -subject_hash +// -subject_hash_old" TEST_F(X509ComparisonTest, X509ToolCompareHashFingerprintOpenSSL) { - std::string tool_command = std::string(tool_executable_path) + " x509 -subject_hash -fingerprint -noout -in " + in_path + " > " + out_path_tool; - std::string openssl_command = std::string(openssl_executable_path) + " x509 -subject_hash -fingerprint -noout -in " + in_path + " > " + out_path_openssl; + std::string tool_command = std::string(tool_executable_path) + + " x509 -subject_hash -fingerprint -noout -in " + + in_path + " > " + out_path_tool; + std::string openssl_command = std::string(openssl_executable_path) + + " x509 -subject_hash -fingerprint -noout -in " + + in_path + " > " + out_path_openssl; - RunCommandsAndCompareOutput(tool_command, openssl_command, out_path_tool, out_path_openssl, tool_output_str, openssl_output_str); + RunCommandsAndCompareOutput(tool_command, openssl_command, out_path_tool, + out_path_openssl, tool_output_str, + openssl_output_str); ASSERT_EQ(tool_output_str, openssl_output_str); } -// Test against OpenSSL output "openssl x509 -in file -noout -subject -fingerprint" -TEST_F(X509ComparisonTest, X509ToolCompareSubjectFingerprintOpenSSL) { - std::string tool_command = std::string(tool_executable_path) + " x509 -in " + in_path + " -noout -subject -fingerprint > " + out_path_tool; - std::string openssl_command = std::string(openssl_executable_path) + " x509 -in " + in_path + " -noout -subject -fingerprint > " + out_path_openssl; +// Test against OpenSSL output "openssl x509 -in file -pubkey" +TEST_F(X509ComparisonTest, X509ToolComparePubkeyOpenSSL) { + std::string tool_command = std::string(tool_executable_path) + " x509 -in " + + in_path + " -pubkey > " + out_path_tool; + std::string openssl_command = std::string(openssl_executable_path) + + " x509 -in " + in_path + " -pubkey > " + + out_path_openssl; - RunCommandsAndCompareOutput(tool_command, openssl_command, out_path_tool, out_path_openssl, tool_output_str, openssl_output_str); + RunCommandsAndCompareOutput(tool_command, openssl_command, out_path_tool, + out_path_openssl, tool_output_str, + openssl_output_str); - // OpenSSL master and versions <= 3.2 have differences in spacing for the subject field + ASSERT_EQ(tool_output_str, openssl_output_str); +} + +// Test against OpenSSL output "openssl x509 -in file -noout -subject +// -fingerprint" +TEST_F(X509ComparisonTest, X509ToolCompareSubjectFingerprintOpenSSL) { + std::string tool_command = std::string(tool_executable_path) + " x509 -in " + + in_path + " -noout -subject -fingerprint > " + + out_path_tool; + std::string openssl_command = + std::string(openssl_executable_path) + " x509 -in " + in_path + + " -noout -subject -fingerprint > " + out_path_openssl; + + RunCommandsAndCompareOutput(tool_command, openssl_command, out_path_tool, + out_path_openssl, tool_output_str, + openssl_output_str); + + // OpenSSL master and versions <= 3.2 have differences in spacing for the + // subject field tool_output_str = normalize_subject(tool_output_str); openssl_output_str = normalize_subject(openssl_output_str); ASSERT_EQ(tool_output_str, openssl_output_str); - tool_command = std::string(tool_executable_path) + " x509 -in " + in_path + " -noout -subject -fingerprint -out " + out_path_tool; - openssl_command = std::string(openssl_executable_path) + " x509 -in " + in_path + " -noout -subject -fingerprint -out " + out_path_openssl; + tool_command = std::string(tool_executable_path) + " x509 -in " + in_path + + " -noout -subject -fingerprint -out " + out_path_tool; + openssl_command = std::string(openssl_executable_path) + " x509 -in " + + in_path + " -noout -subject -fingerprint -out " + + out_path_openssl; - RunCommandsAndCompareOutput(tool_command, openssl_command, out_path_tool, out_path_openssl, tool_output_str, openssl_output_str); + RunCommandsAndCompareOutput(tool_command, openssl_command, out_path_tool, + out_path_openssl, tool_output_str, + openssl_output_str); - // OpenSSL master and versions <= 3.2 have differences in spacing for the subject field + // OpenSSL master and versions <= 3.2 have differences in spacing for the + // subject field tool_output_str = normalize_subject(tool_output_str); openssl_output_str = normalize_subject(openssl_output_str); ASSERT_EQ(tool_output_str, openssl_output_str); } -// Test against OpenSSL output "openssl x509 -in file -noout -subject -fingerprint" +// Test against OpenSSL output "openssl x509 -in file -noout -subject +// -fingerprint" TEST_F(X509ComparisonTest, X509ToolCompareReorderedSubjectFingerprintOpenSSL) { - std::string tool_command = std::string(tool_executable_path) + " x509 -in " + in_path + " -noout -fingerprint -subject > " + out_path_tool; - std::string openssl_command = std::string(openssl_executable_path) + " x509 -in " + in_path + " -noout -fingerprint -subject > " + out_path_openssl; - - RunCommandsAndCompareOutput(tool_command, openssl_command, out_path_tool, out_path_openssl, tool_output_str, openssl_output_str); - - // OpenSSL master and versions <= 3.2 have differences in spacing for the subject field + std::string tool_command = std::string(tool_executable_path) + " x509 -in " + + in_path + " -noout -fingerprint -subject > " + + out_path_tool; + std::string openssl_command = + std::string(openssl_executable_path) + " x509 -in " + in_path + + " -noout -fingerprint -subject > " + out_path_openssl; + + RunCommandsAndCompareOutput(tool_command, openssl_command, out_path_tool, + out_path_openssl, tool_output_str, + openssl_output_str); + + // OpenSSL master and versions <= 3.2 have differences in spacing for the + // subject field tool_output_str = normalize_subject(tool_output_str); openssl_output_str = normalize_subject(openssl_output_str); ASSERT_EQ(tool_output_str, openssl_output_str); - tool_command = std::string(tool_executable_path) + " x509 -in " + in_path + " -noout -fingerprint -subject -out " + out_path_tool; - openssl_command = std::string(openssl_executable_path) + " x509 -in " + in_path + " -noout -fingerprint -subject -out " + out_path_openssl; + tool_command = std::string(tool_executable_path) + " x509 -in " + in_path + + " -noout -fingerprint -subject -out " + out_path_tool; + openssl_command = std::string(openssl_executable_path) + " x509 -in " + + in_path + " -noout -fingerprint -subject -out " + + out_path_openssl; - RunCommandsAndCompareOutput(tool_command, openssl_command, out_path_tool, out_path_openssl, tool_output_str, openssl_output_str); + RunCommandsAndCompareOutput(tool_command, openssl_command, out_path_tool, + out_path_openssl, tool_output_str, + openssl_output_str); - // OpenSSL master and versions <= 3.2 have differences in spacing for the subject field + // OpenSSL master and versions <= 3.2 have differences in spacing for the + // subject field tool_output_str = normalize_subject(tool_output_str); openssl_output_str = normalize_subject(openssl_output_str); @@ -553,111 +759,436 @@ TEST_F(X509ComparisonTest, X509ToolCompareReorderedSubjectFingerprintOpenSSL) { // Test against OpenSSL output "openssl x509 -in in_file -checkend 0" TEST_F(X509ComparisonTest, X509ToolCompareCheckendOpenSSL) { - std::string tool_command = std::string(tool_executable_path) + " x509 -in " + in_path + " -checkend 0 > " + out_path_tool; - std::string openssl_command = std::string(openssl_executable_path) + " x509 -in " + in_path + " -checkend 0 > " + out_path_openssl; + std::string tool_command = std::string(tool_executable_path) + " x509 -in " + + in_path + " -checkend 0 > " + out_path_tool; + std::string openssl_command = std::string(openssl_executable_path) + + " x509 -in " + in_path + " -checkend 0 > " + + out_path_openssl; - RunCommandsAndCompareOutput(tool_command, openssl_command, out_path_tool, out_path_openssl, tool_output_str, openssl_output_str); + RunCommandsAndCompareOutput(tool_command, openssl_command, out_path_tool, + out_path_openssl, tool_output_str, + openssl_output_str); ASSERT_EQ(tool_output_str, openssl_output_str); } -// Test against OpenSSL output "openssl x509 -req -in csr_file -signkey private_key_file -days 80 -out out_file" +// Test against OpenSSL output "openssl x509 -req -in csr_file -signkey +// private_key_file -days 80 -out out_file" TEST_F(X509ComparisonTest, X509ToolCompareReqSignkeyDaysOpenSSL) { - std::string tool_command = std::string(tool_executable_path) + " x509 -req -in " + csr_path + " -signkey " + signkey_path + " -days 80 -out " + out_path_tool; - std::string openssl_command = std::string(openssl_executable_path) + " x509 -req -in " + csr_path + " -signkey " + signkey_path + " -days 80 -out " + out_path_openssl; - - RunCommandsAndCompareOutput(tool_command, openssl_command, out_path_tool, out_path_openssl, tool_output_str, openssl_output_str); - // Certificates will not be identical, therefore testing that cert header and footer are present - trim(tool_output_str); - ASSERT_EQ(tool_output_str.compare(0, CERT_BEGIN.size(), CERT_BEGIN), 0); - ASSERT_EQ(tool_output_str.compare(tool_output_str.size() - CERT_END.size(), CERT_END.size(), CERT_END), 0); - - trim(openssl_output_str); - ASSERT_EQ(openssl_output_str.compare(0, CERT_BEGIN.size(), CERT_BEGIN), 0); - ASSERT_EQ(openssl_output_str.compare(openssl_output_str.size() - CERT_END.size(), CERT_END.size(), CERT_END), 0); + std::string tool_command = std::string(tool_executable_path) + + " x509 -req -in " + csr_path + " -signkey " + + signkey_path + " -days 80 -out " + out_path_tool; + std::string openssl_command = + std::string(openssl_executable_path) + " x509 -req -in " + csr_path + + " -signkey " + signkey_path + " -days 80 -out " + out_path_openssl; + + ExecuteCommand(tool_command); + ExecuteCommand(openssl_command); + + // Load certificates + auto cert_tool = LoadPEMCertificate(out_path_tool); + auto cert_openssl = LoadPEMCertificate(out_path_openssl); + + ASSERT_TRUE(cert_tool != nullptr) + << "Failed to load certificate generated by tool"; + ASSERT_TRUE(cert_openssl != nullptr) + << "Failed to load certificate generated by OpenSSL"; + + // Compare certificates in detail with 365 days validity period + ASSERT_TRUE(CompareCertificates(cert_tool.get(), cert_openssl.get(), + cert_tool.get(), 80)) + << "Certificates generated by tool and OpenSSL have different attributes"; } // Test against OpenSSL output "openssl x509 -in file -dates -noout" TEST_F(X509ComparisonTest, X509ToolCompareDatesNooutOpenSSL) { - std::string tool_command = std::string(tool_executable_path) + " x509 -in " + in_path + " -dates -noout > " + out_path_tool; - std::string openssl_command = std::string(openssl_executable_path) + " x509 -in " + in_path + " -dates -noout > " + out_path_openssl; + std::string tool_command = std::string(tool_executable_path) + " x509 -in " + + in_path + " -dates -noout > " + out_path_tool; + std::string openssl_command = std::string(openssl_executable_path) + + " x509 -in " + in_path + " -dates -noout > " + + out_path_openssl; - RunCommandsAndCompareOutput(tool_command, openssl_command, out_path_tool, out_path_openssl, tool_output_str, openssl_output_str); + RunCommandsAndCompareOutput(tool_command, openssl_command, out_path_tool, + out_path_openssl, tool_output_str, + openssl_output_str); ASSERT_EQ(tool_output_str, openssl_output_str); - tool_command = std::string(tool_executable_path) + " x509 -in " + in_path + " -dates -noout -out " + out_path_tool; - openssl_command = std::string(openssl_executable_path) + " x509 -in " + in_path + " -dates -noout -out " + out_path_openssl; + tool_command = std::string(tool_executable_path) + " x509 -in " + in_path + + " -dates -noout -out " + out_path_tool; + openssl_command = std::string(openssl_executable_path) + " x509 -in " + + in_path + " -dates -noout -out " + out_path_openssl; - RunCommandsAndCompareOutput(tool_command, openssl_command, out_path_tool, out_path_openssl, tool_output_str, openssl_output_str); + RunCommandsAndCompareOutput(tool_command, openssl_command, out_path_tool, + out_path_openssl, tool_output_str, + openssl_output_str); ASSERT_EQ(tool_output_str, openssl_output_str); } -// Test against OpenSSL output "openssl x509 -in file -dates -enddate", notAfter date should only be printed out once +// Test against OpenSSL output "openssl x509 -in file -dates -enddate", notAfter +// date should only be printed out once TEST_F(X509ComparisonTest, X509ToolCompareDatesEnddateOpenSSL) { - std::string tool_command = std::string(tool_executable_path) + " x509 -in " + in_path + " -dates -enddate > " + out_path_tool; - std::string openssl_command = std::string(openssl_executable_path) + " x509 -in " + in_path + " -dates -enddate > " + out_path_openssl; + std::string tool_command = std::string(tool_executable_path) + " x509 -in " + + in_path + " -dates -enddate > " + out_path_tool; + std::string openssl_command = std::string(openssl_executable_path) + + " x509 -in " + in_path + " -dates -enddate > " + + out_path_openssl; - RunCommandsAndCompareOutput(tool_command, openssl_command, out_path_tool, out_path_openssl, tool_output_str, openssl_output_str); + RunCommandsAndCompareOutput(tool_command, openssl_command, out_path_tool, + out_path_openssl, tool_output_str, + openssl_output_str); ASSERT_EQ(tool_output_str, openssl_output_str); - tool_command = std::string(tool_executable_path) + " x509 -in " + in_path + " -dates -enddate -out " + out_path_tool; - openssl_command = std::string(openssl_executable_path) + " x509 -in " + in_path + " -dates -enddate -out " + out_path_openssl; + tool_command = std::string(tool_executable_path) + " x509 -in " + in_path + + " -dates -enddate -out " + out_path_tool; + openssl_command = std::string(openssl_executable_path) + " x509 -in " + + in_path + " -dates -enddate -out " + out_path_openssl; - RunCommandsAndCompareOutput(tool_command, openssl_command, out_path_tool, out_path_openssl, tool_output_str, openssl_output_str); + RunCommandsAndCompareOutput(tool_command, openssl_command, out_path_tool, + out_path_openssl, tool_output_str, + openssl_output_str); ASSERT_EQ(tool_output_str, openssl_output_str); } // Test against OpenSSL output "openssl x509 -in file -inform DER -enddate" TEST_F(X509ComparisonTest, X509ToolCompareInformDEREnddateOpenSSL) { - std::string tool_command = std::string(tool_executable_path) + " x509 -in " + der_cert_path + " -inform DER -enddate > " + out_path_tool; - std::string openssl_command = std::string(openssl_executable_path) + " x509 -in " + der_cert_path + " -inform DER -enddate > " + out_path_openssl; + std::string tool_command = std::string(tool_executable_path) + " x509 -in " + + der_cert_path + " -inform DER -enddate > " + + out_path_tool; + std::string openssl_command = std::string(openssl_executable_path) + + " x509 -in " + der_cert_path + + " -inform DER -enddate > " + out_path_openssl; - RunCommandsAndCompareOutput(tool_command, openssl_command, out_path_tool, out_path_openssl, tool_output_str, openssl_output_str); + RunCommandsAndCompareOutput(tool_command, openssl_command, out_path_tool, + out_path_openssl, tool_output_str, + openssl_output_str); ASSERT_EQ(tool_output_str, openssl_output_str); - tool_command = std::string(tool_executable_path) + " x509 -in " + der_cert_path + " -inform DER -enddate -out " + out_path_tool; - openssl_command = std::string(openssl_executable_path) + " x509 -in " + der_cert_path + " -inform DER -enddate -out " + out_path_openssl; + tool_command = std::string(tool_executable_path) + " x509 -in " + + der_cert_path + " -inform DER -enddate -out " + out_path_tool; + openssl_command = std::string(openssl_executable_path) + " x509 -in " + + der_cert_path + " -inform DER -enddate -out " + + out_path_openssl; - RunCommandsAndCompareOutput(tool_command, openssl_command, out_path_tool, out_path_openssl, tool_output_str, openssl_output_str); + RunCommandsAndCompareOutput(tool_command, openssl_command, out_path_tool, + out_path_openssl, tool_output_str, + openssl_output_str); ASSERT_EQ(tool_output_str, openssl_output_str); } // Test against OpenSSL output "openssl x509 -in file -inform DER -enddate" TEST_F(X509ComparisonTest, X509ToolCompareInformPEMEnddateOpenSSL) { - std::string tool_command = std::string(tool_executable_path) + " x509 -in " + in_path + " -inform PEM -enddate > " + out_path_tool; - std::string openssl_command = std::string(openssl_executable_path) + " x509 -in " + in_path + " -inform PEM -enddate > " + out_path_openssl; + std::string tool_command = std::string(tool_executable_path) + " x509 -in " + + in_path + " -inform PEM -enddate > " + + out_path_tool; + std::string openssl_command = std::string(openssl_executable_path) + + " x509 -in " + in_path + + " -inform PEM -enddate > " + out_path_openssl; - RunCommandsAndCompareOutput(tool_command, openssl_command, out_path_tool, out_path_openssl, tool_output_str, openssl_output_str); + RunCommandsAndCompareOutput(tool_command, openssl_command, out_path_tool, + out_path_openssl, tool_output_str, + openssl_output_str); ASSERT_EQ(tool_output_str, openssl_output_str); - tool_command = std::string(tool_executable_path) + " x509 -in " + in_path + " -inform PEM -enddate -out " + out_path_tool; - openssl_command = std::string(openssl_executable_path) + " x509 -in " + in_path + " -inform PEM -enddate -out " + out_path_openssl; + tool_command = std::string(tool_executable_path) + " x509 -in " + in_path + + " -inform PEM -enddate -out " + out_path_tool; + openssl_command = std::string(openssl_executable_path) + " x509 -in " + + in_path + " -inform PEM -enddate -out " + out_path_openssl; - RunCommandsAndCompareOutput(tool_command, openssl_command, out_path_tool, out_path_openssl, tool_output_str, openssl_output_str); + RunCommandsAndCompareOutput(tool_command, openssl_command, out_path_tool, + out_path_openssl, tool_output_str, + openssl_output_str); ASSERT_EQ(tool_output_str, openssl_output_str); } -// Test against OpenSSL output reading from stdin "openssl x509 -fingerprint -dates" +// Test against OpenSSL output reading from stdin "openssl x509 -fingerprint +// -dates" TEST_F(X509ComparisonTest, X509ToolCompareStdinFingerprintDatesOpenSSL) { - std::string tool_command = "cat " + std::string(in_path) + " | " + std::string(tool_executable_path) + " x509 -fingerprint -dates > " + out_path_tool; - std::string openssl_command = "cat " + std::string(in_path) + " | " + std::string(openssl_executable_path) + " x509 -fingerprint -dates > " + out_path_openssl; - - RunCommandsAndCompareOutput(tool_command, openssl_command, out_path_tool, out_path_openssl, tool_output_str, openssl_output_str); + std::string tool_command = "cat " + std::string(in_path) + " | " + + std::string(tool_executable_path) + + " x509 -fingerprint -dates > " + out_path_tool; + std::string openssl_command = "cat " + std::string(in_path) + " | " + + std::string(openssl_executable_path) + + " x509 -fingerprint -dates > " + + out_path_openssl; + + RunCommandsAndCompareOutput(tool_command, openssl_command, out_path_tool, + out_path_openssl, tool_output_str, + openssl_output_str); ASSERT_EQ(tool_output_str, openssl_output_str); - tool_command = "cat " + std::string(in_path) + " | " + std::string(tool_executable_path) + " x509 -fingerprint -dates -out " + out_path_tool; - openssl_command = "cat " + std::string(in_path) + " | " + std::string(openssl_executable_path) + " x509 -fingerprint -dates -out " + out_path_openssl; + tool_command = "cat " + std::string(in_path) + " | " + + std::string(tool_executable_path) + + " x509 -fingerprint -dates -out " + out_path_tool; + openssl_command = "cat " + std::string(in_path) + " | " + + std::string(openssl_executable_path) + + " x509 -fingerprint -dates -out " + out_path_openssl; - RunCommandsAndCompareOutput(tool_command, openssl_command, out_path_tool, out_path_openssl, tool_output_str, openssl_output_str); + RunCommandsAndCompareOutput(tool_command, openssl_command, out_path_tool, + out_path_openssl, tool_output_str, + openssl_output_str); ASSERT_EQ(tool_output_str, openssl_output_str); } + +// Test against OpenSSL output "openssl x509 -in in_file (-req) -signkey +// private_key_file -out out_file -outform PEM" +TEST_F(X509ComparisonTest, X509ToolCompareSignkeyOutformPEMOpenSSL) { + std::string tool_command = std::string(tool_executable_path) + " x509 -in " + + in_path + " -signkey " + signkey_path + + " -outform PEM -out " + out_path_tool; + std::string openssl_command = + std::string(openssl_executable_path) + " x509 -in " + in_path + + " -signkey " + signkey_path + " -outform PEM -out " + out_path_openssl; + + ExecuteCommand(tool_command); + ExecuteCommand(openssl_command); + + // Load certificates + auto cert_tool = LoadPEMCertificate(out_path_tool); + auto cert_openssl = LoadPEMCertificate(out_path_openssl); + + ASSERT_TRUE(cert_tool != nullptr) + << "Failed to load certificate generated by tool"; + ASSERT_TRUE(cert_openssl != nullptr) + << "Failed to load certificate generated by OpenSSL"; + + // Compare certificates in detail with 365 days validity period + ASSERT_TRUE(CompareCertificates(cert_tool.get(), cert_openssl.get(), + cert_tool.get(), 30)) + << "Certificates generated by tool and OpenSSL have different attributes"; + + + tool_command = std::string(tool_executable_path) + " x509 -in " + csr_path + + " -req -signkey " + signkey_path + " -outform PEM -out " + + out_path_tool; + openssl_command = std::string(openssl_executable_path) + " x509 -in " + + csr_path + " -req -signkey " + signkey_path + + " -outform PEM -out " + out_path_openssl; + + ExecuteCommand(tool_command); + ExecuteCommand(openssl_command); + + // Load certificates + cert_tool = LoadPEMCertificate(out_path_tool); + cert_openssl = LoadPEMCertificate(out_path_openssl); + + ASSERT_TRUE(cert_tool != nullptr) + << "Failed to load certificate generated by tool"; + ASSERT_TRUE(cert_openssl != nullptr) + << "Failed to load certificate generated by OpenSSL"; + + // Compare certificates in detail with 365 days validity period + ASSERT_TRUE(CompareCertificates(cert_tool.get(), cert_openssl.get(), + cert_tool.get(), 30)) + << "Certificates generated by tool and OpenSSL have different attributes"; +} + + +// Test against OpenSSL output "openssl x509 -in in_file -CA certfile -CAkey +// keyfile" +TEST_F(X509ComparisonTest, X509ToolCompareCAOpenSSL) { + std::string tool_command = std::string(tool_executable_path) + " x509 -in " + + in_path + " -CA " + ca_cert_path + " -out " + + out_path_tool; + std::string openssl_command = std::string(openssl_executable_path) + + " x509 -in " + in_path + " -CA " + + ca_cert_path + " -out " + out_path_openssl; + + ExecuteCommand(tool_command); + ExecuteCommand(openssl_command); + + // Load certificates + auto cert_tool = LoadPEMCertificate(out_path_tool); + auto cert_openssl = LoadPEMCertificate(out_path_openssl); + auto ca_cert = LoadPEMCertificate(ca_cert_path); + + ASSERT_TRUE(cert_tool != nullptr) + << "Failed to load certificate generated by tool"; + ASSERT_TRUE(cert_openssl != nullptr) + << "Failed to load certificate generated by OpenSSL"; + + // Compare certificates in detail with 365 days validity period + ASSERT_TRUE(CompareCertificates(cert_tool.get(), cert_openssl.get(), + ca_cert.get(), 30)) + << "Certificates generated by tool and OpenSSL have different attributes"; + + + tool_command = std::string(tool_executable_path) + " x509 -in " + in_path + + " -CA " + ca_cert_path + " -CAkey " + ca_key_path + " -out " + + out_path_tool; + openssl_command = std::string(openssl_executable_path) + " x509 -in " + + in_path + " -CA " + ca_cert_path + " -CAkey " + + ca_key_path + " -out " + out_path_openssl; + + ExecuteCommand(tool_command); + ExecuteCommand(openssl_command); + + // Load certificates + cert_tool = LoadPEMCertificate(out_path_tool); + cert_openssl = LoadPEMCertificate(out_path_openssl); + + ASSERT_TRUE(cert_tool != nullptr) + << "Failed to load certificate generated by tool"; + ASSERT_TRUE(cert_openssl != nullptr) + << "Failed to load certificate generated by OpenSSL"; + + // Compare certificates in detail with 365 days validity period + ASSERT_TRUE(CompareCertificates(cert_tool.get(), cert_openssl.get(), + ca_cert.get(), 30)) + << "Certificates generated by tool and OpenSSL have different attributes"; +} + +// Test against OpenSSL output "openssl x509 -in in_file -CA certfile -CAkey +// keyfile" +TEST_F(X509ComparisonTest, X509ToolCompareReqCAOpenSSL) { + std::string tool_command = std::string(tool_executable_path) + " x509 -in " + + csr_path + " -req -CA " + ca_cert_path + " -out " + + out_path_tool; + std::string openssl_command = std::string(openssl_executable_path) + + " x509 -in " + csr_path + " -req -CA " + + ca_cert_path + " -out " + out_path_openssl; + + ExecuteCommand(tool_command); + ExecuteCommand(openssl_command); + + std::cout << "AWS-LC CLI command: " << std::endl; + std::cout << tool_command << std::endl; + + // Load certificates + auto cert_tool = LoadPEMCertificate(out_path_tool); + auto cert_openssl = LoadPEMCertificate(out_path_openssl); + auto ca_cert = LoadPEMCertificate(ca_cert_path); + + ASSERT_TRUE(cert_tool != nullptr) + << "Failed to load certificate generated by tool"; + ASSERT_TRUE(cert_openssl != nullptr) + << "Failed to load certificate generated by OpenSSL"; + + // Compare certificates in detail with 365 days validity period + ASSERT_TRUE(CompareCertificates(cert_tool.get(), cert_openssl.get(), + ca_cert.get(), 30)) + << "Certificates generated by tool and OpenSSL have different attributes"; + + + tool_command = std::string(tool_executable_path) + " x509 -in " + csr_path + + " -req -CA " + ca_cert_path + " -CAkey " + ca_key_path + + " -out " + out_path_tool; + openssl_command = std::string(openssl_executable_path) + " x509 -in " + + csr_path + " -req -CA " + ca_cert_path + " -CAkey " + + ca_key_path + " -out " + out_path_openssl; + + ExecuteCommand(tool_command); + ExecuteCommand(openssl_command); + + // Load certificates + cert_tool = LoadPEMCertificate(out_path_tool); + cert_openssl = LoadPEMCertificate(out_path_openssl); + + ASSERT_TRUE(cert_tool != nullptr) + << "Failed to load certificate generated by tool"; + ASSERT_TRUE(cert_openssl != nullptr) + << "Failed to load certificate generated by OpenSSL"; + + // Compare certificates in detail with 365 days validity period + ASSERT_TRUE(CompareCertificates(cert_tool.get(), cert_openssl.get(), + ca_cert.get(), 30)) + << "Certificates generated by tool and OpenSSL have different attributes"; +} + +// Test against OpenSSL output "openssl x509 -extfile extfile -extensions +// extension_header" +TEST_F(X509ComparisonTest, X509ToolCompareExtensionsOpenSSL) { + char ext_path[PATH_MAX]; + ASSERT_GT(createTempFILEpath(ext_path), 0u); + + ScopedFILE ext_file(fopen(ext_path, "w")); + ASSERT_TRUE(ext_file); + fprintf(ext_file.get(), "[test_ext]\nbasicConstraints=CA:FALSE\n"); + fclose(ext_file.release()); + + std::string tool_command = std::string(tool_executable_path) + " x509 -in " + + csr_path + " -req -signkey " + signkey_path + + " -extfile " + ext_path + + " -extensions test_ext -out " + out_path_tool; + std::string openssl_command = + std::string(openssl_executable_path) + " x509 -in " + csr_path + + " -req -signkey " + signkey_path + " -extfile " + ext_path + + " -extensions test_ext -out " + out_path_openssl; + + ExecuteCommand(tool_command); + ExecuteCommand(openssl_command); + + auto cert_tool = LoadPEMCertificate(out_path_tool); + auto cert_openssl = LoadPEMCertificate(out_path_openssl); + + ASSERT_TRUE(cert_tool != nullptr); + ASSERT_TRUE(cert_openssl != nullptr); + + ASSERT_TRUE(CompareCertificates(cert_tool.get(), cert_openssl.get(), + cert_tool.get(), 30)); + + // Test extfile without -extensions (default section) + ext_file.reset(fopen(ext_path, "w")); + ASSERT_TRUE(ext_file); + fprintf( + ext_file.get(), + "basicConstraints=CA:FALSE\nkeyUsage=digitalSignature,keyEncipherment\n"); + fclose(ext_file.release()); + + tool_command = std::string(tool_executable_path) + " x509 -in " + csr_path + + " -req -signkey " + signkey_path + " -extfile " + ext_path + + " -out " + out_path_tool; + openssl_command = std::string(openssl_executable_path) + " x509 -in " + + csr_path + " -req -signkey " + signkey_path + " -extfile " + + ext_path + " -out " + out_path_openssl; + + ExecuteCommand(tool_command); + ExecuteCommand(openssl_command); + + cert_tool = LoadPEMCertificate(out_path_tool); + cert_openssl = LoadPEMCertificate(out_path_openssl); + + ASSERT_TRUE(cert_tool != nullptr); + ASSERT_TRUE(cert_openssl != nullptr); + + ASSERT_TRUE(CompareCertificates(cert_tool.get(), cert_openssl.get(), + cert_tool.get(), 30)); + + // Test extfile with extensions variable pointing to section + ext_file.reset(fopen(ext_path, "w")); + ASSERT_TRUE(ext_file); + fprintf(ext_file.get(), + "extensions=v3_req\n\n[v3_req]\nbasicConstraints=CA:FALSE\nkeyUsage=" + "digitalSignature,keyEncipherment\n"); + fclose(ext_file.release()); + + tool_command = std::string(tool_executable_path) + " x509 -in " + in_path + + " -signkey " + signkey_path + " -extfile " + ext_path + + " -out " + out_path_tool; + openssl_command = std::string(openssl_executable_path) + " x509 -in " + + in_path + " -signkey " + signkey_path + " -extfile " + + ext_path + " -out " + out_path_openssl; + + ExecuteCommand(tool_command); + ExecuteCommand(openssl_command); + + cert_tool = LoadPEMCertificate(out_path_tool); + cert_openssl = LoadPEMCertificate(out_path_openssl); + + ASSERT_TRUE(cert_tool != nullptr); + ASSERT_TRUE(cert_openssl != nullptr); + + ASSERT_TRUE(CompareCertificates(cert_tool.get(), cert_openssl.get(), + cert_tool.get(), 30)); + + RemoveFile(ext_path); +} From 0b6695f6b54b3f53b01933104fe5d204e0eeb4e6 Mon Sep 17 00:00:00 2001 From: Hailey Ho Date: Wed, 8 Oct 2025 23:33:21 +0000 Subject: [PATCH 02/14] Add new flag X509_CTX_REPLACE to allow for removing duplicate extensions --- crypto/x509/v3_conf.c | 31 ++++++++++++++++++---- include/openssl/x509.h | 58 +++++++++++++++++++++++------------------- tool-openssl/x509.cc | 2 +- 3 files changed, 59 insertions(+), 32 deletions(-) diff --git a/crypto/x509/v3_conf.c b/crypto/x509/v3_conf.c index f62e632fef..e84552858b 100644 --- a/crypto/x509/v3_conf.c +++ b/crypto/x509/v3_conf.c @@ -82,6 +82,8 @@ static X509_EXTENSION *do_ext_i2d(const X509V3_EXT_METHOD *method, int ext_nid, static unsigned char *generic_asn1(const char *value, const X509V3_CTX *ctx, size_t *ext_len); +static void delete_ext(STACK_OF(X509_EXTENSION) *sk, X509_EXTENSION *dext); + X509_EXTENSION *X509V3_EXT_nconf(const CONF *conf, const X509V3_CTX *ctx, const char *name, const char *value) { // If omitted, fill in an empty |X509V3_CTX|. @@ -344,6 +346,16 @@ static unsigned char *generic_asn1(const char *value, const X509V3_CTX *ctx, return ext_der; } +static void delete_ext(STACK_OF(X509_EXTENSION) *sk, X509_EXTENSION *dext) { + int idx; + ASN1_OBJECT *obj; + + obj = X509_EXTENSION_get_object(dext); + while ((idx = X509v3_get_ext_by_OBJ(sk, obj, -1)) >= 0) { + X509_EXTENSION_free(X509v3_delete_ext(sk, idx)); + } +} + // This is the main function: add a bunch of extensions based on a config // file section to an extension STACK. @@ -357,8 +369,19 @@ int X509V3_EXT_add_nconf_sk(const CONF *conf, const X509V3_CTX *ctx, for (size_t i = 0; i < sk_CONF_VALUE_num(nval); i++) { const CONF_VALUE *val = sk_CONF_VALUE_value(nval, i); X509_EXTENSION *ext = X509V3_EXT_nconf(conf, ctx, val->name, val->value); - int ok = ext != NULL && // - (sk == NULL || X509v3_add_ext(sk, ext, -1) != NULL); + + int ok = 0; + if (ext) { + if (!sk) { + ok = 1; + } else { + if (ctx->flags == X509V3_CTX_REPLACE) { + delete_ext(*sk, ext); + } + ok = X509v3_add_ext(sk, ext, -1) != NULL; + } + } + X509_EXTENSION_free(ext); if (!ok) { return 0; @@ -418,9 +441,7 @@ const STACK_OF(CONF_VALUE) *X509V3_get_section(const X509V3_CTX *ctx, return NCONF_get_section(ctx->db, section); } -void X509V3_set_nconf(X509V3_CTX *ctx, const CONF *conf) { - ctx->db = conf; -} +void X509V3_set_nconf(X509V3_CTX *ctx, const CONF *conf) { ctx->db = conf; } void X509V3_set_ctx(X509V3_CTX *ctx, const X509 *issuer, const X509 *subj, const X509_REQ *req, const X509_CRL *crl, int flags) { diff --git a/include/openssl/x509.h b/include/openssl/x509.h index 32ce545ea7..9efd6553f6 100644 --- a/include/openssl/x509.h +++ b/include/openssl/x509.h @@ -2602,7 +2602,8 @@ OPENSSL_EXPORT X509 *X509_OBJECT_get0_X509(const X509_OBJECT *obj); typedef STACK_OF(X509_CRL) *(*X509_STORE_CTX_lookup_crls_fn)( X509_STORE_CTX *ctx, X509_NAME *nm); -OPENSSL_EXPORT X509_STORE_CTX_lookup_crls_fn X509_STORE_get_lookup_crls(X509_STORE *ctx); +OPENSSL_EXPORT X509_STORE_CTX_lookup_crls_fn +X509_STORE_get_lookup_crls(X509_STORE *ctx); OPENSSL_EXPORT void X509_STORE_set_lookup_crls( X509_STORE *ctx, X509_STORE_CTX_lookup_crls_fn lookup_crls); @@ -2797,7 +2798,8 @@ OPENSSL_EXPORT X509 *X509_STORE_CTX_get0_cert(X509_STORE_CTX *ctx); // X509_STORE_CTX_get0_untrusted returns the stack of untrusted intermediates // used by |ctx| for certificate verification. -OPENSSL_EXPORT STACK_OF(X509) *X509_STORE_CTX_get0_untrusted(X509_STORE_CTX *ctx); +OPENSSL_EXPORT STACK_OF(X509) *X509_STORE_CTX_get0_untrusted( + X509_STORE_CTX *ctx); // X509_STORE_CTX_set0_trusted_stack configures |ctx| to trust the certificates // in |sk|. |sk| must remain valid for the duration of |ctx|. Calling this @@ -3123,19 +3125,21 @@ OPENSSL_EXPORT int X509_VERIFY_PARAM_add1_host(X509_VERIFY_PARAM *param, const char *name, size_t name_len); -// X509_CHECK_FLAG_ALWAYS_CHECK_SUBJECT enables always checking the subject name for host match -// even if subject alt names are present. +// X509_CHECK_FLAG_ALWAYS_CHECK_SUBJECT enables always checking the subject name +// for host match even if subject alt names are present. #define X509_CHECK_FLAG_ALWAYS_CHECK_SUBJECT 0x1 // X509_CHECK_FLAG_NO_WILDCARDS disables wildcard matching for DNS names. #define X509_CHECK_FLAG_NO_WILDCARDS 0x2 -// X509_CHECK_FLAG_SINGLE_LABEL_SUBDOMAINS constrains host name patterns passed to |X509_check_host| -// starting with '.' to only match a single label / subdomain. +// X509_CHECK_FLAG_SINGLE_LABEL_SUBDOMAINS constrains host name patterns passed +// to |X509_check_host| starting with '.' to only match a single label / +// subdomain. // -// For example, by default the host name '.example.com' would match a certificate DNS name like -// 'www.example.com' and 'www.foo.example.com'. Setting this flag would result in the same host name -// only matching 'www.example.com' but not 'www.foo.example.com'. +// For example, by default the host name '.example.com' would match a +// certificate DNS name like 'www.example.com' and 'www.foo.example.com'. +// Setting this flag would result in the same host name only matching +// 'www.example.com' but not 'www.foo.example.com'. #define X509_CHECK_FLAG_SINGLE_LABEL_SUBDOMAINS 0x10 // X509_CHECK_FLAG_NEVER_CHECK_SUBJECT disables the subject fallback, normally @@ -4687,6 +4691,7 @@ struct v3_ext_ctx { }; #define X509V3_CTX_TEST 0x1 +#define X509V3_CTX_REPLACE 0x2 // X509V3_set_ctx initializes |ctx| with the specified objects. Some string // formats will reference fields in these objects. Each object may be NULL to @@ -5006,7 +5011,8 @@ typedef int (*X509_STORE_CTX_verify_cb)(int, X509_STORE_CTX *); OPENSSL_EXPORT void X509_STORE_CTX_set_verify_cb( X509_STORE_CTX *ctx, int (*verify_cb)(int ok, X509_STORE_CTX *ctx)); -OPENSSL_EXPORT X509_STORE_CTX_verify_cb X509_STORE_get_verify_cb(X509_STORE *ctx); +OPENSSL_EXPORT X509_STORE_CTX_verify_cb +X509_STORE_get_verify_cb(X509_STORE *ctx); // X509_STORE_set_verify_cb acts like |X509_STORE_CTX_set_verify_cb| but sets // the verify callback for any |X509_STORE_CTX| created from this |X509_STORE| @@ -5081,17 +5087,17 @@ OPENSSL_EXPORT void X509_STORE_CTX_set0_untrusted(X509_STORE_CTX *ctx, #define NS_OBJSIGN_CA 0x01 #define NS_ANY_CA (NS_SSL_CA | NS_SMIME_CA | NS_OBJSIGN_CA) - typedef struct x509_purpose_st { - int purpose; - int trust; // Default trust ID - int flags; - int (*check_purpose)(const struct x509_purpose_st *, const X509 *, int); - char *name; - char *sname; - void *usr_data; - } X509_PURPOSE; +typedef struct x509_purpose_st { + int purpose; + int trust; // Default trust ID + int flags; + int (*check_purpose)(const struct x509_purpose_st *, const X509 *, int); + char *name; + char *sname; + void *usr_data; +} X509_PURPOSE; - DEFINE_STACK_OF(X509_PURPOSE) +DEFINE_STACK_OF(X509_PURPOSE) // X509_STORE_get0_objects returns a non-owning pointer of |store|'s internal // object list. Although this function is not const, callers must not modify @@ -5151,12 +5157,12 @@ DECLARE_STACK_OF(DIST_POINT) // This is used for a table of trust checking functions struct x509_trust_st { -int trust; -int flags; -int (*check_trust)(const X509_TRUST *, X509 *); -char *name; -int arg1; -void *arg2; + int trust; + int flags; + int (*check_trust)(const X509_TRUST *, X509 *); + char *name; + int arg1; + void *arg2; } /* X509_TRUST */; DEFINE_STACK_OF(X509_TRUST) diff --git a/tool-openssl/x509.cc b/tool-openssl/x509.cc index 0517b3aa93..3dec336990 100644 --- a/tool-openssl/x509.cc +++ b/tool-openssl/x509.cc @@ -133,7 +133,7 @@ static bool LoadExtensionsAndSignCertificate(const X509 *issuer, X509 *subject, X509V3_CTX ext_ctx; bssl::UniquePtr ext_conf(nullptr); - X509V3_set_ctx(&ext_ctx, issuer, subject, NULL, NULL, 0); + X509V3_set_ctx(&ext_ctx, issuer, subject, NULL, NULL, X509V3_CTX_REPLACE); if (ext_file_path.empty()) { if (!ext_section.empty()) { From abeeaf655a2b2d69e2602406d44c5734f5a64fb2 Mon Sep 17 00:00:00 2001 From: Hailey Ho Date: Wed, 8 Oct 2025 23:35:44 +0000 Subject: [PATCH 03/14] Improve test utility functions and add missing test cases --- tool-openssl/req_test.cc | 4 +- tool-openssl/test_util.cc | 115 ++++++++++++++++++++++++++++++-------- tool-openssl/x509.cc | 2 +- tool-openssl/x509_test.cc | 102 ++++++++++++++++++++++----------- 4 files changed, 164 insertions(+), 59 deletions(-) diff --git a/tool-openssl/req_test.cc b/tool-openssl/req_test.cc index 99bfe2a790..762850d323 100644 --- a/tool-openssl/req_test.cc +++ b/tool-openssl/req_test.cc @@ -163,8 +163,8 @@ TEST_F(ReqComparisonTest, GenerateSelfSignedCertificate) { << "Failed to load certificate generated by OpenSSL"; // Compare certificates in detail with 365 days validity period - ASSERT_TRUE(CompareCertificates(cert_tool.get(), cert_openssl.get(), - cert_tool.get(), 365)) + ASSERT_TRUE( + CompareCertificates(cert_tool.get(), cert_openssl.get(), nullptr, 365)) << "Certificates generated by tool and OpenSSL have different attributes"; } diff --git a/tool-openssl/test_util.cc b/tool-openssl/test_util.cc index da270d6679..12dff8070f 100644 --- a/tool-openssl/test_util.cc +++ b/tool-openssl/test_util.cc @@ -22,7 +22,7 @@ void CreateAndSignX509Certificate(bssl::UniquePtr &x509, return; } - EVP_PKEY *pkey = EVP_PKEY_new(); + bssl::UniquePtr pkey(EVP_PKEY_new()); if (!pkey) { fprintf(stderr, "Error creating new private key\n"); @@ -33,11 +33,11 @@ void CreateAndSignX509Certificate(bssl::UniquePtr &x509, bssl::UniquePtr bn(BN_new()); if (!bn || !BN_set_word(bn.get(), RSA_F4) || !RSA_generate_key_ex(rsa.get(), 2048, bn.get(), nullptr) || - !EVP_PKEY_assign_RSA(pkey, rsa.release())) { + !EVP_PKEY_assign_RSA(pkey.get(), rsa.release())) { fprintf(stderr, "Error generating new key\n"); return; } - if (!X509_set_pubkey(x509.get(), pkey)) { + if (!X509_set_pubkey(x509.get(), pkey.get())) { fprintf(stderr, "Error setting public key\n"); return; } @@ -86,15 +86,13 @@ void CreateAndSignX509Certificate(bssl::UniquePtr &x509, } X509_EXTENSION_free(ext); - if (X509_sign(x509.get(), pkey, EVP_sha256()) <= 0) { + if (X509_sign(x509.get(), pkey.get(), EVP_sha256()) <= 0) { fprintf(stderr, "Error signing certificate\n"); return; } if (pkey_p != nullptr) { - pkey_p->reset(pkey); - } else { - EVP_PKEY_free(pkey); + pkey_p->reset(pkey.release()); } } @@ -122,6 +120,17 @@ bssl::UniquePtr LoadPEMCertificate(const char *path) { return cert; } +// Load an X509 certificate from a DER file +bssl::UniquePtr LoadDERCertificate(const char *path) { + bssl::UniquePtr bio(BIO_new_file(path, "r")); + if (!bio) { + return NULL; + } + + bssl::UniquePtr cert(d2i_X509_bio(bio.get(), nullptr)); + return cert; +} + bool CompareCSRs(X509_REQ *csr1, X509_REQ *csr2) { if (!csr1 || !csr2) return false; @@ -192,7 +201,8 @@ bool CheckCertificateValidityPeriod(X509 *cert, int expected_days) { // Improved certificate comparison function bool CompareCertificates(X509 *cert1, X509 *cert2, X509 *ca_cert, int expected_days) { - if (!cert1 || !cert2 || !ca_cert) { + if (!cert1 || !cert2) { + std::cout << "Invalid certificates" << std::endl; return false; } @@ -200,6 +210,7 @@ bool CompareCertificates(X509 *cert1, X509 *cert2, X509 *ca_cert, X509_NAME *subj1 = X509_get_subject_name(cert1); X509_NAME *subj2 = X509_get_subject_name(cert2); if (X509_NAME_cmp(subj1, subj2) != 0) { + std::cout << "Certificates have different subject names" << std::endl; return false; } @@ -207,30 +218,55 @@ bool CompareCertificates(X509 *cert1, X509 *cert2, X509 *ca_cert, X509_NAME *issuer1 = X509_get_issuer_name(cert1); X509_NAME *issuer2 = X509_get_issuer_name(cert2); if (X509_NAME_cmp(issuer1, issuer2) != 0) { + std::cout << "Certificates have different issuer names" << std::endl; return false; } - // 3. Both certificates should be self-signed - X509_NAME *ca_name = X509_get_issuer_name(ca_cert); - if (X509_NAME_cmp(issuer1, ca_name) != 0) { - return false; - } - if (X509_NAME_cmp(issuer2, ca_name) != 0) { - return false; + // 3. Validate issuers + if (ca_cert) { + X509_NAME *ca_name = X509_get_issuer_name(ca_cert); + if (X509_NAME_cmp(issuer1, ca_name) != 0) { + std::cout << "AWS-LC certificate has the wrong issuer name" << std::endl; + return false; + } + if (X509_NAME_cmp(issuer2, ca_name) != 0) { + std::cout << "OpenSSL certificate has the wrong issuer name" << std::endl; + return false; + } + } else { + if (X509_NAME_cmp(subj1, issuer1) != 0) { + std::cout << "Issuer and subject names do not match for AWS-LC's " + "self-signed certificate" + << std::endl; + return false; + } + if (X509_NAME_cmp(subj2, issuer2) != 0) { + std::cout << "Issuer and subject names do not match for OpenSSL's " + "self-signed certificate" + << std::endl; + return false; + } } // 4. Compare signature algorithms int sig_nid1 = X509_get_signature_nid(cert1); int sig_nid2 = X509_get_signature_nid(cert2); if (sig_nid1 != sig_nid2) { + std::cout << "Certificates use different signature algorithms" << std::endl; return false; } // 5. Check validity periods if (!CheckCertificateValidityPeriod(cert1, expected_days)) { + std::cout + << "AWS-LC certificate's validity period is different than expected" + << std::endl; return false; } if (!CheckCertificateValidityPeriod(cert2, expected_days)) { + std::cout + << "OpenSSL certificate's validity period is different than expected" + << std::endl; return false; } @@ -238,10 +274,12 @@ bool CompareCertificates(X509 *cert1, X509 *cert2, X509 *ca_cert, EVP_PKEY *pkey1 = X509_get0_pubkey(cert1); EVP_PKEY *pkey2 = X509_get0_pubkey(cert2); if (!pkey1 || !pkey2) { + std::cout << "Certificates have different public keys" << std::endl; return false; } if (EVP_PKEY_id(pkey1) != EVP_PKEY_id(pkey2)) { + std::cout << "Certificates have different public key types" << std::endl; return false; } @@ -250,31 +288,54 @@ bool CompareCertificates(X509 *cert1, X509 *cert2, X509 *ca_cert, RSA *rsa1 = EVP_PKEY_get0_RSA(pkey1); RSA *rsa2 = EVP_PKEY_get0_RSA(pkey2); if (!rsa1 || !rsa2) { + std::cout << "Failed to obtain RSA keys from certificates" << std::endl; return false; } if (RSA_size(rsa1) != RSA_size(rsa2)) { + std::cout << "Certificates have different RSA key sizes" << std::endl; return false; } } // 7. Verify signatures - EVP_PKEY *ca_pkey = X509_get0_pubkey(ca_cert); - if (X509_verify(cert1, ca_pkey) != 1) { - return false; - } + if (ca_cert) { + EVP_PKEY *ca_pkey = X509_get0_pubkey(ca_cert); + if (!ca_pkey) { + std::cout << "Failed to parse CA public key" << std::endl; + return false; + } - if (X509_verify(cert2, ca_pkey) != 1) { - return false; + if (X509_verify(cert1, ca_pkey) != 1) { + std::cout << "Signature verification failed for AWS-LC's certificate" + << std::endl; + return false; + } + + if (X509_verify(cert2, ca_pkey) != 1) { + std::cout << "Signature verification failed for OpenSSL's certificate" + << std::endl; + return false; + } + } else { + if (X509_verify(cert1, pkey1) != 1) { + std::cout << "Signature verification failed for AWS-LC's certificate" + << std::endl; + return false; + } + + if (X509_verify(cert2, pkey2) != 1) { + std::cout << "Signature verification failed for OpenSSL's certificate" + << std::endl; + return false; + } } - // if (X509_verify(cert1, pkey1) != X509_verify(cert2, pkey2)) { - // return false; - // } // 8. Compare extensions - simplified approach int ext_count1 = X509_get_ext_count(cert1); int ext_count2 = X509_get_ext_count(cert2); if (ext_count1 != ext_count2) { + std::cout << "Certificates have different extension counts" << std::endl; return false; } @@ -283,6 +344,7 @@ bool CompareCertificates(X509 *cert1, X509 *cert2, X509 *ca_cert, X509_EXTENSION *ext1 = X509_get_ext(cert1, i); X509_EXTENSION *ext2 = X509_get_ext(cert2, i); if (!ext1 || !ext2) { + std::cout << "Failed to obtain extensions at location " << i << std::endl; return false; } @@ -290,16 +352,21 @@ bool CompareCertificates(X509 *cert1, X509 *cert2, X509 *ca_cert, ASN1_OBJECT *obj1 = X509_EXTENSION_get_object(ext1); ASN1_OBJECT *obj2 = X509_EXTENSION_get_object(ext2); if (!obj1 || !obj2) { + std::cout << "Failed to obtain extension OID" << std::endl; return false; } if (OBJ_cmp(obj1, obj2) != 0) { + std::cout << "Extension content within the certificates do not match" + << std::endl; return false; } // Compare critical flags if (X509_EXTENSION_get_critical(ext1) != X509_EXTENSION_get_critical(ext2)) { + std::cout << "Certificates have different extension critical flags" + << std::endl; return false; } } diff --git a/tool-openssl/x509.cc b/tool-openssl/x509.cc index 3dec336990..ab7716d8fe 100644 --- a/tool-openssl/x509.cc +++ b/tool-openssl/x509.cc @@ -241,7 +241,7 @@ static bool LoadCA(bssl::UniquePtr &ca, bssl::UniquePtr &ca_key, ScopedFILE ca_file(fopen(ca_file_path.c_str(), "rb")); if (!ca_file) { - fprintf(stderr, "Error: "); + fprintf(stderr, "Error: Failed to open CA file"); return false; } diff --git a/tool-openssl/x509_test.cc b/tool-openssl/x509_test.cc index 7aa5acc3ea..0ddb2148fc 100644 --- a/tool-openssl/x509_test.cc +++ b/tool-openssl/x509_test.cc @@ -470,23 +470,6 @@ static std::string normalize_subject(std::string input) { return input; } -// Test against OpenSSL output "openssl x509 -req -in csr_file -signkey -// private_key_file" -// TEST_F(X509ComparisonTest, X509ToolCompareReqOpenSSL) { -// std::string tool_command = std::string(tool_executable_path) + -// " x509 -req -in " + csr_path + " > " + -// out_path_tool; -// std::string openssl_command = std::string(openssl_executable_path) + -// " x509 -req -in " + csr_path + " > " + -// out_path_openssl; - -// RunCommandsAndCompareOutput(tool_command, openssl_command, out_path_tool, -// out_path_openssl, tool_output_str, -// openssl_output_str); - -// ASSERT_EQ(tool_output_str, openssl_output_str); -// } - // Test against OpenSSL output "openssl x509 -in file -text -noout" TEST_F(X509ComparisonTest, X509ToolCompareTextOpenSSL) { std::string tool_command = std::string(tool_executable_path) + " x509 -in " + @@ -795,8 +778,8 @@ TEST_F(X509ComparisonTest, X509ToolCompareReqSignkeyDaysOpenSSL) { << "Failed to load certificate generated by OpenSSL"; // Compare certificates in detail with 365 days validity period - ASSERT_TRUE(CompareCertificates(cert_tool.get(), cert_openssl.get(), - cert_tool.get(), 80)) + ASSERT_TRUE( + CompareCertificates(cert_tool.get(), cert_openssl.get(), nullptr, 80)) << "Certificates generated by tool and OpenSSL have different attributes"; } @@ -962,8 +945,8 @@ TEST_F(X509ComparisonTest, X509ToolCompareSignkeyOutformPEMOpenSSL) { << "Failed to load certificate generated by OpenSSL"; // Compare certificates in detail with 365 days validity period - ASSERT_TRUE(CompareCertificates(cert_tool.get(), cert_openssl.get(), - cert_tool.get(), 30)) + ASSERT_TRUE( + CompareCertificates(cert_tool.get(), cert_openssl.get(), nullptr, 30)) << "Certificates generated by tool and OpenSSL have different attributes"; @@ -987,8 +970,60 @@ TEST_F(X509ComparisonTest, X509ToolCompareSignkeyOutformPEMOpenSSL) { << "Failed to load certificate generated by OpenSSL"; // Compare certificates in detail with 365 days validity period - ASSERT_TRUE(CompareCertificates(cert_tool.get(), cert_openssl.get(), - cert_tool.get(), 30)) + ASSERT_TRUE( + CompareCertificates(cert_tool.get(), cert_openssl.get(), nullptr, 30)) + << "Certificates generated by tool and OpenSSL have different attributes"; +} + +// Test against OpenSSL output "openssl x509 -in in_file (-req) -signkey +// private_key_file -out out_file -outform DER" +TEST_F(X509ComparisonTest, X509ToolCompareSignkeyOutformDEROpenSSL) { + std::string tool_command = std::string(tool_executable_path) + " x509 -in " + + in_path + " -signkey " + signkey_path + + " -outform DER -out " + out_path_tool; + std::string openssl_command = + std::string(openssl_executable_path) + " x509 -in " + in_path + + " -signkey " + signkey_path + " -outform DER -out " + out_path_openssl; + + ExecuteCommand(tool_command); + ExecuteCommand(openssl_command); + + // Load certificates + auto cert_tool = LoadDERCertificate(out_path_tool); + auto cert_openssl = LoadDERCertificate(out_path_openssl); + + ASSERT_TRUE(cert_tool != nullptr) + << "Failed to load certificate generated by tool"; + ASSERT_TRUE(cert_openssl != nullptr) + << "Failed to load certificate generated by OpenSSL"; + + // Compare certificates in detail with 30 days validity period + ASSERT_TRUE( + CompareCertificates(cert_tool.get(), cert_openssl.get(), nullptr, 30)) + << "Certificates generated by tool and OpenSSL have different attributes"; + + tool_command = std::string(tool_executable_path) + " x509 -in " + csr_path + + " -req -signkey " + signkey_path + " -outform DER -out " + + out_path_tool; + openssl_command = std::string(openssl_executable_path) + " x509 -in " + + csr_path + " -req -signkey " + signkey_path + + " -outform DER -out " + out_path_openssl; + + ExecuteCommand(tool_command); + ExecuteCommand(openssl_command); + + // Load certificates + cert_tool = LoadDERCertificate(out_path_tool); + cert_openssl = LoadDERCertificate(out_path_openssl); + + ASSERT_TRUE(cert_tool != nullptr) + << "Failed to load certificate generated by tool"; + ASSERT_TRUE(cert_openssl != nullptr) + << "Failed to load certificate generated by OpenSSL"; + + // Compare certificates in detail with 30 days validity period + ASSERT_TRUE( + CompareCertificates(cert_tool.get(), cert_openssl.get(), nullptr, 30)) << "Certificates generated by tool and OpenSSL have different attributes"; } @@ -1060,9 +1095,6 @@ TEST_F(X509ComparisonTest, X509ToolCompareReqCAOpenSSL) { ExecuteCommand(tool_command); ExecuteCommand(openssl_command); - std::cout << "AWS-LC CLI command: " << std::endl; - std::cout << tool_command << std::endl; - // Load certificates auto cert_tool = LoadPEMCertificate(out_path_tool); auto cert_openssl = LoadPEMCertificate(out_path_openssl); @@ -1133,8 +1165,8 @@ TEST_F(X509ComparisonTest, X509ToolCompareExtensionsOpenSSL) { ASSERT_TRUE(cert_tool != nullptr); ASSERT_TRUE(cert_openssl != nullptr); - ASSERT_TRUE(CompareCertificates(cert_tool.get(), cert_openssl.get(), - cert_tool.get(), 30)); + ASSERT_TRUE( + CompareCertificates(cert_tool.get(), cert_openssl.get(), nullptr, 30)); // Test extfile without -extensions (default section) ext_file.reset(fopen(ext_path, "w")); @@ -1160,8 +1192,8 @@ TEST_F(X509ComparisonTest, X509ToolCompareExtensionsOpenSSL) { ASSERT_TRUE(cert_tool != nullptr); ASSERT_TRUE(cert_openssl != nullptr); - ASSERT_TRUE(CompareCertificates(cert_tool.get(), cert_openssl.get(), - cert_tool.get(), 30)); + ASSERT_TRUE( + CompareCertificates(cert_tool.get(), cert_openssl.get(), nullptr, 30)); // Test extfile with extensions variable pointing to section ext_file.reset(fopen(ext_path, "w")); @@ -1181,14 +1213,20 @@ TEST_F(X509ComparisonTest, X509ToolCompareExtensionsOpenSSL) { ExecuteCommand(tool_command); ExecuteCommand(openssl_command); + std::cout << "AWS-LC CLI command: " << std::endl; + std::cout << tool_command << std::endl; + + std::cout << "OpenSSL CLI command: " << std::endl; + std::cout << openssl_command << std::endl; + cert_tool = LoadPEMCertificate(out_path_tool); cert_openssl = LoadPEMCertificate(out_path_openssl); ASSERT_TRUE(cert_tool != nullptr); ASSERT_TRUE(cert_openssl != nullptr); - ASSERT_TRUE(CompareCertificates(cert_tool.get(), cert_openssl.get(), - cert_tool.get(), 30)); + ASSERT_TRUE( + CompareCertificates(cert_tool.get(), cert_openssl.get(), nullptr, 30)); RemoveFile(ext_path); } From de70d1a613f1baad4051eab1f649e8024b5c7c3e Mon Sep 17 00:00:00 2001 From: Hailey Ho Date: Wed, 8 Oct 2025 23:38:26 +0000 Subject: [PATCH 04/14] Incorporate feedback --- tool-openssl/internal.h | 2 -- tool-openssl/x509.cc | 24 ++++++++++++++---------- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/tool-openssl/internal.h b/tool-openssl/internal.h index 56ee835fb8..78cc0032e2 100644 --- a/tool-openssl/internal.h +++ b/tool-openssl/internal.h @@ -82,8 +82,6 @@ BSSL_NAMESPACE_BEGIN BORINGSSL_MAKE_DELETER(std::string, pass_util::SensitiveStringDeleter) BSSL_NAMESPACE_END -// bool LoadPrivateKeyAndSignCertificate(X509 *x509, -// const std::string &signkey_path); EVP_PKEY *CreateTestKey(int key_bits); tool_func_t FindTool(const std::string &name); diff --git a/tool-openssl/x509.cc b/tool-openssl/x509.cc index ab7716d8fe..8b39044d28 100644 --- a/tool-openssl/x509.cc +++ b/tool-openssl/x509.cc @@ -249,7 +249,7 @@ static bool LoadCA(bssl::UniquePtr &ca, bssl::UniquePtr &ca_key, nullptr)); // TODO: add password obtained from -passin if (!ca) { - fprintf(stderr, "Error: "); + fprintf(stderr, "Error: Failed to read CA cert from file"); return false; } @@ -257,7 +257,7 @@ static bool LoadCA(bssl::UniquePtr &ca, bssl::UniquePtr &ca_key, ScopedFILE ca_key_file(fopen(ca_key_path.c_str(), "rb")); if (!ca_key_file) { - fprintf(stderr, "Error: "); + fprintf(stderr, "Error: Failed to open CA key file"); return false; } @@ -268,7 +268,7 @@ static bool LoadCA(bssl::UniquePtr &ca, bssl::UniquePtr &ca_key, } if (!ca_key) { - fprintf(stderr, "Error: "); + fprintf(stderr, "Error: Failed to parse CA key from file"); return false; } @@ -630,13 +630,17 @@ bool X509Tool(const args_list_t &args) { } // Set the public key from CSR - bssl::UniquePtr csr_pkey(X509_REQ_get_pubkey(csr.get())); - if ((!signkey_path.empty() && !csr_pkey) || - !X509_set_pubkey(x509.get(), - !signkey_path.empty() ? pkey.get() : csr_pkey.get())) { - fprintf(stderr, - "Error: unable to set public key from either CSR or a provided " - "key\n"); + // Set the public key based on provided options: + // - If no signkey provided: use public key from the CSR + // - If signkey provided: use public key from the signkey + if (signkey_path.empty()) { + bssl::UniquePtr csr_pkey(X509_REQ_get_pubkey(csr.get())); + if (!csr_pkey || !X509_set_pubkey(x509.get(), csr_pkey.get())) { + fprintf(stderr, "Error: unable to set public key from CSR\n"); + return false; + } + } else if (!X509_set_pubkey(x509.get(), pkey.get())) { + fprintf(stderr, "Error: unable to set public key from provided key\n"); return false; } From 4adc0e2d203e7cad4d21d558279d1b5bec1e5dd4 Mon Sep 17 00:00:00 2001 From: Hailey Ho Date: Thu, 9 Oct 2025 00:41:55 +0000 Subject: [PATCH 05/14] nit --- crypto/x509/v3_conf.c | 4 ++-- tool-openssl/test_util.cc | 22 ++++++++++++++-------- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/crypto/x509/v3_conf.c b/crypto/x509/v3_conf.c index e84552858b..94dd651486 100644 --- a/crypto/x509/v3_conf.c +++ b/crypto/x509/v3_conf.c @@ -347,8 +347,8 @@ static unsigned char *generic_asn1(const char *value, const X509V3_CTX *ctx, } static void delete_ext(STACK_OF(X509_EXTENSION) *sk, X509_EXTENSION *dext) { - int idx; - ASN1_OBJECT *obj; + int idx = 0; + ASN1_OBJECT *obj = NULL; obj = X509_EXTENSION_get_object(dext); while ((idx = X509v3_get_ext_by_OBJ(sk, obj, -1)) >= 0) { diff --git a/tool-openssl/test_util.cc b/tool-openssl/test_util.cc index 12dff8070f..a5e8a75874 100644 --- a/tool-openssl/test_util.cc +++ b/tool-openssl/test_util.cc @@ -70,13 +70,14 @@ void CreateAndSignX509Certificate(bssl::UniquePtr &x509, X509V3_set_ctx_nodb(&ctx); X509V3_set_ctx(&ctx, x509.get(), x509.get(), nullptr, nullptr, 0); - X509_EXTENSION *ext; + X509_EXTENSION *ext = nullptr; if (!(ext = X509V3_EXT_conf_nid(nullptr, &ctx, NID_basic_constraints, const_cast("critical,CA:TRUE"))) || !X509_add_ext(x509.get(), ext, -1)) { fprintf(stderr, "Error setting extension\n"); return; } + X509_EXTENSION_free(ext); if (!(ext = X509V3_EXT_conf_nid(nullptr, &ctx, NID_subject_key_identifier, const_cast("hash"))) || @@ -132,20 +133,23 @@ bssl::UniquePtr LoadDERCertificate(const char *path) { } bool CompareCSRs(X509_REQ *csr1, X509_REQ *csr2) { - if (!csr1 || !csr2) + if (!csr1 || !csr2) { return false; + } // 1. Compare subjects X509_NAME *name1 = X509_REQ_get_subject_name(csr1); X509_NAME *name2 = X509_REQ_get_subject_name(csr2); - if (X509_NAME_cmp(name1, name2) != 0) + if (X509_NAME_cmp(name1, name2) != 0) { return false; + } // 2. Compare signature algorithms int sig_nid1 = X509_REQ_get_signature_nid(csr1); int sig_nid2 = X509_REQ_get_signature_nid(csr2); - if (sig_nid1 != sig_nid2) + if (sig_nid1 != sig_nid2) { return false; + } // 3. Compare public key type and parameters EVP_PKEY *pkey1 = X509_REQ_get0_pubkey(csr1); @@ -181,16 +185,18 @@ bool CompareCSRs(X509_REQ *csr1, X509_REQ *csr2) { } bool CheckCertificateValidityPeriod(X509 *cert, int expected_days) { - if (!cert) + if (!cert) { return false; + } const ASN1_TIME *not_before = X509_get0_notBefore(cert); const ASN1_TIME *not_after = X509_get0_notAfter(cert); - if (!not_before || !not_after) + if (!not_before || !not_after) { return false; + } // Get the difference in days between not_before and not_after - int days, seconds; + int days = 0, seconds = 0; if (!ASN1_TIME_diff(&days, &seconds, not_before, not_after)) { return false; } @@ -372,4 +378,4 @@ bool CompareCertificates(X509 *cert1, X509 *cert2, X509 *ca_cert, } return true; -} \ No newline at end of file +} From 9aec823c91d402600aa06450cef0de116dd20898 Mon Sep 17 00:00:00 2001 From: Hailey Ho Date: Mon, 13 Oct 2025 16:46:26 +0000 Subject: [PATCH 06/14] Implement -passin --- tool-openssl/x509.cc | 33 +++++++---- tool-openssl/x509_test.cc | 120 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 142 insertions(+), 11 deletions(-) diff --git a/tool-openssl/x509.cc b/tool-openssl/x509.cc index 8b39044d28..2679f6a947 100644 --- a/tool-openssl/x509.cc +++ b/tool-openssl/x509.cc @@ -31,6 +31,7 @@ static const argument_t kArguments[] = { {"-out", kOptionalArgument, "Filepath to write all output to, if not set write to stdout"}, {"-outform", kOptionalArgument, "Output format (DER or PEM) - default PEM"}, + {"-passin", kOptionalArgument, "Private key pass-phrase source"}, // Micro-CA {"-CA", kOptionalArgument, "Use the given CA certificate to sign certificates. Cannot be used with " @@ -216,15 +217,16 @@ static bool LoadExtensionsAndSignCertificate(const X509 *issuer, X509 *subject, } static bool LoadPrivateKey(bssl::UniquePtr &pkey, - const std::string &signkey_path) { + const std::string &signkey_path, + bssl::UniquePtr &passin) { ScopedFILE signkey_file(fopen(signkey_path.c_str(), "rb")); if (!signkey_file) { fprintf(stderr, "Error: unable to load private key from '%s'\n", signkey_path.c_str()); return false; } - pkey.reset( - PEM_read_PrivateKey(signkey_file.get(), nullptr, nullptr, nullptr)); + pkey.reset(PEM_read_PrivateKey(signkey_file.get(), nullptr, nullptr, + const_cast(passin->c_str()))); if (!pkey) { fprintf(stderr, "Error: error reading private key from '%s'\n", signkey_path.c_str()); @@ -237,7 +239,8 @@ static bool LoadPrivateKey(bssl::UniquePtr &pkey, static bool LoadCA(bssl::UniquePtr &ca, bssl::UniquePtr &ca_key, const std::string &ca_file_path, - const std::string &ca_key_path) { + const std::string &ca_key_path, + bssl::UniquePtr &passin) { ScopedFILE ca_file(fopen(ca_file_path.c_str(), "rb")); if (!ca_file) { @@ -245,8 +248,7 @@ static bool LoadCA(bssl::UniquePtr &ca, bssl::UniquePtr &ca_key, return false; } - ca.reset(PEM_read_X509(ca_file.get(), nullptr, nullptr, - nullptr)); // TODO: add password obtained from -passin + ca.reset(PEM_read_X509(ca_file.get(), nullptr, nullptr, nullptr)); if (!ca) { fprintf(stderr, "Error: Failed to read CA cert from file"); @@ -261,10 +263,11 @@ static bool LoadCA(bssl::UniquePtr &ca, bssl::UniquePtr &ca_key, return false; } - ca_key.reset( - PEM_read_PrivateKey(ca_key_file.get(), nullptr, nullptr, nullptr)); + ca_key.reset(PEM_read_PrivateKey(ca_key_file.get(), nullptr, nullptr, + const_cast(passin->c_str()))); } else { - ca_key.reset(PEM_read_PrivateKey(ca_file.get(), nullptr, nullptr, nullptr)); + ca_key.reset(PEM_read_PrivateKey(ca_file.get(), nullptr, nullptr, + const_cast(passin->c_str()))); } if (!ca_key) { @@ -468,6 +471,7 @@ bool X509Tool(const args_list_t &args) { ca_file_path, ca_key_path, ext_file_path, ext_section; bool noout = false, dates = false, req = false, help = false; std::unique_ptr days; + bssl::UniquePtr passin(new std::string()); ordered_args::GetBoolArgument(&help, "-help", parsed_args); ordered_args::GetString(&in_path, "-in", "", parsed_args); @@ -482,6 +486,7 @@ bool X509Tool(const args_list_t &args) { ordered_args::GetString(&ca_key_path, "-CAkey", "", parsed_args); ordered_args::GetString(&ext_file_path, "-extfile", "", parsed_args); ordered_args::GetString(&ext_section, "-extensions", "", parsed_args); + ordered_args::GetString(passin.get(), "-passin", "", parsed_args); // Display x509 tool option summary if (help) { @@ -575,6 +580,12 @@ bool X509Tool(const args_list_t &args) { } } + // Extract password + if (!passin->empty() && !pass_util::ExtractPassword(passin)) { + fprintf(stderr, "Error: Failed to extract password\n"); + return false; + } + // Read from stdin if no -in path provided ScopedFILE in_file; if (in_path.empty()) { @@ -592,11 +603,11 @@ bool X509Tool(const args_list_t &args) { bssl::UniquePtr ca; bssl::UniquePtr pkey; if (!ca_file_path.empty()) { - if (!LoadCA(ca, pkey, ca_file_path, ca_key_path)) { + if (!LoadCA(ca, pkey, ca_file_path, ca_key_path, passin)) { return false; } } else if (!signkey_path.empty()) { - if (!LoadPrivateKey(pkey, signkey_path)) { + if (!LoadPrivateKey(pkey, signkey_path, passin)) { return false; } } diff --git a/tool-openssl/x509_test.cc b/tool-openssl/x509_test.cc index 0ddb2148fc..fc1aa48dfa 100644 --- a/tool-openssl/x509_test.cc +++ b/tool-openssl/x509_test.cc @@ -19,6 +19,9 @@ class X509Test : public ::testing::Test { ASSERT_GT(createTempFILEpath(der_cert_path), 0u); ASSERT_GT(createTempFILEpath(ca_cert_path), 0u); ASSERT_GT(createTempFILEpath(ca_key_path), 0u); + ASSERT_GT(createTempFILEpath(protected_signkey_path), 0u); + ASSERT_GT(createTempFILEpath(protected_ca_cert_path), 0u); + ASSERT_GT(createTempFILEpath(protected_ca_key_path), 0u); bssl::UniquePtr x509; CreateAndSignX509Certificate(x509, nullptr); @@ -38,6 +41,12 @@ class X509Test : public ::testing::Test { ASSERT_TRUE(PEM_write_PrivateKey(signkey_file.get(), pkey.get(), nullptr, nullptr, 0, nullptr, nullptr)); + ScopedFILE protected_signkey_file(fopen(protected_signkey_path, "wb")); + ASSERT_TRUE(protected_signkey_file); + ASSERT_TRUE(PEM_write_PrivateKey( + protected_signkey_file.get(), pkey.get(), EVP_aes_256_cbc(), + (unsigned char *)"testpassword", 12, nullptr, nullptr)); + ScopedFILE in_file(fopen(in_path, "wb")); ASSERT_TRUE(in_file); ASSERT_TRUE(PEM_write_X509(in_file.get(), x509.get())); @@ -69,10 +78,24 @@ class X509Test : public ::testing::Test { ASSERT_TRUE(PEM_write_PrivateKey(ca_cert_file.get(), ca_pkey.get(), nullptr, nullptr, 0, nullptr, nullptr)); + ScopedFILE protected_ca_cert_file(fopen(protected_ca_cert_path, "wb")); + ASSERT_TRUE(protected_ca_cert_file); + ASSERT_TRUE(PEM_write_X509(protected_ca_cert_file.get(), ca.get())); + ASSERT_TRUE(PEM_write_PrivateKey( + protected_ca_cert_file.get(), ca_pkey.get(), EVP_aes_256_cbc(), + (unsigned char *)"testpassword", 12, nullptr, nullptr)); + + ScopedFILE ca_key_file(fopen(ca_key_path, "wb")); ASSERT_TRUE(ca_key_file); ASSERT_TRUE(PEM_write_PrivateKey(ca_key_file.get(), ca_pkey.get(), nullptr, nullptr, 0, nullptr, nullptr)); + + ScopedFILE protected_ca_key_file(fopen(protected_ca_key_path, "wb")); + ASSERT_TRUE(protected_ca_key_file); + ASSERT_TRUE(PEM_write_PrivateKey( + protected_ca_key_file.get(), ca_pkey.get(), EVP_aes_256_cbc(), + (unsigned char *)"testpassword", 12, nullptr, nullptr)); } void TearDown() override { RemoveFile(in_path); @@ -82,7 +105,11 @@ class X509Test : public ::testing::Test { RemoveFile(der_cert_path); RemoveFile(ca_cert_path); RemoveFile(ca_key_path); + RemoveFile(protected_signkey_path); + RemoveFile(protected_ca_cert_path); + RemoveFile(protected_ca_key_path); } + char in_path[PATH_MAX]; char csr_path[PATH_MAX]; char out_path[PATH_MAX]; @@ -90,6 +117,9 @@ class X509Test : public ::testing::Test { char der_cert_path[PATH_MAX]; char ca_cert_path[PATH_MAX]; char ca_key_path[PATH_MAX]; + char protected_signkey_path[PATH_MAX]; + char protected_ca_cert_path[PATH_MAX]; + char protected_ca_key_path[PATH_MAX]; }; // ----------------------------- X509 Option Tests ----------------------------- @@ -283,6 +313,43 @@ TEST_F(X509Test, X509ToolExtensionTest) { RemoveFile(ext_path); } +// Test -passin with -signkey +TEST_F(X509Test, X509ToolPassinSignkeyTest) { + args_list_t args = {"-in", in_path, + "-signkey", protected_signkey_path, + "-passin", "pass:testpassword"}; + bool result = X509Tool(args); + ASSERT_TRUE(result); +} + +// Test -passin with -CA (key in CA file) +TEST_F(X509Test, X509ToolPassinCATest) { + args_list_t args = {"-in", in_path, + "-CA", protected_ca_cert_path, + "-passin", "pass:testpassword"}; + bool result = X509Tool(args); + ASSERT_TRUE(result); +} + +// Test -passin with -CA and -CAkey +TEST_F(X509Test, X509ToolPassinCAkeyTest) { + args_list_t args = {"-in", in_path, + "-CA", ca_cert_path, + "-CAkey", protected_ca_key_path, + "-passin", "pass:testpassword"}; + bool result = X509Tool(args); + ASSERT_TRUE(result); +} + +// Test -passin with -req and -signkey +TEST_F(X509Test, X509ToolPassinReqSignkeyTest) { + args_list_t args = { + "-in", csr_path, "-req", "-signkey", protected_signkey_path, + "-passin", "pass:testpassword"}; + bool result = X509Tool(args); + ASSERT_TRUE(result); +} + // -------------------- X590 Option Usage Error Tests -------------------------- class X509OptionUsageErrorsTest : public X509Test { @@ -348,6 +415,21 @@ TEST_F(X509OptionUsageErrorsTest, InvalidArgTests) { } } +// Test -passin with invalid formats and wrong passwords +TEST_F(X509OptionUsageErrorsTest, InvalidPassinTest) { + std::vector> testparams = { + {"-in", in_path, "-signkey", protected_signkey_path, "-passin", + "pass:wrongpassword"}, + {"-in", in_path, "-CA", protected_ca_cert_path, "-passin", + "pass:wrongpassword"}, + {"-in", in_path, "-CA", ca_cert_path, "-CAkey", protected_ca_key_path, + "-passin", "pass:wrongpassword"}, + {"-in", in_path, "-signkey", signkey_path, "-passin", "invalid:format"}}; + for (const auto &args : testparams) { + TestOptionUsageErrors(args); + } +} + // -------------------- X509 OpenSSL Comparison Tests -------------------------- // Comparison tests cannot run without set up of environment variables: @@ -372,6 +454,7 @@ class X509ComparisonTest : public ::testing::Test { ASSERT_GT(createTempFILEpath(der_cert_path), 0u); ASSERT_GT(createTempFILEpath(ca_cert_path), 0u); ASSERT_GT(createTempFILEpath(ca_key_path), 0u); + ASSERT_GT(createTempFILEpath(protected_signkey_path), 0u); CreateAndSignX509Certificate(x509, nullptr); ASSERT_TRUE(x509); @@ -396,6 +479,13 @@ class X509ComparisonTest : public ::testing::Test { ASSERT_TRUE(PEM_write_PrivateKey(signkey_file.get(), pkey.get(), nullptr, nullptr, 0, nullptr, nullptr)); + // Create password-protected private key for testing -passin + ScopedFILE protected_signkey_file(fopen(protected_signkey_path, "wb")); + ASSERT_TRUE(protected_signkey_file); + ASSERT_TRUE(PEM_write_PrivateKey( + protected_signkey_file.get(), pkey.get(), EVP_aes_256_cbc(), + (unsigned char *)"testpassword", 12, nullptr, nullptr)); + csr.reset(X509_REQ_new()); ASSERT_TRUE(csr); X509_REQ_set_pubkey(csr.get(), pkey.get()); @@ -434,6 +524,7 @@ class X509ComparisonTest : public ::testing::Test { RemoveFile(der_cert_path); RemoveFile(ca_cert_path); RemoveFile(ca_key_path); + RemoveFile(protected_signkey_path); } } @@ -445,6 +536,7 @@ class X509ComparisonTest : public ::testing::Test { char der_cert_path[PATH_MAX]; char ca_cert_path[PATH_MAX]; char ca_key_path[PATH_MAX]; + char protected_signkey_path[PATH_MAX]; bssl::UniquePtr x509; bssl::UniquePtr csr; const char *tool_executable_path; @@ -1230,3 +1322,31 @@ TEST_F(X509ComparisonTest, X509ToolCompareExtensionsOpenSSL) { RemoveFile(ext_path); } + +// Test against OpenSSL output with -passin option +TEST_F(X509ComparisonTest, X509ToolComparePassinOpenSSL) { + std::string tool_command = std::string(tool_executable_path) + " x509 -in " + + in_path + " -signkey " + protected_signkey_path + + " -passin pass:testpassword -out " + out_path_tool; + std::string openssl_command = + std::string(openssl_executable_path) + " x509 -in " + in_path + + " -signkey " + protected_signkey_path + + " -passin pass:testpassword -out " + out_path_openssl; + + ExecuteCommand(tool_command); + ExecuteCommand(openssl_command); + + // Load certificates + auto cert_tool = LoadPEMCertificate(out_path_tool); + auto cert_openssl = LoadPEMCertificate(out_path_openssl); + + ASSERT_TRUE(cert_tool != nullptr) + << "Failed to load certificate generated by tool"; + ASSERT_TRUE(cert_openssl != nullptr) + << "Failed to load certificate generated by OpenSSL"; + + // Compare certificates in detail with 30 days validity period + ASSERT_TRUE( + CompareCertificates(cert_tool.get(), cert_openssl.get(), nullptr, 30)) + << "Certificates generated by tool and OpenSSL have different attributes"; +} From 8193aedb77311694381e7362643a05a002419d13 Mon Sep 17 00:00:00 2001 From: Hailey Ho Date: Mon, 13 Oct 2025 16:47:59 +0000 Subject: [PATCH 07/14] nit --- tool-openssl/test_util.cc | 6 +++--- tool-openssl/test_util.h | 9 --------- 2 files changed, 3 insertions(+), 12 deletions(-) diff --git a/tool-openssl/test_util.cc b/tool-openssl/test_util.cc index a5e8a75874..c04bac113b 100644 --- a/tool-openssl/test_util.cc +++ b/tool-openssl/test_util.cc @@ -101,7 +101,7 @@ void CreateAndSignX509Certificate(bssl::UniquePtr &x509, bssl::UniquePtr LoadCSR(const char *path) { bssl::UniquePtr bio(BIO_new_file(path, "r")); if (!bio) { - return NULL; + return nullptr; } bssl::UniquePtr csr( @@ -113,7 +113,7 @@ bssl::UniquePtr LoadCSR(const char *path) { bssl::UniquePtr LoadPEMCertificate(const char *path) { bssl::UniquePtr bio(BIO_new_file(path, "r")); if (!bio) { - return NULL; + return nullptr; } bssl::UniquePtr cert( @@ -125,7 +125,7 @@ bssl::UniquePtr LoadPEMCertificate(const char *path) { bssl::UniquePtr LoadDERCertificate(const char *path) { bssl::UniquePtr bio(BIO_new_file(path, "r")); if (!bio) { - return NULL; + return nullptr; } bssl::UniquePtr cert(d2i_X509_bio(bio.get(), nullptr)); diff --git a/tool-openssl/test_util.h b/tool-openssl/test_util.h index 2a58e33991..66f8da3df0 100644 --- a/tool-openssl/test_util.h +++ b/tool-openssl/test_util.h @@ -72,26 +72,17 @@ inline void RunCommandsAndCompareOutput(const std::string &tool_command, ASSERT_EQ(openssl_result, 0) << "OpenSSL command failed: " << openssl_command; std::ifstream tool_output(out_path_tool); - tool_output_str = std::string((std::istreambuf_iterator(tool_output)), - std::istreambuf_iterator()); tool_output_str = std::string((std::istreambuf_iterator(tool_output)), std::istreambuf_iterator()); std::ifstream openssl_output(out_path_openssl); openssl_output_str = std::string((std::istreambuf_iterator(openssl_output)), std::istreambuf_iterator()); - openssl_output_str = - std::string((std::istreambuf_iterator(openssl_output)), - std::istreambuf_iterator()); std::cout << "AWS-LC tool output:" << std::endl << tool_output_str << std::endl; std::cout << "OpenSSL output:" << std::endl << openssl_output_str << std::endl; - std::cout << "AWS-LC tool output:" << std::endl - << tool_output_str << std::endl; - std::cout << "OpenSSL output:" << std::endl - << openssl_output_str << std::endl; } inline void RemoveFile(const char *path) { From a9013e369d9cf9a97edcff3bafe52b240a593982 Mon Sep 17 00:00:00 2001 From: Hailey Ho Date: Thu, 16 Oct 2025 16:28:09 +0000 Subject: [PATCH 08/14] Fix cryptofuzz failure --- crypto/x509/v3_conf.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crypto/x509/v3_conf.c b/crypto/x509/v3_conf.c index 94dd651486..3df994a5ca 100644 --- a/crypto/x509/v3_conf.c +++ b/crypto/x509/v3_conf.c @@ -375,7 +375,7 @@ int X509V3_EXT_add_nconf_sk(const CONF *conf, const X509V3_CTX *ctx, if (!sk) { ok = 1; } else { - if (ctx->flags == X509V3_CTX_REPLACE) { + if (ctx && ctx->flags == X509V3_CTX_REPLACE) { delete_ext(*sk, ext); } ok = X509v3_add_ext(sk, ext, -1) != NULL; From 9dd17df4213ee295b9cb22e7d74d2c8bdcc6ac75 Mon Sep 17 00:00:00 2001 From: Hailey Ho Date: Thu, 16 Oct 2025 16:30:45 +0000 Subject: [PATCH 09/14] Only compare cert extensions for openssl>=3.0 --- tool-openssl/test_util.cc | 75 +++++++++++++++++++++------------------ 1 file changed, 41 insertions(+), 34 deletions(-) diff --git a/tool-openssl/test_util.cc b/tool-openssl/test_util.cc index c04bac113b..e8093b390b 100644 --- a/tool-openssl/test_util.cc +++ b/tool-openssl/test_util.cc @@ -338,44 +338,51 @@ bool CompareCertificates(X509 *cert1, X509 *cert2, X509 *ca_cert, } // 8. Compare extensions - simplified approach - int ext_count1 = X509_get_ext_count(cert1); - int ext_count2 = X509_get_ext_count(cert2); - if (ext_count1 != ext_count2) { - std::cout << "Certificates have different extension counts" << std::endl; - return false; - } - - // Compare each extension by index (assuming same order) - for (int i = 0; i < ext_count1; i++) { - X509_EXTENSION *ext1 = X509_get_ext(cert1, i); - X509_EXTENSION *ext2 = X509_get_ext(cert2, i); - if (!ext1 || !ext2) { - std::cout << "Failed to obtain extensions at location " << i << std::endl; + // Skip extension count check for OpenSSL 1.1.1 comparison + const char *openssl_path = getenv("OPENSSL_TOOL_PATH"); + bool is_openssl_1_1_1 = openssl_path && strstr(openssl_path, "1_1_1"); + + if (!is_openssl_1_1_1) { + std::cout << "DEBUG: Comparing extensions" << std::endl; + int ext_count1 = X509_get_ext_count(cert1); + int ext_count2 = X509_get_ext_count(cert2); + if (ext_count1 != ext_count2) { + std::cout << "Certificates have different extension counts" << std::endl; return false; } - // Compare extension OIDs - ASN1_OBJECT *obj1 = X509_EXTENSION_get_object(ext1); - ASN1_OBJECT *obj2 = X509_EXTENSION_get_object(ext2); - if (!obj1 || !obj2) { - std::cout << "Failed to obtain extension OID" << std::endl; - return false; - } - - if (OBJ_cmp(obj1, obj2) != 0) { - std::cout << "Extension content within the certificates do not match" - << std::endl; - return false; - } - - // Compare critical flags - if (X509_EXTENSION_get_critical(ext1) != - X509_EXTENSION_get_critical(ext2)) { - std::cout << "Certificates have different extension critical flags" - << std::endl; - return false; + // Compare each extension by index (assuming same order) + for (int i = 0; i < ext_count1; i++) { + X509_EXTENSION *ext1 = X509_get_ext(cert1, i); + X509_EXTENSION *ext2 = X509_get_ext(cert2, i); + if (!ext1 || !ext2) { + std::cout << "Failed to obtain extensions at location " << i + << std::endl; + return false; + } + + // Compare extension OIDs + ASN1_OBJECT *obj1 = X509_EXTENSION_get_object(ext1); + ASN1_OBJECT *obj2 = X509_EXTENSION_get_object(ext2); + if (!obj1 || !obj2) { + std::cout << "Failed to obtain extension OID" << std::endl; + return false; + } + + if (OBJ_cmp(obj1, obj2) != 0) { + std::cout << "Extension content within the certificates do not match" + << std::endl; + return false; + } + + // Compare critical flags + if (X509_EXTENSION_get_critical(ext1) != + X509_EXTENSION_get_critical(ext2)) { + std::cout << "Certificates have different extension critical flags" + << std::endl; + return false; + } } } - return true; } From 3683f62f6bd837e4d6fab2490f002ebb6959bae5 Mon Sep 17 00:00:00 2001 From: Hailey Ho Date: Thu, 16 Oct 2025 16:32:14 +0000 Subject: [PATCH 10/14] Modify AdaptKeyIDExtension and add more test coverage --- tool-openssl/x509.cc | 34 +++++------ tool-openssl/x509_test.cc | 122 ++++++++++++++++++++++++++++++-------- 2 files changed, 113 insertions(+), 43 deletions(-) diff --git a/tool-openssl/x509.cc b/tool-openssl/x509.cc index 2679f6a947..e56a90c266 100644 --- a/tool-openssl/x509.cc +++ b/tool-openssl/x509.cc @@ -100,30 +100,29 @@ bool isStringUpperCaseEqual(const std::string &a, const std::string &b) { std::equal(a.begin(), a.end(), b.begin(), isCharUpperCaseEqual); } -static int AdaptExtension(X509 *cert, X509V3_CTX *ext_ctx, const char *name, - const char *value, int add_default) { - bssl::UniquePtr new_ext( +static int AdaptKeyIDExtension(X509 *cert, X509V3_CTX *ext_ctx, + const char *name, const char *value, + int add_if_missing) { + bssl::UniquePtr new_keyid_ext( X509V3_EXT_nconf(NULL, ext_ctx, name, value)); - if (new_ext == NULL) { + if (new_keyid_ext == NULL) { return 0; } - const ASN1_OBJECT *ext_obj = X509_EXTENSION_get_object(new_ext.get()); - if (ext_obj == NULL) { + const ASN1_OBJECT *keyid_ext_obj = + X509_EXTENSION_get_object(new_keyid_ext.get()); + if (keyid_ext_obj == NULL) { return 0; } - int idx = X509_get_ext_by_OBJ(cert, ext_obj, -1); + // Check if the requested key identifier extension is already present + int idx = X509_get_ext_by_OBJ(cert, keyid_ext_obj, -1); if (idx >= 0) { - X509_EXTENSION *found_ext = X509_get_ext(cert, idx); - if (ASN1_STRING_length(X509_EXTENSION_get_data(found_ext)) <= 2) { - X509_delete_ext(cert, idx); - } - return 1; + return 1; // Extension found } - return !add_default || X509_add_ext(cert, new_ext.get(), -1); + return !add_if_missing || X509_add_ext(cert, new_keyid_ext.get(), -1); } static bool LoadExtensionsAndSignCertificate(const X509 *issuer, X509 *subject, @@ -152,7 +151,7 @@ static bool LoadExtensionsAndSignCertificate(const X509 *issuer, X509 *subject, ext_conf.reset(NCONF_new(NULL)); if (!ext_conf) { - fprintf(stderr, "Failed to create extension config\n"); + fprintf(stderr, "Error: Failed to create extension config\n"); return false; } @@ -193,14 +192,15 @@ static bool LoadExtensionsAndSignCertificate(const X509 *issuer, X509 *subject, } /* Prevent X509_V_ERR_MISSING_SUBJECT_KEY_IDENTIFIER */ - if (!AdaptExtension(subject, &ext_ctx, "subjectKeyIdentifier", "hash", 1)) { + if (!AdaptKeyIDExtension(subject, &ext_ctx, "subjectKeyIdentifier", "hash", + 1)) { fprintf(stderr, "Error: Failed to handle subject key identifier\n"); return false; } /* Prevent X509_V_ERR_MISSING_AUTHORITY_KEY_IDENTIFIER */ int self_sign = X509_check_private_key(subject, pkey); - if (!AdaptExtension(subject, &ext_ctx, "authorityKeyIdentifier", - "keyid, issuer", !self_sign)) { + if (!AdaptKeyIDExtension(subject, &ext_ctx, "authorityKeyIdentifier", + "keyid, issuer", !self_sign)) { fprintf(stderr, "Error: Failed to handle authority key identifier\n"); return false; } diff --git a/tool-openssl/x509_test.cc b/tool-openssl/x509_test.cc index fc1aa48dfa..e2b476a59f 100644 --- a/tool-openssl/x509_test.cc +++ b/tool-openssl/x509_test.cc @@ -1228,6 +1228,34 @@ TEST_F(X509ComparisonTest, X509ToolCompareReqCAOpenSSL) { << "Certificates generated by tool and OpenSSL have different attributes"; } +// Test against OpenSSL output with -passin option +TEST_F(X509ComparisonTest, X509ToolComparePassinOpenSSL) { + std::string tool_command = std::string(tool_executable_path) + " x509 -in " + + in_path + " -signkey " + protected_signkey_path + + " -passin pass:testpassword -out " + out_path_tool; + std::string openssl_command = + std::string(openssl_executable_path) + " x509 -in " + in_path + + " -signkey " + protected_signkey_path + + " -passin pass:testpassword -out " + out_path_openssl; + + ExecuteCommand(tool_command); + ExecuteCommand(openssl_command); + + // Load certificates + auto cert_tool = LoadPEMCertificate(out_path_tool); + auto cert_openssl = LoadPEMCertificate(out_path_openssl); + + ASSERT_TRUE(cert_tool != nullptr) + << "Failed to load certificate generated by tool"; + ASSERT_TRUE(cert_openssl != nullptr) + << "Failed to load certificate generated by OpenSSL"; + + // Compare certificates in detail with 30 days validity period + ASSERT_TRUE( + CompareCertificates(cert_tool.get(), cert_openssl.get(), nullptr, 30)) + << "Certificates generated by tool and OpenSSL have different attributes"; +} + // Test against OpenSSL output "openssl x509 -extfile extfile -extensions // extension_header" TEST_F(X509ComparisonTest, X509ToolCompareExtensionsOpenSSL) { @@ -1263,9 +1291,8 @@ TEST_F(X509ComparisonTest, X509ToolCompareExtensionsOpenSSL) { // Test extfile without -extensions (default section) ext_file.reset(fopen(ext_path, "w")); ASSERT_TRUE(ext_file); - fprintf( - ext_file.get(), - "basicConstraints=CA:FALSE\nkeyUsage=digitalSignature,keyEncipherment\n"); + fprintf(ext_file.get(), "basicConstraints=CA:FALSE\n"); + fprintf(ext_file.get(), "keyUsage=digitalSignature,keyEncipherment\n"); fclose(ext_file.release()); tool_command = std::string(tool_executable_path) + " x509 -in " + csr_path + @@ -1290,9 +1317,10 @@ TEST_F(X509ComparisonTest, X509ToolCompareExtensionsOpenSSL) { // Test extfile with extensions variable pointing to section ext_file.reset(fopen(ext_path, "w")); ASSERT_TRUE(ext_file); - fprintf(ext_file.get(), - "extensions=v3_req\n\n[v3_req]\nbasicConstraints=CA:FALSE\nkeyUsage=" - "digitalSignature,keyEncipherment\n"); + fprintf(ext_file.get(), "extensions=v3_req\n\n"); + fprintf(ext_file.get(), "[v3_req]\n"); + fprintf(ext_file.get(), "basicConstraints=CA:FALSE\n"); + fprintf(ext_file.get(), "keyUsage=digitalSignature,keyEncipherment\n"); fclose(ext_file.release()); tool_command = std::string(tool_executable_path) + " x509 -in " + in_path + @@ -1305,12 +1333,6 @@ TEST_F(X509ComparisonTest, X509ToolCompareExtensionsOpenSSL) { ExecuteCommand(tool_command); ExecuteCommand(openssl_command); - std::cout << "AWS-LC CLI command: " << std::endl; - std::cout << tool_command << std::endl; - - std::cout << "OpenSSL CLI command: " << std::endl; - std::cout << openssl_command << std::endl; - cert_tool = LoadPEMCertificate(out_path_tool); cert_openssl = LoadPEMCertificate(out_path_openssl); @@ -1323,30 +1345,78 @@ TEST_F(X509ComparisonTest, X509ToolCompareExtensionsOpenSSL) { RemoveFile(ext_path); } -// Test against OpenSSL output with -passin option -TEST_F(X509ComparisonTest, X509ToolComparePassinOpenSSL) { +// Test AdaptKeyIDExtension with existing valid key identifier extensions +TEST_F(X509ComparisonTest, X509ToolCompareKeyIDExtensionValidOpenSSL) { + char ext_path[PATH_MAX]; + ASSERT_GT(createTempFILEpath(ext_path), 0u); + + // Create extension file with valid key identifiers + ScopedFILE ext_file(fopen(ext_path, "w")); + ASSERT_TRUE(ext_file); + fprintf(ext_file.get(), "[test_ext]\n"); + fprintf(ext_file.get(), "subjectKeyIdentifier=hash\n"); + fprintf(ext_file.get(), "authorityKeyIdentifier=keyid:always\n"); + fclose(ext_file.release()); + std::string tool_command = std::string(tool_executable_path) + " x509 -in " + - in_path + " -signkey " + protected_signkey_path + - " -passin pass:testpassword -out " + out_path_tool; + in_path + " -signkey " + signkey_path + + " -extfile " + ext_path + " -extensions test_ext" + + " -out " + out_path_tool; std::string openssl_command = std::string(openssl_executable_path) + " x509 -in " + in_path + - " -signkey " + protected_signkey_path + - " -passin pass:testpassword -out " + out_path_openssl; + " -signkey " + signkey_path + " -extfile " + ext_path + + " -extensions test_ext -out " + out_path_openssl; ExecuteCommand(tool_command); ExecuteCommand(openssl_command); - // Load certificates auto cert_tool = LoadPEMCertificate(out_path_tool); auto cert_openssl = LoadPEMCertificate(out_path_openssl); - ASSERT_TRUE(cert_tool != nullptr) - << "Failed to load certificate generated by tool"; - ASSERT_TRUE(cert_openssl != nullptr) - << "Failed to load certificate generated by OpenSSL"; + ASSERT_TRUE(cert_tool != nullptr); + ASSERT_TRUE(cert_openssl != nullptr); - // Compare certificates in detail with 30 days validity period ASSERT_TRUE( - CompareCertificates(cert_tool.get(), cert_openssl.get(), nullptr, 30)) - << "Certificates generated by tool and OpenSSL have different attributes"; + CompareCertificates(cert_tool.get(), cert_openssl.get(), nullptr, 30)); + + RemoveFile(ext_path); +} + +// Test AdaptKeyIDExtension with self-signed cert missing authorityKeyIdentifier +TEST_F(X509ComparisonTest, X509ToolCompareKeyIDExtensionSelfSignedOpenSSL) { + char ext_path[PATH_MAX]; + ASSERT_GT(createTempFILEpath(ext_path), 0u); + + // Create extension file with only subjectKeyIdentifier (no + // authorityKeyIdentifier) + ScopedFILE ext_file(fopen(ext_path, "w")); + ASSERT_TRUE(ext_file); + fprintf(ext_file.get(), "[test_ext]\n"); + fprintf(ext_file.get(), "subjectKeyIdentifier=hash\n"); + fprintf(ext_file.get(), "basicConstraints=CA:TRUE\n"); + fclose(ext_file.release()); + + // Self-sign the certificate (same key for signing and subject) + std::string tool_command = std::string(tool_executable_path) + " x509 -in " + + in_path + " -signkey " + signkey_path + + " -extfile " + ext_path + " -extensions test_ext" + + " -out " + out_path_tool; + std::string openssl_command = + std::string(openssl_executable_path) + " x509 -in " + in_path + + " -signkey " + signkey_path + " -extfile " + ext_path + + " -extensions test_ext -out " + out_path_openssl; + + ExecuteCommand(tool_command); + ExecuteCommand(openssl_command); + + auto cert_tool = LoadPEMCertificate(out_path_tool); + auto cert_openssl = LoadPEMCertificate(out_path_openssl); + + ASSERT_TRUE(cert_tool != nullptr); + ASSERT_TRUE(cert_openssl != nullptr); + + ASSERT_TRUE( + CompareCertificates(cert_tool.get(), cert_openssl.get(), nullptr, 30)); + + RemoveFile(ext_path); } From 9740eaa4faa442770ab8a2b3efd036cd37ab2f04 Mon Sep 17 00:00:00 2001 From: Hailey Ho Date: Thu, 16 Oct 2025 16:33:06 +0000 Subject: [PATCH 11/14] nit --- tool-openssl/test_util.cc | 1 - 1 file changed, 1 deletion(-) diff --git a/tool-openssl/test_util.cc b/tool-openssl/test_util.cc index e8093b390b..6067c6db24 100644 --- a/tool-openssl/test_util.cc +++ b/tool-openssl/test_util.cc @@ -343,7 +343,6 @@ bool CompareCertificates(X509 *cert1, X509 *cert2, X509 *ca_cert, bool is_openssl_1_1_1 = openssl_path && strstr(openssl_path, "1_1_1"); if (!is_openssl_1_1_1) { - std::cout << "DEBUG: Comparing extensions" << std::endl; int ext_count1 = X509_get_ext_count(cert1); int ext_count2 = X509_get_ext_count(cert2); if (ext_count1 != ext_count2) { From 263e1c6591dc74ce04fdbfb3215e694f8db1237f Mon Sep 17 00:00:00 2001 From: Hailey Ho Date: Thu, 16 Oct 2025 17:00:31 +0000 Subject: [PATCH 12/14] Fix Windows duplicate symbol errors --- tool-openssl/CMakeLists.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/tool-openssl/CMakeLists.txt b/tool-openssl/CMakeLists.txt index 30e7b713ae..aa034bba31 100644 --- a/tool-openssl/CMakeLists.txt +++ b/tool-openssl/CMakeLists.txt @@ -82,7 +82,6 @@ if(BUILD_TESTING) ../tool/args.cc ../tool/file.cc ../tool/fd.cc - ../crypto/test/test_util.cc ../tool/client.cc ../tool/transport_common.cc From e650995f7c5bf8cfa67a32c901504677048e83fc Mon Sep 17 00:00:00 2001 From: Hailey Ho Date: Fri, 17 Oct 2025 16:52:02 +0000 Subject: [PATCH 13/14] Refactor test utility to handle special edge cases for older openssl version --- tests/ci/run_openssl_comparison_tests.sh | 8 ++ tool-openssl/test_util.cc | 94 +++++++++++++++--------- 2 files changed, 69 insertions(+), 33 deletions(-) diff --git a/tests/ci/run_openssl_comparison_tests.sh b/tests/ci/run_openssl_comparison_tests.sh index 892429c2f3..452393f140 100755 --- a/tests/ci/run_openssl_comparison_tests.sh +++ b/tests/ci/run_openssl_comparison_tests.sh @@ -32,9 +32,17 @@ declare -A openssl_branches=( ["$openssl_master_branch"]="lib64" ) +declare -A openssl_versions=( + ["$openssl_1_1_1_branch"]="1.1.1" + ["$openssl_3_1_branch"]="3.1" + ["$openssl_3_2_branch"]="3.2" + ["$openssl_master_branch"]="master" +) + # Run X509 Comparison Tests against all OpenSSL branches export AWSLC_TOOL_PATH="${BUILD_ROOT}/tool-openssl/openssl" for branch in "${!openssl_branches[@]}"; do + export OPENSSL_TOOL_VERSION="${openssl_versions[$branch]}" export OPENSSL_TOOL_PATH="${install_dir}/openssl-${branch}/bin/openssl" echo "Running ${test} against OpenSSL ${branch}" LD_LIBRARY_PATH="${install_dir}/openssl-${branch}/${openssl_branches[$branch]}" "${BUILD_ROOT}/tool-openssl/tool_openssl_test" diff --git a/tool-openssl/test_util.cc b/tool-openssl/test_util.cc index 6067c6db24..4b9170e0ba 100644 --- a/tool-openssl/test_util.cc +++ b/tool-openssl/test_util.cc @@ -338,50 +338,78 @@ bool CompareCertificates(X509 *cert1, X509 *cert2, X509 *ca_cert, } // 8. Compare extensions - simplified approach - // Skip extension count check for OpenSSL 1.1.1 comparison - const char *openssl_path = getenv("OPENSSL_TOOL_PATH"); - bool is_openssl_1_1_1 = openssl_path && strstr(openssl_path, "1_1_1"); + // Skip extension count check if OpenSSL version <= 3.1 + int ext_count1 = X509_get_ext_count(cert1); + int ext_count2 = X509_get_ext_count(cert2); - if (!is_openssl_1_1_1) { - int ext_count1 = X509_get_ext_count(cert1); - int ext_count2 = X509_get_ext_count(cert2); + const char *openssl_version = getenv("OPENSSL_TOOL_VERSION"); + if (openssl_version && strcmp(openssl_version, "3.1") > 0) { if (ext_count1 != ext_count2) { std::cout << "Certificates have different extension counts" << std::endl; return false; } + } - // Compare each extension by index (assuming same order) - for (int i = 0; i < ext_count1; i++) { - X509_EXTENSION *ext1 = X509_get_ext(cert1, i); - X509_EXTENSION *ext2 = X509_get_ext(cert2, i); - if (!ext1 || !ext2) { - std::cout << "Failed to obtain extensions at location " << i - << std::endl; - return false; - } + // Check for duplicate extension in AWS-LC's certificate + std::set cert1_nids; + std::set cert2_nids; + for (int i = 0; i < ext_count1; i++) { + X509_EXTENSION *ext = X509_get_ext(cert1, i); + if (!ext) + return false; + int nid = OBJ_obj2nid(X509_EXTENSION_get_object(ext)); + if (!cert1_nids.insert(nid).second) { + std::cout << "AWS-LC's certificate has duplicate extensions" << std::endl; + return false; + } + } - // Compare extension OIDs - ASN1_OBJECT *obj1 = X509_EXTENSION_get_object(ext1); - ASN1_OBJECT *obj2 = X509_EXTENSION_get_object(ext2); - if (!obj1 || !obj2) { - std::cout << "Failed to obtain extension OID" << std::endl; - return false; - } + for (int i = ext_count2 - 1; i >= 0; i--) { + X509_EXTENSION *ext2 = X509_get_ext(cert2, i); + if (!ext2) + return false; - if (OBJ_cmp(obj1, obj2) != 0) { - std::cout << "Extension content within the certificates do not match" - << std::endl; - return false; - } + int nid2 = OBJ_obj2nid(X509_EXTENSION_get_object(ext2)); - // Compare critical flags - if (X509_EXTENSION_get_critical(ext1) != - X509_EXTENSION_get_critical(ext2)) { - std::cout << "Certificates have different extension critical flags" - << std::endl; - return false; + // OpenSSL<=3.1 does not clear existing extensions, resulting in duplicates. + // Skip over those duplicates + if (openssl_version && strcmp(openssl_version, "3.1") > 0) { + if (!cert2_nids.insert(nid2).second) { + continue; } } + + int idx = X509_get_ext_by_NID(cert1, nid2, -1); + if (idx < 0) { + std::cout << "Extension " << OBJ_nid2sn(nid2) + << " present in OpenSSL's certificate but missing in AWS-LC's " + "certificate" + << std::endl; + return false; + } + X509_EXTENSION *ext1 = X509_get_ext(cert1, idx); + + // Compare extension OIDs + ASN1_OBJECT *obj1 = X509_EXTENSION_get_object(ext1); + ASN1_OBJECT *obj2 = X509_EXTENSION_get_object(ext2); + if (!obj1 || !obj2) { + std::cout << "Failed to obtain extension OID" << std::endl; + return false; + } + + if (OBJ_cmp(obj1, obj2) != 0) { + std::cout << "Extension content within the certificates do not match" + << std::endl; + return false; + } + + // Compare critical flags + if (X509_EXTENSION_get_critical(ext1) != + X509_EXTENSION_get_critical(ext2)) { + std::cout << "Certificates have different extension critical flags" + << std::endl; + // return false; + } } return true; } From 21f780892b2dc830b9fa833acb7bcc28710e144a Mon Sep 17 00:00:00 2001 From: Hailey Ho Date: Fri, 17 Oct 2025 17:20:59 +0000 Subject: [PATCH 14/14] nit --- tool-openssl/test_util.cc | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/tool-openssl/test_util.cc b/tool-openssl/test_util.cc index 4b9170e0ba..dece214604 100644 --- a/tool-openssl/test_util.cc +++ b/tool-openssl/test_util.cc @@ -355,8 +355,11 @@ bool CompareCertificates(X509 *cert1, X509 *cert2, X509 *ca_cert, std::set cert2_nids; for (int i = 0; i < ext_count1; i++) { X509_EXTENSION *ext = X509_get_ext(cert1, i); - if (!ext) + if (!ext) { + std::cout << "Failed to obtain extension from AWS-LC's certificate" + << std::endl; return false; + } int nid = OBJ_obj2nid(X509_EXTENSION_get_object(ext)); if (!cert1_nids.insert(nid).second) { std::cout << "AWS-LC's certificate has duplicate extensions" << std::endl; @@ -366,14 +369,17 @@ bool CompareCertificates(X509 *cert1, X509 *cert2, X509 *ca_cert, for (int i = ext_count2 - 1; i >= 0; i--) { X509_EXTENSION *ext2 = X509_get_ext(cert2, i); - if (!ext2) + if (!ext2) { + std::cout << "Failed to obtain extension from OpenSSL's certificate" + << std::endl; return false; + } int nid2 = OBJ_obj2nid(X509_EXTENSION_get_object(ext2)); // OpenSSL<=3.1 does not clear existing extensions, resulting in duplicates. // Skip over those duplicates - if (openssl_version && strcmp(openssl_version, "3.1") > 0) { + if (openssl_version && strcmp(openssl_version, "1.1.1") == 0) { if (!cert2_nids.insert(nid2).second) { continue; } @@ -408,7 +414,7 @@ bool CompareCertificates(X509 *cert1, X509 *cert2, X509 *ca_cert, X509_EXTENSION_get_critical(ext2)) { std::cout << "Certificates have different extension critical flags" << std::endl; - // return false; + return false; } } return true;