【PHP】SSL コンテキストのマニュアルに掲載されていないオプション
ext/openssl/xp_ssl.c を眺めていたら SSL コンテキストのマニュアルに掲載されていないオプションをいくつか見つけたので記事を書くことにした。この記事を投稿したのは2024年7月5日時点である
-
alpn_protocolsSSL/TLS ハンドシェイク時にソケットが提示するプロトコル。カンマ区切りの文字列 (GET_VER_OPT_STRING) -
min_proto_versionプロトコルのバージョンの下限。整数(GET_VER_OPT_LONG)。 -
max_proto_versionプロトコルのバージョンの上限。整数(GET_VER_OPT_LONG) -
no_ticketクライアントサイドがセッションチケットをリクエストしないようにする(OpenSSL のSSL_OP_NO_TICKETに対応)。ブール値 (GET_VER_OPT) -
security_levelvoid SSL_CTX_set_security_level(SSL_CTX *ctx, int level)のマニュアルを参照としか書かれていない。レベルは 0 から 5 (256 ビットレベルのセキュリティ)がある。
C 言語の OpenSSL のマニュアルによると SSL コンテキストの生成(SSL_CTX_new())の際に TLS_method(), TLS_server_method(), TLS_client_method() を選び、TLS のバージョンの下限上限を設定する SSL_CTX_set_min_proto_version() と SSL_CTX_set_max_proto_version() を併用するとよいそうだ。
逆に TLSv1_2_method()、TLSv1_2_server_method()、TLSv1_2_client_method() 特定のバージョンを決め打ちして SSL コンテキストを生成すべきではないという。実際 TLS 1.3 のメソッドは存在しない。
PHP の SSL コンテキストに話を戻すとバージョンの下限上限は min_proto_version や max_proto_version で指定する。記事の執筆時点で下限は STREAM_CRYPTO_METHOD_TLSv1_2_SERVER か STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT がよいだろう。
PHP の curl ext では下限の CURLOPT_SSLVERSION オプションに CURL_SSLVERSION_TLSv1_2 を指定すればよい。
OpenSSL では SSL_OP_NO_TLSv1_2 や SSL_OP_NO_TLSv1_3 などの定数を SSL_CTX_set_options() を使うことができるがおすすめしないという
ついでに書いておくと ext/openssl/xp_ssl.c では SSL_CTX *SSL_CTX_new(const SSL_METHOD *method) に渡す method のデフォルト値が method = sslsock->is_client ? SSLv23_client_method() : SSLv23_server_method(); と古いものになっていた。公式マニュアルを読むと SSLv23_method()、SSLv23_server_method()、SSLv23_client_method() は削除されており、プリプロセッサーマクロによって TLS_method()、TLS_server_method()、TLS_client_method() に置き換えられているという。
alpn_protocols の仕様を見ると文字列 (GET_VER_OPT_STRING) であり、コンマで区切られた文字列を php_openssl_alpn_protos_parse が解析して SSL_CTX_set_alpn_protos に渡す処理が行なわれている。h2,http/1 のような例がうまくいくのかは実際に試してみないとわからない。
int SSL_CTX_set_alpn_protos(SSL_CTX *ctx, const unsigned char *protos, unsigned int protos_len) のマニュアルを読むと const unsigned char protos の仕様は「バイト数、プロトコルの文字列の並び」となっているそうだ。マニュアルのコードを転載する。nghttp2 の作者も解説記事を公開している
unsigned char vector[] = {
6, 's', 'p', 'd', 'y', '/', '1',
8, 'h', 't', 't', 'p', '/', '1', '.', '1'
};
unsigned int length = sizeof(vector);
no_ticket に対応する SSL_OP_NO_TICKET のマニュアルを読んだが、メリットはなさそうだ。OpenSSL はステーレスなセッションチケットをデフォルトにしていることを覚えていればよいのではないだろうか
ついでに Python 3.13.0b3 の ssl モジュール を調べるとバージョンの下限は TLS 1.2 で上限はなしとなっていた
>>> import ssl
>>> ssl.create_default_context()
<ssl.SSLContext object at 0x7f4a13f629f0>
>>> ctx = ssl.create_default_context()
>>> ctx.minimum_version
<TLSVersion.TLSv1_2: 771>
>>> ctx.maximum_version
<TLSVersion.MAXIMUM_SUPPORTED: -1>
ctx.options の値は次のとおり
>>> ctx.options
<Options.OP_NO_COMPRESSION|OP_ENABLE_MIDDLEBOX_COMPAT|OP_CIPHER_SERVER_PREFERENCE|OP_NO_SSLv3|OP_ALL: 2186412112>
ssl.OP_NO_TICKET を追加すると次のようになる
>>> ctx.options |= ssl.OP_NO_TICKET
>>> ctx.options
<Options.OP_NO_TICKET|OP_NO_COMPRESSION|OP_ENABLE_MIDDLEBOX_COMPAT|OP_CIPHER_SERVER_PREFERENCE|OP_NO_SSLv3|OP_ALL: 2186428496>
>>>
ALPN の設定は次のように行う
>>> ctx.set_alpn_protocols(['h2', 'http/1.1'])
Discussion