前回はConsumerサイトを実際に作る際のプログラミングに関してお話ししましたが、今回はOpenIDに関するセキュリティについて考えてみます。

今回取り上げるトピックとしては、

通信経路のセキュリティモデル

OpenIDに対するセキュリティ上の脅威

OpenID IdPのReputation（評価）

などを段階的に説明していきます。IdPの構築方法を知る前にOpenIDプロトコルのセキュリティに関して熟知しておきましょう。

OpenIDプロトコルにおける通信経路のセキュリティ

ここまで詳細に解説してきませんでしたがOpenID認証プロトコルのフェイズにおいて、どのようにセキュリティ上の安全性を担保しているかを解説しましょう。

まずはassociateモードを正常に実行するSmartモードの場合です。

ConsumerはユーザーからのClaimed Identifierを受け取ると、associateのキャッシュが存在しない場合は新規にIdPに対してassociateモードのリクエストを行います。第3回で「associateモードは、ConsumerとIdP間で共通鍵を共有するために行う手続きです」と説明しましたが、ここでConsumer—IdP間で共通鍵を共有する手続きを行っています。ここで大事な点は、Consumer—IdP間（さらにUserAgentを介したリダイレクション）でのメッセージのやりとりには必ずHMACを付与して、そのメッセージが改ざんされていない妥当なものであることを確認できるようにしています。

HMACとはkeyed-Hashing for Message Authentication Codeの略で、受信者、送信者ともに同じハッシュ関数と秘密鍵を用いて生成されたコードのことです。HMAC-SHA1ならばハッシュ関数のSHA1を用いたものとなります。

図1 HMACとは

このHMACですが、OpenID Authentication 1.1ではHMAC-SHA1を用いることになっています。さらにいうと、HMACはOpenID認証プロトコルでは2つの使われ方があります。

・checkid_setup／checkid_immediateモードの際に使われるConsumerが生成したIdPへのリダイレクト先URLに付与する署名 ここではConsumerの秘密鍵を用いたHMACが使われる ・id_resモードでIdPから返される認証結果文字列トークンの署名 IdPの秘密鍵を用いたHMAC（Smartモード時）

前者の方は、再びConsumerにリダイレクトされた際に自身の秘密鍵とともに確認すればよいので特に問題はありませんが、後者は認証結果文字列を受け取った際、事前にIdP側の秘密鍵をConsumerが知っていないと確認のしようがありません。もうお分かりかと思いますが、この後者のHMACの照合のために、事前にIdP側の秘密鍵をConsumerに伝える手段としてassociateモードが存在するのです。

このassociateモードで使われている共通鍵の共有手続きはどのようなもので、果たして安全なものなのでしょうか。

Diffie-Hellman鍵共有プロトコルを知る

associateモードで使われている共通鍵の共有方法はDiffie-Hellman（デフィー・ヘルマン）鍵共有（または鍵交換）プロトコルと呼ばれる方式です。この方式は事前に特別な秘密鍵などを共有することなしに盗聴の可能性のある通信路を使っても、秘密鍵の共有を可能にする暗号プロトコルです。

このプロトコルの要点だけを解説します。

まず通信を行いたい2者はおのおの、 公開鍵と秘密鍵を用意し、公開鍵のみ交換する

互いに自分の秘密鍵から生成されるデータを他方に渡すと、渡された方は自分の秘密鍵と相手から渡されたデータを用いて共通鍵を生成できる

という仕組みです。

図1 DH鍵共有のイメージ

いまいちピンとこない方もいるでしょうから、実際にPerlのDiffie-Hellman鍵共有を実装したモジュールを用いて実例を出しましょう。

#!/usr/bin/perl use strict; use warnings; use Crypt::DH;sub default_dh { my $dh = Crypt::DH->new( p => 8995127215884267541995034125870655, g => 2 ); $dh->generate_keys; return $dh; } my $cdh = default_dh(); my $sdh = default_dh(); printf("[consumer] pub: %s, sec: %s

", $cdh->pub_key, $cdh->priv_key); printf("[server] pub: %s, sec: %s

", $sdh->pub_key, $sdh->priv_key); printf("shared secret(1): %s

", $cdh->compute_secret($sdh->pub_key)); printf("shared secret(2): %s

", $sdh->compute_secret($cdh->pub_key)); リスト1 PerlによるDiffie-Hellman鍵共有を実装したモジュール例

実行結果は次のようになります（実行時によって値は異なります）。

$ perl dh.pl [consumer] pub:7802933889436668690536359939538944, sec: 1867083868630338106918631918411838 [server] pub:4449244932917301413656781299646464, sec: 635847400798390060708440216901066 shared secret(1):8768538400044258590443590051168256 shared secret(2):8768538400044258590443590051168256 リスト2 サンプルスクリプトを実行した結果

Consumer、Serverともに異なる秘密鍵、公開鍵ですが、互いに相手の公開鍵を用いて、同じ共通鍵を生成できているのが分かると思います。Diffie-Hellman鍵共有では事前にp値、g値というのを互いに公開していることが前提になります。p値は十分大きい素数で、g値は2以上の自然数です。また生成される公開鍵、秘密鍵は実行ごとにランダムに選ばれます。

この方式では、仮に通信経路で公開鍵が傍受されようともアルゴリズムの性質上、そこから生成される共通鍵を解くのは相当困難という方式なので、まずConsumer—IdP間での通信経路は安全性が確保されているといえます。

OpenIDがLWPx::ParanoidAgentを使う理由

さて、HMACやDiffie-Hellman鍵共有によって一見メッセージのやりとり自体の安全性は高いと思いたくなります。しかしちょっと待ってください。実はまだ危険な点があります。

あなたがConsumerだとして、まさにassociateしようとしている相手が本当に正しい相手かどうか確認できているでしょうか。あるいはClaimed Identifierの確認のために実際にユーザーのURLへアクセスしますが、そのホスト名は正確なIPへ正引きできているでしょうか。

そのような危険性に対して神経質なアクセスを行うのがLWPx::ParanoidAgentです。OpenIDの仕様上でも使うことを推奨しています。またホワイトリスト形式やブラックリスト形式にも対応しているので、詳しくはCPANのオンラインドキュメントを見てください。

1|2|3 次のページへ