diff --git a/tool-openssl/req.cc b/tool-openssl/req.cc index b761c1ea9d..589d14dcc5 100644 --- a/tool-openssl/req.cc +++ b/tool-openssl/req.cc @@ -17,6 +17,7 @@ #define REQ_SECTION "req" #define BITS "default_bits" #define DEFAULT_MD "default_md" +#define DEFAULT_KEYFILE "default_keyfile" #define PROMPT "prompt" #define ENCRYPT_KEY "encrypt_key" #define DISTINGUISHED_NAME "distinguished_name" @@ -30,13 +31,20 @@ #define BUF_SIZE 1024 #define DEFAULT_CHAR_TYPE MBSTRING_ASC -// Notes: -x509 option assumes -new when -in is not passed in with OpenSSL. We -// do not support -in as of now, so -new is implied with -x509. +// NOTES: +// 1. We do not support -in as of now, so -new is implied with -x509. // -// In general, OpenSSL supports a default config file which it defaults to when -// user input is not provided. We don't support this default config file -// interface. For fields that are not overriden by user input, we hardcode -// default values (e.g. X509 extensions, -keyout defaults to privkey.pem, etc.) +// 2. AWS-LC does not support config files by design, but some of our +// dependencies still use this cli command with -config. Therefore, we decided +// to implement -config but will only parse a MINIMAL set of fields (e.g., +// default_md, distiguished_name, etc.). This set will be updated and +// re-evaluated on an as-needed basis. +// +// 3. OpenSSL has a default config file when user input is not provided. +// https://github.com/openssl/openssl/blob/master/apps/openssl.cnf +// We don't support this default config file interface. For fields that are not +// overriden by user input, we hardcode default values (e.g. X509 extensions, +// -keyout defaults to privkey.pem, etc.) static const argument_t kArguments[] = { {"-help", kBooleanArgument, "Display option summary"}, {"-md5", kExclusiveBooleanArgument, "Supported digest function"}, @@ -74,12 +82,6 @@ static const argument_t kArguments[] = { "be formatted as /type0=value0/type1=value1/type2=.... " "Keyword characters may be escaped by \\ (backslash), and " "whitespace is retained."}, - // AWS-LC does not support config files by design, but some of our - // dependencies - // still use this cli command with -config. Therefore, we decided to - // implement -config but will only parse a MINIMAL set of fields (e.g., - // default_md, distiguished_name, etc.). This set will be updated and - // re-evaluated on an as-needed basis. {"-config", kOptionalArgument, "This specifies the request template file"}, {"-extensions", kOptionalArgument, "Cert or request extension section (override value in config file)"}, @@ -324,8 +326,8 @@ const ReqField extra_attributes[] = { "An optional company name", NID_pkcs9_unstructuredName}}; static bssl::UniquePtr BuildSubject( - X509_REQ *req, CONF *req_conf, bool is_csr, bool no_prompt, - unsigned long chtype = MBSTRING_ASC) { + X509_REQ *req, CONF *req_conf, const std::string &req_section, bool is_csr, + bool no_prompt, unsigned long chtype = MBSTRING_ASC) { // Get the subject name from the request bssl::UniquePtr subj(X509_NAME_new()); if (!subj) { @@ -352,7 +354,8 @@ static bssl::UniquePtr BuildSubject( char buffer[BUF_SIZE]; const char *dn_section = NULL; if (req_conf) { - dn_section = NCONF_get_string(req_conf, REQ_SECTION, DISTINGUISHED_NAME); + dn_section = + NCONF_get_string(req_conf, req_section.c_str(), DISTINGUISHED_NAME); } // Process each subject field @@ -394,7 +397,7 @@ static bssl::UniquePtr BuildSubject( const char *attr_section = NULL; if (req_conf) { - attr_section = NCONF_get_string(req_conf, REQ_SECTION, ATTRIBUTES); + attr_section = NCONF_get_string(req_conf, req_section.c_str(), ATTRIBUTES); } // If this is a CSR, handle extra attributes if (is_csr) { @@ -448,7 +451,8 @@ static bssl::UniquePtr BuildSubject( static bool MakeCertificateRequest(X509_REQ *req, EVP_PKEY *pkey, std::string &subject_name, CONF *req_conf, - bool is_csr, bool no_prompt) { + const std::string &req_section, bool is_csr, + bool no_prompt) { bssl::UniquePtr name; // version 1 @@ -457,7 +461,7 @@ static bool MakeCertificateRequest(X509_REQ *req, EVP_PKEY *pkey, } if (subject_name.empty()) { // Prompt the user - name = BuildSubject(req, req_conf, is_csr, no_prompt); + name = BuildSubject(req, req_conf, req_section, is_csr, no_prompt); } else { // Parse user provided string name = ParseSubjectName(subject_name); if (!name) { @@ -527,7 +531,7 @@ static bool LoadConfig(const std::string &config_path, static bool AddCertExtensions(X509 *cert, CONF *req_conf, std::string &ext_section) { // Set up X509V3 context for certificate - bool result = false; + bool result = true; X509V3_CTX ext_ctx; X509V3_set_ctx(&ext_ctx, cert, cert, NULL, NULL, X509V3_CTX_REPLACE); // self-signed @@ -536,8 +540,10 @@ static bool AddCertExtensions(X509 *cert, CONF *req_conf, X509V3_set_nconf(&ext_ctx, req_conf); // Add extensions from config to the certificate - result = X509V3_EXT_add_nconf(req_conf, &ext_ctx, ext_section.c_str(), - cert) != 0; + if (!ext_section.empty()) { + result = X509V3_EXT_add_nconf(req_conf, &ext_ctx, ext_section.c_str(), + cert) != 0; + } /* Prevent X509_V_ERR_MISSING_SUBJECT_KEY_IDENTIFIER */ if (!AdaptKeyIDExtension(cert, &ext_ctx, "subjectKeyIdentifier", "hash", @@ -559,7 +565,8 @@ static bool AddCertExtensions(X509 *cert, CONF *req_conf, "basicConstraints=critical,CA:true\n"; // Create a BIO for the config - bssl::UniquePtr bio(BIO_new_mem_buf(default_config, strlen(default_config))); + bssl::UniquePtr bio( + BIO_new_mem_buf(default_config, strlen(default_config))); if (!bio) { fprintf(stderr, "Failed to create memory BIO\n"); return false; @@ -588,7 +595,7 @@ static bool AddCertExtensions(X509 *cert, CONF *req_conf, static bool AddReqExtensions(X509_REQ *req, CONF *req_conf, std::string &ext_section) { // Set up X509V3 context for certificate - bool result = false; + bool result = true; X509V3_CTX ext_ctx; X509V3_set_ctx(&ext_ctx, NULL, NULL, req, NULL, X509V3_CTX_REPLACE); // self-signed @@ -694,13 +701,8 @@ static bool WritePrivateKey(std::string &out_path, const EVP_CIPHER *cipher) { bssl::UniquePtr out_bio; SetUmaskForPrivateKey(); - if (out_path.empty()) { - // Default to privkey.pem in the current directory - out_path = "privkey.pem"; - fprintf(stderr, "Writing private key to %s (default)\n", out_path.c_str()); - } else { - fprintf(stderr, "Writing private key to %s\n", out_path.c_str()); - } + + fprintf(stderr, "Writing private key to %s\n", out_path.c_str()); out_bio.reset(BIO_new(BIO_s_file())); if (!out_bio) { @@ -799,9 +801,15 @@ bool reqTool(const args_list_t &args) { return false; } + std::string req_section = REQ_SECTION; + if (req_conf.get() && + NCONF_get_section(req_conf.get(), REQ_SECTION) == NULL) { + req_section = "default"; + } + if (ext_section.empty() && req_conf.get()) { const char *ext_str = - NCONF_get_string(req_conf.get(), REQ_SECTION, + NCONF_get_string(req_conf.get(), req_section.c_str(), x509_flag ? V3_EXTENSIONS : REQ_EXTENSIONS); if (ext_str) { ext_section = ext_str; @@ -825,7 +833,7 @@ bool reqTool(const args_list_t &args) { if (digest_name.empty()) { if (!config_path.empty()) { const char *digest_str = - NCONF_get_string(req_conf.get(), REQ_SECTION, DEFAULT_MD); + NCONF_get_string(req_conf.get(), req_section.c_str(), DEFAULT_MD); if (digest_str) { digest_name = digest_str; } else { @@ -844,7 +852,7 @@ bool reqTool(const args_list_t &args) { const char *encrypt_key_str = NULL; if (req_conf.get()) { encrypt_key_str = - NCONF_get_string(req_conf.get(), REQ_SECTION, ENCRYPT_KEY); + NCONF_get_string(req_conf.get(), req_section.c_str(), ENCRYPT_KEY); } if (encrypt_key_str != NULL && @@ -859,12 +867,16 @@ bool reqTool(const args_list_t &args) { // - If -newkey is given: generate key specified by -newkey // - Else: generate default RSA key bssl::UniquePtr pkey; - if (key_file_path.empty()) { + if (!key_file_path.empty()) { + if (!LoadPrivateKey(key_file_path, passin, pkey)) { + return false; + } + } else { // Before generating key, check if config has a default key length specified long default_keylen = DEFAULT_KEY_LENGTH; const char *bits_str = NULL; if (req_conf.get()) { - bits_str = NCONF_get_string(req_conf.get(), REQ_SECTION, BITS); + bits_str = NCONF_get_string(req_conf.get(), req_section.c_str(), BITS); } if (bits_str) { @@ -893,26 +905,39 @@ bool reqTool(const args_list_t &args) { fprintf(stderr, "Error: Failed to generate private key.\n"); return false; } - } else { - if (!LoadPrivateKey(key_file_path, passin, pkey)) { - return false; - } } - const EVP_CIPHER *cipher = NULL; - if (!nodes && encrypt_key) { - cipher = EVP_des_ede3_cbc(); + // If keyout is not provided: + // 1. If -config, use it to set keyout + // 2. If no -config, output key to privkey.pem (this imitates how OpenSSL + // would default to the default openssl.conf file, which has default_keyfile + // set to privkey.pem) + if (keyout.empty()) { + if (req_conf) { + const char *default_keyfile = NCONF_get_string( + req_conf.get(), req_section.c_str(), DEFAULT_KEYFILE); + keyout = default_keyfile != NULL ? default_keyfile : ""; + } else { + keyout = "privkey.pem"; + } } - SetUmaskForPrivateKey(); - if (!WritePrivateKey(keyout, passout, pkey, cipher)) { - return false; + if (!keyout.empty()) { + const EVP_CIPHER *cipher = NULL; + if (!nodes && encrypt_key) { + cipher = EVP_des_ede3_cbc(); + } + + if (!WritePrivateKey(keyout, passout, pkey, cipher)) { + return false; + } } bool no_prompt = false; const char *no_prompt_str = NULL; if (req_conf.get()) { - no_prompt_str = NCONF_get_string(req_conf.get(), REQ_SECTION, PROMPT); + no_prompt_str = + NCONF_get_string(req_conf.get(), req_section.c_str(), PROMPT); } if (no_prompt_str != NULL && isStringUpperCaseEqual(no_prompt_str, "no")) { @@ -927,7 +952,7 @@ bool reqTool(const args_list_t &args) { // Always create a CSR first if (req == NULL || !MakeCertificateRequest(req.get(), pkey.get(), subj, req_conf.get(), - !x509_flag, no_prompt)) { + req_section, !x509_flag, no_prompt)) { fprintf(stderr, "Failed to create certificate request\n"); return false; } @@ -990,7 +1015,7 @@ bool reqTool(const args_list_t &args) { } else { // Add extensions to request if (!AddReqExtensions(req.get(), req_conf.get(), ext_section)) { - fprintf(stderr, "Failed to add extensions to certificate\n"); + fprintf(stderr, "Failed to add extensions to CSR\n"); return false; } diff --git a/tool-openssl/req_test.cc b/tool-openssl/req_test.cc index 3d7c56eadf..b5fc7899ea 100644 --- a/tool-openssl/req_test.cc +++ b/tool-openssl/req_test.cc @@ -11,7 +11,6 @@ #include #include #else -#include #include #endif #include "../tool/internal.h" @@ -33,7 +32,8 @@ class ReqTest : public ::testing::Test { ASSERT_GT(createTempFILEpath(protected_key_path), 0u); // Create a temporary directory and change to it - // This allows testing the default "privkey.pem" behavior in an isolated location + // This allows testing the default "privkey.pem" behavior in an isolated + // location ASSERT_GT(createTempDirPath(temp_dir_path), 0u); #if defined(OPENSSL_WINDOWS) ASSERT_TRUE(_getcwd(original_dir, PATH_MAX) != nullptr); @@ -101,8 +101,8 @@ class ReqTest : public ::testing::Test { char cert_path[PATH_MAX]; char config_path[PATH_MAX]; char protected_key_path[PATH_MAX]; - char temp_dir_path[PATH_MAX]; // Temporary directory for test isolation - char original_dir[PATH_MAX]; // Original working directory + char temp_dir_path[PATH_MAX]; // Temporary directory for test isolation + char original_dir[PATH_MAX]; // Original working directory }; TEST_F(ReqTest, GenerateRSAKey) { @@ -210,6 +210,29 @@ TEST_F(ReqTest, DefaultKeyoutPath) { EXPECT_EQ(EVP_PKEY_id(key.get()), EVP_PKEY_RSA); } +TEST_F(ReqTest, SuppressedKeyWrite) { + // Verify if default_keyfile is missing from config, no private key write + // should happen + ScopedFILE config_file(fopen(config_path, "w")); + ASSERT_TRUE(config_file); + fprintf(config_file.get(), + "[req]\n" + "distinguished_name = req_dn\n" + "encrypt_key = yes\n" + "[req_dn]\n" + "CN = Common Name\n"); + fclose(config_file.release()); + + args_list_t args = {"-new", "-config", config_path, "-out", + csr_path, "-subj", "/CN=test.com"}; + + ASSERT_TRUE(reqTool(args)); + + // Verify that privkey.pem was NOT created + ScopedFILE f(fopen("privkey.pem", "r")); + EXPECT_FALSE(f) << "privkey.pem should not be created"; +} + TEST_F(ReqTest, X509SelfSignedCert) { args_list_t args = {"-new", "-x509", "-days", "365", "-newkey", "rsa:2048", "-nodes", "-keyout", @@ -226,17 +249,17 @@ TEST_F(ReqTest, X509SelfSignedCert) { EXPECT_EQ(X509_verify(cert.get(), key.get()), 1); } -TEST_F(ReqTest, ConfigFileBasic) { - ScopedFILE config(fopen(config_path, "w")); - ASSERT_TRUE(config); - fprintf(config.get(), +TEST_F(ReqTest, BasicConfig) { + ScopedFILE config_file(fopen(config_path, "w")); + ASSERT_TRUE(config_file); + fprintf(config_file.get(), "[req]\n" "default_bits = 3072\n" "default_md = sha384\n" "distinguished_name = req_dn\n" "[req_dn]\n" "CN = Common Name\n"); - fclose(config.release()); + fclose(config_file.release()); args_list_t args = {"-new", "-config", config_path, "-nodes", "-keyout", output_key_path, "-out", csr_path, @@ -251,6 +274,32 @@ TEST_F(ReqTest, ConfigFileBasic) { EXPECT_EQ(RSA_bits(rsa), 3072u); } +TEST_F(ReqTest, NoReqSectionConfig) { + ScopedFILE config_file(fopen(config_path, "w")); + ASSERT_TRUE(config_file); + fprintf(config_file.get(), + "default_bits = 3072\n" + "default_md = sha384\n" + "distinguished_name = req_dn\n" + "prompt = no\n" + "encrypt_key = no\n" + "[req_dn]\n" + "CN = Common Name\n"); + fclose(config_file.release()); + + args_list_t args = {"-new", "-config", config_path, + "-keyout", output_key_path, "-out", + csr_path, "-subj", "/CN=test.com"}; + + ASSERT_TRUE(reqTool(args)); + + bssl::UniquePtr key(DecryptPrivateKey(output_key_path, nullptr)); + ASSERT_TRUE(key); + const RSA *rsa = EVP_PKEY_get0_RSA(key.get()); + ASSERT_TRUE(rsa); + EXPECT_EQ(RSA_bits(rsa), 3072u); +} + TEST_F(ReqTest, ExistingKeyFile) { // Use existing key for new CSR args_list_t use_args = {"-new", "-key", input_key_path, "-nodes", @@ -272,15 +321,15 @@ TEST_F(ReqTest, SubjectNameParsing) { } } -TEST_F(ReqTest, ConfigFileDigest) { - ScopedFILE config(fopen(config_path, "w")); - ASSERT_TRUE(config); - fprintf(config.get(), +TEST_F(ReqTest, DigestSelectionFromConfig) { + ScopedFILE config_file(fopen(config_path, "w")); + ASSERT_TRUE(config_file); + fprintf(config_file.get(), "[req]\n" "default_md = sha512\n" "distinguished_name = req_dn\n" "[req_dn]\n"); - fclose(config.release()); + fclose(config_file.release()); args_list_t args = {"-new", "-config", config_path, "-newkey", "rsa:2048", "-nodes", "-keyout", output_key_path, @@ -289,15 +338,15 @@ TEST_F(ReqTest, ConfigFileDigest) { ASSERT_TRUE(reqTool(args)); } -TEST_F(ReqTest, ConfigFileEncryption) { - ScopedFILE config(fopen(config_path, "w")); - ASSERT_TRUE(config); - fprintf(config.get(), +TEST_F(ReqTest, KeyEncryptionFromConfig) { + ScopedFILE config_file(fopen(config_path, "w")); + ASSERT_TRUE(config_file); + fprintf(config_file.get(), "[req]\n" "encrypt_key = yes\n" "distinguished_name = req_dn\n" "[req_dn]\n"); - fclose(config.release()); + fclose(config_file.release()); args_list_t args = {"-new", "-config", config_path, "-newkey", "rsa:2048", "-passout", "pass:testpass", "-keyout", @@ -310,10 +359,51 @@ TEST_F(ReqTest, ConfigFileEncryption) { EXPECT_TRUE(key_content.find("ENCRYPTED") != std::string::npos); } -TEST_F(ReqTest, ConfigFileExtensions) { - ScopedFILE config(fopen(config_path, "w")); - ASSERT_TRUE(config); - fprintf(config.get(), +TEST_F(ReqTest, ReqExtensions) { + ScopedFILE config_file(fopen(config_path, "w")); + ASSERT_TRUE(config_file); + fprintf(config_file.get(), + "[req]\n" + "distinguished_name = req_dn\n" + "[req_dn]\n" + "[test_ext]\n" + "basicConstraints = CA:FALSE\n" + "keyUsage = digitalSignature, keyEncipherment\n"); + fclose(config_file.release()); + + args_list_t args = {"-new", "-config", config_path, "-extensions", + "test_ext", "-newkey", "rsa:2048", "-nodes", + "-keyout", output_key_path, "-out", csr_path, + "-subj", "/CN=test.com"}; + + ASSERT_TRUE(reqTool(args)); +} + +TEST_F(ReqTest, X509Extensions) { + ScopedFILE config_file(fopen(config_path, "w")); + ASSERT_TRUE(config_file); + fprintf(config_file.get(), + "[req]\n" + "distinguished_name = req_dn\n" + "[req_dn]\n" + "[custom_ext]\n" + "basicConstraints = CA:FALSE\n" + "keyUsage = digitalSignature, keyEncipherment\n" + "subjectAltName = DNS:alt.example.com\n"); + fclose(config_file.release()); + + args_list_t args = {"-x509", "-new", "-config", config_path, + "-extensions", "custom_ext", "-newkey", "rsa:2048", + "-nodes", "-keyout", output_key_path, "-out", + cert_path, "-subj", "/CN=test.com"}; + + ASSERT_TRUE(reqTool(args)); +} + +TEST_F(ReqTest, ReqExtensionsFromConfig) { + ScopedFILE config_file(fopen(config_path, "w")); + ASSERT_TRUE(config_file); + fprintf(config_file.get(), "[req]\n" "distinguished_name = req_dn\n" "req_extensions = v3_req\n" @@ -321,7 +411,41 @@ TEST_F(ReqTest, ConfigFileExtensions) { "[v3_req]\n" "basicConstraints = CA:FALSE\n" "keyUsage = nonRepudiation, digitalSignature, keyEncipherment\n"); - fclose(config.release()); + fclose(config_file.release()); + + args_list_t args = {"-new", "-config", config_path, "-newkey", + "rsa:2048", "-nodes", "-keyout", output_key_path, + "-out", csr_path, "-subj", "/CN=test.com"}; + + ASSERT_TRUE(reqTool(args)); +} + +TEST_F(ReqTest, X509ExtensionsFromConfig) { + ScopedFILE config_file(fopen(config_path, "w")); + ASSERT_TRUE(config_file); + fprintf(config_file.get(), + "[req]\n" + "distinguished_name = req_dn\n" + "x509_extensions = v3_ca\n" + "[req_dn]\n" + "[v3_ca]\n" + "basicConstraints = critical,CA:true\n" + "keyUsage = critical,keyCertSign,cRLSign\n"); + fclose(config_file.release()); + + args_list_t args = {"-x509", "-new", "-config", config_path, + "-newkey", "rsa:2048", "-nodes", "-keyout", + output_key_path, "-out", cert_path, "-subj", + "/CN=test.com"}; + + ASSERT_TRUE(reqTool(args)); +} + +TEST_F(ReqTest, ReqExtensionsFromEmptyConfig) { + ScopedFILE config_file(fopen(config_path, "w")); + ASSERT_TRUE(config_file); + fprintf(config_file.get(), "[req]\n"); + fclose(config_file.release()); args_list_t args = {"-new", "-config", config_path, "-newkey", "rsa:2048", "-nodes", "-keyout", output_key_path, @@ -330,13 +454,26 @@ TEST_F(ReqTest, ConfigFileExtensions) { ASSERT_TRUE(reqTool(args)); } +TEST_F(ReqTest, X509ExtensionsFromEmptyConfig) { + ScopedFILE config_file(fopen(config_path, "w")); + ASSERT_TRUE(config_file); + fprintf(config_file.get(), "[req]\n"); + fclose(config_file.release()); + + args_list_t args = {"-x509", "-new", "-config", config_path, + "-newkey", "rsa:2048", "-nodes", "-keyout", + output_key_path, "-out", cert_path, "-subj", + "/CN=test.com"}; + + ASSERT_TRUE(reqTool(args)); +} + TEST_F(ReqTest, OutformPEM) { args_list_t args = {"-new", "-newkey", "rsa:2048", "-nodes", "-outform", "PEM", "-keyout", output_key_path, "-out", csr_path, "-subj", "/CN=test.com"}; ASSERT_TRUE(reqTool(args)); - std::string csr_content = ReadFileToString(csr_path); EXPECT_TRUE(csr_content.find("-----BEGIN CERTIFICATE REQUEST-----") != std::string::npos); @@ -466,43 +603,6 @@ TEST_F(ReqOptionUsageErrorsTest, InvalidPassinTest) { } } -static void setup_config(char *openssl_config_path) { - ScopedFILE config_file(fopen(openssl_config_path, "w")); - if (!config_file) { - fprintf(stderr, "Error opening config file for writing\n"); - return; - } - - // Write the OpenSSL configuration content - fprintf(config_file.get(), - "[ req ]\n" - "default_bits = 2048\n" - "default_keyfile = keyfile.pem\n" - "distinguished_name = req_distinguished_name\n" - "attributes = req_attributes\n" - "prompt = no\n" - "output_password = mypass\n" - "x509_extensions = v3_ca\n" - "encrypted_key = no\n" - "\n" - "[ req_distinguished_name ]\n" - "C = GB\n" - "ST = Test State or Province\n" - "L = Test Locality\n" - "O = Organization Name\n" - "OU = Organizational Unit Name\n" - "CN = Common Name\n" - "emailAddress = test@email.address\n" - "\n" - "[ req_attributes ]\n" - "challengePassword = A challenge password\n" - "\n" - "[ v3_ca ]\n" - "subjectKeyIdentifier = hash\n" - "authorityKeyIdentifier = keyid:always,issuer:always\n" - "basicConstraints = critical, CA:true\n"); -} - class ReqComparisonTest : public ::testing::Test { protected: void SetUp() override { @@ -520,9 +620,7 @@ class ReqComparisonTest : public ::testing::Test { ASSERT_GT(createTempFILEpath(csr_path_awslc), 0u); ASSERT_GT(createTempFILEpath(key_path_openssl), 0u); ASSERT_GT(createTempFILEpath(key_path_awslc), 0u); - ASSERT_GT(createTempFILEpath(openssl_config_path), 0u); - - setup_config(openssl_config_path); + ASSERT_GT(createTempFILEpath(config_path), 0u); // Create shared key files for signing ASSERT_GT(createTempFILEpath(sign_key_path), 0u); @@ -554,7 +652,7 @@ class ReqComparisonTest : public ::testing::Test { RemoveFile(csr_path_awslc); RemoveFile(key_path_openssl); RemoveFile(key_path_awslc); - RemoveFile(openssl_config_path); + RemoveFile(config_path); RemoveFile(sign_key_path); RemoveFile(protected_sign_key_path); } @@ -566,7 +664,7 @@ class ReqComparisonTest : public ::testing::Test { char csr_path_awslc[PATH_MAX]; char key_path_openssl[PATH_MAX]; char key_path_awslc[PATH_MAX]; - char openssl_config_path[PATH_MAX]; + char config_path[PATH_MAX]; char sign_key_path[PATH_MAX]; char protected_sign_key_path[PATH_MAX]; const char *tool_executable_path; @@ -581,11 +679,10 @@ TEST_F(ReqComparisonTest, GenerateBasicCSR) { "-newkey rsa:2048 -nodes -out " + csr_path_awslc + " -subj \"" + subject + "\""; - std::string openssl_command = - std::string(openssl_executable_path) + " req -new " + - "-newkey rsa:2048 -nodes -config " + openssl_config_path + " -keyout " + - key_path_openssl + " -out " + csr_path_openssl + " -subj \"" + subject + - "\""; + std::string openssl_command = std::string(openssl_executable_path) + + " req -new " + "-newkey rsa:2048 -nodes " + + " -keyout " + key_path_openssl + " -out " + + csr_path_openssl + " -subj \"" + subject + "\""; ExecuteCommand(awslc_command); ExecuteCommand(openssl_command); @@ -615,9 +712,8 @@ TEST_F(ReqComparisonTest, GenerateSelfSignedCertificate) { std::string openssl_command = std::string(openssl_executable_path) + " req -x509 -new " + - "-newkey rsa:2048 -nodes -config " + openssl_config_path + - " -days 365 -keyout " + key_path_openssl + " -out " + cert_path_openssl + - " -subj \"" + subject + "\""; + "-newkey rsa:2048 -nodes -days 365 -keyout " + key_path_openssl + + " -out " + cert_path_openssl + " -subj \"" + subject + "\""; ExecuteCommand(tool_command); ExecuteCommand(openssl_command); @@ -639,9 +735,6 @@ TEST_F(ReqComparisonTest, GenerateSelfSignedCertificate) { // Test no-prompt mode from config TEST_F(ReqComparisonTest, NoPromptConfig) { - char config_path[PATH_MAX]; - ASSERT_GT(createTempFILEpath(config_path), 0u); - ScopedFILE config_file(fopen(config_path, "w")); ASSERT_TRUE(config_file); fprintf(config_file.get(), @@ -703,9 +796,6 @@ TEST_F(ReqComparisonTest, InteractivePrompting) { // Test config private key length parsing TEST_F(ReqComparisonTest, PrivateKeyLengthFromConfig) { // Create config with custom key length - char config_path[PATH_MAX]; - ASSERT_GT(createTempFILEpath(config_path), 0u); - ScopedFILE config_file(fopen(config_path, "w")); ASSERT_TRUE(config_file); fprintf(config_file.get(), @@ -775,9 +865,6 @@ TEST_F(ReqComparisonTest, KeyLengthValidation) { // Test with config fallback TEST_F(ReqComparisonTest, SubjectConfigFallback) { // Create config with subject fields using both long and short names - char config_path[PATH_MAX]; - ASSERT_GT(createTempFILEpath(config_path), 0u); - ScopedFILE config_file(fopen(config_path, "w")); ASSERT_TRUE(config_file); fprintf(config_file.get(), @@ -945,9 +1032,6 @@ TEST_F(ReqComparisonTest, DigestSelection) { // Test config file digest selection TEST_F(ReqComparisonTest, DigestSelectionFromConfig) { // Create config with custom digest - char config_path[PATH_MAX]; - ASSERT_GT(createTempFILEpath(config_path), 0u); - ScopedFILE config_file(fopen(config_path, "w")); ASSERT_TRUE(config_file); fprintf(config_file.get(), @@ -1059,9 +1143,6 @@ TEST_F(ReqComparisonTest, GenerateProtectedPrivateKey) { std::string subject = "/CN=encrypted-key.example.com"; // Create a simple config that disables encryption to avoid password prompts - char config_path[PATH_MAX]; - ASSERT_GT(createTempFILEpath(config_path), 0u); - ScopedFILE config_file(fopen(config_path, "w")); ASSERT_TRUE(config_file); fprintf(config_file.get(), @@ -1113,12 +1194,9 @@ TEST_F(ReqComparisonTest, GenerateProtectedPrivateKey) { << "AWS-LC and OpenSSL private keys are different"; } -// Test -extensions option with -x509 -TEST_F(ReqComparisonTest, X509Extensions) { - // Create config file with custom extension section - char config_path[PATH_MAX]; - ASSERT_GT(createTempFILEpath(config_path), 0u); - +// Test -extensions option without -x509 +TEST_F(ReqComparisonTest, ReqExtensions) { + // Create config file with extension section ScopedFILE config_file(fopen(config_path, "w")); ASSERT_TRUE(config_file); fprintf(config_file.get(), @@ -1129,48 +1207,36 @@ TEST_F(ReqComparisonTest, X509Extensions) { "[ req_distinguished_name ]\n" "CN = extensions-test.example.com\n" "\n" - "[ custom_ext ]\n" + "[ test_ext ]\n" "basicConstraints = CA:FALSE\n" - "keyUsage = digitalSignature, keyEncipherment\n" - "subjectAltName = DNS:alt.example.com\n"); + "keyUsage = digitalSignature, keyEncipherment\n"); fclose(config_file.release()); - std::string subject = "/CN=extensions-test.example.com"; - std::string awslc_command = - std::string(tool_executable_path) + " req -x509 -new " + "-config " + - config_path + " -extensions custom_ext " + - "-newkey rsa:2048 -nodes -days 365 -keyout " + key_path_awslc + " -out " + - cert_path_awslc + " -subj \"" + subject + "\""; + std::string awslc_command = std::string(tool_executable_path) + " req -new " + + "-config " + config_path + + " -extensions test_ext " + + "-newkey rsa:2048 -nodes -keyout " + + key_path_awslc + " -out " + csr_path_awslc; - std::string openssl_command = - std::string(openssl_executable_path) + " req -x509 -new " + "-config " + - config_path + " -extensions custom_ext " + - "-newkey rsa:2048 -nodes -days 365 -keyout " + key_path_openssl + - " -out " + cert_path_openssl + " -subj \"" + subject + "\""; + std::string openssl_command = std::string(openssl_executable_path) + + " req -new " + "-config " + config_path + + " -extensions test_ext " + + "-newkey rsa:2048 -nodes -keyout " + + key_path_openssl + " -out " + csr_path_openssl; ASSERT_EQ(ExecuteCommand(awslc_command), 0); ASSERT_EQ(ExecuteCommand(openssl_command), 0); - auto cert_awslc = LoadPEMCertificate(cert_path_awslc); - auto cert_openssl = LoadPEMCertificate(cert_path_openssl); - - ASSERT_TRUE(cert_awslc != nullptr) - << "Failed to load AWS-LC certificate with custom extensions"; - ASSERT_TRUE(cert_openssl != nullptr) - << "Failed to load OpenSSL certificate with custom extensions"; - - // Compare certificates with custom extensions - ASSERT_TRUE( - CompareCertificates(cert_awslc.get(), cert_openssl.get(), nullptr, 365)) - << "Certificates with custom extensions have different attributes"; + auto csr_awslc = LoadPEMCSR(csr_path_awslc); + auto csr_openssl = LoadPEMCSR(csr_path_openssl); + ASSERT_TRUE(csr_awslc != nullptr); + ASSERT_TRUE(csr_openssl != nullptr); + ASSERT_TRUE(CompareCSRs(csr_awslc.get(), csr_openssl.get())); } -// Test -extensions option without -x509 -TEST_F(ReqComparisonTest, ReqExtensions) { - // Create config file with extension section - char config_path[PATH_MAX]; - ASSERT_GT(createTempFILEpath(config_path), 0u); - +// Test -extensions option with -x509 +TEST_F(ReqComparisonTest, X509Extensions) { + // Create config file with custom extension section ScopedFILE config_file(fopen(config_path, "w")); ASSERT_TRUE(config_file); fprintf(config_file.get(), @@ -1181,39 +1247,43 @@ TEST_F(ReqComparisonTest, ReqExtensions) { "[ req_distinguished_name ]\n" "CN = extensions-test.example.com\n" "\n" - "[ test_ext ]\n" + "[ custom_ext ]\n" "basicConstraints = CA:FALSE\n" - "keyUsage = digitalSignature, keyEncipherment\n"); + "keyUsage = digitalSignature, keyEncipherment\n" + "subjectAltName = DNS:alt.example.com\n"); fclose(config_file.release()); - std::string subject = "/CN=extensions-test.example.com"; - std::string awslc_command = - std::string(tool_executable_path) + " req -new " + "-config " + - config_path + " -extensions test_ext " + - "-newkey rsa:2048 -nodes -keyout " + key_path_awslc + " -out " + - csr_path_awslc + " -subj \"" + subject + "\""; + std::string awslc_command = std::string(tool_executable_path) + + " req -x509 -new " + "-config " + config_path + + " -extensions custom_ext " + + "-newkey rsa:2048 -nodes -days 365 -keyout " + + key_path_awslc + " -out " + cert_path_awslc; - std::string openssl_command = - std::string(openssl_executable_path) + " req -new " + "-config " + - config_path + " -extensions test_ext " + - "-newkey rsa:2048 -nodes -keyout " + key_path_openssl + " -out " + - csr_path_openssl + " -subj \"" + subject + "\""; + std::string openssl_command = std::string(openssl_executable_path) + + " req -x509 -new " + "-config " + config_path + + " -extensions custom_ext " + + "-newkey rsa:2048 -nodes -days 365 -keyout " + + key_path_openssl + " -out " + cert_path_openssl; ASSERT_EQ(ExecuteCommand(awslc_command), 0); ASSERT_EQ(ExecuteCommand(openssl_command), 0); - auto csr_awslc = LoadPEMCSR(csr_path_awslc); - auto csr_openssl = LoadPEMCSR(csr_path_openssl); - ASSERT_TRUE(csr_awslc != nullptr); - ASSERT_TRUE(csr_openssl != nullptr); - ASSERT_TRUE(CompareCSRs(csr_awslc.get(), csr_openssl.get())); + auto cert_awslc = LoadPEMCertificate(cert_path_awslc); + auto cert_openssl = LoadPEMCertificate(cert_path_openssl); + + ASSERT_TRUE(cert_awslc != nullptr) + << "Failed to load AWS-LC certificate with custom extensions"; + ASSERT_TRUE(cert_openssl != nullptr) + << "Failed to load OpenSSL certificate with custom extensions"; + + // Compare certificates with custom extensions + ASSERT_TRUE( + CompareCertificates(cert_awslc.get(), cert_openssl.get(), nullptr, 365)) + << "Certificates with custom extensions have different attributes"; } // Test req extensions obtained from config TEST_F(ReqComparisonTest, ReqExtensionsFromConfig) { - char config_path[PATH_MAX]; - ASSERT_GT(createTempFILEpath(config_path), 0u); - ScopedFILE config_file(fopen(config_path, "w")); ASSERT_TRUE(config_file); fprintf(config_file.get(), @@ -1253,9 +1323,6 @@ TEST_F(ReqComparisonTest, ReqExtensionsFromConfig) { // Test x509 extensions obtained from config TEST_F(ReqComparisonTest, X509ExtensionsFromConfig) { - char config_path[PATH_MAX]; - ASSERT_GT(createTempFILEpath(config_path), 0u); - ScopedFILE config_file(fopen(config_path, "w")); ASSERT_TRUE(config_file); fprintf(config_file.get(), @@ -1297,6 +1364,113 @@ TEST_F(ReqComparisonTest, X509ExtensionsFromConfig) { CompareCertificates(cert_awslc.get(), cert_openssl.get(), nullptr, 365)); } +// Test req extensions when config is provided but has no extension section +TEST_F(ReqComparisonTest, ReqExtentionsFromEmptyConfig) { + ScopedFILE config_file(fopen(config_path, "w")); + ASSERT_TRUE(config_file); + fprintf(config_file.get(), + "[ req ]\n" + "distinguished_name = req_distinguished_name\n" + "prompt = no\n" + "\n" + "[ req_distinguished_name ]\n" + "CN = req-ext-test.example.com\n" + "\n"); + fclose(config_file.release()); + + std::string awslc_command = std::string(tool_executable_path) + " req -new " + + "-config " + config_path + + " -newkey rsa:2048 -nodes -keyout " + + key_path_awslc + " -out " + csr_path_awslc; + + std::string openssl_command = std::string(openssl_executable_path) + + " req -new " + "-config " + config_path + + " -newkey rsa:2048 -nodes -keyout " + + key_path_openssl + " -out " + csr_path_openssl; + + ASSERT_EQ(ExecuteCommand(awslc_command), 0); + ASSERT_EQ(ExecuteCommand(openssl_command), 0); + + auto csr_awslc = LoadPEMCSR(csr_path_awslc); + auto csr_openssl = LoadPEMCSR(csr_path_openssl); + ASSERT_TRUE(csr_awslc != nullptr); + ASSERT_TRUE(csr_openssl != nullptr); + ASSERT_TRUE(CompareCSRs(csr_awslc.get(), csr_openssl.get())); +} + +// Test x509 extensions when config is provided but has no extension section +TEST_F(ReqComparisonTest, X509ExtensionsFromEmptyConfig) { + ScopedFILE config_file(fopen(config_path, "w")); + ASSERT_TRUE(config_file); + fprintf(config_file.get(), + "[ req ]\n" + "distinguished_name = req_distinguished_name\n" + "prompt = no\n" + "\n" + "[ req_distinguished_name ]\n" + "CN = x509-ext-test.example.com\n" + "\n"); + fclose(config_file.release()); + + std::string awslc_command = std::string(tool_executable_path) + + " req -x509 -new " + "-config " + config_path + + " -newkey rsa:2048 -nodes -days 365 -keyout " + + key_path_awslc + " -out " + cert_path_awslc; + + std::string openssl_command = std::string(openssl_executable_path) + + " req -x509 -new " + "-config " + config_path + + " -newkey rsa:2048 -nodes -days 365 -keyout " + + key_path_openssl + " -out " + cert_path_openssl; + + ASSERT_EQ(ExecuteCommand(awslc_command), 0); + ASSERT_EQ(ExecuteCommand(openssl_command), 0); + + auto cert_awslc = LoadPEMCertificate(cert_path_awslc); + auto cert_openssl = LoadPEMCertificate(cert_path_openssl); + ASSERT_TRUE(cert_awslc != nullptr); + ASSERT_TRUE(cert_openssl != nullptr); + ASSERT_TRUE( + CompareCertificates(cert_awslc.get(), cert_openssl.get(), nullptr, 365)); +} + +// Test config that does not have req section +TEST_F(ReqComparisonTest, NoReqSectionConfig) { + ScopedFILE config_file(fopen(config_path, "w")); + ASSERT_TRUE(config_file); + fprintf(config_file.get(), + "distinguished_name = req_distinguished_name\n" + "req_extensions = v3_req\n" + "prompt = no\n" + "\n" + "[ req_distinguished_name ]\n" + "CN = req-ext-test.example.com\n" + "\n" + "[ v3_req ]\n" + "subjectAltName = DNS:alt1.example.com,DNS:alt2.example.com\n" + "keyUsage = digitalSignature, keyEncipherment\n"); + fclose(config_file.release()); + + std::string subject = "/CN=req-ext-test.example.com"; + std::string awslc_command = + std::string(tool_executable_path) + " req -new " + "-config " + + config_path + " -newkey rsa:2048 -nodes -keyout " + key_path_awslc + + " -out " + csr_path_awslc + " -subj \"" + subject + "\""; + + std::string openssl_command = + std::string(openssl_executable_path) + " req -new " + "-config " + + config_path + " -newkey rsa:2048 -nodes -keyout " + key_path_openssl + + " -out " + csr_path_openssl + " -subj \"" + subject + "\""; + + ASSERT_EQ(ExecuteCommand(awslc_command), 0); + ASSERT_EQ(ExecuteCommand(openssl_command), 0); + + auto csr_awslc = LoadPEMCSR(csr_path_awslc); + auto csr_openssl = LoadPEMCSR(csr_path_openssl); + ASSERT_TRUE(csr_awslc != nullptr); + ASSERT_TRUE(csr_openssl != nullptr); + ASSERT_TRUE(CompareCSRs(csr_awslc.get(), csr_openssl.get())); +} + struct SubjectNameTestCase { std::string input; bool expect_success;