mirror of
https://github.com/GSA/notifications-admin.git
synced 2026-06-30 12:18:19 -04:00
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)
53 lines
1.6 KiB
Python
53 lines
1.6 KiB
Python
from fido2 import cbor
|
|
from flask import current_app, request, session
|
|
from flask_login import current_user
|
|
|
|
from app.main import main
|
|
from app.models.webauthn_credential import RegistrationError, WebAuthnCredential
|
|
from app.notify_client.user_api_client import user_api_client
|
|
from app.utils import user_is_platform_admin
|
|
|
|
|
|
@main.route('/webauthn/register')
|
|
@user_is_platform_admin
|
|
def webauthn_begin_register():
|
|
server = current_app.webauthn_server
|
|
|
|
registration_data, state = server.register_begin(
|
|
{
|
|
"id": bytes(current_user.id, 'utf-8'),
|
|
"name": current_user.email_address,
|
|
"displayName": current_user.name,
|
|
},
|
|
credentials=[
|
|
credential.to_credential_data()
|
|
for credential in current_user.webauthn_credentials
|
|
],
|
|
user_verification="discouraged", # don't ask for PIN
|
|
authenticator_attachment="cross-platform",
|
|
)
|
|
|
|
session["webauthn_registration_state"] = state
|
|
return cbor.encode(registration_data)
|
|
|
|
|
|
@main.route('/webauthn/register', methods=['POST'])
|
|
@user_is_platform_admin
|
|
def webauthn_complete_register():
|
|
if 'webauthn_registration_state' not in session:
|
|
return cbor.encode("No registration in progress"), 400
|
|
|
|
try:
|
|
credential = WebAuthnCredential.from_registration(
|
|
session.pop("webauthn_registration_state"),
|
|
cbor.decode(request.get_data()),
|
|
)
|
|
except RegistrationError as e:
|
|
return cbor.encode(str(e)), 400
|
|
|
|
user_api_client.create_webauthn_credential_for_user(
|
|
current_user.id, credential
|
|
)
|
|
|
|
return cbor.encode('')
|