Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding -verify and expanding -x509 options for our OpenSSL tool #1951

Merged
merged 21 commits into from
Nov 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading