OAuth 2.0 MAC Tokenを使った署名付きリクエストの作り方

こんばんは、ritouです。
秋田はようやく、桜が咲き始めました。

以前紹介したOAuth 2.0 MAC Tokenですが、まだどこも対応してくれそうな気配がないので、自分で試してみました。
仕様 : draft-hammer-oauth-v2-mac-token-02 - HTTP Authentication: MAC Access Authentication

Clientがやること

ここで説明するのは、ClientがAccess Token + αを取得したあとからのリソースアクセス処理を想定しています。

1. MAC Credential(token,secret,algorithm)を用意

これらは、OAuth 2.0で定義されているフローで取得します。
ClientCredential(client_id/secret)は不要です。

2. timestamp,nonceを用意

timestampについては省略します。
nonceの作り方については、Clientの実装依存になります。

3. Resource Serverのデータを用意(method,URL,entitybody)

これらの値は、次に署名のもとになるSignature Base String作成のためにごにょごにょする必要があります。
まずは次のように分割します。

  • method
  • host
  • port
  • path
  • query ← 連想配列みたいな形

その後に、queryに次のような処理をして文字列にします。

できあがったものがこちら的サンプルはこちらです。

===
b5=%3D%253D&c%40=&a2=r%20b&c2&a3=2+q
===

↓

===
a2=r%20b\n
a3=2%20q\n
b5=%3D%253D\n
c%40=\n
c2=\n
===

仕様の例では、上記の例にわざとらしくa3=aが追加されて(a3パラメータが)重複しているのですが、PHPでURLをparse_urlしてクエリをparse_strして配列にしてるうちに重複してる値がどっかいったので、とりあえず気にしないでおきます。

POSTなどでentitybodyを利用する場合は、その値をhashした文字列をbodyhashとして利用します。
POST+"application/x-www-form-urlencoded"のときにもentitybodyとして扱われますよ。

4. 署名のもとになる文字列を作成

今まで出てきた値を改行文字をつかって連結させます。

  • token
  • timestamp
  • nonce
  • bodyhash
  • method
  • host
  • port
  • path
  • query

改行文字を\nで表記したサンプルはこちらです。

例 : GET
h480djs93hd8\n
137131200\n
dj83hs9s\n
\n
GET\n
example.com\n
80\n
/resource/1\n
a=2\n
b=1\n

例 : POST
kkk9d7dh3k39sjv7\n
137131201\n
7d8f3e4a\n
Lve95gjOVATpfV8EL5X4nxwjKHE=\n
POST\n
example.com\n
80\n
/request\n
a2=r%20b\n
a3=2%20q\n
b5=%3D%253D\n
c%40=\n
c2=\n
5. 署名作成

4で作った文字列を、secret+hmac関数で署名文字列を作成します。

6. Authorization Header を作成

サンプルはこちらです。(値は正しくありません。)
POSTのentitybodyが利用されるときは、AuthZ Headerにbodyhashが追加されます。

例 : GET
Authorization: MAC token="h480djs93hd8",
                   timestamp="137131200",
                   nonce="dj83hs9s",
                   signature="YTVjyNSujYs1WsDurFnvFi4JK6o=

例 : POST
Authorization: MAC token="h480djs93hd8",
                   timestamp="137131200",
                   nonce="dj83hs9s",
                   bodyhash="k9kbtCIy0CkI3/FEfpS/oIDjk6k=",
                   signature="OQsqDpSwH9fv6E2Iy5xdGhMGyrE="

Resource Serverがやること

1. リクエストの値を取得

環境変数などからリクエストの中身を取得します。

  • AuthZ Header
  • method
  • url
  • entitybody
2. AuthZ Header中の必須パラメータチェック

これらの値がなければエラーですね。

  • token
  • timestamp
  • nonce
  • signature
  • entitybodyがあるときはbodyhash
3. bodyhash検証

entitybodyに値が存在する場合はbodyhashを検証します。
Resource Server側でサポートしているハッシュアルゴリズムが単一の場合はこれは単純にsha1/sha256して比較します。
仮に、Token単位でアルゴリズムが異なる場合、リクエストの中にはアルゴリズムが指定されていませんので先にTokenの検証が必要かもしれません。

4. signature検証

とありますが、signatureの検証には、Tokenの検証が必要です。Secretを使いますからね。
Clientと同じ処理でSignatureを作成して比較します。

5. Timestamp,Nonceの検証

ここも実装依存にはなりますが、以下のような処理が行われるでしょう。

  • xx分(秒)以内に作成されたリクエストのみ有効
  • timestamp,nonce(もしくはnonce)の組み合わせが過去に利用されていないことを確認
6. Access Tokenの検証

ここからは、bearer tokenと同じですね。
ScopeやExpireなどを検証して、問題なければリソースアクセスの処理に入るのでしょう。

こんなところでしょうか。
図にした方が良かったのか…

PHPライブラリ

RubyPerlなら他にライブラリ作って試してくれそうな人がいるので、PHPでちょっと作りました。
GitHub - ritou/php-OAuth2MacToken

AuthZ Request Headerの作り方はこんな感じです。

<?php

include_once("lib/OAuth2MacTokenUtil.php");

$token = "j92fsdjf094gjfdi";
$secret = "8yfrufh348h";
$algorithm = "hmac-sha-1";
$timestamp = 137131206;
$nonce = "f403hksd";
$method = "POST";
$url = "http://example.com/request";
$entitybody = "hello=world%21";

$AuthZHeader = OAuth2MacTokenUtil::genetateAuthZHeader($token, 
                                                       $secret, 
                                                       $algorithm, 
                                                       $timestamp, 
                                                       $nonce, 
                                                       $method, 
                                                       $url, 
                                                       $entitybody);
  • AuthZ Request Header作成
  • signature作成
  • bodyhash計算

上記関数とcurlを使ったサンプルClientと、apache関連の関数を使ったサンプルResource Serverも置いときました。

様々なサービスがbearer tokenに対応していくなか、この署名付きリクエストははたして使われるのでしょうか。
いつかそんな時代がきたら参考にしていただければと思います。ではまた。