Skip to content

Commit 71144f0

Browse files
smittals2justsmth
andauthored
Adding pkeyutl tool to the CLI (#2575)
### Description of changes: Add pkeutl tool with: -help -in -out -sign -verify -inkey -sigfile -pubin -passin 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. --------- Co-authored-by: Justin Smith <[email protected]> Co-authored-by: Justin W Smith <[email protected]>
1 parent ffe498f commit 71144f0

File tree

6 files changed

+621
-7
lines changed

6 files changed

+621
-7
lines changed

.github/workflows/windows-alt.yml

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,7 @@ jobs:
228228
echo "GOPATH=${SYSROOT}" >> $GITHUB_ENV
229229
echo "GOROOT=${SYSROOT}/lib/go" >> $GITHUB_ENV
230230
echo "CMAKE_GENERATOR=${{ matrix.generator }}" >> $GITHUB_ENV
231-
cygpath -w ${SYSROOT}/bin >> $GITHUB_PATH
231+
cygpath -m ${SYSROOT}/bin >> $GITHUB_PATH
232232
- name: Checkout
233233
uses: actions/checkout@v4
234234
- name: Setup CMake
@@ -244,10 +244,5 @@ jobs:
244244
CMAKE_FIND_ROOT_PATH_MODE_INCLUDE=ONLY \
245245
CMAKE_BUILD_TYPE=Release \
246246
- name: Run tests
247-
if: matrix.generator != 'MSYS Makefiles'
248-
shell: 'bash'
249-
run: cmake --build ./build --target run_tests
250-
- name: Run tests
251-
if: matrix.generator == 'MSYS Makefiles'
252247
shell: 'msys2 {0}'
253248
run: cmake --build ./build --target run_tests

tool-openssl/CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ add_executable(
1414
pass_util.cc
1515
pkcs8.cc
1616
pkey.cc
17+
pkeyutl.cc
1718
rehash.cc
1819
req.cc
1920
ordered_args.cc
@@ -100,6 +101,8 @@ if(BUILD_TESTING)
100101
pkcs8_test.cc
101102
pkey.cc
102103
pkey_test.cc
104+
pkeyutl.cc
105+
pkeyutl_test.cc
103106
rehash.cc
104107
rehash_test.cc
105108
req.cc

tool-openssl/internal.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ bool ecTool(const args_list_t &args);
9696
bool md5Tool(const args_list_t &args);
9797
bool pkcs8Tool(const args_list_t &args);
9898
bool pkeyTool(const args_list_t &args);
99+
bool pkeyutlTool(const args_list_t &args);
99100
bool RehashTool(const args_list_t &args);
100101
bool reqTool(const args_list_t &args);
101102
bool rsaTool(const args_list_t &args);

tool-openssl/pkeyutl.cc

Lines changed: 318 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,318 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0 OR ISC
3+
4+
#include <openssl/pem.h>
5+
#include <openssl/err.h>
6+
#include <openssl/evp.h>
7+
#include <openssl/x509.h>
8+
#include <sys/stat.h>
9+
#include "internal.h"
10+
#include <string>
11+
12+
#define KEY_NONE 0
13+
#define KEY_PRIVKEY 1
14+
#define KEY_PUBKEY 2
15+
16+
static const argument_t kArguments[] = {
17+
{ "-help", kBooleanArgument, "Display option summary" },
18+
{ "-in", kOptionalArgument, "Input file - default stdin" },
19+
{ "-out", kOptionalArgument, "Output file - default stdout" },
20+
{ "-sign", kBooleanArgument, "Sign input data with private key" },
21+
{ "-verify", kBooleanArgument, "Verify with public key" },
22+
{ "-sigfile", kOptionalArgument, "Signature file, required for verify operations only" },
23+
{ "-inkey", kOptionalArgument, "Input private key file" },
24+
{ "-pubin", kBooleanArgument, "Input is a public key" },
25+
{ "-passin", kOptionalArgument, "Input file pass phrase source" },
26+
{ "", kOptionalArgument, "" }
27+
};
28+
29+
static bool LoadPrivateKey(const std::string &keyfile, bssl::UniquePtr<std::string> &passin_arg,
30+
bssl::UniquePtr<EVP_PKEY> &pkey) {
31+
ScopedFILE key_file;
32+
if (keyfile.empty()) {
33+
fprintf(stderr, "Error: no private key given (-inkey parameter)\n");
34+
return false;
35+
}
36+
37+
key_file.reset(fopen(keyfile.c_str(), "rb"));
38+
if (!key_file) {
39+
fprintf(stderr, "Error: unable to load private key from '%s'\n", keyfile.c_str());
40+
return false;
41+
}
42+
43+
// Extract password using pass_util if provided
44+
const char *password = nullptr;
45+
if (passin_arg && !passin_arg->empty()) {
46+
if (!pass_util::ExtractPassword(passin_arg)) {
47+
fprintf(stderr, "Error: failed to extract password\n");
48+
return false;
49+
}
50+
password = passin_arg->c_str();
51+
}
52+
53+
pkey.reset(PEM_read_PrivateKey(key_file.get(), nullptr, nullptr,
54+
const_cast<char*>(password)));
55+
if (!pkey) {
56+
fprintf(stderr, "Error: error reading private key from '%s'\n", keyfile.c_str());
57+
ERR_print_errors_fp(stderr);
58+
return false;
59+
}
60+
61+
return true;
62+
}
63+
64+
static bool LoadPublicKey(const std::string &keyfile, bssl::UniquePtr<EVP_PKEY> &pkey) {
65+
ScopedFILE key_file;
66+
if (keyfile.empty()) {
67+
fprintf(stderr, "Error: no public key given (-inkey parameter)\n");
68+
return false;
69+
}
70+
71+
key_file.reset(fopen(keyfile.c_str(), "rb"));
72+
if (!key_file) {
73+
fprintf(stderr, "Error: unable to load public key from '%s'\n", keyfile.c_str());
74+
return false;
75+
}
76+
77+
pkey.reset(PEM_read_PUBKEY(key_file.get(), nullptr, nullptr, nullptr));
78+
if (!pkey) {
79+
fprintf(stderr, "Error: error reading public key from '%s'\n", keyfile.c_str());
80+
ERR_print_errors_fp(stderr);
81+
return false;
82+
}
83+
84+
return true;
85+
}
86+
87+
static bool ReadInputData(const std::string &in_path, std::vector<uint8_t> &data) {
88+
ScopedFILE in_file;
89+
if (in_path.empty()) {
90+
in_file.reset(stdin);
91+
} else {
92+
in_file.reset(fopen(in_path.c_str(), "rb"));
93+
if (!in_file) {
94+
fprintf(stderr, "Error: unable to open input file '%s'\n", in_path.c_str());
95+
return false;
96+
}
97+
}
98+
99+
if (!ReadAll(&data, in_file.get())) {
100+
fprintf(stderr, "Error: error reading input data\n");
101+
return false;
102+
}
103+
104+
return true;
105+
}
106+
107+
static bool DoSign(EVP_PKEY *pkey, const std::vector<uint8_t> &input_data,
108+
std::vector<uint8_t> &signature) {
109+
bssl::UniquePtr<EVP_PKEY_CTX> ctx(EVP_PKEY_CTX_new(pkey, nullptr));
110+
if (!ctx) {
111+
fprintf(stderr, "Error: failed to create signing context\n");
112+
return false;
113+
}
114+
115+
if (EVP_PKEY_sign_init(ctx.get()) <= 0) {
116+
fprintf(stderr, "Error: failed to initialize signing context\n");
117+
ERR_print_errors_fp(stderr);
118+
return false;
119+
}
120+
121+
size_t sig_len = 0;
122+
if (EVP_PKEY_sign(ctx.get(), nullptr, &sig_len, input_data.data(), input_data.size()) <= 0) {
123+
fprintf(stderr, "Error: failed to determine signature length\n");
124+
ERR_print_errors_fp(stderr);
125+
return false;
126+
}
127+
128+
signature.resize(sig_len);
129+
if (EVP_PKEY_sign(ctx.get(), signature.data(), &sig_len,
130+
input_data.data(), input_data.size()) <= 0) {
131+
fprintf(stderr, "Error: failed to sign data\n");
132+
ERR_print_errors_fp(stderr);
133+
return false;
134+
}
135+
136+
signature.resize(sig_len);
137+
return true;
138+
}
139+
140+
static bool DoVerify(EVP_PKEY *pkey, const std::vector<uint8_t> &input_data,
141+
const std::vector<uint8_t> &signature) {
142+
bssl::UniquePtr<EVP_PKEY_CTX> ctx(EVP_PKEY_CTX_new(pkey, nullptr));
143+
if (!ctx) {
144+
fprintf(stderr, "Error: failed to create verification context\n");
145+
return false;
146+
}
147+
148+
if (EVP_PKEY_verify_init(ctx.get()) <= 0) {
149+
fprintf(stderr, "Error: failed to initialize verification context\n");
150+
ERR_print_errors_fp(stderr);
151+
return false;
152+
}
153+
154+
int result = EVP_PKEY_verify(ctx.get(), signature.data(), signature.size(),
155+
input_data.data(), input_data.size());
156+
if (result == 1) {
157+
return true;
158+
} else if (result == 0) {
159+
return false; // Verification failed
160+
} else {
161+
fprintf(stderr, "Error: verification operation failed\n");
162+
ERR_print_errors_fp(stderr);
163+
return false;
164+
}
165+
}
166+
167+
static bool WriteOutput(const std::vector<uint8_t> &data, const std::string &out_path) {
168+
bssl::UniquePtr<BIO> output_bio;
169+
if (out_path.empty()) {
170+
output_bio.reset(BIO_new_fp(stdout, BIO_CLOSE));
171+
} else {
172+
output_bio.reset(BIO_new(BIO_s_file()));
173+
if (BIO_write_filename(output_bio.get(), out_path.c_str()) <= 0) {
174+
fprintf(stderr, "Error: failed to open output file '%s'\n", out_path.c_str());
175+
return false;
176+
}
177+
}
178+
179+
if (!output_bio) {
180+
fprintf(stderr, "Error: unable to create output BIO\n");
181+
return false;
182+
}
183+
184+
BIO_write(output_bio.get(), data.data(), data.size());
185+
return true;
186+
}
187+
188+
bool pkeyutlTool(const args_list_t &args) {
189+
using namespace ordered_args;
190+
ordered_args_map_t parsed_args;
191+
args_list_t extra_args;
192+
193+
if (!ParseOrderedKeyValueArguments(parsed_args, extra_args, args, kArguments) ||
194+
extra_args.size() > 0) {
195+
PrintUsage(kArguments);
196+
return false;
197+
}
198+
199+
std::string in_path, out_path, inkey_path, sigfile_path;
200+
// Use sensitive string handling for password
201+
bssl::UniquePtr<std::string> passin_arg(new std::string());
202+
bool sign = false, verify = false, pubin = false;
203+
204+
GetString(&in_path, "-in", "", parsed_args);
205+
GetString(&out_path, "-out", "", parsed_args);
206+
GetString(&inkey_path, "-inkey", "", parsed_args);
207+
GetString(passin_arg.get(), "-passin", "", parsed_args);
208+
GetString(&sigfile_path, "-sigfile", "", parsed_args);
209+
GetBoolArgument(&sign, "-sign", parsed_args);
210+
GetBoolArgument(&verify, "-verify", parsed_args);
211+
GetBoolArgument(&pubin, "-pubin", parsed_args);
212+
213+
// Display help
214+
if (HasArgument(parsed_args, "-help")) {
215+
PrintUsage(kArguments);
216+
return true;
217+
}
218+
219+
// Validate arguments
220+
if (!sign && !verify) {
221+
fprintf(stderr, "Error: must specify either -sign or -verify\n");
222+
return false;
223+
}
224+
225+
if (sign && verify) {
226+
fprintf(stderr, "Error: cannot specify both -sign and -verify\n");
227+
return false;
228+
}
229+
230+
if (verify && sigfile_path.empty()) {
231+
fprintf(stderr, "Error: No signature file specified for verify (-sigfile parameter)\n");
232+
return false;
233+
}
234+
235+
if (!verify && !sigfile_path.empty()) {
236+
fprintf(stderr, "Error: Signature file specified for non-verify operation\n");
237+
return false;
238+
}
239+
240+
if (inkey_path.empty()) {
241+
fprintf(stderr, "Error: no key given (-inkey parameter)\n");
242+
return false;
243+
}
244+
245+
// Load the key
246+
bssl::UniquePtr<EVP_PKEY> pkey;
247+
if (pubin || verify) {
248+
if (!LoadPublicKey(inkey_path, pkey)) {
249+
return false;
250+
}
251+
} else {
252+
if (!LoadPrivateKey(inkey_path, passin_arg, pkey)) {
253+
return false;
254+
}
255+
}
256+
257+
if (sign) {
258+
std::vector<uint8_t> signature;
259+
std::vector<uint8_t> input_data;
260+
if (!ReadInputData(in_path, input_data)) {
261+
return false;
262+
}
263+
264+
// Sanity check for non-raw input
265+
if (input_data.size() > EVP_MAX_MD_SIZE) {
266+
fprintf(stderr, "Error: input data looks too long to be a hash\n");
267+
return false;
268+
}
269+
270+
if (!DoSign(pkey.get(), input_data, signature)) {
271+
return false;
272+
}
273+
274+
if (!WriteOutput(signature, out_path)) {
275+
return false;
276+
}
277+
} else if (verify) {
278+
// Read signature from sigfile
279+
std::vector<uint8_t> signature;
280+
ScopedFILE sig_file;
281+
sig_file.reset(fopen(sigfile_path.c_str(), "rb"));
282+
if (!sig_file) {
283+
fprintf(stderr, "Error: unable to open signature file '%s'\n", sigfile_path.c_str());
284+
return false;
285+
}
286+
287+
if (!ReadAll(&signature, sig_file.get())) {
288+
fprintf(stderr, "Error: error reading signature data\n");
289+
return false;
290+
}
291+
292+
std::vector<uint8_t> input_data;
293+
if (!ReadInputData(in_path, input_data)) {
294+
return false;
295+
}
296+
297+
bool success = DoVerify(pkey.get(), input_data, signature);
298+
299+
bssl::UniquePtr<BIO> output_bio;
300+
if (out_path.empty()) {
301+
output_bio.reset(BIO_new_fp(stdout, BIO_CLOSE));
302+
} else {
303+
output_bio.reset(BIO_new(BIO_s_file()));
304+
if (BIO_write_filename(output_bio.get(), out_path.c_str()) <= 0) {
305+
fprintf(stderr, "Error: failed to open output file '%s'\n", out_path.c_str());
306+
return false;
307+
}
308+
}
309+
310+
if (success) {
311+
BIO_puts(output_bio.get(), "Signature Verified Successfully\n");
312+
} else {
313+
BIO_puts(output_bio.get(), "Signature Verification Failure\n");
314+
}
315+
}
316+
317+
return true;
318+
}

0 commit comments

Comments
 (0)