LoginSignup
19
25

More than 3 years have passed since last update.

OpenAM で FIDO2(WebAuthn) 認証を実装する

Last updated at Posted at 2018-12-22

OpenAM で FIDO2(WebAuthn) 認証を実装する

今年はブラウザの対応が進んだことから、だいぶ盛り上がったFIDO2認証です。
OpenAM FIDO2(WebAuthn)ユースケースを考え たので、
認証モジュールを書いて、パスワード「よりよい世界に」しましょうかねww

OpenAMの認証モジュール

OpenAMの認証機能はモジュール単位になっており、OpenAMコンソーシアムのツリーでは以下のディレクトリにあります。
https://github.com/openam-jp/openam/tree/master/openam-authentication
ディレクトリ毎に、それぞれ独立した認証機能を提供しています。

OpenAM認証モジュールの概要

OpenAMはフロントエンドにBackbone.jsを使っており、
ブラウザとの通信は json のやり取りが主です。
jsonの中に Callback が含まれていて、それを元にバックエンドが認証処理を進めていきます。
認証モジュールにはステートという状態を管理するための機能があり、
Callbackの内容や、処理の状況に合わせてステートを変え、処理内容を遷移させて行きます。
サンプルの認証モジュール(SampleAuth.java)のコードを抜粋すると

/**
 * SampleAuth authentication module example.
 * AMLoginModuleを元にしています。
 */
public class SampleAuth extends AMLoginModule {
...
    //認証モジュールの初期化処理、設定値の取得など。
    public void init(Subject subject, Map sharedState, Map options) {
        debug.message("SampleAuth::init");
        this.options = options;
        ...
    }
    //認証モジュールの処理本体 引数はブラウザからの応答がcallbacksでstateは現時点での処理すべきstateです。
    public int process(Callback[] callbacks, int state) throws LoginException {
        debug.message("SampleAuth::process state: {}", state);
        //認証モジュールのstate毎に処理内容が移替わっていきます。
        switch (state) {
            ...
            //STATE_AUTH 認証画面から送信されたCallbackを受け取って処理する
            case STATE_AUTH:
                // Get data from callbacks. Refer to callbacks XML file.
                //ブラウザに入力されたユーザーIDの取得
                NameCallback nc = (NameCallback) callbacks[0]
                //ブラウザに入力されたパスワードの取得
                PasswordCallback pc = (PasswordCallback) callbacks[1];
                String username = nc.getName();
                String password = String.valueOf(pc.getPassword());
                //...続く

粛々wwと処理が進んで最終的には LOGIN_SUCCEED となってこのモジュール自体は終了します。

                //usernameとpasswordの確認をして成功を返す。
                if (USERNAME.equals(username) && PASSWORD.equals(password)) {
                    debug.message("SampleAuth::process User '{}' " +
                            "authenticated with success.", username);
                    return ISAuthConstants.LOGIN_SUCCEED;
                }

FIDO2(WebAuthn)認証モジュールの実装内容

認証モジュールの概要がわかったところで、FIDO2のシーケンスを実装に落とし込んでみます。
シーケンスとその間のデータについてはFIDOアライアンスのチュートリアルが参考になります。
もちろんW3Cの方も...
ぜひ日本語でという方はYahoo! JAPANでの生体認証の取り組み(FIDO2サーバーの仕組みについて)が良いと思います。
Window Helloであれば、Web Authentication and Windows Helloをご参照ください。

登録シーケンス

OpenAM FIDO2(WebAuthn)ユースケース 登録の図の再掲です。
fido2reg.png

認証シーケンス

OpenAM FIDO2(WebAuthn)ユースケース 認証の図の再掲です。
fido2auth.png

バックエンドの処理 その1

登録、認証どちらのシーケンスもログインIDを受け取るところを起点としています。
ここは、普通にCallbackを受け取れば良いので問題ありません。
その後、登録シーケンスの図には無いですが、認証シーケンスと同じく[Authenticatorに渡したいオプション]challenge をブラウザへ送り、ブラウザからの戻ってきた challenge を検証するので、challenge を一旦記憶する必要があります。

これはFIDO2Serviceというインスタンスを、認証モジュールの初期化処理で作成するようにします。
これで、認証モジュールの生存期間(ブラウザとの一時的なセッション)中で参照できます。

FIDO2Serviceインスタンス

public FIDO2Service(String challenge) {
        this.challenge = challenge;

FIDO2.java

/**
 * FIDO2 authentication module.
 */
public class FIDO2 extends AMLoginModule {
...
    //認証モジュールの初期化処理、設定値の取得など。
    public void init(Subject subject, Map sharedState, Map options) {
        debug.message("FIDO2::init");
        this.options = options;
        ...
        fido2Service = new FIDO2Service(challenge);
        ...
    //認証モジュールの処理本体
    public int process(Callback[] callbacks, int state) throws LoginException {
        ...

chalenge 以外の情報 [Authenticatorに渡したいオプション] は、 rp、user、pubKeyCredParams などですが、これは適当に生成し、まとめてTextOutputCallbackでブラウザへ送ります。

フロントエンドの処理

FIDO2の場合、入力フォームにパスワードを入れる替わりにAuthenticatorの処理結果をPOSTします。
普通のForm認証ログイン画面では馴染みのない処理となります。

登録の処理

画面としては、このようなダイアログ(Firefoxの場合)が出ます。
この処理は、Javascriptの navigator.credential.create(RPサーバーが送ったオプション)を実行することにより出ています。
fido-5.png

RPサーバーが送ったcreateオプションの例
createoptions.png

認証の処理

画面としては、このようなダイアログ(Firefoxの場合)が出ます。
この処理は、Javascriptの navigator.credential.get(RPサーバーが送ったオプション)を実行することにより出ています。
fido-2.png

RPサーバーが送ったgetオプションの例
getoptions.png

どちらも処理結果をサーバーへPOSTする必要がありますので、以下のような感じになります。
登録時の navigator.credentials.create


/*
* サーバーから来たmakeCredentialOptions
* を引数にnavigator.credentials.createを実行
*/
navigator.credentials.create({
    publicKey: makeCredentialOptions
}).then(function (newCredential) {
    /*
    *Authenticatorの処理結果をArrayBufferに
    * attestationObject
    * clientDataJSON
    * rawID
    */
    let attestationObject = new Uint8Array(newCredential.response.attestationObject);
    let clientDataJSON = new Uint8Array(newCredential.response.clientDataJSON);
    let rawId = new Uint8Array(newCredential.rawId);

    //RPサーバーへポスト
    $.post('/openam/json/authenticate', {
        id: newCredential.id,
        rawId: b64enc(rawId),
        type: newCredential.type,
        attObj: b64RawEnc(attestationObject),
        clientData: b64RawEnc(clientDataJSON),
     })
});


上記の例で送った登録時のPOSTデーター例

clientData.png

認証時のPOSTデーター例

getresponse.png

バックエンドの処理 その2

ブラウザからPOSTされたデーターを処理していきます。

登録

登録の場合はデーターにattestationObjectが含まれます。
実行すべき処理は公開鍵情報を取り出してユーザーデーターストアに格納することです。
公開鍵情報を取り出す。と簡単に書きましたがCBORー>COSEー>PKCSと処理します。
取り出した公開鍵を無事登録できたら、モジュールのステートは振り出しに戻り、ログイン画面へ。
黄色がブラウザ側フロントエンド 青がサーバー側バックエンド(OpenAM)です。
fido2regseq.png

認証

認証の場合はデーターにAuthenticatorDataが含まれます。
実行すべき処理はユーザーデーターストアにある公開鍵でchallengeの署名を検証することです。
OpenAMとしては冒頭の LOGIN_SUCCEED までくれば認証モジュールは役割を終えます。
黄色がブラウザ側フロントエンド 青がサーバー側バックエンド(OpenAM)です。
fido2authseq.png

実食

コードが書けたら早速試して見るのです。
Windows Helloで試してみました。
IMAGE ALT TEXT HERE

設定画面

おまけ
認証モジュールの設定画面で設定する項目はFIDO2のオプションで、サーバーとして認証時に指定したいものを選びます。
fido2config.png

これから

セルフテスト用に各種Authenticatorも買って行きたい
よさげな場所(CA or Europ)で開催されるインターオペラビリティテストに行きたい...

19
25
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
19
25