@kyanny's blog

商品にならぬ技術は役に立たない - トーマス・エジソン

Signing HTTP Messages について

このブログ記事の説明が網羅的でわかりやすい。

仕様は移り変わっており、版も何度も変更されている。ライブラリや API がどの版に基づいているかによって相性問題が発生する。新しい仕様に基づいたライブラリで署名したリクエストは古い仕様に基づいていると思われる API に拒否される、ということが起こる(起こった)。仕様の URL も何種類かあり、紛らわしい。

https://github.com/w3c-ccg/http-signatures このリポジトリで管理されていたが現在は https://github.com/httpwg/http-extensions に移動したようだ。

Ruby 実装だと https://github.com/99designs/http-signatures-ruby というのがある。試したところちゃんと使えた。試してないが https://github.com/bolmaster2/http-signature というのもあり、こちらも使えそうな気がする。 https://github.com/krainboltgreene/net-http-signature.gem というのも試したが、これは使えなかった(Base64.strict_encode64 してなかったり、論外な印象)。

他言語の実装は https://github.com/topics/http-signature, https://github.com/topics/http-signatures などから探せる。上記の Gem のリポジトリが載ってないのはリポジトリの Topics が未設定なためらしい。 Topics 設定はオーナーしかできず、 Pull Request を送ることもできない。

いくつかハマったところなど

  • シグネチャが不正な場合サーバーは 401 Unauthorized とともに WWW-Authenticate: Signature realm="Example",headers="(request-target) (created)" のようなヘッダを返す仕様になっている。 headers= のところにシグネチャ値の計算に用いるべきヘッダ名を入れることになっているようだが、実際に必要なヘッダは (request-target) host date なのに (request-target) host date content-type content-length を返すサーバがいたりする
  • シグネチャの計算にリクエストボディそのものを含めることを要求するサーバがいたりする。ライブラリによっても対応はバラバラで、リクエストボディのコンテンツは含めず Content-Length だけをシグネチャ計算に用いることが決め打ちで実装されていたりする
  • クライアントは Authorization: Signature keyId="rsa-key-1",algorithm="hs2019", headers="(request-target) (created) host digest content-length", signature="Base64(RSA-SHA512(signing string))" のようなリクエストヘッダを送ることになっているが、昔の仕様では algorithm="hmac-sha256" のようにシグネチャ計算に用いるアルゴリズム名を入れることになっていたのが、セキュリティ対策だかで途中から algorithm="hs2019" という決め打ちの値を入れる仕様に変更され、アルゴリズム名は keyId="rsa-key-1" という風に入れることになったようだ。しかしサーバ実装の中にはアルゴリズムを algorithm="" から取得する実装になっているものがあったりする(keyId="" のところにアルゴリズム名ではなく API KEY を入れる仕様になっていたりする)

ハマったことをふまえた所感

RSA とか HMAC とかの難しい計算の部分はどうせ各言語の標準ライブラリ等を使えるので、 Signing HTTP Messages の仕様を満たすための実装はそれほど複雑でもない。シグネチャを計算して所定の書式のヘッダに詰め込むだけ。ライブラリやサーバの実装のばらつき具合を見ると、相性問題に頭を悩ませながら使えるライブラリを探したり使い方を工夫したりする手間をかけるよりも、自前で実装してしまった方が早いしトラブルも少ない気がする。