Debugging mysqlnd.so ~mysql_native_passwordが廃止される未来に備えて~
こんにちわ。せじまです。PHPには疎いんですが、今回は珍しくPHPの話をします。
はじめに
2023-07-18 に MySQL 8.1.0 がリリースされました。いくつか気になる変更が入っているのですが、今回は
The mysql_native_password authentication plugin now is deprecated and subject to removal in a future version of MySQL. CREATE USER, ALTER USER, and SET PASSWORD operations now insert a deprecation warning into the server error log if an account attempts to authenticate using mysql_native_password as an authentication method. (Bug #35336317)
に関連するところについてです。
MySQL8.0でcaching_sha2_passwordがデフォルトの認証プラグインになりましたが、8.0がリリースされた当時、古いクライアントライブラリはcaching_sha2_passwordに対応できていませんでした。default_authentication_plugin=mysql_native_password
という設定を入れてMySQL8.0を導入された方は、少なくなかったでしょう。
将来、MySQLの新しいバージョンでmysql_native_passwordが廃止されたとき、こういった対応は取れなくなってしまいますので、mysql_native_passwordからcaching_sha2_passwordに移行していく必要が発生します。
caching_sha2_password の仕様など
caching_sha2_passwordの仕様は、Doxygenで生成されたソースコードのドキュメントを読むのが確実でしょう。
caching_sha2_passwordに対応するということは、Fast authenticationに対応するだけでなく、Complete authenticationにどのように対応するかが問題になります。
Fast authentication
雑にいいますと、クライアントはパスワードのハッシュ(のScramble)を最初に送信するんですが、それがmysqld上のキャッシュに存在していたらそこで認証終了で、それがFast authenticationになります。
Complete authentication
パスワードのハッシュがmysqld上のキャッシュに存在していなければ、"TCP with TLS OR Socket Or Shared Memory connection"、安全な通信経路であれば、そのままパスワードを送信して認証します。安全な通信経路でなければ、mysqldから公開鍵を取得し(クライアントがすでに公開鍵を持っているならそれを使い)パスワードを暗号化し、mysqldで復号化して認証します。パスワード認証できたら、そのパスワードのハッシュをmysqld上にキャッシュします。これがComplete authenticationです。
libmysqlclientや公式のmysqlコマンドラインクライアントの場合
mysql や mysqladmin などの標準 MySQL クライアントは libmysqlclient ベースであるため、話はシンプルです。
- MYSQL_OPT_SSL_MODEや--ssl-modeのデフォルトはPREFERREDなので、繋げるなら自動でSSLで接続します。
- ssl-mode=DISABLEDな場合は、クライアントは、必要な公開キーのクライアント側コピーを使用するか、サーバーから公開キーをリクエストできます。MYSQL_OPT_GET_SERVER_PUBLIC_KEYや--get-server-public-keyを指定すれば、mysqldから公開鍵を取得できます。
ほとんどの場合はデフォルトでSSL経由で接続し、Complete authenticationについて意識することもないでしょう。もしSSLで繋げない環境でも、--get-server-public-keyを指定するなどすれば良いだけの話です。
PHPの場合は?
MySQLの中のひと曰く、PHP7.4はcaching_sha2_password対応されてる とのことで、実際、7.4.2でFix caching_sha2_password authがmergeされているようです。
なるほど確かにcaching_sha2_password動きそうなんですが、mysqlndのドキュメントを見ると、SHA-256 Authentication Pluginについての記述はあるんですが、--get-server-public-keyのようなオプションは見当たりません。
どないなっとんねん???
と思ったので、mysqlndのソースコード眺めたり、PHPで検証するためのコードを書いたんですが、libmysqlclientと異なる振る舞いだったので、確証が持てませんでした。ただ、
PHPはOSSなんだからgdbでbreakpointはればいいか
と思ったので、デバッガで検証してみることにしました。
今回はお手軽に、 Ubuntu 20.04 LTS on WSL2で検証しました。便利な時代になったものです。
テスト環境のMySQL
WSL2上でデバッグビルド済みのMySQL8.0.34があったので、そこで
1 2 3 |
CREATE USER sejima@'127.0.0.1' IDENTIFIED BY 'sejima'; GRANT ALL ON *.* TO sejima@'127.0.0.1'; |
などしました。
PHPでComplete authenticationを検証するためのスクリプト
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
<?php printf("Client library version: %s\n", mysqli_get_client_info()); $mysqli = new mysqli('127.0.0.1', 'sejima', 'sejima', 'mysql'); if ($mysqli->connect_errno) { echo "Errno: " . $mysqli->connect_errno . PHP_EOL; echo "Error: " . $mysqli->connect_error . PHP_EOL; exit; } echo "PHP" . phpversion() . PHP_EOL; $sql = "SHOW SESSION STATUS LIKE 'Ssl_cipher'"; if (!$result = $mysqli->query($sql)) { echo "Query: " . $sql . PHP_EOL; echo "Errno: " . $mysqli->errno . PHP_EOL; echo "Error: " . $mysqli->error . PHP_EOL; exit; } $rows = $result->fetch_all(MYSQLI_NUM); foreach ($rows as $row) { printf("%s (%s)\n", $row[0], $row[1]); } $result->free(); $sql = "select user, plugin from user where user = 'sejima'"; if (!$result = $mysqli->query($sql)) { echo "Query: " . $sql . PHP_EOL; echo "Errno: " . $mysqli->errno . PHP_EOL; echo "Error: " . $mysqli->error . PHP_EOL; exit; } $rows = $result->fetch_all(MYSQLI_NUM); foreach ($rows as $row) { printf("%s (%s)\n", $row[0], $row[1]); } $mysqli->query("FLUSH PRIVILEGES"); $result->free(); $mysqli->close(); ?> |
実行例は次のようなものになります。
1 2 3 4 5 6 7 |
sejima:~$ php test.php Client library version: mysqlnd 7.4.3-4ubuntu2.19 PHP7.4.3-4ubuntu2.19 Ssl_cipher () sejima (caching_sha2_password) sejima:~$ |
細かいところを補足します。
127.0.0.1
ここをlocalhostにすると socket file 経由の接続になり、安全な通信経路とみなされてしまい、公開鍵を取得する可能性がなくなってしまいます。
SHOW SESSION STATUS LIKE 'Ssl_cipher';
SSL経由であればTLS_AES_256_GCM_SHA384のような値が、SSL経由でなければemptyが返ってきます。これにより、SSL経由で接続されているかが確認できます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
sejima:~/src/mysql/mysql-8.0.34/bld$ bin/mysql -u root -h 127.0.0.1 -p Enter password: Welcome to the MySQL monitor. Commands end with ; or \g. Your MySQL connection id is 76 Server version: 8.0.34-debug Source distribution Copyright (c) 2000, 2023, Oracle and/or its affiliates. Oracle is a registered trademark of Oracle Corporation and/or its affiliates. Other names may be trademarks of their respective owners. Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. mysql> SHOW SESSION STATUS LIKE 'Ssl_cipher'; +---------------+------------------------+ | Variable_name | Value | +---------------+------------------------+ | Ssl_cipher | TLS_AES_256_GCM_SHA384 | +---------------+------------------------+ 1 row in set (0.01 sec) mysql> exit Bye sejima:~/src/mysql/mysql-8.0.34/bld$ bin/mysql -u root -h localhost -p Enter password: Welcome to the MySQL monitor. Commands end with ; or \g. Your MySQL connection id is 77 Server version: 8.0.34-debug Source distribution Copyright (c) 2000, 2023, Oracle and/or its affiliates. Oracle is a registered trademark of Oracle Corporation and/or its affiliates. Other names may be trademarks of their respective owners. Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. mysql> SHOW SESSION STATUS LIKE 'Ssl_cipher'; +---------------+-------+ | Variable_name | Value | +---------------+-------+ | Ssl_cipher | | +---------------+-------+ 1 row in set (0.01 sec) mysql> exit Bye sejima:~/src/mysql/mysql-8.0.34/bld$ |
FLUSH PRIVILEGES
mysqld上のパスワードのハッシュのキャッシュがクリアされます。次回接続時、Complete authenticationを強制するためのクリーンアップの処理として入れています。
mysqlndが公開鍵を取得しているところをgdbで確認する
上記の検証用スクリプトを実行したらSsl_cipherが空だったので、SSL経由じゃないんだろうと思うわけですが、公開鍵を取得しているその瞬間を確認したいわけです。というわけで、Debug Symbol Packageなどを入れていきます。
PHPのインストール
1 2 |
$ sudo apt install php7.4-cli php7.4-mysql |
PHPのソースコードをインストール
/etc/apt/sources.list でdeb-srcがコメントアウトされているようであれば、コメントアウトを無効化して
1 2 |
$ sudo apt-get update |
して
1 2 |
$ apt-get source php7.4-cli php7.4-mysql |
します。
Debug Symbol Packages
Ubuntu公式の Debug Symbol Packages を参考にやっていきます。
1 2 3 4 5 |
echo "deb http://ddebs.ubuntu.com $(lsb_release -cs) main restricted universe multiverse deb http://ddebs.ubuntu.com $(lsb_release -cs)-updates main restricted universe multiverse deb http://ddebs.ubuntu.com $(lsb_release -cs)-proposed main restricted universe multiverse" | \ sudo tee -a /etc/apt/sources.list.d/ddebs.list |
1 2 |
sudo apt install ubuntu-dbgsym-keyring |
1 2 |
sudo apt-get update |
あとは
1 2 |
sudo apt install php7.4-cli-dbgsym php7.4-mysql-dbgsym |
します。
公開鍵ファイルの確認
caching_sha2_password_auto_generate_rsa_keysはデフォルトでONで、caching_sha2_password_public_key_pathのデフォルトはpublic_key.pemなので、
1 2 3 4 5 6 7 8 9 |
mysql> select @@datadir; +-----------------------------------------------+ | @@datadir | +-----------------------------------------------+ | /home/sejima/src/mysql/mysql-8.0.34/bld/data/ | +-----------------------------------------------+ 1 row in set (0.00 sec) mysql> |
な環境であれば
1 2 3 4 5 6 7 8 9 10 11 12 |
sejima:~/src/mysql/mysql-8.0.34/bld$ cat data/public_key.pem -----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArLGxm1WIOs1MJJxqcrcH U0RKPeIMu//K070VNgQof/iWH8NL+yU4q90pS9mEtWnwXr0gTbhkfJZkvJB3dNpz VJRQJPv2gN81pQ/94poRe6lH+GNL5+5nEN6SMiLM2fpM4OgLdM+oPs/Ko2UroUug cKnhRswIcZ72w2puYujpdZZ3ffiIaM522gT6O0K10eFwvCu2FdiK9Elkb/qYJ9Ay 4IRZD8NVDOgkamqIZ2JzIJBYV6NMhDOncq2xM6yY0va0ufredSdlWnT7bBHfzMNN vj4xacmO++abgXsnN+68uXOA5E0Igig7MP8UrGku9F8I0dcRLzdWwXsE41TUApfu cQIDAQAB -----END PUBLIC KEY----- sejima:~/src/mysql/mysql-8.0.34/bld$ |
というように配置されています。
gdbでbreakpoint張って検証用スクリプトを実行
apt-get source して ~/php7.4-7.4.3 とかにソースコードが展開されてると思いますので、
1 2 3 |
$ cd ~/php7.4-7.4.3 $ gdb /usr/bin/php |
します。もし万が一gdbが入ってなかったら、sudo apt install gdbしてください。
とりあえず、
https://github.com/php/php-src/blob/php-7.4.3/ext/mysqlnd/mysqlnd_auth.c#L1112
と
https://github.com/php/php-src/blob/php-7.4.3/ext/mysqlnd/mysqlnd_auth.c#L978
あたりにbreakpointはって、mysqlnd_auth.c:978で止まったら
1 2 |
(gdb) p pk_resp_packet.public_key |
して、さきほどの公開鍵が来てるかどうか確認すれば良いんじゃないか、というところです。というわけで
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
sejima:~/php7.4-7.4.3$ gdb /usr/bin/php GNU gdb (Ubuntu 9.2-0ubuntu1~20.04.1) 9.2 Copyright (C) 2020 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html> This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. Type "show copying" and "show warranty" for details. This GDB was configured as "x86_64-linux-gnu". Type "show configuration" for configuration details. For bug reporting instructions, please see: <http://www.gnu.org/software/gdb/bugs/>. Find the GDB manual and other documentation resources online at: <http://www.gnu.org/software/gdb/documentation/>. For help, type "help". Type "apropos word" to search for commands related to "word"... Reading symbols from /usr/bin/php... Reading symbols from /usr/lib/debug/.build-id/8b/320de7b512fa926aec4ec48d08bdb2e99251a2.debug... warning: File "/home/sejima/php7.4-7.4.3/.gdbinit" auto-loading has been declined by your `auto-load safe-path' set to "$debugdir:$datadir/auto-load". To enable execution of this file add add-auto-load-safe-path /home/sejima/php7.4-7.4.3/.gdbinit line to your configuration file "/home/sejima/.gdbinit". To completely disable this security protection add set auto-load safe-path / line to your configuration file "/home/sejima/.gdbinit". For more information about this security protection see the "Auto-loading safe path" section in the GDB manual. E.g., run from the shell: info "(gdb)Auto-loading safe path" (gdb) info share No shared libraries loaded at this time. (gdb) b mysqlnd_auth.c:1112 No source file named mysqlnd_auth.c. Make breakpoint pending on future shared library load? (y or [n]) y Breakpoint 1 (mysqlnd_auth.c:1112) pending. (gdb) b mysqlnd_auth.c:978 No source file named mysqlnd_auth.c. Make breakpoint pending on future shared library load? (y or [n]) y Breakpoint 2 (mysqlnd_auth.c:978) pending. (gdb) info break Num Type Disp Enb Address What 1 breakpoint keep y <PENDING> mysqlnd_auth.c:1112 2 breakpoint keep y <PENDING> mysqlnd_auth.c:978 (gdb) |
という感じです。
ちなみに、ここが今回のポイントです。info shareで 'No shared libraries loaded at this time.' で、 info break で Address が <PENDING> になっており、 mysqlnd.so はまだロードされていない状態です。 rbreak mysqlnd_caching_sha2_get_key とかやってもこの段階では張れません。gdbは賢いので、mysqlnd.soがロードされるとbreakpointで止まるようになります。(本当はvscodeでやれればよかったんですが、vscode力が低いおっさんなので、今回は手っ取り早くgdbのCLIでやりました。今後の課題にしたいなと思います。)
で、 run /home/sejma/test.php などやりますと
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
(gdb) run /home/sejima/test.php Starting program: /usr/bin/php /home/sejima/test.php [Thread debugging using libthread_db enabled] Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1". Client library version: mysqlnd 7.4.3-4ubuntu2.19 Breakpoint 1, mysqlnd_caching_sha2_get_key (conn=0x7ffff5475000) at ./ext/mysqlnd/mysqlnd_auth.c:1112 1112 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); (gdb) c Continuing. Breakpoint 2, mysqlnd_caching_sha2_get_key (conn=0x7ffff5475000) at ./ext/mysqlnd/mysqlnd_auth.c:978 978 BIO_free(bio); (gdb) p pk_resp_packet.public_key $2 = (zend_uchar *) 0x7ffff5491200 "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArLGxm1WIOs1MJJxqcrcH\nU0RKPeIMu//K070VNgQof/iWH8NL+yU4q90pS9mEtWnwXr0gTbhkfJZkvJB3dNpz\nVJRQJPv2gN81pQ/94poRe6lH+GNL5+5nEN6SMiLM2fp"... (gdb) c Continuing. PHP7.4.3-4ubuntu2.19 Ssl_cipher () sejima (caching_sha2_password) warning: Temporarily disabling breakpoints for unloaded shared library "/usr/lib/php/20190902/mysqlnd.so" [Inferior 1 (process 10840) exited normally] (gdb) |
さきほどの公開鍵を取得しているところが確認できました。これは p pk_resp_packet.public_key してから迅速に c しているので、正常終了していますが、ゆっくりやると
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
(gdb) run /home/sejima/test.php Starting program: /usr/bin/php /home/sejima/test.php [Thread debugging using libthread_db enabled] Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1". Client library version: mysqlnd 7.4.3-4ubuntu2.19 Breakpoint 1, mysqlnd_caching_sha2_get_key (conn=0x7ffff5475000) at ./ext/mysqlnd/mysqlnd_auth.c:1112 1112 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); (gdb) c Continuing. Breakpoint 2, mysqlnd_caching_sha2_get_key (conn=0x7ffff5475000) at ./ext/mysqlnd/mysqlnd_auth.c:978 978 BIO_free(bio); (gdb) p pk_resp_packet.public_key $1 = (zend_uchar *) 0x7ffff5491200 "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArLGxm1WIOs1MJJxqcrcH\nU0RKPeIMu//K070VNgQof/iWH8NL+yU4q90pS9mEtWnwXr0gTbhkfJZkvJB3dNpz\nVJRQJPv2gN81pQ/94poRe6lH+GNL5+5nEN6SMiLM2fp"... (gdb) c Continuing. PHP Warning: Packets out of order. Expected 6 received 5. Packet size=50 in /home/sejima/test.php on line 5 PHP Warning: mysqli::__construct(): (HY000/2006): MySQL server has gone away in /home/sejima/test.php on line 5 Errno: 2006 Error: MySQL server has gone away warning: Temporarily disabling breakpoints for unloaded shared library "/usr/lib/php/20190902/mysqlnd.so" [Inferior 1 (process 10836) exited normally] (gdb) |
となります。内部的なタイムアウトとかに引っかかるか、なんらかの timing issue な気がしますが、今回の主題から外れるので、いったん忘れます。
せっかくなので、Breakpoint 1で止まってるときの info share や info break なども掲載しておきます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
(gdb) run /home/sejima/test.php Starting program: /usr/bin/php /home/sejima/test.php [Thread debugging using libthread_db enabled] Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1". Client library version: mysqlnd 7.4.3-4ubuntu2.19 Breakpoint 1, mysqlnd_caching_sha2_get_key (conn=0x7ffff5475000) at ./ext/mysqlnd/mysqlnd_auth.c:1112 1112 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); (gdb) info break Num Type Disp Enb Address What 1 breakpoint keep y 0x00007ffff50f71c0 in mysqlnd_caching_sha2_get_key at ./ext/mysqlnd/mysqlnd_auth.c:1112 breakpoint already hit 1 time 2 breakpoint keep y 0x00007ffff50f75a1 in mysqlnd_caching_sha2_get_key at ./ext/mysqlnd/mysqlnd_auth.c:978 (gdb) info share From To Syms Read Shared Object Library 0x00007ffff7fd0100 0x00007ffff7ff2684 Yes /lib64/ld-linux-x86-64.so.2 0x00007ffff7fb4110 0x00007ffff7fb8997 Yes (*) /lib/x86_64-linux-gnu/libargon2.so.1 0x00007ffff7f9b720 0x00007ffff7faa11c Yes /lib/x86_64-linux-gnu/libresolv.so.2 0x00007ffff7e553c0 0x00007ffff7efbfa8 Yes /lib/x86_64-linux-gnu/libm.so.6 0x00007ffff7e43220 0x00007ffff7e44179 Yes /lib/x86_64-linux-gnu/libdl.so.2 0x00007ffff7cb5e50 0x00007ffff7ddeb8e Yes (*) /lib/x86_64-linux-gnu/libxml2.so.2 0x00007ffff7c14770 0x00007ffff7c5fbaa Yes (*) /lib/x86_64-linux-gnu/libssl.so.1.1 0x00007ffff7996000 0x00007ffff7b2ff10 Yes (*) /lib/x86_64-linux-gnu/libcrypto.so.1.1 0x00007ffff788e2e0 0x00007ffff78f234e Yes (*) /lib/x86_64-linux-gnu/libpcre2-8.so.0 0x00007ffff7872280 0x00007ffff7882f9b Yes (*) /lib/x86_64-linux-gnu/libz.so.1 0x00007ffff7824280 0x00007ffff785bd89 Yes (*) /lib/x86_64-linux-gnu/libsodium.so.23 0x00007ffff7648630 0x00007ffff77bd27d Yes /lib/x86_64-linux-gnu/libc.so.6 0x00007ffff7609ae0 0x00007ffff7619535 Yes /lib/x86_64-linux-gnu/libpthread.so.0 0x00007ffff7480920 0x00007ffff7564967 Yes (*) /lib/x86_64-linux-gnu/libicuuc.so.66 0x00007ffff73f53c0 0x00007ffff740c3b6 Yes (*) /lib/x86_64-linux-gnu/liblzma.so.5 0x00007ffff5932040 0x00007ffff59320f9 Yes (*) /lib/x86_64-linux-gnu/libicudata.so.66 0x00007ffff57ed160 0x00007ffff58d5452 Yes (*) /lib/x86_64-linux-gnu/libstdc++.so.6 0x00007ffff57375e0 0x00007ffff5748045 Yes (*) /lib/x86_64-linux-gnu/libgcc_s.so.1 0x00007ffff5658fc0 0x00007ffff56b4868 Yes (*) /usr/lib/php/20190902/opcache.so 0x00007ffff5637720 0x00007ffff563ad70 Yes /lib/x86_64-linux-gnu/librt.so.1 0x00007ffff50e6f10 0x00007ffff51050fe Yes /usr/lib/php/20190902/mysqlnd.so 0x00007ffff561f200 0x00007ffff562ae92 Yes (*) /usr/lib/php/20190902/pdo.so 0x00007ffff560f360 0x00007ffff561201e Yes (*) /usr/lib/php/20190902/calendar.so 0x00007ffff56450e0 0x00007ffff5645bac Yes (*) /usr/lib/php/20190902/ctype.so 0x00007ffff50c7920 0x00007ffff50cda9c Yes (*) /usr/lib/php/20190902/exif.so 0x00007ffff509bb20 0x00007ffff50b4ac6 Yes (*) /usr/lib/php/20190902/ffi.so 0x00007ffff508d230 0x00007ffff5092a46 Yes (*) /lib/x86_64-linux-gnu/libffi.so.7 0x00007ffff4b13de0 0x00007ffff4b25b29 Yes (*) /usr/lib/php/20190902/fileinfo.so 0x00007ffff4b01bc0 0x00007ffff4b0874a Yes (*) /usr/lib/php/20190902/ftp.so 0x00007ffff56072c0 0x00007ffff560809c Yes (*) /usr/lib/php/20190902/gettext.so 0x00007ffff4af2920 0x00007ffff4af7ddd Yes (*) /usr/lib/php/20190902/iconv.so 0x00007ffff4ae45a0 0x00007ffff4aea837 Yes (*) /usr/lib/php/20190902/json.so 0x00007ffff4ac5d80 0x00007ffff4ad57a0 Yes /usr/lib/php/20190902/mysqli.so --Type <RET> for more, q to quit, c to continue without paging--q Quit (gdb) c Continuing. Breakpoint 2, mysqlnd_caching_sha2_get_key (conn=0x7ffff5475000) at ./ext/mysqlnd/mysqlnd_auth.c:978 978 BIO_free(bio); (gdb) c Continuing. PHP7.4.3-4ubuntu2.19 Ssl_cipher () sejima (caching_sha2_password) warning: Temporarily disabling breakpoints for unloaded shared library "/usr/lib/php/20190902/mysqlnd.so" [Inferior 1 (process 10842) exited normally] (gdb) |
というように、Addressは解決され、/usr/lib/php/20190902/mysqlnd.soがロードされているのも確認できます。mysqlnd.soがアンロードされると、breakpointが無効化されるのも確認できます。
確認できた mysqlnd の caching_sha2_password の振る舞い
For connections by accounts that authenticate with caching_sha2_password and RSA key pair-based password exchange, the server does not send the RSA public key to clients by default.
とあるので、Complete authenticationに公開鍵を用いるのは、あくまでオプション扱いになっています。実際、libmysqlclientを使った公式のmysqlコマンドラインクライアントは、デフォルトではSSLで接続してComplete authenticationを行います。
一方、公式ドキュメントには
RSA-based password exchange is available regardless of the SSL library against which MySQL is linked.
ともあります。(このあたりの経緯は知りませんが)もしかするとmysqlnd開発者は、SSLのライブラリへの依存度を下げるべく、公開鍵を用いてComplete authenticationするように実装したのでしょうか?
せっかくなので、require_secure_transportを有効にして、先程の検証スクリプトを実行してみましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
sejima:~/src/mysql/mysql-8.0.34/bld$ ./bin/mysql -u root -p Enter password: Welcome to the MySQL monitor. Commands end with ; or \g. Your MySQL connection id is 90 Server version: 8.0.34-debug Source distribution Copyright (c) 2000, 2023, Oracle and/or its affiliates. Oracle is a registered trademark of Oracle Corporation and/or its affiliates. Other names may be trademarks of their respective owners. Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. mysql> select @@require_secure_transport; +----------------------------+ | @@require_secure_transport | +----------------------------+ | 0 | +----------------------------+ 1 row in set (0.00 sec) mysql> set global require_secure_transport=on; Query OK, 0 rows affected (0.00 sec) mysql> select @@require_secure_transport; +----------------------------+ | @@require_secure_transport | +----------------------------+ | 1 | +----------------------------+ 1 row in set (0.00 sec) mysql> exit Bye sejima:~/src/mysql/mysql-8.0.34/bld$ php ~/test.php Client library version: mysqlnd 7.4.3-4ubuntu2.19 PHP Warning: mysqli::__construct(): (HY000/3159): Connections using insecure transport are prohibited while --require_secure_transport=ON. in /home/sejima/test.php on line 5 Errno: 3159 Error: Connections using insecure transport are prohibited while --require_secure_transport=ON. sejima:~/src/mysql/mysql-8.0.34/bld$ |
コケました。
PHP 7.4.3-4ubuntu2.19 で mysqlnd のデフォルトは --ssl-mode=PREFERREDではなく、--ssl-mode=DISABLED --get-server-public-key 相当なんだろうなというのが確認できました。
おわりに
少し調べてみたのですが、
- mysqlndがcaching_sha2_passwordのComplete authenticationに対して、どのように振る舞っているのか
- mysqlndをgdbでデバッグする
といった話が、インターネット上にあまり転がってなさそうだったので、少々難儀していたのですが、とりあえずデバッガで動かせば良いんだからOSS最高ですねと改めて再認識しました。もし万が一、PHPでcaching_sha2_password使うために、公開鍵のファイルやSSL通信するための証明書のファイルをmysqlndにオプションで渡すのが必須になるなら、caching_sha2_passwordに移行するハードル上がるのではと危惧していました。しかし、それはどうやら杞憂に終わりそうでホッとしています。
デバッガと言いますと、MySQL 8.1.0や8.0.34で、ビルド周りにまた変更が入りました。macOSやWindowsでは、8.0.33と同じようにはビルドできない場合がありそうなので、MySQL 8.1.0をWindows/macOS/WSL2でデバッグビルドする話について、近日中にまた書こうかなと思っています。
Special Thanks
今回、MYSQL_OPT_SSL_MODEのSSL_MODE_PREFERREDやMYSQL_OPT_GET_SERVER_PUBLIC_KEY的なものがPHP側になさそうだと嘆いていたら、yoku0825さん曰く「(sha256_passwordのプラグインの方には)ありそうな気がしますー」とのことだったので、「PHPでcaching_sha2_passwordは別のプラグインだしmysqlndのデフォルトの振る舞いがlibmysqlclientと異なりそうだし、PHPのcaching_sha2_password対応についてはデバッガで動かしてみないと何ともわからんなぁ」となったので、gdbを持ち出す運びとなりました。
yoku0825さんには、この場を借りて御礼申し上げます。