diff --git a/tool-openssl/CMakeLists.txt b/tool-openssl/CMakeLists.txt index 815489213d..d9a7c6a72c 100644 --- a/tool-openssl/CMakeLists.txt +++ b/tool-openssl/CMakeLists.txt @@ -9,6 +9,7 @@ add_executable( rsa.cc tool.cc x509.cc + crl.cc version.cc ) @@ -59,6 +60,8 @@ if(BUILD_TESTING) rsa_test.cc x509.cc x509_test.cc + crl.cc + crl_test.cc ) target_link_libraries(tool_openssl_test boringssl_gtest_main ssl crypto) diff --git a/tool-openssl/crl.cc b/tool-openssl/crl.cc new file mode 100644 index 0000000000..5449c4b2b7 --- /dev/null +++ b/tool-openssl/crl.cc @@ -0,0 +1,87 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 OR ISC + +#include "internal.h" +#include +#include +#include + +static const argument_t kArguments[] = { + { "-help", kBooleanArgument, "Display option summary" }, + { "-in", kOptionalArgument, "Input file, default stdin" }, + { "-hash", kBooleanArgument, "Print hash value" }, + { "-fingerprint", kBooleanArgument, "Print CRL fingerprint" }, + { "-noout", kBooleanArgument, "No CRL output" }, + { "", kOptionalArgument, "" } +}; + +bool CRLTool(const args_list_t &args) { + args_map_t parsed_args; + if (!ParseKeyValueArguments(&parsed_args, args, kArguments)) { + PrintUsage(kArguments); + return false; + } + + std::string in; + bool help = false, hash = false, fingerprint = false, noout = false; + + GetBoolArgument(&help, "-help", parsed_args); + GetString(&in, "-in", "", parsed_args); + GetBoolArgument(&hash, "-hash", parsed_args); + GetBoolArgument(&fingerprint, "-fingerprint", parsed_args); + GetBoolArgument(&noout, "-noout", parsed_args); + + // Display crl tool option summary + if (help) { + PrintUsage(kArguments); + return false; + } + + // Read from stdin if no -in path provided + ScopedFILE in_file; + if (in.empty()) { + in_file.reset(stdin); + } else { + in_file.reset(fopen(in.c_str(), "rb")); + if (!in_file) { + fprintf(stderr, "unable to load CRL\n"); + return false; + } + } + + bssl::UniquePtr crl(PEM_read_X509_CRL(in_file.get(), NULL, NULL, NULL)); + + if (crl == NULL) { + fprintf(stderr, "unable to load CRL\n"); + return false; + } + + if (hash) { + fprintf(stdout, "%08x\n", X509_NAME_hash(X509_CRL_get_issuer(crl.get()))); + } + + if (fingerprint) { + int j; + unsigned int n; + unsigned char md[EVP_MAX_MD_SIZE]; + + if (!X509_CRL_digest(crl.get(), EVP_sha1(), md, &n)) { + fprintf(stderr, "unable to get encoding of CRL\n"); + return false; + } + fprintf(stdout, "%s Fingerprint=", OBJ_nid2sn(EVP_MD_type(EVP_sha1()))); + + for (j = 0; j < (int)n; j++) { + fprintf(stdout, "%02X%c", md[j], (j + 1 == (int)n) ? '\n' : ':'); + } + } + + if (!noout) { + if(!PEM_write_X509_CRL(stdout, crl.get())) { + fprintf(stderr, "unable to write CRL\n"); + return false; + } + } + + return true; +} diff --git a/tool-openssl/crl_test.cc b/tool-openssl/crl_test.cc new file mode 100644 index 0000000000..0eba58c807 --- /dev/null +++ b/tool-openssl/crl_test.cc @@ -0,0 +1,184 @@ +// 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 "internal.h" +#include "test_util.h" +#include "../crypto/test/test_util.h" +#include + +static X509_CRL* createTestCRL() { + bssl::UniquePtr crl(X509_CRL_new()); + if (!crl) { + ERR_print_errors_fp(stderr); + return nullptr; + } + + // Set issuer name + bssl::UniquePtr issuer(X509_NAME_new()); + if (!issuer || + !X509_NAME_add_entry_by_txt(issuer.get(), "CN", MBSTRING_ASC, (unsigned char *)"Test CA", -1, -1, 0) || + !X509_CRL_set_issuer_name(crl.get(), issuer.get())) { + return nullptr; + } + + // Set times + bssl::UniquePtr lastUpdate(ASN1_TIME_new()); + bssl::UniquePtr nextUpdate(ASN1_TIME_new()); + if (!lastUpdate || !nextUpdate || !X509_gmtime_adj(lastUpdate.get(), 0) || + !X509_gmtime_adj(nextUpdate.get(), 86400L) || // 24 hours from now + !X509_CRL_set1_lastUpdate(crl.get(), lastUpdate.get()) || + !X509_CRL_set1_nextUpdate(crl.get(), nextUpdate.get())) { + return nullptr; + } + + // Add a revoked certificate + X509_REVOKED *revoked = X509_REVOKED_new(); + bssl::UniquePtr serialNumber(ASN1_INTEGER_new()); + if (!revoked || !serialNumber || !ASN1_INTEGER_set(serialNumber.get(), 1) || // Serial number of revoked cert + !X509_REVOKED_set_serialNumber(revoked, serialNumber.get()) || + !X509_REVOKED_set_revocationDate(revoked, lastUpdate.get()) || + !X509_CRL_add0_revoked(crl.get(), revoked)) { + return nullptr; + } + + // Generate a key pair for signing + bssl::UniquePtr pkey(EVP_PKEY_new()); + RSA *rsa = RSA_generate_key(2048, RSA_F4, NULL, NULL); + if (!pkey || !rsa || !EVP_PKEY_assign_RSA(pkey.get(), rsa)) { + return nullptr; + } + + // Sign the CRL + if (!X509_CRL_sign(crl.get(), pkey.get(), EVP_sha256())) { + return nullptr; + } + return crl.release(); +} + +class CRLTest : public ::testing::Test { +protected: + void SetUp() override { + ASSERT_GT(createTempFILEpath(in_path), 0u); + + // Create a test CRL + crl.reset(createTestCRL()); + ASSERT_TRUE(crl); + + + ScopedFILE in_file(fopen(in_path, "wb")); + ASSERT_TRUE(in_file); + PEM_write_X509_CRL(in_file.get(), crl.get()); + } + + void TearDown() override { + RemoveFile(in_path); + } + + char in_path[PATH_MAX]; + bssl::UniquePtr crl; +}; + + +// ----------------------------- CRL Option Tests ----------------------------- + +// Test -in +TEST_F(CRLTest, CRLTestIn) { + args_list_t args = {"-in", in_path}; + bool result = CRLTool(args); + ASSERT_TRUE(result); +} + +// Test -hash +TEST_F(CRLTest, CRLTestHash) { + args_list_t args = {"-in", in_path, "-hash"}; + bool result = CRLTool(args); + ASSERT_TRUE(result); +} + +// Test -fingerprint +TEST_F(CRLTest, CRLTestFingerprint) { + args_list_t args = {"-in", in_path, "-fingerprint"}; + bool result = CRLTool(args); + ASSERT_TRUE(result); +} + +// Test -noout +TEST_F(CRLTest, CRLTestNoout) { + args_list_t args = {"-in", in_path, "-noout"}; + bool result = CRLTool(args); + ASSERT_TRUE(result); +} + +// -------------------- CRL OpenSSL Comparison Tests -------------------------- + +// Comparison tests cannot run without set up of environment variables: +// AWSLC_TOOL_PATH and OPENSSL_TOOL_PATH. + +class CRLComparisonTest : 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(out_path_tool), 0u); + ASSERT_GT(createTempFILEpath(out_path_openssl), 0u); + + // Create a test CRL + crl.reset(createTestCRL()); + ASSERT_TRUE(crl); + + ScopedFILE in_file(fopen(in_path, "wb")); + ASSERT_TRUE(in_file); + PEM_write_X509_CRL(in_file.get(), crl.get()); + } + + void TearDown() override { + if (tool_executable_path != nullptr && openssl_executable_path != nullptr) { + RemoveFile(in_path); + RemoveFile(out_path_tool); + RemoveFile(out_path_openssl); + } + } + + char in_path[PATH_MAX]; + char out_path_tool[PATH_MAX]; + char out_path_openssl[PATH_MAX]; + const char* tool_executable_path; + const char* openssl_executable_path; + std::string tool_output_str; + std::string openssl_output_str; + bssl::UniquePtr crl; +}; + +// Test against OpenSSL output "openssl crl -in file" +TEST_F(CRLComparisonTest, CRLToolCompareOpenSSL) { + std::string tool_command = std::string(tool_executable_path) + " crl -in " + in_path + " > " + out_path_tool; + std::string openssl_command = std::string(openssl_executable_path) + " crl -in " + in_path + " > " + out_path_openssl; + + RunCommandsAndCompareOutput(tool_command, openssl_command, out_path_tool, out_path_openssl, tool_output_str, openssl_output_str); +} + +// Test against OpenSSL output "openssl crl -in file -hash -fingerprint" +TEST_F(CRLComparisonTest, CRLToolCompareHashFingerprintOpenSSL) { + std::string tool_command = std::string(tool_executable_path) + " crl -in " + in_path + " -hash -fingerprint > " + out_path_tool; + std::string openssl_command = std::string(openssl_executable_path) + " crl -in " + in_path + " -hash -fingerprint > " + out_path_openssl; + + RunCommandsAndCompareOutput(tool_command, openssl_command, out_path_tool, out_path_openssl, tool_output_str, openssl_output_str); +} + +// Test against OpenSSL output "openssl crl -in file -hash -fingerprint -noout" +TEST_F(CRLComparisonTest, CRLToolCompareHashFingerprintNoOutOpenSSL) { + std::string tool_command = std::string(tool_executable_path) + " crl -in " + in_path + " -hash -fingerprint -noout > " + out_path_tool; + std::string openssl_command = std::string(openssl_executable_path) + " crl -in " + in_path + " -hash -fingerprint -noout > " + out_path_openssl; + + RunCommandsAndCompareOutput(tool_command, openssl_command, out_path_tool, out_path_openssl, tool_output_str, openssl_output_str); +} diff --git a/tool-openssl/internal.h b/tool-openssl/internal.h index 5453a5fdae..3364f4639f 100644 --- a/tool-openssl/internal.h +++ b/tool-openssl/internal.h @@ -32,6 +32,7 @@ bool dgstTool(const args_list_t &args); bool md5Tool(const args_list_t &args); bool rsaTool(const args_list_t &args); bool X509Tool(const args_list_t &args); +bool CRLTool(const args_list_t &args); bool VersionTool(const args_list_t &args); #endif //INTERNAL_H diff --git a/tool-openssl/tool.cc b/tool-openssl/tool.cc index c4d2f24412..ae42679eca 100644 --- a/tool-openssl/tool.cc +++ b/tool-openssl/tool.cc @@ -15,11 +15,12 @@ #include "./internal.h" -static const std::array kTools = {{ +static const std::array kTools = {{ {"dgst", dgstTool}, {"md5", md5Tool}, {"rsa", rsaTool}, {"x509", X509Tool}, + {"crl", CRLTool}, {"version", VersionTool} }};