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

Added options to x509 tool #1696

Merged
merged 16 commits into from
Jul 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
9 changes: 4 additions & 5 deletions tool-openssl/README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
# OpenSSL Tools for AWS-LC
*Files expected to change*

Current status:
* Developed structure for new OpenSSL tools
* Contains initial implementation for OpenSSL x509 tool, options -in and -out (x509.cc), and unit test (x509_test.cc)
* x509_test.cc contains test portions ultimately to be used for future options but unnecessary for -in/-out unit test

Current status:
* Contains initial implementation for OpenSSL x509 tool, options -in -out, -req, -signkey, -modulus, -days, -dates,
-checkend, -noout (x509.cc), and unit test (x509_test.cc)
* OpenSSL rsa tool yet to be implemented
9 changes: 7 additions & 2 deletions tool-openssl/internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,17 @@

typedef bool (*tool_func_t)(const std::vector<std::string> &args);

bool IsNumeric(const std::string& str);

X509* CreateAndSignX509Certificate();

bool WriteSignedCertificate(X509 *x509, const std::string &out_path);

bool LoadPrivateKeyAndSignCertificate(X509 *x509, const std::string &signkey_path);

tool_func_t FindTool(const std::string &name);
tool_func_t FindTool(int argc, char **argv, int &starting_arg);

bool X509Tool(const args_list_t &args);

#endif //INTERNAL_H


287 changes: 247 additions & 40 deletions tool-openssl/x509.cc
Original file line number Diff line number Diff line change
Expand Up @@ -3,72 +3,279 @@

#include <openssl/x509.h>
#include <openssl/pem.h>
#include <openssl/rsa.h>
#include <cstdio>
#include <ctime>
#include "internal.h"

static const argument_t kArguments[] = {
{ "-in", kRequiredArgument, "Input file" },
{ "-out", kRequiredArgument, "Output file" },
{ "-help", kBooleanArgument, "Display option summary" },
{ "-in", kOptionalArgument, "Certificate input, or CSR input file with -req" },
{ "-req", kBooleanArgument, "Input is a CSR file (rather than a certificate)" },
{ "-signkey", kOptionalArgument, "Causes input file to be self signed using supplied private key" },
{ "-out", kOptionalArgument, "Output file to write to" },
{ "-noout", kBooleanArgument, "Prevents output of the encoded version of the certificate" },
{ "-dates", kBooleanArgument, "Print the start and expiry dates of a certificate" },
{ "-modulus", kBooleanArgument, "Prints out value of the modulus of the public key contained in the certificate" },
{ "-checkend", kOptionalArgument, "Check whether cert expires in the next arg seconds" },
{ "-days", kOptionalArgument, "Number of days until newly generated certificate expires - default 30" },
{ "", kOptionalArgument, "" }
};

bool WriteSignedCertificate(X509 *x509, const std::string &out_path) {
ScopedFILE out_file(fopen(out_path.c_str(), "wb"));
if (!out_file) {
fprintf(stderr, "Error: unable to open output file '%s'\n", out_path.c_str());
return false;
}
if (!PEM_write_X509(out_file.get(), x509)) {
fprintf(stderr, "Error: error writing certificate to '%s'\n", out_path.c_str());
ERR_print_errors_fp(stderr);
return false;
}
return true;
}

bool LoadPrivateKeyAndSignCertificate(X509 *x509, const std::string &signkey_path) {
ScopedFILE signkey_file(fopen(signkey_path.c_str(), "rb"));
if (!signkey_file) {
fprintf(stderr, "Error: unable to load private key from '%s'\n", signkey_path.c_str());
return false;
}
bssl::UniquePtr<EVP_PKEY> pkey(PEM_read_PrivateKey(signkey_file.get(), nullptr, nullptr, nullptr));
if (!pkey) {
fprintf(stderr, "Error: error reading private key from '%s'\n", signkey_path.c_str());
ERR_print_errors_fp(stderr);
return false;
}
// TODO: make customizable with -digest option
if (!X509_sign(x509, pkey.get(), EVP_sha256())) {
fprintf(stderr, "Error: error signing certificate with key from '%s'\n", signkey_path.c_str());
ERR_print_errors_fp(stderr);
return false;
}
return true;
}

bool IsNumeric(const std::string& str) {
return !str.empty() && std::all_of(str.begin(), str.end(), ::isdigit);
}

// Map arguments using tool/args.cc
bool X509Tool(const args_list_t &args) {
args_map_t parsed_args;
if (!ParseKeyValueArguments(&parsed_args, args, kArguments)) {
PrintUsage(kArguments);
return false;
args_map_t parsed_args;
if (!ParseKeyValueArguments(&parsed_args, args, kArguments)) {
PrintUsage(kArguments);
return false;
}

std::string in_path, out_path, signkey_path, checkend_str, days_str;
bool noout = false, modulus = false, dates = false, req = false, help = false;
std::unique_ptr<unsigned> checkend, days;

GetBoolArgument(&help, "-help", parsed_args);
GetString(&in_path, "-in", "", parsed_args);
GetBoolArgument(&req, "-req", parsed_args);
GetString(&signkey_path, "-signkey", "", parsed_args);
GetString(&out_path, "-out", "", parsed_args);
GetBoolArgument(&noout, "-noout", parsed_args);
GetBoolArgument(&dates, "-dates", parsed_args);
GetBoolArgument(&modulus, "-modulus", parsed_args);

// Display x509 tool option summary
if (help) {
PrintUsage(kArguments);
return false;
}

// Check for required option -in, and -req must include -signkey
if (in_path.empty()) {
fprintf(stderr, "Error: missing required argument '-in'\n");
return false;
}
if (req && signkey_path.empty()) {
fprintf(stderr, "Error: '-req' option must be used with '-signkey' option\n");
return false;
}

// Check for mutually exclusive options
if (noout && (!out_path.empty() || modulus || dates || parsed_args.count("-checkend"))) {
fprintf(stderr, "Error: '-noout' option cannot be used with '-out', '-modulus', '-dates', and '-checkend' options\n");
return false;
}
if (req && (dates || parsed_args.count("-checkend"))){
fprintf(stderr, "Error: '-req' option cannot be used with '-dates' and '-checkend' options\n");
return false;
}
if (!signkey_path.empty() && (dates || parsed_args.count("-checkend"))){
fprintf(stderr, "Error: '-signkey' option cannot be used with '-dates' and '-checkend' options\n");
return false;
}
if (parsed_args.count("-days") && (dates || parsed_args.count("-checkend"))){
fprintf(stderr, "Error: '-days' option cannot be used with '-dates' and '-checkend' options\n");
return false;
}

// Check that -checkend argument is valid, int >=0
if (parsed_args.count("-checkend")) {
checkend_str = parsed_args["-checkend"];
if (!IsNumeric(checkend_str)) {
fprintf(stderr, "Error: '-checkend' option must include a non-negative integer\n");
return false;
}
checkend.reset(new unsigned(std::stoul(checkend_str)));
}

// Check for required arguments
std::string in_path, out_path;
if (!GetString(&in_path, "-in", "", parsed_args)) {
fprintf(stderr, "Missing required argument: -in\n");
PrintUsage(kArguments);
return false;
// Check that -days argument is valid, int > 0
if (parsed_args.count("-days")) {
days_str = parsed_args["-days"];
if (!IsNumeric(days_str) || std::stoul(days_str) == 0) {
fprintf(stderr, "Error: '-days' option must include a positive integer\n");
return false;
}
if (!GetString(&out_path, "-out", "", parsed_args)) {
fprintf(stderr, "Missing required argument: -out\n");
PrintUsage(kArguments);
return false;
days.reset(new unsigned(std::stoul(days_str)));
}

ScopedFILE in_file(fopen(in_path.c_str(), "rb"));
if (!in_file) {
fprintf(stderr, "Error: unable to load certificate from '%s'\n", in_path.c_str());
return false;
}

if (req) {
bssl::UniquePtr<X509_REQ> csr(PEM_read_X509_REQ(in_file.get(), nullptr, nullptr, nullptr));
if (!csr) {
fprintf(stderr, "Error: error parsing CSR from '%s'\n", in_path.c_str());
ERR_print_errors_fp(stderr);
return false;
}

// Create and sign certificate based on CSR
bssl::UniquePtr<X509> x509(X509_new());
if (!x509) {
fprintf(stderr, "Error: unable to create new X509 certificate\n");
return false;
}

// Read input file using ReadAll function from tool/file.cc
std::vector<uint8_t> input_data;
ScopedFILE in_file(fopen(in_path.c_str(), "rb"));
if (!in_file) {
fprintf(stderr, "Failed to open input file '%s'.\n", in_path.c_str());
// Set the subject from CSR
if (!X509_set_subject_name(x509.get(), X509_REQ_get_subject_name(csr.get()))) {
fprintf(stderr, "Error: unable to set subject name from CSR\n");
return false;
}
if (!ReadAll(&input_data, in_file.get())) {
fprintf(stderr, "Failed to read input file '%s'.\n", in_path.c_str());

// Set the public key from CSR
bssl::UniquePtr<EVP_PKEY> csr_pkey(X509_REQ_get_pubkey(csr.get()));
if (!csr_pkey || !X509_set_pubkey(x509.get(), csr_pkey.get())) {
fprintf(stderr, "Error: unable to set public key from CSR\n");
return false;
}

// Set issuer name
if (!X509_set_issuer_name(x509.get(), X509_REQ_get_subject_name(csr.get()))) {
fprintf(stderr, "Error: unable to set issuer name\n");
return false;
}

// Set validity period, default 30 days if not specified
unsigned valid_days = days ? *days : 30;
if (!X509_gmtime_adj(X509_getm_notBefore(x509.get()), 0) ||
!X509_gmtime_adj(X509_getm_notAfter(x509.get()), 60 * 60 * 24 * valid_days)) {
fprintf(stderr, "Error: unable to set validity period\n");
return false;
}

// Parse x509 certificate from input file
const uint8_t *p = input_data.data();
bssl::UniquePtr<X509> x509(d2i_X509(nullptr, &p, input_data.size()));
// Sign the certificate with the provided key
if (!signkey_path.empty()) {
if (!LoadPrivateKeyAndSignCertificate(x509.get(), signkey_path)) {
return false;
}
}

// Write the signed certificate to output file
if (!noout && !out_path.empty()) {
if (!WriteSignedCertificate(x509.get(), out_path)) {
return false;
}
}
} else {
// Parse x509 certificate from input PEM file
bssl::UniquePtr<X509> x509(PEM_read_X509(in_file.get(), nullptr, nullptr, nullptr));
if (!x509) {
fprintf(stderr, "Failed to parse X509 certificate from '%s'.\n", in_path.c_str());
ERR_print_errors_fp(stderr);
fprintf(stderr, "Error: error parsing certificate from '%s'\n", in_path.c_str());
ERR_print_errors_fp(stderr);
return false;
}

if (dates) {
bssl::UniquePtr<BIO> bio(BIO_new(BIO_s_mem()));

if (ASN1_TIME_print(bio.get(), X509_get_notBefore(x509.get()))) {
char not_before[30] = {};
BIO_read(bio.get(), not_before, sizeof(not_before) - 1);
fprintf(stdout, "notBefore=%s\n", not_before);
}

if (ASN1_TIME_print(bio.get(), X509_get_notAfter(x509.get()))) {
char not_after[30] = {};
BIO_read(bio.get(), not_after, sizeof(not_after) - 1);
fprintf(stdout, "notAfter=%s\n", not_after);
}
}

if (modulus) {
bssl::UniquePtr<EVP_PKEY> pkey(X509_get_pubkey(x509.get()));
if (!pkey) {
fprintf(stderr, "Error: unable to load public key from certificate\n");
return false;
}

if (EVP_PKEY_base_id(pkey.get()) == EVP_PKEY_RSA) {
const RSA *rsa = EVP_PKEY_get0_RSA(pkey.get());
if (!rsa) {
fprintf(stderr, "Error: unable to load RSA key\n");
return false;
}
const BIGNUM *n = RSA_get0_n(rsa);
if (!n) {
fprintf(stderr, "Error: unable to load modulus\n");
return false;
}
printf("Modulus=");
BN_print_fp(stdout, n);
printf("\n");
} else {
fprintf(stderr, "Error: public key is not an RSA key\n");
return false;
}
}

// Serialize certificate to DER format
uint8_t *out_data = nullptr;
int len = i2d_X509(x509.get(), &out_data);
if (len < 0) {
fprintf(stderr, "Failed to serialize X509 certificate.\n");
ERR_print_errors_fp(stderr);
if (checkend) {
ecdeye marked this conversation as resolved.
Show resolved Hide resolved
bssl::UniquePtr<ASN1_TIME> current_time(ASN1_TIME_set(nullptr, std::time(nullptr)));
ASN1_TIME *end_time = X509_getm_notAfter(x509.get());
int days_left, seconds_left;
if (!ASN1_TIME_diff(&days_left, &seconds_left, current_time.get(), end_time)) {
fprintf(stderr, "Error: failed to calculate time difference\n");
return false;
}

if ((days_left * 86400 + seconds_left) < static_cast<int>(*checkend)) {
printf("Certificate will expire\n");
} else {
printf("Certificate will not expire\n");
}
}

// Write output file using WriteToFile function from tool/file.cc
if (!WriteToFile(out_path, out_data, len)) {
fprintf(stderr, "Failed to write X509 certificate to '%s'.\n", out_path.c_str());
OPENSSL_free(out_data);
if (!signkey_path.empty()) {
if (!LoadPrivateKeyAndSignCertificate(x509.get(), signkey_path)) {
return false;
}
}

OPENSSL_free(out_data);
return true;
if (!noout && !out_path.empty()) {
if (!WriteSignedCertificate(x509.get(), out_path)) {
return false;
}
}
}
return true;
}
Loading
Loading