Skip to content

Commit

Permalink
Adding -verify and expanding -x509 options for our OpenSSL tool (#1951)
Browse files Browse the repository at this point in the history
### Issues:
`CryptoAlg-2679`

### Description of changes: 
- `-subject`, `-fingerprint`, `-inform`, `-enddate`, `-subject_hash`,
`-subject_hash_old` options added to the x509 tool
- refactored the x509 tool to read from stdin
- A new `verify` tool with `-CAfile`
- The `verify` tool takes a file as input or defaults to reading from
stdin

### Callouts: 
The subject field differs in OpenSSL master and versions <= 3.2. A
normalization function that removes whitespaces is used to compare
output.

### Testing:
Added option tests for new functionality and OpenSSL comparison tests
for the new options.

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
smittals2 authored Nov 26, 2024
1 parent 412018d commit 5597715
Show file tree
Hide file tree
Showing 7 changed files with 725 additions and 69 deletions.
15 changes: 9 additions & 6 deletions tool-openssl/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,14 @@ add_executable(
../tool/client.cc
../tool/transport_common.cc

crl.cc
dgst.cc
rsa.cc
s_client.cc
tool.cc
x509.cc
crl.cc
verify.cc
version.cc
s_client.cc
x509.cc
)

target_include_directories(openssl PUBLIC ${PROJECT_SOURCE_DIR}/include)
Expand Down Expand Up @@ -59,15 +60,17 @@ if(BUILD_TESTING)
../tool/client.cc
../tool/transport_common.cc

crl.cc
crl_test.cc
dgst.cc
dgst_test.cc
rsa.cc
rsa_test.cc
s_client.cc
verify.cc
verify_test.cc
x509.cc
x509_test.cc
s_client.cc
crl.cc
crl_test.cc
)

target_link_libraries(tool_openssl_test boringssl_gtest_main ssl crypto)
Expand Down
7 changes: 4 additions & 3 deletions tool-openssl/internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,13 @@ bool LoadPrivateKeyAndSignCertificate(X509 *x509, const std::string &signkey_pat
tool_func_t FindTool(const std::string &name);
tool_func_t FindTool(int argc, char **argv, int &starting_arg);

bool CRLTool(const args_list_t &args);
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);
bool SClientTool(const args_list_t &args);
bool VerifyTool(const args_list_t &args);
bool VersionTool(const args_list_t &args);
bool X509Tool(const args_list_t &args);

#endif //INTERNAL_H
3 changes: 2 additions & 1 deletion tool-openssl/tool.cc
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,13 @@

#include "./internal.h"

static const std::array<Tool, 7> kTools = {{
static const std::array<Tool, 8> kTools = {{
{"crl", CRLTool},
{"dgst", dgstTool},
{"md5", md5Tool},
{"rsa", rsaTool},
{"s_client", SClientTool},
{"verify", VerifyTool},
{"version", VersionTool},
{"x509", X509Tool},
}};
Expand Down
186 changes: 186 additions & 0 deletions tool-openssl/verify.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 OR ISC

#include <openssl/base.h>
#include <openssl/x509.h>
#include <openssl/pem.h>
#include "internal.h"

// TO-DO: We do not support using a default trust store, therefore -CAfile must
// be a required argument. Once support for default trust stores is added,
// make it an optional argument.
static const argument_t kArguments[] = {
{ "-help", kBooleanArgument, "Display option summary" },
{ "-CAfile", kRequiredArgument, "A file of trusted certificates. The "
"file should contain one or more certificates in PEM format." },
{ "", kOptionalArgument, "" }
};

// setup_verification_store sets up an X509 certificate store for verification.
// It configures the store with file and directory lookups. It loads the
// specified CA file if provided and otherwise uses default locations.
static X509_STORE *setup_verification_store(std::string CAfile) {
bssl::UniquePtr<X509_STORE> store(X509_STORE_new());
X509_LOOKUP *lookup;

if (!store) {
return nullptr;
}

if (!CAfile.empty()) {
lookup = X509_STORE_add_lookup(store.get(), X509_LOOKUP_file());
if (!lookup || !X509_LOOKUP_load_file(lookup, CAfile.c_str(), X509_FILETYPE_PEM)) {
fprintf(stderr, "Error loading file %s\n", CAfile.c_str());
return nullptr;
}
}

// Add default dir path
lookup = X509_STORE_add_lookup(store.get(), X509_LOOKUP_hash_dir());
if (!lookup || !X509_LOOKUP_add_dir(lookup, NULL, X509_FILETYPE_DEFAULT)) {
return nullptr;
}

return store.release();
}

static int cb(int ok, X509_STORE_CTX *ctx) {
if (!ok) {
int cert_error = X509_STORE_CTX_get_error(ctx);
X509 *current_cert = X509_STORE_CTX_get_current_cert(ctx);

if (current_cert != NULL) {
X509_NAME_print_ex_fp(stderr,
X509_get_subject_name(current_cert),
0, XN_FLAG_ONELINE);
fprintf(stderr, "\n");
}
fprintf(stderr, "%serror %d at %d depth lookup: %s\n",
X509_STORE_CTX_get0_parent_ctx(ctx) ? "[CRL path] " : "",
cert_error,
X509_STORE_CTX_get_error_depth(ctx),
X509_verify_cert_error_string(cert_error));

/*
* Pretend that some errors are ok, so they don't stop further
* processing of the certificate chain. Setting ok = 1 does this.
* After X509_verify_cert() is done, we verify that there were
* no actual errors, even if the returned value was positive.
*/
switch (cert_error) {
case X509_V_ERR_NO_EXPLICIT_POLICY:
/* fall thru */
case X509_V_ERR_CERT_HAS_EXPIRED:
/* Continue even if the leaf is a self-signed cert */
case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT:
/* Continue after extension errors too */
case X509_V_ERR_INVALID_CA:
case X509_V_ERR_INVALID_NON_CA:
case X509_V_ERR_PATH_LENGTH_EXCEEDED:
case X509_V_ERR_CRL_HAS_EXPIRED:
case X509_V_ERR_CRL_NOT_YET_VALID:
case X509_V_ERR_UNHANDLED_CRITICAL_EXTENSION:
/* errors due to strict conformance checking (-x509_strict) */
case X509_V_ERR_INVALID_PURPOSE:
ok = 1;
}
}
return ok;
}

static int check(X509_STORE *ctx, const char *file) {
bssl::UniquePtr<X509> cert;
int i = 0, ret = 0;

if (file) {
ScopedFILE cert_file(fopen(file, "rb"));
if (!cert_file) {
fprintf(stderr, "error %s: reading certificate failed\n", file);
return 0;
}
cert.reset(PEM_read_X509(cert_file.get(), nullptr, nullptr, nullptr));

} else {
bssl::UniquePtr<BIO> input(BIO_new_fp(stdin, BIO_CLOSE));
cert.reset(PEM_read_bio_X509(input.get(), nullptr, nullptr, nullptr));
}

if (cert.get() == nullptr) {
return 0;
}

bssl::UniquePtr<X509_STORE_CTX> store_ctx(X509_STORE_CTX_new());
if (store_ctx == nullptr || store_ctx.get() == nullptr) {
fprintf(stderr, "error %s: X.509 store context allocation failed\n",
(file == nullptr) ? "stdin" : file);
return 0;
}

if (!X509_STORE_CTX_init(store_ctx.get(), ctx, cert.get(), nullptr)) {
fprintf(stderr,
"error %s: X.509 store context initialization failed\n",
(file == nullptr) ? "stdin" : file);
return 0;
}

i = X509_verify_cert(store_ctx.get());
if (i > 0 && X509_STORE_CTX_get_error(store_ctx.get()) == X509_V_OK) {
fprintf(stdout, "%s: OK\n", (file == nullptr) ? "stdin" : file);
ret = 1;
} else {
fprintf(stderr,
"error %s: verification failed\n",
(file == nullptr) ? "stdin" : file);
}

return ret;
}

bool VerifyTool(const args_list_t &args) {
std::string cafile;
size_t i = 0;

if (args.size() == 1 && args[0] == "-help") {
fprintf(stderr,
"Usage: verify [options] [cert.pem...]\n"
"Certificates must be in PEM format. They can be specified in one or more files.\n"
"If no files are specified, the tool will read from stdin.\n\n"
"Valid options are:\n");
PrintUsage(kArguments);
return false;
}

// i helps track whether input will be provided via stdin or through a file
if (args.size() >= 1 && args[0] == "-CAfile") {
cafile = args[1];
i += 2;
} else {
fprintf(stderr, "-CAfile must be specified. This tool does not load"
" the default trust store.\n");
return false;
}

bssl::UniquePtr<X509_STORE> store(setup_verification_store(cafile));
// Initialize certificate verification store
if (!store.get()) {
fprintf(stderr, "Error: Unable to setup certificate verification store.");
return false;
}
X509_STORE_set_verify_cb(store.get(), cb);

ERR_clear_error();

int ret = 1;

// No additional file or certs provided, read from stdin
if (args.size() == i) {
ret &= check(store.get(), NULL);
} else {
// Certs provided as files
for (; i < args.size(); i++) {
ret &= check(store.get(), args[i].c_str());
}
}

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

#include "openssl/x509.h"
#include <gtest/gtest.h>
#include <openssl/pem.h>
#include "internal.h"
#include "test_util.h"
#include "../crypto/test/test_util.h"


class VerifyTest : public ::testing::Test {
protected:
void SetUp() override {
ASSERT_GT(createTempFILEpath(ca_path), 0u);
ASSERT_GT(createTempFILEpath(in_path), 0u);

bssl::UniquePtr<X509> 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()));

ScopedFILE ca_file(fopen(ca_path, "wb"));
ASSERT_TRUE(ca_file);
ASSERT_TRUE(PEM_write_X509(ca_file.get(), x509.get()));
}
void TearDown() override {
RemoveFile(ca_path);
RemoveFile(in_path);
}
char ca_path[PATH_MAX];
char in_path[PATH_MAX];
};


// ----------------------------- Verify Option Tests -----------------------------

// Test -CAfile with self-signed certificate
TEST_F(VerifyTest, VerifyTestSelfSignedCertWithCAfileTest) {
args_list_t args = {"-CAfile", ca_path, in_path};
bool result = VerifyTool(args);
ASSERT_TRUE(result);
}

// Test certificate without -CAfile
TEST_F(VerifyTest, VerifyTestSelfSignedCertWithoutCAfile) {
args_list_t args = {in_path};
bool result = VerifyTool(args);
ASSERT_FALSE(result);
}


// -------------------- Verify OpenSSL Comparison Tests --------------------------

// Comparison tests cannot run without set up of environment variables:
// AWSLC_TOOL_PATH and OPENSSL_TOOL_PATH.

class VerifyComparisonTest : 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(ca_path), 0u);
ASSERT_GT(createTempFILEpath(out_path_tool), 0u);
ASSERT_GT(createTempFILEpath(out_path_openssl), 0u);

x509.reset(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()));

ScopedFILE ca_file(fopen(ca_path, "wb"));
ASSERT_TRUE(ca_file);
ASSERT_TRUE(PEM_write_X509(ca_file.get(), x509.get()));
}

void TearDown() override {
if (tool_executable_path != nullptr && openssl_executable_path != nullptr) {
RemoveFile(in_path);
RemoveFile(out_path_tool);
RemoveFile(out_path_openssl);
RemoveFile(ca_path);
}
}

char in_path[PATH_MAX];
char ca_path[PATH_MAX];
char out_path_tool[PATH_MAX];
char out_path_openssl[PATH_MAX];
bssl::UniquePtr<X509> x509;
const char* tool_executable_path;
const char* openssl_executable_path;
std::string tool_output_str;
std::string openssl_output_str;
};

// Test against OpenSSL with -CAfile & self-signed cert fed in as a file
// "openssl verify -CAfile cert.pem cert.pem"
TEST_F(VerifyComparisonTest, VerifyToolOpenSSLCAFileSelfSignedComparison) {
std::string tool_command = std::string(tool_executable_path) + " verify -CAfile " + ca_path + " " + in_path + " &> " + out_path_tool;
std::string openssl_command = std::string(openssl_executable_path) + " verify -CAfile " + ca_path + " " + in_path + " &> " + out_path_openssl;

RunCommandsAndCompareOutput(tool_command, openssl_command, out_path_tool, out_path_openssl, tool_output_str, openssl_output_str);

ASSERT_EQ(tool_output_str, openssl_output_str);
}

// Test against OpenSSL with -CAfile & 2 self-signed cert fed in as files
// "openssl verify -CAfile cert.pem cert.pem cert.pem"
TEST_F(VerifyComparisonTest, VerifyToolOpenSSLCAFileMultipleFilesComparison) {
std::string tool_command = std::string(tool_executable_path) + " verify -CAfile " + ca_path + " " + in_path + " " + in_path + " &> " + out_path_tool;
std::string openssl_command = std::string(openssl_executable_path) + " verify -CAfile " + ca_path + " " + in_path + " " + in_path + " &> " + out_path_openssl;

RunCommandsAndCompareOutput(tool_command, openssl_command, out_path_tool, out_path_openssl, tool_output_str, openssl_output_str);

ASSERT_EQ(tool_output_str, openssl_output_str);
}

// Test against OpenSSL with -CAfile & self-signed cert fed through stdin
// "cat cert.pem | openssl verify -CAfile cert.pem"
TEST_F(VerifyComparisonTest, VerifyToolOpenSSLCAFileSelfSignedStdinComparison) {
std::string tool_command = "cat " + std::string(ca_path) + " | " + std::string(tool_executable_path) + " verify -CAfile " + ca_path + " &> " + out_path_tool;
std::string openssl_command = "cat " + std::string(ca_path) + " | " + std::string(openssl_executable_path) + " verify -CAfile " + ca_path + " &> " + out_path_openssl;

RunCommandsAndCompareOutput(tool_command, openssl_command, out_path_tool, out_path_openssl, tool_output_str, openssl_output_str);

ASSERT_EQ(tool_output_str, openssl_output_str);
}
Loading

0 comments on commit 5597715

Please sign in to comment.