Skip to content

Native Windows support for mysqlnd sha256 authentification #5210

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 5 commits into from
Closed
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: 3 additions & 0 deletions ext/mysqlnd/config.w32
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ if (PHP_MYSQLND != "no") {
{
AC_DEFINE("MYSQLND_COMPRESSION_ENABLED", 1, "Compression support");
AC_DEFINE("MYSQLND_SSL_SUPPORTED", 1, "SSL support");
if (CHECK_LIB("crypt32.lib", "mysqlnd")) {
AC_DEFINE("MYSQLND_HAVE_SSL", 1, "Extended SSL support");
}
}
PHP_INSTALL_HEADERS("", "ext/mysqlnd");
}
Expand Down
267 changes: 206 additions & 61 deletions ext/mysqlnd/mysqlnd_auth.c
Original file line number Diff line number Diff line change
Expand Up @@ -694,20 +694,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);
Expand Down Expand Up @@ -739,11 +865,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);
Expand All @@ -762,9 +884,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);
Expand All @@ -788,7 +908,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);
Expand All @@ -805,31 +925,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);
}
}
Expand Down Expand Up @@ -901,6 +1002,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 *
Expand Down Expand Up @@ -938,10 +1106,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:
Expand Down Expand Up @@ -975,11 +1143,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);
Expand All @@ -998,9 +1162,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);
Expand All @@ -1013,16 +1175,15 @@ 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,
unsigned char **crypted,
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(");

Expand All @@ -1033,23 +1194,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);
}
Expand Down