一覧SC-09-1 · TLSの保護範囲

TLS 1.3のハンドシェイク

提案と選択、相手の署名、交渉全体の改変検出を具体的なフィールドへ対応付けます。

説明

TLS 1.3は、接続のパラメーターを合意し、相手の鍵を認証し、暗号化用の鍵を導出します。暗号スイート、鍵交換グループ、証明書の署名方式、アプリのプロトコルはそれぞれ別の値です。

固定のTLS接続記録をバイト列から解析します。ClientHelloとServerHello、暗号化されたハンドシェイク、CertificateVerifyとFinishedを順に調べ、何がどの時点で確認されるかを追います。

提案と選択、相手の署名、交渉全体の改変検出を具体的なフィールドへ対応付けます。

1レコードの外側を確認する

TLSレコードのヘッダーは型1バイト・互換用バージョン2バイト・長さ2バイトです。handshakeは型22、暗号化されたレコードの外側は23です。

from sc09 import * print([(direction,r[0]レコード型を読む,r[1:3].hex(),int.from_bytes(r[3:5]長さフィールドを読む,"big")) for direction,r in flights()送受信のレコードを分解する])

レコードの外側だけでは暗号化された中身の種類は分かりません。型20は互換性用のChangeCipherSpecであり、この接続の鍵交換そのものではありません。

2ClientHelloの提案を読む

Helloの先頭にはhandshake型1バイトと長さ3バイトがあります。提案されたTLS 1.3スイートを取り出します。

ch,sh = hellos() client = hello(ch)ClientHelloのフィールドを解析する print(ch[:4].hex(),client["legacy_version"],client["ciphers"]) print(client["extensions"][43]対応バージョン拡張を読む.hex())

legacy_versionの0303だけでTLS 1.2と判断しません。supported_versions拡張43でTLS 1.3の0304を提案しています。

3ServerHelloの選択を照合する

サーバーが選んだスイートとバージョンを読みます。

server = hello(sh,True)ServerHelloを解析する print(server["ciphers"],server["extensions"][43].hex()) print(server["ciphers"][0] in client["ciphers"]提案の範囲内か照合する) print(T["cipher"],T["version"])

1302はTLS_AES_256_GCM_SHA384、0304はTLS 1.3です。選択されたスイートはクライアントの提案に含まれています。

4鍵交換グループと公開値を読む

key_share拡張51の先頭2バイトがグループ、次の2バイトが公開値の長さです。

share = server["extensions"][51]選択された鍵共有を取得する print(share[:2]グループ識別子を読む.hex(),int.from_bytes(share[2:4],"big"),share[4:].hex())

001dはX25519、公開値は32バイトです。AES-256-GCMというスイート名だけで鍵交換グループは決まりません。

5暗号化されたハンドシェイクを復号する

試料には解析用のtraffic secretも保存しています。そこからkeyとivを導出し、レコード番号ごとのnonceで復号します。

msgs = server_messages()サーバーのハンドシェイクレコードを復号する print([(NAMES[m[0]]復号したメッセージ型を表示する,int.from_bytes(m[1:4],"big")) for m in msgs])

EncryptedExtensions、Certificate、CertificateVerify、Finishedが得られます。証明書自体もServerHelloの後の暗号化された領域にあります。

6ALPNの合意を確認する

EncryptedExtensionsのALPN拡張16を取り出します。

ee = extensions(msgs[0][6:])暗号化された拡張の一覧を読む print(ee[16]ALPN拡張を取得する.hex(),T["alpn"])

0003026832はリスト長3・名前長2・h2です。TLSのバージョンや暗号方式とは別に、HTTP/2を使う合意が記録されています。

7証明書の鍵で交渉への署名を検証する

CertificateVerifyは文脈文字列とここまでのハンドシェイクのハッシュを署名します。試料の証明書はEd25519です。

print(msgs[2][4:6]署名方式を読む.hex(),int.from_bytes(msgs[2][6:8],"big")) print(certificate_verify()証明書の公開鍵で交渉への署名を検証する)

署名方式0807、署名長64バイトで検証に成功します。証明書経路と名前の確認に加え、その鍵を相手が使えることを交渉記録へ結び付けます。

8Finishedの検証値を再計算する

Finishedはここまでのハンドシェイクのハッシュに対し、導出したfinished_keyでHMACを計算した値です。

print(msgs[-1][4:]受領したFinishedの検証値を表示する.hex()) print(finished_check()交渉全体のハッシュからHMACを再計算する)

48バイトの値が一致します。証明書の署名だけでなく、合意内容を含む交渉全体が対応していることを確認します。

まとめ

暗号スイート・鍵交換グループ・署名方式・ALPNは別の値です。

ServerHelloの後のハンドシェイクは暗号化されます。

CertificateVerifyとFinishedは、鍵の保有と交渉の整合性を確認します。

操作履歴と解説を振り返れます。

Python 3.15 / cryptography 50.0.2 / 公開試料と固定時刻

1
from sc09 import * print([(direction,r[0],r[1:3].hex(),int.from_bytes(r[3:5],"big")) for direction,r in flights()])
[('client', 22, '0301', 240), ('server', 22, '0303', 122), ('server', 20, '0303', 1), ('server', 23, '0303', 32), ('server', 23, '0303', 738), ('server', 23, '0303', 89), ('server', 23, '0303', 69), ('client', 20, '0303', 1), ('client', 23, '0303', 69), ('server', 23, '0303', 250), ('server', 23, '0303', 250)]
2
ch,sh = hellos() client = hello(ch) print(ch[:4].hex(),client["legacy_version"],client["ciphers"]) print(client["extensions"][43].hex())
010000ec 0303 ['1302', '1303', '1301']
020304
3
server = hello(sh,True) print(server["ciphers"],server["extensions"][43].hex()) print(server["ciphers"][0] in client["ciphers"]) print(T["cipher"],T["version"])
['1302'] 0304
True
TLS_AES_256_GCM_SHA384 TLSv1.3
4
share = server["extensions"][51] print(share[:2].hex(),int.from_bytes(share[2:4],"big"),share[4:].hex())
001d 32 aaf850301b8981a3752d0686aa06fb8899698be131d0dcdd9f231133fa10ad2a
5
msgs = server_messages() print([(NAMES[m[0]],int.from_bytes(m[1:4],"big")) for m in msgs])
[('EncryptedExtensions', 11), ('Certificate', 717), ('CertificateVerify', 68), ('Finished', 48)]
6
ee = extensions(msgs[0][6:]) print(ee[16].hex(),T["alpn"])
0003026832 h2
7
print(msgs[2][4:6].hex(),int.from_bytes(msgs[2][6:8],"big")) print(certificate_verify())
0807 64
True
8
print(msgs[-1][4:].hex()) print(finished_check())
49bdefb9ba5ec46a177d7cc328935ca96d934a5203388902a1c7878f768121f2ceafcfe3cdf465b89f42a56ce7b34f5d
True

演習完了