diff --git a/CMakeLists.txt b/CMakeLists.txt index efeefc040f..6ed581521f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -949,6 +949,7 @@ if(BUILD_LIBSSL) add_subdirectory(ssl) if(BUILD_TOOL) add_subdirectory(tool) + add_subdirectory(tool-openssl) endif() endif() add_subdirectory(util/fipstools) diff --git a/tool-openssl/CMakeLists.txt b/tool-openssl/CMakeLists.txt new file mode 100644 index 0000000000..7f5b4231a0 --- /dev/null +++ b/tool-openssl/CMakeLists.txt @@ -0,0 +1,55 @@ +add_executable( + openssl + + ../tool/args.cc + ../tool/file.cc + tool.cc + x509.cc +) + +target_include_directories(openssl PUBLIC ${PROJECT_SOURCE_DIR}/include) +target_compile_options(openssl PUBLIC -DINTERNAL_TOOL) + +if(WIN32) + target_link_libraries(openssl ws2_32) +endif() + +if(APPLE OR WIN32 OR ANDROID) + target_link_libraries(openssl ssl crypto) + set(LIBRT_FLAG "") +else() + find_library(FOUND_LIBRT rt) + if(FOUND_LIBRT) + target_link_libraries(openssl ssl crypto -lrt) + set(LIBRT_FLAG "-lrt") + else() + target_link_libraries(openssl ssl crypto) + set(LIBRT_FLAG "") + endif() +endif() + +target_include_directories(openssl BEFORE PRIVATE ${PROJECT_BINARY_DIR}/symbol_prefix_include) + +install(TARGETS openssl + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + BUNDLE DESTINATION ${CMAKE_INSTALL_BINDIR} +) + +if(MSVC AND CMAKE_BUILD_TYPE_LOWER MATCHES "relwithdebinfo" AND FIPS) + install (FILES $/openssl.pdb DESTINATION ${CMAKE_INSTALL_LIBDIR}) +endif() + +if(BUILD_TESTING) + add_executable( + tool_openssl_test + + x509_test.cc + ../tool/args.cc + ../tool/file.cc + x509.cc + ) + + target_link_libraries(tool_openssl_test boringssl_gtest_main ssl crypto) + target_include_directories(tool_openssl_test BEFORE PRIVATE ${PROJECT_BINARY_DIR}/symbol_prefix_include) + add_dependencies(all_tests tool_openssl_test) +endif() diff --git a/tool-openssl/README.md b/tool-openssl/README.md new file mode 100644 index 0000000000..a6a4da6f4c --- /dev/null +++ b/tool-openssl/README.md @@ -0,0 +1,8 @@ +# 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 + diff --git a/tool-openssl/internal.h b/tool-openssl/internal.h new file mode 100644 index 0000000000..0078af1eba --- /dev/null +++ b/tool-openssl/internal.h @@ -0,0 +1,21 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 OR ISC + +#ifndef INTERNAL_H +#define INTERNAL_H + +#include "../tool/internal.h" +#include +#include + +typedef bool (*tool_func_t)(const std::vector &args); + +X509* CreateAndSignX509Certificate(); +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/tool.cc b/tool-openssl/tool.cc new file mode 100644 index 0000000000..f363d885ce --- /dev/null +++ b/tool-openssl/tool.cc @@ -0,0 +1,113 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 OR ISC + +#include +#include +#include + +#if defined(OPENSSL_WINDOWS) +#include +#include +#else +#include +#include +#endif + +#include "./internal.h" + +typedef bool (*tool_func_t)(const std::vector &args); + +struct Tool { + const char *name; + tool_func_t func; +}; + +static const std::array kTools = {{ + { "x509", X509Tool }, +}}; + +static void usage(const std::string &name) { + std::cout << "Usage: " << name << " COMMAND\n\n"; + std::cout << "Available commands:\n"; + + for (const auto& tool : kTools) { + if (tool.func == nullptr) { + break; + } + std::cout << " " << tool.name << "\n"; + } +} + +static void initialize() { +#if defined(OPENSSL_WINDOWS) + // Read and write in binary mode. This makes bssl on Windows consistent with + // bssl on other platforms, and also makes it consistent with MSYS's commands + // like diff(1) and md5sum(1). This is especially important for the digest + // commands. + if (_setmode(_fileno(stdin), _O_BINARY) == -1) { + perror("_setmode(_fileno(stdin), O_BINARY)"); + exit(1); + } + if (_setmode(_fileno(stdout), _O_BINARY) == -1) { + perror("_setmode(_fileno(stdout), O_BINARY)"); + exit(1); + } + if (_setmode(_fileno(stderr), _O_BINARY) == -1) { + perror("_setmode(_fileno(stderr), O_BINARY)"); + exit(1); + } +#else + // Ignore SIGPIPE to prevent the process from terminating if it tries to + // write to a pipe that has been closed by the reading end. SIGPIPE can be + // received when writing to sockets or pipes that are no longer connected. + signal(SIGPIPE, SIG_IGN); +#endif +} + +tool_func_t FindTool(const std::string &name) { + for (const auto& tool : kTools) { + if (tool.name == name) { + return tool.func; + } + } + return nullptr; +} + +tool_func_t FindTool(int argc, char **argv, int &starting_arg) { +#if !defined(OPENSSL_WINDOWS) + tool_func_t tool = FindTool(basename(argv[0])); + if (tool != nullptr) { + return tool; + } +#endif + starting_arg++; + if (argc > 1) { + return FindTool(argv[1]); + } + return nullptr; +} + +int main(int argc, char **argv) { + initialize(); + CRYPTO_library_init(); + + int starting_arg = 1; + tool_func_t tool = FindTool(argc, argv, starting_arg); + + if (tool == nullptr) { + usage(argv[0]); + return 1; + } + + args_list_t args; + for (int i = starting_arg; i < argc; i++) { + args.emplace_back(argv[i]); + } + + if (!tool(args)) { + ERR_print_errors_fp(stderr); + return 1; + } + + return 0; +} diff --git a/tool-openssl/x509.cc b/tool-openssl/x509.cc new file mode 100644 index 0000000000..aca034b01e --- /dev/null +++ b/tool-openssl/x509.cc @@ -0,0 +1,74 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 OR ISC + +#include +#include +#include "internal.h" + +static const argument_t kArguments[] = { + { "-in", kRequiredArgument, "Input file" }, + { "-out", kRequiredArgument, "Output file" }, + { "", kOptionalArgument, "" } +}; + +// 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; + } + + // 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; + } + if (!GetString(&out_path, "-out", "", parsed_args)) { + fprintf(stderr, "Missing required argument: -out\n"); + PrintUsage(kArguments); + 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()); + return false; + } + if (!ReadAll(&input_data, in_file.get())) { + fprintf(stderr, "Failed to read input file '%s'.\n", in_path.c_str()); + 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())); + if (!x509) { + fprintf(stderr, "Failed to parse X509 certificate from '%s'.\n", in_path.c_str()); + ERR_print_errors_fp(stderr); + 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); + return false; + } + + // 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); + return false; + } + + OPENSSL_free(out_data); + return true; +} diff --git a/tool-openssl/x509_test.cc b/tool-openssl/x509_test.cc new file mode 100644 index 0000000000..f0dc90e97d --- /dev/null +++ b/tool-openssl/x509_test.cc @@ -0,0 +1,105 @@ +// 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 "../tool/internal.h" +#include "internal.h" + +#ifdef _WIN32 +#include +#ifndef PATH_MAX +#define PATH_MAX MAX_PATH +#endif +#else +#include +#ifndef PATH_MAX +#define PATH_MAX 4096 +#endif +#endif + +size_t createTempFILEpath(char buffer[PATH_MAX]); + +X509* CreateAndSignX509Certificate() { + bssl::UniquePtr x509(X509_new()); + if (!x509) return nullptr; + + // Set validity period for 1 year + if (!X509_gmtime_adj(X509_getm_notBefore(x509.get()), 0) || + !X509_gmtime_adj(X509_getm_notAfter(x509.get()), 31536000L)) { + return nullptr; + } + + // Generate and set the public key + 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; + } + + // Sign certificate + if (X509_sign(x509.get(), pkey.get(), EVP_sha256()) <= 0) { + return nullptr; + } + + return x509.release(); +} + +// Test x509 -in and -out +TEST(X509Test, X509ToolTest) { + 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); + + // Serialize certificate to DER format + uint8_t *der_data = nullptr; + int len = i2d_X509(x509.get(), &der_data); + ASSERT_GT(static_cast(len), 0u); + + ScopedFILE in_file(fopen(in_path, "wb")); + ASSERT_TRUE(in_file); + fwrite(der_data, 1, len, in_file.get()); + OPENSSL_free(der_data); + + in_file.reset(); + + // Set up x509 tool arguments + args_list_t args = {"-in", in_path, "-out", out_path}; + + // Call x509 tool function + bool result = X509Tool(args); + ASSERT_TRUE(result); + + // Read and verify output file + ScopedFILE out_file(fopen(out_path, "rb")); + ASSERT_TRUE(out_file); + + std::vector output_data; + ASSERT_TRUE(ReadAll(&output_data, out_file.get())); + + // Ensure output data not empty + ASSERT_FALSE(output_data.empty()); + + // 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); + + remove(in_path); + remove(out_path); +} diff --git a/util/all_tests.json b/util/all_tests.json index 464414ad8d..246b5af026 100644 --- a/util/all_tests.json +++ b/util/all_tests.json @@ -125,5 +125,10 @@ "cmd": [ "crypto/rwlock_static_init" ] + }, + { + "cmd": [ + "tool-openssl/tool_openssl_test" + ] } ]