Skip to content

Commit 3abe64f

Browse files
committed
Merge branch 'PHP-7.4'
* PHP-7.4: Native Windows support for mysqlnd sha256 authentification Abstract over crypto operations
2 parents 336eb48 + a037702 commit 3abe64f

File tree

2 files changed

+209
-61
lines changed

2 files changed

+209
-61
lines changed

ext/mysqlnd/config.w32

+3
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ if (PHP_MYSQLND != "no") {
3636
{
3737
AC_DEFINE("MYSQLND_COMPRESSION_ENABLED", 1, "Compression support");
3838
AC_DEFINE("MYSQLND_SSL_SUPPORTED", 1, "SSL support");
39+
if (CHECK_LIB("crypt32.lib", "mysqlnd")) {
40+
AC_DEFINE("MYSQLND_HAVE_SSL", 1, "Extended SSL support");
41+
}
3942
}
4043
PHP_INSTALL_HEADERS("", "ext/mysqlnd");
4144
}

ext/mysqlnd/mysqlnd_auth.c

+206-61
Original file line numberDiff line numberDiff line change
@@ -692,20 +692,146 @@ mysqlnd_xor_string(char * dst, const size_t dst_len, const char * xor_str, const
692692
}
693693
}
694694

695+
#ifndef PHP_WIN32
695696

696697
#include <openssl/rsa.h>
697698
#include <openssl/pem.h>
698699
#include <openssl/err.h>
699700

701+
typedef RSA * mysqlnd_rsa_t;
702+
703+
/* {{{ mysqlnd_sha256_get_rsa_from_pem */
704+
static mysqlnd_rsa_t
705+
mysqlnd_sha256_get_rsa_from_pem(const char *buf, size_t len)
706+
{
707+
BIO * bio = BIO_new_mem_buf(buf, len);
708+
RSA * ret = PEM_read_bio_RSA_PUBKEY(bio, NULL, NULL, NULL);
709+
BIO_free(bio);
710+
return ret;
711+
}
712+
/* }}} */
713+
714+
/* {{{ mysqlnd_sha256_public_encrypt */
715+
static zend_uchar *
716+
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)
717+
{
718+
zend_uchar * ret = NULL;
719+
size_t server_public_key_len = (size_t) RSA_size(server_public_key);
720+
721+
DBG_ENTER("mysqlnd_sha256_public_encrypt");
722+
/*
723+
Because RSA_PKCS1_OAEP_PADDING is used there is a restriction on the passwd_len.
724+
RSA_PKCS1_OAEP_PADDING is recommended for new applications. See more here:
725+
http://www.openssl.org/docs/crypto/RSA_public_encrypt.html
726+
*/
727+
if (server_public_key_len <= passwd_len + 41) {
728+
/* password message is to long */
729+
SET_CLIENT_ERROR(conn->error_info, CR_UNKNOWN_ERROR, UNKNOWN_SQLSTATE, "password is too long");
730+
DBG_ERR("password is too long");
731+
DBG_RETURN(NULL);
732+
}
733+
734+
*auth_data_len = server_public_key_len;
735+
ret = malloc(*auth_data_len);
736+
RSA_public_encrypt(passwd_len + 1, (zend_uchar *) xor_str, ret, server_public_key, RSA_PKCS1_OAEP_PADDING);
737+
RSA_free(server_public_key);
738+
DBG_RETURN(ret);
739+
}
740+
/* }}} */
741+
742+
#else
743+
744+
#include <wincrypt.h>
745+
#include <bcrypt.h>
746+
747+
typedef HANDLE mysqlnd_rsa_t;
748+
749+
/* {{{ mysqlnd_sha256_get_rsa_from_pem */
750+
static mysqlnd_rsa_t
751+
mysqlnd_sha256_get_rsa_from_pem(const char *buf, size_t len)
752+
{
753+
BCRYPT_KEY_HANDLE ret = 0;
754+
LPCSTR der_buf = NULL;
755+
DWORD der_len;
756+
CERT_PUBLIC_KEY_INFO *key_info = NULL;
757+
DWORD key_info_len;
758+
ALLOCA_FLAG(use_heap);
759+
760+
if (!CryptStringToBinaryA(buf, len, CRYPT_STRING_BASE64HEADER, NULL, &der_len, NULL, NULL)) {
761+
goto finish;
762+
}
763+
der_buf = do_alloca(der_len, use_heap);
764+
if (!CryptStringToBinaryA(buf, len, CRYPT_STRING_BASE64HEADER, der_buf, &der_len, NULL, NULL)) {
765+
goto finish;
766+
}
767+
if (!CryptDecodeObjectEx(X509_ASN_ENCODING, X509_PUBLIC_KEY_INFO, der_buf, der_len, CRYPT_ENCODE_ALLOC_FLAG, NULL, &key_info, &key_info_len)) {
768+
goto finish;
769+
}
770+
if (!CryptImportPublicKeyInfoEx2(X509_ASN_ENCODING, key_info, CRYPT_OID_INFO_PUBKEY_ENCRYPT_KEY_FLAG, NULL, &ret)) {
771+
goto finish;
772+
}
773+
774+
finish:
775+
if (key_info) {
776+
LocalFree(key_info);
777+
}
778+
if (der_buf) {
779+
free_alloca(der_buf, use_heap);
780+
}
781+
return (mysqlnd_rsa_t) ret;
782+
}
783+
/* }}} */
784+
785+
/* {{{ mysqlnd_sha256_public_encrypt */
786+
static zend_uchar *
787+
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)
788+
{
789+
zend_uchar * ret = NULL;
790+
DWORD server_public_key_len = passwd_len;
791+
BCRYPT_OAEP_PADDING_INFO padding_info;
792+
793+
DBG_ENTER("mysqlnd_sha256_public_encrypt");
794+
795+
ZeroMemory(&padding_info, sizeof padding_info);
796+
padding_info.pszAlgId = BCRYPT_SHA1_ALGORITHM;
797+
if (BCryptEncrypt((BCRYPT_KEY_HANDLE) server_public_key, xor_str, passwd_len + 1, &padding_info,
798+
NULL, 0, NULL, 0, &server_public_key_len, BCRYPT_PAD_OAEP)) {
799+
DBG_RETURN(0);
800+
}
801+
802+
/*
803+
Because RSA_PKCS1_OAEP_PADDING is used there is a restriction on the passwd_len.
804+
RSA_PKCS1_OAEP_PADDING is recommended for new applications. See more here:
805+
http://www.openssl.org/docs/crypto/RSA_public_encrypt.html
806+
*/
807+
if ((size_t) server_public_key_len <= passwd_len + 41) {
808+
/* password message is to long */
809+
SET_CLIENT_ERROR(conn->error_info, CR_UNKNOWN_ERROR, UNKNOWN_SQLSTATE, "password is too long");
810+
DBG_ERR("password is too long");
811+
DBG_RETURN(0);
812+
}
813+
814+
*auth_data_len = server_public_key_len;
815+
ret = malloc(*auth_data_len);
816+
if (BCryptEncrypt((BCRYPT_KEY_HANDLE) server_public_key, xor_str, passwd_len + 1, &padding_info,
817+
NULL, 0, ret, server_public_key_len, &server_public_key_len, BCRYPT_PAD_OAEP)) {
818+
DBG_RETURN(0);
819+
}
820+
BCryptDestroyKey((BCRYPT_KEY_HANDLE) server_public_key);
821+
DBG_RETURN(ret);
822+
}
823+
/* }}} */
824+
825+
#endif
700826

701827
/* {{{ mysqlnd_sha256_get_rsa_key */
702-
static RSA *
828+
static mysqlnd_rsa_t
703829
mysqlnd_sha256_get_rsa_key(MYSQLND_CONN_DATA * conn,
704830
const MYSQLND_SESSION_OPTIONS * const session_options,
705831
const MYSQLND_PFC_DATA * const pfc_data
706832
)
707833
{
708-
RSA * ret = NULL;
834+
mysqlnd_rsa_t ret = NULL;
709835
const char * fname = (pfc_data->sha256_server_public_key && pfc_data->sha256_server_public_key[0] != '\0')?
710836
pfc_data->sha256_server_public_key:
711837
MYSQLND_G(sha256_server_public_key);
@@ -737,11 +863,7 @@ mysqlnd_sha256_get_rsa_key(MYSQLND_CONN_DATA * conn,
737863
}
738864
DBG_INF_FMT("Public key(%d):\n%s", pk_resp_packet.public_key_len, pk_resp_packet.public_key);
739865
/* now extract the public key */
740-
{
741-
BIO * bio = BIO_new_mem_buf(pk_resp_packet.public_key, pk_resp_packet.public_key_len);
742-
ret = PEM_read_bio_RSA_PUBKEY(bio, NULL, NULL, NULL);
743-
BIO_free(bio);
744-
}
866+
ret = mysqlnd_sha256_get_rsa_from_pem((const char *) pk_resp_packet.public_key, pk_resp_packet.public_key_len);
745867
} while (0);
746868
PACKET_FREE(&pk_req_packet);
747869
PACKET_FREE(&pk_resp_packet);
@@ -760,9 +882,7 @@ mysqlnd_sha256_get_rsa_key(MYSQLND_CONN_DATA * conn,
760882

761883
if (stream) {
762884
if ((key_str = php_stream_copy_to_mem(stream, PHP_STREAM_COPY_ALL, 0)) != NULL) {
763-
BIO * bio = BIO_new_mem_buf(ZSTR_VAL(key_str), ZSTR_LEN(key_str));
764-
ret = PEM_read_bio_RSA_PUBKEY(bio, NULL, NULL, NULL);
765-
BIO_free(bio);
885+
ret = mysqlnd_sha256_get_rsa_from_pem(ZSTR_VAL(key_str), ZSTR_LEN(key_str));
766886
DBG_INF("Successfully loaded");
767887
DBG_INF_FMT("Public key:%*.s", ZSTR_LEN(key_str), ZSTR_VAL(key_str));
768888
zend_string_release_ex(key_str, 0);
@@ -786,7 +906,7 @@ mysqlnd_sha256_auth_get_auth_data(struct st_mysqlnd_authentication_plugin * self
786906
const zend_ulong mysql_flags
787907
)
788908
{
789-
RSA * server_public_key;
909+
mysqlnd_rsa_t server_public_key;
790910
zend_uchar * ret = NULL;
791911
DBG_ENTER("mysqlnd_sha256_auth_get_auth_data");
792912
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
803923
server_public_key = mysqlnd_sha256_get_rsa_key(conn, session_options, pfc_data);
804924

805925
if (server_public_key) {
806-
int server_public_key_len;
807926
ALLOCA_FLAG(use_heap);
808927
char *xor_str = do_alloca(passwd_len + 1, use_heap);
809928
memcpy(xor_str, passwd, passwd_len);
810929
xor_str[passwd_len] = '\0';
811930
mysqlnd_xor_string(xor_str, passwd_len, (char *) auth_plugin_data, auth_plugin_data_len);
812-
813-
server_public_key_len = RSA_size(server_public_key);
814-
/*
815-
Because RSA_PKCS1_OAEP_PADDING is used there is a restriction on the passwd_len.
816-
RSA_PKCS1_OAEP_PADDING is recommended for new applications. See more here:
817-
http://www.openssl.org/docs/crypto/RSA_public_encrypt.html
818-
*/
819-
if ((size_t) server_public_key_len - 41 <= passwd_len) {
820-
/* password message is to long */
821-
free_alloca(xor_str, use_heap);
822-
SET_CLIENT_ERROR(conn->error_info, CR_UNKNOWN_ERROR, UNKNOWN_SQLSTATE, "password is too long");
823-
DBG_ERR("password is too long");
824-
DBG_RETURN(NULL);
825-
}
826-
827-
*auth_data_len = server_public_key_len;
828-
ret = malloc(*auth_data_len);
829-
RSA_public_encrypt(passwd_len + 1, (zend_uchar *) xor_str, ret, server_public_key, RSA_PKCS1_OAEP_PADDING);
830-
RSA_free(server_public_key);
931+
ret = mysqlnd_sha256_public_encrypt(conn, server_public_key, passwd_len, auth_data_len, xor_str);
831932
free_alloca(xor_str, use_heap);
832933
}
833934
}
@@ -899,6 +1000,73 @@ void php_mysqlnd_scramble_sha2(zend_uchar * const buffer, const zend_uchar * con
8991000
}
9001001
/* }}} */
9011002

1003+
#ifndef PHP_WIN32
1004+
1005+
/* {{{ mysqlnd_caching_sha2_public_encrypt */
1006+
static size_t
1007+
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)
1008+
{
1009+
size_t server_public_key_len = (size_t) RSA_size(server_public_key);
1010+
1011+
DBG_ENTER("mysqlnd_caching_sha2_public_encrypt");
1012+
/*
1013+
Because RSA_PKCS1_OAEP_PADDING is used there is a restriction on the passwd_len.
1014+
RSA_PKCS1_OAEP_PADDING is recommended for new applications. See more here:
1015+
http://www.openssl.org/docs/crypto/RSA_public_encrypt.html
1016+
*/
1017+
if (server_public_key_len <= passwd_len + 41) {
1018+
/* password message is to long */
1019+
SET_CLIENT_ERROR(conn->error_info, CR_UNKNOWN_ERROR, UNKNOWN_SQLSTATE, "password is too long");
1020+
DBG_ERR("password is too long");
1021+
DBG_RETURN(0);
1022+
}
1023+
1024+
*crypted = emalloc(server_public_key_len);
1025+
RSA_public_encrypt(passwd_len + 1, (zend_uchar *) xor_str, *crypted, server_public_key, RSA_PKCS1_OAEP_PADDING);
1026+
DBG_RETURN(server_public_key_len);
1027+
}
1028+
/* }}} */
1029+
1030+
#else
1031+
1032+
/* {{{ mysqlnd_caching_sha2_public_encrypt */
1033+
static size_t
1034+
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)
1035+
{
1036+
DWORD server_public_key_len = passwd_len;
1037+
BCRYPT_OAEP_PADDING_INFO padding_info;
1038+
1039+
DBG_ENTER("mysqlnd_caching_sha2_public_encrypt");
1040+
1041+
ZeroMemory(&padding_info, sizeof padding_info);
1042+
padding_info.pszAlgId = BCRYPT_SHA1_ALGORITHM;
1043+
if (BCryptEncrypt((BCRYPT_KEY_HANDLE) server_public_key, xor_str, passwd_len + 1, &padding_info,
1044+
NULL, 0, NULL, 0, &server_public_key_len, BCRYPT_PAD_OAEP)) {
1045+
DBG_RETURN(0);
1046+
}
1047+
1048+
/*
1049+
Because RSA_PKCS1_OAEP_PADDING is used there is a restriction on the passwd_len.
1050+
RSA_PKCS1_OAEP_PADDING is recommended for new applications. See more here:
1051+
http://www.openssl.org/docs/crypto/RSA_public_encrypt.html
1052+
*/
1053+
if ((size_t) server_public_key_len <= passwd_len + 41) {
1054+
/* password message is to long */
1055+
SET_CLIENT_ERROR(conn->error_info, CR_UNKNOWN_ERROR, UNKNOWN_SQLSTATE, "password is too long");
1056+
DBG_ERR("password is too long");
1057+
DBG_RETURN(0);
1058+
}
1059+
1060+
*crypted = emalloc(server_public_key_len);
1061+
if (BCryptEncrypt((BCRYPT_KEY_HANDLE) server_public_key, xor_str, passwd_len + 1, &padding_info,
1062+
NULL, 0, *crypted, server_public_key_len, &server_public_key_len, BCRYPT_PAD_OAEP)) {
1063+
DBG_RETURN(0);
1064+
}
1065+
DBG_RETURN(server_public_key_len);
1066+
}
1067+
/* }}} */
1068+
1069+
#endif
9021070

9031071
/* {{{ mysqlnd_native_auth_get_auth_data */
9041072
static zend_uchar *
@@ -936,10 +1104,10 @@ mysqlnd_caching_sha2_get_auth_data(struct st_mysqlnd_authentication_plugin * sel
9361104
}
9371105
/* }}} */
9381106

939-
static RSA *
1107+
static mysqlnd_rsa_t
9401108
mysqlnd_caching_sha2_get_key(MYSQLND_CONN_DATA *conn)
9411109
{
942-
RSA * ret = NULL;
1110+
mysqlnd_rsa_t ret = NULL;
9431111
const MYSQLND_PFC_DATA * const pfc_data = conn->protocol_frame_codec->data;
9441112
const char * fname = (pfc_data->sha256_server_public_key && pfc_data->sha256_server_public_key[0] != '\0')?
9451113
pfc_data->sha256_server_public_key:
@@ -973,11 +1141,7 @@ mysqlnd_caching_sha2_get_key(MYSQLND_CONN_DATA *conn)
9731141
}
9741142
DBG_INF_FMT("Public key(%d):\n%s", pk_resp_packet.public_key_len, pk_resp_packet.public_key);
9751143
/* now extract the public key */
976-
{
977-
BIO * bio = BIO_new_mem_buf(pk_resp_packet.public_key, pk_resp_packet.public_key_len);
978-
ret = PEM_read_bio_RSA_PUBKEY(bio, NULL, NULL, NULL);
979-
BIO_free(bio);
980-
}
1144+
ret = mysqlnd_sha256_get_rsa_from_pem((const char *) pk_resp_packet.public_key, pk_resp_packet.public_key_len);
9811145
} while (0);
9821146
PACKET_FREE(&req_packet);
9831147
PACKET_FREE(&pk_resp_packet);
@@ -996,9 +1160,7 @@ mysqlnd_caching_sha2_get_key(MYSQLND_CONN_DATA *conn)
9961160

9971161
if (stream) {
9981162
if ((key_str = php_stream_copy_to_mem(stream, PHP_STREAM_COPY_ALL, 0)) != NULL) {
999-
BIO * bio = BIO_new_mem_buf(ZSTR_VAL(key_str), ZSTR_LEN(key_str));
1000-
ret = PEM_read_bio_RSA_PUBKEY(bio, NULL, NULL, NULL);
1001-
BIO_free(bio);
1163+
ret = mysqlnd_sha256_get_rsa_from_pem(ZSTR_VAL(key_str), ZSTR_LEN(key_str));
10021164
DBG_INF("Successfully loaded");
10031165
DBG_INF_FMT("Public key:%*.s", ZSTR_LEN(key_str), ZSTR_VAL(key_str));
10041166
zend_string_release(key_str);
@@ -1011,16 +1173,15 @@ mysqlnd_caching_sha2_get_key(MYSQLND_CONN_DATA *conn)
10111173
}
10121174

10131175

1014-
/* {{{ mysqlnd_caching_sha2_get_key */
1176+
/* {{{ mysqlnd_caching_sha2_get_and_use_key */
10151177
static size_t
10161178
mysqlnd_caching_sha2_get_and_use_key(MYSQLND_CONN_DATA *conn,
10171179
const zend_uchar * auth_plugin_data, const size_t auth_plugin_data_len,
10181180
unsigned char **crypted,
10191181
const char * const passwd,
10201182
const size_t passwd_len)
10211183
{
1022-
static RSA *server_public_key;
1023-
server_public_key = mysqlnd_caching_sha2_get_key(conn);
1184+
mysqlnd_rsa_t server_public_key = mysqlnd_caching_sha2_get_key(conn);
10241185

10251186
DBG_ENTER("mysqlnd_caching_sha2_get_and_use_key(");
10261187

@@ -1031,23 +1192,7 @@ mysqlnd_caching_sha2_get_and_use_key(MYSQLND_CONN_DATA *conn,
10311192
memcpy(xor_str, passwd, passwd_len);
10321193
xor_str[passwd_len] = '\0';
10331194
mysqlnd_xor_string(xor_str, passwd_len, (char *) auth_plugin_data, SCRAMBLE_LENGTH);
1034-
1035-
server_public_key_len = RSA_size(server_public_key);
1036-
/*
1037-
Because RSA_PKCS1_OAEP_PADDING is used there is a restriction on the passwd_len.
1038-
RSA_PKCS1_OAEP_PADDING is recommended for new applications. See more here:
1039-
http://www.openssl.org/docs/crypto/RSA_public_encrypt.html
1040-
*/
1041-
if ((size_t) server_public_key_len - 41 <= passwd_len) {
1042-
/* password message is to long */
1043-
free_alloca(xor_str, use_heap);
1044-
SET_CLIENT_ERROR(conn->error_info, CR_UNKNOWN_ERROR, UNKNOWN_SQLSTATE, "password is too long");
1045-
DBG_ERR("password is too long");
1046-
DBG_RETURN(0);
1047-
}
1048-
1049-
*crypted = emalloc(server_public_key_len);
1050-
RSA_public_encrypt(passwd_len + 1, (zend_uchar *) xor_str, *crypted, server_public_key, RSA_PKCS1_OAEP_PADDING);
1195+
server_public_key_len = mysqlnd_caching_sha2_public_encrypt(conn, server_public_key, passwd_len, crypted, xor_str);
10511196
free_alloca(xor_str, use_heap);
10521197
DBG_RETURN(server_public_key_len);
10531198
}

0 commit comments

Comments
 (0)