【入門編】 OpenID Connect (OIDC) におけるIDトークンの検証手順 – 暗号理論・認証基盤 & エンドポイントセキュリティ防御ガイド

泥棒も顔負け?OpenID ConnectのIDトークン検証、家の鍵に例えて優しく解説します!

皆さん、こんにちは!サイバーセキュリティの世界へようこそ。
今回は、Webサービスで「ログイン」を簡単かつ安全にするための重要な仕組み、「OpenID Connect(OIDC)」、特にその中でも「IDトークン」という、いわば「身分証明書」をどうやって信用できるか、その検証方法について、皆さんの身近な「家の鍵」に例えながら、泥棒の手口と合わせて分かりやすく解説していきますね。

IT担当者の方や、これからセキュリティを学び始める開発者の皆さんにとって、ちょっと難しそう…と感じるかもしれませんが、大丈夫!一つずつ丁寧に紐解いていきましょう。

なぜIDトークンを検証する必要があるの?

Webサービスでログインする時、皆さんはIDやパスワードを入力しますよね。でも、最近はGoogleアカウントやFacebookアカウントでログインできるサービスも増えています。これがOpenID Connectの得意技なんです。

OpenID Connectは、色々なサービス間で「あなたが誰であるか」を安全に証明してくれる仕組みです。その証明書のようなものが「IDトークン」と呼ばれます。

でも、考えてみてください。もし、偽物の身分証明書を持った泥棒が、あなたの家に入ろうとしたら…?
「俺は〇〇さんだ!」と、偽の身分証明書を差し出されたら、あなたはそれを鵜呑みにして家に入れてしまいますよね。

IDトークンも同じです。悪意のある攻撃者は、偽のIDトークンを作って、サービスに「私はこの人です!」と騙そうとする可能性があります。だからこそ、受け取ったIDトークンが本物かどうか、しっかりと検証する必要があるんです。

IDトークンって、どんな情報が入ってるの?(家の鍵と鍵穴)

IDトークンは、JSONという形式で書かれたデータで、中には色々な情報が含まれています。

例えば、こんな情報です。

  • iss (Issuer): このIDトークンを発行したサービスの名前(例:「Google」や「Facebook」)
  • aud (Audience): このIDトークンが、どのサービスのために作られたか(例:「あなたの使っているSNSアプリ」)
  • exp (Expiration Time): このIDトークンがいつまで有効か

これらの情報は、まるで家の鍵と鍵穴の関係に似ています。

  • iss(発行者): 鍵を作る「鍵屋さん」の名前
  • aud(対象者): その鍵が「あなたの家の玄関」でしか開かない、という情報
  • exp(有効期限): 鍵が「いつまで有効か」という情報(古い鍵は効かないですよね)

そして、IDトークンには、そのトークンが改ざんされていないことを証明するための「署名」も付いています。これは、鍵に「この鍵は〇〇さんの家専用ですよ」という刻印がされているようなイメージです。

攻撃者の手口:「偽の鍵」を差し出す!(署名アルゴリズムの固定化攻撃)

さて、ここからが泥棒の怖いところです。
攻撃者は、IDトークンが本物かどうかを検証する仕組みの「盲点」を突こうとします。

その一つが、「署名アルゴリズムの固定化攻撃」と呼ばれるものです。
IDトークンに付いている署名は、特定の「暗号化のルール(アルゴリズム)」を使って作られています。このルールが分かれば、本物そっくりの偽の署名を作ることができてしまうんです。

泥棒が、あなたの家の鍵に似た別の鍵を持ってきて、「これで開くはずだ!」と言ってくるようなものです。もし、あなたが「どんな鍵でも開くかも?」と、鍵穴に合わない鍵でも無理やり差し込もうとしたら、どうなるでしょうか?

攻撃者は、IDトークンを受け取る側(あなたのサービス)が、使用する署名アルゴリズムを限定せずに、どんなアルゴリズムの署名でも受け入れてしまう脆弱性を狙います。本来であれば、「この鍵は、この鍵穴専用のルール(アルゴリズム)で作られていないとダメだよ」とチェックすべきところを、チェックせずに受け入れてしまうのです。

泥棒の侵入を防ぐ!IDトークンの「厳重な検証」手順

では、どうすればこの偽の鍵(改ざんされたIDトークン)で泥棒が侵入するのを防げるのでしょうか?
受け取ったIDトークンが本物であると確認するために、以下の3つのステップを丁寧に行う必要があります。

ステップ1:発行者(iss)と対象者(aud)は間違いない?(「この鍵は、本当に〇〇さんの家のですか?」)

まず、IDトークンに書かれている発行者(iss)と対象者(aud)が、あなたが期待しているものと一致するか確認します。

  • iss の検証: 「あれ?このIDトークン、『Google』って書いてあるけど、私が連携したのは『Facebook』のはず…」となったら、それは怪しい!発行者が一致しないIDトークンは、すぐに破棄しましょう。
  • aud の検証: 「このIDトークン、私のサービス(アプリ)宛てじゃなくて、他のサービス宛てのものじゃない?」という場合も危険です。対象者(aud)が、あなたのサービス(クライアントIDなど)と一致するか確認します。

ステップ2:まだ有効なトークン?(「この鍵、まだ使えますか?」)

次に、IDトークンに書かれている有効期限(exp)を確認します。
「このIDトークン、もう期限切れだよ!」という場合は、残念ながら使えません。泥棒が古い鍵を使い回そうとしている、という状況に似ていますね。

現在時刻が、exp の値よりも前であることを確認してください。

ステップ3:署名は本物?(「この刻印は、本物の鍵職人さんのものです!」)

ここが一番重要で、泥棒が最も狙いやすい部分です。
IDトークンには、そのトークンが改ざんされていないことを証明するための「署名」が付いています。この署名を検証することで、IDトークンが本当に発行者によって正しく作られたものであることを確認します。

この検証には、「公開鍵暗号方式」という、ちょっと高度な技術が使われます。

公開鍵暗号方式って、何?

これは、世の中に「公開鍵」と「秘密鍵」という、ペアになる2つの鍵がある仕組みです。

  • 秘密鍵: 発行者(例:Google)だけが持っていて、IDトークンに「署名」するために使われます。これは、家の「マスターキー」のようなもので、絶対に誰にも知られてはいけません。
  • 公開鍵: 誰でも入手できるように公開されている鍵です。この公開鍵を使うと、「秘密鍵で署名されたものが本物かどうか」を検証できます。これは、家の「鍵穴」に似ています。

IDトークン検証における公開鍵の役割

1. 発行者(例:Google) は、秘密鍵を使ってIDトークンに署名をします。
2. あなたのサービス は、発行者(Google)が公開している「公開鍵」を入手します。
3. あなたのサービス は、受け取ったIDトークンの署名と、IDトークン自体の情報、そして入手した公開鍵を使って、「この署名は、この公開鍵に対応する秘密鍵で作成されたものか?」を検証します。

署名アルゴリズムの固定化攻撃を防ぐための「公開鍵検証」

ここで、先ほどの攻撃の話に戻ります。
攻撃者は、本来想定されている署名アルゴリズムとは違う、脆弱なアルゴリズム(例えば、署名が全く必要ない、なんていうルール)を使って署名したIDトークンを送りつけてくるかもしれません。

「この鍵は、どんな形でもいいから、とにかく鍵穴に入ればOK!」という、緩すぎるルールになっていたら、どんな偽の鍵でも通ってしまいますよね。

これを防ぐために、あなたのサービスでは、発行者が使用している「正しい署名アルゴリズム」を事前に決めておき、そのアルゴリズムで生成された署名でなければ受け入れない、という厳格なルールを設定する必要があります。

例えば、IDトークンに alg というヘッダーがあり、そこで署名アルゴリズムが指定されています。
「このIDトークンは、RS256 というアルゴリズムで署名されていますね。」
と書かれていたら、あなたのサービスでは、RS256 以外で署名されたIDトークンは、たとえ署名が正しくても「偽物だ!」と判断して拒否するのです。

これが、攻撃者が脆弱なアルゴリズムを悪用して偽のIDトークンを送りつけてくるのを防ぐための、最も重要な防御策となります。

実際の検証プロセス(JavaScriptでの例)

ここでは、JavaScriptを使って、OpenID ConnectのIDトークンを検証する際の基本的な流れを、擬似的なコードで示してみます。実際には、oidc-client-js のようなライブラリを使うのが一般的ですが、ここでは原理を理解するために、少し簡略化して説明しますね。

まず、IDトークンを受け取ったと仮定しましょう。

// 仮のIDトークン(実際には、認証サーバーから取得します)
const idToken = "eyJhbGciOiJSUzI1NiIsImtpZCI6IjEyMzQ1Njc4OSJ9.eyJpc3MiOiJodHRwczovL2V4YW1wbGUuY29tL2F1dGgiLCJhdWQiOiJ5b3VyX2NsaWVudF9pZCIsImlhdCI6MTY3ODg4NjQwMCwiZXhwIjoxNjc4ODkwMDAwfQ.SOME_SIGNATURE_HERE";

// 設定情報(これらの値は、認証サーバーの設定によって異なります)
const issuer = "https://example.com/auth"; // 発行者 (iss)
const clientId = "your_client_id"; // 対象者 (aud)
const expectedAlgorithm = "RS256"; // 期待する署名アルゴリズム

// IDトークンをデコードして、ヘッダーとペイロードを取り出す
// Base64エンコードされているので、デコードが必要です
function base64UrlDecode(str) {
    let base64 = str.replace(/-/g, '+').replace(/_/g, '/');
    let padding = base64.length % 4 === 0 ? '' : '='.repeat(4 - base64.length % 4);
    return decodeURIComponent(atob(base64 + padding).split('').map(function(c) {
        return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
    }).join(''));
}

const parts = idToken.split('.');
const header = JSON.parse(base64UrlDecode(parts[0]));
const payload = JSON.parse(base64UrlDecode(parts[1]));
const signature = parts[2]; // 署名部分

// --- ここからが検証処理 ---

// 1. 発行者 (iss) と対象者 (aud) の検証
if (payload.iss !== issuer) {
    console.error("IDトークンの発行者 (iss) が一致しません。");
    // ここで処理を中断し、エラーレスポンスを返します
    return;
}

if (payload.aud !== clientId) {
    console.error("IDトークンの対象者 (aud) が一致しません。");
    // ここで処理を中断し、エラーレスポンスを返します
    return;
}

// 2. 有効期限 (exp) の検証
const now = Math.floor(Date.now() / 1000); // 現在時刻を秒単位で取得
if (payload.exp < now) {
    console.error("IDトークンは有効期限切れです。");
    // ここで処理を中断し、エラーレスポンスを返します
    return;
}

// 3. 署名アルゴリズムの固定化攻撃を防ぐための検証
if (header.alg !== expectedAlgorithm) {
    console.error(`IDトークンの署名アルゴリズム (${header.alg}) が期待されるアルゴリズム (${expectedAlgorithm}) と一致しません。`);
    // ここで処理を中断し、エラーレスポンスを返します
    return;
}

// 4. 公開鍵による署名の検証(※これは概念的な説明であり、実際の検証はライブラリに任せるのが安全です)
// 実際には、認証サーバーから公開鍵を取得し、署名検証ライブラリ(例:crypto-js, joseなど)を使用して検証します。
// ここでは、署名が有効であると仮定します。
console.log("署名アルゴリズムも一致し、IDトークンは有効な署名を持っていると仮定します。(実際の検証はライブラリで行います)");

// 全ての検証に成功した場合
console.log("IDトークンの検証に成功しました!");
console.log("ユーザー情報:", payload);
// ここで、取得したユーザー情報に基づいてログイン処理などを続行します。

コードのポイント解説

  • base64UrlDecode: IDトークンはBase64URLという形式でエンコードされています。これを元のJSON形式に戻すための関数です。
  • header と payload: IDトークンは「ヘッダー」「ペイロード」「署名」の3つの部分に分かれています。ヘッダーにはアルゴリズムなどの情報、ペイロードには発行者や有効期限などの情報が入っています。
  • payload.iss !== issuer: 発行者が一致するかどうかをチェックしています。
  • payload.exp < now: 有効期限が切れていないかチェックしています。
  • header.alg !== expectedAlgorithm: ここが、署名アルゴリズムの固定化攻撃を防ぐための肝となります。「このアルゴリズムじゃないとダメ!」と、固定しているのがポイントです。
  • 署名検証の注意点: 上記コードの「4. 公開鍵による署名の検証」の部分は、実際の署名検証処理を省略しています。なぜなら、公開鍵暗号方式を使った署名検証は非常に複雑で、実装ミスがセキュリティホールにつながりやすいからです。

実務では、信頼できるライブラリを使いましょう!

実際に開発する際には、上記のような手動での検証ではなく、oidc-client-js (JavaScript)、omniauth (Ruby on Rails)、Spring Security OAuth (Java) のような、OpenID ConnectやOAuth 2.0を扱うための実績のあるライブラリを利用することを強くお勧めします。これらのライブラリは、署名検証などの複雑な処理を安全に、かつ効率的に行ってくれます。

まとめ:あなたのサービスを守るために

今回は、OpenID ConnectのIDトークン検証について、泥棒の手口や家の鍵に例えながら解説しました。

  • IDトークンは、サービス間の「身分証明書」
  • 検証の基本は、「発行者」「対象者」「有効期限」の確認
  • 最も重要なのは、「署名」の検証。特に、alg ヘッダーで指定された「署名アルゴリズム」が、意図しないものになっていないか厳しくチェックすることが、攻撃を防ぐ鍵!

これらの検証を怠ると、攻撃者は偽のIDトークンを使って、あたかも正規のユーザーであるかのようにあなたのサービスに侵入できてしまうかもしれません。

セキュリティは、一つ一つ理解し、地道に対策を積み重ねていくことが大切です。
今回の解説が、皆さんのサービスをより安全にするための一歩となれば幸いです。

これからも、一緒にセキュリティの知識を深めていきましょう!

コメント

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