【RFC 9421】HTTPメッセージの電子署名に関するインターネット標準について

先日新しくインターネット標準となった『RFC 9421 : HTTP Message Signatures』が気になって読み、自分の考えをまとめてみましたので、その内容を共有します。

仕様の概要

RFC 9421 は HTTP メッセージの電子署名に関する仕様です。HTTP メッセージの送信者はヘッダの一部やボディから署名を作成し、その署名を HTTP ヘッダに付加して送信し、受信者はそれを検証するというものです。RFC には署名の作成・検証方法、署名やアルゴリズム等の伝達方法、その他考慮点が記載されています。

署名には通信相手の認証や改ざん検知といった機能がありますが、TLS には署名の機能が含まれているにも関わらずなぜ HTTP のレイヤで署名が必要なのか、当然気になります。この疑問の答えは第一章に書かれています。

TLS はそのピア同士の間の通信を保護するものですが、HTTP クライアントとサーバのエンドツーエンドで保護されているわけではないということが課題感として挙げられています。例えば CDN が利用されているようなケースでは、クライアントは CDN との間で TLS 通信を行い、CDN はオリジンであるサーバとの間で TLS 通信を行います。このとき CDN で設定ミス・バグ・侵害・内部不正などがあって不正な HTTP レスポンスを返したとしても、そのことにクライアントは機械的には気付けません。また、クライアント側で TLS インスペクションを行うセキュリティ製品を利用しているようなケースでも、同様のことが言えます。こういった課題を HTTP レイヤで解決しようというのが RFC 9421 です。

なお、AWS の API ではクライアントはリクエストに署名を行い、API サーバは署名を検証してクライアントの認証を行っています。他にも同様のことを行っているサービスやシステムがあり、こういった既存の実装もこの RFC の下地になっているものと思います。

仕様で定められていること

HTTP メッセージの署名を作成・検証する際、署名の対象とする HTTP メッセージのバイトデータの形式が問題になります。

HTTP では次のような仕様や実運用が存在します。

  • ロードバランサやリバースプロキシなどの中継サーバが HTTP ヘッダを追加・変更する
  • ヘッダ名の大文字・小文字の違いが許容されている
  • HTTP/2 では HTTP ヘッダがキャッシュされており、毎回送受信されるわけではない
  • HTTP/2 と HTTP/1.1 ではメッセージの形式が根本的に異なる(バイナリ/テキスト)

HTTP メッセージをバイトデータとして表現したときの形式が署名者・検証者の実装によって異なってしまうと署名が機能しないため、メッセージの正規化や署名対象ヘッダの選択方法が仕様として定められています。これにより複数の実装間での相互運用性を保とうとしたわけですね。

また、署名者と検証者は同一の暗号アルゴリズムやハッシュアルゴリズムを用いる必要があります。これについて、仕様ではどのアルゴリズムを用いて署名を作成したか、署名者から検証者に HTTP ヘッダを用いて伝達する方法が定められています。さらに、署名の作成日時や有効期限、nonce、利用した鍵のIDなども伝達情報として定められています。

一方で、署名で利用する鍵をどのようにして交換するかについては定められておらず、共通鍵暗号方式と公開鍵暗号方式のどちらを利用するかについても定められていません。TLS のように公開鍵を伝達する仕組みは定められていませんので、クライアントとサーバの間であらかじめ鍵を交換し合っておく必要があるものと思います。DKIM 署名のように DNS を用いて公開鍵を配布するという方法も考えられます。

署名の仕組みがどこで活用されるか

RFC で定められているのはあくまでも署名の仕様であり、署名をどこで活用するかについては各開発者が考えるべきことですね。

まず、ブラウザベースの Web アプリケーションでの活用を考えたくなります。Web サーバが署名を付けたレスポンスを返してきたときに、ブラウザが自動的に署名を検証し、検証失敗ならユーザに通知するといった感じのことです。しかし、これは各ブラウザが実装してくれるかどうかに依存しますし、鍵交換の部分の仕様が今は存在しないので、まだその時期ではないように思います。

Web アプリケーションでの活用の他実装として、JavaScript で署名を検証するという方法も考えられます。RFC では、ブラウザ上で動く JavaScript からも署名を扱えるようにするということが要件事項として挙げられていますので、想定の活用方法のはずです。ただし、署名の検証を行う JavaScript を Web サーバがクライアントに返すようにしていたとしても、中継サーバを侵害した悪意者は署名の検証を行わない JavaScript を返せてしまい、署名が機能しないようにできてしまいます。そのため、こういった不正を防止したい組織(金融機関など)は署名の検証を行うブラウザの拡張機能を独自に開発し、ユーザにあらかじめ導入しておいてもらうといった活用事例が生まれてくる可能性はありえそうです。検証のための公開鍵の配布の問題もブラウザの拡張機能の中に埋め込むことで解決できますので。とはいえ、自前の Web サーバで TLS を終端し、かつ証明書のピンニングも行えば中間者攻撃を防止できますので、積極的に署名を行う動機にはなりにくいかもしれません。

サーバがクライアントを認証する際の活用も当然考えられます。クライアント側で秘密鍵と公開鍵を生成し、その公開鍵をサーバ側に登録しておいた上で、その後の通信では署名を用いてクライアントを認証するといった活用です。ただし、ブラウザベースの Web アプリケーションでは鍵交換の問題やどうやって署名するかという問題があるため現実的ではなく、そのようなことがしたいのであれば今はパスキーを利用すべきですね。非ブラウザ環境で Web API を実行するクライアントの認証という活用であれば、ブラウザによるパスキーのサポートがありませんので、署名の活用は十分ありえそうです。もちろん、証明書のピンニングでサーバが期待する通信相手であることを保証したうえで、ヘッダベースの Bearer 認証でも良いとは思いますが。。

まとめ

RFC 9421 : HTTP Message Signatures を読んで自分の考えをまとめてみました。

活用方法として2点(ブラウザ拡張機能を用いた中間者攻撃の防止、Web API でのクライアント認証)挙げてみましたが、それが合っているのかどうかはよくわかりませんし、他にもあるとも思います。

インターネット標準だからといってそれを活用しなければならないものではありませんが、将来それがどう活用されるかを想像しつつ、これまで解決できずに困っていた課題を解決できるのであれば、それに向けて取り組むのは非常に楽しいですね。

タイトルとURLをコピーしました