Upgrade to Pro — share decks privately, control downloads, hide ads and more …

PHPで TLSのプロトコルを実装してみるをもう一度しゃべりたい

PHPで TLSのプロトコルを実装してみるをもう一度しゃべりたい

PHPerKaigi2026のアンカンファレンスで喋った内容です。
PHPerKaigi2026の2026/03/21 11:35〜 Track B で「PHPでTLSのプロトコルを実装してみる」で喋った内容に対するフィードバックをいただいて資料編集したものになります。

編集前の資料はこちらになります。
https://speakerdeck.com/higaki_program/phpde-tlsnopurotokoruwoshi-zhuang-sitemiru

Avatar for higaki

higaki

March 29, 2026
Tweet

More Decks by higaki

Other Decks in Programming

Transcript

  1. TLSとは SSL/TLS はセッション層に位置するセキュアプロトコルで、通信の 暗号化、データ完全性の確保、サーバ(場合によりクライアント)の 認証を行うことができる。 • 通信の暗号化 • データ完全性の確保 •

    サーバ(場合によりクライアント)の認証 引用:IPA TLS 暗号設定 ガイドライン [https://www.ipa.go.jp/security/crypto/guideline/gmcbt80000005ufv-att/ipa-cryptrec-gl-3001-3.1.1.pdf]
  2. TLS1.2とTLS1.3の大きな違い(補足スライド) 参考:RFC8446 Major Differences from TLS 1.2 [https://datatracker.ietf.org/doc/html/rfc8446#section-1.2] • ServerHello

    以降の通信が暗号化 • 1-RTTでハンドシェイクが完結 • 0-RTTモードが追加 • 鍵交換は DHE、ECDHE、PSK のみが規定され、いずれかの利用が必須になった • HKDF-Expand, HKDF-Extractを使った鍵導出 に変更 • 共通鍵暗号は AES-GCM、AES-CCM、ChaCha20-Poly1305 のみが規定された • 署名は RSA-PSS、RSASSA-PKCS1-v1_5、ECDSA が必須になった 参考:IPA TLS 暗号設定 ガイドライン [https://www.ipa.go.jp/security/crypto/guideline/gmcbt80000005ufv-att/ipa-cryptrec-gl-3001-3.1.1.pdf]
  3. TLSとは(再掲) SSL/TLS はセッション層に位置するセキュアプロトコルで、通信の 暗号化、データ完全性の確保、サーバ(場合によりクライアント)の 認証を行うことができる。 • 通信の暗号化 • データ完全性の確保 •

    サーバ(場合によりクライアント)の認証 引用:IPA TLS 暗号設定 ガイドライン [https://www.ipa.go.jp/security/crypto/guideline/gmcbt80000005ufv-att/ipa-cryptrec-gl-3001-3.1.1.pdf]
  4. TLSとは(再掲) SSL/TLS はセッション層に位置するセキュアプロトコルで、通信の 暗号化、データ完全性の確保、サーバ(場合によりクライアント)の 認証を行うことができる。 • 通信の暗号化 • データ完全性の確保 •

    サーバ(場合によりクライアント)の認証 引用:IPA TLS 暗号設定 ガイドライン [https://www.ipa.go.jp/security/crypto/guideline/gmcbt80000005ufv-att/ipa-cryptrec-gl-3001-3.1.1.pdf]
  5. 証明書 関連 TLS1.3 やり取りはこんな流れ Client Server ApplicationData 鍵交換 Server公開鍵 Server公開鍵

    Client公開鍵 Client秘密鍵 Server秘密鍵 Client公開鍵 暗号化するための共通鍵が欲しい
  6. 証明書 関連 TLS1.3 やり取りはこんな流れ Client Server ApplicationData 鍵交換 Server公開鍵 Server公開鍵

    Client公開鍵 Client秘密鍵 Server秘密鍵 Client公開鍵 暗号化するための共通鍵が欲しい
  7. 証明書 関連 TLS1.3 やり取りはこんな流れ Client Server ApplicationData 鍵交換 Server公開鍵 Server公開鍵

    Client公開鍵 Client秘密鍵 Server秘密鍵 Client公開鍵 暗号化するための共通鍵が欲しい
  8. 証明書 関連 TLS1.3 やり取りはこんな流れ Client Server ApplicationData 鍵交換 Server公開鍵 Server公開鍵

    Client公開鍵 Client秘密鍵 Server秘密鍵 Client公開鍵 共通の値 共通の値 Diffie-Hellman(DH) 楕円曲線DH(ECDH) Diffie-Hellman(DH) 楕円曲線DH(ECDH) 暗号化するための共通鍵が欲しい
  9. 証明書 関連 TLS1.3 やり取りはこんな流れ Client Server ApplicationData 鍵交換 Server公開鍵 Server公開鍵

    Client公開鍵 Client秘密鍵 Server秘密鍵 Client公開鍵 共通の値 共通の値 暗号化するための共通鍵が欲しい
  10. 証明書 関連 TLS1.3 やり取りはこんな流れ Client Server ApplicationData 鍵交換 Server公開鍵 Server公開鍵

    Client公開鍵 Client秘密鍵 Server秘密鍵 Client公開鍵 共通の値 共通の値 暗号化するための共通鍵が欲しい
  11. 証明書 関連 TLS1.3 やり取りはこんな流れ Client Server ApplicationData 鍵交換 Server公開鍵 Server公開鍵

    Client公開鍵 Client秘密鍵 Server秘密鍵 Client公開鍵 共通の値 共通の値 共通鍵 共通鍵 暗号化するための共通鍵が欲しい HKDFによる鍵導出 HKDFによる鍵導出
  12. 証明書 関連 TLS1.3 やり取りはこんな流れ Client Server ApplicationData 鍵交換 Server公開鍵 Server公開鍵

    Client公開鍵 Client秘密鍵 Server秘密鍵 Client公開鍵 共通の値 共通の値 共通鍵 共通鍵 暗号化するための共通鍵が欲しい
  13. TLSとは(再掲) SSL/TLS はセッション層に位置するセキュアプロトコルで、通信の 暗号化、データ完全性の確保、サーバ(場合によりクライアント)の 認証を行うことができる。 • 通信の暗号化 • データ完全性の確保 •

    サーバ(場合によりクライアント)の認証 引用:IPA TLS 暗号設定 ガイドライン [https://www.ipa.go.jp/security/crypto/guideline/gmcbt80000005ufv-att/ipa-cryptrec-gl-3001-3.1.1.pdf]
  14. TLSとは(再掲) SSL/TLS はセッション層に位置するセキュアプロトコルで、通信の 暗号化、データ完全性の確保、サーバ(場合によりクライアント)の 認証を行うことができる。 • 通信の暗号化 • データ完全性の確保 •

    サーバ(場合によりクライアント)の認証 引用:IPA TLS 暗号設定 ガイドライン [https://www.ipa.go.jp/security/crypto/guideline/gmcbt80000005ufv-att/ipa-cryptrec-gl-3001-3.1.1.pdf]
  15. 証明書 関連 TLS1.3 やり取りはこんな流れ Client Server ApplicationData 鍵交換 共通鍵 共通鍵

    アプリケーションデータ TLSハンドシェイク ハンドシェイクの終わりに 改ざんされていないかを確認する (Finished)
  16. TLSとは(再掲) SSL/TLS はセッション層に位置するセキュアプロトコルで、通信の 暗号化、データ完全性の確保、サーバ(場合によりクライアント)の 認証を行うことができる。 • 通信の暗号化 • データ完全性の確保 •

    サーバ(場合によりクライアント)の認証 引用:IPA TLS 暗号設定 ガイドライン [https://www.ipa.go.jp/security/crypto/guideline/gmcbt80000005ufv-att/ipa-cryptrec-gl-3001-3.1.1.pdf]
  17. TLSとは(再掲) SSL/TLS はセッション層に位置するセキュアプロトコルで、通信の 暗号化、データ完全性の確保、サーバ(場合によりクライアント)の 認証を行うことができる。 • 通信の暗号化 • データ完全性の確保 •

    サーバ(場合によりクライアント)の認証 引用:IPA TLS 暗号設定 ガイドライン [https://www.ipa.go.jp/security/crypto/guideline/gmcbt80000005ufv-att/ipa-cryptrec-gl-3001-3.1.1.pdf]
  18. ClientHello(Client → Server) TLSの最初のメッセージ • 対応できる鍵交換・署名アルゴリズム・暗号スイート • サポートversion(TLS1.2, TLS1.3) ClientHello

    やっほー、話したいんだけどさ。 暗号はこのへん使えるよ( cipher suites)。 署名はこのへんならいける( signature_algorithms)。 鍵交換のパーツも先にいくつか持ってきた( key_share)。 Client Server
  19. EncryptedExtensions(Client ← Server) ServerHelloの追加情報 • ServerHelloで送信したExtension以外 ◦ key_share ◦ pre_shared_key

    ◦ supported_versions EncryptedExtensions Client (お互い共通鍵を計算できるし、ここから暗号化して送るか) これ補足事項ね。 Server
  20. Finished(Client ← Server) サーバ側TLSハンドシェイク終わりのメッセージ • 改ざん検知のためにHMAC(後述)を付与する ◦ ハンドシェイク開始から現在までの全メッセージをハッシュ化 ◦ 導出した鍵でHMAC(メッセージ認証コード)を計算

    ▪ 一致:改ざんされていない + 同じ鍵が導出されたことを確認できる ▪ 不一致:改ざんされた or 鍵導出計算ミス(正しい実装なら起きない) Client Server Finished ほい、ワイは準備OK で、本題(ApplicationData)なんやっけ?
  21. ApplicationData(Client ←→ Server) • アプリケーション層のやり取り ◦ HTTPとかそのあたり ▪ GET /

    HTTP/1.1 Client Server ApplicationData (ApplicationDataは別の共通鍵で暗号化する) 本題(ApplicationData)は GET / HTTP/1.1
  22. 注意事項 • TLS1.3のサーバ側を実装。 • パフォーマンスは考慮できていない。 ◦ いろいろ漏れはあるけど、動くものを実装。 • 特定の鍵交換・暗号方式しか対応していない。 ◦

    鍵交換:ECDHE(X25519) ◦ 暗号化:TLS_AES_256_GCM_SHA384 ◦ 署名:ECDSA • OpenSSL使用。 • 0-RTT未対応。 • 1プロセス1リクエスト。 ◦ 複数リクエストはタイミングによって壊れる • 表示されているプログラムは抜粋してます
  23. • RFC8446 ◦ https://datatracker.ietf.org/doc/html/rfc8446 • TLS 暗号設定 ガイドライン ◦ https://www.ipa.go.jp/security/crypto/guideline/gmcbt80000005ufv-att/ipa-cryptrec-gl-3001-3.1.1.pdf

    • エムスリーテックブック8 ◦ https://techbookfest.org/product/b94hFWewG7fVRLgqEmSjT1?productVariantID=dWQKDmPPqwY 3dfr28X7j4L • SSL/TLS実践入門 ◦ https://gihyo.jp/book/2024/978-4-297-14178-3 • pizzacatさんのtails(HaskellのTLS実装) ◦ https://github.com/pizzacat83/tails • nsfisisさんのphpcon-kagawa-2025 ◦ https://github.com/nsfisis/nil.ninja/tree/main/vhosts/t/phpcon-kagawa-2025 • ichikawaさんの迂闊にTLS/SSLをPHPで実装してみたら最高だった件 ◦ https://blog.ichikaway.com/entry/20240801/ore-no-tls 参考
  24. • TCPソケットを作成し、リクエストを受け取る • リクエストが Handshake or ApplicationDataを判定 • (Handshakeの場合) ◦

    ClientHelloの内容を読み取る ◦ ServerHello 作成 → そのままレスポンスとして返す ◦ EncryptedExtensions 作成 → 暗号化してレスポンスとして返す ◦ … ◦ Finished 作成 → 暗号化してレスポンスとして返す • (ApplicationDataの場合) ◦ 復号化する ◦ リクエストの内容に応じたレスポンスを返す 流れ
  25. • TCPソケットを作成し、リクエストを受け取る • リクエストが Handshake or ApplicationDataを判定 • (Handshakeの場合) ◦

    ClientHelloの内容を読み取る ◦ ServerHello 作成 → そのままレスポンスとして返す ◦ EncryptedExtensions 作成 → 暗号化してレスポンスとして返す ◦ … ◦ Finished 作成 → 暗号化してレスポンスとして返す • (ApplicationDataの場合) ◦ 復号化する ◦ リクエストの内容に応じたレスポンスを返す 最初の一歩
  26. • TCPソケットを作成し、リクエストを受け取る • リクエストが Handshake or ApplicationDataを判定 • (Handshakeの場合) ◦

    ClientHelloの内容を読み取る ◦ ServerHello 作成 → そのままレスポンスとして返す ◦ EncryptedExtensions 作成 → 暗号化してレスポンスとして返す ◦ … ◦ Finished 作成 → 暗号化してレスポンスとして返す • (ApplicationDataの場合) ◦ 復号化する ◦ リクエストの内容に応じたレスポンスを返す 流れ
  27. $socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); socket_bind($socket, '0.0.0.0', 443); socket_listen($socket, 5);

    $sock = socket_accept($socket); $chunk = ''; :/ $chunk にリクエストが格納される $bytes = socket_recv($sock, $chunk, 8192, 0);
  28. 16 03 01 06 b2 01 00 06 ae 03

    03 a6 c0 23 df 11 … このbinaryから意味を見出していく リクエストのParse
  29. ClientHelloのフォーマット(RFC8446で設定) 固定値 (0x16) (Handshake) 固定値(0x0301 or 0x0303) TLSRecordの長さ 固定値 (0x01)

    (ClientHello) Handshakeの長さ 固定値(0x0303) ランダム値 対応できる暗号スイート一覧 固定値(0x00) 拡張 レガシーセッションID
  30. ClientHelloのフォーマット(RFC8446で設定) 固定値 (0x16) (Handshake) 固定値(0x0301 or 0x0303) TLSRecordの長さ 固定値 (0x01)

    (ClientHello) Handshakeの長さ 固定値(0x0303) ランダム値 対応できる暗号スイート一覧 固定値(0x00) 拡張 レガシーセッションID 対応できる署名アルゴリズム 鍵交換のパーツ
  31. 16 03 01 06 b2 01 00 06 ae 03

    03 a6 c0 23 df 11 … リクエストのParse 引用:RFC8446 [https://datatracker.ietf.org/doc/html/rfc8446]
  32. 16 03 01 06 b2 01 00 06 ae 03

    03 a6 c0 23 df 11 … リクエストのParse 引用:RFC8446 [https://datatracker.ietf.org/doc/html/rfc8446] この通信は Handshake(0x16 = 22)
  33. 16 03 01 06 b2 01 00 06 ae 03

    03 a6 c0 23 df 11 … リクエストのParse 引用:RFC8446 [https://datatracker.ietf.org/doc/html/rfc8446] 固定値(0x0301 or 0x0303)
  34. 16 03 01 06 b2 01 00 06 ae 03

    03 a6 c0 23 df 11 … リクエストのParse 引用:RFC8446 [https://datatracker.ietf.org/doc/html/rfc8446] TLSPlaintext長さは 1714(0x06b2)
  35. 16 03 01 06 b2 01 00 06 ae 03

    03 a6 c0 23 df 11 … リクエストのParse 引用:RFC8446 [https://datatracker.ietf.org/doc/html/rfc8446] Handshakeの中身
  36. 16 03 01 06 b2 01 00 06 ae 03

    03 a6 c0 23 df 11 … リクエストのParse 引用:RFC8446 [https://datatracker.ietf.org/doc/html/rfc8446]
  37. 16 03 01 06 b2 01 00 06 ae 03

    03 a6 c0 23 df 11 … リクエストのParse 引用:RFC8446 [https://datatracker.ietf.org/doc/html/rfc8446] ClientHello(1)
  38. 16 03 01 06 b2 01 00 06 ae 03

    03 a6 c0 23 df 11 … リクエストのParse 引用:RFC8446 [https://datatracker.ietf.org/doc/html/rfc8446] Handshakeの長さ 1710(0x0006ae)
  39. 16 03 01 06 b2 01 00 06 ae 03

    03 a6 c0 23 df 11 … リクエストのParse 引用:RFC8446 [https://datatracker.ietf.org/doc/html/rfc8446] ClientHelloの中身
  40. 16 03 01 06 b2 01 00 06 ae 03

    03 a6 c0 23 df 11 … リクエストのParse 引用:RFC8446 [https://datatracker.ietf.org/doc/html/rfc8446]
  41. 16 03 01 06 b2 01 00 06 ae 03

    03 a6 c0 23 df 11 … リクエストのParse 引用:RFC8446 [https://datatracker.ietf.org/doc/html/rfc8446]
  42. 16 03 01 06 b2 01 00 06 ae 03

    03 a6 c0 23 df 11 … リクエストのParse 引用:RFC8446 [https://datatracker.ietf.org/doc/html/rfc8446]
  43. 16 03 01 06 b2 01 00 06 ae 03

    03 a6 c0 23 df 11 … 省略するが流れは同じ リクエストのParse 引用:RFC8446 [https://datatracker.ietf.org/doc/html/rfc8446]
  44. ... $chunk = ''; :/ $chunk にリクエストが格納される $bytes = socket_recv($sock,

    $chunk, 8192, 0); :/ リクエストをParse $tlsRecord = TlsRecord::from($chunkHex); ここから補助スライド
  45. ... $chunk = ''; :/ $chunk にリクエストが格納される $bytes = socket_recv($sock,

    $chunk, 8192, 0); :/ リクエストをParse $tlsRecord = TlsRecord::from($chunkHex); TLS通信における データの送受信単位
  46. final readonly class TlsRecord { public function :_construct( public TlsPlaintext|TlsCiphertext|HexString

    $element, ) {} public static function from(HexString $hex): self { $contentType = ContentType::fromHex($hex:>sub(0, 1)); $_legacyRecordVersion = ProtocolVersion::fromHex($hex:>sub(1, 2)); $_length = HexLength::from($hex:>sub(3, 2)); $element = match ($contentType) { ContentType::Handshake :> TlsPlaintext::new($contentType, Handshake::from($hex:>sub(5))), ContentType::ApplicationData :> TlsCiphertext::new($hex:>sub(5)), default :> $hex:>sub(5), }; return new self( element: $element, ); } }
  47. final readonly class TlsRecord { public function :_construct( public TlsPlaintext|TlsCiphertext|HexString

    $element, ) {} public static function from(HexString $hex): self { $contentType = ContentType::fromHex($hex:>sub(0, 1)); $_legacyRecordVersion = ProtocolVersion::fromHex($hex:>sub(1, 2)); $_length = HexLength::from($hex:>sub(3, 2)); $element = match ($contentType) { ContentType::Handshake :> TlsPlaintext::new($contentType, Handshake::from($hex:>sub(5))), ContentType::ApplicationData :> TlsCiphertext::new($hex:>sub(5)), default :> $hex:>sub(5), }; return new self( element: $element, ); } } 16 03 01 06 b2 01 00 06 ae 03 03 a6 c0 23 df 11 …
  48. final readonly class TlsRecord { public function :_construct( public TlsPlaintext|TlsCiphertext|HexString

    $element, ) {} public static function from(HexString $hex): self { $contentType = ContentType::fromHex($hex:>sub(0, 1)); $_legacyRecordVersion = ProtocolVersion::fromHex($hex:>sub(1, 2)); $_length = HexLength::from($hex:>sub(3, 2)); $element = match ($contentType) { ContentType::Handshake :> TlsPlaintext::new($contentType, Handshake::from($hex:>sub(5))), ContentType::ApplicationData :> TlsCiphertext::new($hex:>sub(5)), default :> $hex:>sub(5), }; return new self( element: $element, ); } } 引用:RFC8446 [https://datatracker.ietf.org/doc/html/rfc8446] 16 03 01 06 b2 01 00 06 ae 03 03 a6 c0 23 df 11 …
  49. final readonly class TlsRecord { public function :_construct( public TlsPlaintext|TlsCiphertext|HexString

    $element, ) {} public static function from(HexString $hex): self { $contentType = ContentType::fromHex($hex:>sub(0, 1)); $_legacyRecordVersion = ProtocolVersion::fromHex($hex:>sub(1, 2)); $_length = HexLength::from($hex:>sub(3, 2)); $element = match ($contentType) { ContentType::Handshake :> TlsPlaintext::new($contentType, Handshake::from($hex:>sub(5))), ContentType::ApplicationData :> TlsCiphertext::new($hex:>sub(5)), default :> $hex:>sub(5), }; return new self( element: $element, ); } } 16 03 01 06 b2 01 00 06 ae 03 03 a6 c0 23 df 11 … 引用:RFC8446 [https://datatracker.ietf.org/doc/html/rfc8446] この通信は Handshake(0x16 = 22)
  50. final readonly class TlsRecord { public function :_construct( public TlsPlaintext|TlsCiphertext|HexString

    $element, ) {} public static function from(HexString $hex): self { $contentType = ContentType::fromHex($hex:>sub(0, 1)); $_legacyRecordVersion = ProtocolVersion::fromHex($hex:>sub(1, 2)); $_length = HexLength::from($hex:>sub(3, 2)); $element = match ($contentType) { ContentType::Handshake :> TlsPlaintext::new($contentType, Handshake::from($hex:>sub(5))), ContentType::ApplicationData :> TlsCiphertext::new($hex:>sub(5)), default :> $hex:>sub(5), }; return new self( element: $element, ); } } 16 03 01 06 b2 01 00 06 ae 03 03 a6 c0 23 df 11 … 引用:RFC8446 [https://datatracker.ietf.org/doc/html/rfc8446] 固定値(0x0301 or 0x0303)
  51. final readonly class TlsRecord { public function :_construct( public TlsPlaintext|TlsCiphertext|HexString

    $element, ) {} public static function from(HexString $hex): self { $contentType = ContentType::fromHex($hex:>sub(0, 1)); $_legacyRecordVersion = ProtocolVersion::fromHex($hex:>sub(1, 2)); $_length = HexLength::from($hex:>sub(3, 2)); $element = match ($contentType) { ContentType::Handshake :> TlsPlaintext::new($contentType, Handshake::from($hex:>sub(5))), ContentType::ApplicationData :> TlsCiphertext::new($hex:>sub(5)), default :> $hex:>sub(5), }; return new self( element: $element, ); } } 16 03 01 06 b2 01 00 06 ae 03 03 a6 c0 23 df 11 … 引用:RFC8446 [https://datatracker.ietf.org/doc/html/rfc8446] TLSPlaintext長さは 1714(0x06b2)
  52. final readonly class TlsRecord { public function :_construct( public TlsPlaintext|TlsCiphertext|HexString

    $element, ) {} public static function from(HexString $hex): self { $contentType = ContentType::fromHex($hex:>sub(0, 1)); $_legacyRecordVersion = ProtocolVersion::fromHex($hex:>sub(1, 2)); $_length = HexLength::from($hex:>sub(3, 2)); $element = match ($contentType) { ContentType::Handshake :> TlsPlaintext::new($contentType, Handshake::from($hex:>sub(5))), ContentType::ApplicationData :> TlsCiphertext::new($hex:>sub(5)), default :> $hex:>sub(5), }; return new self( element: $element, ); } } 16 03 01 06 b2 01 00 06 ae 03 03 a6 c0 23 df 11 … 0x16(22)は Handshake 引用:RFC8446 [https://datatracker.ietf.org/doc/html/rfc8446]
  53. final readonly class TlsRecord { public function :_construct( public TlsPlaintext|TlsCiphertext|HexString

    $element, ) {} public static function from(HexString $hex): self { $contentType = ContentType::fromHex($hex:>sub(0, 1)); $_legacyRecordVersion = ProtocolVersion::fromHex($hex:>sub(1, 2)); $_length = HexLength::from($hex:>sub(3, 2)); $element = match ($contentType) { ContentType::Handshake :> TlsPlaintext::new($contentType, Handshake::from($hex:>sub(5))), ContentType::ApplicationData :> TlsCiphertext::new($hex:>sub(5)), default :> $hex:>sub(5), }; return new self( element: $element, ); } } 16 03 01 06 b2 01 00 06 ae 03 03 a6 c0 23 df 11 … 引用:RFC8446 [https://datatracker.ietf.org/doc/html/rfc8446]
  54. final readonly class TlsRecord { public function :_construct( public TlsPlaintext|TlsCiphertext|HexString

    $element, ) {} public static function from(HexString $hex): self { $contentType = ContentType::fromHex($hex:>sub(0, 1)); $_legacyRecordVersion = ProtocolVersion::fromHex($hex:>sub(1, 2)); $_length = HexLength::from($hex:>sub(3, 2)); $element = match ($contentType) { ContentType::Handshake :> TlsPlaintext::new($contentType, Handshake::from($hex:>sub(5))), ContentType::ApplicationData :> TlsCiphertext::new($hex:>sub(5)), default :> $hex:>sub(5), }; return new self( element: $element, ); } } ちなみに、暗号化されると全てApplicationDataになる
  55. • TCPソケットを作成し、リクエストを受け取る • リクエストが Handshake or ApplicationDataを判定 • (Handshakeの場合) ◦

    ClientHelloの内容を読み取る ◦ ServerHello 作成 → そのままレスポンスとして返す ◦ EncryptedExtensions 作成 → 暗号化してレスポンスとして返す ◦ … ◦ Finished 作成 → 暗号化してレスポンスとして返す • (ApplicationDataの場合) ◦ 復号化する ◦ リクエストの内容に応じたレスポンスを返す 流れ
  56. • TCPソケットを作成し、リクエストを受け取る • リクエストが Handshake or ApplicationDataを判定 • (Handshakeの場合) ◦

    ClientHelloの内容を読み取る ◦ ServerHello 作成 → そのままレスポンスとして返す ◦ EncryptedExtensions 作成 → 暗号化してレスポンスとして返す ◦ … ◦ Finished 作成 → 暗号化してレスポンスとして返す • (ApplicationDataの場合) ◦ 復号化する ◦ リクエストの内容に応じたレスポンスを返す 流れ
  57. final readonly class Handshake { public function :_construct( public HandshakeType

    $msgType, public HexLength $length, public ClientHello|HandshakeResponder $body, ) {} … } public static function from(HexString $hex): self { $msgType = HandshakeType::fromHex($hex:>sub(0, 1)); $length = HexLength::from($hex:>sub(1, 3)); $body = match ($msgType) { HandshakeType::ClientHello :> ClientHello::from($hex:>sub(4)), default :> throw new \RuntimeException('not implemented'), }; return new self( msgType: $msgType, length: $length, body: $body, ); }
  58. final readonly class Handshake { public function :_construct( public HandshakeType

    $msgType, public HexLength $length, public ClientHello|HandshakeResponder $body, ) {} … } public static function from(HexString $hex): self { $msgType = HandshakeType::fromHex($hex:>sub(0, 1)); $length = HexLength::from($hex:>sub(1, 3)); $body = match ($msgType) { HandshakeType::ClientHello :> ClientHello::from($hex:>sub(4)), default :> throw new \RuntimeException('not implemented'), }; return new self( msgType: $msgType, length: $length, body: $body, ); } 引用:RFC8446 [https://datatracker.ietf.org/doc/html/rfc8446] 01 00 06 ae 03 03 a6 c0 23 df 11 …
  59. final readonly class Handshake { public function :_construct( public HandshakeType

    $msgType, public HexLength $length, public ClientHello|HandshakeResponder $body, ) {} … } public static function from(HexString $hex): self { $msgType = HandshakeType::fromHex($hex:>sub(0, 1)); $length = HexLength::from($hex:>sub(1, 3)); $body = match ($msgType) { HandshakeType::ClientHello :> ClientHello::from($hex:>sub(4)), default :> throw new \RuntimeException('not implemented'), }; return new self( msgType: $msgType, length: $length, body: $body, ); } 01 00 06 ae 03 03 a6 c0 23 df 11 … 引用:RFC8446 [https://datatracker.ietf.org/doc/html/rfc8446]
  60. final readonly class Handshake { public function :_construct( public HandshakeType

    $msgType, public HexLength $length, public ClientHello|HandshakeResponder $body, ) {} … } public static function from(HexString $hex): self { $msgType = HandshakeType::fromHex($hex:>sub(0, 1)); $length = HexLength::from($hex:>sub(1, 3)); $body = match ($msgType) { HandshakeType::ClientHello :> ClientHello::from($hex:>sub(4)), default :> throw new \RuntimeException('not implemented'), }; return new self( msgType: $msgType, length: $length, body: $body, ); } 01 00 06 ae 03 03 a6 c0 23 df 11 … 引用:RFC8446 [https://datatracker.ietf.org/doc/html/rfc8446]
  61. final readonly class Handshake { public function :_construct( public HandshakeType

    $msgType, public HexLength $length, public ClientHello|HandshakeResponder $body, ) {} … } public static function from(HexString $hex): self { $msgType = HandshakeType::fromHex($hex:>sub(0, 1)); $length = HexLength::from($hex:>sub(1, 3)); $body = match ($msgType) { HandshakeType::ClientHello :> ClientHello::from($hex:>sub(4)), default :> throw new \RuntimeException('not implemented'), }; return new self( msgType: $msgType, length: $length, body: $body, ); } 01 00 06 ae 03 03 a6 c0 23 df 11 … 0x01(1)は ClientHello 引用:RFC8446 [https://datatracker.ietf.org/doc/html/rfc8446]
  62. final readonly class Handshake { public function :_construct( public HandshakeType

    $msgType, public HexLength $length, public ClientHello|HandshakeResponder $body, ) {} … } public static function from(HexString $hex): self { $msgType = HandshakeType::fromHex($hex:>sub(0, 1)); $length = HexLength::from($hex:>sub(1, 3)); $body = match ($msgType) { HandshakeType::ClientHello :> ClientHello::from($hex:>sub(4)), default :> throw new \RuntimeException('not implemented'), }; return new self( msgType: $msgType, length: $length, body: $body, ); } 01 00 06 ae 03 03 a6 c0 23 df 11 … 引用:RFC8446 [https://datatracker.ietf.org/doc/html/rfc8446]
  63. final readonly class Handshake { public function :_construct( public HandshakeType

    $msgType, public HexLength $length, public ClientHello|HandshakeResponder $body, ) {} … } public static function from(HexString $hex): self { $msgType = HandshakeType::fromHex($hex:>sub(0, 1)); $length = HexLength::from($hex:>sub(1, 3)); $body = match ($msgType) { HandshakeType::ClientHello :> ClientHello::from($hex:>sub(4)), default :> throw new \RuntimeException('not implemented'), }; return new self( msgType: $msgType, length: $length, body: $body, ); } 01 00 06 ae 03 03 a6 c0 23 df 11 … 引用:RFC8446 [https://datatracker.ietf.org/doc/html/rfc8446] 省略しますが、流れは同じです。 ここまで補助スライド
  64. 証明書 関連 TLS1.3 やり取りはこんな流れ Client Server ApplicationData 鍵交換 Server公開鍵 Server公開鍵

    Client公開鍵 Client秘密鍵 Server秘密鍵 Client公開鍵 共通の値 共通の値 暗号化するための共通鍵が欲しい
  65. 証明書 関連 TLS1.3 やり取りはこんな流れ Client Server ApplicationData 鍵交換 Server公開鍵 Server公開鍵

    Client公開鍵 Client秘密鍵 Server秘密鍵 Client公開鍵 共通の値 共通の値 共通鍵 共通鍵 暗号化するための共通鍵が欲しい HKDFによる鍵導出 HKDFによる鍵導出
  66. 証明書 関連 TLS1.3 やり取りはこんな流れ Client Server ApplicationData 鍵交換 Server公開鍵 Server公開鍵

    Client公開鍵 Client秘密鍵 Server秘密鍵 Client公開鍵 共通の値 共通の値 共通鍵 共通鍵 暗号化するための共通鍵が欲しい HKDFによる鍵導出 HKDFによる鍵導出
  67. stringの扱い方 16 03 01 06 b2 01 00 06 "�"

    var_dump() リクエストで 受け取るデータ
  68. stringの扱い方 16 03 01 06 b2 01 00 06 "�"

    var_dump() リクエストで 受け取るデータ 😭
  69. stringの扱い方 0b00010110 0b00000011 0b00000001 0b00000110 0b10110010 0b00000001 0b00000000 0b00000110 "�"

    var_dump() 0b00110001 0b00110110 0b00110000 0b00110011 0b00110000 0b00110001 0b00110000 0b00110110 0b01100010 0b00110010 0b00110000 0b00110001 0b00110000 0b00110000 0b00110000 0b00110110 bin2hex()
  70. stringの扱い方 0b00010110 0b00000011 0b00000001 0b00000110 0b10110010 0b00000001 0b00000000 0b00000110 "�"

    var_dump() 0b00110001 0b00110110 0b00110000 0b00110011 0b00110000 0b00110001 0b00110000 0b00110110 0b01100010 0b00110010 0b00110000 0b00110001 0b00110000 0b00110000 0b00110000 0b00110110 bin2hex() "16030106b2010006" var_dump()
  71. stringの扱い方 0b00010110 0b00000011 0b00000001 0b00000110 0b10110010 0b00000001 0b00000000 0b00000110 "�"

    var_dump() 0b00110001 0b00110110 0b00110000 0b00110011 0b00110000 0b00110001 0b00110000 0b00110110 0b01100010 0b00110010 0b00110000 0b00110001 0b00110000 0b00110000 0b00110000 0b00110110 bin2hex() "16030106b2010006" var_dump() ☺
  72. stringの扱い方 0b00010110 0b00000011 0b00000001 0b00000110 0b10110010 0b00000001 0b00000000 0b00000110 "�"

    var_dump() 0b00110001 0b00110110 0b00110000 0b00110011 0b00110000 0b00110001 0b00110000 0b00110110 0b01100010 0b00110010 0b00110000 0b00110001 0b00110000 0b00110000 0b00110000 0b00110110 bin2hex() "16030106b2010006" var_dump() 全部string型 😳
  73. クラス分け 0b00110001 0b00110110 0b00110000 0b00110011 0b00110000 0b00110001 0b00110000 0b00110110 0b01100010

    0b00110010 0b00110000 0b00110001 0b00110000 0b00110000 0b00110000 0b00110110 "16030106b2010006" 0b00010110 0b00000011 0b00000001 0b00000110 0b10110010 0b00000001 0b00000000 0b00000110 "�" string HexString
  74. $socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); socket_bind($socket, $this:>host, $this:>port) socket_listen($socket, 5)

    $sock = socket_accept($socket); $chunk = ''; :/ $chunk にリクエストが格納される $bytes = socket_recv($sock, $chunk, 8192, 0); :/ $chunk を HexString に変換、$chunkHex を Parseしていく $chunkHex = HexString::from($chunk);
  75. ... socket_listen($socket, 5) $sock = socket_accept($socket); $chunk = ''; :/

    $chunk にリクエストが格納される $bytes = socket_recv($sock, $chunk, 8192, 0); :/ $chunk を HexString に変換、$chunkHex を Parseしていく $chunkHex = HexString::from($chunk);
  76. ... $chunk = ''; :/ $chunk にリクエストが格納される $bytes = socket_recv($sock,

    $chunk, 8192, 0); :/ $chunk を HexString に変換、$chunkHex を Parseしていく $chunkHex = HexString::from($chunk);
  77. ... :/ $chunk を HexString に変換、$chunkHex を Parseしていく $chunkHex =

    HexString::from($chunk); :/ リクエストをParse $tlsRecord = TlsRecord::from($chunkHex);