Webauthn Tutorial

45
Welcome to WebAuthn workshop Welcome to WebAuthn workshop While you are waiting, please check that you have installed LATEST LATEST Nodejs(v6.x), NPM, Git and Nodejs(v6.x), NPM, Git and Firefox Nightly(59 =<) Firefox Nightly(59 =<) Open slides from http://bit.ly/2FV5u9r http://bit.ly/2FV5u9r 1

Transcript of Webauthn Tutorial

Page 1: Webauthn Tutorial

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

Page 2: Webauthn Tutorial

2

Page 3: Webauthn Tutorial

AuthenticatingAuthenticatingyour Webyour Web

3

Page 4: Webauthn Tutorial

Yuriy AckermannYuriy AckermannSr. Certification EngineerSr. Certification Engineer

@FIDOAlliance@FIDOAlliance

twitter/github: @herrjemandtwitter/github: @herrjemand4

Page 5: Webauthn Tutorial

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

Page 6: Webauthn Tutorial

So... before we startSo... before we startcheck that:check that:

You have installed latest latest NodeJS...and NPMFirefox nightlyGitText editor

6

Page 7: Webauthn Tutorial

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

Page 8: Webauthn Tutorial

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

Page 9: Webauthn Tutorial

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

Page 10: Webauthn Tutorial

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

Page 11: Webauthn Tutorial

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

Page 12: Webauthn Tutorial

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

Page 13: Webauthn Tutorial

Request challengeRequest challenge

Process challengeProcess challenge

Return responseReturn response

What are we doing?What are we doing?

13

Page 14: Webauthn Tutorial

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

Page 15: Webauthn Tutorial

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

Page 16: Webauthn Tutorial

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

Page 17: Webauthn Tutorial

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

Page 18: Webauthn Tutorial

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

Page 19: Webauthn Tutorial

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

Page 20: Webauthn Tutorial

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

Page 21: Webauthn Tutorial

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

Page 22: Webauthn Tutorial

Back to the html...

22

Page 23: Webauthn Tutorial

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

Page 24: Webauthn Tutorial

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

Page 25: Webauthn Tutorial

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

Page 26: Webauthn Tutorial

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

Page 27: Webauthn Tutorial

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

Page 28: Webauthn Tutorial

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

Page 29: Webauthn Tutorial

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

Page 30: Webauthn Tutorial

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

Page 31: Webauthn Tutorial

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

Page 32: Webauthn Tutorial

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

Page 33: Webauthn Tutorial

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

Page 34: Webauthn Tutorial

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

Page 35: Webauthn Tutorial

DEMO

35

Page 36: Webauthn Tutorial

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

Page 37: Webauthn Tutorial

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

Page 38: Webauthn Tutorial

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

Page 39: Webauthn Tutorial

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

Page 40: Webauthn Tutorial

/* 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

Page 41: Webauthn Tutorial

} 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

Page 42: Webauthn Tutorial

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

Page 43: Webauthn Tutorial

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

Page 44: Webauthn Tutorial

} 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

Page 45: Webauthn Tutorial

DEMO

45