Skip to content

Commit

Permalink
Add initial x509 tool (#1666)
Browse files Browse the repository at this point in the history
Description: 
- 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

_Files expected to change_


By submitting this pull request, I confirm that my contribution is made
under the terms of the Apache 2.0 license and the ISC license.
  • Loading branch information
ecdeye authored Jul 2, 2024
1 parent 5d71da8 commit 3da2c4a
Show file tree
Hide file tree
Showing 8 changed files with 382 additions and 0 deletions.
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
55 changes: 55 additions & 0 deletions tool-openssl/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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 $<TARGET_FILE_DIR:openssl>/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()
8 changes: 8 additions & 0 deletions tool-openssl/README.md
Original file line number Diff line number Diff line change
@@ -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

21 changes: 21 additions & 0 deletions tool-openssl/internal.h
Original file line number Diff line number Diff line change
@@ -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 <string>
#include <vector>

typedef bool (*tool_func_t)(const std::vector<std::string> &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


113 changes: 113 additions & 0 deletions tool-openssl/tool.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 OR ISC

#include <array>
#include <iostream>
#include <openssl/ssl.h>

#if defined(OPENSSL_WINDOWS)
#include <fcntl.h>
#include <io.h>
#else
#include <libgen.h>
#include <signal.h>
#endif

#include "./internal.h"

typedef bool (*tool_func_t)(const std::vector<std::string> &args);

struct Tool {
const char *name;
tool_func_t func;
};

static const std::array<Tool, 1> 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;
}
74 changes: 74 additions & 0 deletions tool-openssl/x509.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 OR ISC

#include <openssl/x509.h>
#include <openssl/pem.h>
#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<uint8_t> 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> 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;
}
105 changes: 105 additions & 0 deletions tool-openssl/x509_test.cc
Original file line number Diff line number Diff line change
@@ -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 <openssl/err.h>
#include <gtest/gtest.h>
#include "../tool/internal.h"
#include "internal.h"

#ifdef _WIN32
#include <windows.h>
#ifndef PATH_MAX
#define PATH_MAX MAX_PATH
#endif
#else
#include <unistd.h>
#ifndef PATH_MAX
#define PATH_MAX 4096
#endif
#endif

size_t createTempFILEpath(char buffer[PATH_MAX]);

X509* CreateAndSignX509Certificate() {
bssl::UniquePtr<X509> 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<EVP_PKEY> pkey(EVP_PKEY_new());
if (!pkey) {
return nullptr;
}
bssl::UniquePtr<RSA> rsa(RSA_new());
bssl::UniquePtr<BIGNUM> 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> 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<size_t>(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<uint8_t> 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<X509> parsed_x509(d2i_X509(nullptr, &p, output_data.size()));
ASSERT_TRUE(parsed_x509);

remove(in_path);
remove(out_path);
}
Loading

0 comments on commit 3da2c4a

Please sign in to comment.