diff options
author | Christoph M. Becker <cmbecker69@gmx.de> | 2020-03-02 16:33:40 +0100 |
---|---|---|
committer | Christoph M. Becker <cmbecker69@gmx.de> | 2020-03-02 16:33:40 +0100 |
commit | 3abe64f16a8ab554491d522da248abf4b34a8d3c (patch) | |
tree | bef6371a34826d52a0dd8dc24f66ea46c3f73d41 /ext/mysqlnd/mysqlnd_auth.c | |
parent | 336eb48c36f3c1c115349307c18e6cf16ab003df (diff) | |
parent | a0377021c5640cd6e6ad719a76dd38f91f7c367b (diff) | |
download | php-git-3abe64f16a8ab554491d522da248abf4b34a8d3c.tar.gz |
Merge branch 'PHP-7.4'
* PHP-7.4:
Native Windows support for mysqlnd sha256 authentification
Abstract over crypto operations
Diffstat (limited to 'ext/mysqlnd/mysqlnd_auth.c')
-rw-r--r-- | ext/mysqlnd/mysqlnd_auth.c | 267 |
1 files changed, 206 insertions, 61 deletions
diff --git a/ext/mysqlnd/mysqlnd_auth.c b/ext/mysqlnd/mysqlnd_auth.c index 6ccb28dd0f..f8791b08ce 100644 --- a/ext/mysqlnd/mysqlnd_auth.c +++ b/ext/mysqlnd/mysqlnd_auth.c @@ -692,20 +692,146 @@ mysqlnd_xor_string(char * dst, const size_t dst_len, const char * xor_str, const } } +#ifndef PHP_WIN32 #include <openssl/rsa.h> #include <openssl/pem.h> #include <openssl/err.h> +typedef RSA * mysqlnd_rsa_t; + +/* {{{ mysqlnd_sha256_get_rsa_from_pem */ +static mysqlnd_rsa_t +mysqlnd_sha256_get_rsa_from_pem(const char *buf, size_t len) +{ + BIO * bio = BIO_new_mem_buf(buf, len); + RSA * ret = PEM_read_bio_RSA_PUBKEY(bio, NULL, NULL, NULL); + BIO_free(bio); + return ret; +} +/* }}} */ + +/* {{{ mysqlnd_sha256_public_encrypt */ +static zend_uchar * +mysqlnd_sha256_public_encrypt(MYSQLND_CONN_DATA * conn, mysqlnd_rsa_t server_public_key, size_t passwd_len, size_t * auth_data_len, char *xor_str) +{ + zend_uchar * ret = NULL; + size_t server_public_key_len = (size_t) RSA_size(server_public_key); + + DBG_ENTER("mysqlnd_sha256_public_encrypt"); + /* + Because RSA_PKCS1_OAEP_PADDING is used there is a restriction on the passwd_len. + RSA_PKCS1_OAEP_PADDING is recommended for new applications. See more here: + http://www.openssl.org/docs/crypto/RSA_public_encrypt.html + */ + if (server_public_key_len <= passwd_len + 41) { + /* password message is to long */ + SET_CLIENT_ERROR(conn->error_info, CR_UNKNOWN_ERROR, UNKNOWN_SQLSTATE, "password is too long"); + DBG_ERR("password is too long"); + DBG_RETURN(NULL); + } + + *auth_data_len = server_public_key_len; + ret = malloc(*auth_data_len); + RSA_public_encrypt(passwd_len + 1, (zend_uchar *) xor_str, ret, server_public_key, RSA_PKCS1_OAEP_PADDING); + RSA_free(server_public_key); + DBG_RETURN(ret); +} +/* }}} */ + +#else + +#include <wincrypt.h> +#include <bcrypt.h> + +typedef HANDLE mysqlnd_rsa_t; + +/* {{{ mysqlnd_sha256_get_rsa_from_pem */ +static mysqlnd_rsa_t +mysqlnd_sha256_get_rsa_from_pem(const char *buf, size_t len) +{ + BCRYPT_KEY_HANDLE ret = 0; + LPCSTR der_buf = NULL; + DWORD der_len; + CERT_PUBLIC_KEY_INFO *key_info = NULL; + DWORD key_info_len; + ALLOCA_FLAG(use_heap); + + if (!CryptStringToBinaryA(buf, len, CRYPT_STRING_BASE64HEADER, NULL, &der_len, NULL, NULL)) { + goto finish; + } + der_buf = do_alloca(der_len, use_heap); + if (!CryptStringToBinaryA(buf, len, CRYPT_STRING_BASE64HEADER, der_buf, &der_len, NULL, NULL)) { + goto finish; + } + if (!CryptDecodeObjectEx(X509_ASN_ENCODING, X509_PUBLIC_KEY_INFO, der_buf, der_len, CRYPT_ENCODE_ALLOC_FLAG, NULL, &key_info, &key_info_len)) { + goto finish; + } + if (!CryptImportPublicKeyInfoEx2(X509_ASN_ENCODING, key_info, CRYPT_OID_INFO_PUBKEY_ENCRYPT_KEY_FLAG, NULL, &ret)) { + goto finish; + } + +finish: + if (key_info) { + LocalFree(key_info); + } + if (der_buf) { + free_alloca(der_buf, use_heap); + } + return (mysqlnd_rsa_t) ret; +} +/* }}} */ + +/* {{{ mysqlnd_sha256_public_encrypt */ +static zend_uchar * +mysqlnd_sha256_public_encrypt(MYSQLND_CONN_DATA * conn, mysqlnd_rsa_t server_public_key, size_t passwd_len, size_t * auth_data_len, char *xor_str) +{ + zend_uchar * ret = NULL; + DWORD server_public_key_len = passwd_len; + BCRYPT_OAEP_PADDING_INFO padding_info; + + DBG_ENTER("mysqlnd_sha256_public_encrypt"); + + ZeroMemory(&padding_info, sizeof padding_info); + padding_info.pszAlgId = BCRYPT_SHA1_ALGORITHM; + if (BCryptEncrypt((BCRYPT_KEY_HANDLE) server_public_key, xor_str, passwd_len + 1, &padding_info, + NULL, 0, NULL, 0, &server_public_key_len, BCRYPT_PAD_OAEP)) { + DBG_RETURN(0); + } + + /* + Because RSA_PKCS1_OAEP_PADDING is used there is a restriction on the passwd_len. + RSA_PKCS1_OAEP_PADDING is recommended for new applications. See more here: + http://www.openssl.org/docs/crypto/RSA_public_encrypt.html + */ + if ((size_t) server_public_key_len <= passwd_len + 41) { + /* password message is to long */ + SET_CLIENT_ERROR(conn->error_info, CR_UNKNOWN_ERROR, UNKNOWN_SQLSTATE, "password is too long"); + DBG_ERR("password is too long"); + DBG_RETURN(0); + } + + *auth_data_len = server_public_key_len; + ret = malloc(*auth_data_len); + if (BCryptEncrypt((BCRYPT_KEY_HANDLE) server_public_key, xor_str, passwd_len + 1, &padding_info, + NULL, 0, ret, server_public_key_len, &server_public_key_len, BCRYPT_PAD_OAEP)) { + DBG_RETURN(0); + } + BCryptDestroyKey((BCRYPT_KEY_HANDLE) server_public_key); + DBG_RETURN(ret); +} +/* }}} */ + +#endif /* {{{ mysqlnd_sha256_get_rsa_key */ -static RSA * +static mysqlnd_rsa_t mysqlnd_sha256_get_rsa_key(MYSQLND_CONN_DATA * conn, const MYSQLND_SESSION_OPTIONS * const session_options, const MYSQLND_PFC_DATA * const pfc_data ) { - RSA * ret = NULL; + mysqlnd_rsa_t ret = NULL; const char * fname = (pfc_data->sha256_server_public_key && pfc_data->sha256_server_public_key[0] != '\0')? pfc_data->sha256_server_public_key: MYSQLND_G(sha256_server_public_key); @@ -737,11 +863,7 @@ mysqlnd_sha256_get_rsa_key(MYSQLND_CONN_DATA * conn, } DBG_INF_FMT("Public key(%d):\n%s", pk_resp_packet.public_key_len, pk_resp_packet.public_key); /* now extract the public key */ - { - BIO * bio = BIO_new_mem_buf(pk_resp_packet.public_key, pk_resp_packet.public_key_len); - ret = PEM_read_bio_RSA_PUBKEY(bio, NULL, NULL, NULL); - BIO_free(bio); - } + ret = mysqlnd_sha256_get_rsa_from_pem((const char *) pk_resp_packet.public_key, pk_resp_packet.public_key_len); } while (0); PACKET_FREE(&pk_req_packet); PACKET_FREE(&pk_resp_packet); @@ -760,9 +882,7 @@ mysqlnd_sha256_get_rsa_key(MYSQLND_CONN_DATA * conn, if (stream) { if ((key_str = php_stream_copy_to_mem(stream, PHP_STREAM_COPY_ALL, 0)) != NULL) { - BIO * bio = BIO_new_mem_buf(ZSTR_VAL(key_str), ZSTR_LEN(key_str)); - ret = PEM_read_bio_RSA_PUBKEY(bio, NULL, NULL, NULL); - BIO_free(bio); + ret = mysqlnd_sha256_get_rsa_from_pem(ZSTR_VAL(key_str), ZSTR_LEN(key_str)); DBG_INF("Successfully loaded"); DBG_INF_FMT("Public key:%*.s", ZSTR_LEN(key_str), ZSTR_VAL(key_str)); zend_string_release_ex(key_str, 0); @@ -786,7 +906,7 @@ mysqlnd_sha256_auth_get_auth_data(struct st_mysqlnd_authentication_plugin * self const zend_ulong mysql_flags ) { - RSA * server_public_key; + mysqlnd_rsa_t server_public_key; zend_uchar * ret = NULL; DBG_ENTER("mysqlnd_sha256_auth_get_auth_data"); DBG_INF_FMT("salt(%d)=[%.*s]", auth_plugin_data_len, auth_plugin_data_len, auth_plugin_data); @@ -803,31 +923,12 @@ mysqlnd_sha256_auth_get_auth_data(struct st_mysqlnd_authentication_plugin * self server_public_key = mysqlnd_sha256_get_rsa_key(conn, session_options, pfc_data); if (server_public_key) { - int server_public_key_len; ALLOCA_FLAG(use_heap); char *xor_str = do_alloca(passwd_len + 1, use_heap); memcpy(xor_str, passwd, passwd_len); xor_str[passwd_len] = '\0'; mysqlnd_xor_string(xor_str, passwd_len, (char *) auth_plugin_data, auth_plugin_data_len); - - server_public_key_len = RSA_size(server_public_key); - /* - Because RSA_PKCS1_OAEP_PADDING is used there is a restriction on the passwd_len. - RSA_PKCS1_OAEP_PADDING is recommended for new applications. See more here: - http://www.openssl.org/docs/crypto/RSA_public_encrypt.html - */ - if ((size_t) server_public_key_len - 41 <= passwd_len) { - /* password message is to long */ - free_alloca(xor_str, use_heap); - SET_CLIENT_ERROR(conn->error_info, CR_UNKNOWN_ERROR, UNKNOWN_SQLSTATE, "password is too long"); - DBG_ERR("password is too long"); - DBG_RETURN(NULL); - } - - *auth_data_len = server_public_key_len; - ret = malloc(*auth_data_len); - RSA_public_encrypt(passwd_len + 1, (zend_uchar *) xor_str, ret, server_public_key, RSA_PKCS1_OAEP_PADDING); - RSA_free(server_public_key); + ret = mysqlnd_sha256_public_encrypt(conn, server_public_key, passwd_len, auth_data_len, xor_str); free_alloca(xor_str, use_heap); } } @@ -899,6 +1000,73 @@ void php_mysqlnd_scramble_sha2(zend_uchar * const buffer, const zend_uchar * con } /* }}} */ +#ifndef PHP_WIN32 + +/* {{{ mysqlnd_caching_sha2_public_encrypt */ +static size_t +mysqlnd_caching_sha2_public_encrypt(MYSQLND_CONN_DATA * conn, mysqlnd_rsa_t server_public_key, size_t passwd_len, unsigned char **crypted, char *xor_str) +{ + size_t server_public_key_len = (size_t) RSA_size(server_public_key); + + DBG_ENTER("mysqlnd_caching_sha2_public_encrypt"); + /* + Because RSA_PKCS1_OAEP_PADDING is used there is a restriction on the passwd_len. + RSA_PKCS1_OAEP_PADDING is recommended for new applications. See more here: + http://www.openssl.org/docs/crypto/RSA_public_encrypt.html + */ + if (server_public_key_len <= passwd_len + 41) { + /* password message is to long */ + SET_CLIENT_ERROR(conn->error_info, CR_UNKNOWN_ERROR, UNKNOWN_SQLSTATE, "password is too long"); + DBG_ERR("password is too long"); + DBG_RETURN(0); + } + + *crypted = emalloc(server_public_key_len); + RSA_public_encrypt(passwd_len + 1, (zend_uchar *) xor_str, *crypted, server_public_key, RSA_PKCS1_OAEP_PADDING); + DBG_RETURN(server_public_key_len); +} +/* }}} */ + +#else + +/* {{{ mysqlnd_caching_sha2_public_encrypt */ +static size_t +mysqlnd_caching_sha2_public_encrypt(MYSQLND_CONN_DATA * conn, mysqlnd_rsa_t server_public_key, size_t passwd_len, unsigned char **crypted, char *xor_str) +{ + DWORD server_public_key_len = passwd_len; + BCRYPT_OAEP_PADDING_INFO padding_info; + + DBG_ENTER("mysqlnd_caching_sha2_public_encrypt"); + + ZeroMemory(&padding_info, sizeof padding_info); + padding_info.pszAlgId = BCRYPT_SHA1_ALGORITHM; + if (BCryptEncrypt((BCRYPT_KEY_HANDLE) server_public_key, xor_str, passwd_len + 1, &padding_info, + NULL, 0, NULL, 0, &server_public_key_len, BCRYPT_PAD_OAEP)) { + DBG_RETURN(0); + } + + /* + Because RSA_PKCS1_OAEP_PADDING is used there is a restriction on the passwd_len. + RSA_PKCS1_OAEP_PADDING is recommended for new applications. See more here: + http://www.openssl.org/docs/crypto/RSA_public_encrypt.html + */ + if ((size_t) server_public_key_len <= passwd_len + 41) { + /* password message is to long */ + SET_CLIENT_ERROR(conn->error_info, CR_UNKNOWN_ERROR, UNKNOWN_SQLSTATE, "password is too long"); + DBG_ERR("password is too long"); + DBG_RETURN(0); + } + + *crypted = emalloc(server_public_key_len); + if (BCryptEncrypt((BCRYPT_KEY_HANDLE) server_public_key, xor_str, passwd_len + 1, &padding_info, + NULL, 0, *crypted, server_public_key_len, &server_public_key_len, BCRYPT_PAD_OAEP)) { + DBG_RETURN(0); + } + DBG_RETURN(server_public_key_len); +} +/* }}} */ + +#endif /* {{{ mysqlnd_native_auth_get_auth_data */ static zend_uchar * @@ -936,10 +1104,10 @@ mysqlnd_caching_sha2_get_auth_data(struct st_mysqlnd_authentication_plugin * sel } /* }}} */ -static RSA * +static mysqlnd_rsa_t mysqlnd_caching_sha2_get_key(MYSQLND_CONN_DATA *conn) { - RSA * ret = NULL; + mysqlnd_rsa_t ret = NULL; const MYSQLND_PFC_DATA * const pfc_data = conn->protocol_frame_codec->data; const char * fname = (pfc_data->sha256_server_public_key && pfc_data->sha256_server_public_key[0] != '\0')? pfc_data->sha256_server_public_key: @@ -973,11 +1141,7 @@ mysqlnd_caching_sha2_get_key(MYSQLND_CONN_DATA *conn) } DBG_INF_FMT("Public key(%d):\n%s", pk_resp_packet.public_key_len, pk_resp_packet.public_key); /* now extract the public key */ - { - BIO * bio = BIO_new_mem_buf(pk_resp_packet.public_key, pk_resp_packet.public_key_len); - ret = PEM_read_bio_RSA_PUBKEY(bio, NULL, NULL, NULL); - BIO_free(bio); - } + ret = mysqlnd_sha256_get_rsa_from_pem((const char *) pk_resp_packet.public_key, pk_resp_packet.public_key_len); } while (0); PACKET_FREE(&req_packet); PACKET_FREE(&pk_resp_packet); @@ -996,9 +1160,7 @@ mysqlnd_caching_sha2_get_key(MYSQLND_CONN_DATA *conn) if (stream) { if ((key_str = php_stream_copy_to_mem(stream, PHP_STREAM_COPY_ALL, 0)) != NULL) { - BIO * bio = BIO_new_mem_buf(ZSTR_VAL(key_str), ZSTR_LEN(key_str)); - ret = PEM_read_bio_RSA_PUBKEY(bio, NULL, NULL, NULL); - BIO_free(bio); + ret = mysqlnd_sha256_get_rsa_from_pem(ZSTR_VAL(key_str), ZSTR_LEN(key_str)); DBG_INF("Successfully loaded"); DBG_INF_FMT("Public key:%*.s", ZSTR_LEN(key_str), ZSTR_VAL(key_str)); zend_string_release(key_str); @@ -1011,7 +1173,7 @@ mysqlnd_caching_sha2_get_key(MYSQLND_CONN_DATA *conn) } -/* {{{ mysqlnd_caching_sha2_get_key */ +/* {{{ mysqlnd_caching_sha2_get_and_use_key */ static size_t mysqlnd_caching_sha2_get_and_use_key(MYSQLND_CONN_DATA *conn, const zend_uchar * auth_plugin_data, const size_t auth_plugin_data_len, @@ -1019,8 +1181,7 @@ mysqlnd_caching_sha2_get_and_use_key(MYSQLND_CONN_DATA *conn, const char * const passwd, const size_t passwd_len) { - static RSA *server_public_key; - server_public_key = mysqlnd_caching_sha2_get_key(conn); + mysqlnd_rsa_t server_public_key = mysqlnd_caching_sha2_get_key(conn); DBG_ENTER("mysqlnd_caching_sha2_get_and_use_key("); @@ -1031,23 +1192,7 @@ mysqlnd_caching_sha2_get_and_use_key(MYSQLND_CONN_DATA *conn, memcpy(xor_str, passwd, passwd_len); xor_str[passwd_len] = '\0'; mysqlnd_xor_string(xor_str, passwd_len, (char *) auth_plugin_data, SCRAMBLE_LENGTH); - - server_public_key_len = RSA_size(server_public_key); - /* - Because RSA_PKCS1_OAEP_PADDING is used there is a restriction on the passwd_len. - RSA_PKCS1_OAEP_PADDING is recommended for new applications. See more here: - http://www.openssl.org/docs/crypto/RSA_public_encrypt.html - */ - if ((size_t) server_public_key_len - 41 <= passwd_len) { - /* password message is to long */ - free_alloca(xor_str, use_heap); - SET_CLIENT_ERROR(conn->error_info, CR_UNKNOWN_ERROR, UNKNOWN_SQLSTATE, "password is too long"); - DBG_ERR("password is too long"); - DBG_RETURN(0); - } - - *crypted = emalloc(server_public_key_len); - RSA_public_encrypt(passwd_len + 1, (zend_uchar *) xor_str, *crypted, server_public_key, RSA_PKCS1_OAEP_PADDING); + server_public_key_len = mysqlnd_caching_sha2_public_encrypt(conn, server_public_key, passwd_len, crypted, xor_str); free_alloca(xor_str, use_heap); DBG_RETURN(server_public_key_len); } |