diff --git a/crypto/x509/v3_conf.c b/crypto/x509/v3_conf.c index f62e632fef3..3df994a5ca1 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 = 0; + ASN1_OBJECT *obj = NULL; + + 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 && 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 32ce545ea71..9efd6553f69 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/tests/ci/run_openssl_comparison_tests.sh b/tests/ci/run_openssl_comparison_tests.sh index 892429c2f3a..452393f1402 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/CMakeLists.txt b/tool-openssl/CMakeLists.txt index 8e98507c89e..244037d2126 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 ecparam.cc @@ -84,10 +84,10 @@ if(BUILD_TESTING) ../tool/args.cc ../tool/file.cc ../tool/fd.cc - ../crypto/test/test_util.cc ../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 45f15eda197..5e763ea6d94 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,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/rehash_test.cc b/tool-openssl/rehash_test.cc index 2c0fe125313..f00328efe74 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 560523c6d4f..762850d323f 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(), 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 new file mode 100644 index 00000000000..dece2146044 --- /dev/null +++ b/tool-openssl/test_util.cc @@ -0,0 +1,421 @@ +// 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; + } + + bssl::UniquePtr 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.get(), rsa.release())) { + fprintf(stderr, "Error generating new key\n"); + return; + } + if (!X509_set_pubkey(x509.get(), pkey.get())) { + 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 = 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"))) || + !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.get(), EVP_sha256()) <= 0) { + fprintf(stderr, "Error signing certificate\n"); + return; + } + + if (pkey_p != nullptr) { + pkey_p->reset(pkey.release()); + } +} + +// Load a CSR from a PEM file +bssl::UniquePtr LoadCSR(const char *path) { + bssl::UniquePtr bio(BIO_new_file(path, "r")); + if (!bio) { + return nullptr; + } + + 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 nullptr; + } + + bssl::UniquePtr cert( + PEM_read_bio_X509(bio.get(), nullptr, nullptr, nullptr)); + 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 nullptr; + } + + bssl::UniquePtr cert(d2i_X509_bio(bio.get(), 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 = 0, seconds = 0; + 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) { + std::cout << "Invalid certificates" << std::endl; + 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) { + std::cout << "Certificates have different subject names" << std::endl; + 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) { + std::cout << "Certificates have different issuer names" << std::endl; + 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; + } + + // 6. Check public key type and parameters + 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; + } + + // 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) { + 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 + 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(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; + } + } + + // 8. Compare extensions - simplified approach + // 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); + + 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; + } + } + + // 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) { + 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; + return false; + } + } + + for (int i = ext_count2 - 1; i >= 0; i--) { + X509_EXTENSION *ext2 = X509_get_ext(cert2, i); + 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, "1.1.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; +} diff --git a/tool-openssl/test_util.h b/tool-openssl/test_util.h index a96f78f4ec0..66f8da3df0e 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(); } @@ -88,7 +94,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 3c905aefdfc..cde5c6342f4 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 8d7fb7927aa..e56a90c2669 100644 --- a/tool-openssl/x509.cc +++ b/tool-openssl/x509.cc @@ -11,69 +11,281 @@ #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"}, + {"-passin", kOptionalArgument, "Private key pass-phrase source"}, + // 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); +} + +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_keyid_ext == NULL) { + return 0; + } + + const ASN1_OBJECT *keyid_ext_obj = + X509_EXTENSION_get_object(new_keyid_ext.get()); + if (keyid_ext_obj == NULL) { + return 0; + } + + // 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) { + return 1; // Extension found + } + + return !add_if_missing || X509_add_ext(cert, new_keyid_ext.get(), -1); } -bool LoadPrivateKeyAndSignCertificate(X509 *x509, const std::string &signkey_path) { +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, X509V3_CTX_REPLACE); + + 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, "Error: 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 (!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 (!AdaptKeyIDExtension(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, + 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()); + 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, + const_cast(passin->c_str()))); 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, + bssl::UniquePtr &passin) { + ScopedFILE ca_file(fopen(ca_file_path.c_str(), "rb")); + + if (!ca_file) { + fprintf(stderr, "Error: Failed to open CA file"); + return false; + } + + ca.reset(PEM_read_X509(ca_file.get(), nullptr, nullptr, nullptr)); + + if (!ca) { + fprintf(stderr, "Error: Failed to read CA cert from file"); + 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: Failed to open CA key file"); + return false; + } + + 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, + const_cast(passin->c_str()))); + } + + if (!ca_key) { + fprintf(stderr, "Error: Failed to parse CA key from file"); + 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 +327,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 +416,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,15 +460,18 @@ 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; + bssl::UniquePtr passin(new std::string()); ordered_args::GetBoolArgument(&help, "-help", parsed_args); ordered_args::GetString(&in_path, "-in", "", parsed_args); @@ -248,6 +481,12 @@ 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); + ordered_args::GetString(passin.get(), "-passin", "", parsed_args); // Display x509 tool option summary if (help) { @@ -265,23 +504,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,20 +551,41 @@ 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; + } + } + + // 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()) { @@ -310,7 +593,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, passin)) { + return false; + } + } else if (!signkey_path.empty()) { + if (!LoadPrivateKey(pkey, signkey_path, passin)) { return false; } } @@ -337,20 +634,33 @@ 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"); + // 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; } // 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 +668,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 +702,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 +742,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 cfef362dbad..e2b476a59f6 100644 --- a/tool-openssl/x509_test.cc +++ b/tool-openssl/x509_test.cc @@ -4,100 +4,48 @@ #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); + 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); + 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)); + ASSERT_TRUE(PEM_write_PrivateKey(signkey_file.get(), pkey.get(), nullptr, + nullptr, 0, nullptr, nullptr)); - bssl::UniquePtr x509(CreateAndSignX509Certificate()); - ASSERT_TRUE(x509); + 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); @@ -117,6 +65,37 @@ 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 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); @@ -124,15 +103,25 @@ class X509Test : public ::testing::Test { RemoveFile(out_path); RemoveFile(signkey_path); 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]; char signkey_path[PATH_MAX]; 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 ----------------------------- // Test -in and -out @@ -143,7 +132,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 +179,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 +210,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,7 +240,112 @@ 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); +} + +// 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); } @@ -236,10 +353,10 @@ TEST_F(X509Test, X509ToolReqTest) { // -------------------- 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 +367,68 @@ 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); } } +// 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 -------------------------- @@ -294,14 +436,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 +452,11 @@ 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); + ASSERT_GT(createTempFILEpath(protected_signkey_path), 0u); - x509.reset(CreateAndSignX509Certificate()); + CreateAndSignX509Certificate(x509, nullptr); ASSERT_TRUE(x509); ScopedFILE in_file(fopen(in_path, "wb")); @@ -323,15 +468,23 @@ 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)); + + // 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); @@ -341,6 +494,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 +522,9 @@ class X509ComparisonTest : public ::testing::Test { RemoveFile(out_path_openssl); RemoveFile(signkey_path); RemoveFile(der_cert_path); + RemoveFile(ca_cert_path); + RemoveFile(ca_key_path); + RemoveFile(protected_signkey_path); } } @@ -360,10 +534,13 @@ 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]; + char protected_signkey_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 +552,30 @@ 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 -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 + 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 +583,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); + + ASSERT_EQ(tool_output_str, openssl_output_str); +} - // OpenSSL master and versions <= 3.2 have differences in spacing for the subject field +// 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 +834,589 @@ 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(), nullptr, 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(), 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 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(), 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"; +} + + +// 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); + + // 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 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) { + 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(), nullptr, 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\n"); + fprintf(ext_file.get(), "keyUsage=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(), nullptr, 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"); + 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 + + " -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(), nullptr, 30)); + + RemoveFile(ext_path); +} + +// 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 " + 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); +} + +// 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); +}