Skip to content
Open
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
3 changes: 3 additions & 0 deletions tool-openssl/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ add_executable(

crl.cc
dgst.cc
ec.cc
pkcs8.cc
pkey.cc
rehash.cc
Expand Down Expand Up @@ -87,6 +88,8 @@ if(BUILD_TESTING)
crl_test.cc
dgst.cc
dgst_test.cc
ec.cc
ec_test.cc
pkcs8.cc
pkcs8_test.cc
pkey.cc
Expand Down
115 changes: 115 additions & 0 deletions tool-openssl/ec.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 OR ISC

#include <openssl/bio.h>
#include <openssl/ec.h>
#include <openssl/err.h>
#include <openssl/pem.h>
#include <string>
#include "internal.h"

enum Format {
FORMAT_PEM = 1,
FORMAT_DER = 2
};

static const argument_t kArguments[] = {
{"-help", kBooleanArgument, "Display this summary"},
{"-inform", kOptionalArgument, "Input format (PEM or DER), default PEM"},
{"-in", kOptionalArgument, "Input file, default stdin"},
{"-pubout", kBooleanArgument, "Output public key, not private"},
{"-out", kOptionalArgument, "Output file, default stdout"},
{"-outform", kOptionalArgument, "Output format (PEM or DER), default PEM"},
{"", kOptionalArgument, ""}};

bool ecTool(const args_list_t &args) {
ordered_args::ordered_args_map_t parsed_args;
args_list_t extra_args;
std::string in_path, out_path, inform_str, outform_str;
bool help = false, pubout = false;
int input_format = FORMAT_PEM, output_format = FORMAT_PEM;
bssl::UniquePtr<BIO> input_bio, output_bio;
bssl::UniquePtr<EC_KEY> ec_key;

if (!ordered_args::ParseOrderedKeyValueArguments(parsed_args, extra_args,
args, kArguments)) {
PrintUsage(kArguments);
goto err;
}

ordered_args::GetBoolArgument(&help, "-help", parsed_args);
ordered_args::GetString(&in_path, "-in", "", parsed_args);
ordered_args::GetString(&out_path, "-out", "", parsed_args);
ordered_args::GetString(&inform_str, "-inform", "PEM", parsed_args);
ordered_args::GetString(&outform_str, "-outform", "PEM", parsed_args);
ordered_args::GetBoolArgument(&pubout, "-pubout", parsed_args);

if (help) {
PrintUsage(kArguments);
return true;
}

if (inform_str == "PEM" || inform_str == "pem") {
input_format = FORMAT_PEM;
} else if (inform_str == "DER" || inform_str == "der") {
input_format = FORMAT_DER;
} else {
fprintf(stderr, "Error: Invalid input format '%s'. Must be PEM, pem, DER, or der\n", inform_str.c_str());
goto err;
}

if (outform_str == "PEM" || outform_str == "pem") {
output_format = FORMAT_PEM;
} else if (outform_str == "DER" || outform_str == "der") {
output_format = FORMAT_DER;
} else {
fprintf(stderr, "Error: Invalid output format '%s'. Must be PEM, pem, DER, or der\n", outform_str.c_str());
goto err;
}

input_bio.reset(in_path.empty() ? BIO_new_fp(stdin, BIO_NOCLOSE)
: BIO_new_file(in_path.c_str(), "rb"));
if (!input_bio) {
fprintf(stderr, "Error: Could not open input\n");
goto err;
}

ec_key.reset(input_format == FORMAT_DER
? d2i_ECPrivateKey_bio(input_bio.get(), nullptr)
: PEM_read_bio_ECPrivateKey(input_bio.get(), nullptr,
nullptr, nullptr));
if (!ec_key) {
fprintf(stderr, "Error: Could not read EC key in %s format\n",
input_format == FORMAT_DER ? "DER" : "PEM");
goto err;
}

output_bio.reset(out_path.empty() ? BIO_new_fp(stdout, BIO_NOCLOSE)
: BIO_new_file(out_path.c_str(), "wb"));
if (!output_bio) {
fprintf(stderr, "Error: Could not open output\n");
goto err;
}

if (pubout) {
if (!(output_format == FORMAT_DER
? i2d_EC_PUBKEY_bio(output_bio.get(), ec_key.get())
: PEM_write_bio_EC_PUBKEY(output_bio.get(), ec_key.get()))) {
goto err;
}
} else {
if (!(output_format == FORMAT_DER
? i2d_ECPrivateKey_bio(output_bio.get(), ec_key.get())
: PEM_write_bio_ECPrivateKey(output_bio.get(), ec_key.get(),
nullptr, nullptr, 0, nullptr,
nullptr))) {
goto err;
}
}

return true;

err:
ERR_print_errors_fp(stderr);
return false;
}
227 changes: 227 additions & 0 deletions tool-openssl/ec_test.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 OR ISC

#include <gtest/gtest.h>
#include <openssl/pem.h>
#include <openssl/ec.h>
#include "internal.h"
#include "test_util.h"
#include "../crypto/test/test_util.h"

static EC_KEY* CreateTestECKey() {
bssl::UniquePtr<EC_KEY> ec_key(EC_KEY_new_by_curve_name(NID_X9_62_prime256v1));
if (!ec_key || !EC_KEY_generate_key(ec_key.get())) {
return nullptr;
}
return ec_key.release();
}

class ECTest : public ::testing::Test {
protected:
void SetUp() override {
ASSERT_GT(createTempFILEpath(pem_key_path), 0u);
ASSERT_GT(createTempFILEpath(der_key_path), 0u);
ASSERT_GT(createTempFILEpath(out_path), 0u);

tool_executable_path = getenv("AWSLC_TOOL_PATH");
openssl_executable_path = getenv("OPENSSL_TOOL_PATH");

if (tool_executable_path != nullptr && openssl_executable_path != nullptr) {
ASSERT_GT(createTempFILEpath(out_path_openssl), 0u);

// Use OpenSSL to generate test keys for better cross-compatibility
std::string pem_cmd = std::string(openssl_executable_path) + " ecparam -genkey -name prime256v1 -out " + pem_key_path;
std::string der_cmd = std::string(openssl_executable_path) + " ecparam -genkey -name prime256v1 | " +
std::string(openssl_executable_path) + " ec -outform DER -out " + der_key_path;

ASSERT_EQ(system(pem_cmd.c_str()), 0) << "Failed to generate PEM key with OpenSSL";
ASSERT_EQ(system(der_cmd.c_str()), 0) << "Failed to generate DER key with OpenSSL";
} else {
// Fallback to AWS-LC key generation
ec_key.reset(CreateTestECKey());
ASSERT_TRUE(ec_key);

bssl::UniquePtr<BIO> pem_bio(BIO_new_file(pem_key_path, "wb"));
ASSERT_TRUE(pem_bio);
ASSERT_TRUE(PEM_write_bio_ECPrivateKey(pem_bio.get(), ec_key.get(), nullptr, nullptr, 0, nullptr, nullptr));
BIO_flush(pem_bio.get());

bssl::UniquePtr<BIO> der_bio(BIO_new_file(der_key_path, "wb"));
ASSERT_TRUE(der_bio);
ASSERT_TRUE(i2d_ECPrivateKey_bio(der_bio.get(), ec_key.get()));
BIO_flush(der_bio.get());
}
}

void TearDown() override {
RemoveFile(pem_key_path);
RemoveFile(der_key_path);
RemoveFile(out_path);
if (tool_executable_path != nullptr && openssl_executable_path != nullptr) {
RemoveFile(out_path_openssl);
}
}

char pem_key_path[PATH_MAX];
char der_key_path[PATH_MAX];
char out_path[PATH_MAX];
char out_path_openssl[PATH_MAX];
const char* tool_executable_path;
const char* openssl_executable_path;
bssl::UniquePtr<EC_KEY> ec_key;
};

TEST_F(ECTest, ReadPEMOutputPEM) {
args_list_t args = {"-in", pem_key_path, "-out", out_path};
ASSERT_TRUE(ecTool(args));

bssl::UniquePtr<BIO> out_bio(BIO_new_file(out_path, "rb"));
ASSERT_TRUE(out_bio);
bssl::UniquePtr<EC_KEY> parsed_key(PEM_read_bio_ECPrivateKey(out_bio.get(), nullptr, nullptr, nullptr));
ASSERT_TRUE(parsed_key);
}

TEST_F(ECTest, ReadPEMOutputDER) {
args_list_t args = {"-in", pem_key_path, "-outform", "DER", "-out", out_path};
ASSERT_TRUE(ecTool(args));

bssl::UniquePtr<BIO> out_bio(BIO_new_file(out_path, "rb"));
ASSERT_TRUE(out_bio);
bssl::UniquePtr<EC_KEY> parsed_key(d2i_ECPrivateKey_bio(out_bio.get(), nullptr));
ASSERT_TRUE(parsed_key);
}

TEST_F(ECTest, ReadDEROutputPEM) {
args_list_t args = {"-in", der_key_path, "-inform", "DER", "-out", out_path};
ASSERT_TRUE(ecTool(args));

bssl::UniquePtr<BIO> out_bio(BIO_new_file(out_path, "rb"));
ASSERT_TRUE(out_bio);
bssl::UniquePtr<EC_KEY> parsed_key(PEM_read_bio_ECPrivateKey(out_bio.get(), nullptr, nullptr, nullptr));
ASSERT_TRUE(parsed_key);
}

TEST_F(ECTest, ReadDEROutputDER) {
args_list_t args = {"-in", der_key_path, "-inform", "DER", "-outform", "DER", "-out", out_path};
ASSERT_TRUE(ecTool(args));

bssl::UniquePtr<BIO> out_bio(BIO_new_file(out_path, "rb"));
ASSERT_TRUE(out_bio);
bssl::UniquePtr<EC_KEY> parsed_key(d2i_ECPrivateKey_bio(out_bio.get(), nullptr));
ASSERT_TRUE(parsed_key);
}

TEST_F(ECTest, PublicKeyExtractionPEM) {
args_list_t args = {"-in", pem_key_path, "-pubout", "-out", out_path};
ASSERT_TRUE(ecTool(args));

bssl::UniquePtr<BIO> out_bio(BIO_new_file(out_path, "rb"));
ASSERT_TRUE(out_bio);
bssl::UniquePtr<EC_KEY> parsed_key(PEM_read_bio_EC_PUBKEY(out_bio.get(), nullptr, nullptr, nullptr));
ASSERT_TRUE(parsed_key);
}

TEST_F(ECTest, PublicKeyExtractionDER) {
args_list_t args = {"-in", der_key_path, "-inform", "DER", "-pubout", "-outform", "DER", "-out", out_path};
ASSERT_TRUE(ecTool(args));

bssl::UniquePtr<BIO> out_bio(BIO_new_file(out_path, "rb"));
ASSERT_TRUE(out_bio);
bssl::UniquePtr<EC_KEY> parsed_key(d2i_EC_PUBKEY_bio(out_bio.get(), nullptr));
ASSERT_TRUE(parsed_key);
}

TEST_F(ECTest, RoundTripPEMtoDERtoPEM) {
char temp_der[PATH_MAX];
ASSERT_GT(createTempFILEpath(temp_der), 0u);

args_list_t args1 = {"-in", pem_key_path, "-outform", "DER", "-out", temp_der};
ASSERT_TRUE(ecTool(args1));

args_list_t args2 = {"-in", temp_der, "-inform", "DER", "-out", out_path};
ASSERT_TRUE(ecTool(args2));

bssl::UniquePtr<BIO> out_bio(BIO_new_file(out_path, "rb"));
ASSERT_TRUE(out_bio);
bssl::UniquePtr<EC_KEY> parsed_key(PEM_read_bio_ECPrivateKey(out_bio.get(), nullptr, nullptr, nullptr));
ASSERT_TRUE(parsed_key);

RemoveFile(temp_der);
}

TEST_F(ECTest, RoundTripDERtoPEMtoDER) {
char temp_pem[PATH_MAX];
ASSERT_GT(createTempFILEpath(temp_pem), 0u);

args_list_t args1 = {"-in", der_key_path, "-inform", "DER", "-out", temp_pem};
ASSERT_TRUE(ecTool(args1));

args_list_t args2 = {"-in", temp_pem, "-outform", "DER", "-out", out_path};
ASSERT_TRUE(ecTool(args2));

bssl::UniquePtr<BIO> out_bio(BIO_new_file(out_path, "rb"));
ASSERT_TRUE(out_bio);
bssl::UniquePtr<EC_KEY> parsed_key(d2i_ECPrivateKey_bio(out_bio.get(), nullptr));
ASSERT_TRUE(parsed_key);

RemoveFile(temp_pem);
}

TEST_F(ECTest, HelpOption) {
args_list_t args = {"-help"};
ASSERT_TRUE(ecTool(args));
}

TEST_F(ECTest, InvalidInputFile) {
args_list_t args = {"-in", "/nonexistent/file.pem", "-out", out_path};
ASSERT_FALSE(ecTool(args));
}

TEST_F(ECTest, InvalidOutputPath) {
args_list_t args = {"-in", pem_key_path, "-out", "/nonexistent/dir/output.pem"};
ASSERT_FALSE(ecTool(args));
}

TEST_F(ECTest, CompareWithOpenSSLPEMOutput) {
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";
}

std::string tool_cmd = std::string(tool_executable_path) + " ec -in " + pem_key_path + " -out " + out_path;
std::string openssl_cmd = std::string(openssl_executable_path) + " ec -in " + pem_key_path + " -out " + out_path_openssl;

ASSERT_EQ(system(tool_cmd.c_str()), 0);
ASSERT_EQ(system(openssl_cmd.c_str()), 0);

bssl::UniquePtr<BIO> tool_bio(BIO_new_file(out_path, "rb"));
bssl::UniquePtr<BIO> openssl_bio(BIO_new_file(out_path_openssl, "rb"));
ASSERT_TRUE(tool_bio);
ASSERT_TRUE(openssl_bio);

bssl::UniquePtr<EC_KEY> tool_key(PEM_read_bio_ECPrivateKey(tool_bio.get(), nullptr, nullptr, nullptr));
bssl::UniquePtr<EC_KEY> openssl_key(PEM_read_bio_ECPrivateKey(openssl_bio.get(), nullptr, nullptr, nullptr));
ASSERT_TRUE(tool_key);
ASSERT_TRUE(openssl_key);
}

TEST_F(ECTest, CompareWithOpenSSLDEROutput) {
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";
}

std::string tool_cmd = std::string(tool_executable_path) + " ec -in " + pem_key_path + " -outform DER -out " + out_path;
std::string openssl_cmd = std::string(openssl_executable_path) + " ec -in " + pem_key_path + " -outform DER -out " + out_path_openssl;

ASSERT_EQ(system(tool_cmd.c_str()), 0);
ASSERT_EQ(system(openssl_cmd.c_str()), 0);

bssl::UniquePtr<BIO> tool_bio(BIO_new_file(out_path, "rb"));
bssl::UniquePtr<BIO> openssl_bio(BIO_new_file(out_path_openssl, "rb"));
ASSERT_TRUE(tool_bio);
ASSERT_TRUE(openssl_bio);

bssl::UniquePtr<EC_KEY> tool_key(d2i_ECPrivateKey_bio(tool_bio.get(), nullptr));
bssl::UniquePtr<EC_KEY> openssl_key(d2i_ECPrivateKey_bio(openssl_bio.get(), nullptr));
ASSERT_TRUE(tool_key);
ASSERT_TRUE(openssl_key);
}
1 change: 1 addition & 0 deletions tool-openssl/internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ 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 ecTool(const args_list_t &args);
bool md5Tool(const args_list_t &args);
bool pkcs8Tool(const args_list_t &args);
bool pkeyTool(const args_list_t &args);
Expand Down
3 changes: 2 additions & 1 deletion tool-openssl/tool.cc
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,10 @@

#include "./internal.h"

static const std::array<Tool, 12> kTools = {{
static const std::array<Tool, 13> kTools = {{
{"crl", CRLTool},
{"dgst", dgstTool},
{"ec", ecTool},
{"md5", md5Tool},
{"pkcs8", pkcs8Tool},
{"pkey", pkeyTool},
Expand Down
Loading