以下の内容はhttps://kazu-yamamoto.hatenablog.jp/entry/2026/01/29/220420より取得しました。


証明書圧縮

TLS レコード・サイズ制限拡張では、証明書圧縮について書くことはないと言ったが、書くべきことが出てきたので記録しておく。この前、Haskell tls ライブラリと一緒に配布しているテストプログラム tls-clientgraph.facebook.com と通信できないという報告を受けた

デバッグ・メッセージを見ると、圧縮された証明書の直後で、CertificateVerify の検証に失敗している。CertificateVerify とは、この直前にサーバが送ってくる証明書が本当にそのサーバのものだという証拠である。簡単に言えば、サーバが「あるデータ」に施した署名だ。証明書に入っている公開鍵で検証すれば、本当にそのサーバが対応する秘密鍵を持っているかが分かる。

「あるデータ」とは、ClientHelloから証明書までのクライアントとサーバが交換したハンドシェイク・メッセージに対する暗号学的ハッシュの値である。TLS 1.3では、これをトランスクリプト・ハッシュと呼ぶ。

トランスクリプト・ハッシュは、基本的には連結したハンドシェイク・メッセージに対する暗号学的ハッシュの値だが、HelloRetryRequestに対してはハッシュのハッシュを取らなければならず、扱いが煩わしい。そのため Haskell tls ライブラリにはトランスクリプト・ハッシュをトレースする機能を実装している。tls-clientでは--trace-keyオプションを指定すると、トランスクリプト・ハッシュの経過が可視化される。ただし、トレース機能はサーバ側と対で使わないと、あまり有益でないかもしれない。

試しに証明書圧縮の機能を無効にしてみると、graph.facebook.comとちゃんと通信できた。次に、Wiresharkでパケットをダンプしてみたが、原因はよく分からなかった。しかし、サーバがCertificateVerifyに使うトランスクリプト・ハッシュの値とクライアント側のそれとが異なるのは確実だ。

伸長した証明書を眺めてみたが、どこにも問題がない。何が原因なんだろう? ここで、伸長した証明書をクライアント側でもう一度圧縮してみたところ、サーバ側の圧縮率と異なることを発見した。

分かった!分かった!

Haskell tls ライブラリのダサいと思っていたところを見事に突いているバグだ。Haskell tls ライブラリの送信側は、符号化したワイヤーフォーマットに対してトランスクリプト・ハッシュを計算し、ネットワークに送信する。

一方で受信側は、相手が送って来たワイヤーフォーマットに対してトランスクリプト・ハッシュを計算するのではなく、次のような処理をする。

  1. ワイヤーフォーマットを復号して、Haskell のデータ構造に直す
  2. Haskell のデータ構造を再びワイヤーフォーマットに符号化してトランスクリプト・ハッシュを計算する

圧縮率が異なれば、ワイヤーフォーマットも異なり、従って間違ったトランスクリプト・ハッシュの値が算出される。

このような冗長な符号化が採用されているのは、コードが簡潔になるからだ。ここは気に入らなかったので、以前修正しようと試みたが、大幅な改良が必要になることが分かり諦めた。しかし、今回は直さなければいけないバグがそこにあるので、やる気が出る。

というわけで、受信側に新しいトランスクリプト・ハッシュの仕組みを実装しマージした。ユニット・テストも、tlsfuzzerによる回帰テストも、graph.facebook.comとの相互接続性試験も通ったので、たぶん安定している。この実装には、トレース機能が大活躍したので、作っておいてよかった。




以上の内容はhttps://kazu-yamamoto.hatenablog.jp/entry/2026/01/29/220420より取得しました。
このページはhttp://font.textar.tv/のウェブフォントを使用してます

不具合報告/要望等はこちらへお願いします。
モバイルやる夫Viewer Ver0.14