0012_auto_20240727_1849.py 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
  1. # Generated by Django 5.0.7 on 2024-07-27 18:49
  2. from django.db import migrations
  3. from django.conf import settings
  4. from fido2.utils import websafe_decode, websafe_encode
  5. from fido2.webauthn import AttestedCredentialData
  6. from fido2.cbor import encode
  7. import hashlib
  8. from allauth.mfa.adapter import get_adapter
  9. def migrate_mfa(apps, schema_editor):
  10. UserKey = apps.get_model("django_rest_mfa", "UserKey")
  11. Authenticator = apps.get_model("mfa", "Authenticator")
  12. adapter = get_adapter()
  13. authenticators = []
  14. for totp in UserKey.objects.filter(key_type="TOTP").iterator():
  15. recovery_codes = set()
  16. for backup_code in UserKey.objects.filter(
  17. key_type="Backup Codes", user_id=totp.user_id
  18. ):
  19. recovery_codes.update(backup_code.properties["codes"])
  20. secret = totp.properties["secret_key"]
  21. authenticators.append(
  22. Authenticator(
  23. user_id=totp.user_id,
  24. type="totp",
  25. data={"secret": adapter.encrypt(secret)},
  26. )
  27. )
  28. if recovery_codes:
  29. authenticators.append(
  30. Authenticator(
  31. user_id=totp.user_id,
  32. type="recovery_codes",
  33. data={
  34. "migrated_codes": [adapter.encrypt(c) for c in recovery_codes],
  35. },
  36. )
  37. )
  38. Authenticator.objects.bulk_create(authenticators)
  39. authenticators = []
  40. for user_key in UserKey.objects.filter(key_type="FIDO2").iterator():
  41. try:
  42. device = user_key.properties["device"]
  43. decoded_credential = websafe_decode(device)
  44. attested_credential = AttestedCredentialData(decoded_credential)
  45. aaguid = attested_credential.aaguid
  46. aaguid_bytes = aaguid.decode().encode()
  47. credential_id = attested_credential.credential_id
  48. public_key = attested_credential.public_key
  49. rp_id = settings.GLITCHTIP_URL.hostname
  50. rp_id_hash = hashlib.sha256(rp_id.encode("utf-8")).digest()
  51. flags = b"\x41" # 0x41: User present and attestation
  52. sign_count = b"\x00\x00\x00\x01"
  53. public_key_cbor = encode(public_key)
  54. auth_data = (
  55. rp_id_hash # 32-byte RP ID hash
  56. + flags # 1-byte flags
  57. + sign_count # 4-byte sign count
  58. + aaguid_bytes # 16-byte AAGUID
  59. + len(credential_id).to_bytes(2, "big")
  60. + credential_id # Credential ID length + Credential ID
  61. + public_key_cbor # CBOR-encoded public key
  62. )
  63. attestation_object = {
  64. "fmt": "none",
  65. "attStmt": {},
  66. "authData": auth_data,
  67. }
  68. attestation_object_cbor = encode(attestation_object)
  69. attestation_object_b64 = websafe_encode(attestation_object_cbor)
  70. new_credential_format = {
  71. "name": user_key.name,
  72. "credential": {
  73. "id": websafe_encode(attested_credential.credential_id),
  74. "type": "public-key",
  75. "rawId": websafe_encode(attested_credential.credential_id),
  76. "response": {
  77. "transports": ["usb"],
  78. # Fake
  79. "clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoiUU1zS0ZtWVFzZzRVZGVOMHdnLTg0YXAzcExiUTkzTGw5dURTQzFvbFhGcyIsIm9yaWdpbiI6Imh0dHA6Ly9sb2NhbGhvc3Q6ODAwMCIsImNyb3NzT3JpZ2luIjpmYWxzZX0",
  80. "attestationObject": attestation_object_b64,
  81. },
  82. "clientExtensionResults": {"credProps": {"rk": False}},
  83. "authenticatorAttachment": "cross-platform",
  84. },
  85. }
  86. authenticators.append(
  87. Authenticator(
  88. user_id=user_key.user_id,
  89. type="webauthn",
  90. data=new_credential_format,
  91. )
  92. )
  93. except Exception:
  94. continue
  95. Authenticator.objects.bulk_create(authenticators)
  96. class Migration(migrations.Migration):
  97. dependencies = [
  98. ("users", "0011_alter_user_email"),
  99. ]
  100. operations = [migrations.RunPython(migrate_mfa, migrations.RunPython.noop)]