Skip to content

Centralize password handling tool-openssl #2555

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

Open
wants to merge 35 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
be9365c
refactor(tool-openssl): Centralize password handling with HandlePassO…
kingstjo Jul 17, 2025
fb32a81
test(tool-openssl): Add comprehensive tests for password handling
kingstjo Jul 17, 2025
1e5f7b0
test(tool-openssl): improve password test assertions
kingstjo Jul 17, 2025
c567c79
fix(test): improve cross-platform password test compatibility
kingstjo Jul 17, 2025
2e01934
fix(test): Fix heap-use-after-free in PasswordTest.SensitiveStringDel…
kingstjo Jul 17, 2025
d51be8a
Merge branch 'main' into password-handling-clean
kingstjo Jul 17, 2025
0beec94
refactor(test): Improve password_test.cc organization and test coverage
kingstjo Jul 18, 2025
9de6fba
Merge remote-tracking branch 'origin/password-handling-clean' into pa…
kingstjo Jul 18, 2025
873bc7d
Merge remote-tracking branch 'upstream/main' into password-handling-c…
kingstjo Jul 18, 2025
782eac7
refactor(tool-openssl): Rename password.cc to pass_util.cc
kingstjo Jul 21, 2025
fd29186
refactor(tool-openssl): Rename password files and fix empty password …
kingstjo Jul 21, 2025
79de4b7
refactor(tool-openssl): Modify ExtractPassword to update source in-place
kingstjo Jul 22, 2025
f2db8f6
refactor(tool-openssl): Rename password namespace to pass_util
kingstjo Jul 22, 2025
85fae9d
refactor(tool-openssl): rename test classes to match pass_util naming
kingstjo Jul 23, 2025
f2ca6c4
test(tool-openssl): improve file truncation test coverage
kingstjo Jul 23, 2025
ad44167
fix: Add constructors to PassUtilSourceParams to fix uninitialized me…
kingstjo Jul 24, 2025
3e50e56
Merge remote-tracking branch 'upstream/main' into password-handling-c…
kingstjo Jul 24, 2025
4865e83
refactor(tool-openssl): Change validate_bio_size to use reference ins…
kingstjo Jul 24, 2025
94f91af
feat(pass_util): Refactor password extraction with consistent API and…
kingstjo Jul 25, 2025
e2d63d3
refactor(pass_util): Add ValidateSource helper and remove redundant t…
kingstjo Jul 25, 2025
c110c35
(fix) update enum to uint8 for bitflag in order to avoid shadowing
kingstjo Jul 25, 2025
bb19693
Merge branch 'main' into password-handling-clean
kingstjo Jul 25, 2025
83bbaa0
refactor(tool-openssl): Replace password source constants with enum c…
kingstjo Jul 29, 2025
bced12a
feat(tool-openssl): Include PEM_BUFSIZE limit in password error messages
kingstjo Jul 29, 2025
4ee6c29
style(tool-openssl): Fix truncation detection parentheses for clarity
kingstjo Jul 29, 2025
5d006be
docs(tool-openssl): Remove outdated comment about SensitiveStringDele…
kingstjo Jul 29, 2025
9eff078
removed extra comments
kingstjo Jul 29, 2025
5aec371
Using PEM_read_bio_PrivateKey() directly in pkcs8.cc
kingstjo Jul 29, 2025
a900bb8
fix: Allow zero-length passwords in PEM key decryption
kingstjo Jul 30, 2025
d047863
refactor(tool-openssl): Simplify pkcs8 key reading with read_private_der
kingstjo Jul 30, 2025
dede45e
test: Remove redundant comments from pass_util_test.cc
kingstjo Jul 30, 2025
499285b
fix(pkcs8): Use ExtractPasswords instead of separate ExtractPassword …
kingstjo Jul 30, 2025
7798f1a
test: Improve SensitiveStringDeleter test to use real usage pattern
kingstjo Jul 30, 2025
8ded619
fix: Merge upstream ordered args with centralized password handling
kingstjo Aug 17, 2025
8176847
style: Clean up formatting noise in pkcs8.cc
kingstjo Aug 17, 2025
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
2 changes: 1 addition & 1 deletion crypto/fipsmodule/evp/evp.c
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ int EVP_read_pw_string_min(char *buf, int min_length, int length,
int ret = -1;
char verify_buf[1024];

if (!buf || min_length <= 0 || min_length >= length) {
if (!buf || min_length < 0 || min_length >= length) {
return -1;
}

Expand Down
2 changes: 1 addition & 1 deletion crypto/pem/pem_pkey.c
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ EVP_PKEY *PEM_read_bio_PrivateKey(BIO *bp, EVP_PKEY **x, pem_password_cb *cb,
cb = PEM_def_callback;
}
int pass_len = cb(psbuf, PEM_BUFSIZE, 0, u);
if (pass_len <= 0) {
if (pass_len < 0) {
OPENSSL_PUT_ERROR(PEM, PEM_R_BAD_PASSWORD_READ);
X509_SIG_free(p8);
goto err;
Expand Down
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
pass_util.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
pass_util.cc
pass_util_test.cc
pkcs8.cc
pkcs8_test.cc
pkey.cc
Expand Down
54 changes: 50 additions & 4 deletions tool-openssl/internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
#define TOOL_OPENSSL_INTERNAL_H

#include <openssl/digest.h>
#include <memory>
#include <algorithm>
#include <memory>
#include <string>
#include <utility>
#include <vector>
Expand All @@ -24,13 +24,59 @@ struct Tool {

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

X509* CreateAndSignX509Certificate();
X509_CRL* createTestCRL();
X509 *CreateAndSignX509Certificate();
X509_CRL *createTestCRL();
bool isStringUpperCaseEqual(const std::string &a, const std::string &b);

// Password extracting utility for -passin and -passout options
namespace pass_util {
// Password source types for handling different input methods
enum class Source : uint8_t {
kNone, // Empty or invalid source
kPass, // Direct password with pass: prefix
kFile, // Password from file with file: prefix
kEnv, // Password from environment with env: prefix
};

// Custom deleter for sensitive strings that securely clears memory before
// deletion. This ensures passwords are securely removed from memory when no
// longer needed, preventing potential exposure in memory dumps or swap files.
void SensitiveStringDeleter(std::string *str);

// Extracts password from a source string, modifying it in place if successful.
// source: Password source string in one of the following formats:
// - pass:password (direct password, e.g., "pass:mypassword")
// - file:/path/to/file (password from file)
// - env:VAR_NAME (password from environment variable)
// The source string will be replaced with the extracted password if successful.
// Returns bool indicating success or failure:
// - true: Password was successfully extracted and stored in source
// - false: Error occurred, error message printed to stderr
// Error cases:
// - Invalid format string (missing or unknown prefix)
// - File access errors (file not found, permission denied)
// - Environment variable not set
// - Memory allocation failures
bool ExtractPassword(bssl::UniquePtr<std::string> &source);

// Same process as ExtractPassword but used for -passin and -passout within same
// tool. Special handling:
// - If same file is used for both passwords, reads first line for passin
// and second line for passout in a single file operation matching OpenSSL
// behavior
bool ExtractPasswords(bssl::UniquePtr<std::string> &passin,
bssl::UniquePtr<std::string> &passout);

} // namespace pass_util

// Custom deleter used for -passin -passout options
BSSL_NAMESPACE_BEGIN
BORINGSSL_MAKE_DELETER(std::string, pass_util::SensitiveStringDeleter)
BSSL_NAMESPACE_END

bool LoadPrivateKeyAndSignCertificate(X509 *x509,
const std::string &signkey_path);
EVP_PKEY* CreateTestKey(int key_bits);
EVP_PKEY *CreateTestKey(int key_bits);

tool_func_t FindTool(const std::string &name);
tool_func_t FindTool(int argc, char **argv, int &starting_arg);
Expand Down
255 changes: 255 additions & 0 deletions tool-openssl/pass_util.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,255 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 OR ISC

#include <openssl/base.h>
#include <openssl/err.h>
#include <openssl/evp.h>
#include <openssl/pem.h>
#include <cstring>
#include <string>
#include "internal.h"

// Use PEM_BUFSIZE (defined in openssl/pem.h) for password buffer size to ensure
// compatibility with PEM functions and password callbacks throughout AWS-LC

// Detect the type of password source
static pass_util::Source DetectSource(
const bssl::UniquePtr<std::string> &source) {
if (!source || source->empty()) {
return pass_util::Source::kNone;
}
if (source->compare(0, 5, "pass:") == 0) {
return pass_util::Source::kPass;
}
if (source->compare(0, 5, "file:") == 0) {
return pass_util::Source::kFile;
}
if (source->compare(0, 4, "env:") == 0) {
return pass_util::Source::kEnv;
}
return pass_util::Source::kNone;
}

// Helper function to validate password sources and detect same-file case
static bool ValidateSource(bssl::UniquePtr<std::string> &passin,
bssl::UniquePtr<std::string> *passout = nullptr,
bool *same_file = nullptr) {
// Validate passin
if (!passin) {
fprintf(stderr, "Invalid password format (use pass:, file:, or env:)\n");
return false;
}

// Validate passout if provided
if (passout && !*passout) {
fprintf(stderr, "Invalid password format (use pass:, file:, or env:)\n");
return false;
}

// Validate passin format (if not empty)
if (!passin->empty()) {
pass_util::Source passin_type = DetectSource(passin);
if (passin_type == pass_util::Source::kNone) {
fprintf(stderr, "Invalid password format (use pass:, file:, or env:)\n");
return false;
}
}

// Validate passout format (if provided and not empty)
if (passout && *passout && !(*passout)->empty()) {
pass_util::Source passout_type = DetectSource(*passout);
if (passout_type == pass_util::Source::kNone) {
fprintf(stderr, "Invalid password format (use pass:, file:, or env:)\n");
return false;
}

// Detect same-file case if requested
if (same_file && !passin->empty()) {
pass_util::Source passin_type = DetectSource(passin);
*same_file =
(passin_type == pass_util::Source::kFile &&
passout_type == pass_util::Source::kFile && *passin == **passout);
}
}

// Initialize same_file to false if not detected
if (same_file && (!passout || !*passout)) {
*same_file = false;
}

return true;
}

static bool ExtractDirectPassword(bssl::UniquePtr<std::string> &source) {
// Check for additional colons in password portion after prefix
if (source->find(':', 5) != std::string::npos) {
fprintf(stderr, "Invalid password format (use pass:, file:, or env:)\n");
return false;
}

// Check length before modification
if (source->length() - 5 > PEM_BUFSIZE) {
fprintf(stderr, "Password exceeds maximum allowed length (%d bytes)\n",
PEM_BUFSIZE);
return false;
}

// Remove "pass:" prefix by shifting the remaining content to the beginning
source->erase(0, 5);
return true;
}

static bool ExtractPasswordFromFile(bssl::UniquePtr<std::string> &source,
bool skip_first_line = false) {
// Remove "file:" prefix
source->erase(0, 5);

bssl::UniquePtr<BIO> bio(BIO_new_file(source->c_str(), "r"));
if (!bio) {
fprintf(stderr, "Cannot open password file\n");
return false;
}

char buf[PEM_BUFSIZE] = {};

// Skip first line if requested (for passout when using same file)
if (skip_first_line) {
if (BIO_gets(bio.get(), buf, sizeof(buf)) <= 0) {
OPENSSL_cleanse(buf, sizeof(buf));
fprintf(stderr, "Cannot read password file\n");
return false;
}
OPENSSL_cleanse(buf, sizeof(buf));
}

// Read the password line
int len = BIO_gets(bio.get(), buf, sizeof(buf));
if (len <= 0) {
OPENSSL_cleanse(buf, sizeof(buf));
fprintf(stderr, "Cannot read password file\n");
return false;
}

const bool possible_truncation =
(static_cast<size_t>(len) == PEM_BUFSIZE - 1) && buf[len - 1] != '\n' &&
buf[len - 1] != '\r';
if (possible_truncation) {
OPENSSL_cleanse(buf, sizeof(buf));
fprintf(stderr, "Password file content too long (maximum %d bytes)\n",
PEM_BUFSIZE);
return false;
}

// Trim trailing newlines
size_t buf_len = len;
while (buf_len > 0 &&
(buf[buf_len - 1] == '\n' || buf[buf_len - 1] == '\r')) {
buf[--buf_len] = '\0';
}

// Replace source content with password
*source = std::string(buf, buf_len);
OPENSSL_cleanse(buf, sizeof(buf));
return true;
}

static bool ExtractPasswordFromEnv(bssl::UniquePtr<std::string> &source) {
// Remove "env:" prefix
source->erase(0, 4);

if (source->empty()) {
fprintf(stderr, "Empty environment variable name\n");
return false;
}

const char *env_val = getenv(source->c_str());
if (!env_val) {
fprintf(stderr, "Environment variable '%s' not set\n", source->c_str());
return false;
}

size_t env_val_len = strlen(env_val);
if (env_val_len == 0) {
fprintf(stderr, "Environment variable '%s' is empty\n", source->c_str());
return false;
}
if (env_val_len > PEM_BUFSIZE) {
fprintf(stderr, "Environment variable value too long (maximum %d bytes)\n",
PEM_BUFSIZE);
return false;
}

// Replace source content with environment value
*source = std::string(env_val);
return true;
}

// Internal helper to extract password based on source type
static bool ExtractPasswordFromSource(bssl::UniquePtr<std::string> &source,
pass_util::Source type,
bool skip_first_line = false) {
switch (type) {
case pass_util::Source::kPass:
return ExtractDirectPassword(source);
case pass_util::Source::kFile:
return ExtractPasswordFromFile(source, skip_first_line);
case pass_util::Source::kEnv:
return ExtractPasswordFromEnv(source);
default:
fprintf(stderr, "Invalid password format (use pass:, file:, or env:)\n");
return false;
}
}

namespace pass_util {

void SensitiveStringDeleter(std::string *str) {
if (str && !str->empty()) {
OPENSSL_cleanse(&(*str)[0], str->size());
}
delete str;
}

bool ExtractPassword(bssl::UniquePtr<std::string> &source) {
if (!ValidateSource(source)) {
return false;
}

if (source->empty()) {
fprintf(stderr, "Invalid password format (use pass:, file:, or env:)\n");
return false;
}

pass_util::Source type = DetectSource(source);
return ExtractPasswordFromSource(source, type);
}

bool ExtractPasswords(bssl::UniquePtr<std::string> &passin,
bssl::UniquePtr<std::string> &passout) {
// Use ValidateSource for all validation and same-file detection
bool same_file = false;
if (!ValidateSource(passin, &passout, &same_file)) {
return false;
}

// Extract passin (always from first line)
if (!passin->empty()) {
pass_util::Source passin_type = DetectSource(passin);
if (!ExtractPasswordFromSource(passin, passin_type, false)) {
return false;
}
}

// Extract passout (from first line if different files, second line if same
// file)
if (!passout->empty()) {
pass_util::Source passout_type = DetectSource(passout);
if (!ExtractPasswordFromSource(passout, passout_type, same_file)) {
return false;
}
}

return true;
}

} // namespace pass_util
Loading
Loading