diff --git a/include/ma_crypt.h b/include/ma_crypt.h new file mode 100644 index 00000000..18fcd6ca --- /dev/null +++ b/include/ma_crypt.h @@ -0,0 +1,166 @@ +/* + Copyright (C) 2018 MariaDB Corporation AB + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not see + or write to the Free Software Foundation, Inc., + 51 Franklin St., Fifth Floor, Boston, MA 02110, USA +*/ + +#ifndef _ma_hash_h_ +#define _ma_hash_h_ + +#include +#include + +/*! Hash algorithms */ +#define MA_HASH_MD5 1 +#define MA_HASH_SHA1 2 +#define MA_HASH_SHA224 3 +#define MA_HASH_SHA256 4 +#define MA_HASH_SHA384 5 +#define MA_HASH_SHA512 6 +#define MA_HASH_RIPEMD160 7 + +/*! Hash digest sizes */ +#define MA_MD5_HASH_SIZE 16 +#define MA_SHA1_HASH_SIZE 20 +#define MA_SHA224_HASH_SIZE 28 +#define MA_SHA256_HASH_SIZE 32 +#define MA_SHA384_HASH_SIZE 48 +#define MA_SHA512_HASH_SIZE 64 +#define MA_RIPEMD160_HASH_SIZE 20 + +#define MA_MAX_HASH_SIZE 64 +/** \typedef MRL hash context */ + +#if defined(HAVE_OPENSSL) +typedef void MA_HASH_CTX; +#elif defined(HAVE_GNUTLS) +typedef struct { + void *ctx; + const struct nettle_hash *hash; +} MA_HASH_CTX; +#elif defined(HAVE_SCHANNEL) +#include +#include +typedef struct { + char free_me; + BCRYPT_ALG_HANDLE hAlg; + BCRYPT_HASH_HANDLE hHash; + PBYTE hashObject; + DWORD digest_len; +} MA_HASH_CTX; +#endif + +/** + @brief acquire and initialize new hash context + + @param[in] algorithm hash algorithm + @param[in] ctx pointer to a crypto context + + @return hash context on success, NULL on error +*/ +MA_HASH_CTX *ma_hash_new(unsigned int algorithm, MA_HASH_CTX *ctx); + +/** + @brief release and deinitializes a hash context + + @param[in] hash context + + @return void +*/ +void ma_hash_free(MA_HASH_CTX *ctx); + +/** + @brief hashes len bytes of data into the hash context. + This function can be called several times on same context to + hash additional data. + + @param[in] ctx hash context + @param[in] buffer data buffer + @param[in] len size of buffer + + @return void +*/ +void ma_hash_input(MA_HASH_CTX *ctx, + const unsigned char *buffer, + size_t len); + +/** + @brief retrieves the hash value from hash context + + @param[in] ctx hash context + @param[out] digest digest containing hash value + + @return void + */ +void ma_hash_result(MA_HASH_CTX *ctx, unsigned char *digest); + + +/** + @brief returns digest size for a given hash algorithm + + @param[in] hash algorithm + + @retuns digest size or 0 on error +*/ +static inline size_t ma_hash_digest_size(unsigned int hash_alg) +{ + switch(hash_alg) { + case MA_HASH_MD5: + return MA_MD5_HASH_SIZE; + case MA_HASH_SHA1: + return MA_SHA1_HASH_SIZE; + case MA_HASH_SHA224: + return MA_SHA224_HASH_SIZE; + case MA_HASH_SHA256: + return MA_SHA256_HASH_SIZE; + case MA_HASH_SHA384: + return MA_SHA384_HASH_SIZE; + case MA_HASH_SHA512: + return MA_SHA512_HASH_SIZE; + case MA_HASH_RIPEMD160: + return MA_RIPEMD160_HASH_SIZE; + default: + return 0; + } +} + +/** + @brief function to compute hash from buffer. + + @param[in] hash_alg hash algorithm + @param[in] buffer buffer + @param[in] buffer_leng length of buffer + @param[out] digest computed hash digest + + @return void +*/ +static inline void ma_hash(unsigned int algorithm, + const unsigned char *buffer, + size_t buffer_length, + unsigned char *digest) +{ + MA_HASH_CTX *ctx= NULL; +#ifdef HAVE_SCHANNEL + MA_HASH_CTX dctx; + ctx= &dctx; +#endif + ctx= ma_hash_new(algorithm, ctx); + ma_hash_input(ctx, buffer, buffer_length); + ma_hash_result(ctx, digest); + ma_hash_free(ctx); +} + +#endif /* _ma_hash_h_ */ diff --git a/libmariadb/mariadb_lib.c b/libmariadb/mariadb_lib.c index d914de61..69068ed6 100644 --- a/libmariadb/mariadb_lib.c +++ b/libmariadb/mariadb_lib.c @@ -639,6 +639,7 @@ struct st_default_options mariadb_defaults[] = {MARIADB_OPT_SSL_FP_LIST, MARIADB_OPTION_STR, "ssl-fplist"}, {MARIADB_OPT_TLS_PASSPHRASE, MARIADB_OPTION_STR, "ssl-passphrase"}, {MARIADB_OPT_TLS_VERSION, MARIADB_OPTION_STR, "tls_version"}, + {MYSQL_SERVER_PUBLIC_KEY, MARIADB_OPTION_STR, "server_public_key"}, {MYSQL_OPT_BIND, MARIADB_OPTION_STR, "bind-address"}, {0, 0, NULL} }; diff --git a/libmariadb/secure/gnutls_crypt.c b/libmariadb/secure/gnutls_crypt.c new file mode 100644 index 00000000..0d3712f7 --- /dev/null +++ b/libmariadb/secure/gnutls_crypt.c @@ -0,0 +1,77 @@ +/* + Copyright (C) 2018 MariaDB Corporation AB + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not see + or write to the Free Software Foundation, Inc., + 51 Franklin St., Fifth Floor, Boston, MA 02110, USA +*/ +#include +#include +#include + +static gnutls_digest_algorithm_t ma_hash_get_algorithm(unsigned int alg) +{ + switch(alg) + { + case MA_HASH_MD5: + return GNUTLS_DIG_MD5; + case MA_HASH_SHA1: + return GNUTLS_DIG_SHA1; + case MA_HASH_SHA256: + return GNUTLS_DIG_SHA256; + case MA_HASH_SHA384: + return GNUTLS_DIG_SHA384; + case MA_HASH_SHA512: + return GNUTLS_DIG_SHA512; + case MA_HASH_RIPEMD160: + return GNUTLS_DIG_RMD160; + default: + return GNUTLS_DIG_UNKNOWN; + } +} + +MA_HASH_CTX *ma_hash_new(unsigned int algorithm, MA_HASH_CTX *unused_ctx __attribute__((unused))) +{ + gnutls_hash_hd_t ctx= NULL; + gnutls_digest_algorithm_t hash_alg= ma_hash_get_algorithm(algorithm); + + /* unknown or unsupported hash algorithm */ + if (hash_alg == GNUTLS_DIG_UNKNOWN) + return NULL; + + if (gnutls_hash_init(&ctx, hash_alg) < 0) + return NULL; + + return (MA_HASH_CTX *)ctx; +} + +void ma_hash_free(MA_HASH_CTX *ctx) +{ + if (ctx) + gnutls_hash_deinit((gnutls_hash_hd_t)ctx, NULL); +} + +void ma_hash_input(MA_HASH_CTX *ctx, + const unsigned char *buffer, + size_t len) +{ + gnutls_hash((gnutls_hash_hd_t)ctx, (const void *)buffer, len); +} + +void ma_hash_result(MA_HASH_CTX *ctx, unsigned char *digest) +{ + gnutls_hash_output((gnutls_hash_hd_t)ctx, digest); +} + + diff --git a/libmariadb/secure/openssl_crypt.c b/libmariadb/secure/openssl_crypt.c new file mode 100644 index 00000000..ff885c59 --- /dev/null +++ b/libmariadb/secure/openssl_crypt.c @@ -0,0 +1,87 @@ +/* + Copyright (C) 2018 MariaDB Corporation AB + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not see + or write to the Free Software Foundation, Inc., + 51 Franklin St., Fifth Floor, Boston, MA 02110, USA +*/ +#include +#include + +static const EVP_MD *ma_hash_get_algorithm(unsigned int alg) +{ + switch(alg) + { + case MA_HASH_MD5: + return EVP_md5(); + case MA_HASH_SHA1: + return EVP_sha1(); + case MA_HASH_SHA224: + return EVP_sha224(); + case MA_HASH_SHA256: + return EVP_sha256(); + case MA_HASH_SHA384: + return EVP_sha384(); + case MA_HASH_SHA512: + return EVP_sha512(); + case MA_HASH_RIPEMD160: + return EVP_ripemd160(); + default: + return NULL; + } +} + +MA_HASH_CTX *ma_hash_new(unsigned int algorithm, MA_HASH_CTX *unused __attribute__((unused))) +{ + EVP_MD_CTX *ctx= NULL; + const EVP_MD *evp_md= ma_hash_get_algorithm(algorithm); + + /* unknown or unsupported hash algorithm */ + if (!evp_md) + return NULL; +#if OPENSSL_VERSION_NUMBER >= 0x10100000L + if (!(ctx= EVP_MD_CTX_new())) +#else + if (!(ctx= EVP_MD_CTX_create())) +#endif + return NULL; + if (!EVP_DigestInit(ctx, evp_md)) + { + ma_hash_free(ctx); + return NULL; + } + return ctx; +} + +void ma_hash_free(MA_HASH_CTX *ctx) +{ + if (ctx) +#if OPENSSL_VERSION_NUMBER >= 0x10100000L + EVP_MD_CTX_free(ctx); +#else + EVP_MD_CTX_destroy(ctx); +#endif +} + +void ma_hash_input(MA_HASH_CTX *ctx, + const unsigned char *buffer, + size_t len) +{ + EVP_DigestUpdate(ctx, buffer, len); +} + +void ma_hash_result(MA_HASH_CTX *ctx, unsigned char *digest) +{ + EVP_DigestFinal_ex(ctx, digest, NULL); +} diff --git a/libmariadb/secure/win_crypt.c b/libmariadb/secure/win_crypt.c new file mode 100644 index 00000000..c4f5058c --- /dev/null +++ b/libmariadb/secure/win_crypt.c @@ -0,0 +1,101 @@ +/* + Copyright (C) 2018 MariaDB Corporation AB + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not see + or write to the Free Software Foundation, Inc., + 51 Franklin St., Fifth Floor, Boston, MA 02110, USA +*/ +#include +#include +#include + +#pragma comment(lib, "bcrypt.lib") + +BCRYPT_ALG_HANDLE Sha256Prov= 0; +BCRYPT_ALG_HANDLE RsaProv= 0; + +static LPCWSTR ma_hash_get_algorithm(unsigned int alg, BCRYPT_ALG_HANDLE *algHdl) +{ + switch(alg) + { + case MA_HASH_SHA256: + *algHdl= Sha256Prov; + return BCRYPT_SHA256_ALGORITHM; + default: + *algHdl= 0; + return NULL; + } +} + +MA_HASH_CTX *ma_hash_new(unsigned int algorithm, MA_HASH_CTX *ctx) +{ + MA_HASH_CTX *newctx= ctx; + DWORD cbObjSize, cbData; + LPCWSTR alg; + BCRYPT_ALG_HANDLE algHdl= 0; + + alg= ma_hash_get_algorithm(algorithm, &algHdl); + + if (!alg || !algHdl) + return NULL; + + if (BCryptGetProperty(algHdl, BCRYPT_OBJECT_LENGTH, + (PBYTE)&cbObjSize, sizeof(DWORD), + &cbData, 0) < 0) + goto error; + + if (!newctx) + { + newctx= (MA_HASH_CTX *)calloc(1, sizeof(MA_HASH_CTX)); + newctx->free_me= 1; + } + else + memset(newctx, 0, sizeof(MA_HASH_CTX)); + + newctx->hashObject= (PBYTE)malloc(cbObjSize); + newctx->digest_len= (DWORD)ma_hash_digest_size(algorithm); + BCryptCreateHash(algHdl, &newctx->hHash, newctx->hashObject, cbObjSize, NULL, 0, 0); + + return newctx; +error: + if (newctx && !ctx) + free(newctx); + return NULL; +} + +void ma_hash_free(MA_HASH_CTX *ctx) +{ + if (ctx) + { + if (ctx->hHash) + BCryptDestroyHash(ctx->hHash); + if (ctx->hashObject) + free(ctx->hashObject); + if (ctx->free_me) + free(ctx); + } +} + +void ma_hash_input(MA_HASH_CTX *ctx, + const unsigned char *buffer, + size_t len) +{ + BCryptHashData(ctx->hHash, (PUCHAR)buffer, (LONG)len, 0); +} + +void ma_hash_result(MA_HASH_CTX *ctx, unsigned char *digest) +{ + BCryptFinishHash(ctx->hHash, digest, ctx->digest_len, 0); +} + diff --git a/plugins/auth/CMakeLists.txt b/plugins/auth/CMakeLists.txt index 99548dc2..4f5ed198 100644 --- a/plugins/auth/CMakeLists.txt +++ b/plugins/auth/CMakeLists.txt @@ -18,6 +18,27 @@ REGISTER_PLUGIN(TARGET dialog SOURCES ${CC_SOURCE_DIR}/plugins/auth/dialog.c ${CC_SOURCE_DIR}/libmariadb/get_password.c) +# SHA256 caching plugin for MySQL 8.0 connection +IF(WITH_SSL) + IF(${WITH_SSL} STREQUAL "OPENSSL") + SET(CRYPT_SOURCE ${CC_SOURCE_DIR}/libmariadb/secure/openssl_crypt.c) + SET(CACHING_SHA2_LIBS ${SSL_LIBRARIES}) + ELSEIF(${WITH_SSL} STREQUAL "SCHANNEL") + SET(CRYPT_SOURCE ${CC_SOURCE_DIR}/libmariadb/secure/win_crypt.c) + SET(CACHING_SHA2_LIBS crypt32 bcrypt) + ELSEIF(${WITH_SSL} STREQUAL "GNUTLS") + SET(CRYPT_SOURCE ${CC_SOURCE_DIR}/libmariadb/secure/gnutls_crypt.c) + SET(CACHING_SHA2_LIBS ${SSL_LIBRARIES}) + ENDIF() + REGISTER_PLUGIN(TARGET caching_sha2_password + TYPE MARIADB_CLIENT_PLUGIN_AUTH + CONFIGURATIONS DYNAMIC STATIC OFF + DEFAULT DYNAMIC + SOURCES ${CC_SOURCE_DIR}/plugins/auth/caching_sha2_pw.c + ${CRYPT_SOURCE} + LIBRARIES ${CACHING_SHA2_LIBS}) +ENDIF() + #GSSAPI client authentication plugin IF(NOT WIN32) INCLUDE(${CC_SOURCE_DIR}/cmake/FindGSSAPI.cmake) diff --git a/plugins/auth/caching_sha2_pw.c b/plugins/auth/caching_sha2_pw.c new file mode 100644 index 00000000..b869f596 --- /dev/null +++ b/plugins/auth/caching_sha2_pw.c @@ -0,0 +1,470 @@ +/************************************************************************************ + Copyright (C) 2017 MariaDB Corporation AB + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not see + or write to the Free Software Foundation, Inc., + 51 Franklin St., Fifth Floor, Boston, MA 02110, USA + *************************************************************************************/ +#ifndef _WIN32 +#define _GNU_SOURCE 1 +#endif + +#ifdef _WIN32 +#if !defined(HAVE_OPENSSL) +#define HAVE_WINCRYPT +#endif +#endif + +#if defined(HAVE_OPENSSL) || defined(HAVE_SCHANNEL) || defined(HAVE_GNUTLS) + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef WIN32 +#include +#endif + +#if defined(HAVE_OPENSSL) +#include +#include +#include +#elif defined(HAVE_GNUTLS) +#include +#elif defined(HAVE_SCHANNEL) +#include +#include +#include +#pragma comment(lib, "bcrypt.lib") +#pragma comment(lib, "crypt32.lib") +extern BCRYPT_ALG_HANDLE RsaProv; +extern BCRYPT_ALG_HANDLE Sha256Prov; +#endif + +#include + +#define MAX_PW_LEN 1024 + +#define REQUEST_PUBLIC_KEY 2 +#define CACHED_LOGIN_SUCCEEDED 3 +#define RSA_LOGIN_REQUIRED 4 + +/* MySQL server allows requesting public key only for non secure connections. + secure connections are: + - TLS/SSL connections + - unix_socket connections +*/ +static unsigned char is_connection_secure(MYSQL *mysql) +{ + if (mysql->options.use_ssl || + mysql->net.pvio->type != PVIO_TYPE_SOCKET) + return 1; + return 0; +} + +static int ma_sha256_scramble(unsigned char *scramble, size_t scramble_len, + unsigned char *source, size_t source_len, + unsigned char *salt, size_t salt_len) +{ + unsigned char digest1[MA_SHA256_HASH_SIZE], + digest2[MA_SHA256_HASH_SIZE], + new_scramble[MA_SHA256_HASH_SIZE]; +#ifdef HAVE_SCHANNEL + MA_HASH_CTX myctx; + MA_HASH_CTX *ctx= &myctx; +#else + MA_HASH_CTX *ctx = NULL; +#endif + size_t i; + + /* check if all specified lenghts are valid */ + if (!scramble_len || !source_len || !salt_len) + return 1; + + + /* Step1: create sha256 from source */ + if (!(ctx= ma_hash_new(MA_HASH_SHA256, ctx))) + return 1; + ma_hash_input(ctx, source, source_len); + ma_hash_result(ctx, digest1); + ma_hash_free(ctx); +#ifndef HAVE_SCHANNEL + ctx = NULL; +#endif + + /* Step2: create sha256 digest from digest1 */ + if (!(ctx= ma_hash_new(MA_HASH_SHA256, ctx))) + return 1; + ma_hash_input(ctx, digest1, MA_SHA256_HASH_SIZE); + ma_hash_result(ctx, digest2); + ma_hash_free(ctx); +#ifndef HAVE_SCHANNEL + ctx = NULL; +#endif + + /* Step3: create sha256 digest from digest2 + salt */ + if (!(ctx= ma_hash_new(MA_HASH_SHA256, ctx))) + return 1; + ma_hash_input(ctx, digest2, MA_SHA256_HASH_SIZE); + ma_hash_input(ctx, salt, salt_len); + ma_hash_result(ctx, new_scramble); + ma_hash_free(ctx); + + /* Step4: xor(digest1, scramble1) */ + for (i= 0; i < scramble_len; i++) + scramble[i]= digest1[i] ^ new_scramble[i]; + return 0; +} + +/* function prototypes */ +static int auth_caching_sha2_client(MYSQL_PLUGIN_VIO *vio, MYSQL *mysql); +static int auth_caching_sha2_deinit(); +static int auth_caching_sha2_init(char *unused1, + size_t unused2, + int unused3, + va_list); + + +#ifndef PLUGIN_DYNAMIC +struct st_mysql_client_plugin_AUTHENTICATION caching_sha2_password_client_plugin= +#else +struct st_mysql_client_plugin_AUTHENTICATION _mysql_client_plugin_declaration_ = +#endif +{ + MYSQL_CLIENT_AUTHENTICATION_PLUGIN, + MYSQL_CLIENT_AUTHENTICATION_PLUGIN_INTERFACE_VERSION, + "caching_sha2_password", + "Georg Richter", + "Caching SHA2 Authentication Plugin", + {0,1,0}, + "LGPL", + NULL, + auth_caching_sha2_init, + auth_caching_sha2_deinit, + NULL, + auth_caching_sha2_client +}; + +#ifdef HAVE_SCHANNEL +static LPBYTE ma_load_pem(const char *buffer, DWORD *buffer_len) +{ + LPBYTE der_buffer= NULL; + DWORD der_buffer_length; + + if (buffer_len == NULL || *buffer_len == 0) + return NULL; + /* calculate the length of DER binary */ + if (!CryptStringToBinaryA(buffer, *buffer_len, CRYPT_STRING_BASE64HEADER, + NULL, &der_buffer_length, NULL, NULL)) + goto end; + /* allocate DER binary buffer */ + if (!(der_buffer= (LPBYTE)malloc(der_buffer_length))) + goto end; + /* convert to DER binary */ + if (!CryptStringToBinaryA(buffer, *buffer_len, CRYPT_STRING_BASE64HEADER, + der_buffer, &der_buffer_length, NULL, NULL)) + goto end; + + *buffer_len= der_buffer_length; + + return der_buffer; + +end: + if (der_buffer) + free(der_buffer); + *buffer_len= 0; + return NULL; +} +#endif + +char *load_pub_key_file(const char *filename, int *pub_key_size) +{ + FILE *fp= NULL; + char *buffer= NULL; + unsigned char error= 1; + + if (!pub_key_size) + return NULL; + + if (!(fp= fopen(filename, "r"))) + goto end; + + if (fseek(fp, 0, SEEK_END)) + goto end; + + *pub_key_size= ftell(fp); + rewind(fp); + + if (!(buffer= malloc(*pub_key_size + 1))) + goto end; + + if (!fread(buffer, *pub_key_size, 1, fp)) + goto end; + + error= 0; + +end: + if (fp) + fclose(fp); + if (error && buffer) + { + free(buffer); + buffer= NULL; + } + return buffer; +} + + +static int auth_caching_sha2_client(MYSQL_PLUGIN_VIO *vio, MYSQL *mysql) +{ + unsigned char *packet; + int packet_length; + int rc= CR_ERROR; +#if !defined(HAVE_GNUTLS) + char passwd[MAX_PW_LEN]; + unsigned char rsa_enc_pw[MAX_PW_LEN]; +#ifdef HAVE_OPENSSL + int rsa_size; +#else + ULONG rsa_size; +#endif + unsigned int pwlen, i; + char *filebuffer= NULL; +#endif + unsigned char buf[MA_SHA256_HASH_SIZE]; + +#if defined(HAVE_OPENSSL) + RSA *pubkey= NULL; + BIO *bio; +#elif defined(HAVE_SCHANNEL) + BCRYPT_KEY_HANDLE pubkey= 0; + BCRYPT_OAEP_PADDING_INFO paddingInfo; + LPBYTE der_buffer= NULL; + DWORD der_buffer_len= 0; + CERT_PUBLIC_KEY_INFO *publicKeyInfo= NULL; + DWORD publicKeyInfoLen; +#endif + + /* read error */ + if ((packet_length= vio->read_packet(vio, &packet)) < 0) + return CR_ERROR; + + if (packet_length != SCRAMBLE_LENGTH + 1) + return CR_SERVER_HANDSHAKE_ERR; + + memmove(mysql->scramble_buff, packet, SCRAMBLE_LENGTH); + mysql->scramble_buff[SCRAMBLE_LENGTH]= 0; + + /* if a tls session is active we need to send plain password */ + if (mysql->client_flag & CLIENT_SSL) + { + if (vio->write_packet(vio, (unsigned char *)mysql->passwd, (int)strlen(mysql->passwd) + 1)) + return CR_ERROR; + return CR_OK; + } + + /* send empty packet if no password was provided */ + if (!mysql->passwd || !mysql->passwd[0]) + { + if (vio->write_packet(vio, 0, 0)) + return CR_ERROR; + return CR_OK; + } + + /* This is the normal authentication, if the host/user key is already in server + cache. In case authentication will fail, we will not return an error but will + try to connect via RSA encryption. + */ + if (ma_sha256_scramble(buf, MA_SHA256_HASH_SIZE, + (unsigned char *)mysql->passwd, strlen(mysql->passwd), + (unsigned char *)mysql->scramble_buff, SCRAMBLE_LENGTH)) + return CR_ERROR; + + if (vio->write_packet(vio, buf, MA_SHA256_HASH_SIZE)) + return CR_ERROR; + if ((packet_length=vio->read_packet(vio, &packet)) == -1) + return CR_ERROR; + if (packet_length == 1) + { + switch (*packet) { + case CACHED_LOGIN_SUCCEEDED: + return CR_OK; + case RSA_LOGIN_REQUIRED: + break; + default: + return CR_ERROR; + } + } + + if (!is_connection_secure(mysql)) + { +#if defined(HAVE_GNUTLS) + mysql->methods->set_error(mysql, CR_AUTH_PLUGIN_ERR, "HY000", + "RSA Encrytion not supported - caching_sha2_password plugin was built with GnuTLS support"); + return CR_ERROR; +#else + /* read public key file (if specified) */ + if (mysql->options.extension && + mysql->options.extension->server_public_key) + { + filebuffer= load_pub_key_file(mysql->options.extension->server_public_key, + &packet_length); + } + + /* if no public key file was specified or if we couldn't read the file, + we ask server to send public key */ + if (!filebuffer) + { + unsigned char request= REQUEST_PUBLIC_KEY; + if (vio->write_packet(vio, &request, 1) || + (packet_length=vio->read_packet(vio, &packet)) == -1) + { + mysql->methods->set_error(mysql, CR_AUTH_PLUGIN_ERR, "HY000", "Couldn't read RSA public key from server"); + return CR_ERROR; + } + } +#if defined(HAVE_OPENSSL) + bio= BIO_new_mem_buf(filebuffer ? (unsigned char *)filebuffer : packet, + packet_length); + if ((pubkey= PEM_read_bio_RSA_PUBKEY(bio, NULL, NULL, NULL))) + rsa_size= RSA_size(pubkey); + BIO_free(bio); + ERR_clear_error(); +#elif defined(HAVE_SCHANNEL) + der_buffer_len= packet_length; + /* Load pem and convert it to binary object. New length will be returned + in der_buffer_len */ + if (!(der_buffer= ma_load_pem(filebuffer ? filebuffer : (char *)packet, &der_buffer_len))) + goto error; + + /* Create context and load public key */ + if (!CryptDecodeObjectEx(X509_ASN_ENCODING, X509_PUBLIC_KEY_INFO, + der_buffer, der_buffer_len, + CRYPT_DECODE_ALLOC_FLAG, NULL, + &publicKeyInfo, &publicKeyInfoLen)) + goto error; + free(der_buffer); + + /* Import public key as cng key */ + if (!CryptImportPublicKeyInfoEx2(X509_ASN_ENCODING, publicKeyInfo, + CRYPT_OID_INFO_PUBKEY_ENCRYPT_KEY_FLAG, + NULL, &pubkey)) + goto error; + +#endif + if (!pubkey) + return CR_ERROR; + + pwlen= (unsigned int)strlen(mysql->passwd) + 1; /* include terminating zero */ + if (pwlen > MAX_PW_LEN) + goto error; + memcpy(passwd, mysql->passwd, pwlen); + + /* xor password with scramble */ + for (i=0; i < pwlen; i++) + passwd[i]^= *(mysql->scramble_buff + i % SCRAMBLE_LENGTH); + + /* encrypt scrambled password */ +#if defined(HAVE_OPENSSL) + if (RSA_public_encrypt(pwlen, (unsigned char *)passwd, rsa_enc_pw, pubkey, RSA_PKCS1_OAEP_PADDING) < 0) + goto error; +#elif defined(HAVE_SCHANNEL) + ZeroMemory(&paddingInfo, sizeof(paddingInfo)); + paddingInfo.pszAlgId = BCRYPT_SHA1_ALGORITHM; + if ((rc= BCryptEncrypt(pubkey, (PUCHAR)passwd, pwlen, &paddingInfo, NULL, 0, rsa_enc_pw, + MAX_PW_LEN, &rsa_size, BCRYPT_PAD_OAEP))) + goto error; + +#endif + if (vio->write_packet(vio, rsa_enc_pw, rsa_size)) + goto error; + + rc= CR_OK; +#endif + } + else + { + if (vio->write_packet(vio, (unsigned char *)mysql->passwd, (int)strlen(mysql->passwd) + 1)) + return CR_ERROR; + return CR_OK; + } +#if !defined(HAVE_GNUTLS) +error: +#if defined(HAVE_OPENSSL) + if (pubkey) + RSA_free(pubkey); +#elif defined(HAVE_SCHANNEL) + if (pubkey) + BCryptDestroyKey(pubkey); + if (publicKeyInfo) + LocalFree(publicKeyInfo); +#endif + free(filebuffer); +#endif + return rc; +} +/* }}} */ + +/* {{{ static int auth_caching_sha2_init */ +/* + Initialization routine + + SYNOPSIS + auth_sha256_init + unused1 + unused2 + unused3 + unused4 + + DESCRIPTION + Init function checks if the caller provides own dialog function. + The function name must be mariadb_auth_dialog or + mysql_authentication_dialog_ask. If the function cannot be found, + we will use owr own simple command line input. + + RETURN + 0 success + */ +static int auth_caching_sha2_init(char *unused1 __attribute__((unused)), + size_t unused2 __attribute__((unused)), + int unused3 __attribute__((unused)), + va_list unused4 __attribute__((unused))) +{ +#if defined(HAVE_SCHANNEL) + BCryptOpenAlgorithmProvider(&Sha256Prov, BCRYPT_SHA256_ALGORITHM, NULL, 0); + BCryptOpenAlgorithmProvider(&RsaProv, BCRYPT_RSA_ALGORITHM, NULL, 0); +#endif + return 0; +} +/* }}} */ + +/* {{{ auth_caching_sha2_deinit */ +static int auth_caching_sha2_deinit() +{ +#if defined(HAVE_SCHANNEL) + BCryptCloseAlgorithmProvider(Sha256Prov, 0); + BCryptCloseAlgorithmProvider(RsaProv, 0); +#endif + return 0; +} +/* }}} */ + +#endif /* defined(HAVE_OPENSSL) || defined(HAVE_WINCRYPT) || defined(HAVE_GNUTLS)*/ + diff --git a/unittest/libmariadb/connection.c b/unittest/libmariadb/connection.c index c1eadd3f..e06088b6 100644 --- a/unittest/libmariadb/connection.c +++ b/unittest/libmariadb/connection.c @@ -1560,7 +1560,47 @@ static int test_conc351(MYSQL *unused __attribute__((unused))) return OK; } +static int test_conc312(MYSQL *my) +{ + int rc; + char query[1024]; + MYSQL *mysql; + + sprintf(query, "DROP USER 'foo'@'%s'", this_host); + rc= mysql_query(my, query); + + sprintf(query, "CREATE USER 'foo'@'%s' IDENTIFIED WITH caching_sha2_password BY 'foo'", this_host); + rc= mysql_query(my, query); + + if (rc) + { + diag("caching_sha256_password not supported"); + return SKIP; + } + + sprintf(query, "GRANT ALL ON %s.* TO 'foo'@'%s'", schema, this_host); + rc= mysql_query(my, query); + check_mysql_rc(rc, my); + + mysql= mysql_init(NULL); + if (!mysql_real_connect(mysql, hostname, "foo", "foo", schema, port, socketname, 0)) + { + diag("Error: %s", mysql_error(mysql)); + return FAIL; + } + + mysql_close(mysql); + + sprintf(query, "DROP USER 'foo'@'%s'", this_host); + rc= mysql_query(my, query); + check_mysql_rc(rc, mysql); + + return OK; +} + + struct my_tests_st my_tests[] = { + {"test_conc312", test_conc312, TEST_CONNECTION_DEFAULT, 0, NULL, NULL}, {"test_conc351", test_conc351, TEST_CONNECTION_NONE, 0, NULL, NULL}, {"test_conc332", test_conc332, TEST_CONNECTION_NONE, 0, NULL, NULL}, #ifndef WIN32