diff --git a/tool-openssl/README.md b/tool-openssl/README.md index a6a4da6f4c..5bbd4961b2 100644 --- a/tool-openssl/README.md +++ b/tool-openssl/README.md @@ -1,8 +1,7 @@ # OpenSSL Tools for AWS-LC *Files expected to change* -Current status: -* Developed structure for new OpenSSL tools -* Contains initial implementation for OpenSSL x509 tool, options -in and -out (x509.cc), and unit test (x509_test.cc) -* x509_test.cc contains test portions ultimately to be used for future options but unnecessary for -in/-out unit test - +Current status: +* Contains initial implementation for OpenSSL x509 tool, options -in -out, -req, -signkey, -modulus, -days, -dates, + -checkend, -noout (x509.cc), and unit test (x509_test.cc) +* OpenSSL rsa tool yet to be implemented diff --git a/tool-openssl/internal.h b/tool-openssl/internal.h index 0078af1eba..1eba7fba2a 100644 --- a/tool-openssl/internal.h +++ b/tool-openssl/internal.h @@ -10,12 +10,17 @@ typedef bool (*tool_func_t)(const std::vector &args); +bool IsNumeric(const std::string& str); + X509* CreateAndSignX509Certificate(); + +bool WriteSignedCertificate(X509 *x509, const std::string &out_path); + +bool LoadPrivateKeyAndSignCertificate(X509 *x509, const std::string &signkey_path); + tool_func_t FindTool(const std::string &name); tool_func_t FindTool(int argc, char **argv, int &starting_arg); bool X509Tool(const args_list_t &args); #endif //INTERNAL_H - - diff --git a/tool-openssl/x509.cc b/tool-openssl/x509.cc index aca034b01e..853804aee7 100644 --- a/tool-openssl/x509.cc +++ b/tool-openssl/x509.cc @@ -3,72 +3,279 @@ #include #include +#include +#include +#include #include "internal.h" static const argument_t kArguments[] = { - { "-in", kRequiredArgument, "Input file" }, - { "-out", kRequiredArgument, "Output file" }, + { "-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, "Output file to write to" }, + { "-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" }, + { "-checkend", kOptionalArgument, "Check whether cert expires in the next arg seconds" }, + { "-days", kOptionalArgument, "Number of days until newly generated certificate expires - default 30" }, { "", kOptionalArgument, "" } }; +bool WriteSignedCertificate(X509 *x509, const std::string &out_path) { + ScopedFILE out_file(fopen(out_path.c_str(), "wb")); + if (!out_file) { + fprintf(stderr, "Error: unable to open output file '%s'\n", out_path.c_str()); + return false; + } + if (!PEM_write_X509(out_file.get(), x509)) { + fprintf(stderr, "Error: error writing certificate to '%s'\n", out_path.c_str()); + ERR_print_errors_fp(stderr); + return false; + } + return true; +} + +bool LoadPrivateKeyAndSignCertificate(X509 *x509, const std::string &signkey_path) { + ScopedFILE signkey_file(fopen(signkey_path.c_str(), "rb")); + if (!signkey_file) { + fprintf(stderr, "Error: unable to load private key from '%s'\n", signkey_path.c_str()); + return false; + } + bssl::UniquePtr pkey(PEM_read_PrivateKey(signkey_file.get(), nullptr, nullptr, nullptr)); + if (!pkey) { + fprintf(stderr, "Error: error reading private key from '%s'\n", signkey_path.c_str()); + 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 false; + } + return true; +} + +bool IsNumeric(const std::string& str) { + return !str.empty() && std::all_of(str.begin(), str.end(), ::isdigit); +} + // Map arguments using tool/args.cc bool X509Tool(const args_list_t &args) { - args_map_t parsed_args; - if (!ParseKeyValueArguments(&parsed_args, args, kArguments)) { - PrintUsage(kArguments); - return false; + args_map_t parsed_args; + if (!ParseKeyValueArguments(&parsed_args, args, kArguments)) { + PrintUsage(kArguments); + return false; + } + + std::string in_path, out_path, signkey_path, checkend_str, days_str; + bool noout = false, modulus = false, dates = false, req = false, help = false; + std::unique_ptr checkend, days; + + GetBoolArgument(&help, "-help", parsed_args); + GetString(&in_path, "-in", "", parsed_args); + GetBoolArgument(&req, "-req", parsed_args); + GetString(&signkey_path, "-signkey", "", parsed_args); + GetString(&out_path, "-out", "", parsed_args); + GetBoolArgument(&noout, "-noout", parsed_args); + GetBoolArgument(&dates, "-dates", parsed_args); + GetBoolArgument(&modulus, "-modulus", parsed_args); + + // Display x509 tool option summary + if (help) { + PrintUsage(kArguments); + return false; + } + + // Check for required option -in, and -req must include -signkey + if (in_path.empty()) { + fprintf(stderr, "Error: missing required argument '-in'\n"); + return false; + } + if (req && signkey_path.empty()) { + fprintf(stderr, "Error: '-req' option must be used with '-signkey' option\n"); + return false; + } + + // Check for mutually exclusive options + if (noout && (!out_path.empty() || modulus || dates || parsed_args.count("-checkend"))) { + fprintf(stderr, "Error: '-noout' option cannot be used with '-out', '-modulus', '-dates', and '-checkend' options\n"); + return false; + } + if (req && (dates || parsed_args.count("-checkend"))){ + fprintf(stderr, "Error: '-req' option cannot be used with '-dates' and '-checkend' options\n"); + return false; + } + if (!signkey_path.empty() && (dates || parsed_args.count("-checkend"))){ + fprintf(stderr, "Error: '-signkey' option cannot be used with '-dates' and '-checkend' options\n"); + return false; + } + if (parsed_args.count("-days") && (dates || parsed_args.count("-checkend"))){ + fprintf(stderr, "Error: '-days' option cannot be used with '-dates' and '-checkend' options\n"); + return false; + } + + // Check that -checkend argument is valid, int >=0 + if (parsed_args.count("-checkend")) { + checkend_str = parsed_args["-checkend"]; + if (!IsNumeric(checkend_str)) { + fprintf(stderr, "Error: '-checkend' option must include a non-negative integer\n"); + return false; } + checkend.reset(new unsigned(std::stoul(checkend_str))); + } - // Check for required arguments - std::string in_path, out_path; - if (!GetString(&in_path, "-in", "", parsed_args)) { - fprintf(stderr, "Missing required argument: -in\n"); - PrintUsage(kArguments); - return false; + // Check that -days argument is valid, int > 0 + if (parsed_args.count("-days")) { + days_str = parsed_args["-days"]; + if (!IsNumeric(days_str) || std::stoul(days_str) == 0) { + fprintf(stderr, "Error: '-days' option must include a positive integer\n"); + return false; } - if (!GetString(&out_path, "-out", "", parsed_args)) { - fprintf(stderr, "Missing required argument: -out\n"); - PrintUsage(kArguments); - return false; + days.reset(new unsigned(std::stoul(days_str))); + } + + ScopedFILE in_file(fopen(in_path.c_str(), "rb")); + if (!in_file) { + fprintf(stderr, "Error: unable to load certificate from '%s'\n", in_path.c_str()); + return false; + } + + if (req) { + bssl::UniquePtr csr(PEM_read_X509_REQ(in_file.get(), nullptr, nullptr, nullptr)); + if (!csr) { + fprintf(stderr, "Error: error parsing CSR from '%s'\n", in_path.c_str()); + ERR_print_errors_fp(stderr); + return false; + } + + // Create and sign certificate based on CSR + bssl::UniquePtr x509(X509_new()); + if (!x509) { + fprintf(stderr, "Error: unable to create new X509 certificate\n"); + return false; } - // Read input file using ReadAll function from tool/file.cc - std::vector input_data; - ScopedFILE in_file(fopen(in_path.c_str(), "rb")); - if (!in_file) { - fprintf(stderr, "Failed to open input file '%s'.\n", in_path.c_str()); + // Set the subject from CSR + 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; } - if (!ReadAll(&input_data, in_file.get())) { - fprintf(stderr, "Failed to read input file '%s'.\n", in_path.c_str()); + + // 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"); + return false; + } + + // Set issuer name + if (!X509_set_issuer_name(x509.get(), X509_REQ_get_subject_name(csr.get()))) { + fprintf(stderr, "Error: unable to set issuer name\n"); + return false; + } + + // 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)) { + fprintf(stderr, "Error: unable to set validity period\n"); return false; } - // Parse x509 certificate from input file - const uint8_t *p = input_data.data(); - bssl::UniquePtr x509(d2i_X509(nullptr, &p, input_data.size())); + // Sign the certificate with the provided key + if (!signkey_path.empty()) { + if (!LoadPrivateKeyAndSignCertificate(x509.get(), signkey_path)) { + return false; + } + } + + // Write the signed certificate to output file + if (!noout && !out_path.empty()) { + if (!WriteSignedCertificate(x509.get(), out_path)) { + return false; + } + } + } else { + // Parse x509 certificate from input PEM file + bssl::UniquePtr x509(PEM_read_X509(in_file.get(), nullptr, nullptr, nullptr)); if (!x509) { - fprintf(stderr, "Failed to parse X509 certificate from '%s'.\n", in_path.c_str()); - ERR_print_errors_fp(stderr); + fprintf(stderr, "Error: error parsing certificate from '%s'\n", in_path.c_str()); + ERR_print_errors_fp(stderr); + return false; + } + + if (dates) { + bssl::UniquePtr bio(BIO_new(BIO_s_mem())); + + if (ASN1_TIME_print(bio.get(), X509_get_notBefore(x509.get()))) { + char not_before[30] = {}; + BIO_read(bio.get(), not_before, sizeof(not_before) - 1); + fprintf(stdout, "notBefore=%s\n", not_before); + } + + if (ASN1_TIME_print(bio.get(), X509_get_notAfter(x509.get()))) { + char not_after[30] = {}; + BIO_read(bio.get(), not_after, sizeof(not_after) - 1); + fprintf(stdout, "notAfter=%s\n", not_after); + } + } + + if (modulus) { + bssl::UniquePtr pkey(X509_get_pubkey(x509.get())); + if (!pkey) { + fprintf(stderr, "Error: unable to load public key from certificate\n"); + return false; + } + + if (EVP_PKEY_base_id(pkey.get()) == EVP_PKEY_RSA) { + const RSA *rsa = EVP_PKEY_get0_RSA(pkey.get()); + if (!rsa) { + fprintf(stderr, "Error: unable to load RSA key\n"); + return false; + } + const BIGNUM *n = RSA_get0_n(rsa); + if (!n) { + fprintf(stderr, "Error: unable to load modulus\n"); + return false; + } + printf("Modulus="); + BN_print_fp(stdout, n); + printf("\n"); + } else { + fprintf(stderr, "Error: public key is not an RSA key\n"); return false; + } } - // Serialize certificate to DER format - uint8_t *out_data = nullptr; - int len = i2d_X509(x509.get(), &out_data); - if (len < 0) { - fprintf(stderr, "Failed to serialize X509 certificate.\n"); - ERR_print_errors_fp(stderr); + if (checkend) { + bssl::UniquePtr current_time(ASN1_TIME_set(nullptr, std::time(nullptr))); + ASN1_TIME *end_time = X509_getm_notAfter(x509.get()); + int days_left, seconds_left; + if (!ASN1_TIME_diff(&days_left, &seconds_left, current_time.get(), end_time)) { + fprintf(stderr, "Error: failed to calculate time difference\n"); return false; + } + + if ((days_left * 86400 + seconds_left) < static_cast(*checkend)) { + printf("Certificate will expire\n"); + } else { + printf("Certificate will not expire\n"); + } } - // Write output file using WriteToFile function from tool/file.cc - if (!WriteToFile(out_path, out_data, len)) { - fprintf(stderr, "Failed to write X509 certificate to '%s'.\n", out_path.c_str()); - OPENSSL_free(out_data); + if (!signkey_path.empty()) { + if (!LoadPrivateKeyAndSignCertificate(x509.get(), signkey_path)) { return false; + } } - OPENSSL_free(out_data); - return true; + if (!noout && !out_path.empty()) { + if (!WriteSignedCertificate(x509.get(), out_path)) { + return false; + } + } + } + return true; } diff --git a/tool-openssl/x509_test.cc b/tool-openssl/x509_test.cc index f0dc90e97d..aeb0d373d5 100644 --- a/tool-openssl/x509_test.cc +++ b/tool-openssl/x509_test.cc @@ -2,13 +2,17 @@ // SPDX-License-Identifier: Apache-2.0 OR ISC #include "openssl/x509.h" -#include #include -#include "../tool/internal.h" +#include #include "internal.h" +#include +#include +#include +#include +#include #ifdef _WIN32 -#include +#include #ifndef PATH_MAX #define PATH_MAX MAX_PATH #endif @@ -21,17 +25,18 @@ size_t createTempFILEpath(char buffer[PATH_MAX]); +void RemoveFile(const char* path); + X509* CreateAndSignX509Certificate() { bssl::UniquePtr x509(X509_new()); if (!x509) return nullptr; - // Set validity period for 1 year + // Set validity period for 30 days if (!X509_gmtime_adj(X509_getm_notBefore(x509.get()), 0) || - !X509_gmtime_adj(X509_getm_notAfter(x509.get()), 31536000L)) { + !X509_gmtime_adj(X509_getm_notAfter(x509.get()), 60 * 60 * 24 * 30L)) { return nullptr; } - // Generate and set the public key bssl::UniquePtr pkey(EVP_PKEY_new()); if (!pkey) { return nullptr; @@ -47,7 +52,6 @@ X509* CreateAndSignX509Certificate() { return nullptr; } - // Sign certificate if (X509_sign(x509.get(), pkey.get(), EVP_sha256()) <= 0) { return nullptr; } @@ -55,51 +59,326 @@ X509* CreateAndSignX509Certificate() { return x509.release(); } +void RemoveFile(const char* path) { + if (remove(path) != 0) { + fprintf(stderr, "Error deleting %s: %s\n", path, strerror(errno)); + } +} + // Test x509 -in and -out TEST(X509Test, X509ToolTest) { - char in_path[PATH_MAX]; - char out_path[PATH_MAX]; + char in_path[PATH_MAX]; + char out_path[PATH_MAX]; + + ASSERT_GT(createTempFILEpath(in_path), 0u); + ASSERT_GT(createTempFILEpath(out_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())); + } + + args_list_t args = {"-in", in_path, "-out", out_path}; + bool result = X509Tool(args); + ASSERT_TRUE(result); + + { + ScopedFILE out_file(fopen(out_path, "rb")); + ASSERT_TRUE(out_file); - ASSERT_GT(createTempFILEpath(in_path), 0u); - ASSERT_GT(createTempFILEpath(out_path), 0u); + bssl::UniquePtr parsed_x509(PEM_read_X509(out_file.get(), nullptr, nullptr, nullptr)); + ASSERT_TRUE(parsed_x509); + } + + RemoveFile(in_path); + RemoveFile(out_path); +} - bssl::UniquePtr x509(CreateAndSignX509Certificate()); - ASSERT_TRUE(x509); +// Test -modulus +TEST(X509Test, X509ToolModulusTest) { + char in_path[PATH_MAX]; - // Serialize certificate to DER format - uint8_t *der_data = nullptr; - int len = i2d_X509(x509.get(), &der_data); - ASSERT_GT(static_cast(len), 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); - fwrite(der_data, 1, len, in_file.get()); - OPENSSL_free(der_data); + ASSERT_TRUE(PEM_write_X509(in_file.get(), x509.get())); + } - in_file.reset(); + args_list_t args = {"-in", in_path, "-modulus"}; + bool result = X509Tool(args); + ASSERT_TRUE(result); - // Set up x509 tool arguments - args_list_t args = {"-in", in_path, "-out", out_path}; + RemoveFile(in_path); +} - // Call x509 tool function - bool result = X509Tool(args); - ASSERT_TRUE(result); +// Test -signkey +TEST(X509Test, X509ToolSignKeyTest) { + char in_path[PATH_MAX]; + char out_path[PATH_MAX]; + char signkey_path[PATH_MAX]; - // Read and verify output file - ScopedFILE out_file(fopen(out_path, "rb")); - ASSERT_TRUE(out_file); + ASSERT_GT(createTempFILEpath(in_path), 0u); + ASSERT_GT(createTempFILEpath(out_path), 0u); + ASSERT_GT(createTempFILEpath(signkey_path), 0u); - std::vector output_data; - ASSERT_TRUE(ReadAll(&output_data, out_file.get())); + bssl::UniquePtr pkey(EVP_PKEY_new()); + ASSERT_TRUE(pkey); - // Ensure output data not empty - ASSERT_FALSE(output_data.empty()); + bssl::UniquePtr rsa(RSA_new()); + ASSERT_TRUE(rsa); + 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(EVP_PKEY_assign_RSA(pkey.get(), rsa.release())); - // Parse x509 cert from output file - const uint8_t *p = output_data.data(); - bssl::UniquePtr parsed_x509(d2i_X509(nullptr, &p, output_data.size())); - ASSERT_TRUE(parsed_x509); + { + ScopedFILE signkey_file(fopen(signkey_path, "wb")); + ASSERT_TRUE(signkey_file); + ASSERT_TRUE(PEM_write_PrivateKey(signkey_file.get(), pkey.get(), nullptr, nullptr, 0, nullptr, nullptr)); + } + + bssl::UniquePtr x509(CreateAndSignX509Certificate()); + ASSERT_TRUE(x509); + + { + ScopedFILE in_file(fopen(in_path, "wb")); + ASSERT_TRUE(in_file); + ASSERT_TRUE(PEM_write_X509(in_file.get(), x509.get())); + } + + args_list_t args = {"-in", in_path, "-out", out_path, "-signkey", signkey_path}; + bool result = X509Tool(args); + ASSERT_TRUE(result); + + RemoveFile(in_path); + RemoveFile(out_path); + RemoveFile(signkey_path); +} + +// Test -days +TEST(X509Test, X509ToolDaysTest) { + char in_path[PATH_MAX]; + char out_path[PATH_MAX]; + char signkey_path[PATH_MAX]; + + ASSERT_GT(createTempFILEpath(in_path), 0u); + ASSERT_GT(createTempFILEpath(out_path), 0u); + ASSERT_GT(createTempFILEpath(signkey_path), 0u); + + 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 && BN_set_word(bn.get(), RSA_F4) && RSA_generate_key_ex(rsa.get(), 2048, bn.get(), nullptr)); + ASSERT_TRUE(EVP_PKEY_assign_RSA(pkey.get(), rsa.release())); + + { + ScopedFILE signkey_file(fopen(signkey_path, "wb")); + ASSERT_TRUE(signkey_file); + ASSERT_TRUE(PEM_write_PrivateKey(signkey_file.get(), pkey.get(), nullptr, nullptr, 0, nullptr, nullptr)); + } + + bssl::UniquePtr x509(CreateAndSignX509Certificate()); + ASSERT_TRUE(x509); + + { + ScopedFILE in_file(fopen(in_path, "wb")); + ASSERT_TRUE(in_file); + ASSERT_TRUE(PEM_write_X509(in_file.get(), x509.get())); + } + + args_list_t args = {"-in", in_path, "-out", out_path, "-signkey", signkey_path, "-days", "365"}; + bool result = X509Tool(args); + ASSERT_TRUE(result); + + RemoveFile(in_path); + RemoveFile(out_path); + RemoveFile(signkey_path); +} + +// Test -dates +TEST(X509Test, X509ToolDatesTest) { + char in_path[PATH_MAX]; + + 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())); + } + + args_list_t args = {"-in", in_path, "-dates"}; + bool result = X509Tool(args); + ASSERT_TRUE(result); + + RemoveFile(in_path); +} + +// Test -req +TEST(X509Test, X509ToolReqTest) { + char in_path[PATH_MAX]; + char out_path[PATH_MAX]; + char signkey_path[PATH_MAX]; + + ASSERT_GT(createTempFILEpath(in_path), 0u); + ASSERT_GT(createTempFILEpath(out_path), 0u); + ASSERT_GT(createTempFILEpath(signkey_path), 0u); + + 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 && BN_set_word(bn.get(), RSA_F4) && RSA_generate_key_ex(rsa.get(), 2048, bn.get(), nullptr)); + ASSERT_TRUE(EVP_PKEY_assign_RSA(pkey.get(), rsa.release())); + + { + ScopedFILE signkey_file(fopen(signkey_path, "wb")); + ASSERT_TRUE(signkey_file); + ASSERT_TRUE(PEM_write_PrivateKey(signkey_file.get(), pkey.get(), nullptr, nullptr, 0, nullptr, nullptr)); + } + + bssl::UniquePtr req(X509_REQ_new()); + ASSERT_TRUE(req); + X509_REQ_set_pubkey(req.get(), pkey.get()); + X509_REQ_sign(req.get(), pkey.get(), EVP_sha256()); + + { + ScopedFILE in_file(fopen(in_path, "wb")); + ASSERT_TRUE(in_file); + ASSERT_TRUE(PEM_write_X509_REQ(in_file.get(), req.get())); + } + + args_list_t args = {"-in", in_path, "-out", out_path, "-req", "-signkey", signkey_path}; + bool result = X509Tool(args); + ASSERT_TRUE(result); + + RemoveFile(in_path); + RemoveFile(out_path); + RemoveFile(signkey_path); +} + +// Test -checkend +TEST(X509Test, X509ToolCheckEndTest) { + char in_path[PATH_MAX]; + + 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())); + } + + args_list_t args = {"-in", in_path, "-checkend", "3600"}; + bool result = X509Tool(args); + ASSERT_TRUE(result); + + RemoveFile(in_path); +} + +// Test mutually exclusive options, required options, and required arugments +TEST(X509Test, MutuallyExclusiveOptionsTest) { + char in_path[PATH_MAX]; + char out_path[PATH_MAX]; + char signkey_path[PATH_MAX]; + + ASSERT_GT(createTempFILEpath(in_path), 0u); + ASSERT_GT(createTempFILEpath(out_path), 0u); + ASSERT_GT(createTempFILEpath(signkey_path), 0u); + + 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 && BN_set_word(bn.get(), RSA_F4) && RSA_generate_key_ex(rsa.get(), 2048, bn.get(), nullptr)); + ASSERT_TRUE(EVP_PKEY_assign_RSA(pkey.get(), rsa.release())); + + { + ScopedFILE signkey_file(fopen(signkey_path, "wb")); + ASSERT_TRUE(signkey_file); + ASSERT_TRUE(PEM_write_PrivateKey(signkey_file.get(), pkey.get(), nullptr, nullptr, 0, nullptr, nullptr)); + } + + bssl::UniquePtr x509(CreateAndSignX509Certificate()); + ASSERT_TRUE(x509); + + { + ScopedFILE in_file(fopen(in_path, "wb")); + ASSERT_TRUE(in_file); + ASSERT_TRUE(PEM_write_X509(in_file.get(), x509.get())); + } + + // -noout with -out, -modulues, -dates, -checkend + args_list_t args = {"-in", in_path, "-noout", "-out", out_path}; + ASSERT_FALSE(X509Tool(args)); + args = {"-in", in_path, "-noout", "-modulus"}; + ASSERT_FALSE(X509Tool(args)); + args = {"-in", in_path, "-noout", "-dates"}; + ASSERT_FALSE(X509Tool(args)); + args = {"-in", in_path, "-noout", "-checkend", "3600"}; + ASSERT_FALSE(X509Tool(args)); + + // -req with -dates, -checkend + args = {"-in", in_path, "-req", "-dates"}; + ASSERT_FALSE(X509Tool(args)); + args = {"-in", in_path, "-req", "-checkend", "3600"}; + ASSERT_FALSE(X509Tool(args)); + + // -signkey with -dates, -checkend + args = {"-in", in_path, "-signkey", signkey_path, "-dates"}; + ASSERT_FALSE(X509Tool(args)); + args = {"-in", in_path, "-signkey", signkey_path, "-checkend", "3600"}; + ASSERT_FALSE(X509Tool(args)); + + // -days with -dates, -checkend + args = {"-in", in_path, "-days", "365", "-dates"}; + ASSERT_FALSE(X509Tool(args)); + args = {"-in", in_path, "-days", "365", "-checkend", "3600"}; + ASSERT_FALSE(X509Tool(args)); + + // Test missing -in + args = {"-out", "output.pem"}; + ASSERT_FALSE(X509Tool(args)); + + // Test -req without -signkey + args = {"-in", in_path, "-req"}; + ASSERT_FALSE(X509Tool(args)); + + // Test invalid arguments for -checkend + args = {"-in", in_path, "-checkend", "abc"}; + ASSERT_FALSE(X509Tool(args)); + args = {"-in", in_path, "-checkend", "-1"}; + ASSERT_FALSE(X509Tool(args)); + + // Test invalid arguments for -days + args = {"-in", in_path, "-signkey", signkey_path, "-days", "abc"}; + ASSERT_FALSE(X509Tool(args)); + args = {"-in", in_path, "-signkey", signkey_path, "-days", "0"}; + ASSERT_FALSE(X509Tool(args)); + args = {"-in", in_path, "-signkey", signkey_path, "-days", "-1.7"}; + ASSERT_FALSE(X509Tool(args)); - remove(in_path); - remove(out_path); + RemoveFile(in_path); + RemoveFile(out_path); + RemoveFile(signkey_path); }