aboutsummaryrefslogtreecommitdiffstats
path: root/contrib/libs/libmysql_r/sql-common/client_authentication.cc
diff options
context:
space:
mode:
authorhcpp <hcpp@ydb.tech>2023-11-08 12:09:41 +0300
committerhcpp <hcpp@ydb.tech>2023-11-08 12:56:14 +0300
commita361f5b98b98b44ea510d274f6769164640dd5e1 (patch)
treec47c80962c6e2e7b06798238752fd3da0191a3f6 /contrib/libs/libmysql_r/sql-common/client_authentication.cc
parent9478806fde1f4d40bd5a45e7cbe77237dab613e9 (diff)
downloadydb-a361f5b98b98b44ea510d274f6769164640dd5e1.tar.gz
metrics have been added
Diffstat (limited to 'contrib/libs/libmysql_r/sql-common/client_authentication.cc')
-rw-r--r--contrib/libs/libmysql_r/sql-common/client_authentication.cc878
1 files changed, 878 insertions, 0 deletions
diff --git a/contrib/libs/libmysql_r/sql-common/client_authentication.cc b/contrib/libs/libmysql_r/sql-common/client_authentication.cc
new file mode 100644
index 0000000000..831349b85b
--- /dev/null
+++ b/contrib/libs/libmysql_r/sql-common/client_authentication.cc
@@ -0,0 +1,878 @@
+/* Copyright (c) 2011, 2019, Oracle and/or its affiliates. All rights reserved.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License, version 2.0,
+ as published by the Free Software Foundation.
+
+ This program is also distributed with certain software (including
+ but not limited to OpenSSL) that is licensed under separate terms,
+ as designated in a particular file or component or in included license
+ documentation. The authors of MySQL hereby grant you an additional
+ permission to link the program and your derivative works with the
+ separately licensed software that they have included with MySQL.
+
+ Without limiting anything contained in the foregoing, this file,
+ which is part of C Driver for MySQL (Connector/C), is also subject to the
+ Universal FOSS Exception, version 1.0, a copy of which can be found at
+ http://oss.oracle.com/licenses/universal-foss-exception.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License, version 2.0, for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */
+
+// First include (the generated) my_config.h, to get correct platform defines.
+#include "my_config.h"
+
+#include <stdarg.h>
+#include <string.h>
+
+#include "my_dbug.h"
+#include "my_inttypes.h"
+
+#if defined(HAVE_OPENSSL)
+#include <openssl/err.h>
+#include <openssl/pem.h>
+#include <openssl/rsa.h>
+#include <wolfssl_fix_namespace_pollution_pre.h>
+#include "crypt_genhash_impl.h"
+#include "errmsg.h"
+#include "m_ctype.h"
+#include "mysql/client_authentication.h"
+#include "mysql/psi/mysql_mutex.h"
+#include "mysys_err.h"
+#include "sql_common.h"
+#include "sql_string.h"
+#if defined(_WIN32) && !defined(_OPENSSL_Applink) && \
+ defined(HAVE_OPENSSL_APPLINK_C)
+#include <openssl/applink.c>
+#endif
+#include <wolfssl_fix_namespace_pollution.h>
+#include "client_async_authentication.h"
+#include "mysql/plugin.h"
+#include "sha2.h"
+#include "violite.h"
+
+#define MAX_CIPHER_LENGTH 1024
+
+#define SHA2_SCRAMBLE_LENGTH SHA256_DIGEST_LENGTH
+
+mysql_mutex_t g_public_key_mutex;
+
+int sha256_password_init(char *, size_t, int, va_list) {
+ mysql_mutex_init(0, &g_public_key_mutex, MY_MUTEX_INIT_SLOW);
+ return 0;
+}
+
+int sha256_password_deinit(void) {
+ mysql_reset_server_public_key();
+ mysql_mutex_destroy(&g_public_key_mutex);
+ return 0;
+}
+
+/**
+ Reads and parse RSA public key data from a file.
+
+ @param mysql connection handle with file path data
+
+ @return Pointer to the RSA public key storage buffer
+*/
+
+static RSA *g_public_key = NULL;
+
+static RSA *rsa_init(MYSQL *mysql) {
+ RSA *key = NULL;
+
+ mysql_mutex_lock(&g_public_key_mutex);
+ key = g_public_key;
+ mysql_mutex_unlock(&g_public_key_mutex);
+
+ if (key != NULL) return key;
+
+ FILE *pub_key_file = NULL;
+
+ if (mysql->options.extension != NULL &&
+ mysql->options.extension->server_public_key_path != NULL &&
+ mysql->options.extension->server_public_key_path[0] != '\0') {
+ pub_key_file =
+ fopen(mysql->options.extension->server_public_key_path, "rb");
+ }
+ /* No public key is used; return 0 without errors to indicate this. */
+ else
+ return 0;
+
+ if (pub_key_file == NULL) {
+ /*
+ If a key path was submitted but no key located then we print an error
+ message. Else we just report that there is no public key.
+ */
+ my_message_local(WARNING_LEVEL, EE_FAILED_TO_LOCATE_SERVER_PUBLIC_KEY,
+ mysql->options.extension->server_public_key_path);
+
+ return 0;
+ }
+
+ mysql_mutex_lock(&g_public_key_mutex);
+ key = g_public_key = PEM_read_RSA_PUBKEY(pub_key_file, 0, 0, 0);
+ mysql_mutex_unlock(&g_public_key_mutex);
+ fclose(pub_key_file);
+ if (g_public_key == NULL) {
+#if !defined(HAVE_WOLFSSL)
+ ERR_clear_error();
+#endif
+ my_message_local(WARNING_LEVEL, EE_PUBLIC_KEY_NOT_IN_PEM_FORMAT,
+ mysql->options.extension->server_public_key_path);
+ return 0;
+ }
+
+ return key;
+}
+
+/**
+ Authenticate the client using the RSA or TLS and a SHA256 salted password.
+
+ @param vio Provides plugin access to communication channel
+ @param mysql Client connection handler
+
+ @return Error status
+ @retval CR_ERROR An error occurred.
+ @retval CR_OK Authentication succeeded.
+*/
+
+int sha256_password_auth_client(MYSQL_PLUGIN_VIO *vio, MYSQL *mysql) {
+ bool uses_password = mysql->passwd[0] != 0;
+#if !defined(HAVE_WOLFSSL)
+ unsigned char encrypted_password[MAX_CIPHER_LENGTH];
+ static char request_public_key = '\1';
+ RSA *public_key = NULL;
+ bool got_public_key_from_server = false;
+#endif
+ bool connection_is_secure = false;
+ unsigned char scramble_pkt[20];
+ unsigned char *pkt;
+
+ DBUG_ENTER("sha256_password_auth_client");
+
+ /*
+ Get the scramble from the server because we need it when sending encrypted
+ password.
+ */
+ if (vio->read_packet(vio, &pkt) != SCRAMBLE_LENGTH + 1) {
+ DBUG_PRINT("info", ("Scramble is not of correct length."));
+ DBUG_RETURN(CR_ERROR);
+ }
+ if (pkt[SCRAMBLE_LENGTH] != '\0') {
+ DBUG_PRINT("info", ("Missing protocol token in scramble data."));
+ DBUG_RETURN(CR_ERROR);
+ }
+ /*
+ Copy the scramble to the stack or it will be lost on the next use of the
+ net buffer.
+ */
+ memcpy(scramble_pkt, pkt, SCRAMBLE_LENGTH);
+
+ if (mysql_get_ssl_cipher(mysql) != NULL) connection_is_secure = true;
+
+ /* If connection isn't secure attempt to get the RSA public key file */
+ if (!connection_is_secure) {
+#if !defined(HAVE_WOLFSSL)
+ public_key = rsa_init(mysql);
+#endif
+ }
+
+ if (!uses_password) {
+ /* We're not using a password */
+ static const unsigned char zero_byte = '\0';
+ if (vio->write_packet(vio, &zero_byte, 1)) DBUG_RETURN(CR_ERROR);
+ } else {
+ /* Password is a 0-terminated byte array ('\0' character included) */
+ unsigned int passwd_len =
+ static_cast<unsigned int>(strlen(mysql->passwd) + 1);
+ if (!connection_is_secure) {
+#if !defined(HAVE_WOLFSSL)
+ /*
+ If no public key; request one from the server.
+ */
+ if (public_key == NULL) {
+ if (vio->write_packet(vio, (const unsigned char *)&request_public_key,
+ 1))
+ DBUG_RETURN(CR_ERROR);
+
+ int pkt_len = 0;
+ unsigned char *pkt;
+ if ((pkt_len = vio->read_packet(vio, &pkt)) == -1)
+ DBUG_RETURN(CR_ERROR);
+ BIO *bio = BIO_new_mem_buf(pkt, pkt_len);
+ public_key = PEM_read_bio_RSA_PUBKEY(bio, NULL, NULL, NULL);
+ BIO_free(bio);
+ if (public_key == 0) {
+ ERR_clear_error();
+ DBUG_RETURN(CR_ERROR);
+ }
+ got_public_key_from_server = true;
+ }
+
+ /*
+ An arbitrary limitation based on the assumption that passwords
+ larger than e.g. 15 symbols don't contribute to security.
+ Note also that it's furter restricted to RSA_size() - 41 down
+ below, so this leaves 471 bytes of possible RSA key sizes which
+ should be reasonably future-proof.
+ We avoid heap allocation for speed reasons.
+ */
+ char passwd_scramble[512];
+
+ if (passwd_len > sizeof(passwd_scramble)) {
+ /* password too long for the buffer */
+ if (got_public_key_from_server) RSA_free(public_key);
+ DBUG_RETURN(CR_ERROR);
+ }
+ memmove(passwd_scramble, mysql->passwd, passwd_len);
+
+ /* Obfuscate the plain text password with the session scramble */
+ xor_string(passwd_scramble, passwd_len - 1, (char *)scramble_pkt,
+ SCRAMBLE_LENGTH);
+ /* Encrypt the password and send it to the server */
+ int cipher_length = RSA_size(public_key);
+ /*
+ When using RSA_PKCS1_OAEP_PADDING the password length must be less
+ than RSA_size(rsa) - 41.
+ */
+ if (passwd_len + 41 >= (unsigned)cipher_length) {
+ /* password message is to long */
+ if (got_public_key_from_server) RSA_free(public_key);
+ DBUG_RETURN(CR_ERROR);
+ }
+ RSA_public_encrypt(passwd_len, (unsigned char *)passwd_scramble,
+ encrypted_password, public_key,
+ RSA_PKCS1_OAEP_PADDING);
+ if (got_public_key_from_server) RSA_free(public_key);
+
+ if (vio->write_packet(vio, (uchar *)encrypted_password, cipher_length))
+ DBUG_RETURN(CR_ERROR);
+#else
+ set_mysql_extended_error(mysql, CR_AUTH_PLUGIN_ERR, unknown_sqlstate,
+ ER_CLIENT(CR_AUTH_PLUGIN_ERR), "sha256_password",
+ "Authentication requires SSL encryption");
+ DBUG_RETURN(CR_ERROR); // If no openssl support
+#endif
+ } else {
+ /* The vio is encrypted already; just send the plain text passwd */
+ if (vio->write_packet(vio, (uchar *)mysql->passwd, passwd_len))
+ DBUG_RETURN(CR_ERROR);
+ }
+ }
+
+ DBUG_RETURN(CR_OK);
+}
+
+/**
+ Non blocking version of sha256_password_auth_client
+*/
+net_async_status sha256_password_auth_client_nonblocking(MYSQL_PLUGIN_VIO *vio,
+ MYSQL *mysql,
+ int *result) {
+ DBUG_ENTER(__func__);
+ net_async_status status = NET_ASYNC_NOT_READY;
+#if !defined(HAVE_WOLFSSL)
+ unsigned char encrypted_password[MAX_CIPHER_LENGTH];
+ static char request_public_key = '\1';
+ static RSA *public_key = NULL;
+ bool got_public_key_from_server = false;
+#endif
+ int io_result;
+ bool connection_is_secure = (mysql_get_ssl_cipher(mysql) != NULL);
+ unsigned char scramble_pkt[20];
+ unsigned char *pkt;
+ unsigned int passwd_len =
+ static_cast<unsigned int>(strlen(mysql->passwd) + 1);
+
+ mysql_async_auth *ctx = ASYNC_DATA(mysql)->connect_context->auth_context;
+ switch (static_cast<client_auth_sha256_password_plugin_status>(
+ ctx->client_auth_plugin_state)) {
+ case client_auth_sha256_password_plugin_status::SHA256_READING_PASSWORD:
+ status = vio->read_packet_nonblocking(vio, &pkt, &io_result);
+ if (status == NET_ASYNC_NOT_READY) {
+ DBUG_RETURN(NET_ASYNC_NOT_READY);
+ }
+ if (io_result != SCRAMBLE_LENGTH + 1) {
+ DBUG_PRINT("info", ("Scramble is not of correct length."));
+ *result = CR_ERROR;
+ DBUG_RETURN(NET_ASYNC_COMPLETE);
+ }
+ if (pkt[SCRAMBLE_LENGTH] != '\0') {
+ DBUG_PRINT("info", ("Missing protocol token in scramble data."));
+ *result = CR_ERROR;
+ DBUG_RETURN(NET_ASYNC_COMPLETE);
+ }
+ memcpy(scramble_pkt, pkt, SCRAMBLE_LENGTH);
+ if (connection_is_secure)
+ ctx->client_auth_plugin_state =
+ client_auth_sha256_password_plugin_status::
+ SHA256_SEND_PLAIN_PASSWORD;
+ else
+ ctx->client_auth_plugin_state =
+ client_auth_sha256_password_plugin_status::
+ SHA256_REQUEST_PUBLIC_KEY;
+ DBUG_RETURN(NET_ASYNC_NOT_READY);
+#if !defined(HAVE_WOLFSSL)
+ case client_auth_sha256_password_plugin_status::SHA256_REQUEST_PUBLIC_KEY: {
+ public_key = rsa_init(mysql);
+ /* If no public key; request one from the server. */
+ if (public_key == NULL) {
+ status = vio->write_packet_nonblocking(
+ vio, (const unsigned char *)&request_public_key, 1, &io_result);
+ if (status == NET_ASYNC_NOT_READY) {
+ DBUG_RETURN(NET_ASYNC_NOT_READY);
+ }
+ if (io_result) {
+ *result = CR_ERROR;
+ DBUG_RETURN(NET_ASYNC_COMPLETE);
+ }
+ }
+ set_mysql_extended_error(mysql, CR_AUTH_PLUGIN_ERR, unknown_sqlstate,
+ ER_CLIENT(CR_AUTH_PLUGIN_ERR), "sha256_password",
+ "Authentication requires SSL encryption");
+ *result = CR_ERROR;
+ DBUG_RETURN(NET_ASYNC_COMPLETE);
+ }
+ ctx->client_auth_plugin_state =
+ client_auth_sha256_password_plugin_status::SHA256_READ_PUBLIC_KEY;
+ /* FALLTHROUGH */
+ case client_auth_sha256_password_plugin_status::SHA256_READ_PUBLIC_KEY:
+ if (public_key == NULL) {
+ status = vio->read_packet_nonblocking(vio, &pkt, &io_result);
+ if (status == NET_ASYNC_NOT_READY) {
+ DBUG_RETURN(NET_ASYNC_NOT_READY);
+ }
+ if (io_result <= 0) {
+ *result = CR_ERROR;
+ DBUG_RETURN(NET_ASYNC_COMPLETE);
+ }
+ BIO *bio = BIO_new_mem_buf(pkt, io_result);
+ public_key = PEM_read_bio_RSA_PUBKEY(bio, NULL, NULL, NULL);
+ BIO_free(bio);
+ if (public_key == 0) {
+ ERR_clear_error();
+ *result = CR_ERROR;
+ DBUG_RETURN(NET_ASYNC_COMPLETE);
+ }
+ got_public_key_from_server = true;
+ }
+ ctx->client_auth_plugin_state =
+ client_auth_sha256_password_plugin_status::
+ SHA256_SEND_ENCRYPTED_PASSWORD;
+ /* FALLTHROUGH */
+ case client_auth_sha256_password_plugin_status::
+ SHA256_SEND_ENCRYPTED_PASSWORD: {
+ char passwd_scramble[512];
+
+ if (passwd_len > sizeof(passwd_scramble)) {
+ /* password too long for the buffer */
+ if (got_public_key_from_server) RSA_free(public_key);
+ *result = CR_ERROR;
+ DBUG_RETURN(NET_ASYNC_COMPLETE);
+ }
+ memmove(passwd_scramble, mysql->passwd, passwd_len);
+
+ /* Obfuscate the plain text password with the session scramble */
+ xor_string(passwd_scramble, passwd_len - 1, (char *)scramble_pkt,
+ SCRAMBLE_LENGTH);
+ /* Encrypt the password and send it to the server */
+ int cipher_length = RSA_size(public_key);
+ /*
+ When using RSA_PKCS1_OAEP_PADDING the password length must be less
+ than RSA_size(rsa) - 41.
+ */
+ if (passwd_len + 41 >= (unsigned)cipher_length) {
+ /* password message is to long */
+ if (got_public_key_from_server) RSA_free(public_key);
+ *result = CR_ERROR;
+ DBUG_RETURN(NET_ASYNC_COMPLETE);
+ }
+ RSA_public_encrypt(passwd_len, (unsigned char *)passwd_scramble,
+ encrypted_password, public_key,
+ RSA_PKCS1_OAEP_PADDING);
+ if (got_public_key_from_server) RSA_free(public_key);
+ status = vio->write_packet_nonblocking(vio, (uchar *)encrypted_password,
+ cipher_length, &io_result);
+ if (status == NET_ASYNC_NOT_READY) {
+ DBUG_RETURN(NET_ASYNC_NOT_READY);
+ }
+ if (io_result < 0) {
+ *result = CR_ERROR;
+ DBUG_RETURN(NET_ASYNC_COMPLETE);
+ }
+ } break;
+#endif
+ case client_auth_sha256_password_plugin_status::
+ SHA256_SEND_PLAIN_PASSWORD: {
+ status = vio->write_packet_nonblocking(vio, (uchar *)mysql->passwd,
+ passwd_len, &io_result);
+ if (status == NET_ASYNC_NOT_READY) {
+ DBUG_RETURN(NET_ASYNC_NOT_READY);
+ }
+ if (io_result < 0) {
+ *result = CR_ERROR;
+ DBUG_RETURN(NET_ASYNC_COMPLETE);
+ }
+ } break;
+ default:
+ assert(0);
+ }
+ *result = CR_OK;
+ DBUG_RETURN(NET_ASYNC_COMPLETE);
+}
+/* caching_sha2_password */
+
+int caching_sha2_password_init(char *, size_t, int, va_list) { return 0; }
+
+int caching_sha2_password_deinit(void) { return 0; }
+
+static bool is_secure_transport(MYSQL *mysql) {
+ if (!mysql || !mysql->net.vio) return false;
+ switch (mysql->net.vio->type) {
+ case VIO_TYPE_SSL: {
+ if (mysql_get_ssl_cipher(mysql) == NULL) return false;
+ }
+ // Fall through
+ case VIO_TYPE_SHARED_MEMORY:
+ // Fall through
+ case VIO_TYPE_SOCKET:
+ return true;
+ default:
+ return false;
+ }
+ return false;
+}
+
+/**
+ Authenticate the client using the RSA or TLS and a SHA2 salted password.
+
+ @param vio Provides plugin access to communication channel
+ @param mysql Client connection handler
+
+ @return Error status
+ @retval CR_ERROR An error occurred.
+ @retval CR_OK Authentication succeeded.
+*/
+
+static char request_public_key = '\2';
+static char fast_auth_success = '\3';
+static char perform_full_authentication = '\4';
+
+int caching_sha2_password_auth_client(MYSQL_PLUGIN_VIO *vio, MYSQL *mysql) {
+ bool uses_password = mysql->passwd[0] != 0;
+ unsigned char encrypted_password[MAX_CIPHER_LENGTH];
+ // static char request_public_key= '\1';
+ RSA *public_key = NULL;
+ bool got_public_key_from_server = false;
+ bool connection_is_secure = false;
+ unsigned char scramble_pkt[20];
+ unsigned char *pkt;
+
+ DBUG_ENTER("caching_sha2_password_auth_client");
+
+ /*
+ Get the scramble from the server because we need it when sending encrypted
+ password.
+ */
+ if (vio->read_packet(vio, &pkt) != SCRAMBLE_LENGTH + 1) {
+ DBUG_PRINT("info", ("Scramble is not of correct length."));
+ DBUG_RETURN(CR_ERROR);
+ }
+ if (pkt[SCRAMBLE_LENGTH] != '\0') {
+ DBUG_PRINT("info", ("Missing protocol token in scramble data."));
+ DBUG_RETURN(CR_ERROR);
+ }
+
+ /*
+ Copy the scramble to the stack or it will be lost on the next use of the
+ net buffer.
+ */
+ memcpy(scramble_pkt, pkt, SCRAMBLE_LENGTH);
+
+ connection_is_secure = is_secure_transport(mysql);
+
+ if (!uses_password) {
+ /* We're not using a password */
+ static const unsigned char zero_byte = '\0';
+ if (vio->write_packet(vio, &zero_byte, 1)) DBUG_RETURN(CR_ERROR);
+ DBUG_RETURN(CR_OK);
+ } else {
+ /* Password is a 0-terminated byte array ('\0' character included) */
+ unsigned int passwd_len =
+ static_cast<unsigned int>(strlen(mysql->passwd) + 1);
+ int pkt_len = 0;
+ {
+ /* First try with SHA2 scramble */
+ unsigned char sha2_scramble[SHA2_SCRAMBLE_LENGTH];
+ if (generate_sha256_scramble(sha2_scramble, SHA2_SCRAMBLE_LENGTH,
+ mysql->passwd, passwd_len - 1,
+ (char *)scramble_pkt, SCRAMBLE_LENGTH)) {
+ set_mysql_extended_error(mysql, CR_AUTH_PLUGIN_ERR, unknown_sqlstate,
+ ER_CLIENT(CR_AUTH_PLUGIN_ERR),
+ "caching_sha2_password",
+ "Failed to generate scramble");
+ DBUG_RETURN(CR_ERROR);
+ }
+
+ if (vio->write_packet(vio, sha2_scramble, SHA2_SCRAMBLE_LENGTH))
+ DBUG_RETURN(CR_ERROR);
+
+ if ((pkt_len = vio->read_packet(vio, &pkt)) == -1) DBUG_RETURN(CR_ERROR);
+
+ if (pkt_len == 1 && *pkt == fast_auth_success) DBUG_RETURN(CR_OK);
+
+ /* An OK packet would follow */
+ }
+
+ if (pkt_len != 1 || *pkt != perform_full_authentication) {
+ DBUG_PRINT("info", ("Unexpected reply from server."));
+ DBUG_RETURN(CR_ERROR);
+ }
+
+ /* If connection isn't secure attempt to get the RSA public key file */
+ if (!connection_is_secure) {
+ public_key = rsa_init(mysql);
+
+ if (public_key == NULL && mysql->options.extension &&
+ mysql->options.extension->get_server_public_key) {
+ // If no public key; request one from the server.
+ if (vio->write_packet(vio, (const unsigned char *)&request_public_key,
+ 1))
+ DBUG_RETURN(CR_ERROR);
+
+ if ((pkt_len = vio->read_packet(vio, &pkt)) <= 0) DBUG_RETURN(CR_ERROR);
+ BIO *bio = BIO_new_mem_buf(pkt, pkt_len);
+ public_key = PEM_read_bio_RSA_PUBKEY(bio, NULL, NULL, NULL);
+ BIO_free(bio);
+ if (public_key == 0) {
+#ifndef HAVE_WOLFSSL
+ ERR_clear_error();
+#endif /* !HAVE_WOLFSSL */
+ DBUG_PRINT("info", ("Failed to parse public key"));
+ DBUG_RETURN(CR_ERROR);
+ }
+ got_public_key_from_server = true;
+ }
+
+ if (public_key) {
+ /*
+ An arbitrary limitation based on the assumption that passwords
+ larger than e.g. 15 symbols don't contribute to security.
+ Note also that it's further restricted to RSA_size() - 11 down
+ below, so this leaves 471 bytes of possible RSA key sizes which
+ should be reasonably future-proof.
+ We avoid heap allocation for speed reasons.
+ */
+ char passwd_scramble[512];
+
+ if (passwd_len > sizeof(passwd_scramble)) {
+ /* password too long for the buffer */
+ if (got_public_key_from_server) RSA_free(public_key);
+ DBUG_PRINT("info", ("Password is too long."));
+ DBUG_RETURN(CR_ERROR);
+ }
+ memmove(passwd_scramble, mysql->passwd, passwd_len);
+
+ /* Obfuscate the plain text password with the session scramble */
+ xor_string(passwd_scramble, passwd_len - 1, (char *)scramble_pkt,
+ SCRAMBLE_LENGTH);
+ /* Encrypt the password and send it to the server */
+ int cipher_length = RSA_size(public_key);
+ /*
+ When using RSA_PKCS1_OAEP_PADDING the password length must be less
+ than RSA_size(rsa) - 41.
+ */
+ if (passwd_len + 41 >= (unsigned)cipher_length) {
+ /* password message is to long */
+ if (got_public_key_from_server) RSA_free(public_key);
+ DBUG_PRINT("info", ("Password is too long to be encrypted using "
+ "given public key."));
+ DBUG_RETURN(CR_ERROR);
+ }
+ RSA_public_encrypt(passwd_len, (unsigned char *)passwd_scramble,
+ encrypted_password, public_key,
+ RSA_PKCS1_OAEP_PADDING);
+ if (got_public_key_from_server) RSA_free(public_key);
+
+ if (vio->write_packet(vio, (uchar *)encrypted_password, cipher_length))
+ DBUG_RETURN(CR_ERROR);
+ } else {
+ set_mysql_extended_error(mysql, CR_AUTH_PLUGIN_ERR, unknown_sqlstate,
+ ER_CLIENT(CR_AUTH_PLUGIN_ERR),
+ "caching_sha2_password",
+ "Authentication requires secure connection.");
+ DBUG_RETURN(CR_ERROR);
+ }
+ } else {
+ /* The vio is encrypted already; just send the plain text passwd */
+ if (vio->write_packet(vio, (uchar *)mysql->passwd, passwd_len))
+ DBUG_RETURN(CR_ERROR);
+ }
+ }
+
+ DBUG_RETURN(CR_OK);
+}
+
+/**
+ non blocking version of caching_sha2_password_auth_client
+*/
+net_async_status caching_sha2_password_auth_client_nonblocking(
+ MYSQL_PLUGIN_VIO *vio, MYSQL *mysql, int *result) {
+ DBUG_ENTER(__func__);
+ int io_result;
+ net_async_status status = NET_ASYNC_NOT_READY;
+ static unsigned char encrypted_password[MAX_CIPHER_LENGTH];
+ static RSA *public_key = NULL;
+ bool connection_is_secure = is_secure_transport(mysql);
+ bool got_public_key_from_server = false;
+ static unsigned char scramble_pkt[20];
+ static int cipher_length = 0;
+ static unsigned int passwd_len = 0;
+ unsigned char *pkt;
+ mysql_async_auth *ctx = ASYNC_DATA(mysql)->connect_context->auth_context;
+
+ switch (static_cast<client_auth_caching_sha2_password_plugin_status>(
+ ctx->client_auth_plugin_state)) {
+ case client_auth_caching_sha2_password_plugin_status::
+ CACHING_SHA2_READING_PASSWORD:
+ /*
+ Get the scramble from the server because we need it when sending
+ encrypted password.
+ */
+ status = vio->read_packet_nonblocking(vio, &pkt, &io_result);
+ if (status == NET_ASYNC_NOT_READY) {
+ DBUG_RETURN(NET_ASYNC_NOT_READY);
+ }
+ if (io_result != SCRAMBLE_LENGTH + 1) {
+ DBUG_PRINT("info", ("Scramble is not of correct length."));
+ *result = CR_ERROR;
+ DBUG_RETURN(NET_ASYNC_COMPLETE);
+ }
+ if (pkt[SCRAMBLE_LENGTH] != '\0') {
+ DBUG_PRINT("info", ("Missing protocol token in scramble data."));
+ *result = CR_ERROR;
+ DBUG_RETURN(NET_ASYNC_COMPLETE);
+ }
+ /*
+ Copy the scramble to the stack or it will be lost on the next use
+ of the net buffer.
+ */
+ memcpy(scramble_pkt, pkt, SCRAMBLE_LENGTH);
+ ctx->client_auth_plugin_state =
+ client_auth_caching_sha2_password_plugin_status::
+ CACHING_SHA2_WRITING_RESPONSE;
+ /* FALLTHROUGH */
+ case client_auth_caching_sha2_password_plugin_status::
+ CACHING_SHA2_WRITING_RESPONSE:
+ if (mysql->passwd[0] == 0) {
+ /* We're not using a password */
+ static const unsigned char zero_byte = '\0';
+ status = vio->write_packet_nonblocking(vio, &zero_byte, 1, &io_result);
+ if (status == NET_ASYNC_NOT_READY) {
+ DBUG_RETURN(NET_ASYNC_NOT_READY);
+ }
+ if (io_result) {
+ *result = CR_ERROR;
+ DBUG_RETURN(NET_ASYNC_COMPLETE);
+ }
+ *result = CR_OK;
+ DBUG_RETURN(NET_ASYNC_COMPLETE);
+ } else {
+ /* Password is a 0-terminated byte array ('\0' character included) */
+ passwd_len = static_cast<unsigned int>(strlen(mysql->passwd) + 1);
+ /* First try with SHA2 scramble */
+ unsigned char sha2_scramble[SHA2_SCRAMBLE_LENGTH];
+ if (generate_sha256_scramble(sha2_scramble, SHA2_SCRAMBLE_LENGTH,
+ mysql->passwd, passwd_len - 1,
+ (char *)scramble_pkt, SCRAMBLE_LENGTH)) {
+ set_mysql_extended_error(mysql, CR_AUTH_PLUGIN_ERR, unknown_sqlstate,
+ ER_CLIENT(CR_AUTH_PLUGIN_ERR),
+ "caching_sha2_password",
+ "Failed to generate scramble");
+ *result = CR_ERROR;
+ DBUG_RETURN(NET_ASYNC_COMPLETE);
+ }
+ status = vio->write_packet_nonblocking(
+ vio, sha2_scramble, SHA2_SCRAMBLE_LENGTH, &io_result);
+ if (status == NET_ASYNC_NOT_READY) {
+ DBUG_RETURN(NET_ASYNC_NOT_READY);
+ }
+ if (io_result) {
+ *result = CR_ERROR;
+ DBUG_RETURN(NET_ASYNC_COMPLETE);
+ }
+ }
+ ctx->client_auth_plugin_state =
+ client_auth_caching_sha2_password_plugin_status::
+ CACHING_SHA2_CHALLENGE_RESPONSE;
+ /* FALLTHROUGH */
+ case client_auth_caching_sha2_password_plugin_status::
+ CACHING_SHA2_CHALLENGE_RESPONSE:
+ status = vio->read_packet_nonblocking(vio, &pkt, &io_result);
+ if (status == NET_ASYNC_NOT_READY) {
+ DBUG_RETURN(NET_ASYNC_NOT_READY);
+ }
+ if (io_result == -1) {
+ *result = CR_ERROR;
+ DBUG_RETURN(NET_ASYNC_COMPLETE);
+ }
+ if (io_result == 1 && *pkt == fast_auth_success) {
+ *result = CR_OK;
+ DBUG_RETURN(NET_ASYNC_COMPLETE);
+ }
+ if (io_result != 1 || *pkt != perform_full_authentication) {
+ DBUG_PRINT("info", ("Unexpected reply from server."));
+ *result = CR_ERROR;
+ DBUG_RETURN(NET_ASYNC_COMPLETE);
+ }
+ if (!connection_is_secure)
+ ctx->client_auth_plugin_state =
+ client_auth_caching_sha2_password_plugin_status::
+ CACHING_SHA2_REQUEST_PUBLIC_KEY;
+ else
+ ctx->client_auth_plugin_state =
+ client_auth_caching_sha2_password_plugin_status::
+ CACHING_SHA2_SEND_PLAIN_PASSWORD;
+ DBUG_RETURN(NET_ASYNC_NOT_READY);
+ /* FALLTHROUGH */
+ case client_auth_caching_sha2_password_plugin_status::
+ CACHING_SHA2_REQUEST_PUBLIC_KEY:
+ /* If connection isn't secure attempt to get the RSA public key file */
+ {
+ public_key = rsa_init(mysql);
+
+ if (public_key == NULL && mysql->options.extension &&
+ mysql->options.extension->get_server_public_key) {
+ status = vio->write_packet_nonblocking(
+ vio, (const unsigned char *)&request_public_key, 1, &io_result);
+ if (status == NET_ASYNC_NOT_READY) {
+ DBUG_RETURN(NET_ASYNC_NOT_READY);
+ }
+ if (io_result) {
+ *result = CR_ERROR;
+ DBUG_RETURN(NET_ASYNC_COMPLETE);
+ }
+ }
+ }
+ ctx->client_auth_plugin_state =
+ client_auth_caching_sha2_password_plugin_status::
+ CACHING_SHA2_READ_PUBLIC_KEY;
+ /* FALLTHROUGH */
+ case client_auth_caching_sha2_password_plugin_status::
+ CACHING_SHA2_READ_PUBLIC_KEY: {
+ if (public_key == NULL && mysql->options.extension &&
+ mysql->options.extension->get_server_public_key) {
+ status = vio->read_packet_nonblocking(vio, &pkt, &io_result);
+ if (status == NET_ASYNC_NOT_READY) {
+ DBUG_RETURN(NET_ASYNC_NOT_READY);
+ }
+ if (io_result <= 0) {
+ *result = CR_ERROR;
+ DBUG_RETURN(NET_ASYNC_COMPLETE);
+ }
+ int pkt_len = 0;
+ BIO *bio = BIO_new_mem_buf(pkt, pkt_len);
+ public_key = PEM_read_bio_RSA_PUBKEY(bio, NULL, NULL, NULL);
+ BIO_free(bio);
+ if (public_key == 0) {
+#ifndef HAVE_WOLFSSL
+ ERR_clear_error();
+#endif /* !HAVE_WOLFSSL */
+ DBUG_PRINT("info", ("Failed to parse public key"));
+ *result = CR_ERROR;
+ DBUG_RETURN(NET_ASYNC_COMPLETE);
+ }
+ got_public_key_from_server = true;
+ }
+ if (public_key) {
+ char passwd_scramble[512];
+ if (passwd_len > sizeof(passwd_scramble)) {
+ /* password too long for the buffer */
+ if (got_public_key_from_server) RSA_free(public_key);
+ DBUG_PRINT("info", ("Password is too long."));
+ *result = CR_ERROR;
+ DBUG_RETURN(NET_ASYNC_COMPLETE);
+ }
+ memmove(passwd_scramble, mysql->passwd, passwd_len);
+ /* Obfuscate the plain text password with the session scramble */
+ xor_string(passwd_scramble, passwd_len - 1, (char *)scramble_pkt,
+ SCRAMBLE_LENGTH);
+ /* Encrypt the password and send it to the server */
+ cipher_length = RSA_size(public_key);
+ /*
+ When using RSA_PKCS1_OAEP_PADDING the password length
+ must be less than RSA_size(rsa) - 41.
+ */
+ if (passwd_len + 41 >= (unsigned)cipher_length) {
+ /* password message is to long */
+ if (got_public_key_from_server) RSA_free(public_key);
+ DBUG_PRINT("info", ("Password is too long to be encrypted using "
+ "given public key."));
+ *result = CR_ERROR;
+ DBUG_RETURN(NET_ASYNC_COMPLETE);
+ }
+ RSA_public_encrypt(passwd_len, (unsigned char *)passwd_scramble,
+ encrypted_password, public_key,
+ RSA_PKCS1_OAEP_PADDING);
+ if (got_public_key_from_server) RSA_free(public_key);
+ } else {
+ set_mysql_extended_error(mysql, CR_AUTH_PLUGIN_ERR, unknown_sqlstate,
+ ER_CLIENT(CR_AUTH_PLUGIN_ERR),
+ "caching_sha2_password",
+ "Authentication requires secure connection.");
+ *result = CR_ERROR;
+ DBUG_RETURN(NET_ASYNC_COMPLETE);
+ }
+ }
+ ctx->client_auth_plugin_state =
+ client_auth_caching_sha2_password_plugin_status::
+ CACHING_SHA2_SEND_ENCRYPTED_PASSWORD;
+ /* FALLTHROUGH */
+ case client_auth_caching_sha2_password_plugin_status::
+ CACHING_SHA2_SEND_ENCRYPTED_PASSWORD: {
+ status = vio->write_packet_nonblocking(vio, (uchar *)encrypted_password,
+ cipher_length, &io_result);
+ if (status == NET_ASYNC_NOT_READY) {
+ DBUG_RETURN(NET_ASYNC_NOT_READY);
+ }
+ if (io_result < 0) {
+ *result = CR_ERROR;
+ DBUG_RETURN(NET_ASYNC_COMPLETE);
+ }
+ } break;
+ case client_auth_caching_sha2_password_plugin_status::
+ CACHING_SHA2_SEND_PLAIN_PASSWORD: {
+ status = vio->write_packet_nonblocking(vio, (uchar *)mysql->passwd,
+ passwd_len, &io_result);
+ if (status == NET_ASYNC_NOT_READY) {
+ DBUG_RETURN(NET_ASYNC_NOT_READY);
+ }
+ if (io_result < 0) {
+ *result = CR_ERROR;
+ DBUG_RETURN(NET_ASYNC_COMPLETE);
+ }
+ } break;
+ default:
+ assert(0);
+ }
+ *result = CR_OK;
+ DBUG_RETURN(NET_ASYNC_COMPLETE);
+}
+
+void STDCALL mysql_reset_server_public_key(void) {
+ DBUG_ENTER("mysql_reset_server_public_key");
+ mysql_mutex_lock(&g_public_key_mutex);
+ if (g_public_key) RSA_free(g_public_key);
+ g_public_key = NULL;
+ mysql_mutex_unlock(&g_public_key_mutex);
+ DBUG_VOID_RETURN;
+}
+
+#endif