Skip to content

Commit 4f06e67

Browse files
committed
Re-commit MySQL 8 cached SHA auth support
With changes to (hopefully) correctly fall back if OpenSSL support is missing. Furthermore the hard-coded dependency on ext/hash is no longer an issue, as this extension is required in master. This reverts commit 63072e9, reversing changes made to 4cbabb6.
1 parent 35be059 commit 4f06e67

7 files changed

+392
-29
lines changed

Diff for: ext/mysqlnd/mysqlnd_auth.c

+276-7
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ mysqlnd_run_authentication(
8989
}
9090
}
9191

92+
9293
{
9394
zend_uchar * switch_to_auth_protocol_data = NULL;
9495
size_t switch_to_auth_protocol_data_len = 0;
@@ -113,10 +114,11 @@ mysqlnd_run_authentication(
113114
DBG_INF_FMT("salt(%d)=[%.*s]", plugin_data_len, plugin_data_len, plugin_data);
114115
/* The data should be allocated with malloc() */
115116
if (auth_plugin) {
116-
scrambled_data =
117-
auth_plugin->methods.get_auth_data(NULL, &scrambled_data_len, conn, user, passwd, passwd_len,
118-
plugin_data, plugin_data_len, session_options,
119-
conn->protocol_frame_codec->data, mysql_flags);
117+
scrambled_data = auth_plugin->methods.get_auth_data(
118+
NULL, &scrambled_data_len, conn, user, passwd,
119+
passwd_len, plugin_data, plugin_data_len,
120+
session_options, conn->protocol_frame_codec->data,
121+
mysql_flags);
120122
}
121123

122124
if (conn->error_info->error_no) {
@@ -127,6 +129,7 @@ mysqlnd_run_authentication(
127129
charset_no,
128130
first_call,
129131
requested_protocol,
132+
auth_plugin, plugin_data, plugin_data_len,
130133
scrambled_data, scrambled_data_len,
131134
&switch_to_auth_protocol, &switch_to_auth_protocol_len,
132135
&switch_to_auth_protocol_data, &switch_to_auth_protocol_data_len
@@ -244,6 +247,9 @@ mysqlnd_auth_handshake(MYSQLND_CONN_DATA * conn,
244247
unsigned int server_charset_no,
245248
zend_bool use_full_blown_auth_packet,
246249
const char * const auth_protocol,
250+
struct st_mysqlnd_authentication_plugin * auth_plugin,
251+
const zend_uchar * const orig_auth_plugin_data,
252+
const size_t orig_auth_plugin_data_len,
247253
const zend_uchar * const auth_plugin_data,
248254
const size_t auth_plugin_data_len,
249255
char ** switch_to_auth_protocol,
@@ -313,6 +319,11 @@ mysqlnd_auth_handshake(MYSQLND_CONN_DATA * conn,
313319
PACKET_FREE(&auth_packet);
314320
}
315321

322+
if (auth_plugin && auth_plugin->methods.handle_server_response) {
323+
auth_plugin->methods.handle_server_response(auth_plugin, conn,
324+
orig_auth_plugin_data, orig_auth_plugin_data_len, passwd, passwd_len);
325+
}
326+
316327
if (FAIL == PACKET_READ(conn, &auth_resp_packet) || auth_resp_packet.response_code >= 0xFE) {
317328
if (auth_resp_packet.response_code == 0xFE) {
318329
/* old authentication with new server !*/
@@ -596,7 +607,8 @@ static struct st_mysqlnd_authentication_plugin mysqlnd_native_auth_plugin =
596607
}
597608
},
598609
{/* methods */
599-
mysqlnd_native_auth_get_auth_data
610+
mysqlnd_native_auth_get_auth_data,
611+
NULL
600612
}
601613
};
602614

@@ -645,7 +657,8 @@ static struct st_mysqlnd_authentication_plugin mysqlnd_pam_authentication_plugin
645657
}
646658
},
647659
{/* methods */
648-
mysqlnd_pam_auth_get_auth_data
660+
mysqlnd_pam_auth_get_auth_data,
661+
NULL
649662
}
650663
};
651664

@@ -820,18 +833,274 @@ static struct st_mysqlnd_authentication_plugin mysqlnd_sha256_authentication_plu
820833
}
821834
},
822835
{/* methods */
823-
mysqlnd_sha256_auth_get_auth_data
836+
mysqlnd_sha256_auth_get_auth_data,
837+
NULL
838+
}
839+
};
840+
#endif
841+
842+
/*************************************** CACHING SHA2 Password *******************************/
843+
#ifdef MYSQLND_HAVE_SSL
844+
845+
#undef L64
846+
847+
#include "ext/hash/php_hash.h"
848+
#include "ext/hash/php_hash_sha.h"
849+
850+
#define SHA256_LENGTH 32
851+
852+
/* {{{ php_mysqlnd_scramble_sha2 */
853+
void php_mysqlnd_scramble_sha2(zend_uchar * const buffer, const zend_uchar * const scramble, const zend_uchar * const password, const size_t password_len)
854+
{
855+
PHP_SHA256_CTX context;
856+
zend_uchar sha1[SHA256_LENGTH];
857+
zend_uchar sha2[SHA256_LENGTH];
858+
859+
/* Phase 1: hash password */
860+
PHP_SHA256Init(&context);
861+
PHP_SHA256Update(&context, password, password_len);
862+
PHP_SHA256Final(sha1, &context);
863+
864+
/* Phase 2: hash sha1 */
865+
PHP_SHA256Init(&context);
866+
PHP_SHA256Update(&context, (zend_uchar*)sha1, SHA256_LENGTH);
867+
PHP_SHA256Final(sha2, &context);
868+
869+
/* Phase 3: hash scramble + sha2 */
870+
PHP_SHA256Init(&context);
871+
PHP_SHA256Update(&context, (zend_uchar*)sha2, SHA256_LENGTH);
872+
PHP_SHA256Update(&context, scramble, SCRAMBLE_LENGTH);
873+
PHP_SHA256Final(buffer, &context);
874+
875+
/* let's crypt buffer now */
876+
php_mysqlnd_crypt(buffer, (const zend_uchar *)sha1, (const zend_uchar *)buffer, SHA256_LENGTH);
877+
}
878+
/* }}} */
879+
880+
881+
/* {{{ mysqlnd_native_auth_get_auth_data */
882+
static zend_uchar *
883+
mysqlnd_caching_sha2_get_auth_data(struct st_mysqlnd_authentication_plugin * self,
884+
size_t * auth_data_len,
885+
MYSQLND_CONN_DATA * conn, const char * const user, const char * const passwd,
886+
const size_t passwd_len, zend_uchar * auth_plugin_data, size_t auth_plugin_data_len,
887+
const MYSQLND_SESSION_OPTIONS * const session_options,
888+
const MYSQLND_PFC_DATA * const pfc_data,
889+
zend_ulong mysql_flags
890+
)
891+
{
892+
zend_uchar * ret = NULL;
893+
DBG_ENTER("mysqlnd_caching_sha2_get_auth_data");
894+
DBG_INF_FMT("salt(%d)=[%.*s]", auth_plugin_data_len, auth_plugin_data_len, auth_plugin_data);
895+
*auth_data_len = 0;
896+
897+
DBG_INF("First auth step: send hashed password");
898+
/* copy scrambled pass*/
899+
if (passwd && passwd_len) {
900+
ret = malloc(SHA256_LENGTH + 1);
901+
*auth_data_len = SHA256_LENGTH;
902+
php_mysqlnd_scramble_sha2((zend_uchar*)ret, auth_plugin_data, (zend_uchar*)passwd, passwd_len);
903+
ret[SHA256_LENGTH] = '\0';
904+
DBG_INF_FMT("hash(%d)=[%.*s]", *auth_data_len, *auth_data_len, ret);
905+
}
906+
907+
DBG_RETURN(ret);
908+
}
909+
/* }}} */
910+
911+
static RSA *
912+
mysqlnd_caching_sha2_get_key(MYSQLND_CONN_DATA *conn)
913+
{
914+
RSA * ret = NULL;
915+
const MYSQLND_PFC_DATA * const pfc_data = conn->protocol_frame_codec->data;
916+
const char * fname = (pfc_data->sha256_server_public_key && pfc_data->sha256_server_public_key[0] != '\0')?
917+
pfc_data->sha256_server_public_key:
918+
MYSQLND_G(sha256_server_public_key);
919+
php_stream * stream;
920+
DBG_ENTER("mysqlnd_cached_sha2_get_key");
921+
DBG_INF_FMT("options_s256_pk=[%s] MYSQLND_G(sha256_server_public_key)=[%s]",
922+
pfc_data->sha256_server_public_key? pfc_data->sha256_server_public_key:"n/a",
923+
MYSQLND_G(sha256_server_public_key)? MYSQLND_G(sha256_server_public_key):"n/a");
924+
if (!fname || fname[0] == '\0') {
925+
MYSQLND_PACKET_CACHED_SHA2_RESULT req_packet;
926+
MYSQLND_PACKET_SHA256_PK_REQUEST_RESPONSE pk_resp_packet;
927+
928+
do {
929+
DBG_INF("requesting the public key from the server");
930+
conn->payload_decoder_factory->m.init_cached_sha2_result_packet(&req_packet);
931+
conn->payload_decoder_factory->m.init_sha256_pk_request_response_packet(&pk_resp_packet);
932+
req_packet.request = 1;
933+
934+
if (! PACKET_WRITE(conn, &req_packet)) {
935+
DBG_ERR_FMT("Error while sending public key request packet");
936+
php_error(E_WARNING, "Error while sending public key request packet. PID=%d", getpid());
937+
SET_CONNECTION_STATE(&conn->state, CONN_QUIT_SENT);
938+
break;
939+
}
940+
if (FAIL == PACKET_READ(conn, &pk_resp_packet) || NULL == pk_resp_packet.public_key) {
941+
DBG_ERR_FMT("Error while receiving public key");
942+
php_error(E_WARNING, "Error while receiving public key. PID=%d", getpid());
943+
SET_CONNECTION_STATE(&conn->state, CONN_QUIT_SENT);
944+
break;
945+
}
946+
DBG_INF_FMT("Public key(%d):\n%s", pk_resp_packet.public_key_len, pk_resp_packet.public_key);
947+
/* now extract the public key */
948+
{
949+
BIO * bio = BIO_new_mem_buf(pk_resp_packet.public_key, pk_resp_packet.public_key_len);
950+
ret = PEM_read_bio_RSA_PUBKEY(bio, NULL, NULL, NULL);
951+
BIO_free(bio);
952+
}
953+
} while (0);
954+
PACKET_FREE(&req_packet);
955+
PACKET_FREE(&pk_resp_packet);
956+
957+
DBG_INF_FMT("ret=%p", ret);
958+
DBG_RETURN(ret);
959+
960+
SET_CLIENT_ERROR(conn->error_info, CR_UNKNOWN_ERROR, UNKNOWN_SQLSTATE,
961+
"caching_sha2_server_public_key is not set for the connection or as mysqlnd.sha256_server_public_key");
962+
DBG_ERR("server_public_key is not set");
963+
DBG_RETURN(NULL);
964+
} else {
965+
zend_string * key_str;
966+
DBG_INF_FMT("Key in a file. [%s]", fname);
967+
stream = php_stream_open_wrapper((char *) fname, "rb", REPORT_ERRORS, NULL);
968+
969+
if (stream) {
970+
if ((key_str = php_stream_copy_to_mem(stream, PHP_STREAM_COPY_ALL, 0)) != NULL) {
971+
BIO * bio = BIO_new_mem_buf(ZSTR_VAL(key_str), ZSTR_LEN(key_str));
972+
ret = PEM_read_bio_RSA_PUBKEY(bio, NULL, NULL, NULL);
973+
BIO_free(bio);
974+
DBG_INF("Successfully loaded");
975+
DBG_INF_FMT("Public key:%*.s", ZSTR_LEN(key_str), ZSTR_VAL(key_str));
976+
zend_string_release(key_str);
977+
}
978+
php_stream_close(stream);
979+
}
980+
}
981+
DBG_RETURN(ret);
982+
983+
}
984+
985+
986+
/* {{{ mysqlnd_caching_sha2_get_key */
987+
static size_t
988+
mysqlnd_caching_sha2_get_and_use_key(MYSQLND_CONN_DATA *conn,
989+
const zend_uchar * auth_plugin_data, size_t auth_plugin_data_len,
990+
unsigned char **crypted,
991+
const char * const passwd,
992+
const size_t passwd_len)
993+
{
994+
static RSA *server_public_key;
995+
server_public_key = mysqlnd_caching_sha2_get_key(conn);
996+
997+
DBG_ENTER("mysqlnd_caching_sha2_get_and_use_key(");
998+
999+
if (server_public_key) {
1000+
int server_public_key_len;
1001+
char xor_str[passwd_len + 1];
1002+
memcpy(xor_str, passwd, passwd_len);
1003+
xor_str[passwd_len] = '\0';
1004+
mysqlnd_xor_string(xor_str, passwd_len, (char *) auth_plugin_data, auth_plugin_data_len);
1005+
1006+
server_public_key_len = RSA_size(server_public_key);
1007+
/*
1008+
Because RSA_PKCS1_OAEP_PADDING is used there is a restriction on the passwd_len.
1009+
RSA_PKCS1_OAEP_PADDING is recommended for new applications. See more here:
1010+
http://www.openssl.org/docs/crypto/RSA_public_encrypt.html
1011+
*/
1012+
if ((size_t) server_public_key_len - 41 <= passwd_len) {
1013+
/* password message is to long */
1014+
SET_CLIENT_ERROR(conn->error_info, CR_UNKNOWN_ERROR, UNKNOWN_SQLSTATE, "password is too long");
1015+
DBG_ERR("password is too long");
1016+
DBG_RETURN(0);
1017+
}
1018+
1019+
*crypted = emalloc(server_public_key_len);
1020+
RSA_public_encrypt(passwd_len + 1, (zend_uchar *) xor_str, *crypted, server_public_key, RSA_PKCS1_OAEP_PADDING);
1021+
DBG_RETURN(server_public_key_len);
1022+
}
1023+
DBG_RETURN(0);
1024+
}
1025+
/* }}} */
1026+
1027+
/* {{{ mysqlnd_native_auth_get_auth_data */
1028+
static void
1029+
mysqlnd_caching_sha2_handle_server_response(struct st_mysqlnd_authentication_plugin *self,
1030+
MYSQLND_CONN_DATA * conn,
1031+
const zend_uchar * auth_plugin_data, size_t auth_plugin_data_len,
1032+
const char * const passwd,
1033+
const size_t passwd_len)
1034+
{
1035+
DBG_ENTER("mysqlnd_caching_sha2_handle_server_response");
1036+
MYSQLND_PACKET_CACHED_SHA2_RESULT result_packet;
1037+
conn->payload_decoder_factory->m.init_cached_sha2_result_packet(&result_packet);
1038+
1039+
if (FAIL == PACKET_READ(conn, &result_packet)) {
1040+
DBG_VOID_RETURN;
1041+
}
1042+
1043+
switch (result_packet.response_code) {
1044+
case 3:
1045+
DBG_INF("fast path succeeded");
1046+
DBG_VOID_RETURN;
1047+
case 4:
1048+
if (conn->vio->data->ssl || conn->unix_socket.s) {
1049+
DBG_INF("fast path failed, doing full auth via SSL");
1050+
result_packet.password = (zend_uchar *)passwd;
1051+
result_packet.password_len = passwd_len + 1;
1052+
PACKET_WRITE(conn, &result_packet);
1053+
} else {
1054+
DBG_INF("fast path failed, doing full auth without SSL");
1055+
result_packet.password_len = mysqlnd_caching_sha2_get_and_use_key(conn, auth_plugin_data, auth_plugin_data_len, &result_packet.password, passwd, passwd_len);
1056+
PACKET_WRITE(conn, &result_packet);
1057+
efree(result_packet.password);
1058+
}
1059+
DBG_VOID_RETURN;
1060+
case 2:
1061+
// The server tried to send a key, which we didn't expect
1062+
// fall-through
1063+
default:
1064+
php_error_docref(NULL, E_WARNING, "Unexpected server respose while doing caching_sha2 auth: %i", result_packet.response_code);
1065+
}
1066+
1067+
DBG_VOID_RETURN;
1068+
}
1069+
/* }}} */
1070+
1071+
static struct st_mysqlnd_authentication_plugin mysqlnd_caching_sha2_auth_plugin =
1072+
{
1073+
{
1074+
MYSQLND_PLUGIN_API_VERSION,
1075+
"auth_plugin_caching_sha2_password",
1076+
MYSQLND_VERSION_ID,
1077+
PHP_MYSQLND_VERSION,
1078+
"PHP License 3.01",
1079+
"Johannes Schlüter <[email protected]>",
1080+
{
1081+
NULL, /* no statistics , will be filled later if there are some */
1082+
NULL, /* no statistics */
1083+
},
1084+
{
1085+
NULL /* plugin shutdown */
1086+
}
1087+
},
1088+
{/* methods */
1089+
mysqlnd_caching_sha2_get_auth_data,
1090+
mysqlnd_caching_sha2_handle_server_response
8241091
}
8251092
};
8261093
#endif
8271094

1095+
8281096
/* {{{ mysqlnd_register_builtin_authentication_plugins */
8291097
void
8301098
mysqlnd_register_builtin_authentication_plugins(void)
8311099
{
8321100
mysqlnd_plugin_register_ex((struct st_mysqlnd_plugin_header *) &mysqlnd_native_auth_plugin);
8331101
mysqlnd_plugin_register_ex((struct st_mysqlnd_plugin_header *) &mysqlnd_pam_authentication_plugin);
8341102
#ifdef MYSQLND_HAVE_SSL
1103+
mysqlnd_plugin_register_ex((struct st_mysqlnd_plugin_header *) &mysqlnd_caching_sha2_auth_plugin);
8351104
mysqlnd_plugin_register_ex((struct st_mysqlnd_plugin_header *) &mysqlnd_sha256_authentication_plugin);
8361105
#endif
8371106
}

Diff for: ext/mysqlnd/mysqlnd_auth.h

+3-20
Original file line numberDiff line numberDiff line change
@@ -31,26 +31,9 @@ mysqlnd_auth_handshake(MYSQLND_CONN_DATA * conn,
3131
unsigned int server_charset_no,
3232
zend_bool use_full_blown_auth_packet,
3333
const char * const auth_protocol,
34-
const zend_uchar * const auth_plugin_data,
35-
const size_t auth_plugin_data_len,
36-
char ** switch_to_auth_protocol,
37-
size_t * switch_to_auth_protocol_len,
38-
zend_uchar ** switch_to_auth_protocol_data,
39-
size_t * switch_to_auth_protocol_data_len
40-
);
41-
42-
enum_func_status
43-
mysqlnd_auth_handshake(MYSQLND_CONN_DATA * conn,
44-
const char * const user,
45-
const char * const passwd,
46-
const size_t passwd_len,
47-
const char * const db,
48-
const size_t db_len,
49-
const MYSQLND_SESSION_OPTIONS * const session_options,
50-
zend_ulong mysql_flags,
51-
unsigned int server_charset_no,
52-
zend_bool use_full_blown_auth_packet,
53-
const char * const auth_protocol,
34+
struct st_mysqlnd_authentication_plugin * auth_plugin,
35+
const zend_uchar * const orig_auth_plugin_data,
36+
const size_t orig_auth_plugin_data_len,
5437
const zend_uchar * const auth_plugin_data,
5538
const size_t auth_plugin_data_len,
5639
char ** switch_to_auth_protocol,

Diff for: ext/mysqlnd/mysqlnd_connection.c

+4
Original file line numberDiff line numberDiff line change
@@ -666,9 +666,13 @@ MYSQLND_METHOD(mysqlnd_conn_data, connect)(MYSQLND_CONN_DATA * conn,
666666

667667
{
668668
const MYSQLND_CSTRING scheme = { transport.s, transport.l };
669+
/* This will be overwritten below with a copy, but we can use it during authentication */
670+
conn->unix_socket.s = (char *)socket_or_pipe.s;
669671
if (FAIL == conn->m->connect_handshake(conn, &scheme, &username, &password, &database, mysql_flags)) {
672+
conn->unix_socket.s = NULL;
670673
goto err;
671674
}
675+
conn->unix_socket.s = NULL;
672676
}
673677

674678
{

0 commit comments

Comments
 (0)