【lwIP】Tips #2|TLS1.3通信をWiresharkで復号する方法|mbedTLS 3.x/4.x対応
(English Version Here.)
このnoteでは、lwIPで実際に起こり得る不具合・脆弱性・再現方法・最小修正の考え方やデバッグのノウハウをシリーズで解説しています。
▶ 日本語記事一覧はこちら:lwIPトラブル対策室|日本語記事一覧
■ こんな時に役立ちます
WiresharkでキャプチャしたのにTLS通信の中身が見えない。TLS 1.3に切り替えたら今まで使えていたキーログが使えなくなった。そんな経験はありませんか?
次のような場面で、この記事が役立ちます。
TLS 1.3接続でWiresharkに `Application Data` しか表示されず中身が見えない
MQTT over TLS 1.3 の通信異常の原因を調べたい
TLS 1.3 ハンドシェイクの途中で切断されるがどのメッセージで失敗しているか分からない
証明書エラーで切断されているはずなのに、TLS 1.2のときのように平文のAlert(理由コード)が見えない
TLS 1.2 の `CLIENT_RANDOM` 形式ではTLS 1.3 の復号ができないと気づいた
mbedTLS 3.x または 4.x でキーログを出力する方法が分からない
TLS 1.3対応アプリケーションの開発・デバッグを効率化したい
lwIP + mbedTLS 3.x/4.x 環境で TLS1.3 通信をWiresharkで復号する手順を解説します。
※TLS 1.2 についてはシークレットの種類と出力手順が TLS 1.3 と異なるため、別記事(Tips #1)で解説しています。wolfSSL・OpenSSLは本記事の対象外です。
■ 対象環境
mbedTLSバージョン: 3.1.x以降 / 4.x
lwIPバージョン: 2.x
TLSバージョン: 1.3
本記事の主対象は、現在の組み込み向けSDKで採用が多い mbedTLS 3.1.x〜3.6.x です。4.0.x についても同一のコードでそのまま対応できます。
■ この記事で分かること
TLS 1.3 ではハンドシェイクのどの範囲が暗号化され、TLS 1.2 と比べてWiresharkで何が見えなくなるのか(復号環境の重要性が一段と高まる理由)
TLS 1.3 のシークレット構造が TLS 1.2 と根本的に異なる理由と、NSS Key Log 形式で1接続あたり4行必要な仕組み
mbedTLS 3.1.x〜3.6.x、4.0.x でTLS 1.3キーログを出力するコールバックの実装方法
Wiresharkで平文を確認する手順と、復号できない場合のトラブルシューティング(チェックリスト付き)
■ TLS 1.3はハンドシェイクから暗号化される
TLS 1.2では、ハンドシェイクの大部分が平文で流れていました。Wiresharkでキャプチャするだけで、復号しなくても次のように読み取れます。
TLSv1.2 Record Layer: Handshake Protocol: Client Hello
TLSv1.2 Record Layer: Handshake Protocol: Server Hello
TLSv1.2 Record Layer: Handshake Protocol: Certificate ← 証明書の中身が平文で見える
TLSv1.2 Record Layer: Handshake Protocol: Server Key Exchange
TLSv1.2 Record Layer: Alert (Level: Fatal, Description: Unknown CA) ← 検証失敗時は理由コードも平文サーバー証明書の内容(発行者・有効期限・SAN)
選択された暗号スイートや鍵交換パラメータ
ハンドシェイクが失敗したときのAlertの理由コード(unknown_ca、bad_certificate など)
つまりTLS 1.2では、「ハンドシェイクのどこで・なぜ失敗したか」は復号しなくても分かることが多く、キーログが必要になるのは主にアプリケーションデータの中身を見たい場合でした。
TLS 1.3では、この前提が崩れます。RFC 8446の規定により、ServerHelloより後のハンドシェイクメッセージはすべて暗号化されます。同じ接続をキャプチャすると、こう見えます。
TLSv1.3 Record Layer: Handshake Protocol: Client Hello ← 平文で見えるのはここと
TLSv1.3 Record Layer: Handshake Protocol: Server Hello ← ここまで
TLSv1.3 Record Layer: Change Cipher Spec ← 旧バージョン互換のためのダミー
TLSv1.3 Record Layer: Application Data
TLSv1.3 Record Layer: Application Data`Application Data` と表示されているレコードの実体は、暗号化された EncryptedExtensions・Certificate・CertificateVerify・Finished です。サーバー証明書すら平文では見えません。
さらに重要な点として、証明書検証エラーなどハンドシェイク失敗時のAlertも暗号化されます。クライアントが証明書を拒否してAlertを送るのはハンドシェイク鍵の確立後なので、TLS 1.2のように「Description: Unknown CA」が平文で読めることはなく、中身不明の暗号化レコードにしか見えません。
整理すると、キーログによる復号の位置づけはこう変わります。
TLS 1.2:アプリケーションデータの中身を見るための手段
TLS 1.3:それに加えて、証明書・拡張・失敗理由など、ハンドシェイク自体のトラブルシュートに必須の手段
「ハンドシェイクの途中で切断されるが、原因がパケットから読み取れない」── TLS 1.3への移行でこの状況は確実に増えており、復号環境の重要性はTLS 1.2のときより一段と高くなっています。

■ TLS1.3通信をWiresharkで復号する仕組み
Wiresharkでキャプチャした直後、TLS 1.3通信はこのように見えます。

前節で見たとおり、アプリケーションデータだけでなくハンドシェイクの大部分もこの「中身の見えない暗号化レコード」になっています。
NSS Key Log形式のキーログファイルをWiresharkに設定すると、暗号化されていた通信の中身(ハンドシェイクメッセージを含む)が平文で読めるようになります。

TLS 1.2 のキーログは1接続につき1行です。
CLIENT_RANDOM <ClientRandom 32バイト hex> <MasterSecret 48バイト hex>TLS 1.3 では鍵導出の仕組みが根本的に変わり、フェーズごとに異なるシークレットが生成されます。Wiresharkが復号するには最低4行のキーログが必要です。
CLIENT_HANDSHAKE_TRAFFIC_SECRET <ClientRandom 32バイト hex> <secret Hash長 hex>
SERVER_HANDSHAKE_TRAFFIC_SECRET <ClientRandom 32バイト hex> <secret Hash長 hex>
CLIENT_TRAFFIC_SECRET_0 <ClientRandom 32バイト hex> <secret Hash長 hex>
SERVER_TRAFFIC_SECRET_0 <ClientRandom 32バイト hex> <secret Hash長 hex>secretの長さは、ネゴシエートした暗号スイートのハッシュ関数に依存します。TLS_AES_128_GCM_SHA256 のようなSHA-256系では32バイト、TLS_AES_256_GCM_SHA384 のようなSHA-384系では48バイトになります。
TLS 1.2 の `CLIENT_RANDOM` 形式ではTLS 1.3 の通信は復号できません。

■ PCブラウザやcurlコマンドなら簡単
WiresharkでTLS 1.3通信を復号するにも、TLS 1.2 と同様に `SSLKEYLOGFILE` 環境変数を使う方法が標準的です。
WindowsやLinuxでChromeやFirefoxを使っている場合、`SSLKEYLOGFILE` 環境変数を設定してブラウザを起動するだけで、キーログが自動的にファイルへ出力されます。
Windowsの例(コマンドプロンプト):
set SSLKEYLOGFILE=C:\keylog.txt
start chromeLinuxの例:
export SSLKEYLOGFILE=/tmp/keylog.txt
google-chrome &あとはWiresharkの設定画面(編集 → 設定 → Protocols → TLS)で `(Pre)-Master-Secret log filename` にそのファイルを指定するだけです。`Application Data` だったパケットが平文で見えるようになります。
ChromeやFirefoxはTLSライブラリに `SSLKEYLOGFILE` の検出機能が組み込まれているため、環境変数を設定するだけで自動的にキーログを出力します。
curl(OpenSSLビルド版)でも同じ環境変数が使えます。
Windowsの例(コマンドプロンプト):
set SSLKEYLOGFILE=C:\keylog.txt
curl https://example.com⚠️ 注意:Windows標準搭載のcurlは動作しない場合があります
Windows 10/11 に標準搭載されている `curl.exe` は、OpenSSL ではなく Windows 独自の Schannel バックエンドでビルドされています。Schannel は `SSLKEYLOGFILE` に対応していないため、環境変数を設定してもキーログが出力されません。
動作しない場合は、curl公式サイト(curl.se/windows) から OpenSSL ビルド版 の curl を入手してください。
■ 組み込みでは同じようにはできない
組み込み機器でlwIP + mbedTLSを使ってTLS通信を実装している場合、`SSLKEYLOGFILE` 環境変数は使えません。
mbedTLSでTLS 1.3のキーログを出力するには、ハンドシェイク中に各シークレットが確定した瞬間に呼び出されるコールバック関数を実装し、シリアル UART などに出力する処理を追加する必要があります。実装方法はmbedTLSのバージョンによって異なります。
■ 有料パートについて
TLS 1.3では、復号環境がなければアプリケーションデータどころか「ハンドシェイクがなぜ失敗したのか」の特定すら困難です。組み込み機器のTLSを1.3へ移行するなら、キーログ出力はデバッグ環境の必須装備と言えます。
有料パートでは、lwIP + mbedTLS 3.x/4.x のTLS 1.3通信を復号するための実装例を具体的に解説します。
またlwIPの全体像や、各ベンダーSDKごとの差異、なぜlwIPの不具合情報が重要なのか、といった背景については、導入記事で整理しています。
【lwIP】よくある不具合と対策まとめ|組み込みTCP/IPトラブル事例集
■ 実装手順
mbedTLS のバージョンによって実装手順が一部異なります。
ここから先は
¥ 800
この記事が気に入ったらチップで応援してみませんか?
