2021-09-28 11:30:14 +00:00
|
|
|
let page s b =
|
|
|
|
Printf.sprintf {|
|
|
|
|
<html>
|
|
|
|
<head>
|
|
|
|
<title>WebAuthn Demo</title>
|
|
|
|
<script type="text/javascript" src="/static/base64.js"></script>
|
|
|
|
<script>
|
|
|
|
function bufferEncode(value) {
|
|
|
|
return base64js.fromByteArray(value)
|
|
|
|
.replace(/\+/g, "-")
|
|
|
|
.replace(/\//g, "_")
|
|
|
|
.replace(/=/g, "");
|
|
|
|
}
|
|
|
|
</script>
|
|
|
|
<script>%s</script>
|
|
|
|
</head><body>%s</body></html>|} s b
|
|
|
|
|
|
|
|
let overview notes authenticated_as users =
|
|
|
|
let authenticated_as =
|
|
|
|
match authenticated_as with
|
|
|
|
| None -> "<h2>Not authenticated</h2>"
|
|
|
|
| Some user -> Printf.sprintf {|<h2>Authenticated as %s</h2>
|
|
|
|
<form action="/logout" method="post"><input type="submit" value="Log out"/></form>
|
|
|
|
|} user
|
|
|
|
and links =
|
|
|
|
{|<h2>Register</h2><ul>
|
|
|
|
<li><a href="/register">register</a></li>
|
|
|
|
</ul>
|
|
|
|
|}
|
|
|
|
and users =
|
|
|
|
String.concat ""
|
|
|
|
("<h2>Users</h2><ul>" ::
|
|
|
|
Hashtbl.fold (fun name keys acc ->
|
|
|
|
let handles = List.map (fun (_, h, _) -> h) keys in
|
|
|
|
(Printf.sprintf "<li>%s [<a href=/authenticate/%s>authenticate</a>] (%s)</li>" name name (String.concat ", " handles)) :: acc)
|
|
|
|
users [] @ [ "</ul>" ])
|
|
|
|
in
|
|
|
|
page "" (String.concat "" (notes @ [authenticated_as;links;users]))
|
|
|
|
|
2021-09-29 14:34:09 +00:00
|
|
|
let register_view origin user challenge userid =
|
2021-09-28 11:30:14 +00:00
|
|
|
let script = Printf.sprintf {|
|
|
|
|
var publicKey = {
|
|
|
|
challenge: Uint8Array.from(window.atob("%s"), c=>c.charCodeAt(0)),
|
|
|
|
rp: {
|
2021-09-29 14:34:09 +00:00
|
|
|
id: "%s",
|
2021-09-28 11:30:14 +00:00
|
|
|
name: "WebAuthn Demo from robur.coop"
|
|
|
|
},
|
|
|
|
user: {
|
|
|
|
id: Uint8Array.from(window.atob("%s"), c=>c.charCodeAt(0)),
|
|
|
|
displayName: "%s",
|
|
|
|
name: "%s"
|
|
|
|
},
|
|
|
|
pubKeyCredParams: [
|
|
|
|
{
|
|
|
|
type: "public-key",
|
|
|
|
alg: -7
|
|
|
|
}
|
2021-09-29 14:34:09 +00:00
|
|
|
],
|
|
|
|
attestation: "direct"
|
2021-09-28 11:30:14 +00:00
|
|
|
};
|
|
|
|
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 =
|
|
|
|
JSON.stringify({
|
|
|
|
id: credential.id,
|
|
|
|
rawId: bufferEncode(rawId),
|
|
|
|
type: credential.type,
|
|
|
|
response: {
|
|
|
|
attestationObject: bufferEncode(attestationObject),
|
|
|
|
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) {
|
|
|
|
console.log("bad response: " + response.status);
|
|
|
|
};
|
|
|
|
});
|
|
|
|
}).catch(function (err) {
|
|
|
|
console.error(err);
|
|
|
|
});
|
2021-09-29 14:34:09 +00:00
|
|
|
|} challenge origin userid user user
|
2021-09-28 11:30:14 +00:00
|
|
|
and body =
|
|
|
|
Printf.sprintf {|
|
|
|
|
<p>Welcome %s.</p>
|
|
|
|
|} user
|
|
|
|
in
|
|
|
|
page script body
|
|
|
|
|
2021-09-29 14:34:09 +00:00
|
|
|
let authenticate_view challenge credentials user =
|
2021-09-28 11:30:14 +00:00
|
|
|
let script =
|
|
|
|
Printf.sprintf {|
|
2021-09-29 14:34:09 +00:00
|
|
|
var 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);
|
|
|
|
let clientDataJSON = new Uint8Array(assertion.response.clientDataJSON);
|
|
|
|
let signature = new Uint8Array(assertion.response.signature);
|
|
|
|
let userHandle = assertion.response.userHandle ? new Uint8Array(assertion.response.userHandle) : null;
|
|
|
|
|
|
|
|
var body =
|
|
|
|
JSON.stringify({
|
|
|
|
id: assertion.id,
|
|
|
|
rawId: bufferEncode(rawId),
|
|
|
|
type: assertion.type,
|
|
|
|
response: {
|
|
|
|
authenticatorData: bufferEncode(authenticatorData),
|
|
|
|
clientDataJSON: bufferEncode(clientDataJSON),
|
|
|
|
signature: bufferEncode(signature),
|
|
|
|
userHandle: userHandle ? bufferEncode(userHandle) : null,
|
2021-09-28 11:30:14 +00:00
|
|
|
}
|
2021-09-29 14:34:09 +00:00
|
|
|
});
|
|
|
|
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);
|
|
|
|
};
|
|
|
|
});
|
|
|
|
}).catch(function (err) {
|
|
|
|
console.error(err);
|
|
|
|
});
|
|
|
|
|} challenge
|
|
|
|
(Yojson.to_string (`List
|
|
|
|
(List.map (fun credential_id ->
|
|
|
|
(`Assoc ["id", `String credential_id; "type", `String "public-key"]))
|
|
|
|
credentials)))
|
2021-09-28 11:30:14 +00:00
|
|
|
and body =
|
|
|
|
Printf.sprintf {|
|
2021-09-29 14:34:09 +00:00
|
|
|
<p>Touch your token to authenticate as %S.</p>
|
2021-09-28 11:30:14 +00:00
|
|
|
|} user
|
|
|
|
in
|
|
|
|
page script body
|