Skip to content
Draft
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: 2 additions & 1 deletion include/violite.h
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,8 @@ 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, const char *passphrase);
ulonglong tls_version, const char *passphrase,
const char *alt_key_file, const char *alt_cert_file);
void free_vio_ssl_acceptor_fd(struct st_VioSSLFd *fd);
#endif /* HAVE_OPENSSL */

Expand Down
49 changes: 49 additions & 0 deletions mysql-test/main/ssl_alt_cert.result
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#
# Generate ECDSA certificate signed by the test CA
#
#
# Restart server with both RSA (primary) and ECDSA (alternate) certs
#
# Kill the server
# restart: --ssl-alt-cert=MYSQLTEST_VARDIR/tmp/server-ecdsa-cert.pem --ssl-alt-key=MYSQLTEST_VARDIR/tmp/server-ecdsa-key.pem --tls-version=TLSv1.2
#
# Verify both ssl_alt_cert and ssl_alt_key are set
#
SELECT VARIABLE_VALUE <> '' AS alt_cert_set FROM INFORMATION_SCHEMA.GLOBAL_VARIABLES WHERE VARIABLE_NAME='ssl_alt_cert';
alt_cert_set
1
SELECT VARIABLE_VALUE <> '' AS alt_key_set FROM INFORMATION_SCHEMA.GLOBAL_VARIABLES WHERE VARIABLE_NAME='ssl_alt_key';
alt_key_set
1
#
# Test 1: Connect with RSA cipher - should get RSA cert
#
connect rsa_con,localhost,root,,,,,SSL-CIPHER=ECDHE-RSA-AES128-GCM-SHA256;
SHOW STATUS LIKE 'Ssl_cipher';
Variable_name Value
Ssl_cipher ECDHE-RSA-AES128-GCM-SHA256
disconnect rsa_con;
#
# Test 2: Connect with ECDSA cipher - should get ECDSA cert
#
connection default;
connect ecdsa_con,localhost,root,,,,,SSL-CIPHER=ECDHE-ECDSA-AES128-GCM-SHA256;
SHOW STATUS LIKE 'Ssl_cipher';
Variable_name Value
Ssl_cipher ECDHE-ECDSA-AES128-GCM-SHA256
disconnect ecdsa_con;
#
# Test 3: Connect with default ciphers - should succeed
#
connection default;
connect default_con,localhost,root,,,,,SSL;
SELECT (VARIABLE_VALUE <> '') AS have_ssl FROM INFORMATION_SCHEMA.SESSION_STATUS WHERE VARIABLE_NAME='Ssl_cipher';
have_ssl
1
disconnect default_con;
connection default;
#
# Cleanup
#
# Kill the server
# restart
75 changes: 75 additions & 0 deletions mysql-test/main/ssl_alt_cert.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
#
# Test dual RSA + ECDSA certificate support via --ssl-alt-cert / --ssl-alt-key
#
--source include/have_ssl_communication.inc

# Skip on WolfSSL which may not support dual certs
if (`select @@version_ssl_library like 'wolfSSL%'`) {
skip WolfSSL;
}

# Skip on older TLS that doesn't have ECDHE-ECDSA ciphers
if (`select @@version_ssl_library like 'OpenSSL 1.0%'`) {
skip OpenSSL too old;
}

--echo #
--echo # Generate ECDSA certificate signed by the test CA
--echo #

let $ecdsa_cert=$MYSQLTEST_VARDIR/tmp/server-ecdsa-cert.pem;
let $ecdsa_key=$MYSQLTEST_VARDIR/tmp/server-ecdsa-key.pem;
let $ecdsa_csr=$MYSQLTEST_VARDIR/tmp/server-ecdsa.csr;

--exec openssl ecparam -genkey -name prime256v1 -out $ecdsa_key 2>/dev/null
--exec openssl req -new -key $ecdsa_key -out $ecdsa_csr -subj "/CN=localhost" -batch 2>/dev/null
--exec openssl x509 -req -in $ecdsa_csr -CA $MYSQL_TEST_DIR/std_data/cacert.pem -CAkey $MYSQL_TEST_DIR/std_data/cakey.pem -CAcreateserial -out $ecdsa_cert -days 3650 2>/dev/null
--remove_file $ecdsa_csr

--echo #
--echo # Restart server with both RSA (primary) and ECDSA (alternate) certs
--echo #

--let $restart_parameters=--ssl-alt-cert=$ecdsa_cert --ssl-alt-key=$ecdsa_key --tls-version=TLSv1.2
--source include/kill_mysqld.inc
--source include/start_mysqld.inc

--echo #
--echo # Verify both ssl_alt_cert and ssl_alt_key are set
--echo #
SELECT VARIABLE_VALUE <> '' AS alt_cert_set FROM INFORMATION_SCHEMA.GLOBAL_VARIABLES WHERE VARIABLE_NAME='ssl_alt_cert';
SELECT VARIABLE_VALUE <> '' AS alt_key_set FROM INFORMATION_SCHEMA.GLOBAL_VARIABLES WHERE VARIABLE_NAME='ssl_alt_key';

--echo #
--echo # Test 1: Connect with RSA cipher - should get RSA cert
--echo #
connect (rsa_con,localhost,root,,,,,SSL-CIPHER=ECDHE-RSA-AES128-GCM-SHA256);
SHOW STATUS LIKE 'Ssl_cipher';
disconnect rsa_con;

--echo #
--echo # Test 2: Connect with ECDSA cipher - should get ECDSA cert
--echo #
connection default;
connect (ecdsa_con,localhost,root,,,,,SSL-CIPHER=ECDHE-ECDSA-AES128-GCM-SHA256);
SHOW STATUS LIKE 'Ssl_cipher';
disconnect ecdsa_con;

--echo #
--echo # Test 3: Connect with default ciphers - should succeed
--echo #
connection default;
connect (default_con,localhost,root,,,,,SSL);
SELECT (VARIABLE_VALUE <> '') AS have_ssl FROM INFORMATION_SCHEMA.SESSION_STATUS WHERE VARIABLE_NAME='Ssl_cipher';
disconnect default_con;
connection default;

--echo #
--echo # Cleanup
--echo #
--remove_file $ecdsa_cert
--remove_file $ecdsa_key

--let $restart_parameters=
--source include/kill_mysqld.inc
--source include/start_mysqld.inc
20 changes: 20 additions & 0 deletions mysql-test/main/ssl_alt_cert_errors.result
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#
# Test 1: --ssl-alt-cert without --ssl-alt-key
#
FOUND 1 /SSL error: Both alternate certificate and key must be specified/ in ssl_alt_cert_errors.err
#
# Test 2: --ssl-alt-key without --ssl-alt-cert
#
FOUND 1 /SSL error: Both alternate certificate and key must be specified/ in ssl_alt_cert_errors.err
#
# Test 3: --ssl-alt-cert with invalid file
#
FOUND 1 /\[ERROR\] SSL error: Unable to get certificate/ in ssl_alt_cert_errors.err
#
# Test 4: --ssl-alt-key with invalid file
#
FOUND 1 /\[ERROR\] SSL error: Unable to get private key/ in ssl_alt_cert_errors.err
#
# Cleanup
#
# restart
71 changes: 71 additions & 0 deletions mysql-test/main/ssl_alt_cert_errors.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
#
# Test error handling for --ssl-alt-cert / --ssl-alt-key options
# Server must refuse to start when:
# - only one of the two options is specified
# - an alternate cert or key file is invalid
#
--source include/not_embedded.inc
--source include/have_ssl_communication.inc

# Skip on WolfSSL which may not support dual certs
if (`select @@version_ssl_library like 'wolfSSL%'`) {
skip WolfSSL;
}

--source include/shutdown_mysqld.inc

--let errorlog=$MYSQL_TMP_DIR/ssl_alt_cert_errors.err
--let SEARCH_FILE=$errorlog

# Generate a valid ECDSA cert/key
--let $ecdsa_cert=$MYSQLTEST_VARDIR/tmp/alt-ecdsa-cert.pem
--let $ecdsa_key=$MYSQLTEST_VARDIR/tmp/alt-ecdsa-key.pem
--let $ecdsa_csr=$MYSQLTEST_VARDIR/tmp/alt-ecdsa.csr
--exec openssl ecparam -genkey -name prime256v1 -out $ecdsa_key 2>/dev/null
--exec openssl req -new -key $ecdsa_key -out $ecdsa_csr -subj "/CN=localhost" -batch 2>/dev/null
--exec openssl x509 -req -in $ecdsa_csr -CA $MYSQL_TEST_DIR/std_data/cacert.pem -CAkey $MYSQL_TEST_DIR/std_data/cakey.pem -CAcreateserial -out $ecdsa_cert -days 3650 2>/dev/null
--remove_file $ecdsa_csr

--echo #
--echo # Test 1: --ssl-alt-cert without --ssl-alt-key
--echo #
--error 1
--exec $MYSQLD --defaults-group-suffix=.1 --defaults-file=$MYSQLTEST_VARDIR/my.cnf --user=root --ssl-alt-cert=$ecdsa_cert --log-error=$errorlog
--let SEARCH_PATTERN=SSL error: Both alternate certificate and key must be specified
--source include/search_pattern_in_file.inc
--remove_file $SEARCH_FILE

--echo #
--echo # Test 2: --ssl-alt-key without --ssl-alt-cert
--echo #
--error 1
--exec $MYSQLD --defaults-group-suffix=.1 --defaults-file=$MYSQLTEST_VARDIR/my.cnf --user=root --ssl-alt-key=$ecdsa_key --log-error=$errorlog
--let SEARCH_PATTERN=SSL error: Both alternate certificate and key must be specified
--source include/search_pattern_in_file.inc
--remove_file $SEARCH_FILE

--echo #
--echo # Test 3: --ssl-alt-cert with invalid file
--echo #
--error 1
--exec $MYSQLD --defaults-group-suffix=.1 --defaults-file=$MYSQLTEST_VARDIR/my.cnf --user=root --ssl-alt-cert=/nonexistent/bad.pem --ssl-alt-key=$ecdsa_key --log-error=$errorlog
--let SEARCH_PATTERN=\[ERROR\] SSL error: Unable to get certificate
--source include/search_pattern_in_file.inc
--remove_file $SEARCH_FILE

--echo #
--echo # Test 4: --ssl-alt-key with invalid file
--echo #
--error 1
--exec $MYSQLD --defaults-group-suffix=.1 --defaults-file=$MYSQLTEST_VARDIR/my.cnf --user=root --ssl-alt-cert=$ecdsa_cert --ssl-alt-key=/nonexistent/bad.pem --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

--echo #
--echo # Cleanup
--echo #
--remove_file $ecdsa_cert
--remove_file $ecdsa_key

--source include/start_mysqld.inc
4 changes: 4 additions & 0 deletions mysql-test/main/variables.result
Original file line number Diff line number Diff line change
Expand Up @@ -955,6 +955,8 @@ select @@ssl_ca, @@ssl_capath, @@ssl_cert, @@ssl_cipher, @@ssl_key;
# # # # #
show variables like 'ssl%';
Variable_name Value
ssl_alt_cert #
ssl_alt_key #
ssl_ca #
ssl_capath #
ssl_cert #
Expand All @@ -965,6 +967,8 @@ ssl_key #
ssl_passphrase #
select * from information_schema.session_variables where variable_name like 'ssl%' order by 1;
VARIABLE_NAME VARIABLE_VALUE
SSL_ALT_CERT #
SSL_ALT_KEY #
SSL_CA #
SSL_CAPATH #
SSL_CERT #
Expand Down
7 changes: 5 additions & 2 deletions sql/mysqld.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1524,6 +1524,7 @@ my_bool opt_use_ssl = 1;
char *opt_ssl_ca= NULL, *opt_ssl_capath= NULL, *opt_ssl_cert= NULL,
*opt_ssl_cipher= NULL, *opt_ssl_key= NULL, *opt_ssl_crl= NULL,
*opt_ssl_crlpath= NULL, *opt_tls_version= NULL;
char *opt_ssl_alt_cert= NULL, *opt_ssl_alt_key= NULL;
ulonglong tls_version= 0;

static scheduler_functions thread_scheduler_struct, extra_thread_scheduler_struct;
Expand Down Expand Up @@ -4823,7 +4824,8 @@ static void init_ssl()
opt_ssl_ca, opt_ssl_capath,
opt_ssl_cipher, &error,
opt_ssl_crl, opt_ssl_crlpath,
tls_version, get_ssl_passphrase());
tls_version, get_ssl_passphrase(),
opt_ssl_alt_key, opt_ssl_alt_cert);
DBUG_PRINT("info",("ssl_acceptor_fd: %p", ssl_acceptor_fd));
if (!ssl_acceptor_fd)
{
Expand Down Expand Up @@ -4868,7 +4870,8 @@ 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, get_ssl_passphrase());
opt_ssl_crlpath, tls_version, get_ssl_passphrase(),
opt_ssl_alt_key, opt_ssl_alt_cert);

if (!new_fd)
{
Expand Down
3 changes: 2 additions & 1 deletion sql/mysqld.h
Original file line number Diff line number Diff line change
Expand Up @@ -738,7 +738,8 @@ extern mysql_cond_t COND_slave_deadlock_handler;

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;
*opt_ssl_key, *opt_ssl_crl, *opt_ssl_crlpath,
*opt_ssl_alt_cert, *opt_ssl_alt_key;

extern const char *get_ssl_passphrase();

Expand Down
14 changes: 14 additions & 0 deletions sql/sys_vars.cc
Original file line number Diff line number Diff line change
Expand Up @@ -4287,6 +4287,20 @@ static Sys_var_charptr_fscs Sys_ssl_key(
READ_ONLY GLOBAL_VAR(opt_ssl_key), SSL_OPT(OPT_SSL_KEY),
DEFAULT(0));

static Sys_var_charptr_fscs Sys_ssl_alt_cert(
"ssl_alt_cert",
"Alternate X509 cert in PEM format for dual-certificate support"
" (implies --ssl)",
READ_ONLY GLOBAL_VAR(opt_ssl_alt_cert), SSL_OPT(OPT_SSL_CERT),
DEFAULT(0));

static Sys_var_charptr_fscs Sys_ssl_alt_key(
"ssl_alt_key",
"Alternate X509 key in PEM format for dual-certificate support"
" (implies --ssl)",
READ_ONLY GLOBAL_VAR(opt_ssl_alt_key), SSL_OPT(OPT_SSL_KEY),
DEFAULT(0));

static Sys_var_charptr_fscs Sys_ssl_crl(
"ssl_crl",
"CRL file in PEM format (check OpenSSL docs, implies --ssl)",
Expand Down
52 changes: 48 additions & 4 deletions vio/viosslfactories.c
Original file line number Diff line number Diff line change
Expand Up @@ -439,7 +439,8 @@ 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 *passphrase)
const char *crl_path, ulonglong tls_version, const char *passphrase,
const char *alt_key_file, const char *alt_cert_file)
{
struct st_VioSSLFd *ssl_fd;
long ssl_ctx_options;
Expand Down Expand Up @@ -562,6 +563,46 @@ new_VioSSLFd(const char *key_file, const char *cert_file, const char *ca_file,
goto err2;
}

/* Load alternate certificate (e.g. ECDSA alongside RSA) into the same context */
fix_value(alt_cert_file);
fix_value(alt_key_file);
if (alt_cert_file || alt_key_file)
{
if (!alt_cert_file || !alt_key_file)
{
*error= !alt_cert_file ? SSL_INITERR_CERT : SSL_INITERR_KEY;
fprintf(stderr, "SSL error: Both alternate certificate and key must be specified\n");
fflush(stderr);
goto err2;
}
if (SSL_CTX_use_certificate_file(ssl_fd->ssl_context, alt_cert_file,
SSL_FILETYPE_PEM) <= 0)
{
*error= SSL_INITERR_CERT;
fprintf(stderr, "SSL error: %s from '%s'\n",
sslGetErrString(*error), alt_cert_file);
fflush(stderr);
goto err2;
}
if (SSL_CTX_use_PrivateKey_file(ssl_fd->ssl_context, alt_key_file,
SSL_FILETYPE_PEM) <= 0)
{
*error= SSL_INITERR_KEY;
fprintf(stderr, "SSL error: %s from '%s'\n",
sslGetErrString(*error), alt_key_file);
fflush(stderr);
goto err2;
}
if (!SSL_CTX_check_private_key(ssl_fd->ssl_context))
{
*error= SSL_INITERR_NOMATCH;
fprintf(stderr, "SSL error: %s (alternate cert)\n",
sslGetErrString(*error));
fflush(stderr);
goto err2;
}
}

#ifndef HAVE_WOLFSSL
/* DH stuff */
if (!is_client_method)
Expand Down Expand Up @@ -621,7 +662,8 @@ 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, NULL)))
TRUE, error, crl_file, crl_path, 0, NULL,
NULL, NULL)))
{
return 0;
}
Expand All @@ -637,14 +679,16 @@ 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, const char *passphrase)
ulonglong tls_version, const char *passphrase,
const char *alt_key_file, const char *alt_cert_file)
{
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, passphrase)))
FALSE, error, crl_file, crl_path, tls_version, passphrase,
alt_key_file, alt_cert_file)))
{
return 0;
}
Expand Down