2024年5月21日火曜日

コンテンツの真正性検証に関する取り組み

こんにちは、富士榮です。

先日のInternet Identity Workshop(IIW)でも何個かセッションがあったのですが、生成AIなどの普及を踏まえてコンテンツの真正性(Content Authenticity)に関する関心が高まっているようです。

生成AIで生成AIのイメージを生成させた絵

関連する取り組みとしては、こんなものがあります。

  • CAI(Content Authenticity Initiative)
    • https://contentauthenticity.org/
  • C2PA(Coalition for Content Provenance and Authenticity)
    • https://c2pa.org/
    • CAIのEffortも統合して活動していますね

また、日本でもOriginator Profile技術研究組合が活発に活動している領域です。


最近面白いなぁ、と思ったのはTikTokがCAIとC2PAに参加したって言うニュースですかね。

https://contentauthenticity.org/blog/tiktok-joins-content-authenticity-efforts


この辺りもデジタルアイデンティティと密接に結びつく分野なので要ウォッチです。


2024年5月20日月曜日

response_mode、”form_post"の実装

こんにちは、富士榮です。

EntraをはじめとするMicrosoftのIDスタックを使って開発をしていると必ずといっていいほど出てくるのがreponse_mode=form_postの壁です。特に非Microsoftのライブラリを使ってリライングパーティを開発する場合や、MicrosoftのIDスタックをリライングパーティとして非MicrosoftのIDaaSを構築する場合にはこのパラメータへの対応の有無で悩むことになります。

ということでresponse_modeって何?と言う話と自前でIdPを構築する際にform_postを実装する場合はどんな実装になるのか?を解説してみます。

response_modeとは?
OpenID Connect 1.0の仕様にはこのように定義されています。
OPTIONAL. Informs the Authorization Server of the mechanism to be used for returning parameters from the Authorization Endpoint. This use of this parameter is NOT RECOMMENDED when the Response Mode that would be requested is the default mode specified for the Response Type.

まぁ、元々はOAuth2.0のレスポンスの定義なので、こちらの仕様をみる方が適切といえそうです。

https://openid.net/specs/oauth-v2-multiple-response-types-1_0.html#ResponseModes

OPTIONAL. Informs the Authorization Server of the mechanism to be used for returning Authorization Response parameters from the Authorization Endpoint. This use of this parameter is NOT RECOMMENDED with a value that specifies the same Response Mode as the default Response Mode for the Response Type used. 

このOAuth2.0の仕様が発行されたのが2014年なのでその時点ではqueryとfragmentの2種類だけが本文には記載されており、追加モードとして今回の主役であるform_postの定義への参照が記載されています。


と、ここでちょっと待て、、と。

上記を見るとresponse_typeのデフォルトのresponse_modeの値を取る場合以外はこのパラメータを使うことは推奨しない、と明確に書いてありますね。

ということは、Azure AD B2Cが外部IdPと連携する際にresponse_typeの値に関わらずresponse_modeを必ず指定するのは推奨外の動作ってことですよね。。

また、先日書いたEntra IDの外部認証プロバイダ連携の場合はresponse_type=id_tokenでリクエストがされ、かつ外部認証プロバイダからはform_postでレスポンスが返されることを期待するにも関わらずresponse_modeが指定されないのはどうなんだ、、、という話です。(というか世の中にあるIdP製品やサービスでresponse_type=id_tokenだけをリクエストされてform_postでレスポンスするものは存在しないと思います)

この辺はMicrosoftさんちゃんと仕様を見ようよ・・・って思いますね。。

ちなみにresponse_type=id_tokenの場合のデフォルトのresponse_modeはfragmentです。(こちらに定義されています)

https://openid.net/specs/oauth-v2-multiple-response-types-1_0.html#id_token

The default Response Mode for this Response Type is the fragment encoding and the query encoding MUST NOT be used.


form_postとは?

気を取り直してresponse_mode=form_postの話に戻ります。response_modeは認可サーバからの認可レスポンスを返却するための方式を示すパラメータであることはわかりましたが、値にform_postが指定するというのはどう言うことなのか、についてはこちらの追加仕様に記載されています。

https://openid.net/specs/oauth-v2-form-post-response-mode-1_0.html

Abstractにこう記載されています。

This specification defines the Form Post Response Mode. In this mode, Authorization Response parameters are encoded as HTML form values that are auto-submitted in the User Agent, and thus are transmitted via the HTTP POST method to the Client, with the result parameters being encoded in the body using the application/x-www-form-urlencoded format.

まぁ、要するにHTML formにresponse情報を入れてredirect_uriにPOSTしますよ、ってことです。ws-federationやSAMLのHTTP POST Bindingですね。

ここでも話は横にそれますが、今でこそOAuth2.0やOpenID Connectは認可コードをリライングパーティに発行し、リライングパーティは認可サーバのTokenエンドポイントへ投げ込んでaccess_tokenやid_tokenを受け取る、いわゆるresponse_type=codeのコードフローが主流になっていますが、SAMLも当初はHTTP POSTやRedirect BindingではなくArtifact Bindingが主に使われていた時代がありました。当時はフィーチャーフォンのブラウザなど扱えるURL長に制限があったり、フロントに大きなPOSTデータを持ってくると通信量が増えてパフォーマンスに大きく影響が出るなどの問題があり、ArtifactといわれるコードをService Providerへ提供、Service ProviderがIdentity ProviderへSAMLトークンをとりにいく、という流れが必要だったためです。当時と今では事情がことなりますが、認可コードフローとのままですね。なんといってもOpenID Connectは、開発中はOpenID ABC(Artifact Binding and Connect)って名前でしたし、OpenID Connect Coreの定義をしている、OpenID FoundationのAB/ConnectワーキンググループはArtifact Binding Working GroupとConnect Working Groupから組成されており、ABはArtifact Bindingなわけです。


話を戻すと、Entra IDの外部認証プロバイダの際にも書いた通り、id_tokenとstateをformに乗せてPOSTしてあげるためのHTMLをレンダリングするコードを書けば良いってことになります。

node_expressとejsで書くとこんな感じです。

res.render("./form_post.ejs",
{
redirect_uri : req.body.redirect_uri,
id_token: id_token,
state: req.body.state
}
);

レンダリングされるhtmlテンプレートはこんな感じですね。(前回の記事では動きを見るために自動POSTしていませんでしたが、今回はJavaScriptで自動POSTする形にしています)

<html>
<head><title>Submit This Form</title></head>
<body onload="javascript:document.forms[0].submit()">
<form method="POST" action="<%= redirect_uri%>">
<input name="id_token" type="hidden" value="<%= id_token%>">
<input name="state" type="hidden" value="<%= state%>">
</form>
</body>
</html>


なぜform_postにこだわるのか?

しかし、form_postを使っているのって実態としてMicrosoftくらいしかいないんですよね・・・(Sign In with Appleも使ってたかも)

現状、ブラウザを経由してトークンのやり取りをする、つまりArtifactや認可コードを使わないパターンを使いたい大きな理由は先ほど挙げたフィーチャーフォンのブラウザの制限や通信速度・通信量の問題というよりも、エンタープライズなどIdPがファイアウォールの内側にありリライングパーティが外側にあるというケースやWalletがIdPになるケースなど、IdPが外部からのアクセスできない(Walletの場合はエンドポイントを持たない)という問題である場合が多いと思います。

その場合は当然Implicitを使う、つまりフラグメントにトークンを入れてJSでリライングパーティへ渡す、というやり方になるわけですが、これだとリライングパーティのredirect_uriがブラウザ上でJSをハンドリングする機能を実装することが前提になってしまいます。もちろんこれができるケースならば良いのですが、リライングパーティ向けの共通ライブラリを提供しようとすると単純にバックエンドでPOSTを受け取るAPIを作っておく方が楽なんじゃないかと思います。さらに言うと、これまでws-federationやSAMLのSP向けのライブラリを提供してきたベンダであれば少しパラメータを変えれば対応できると言う意味でform_postの方がありがたかった、と言うのが実情だったんだろうなぁ、、と聞いた話から推測しています(あくまで推測です)。まぁ、SameSiteの問題もあったりしますがこの辺は各社対応してきているので現状は大丈夫だと思いますが。


ということで、今回はresponse_modeとform_postの話をしましたが、みなさんが作るIDシステムやアプリケーションのシステム配置や要件によって適切な実装をしていきましょう。








2024年5月19日日曜日

NIST SP800-63B-3の同期可能クレデンシャルに関する追補版を読む(7)

こんにちは、富士榮です。

NIST SP 800-63B-3の追補版も最後のまとめ部分を残すのみです。

早速ですが本文書でどのように結論づけているのかをみていきましょう

同期可能な認証器は、認証の状況、特に多要素暗号認証子の使用における実質的な技術的変化を示すものである。暗号鍵の複製とクラウド・インフラへの保存を許可することに関連するトレードオフの 評価は、必然的に行われることになる。このことは、明確なリスク(認証鍵に対する企業管理の喪失など)をもたらすが、同時に、 一般市民や企業にとっての主要な脅威ベクトルを排除する、便利でフィッシングに強い認証機能への道筋を提供する。同期可能な認証器は、すべてのユースケースに適しているわけではない。しかし、本補足文書に含まれるガイドラインに沿った方法で導入される場合、AAL2 リスク軽減策との整合性を達成し、フィッシング耐性認証の採用をより広範に促進することができる。

企業等で利用する場合は鍵が制御外のファブリック(iCloudなど)に保存されることによるリスクがありますが、一般ユーザのシナリオではフィッシング耐性のない認証器を使うよりはマシっていうことなんでしょうねぇ。要はユースケースに応じたリスク分析をちゃんとやろうねっていうNISTのガイドラインの根本は変わらないわけで。

この文書は、既存のデジタル・アイデンティティ・ガイドラインに付随するものであり、各省庁 が十分な情報に基づきリスクベースの決定を行い、適切な場合には最新の業界イノベーション を統合するための情報を提供する。

改めて本文書は既存のガイドラインの付随文書であることを強調し、先に書いたとおりちゃんとしたリスク分析に則って対策を考えましょう、ということです。

ということでこれで一通り見てきたわけですが、私的なまとめとしては、

  • 同期と共有は違うぞ
  • 同期を許容する場合はバックエンドの同期ファブリックの信頼性も評価しないとだめ
  • 統制レベルを上げる場合は同期を不許可にするようにMDMなどで制御することも必要
  • ただ、かといってフィッシング耐性がない認証器を許容するよりはマシな場合もある
  • 結局ユースケースごとにリスク評価をしましょうね
  • その意味でこのガイドライン追補版では新たなタイプの認証器の特徴とリスクなどをまとめたのでちゃんと導入するように
ということくらいでしょうか。


2024年5月18日土曜日

NIST SP800-63B-3の同期可能クレデンシャルに関する追補版を読む(6)

こんにちは、富士榮です。

少し間が開きましたがNIST SP 800-63B-3の追補版の続きです。

今回は同期と共有は違うぞ!という話のうちの共有の部分です。1回目にも書きましたがAppleの共有パスワードグループの話などもありますので、非常に重要な部分だと思います。

サイバーセキュリティ・ガイドラインは、歴史的に、ユーザ間で認証器を共有しないよう警告してきた。このガイダンスにもかかわらず、一部のユーザ・グループやアプリケーションでは、個人がデジタル・アカウントへのアクセスを共有できるようにするために、認証器とパスワードの共有が行われている。

表 3 に示すように、一部の同期可能な認証器の実装は、このようなユーザの行動を受け入れ、 異なるユーザ間で認証キーを共有する方法を確立している。さらに、一部の実装は、一般的なサービスにおいて、パスワード共有に代わる便利で より安全な方法として、同期可能な認証器の共有を積極的に推奨している。 

言われてますよ、Appleさん。。

企業ユースケースの場合、鍵の共有に関する懸念は、鍵が承認されたデバイスや同期 ファブリックから移動されることを制限するデバイス管理技術を使用することで、効果的に緩和することができる。しかし、公衆向けのユースケースでは、同様の緩和策は現在のところ利用できないため、 リライングパーティは、同期可能な認証プロバイダが採用する共有モデルに依存することになる。公衆向けアプリケーションの所有者は、共有認証器に関連するリスクを認識する必要がある。公衆と対話する場合、機関は、ユーザがどの特定の認証器を使用しているのかについて限られ た可視性しか持たないため、すべての同期可能な認証器が共有の対象となる可能性があることを想定する必要がある。多くの共有モデルには、リスクを最小化する実質的な制御(例えば、共有を許可するためにデバイス間の近接を要求する)があるが、他の実装はそれほど制限的ではない。

やっぱり共有を制御する方法とセットになっていることが必要なんじゃないかと思いますね。この辺りの判断をリライングパーティに全部任せちゃうのは実質不可能だと思いますし。

この新しいクラスの認証者がもたらす共有のリスクは、特別なものではない。実際には、すべての AAL2 認証タイプに適用され、中には同期可能な認証タイプより弱いものもある。AAL2 のどの認証子も、それを共有しようとするユーザによって共有される可能性がある。ユーザは、パスワード、OTP、帯域外認証子、さらにはプッシュ認証イベントを積極的に共有したり、被指名人(正式か否かを問わない)がエンドユーザに代わって認証を行うことを許可したりすることができる。

各省庁は、直面する特定のリスク、脅威、およびユーザビリティの考慮事項に基づいて、アプリ ケーションにどの認証手段を受け入れるかを決定する。同期可能な認証方式は、AAL2 までの実装を求めるアプリケーションの新しいオプションとして提供されるかもしれず、他の認証方式と同様に、この技術のトレードオフは、セキュリティ、プライバシー、公平性、およびユーザビリティに対する期待される結果に基づいて、うまくバランスをとるべきである。

そろそろこのレポートもおしまいです。

というか早くSP800-63-4の次のドラフトが出て欲しいですね。

 

2024年5月17日金曜日

IIWの振り返り by Phil Windley

こんにちは、富士榮です。

昨日はOpenID TechnightでInternet Identity WorkshopとOpenID Foundation Workshopの振り返りをしましたが、そういえば主催者の一人のPhil Windleyも振り返りのポストをしていたなぁ、と思い紹介したいと思います。

https://www.windley.com/archives/2024/04/internet_identity_workshop_xxxviii_report.shtml

参加者の分布が分析されています。


US (241), followed by Canada (11). Germany, India, and Switzerland rounded out the top five with 9, 8, and 7 attendees respectively. Attendees from India (5), Thailand (3), and Korea (3) showed IIW's diversity with attendees from APAC. And there were 4 attendees from South America this time. Sadly, there were no attendees from Africa again.

やっぱり北米が多いですねぇ。ローケーション的に仕方ありませんが。

なお、ここには記載がありませんが日本からは5〜6名だったと思います。それなりに参加していると思います。

会場の写真も紹介されています。私も左の端の方にこそっと写っています(人の影なので顔は隠れていますが)



毎回ですがDoc SearlsがFlickrにアルバムを作っているので現地の様子が知りたい方はぜひご覧ください。


https://www.flickr.com/photos/docsearls/albums/72177720316609417/








2024年5月16日木曜日

OpenID Technight #21のスライド

こんにちは、富士榮です。

以前告知した通り、本日はOpenID Technightでしたね。

https://openid.connpass.com/event/316748/





私は4月に開催されたOpenID Foundation WorkshopとInternet Identity Workshopからトピックスをお伝えしたわけですが、時間もあまりなかったので伝えきれなかったところも多々あります。スライドをこちらに置いておきます。よろしければどうぞ。

https://speakerdeck.com/fujie/openid-foundation-updates








2024年5月15日水曜日

今年もBuildのシーズンがやってきた

こんにちは、富士榮です。

今年もBuildのシーズンですね。


https://build.microsoft.com/en-US/home

今年もシアトルでの対面とオンラインのハイブリッド開催のようです。しかし昨年もそうだったんですが、今年もセキュリティやアイデンティティ系のセッションは殆どがIn person(対面)のみ、かつレコーディングもされないという悲しい感じです。

ざっくりセキュリティ、アイデンティティで絞り込むとこのくらいのセッションが出てきます。(少ないですね・・・)

codetitletypedatetimedescription
DEM764Combatting fraud with real time identity verificationin person5/233:45 AM - 4:00 AMAnyone can identify a bicycle or count stop lights. How do we know our apps are serving the right users? CAPTCHAs do not work. Introducing Face Check using Microsoft Entra Verified ID: reduce sign-up friction, the risk of fraud and account takeover. Face Check enables apps to perform real time biometric match against identity documents issued by government (e.g. driver's license or passports) or businesses and education institutions.
-On-Demand: Boost your app security with real time biometric authenticain personon-demandIntegrate simple-to-use APIs to upgrade your mobile, web or desktop apps with high-assurance identity verification to reduce friction and risk from account takeover and impersonation.
DEM760Create secure applications in minutes with VS Code and External IDin person5/223:30 AM - 3:45 AMLearn how to use the Microsoft Entra External ID extension for Visual Studio Code to create your first External ID application completely within your IDE. Bootstrap your development with pre-configured sample applications to quickly get you started.
DEM768Create pixel perfect authentication experiences for native mobile appsin person5/244:45 AM - 5:00 AMThe Authentication API and SDK in External ID allow developers to create pixel perfect UX for sign in and sign up experiences in their mobile applications. Join our product experts to explore the APIs and SDKs for Microsoft Entra External ID that give you the control and flexibility to create fully custom and secure login experiences on mobile devices.
-True zero-trust runtime security in AKSonlineon-demandNeuVector is open source, container native and can make your containerized workloads more secure… today! See first hand how to get to true zero-trust runtime security in AKS and other Kubernetes deployments with NeuVector by SUSE. We will take an application through its deployment lifecycle, from Dev/Test to Q/A to Production, and automate the 'fingerprint" of appropriate behaviors in your software stack. Join us for this real-world example of how to not just identify attacks but to actually prevent them.
DEM766Simple and secure app authentication with authentication brokersin person5/236:45 AM - 7:00 AMWe delve into the integration of Web Account Manager (WAM) on Windows through various MSAL libraries such as MSAL.NET, MSAL Python, and MSAL Java. The session will highlight the seamless authentication experiences enabled by WAM, which simplifies account management on Windows devices. We’ll explore how MSAL libraries facilitate public client authentication flows with Microsoft Entra ID, enhancing web, mobile, and desktop applications.
DEMFP867Unleash the power of network APIsin person5/247:45 AM - 8:00 AMLearn how you can leverage the advanced capabilities of telecom operator networks through APIs to enhance your applications. This session will cover GSMA Open Gateway and CAMARA, examples of how mobile operators are working with developers to open up network APIs, and how developers can engage via Microsoft’s platform and services with Azure Programmable Connectivity (APC). We will also showcase a demo mobile app using the network's APIs through APC and show the code on how to develop with it.


個人的に興味があるのは、1行目のCombatting fraud with real time identity verificationと最後の行のUnleash the power of network APIsくらいでしょうか。前者はEntra Verified IDとfacecheckの話です。後者はMicrosoftのイベントでは結構珍しくネットワークキャリアを対象としておりGSMAやCAMARAの話なんかをカバーしているっぽいので聞いてみたいですね。

レコーディングもされませんし配信もされないので聞けませんが・・・(残念)



2024年5月14日火曜日

Entra IDの外部認証プロバイダの設定を試す(3)

こんにちは、富士榮です。

引き続きEntra IDの外部認証について見ていきます。


今回は、前回のポストに記載したハマりポイントにも記載したjwksで公開する鍵の作り方を書いておきます。

ポイントはx5cを含む形でjwkを作成〜公開すること、です。要するに証明書を含め公開することでキーチェインがわかるようにする必要があるってことですね。

今回は当然自己証明証明書を使っているので、opensslで鍵ペアの作成等を行っています。

大まかには川崎さんが公開しているこちらのQiitaと類似の手順を通ればOKですが、今回はRSAでやったのでちょっと手順も異なる部分もあります。

前提)

  • MacOSを使っています
  • opensslはMacOS標準ではなくbrew install opensslをインストールした以下のバージョンを利用する必要があります(標準版だと一部新たなパラメータに対応していないため)
    • OpenSSL 3.3.0 9 Apr 2024 (Library: OpenSSL 3.3.0 9 Apr 2024)
  • pem-jwkを使いますのでnpm installしておいてください
  • jqを使いますので同じくnpm installしておいてください
ということで鍵生成をしていきます。

まずは鍵ペアの生成をします。いきなりjwk形式で作ります。

openssl genrsa -traditional 2048 | pem-jwk > keypair.jwk

次にpemに変換します。

pem-jwk keypair.jwk > keypair.pem

公開鍵を抽出します。

openssl pkey -pubout -in keypair.pem > public.pem

証明書を作ります。CNにはissuer名を指定します。 

openssl req -x509 -key keypair.pem -subj /CN=7...省略...25.ngrok-free.app -days 3650 > certificate.pem

jwkに証明書を入れます。

CERT=$(sed /-/d certificate.pem | tr -d \\n)

jq ".+{\"x5c\":[\"$CERT\"]}" keypair.jwk > key+cert.jwk


これで出来上がったjwkをjwks_uriに入れ込んで公開します。ちなみに上記ではuseのパラメータが設定されないことがあるので必要に応じて"sig"を手動でセットしておきます。

その後、こんな感じでjwksとしてセットします。


これをkeystoreとして読み込んでjwks_uriエンドポイントで公開します。

// jwks_uriエンドポイント
router.get('/jwks_uri', async (req, res) => {
const ks = fs.readFileSync(path.resolve(__dirname, "../keys/keystoreSign.json"));
const keyStore = await jose.JWK.asKeyStore(ks.toString());
res.json(keyStore.toJSON())
});

これでEntra IDが署名検証に使える状態で鍵を公開することができました。

こんな感じです。


まぁ、雑ですが一通りの原理はわかる状態まで持っていくことができました。

あとはちゃんと外部認証プロバイダ側を実装するだけですね。 

2024年5月13日月曜日

Entra IDの外部認証プロバイダの設定を試す(2)

こんにちは、富士榮です。

前回に引き続きEntra IDの外部認証を試していきます。

こちらのドキュメントを見つつ設定をしていきますが、なかなかハマりポイントがあります。ちなみに外部認証は細かい動きを見るためにも自前のIdPを使っていきます。

ハマりポイントだけ先に書いておきます。

  • jwks_uriエンドポイントに公開するjwk(id_tokenの署名検証するための鍵)はx5cを含む必要がある
  • id_tokenのJWTヘッダのメディアタイプは大文字"JWT”を指定する必要がある(昨日のポストの通り)
  • 外部認証プロバイダのdiscoveryとjwks_uriの情報をEntra ID側がキャッシュをするので変更があっても24時間は読みにきてくれない
  • 外部認証プロバイダから返却するid_tokenの中のamrは配列で返す必要はあるが、返す値は単一である必要がある
  • response_typeはid_token、reponse_modeはform_postで認証レスポンスを返却する必要がある(まぁ、この辺はいつものMicrosoftですね)
  • 外部認証プロバイダの認可エンドポイントへ各種パラメータがPOSTされてくる(こちらは前回書いた通り)


とりあえず、こんな感じで動きます。ngrokでローカルで動かしているIdPを読みにこさせているので画面左側にtrafic inspectorを出しています。Entra IDでログインする際にリクエストが来ているのがわかります。



外部認証プロバイダの認可エンドポイントへPOSTされてくるデータなどをtrafic inspectorで確認しながら実装を進めていくのが良いと思います。

ということで、外部認証プロバイダを実装していきます。

まずは認可エンドポイントです。

こちらが認可エンドポイントへ投げ込まれてくるパラメータですので、こちらに対応する形でid_tokenを発行して返してあげれば良いはずです。

パラメータ
scopeopenid
response_modeid_token
client_id外部認証プロバイダ側にEntra IDをRPとして登録した際のclient_idの値
redirect_urihttps://login.microsoftonline.com/common/federation/externalauthprovider
claims{"id_token":{"amr":{"essential":true,"values":["face","fido","fpt","hwk","iris","otp","tel","pop","retina","sc","sms","swk","vbm"]},"acr":{"essential":true,"values":["possessionorinherence"]}}}'
nonceEntra IDが払い出すnonceの値
id_token_hintEntra ID側で認証済みのユーザに関する情報
client-request-idEntra ID側でのトラッキングに使う識別子(サポート用)
stateEntra ID側が払い出すstateの値


最終ゴールはid_tokenを生成し、リクエスト内のstateと合わせてredirect_uriへPOSTしてあげることとなります。

こんな感じでエンドポイントを作っていきましょう。

// 認可エンドポイント(POST)
router.post("/authorize", async (req, res) => {
// Todo
// - redirect_uriが登録済みでEntra IDから提供されている規定値(https://login.microsoftonline.com/common/federation/externalauthprovider)であることの検証
// - client_idがEntra IDに割り当てたものであることの検証
// - id_token_hintの署名等の検証
// - ユーザ認証(id_token_hintに含まれるoid/tidを使ってユーザとの紐付け)
// - 認証応答を行う
// - redirect_uriへPOSTする
// - id_token
// - state : リクエストに含まれるstate(存在する場合)
// - id_tokenの中身
// - iss : idpのopenid-configurationで公開されているものと一致すること
// - aud : Entra IDに割り当てたclient_id
// - exp : 有効期限
// - iat : 発行時刻
// - sub : id_token_hintのsubと一致すること
// - nonce : リクエストに含まれるnonce
// - acr : リクエストのclaimsに含まれる値の一つと一致すること
// - amr : リクエストのclaimsに含まれる値と一致すること(配列)

結構やることはありますが、今回はまずはid_tokenを発行するところにフォーカスを当てますので、ユーザの認証や各種パラメータの検証は省略します。

id_tokenに含めるべき値にリクエスト内のid_token_hintに含まれる情報があるので、まずばid_token_hintのpayloadをデコードして値を取り出せる状態にパースします。

// とりあえずpayloadだけパースする(検証は後回し)
const id_token_hint_payload = req.body.id_token_hint.split('.');
const raw_id_token_hint = base64url.decode(id_token_hint_payload[1]);
const obj_id_token_hint = JSON.parse(raw_id_token_hint);

そしてid_tokenのpayloadを生成します。今回は検証もユーザ認証もしないので、acrやamrの値も決め打ちで設定しています。ちなみにハマりポイントにも記載した通り、amrは配列で値を設定する必要がありますが、値は単一でなければなりません。(今回はfidoを設定)

const date = new Date();

const raw_id_token = {
iss: 'https://' + req.headers.host,
aud: req.body.client_id,
exp: Math.floor((date.getTime() + (1000 * 60 * 10)) / 1000),
iat: Math.floor(date.getTime() / 1000),
sub: obj_id_token_hint.sub,
nonce: req.body.nonce,
acr: 'possessionorinherence',
amr: ['fido']
};

そして、このpayloadを署名してJWTと作ります。

const id_token = await utils.generateJWS(raw_id_token);

こちらも面倒だったので、opensslで作った秘密鍵をベタ打ちでコードに埋め込んでいますし、kidも生成したものをベタで指定しています。この辺はおいおい直します。

const jwt = require('jsonwebtoken');
const privatekey = `-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAmbjCIt20NKwMrH78TGOA8w9LS/R6B81RNHoJ/J+7UljRUfMN
sGC+RR6bqtDxeLgLjmt4s7CccbVf380DQx4b2XtCvoP0QW4GsJm7b13XkwCD6fWT
xSX5RTqXyTrLFk0ifOhRZ09QxZnAOGgUN12HeKjWj24XLQKFOROi8BhwHxLLSd+n
-- 省略 --
52EKdTgNOrVFGV/1qACGuDgLNDss+z2f2HgO/pk5UtS0EaXltv62IV6izkZh7f9O
aPXrS0BclfaGBZ0RcQIt0lJ2UMSTd8CFKX+k5efoFthX2ddWY24A
-----END RSA PRIVATE KEY-----`

exports.sign = async function(payload){
return jwt.sign(payload, privatekey, {
algorithm: "RS256",
keyid: "Vzy3LDbuzSrt0cQldElZp5R92etQvOCENEu5aOOppYs"
});
}

あとはid_tokenとstateをPOSTしてあげるだけですが、node+express+ejsを使っているのでres.renderで値をejsへ渡してあげます。

// form postするためのページをレンダリング
res.render("./form_post.ejs",
{
redirect_uri : req.body.redirect_uri,
id_token: id_token,
state: req.body.state
}
);

フォーム側はこんな感じです。実際は値はhiddenにして、JavaScriptで自動POSTするようにしますが、今回はステップバイステップで進めたかったので一旦フォームを表示するようにしています。

<html>
<body>
<div id="login_div" >
<form name="id_token" method="POST" action="<%= redirect_uri%>">
<input name="id_token" type="text" value="<%= id_token%>">
<input name="state" type="text" value="<%= state%>">
<input type="submit" value="POST">
</form>
</div>
</body>
</html>

これでうまくいけば上記の動画のように認証が完了します。

一旦、今回はここまでです。

2024年5月12日日曜日

JWTヘッダのメディアタイプの大文字・小文字問題でハマった話

こんにちは、富士榮です。

id_tokenを作るときにJWTヘッダのメディアタイプ(typパラメータ)の値として'JWT'を指定すると思いますが、この値の大文字・小文字ではまった話です。

ちなみに、こんな感じでjoseのライブラリを使ってJWTを作っていたのですが、下から2行目のオプション指定のところで{ typ: 'jwt' }という形で小文字指定をしていました。

// JWSの作成
exports.generateJWS = async function(payload) {
const ks = fs.readFileSync(path.resolve(__dirname, keyStoreFile));
const keyStore = await jose.JWK.asKeyStore(ks.toString());
const [key] = keyStore.all({ use: 'sig' });
   const opt = { compact: true, jwk: key, fields: { typ: 'jwt' } };
return jose.JWS.createSign(opt, key).update(JSON.stringify(payload)).final();
}

JWTの仕様を見る限りnot case sentisiveとあるので、その後にあるレガシー実装との互換性のために常に"JWT(大文字)"を使うことを推奨という文言を舐めていました。

If present, it is RECOMMENDED that its value be "JWT" to indicate that this object is a JWT.  While media type names are not case sensitive, it is RECOMMENDED that "JWT" always be spelled using uppercase characters for compatibility with legacy implementations.

仕様)

https://datatracker.ietf.org/doc/html/rfc7519#section-5.1


ということで、MicrosoftのRPに小文字'jwt'で作ったid_tokenを投げ込むとこんな感じで怒られます。

仕方なく、コードを修正しました。

exports.generateJWS = async function(payload) {
const ks = fs.readFileSync(path.resolve(__dirname, keyStoreFile));
const keyStore = await jose.JWK.asKeyStore(ks.toString());
const [key] = keyStore.all({ use: 'sig' });
const opt = { compact: true, jwk: key, fields: { typ: 'JWT' } };
return jose.JWS.createSign(opt, key).update(JSON.stringify(payload)).final();
}


確かに軽く他社の実装(Microsoft以外だとAuth0とかGoogleとか)を見るとちゃんと大文字になってますね。

2024年5月11日土曜日

NIST SP800-63B-3の同期可能クレデンシャルに関する追補版を読む(5)

こんにちは、富士榮です。
引き続きNIST SP 800-63B-3への追補版を見ていきましょう。



今回は同期可能な認証器に関する脅威とチャレンジについてです。

Syncable Authenticator Threats and Challenges

同期可能な認証機能には、導入または展開の前に評価すべきいくつかの明確な脅威および課題がある。表3にこれらのリスクと推奨される軽減策の概要を示す。

表3. 同期可能な認証器の脅威、課題、緩和策
脅威と課題説明緩和策
鍵の不正使用または管理の喪失同期可能な認証器の中には、鍵を悪用する可能性のある他のユーザが所有するデバ イスへの秘密鍵の共有をサポートするものもある。- 同期されたキーの共有を防止するエンタープライズ・デバイス管理機能または管理されたプロファイルを強制する。
- 利用可能なすべての通知チャネルを通じて、鍵共有イベントをユーザーに通知する。
- ユーザが鍵、鍵の状態、鍵が共有されたかどうかを確認できる仕組みを提供する。
- 既存の認識およびトレーニングの仕組みを通じて、鍵の不正使用のリスクについてユー ザーを教育する。
同期ファブリックへの侵害鍵の同期をサポートするために、ほとんどの実装は、アカウントに関連付けられた複数のデバイスに接続されたクラウドベースのサービスである「同期ファブリック」に鍵をクローンする。- 暗号化された鍵マテリアルのみを保存する。
- 認証されたユーザ以外が秘密鍵にアクセスできないように、同期ファブリックのアクセス制御を実装する。
- クラウドサービスの基本的なセキュリティ機能(FISMA Moderate 保護または同等の保護など)を評価する。
- ハードウェア・セキュリティ・モジュールを活用し、暗号化鍵を保護する。
同期ファブリックへの不正アクセスと復旧同期された鍵は、クラウドベースのアカウント回復プロセスを通じてアクセスできる。これらのプロセスは、認証者にとって潜在的な弱点となる。- SP 800-63B に準拠した認証回復プロセスを導入する。
- デバイス管理またはマネージド・アカウント機能により、連邦エンタープライズ鍵の回復機能を 制限する。
- 復旧をサポートするために、AAL2 以上の複数の認証機関をバインドする。
- 同期ファブリックへのユーザ・アクセスに新しい認証者を追加する場合は、AAL2 認証を要求する。
- 連邦政府の企業シナリオでは、派生認証としてのみ使用する。
- ユーザにリカバリ活動を通知する。
- ユーザが管理する秘密鍵(シンク・ファブリック・プロバイダが知らないもの)を利用し、鍵の暗号化と回復を行う。
取消同期可能な認証器はRP固有のキーを使用するため、そのキーに基づくアクセ スを一元的に取り消すことは困難である。たとえば、従来の PKI では、CRL を使用してアクセスを一元的に取り消すことができる。同様のプロセスは、同期可能な認証器(または FIDO WebAuthn ベースの認証器)では利用できない。- ユーザが認証情報を管理するための中央 ID 管理(IDM)アカウントを導入し、認証情報が漏 洩したり失効したりした場合は、「所属機関」アカウントから認証情報を削除する。
- SSO およびフェデレーションを活用し、インシデント発生時に失効させる必要のある RP 固 有の鍵の数を制限する。
- ユーザが鍵の有効性と最新性を確認するよう定期的に要求するポリシーとツールを確立する。

ここにありましたね。同期と共有の違い。やっぱりMDMとかでコントロールするしかなさそうですね。
ちなみに次の章は共有に関して丸々一章を割いているのでやはり課題感は大きいんだと思います。

2024年5月10日金曜日

Entra IDの外部認証プロバイダの設定を試す(1)

こんにちは、富士榮です。

先日当Blogにも書いたとおり、最近Previewが公開されたEntra IDの外部認証を少しずつ試していきます。

設定はこの辺りのドキュメントを見ればできそうです。

https://learn.microsoft.com/ja-jp/entra/identity/authentication/how-to-authentication-external-method-manage


ざっとやり方と条件を見ていくと、こんな感じっぽいです。

  • Entra ID上にマルチテナントアプリとして外部認証システムをクライアント登録する
  • 外部認証システムがEntra IDが発行したid_token_hintを検証できるようにEntra IDのDiscoveryエンドポイントを解釈できるように構成する
  • 外部認証システムへEntra IDをクライアント登録する
  • 外部認証システムの認証強度の要求はclaimsパラメータを利用し、amr/acrの値が要求される。そのため外部認証システムは指定された値のamr/acrを返却できるように構成する必要がある
  • 外部認証システムの認可エンドポイントへのアクセスはPOSTで行われる
  • 外部認証システムからEntra IDへのid_tokenの引渡しはform_postを使う必要がある

まだ細かいところは色々とありますが、やりながら潰していきましょう。

しかしまぁ、結構条件厳しめですね。やはり自作IdPをカスタマイズしつつ対応させていくのが良さそうです。


と言うことで徐々に試します。今回は外部認証システムへ認証要求が飛ぶところまでを見ていきます。

マルチテナントアプリの登録

まずはEntra IDに外部認証システムを登録してあげる必要があります。ログイン前に参照することが必要になる、かつ他のマルチテナントアプリに対して認証する可能性があることから、外部認証システム自体の登録もマルチテナントアプリとしてクライアント登録を行います。

この際、redirect_uriには外部認証システムの認可エンドポイントのURLを指定する必要があります。

次に、APIアクセス許可を行います。

Microsoft Graphへのアクセス許可をする(委任されたアクセス許可)必要があります。

対象の権限にはopenidとprofileを指定します。

管理者の同意を付与しておきます。


外部認証の設定を行う

いよいよ外部認証システムの登録を行います。
  • 名前は適当でも良いのですが後から変えられないので注意が必要です。
  • クライアントIDは外部認証システム側にEntra IDを登録する際に発行されるクライアントIDを指定します。(次回以降で外部認証システム側への登録の話をします)
  • 検出エンドポイントには外部認証システムのdiscoveryエンドポイントを指定します。
  • アプリIDは先ほどEntra ID上に登録したマルチテナントアプリのクライアントIDを指定します。

また、この認証方式を使うことができるユーザが所属するグループを指定し、設定を有効にして保存します。

動作を確認する

この状態でユーザでログインをする際に多要素認証が求められるように設定しておくと、認証方式として先ほど指定した方式が出てきます。(出てこない場合はMicrosoft Authenticatorが現在使えない、というリンクをクリックすると出てきます)

こちらを選択すると、外部認証システムへリダイレクトされるのですが、トレーサーでリクエストを見ると以下のように認可エンドポイントへPOSTでid_tokenやclaimsを含んだリクエストが飛んでいることがわかります。

id_token_hintを紐解くとこんな感じです。

おすすめは外部認証システム側にoid(オブジェクトID)とtid(テナントID)を紐づけた形でユーザを作成しておき、当該のユーザで認証することです。(preferred_usernameだと変わってしまう可能性があるので)

また、認証方式・強度などに関する要求はclaimsパラメータで指定がされていますので、外部認証システムはこの条件を満たす形で認証を行い、id_tokenのacr/amrに値を含めてEntra IDへ送出してあげる必要があります。 

{
"claims": {
"id_token": {
"amr": {
"essential": true,
"values": [
"face",
"fido",
"fpt",
"hwk",
"iris",
"otp",
"tel",
"pop",
"retina",
"sc",
"sms",
"swk",
"vbm"
]
},
"acr": {
"essential": true,
"values": [
"possessionorinherence"
]
}
}
}
}

次回以降は外部認証システム側の設定を調整していきたいと思います。