- From: Craig Francis via GitHub <sysbot+gh@w3.org>
- Date: Wed, 15 Jan 2020 23:06:13 +0000
- To: public-webauthn@w3.org
craigfrancis has just created a new issue for https://github.com/w3c/webauthn:
== Use of CBOR, and Uint8Array/ArrayBuffer ==
Forgive me for a simplistic question, but why does WebAuthn use CBOR encoding?
This is an API for users to do a single authentication, so there is pretty much no performance benefit of using a binary format, it just makes the API harder for developers to understand, make mistakes, and requires every website to pull in a 3rd party library to parse it (not good for security).
Couldn't this API simply return a JavaScript object, with the values ready to be sent back to the server?
And because the binary data needs to be sent be sent to/from the server, the use of Uint8Array/ArrayBuffer is a pain - can't it use base64 encoding? It's supported in every language, can be easily sent in a POST request, and is used in other fields (e.g. the [Client Data `challenge` is base64 encoded](https://w3c.github.io/webauthn/#dictionary-client-data), but the [Credential Creation `challenge` is not](https://w3c.github.io/webauthn/#dom-publickeycredentialcreationoptions-challenge)).
---
I started with this ugly mix of PHP and (unsafe-inline) JavaScript to provide the challenge:
<script nonce="<?= htmlentities($script_nonce) ?>" integrity="haha, nope">
challenge_uInt8 = new Uint8Array([<?= htmlentities(implode(',', unpack('C*', $challenge))) ?>])
</script>
It's much easier/safer to pass a base64 encoded value to JavaScript with a `data-` attribute:
<input type="button" ... data-challenge="<?= htmlentities(base64_encode($challenge)) ?>" />
Unfortunately I then need to convert this base64 value to something WebAuthn understands:
function base64_to_uint8array(base64) {
var binary = window.atob(base64),
array = new Uint8Array(new ArrayBuffer(binary.length));
for (var k = (binary.length - 1); k >= 0; k--) {
array[k] = binary.charCodeAt(k);
}
return array;
}
var challenge_base64 = this.getAttribute('data-challenge'),
challenge_uInt8 = base64_to_uint8array(challenge_base64);
And it's worse getting the credentials.create() response into something usable:
var decoder = new TextDecoder('utf-8'),
clientData = JSON.parse(decoder.decode(result.response.clientDataJSON));
var attestationData = CBOR.decode(result.response.attestationObject); // A whole CBOR parsing library needed for this.
var dataView = new DataView(new ArrayBuffer(2));
var idLenBytes = attestationData.authData.slice(53, 55); // Great, another binary format to parse.
idLenBytes.forEach(function(value, index) {
dataView.setUint8(index, value)
});
var credentialIdLength = dataView.getUint16();
var credentialId = attestationData.authData.slice(55, credentialIdLength);
var publicKeyBytes = attestationData.authData.slice(55 + credentialIdLength);
var publicKeyObject1 = CBOR.decode(publicKeyBytes.buffer); // And CBOR is back again.
var publicKeyObject2 = {
'id': result.id,
'type': result.type,
'client_type': client_data.type,
'client_origin': client_data.origin,
'client_challenge': client_data.challenge,
'key_type': publicKeyObject1[1], // 2 = Elliptic Curve; using more magic numbers for keys and values, does this save a few bytes somewhere?
'key_algorithm': publicKeyObject1[3], // -7 = ECDSA with SHA256
'key_curve_type': publicKeyObject1[-1], // 1 = P-256
'key_curve_x': btoa(String.fromCharCode.apply(null, publicKeyObject1[-2])),
'key_curve_y': btoa(String.fromCharCode.apply(null, publicKeyObject1[-3]))
};
return JSON.stringify(publicKeyObject2); // Finally, something that can be sent to the server.
---
As an aside, would the [non-JavaScript approach](https://github.com/w3c/webauthn/issues/1255) be able to skip most of this extra processing?
Please view or discuss this issue at https://github.com/w3c/webauthn/issues/1362 using your GitHub account
Received on Wednesday, 15 January 2020 23:06:15 UTC