From 343672250524ab6a4e834a2da2b3b611a732e85d Mon Sep 17 00:00:00 2001 From: Robur Date: Mon, 4 Oct 2021 14:36:00 +0000 Subject: [PATCH] cleanup authentication flow, similar to registration flow --- bin/template.ml | 38 +++++++++++++++++--------------------- bin/webauthn_demo.ml | 30 +++++++++++++++--------------- 2 files changed, 32 insertions(+), 36 deletions(-) diff --git a/bin/template.ml b/bin/template.ml index 6412af4..5d9250d 100644 --- a/bin/template.ml +++ b/bin/template.ml @@ -62,29 +62,24 @@ let register_view origin user = alg: -7 } ], - attestation: "direct" + attestation: "direct", + excludeCredentials: challengeData.excludeCredentials.map(id => ({ type: "public-key", + id: Uint8Array.from(window.atob(id), c=>c.charCodeAt(0))})) }; } function do_register(username) { - fetch("/challenge/"+username) + fetch("/registration-challenge/"+username) .then(response => response.json()) .then(function (challengeData) { - console.log("got challenge data"); - console.log(challengeData); let publicKey = makePublicKey(challengeData); navigator.credentials.create({ publicKey }) .then(function (credential) { - // send attestation response and client extensions - // to the server to proceed with the registration - // of the credential - console.log(credential); - // Move data into Arrays incase it is super long let response = credential.response; let attestationObject = new Uint8Array(response.attestationObject); let clientDataJSON = new Uint8Array(response.clientDataJSON); let rawId = new Uint8Array(credential.rawId); - var body = + let body = JSON.stringify({ id: credential.id, rawId: bufferEncode(rawId), @@ -94,16 +89,14 @@ let register_view origin user = clientDataJSON: bufferEncode(clientDataJSON), }, }); - console.log(body); let headers = {'Content-type': "application/json; charset=utf-8"}; let request = new Request('/register_finish', { method: 'POST', body: body, headers: headers } ); fetch(request) .then(function (response) { - console.log(response); if (!response.ok && response.status != 403) { - console.log("bad response: " + response.status); + alert("bad response: " + response.status); return }; response.json().then(function (success) { @@ -112,7 +105,7 @@ let register_view origin user = }); }); }).catch(function (err) { - console.error(err); + alert("exception: " + err); }); }); } @@ -135,13 +128,12 @@ let register_view origin user = let authenticate_view challenge credentials user = let script = Printf.sprintf {| - var request_options = { + let request_options = { challenge: Uint8Array.from(window.atob("%s"), c=>c.charCodeAt(0)), allowCredentials: %s.map(x => { x.id = Uint8Array.from(window.atob(x.id), c=>c.charCodeAt(0)); return x }), }; navigator.credentials.get({ publicKey: request_options }) .then(function (assertion) { - console.log(assertion); let response = assertion.response; let rawId = new Uint8Array(assertion.rawId); let authenticatorData = new Uint8Array(assertion.response.authenticatorData); @@ -149,7 +141,7 @@ let authenticate_view challenge credentials user = let signature = new Uint8Array(assertion.response.signature); let userHandle = assertion.response.userHandle ? new Uint8Array(assertion.response.userHandle) : null; - var body = + let body = JSON.stringify({ id: assertion.id, rawId: bufferEncode(rawId), @@ -161,20 +153,24 @@ let authenticate_view challenge credentials user = userHandle: userHandle ? bufferEncode(userHandle) : null, } }); - console.log(body); let headers = {'Content-type': "application/json; charset=utf-8"}; let request = new Request('/authenticate_finish', { method: 'POST', body: body, headers: headers } ); fetch(request) .then(function (response) { - console.log(response); if (!response.ok) { - console.log("bad response: " + response.status); + alert("bad response: " + response.status); + window.location = "/"; + return }; + response.json().then(function (success) { + alert(success ? "Successfully authenticated!" : "Failed to authenticate :("); + window.location = "/"; + }); }); }).catch(function (err) { - console.error(err); + alert("exception: " + err); }); |} challenge (Yojson.to_string (`List diff --git a/bin/webauthn_demo.ml b/bin/webauthn_demo.ml index e7ae2af..6e2ff99 100644 --- a/bin/webauthn_demo.ml +++ b/bin/webauthn_demo.ml @@ -37,26 +37,25 @@ let add_routes t = Dream.html (Template.overview flash authenticated_as users) in - let register _req = + let register req = let user = - (* match Dream.session "authenticated_as" req with - | None -> *) gen_data ~alphabet:Base64.uri_safe_alphabet 8 - (* | Some username -> username *) - in - let _key_handles = match Hashtbl.find_opt users user with - | None -> [] - | Some keys -> List.map (fun (_, kh, _) -> kh) keys + match Dream.session "authenticated_as" req with + | None -> gen_data ~alphabet:Base64.uri_safe_alphabet 8 + | Some username -> username in Dream.html (Template.register_view (Webauthn.rpid t) user) in - (* XXX: should we distinguish between register and authenticate challenges? *) - let challenge req = + let registration_challenge req = let user = Dream.param "user" req in let challenge = Cstruct.to_string (Mirage_crypto_rng.generate 16) (* [userid] should be a random value *) and userid = Base64.encode_string ~pad:false ~alphabet:Base64.uri_safe_alphabet user in Hashtbl.replace challenges challenge user; + let credentials = match Hashtbl.find_opt users user with + | None -> [] + | Some credentials -> List.map (fun (_, cid, _) -> cid) credentials + in let challenge_b64 = (Base64.encode_string challenge) in let json = `Assoc [ "challenge", `String challenge_b64 ; @@ -65,6 +64,7 @@ let add_routes t = "name", `String user ; "displayName", `String user ; ] ; + "excludeCredentials", `List (List.map (fun s -> `String (Base64.encode_string s)) credentials) ; ] in Logs.info (fun m -> m "produced challenge for user %s: %s" user challenge_b64); @@ -119,7 +119,7 @@ let add_routes t = Dream.json "true" end else (Logs.info (fun m -> m "session_user %s, user %s" session_user user); - Dream.respond ~status:`Forbidden "Forbidden.") + Dream.json ~status:`Forbidden "false") | None, Some _keys -> Logs.app (fun m -> m "no session user"); Dream.json ~status:`Forbidden "false" @@ -163,19 +163,19 @@ let add_routes t = Flash_message.put_flash "" "Successfully authenticated" req; Dream.put_session "user" user req >>= fun () -> Dream.put_session "authenticated_as" user req >>= fun () -> - Dream.redirect req "/" + Dream.json "true" end else begin Logs.warn (fun m -> m "credential %S for user %S: counter not strictly increasing! \ Got %ld, expected >%ld. webauthn device compromised?" (fst credential) user counter (KhPubHashtbl.find counters credential)); Flash_message.put_flash "" "Authentication failure: key compromised?" req; - Dream.redirect req "/" + Dream.json "false" end | Error e -> Logs.warn (fun m -> m "error %a" Webauthn.pp_error e); let err = to_string e in Flash_message.put_flash "" ("Authentication failure: " ^ err) req; - Dream.redirect req "/" + Dream.json "false" in let logout req = @@ -191,7 +191,7 @@ let add_routes t = Dream.router [ Dream.get "/" main; Dream.get "/register" register; - Dream.get "/challenge/:user" challenge; + Dream.get "/registration-challenge/:user" registration_challenge; Dream.post "/register_finish" register_finish; Dream.get "/authenticate/:user" authenticate; Dream.post "/authenticate_finish" authenticate_finish;