【PHP】SSL コンテキストのマニュアルに掲載されていないオプション
ext/openssl/xp_ssl.c
を眺めていたら SSL コンテキストのマニュアルに掲載されていないオプションをいくつか見つけたので記事を書くことにした。この記事を投稿したのは2024年7月5日時点である
-
alpn_protocols
SSL/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_level
void 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