OpenSSL での TLSv1.3 の使用について。

投稿アップデート情報  追記1・2(5/9)

 実際には,全く取り掛かれていない TLSv1.3 (2018.8 RFC8446 になりました) なんだけれども,読み始めたら面白くてためになりそうだったので,調子に乗って訳してみた。くりくりさんが仰っていたように,相当変わるんだな。
 元の文章は, Matt Caswell 氏の Using TLS1.3 With OpenSSL である。タイムスタンプは MAY 4TH, 2017 11:00 AM になっている。ブログの HTML ソースから見るに, UTC みたい。変なところは,ご容赦。明らかな誤訳は,指摘いただければありがたいです m(_”_)m。以下、訳文です。

————————————————–

OpenSSL での TLSv1.3 の使用

 今度の OpenSSL 1.1.1 のリリースには TLSv1.3 のサポートが含まれます。新リリースはバイナリと API において OpenSSL 1.1.0 と互換です。お使いのアプリケーションがすでに OpenSSL 1.1.0 をサポートしている場合,理論的には,新バージョンの OpenSSL にアップグレードするだけで,自動的に TLSv1.3 が使えるようになるはずです。しかし実際には,開発者や設置者が注意すべき問題がいくつかあります。この記事ではそのあたりの話をしたいと思います。

TLS1.2 以下との相違点

 TLSv1.3 においては仕様が大幅に書き換えられたので, TLSv2.0 と呼ぶべきではないかとの議論もありました ― しかし,最終的に TLSv1.3 と呼ばれることに落ち着きました。TLSv1.3 には大幅な変更が加えられ,いくつかの点では TLSv1.2 以下と全く違う動きをします。大雑把に要約をすると,大きな相違点は以下の通りです:

  • TLSv1.3 で使用できる暗号スイートは限定されており,これら以外の旧い暗号スイートでは TLSv1.3 接続ができません。
  • この新しい暗号スイートは,これまでと異なる方法で定義され,証明書タイプ (RSA, DSA, ECDSA など) や鍵交換メカニズム (DHE や ECHDE など)を指定していません。このことは,暗号の構成に影響する可能性があります。
  • クライアントは, ClientHello 時に「key_share」をサーバに送信します。このため「グループ」構成に注意が必要です。
  • セッションは,メインハンドシェイクが完了するまで確立されません。ハンドシェイクの終了とセッションの確立の間にはタイムラグが存在する可能性があります(理論上は,セッションが全く確立されない可能性もあります)。このことは「セッション再開コード」の動きに大きく影響します。
  • TLSv1.3 接続では再ネゴシエーションを行うことはできません。
  • より多くのハンドシェイクが暗号化されました。
  • より多くの種類のメッセージに拡張機能を追加できるようになりました(これはカスタム拡張機能 API と Certificate Transparency に影響します)。
  • DSA証明書は TLSv1.3 接続では使用できなくなりました。

 現段階では TLSv1.3 のサポートのみにとどまっていることに留意してください。 DTLSv1.3 はまだ仕様変更の初期段階にあり,今回の OpenSSL のサポートには含まれていません。

TLSv1.3 標準の現在の状況

 この記事の執筆時点では, TLSv1.3 は未だ draft の段階です。基本 draft は TLS ワーキンググループによって,定期的に新バージョンが発行されています。 draft をもとにする実装に当たっては,使用されている draft バージョンの特定が必要です。なぜならば,異なる draft に基づく実装は,相互に互換性がないからです。

 OpenSSL 1.1.1 は (少なくとも) TLSv1.3 が完成されるまでは,正式にリリースされません。一方, OpenSSL git の master branch は開発用の TLSv1.3 コードを含んでいるので,テスト目的の使用が可能です (つまり,これらは本番環境用ではないのだということです)。 tls1.h ファイルの TLS1_3_VERSION_DRAFT_TXT のマクロの値を調べることにより, OpenSSL チェックアウトで実装されている TLSv1.3 の draft のバージョンを確認できます。このマクロは,正式に最終版がリリースされると削除されます。

 TLSv1.3 サポートで OpenSSL をコンパイルするには, “config” または “Configure” において “enable-tls1_3″ オプションを使用する必要があります。

 現時点で, OpenSSL は TLSv1.3 の ” draft -20″ をもとにする実装をしていますが,他の多くのライブラリは,今もより古い draft での実装になっています。特に,多くの一般的なブラウザが「 draft -18」を用いていることに注意してください。これは,相互運用性の問題の一般的な原因です。 draft -18 の相互運用性は BoringSSL, NSS, picotls でテストされています。

 現在, OpenSSL git ソースコードレポには 2 つのブランチが存在します:
これらは「 draft -18」と「 draft -19」をもとにしています。これらのブランチを利用することで,他の TLSv1.3 実装との相互運用性をテストすることができます。これらのブランチは,将来必要がなくなったとみなされたときには,削除されることになっています。

暗号スイート

 OpenSSL は,下記の 5 つの TLSv1.3 暗号スイートをサポートしています:

  • TLS13-AES-256-GCM-SHA384
  • TLS13-CHACHA20-POLY1305-SHA256
  • TLS13-AES-128-GCM-SHA256
  • TLS13-AES-128-CCM-8-SHA256
  • TLS13-AES-128-CCM-SHA256

 最初の 3 つが, DEFAULT の暗号スイートグループです。これは明示的に暗号スイートを設定しない場合,この3つが自動的に TLSv1.3 のネゴシエートに使われることを意味しています。

 全ての TLSv1.3 暗号スイートは HIGH 暗号スイートに含まれます。CHACHA20, AES, AES128, AES256, AESGCM, AESCCM, AESCCM8 には,これらの名前から予想される暗号スイートのサブセットが含まれています。鍵交換と認証プロパティは TLSv1.2 以下の暗号スイートの定義の一部でしたが, TLSv1.3 においてはそうではなくなったので, ECDHE, ECDSA, RSA などには, TLSv1.3 の暗号スイートは含まれていません。

 暗号スイートを明示的に設定する場合は, TLSv1.3 互換の暗号スイートを誤って排除しないようにしなければなりません。クライアント側で TLSv1.3 が有効であっても TLSv1.3 暗号スイートが設定されていない場合は,接続は失敗し下記のようなエラーメッセージが表示されます (サーバ側が TLSv1.3 をサポートしているしていないにかかわらず同じです):

140460646909376:error:141A90B5:SSL routines:ssl_cipher_list_to_bytes:no ciphers available:ssl/statem/statem_clnt.c:3562:No ciphers enabled for max supported SSL/TLS version

 同様に,サーバ側で TLSv1.3 が有効になっているが TLSv1.3 暗号スイートが設定されていない場合も,接続は失敗し下記のようなエラーメッセージが表示されます (クライアント側が TLSv1.3 をサポートしているしていないにかかわらず同じです):

140547390854592:error:141FC0B5:SSL routines:tls_setup_handshake:no ciphers available:ssl/statem/statem_lib.c:108:No ciphers enabled for max supported SSL/TLS version

 例えば, ECDHE:!COMPLEMENTOFDEFAULT という選択文字列を設定したとすると, OpenSSL 1.1.0 では何の問題もなく, DEFAULT の暗号スイートが選ばれ ECDHE が鍵交換に使われました。しかし, ECDHE グループには TLSv1.3 暗号スイートが含まれていないので,この設定は TLSv1.3 が有効になった OpenSSL 1.1.1 では失敗するということになります。

 問題の発生を避けるためには, TLSv1.3 暗号スイートを明示的に用いましょう。例えば,下のように:

"TLS13-CHACHA20-POLY1305-SHA256:TLS13-AES-128-GCM-SHA256:TLS13-AES-256-GCM-SHA384:ECDHE:!COMPLEMENTOFDEFAULT"

 その選択文字列にどんな暗号スイートが含まれているかは, openssl ciphers -s -v で確認できます:

$ openssl ciphers -s -v "ECDHE:!COMPLEMENTOFDEFAULT"

 実際に設定するときは,上記コマンドで少なくとも 1 つの暗号スイートが, TLSv1.3 でサポートされていることを確認してください。

グループ

 TLSv1.3 においては,クライアントは鍵交換のために “group” というものを選定します。執筆時点では, OpenSSL は ECDHE グループだけをサポートしています (OpenSSL 1.1.1 が実際にリリースになるころには, DHE グループもサポートされる可能性があります)。クライアントは ClientHello において,選定したグループの “key_share” をサーバに送ります。

 サポートするグループのリストは設定可能です。クライアントは,サーバがサポートしていないグループを選択することも可能で,この場合サーバは,クライアントにサポートしている別のグループの key_share を送信するよう要求します。これはこの間接続が確立されたままだということですが(相互にサポートされているグループが存在すると仮定して),余計な round trip をサーバに強いることになるため,パフォーマンスに影響がでます。クライアントが最初のインスタンスで,サーバがサポートするグループを選択するのが理想です。

 実際には,ほとんどのクライアントは,最初の key_share に X25519 か P-256 を使用します。少なくともこの 2 つのグループをサポートするようにサーバーを構成すれば,最高のパフォーマンスを得ることが出来るでしょう。クライアントは,最初の key_share に 2 つのうちの 1 つを使用します。これがデフォルトです (OpenSSL クライアントの場合は,これに X25519 を使用します)。

 グループ設定では, TLSv1.2 以下の許可グループも制御します。アプリケーションに OpenSSL 1.1.0 でグループを設定していた場合は,その設定を見直して TLSv1.3 で不都合がないか確認する必要があります。最初に名前を挙げた(すなわち,最も好ましい)グループが, OpenSSL クライアントが最初の key_share で使用するグループになります。

 アプリケーションは, SSL_CTX_set1_groups() あるいは同様の関数を使用して,グループリストを構成できます(詳細はここを参照)。 SSL_CONF 型の設定ファイルを使用する場合は Groups あるいは Curves コマンドを使用できます(ここを参照)。

セッション

 TLSv1.2 以下においては,セッションはハンドシェイクの一部として確立されていました。このセッションはその後の接続で利用されて,短縮ハンドシェイクを実現していました。アプリケーションは,ハンドシェイクが完了したのち, SSL_get1_session() (あるいは同様の関数) でセッションでのハンドルを取得することがありました。詳細はこちらをご覧ください。

 それに対し TLSv1.3 では,セッションはメインハンドシェイクが完了するまで確立されません。サーバはセッションの詳細を含む別のポストハンドシェイクメッセージをクライアントに送ります。これは普通はメインハンドシェイク完了直後に行われるのですが,遅れたりあるいは全く行われなかったりすることもあります。

 仕様は,アプリケーションにセッションの使用を 1 度に限ることを推奨しています(強制されているわけではありませんが)。このため,一部のサーバーはクライアントに複数のセッションメッセージを送信します。「1 度に限る」推奨を適用するために,アプリケーションは SSL_CTX_remove_session() を使用してセッションを non-resumable とマークすることができます(これによりキャッシュから削除されます)。

 旧 SSL_get1_session() および類似の API は, TLSv1.2 以下で作成されたクライアントアプリケーションに対して期待通りに動作しないかもしれません。具体的には,セッションの詳細を含むサーバーメッセージが返される前にクライアントアプリケーションが SSL_SESSION を呼び出すと, SSL_SESSIONオブジェクトは返されますが,再開は失敗し改めてフルハンドシェイクが行われます。この場合,複数のセッションがサーバーによって送信されていたときは,最後のセッションだけが SSL_get1_session() によって返されます。

 クライアントアプリケーションの開発者は代わりに SSL_CTX_sess_set_new_cb() を使うことを検討してください (ここを参照)。これは,新セッションが確立されるたびに呼びだされるコールバックメカニズムを提供します。サーバが複数のセッションメッセージを送った際, 1 回の接続で複数回呼び出されることが可能です。

 SSL_CTX_sess_set_new_cb() は OpenSSL 1.1.0 でも使用できました。すでにこの API を使っているアプリケーションではそのまま動きますが,想定外の時間にコールバックが発生するかもしれないということに留意してください。例えば,ポストハンドシェイク時とか。

 OpenSSL サーバは,メインハンドシェイクが終わり次第,すぐにセッションの詳細を送ろうとします。サーバアプリケーションにとっては,このポストハンドシェイクの状態がメインハンドシェイクの一部なので, SSL_get1_session() の呼び出しは,前と同じように動作するはずです。

カスタム拡張機能と Certificate Transparency

 TLSv1.2 以下においては,最初の ClientHello と ServerHello メッセージに「拡張機能」を含めることができました。これにより,すべての状況で適用されるわけではなかったり基本仕様書が書かれた時点では予想できなかった特徴や機能を,仕様に付け加えることができました。 OpenSSL は「組み込み」拡張機能をいくつかサポートしています。

 さらに,カスタム拡張機能 API は,アプリケーション開発者が OpenSSL に組み込まれていない新しい拡張機能のサポートを追加するための基本機能のいくつかを提供していました。

 カスタム拡張機能 API の上位には, “serverinfo” APIが組み込まれていました。これにより,実行時に構成できるさらに基本的なインターフェイスが提供されます。これの 1 つが, Certificate Transparency です。 OpenSSL は, Certificate Transparency のクライアント側の組み込みをサポートしますが,サーバー側の組み込みサポートはありません。しかし,これは “serverinfo” ファイルを使って簡単に達成できます。 Certificate Transparency 情報を含むserverinfoファイルは, OpenSSL 内で設定することができ,適切にクライアントに送り返されます。

 TLSv1.3 においては,拡張機能の使用が大幅に拡張され,それを含むことができるメッセージがさらに多くなりました。さらに, TLSv1.2 以下で適用可能だった拡張機能のいくつかは TLSv1.3 では使用できなくなり,一部の拡張機能は ServerHello メッセージから EncryptedExtensions メッセージに移動されました。旧いカスタム拡張機能 API には,メッセージを拡張機能に関連付ける機能がありません。そのため,新しいカスタム拡張機能 API が必要となりました。

 旧い API は動作しますが,カスタム拡張機能は TLSv1.2 以下のネゴシエートにおいてのみ追加されます。すべての TLS バージョンで機能するカスタム拡張をアプリケーションに追加するには,開発者は新しい API を使う必要があります (詳細はここを参照)。

 ”serverinfo” データフォーマットも,メッセージを拡張機能に関連付けることができるように変更されました。 TLSv1.3 でアプリケーションが動作できるようにするには, “serverinfo” ファイルの形式を “version 2” 形式に更新する必要があります(詳細はここここを参照)。

再ネゴシエーション

 TLSv1.3 には再ネゴシエーションの機能がありませんので, SSL_renegotiate() あるいは SSL_renegotiate_abbreviated() の呼び出しは,失敗します。

 再ネゴシエーションの最も一般的な使用例は,接続キーを更新することです。TLSv1.3 においては,関数 SSL_key_update() がこの目的に使用できます(https://www.openssl.org/docs/manmaster/man3/SSL_key_update.htmlを参照)。

DSA 証明書

 DSA 証明書は TLSv1.3 では使用できません。サーバアプリケーションが DSA 証明書を使って TLSv1.3 接続を行うと,失敗して下記のようなエラーメッセージが表示されます:

140348850206144:error:14201076:SSL routines:tls_choose_sigalg:no suitable signature algorithm:ssl/t1_lib.c:2308:

 代わりに, ECDSA か RSA 証明書を使ってください。

最後に

 TLSv1.3 は大きく前進し,いくつかのエキサイティングな新機能を備えていますが,アップグレードする際には注意深くしないとトラブルを招くかもしれない点がいくつかあります。ほとんどの場合,これらの問題には比較的簡単な解決策があります。アプリケーション開発者は, TLSv1.3 でより効果的に作業するために,コードを見直して何かを更新する必要があるかどうかを検討してください。同様に,アプリケーション設置者も構成を見直さなければいけません。

————————————————–
追記1(5/9):
 ありがたいことに Shigeki Ohtsu さんからツイートをいただきました。「草案-19とかはブランチ名なのでdraft-19の方が自然」ということですので,訂正しました。

追記2(5/9):
 Shigeki Ohtsu さんからのサジェスチョンで,掲載許可をいただくために今日のお昼ごろ Matt Caswell 氏にメールを差し上げていたのですが,先ほど,許可のメールをいただきました。これで,心配しないで訳文を掲載できます。お二人とも,ありがとうございました。

「OpenSSL での TLSv1.3 の使用について。」への2件のフィードバック

  1. あすのかぜさんにもでていたけど、
    openssl-1.1.0が動く環境なら、1.1.1は問題なく動く可能性が高い。つまり、apache2.4でも対応できるということですね。tls1.3早くつかいたいものです。

    1. こんにちは。

      鷹君を見てます。セッツンがよれよれになってて,辛いです。

      Twitterのフォローに,大津さんとあすのかぜさんを追加しました。夕べ, Using TLS1.3 With OpenSSL を読んでつくづく思いましたが,結構,変更点がありますね。

      > openssl-1.1.0が動く環境なら、1.1.1は問題なく動く可能性が高い。

      は確かなようですが,再ネゴシエーションの問題なんか,くりくりさんとこでも引っかかってるようだし, openssl-1.1.0 が動くところまで行ってない環境では,移行は結構トラブルがありそうです。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください