Upload
fido-alliance
View
433
Download
1
Embed Size (px)
Citation preview
Welcome to WebAuthn workshopWelcome to WebAuthn workshop
While you are waiting, please check that you haveinstalled LATESTLATEST Nodejs(v6.x), NPM, Git andNodejs(v6.x), NPM, Git and
Firefox Nightly(59 =<)Firefox Nightly(59 =<)
Open slides fromhttp://bit.ly/2FV5u9rhttp://bit.ly/2FV5u9r
1
2
AuthenticatingAuthenticatingyour Webyour Web
3
Yuriy AckermannYuriy AckermannSr. Certification EngineerSr. Certification Engineer
@FIDOAlliance@FIDOAlliance
twitter/github: @herrjemandtwitter/github: @herrjemand4
Todays plan:Todays plan:Learn what is webauthnLearn how to webauthn
Learn how to make...manage...and assert creds.
Not learning today:Not learning today:Good code and/or security practices
5
So... before we startSo... before we startcheck that:check that:
You have installed latest latest NodeJS...and NPMFirefox nightlyGitText editor
6
Short recap ofShort recap ofCredManAPICredManAPI
JS API for credentials management in user management
Official W3C specBasically JS API for autofillRead/watch:https://www.w3.org/TR/credential-management/https://developers.google.com/web/fundamentals/security/credential-management/https://pusher.com/sessions/meetup/js-monthly-london/building-a-better-login-with-the-credential-management-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) => { ... })
7
What is WebAuthn?What is WebAuthn?
PublicKey extension to credential management APIAn official W3C standardA sub-spec of FIDO2 specsBasically public keys for authentication in browsersRead:https://w3c.github.io/webauthn/https://webauthn.org/http://slides.com/herrjemand/webauthn-isig
8
MakeCredentials requestMakeCredentials request var publicKey = { challenge: new Uint8Array([21,31,105, ..., 55]),
rp: { name: "ACME Corporation" },
user: { id: Uint8Array.from(window.atob("MIIBkzCCATigAwIBAjCCAZMwggE4oAMCAQIwggGTMII="), c=>c.charCodeAt(0 name: "[email protected]", displayName: "Alex P. Müller" },
pubKeyCredParams: [ { type: "public-key", alg: -7 // "ES256" IANA COSE Algorithms registry }, { type: "public-key", alg: -257 // "RS256" IANA COSE Algorithms registry } ] }
navigator.credentials.create({ publicKey }) .then((newCredentialInfo) => { /* Public key credential */ }).catch((error) => { /* Error */ })
Random 32byte challenge buffer
Friendly RP name
User names and userid buffer
Signature algorithmnegotiation
9
Let's try it our selves:Let's try it our selves: 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: "[email protected]", displayName: "Alice von Wunderland" },
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) })
Go to and run in the consolehttps://webauthn.bin.coffee
10
MakeCredentials responseMakeCredentials response
Inner authr id. id == base64url(rawId)
{ "challenge": "HDhbiI5a6F_ndjVmnkCYKM_Mjt5Nv7OQwrYAeI8zX5E", "hashAlgorithm": "SHA-256", "origin": "https://webauthn.bin.coffee"}
Client collected data
Authr assertion
ATTESTATION OBJECT
RP ID hash FLAGS
0 0 0 UV 0ATED UP
COUNTER ATTESTED CRED. DATA EXTENSIONS
32 bytes 1 byte 4 bytes (big-endian uint32) variable length variable length if present (CBOR)
7
“authData“: ... “fmt“: “packed“ “attStmt“: ...
AUTHENTICATOR DATA
“sig“: ...“alg“: ... “x5c“: ...If Basic or Privacy CA:
“ecdaaKeyId“: ...If ECDAA:
AAGUID L CREDENTIAL ID CREDENTIAL PUBLIC KEY
variable length (COSE_Key)LENGTH L(variable length)
2 bytes16 bytes
0
ATTESTATION STATEMENT (in "packed" attestsion statement format)
“sig“: ...“alg“: ...
11
Okay, hands on deck, lets codeOkay, hands on deck, lets code
Open terminalgit clonegit clone https://github.com/fido-alliance/webauthn-democd webauthn-demonpm installnode app.jsIn Firefox Nightly http://localhost:3000
12
Request challengeRequest challenge
Process challengeProcess challenge
Return responseReturn response
What are we doing?What are we doing?
13
App architectureApp architectureSimple Expressapp.js + config.json - app configroutes/ - express routers + dbutils.js - help functions + cryptostatic/ - static frontend
Frontend architectureFrontend architectureSimple HTML framework + jQueryjs/password.auth.js - well... you get itjs/helpers.js - help functionsjs/view.js - some gui help functions
14
For registration:For registration:
Get username and name(password field isobsolete, lol)Send them to the serverServer responds with challengeMakeCredentialSend response to the serverCheck that server likes itPROFIT!
15
Remove password section from registration form inindex.htmlIn "password.auth.js" comment #register handlerIn "webauthn.auth.js" add:
/* Handle for register form submission */ $('#register').submit(function(event) { event.preventDefault();
let username = this.username.value; let name = this.name.value;
if(!username || !name) { alert('Name or username is missing!') return }
})
16
Then we need to get MakeCred challenge. For that wewill have /webauthn/register /webauthn/register endpointAdding getMakeCredentialsChallenge fetch function to"webauthn.auth.js"
let getMakeCredentialsChallenge = (formBody) => { return fetch('/webauthn/register', { method: 'POST', credentials: 'include', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(formBody) }) .then((response) => response.json()) .then((response) => { if(response.status !== 'ok') throw new Error(`Server responed with error. The message is: ${response.message}`);
return response })}
POST requestPOST request
Its a JSON request, and itIts a JSON request, and ittakes JS object and JSONtakes JS object and JSONencodes itencodes it
Server responds with JSONServer responds with JSON
response key "status" can be "ok" orresponse key "status" can be "ok" or"failed""failed"
17
Adding "getMakeCredentialsChallengegetMakeCredentialsChallenge" to "#register" formprocessor in "webauthn.auth.js"
/* Handle for register form submission */ $('#register').submit(function(event) { event.preventDefault();
let username = this.username.value; let name = this.name.value;
if(!username || !name) { alert('Name or username is missing!') return }
getMakeCredentialsChallenge({username, name}) .then((response) => { console.log(response) }) })
18
It receives name and usernameAdds adds them to the DB tagged as not registeredGenerates registration requestAnd sends it back to the browser
Now for the /webauthn/registerNow for the /webauthn/registerendpointendpoint
19
Now lets create /webauthn/register endpointIn routes/webauthn.js insert this code
router.post('/register', (request, response) => { if(!request.body || !request.body.username || !request.body.name) { response.json({ 'status': 'failed', 'message': 'Request missing name or username field!' })
return }
let username = request.body.username; let name = request.body.name;
if(database[username] && database[username].registered) { response.json({ 'status': 'failed', 'message': `Username ${username} already exists` })
return }
database[username] = { 'name': name, 'registered': false, 'id': utils.randomBase64URLBuffer(), 'authenticators': [] }
})
Check all field. The body is theCheck all field. The body is therequest.bodyrequest.body
Check that user does not exist orCheck that user does not exist orhe is not registeredhe is not registered
Creating userCreating user
Generating user random IDGenerating user random ID
This is where we storeThis is where we storeregistered authenticatorsregistered authenticators
20
To generate makeCredential challenge utils have"generateServerMakeCredRequestgenerateServerMakeCredRequest" method
let generateServerMakeCredRequest = (username, displayName, id) => { return { challenge: randomBase64URLBuffer(32), rp: { name: "FIDO Examples Corporation" }, user: { id: id, name: username, displayName: displayName }, pubKeyCredParams: [ { type: "public-key", alg: -7 // "ES256" IANA COSE Algorithms registry } ] } }
router.post('/register', (request, response) => {
...
let challengeMakeCred = utils.generateServerMakeCredRequest(username, name, database[username].id) challengeMakeCred.status = 'ok'
request.session.challenge = challengeMakeCred.challenge; request.session.username = username;
response.json(challengeMakeCred)
})
Generate makeCred challenge:Generate makeCred challenge:passing username, name, and idpassing username, name, and id
Saving username and challengeSaving username and challengein session for laterin session for later
Don't forget to let browserDon't forget to let browserknow that you are ok!know that you are ok!
Send responseSend response
In routes/webauthn.js add new block of code21
Back to the html...
22
Remember this?Remember this? 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: "[email protected]", displayName: "Alice von Wunderland" },
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 is a bufferchallenge is a buffer
id is a bufferid is a buffer
23
Here is our server responseHere is our server response { "challenge": "IAomGjp6nnS9GvPhdRdd3ATQWdL0PXTOAHDR6pPgeXM", "rp": { "name": "ACME Corporation" }, "user": { "id": "38cuhE0p0bN5PM9hSp7WEE8oTS08OQE0igXtuBifxfo", "name": "alice", "displayName": "Alice" }, "pubKeyCredParams": [{ "type": "public-key", "alg": -7 }], "status": "ok" }
Oh boy, challenge is not BUFFER!Oh boy, challenge is not BUFFER!...cause no buffers in JSON...cause no buffers in JSON
...and id as well...and id as well
Good that helpers.js have "Good that helpers.js have "preformatMakeCredReq" method method var preformatMakeCredReq = (makeCredReq) => { makeCredReq.challenge = base64url.decode(makeCredReq.challenge); makeCredReq.user.id = base64url.decode(makeCredReq.user.id); return makeCredReq }
/* Handle for register form submission */ $('#register').submit(function(event) { event.preventDefault(); let username = this.username.value; let name = this.name.value; if(!username || !name) { alert('Name or username is missing!') return } getMakeCredentialsChallenge({username, name}) .then((response) => { let publicKey = preformatMakeCredReq(response); return navigator.credentials.create({ publicKey }) }) .then((newCred) => { console.log(newCred) }) })
Updating #registration processor in webauthn.auth.js24
Back to MakeCredentials responseBack to MakeCredentials response
BUFFERBUFFER
BUFFERBUFFER
BUFFERBUFFER
Guess what? JSON does not do buffers *(Guess what? JSON does not do buffers *(But don't worry, utils have But don't worry, utils have publicKeyCredentialToJSONpublicKeyCredentialToJSON
methodmethod
getMakeCredentialsChallenge({username, name}) .then((response) => { let publicKey = preformatMakeCredReq(response); return navigator.credentials.create({ publicKey }) }) .then((newCred) => { let makeCredResponse = publicKeyCredentialToJSON(newCred); console.log(makeCredResponse) })
Updating #registration processor inwebauthn.auth.js
{ "rawId": "Gw7nqgWzci8jwIX9yXzYtynmJbQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" "response": { "attestationObject": "o2NmbXRoZmlkby11MmZnYXR0U3RtdKJjc2lnWEcwRQIhAIdC3J6jt_rxTF3mo_HdQ_HUWOW3b9GPzpLMz-Wt78UTAiBjE0JgQNTkglEg2eEv8aJIYZCqJUzm5LO8tVax7m-gz2N4NWOBWQGCMIIBfjCCASSgAwIBAgIBATAKBggqhkjOPQQDAjA8MREwDwYDVQQDDAhTb2Z0IFUyRjEUMBIGA1UECgwLR2l0SHViIEluYy4xETAPBgNVBAsMCFNlY3VyaXR5MB4XDTE3MDcyNjIwMDkwOFoXDTI3MDcyNDIwMDkwOFowPDERMA8GA1UEAwwIU29mdCBVMkYxFDASBgNVBAoMC0dpdEh1YiBJbmMuMREwDwYDVQQLDAhTZWN1cml0eTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABPacqyQUS7Tvh_cPIxxc1PV4BKz44Mays-NSGD2AOR9r0nnSakyDZHTmwtojk_-sHVA0bFwjkGVXkz7Lk_9u3tGjFzAVMBMGCysGAQQBguUcAgEBBAQDAgMIMAoGCCqGSM49BAMCA0gAMEUCIQD-Ih2XuOrqErufQhSFD0gXZbXglZNeoaPWbQ-xbzn3IgIgZNfcL1xsOCr3ZfV4ajmwsUqXRSjvfd8hAhUbiErUQXpoYXV0aERhdGFYykmWDeWIDoxodDQXD2R2YFuP5K65ooYyx5lc87qDHZdjQQAAAAAAAAAAAAAAAAAAAAAAAAAAAEYbDueqBbNyLyPAhf3JfNi3KeYltAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAApQECAyYgASFYIJ8Lv8N1eB_A9d6z3k0B4d9ii7fHSyZChIG3lwlqsgHcIlggglrXCklNPmjLdnXDijGxDh0b2k52p2N6EDET0BScCjo" "clientDataJSON": "eyJjaGFsbGVuZ2UiOiJLMlEwdHdnXzVGNDJRMEtnYll6OXdxaGVEN3ZBbmlFdEJ0N190a3g3ZEo0IiwiaGFzaEFsZ29yaXRobSI6IlNIQS0yNTYiLCJvcmlnaW4iOiJodHRwOi8vbG9jYWxob3N0OjMwMDAifQ" }, "id": "Gw7nqgWzci8jwIX9yXzYtynmJbQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" "type": "public-key"}
That's better!That's better!25
For registration:For registration:
Get username and name(password field isobsolete, lol)Send them to the serverServer responds with challengeMakeCredentialSend response to the serverCheck that server likes itPROFIT!
26
Sending response to the serverSending response to the server
Auth and Reg responses both going to the sameendpoint /webauthn/response/webauthn/response
let sendWebAuthnResponse = (body) => { return fetch('/webauthn/response', { method: 'POST', credentials: 'include', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) }) .then((response) => response.json()) .then((response) => { if(response.status !== 'ok') throw new Error(`Server responed with error. The message is: ${response.message}`);
return response })}
.then((response) => { let makeCredResponse = publicKeyCredentialToJSON(response); return sendWebAuthnResponse(makeCredResponse) }) .then((response) => { if(response.status === 'ok') { loadMainContainer() } else { alert(`Server responed with error. The message is: ${response.message}`); } }) .catch((error) => alert(error))
New function to be added to webauthn.auth.js
Updating #registration processor in webauthn.auth.js27
Back to the serverBack to the serverrouter.post('/response', (request, response) => { if(!request.body || !request.body.id || !request.body.rawId || !request.body.response || !request.body.type || request.body.type !== 'public-key' ) { response.json({ 'status': 'failed', 'message': 'Response missing one or more of id/rawId/response/type fields, or type is not public-key!' })
return }
let webauthnResp = request.body let clientData = JSON.parse(base64url.decode(webauthnResp.response.clientDataJSON));
/* Check challenge... */ if(clientData.challenge !== request.session.challenge) { response.json({ 'status': 'failed', 'message': 'Challenges don\'t match!' }) }
/* ...and origin */ if(clientData.origin !== config.origin) { response.json({ 'status': 'failed', 'message': 'Origins don\'t match!' }) }
})
Add this code to routes/webauthn.jsroutes/webauthn.js
Checking responseChecking response
Parsing client dataParsing client data
Checking that origin and challenge matchChecking that origin and challenge match
28
Ok, so we got the response. How doOk, so we got the response. How dowe know that it's a reg?we know that it's a reg?
attestationObjectattestationObject
MakeCredentialsMakeCredentials
authenticatorDataauthenticatorData
GetAssertionGetAssertion
router.post('/response', (request, response) => { ...
if(webauthnResp.response.attestationObject !== undefined) { /* This is create cred */
} else if(webauthnResp.response.authenticatorData !== undefined) { /* This is get assertion */ } else { response.json({ 'status': 'failed', 'message': 'Can not determine type of response!' }) }
})
Updating /response endpoint in routes/webauthn.js
29
AttestationVerificationAttestationVerificationIn utils.js there is a method
"verifyAuthenticatorAttestationResponse"
let verifyAuthenticatorAttestationResponse = (webAuthnResponse) => { let attestationBuffer = base64url.toBuffer(webAuthnResponse.response.attestationObject); let ctapMakeCredResp = cbor.decodeAllSync(attestationBuffer)[0];
let response = {'verified': false}; if(ctapMakeCredResp.fmt === 'fido-u2f') { let authrDataStruct = parseMakeCredAuthData(ctapMakeCredResp.authData);
if(!(authrDataStruct.flags & U2F_USER_PRESENTED)) throw new Error('User was NOT presented durring authentication!');
let clientDataHash = hash(base64url.toBuffer(webAuthnResponse.response.clientDataJSON)) let reservedByte = Buffer.from([0x00]); let publicKey = COSEECDHAtoPKCS(authrDataStruct.COSEPublicKey) let signatureBase = Buffer.concat([reservedByte, authrDataStruct.rpIdHash, clientDataHash, authrDataStruct.credID, publicKey]);
let PEMCertificate = ASN1toPEM(ctapMakeCredResp.attStmt.x5c[0]); let signature = ctapMakeCredResp.attStmt.sig;
response.verified = verifySignature(signature, signatureBase, PEMCertificate)
if(response.verified) { response.authrInfo = { fmt: 'fido-u2f', publicKey: base64url.encode(publicKey), counter: authrDataStruct.counter, credID: base64url.encode(authrDataStruct.credID) } } }
return response}
Decode base64url encoded assertionDecode base64url encoded assertionbuffer, and CBOR parse itbuffer, and CBOR parse it
It's a U2F statementIt's a U2F statement
Parsing raw authData bufferParsing raw authData buffer
Check that TUP flag is setCheck that TUP flag is set
Generate signature baseGenerate signature base COSE to PKCS conversionCOSE to PKCS conversion
X509 Cert buffer into PEMX509 Cert buffer into PEM
Verify signatureVerify signature
On verification, returnOn verification, returnresponse with new Authrresponse with new Authr
30
AuthenticatorDataAuthenticatorData
let parseMakeCredAuthData = (buffer) => { let rpIdHash = buffer.slice(0, 32); buffer = buffer.slice(32); let flagsBuf = buffer.slice(0, 1); buffer = buffer.slice(1); let flags = flagsBuf[0]; let counterBuf = buffer.slice(0, 4); buffer = buffer.slice(4); let counter = counterBuf.readUInt32BE(0); let aaguid = buffer.slice(0, 16); buffer = buffer.slice(16); let credIDLenBuf = buffer.slice(0, 2); buffer = buffer.slice(2); let credIDLen = credIDLenBuf.readUInt16BE(0); let credID = buffer.slice(0, credIDLen); buffer = buffer.slice(credIDLen); let COSEPublicKey = buffer;
return {rpIdHash, flagsBuf, flags, counter, counterBuf, aaguid, credID, COSEPublicKey}}
31
COSE PublicKey to PKCSCOSE PublicKey to PKCSlet COSEECDHAtoPKCS = (COSEPublicKey) => { /* +------+-------+-------+---------+----------------------------------+ | name | key | label | type | description | | | type | | | | +------+-------+-------+---------+----------------------------------+ | crv | 2 | -1 | int / | EC Curve identifier - Taken from | | | | | tstr | the COSE Curves registry | | | | | | | | x | 2 | -2 | bstr | X Coordinate | | | | | | | | y | 2 | -3 | bstr / | Y Coordinate | | | | | bool | | | | | | | | | d | 2 | -4 | bstr | Private key | +------+-------+-------+---------+----------------------------------+ */
let coseStruct = cbor.decodeAllSync(COSEPublicKey)[0]; let tag = Buffer.from([0x04]); let x = coseStruct.get(-2); let y = coseStruct.get(-3);
return Buffer.concat([tag, x, y])}
32
Final registration responseFinal registration response let result; if(webauthnResp.response.attestationObject !== undefined) { /* This is create cred */ result = utils.verifyAuthenticatorAttestationResponse(webauthnResp);
if(result.verified) { database[request.session.username].authenticators.push(result.authrInfo); database[request.session.username].registered = true } } else if(webauthnResp.response.authenticatorData !== undefined) { /* This is get assertion */
} else { response.json({ 'status': 'failed', 'message': 'Can not determine type of response!' }) }
if(result.verified) { request.session.loggedIn = true; response.json({ 'status': 'ok' }) } else { response.json({ 'status': 'failed', 'message': 'Can not authenticate signature!' }) }
Updating /response endpoint in routes/webauthn.js 33
For registration:For registration:
Get username and name(password field isobsolete, lol)Send them to the serverServer responds with challengeMakeCredentialSend response to the serverCheck that server likes itPROFIT!
34
DEMO
35
For authentication:For authentication:
Get username(remove password field)Send to the serverServer responds with challengeGetAssertionSend response to the serverCheck that server likes itPROFIT!
36
Take usernameCheck that it's exitsGenerate challengeSend it to the browser
This time we start with serverThis time we start with server/webauthn/login endpoint/webauthn/login endpoint
router.post('/login', (request, response) => { if(!request.body || !request.body.username) { response.json({ 'status': 'failed', 'message': 'Request missing username field!' })
return }
let username = request.body.username;
if(!database[username] || !database[username].registered) { response.json({ 'status': 'failed', 'message': `User ${username} does not exist!` })
return }
let getAssertion = utils.generateServerGetAssertion(database[username].authenticators) getAssertion.status = 'ok'
request.session.challenge = getAssertion.challenge; request.session.username = username;
response.json(getAssertion)})
Save username and challengeSave username and challenge
Adding new /login endpoint in routes/webauthn.js
37
let generateServerGetAssertion = (authenticators) => { let allowCredentials = []; for(let authr of authenticators) { allowCredentials.push({ type: 'public-key', id: authr.credID, transports: ['usb', 'nfc', 'ble'] }) } return { challenge: randomBase64URLBuffer(32), allowCredentials: allowCredentials }}
generateServerGetAssertiongenerateServerGetAssertion
38
Back to html...Back to html...Comment login handler in password.auth.jsAdd getAssertionChallenge function towebauthn.auth.js
let getGetAssertionChallenge = (formBody) => { return fetch('/webauthn/login', { method: 'POST', credentials: 'include', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(formBody) }) .then((response) => response.json()) .then((response) => { if(response.status !== 'ok') throw new Error(`Server responed with error. The message is: ${response.message}`);
return response })}
39
/* Handle for login form submission */$('#login').submit(function(event) { event.preventDefault();
let username = this.username.value;
if(!username) { alert('Username is missing!') return }
getGetAssertionChallenge({username}) .then((response) => { let publicKey = preformatGetAssertReq(response); return navigator.credentials.get({ publicKey }) }) .then((response) => { let getAssertionResponse = publicKeyCredentialToJSON(response); return sendWebAuthnResponse(getAssertionResponse) }) .then((response) => { if(response.status === 'ok') { loadMainContainer() } else { alert(`Server responed with error. The message is: ${response.message}`); } }) .catch((error) => alert(error))})
Adding login processor to webauthn.auth.js
40
} else if(webauthnResp.response.authenticatorData !== undefined) { /* This is get assertion */
} else {
Back to /response processor inBack to /response processor inroutes/webauthn.jsroutes/webauthn.js
41
AssertionVerificationAssertionVerificationIn utils.js there is a method
"verifyAuthenticatorAssertionResponse"
let verifyAuthenticatorAssertionResponse = (webAuthnResponse, authenticators) => { let authr = findAuthr(webAuthnResponse.id, authenticators); let authenticatorData = base64url.toBuffer(webAuthnResponse.response.authenticatorData);
let response = {'verified': false}; if(authr.fmt === 'fido-u2f') { let authrDataStruct = parseGetAssertAuthData(authenticatorData);
if(!(authrDataStruct.flags & U2F_USER_PRESENTED)) throw new Error('User was NOT presented durring authentication!');
let clientDataHash = hash(base64url.toBuffer(webAuthnResponse.response.clientDataJSON)) let signatureBase = Buffer.concat([authrDataStruct.rpIdHash, authrDataStruct.flagsBuf, authrDataStruct.counterBuf, clientDataHash]);
let publicKey = ASN1toPEM(base64url.toBuffer(authr.publicKey)); let signature = base64url.toBuffer(webAuthnResponse.response.signature);
response.verified = verifySignature(signature, signatureBase, publicKey)
if(response.verified) { if(response.counter <= authr.counter) throw new Error('Authr counter did not increase!');
authr.counter = authrDataStruct.counter } }
return response}
Searching for authenticator specified bySearching for authenticator specified byrawIDrawID
It's a U2F statementIt's a U2F statement
Parsing raw authData bufferParsing raw authData buffer
Check that TUP flag is setCheck that TUP flag is set
Generate signature baseGenerate signature base
PKCS PubKey to PEMPKCS PubKey to PEM
Verify signatureVerify signature
Check that counter increasedCheck that counter increased
Update counterUpdate counter
42
authenticatorData parsingauthenticatorData parsing
let parseGetAssertAuthData = (buffer) => { let rpIdHash = buffer.slice(0, 32); buffer = buffer.slice(32); let flagsBuf = buffer.slice(0, 1); buffer = buffer.slice(1); let flags = flagsBuf[0]; let counterBuf = buffer.slice(0, 4); buffer = buffer.slice(4); let counter = counterBuf.readUInt32BE(0); return {rpIdHash, flagsBuf, flags, counter, counterBuf} }
43
} else if(webauthnResp.response.authenticatorData !== undefined) { /* This is get assertion */ result = utils.verifyAuthenticatorAssertionResponse(webauthnResp, database[request.session.username].authenticators); } else {
New function to be added to webauthn.auth.js
Updating assertion verification in webauthn.js
44
DEMO
45