Skip to content

Commit 5597715

Browse files
authored
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.
1 parent 412018d commit 5597715

File tree

7 files changed

+725
-69
lines changed

7 files changed

+725
-69
lines changed

tool-openssl/CMakeLists.txt

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,14 @@ add_executable(
77
../tool/client.cc
88
../tool/transport_common.cc
99

10+
crl.cc
1011
dgst.cc
1112
rsa.cc
13+
s_client.cc
1214
tool.cc
13-
x509.cc
14-
crl.cc
15+
verify.cc
1516
version.cc
16-
s_client.cc
17+
x509.cc
1718
)
1819

1920
target_include_directories(openssl PUBLIC ${PROJECT_SOURCE_DIR}/include)
@@ -59,15 +60,17 @@ if(BUILD_TESTING)
5960
../tool/client.cc
6061
../tool/transport_common.cc
6162

63+
crl.cc
64+
crl_test.cc
6265
dgst.cc
6366
dgst_test.cc
6467
rsa.cc
6568
rsa_test.cc
69+
s_client.cc
70+
verify.cc
71+
verify_test.cc
6672
x509.cc
6773
x509_test.cc
68-
s_client.cc
69-
crl.cc
70-
crl_test.cc
7174
)
7275

7376
target_link_libraries(tool_openssl_test boringssl_gtest_main ssl crypto)

tool-openssl/internal.h

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,13 @@ bool LoadPrivateKeyAndSignCertificate(X509 *x509, const std::string &signkey_pat
2828
tool_func_t FindTool(const std::string &name);
2929
tool_func_t FindTool(int argc, char **argv, int &starting_arg);
3030

31+
bool CRLTool(const args_list_t &args);
3132
bool dgstTool(const args_list_t &args);
3233
bool md5Tool(const args_list_t &args);
3334
bool rsaTool(const args_list_t &args);
34-
bool X509Tool(const args_list_t &args);
35-
bool CRLTool(const args_list_t &args);
36-
bool VersionTool(const args_list_t &args);
3735
bool SClientTool(const args_list_t &args);
36+
bool VerifyTool(const args_list_t &args);
37+
bool VersionTool(const args_list_t &args);
38+
bool X509Tool(const args_list_t &args);
3839

3940
#endif //INTERNAL_H

tool-openssl/tool.cc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,13 @@
1515

1616
#include "./internal.h"
1717

18-
static const std::array<Tool, 7> kTools = {{
18+
static const std::array<Tool, 8> kTools = {{
1919
{"crl", CRLTool},
2020
{"dgst", dgstTool},
2121
{"md5", md5Tool},
2222
{"rsa", rsaTool},
2323
{"s_client", SClientTool},
24+
{"verify", VerifyTool},
2425
{"version", VersionTool},
2526
{"x509", X509Tool},
2627
}};

tool-openssl/verify.cc

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0 OR ISC
3+
4+
#include <openssl/base.h>
5+
#include <openssl/x509.h>
6+
#include <openssl/pem.h>
7+
#include "internal.h"
8+
9+
// TO-DO: We do not support using a default trust store, therefore -CAfile must
10+
// be a required argument. Once support for default trust stores is added,
11+
// make it an optional argument.
12+
static const argument_t kArguments[] = {
13+
{ "-help", kBooleanArgument, "Display option summary" },
14+
{ "-CAfile", kRequiredArgument, "A file of trusted certificates. The "
15+
"file should contain one or more certificates in PEM format." },
16+
{ "", kOptionalArgument, "" }
17+
};
18+
19+
// setup_verification_store sets up an X509 certificate store for verification.
20+
// It configures the store with file and directory lookups. It loads the
21+
// specified CA file if provided and otherwise uses default locations.
22+
static X509_STORE *setup_verification_store(std::string CAfile) {
23+
bssl::UniquePtr<X509_STORE> store(X509_STORE_new());
24+
X509_LOOKUP *lookup;
25+
26+
if (!store) {
27+
return nullptr;
28+
}
29+
30+
if (!CAfile.empty()) {
31+
lookup = X509_STORE_add_lookup(store.get(), X509_LOOKUP_file());
32+
if (!lookup || !X509_LOOKUP_load_file(lookup, CAfile.c_str(), X509_FILETYPE_PEM)) {
33+
fprintf(stderr, "Error loading file %s\n", CAfile.c_str());
34+
return nullptr;
35+
}
36+
}
37+
38+
// Add default dir path
39+
lookup = X509_STORE_add_lookup(store.get(), X509_LOOKUP_hash_dir());
40+
if (!lookup || !X509_LOOKUP_add_dir(lookup, NULL, X509_FILETYPE_DEFAULT)) {
41+
return nullptr;
42+
}
43+
44+
return store.release();
45+
}
46+
47+
static int cb(int ok, X509_STORE_CTX *ctx) {
48+
if (!ok) {
49+
int cert_error = X509_STORE_CTX_get_error(ctx);
50+
X509 *current_cert = X509_STORE_CTX_get_current_cert(ctx);
51+
52+
if (current_cert != NULL) {
53+
X509_NAME_print_ex_fp(stderr,
54+
X509_get_subject_name(current_cert),
55+
0, XN_FLAG_ONELINE);
56+
fprintf(stderr, "\n");
57+
}
58+
fprintf(stderr, "%serror %d at %d depth lookup: %s\n",
59+
X509_STORE_CTX_get0_parent_ctx(ctx) ? "[CRL path] " : "",
60+
cert_error,
61+
X509_STORE_CTX_get_error_depth(ctx),
62+
X509_verify_cert_error_string(cert_error));
63+
64+
/*
65+
* Pretend that some errors are ok, so they don't stop further
66+
* processing of the certificate chain. Setting ok = 1 does this.
67+
* After X509_verify_cert() is done, we verify that there were
68+
* no actual errors, even if the returned value was positive.
69+
*/
70+
switch (cert_error) {
71+
case X509_V_ERR_NO_EXPLICIT_POLICY:
72+
/* fall thru */
73+
case X509_V_ERR_CERT_HAS_EXPIRED:
74+
/* Continue even if the leaf is a self-signed cert */
75+
case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT:
76+
/* Continue after extension errors too */
77+
case X509_V_ERR_INVALID_CA:
78+
case X509_V_ERR_INVALID_NON_CA:
79+
case X509_V_ERR_PATH_LENGTH_EXCEEDED:
80+
case X509_V_ERR_CRL_HAS_EXPIRED:
81+
case X509_V_ERR_CRL_NOT_YET_VALID:
82+
case X509_V_ERR_UNHANDLED_CRITICAL_EXTENSION:
83+
/* errors due to strict conformance checking (-x509_strict) */
84+
case X509_V_ERR_INVALID_PURPOSE:
85+
ok = 1;
86+
}
87+
}
88+
return ok;
89+
}
90+
91+
static int check(X509_STORE *ctx, const char *file) {
92+
bssl::UniquePtr<X509> cert;
93+
int i = 0, ret = 0;
94+
95+
if (file) {
96+
ScopedFILE cert_file(fopen(file, "rb"));
97+
if (!cert_file) {
98+
fprintf(stderr, "error %s: reading certificate failed\n", file);
99+
return 0;
100+
}
101+
cert.reset(PEM_read_X509(cert_file.get(), nullptr, nullptr, nullptr));
102+
103+
} else {
104+
bssl::UniquePtr<BIO> input(BIO_new_fp(stdin, BIO_CLOSE));
105+
cert.reset(PEM_read_bio_X509(input.get(), nullptr, nullptr, nullptr));
106+
}
107+
108+
if (cert.get() == nullptr) {
109+
return 0;
110+
}
111+
112+
bssl::UniquePtr<X509_STORE_CTX> store_ctx(X509_STORE_CTX_new());
113+
if (store_ctx == nullptr || store_ctx.get() == nullptr) {
114+
fprintf(stderr, "error %s: X.509 store context allocation failed\n",
115+
(file == nullptr) ? "stdin" : file);
116+
return 0;
117+
}
118+
119+
if (!X509_STORE_CTX_init(store_ctx.get(), ctx, cert.get(), nullptr)) {
120+
fprintf(stderr,
121+
"error %s: X.509 store context initialization failed\n",
122+
(file == nullptr) ? "stdin" : file);
123+
return 0;
124+
}
125+
126+
i = X509_verify_cert(store_ctx.get());
127+
if (i > 0 && X509_STORE_CTX_get_error(store_ctx.get()) == X509_V_OK) {
128+
fprintf(stdout, "%s: OK\n", (file == nullptr) ? "stdin" : file);
129+
ret = 1;
130+
} else {
131+
fprintf(stderr,
132+
"error %s: verification failed\n",
133+
(file == nullptr) ? "stdin" : file);
134+
}
135+
136+
return ret;
137+
}
138+
139+
bool VerifyTool(const args_list_t &args) {
140+
std::string cafile;
141+
size_t i = 0;
142+
143+
if (args.size() == 1 && args[0] == "-help") {
144+
fprintf(stderr,
145+
"Usage: verify [options] [cert.pem...]\n"
146+
"Certificates must be in PEM format. They can be specified in one or more files.\n"
147+
"If no files are specified, the tool will read from stdin.\n\n"
148+
"Valid options are:\n");
149+
PrintUsage(kArguments);
150+
return false;
151+
}
152+
153+
// i helps track whether input will be provided via stdin or through a file
154+
if (args.size() >= 1 && args[0] == "-CAfile") {
155+
cafile = args[1];
156+
i += 2;
157+
} else {
158+
fprintf(stderr, "-CAfile must be specified. This tool does not load"
159+
" the default trust store.\n");
160+
return false;
161+
}
162+
163+
bssl::UniquePtr<X509_STORE> store(setup_verification_store(cafile));
164+
// Initialize certificate verification store
165+
if (!store.get()) {
166+
fprintf(stderr, "Error: Unable to setup certificate verification store.");
167+
return false;
168+
}
169+
X509_STORE_set_verify_cb(store.get(), cb);
170+
171+
ERR_clear_error();
172+
173+
int ret = 1;
174+
175+
// No additional file or certs provided, read from stdin
176+
if (args.size() == i) {
177+
ret &= check(store.get(), NULL);
178+
} else {
179+
// Certs provided as files
180+
for (; i < args.size(); i++) {
181+
ret &= check(store.get(), args[i].c_str());
182+
}
183+
}
184+
185+
return ret == 1;
186+
}

tool-openssl/verify_test.cc

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0 OR ISC
3+
4+
#include "openssl/x509.h"
5+
#include <gtest/gtest.h>
6+
#include <openssl/pem.h>
7+
#include "internal.h"
8+
#include "test_util.h"
9+
#include "../crypto/test/test_util.h"
10+
11+
12+
class VerifyTest : public ::testing::Test {
13+
protected:
14+
void SetUp() override {
15+
ASSERT_GT(createTempFILEpath(ca_path), 0u);
16+
ASSERT_GT(createTempFILEpath(in_path), 0u);
17+
18+
bssl::UniquePtr<X509> x509(CreateAndSignX509Certificate());
19+
ASSERT_TRUE(x509);
20+
21+
ScopedFILE in_file(fopen(in_path, "wb"));
22+
ASSERT_TRUE(in_file);
23+
ASSERT_TRUE(PEM_write_X509(in_file.get(), x509.get()));
24+
25+
ScopedFILE ca_file(fopen(ca_path, "wb"));
26+
ASSERT_TRUE(ca_file);
27+
ASSERT_TRUE(PEM_write_X509(ca_file.get(), x509.get()));
28+
}
29+
void TearDown() override {
30+
RemoveFile(ca_path);
31+
RemoveFile(in_path);
32+
}
33+
char ca_path[PATH_MAX];
34+
char in_path[PATH_MAX];
35+
};
36+
37+
38+
// ----------------------------- Verify Option Tests -----------------------------
39+
40+
// Test -CAfile with self-signed certificate
41+
TEST_F(VerifyTest, VerifyTestSelfSignedCertWithCAfileTest) {
42+
args_list_t args = {"-CAfile", ca_path, in_path};
43+
bool result = VerifyTool(args);
44+
ASSERT_TRUE(result);
45+
}
46+
47+
// Test certificate without -CAfile
48+
TEST_F(VerifyTest, VerifyTestSelfSignedCertWithoutCAfile) {
49+
args_list_t args = {in_path};
50+
bool result = VerifyTool(args);
51+
ASSERT_FALSE(result);
52+
}
53+
54+
55+
// -------------------- Verify OpenSSL Comparison Tests --------------------------
56+
57+
// Comparison tests cannot run without set up of environment variables:
58+
// AWSLC_TOOL_PATH and OPENSSL_TOOL_PATH.
59+
60+
class VerifyComparisonTest : public ::testing::Test {
61+
protected:
62+
void SetUp() override {
63+
64+
// Skip gtests if env variables not set
65+
tool_executable_path = getenv("AWSLC_TOOL_PATH");
66+
openssl_executable_path = getenv("OPENSSL_TOOL_PATH");
67+
if (tool_executable_path == nullptr || openssl_executable_path == nullptr) {
68+
GTEST_SKIP() << "Skipping test: AWSLC_TOOL_PATH and/or OPENSSL_TOOL_PATH environment variables are not set";
69+
}
70+
71+
ASSERT_GT(createTempFILEpath(in_path), 0u);
72+
ASSERT_GT(createTempFILEpath(ca_path), 0u);
73+
ASSERT_GT(createTempFILEpath(out_path_tool), 0u);
74+
ASSERT_GT(createTempFILEpath(out_path_openssl), 0u);
75+
76+
x509.reset(CreateAndSignX509Certificate());
77+
ASSERT_TRUE(x509);
78+
79+
ScopedFILE in_file(fopen(in_path, "wb"));
80+
ASSERT_TRUE(in_file);
81+
ASSERT_TRUE(PEM_write_X509(in_file.get(), x509.get()));
82+
83+
ScopedFILE ca_file(fopen(ca_path, "wb"));
84+
ASSERT_TRUE(ca_file);
85+
ASSERT_TRUE(PEM_write_X509(ca_file.get(), x509.get()));
86+
}
87+
88+
void TearDown() override {
89+
if (tool_executable_path != nullptr && openssl_executable_path != nullptr) {
90+
RemoveFile(in_path);
91+
RemoveFile(out_path_tool);
92+
RemoveFile(out_path_openssl);
93+
RemoveFile(ca_path);
94+
}
95+
}
96+
97+
char in_path[PATH_MAX];
98+
char ca_path[PATH_MAX];
99+
char out_path_tool[PATH_MAX];
100+
char out_path_openssl[PATH_MAX];
101+
bssl::UniquePtr<X509> x509;
102+
const char* tool_executable_path;
103+
const char* openssl_executable_path;
104+
std::string tool_output_str;
105+
std::string openssl_output_str;
106+
};
107+
108+
// Test against OpenSSL with -CAfile & self-signed cert fed in as a file
109+
// "openssl verify -CAfile cert.pem cert.pem"
110+
TEST_F(VerifyComparisonTest, VerifyToolOpenSSLCAFileSelfSignedComparison) {
111+
std::string tool_command = std::string(tool_executable_path) + " verify -CAfile " + ca_path + " " + in_path + " &> " + out_path_tool;
112+
std::string openssl_command = std::string(openssl_executable_path) + " verify -CAfile " + ca_path + " " + in_path + " &> " + out_path_openssl;
113+
114+
RunCommandsAndCompareOutput(tool_command, openssl_command, out_path_tool, out_path_openssl, tool_output_str, openssl_output_str);
115+
116+
ASSERT_EQ(tool_output_str, openssl_output_str);
117+
}
118+
119+
// Test against OpenSSL with -CAfile & 2 self-signed cert fed in as files
120+
// "openssl verify -CAfile cert.pem cert.pem cert.pem"
121+
TEST_F(VerifyComparisonTest, VerifyToolOpenSSLCAFileMultipleFilesComparison) {
122+
std::string tool_command = std::string(tool_executable_path) + " verify -CAfile " + ca_path + " " + in_path + " " + in_path + " &> " + out_path_tool;
123+
std::string openssl_command = std::string(openssl_executable_path) + " verify -CAfile " + ca_path + " " + in_path + " " + in_path + " &> " + out_path_openssl;
124+
125+
RunCommandsAndCompareOutput(tool_command, openssl_command, out_path_tool, out_path_openssl, tool_output_str, openssl_output_str);
126+
127+
ASSERT_EQ(tool_output_str, openssl_output_str);
128+
}
129+
130+
// Test against OpenSSL with -CAfile & self-signed cert fed through stdin
131+
// "cat cert.pem | openssl verify -CAfile cert.pem"
132+
TEST_F(VerifyComparisonTest, VerifyToolOpenSSLCAFileSelfSignedStdinComparison) {
133+
std::string tool_command = "cat " + std::string(ca_path) + " | " + std::string(tool_executable_path) + " verify -CAfile " + ca_path + " &> " + out_path_tool;
134+
std::string openssl_command = "cat " + std::string(ca_path) + " | " + std::string(openssl_executable_path) + " verify -CAfile " + ca_path + " &> " + out_path_openssl;
135+
136+
RunCommandsAndCompareOutput(tool_command, openssl_command, out_path_tool, out_path_openssl, tool_output_str, openssl_output_str);
137+
138+
ASSERT_EQ(tool_output_str, openssl_output_str);
139+
}

0 commit comments

Comments
 (0)