W3C home > Mailing lists > Public > public-webauthn@w3.org > January 2020

[webauthn] Use of CBOR, and Uint8Array/ArrayBuffer (#1362)

From: Craig Francis via GitHub <sysbot+gh@w3.org>
Date: Wed, 15 Jan 2020 23:06:13 +0000
To: public-webauthn@w3.org
Message-ID: <issues.opened-550478788-1579129571-sysbot+gh@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

This archive was generated by hypermail 2.4.0 : Tuesday, 5 July 2022 07:26:39 UTC