FIDO2-パスワードは死ななければならない

「パスワードが死んだ」、「パスワードが死にかけている」、「新しい技術がパスワードを殺す」などと繰り返し聞いていると思います。



FIDO Allianceの私たちは、認証においてパスワードがすべて消滅することをお知らせしました。



詳細な爬虫類の大量破壊について話を始める前に、 先ほど書いた最も人気のあるFIDO U2Fプロトコルに戻りましょう。







つまり、U2Fでの認証シナリオは次のとおりです。



登録:



-ユーザーはログインとパスワードを使用してログインします

-サーバーは、scrypt、argon2、bcryptを使用してパスワードをハッシュし、データベースに保存します

-ユーザーは、サーバー上のデバイスに公開鍵とキーペアのIDを保存して、デバイスを追加します。



認証:



-ユーザーはログインとパスワードを入力します

-サーバーはパスワードをハッシュし、データベース内のハッシュと比較します

-サーバーはランダムな「呼び出し」を生成し、IDと共にクライアントに送信します

-クライアントは、呼び出し、ID、トークンバインディング、およびU2Fセッションの起点をトークンに渡します

-オーセンティケーターは秘密鍵でこれらすべてに署名し、クライアントとそのサーバーに送信します

-利益



現在、U2Fを使用した多要素認証は、最も簡単でフィッシングに耐性があり、安全な認証方法です。 しかし、この方法にも欠点があります。



どれ?



パスワード!







パスワードは認証のすべての段階で盗まれます。



-キーロガーを使用して盗むことができます。

-TLSの問題で盗むことができます(Heartbleedを参照)。

-サーバーがコードインジェクションに対して脆弱な場合、またはコードにエラーがある場合は盗むことができます(Twitterを参照)

-パスワードが弱くハッシュされているか、まったくハッシュされていない場合は盗むことができます。



パスワードがあれば盗まれます



しかし、パスワードをまったく削除するとどうなりますか?



実際、これはFIDO Allianceで考えたものであり(ハイパーテキストfidonetとは関係ありませんが、潜在的にそこで使用できます)、FIDO2を作成しました







私たちのことを聞いたことがない人のために、FIDO Allianceは安全な認証のためのオープンスタンダードを開発するコンソーシアムです。 上の写真では、実際に標準を開発している役員を見ることができます。



FIDO2は、3年以上開発している最後のプロジェクトです。 次の2つの部分で構成されます。



-WebAuthn-公開鍵アカウントを管理するためのJS API。 これはW3C標準であるため、すべてのブラウザーに必要です。 実際、Chrome、Firefox、およびEdgeは、そのサポートに関する作業を既に公表しています。

-CTAP2-クライアントからオーセンティケーター2は、USB、NFC、およびBLEを介してオーセンティケーターと通信するためのCBORプロトコルを記述する標準です。



ユーザー



ユーザーにとって、これは簡単です。



-ユーザーが名前を入力

-検証に合格

-利益!







実際には、デモ:







プロトコル



FIDO2は、FIDOプロトコルコールレスポンスファミリのメンバーです。 このプロトコルには、6つの基本的なセキュリティメカニズムがあります。



1.コールアンサー







最初のメカニズムはチャレンジ/レスポンスです。 サーバーはランダムコールを生成し、クライアントに送信します。 クライアントはこの呼び出しをオーセンティケーターに渡し、オーセンティケーターは呼び出しに署名してクライアントに返し、クライアントはそれをサーバーに返します。 呼び出し、署名、および公開鍵を知っているサーバーは、署名を確認し、クライアントを認証できます。



2.フィッシング保護







2番目のメカニズムは、セッション依存のトランザクションです。 クライアントは、サーバーから呼び出しを受信すると、呼び出しにセッション情報を追加します:origin(note: example.com )、トークンバインディング。 この情報はオーセンティケーターによって署名され、サーバーに返されます。 サーバーは応答をデコードして、それ自体またはソースそのものでした。ユーザーがフィッシング攻撃を受けた場合、サーバーはソースがexample.comではなくattacker.comであることがわかり、攻撃を防ぐことができます



3.リプレイ攻撃に対する保護







リプレイ攻撃から保護するために、トランザクションにカウンターが存在します。カウンターは操作ごとに1つずつ増加します。 サーバーが、値が最後に保存された値以下のカウンターを持つ応答を受信した場合、サーバーはこれがリプレイ攻撃であることを確認できます。



4.プライバシー







4番目の保護メカニズムは、登録固有のキーペアです。 認証のたびに、認証者はランダムな識別子を持つランダムなキーペアを生成します。 したがって、あなたとあなたのパートナーが両方のアカウントに同じオーセンティケーターを使用していても、サーバーはそれを見つけることができません。



認証中に、キーペアの識別子がオーセンティケーターに渡され、オーセンティケーターは、オリジンと識別子を使用して、安全なデータベースでキーペアを見つけ、署名を返します。







識別子転送の代替手段は、常駐キー(RK、常駐キー)の使用です。 認証中、認証者はすべてのRKキーペアの署名を返します。 クライアントはユーザーにペアの選択肢を提供し、サーバーに署名を返します。 詳細については、以下にRKを記述します。



5.認証







5番目のメカニズムは認証です。 サイトがユーザーからデバイスの種類を見つける必要がある場合があります。 たとえば、米国では、すべての政府機関がFIPS(連邦情報処理標準)認定デバイスのみを使用する必要があります。 フランス、イスラエル、ロシアなどの国でも同様の要件があります。 これらすべてのために、認証があります。



オーセンティケーターの生産では、会社は10万台のデバイスごとに「バッチ証明書」とそのキーを生成し、各デバイスにインストールします。 登録時に、デバイスはすべての情報にパーティ証明書の秘密鍵で署名し、公開鍵の代わりに証明書を返します。 証明書には、オーセンティケーターに関する情報とそのAAGUID(オーセンティケーター認証GUID)が含まれています。 また、FIDO認定を受け取ると、会社は特別なメタデータドキュメント、または単にメタデータを発行し、誰でもアクセスできるリポジトリメタデータに配置します。 メタデータには、暗号特性、生体認証をサポートしている場合は生体認証特性、アルゴリズム、セキュリティレベル、基本的な説明など、オーセンティケータに関する情報が含まれています。 そして、利用可能な情報に基づいて、サーバーはオーセンティケーターを受け入れるべきかどうかを判断できます。



認証は異なります。 サーバーが証明書を返す完全版の認証があります。 デバイスが証明書をサポートせず、代わりに新しく生成されたペアの公開キーを返す自己認証があります。



また、ユーザーは、証明書を返す方法をカスタマイズできます。 そのため、たとえば、ユーザーは通常、証明書の返却を禁止でき、クライアントは証明書なしで登録応答を返すことで証明書を削除するだけです。 これについては後で説明します。



6.ユーザー可用性テスト



FIDOの基本的な要件の1つは、ユーザープレゼンステストです。 この場合、認証者はユーザーの存在を確認する必要があると理解されています...これは、ボタン、指紋、ピンコードなどの単純なクリックなど、さまざまな方法で実行できます。



Webauthn



このセクションでは、FIDO2を操作するためのJS APIを見ていきます。



資格情報管理API


WebAuthnは、公開鍵アカウントを管理するための認証情報管理APIのアドオンです。 CredMan APIは(予想外に!)ユーザーの資格情報を管理するためのAPI、またはもっと簡単に言うと、ブラウザーでオートコンプリートにアクセスするためのAPIです。



次に、資格情報の作成例を見てみましょう。



navigator.credentials.store({
    'type': 'password',
    'id': 'alice',
    'password': 'VeryRandomPassword123456'
})

      
      





:



navigator.credentials
    .get({ 'password': true })
    .then(credential => {
        if (!credential) {
            throw new Error('No credentials returned!')
        }

        let credentials = {
            'username': credential.id,
            'password': credential.password
        }

        return fetch('https://example.com/loginEndpoint', {
            method: 'POST',
            body: JSON.stringify(credentials),
            credentials: 'include'
        })
    })
    .then((response) => {
        ...
    })

      
      





CredManAPI

pusher.com/sessions/meetup/js-monthly-london/building-a-better-login-with-the-credential-management-api



Create public-key credential


Public-Key Credential navigator.credentials.create , CreateCredential «publicKey».



        var randomChallengeBuffer = new Uint8Array(32);
        window.crypto.getRandomValues(randomChallengeBuffer);

        var base64id = 'MIIBkzCCATigAwIBAjCCAZMwggE4oAMCAQIwggGTMII='
        var idBuffer = Uint8Array.from(window.atob(base64id), c=>c.charCodeAt(0))

        var publicKey = {
            challenge: randomChallengeBuffer,

            rp: { name: "FIDO Example Corporation" },

            user: {
                id: idBuffer,
                name: "alice@example.com",
                displayName: "Alice von Wunderland"
            },
            attestation: 'direct',
            pubKeyCredParams: [
                { type: 'public-key',  alg: -7  }, // ES256
                { type: 'public-key', alg: -257 }  // RS256
            ]
        }

        // Note: The following call will cause the authenticator to display UI.
        navigator.credentials.create({ publicKey })
            .then((newCredentialInfo) => {
                console.log('SUCCESS', newCredentialInfo)
            })
            .catch((error) => {
                console.log('FAIL', error)
            })

      
      





, , :

— challenge user.id

— pubKeyCredParams — , . FIDO2 : RSA-PSS 2048 SHA256/384/512, RSA-PKCS1_3 SHA256/384/512/1, EC SHA256/384/512, secp256/384/521p1(NIST) secp256k1, EdDSA ED25519.

— attestation — , . : none, direct, indirect. Direct , , . None , AAGUID 0x00000000000000000000000000000000. Indirect privacy-ca. - attestation none. , . , 'none' .







Firefox Nightly



:







:



— id/rawId — credId. id base64url rawId.

— response.clientDataJSON — CollectedDataJSON, JSON . clientDataJSON:



{
	"challenge": "1KnytoY-it44IiEMx0lK5GBlBvAJeVULPSCw5E-5qpA",
	"origin": "http://localhost:3000",
	"tokenBinding": {
		"status": "not-supported"
	},
	"type": "webauthn.create"
}

      
      





— attestationObject — CBOR , , , , .







— authData — , rpIdHash, , , , .

— fmt — . WebAuthn «packed», «fido-u2f», «android», «tpm» «safety-net» .

— attStmt — . , .



clientDataJSON clientDataHash. authData clientDataHash signatureBase. attStmt.sig, signatureBase.







Get assertion


navigator.credentials.create , «publicKey».



var randomChallengeBuffer = new Uint8Array(32);

window.crypto.getRandomValues(randomChallengeBuffer);



    var idBuffer = encoder.encode("mFuXJYt-0qYZDUBFyN_SW_Cach6U56mrnHA_ZuxtlOnjHLhjfWjYVftbxr4WMmxp1-MG3nRRNQoI7WvvAM0pmw")
    var options = {
      challenge: randomChallengeBuffer,
      allowCredentials: [{ type: "public-key", id: idBuffer }]
    }

    navigator.credentials.get({ "publicKey": options })
            .then((assertion) => {
                console.log('SUCCESS', assertion)
            })
            .catch((error) => {
                console.log('FAIL', error)
            })

      
      





allowCredentials — , (credId). allowCredentials c credId . authenticatorSelection.requireResidentialKey, allowCredentials.



:







, , :

— clientDataJSON.type «webauthn.get»



{
	"challenge": "-0NPkC-1zxpcGanUJMbKFdd7Ze0kRphWFy1_Sgc4FpI",
	"origin": "http://localhost:3000",
	"tokenBinding": {
		"status": "not-supported"
	},
	"type": "webauthn.get"
}

      
      





— attestationObject

— authenticatorData authData, authenticatorAttestation

— signature

— userHandle — , user.id



, .



UPD: webauthn.org ( U2F )



. CTAP2(Client-to-Authenticator 2).



.



All Articles