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
59 changes: 57 additions & 2 deletions tool-openssl/pkcs8.cc
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,45 @@ static bssl::UniquePtr<EVP_PKEY> read_private_der(BIO *in_bio,
return bssl::UniquePtr<EVP_PKEY>(d2i_PrivateKey_bio(in_bio, nullptr));
}

// Returns 1 if PEM is encrypted, 0 if not, -1 on error
static int is_pem_encrypted(BIO *bio) {
char *name_ptr = nullptr;
char *header_ptr = nullptr;
unsigned char *data_ptr = nullptr;
long len = 0;

// Read the PEM block
if (!PEM_read_bio(bio, &name_ptr, &header_ptr, &data_ptr, &len)) {
return -1; // Error reading PEM
}

// We are responsible for freeing these
bssl::UniquePtr<char> name(name_ptr);
bssl::UniquePtr<char> header(header_ptr);
bssl::UniquePtr<uint8_t> data(data_ptr);

int is_encrypted = 0;

// Check if there's a header with encryption info
if (name && strcmp(name.get(), "ENCRYPTED PRIVATE KEY") == 0) {
is_encrypted = 1;
}
// Check for traditional PEM encryption (by header)
else if (header && header.get()[0] != '\0') {
EVP_CIPHER_INFO cipher;
if (PEM_get_EVP_CIPHER_INFO(header.get(), &cipher)) {
is_encrypted = (cipher.cipher != nullptr) ? 1 : 0;
}
}

// Rewind buffer so it can be parsed to obtain a private key
if (BIO_seek(bio, 0) >= 0) {
return is_encrypted;
}

return -1;
}

static const argument_t kArguments[] = {
{"-help", kBooleanArgument, "Display option summary"},
{"-in", kOptionalArgument, "Input file"},
Expand Down Expand Up @@ -133,7 +172,7 @@ bool pkcs8Tool(const args_list_t &args) {
bssl::UniquePtr<EVP_PKEY> pkey;
const EVP_CIPHER *cipher = nullptr;
bssl::UniquePtr<PKCS8_PRIV_KEY_INFO> p8inf;

bool input_is_encrypted = false;

if (!ParseOrderedKeyValueArguments(parsed_args, extra_args, args,
kArguments)) {
Expand Down Expand Up @@ -193,6 +232,18 @@ bool pkcs8Tool(const args_list_t &args) {
if (!in) {
fprintf(stderr, "Cannot open input file\n");
return false;
} else if (inform == "PEM") {
switch(is_pem_encrypted(in.get())) {
case 0:
input_is_encrypted = false;
break;
case 1:
input_is_encrypted = true;
break;
default:
fprintf(stderr, "Unable to load PEM file\n");
return false;
}
}
if (!validate_bio_size(in.get())) {
return false;
Expand All @@ -218,7 +269,11 @@ bool pkcs8Tool(const args_list_t &args) {
in.get(), passin_arg->empty() ? nullptr : passin_arg->c_str())
.release());
if (!pkey) {
fprintf(stderr, "Unable to load private key\n");
if (input_is_encrypted) {
fprintf(stderr, "Error decrypting key\n");
} else {
fprintf(stderr, "Unable to load private key\n");
}
return false;
}

Expand Down
53 changes: 53 additions & 0 deletions tool-openssl/pkcs8_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,59 @@ TEST_F(PKCS8Test, PKCS8ToolEncryptionTest) {
ASSERT_TRUE(result);
}

// Verify failure output contains "Error decrypting key"
TEST_F(PKCS8Test, PKCS8ErrorDecryptingKey) {
{
const char* passwd = "test1234";
bssl::UniquePtr<BIO> pass_bio(BIO_new_file(pass_path, "wb"));
BIO_write(pass_bio.get(), passwd, strlen(passwd));
BIO_flush(pass_bio.get());
}

std::string passfile = std::string("file:") + pass_path;

// Phase 1: Encrypt the key
args_list_t args_encrypt = {
"-passin", "pass:''",
"-inform", "PEM",
"-in", in_path,
"-topk8",
"-v2", "aes-256-cbc",
"-passout", passfile.c_str(),
"-outform", "PEM",
"-out", out_path
};

ASSERT_TRUE(pkcs8Tool(args_encrypt));

// Phase 2: Try to decrypt with wrong password (should fail)
args_list_t args_verify = {
"-passin", "pass:''",
"-inform", "PEM",
"-in", out_path,
"-outform", "PEM",
};

// Capture stderr to verify the error message
testing::internal::CaptureStderr();
bool verify_result = pkcs8Tool(args_verify);
std::string captured_stderr = testing::internal::GetCapturedStderr();

ASSERT_FALSE(verify_result) << "Expected decryption to fail with wrong password";
EXPECT_TRUE(captured_stderr.find("Error decrypting key") != std::string::npos)
<< "Expected 'Error decrypting key' in stderr, but got: " << captured_stderr;

// Phase 3: Decrypt with correct password (should succeed)
args_list_t args_decrypt = {
"-passin", passfile.c_str(),
"-inform", "PEM",
"-in", out_path,
"-outform", "PEM",
};

ASSERT_TRUE(pkcs8Tool(args_decrypt));
}

// Test with a direct password rather than using environment variables
TEST_F(PKCS8Test, PKCS8ToolEnvVarPasswordTest) {
// Phase 1: Create an unencrypted PKCS8 file first
Expand Down
Loading