【 FIDO Conformance Toolsメモ 】Server-ServerAuthenticatorAttestationResponse-Resp-5 Test server processing "packed" FULL attestation

はじめに

Server-ServerAuthenticatorAttestationResponse-Resp-5 Test server processing "packed" FULL attestationというテスト項目について書きます。
こちらのテスト項目はPacked Attestationの中でもFULL attestationというattestation statementのx5cというフィールドに証明書が格納されているパターンのテストになります。 Packed Attestationの検証については以下の記事に詳しく記載されています。 medium.com

P-1~2

  • P-1 Send a valid ServerAuthenticatorAttestationResponse with FULL "packed" attestation, and check that server succeeds
    FULL Attestationの検証については上記で紹介したブログに詳細に記載されていますのでそちらをご覧ください。(気が向いたら書きます)
    FULL AttestationではAuthenticatorAttestationReponseが証明書を含んだ状態で送られてきます。簡単にやることを説明すると、「証明書の各フィールドの検証」、「証明書から取り出した公開鍵で署名の検証」の2点になります。
...
        const leafCert = base64ToPem(attestationStruct.attStmt.x5c[0].toString('base64'));
        const certInfo = getCertificateInfo(leafCert);

        if (certInfo.subject.OU !== 'Authenticator Attestation')
            throw new Error('Batch certificate OU MUST be set strictly to "Authenticator Attestation"!');

        if (!certInfo.subject.CN)
            throw new Error('Batch certificate CN MUST no be empty!');

        if (!certInfo.subject.O)
            throw new Error('Batch certificate CN MUST no be empty!');

        if (!certInfo.subject.C || certInfo.subject.C.length !== 2)
            throw new Error('Batch certificate C MUST be set to two character ISO 3166 code!');

        if (certInfo.basicConstraintsCA)
            throw new Error('Batch certificate basic constraints CA MUST be false!');

        if (certInfo.version !== 3)
            throw new Error('Batch certificate version MUST be 3(ASN1 2)!');

        response.verified = crypto.createVerify('sha256')
            .update(signatureBaseBuffer)
            .verify(leafCert, signatureBuffer);
...
  • P-2 Send a valid ServerAuthenticatorAttestationResponse with FULL "packed" attestation that contains chain that links to the root certificate in the metadata in it's response, and check that server succeeds
    こちら詳しくわかっていないのですが、送信されてきたリクエストのx5cに複数証明書が入っていたので各証明書の検証を行い、結果を返します。 中間証明書のIssuerとmetadataのRoot証明書のIssuerとSubjectが合うことも加えて検証して結果を返せば良いような気がします。

F-1~14

  • F-1 Send ServerAuthenticatorAttestationResponse with FULL "packed" attestation, with fmt set to an unknown attestation format, and check that server returns an error
    fmtに関するテストのようです。
    以下のように意図していないfmtが送信されていた場合にエラーを返せば良さそうです。
if (attestationStruct.fmt === 'packed')
  • F-2 Send ServerAuthenticatorAttestationResponse with FULL "packed" attestation, and with attStmt.sig contains a signature that can not be verified, and check that server returns an error
    attStmtのsigに関するテストのようです。
    以下のようにsignatureの検証に失敗した場合にエラーを返します。
        if (!signatureIsValid)
            throw new Error('Failed to verify the signature!')
  • F-3 Send ServerAuthenticatorAttestationResponse with FULL "packed" attestation, with attStmt missing "x5c" field, and check that server returns an error
    attStmtのx5cに関するテストのようです。
    以下のようにfmtがpackedでx5cに何も入っていなければエラーを返します。
  if (attestationStruct.attStmt.x5c) 
  • F-4 Send ServerAuthenticatorAttestationResponse with FULL "packed" attestation, with attStmt.x5c is not of type ARRAY, and check that server returns an error
    attStmtのx5cに関するテストのようです。
    x5cは以下のような配列のはずなので、配列でなければエラーを返します。
{
    "fmt": "packed",
    "authData": "9569088f1ecee3232954035dbd10d7cae391305a2751b559bb8fd7cbb229bdd44100000000f8a011f38c0a4d15800617111f9edc7d0040c2c2ebc8e0315cc539e2cd9f09258fcd68d71ce04a3e996837e5077ed8f8feba48bba059c4d5e9a66f367fb63a897f4510e28a792e7e3764c02decb32e725f00a50102032620012158200180f54d2a5fd76d03495c5e9fc922e7a905d5b993e045cef8fd099d293998cc225820774e5319051a8f63a965cc371f1ddfa85b410775a14d8017651861686e91ca97",
    "attStmt": {
        "alg": -7,
        "sig": "30450221008cce8a10ba05af34a39267a1838927f12d11599641c02fc550a7b0216755141902201f78875d2cfb0f97515e497f01e7164035c40a18da0eee96f44d93e7b8975e6c",
        "x5c": [
            "308202af30820197a0030201020204485b3db6300d06092a864886f70d01010b05003021311f301d06035504030c1659756269636f204649444f2050726576696577204341301e170d3138303431323130353731305a170d3138313233313130353731305a306f310b300906035504061302534531123010060355040a0c0959756269636f20414231223020060355040b0c1941757468656e74696361746f72204174746573746174696f6e3128302606035504030c1f59756269636f205532462045452053657269616c20313231333933393132363059301306072a8648ce3d020106082a8648ce3d03010703420004fb2cdd304328c5724a50cce6f60bad7d27a91b59e1e66f297b89c9d43dc2b2c77889b4f0ff9d0228cb946dfce01b19589b67804aac977f28189ccdb32574ca28a36c306a302206092b0601040182c40a020415312e332e362e312e342e312e34313438322e312e363013060b2b0601040182e51c0201010404030204303021060b2b0601040182e51c01010404120410f8a011f38c0a4d15800617111f9edc7d300c0603551d130101ff04023000300d06092a864886f70d01010b0500038201010032f3e4bd58d7422baf499986081f0da93bc6aa1c7211f92853ebf3eb73da693b06de31338e5d02ecf676e95c42bea58f25d3373f77bb2a9d7cb23e118c41d49a4c9ad8f3e2a4ec01777a74a8c41243c31ece208f2d0f6ebc619be184a172f6a9accbf8736d5be298b36bece71e778d0a69aaf994b8636de8faf62fd3ce7f044c322cf7263e3499e6a5b2b02abbad5bd9ece5b0714d73bb9461499c942a5f1dccaf65033b3939d447d9fcc47b0b16d8e901fcec3f8c1bc0c6ac0b5d74c7bb03056917e9981a19b9095ca1f4ab9f027c280f8af9ed1d293cf6cc2f046d9ad662b4a96eb1cacaac5e053e8391477c1f8b6001de653abff2aabb559886917ead3b36"
        ]
    }
}
  • F-5 Send ServerAuthenticatorAttestationResponse with FULL "packed" attestation, with attStmt.x5c is an empty ARRAY, and check that server returns an error
    attStmtのx5cに関するテストのようです。
    x5cの配列の中身が空の場合にエラーを返します。

  • F-6 Send ServerAuthenticatorAttestationResponse with FULL "packed" attestation, with attStmt.x5c contains a leaf certificate that is expired, and check that server returns an error
    attStmtのx5cに関するテストのようです。
    x5cの中に入っている証明書が期限切れであればエラーを返えすと良さそうです。現在の時間と証明書内のNot Afterを比較します。
    この時、証明書の中のNot BeforeとNot AfterはGeneralizedTimeという形式で入っているようなので、Unix TimeStampを比較できるように変換する必要があります。 en.wikipedia.org

今回はldap2dateというパッケージを利用してGeneralizedTimeの変換を行いました。

const ldap2date = require('ldap2date');
const jsrsasign = require('jsrsasign');
...

   const subjectCert = new jsrsasign.X509();
    subjectCert.readCertPEM(certificate);
    const notbefore = ldap2date.parse('20' + subjectCert.getNotBefore()).getTime();
    const notafter = ldap2date.parse('20' + subjectCert.getNotAfter()).getTime();
    const now = new Date().getTime();

    if (notafter < now)
        throw new Error('Leaf certificate is expired!')
  • F-7 Send ServerAuthenticatorAttestationResponse with FULL "packed" attestation, with attStmt.x5c contains a leaf certificate that is not yet started, and check that server returns an error
    attStmtのx5cに関するテストのようです。
    F-6とほぼ同様です。今回はNot Beforeと比較します。
const ldap2date = require('ldap2date');
const jsrsasign = require('jsrsasign');
...

   const subjectCert = new jsrsasign.X509();
    subjectCert.readCertPEM(certificate);
    const notbefore = ldap2date.parse('20' + subjectCert.getNotBefore()).getTime();
    const notafter = ldap2date.parse('20' + subjectCert.getNotAfter()).getTime();
    const now = new Date().getTime();

    if (now < notbefore)
        throw new Error('Leaf certificate is not yet started!')
  • F-8 Send ServerAuthenticatorAttestationResponse with FULL "packed" attestation, with attStmt.x5c contains a leaf certificate algorithm does not equal to the one that is specified in MetadataStatement, and check that server returns an error
    attStmtのx5cに関するテストのようです。
    このテストに関しては記憶にない。アルゴリズムの検証としては、attestation statementのalgのみな気がするのでこれかもしれない。
    もしくは以下のように利用しているライブラリの中でエラーを返しているのかもしれない。要確認。
        const algorithm = subjectCert.getSignatureAlgorithmField();
...
        const Signature = new jsrsasign.crypto.Signature({ alg: algorithm });
  • F-9 Send ServerAuthenticatorAttestationResponse with FULL "packed" attestation, with attStmt.x5c contains certificate chain, that can not be verified, and check that server returns an error
    attStmtのx5cに関するテストのようです。
    こちらのテストは以下のようにx5cに証明書が複数格納されていた場合にも検証結果を元にエラーを返していれば良さそうです。
const validateCertificatePath = (certificates) => {
...
    for (let i = 0; i < certificates.length - 1; i++) {
        const subjectPem = certificates[i];
        const subjectCert = new jsrsasign.X509();
        subjectCert.readCertPEM(subjectPem);

        const issuerPem = certificates[i + 1];
        const issuerCert = new jsrsasign.X509();
        issuerCert.readCertPEM(issuerPem);
... 
        const subjectCertStruct = jsrsasign.ASN1HEX.getTLVbyList(subjectCert.hex, 0, [0]);
        const algorithm = subjectCert.getSignatureAlgorithmField();
        const signatureHex = subjectCert.getSignatureValueHex();

        const Signature = new jsrsasign.crypto.Signature({ alg: algorithm });
        Signature.init(issuerPem);
        Signature.updateHex(subjectCertStruct);

        if (!Signature.verify(signatureHex))
            throw new Error('Failed to validate certificate path!')

    }

    return true
}
  • F-10 Send ServerAuthenticatorAttestationResponse with FULL "packed" attestation, with attStmt.x5c containing full chain, and check that server returns an error
    attStmtのx5cに関するテストのようです。
    Root CAの証明書が含まれている場合にエラーを返さないといけないらしい。 github.com

Root CAの証明書はIssuerとSubjectが同じになっているはずなので以下のような検証を行った。注意点としては、オレオレ証明書もIssuerとSubjectが同じになるはずなので、あくまでx5cのlengthが2以上の場合のみこちらの処理を適用します。

const verifyRootCert = (certificate) => {
    const rootPem = certificate;
    const rootCert = new jsrsasign.X509();
    rootCert.readCertPEM(rootPem);
    if (rootCert.getIssuerString() == rootCert.getSubjectString()) {
        return true;
    }
    return false;
}
  • F-11 Send ServerAuthenticatorAttestationResponse with FULL "packed" attestation, with attStmt.x5c containing full chain, that is not correctly ordered, and check that server returns an error
    attStmtのx5cに関するテストのようです。
    証明書チェーンの検証が正しくできていれば良さそうです。
    具体的には、IssuerとSubjectが正しく合うかどうか検証します。
    for (let i = 0; i < certificates.length - 1; i++) {
        const subjectPem = certificates[i];
        const subjectCert = new jsrsasign.X509();
        subjectCert.readCertPEM(subjectPem);

        const issuerPem = certificates[i + 1];
        const issuerCert = new jsrsasign.X509();
        issuerCert.readCertPEM(issuerPem);
...
        if (subjectCert.getIssuerString() !== issuerCert.getSubjectString())
            throw new Error(`Failed to validate certificate path! Issuers dont match!`)

証明書チェーンの検証項目は以下のエントリにわかりやすく記載されていました。

qiita.com

  • F-12 Send ServerAuthenticatorAttestationResponse with FULL "packed" attestation, with attStmt.x5c contains expired intermediate certificate, and check that server returns an error
    attStmtのx5cに関するテストのようです。
    こちらのテストは中間証明書の期限が切れていないかどうか検証すれば良さそうです。検証方法をF-6と同様です。
const validateCertificatePath = (certificates) => {
...
    for (let i = 0; i < certificates.length - 1; i++) {
        const subjectPem = certificates[i];
        const subjectCert = new jsrsasign.X509();
        subjectCert.readCertPEM(subjectPem);

        const issuerPem = certificates[i + 1];
        const issuerCert = new jsrsasign.X509();
        issuerCert.readCertPEM(issuerPem);
        const notbefore = ldap2date.parse('20' + issuerCert.getNotBefore()).getTime();
        const notafter = ldap2date.parse('20' + issuerCert.getNotAfter()).getTime();
        const now = new Date().getTime();
        if (now < notbefore)
            throw new Error('Leaf certificate is not yet started!')

        if (notafter < now)
            throw new Error('Leaf certificate is expired!')
...

    }

    return true
}
  • F-13 Send ServerAuthenticatorAttestationResponse with FULL "packed" attestation, with signature that can not be verified by the public key extracted from leaf certificate, and check that server returns an error
    signatureの検証に関するテストのようです。
    証明書から取得した公開鍵で検証を行っていればこちらのテストはパスできそうです。
        const leafCert = base64ToPem(attestationStruct.attStmt.x5c[0].toString('base64'));
...
        response.verified = crypto.createVerify('sha256')
            .update(signatureBaseBuffer)
            .verify(leafCert, signatureBuffer);
  • F-14 Send ServerAuthenticatorAttestationResponse with FULL "packed" attestation, with signature that is generated using new credential private key, and not attestation batch private key, and check that server returns an error
    signatureの検証に関するテストのようです。
    検証に利用する公開鍵を正しく保存し、検証に利用していればこちらのテストはパスできそうです。

参考