Files
notifications-admin/app/assets/javascripts/registerSecurityKey.js
Ben Thorner 8502827afb Handle errors when registration fails
Previously we would raise a 500 error in a variety of cases:

- If a second key was being registered simultaneously (e.g. in a
separate tab), which means the registration state could be missing
after the first registration completes. That smells like an attack.

- If the server-side verification failed e.g. origin verification,
challenge verification, etc. The library seems to use 'ValueError'
for all such errors [1] (after auditing its 'raise' statements, and
excluding AttestationError [2], since we're not doing that).

- If a key is used that attempts to sign with an unsupported
algorithm. This would normally raise a NotImplemented error as part
of verifying attestation [3], but we don't do that, so we need to
verify the algorithm is supported by the library manually.

This adds error handling to return a 400 response and error message
in these cases, since the error is not unexpected (i.e. not a 500).
A 400 seems more appropriate than a 403, since in many cases it's
not clear if the request data is valid.

I've used CBOR for the transport encoding, to match the successful
request / response encoding. Note that the ordering of then/catch
matters in JS - we don't want to catch our own throws!

[1]: 142587b3e6/fido2/server.py (L255)
[2]: c42d9628a4/fido2/attestation/base.py (L39)
[3]: c42d9628a4/fido2/cose.py (L92)
2021-05-17 12:18:24 +01:00

66 lines
2.1 KiB
JavaScript

(function(window) {
"use strict";
window.GOVUK.Modules.RegisterSecurityKey = function() {
this.start = function(component) {
$(component)
.on('click', function(event) {
event.preventDefault();
fetch('/webauthn/register')
.then((response) => {
if (!response.ok) {
throw Error(response.statusText);
}
return response.arrayBuffer();
})
.then((data) => {
var options = window.CBOR.decode(data);
// triggers browser dialogue to select authenticator
return window.navigator.credentials.create(options);
})
.then((credential) => {
return postWebAuthnCreateResponse(
credential.response, component.data('csrfToken')
);
})
.then((response) => {
if (!response.ok) {
return response.arrayBuffer()
.then((cbor) => {
return Promise.resolve(window.CBOR.decode(cbor));
})
.catch(() => {
throw Error(response.statusText);
})
.then((text) => {
throw Error(text);
});
}
window.location.reload();
})
.catch((error) => {
console.error(error);
// some browsers will show an error dialogue for some
// errors; to be safe we always pop up an alert
var message = error.message || error;
alert('Error during registration.\n\n' + message);
});
});
};
};
function postWebAuthnCreateResponse(response, csrf_token) {
return fetch('/webauthn/register', {
method: 'POST',
headers: { 'X-CSRFToken': csrf_token },
body: window.CBOR.encode({
attestationObject: new Uint8Array(response.attestationObject),
clientDataJSON: new Uint8Array(response.clientDataJSON),
})
});
}
})(window);