# Generated by Django 5.0.7 on 2024-07-27 18:49 from django.db import migrations from django.conf import settings from fido2.utils import websafe_decode, websafe_encode from fido2.webauthn import AttestedCredentialData from fido2.cbor import encode import hashlib from allauth.mfa.adapter import get_adapter def migrate_mfa(apps, schema_editor): UserKey = apps.get_model("django_rest_mfa", "UserKey") Authenticator = apps.get_model("mfa", "Authenticator") adapter = get_adapter() authenticators = [] for totp in UserKey.objects.filter(key_type="TOTP").iterator(): recovery_codes = set() for backup_code in UserKey.objects.filter( key_type="Backup Codes", user_id=totp.user_id ): recovery_codes.update(backup_code.properties["codes"]) secret = totp.properties["secret_key"] authenticators.append( Authenticator( user_id=totp.user_id, type="totp", data={"secret": adapter.encrypt(secret)}, ) ) if recovery_codes: authenticators.append( Authenticator( user_id=totp.user_id, type="recovery_codes", data={ "migrated_codes": [adapter.encrypt(c) for c in recovery_codes], }, ) ) Authenticator.objects.bulk_create(authenticators) authenticators = [] for user_key in UserKey.objects.filter(key_type="FIDO2").iterator(): try: device = user_key.properties["device"] decoded_credential = websafe_decode(device) attested_credential = AttestedCredentialData(decoded_credential) aaguid = attested_credential.aaguid aaguid_bytes = aaguid.decode().encode() credential_id = attested_credential.credential_id public_key = attested_credential.public_key rp_id = settings.GLITCHTIP_URL.hostname rp_id_hash = hashlib.sha256(rp_id.encode("utf-8")).digest() flags = b"\x41" # 0x41: User present and attestation sign_count = b"\x00\x00\x00\x01" public_key_cbor = encode(public_key) auth_data = ( rp_id_hash # 32-byte RP ID hash + flags # 1-byte flags + sign_count # 4-byte sign count + aaguid_bytes # 16-byte AAGUID + len(credential_id).to_bytes(2, "big") + credential_id # Credential ID length + Credential ID + public_key_cbor # CBOR-encoded public key ) attestation_object = { "fmt": "none", "attStmt": {}, "authData": auth_data, } attestation_object_cbor = encode(attestation_object) attestation_object_b64 = websafe_encode(attestation_object_cbor) new_credential_format = { "name": user_key.name, "credential": { "id": websafe_encode(attested_credential.credential_id), "type": "public-key", "rawId": websafe_encode(attested_credential.credential_id), "response": { "transports": ["usb"], # Fake "clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoiUU1zS0ZtWVFzZzRVZGVOMHdnLTg0YXAzcExiUTkzTGw5dURTQzFvbFhGcyIsIm9yaWdpbiI6Imh0dHA6Ly9sb2NhbGhvc3Q6ODAwMCIsImNyb3NzT3JpZ2luIjpmYWxzZX0", "attestationObject": attestation_object_b64, }, "clientExtensionResults": {"credProps": {"rk": False}}, "authenticatorAttachment": "cross-platform", }, } authenticators.append( Authenticator( user_id=user_key.user_id, type="webauthn", data=new_credential_format, ) ) except Exception: continue Authenticator.objects.bulk_create(authenticators) class Migration(migrations.Migration): dependencies = [ ("users", "0011_alter_user_email"), ("django_rest_mfa", "0002_alter_userkey_key_type"), ] operations = [migrations.RunPython(migrate_mfa, migrations.RunPython.noop)]