2021-05-07 18:10:07 +01:00
|
|
|
import base64
|
|
|
|
|
|
|
|
|
|
from fido2 import cbor
|
|
|
|
|
from fido2.client import ClientData
|
2021-05-14 09:17:12 +01:00
|
|
|
from fido2.cose import UnsupportedKey
|
2021-05-07 18:10:07 +01:00
|
|
|
from fido2.ctap2 import AttestationObject, AttestedCredentialData
|
|
|
|
|
from flask import current_app
|
|
|
|
|
|
2021-06-08 09:41:39 +01:00
|
|
|
from app.models import JSONModel, ModelList
|
|
|
|
|
from app.notify_client.user_api_client import user_api_client
|
2021-05-07 15:00:01 +01:00
|
|
|
|
|
|
|
|
|
2021-05-14 09:17:12 +01:00
|
|
|
class RegistrationError(Exception):
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
2021-05-07 15:00:01 +01:00
|
|
|
class WebAuthnCredential(JSONModel):
|
|
|
|
|
ALLOWED_PROPERTIES = {
|
|
|
|
|
'id',
|
|
|
|
|
'name',
|
2021-05-07 18:10:07 +01:00
|
|
|
'credential_data', # contains public key and credential ID for auth
|
|
|
|
|
'registration_response', # sent to API for later auditing (not used)
|
2021-05-07 15:00:01 +01:00
|
|
|
'created_at',
|
|
|
|
|
'updated_at'
|
|
|
|
|
}
|
2021-05-07 18:10:07 +01:00
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
|
def from_registration(cls, state, response):
|
|
|
|
|
server = current_app.webauthn_server
|
|
|
|
|
|
2021-05-14 09:17:12 +01:00
|
|
|
try:
|
|
|
|
|
auth_data = server.register_complete(
|
|
|
|
|
state,
|
|
|
|
|
ClientData(response["clientDataJSON"]),
|
|
|
|
|
AttestationObject(response["attestationObject"]),
|
|
|
|
|
)
|
|
|
|
|
except ValueError as e:
|
|
|
|
|
raise RegistrationError(e)
|
|
|
|
|
|
|
|
|
|
if isinstance(auth_data.credential_data.public_key, UnsupportedKey):
|
|
|
|
|
raise RegistrationError("Encryption algorithm not supported")
|
2021-05-07 18:10:07 +01:00
|
|
|
|
|
|
|
|
return cls({
|
|
|
|
|
'name': 'Unnamed key',
|
|
|
|
|
'credential_data': base64.b64encode(
|
|
|
|
|
cbor.encode(auth_data.credential_data),
|
2021-05-13 15:54:05 +01:00
|
|
|
).decode('utf-8'),
|
2021-05-07 18:10:07 +01:00
|
|
|
'registration_response': base64.b64encode(
|
|
|
|
|
cbor.encode(response),
|
2021-05-13 15:54:05 +01:00
|
|
|
).decode('utf-8')
|
2021-05-07 18:10:07 +01:00
|
|
|
})
|
|
|
|
|
|
|
|
|
|
def to_credential_data(self):
|
|
|
|
|
return AttestedCredentialData(
|
2021-05-13 15:54:05 +01:00
|
|
|
cbor.decode(base64.b64decode(self.credential_data.encode()))
|
2021-05-07 18:10:07 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
def serialize(self):
|
|
|
|
|
return {
|
|
|
|
|
'name': self.name,
|
|
|
|
|
'credential_data': self.credential_data,
|
|
|
|
|
'registration_response': self.registration_response,
|
|
|
|
|
}
|
2021-06-08 09:41:39 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
class WebAuthnCredentials(ModelList):
|
|
|
|
|
|
|
|
|
|
model = WebAuthnCredential
|
|
|
|
|
client_method = user_api_client.get_webauthn_credentials_for_user
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def as_cbor(self):
|
|
|
|
|
return [credential.to_credential_data() for credential in self]
|
|
|
|
|
|
|
|
|
|
def by_id(self, key_id):
|
|
|
|
|
return next((key for key in self if key.id == key_id), None)
|