Password-less Web applications created with WebAuthn.

Password-less Web applications created with WebAuthn.

# WebAuthn で作るパスワードレスWEBアプリケーション

デモのURLはこちら:https://laravel-webauthn-demo.k-masatany.com/
- しばらく放置しているので、お試しください。
- Windows Hello は起動しますが、証明書のパースを実装していないのでエラーが起きますw

22e96aa0059e6788659d156463820248?s=128

k-masatany

June 29, 2019
Tweet

Transcript

  1. 2.

    ࣗݾ঺հ • ੓୩ ݡ༞ (@k_masatany) • גࣜձࣾ Fusic ॴଐ •

    ݩ૊ΈࠐΈܥΤϯδχΞ • ࠓ͸ Laravel ϝΠϯͷ PHPer • AWS ΋ಘҙʢͰ΋͍ͭઌ೔ೝఆ੾ΕͪΌ͍·ͨ͠ʣ • ࠷ۙ͸αʔόʔϨεͳ Laravel Λಈ͔ͯ͠༡ΜͰ͍Δ
  2. 3.

    ΞδΣϯμ ✅ ࣗݾ঺հ WebAuthn ͱ͸ ɾ WebAuthn ͷ۩ମతͳೝূಈ࡞ ɾ WebAuthn

    Λ Laravel ʹ૊ΈࠐΜͰΈΔ ɾ WebAuthn Laravel σϞ ɾ ·ͱΊ
  3. 5.

    WebAuthn ͱ͸ • ਖ਼໊ࣜশ͸ Web Authentication • ϒϥ΢βΛར༻͠ύεϫʔυϨεೝূΛߦ͏ͨΊͷ ࢓༷ʢن֨ʣ͓ΑͼͦͷAPI •

    2019೥3݄ קࠂԽ → ۙʑඪ४Խʢͷ͸ͣʣ https://www.w3.org/2019/03/pressrelease-webauthn-rec.html • ओཁϒϥ΢βͰ͸͢Ͱʹ WebAuthn ͕࣮૷ࡁΈ • WebAuthn = FIDO + CTAP
  4. 6.

    WebAuthn ͱ͸ • ਖ਼໊ࣜশ͸ Web Authentication • ϒϥ΢βΛར༻͠ύεϫʔυϨεೝূΛߦ͏ͨΊͷ ࢓༷ʢن֨ʣ͓ΑͼͦͷAPI •

    2019೥3݄ קࠂԽ → ۙʑඪ४Խʢͷ͸ͣʣ https://www.w3.org/2019/03/pressrelease-webauthn-rec.html • ओཁϒϥ΢βͰ͸͢Ͱʹ WebAuthn ͕࣮૷ࡁΈ • WebAuthn = FIDO + CTAP
  5. 8.

    WebAuthn ͱ͸ • ਖ਼໊ࣜশ͸ Web Authentication API • ϒϥ΢βΛར༻͠ύεϫʔυϨεೝূΛߦ͏ͨΊͷ ࢓༷ʢن֨ʣ͓ΑͼͦͷAPI

    • 2019೥3݄ קࠂԽ → ۙʑඪ४Խʢͷ͸ͣʣ https://www.w3.org/2019/03/pressrelease-webauthn-rec.html • ओཁϒϥ΢βͰ͸͢Ͱʹ WebAuthn ͕࣮૷ࡁΈ • WebAuthn = FIDO + CTAP
  6. 9.

    WebAuthn = FIDO2 + CTAP FIDOʢFast IDentity Onlineʣ • FIDO

    Alliance͕ਪਐ͢Δೝূٕज़ • https://fidoalliance.org • Webϒϥ΢β্Ͱࢦ໲ೝূͳͲͷੜମೝূɺ USBΩʔͳͲͷ෺ཧΩʔΛར༻͢ΔͨΊͷٕज़ɻ
  7. 10.

    WebAuthn = FIDO2 + CTAP CTAPʢClient to Authentication Protocolʣ •

    FIDO ͷن֨ͷҰͭ • ΫϥΠΞϯτͱೝূσόΠεؒͷ௨৴ϓϩτίϧ • Webϒϥ΢βͱೝূػثͷؒͰ࢖༻͢Δ௨৴಺༰Λ نఆͯ͠ɺAPIͱͯ͠੔උɻ • CTAPʹΑΓɺFIDO४ڌͷೝূػثͰ͋Ε͹ɺ ΞϓϦέʔγϣϯ͔Βͷར༻͕༰қʹͳΔʢ͸ͣʣɻ
  8. 36.

    ΞδΣϯμ ✅ ࣗݾ঺հ ✅ WebAuthn ͱ͸ WebAuthn ͷ۩ମతͳೝূಈ࡞ ɾ WebAuthn

    Λ Laravel ʹ૊ΈࠐΜͰΈΔ ɾ WebAuthn Laravel σϞ ɾ ·ͱΊ
  9. 38.

    WebAuthn ͷ 3େཁૉ ໊শ ໾ׂ $MJFOU ʢΫϥΠΞϯτʣ 8FC"VUIOΛ࣮ߦ͢Δϒϥ΢β 8FC"VUIOͷ"1*ʹରԠ͍ͯ͠Δඞཁ͕͋Δ "VUIFOUJDBUPS

    ʢೝূثʣ ࣮ࡍʹຊਓͷ֬ೝͱΩʔϖΞͷੜ੒Λߦ͏ ֎෦ೝূثʢ64#Ωʔɺ:VCJLFZͳͲʣ ಺෦ೝূثʢεϚϗ΍.BDͷࢦ໲ೝূͳͲʣ 3FMZJOH1BSUZ ʢ31ʣ ࣮ࡍʹϢʔβʔͷొ࿥΍؅ཧΛߦ͏αΠτ ೝূث͔Βૹ৴͞ΕΔެ։伴Λอଘ
  10. 43.

    Client Authenticator RP ᶃ Request Challenge ᶄ Response Challenge ᶅ

    Request KeyPair RP͔Βड͚औͬͨ৘ใΛݩʹ ೝূثʹ伴ͷੜ੒ΛϦΫΤετ
  11. 44.

    Client Authenticator RP ᶃ Request Challenge ᶄ Response Challenge ᶅ

    Request KeyPair ᶆ Verify User ೝূث͕Ϣʔβʔͷ֬ೝΛ࣮ߦ
  12. 45.

    Client KeyPair Authenticator RP ᶃ Request Challenge ᶄ Response Challenge

    ᶅ Request KeyPair ᶆ Verify User ᶇ Create KeyPair Ϣʔβʔͷ֬ೝ͕औΕͨΒ ΩʔϖΞͳͲͷೝূ৘ใΛ࡞੒
  13. 46.

    Client KeyPair Authenticator RP ᶃ Request Challenge ᶄ Response Challenge

    ᶅ Request KeyPair ᶆ Verify User ᶇ Create KeyPair ᶈ Response AD Authenticator Data ੜ੒ͨ͠ೝূ৘ใΛΫϥΠΞϯτ΁
  14. 47.

    Client KeyPair Authenticator RP ᶃ Request Challenge ᶄ Response Challenge

    ᶅ Request KeyPair ᶆ Verify User ᶇ Create KeyPair ᶈ Response AD Authenticator Data ᶉ Send AD ೝূ৘ใΛRP΁
  15. 48.

    Client KeyPair Authenticator RP ᶃ Request Challenge ᶄ Response Challenge

    ᶅ Request KeyPair ᶆ Verify User ᶇ Create KeyPair ᶈ Response AD Authenticator Data ᶉ Send AD ᶊ Verify AD ೝূ৘ใΛݕূ Ϣʔβʔͷ֬ೝ͕औΕͨΒɺެ։伴Λੜ੒
  16. 49.

    Client KeyPair ᶇ Create KeyPair ᶉ Send AD ᶆ Verify

    User ᶃ Request Challenge ᶄ Response Challenge ᶊ Verify AD ᶈ Response AD ᶅ Request KeyPair Authenticator Data Authenticator RP ᶋ Create User Ϣʔβʔొ࿥ ੜ੒ͨ͠ެ։伴͸Ϣʔβʔʹඥ͚ͮͯอଘ
  17. 50.

    Client KeyPair ᶇ Create KeyPair ᶉ Send AD ᶆ Verify

    User ᶃ Request Challenge ᶄ Response Challenge ᶊ Verify AD ᶈ Response AD ᶅ Request KeyPair Authenticator Data Authenticator RP ᶋ Create User ొ࿥׬ྃ
  18. 54.

    Client ᶃ Request Challenge & KeyID Authenticator RP ᶄ Response

    Challenge ϩάΠϯର৅ͷϢʔβʔͷσʔλΛݕࡧ νϟϨϯδͱͱ΋ʹࣝผ৘ใΛૹ৴
  19. 55.

    Client ᶃ Request Challenge & KeyID Authenticator RP ᶄ Response

    Challenge ᶅ Request Signature ϢʔβʔσʔλΛೝূثʹૹ৴ͯ͠ ॺ໊ͷੜ੒ΛϦΫΤετ
  20. 56.

    Client ᶆ Verify User ᶃ Request Challenge & KeyID Authenticator

    RP ᶄ Response Challenge ᶅ Request Signature ೝূث͕Ϣʔβʔͷ֬ೝΛ࣮ߦ
  21. 57.

    Client Signeture ᶇ Create Signature ᶆ Verify User ᶃ Request

    Challenge & KeyID Authenticator RP ᶄ Response Challenge ᶅ Request Signature Ϣʔβʔͷ֬ೝ͕औΕͨΒ ൿີ伴Ͱॺ໊Λ࡞੒
  22. 58.

    Client Signeture ᶇ Create Signature ᶆ Verify User ᶃ Request

    Challenge & KeyID ᶄ Response Challenge ᶈ Response AD Authenticator Data Authenticator RP ᶅ Request Signature ੜ੒ͨ͠ॺ໊ΛΫϥΠΞϯτ΁
  23. 59.

    ॺ໊ΛRP΁ Client Signeture ᶇ Create Signature ᶉ Send AD ᶆ

    Verify User ᶃ Request Challenge & KeyID ᶈ Response AD Authenticator Data Authenticator RP ᶄ Response Challenge ᶅ Request Signature
  24. 60.

    Client Signeture ᶇ Create Signature ᶉ Send AD ᶆ Verify

    User ᶃ Request Challenge & KeyID ᶊ Verify Signature ᶈ Response AD Authenticator Data Authenticator RP ᶄ Response Challenge ᶅ Request Signature ॺ໊Λެ։伴Ͱݕূ
  25. 61.

    Client Signeture ᶇ Create Signature ᶉ Send AD ᶆ Verify

    User ᶃ Request Challenge & KeyID ᶊ Verify Signature ᶈ Response AD Authenticator Data Authenticator RP ᶋ Authenticate User ᶄ Response Challenge ᶅ Request Signature ϩάΠϯ੒ޭ
  26. 62.

    ΞδΣϯμ ✅ ࣗݾ঺հ ✅ WebAuthn ͱ͸ ✅ WebAuthn ͷ۩ମతͳೝূಈ࡞ WebAuthn

    Λ Laravel ʹ૊ΈࠐΜͰΈΔ ɾ WebAuthn Laravel σϞ ɾ ·ͱΊ
  27. 67.

    2. ύεϫʔυΧϥϜͷຣࡴ database/migrations/2014_10_12_000000_create_users_table.php public function up() { Schema::create('users', function (Blueprint

    $table) { $table->bigIncrements('id'); $table->string('name'); $table->string('email')->unique(); $table->timestamp('email_verified_at')->nullable(); $table->string('password'); $table->rememberToken(); $table->timestamps(); }); }
  28. 68.

    database/migrations/2019_06_29_000000_create_public_keys_table.php ͦΕͬΆ͘࡞੒ public function up() { Schema::create('public_keys', function (Blueprint $table)

    { $table->bigIncrements('id'); $table->bigInteger('user_id'); $table->string('type'); $table->string('raw_id'); $table->string('key'); $table->timestamps(); $table->foreign('user_id')->references('id')->on('users'); $table->index(['user_id']); }); } 3. 伴อଘ༻ͷςʔϒϧΛ࡞੒
  29. 69.

    4. ϧʔςΟϯάΛ௥Ճ routes/web.php ɾmake:auth Ͱ࡞੒͞ΕΔ Controller ʹ௥Ճͷ action Λ࡞੒ ɾjs

    ͕ۤखͳͷͰɺajax Ͱ͸ͳ͘ී௨ʹϨϯμϦϯά͢ΔํࣜͰ࡞੒ ɾ Route::post('/register/confirm', ‘Auth\RegisterController@confirmRegister'); Route::post('/register', ‘Auth\RegisterController@save'); Route::post('/login/confirm', ‘Auth\LoginController@confirmLogin'); Route::post('/login', ‘Auth\LoginController@login');
  30. 70.

    5. view ϑΝΠϧͷฤू & ௥Ճ ɾresources/views/auth/register.blade.php ɾresources/views/auth/login.blade.php ɾ΄΅σϑΥϧτͷ··࢖༻ ɾύεϫʔυΛೖྗ͢ΔϑΥʔϜΛ࡟আ ɾPOST

    ઌΛɺઌ΄Ͳ࡞੒ͨ͠ *-confirm ΞΫγϣϯʹมߋ <form method="POST" action="/register/confirm"> <form method="POST" action="/register">
  31. 72.

    6. 伴ੜ੒༻ͷϨεϙϯεΛฦ͢ΞΫγϣϯΛ௥Ճ app/Http/Controllers/Auth/RegisterController.php RP ʹؔ͢Δ৘ใΛઃఆ public function confirmRegister(Request $request) {

    // RP Entity $rpEntity = new PublicKeyCredentialRpEntity( config('webauthn.rp.name'), //Name config('webauthn.rp.id'), // ID null//Icon );
  32. 73.

    6. 伴ੜ੒༻ͷϨεϙϯεΛฦ͢ΞΫγϣϯΛ௥Ճ app/Http/Controllers/Auth/RegisterController.php ొ࿥͢ΔϢʔβʔʹؔ͢Δ৘ใΛઃఆ // User Entity $userEntity = new

    PublicKeyCredentialUserEntity( $request->email, //Name (string) Str::uuid(), //ID $request->name, //Display name null//Icon );
  33. 75.

    6. 伴ੜ੒༻ͷϨεϙϯεΛฦ͢ΞΫγϣϯΛ௥Ճ app/Http/Controllers/Auth/RegisterController.php 伴ੜ੒ͷύϥϝʔλΛઃఆ // Public Key Credential Parameters $publicKeyCredentialParametersList

    = [ new PublicKeyCredentialParameters( ‘public-key', Algorithms::COSE_ALGORITHM_ES256 ), ]; ɾެ։伴ೝূํࣜ ɾES256ͱ͍͏ΞϧΰϦζϜͷΈڐՄ ɾWindows Hello Λ࢖͍͍ͨͳΒ RS256 ΋ڐՄ
  34. 76.

    6. 伴ੜ੒༻ͷϨεϙϯεΛฦ͢ΞΫγϣϯΛ௥Ճ app/Http/Controllers/Auth/RegisterController.php ଞͷ߲໨ΛͳΜ΍͔Μ΍ઃఆ // Timeout $timeout = 10000; //

    Devices to exclude $excludedPublicKeyDescriptors = []; // Authenticator Selection Criteria (we used default values) $authenticatorSelectionCriteria = new AuthenticatorSelectionCriteria(); // Extensions $extensions = new AuthenticationExtensionsClientInputs(); ɹ $extensions->add(new AuthenticationExtension('loc', true));
  35. 77.

    6. 伴ੜ੒༻ͷϨεϙϯεΛฦ͢ΞΫγϣϯΛ௥Ճ app/Http/Controllers/Auth/RegisterController.php ࡞੒ͨ͠৘ใΛͻͱ·ͱΊʹͯ͠ view ʹຒΊࠐΜͰϨϯμϦϯά $publicKeyCredentialCreationOptions = new PublicKeyCredentialCreationOptions(

    $rpEntity, $userEntity, $challenge, $publicKeyCredentialParametersList, $timeout, $excludedPublicKeyDescriptors, $authenticatorSelectionCriteria, PublicKeyCredentialCreationOptions::ATTESTATION_CONVEYANCE_PREFERENCE_NONE, $extensions ); return view('auth.register-confirm', compact('publicKeyCredentialCreationOptions'));
  36. 78.

    7. 伴Λੜ੒͢ΔͨΊͷεΫϦϓτΛviewʹ௥Ճ resources/views/auth/login-confirm.blade.php αʔόʔͰੜ੒ͨ͠σʔλΛ js ଆͰ࢖͏ͨΊʹௐ੔ <script> let publicKey =

    @php echo json_encode($publicKeyCredentialRequestOptions, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); @endphp function arrayToBase64String(a) { return btoa(String.fromCharCode(...a)); } console.log(publicKey); publicKey.challenge = Uint8Array.from(window.atob(publicKey.challenge), c => c.charCodeAt(0)); publicKey.allowCredentials = publicKey.allowCredentials.map(function (data) { return { ...data, 'id': Uint8Array.from(atob(data.id), c => c.charCodeAt(0)) }; }); https://github.com/web-auth/webauthn-framework/blob/v1.2/doc/ webauthn/PublicKeyCredentialCreation.md#example Λࢀߟʹ࣮૷
  37. 79.

    7. 伴Λੜ੒͢ΔͨΊͷεΫϦϓτΛviewʹ௥Ճ resources/views/auth/login-confirm.blade.php APIΛհͯ͠ೝূثʹ伴ੜ੒ϦΫΤετ ʢݟʹͯ͘͘͝ΊΜͳ͍͞ʣ navigator.credentials.get({ publicKey }) .then(data =>

    { let publicKeyCredential = { id: data.id, type: data.type, rawId: arrayToBase64String(new Uint8Array(data.rawId)), response: { authenticatorData: arrayToBase64String(new Uint8Array(data.response.authenticatorData)), clientDataJSON: arrayToBase64String(new Uint8Array(data.response.clientDataJSON)), signature: arrayToBase64String(new Uint8Array(data.response.signature)), userHandle: data.response.userHandle ? arrayToBase64String(new Uint8Array(data.response.userHandle)) : null } }; document.querySelector("input[name='publicKeyCredential']").value = JSON.stringify(publicKeyCredential); document.getElementById('login').submit(); }, function (error) { window.location = '/login'; });
  38. 80.

    8. 伴ݕূ༻ͷΞΫγϣϯΛ௥Ճ app/Http/Controllers/Auth/RegisterController.php ϨεϙϯεΛΰϦͬͱղੳ /** * save user */ public

    function save(UserRegisterRequest $request) { $data = $request->publicKeyCredential; $publicKeyCredential = json_decode($data); // Ϩεϙϯε಺ͷ ClientDataJSON Λσίʔυ $clientDataJSON = json_decode(base64_decode($publicKeyCredential->response->clientDataJSON)); // ηογϣϯʹอଘͨ͠ challenge ΛऔΓग़ͯ͠࡟আ $challenge = base64_decode(session('register.challenge')); session()->forget('register.challenge'); // Ϩεϙϯε಺ͷ AttestationObject Λσίʔυ & ύʔε $encodedAttestationObject = base64_decode($publicKeyCredential->response->attestationObject); $attestationObject = CBOREncoder::decode($encodedAttestationObject); $authDataByteString = $attestationObject['authData']->get_byte_string(); $authDataByteArray = array_values(unpack('C*', $authDataByteString)); $rpIdHash = array_slice($authDataByteArray, 0, 32); $flag = str_pad(decbin($authDataByteArray[32]), 8, 0, STR_PAD_LEFT); $signCount = $this->byteArrayToEndian(array_slice($authDataByteArray, 33, 4));
  39. 81.

    8. 伴ݕূ༻ͷΞΫγϣϯΛ௥Ճ app/Http/Controllers/Auth/RegisterController.php ઌ΄Ͳొ࿥ϦΫΤετͨ͠Ϣʔβʔ͔Ͳ͏͔ΛνΣοΫ // check RP ID hash if

    ($this->byteArrayToHex($rpIdHash) != hash('sha256', config('webauthn.rp.id'))) { throw new \Exception('Invalid! Not match RP ID Hash'); } // check challenge $clientChallenge = base64_decode( str_replace('-', '+', str_replace('_', ‘/', $clientDataJSON->challenge))); if ($clientChallenge != $challenge) { throw new \Exception('Invalid! Not match Challenge'); } // check type if ($clientDataJSON->type != 'webauthn.create') { throw new \Exception('Invalid! Type is not "webauthn.create"'); }
  40. 82.

    8. 伴ݕূ༻ͷΞΫγϣϯΛ௥Ճ app/Http/Controllers/Auth/RegisterController.php ެ։伴ΛΰϦͬͱ࡞੒ Ϣʔβʔʹඥ͚ͮͯอଘ // attestedCredentialData ͷύʔεʢ؆қ൛ʣ if (substr($flag,

    1, 1)) { $aaguid = array_slice($authDataByteArray, 37, 16); $credentialIdLength = array_slice($authDataByteArray, 53, 2); $credentialIdLength = $this->byteArrayToEndian($credentialIdLength); $credentialId = array_slice($authDataByteArray, 55, $credentialIdLength); $credentialPublicKey = $this->byteArrayToString(array_slice($authDataByteArray, 55 + $credentialIdLength)); $credentialPublicKeyData = CBOREncoder::decode($credentialPublicKey); if ($this->byteArrayToHex($credentialId) !== bin2hex(base64_decode($publicKeyCredential->rawId))) { throw new \Exception('invalid! Not match Credential ID'); } } else { throw new \Exception('Invalid! AttestedCredentialData is not Include.'); } // ެ։伴ͷௐ੔ $key1 = unpack('C*', $credentialPublicKeyData['-2']->get_byte_string()); $key2 = unpack('C*', $credentialPublicKeyData['-3']->get_byte_string()); $credentialPublicKey = array_merge([4], $key1, $key2); $credentialPublicKey = $this->createPubkeyPem($credentialPublicKey);
  41. 85.

    ΞδΣϯμ ✅ ࣗݾ঺հ ✅ WebAuthn ͱ͸ ✅ WebAuthn ͷ۩ମతͳೝূಈ࡞ ✅

    WebAuthn Λ Laravel ʹ૊ΈࠐΜͰΈΔ WebAuthn Laravel σϞ ɾ ·ͱΊ
  42. 88.

    ΞδΣϯμ ✅ ࣗݾ঺հ ✅ WebAuthn ͱ͸ ✅ WebAuthn ͷ۩ମతͳೝূಈ࡞ ✅

    WebAuthn Λ Laravel ʹ૊ΈࠐΜͰΈΔ ✅ WebAuthn Laravel σϞ ·ͱΊ
  43. 90.

    ࢀߟจݙ [3] WebAuthn͜ͱ͸͡Ί https://tech.mercari.com/entry/2019/06/04/120000 [4] ύεϫʔυϨεΛࢧ͑Δٕज़! ݕূ! ࣍ੈ୅Webೝূ ~WebAuthnೖ໳ɾPHP࣮૷ฤ~ https://recruit.gmo.jp/engineer/jisedai/blog/webauthn/

    [5] ηΩϡϦςΟാͰ͔ͭ·͑ͯ https://booth.pm/ja/items/1317173 ຊεϥΠυͷ࡞੒ʹ͋ͨΓɺ ԼهͷαΠτٴͼॻ੶Λࢀߟʹ͍͖ͤͯͨͩ͞·ͨ͠ɻ [2] Web Authentication API https://developer.mozilla.org/ja/docs/Web/API/Web_Authentication_API [1] Web Authentication: An API for accessing Public Key Credentials Level 1 https://www.w3.org/TR/webauthn/