💨

【PHP】SSL コンテキストのマニュアルに掲載されていないオプション

2024/07/05に公開

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_versionmax_proto_version で指定する。記事の執筆時点で下限は STREAM_CRYPTO_METHOD_TLSv1_2_SERVERSTREAM_CRYPTO_METHOD_TLSv1_2_CLIENT がよいだろう。

PHP の curl ext では下限の CURLOPT_SSLVERSION オプションに CURL_SSLVERSION_TLSv1_2 を指定すればよい。

OpenSSL では SSL_OP_NO_TLSv1_2SSL_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