diff --git a/crypto/CMakeLists.txt b/crypto/CMakeLists.txt index 1cb265d21a..9218af9fc8 100644 --- a/crypto/CMakeLists.txt +++ b/crypto/CMakeLists.txt @@ -397,6 +397,7 @@ add_library( cipher_extra/e_tls.c cipher_extra/tls_cbc.c conf/conf.c + console/console.c crypto.c des/des.c dh_extra/params.c @@ -759,6 +760,7 @@ if(BUILD_TESTING) cipher_extra/cipher_test.cc compiler_test.cc conf/conf_test.cc + console/console_test.cc constant_time_test.cc crypto_test.cc ecdh_extra/ecdh_test.cc diff --git a/crypto/console/console.c b/crypto/console/console.c new file mode 100644 index 0000000000..ee0be86617 --- /dev/null +++ b/crypto/console/console.c @@ -0,0 +1,332 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 OR ISC + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "internal.h" +#include "../internal.h" + + +// We support two types of terminal interface: +// - termios for Linux/Unix +// - WIN32 Console for Windows +#if !defined(OPENSSL_WINDOWS) +#include +#define DEV_TTY "/dev/tty" +#define TTY_STRUCT struct termios +#define TTY_FLAGS c_lflag +#define TTY_get(tty,data) tcgetattr(tty, data) +#define TTY_set(tty,data) tcsetattr(tty, TCSANOW, data) +#else /* OPENSSL_WINDOWS */ +#include +#endif + +#define NUM_SIG 32 + +static volatile sig_atomic_t intr_signal; +static struct CRYPTO_STATIC_MUTEX console_global_mutex = CRYPTO_STATIC_MUTEX_INIT; + +#if !defined(OPENSSL_WINDOWS) +static struct sigaction savsig[NUM_SIG]; +#else +static void (*savsig[NUM_SIG]) (int); +#endif + +#if defined(OPENSSL_WINDOWS) + DWORD tty_orig, tty_new; +#else + TTY_STRUCT tty_orig, tty_new; +#endif + +FILE *tty_in, *tty_out; +int is_a_tty; + + +static void popsig(void) { +#if !defined(OPENSSL_WINDOWS) + for (int i = 1; i < NUM_SIG; i++) { + if (i == SIGUSR1 || i == SIGUSR2 || i == SIGKILL) { + continue; + } + sigaction(i, &savsig[i], NULL); + } +#else + signal(SIGABRT, savsig[SIGABRT]); + signal(SIGFPE, savsig[SIGFPE]); + signal(SIGILL, savsig[SIGILL]); + signal(SIGINT, savsig[SIGINT]); + signal(SIGSEGV, savsig[SIGSEGV]); + signal(SIGTERM, savsig[SIGTERM]); +#endif +} + +static void recsig(int signal) { + intr_signal = signal; +} + +static int discard_line_remainder(FILE *in) { + char buf[5]; + + do { + if (!fgets(buf, 4, in)) { + if (ferror(in)) { + OPENSSL_PUT_ERROR(PEM, PEM_R_PROBLEMS_GETTING_PASSWORD); + ERR_add_error_data(2, "System error: ", strerror(errno)); + clearerr(tty_in); + } else if(feof(in)) { + return 1; + } + return 0; + } + } while (strchr(buf, '\n') == NULL); + return 1; +} + +/* Signal handling functions */ +static void pushsig(void) { +#if !defined(OPENSSL_WINDOWS) + struct sigaction sa; + OPENSSL_cleanse(&sa, sizeof(sa)); + + sa.sa_handler = recsig; + + for (int i = 1; i < NUM_SIG; i++) { + if (i == SIGUSR1 || i == SIGUSR2 || i == SIGKILL) { + continue; + } + sigaction(i, &sa, &savsig[i]); + } +#else // Specific error codes for windows + savsig[SIGABRT] = signal(SIGABRT, recsig); + savsig[SIGFPE] = signal(SIGFPE, recsig); + savsig[SIGILL] = signal(SIGILL, recsig); + savsig[SIGINT] = signal(SIGINT, recsig); + savsig[SIGSEGV] = signal(SIGSEGV, recsig); + savsig[SIGTERM] = signal(SIGTERM, recsig); +#endif + +// set SIGWINCH handler to default so our workflow is not +// triggered on user resizing of window +#if defined(SIGWINCH) && defined(SIG_DFL) + signal(SIGWINCH, SIG_DFL); +#endif +} + +/* Console management functions */ +void openssl_console_acquire_mutex(void) { + CRYPTO_STATIC_MUTEX_lock_write(&console_global_mutex); +} + +void openssl_console_release_mutex(void) { + CRYPTO_STATIC_MUTEX_unlock_write(&console_global_mutex); +} + +int openssl_console_open(void) { + is_a_tty = 1; + assert(CRYPTO_STATIC_MUTEX_is_write_locked(&console_global_mutex)); + +#if !defined(OPENSSL_WINDOWS) + if ((tty_in = fopen(DEV_TTY, "r")) == NULL) { + tty_in = stdin; + } + if ((tty_out = fopen(DEV_TTY, "w")) == NULL) { + tty_out = stderr; + } + if (TTY_get(fileno(tty_in), &tty_orig) == -1) { + if (errno == ENOTTY || errno == EINVAL || errno == ENXIO || errno == EIO + || errno == EPERM || errno == ENODEV) { + is_a_tty = 0; + } else { + OPENSSL_PUT_ERROR(PEM, ERR_R_INTERNAL_ERROR); + return 0; + } + } +#else + DWORD console_mode; + HANDLE hStdIn = GetStdHandle(STD_INPUT_HANDLE); + + // Check if console (equivalent to checking for /dev/tty on Linux) + if (GetConsoleMode(hStdIn, &console_mode)) { + // It's a real console, use conin$ and conout$ to bypass any redirection + if ((tty_in = fopen("conin$", "r")) == NULL) { + tty_in = stdin; + } + if ((tty_out = fopen("conout$", "w")) == NULL) { + tty_out = stderr; + } + + tty_orig = console_mode; + } else { + // Not a console, use stdin/stderr + tty_in = stdin; + tty_out = stderr; + is_a_tty = 0; + } +#endif + return 1; +} + +int openssl_console_close(void) { + assert(CRYPTO_STATIC_MUTEX_is_write_locked(&console_global_mutex)); + if (tty_in != stdin) { + fclose(tty_in); + } + if (tty_out != stderr) { + fclose(tty_out); + } + + return 1; +} + +static int openssl_console_echo_disable(void) { +#if !defined(OPENSSL_WINDOWS) + OPENSSL_memcpy(&(tty_new), &(tty_orig), sizeof(tty_orig)); + tty_new.TTY_FLAGS &= ~ECHO; + + if (is_a_tty && (TTY_set(fileno(tty_in), &tty_new) == -1)) { + OPENSSL_PUT_ERROR(PEM, ERR_R_INTERNAL_ERROR); + return 0; + } +#else + if (is_a_tty) { + tty_new = tty_orig; + tty_new &= ~ENABLE_ECHO_INPUT; + SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), tty_new); + } +#endif + + return 1; +} + +static int openssl_console_echo_enable(void) { +#if !defined(OPENSSL_WINDOWS) + OPENSSL_memcpy(&(tty_new), &(tty_orig), sizeof(tty_orig)); + if (is_a_tty && (TTY_set(fileno(tty_in), &tty_new) == -1)) { + OPENSSL_PUT_ERROR(PEM, ERR_R_INTERNAL_ERROR); + return 0; + } +#else + if (is_a_tty) { + tty_new = tty_orig; + SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), tty_new); + } +#endif + return 1; +} + +int openssl_console_write(const char *str) { + assert(CRYPTO_STATIC_MUTEX_is_write_locked(&console_global_mutex)); + if (fputs(str, tty_out) < 0 || fflush(tty_out) != 0) { + OPENSSL_PUT_ERROR(PEM, PEM_R_PROBLEMS_GETTING_PASSWORD); + if (ferror(tty_out)) { + ERR_add_error_data(2, "System error: ", strerror(errno)); + clearerr(tty_out); + } + return 0; + } + + return 1; +} + +// returns 0 on success, -1 on error, -2 if interrupt signal received +int openssl_console_read(char *buf, int minsize, int maxsize, int echo) { + int ok = 0; + char *p = NULL; + int echo_eol = !echo; + + intr_signal = 0; + int phase = 0; + + if (!buf || maxsize < minsize) { + return -1; + } + + assert(CRYPTO_STATIC_MUTEX_is_write_locked(&console_global_mutex)); + + pushsig(); + phase = 1; + + if (!echo && !openssl_console_echo_disable()) { + goto error; + } + phase = 2; + + buf[0] = '\0'; +#if defined(OPENSSL_WINDOWS) + if (is_a_tty) { + DWORD numread; + // for now assuming UTF-8.... + WCHAR wresult[BUFSIZ]; + OPENSSL_cleanse(wresult, sizeof(wresult)); + + if (ReadConsoleW(GetStdHandle(STD_INPUT_HANDLE), + wresult, maxsize, &numread, NULL)) { + if (numread >= 2 && wresult[numread-2] == L'\r' && + wresult[numread-1] == L'\n') { + wresult[numread-2] = L'\n'; + numread--; + } + wresult[numread] = L'\0'; + if (WideCharToMultiByte(CP_UTF8, 0, wresult, -1, buf, maxsize + 1, NULL, 0) > 0) { + p = buf; + } + OPENSSL_cleanse(wresult, sizeof(wresult)); + } + } else { + p = fgets(buf, maxsize, tty_in); + } +#else + p = fgets(buf, maxsize, tty_in); +#endif + if (p == NULL || feof(tty_in) || ferror(tty_in)) { + OPENSSL_PUT_ERROR(PEM, PEM_R_PROBLEMS_GETTING_PASSWORD); + if (ferror(tty_in)) { + ERR_add_error_data(2, "System error: ", strerror(errno)); + clearerr(tty_in); + } + ok = -1; + goto error; + } + + // check if we see a new line, otherwise clear out remaining input buffer + if ((p = strchr(buf, '\n')) != NULL) { + *p = '\0'; + } else if (!discard_line_remainder(tty_in)) { + ok = -1; + goto error; + } + + // Validate input length meets minimum requirement + size_t input_len = strlen(buf); + if (input_len < (size_t)minsize) { + ok = -1; + } + +error: + if (intr_signal == SIGINT) { + ok = -2; // interrupted + } + if (echo_eol) { + fprintf(tty_out, "\n"); + } + if (phase >= 2 && !echo && !openssl_console_echo_enable()) { + ok = -1; // general errors + } + if (phase >= 1) { + popsig(); + } + + return ok; +} diff --git a/crypto/console/console_test.cc b/crypto/console/console_test.cc new file mode 100644 index 0000000000..c1d6bdb253 --- /dev/null +++ b/crypto/console/console_test.cc @@ -0,0 +1,237 @@ +#include +#include + +#include "internal.h" +#include "../test/test_util.h" + +#if defined(OPENSSL_WINDOWS) +#include +#define dup _dup +#define dup2 _dup2 +#define fileno _fileno +#define close _close +#else +#include +#endif + + +#if !defined(OPENSSL_ANDROID) +// On Android, when running from an APK, |tmpfile| does not work. See +// b/36991167#comment8. + +// Consolidated password testing +class PemPasswdTest : public testing::Test { + protected: + void SetUp() override { + // Save original file descriptors + original_stdin = dup(fileno(stdin)); + original_stderr = dup(fileno(stderr)); + + // Create temporary files + stdin_file = createRawTempFILE(); + stderr_file = createRawTempFILE(); + ASSERT_TRUE(stdin_file != nullptr); + ASSERT_TRUE(stderr_file != nullptr); + + // Redirect stdin/stderr to our temp files + ASSERT_NE(-1, dup2(fileno(stdin_file), fileno(stdin))); + ASSERT_NE(-1, dup2(fileno(stderr_file), fileno(stderr))); + + // Initialize console for each test + openssl_console_acquire_mutex(); + ASSERT_TRUE(openssl_console_open()); + } + + void TearDown() override { + // Close console for each test + ASSERT_TRUE(openssl_console_close()); + openssl_console_release_mutex(); + + // Restore original streams + ASSERT_NE(-1, dup2(original_stdin, fileno(stdin))); + ASSERT_NE(-1, dup2(original_stderr, fileno(stderr))); + + // Close temp files + if (stdin_file) { + fclose(stdin_file); + } + if (stderr_file) { + fclose(stderr_file); + } + } + + void MockStdinInput(const std::string& input) { + ASSERT_GT(fwrite(input.c_str(), 1, input.length(), stdin_file), (size_t)0); + rewind(stdin_file); + } + + std::string GetStderrOutput() { + std::string output; + char buf[1024]; + rewind(stderr_file); + while (fgets(buf, sizeof(buf), stderr_file) != nullptr) { + output += buf; + } + + return output; + } + + void ResetTempFiles() { + fclose(stdin_file); + fclose(stderr_file); + + stdin_file = tmpfile(); + stderr_file = tmpfile(); + ASSERT_TRUE(stdin_file != nullptr); + ASSERT_TRUE(stderr_file != nullptr); + + // Redirect stdin/stderr to our NEW temp files + ASSERT_NE(-1, dup2(fileno(stdin_file), fileno(stdin))); + ASSERT_NE(-1, dup2(fileno(stderr_file), fileno(stderr))); + } + + + FILE* stdin_file = nullptr; + FILE* stderr_file = nullptr; + int original_stdin = -1; + int original_stderr = -1; + const char* default_prompt = "Enter password:"; +}; + +// Test basic password functionality with various inputs +TEST_F(PemPasswdTest, PasswordInputVariations) { + struct TestCase { + std::string description; + std::string input; + int min_size; + int expected_result; + std::string expected_output; + }; + + std::vector test_cases = { + // Normal password + {"Normal password", "test_password\n", 0, 0, "test_password"}, + // + // // Empty password + {"Empty password allowed", "\n", 0, 0, ""}, + {"Empty password rejected", "\n", 2, -1, ""}, + + // Length requirements + {"Password too short", "short\n", 10, -1, "short"}, + {"Password meets min length", "longenoughpass\n", 10, 0, "longenoughpass"}, + + // Special characters + {"Special characters", "!@#$%^&*()\n", 0, 0, "!@#$%^&*()"}, + {"Unicode characters", "パスワード\n", 0, 0, "パスワード"} + }; + + for (const auto& tc : test_cases) { + SCOPED_TRACE(tc.description); + + char buf[1024] = {0}; + MockStdinInput(tc.input); + + ASSERT_TRUE(openssl_console_write(default_prompt)); + + ASSERT_EQ(openssl_console_read(buf, tc.min_size, sizeof(buf), 0), tc.expected_result); + + if (tc.expected_result == 0) { + ASSERT_STREQ(buf, tc.expected_output.c_str()); + } + + // Verify prompt was written + std::string output = GetStderrOutput(); + ASSERT_TRUE(output.find(default_prompt) != std::string::npos); + + ResetTempFiles(); + } +} + +// Test password verification flow (matching and non-matching) +TEST_F(PemPasswdTest, PasswordVerification) { + struct TestCase { + std::string description; + std::string first_password; + std::string second_password; + bool should_match; + }; + + std::vector test_cases = { + {"Matching passwords", "test_password\n", "test_password\n", true}, + {"Non-matching passwords", "password1\n", "password2\n", false} + }; + + for (const auto& tc : test_cases) { + SCOPED_TRACE(tc.description); + + char buf1[1024] = {0}; + char buf2[1024] = {0}; + + // Mock both password inputs + std::string combined_input = tc.first_password + tc.second_password; + MockStdinInput(combined_input); + + // First password entry + ASSERT_TRUE(openssl_console_write(default_prompt)); + ASSERT_EQ(0, openssl_console_read(buf1, 0, sizeof(buf1), 0)); + + // Verification prompt + ASSERT_TRUE(openssl_console_write("Verifying - ")); + ASSERT_TRUE(openssl_console_write(default_prompt)); + ASSERT_EQ(0, openssl_console_read(buf2, 0, sizeof(buf2), 0)); + + // Verify match/mismatch as expected + if (tc.should_match) { + ASSERT_STREQ(buf1, buf2); + } else { + ASSERT_STRNE(buf1, buf2); + } + + // Verify prompts were written + std::string output = GetStderrOutput(); + ASSERT_TRUE(output.find(default_prompt) != std::string::npos); + ASSERT_TRUE(output.find("Verifying - ") != std::string::npos); + + ResetTempFiles(); + } +} + +// Test buffer handling (truncation of long passwords) +TEST_F(PemPasswdTest, BufferHandling) { + // Small buffer to test truncation + char small_buf[16] = {0}; + + // Create a password longer than the buffer + std::string long_password(32, 'a'); + long_password += "\n"; + + MockStdinInput(long_password); + ASSERT_TRUE(openssl_console_write(default_prompt)); + ASSERT_EQ(0, openssl_console_read(small_buf, 0, sizeof(small_buf),0)); + + // Verify the password was truncated to fit the buffer (15 chars + null terminator) + std::string expected(15, 'a'); + ASSERT_STREQ(small_buf, expected.c_str()); +} + +// Test echo modes +TEST_F(PemPasswdTest, EchoModes) { + const char* test_password = "test_password\n"; + char buf_no_echo[1024] = {0}; + char buf_with_echo[1024] = {0}; + + // Test with echo disabled + MockStdinInput(test_password); + ASSERT_TRUE(openssl_console_write(default_prompt)); + ASSERT_EQ(0, openssl_console_read(buf_no_echo, 0, sizeof(buf_no_echo), 0)); + + // Test with echo enabled + MockStdinInput(test_password); + ASSERT_TRUE(openssl_console_write(default_prompt)); + ASSERT_EQ(0, openssl_console_read(buf_with_echo, 0, sizeof(buf_with_echo), 1)); + + // Both should have the same result + ASSERT_STREQ(buf_no_echo, "test_password"); + ASSERT_STREQ(buf_with_echo, "test_password"); +} +#endif diff --git a/crypto/console/internal.h b/crypto/console/internal.h new file mode 100644 index 0000000000..9ee5de2c0d --- /dev/null +++ b/crypto/console/internal.h @@ -0,0 +1,33 @@ +#include + +#define MIN_LENGTH 4 + +#if defined(__cplusplus) +extern "C" { +#endif + +// Console Management API +// +// This API provides functions for secure console I/O operations, supporting both Unix/Linux +// (termios) and Windows systems. It handles terminal operations like reading input with +// optional echo suppression (for password entry) and signal handling. +// +// Usage requires proper lock management: +// 1. Acquire the console mutex with openssl_console_acquire_mutex() +// 2. Initialize console with openssl_console_open() +// 3. Perform console operations (read/write) +// 4. Clean up with openssl_console_close() +// 5. Release the mutex with openssl_console_release_mutex() +// +// The global mutex must be held during all console operations and released after closing the console. +OPENSSL_EXPORT void openssl_console_acquire_mutex(void); +OPENSSL_EXPORT void openssl_console_release_mutex(void); +OPENSSL_EXPORT int openssl_console_open(void); +OPENSSL_EXPORT int openssl_console_close(void); +OPENSSL_EXPORT int openssl_console_write(const char *str); +OPENSSL_EXPORT int openssl_console_read(char *buf, int minsize, int maxsize, int echo); + + +#if defined(__cplusplus) +} // extern C +#endif diff --git a/crypto/fipsmodule/evp/evp.c b/crypto/fipsmodule/evp/evp.c index 6190010568..8b5f504e06 100644 --- a/crypto/fipsmodule/evp/evp.c +++ b/crypto/fipsmodule/evp/evp.c @@ -69,6 +69,8 @@ #include #include "../../evp_extra/internal.h" +#include "../../pem/internal.h" +#include "../../console/internal.h" #include "../../internal.h" #include "internal.h" @@ -156,6 +158,76 @@ int EVP_PKEY_cmp(const EVP_PKEY *a, const EVP_PKEY *b) { return -2; } +char *EVP_get_pw_prompt(void) { + return (char*)"Enter PEM passphrase:"; +} + +int EVP_read_pw_string(char *buf, int length, const char *prompt, int verify) { + return EVP_read_pw_string_min(buf, 0, length, prompt, verify); +} + +int EVP_read_pw_string_min(char *buf, int min_length, int length, + const char *prompt, int verify) { + int ret = -1; + char verify_buf[1024]; + + if (!buf || min_length <= 0 || min_length >= length) { + return -1; + } + + if (prompt == NULL) { + prompt = EVP_get_pw_prompt(); + } + + // Proactively zeroize |buf| and verify_buf + OPENSSL_cleanse(buf, sizeof(buf)); + OPENSSL_cleanse(verify_buf, sizeof(verify_buf)); + + // acquire write lock + openssl_console_acquire_mutex(); + + if (!openssl_console_open()) { + goto err; + } + + // Write initial password prompt + if (!openssl_console_write(prompt)) { + goto err; + } + + // Read password with echo disabled, returns 1 on success, 0 on error, -2 on interrupt + ret = openssl_console_read(buf, min_length, length, 0); + if (ret != 0) { + OPENSSL_cleanse(buf, sizeof(buf)); + OPENSSL_PUT_ERROR(PEM, PEM_R_PROBLEMS_GETTING_PASSWORD); + goto err; + } + + if (verify) { + openssl_console_write("Verifying - "); + openssl_console_write(prompt); + + ret = openssl_console_read(verify_buf, min_length, sizeof(verify_buf), 0); + + if (ret == 0) { + if (strncmp(buf, verify_buf, length >= 1024 ? 1024 : length) != 0) { + openssl_console_write("Verify failure\n"); + ret = -1; + } + } else { + OPENSSL_PUT_ERROR(PEM, PEM_R_PROBLEMS_GETTING_PASSWORD); + goto err; + } + } + + openssl_console_close(); + +err: + openssl_console_release_mutex(); + OPENSSL_cleanse(verify_buf, sizeof(verify_buf)); + return ret; +} + int EVP_PKEY_copy_parameters(EVP_PKEY *to, const EVP_PKEY *from) { SET_DIT_AUTO_RESET; if (to->type == EVP_PKEY_NONE) { diff --git a/crypto/internal.h b/crypto/internal.h index 14de738fab..82fce99699 100644 --- a/crypto/internal.h +++ b/crypto/internal.h @@ -705,6 +705,15 @@ OPENSSL_EXPORT void CRYPTO_STATIC_MUTEX_unlock_read( OPENSSL_EXPORT void CRYPTO_STATIC_MUTEX_unlock_write( struct CRYPTO_STATIC_MUTEX *lock); +#if !defined(NDEBUG) +// CRYPTO_STATIC_MUTEX_is_write_locked checks whether |lock| has an active write +// lock. If it does, the function returns 1. If it doesn't, it returns 0. Returns -1 +// on any other error. Note that due to the concurrent nature of locks, the result +// may be stale by the time it is used. +OPENSSL_EXPORT int CRYPTO_STATIC_MUTEX_is_write_locked( + struct CRYPTO_STATIC_MUTEX *lock); +#endif + #if defined(__cplusplus) extern "C++" { diff --git a/crypto/pem/internal.h b/crypto/pem/internal.h index 9750718977..48f1452334 100644 --- a/crypto/pem/internal.h +++ b/crypto/pem/internal.h @@ -1,5 +1,8 @@ #include +#if defined(__cplusplus) +extern "C" { +#endif // PEM utility functions // PEM_proc_type appends a Proc-Type header to |buf|, determined by |type|. @@ -9,3 +12,7 @@ void PEM_proc_type(char buf[PEM_BUFSIZE], int type); // and a single parameter, specified by hex-encoding |len| bytes from |str|. void PEM_dek_info(char buf[PEM_BUFSIZE], const char *type, size_t len, char *str); + +#if defined(__cplusplus) +} // extern C +#endif diff --git a/crypto/pem/pem_lib.c b/crypto/pem/pem_lib.c index 102713a5b4..6ac5dd8a56 100644 --- a/crypto/pem/pem_lib.c +++ b/crypto/pem/pem_lib.c @@ -72,11 +72,10 @@ #include "internal.h" #include "../internal.h" +#include "../console/internal.h" #include "../fipsmodule/evp/internal.h" -#define MIN_LENGTH 4 - static int load_iv(char **fromp, unsigned char *to, size_t num); static int check_pem(const char *nm, const char *name); @@ -785,13 +784,40 @@ int PEM_read_bio(BIO *bp, char **name, char **header, unsigned char **data, } int PEM_def_callback(char *buf, int size, int rwflag, void *userdata) { - if (!buf || !userdata || size < 0) { + if (!buf || size <= 0) { return 0; } - size_t len = strlen((char *)userdata); - if (len >= (size_t)size) { + + // Proactively zeroize |buf| + OPENSSL_cleanse(buf, size); + + if (userdata) { + size_t len = strlen((char *)userdata); + if (len >= (size_t)size) { + return 0; + } + OPENSSL_strlcpy(buf, userdata, (size_t)size); + return (int)len; + } + + const char *prompt = EVP_get_pw_prompt(); + if (prompt == NULL) { + prompt = "Enter PEM pass phrase:"; + } + + /* + * rwflag == 0 means decryption + * rwflag == 1 means encryption + * + * We assume that for encryption, we want a minimum length, while for + * decryption, we cannot know any minimum length, so we assume zero. + */ + int min_len = rwflag ? MIN_LENGTH : 0; + + int ret = EVP_read_pw_string_min(buf, min_len, size, prompt, rwflag); + if (ret != 0 || size > INT_MAX) { return 0; } - OPENSSL_strlcpy(buf, userdata, (size_t)size); - return (int)len; + + return (int)OPENSSL_strnlen(buf, size); } diff --git a/crypto/pem/pem_test.cc b/crypto/pem/pem_test.cc index e692231241..0c96a4eaea 100644 --- a/crypto/pem/pem_test.cc +++ b/crypto/pem/pem_test.cc @@ -24,9 +24,6 @@ #include #include -#include "../test/test_util.h" - - #include "../test/test_util.h" const char* SECRET = "test"; @@ -452,10 +449,7 @@ TEST(PEMTest, WriteReadTraditionalPem) { ASSERT_TRUE(pkey_read); DSA *pkey_dsa = EVP_PKEY_get0_DSA(pkey.get()); - EXPECT_EQ(0, - BN_cmp(DSA_get0_priv_key(pkey_dsa), DSA_get0_priv_key(dsa.get()))); - EXPECT_EQ(0, - BN_cmp(DSA_get0_priv_key(pkey_dsa), DSA_get0_priv_key(dsa.get()))); + EXPECT_EQ(0, BN_cmp(DSA_get0_priv_key(pkey_dsa), DSA_get0_priv_key(dsa.get()))); // Test |PEM_write_bio_PrivateKey_traditional| with |DH|. This should fail, // since it's not supported by the API. diff --git a/crypto/thread_none.c b/crypto/thread_none.c index 4f07b9d9c8..5d0656a940 100644 --- a/crypto/thread_none.c +++ b/crypto/thread_none.c @@ -36,6 +36,12 @@ void CRYPTO_STATIC_MUTEX_unlock_read(struct CRYPTO_STATIC_MUTEX *lock) {} void CRYPTO_STATIC_MUTEX_unlock_write(struct CRYPTO_STATIC_MUTEX *lock) {} +#if !defined(NDEBUG) +int CRYPTO_STATIC_MUTEX_is_write_locked(struct CRYPTO_STATIC_MUTEX *lock) { + return 1; +} +#endif + void CRYPTO_once(CRYPTO_once_t *once, void (*init)(void)) { if (*once) { return; diff --git a/crypto/thread_pthread.c b/crypto/thread_pthread.c index 2ab8444e66..18c1df4d33 100644 --- a/crypto/thread_pthread.c +++ b/crypto/thread_pthread.c @@ -118,6 +118,27 @@ void CRYPTO_STATIC_MUTEX_unlock_write(struct CRYPTO_STATIC_MUTEX *lock) { } } +#if !defined(NDEBUG) +int CRYPTO_STATIC_MUTEX_is_write_locked(struct CRYPTO_STATIC_MUTEX *lock) { + assert(lock != NULL); + + int result = pthread_rwlock_tryrdlock(&lock->lock); + + if (result == 0) { + // If we successfully acquired the lock, it wasn't locked + // Release it immediately and return false + pthread_rwlock_unlock(&lock->lock); + return 0; + } + // errno may be set to EDEADLK if the current thread is already has a write lock + if (result == EBUSY || result == EDEADLK) { + return 1; + } + + return -1; +} +#endif + void CRYPTO_once(CRYPTO_once_t *once, void (*init)(void)) { if (pthread_once(once, init) != 0) { abort(); diff --git a/crypto/thread_win.c b/crypto/thread_win.c index 0606e6db0f..57b19c40e0 100644 --- a/crypto/thread_win.c +++ b/crypto/thread_win.c @@ -85,6 +85,21 @@ void CRYPTO_STATIC_MUTEX_unlock_write(struct CRYPTO_STATIC_MUTEX *lock) { ReleaseSRWLockExclusive(&lock->lock); } +#if !defined(NDEBUG) +int CRYPTO_STATIC_MUTEX_is_write_locked(struct CRYPTO_STATIC_MUTEX *lock) { + assert(lock != NULL); + + if (TryAcquireSRWLockShared(&lock->lock)) { + // If successful, the lock is not write-locked + // Release it immediately and return false (0) + ReleaseSRWLockShared(&lock->lock); + return 0; + } + + return 1; +} +#endif + static SRWLOCK g_destructors_lock = SRWLOCK_INIT; static thread_local_destructor_t g_destructors[NUM_OPENSSL_THREAD_LOCALS]; diff --git a/include/openssl/evp.h b/include/openssl/evp.h index 4918718b52..5a6664b1de 100644 --- a/include/openssl/evp.h +++ b/include/openssl/evp.h @@ -142,6 +142,29 @@ OPENSSL_EXPORT const char *EVP_MD_get0_name(const EVP_MD *md); // EVP_MD_name calls |EVP_MD_get0_name| OPENSSL_EXPORT const char *EVP_MD_name(const EVP_MD *md); +// EVP Password Utility Functions + +// EVP_get_pw_prompt returns an internal pointer to static memory containing +// the default prompt. In AWS-LC, this default is hardcoded. In OpenSSL, +// the default prompt must be configured by a user and is otherwise NULL. +OPENSSL_EXPORT char *EVP_get_pw_prompt(void); + +// EVP_read_pw_string writes the prompt to /dev/tty, or, if that could not be opened, +// to standard output, turns echo off, and reads an input string from /dev/tty, or, +// if that could not be opened, from standard input. If |prompt| is NULL, the default +// prompt is used. The user input is returned in |buf|, which must have space for at +// least length bytes. If verify is set, the user is asked for the password twice and +// unless the two copies match, an error is returned. +// Returns 0 on success, -1 on error, or -2 on out-of-band events (Interrupt, Cancel, ...). +OPENSSL_EXPORT int EVP_read_pw_string(char *buf, int length, const char *prompt, int verify); + +// EVP_read_pw_string_min implements the functionality for |EVP_read_pw_string|. It +// additionally checks that the password is at least |min_length| bytes long. +// Returns 0 on success, -1 on error, or -2 on out-of-band events (Interrupt, Cancel, ...). +OPENSSL_EXPORT int EVP_read_pw_string_min(char *buf, int min_length, int length, + const char *prompt, int verify); + + // Getting and setting concrete public key types. // // The following functions get and set the underlying public key in an diff --git a/include/openssl/pem.h b/include/openssl/pem.h index bb3c44f3cd..6b3f4284e2 100644 --- a/include/openssl/pem.h +++ b/include/openssl/pem.h @@ -316,6 +316,12 @@ typedef int pem_password_cb(char *buf, int size, int rwflag, void *userdata); OPENSSL_EXPORT int PEM_get_EVP_CIPHER_INFO(char *header, EVP_CIPHER_INFO *cipher); + +// PEM_do_header decrypts PEM-encoded data using the cipher info in |cipher|. +// It processes |data| of length |len| using a password obtained via |callback| +// (or the default callback provided via |PEM_def_callback| if NULL) with callback +// data |u|. It then updates |len| with decrypted length. +// Returns 1 on success or if |cipher| is NULL, 0 on failure. OPENSSL_EXPORT int PEM_do_header(EVP_CIPHER_INFO *cipher, unsigned char *data, long *len, pem_password_cb *callback, void *u); @@ -357,6 +363,11 @@ OPENSSL_EXPORT int PEM_bytes_read_bio(unsigned char **pdata, long *plen, OPENSSL_EXPORT void *PEM_ASN1_read_bio(d2i_of_void *d2i, const char *name, BIO *bp, void **x, pem_password_cb *cb, void *u); + +// PEM_ASN1_write_bio writes ASN.1 structure |x| encoded by |i2d| to BIO |bp| in PEM format +// with name |name|. If |enc| is non-NULL, encrypts data using cipher with password from +// |pass| and |pass_len|, or via |callback| with user data |u| (uses PEM_def_callback if +// callback is NULL). Returns 1 on success, 0 on failure. OPENSSL_EXPORT int PEM_ASN1_write_bio(i2d_of_void *i2d, const char *name, BIO *bp, void *x, const EVP_CIPHER *enc, const unsigned char *pass, int pass_len, @@ -410,11 +421,17 @@ OPENSSL_EXPORT int PEM_ASN1_write(i2d_of_void *i2d, const char *name, FILE *fp, const unsigned char *pass, int pass_len, pem_password_cb *callback, void *u); -// PEM_def_callback treats |userdata| as a string and copies it into |buf|, -// assuming its |size| is sufficient. Returns the length of the string, or 0 -// if there is not enough room. If either |buf| or |userdata| is NULL, 0 is -// returned. Note that this is different from OpenSSL, which prompts for a -// password. +// PEM_def_callback provides a password for PEM encryption/decryption operations. +// This function is used as the default callback to provide a password for PEM +// functions such as |PEM_do_header| and |PEM_ASN1_write_bio|. +// If |userdata| is non-NULL, it treats |userdata| as a string and copies it +// into |buf|, assuming |size| is sufficient. If |userdata| is NULL, it prompts +// the user for a password using the prompt from EVP_get_pw_prompt() (or default +// "Enter PEM pass phrase:"). For encryption (|rwflag|=1), a minimum password +// length is enforced, while for decryption (|rwflag|=0) any password length is +// accepted. Returns the length of the password (excluding null +// terminator) on success, or 0 on error or if |buf| is null, if |buf| is too small, +// or |size| is negative, or |size| is smaller than user input length. OPENSSL_EXPORT int PEM_def_callback(char *buf, int size, int rwflag, void *userdata); diff --git a/tool-openssl/req.cc b/tool-openssl/req.cc index d9fa514002..7b66a2d510 100644 --- a/tool-openssl/req.cc +++ b/tool-openssl/req.cc @@ -226,12 +226,18 @@ static const char *prompt_field(const ReqField &field, char *buffer, return NULL; } - // Remove newline character if present + // Remove newline character and carriage return if present size_t len = OPENSSL_strnlen(buffer, buffer_size); if (len > 0 && buffer[len - 1] == '\n') { buffer[len - 1] = '\0'; len--; } +#if defined(_WIN32) + if (len > 0 && buffer[len - 1] == '\r') { + buffer[len - 1] = '\0'; + len--; + } +#endif if (strcmp(buffer, ".") == 0) { // Empty entry requested @@ -381,60 +387,6 @@ static int make_certificate_request(X509_REQ *req, EVP_PKEY *pkey, return 1; } -static int req_password_callback(char *buf, int size, int rwflag, - void *userdata) { - const char *prompt = "Enter PEM pass phrase:"; - char verify_buf[BUF_SIZE]; - int len; - - // Display prompt - fprintf(stderr, "%s", prompt); - fflush(stderr); - - // Get password - if (fgets(buf, size, stdin) == NULL) { - fprintf(stderr, "Error reading password\n"); - return 0; - } - - // Remove trailing newline - len = OPENSSL_strnlen(buf, sizeof(buf)); - if (len > 0 && buf[len - 1] == '\n') { - buf[--len] = '\0'; - } - - // For encryption only (which is the case for req tool) - if (rwflag) { - // Verify password - fprintf(stderr, "Verifying - %s", prompt); - fflush(stderr); - - if (fgets(verify_buf, sizeof(verify_buf), stdin) == NULL) { - fprintf(stderr, "Error reading verification password\n"); - return 0; - } - - // Remove trailing newline - int verify_len = OPENSSL_strnlen(verify_buf, sizeof(verify_buf)); - if (verify_len > 0 && verify_buf[verify_len - 1] == '\n') - verify_buf[--verify_len] = '\0'; - - // Check if passwords match - if (strncmp(buf, verify_buf, BUF_SIZE) != 0) { - fprintf(stderr, "Passwords don't match\n"); - return 0; - } - - // Enforce minimum length - if (len < 4) { - fprintf(stderr, "Password too short (minimum 4 characters)\n"); - return 0; - } - } - - return len; -} - // Function to add extensions to a certificate static bool add_cert_extensions(X509 *cert) { const char *config = @@ -569,7 +521,7 @@ bool reqTool(const args_list_t &args) { // If encryption disabled, don't use password prompting callback if (!out_bio || !PEM_write_bio_PrivateKey(out_bio.get(), pkey.get(), cipher, NULL, 0, - cipher ? req_password_callback : NULL, NULL)) { + NULL, NULL)) { fprintf(stderr, "Failed to write private key.\n"); return false; }