-
Notifications
You must be signed in to change notification settings - Fork 123
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Adding -verify and expanding -x509 options for our OpenSSL tool (#1951)
### 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
Showing
7 changed files
with
725 additions
and
69 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} |
Oops, something went wrong.