【 FIDO Conformance Toolsメモ 】Server-ServerPublicKeyCredentialCreationOptions-Req-1 Test server generating ServerPublicKeyCredentialCreationOptionsRequest

はじめに

本日はServer-ServerPublicKeyCredentialCreationOptions-Req-1 Test server generating ServerPublicKeyCredentialCreationOptionsRequest という項目について書いていきます。
前回はv0.10.110(BETA)(BETA FIDO2)というバージョンを利用していましたが、おかしい箇所があったので報告したら、「Konichiva Kento-san. Please try v0.10.207」ということでv0.10.207を現在は利用しています。
テスト項目についても以前の記事に追記しているのでよろければ確認ください。

kent056-n.hatenablog.com

Server-ServerPublicKeyCredentialCreationOptions-Req-1 Test server generating ServerPublicKeyCredentialCreationOptionsRequest

こちらのテスト項目については目次の通りになります。

P-1 Get ServerPublicKeyCredentialCreationOptionsResponse, and check that:

(a) response MUST contain "status" field, and it MUST be of type DOMString and set to "ok"

こちらはレスポンスにstatusフィールドがあるかどうかのチェックのようなので、成功時、失敗時それぞれ以下のようなレスポンスを返しているかどうか確認してあげれば良いです。

  • 成功時の例
{
    "status": "ok",
    "errorMessage": ""
}
  • 失敗時の例
    {
        "status": "failed",
        "errorMessage": "User does not exists!"
    }

(b) response MUST contain "errorMessage" field, and it MUST be of type DOMString and set to an empty string

こちらも(a)と同様にerrorMessageを全てのレスポンスで返しているか確認して終了です。
成功時のレスポンスは忘れがちなので注意。 * 成功時の例

{
    "status": "ok",
    "errorMessage": ""
}
  • 失敗時の例
    {
        "status": "failed",
        "errorMessage": "User does not exists!"
    }

(c) response contains "user" field, of type Object and:

(1) check that user.name is not missing, and is of type DOMString
ユーザー名があるかどうかのチェックのようです。以下のようにチェックしてあげれば問題なさそうです。
これで通らなければ、stringかどうかのチェックも追加してみてください。(自分はなんかパスしていた)

    if(!request.body || !request.body.username || !request.body.displayName) {
        response.json({
            'status': 'failed',
            'errorMessage': 'Request missing display name or username field!'
        })
        return
    }

(2) check that user.displayName is not missing, and is of type DOMString
こちらもdisplayNameが存在するかどうかのチェックを行なってください。

    if(!request.body || !request.body.username || !request.body.displayName) {
        response.json({
            'status': 'failed',
            'errorMessage': 'Request missing display name or username field!'
        })
        return
    }

(3) check that user.id is not missing, and is of type DOMString, and is not empty. It MUST be base64url encoded byte sequence, and is not longer than 64 bytes.
idについてのチェックのようです。
idは/attestation/optionsのレスポンスで、navigator.credentials.get()の引数になるパラメーターになります。
ここでは、そのidが存在しているか、typeがDOMStringか、base64url encodeされているか、64byteより長くないかどうかチェックされていそうです。
こんな感じで乱数を生成して、base64urlエンコードしてレスポンスに含めてやれば良さそう。

    let buff = crypto.randomBytes(32);

    return base64url(buff);

(4)If user.icon is presented, check that it's is of type DOMString 全然チェックしたつもりないけど通っていた。そもそもオプションでiconを返していなければ問題なさそう??

(d) response contains "rp" field, of type Object and:

(1) check that rp.name is not missing, and is of type DOMString
/attestation/optionsのレスポンスでrpにnameを含めてレスポンスを返しておけば問題なさそう。

    {
        "status": "ok",
        "errorMessage": "",
        "rp": {
            "name": "Example Corporation"
        },
    ...
    }

(2) check that rp.id is not missing, and is of type DOMString. 特に/attestation/optionsではrp.idを返すようにしていなかったはずだが通っていた。おそらく/attestation/resultの以下の処理だろうか??

    if(!request.body       || !request.body.id
    || !request.body.rawId || !request.body.response
    || !request.body.type  || request.body.type !== 'public-key' ) {
        response.json({
            'status': 'failed',
            'errorMessage': 'Response missing one or more of id/rawId/response/type fields, or type is not public-key!'
        })
        return
    }

(3) If rp.icon is presented, check that it's is of type DOMString.
チェックした記憶がないが通っていた。そもそもrp.iconをオプションとして返していなければ問題なさそう??

(e) response contains "challenge" field, of type String, base64url encoded and not less than 16 bytes.

こんなかんじで乱数を生成してbase64urlエンコードしてレスポンスに含めれば終わり。

    let buff = crypto.randomBytes(32);

    return base64url(buff)

(f) response contains "pubKeyCredParams" field, of type Array and:

/attestation/optionsのレスポンスのpubKeyCredParamsのチェックのようです。

    {
        "status": "ok",
        "errorMessage": "",
        ...
        "pubKeyCredParams": [
            {
                "type": "public-key",
                "alg": -7
            }
        ],
     ...
    }

(1) each member MUST be of type Object
こんな感じで/attestation/optionsでレスポンス生成時にpubKeyCredParamsをObjectにしておけば問題なし。

 ...
        pubKeyCredParams: [
            {
                type: "public-key",
                alg: -7
            }
        ]
 ...

(2) each member MUST contain "type" field of type DOMString
pubKeyCredParamsにtypeがあるかどうかのチェックなので、tyepを含めてレスポンスを返していれば大丈夫。

 ...
        pubKeyCredParams: [
            {
                type: "public-key",
                ...
            }
        ]
 ...

(3) each member MUST contain "alg" field of type Number
pubKeyCredParamsにalgがあるかどうかのチェックなので、型に注意してalgを含めてレスポンスを返していれば大丈夫。

 ...
        pubKeyCredParams: [
            {
                ...
                alg: -7
            }
        ]
 ...

(4) MUST contain one member with type set to "public-key" and alg set to an algorithm that is supported by the authenticator
typeはpublic-key固定なので、それのチェック。
あと、algで返しているのがサポートされているアルゴリズムであるかどうかのチェック。以下のような内容で問題ない。

 ...
        pubKeyCredParams: [
            {
                type: "public-key",
                alg: -7
            }
        ]
 ...

(g) If response contains "timeout" field, check that it's of type Number and is bigger than 0

そもそもオプションでtimeoutを返さなければ問題なさそう。

(h) response contains "extensions" field, with "example.extension" key presented

あまりよくわかっていない。
該当するテストの/attestation/optionsに対するリクエストにextensionsのフィールドがあった。
よくわからなかったが、/attestation/optionsにリクエストにextensionsがあったらそのままオプションに含めて返す処理を追加した。

   ... 
    challengeMakeCred.extensions = request.body.extensions;
   ...
    response.json(challengeMakeCred)
})

P-2 Request from server ServerPublicKeyCredentialCreationOptionsResponse with "none" attestation, and check that server, and check that ServerPublicKeyCredentialCreationOptionsResponse.attestation is set to "none"

該当するテストの/attestation/optionsに対するリクエストにattestationのフィールドがあり、そこに"none"が指定されていた。 リクエストでattestationが指定された場合はそれを優先的にレスポンスに含めてオプションを返してやれば良さそう。というわけでオプション生成時に以下のような処理を追加。

   }
   ...

        attestation: request.body.attestation || 'direct',

    ...
    }

P-3 Get two ServerPublicKeyCredentialCreationOptionsResponses, and check that challenge in Request1 is different to challenge in Request2

気づいたらパスしていた。多分以下の処理だと思う。

   if(clientData.challenge !== request.session.challenge) {
        response.json({
            'status': 'failed',
            'errorMessage': 'Challenges don\'t match!'
        })
    }

おわりに

つかれた...

参考

https://fidoalliance.org/specs/fido-v2.0-rd-20180702/fido-server-v2.0-rd-20180702.html