From 86ec20189abaf0b07150a0d73c6d6e0f2a3f4780 Mon Sep 17 00:00:00 2001 From: Vladislav Vaintroub Date: Mon, 17 Feb 2025 14:50:01 +0100 Subject: [PATCH] MDEV-14091 Support password protected SSL key in server. Add ssl_passphrase server parameter, which works similarly to --passout/--passin openssl command line parameters. Pass phrase value can be formatted as follows. - pass:password Provide actual password after the pass: prefix. - env:var Obtain the password from the environment variable 'var'a - file:pathname Reads the password from the specified file pathname. Only the first line, up to the newline character, is read from the stream. If ssl_passphrase was set, SHOW VARIABLE will show "file:", "env:" or "pass:" (but won't reveal sensitive data) --- include/violite.h | 2 +- .../lib/encrypted-server-key-password.txt | 2 + mysql-test/lib/generate-ssl-certs.sh | 3 + mysql-test/main/bad_startup_options.result | 9 ++ mysql-test/main/bad_startup_options.test | 46 ++++++ mysql-test/main/ssl_encrypted_key-master.opt | 1 + mysql-test/main/ssl_encrypted_key.cnf | 4 + .../main/ssl_encrypted_key.combinations | 8 ++ mysql-test/main/ssl_encrypted_key.result | 8 ++ mysql-test/main/ssl_encrypted_key.test | 6 + mysql-test/main/variables.result | 2 + .../encrypted-server-key-password.txt | 1 + mysql-test/std_data/encrypted-server-key.pem | 54 +++++++ .../sys_vars/r/sysvars_server_embedded.result | 10 ++ .../r/sysvars_server_notembedded.result | 10 ++ sql/mysqld.cc | 10 +- sql/mysqld.h | 3 + sql/sys_vars.cc | 61 ++++++++ vio/viosslfactories.c | 135 +++++++++++++++++- 19 files changed, 365 insertions(+), 10 deletions(-) create mode 100644 mysql-test/lib/encrypted-server-key-password.txt create mode 100644 mysql-test/main/ssl_encrypted_key-master.opt create mode 100644 mysql-test/main/ssl_encrypted_key.cnf create mode 100644 mysql-test/main/ssl_encrypted_key.combinations create mode 100644 mysql-test/main/ssl_encrypted_key.result create mode 100644 mysql-test/main/ssl_encrypted_key.test create mode 100644 mysql-test/std_data/encrypted-server-key-password.txt create mode 100644 mysql-test/std_data/encrypted-server-key.pem diff --git a/include/violite.h b/include/violite.h index f1e5c95a648..b1d814be847 100644 --- a/include/violite.h +++ b/include/violite.h @@ -191,7 +191,7 @@ struct st_VioSSLFd const char *ca_file,const char *ca_path, const char *cipher, enum enum_ssl_init_error *error, const char *crl_file, const char *crl_path, - ulonglong tls_version); + ulonglong tls_version, const char *passphrase); void free_vio_ssl_acceptor_fd(struct st_VioSSLFd *fd); #endif /* HAVE_OPENSSL */ diff --git a/mysql-test/lib/encrypted-server-key-password.txt b/mysql-test/lib/encrypted-server-key-password.txt new file mode 100644 index 00000000000..ff6d60fbe27 --- /dev/null +++ b/mysql-test/lib/encrypted-server-key-password.txt @@ -0,0 +1,2 @@ +MySecretPass + diff --git a/mysql-test/lib/generate-ssl-certs.sh b/mysql-test/lib/generate-ssl-certs.sh index 050a5aa8491..f698464b932 100755 --- a/mysql-test/lib/generate-ssl-certs.sh +++ b/mysql-test/lib/generate-ssl-certs.sh @@ -24,6 +24,9 @@ openssl req -x509 -newkey rsa:4096 -keyout cakey.pem -out cacert.pem -days 7300 openssl req -newkey rsa:4096 -keyout server-key.pem -out demoCA/server-req.pem -days 7300 -nodes -subj '/CN=localhost/C=FI/ST=state or province within country, in other certificates in this file it is the same as L/L=location, usually an address but often ambiguously used/OU=organizational unit name, a division name within an organization/O=organization name, typically a company name' # convert the key to yassl compatible format openssl rsa -in server-key.pem -out server-key.pem +# also create a password-protected server key +echo MySecretPass > encrypted-server-key-password.txt +openssl rsa -aes256 -in server-key.pem -out encrypted-server-key.pem -passout file:encrypted-server-key-password.txt # sign the server certificate with CA certificate openssl ca -keyfile cakey.pem -days 7300 -batch -cert cacert.pem -policy policy_anything -out server-cert.pem -in demoCA/server-req.pem diff --git a/mysql-test/main/bad_startup_options.result b/mysql-test/main/bad_startup_options.result index 7846e629ef8..5bcb954ae89 100644 --- a/mysql-test/main/bad_startup_options.result +++ b/mysql-test/main/bad_startup_options.result @@ -1,3 +1,12 @@ FOUND 1 /\[ERROR\] SSL error: Unable to get certificate/ in errorlog.err FOUND 1 /\[ERROR\] SSL error: Failed to set ciphers to use/ in errorlog.err +FOUND 1 /\[ERROR\] SSL error: Unable to get private key/ in errorlog.err +FOUND 1 /SSL passphrase error: failed to open file 'BadFile'/ in errorlog.err +FOUND 1 /\[ERROR\] SSL error: Unable to get private key/ in errorlog.err +FOUND 1 /\[Warning\] ssl passphrase file '.*' is not secure/ in errorlog.err +FOUND 1 /\[ERROR\] SSL error: Unable to get private key/ in errorlog.err +FOUND 1 /SSL passphrase error: environment variable 'BadEnv' not found/ in errorlog.err +FOUND 1 /\[ERROR\] SSL error: Unable to get private key/ in errorlog.err +FOUND 1 /SSL passphrase error: ssl-passphrase value must be prefixed with 'file:', 'env:', or 'pass:'/ in errorlog.err +FOUND 1 /\[ERROR\] SSL error: Unable to get private key/ in errorlog.err # restart diff --git a/mysql-test/main/bad_startup_options.test b/mysql-test/main/bad_startup_options.test index ba88476625d..5879e32f558 100644 --- a/mysql-test/main/bad_startup_options.test +++ b/mysql-test/main/bad_startup_options.test @@ -26,4 +26,50 @@ --source include/search_pattern_in_file.inc --remove_file $SEARCH_FILE +# Wrong SSL passphrase(pass) +--error 1 +--exec $MYSQLD --defaults-group-suffix=.1 --defaults-file=$MYSQLTEST_VARDIR/my.cnf --ssl-key=$MYSQL_TEST_DIR/std_data/encrypted-server-key.pem --ssl-passphrase=pass:BadPassword --log-error=$errorlog +--let SEARCH_PATTERN=\[ERROR\] SSL error: Unable to get private key +--source include/search_pattern_in_file.inc +--remove_file $SEARCH_FILE + +# Bad SSL passphrase(file) +--error 1 +--exec $MYSQLD --defaults-group-suffix=.1 --defaults-file=$MYSQLTEST_VARDIR/my.cnf --secure-file-priv=$MYSQLTEST_VARDIR/tmp --ssl-key=$MYSQL_TEST_DIR/std_data/encrypted-server-key.pem --ssl-passphrase=file:BadFile --log-error=$errorlog +--let SEARCH_PATTERN=SSL passphrase error: failed to open file 'BadFile' +--source include/search_pattern_in_file.inc +--let SEARCH_PATTERN=\[ERROR\] SSL error: Unable to get private key +--source include/search_pattern_in_file.inc +--remove_file $SEARCH_FILE + + +# Check "insecure SSL passphrase file location" warning (secure-file-priv empty) +# We still let it fail later, by supply non-existing file +--error 1 +--exec $MYSQLD --defaults-group-suffix=.1 --defaults-file=$MYSQLTEST_VARDIR/my.cnf --secure-file-priv= --ssl-key=$MYSQL_TEST_DIR/std_data/encrypted-server-key.pem --ssl-passphrase=file:$BadFile --log-error=$errorlog +--let SEARCH_PATTERN=\[Warning\] ssl passphrase file '.*' is not secure +--source include/search_pattern_in_file.inc +--let SEARCH_PATTERN=\[ERROR\] SSL error: Unable to get private key +--source include/search_pattern_in_file.inc +--remove_file $SEARCH_FILE + + +# Bad SSL passphrase(env) +--error 1 +--exec $MYSQLD --defaults-group-suffix=.1 --defaults-file=$MYSQLTEST_VARDIR/my.cnf --ssl-key=$MYSQL_TEST_DIR/std_data/encrypted-server-key.pem --ssl-passphrase=env:BadEnv --log-error=$errorlog +--let SEARCH_PATTERN=SSL passphrase error: environment variable 'BadEnv' not found +--source include/search_pattern_in_file.inc +--let SEARCH_PATTERN=\[ERROR\] SSL error: Unable to get private key +--source include/search_pattern_in_file.inc +--remove_file $SEARCH_FILE + +# Bad SSL passphrase(invalid prefix) +--error 1 +--exec $MYSQLD --defaults-group-suffix=.1 --defaults-file=$MYSQLTEST_VARDIR/my.cnf --ssl-key=$MYSQL_TEST_DIR/std_data/encrypted-server-key.pem --ssl-passphrase=BadVal --log-error=$errorlog +--let SEARCH_PATTERN=SSL passphrase error: ssl-passphrase value must be prefixed with 'file:', 'env:', or 'pass:' +--source include/search_pattern_in_file.inc +--let SEARCH_PATTERN=\[ERROR\] SSL error: Unable to get private key +--source include/search_pattern_in_file.inc +--remove_file $SEARCH_FILE + --source include/start_mysqld.inc diff --git a/mysql-test/main/ssl_encrypted_key-master.opt b/mysql-test/main/ssl_encrypted_key-master.opt new file mode 100644 index 00000000000..2f1c424b67b --- /dev/null +++ b/mysql-test/main/ssl_encrypted_key-master.opt @@ -0,0 +1 @@ +--loose-ssl_key=$MYSQL_TEST_DIR/std_data/encrypted-server-key.pem diff --git a/mysql-test/main/ssl_encrypted_key.cnf b/mysql-test/main/ssl_encrypted_key.cnf new file mode 100644 index 00000000000..81591225496 --- /dev/null +++ b/mysql-test/main/ssl_encrypted_key.cnf @@ -0,0 +1,4 @@ +!include include/default_my.cnf + +[ENV] +SSL_KEY_PASSWORD=MySecretPass diff --git a/mysql-test/main/ssl_encrypted_key.combinations b/mysql-test/main/ssl_encrypted_key.combinations new file mode 100644 index 00000000000..8d24f6bb06e --- /dev/null +++ b/mysql-test/main/ssl_encrypted_key.combinations @@ -0,0 +1,8 @@ +[pass] +loose-ssl_passphrase=pass:MySecretPass + +[env] +loose-ssl_passphrase=env:SSL_KEY_PASSWORD + +[file] +loose-ssl_passphrase=file:$MYSQL_TEST_DIR/std_data/encrypted-server-key-password.txt diff --git a/mysql-test/main/ssl_encrypted_key.result b/mysql-test/main/ssl_encrypted_key.result new file mode 100644 index 00000000000..2559d31608e --- /dev/null +++ b/mysql-test/main/ssl_encrypted_key.result @@ -0,0 +1,8 @@ +SELECT @@ssl_passphrase; +@@ssl_passphrase +pass: +have_ssl +1 +FLUSH SSL; +have_ssl +1 diff --git a/mysql-test/main/ssl_encrypted_key.test b/mysql-test/main/ssl_encrypted_key.test new file mode 100644 index 00000000000..89fc9a63bc2 --- /dev/null +++ b/mysql-test/main/ssl_encrypted_key.test @@ -0,0 +1,6 @@ +-- source include/have_ssl_communication.inc +--replace_result env pass file pass +SELECT @@ssl_passphrase; +--exec $MYSQL --ssl -e "SELECT (VARIABLE_VALUE <> '') as have_ssl FROM INFORMATION_SCHEMA.SESSION_STATUS WHERE VARIABLE_NAME='Ssl_cipher'" 2>&1 +FLUSH SSL; +--exec $MYSQL --ssl -e "SELECT (VARIABLE_VALUE <> '') as have_ssl FROM INFORMATION_SCHEMA.SESSION_STATUS WHERE VARIABLE_NAME='Ssl_cipher'" 2>&1 diff --git a/mysql-test/main/variables.result b/mysql-test/main/variables.result index baf565fa8e3..5c37b5f751b 100644 --- a/mysql-test/main/variables.result +++ b/mysql-test/main/variables.result @@ -962,6 +962,7 @@ ssl_cipher # ssl_crl # ssl_crlpath # ssl_key # +ssl_passphrase # select * from information_schema.session_variables where variable_name like 'ssl%' order by 1; VARIABLE_NAME VARIABLE_VALUE SSL_CA # @@ -971,6 +972,7 @@ SSL_CIPHER # SSL_CRL # SSL_CRLPATH # SSL_KEY # +SSL_PASSPHRASE # select @@log_queries_not_using_indexes; @@log_queries_not_using_indexes 0 diff --git a/mysql-test/std_data/encrypted-server-key-password.txt b/mysql-test/std_data/encrypted-server-key-password.txt new file mode 100644 index 00000000000..cb7534931fd --- /dev/null +++ b/mysql-test/std_data/encrypted-server-key-password.txt @@ -0,0 +1 @@ +MySecretPass diff --git a/mysql-test/std_data/encrypted-server-key.pem b/mysql-test/std_data/encrypted-server-key.pem new file mode 100644 index 00000000000..62936810518 --- /dev/null +++ b/mysql-test/std_data/encrypted-server-key.pem @@ -0,0 +1,54 @@ +-----BEGIN ENCRYPTED PRIVATE KEY----- +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 +-----END ENCRYPTED PRIVATE KEY----- diff --git a/mysql-test/suite/sys_vars/r/sysvars_server_embedded.result b/mysql-test/suite/sys_vars/r/sysvars_server_embedded.result index fb801246bdb..c735f2cbca5 100644 --- a/mysql-test/suite/sys_vars/r/sysvars_server_embedded.result +++ b/mysql-test/suite/sys_vars/r/sysvars_server_embedded.result @@ -3792,6 +3792,16 @@ NUMERIC_BLOCK_SIZE NULL ENUM_VALUE_LIST NULL READ_ONLY YES COMMAND_LINE_ARGUMENT NULL +VARIABLE_NAME SSL_PASSPHRASE +VARIABLE_SCOPE GLOBAL +VARIABLE_TYPE VARCHAR +VARIABLE_COMMENT SSL certificate key passphrase +NUMERIC_MIN_VALUE NULL +NUMERIC_MAX_VALUE NULL +NUMERIC_BLOCK_SIZE NULL +ENUM_VALUE_LIST NULL +READ_ONLY YES +COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME STANDARD_COMPLIANT_CTE VARIABLE_SCOPE SESSION VARIABLE_TYPE BOOLEAN diff --git a/mysql-test/suite/sys_vars/r/sysvars_server_notembedded.result b/mysql-test/suite/sys_vars/r/sysvars_server_notembedded.result index 4271bab38e0..44e3798e38e 100644 --- a/mysql-test/suite/sys_vars/r/sysvars_server_notembedded.result +++ b/mysql-test/suite/sys_vars/r/sysvars_server_notembedded.result @@ -4552,6 +4552,16 @@ NUMERIC_BLOCK_SIZE NULL ENUM_VALUE_LIST NULL READ_ONLY YES COMMAND_LINE_ARGUMENT REQUIRED +VARIABLE_NAME SSL_PASSPHRASE +VARIABLE_SCOPE GLOBAL +VARIABLE_TYPE VARCHAR +VARIABLE_COMMENT SSL certificate key passphrase +NUMERIC_MIN_VALUE NULL +NUMERIC_MAX_VALUE NULL +NUMERIC_BLOCK_SIZE NULL +ENUM_VALUE_LIST NULL +READ_ONLY YES +COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME STANDARD_COMPLIANT_CTE VARIABLE_SCOPE SESSION VARIABLE_TYPE BOOLEAN diff --git a/sql/mysqld.cc b/sql/mysqld.cc index def9be80ec0..d4e8e9156ff 100644 --- a/sql/mysqld.cc +++ b/sql/mysqld.cc @@ -4737,10 +4737,10 @@ static void init_ssl() /* having ssl_acceptor_fd != 0 signals the use of SSL */ ssl_acceptor_fd= new_VioSSLAcceptorFd(opt_ssl_key, opt_ssl_cert, - opt_ssl_ca, opt_ssl_capath, - opt_ssl_cipher, &error, - opt_ssl_crl, opt_ssl_crlpath, - tls_version); + opt_ssl_ca, opt_ssl_capath, + opt_ssl_cipher, &error, + opt_ssl_crl, opt_ssl_crlpath, + tls_version, get_ssl_passphrase()); DBUG_PRINT("info",("ssl_acceptor_fd: %p", ssl_acceptor_fd)); if (!ssl_acceptor_fd) { @@ -4789,7 +4789,7 @@ int reinit_ssl() enum enum_ssl_init_error error = SSL_INITERR_NOERROR; st_VioSSLFd *new_fd = new_VioSSLAcceptorFd(opt_ssl_key, opt_ssl_cert, opt_ssl_ca, opt_ssl_capath, opt_ssl_cipher, &error, opt_ssl_crl, - opt_ssl_crlpath, tls_version); + opt_ssl_crlpath, tls_version, get_ssl_passphrase()); if (!new_fd) { diff --git a/sql/mysqld.h b/sql/mysqld.h index 3fe63b7bfc6..03494875872 100644 --- a/sql/mysqld.h +++ b/sql/mysqld.h @@ -733,6 +733,9 @@ extern mysql_cond_t COND_manager; extern my_bool opt_use_ssl; extern char *opt_ssl_ca, *opt_ssl_capath, *opt_ssl_cert, *opt_ssl_cipher, *opt_ssl_key, *opt_ssl_crl, *opt_ssl_crlpath; + +extern const char *get_ssl_passphrase(); + extern ulonglong tls_version; #ifdef MYSQL_SERVER diff --git a/sql/sys_vars.cc b/sql/sys_vars.cc index 379401644b2..c5147c62f5a 100644 --- a/sql/sys_vars.cc +++ b/sql/sys_vars.cc @@ -4136,6 +4136,67 @@ static Sys_var_charptr_fscs Sys_ssl_crlpath( READ_ONLY GLOBAL_VAR(opt_ssl_crlpath), SSL_OPT(OPT_SSL_CRLPATH), DEFAULT(0)); +static char *opt_ssl_passphrase; +static Sys_var_charptr Sys_ssl_passphrase( + "ssl_passphrase", + "SSL certificate key passphrase", + READ_ONLY GLOBAL_VAR(opt_ssl_passphrase), CMD_LINE(REQUIRED_ARG), + DEFAULT(0)); + +/** + Retrieve ssl passphrase. + + This function should be used instead of directly accessing + opt_ssl_passphrase. + + If system variable ssl_passphrase is set, this function + saves the original value of, then changes system variable, + hiding security sensitive info in SHOW VARIABLES. + + We store original value internally, it will be needed in + FLUSH SSL +*/ +const char *get_ssl_passphrase() +{ + static std::string saved_ssl_passphrase; + + if (!saved_ssl_passphrase.empty()) + return saved_ssl_passphrase.c_str(); + + if (!opt_ssl_passphrase) + return NULL; + + /* + Warn, if file-based passphrase is readable by LOAD_FILE() + or LOAD DATA INFILE. + */ + if (!strncmp(opt_ssl_passphrase, STRING_WITH_LEN("file:"))) + { + char *file= opt_ssl_passphrase + 5; + if (is_secure_file_path(file)) + { + sql_print_warning("ssl passphrase file '%s' is not secure, can be read " + "by LOAD_FILE() or LOAD DATA. Define secure-file-dir, and place " + "passphrase file outside of this directory, to avoid this warning", + file); + } + } + saved_ssl_passphrase= opt_ssl_passphrase; + /* + Modify opt_ssl_passphrase to wipe everything after prefix ending + in colon char.It will just leave just one of "file:", "env:", or "pass:" + at the end. + */ + char *p= strchr(opt_ssl_passphrase, ':'); + if (p) + { + p++; + while (*p) + *p++= 0; + } + return saved_ssl_passphrase.c_str(); +} + static const char *tls_version_names[]= { "TLSv1.0", diff --git a/vio/viosslfactories.c b/vio/viosslfactories.c index 35243d96a0b..e001289b933 100644 --- a/vio/viosslfactories.c +++ b/vio/viosslfactories.c @@ -16,6 +16,9 @@ #include "vio_priv.h" #include +#include +#include +#include #ifdef HAVE_OPENSSL #include @@ -304,6 +307,124 @@ static long vio_tls_protocol_options(ulonglong tls_version) return (disabled_tls_protocols | disabled_ssl_protocols); } +/* Passphrase handlers */ + +/** + Read a passphrase from a file + @param buf Buffer to store the passphrase + @param size Size of the buffer + @param filename Name of the file to read the passphrase from + @retval Length of the passphrase +*/ +static int passwd_from_file(char* buf, int size, const char* filename) +{ + char *passwd= NULL; + FILE *fp= fopen(filename, "r"); + if (fp) + { + passwd= fgets(buf, size, fp); + fclose(fp); + } + else + { + fprintf(stderr, + "SSL passphrase error: failed to open file '%s': %s (errno %d)\n ", + filename, strerror(errno), errno); + } + return passwd?(int)strlen(passwd):0; +} + +/** + Read a passphrase from a given string + @param buf Buffer to store the passphrase + @param size Size of the buffer + @param pass clear text passphrase + @retval Length of the passphrase +*/ +static int passwd_from_string(char *buf, int size, const char *pass) +{ + int len= (int) (strmake(buf, pass, size) - buf); + return MY_MIN(len, size - 1); +} + +/** + Read a passphrase from an environment variable + @param buf Buffer to store the passphrase + @param size Size of the buffer + @param var Name of the environment variable + @retval Length of the passphrase +*/ +static int passwd_from_env(char *buf, int size, const char *var) +{ + char *val= getenv(var); + if (!val) + { + fprintf(stderr, + "SSL passphrase error: environment variable '%s' not found\n", var); + return 0; + } + return passwd_from_string(buf, size, val); +} + +/** + Passphrase callback for SSL_CTX_set_default_passwd_cb + + Depending on the prefix in the command string, it will call the appropriate + handler to get the passphrase + + Currently supported prefixes are: + - pass: - passphrase is given as a string + - file: - passphrase is read from a file + - env: - passphrase is read from an environment variable + + The meaning is the same as passphrase parameter for openssl command line + utility (see https://docs.openssl.org/3.4/man1/openssl-passphrase-options/#synopsis) + Some prefixes are not supported, e.g stdin: or fd: + + @param buf Buffer to store the passphrase + @retval length of the passphrase +*/ +static int ssl_external_passwd_cb(char *buf, int size, int rw, void *userdata) +{ + /* Prefixes and their handlers */ + struct Handler + { + const char *prefix; + size_t prefix_len; + int (*handler)(char *, int, const char *); + }; + + static const struct Handler handlers[]= + { + {STRING_WITH_LEN("pass:"), passwd_from_string}, + {STRING_WITH_LEN("file:"), passwd_from_file}, + {STRING_WITH_LEN("env:"), passwd_from_env} + }; + const char *strdata= (const char *) userdata; + (void) rw; /* unused */ + DBUG_ASSERT(buf); + DBUG_ASSERT(size > 0); + DBUG_ASSERT(userdata); + + for (size_t i= 0; i < array_elements(handlers); i++) + { + const struct Handler *h= &handlers[i]; + if (strncmp(strdata, h->prefix, h->prefix_len) == 0) + { + int len= h->handler(buf, size, strdata + h->prefix_len); + // Trim trailing newlines + while (len > 0 && (buf[len - 1] == '\n' || buf[len - 1] == '\r')) + { + buf[--len]= 0; + } + return len; + } + } + fprintf(stderr, "SSL passphrase error: ssl-passphrase value must be " + "prefixed with 'file:', 'env:', or 'pass:'\n"); + return 0; // No matching prefix found +} + /* If some optional parameters indicate empty strings, then for compatibility with SSL libraries, replace them with NULL, @@ -318,7 +439,7 @@ static struct st_VioSSLFd * new_VioSSLFd(const char *key_file, const char *cert_file, const char *ca_file, const char *ca_path, const char *cipher, my_bool is_client_method, enum enum_ssl_init_error *error, const char *crl_file, - const char *crl_path, ulonglong tls_version) + const char *crl_path, ulonglong tls_version, const char *passphrase) { struct st_VioSSLFd *ssl_fd; long ssl_ctx_options; @@ -352,6 +473,12 @@ new_VioSSLFd(const char *key_file, const char *cert_file, const char *ca_file, goto err1; } + if (passphrase) + { + SSL_CTX_set_default_passwd_cb_userdata(ssl_fd->ssl_context, (void *)passphrase); + SSL_CTX_set_default_passwd_cb(ssl_fd->ssl_context, ssl_external_passwd_cb); + } + ssl_ctx_options= vio_tls_protocol_options(tls_version); if (ssl_ctx_options == -1) { @@ -494,7 +621,7 @@ new_VioSSLConnectorFd(const char *key_file, const char *cert_file, /* Init the VioSSLFd as a "connector" ie. the client side */ if (!(ssl_fd= new_VioSSLFd(key_file, cert_file, ca_file, ca_path, cipher, - TRUE, error, crl_file, crl_path, 0))) + TRUE, error, crl_file, crl_path, 0, NULL))) { return 0; } @@ -510,14 +637,14 @@ new_VioSSLAcceptorFd(const char *key_file, const char *cert_file, const char *ca_file, const char *ca_path, const char *cipher, enum enum_ssl_init_error* error, const char *crl_file, const char *crl_path, - ulonglong tls_version) + ulonglong tls_version, const char *passphrase) { struct st_VioSSLFd *ssl_fd; int verify= SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE; /* Init the the VioSSLFd as a "acceptor" ie. the server side */ if (!(ssl_fd= new_VioSSLFd(key_file, cert_file, ca_file, ca_path, cipher, - FALSE, error, crl_file, crl_path, tls_version))) + FALSE, error, crl_file, crl_path, tls_version, passphrase))) { return 0; }