- commit
- c1d45f9f973b310f7225a3604e17e6d7a2dfc2ad
- parent
- c3896f0e1343e938df0ecab89102a9620de3eff4
- Author
- Tobias Bengfort <tobias.bengfort@posteo.de>
- Date
- 2025-02-03 15:06
fido2: use JSON encoding
Diffstat
| M | README.md | 2 | +- |
| M | mfa/methods/fido2.py | 44 | +++++++++++++------------------------------- |
| M | mfa/static/mfa/fido2.js | 38 | +++++++++++--------------------------- |
| M | mfa/templates/mfa/auth_FIDO2.html | 1 | - |
| M | mfa/templates/mfa/create_FIDO2.html | 1 | - |
| M | tests/tests.py | 6 | ------ |
6 files changed, 25 insertions, 67 deletions
diff --git a/README.md b/README.md
@@ -29,7 +29,7 @@ pip install django-mfa3 29 29 `settings.py` for a full list of settings. 30 30 4. Register URLs: `path('mfa/', include('mfa.urls', namespace='mfa')` 31 31 5. The included templates are just examples, so you should [replace them](https://docs.djangoproject.com/en/stable/howto/overriding-templates/) with your own32 -1 6. FIDO2 requires client side code. You can either implement it yourself or use the included fido2.js (in which case you will have to provide the third party library [cbor-js](https://www.npmjs.com/package/cbor-js)).-1 32 6. FIDO2 requires client side code. You can either implement it yourself or use the included fido2.js (in which case you will have to provide the third party library [@github/webauthn-json](https://www.npmjs.com/package/@github/webauthn-json)). 33 33 7. Somewhere in your app, add a link to `'mfa:list'` 34 34 35 35 ## Enforce MFA
diff --git a/mfa/methods/fido2.py b/mfa/methods/fido2.py
@@ -1,32 +1,23 @@1 -1 from fido2 import cbor-1 1 import json -1 2 2 3 from fido2.features import webauthn_json_mapping 3 4 from fido2.server import Fido2Server 4 5 from fido2.utils import websafe_decode 5 6 from fido2.utils import websafe_encode6 -1 from fido2.webauthn import AttestationObject7 7 from fido2.webauthn import AttestedCredentialData8 -1 from fido2.webauthn import AuthenticatorData9 -1 from fido2.webauthn import CollectedClientData10 8 from fido2.webauthn import PublicKeyCredentialRpEntity -1 9 from fido2.webauthn import PublicKeyCredentialUserEntity 11 10 12 11 from .. import settings 13 12 14 13 name = 'FIDO2' 15 1416 -1 webauthn_json_mapping.enabled = False-1 15 webauthn_json_mapping.enabled = True 17 16 fido2 = Fido2Server( 18 17 PublicKeyCredentialRpEntity(id=settings.DOMAIN, name=settings.SITE_TITLE), 19 18 ) 20 19 21 2022 -1 def encode(data):23 -1 return cbor.encode(data).hex()24 -125 -126 -1 def decode(s):27 -1 return cbor.decode(bytes.fromhex(s))28 -129 -130 21 def get_credentials(user): 31 22 keys = user.mfakey_set.filter(method=name) 32 23 return [AttestedCredentialData(websafe_decode(key.secret)) for key in keys] @@ -34,24 +25,19 @@ def get_credentials(user): 34 25 35 26 def register_begin(user): 36 27 registration_data, state = fido2.register_begin(37 -1 {38 -1 'id': str(user.id).encode('utf-8'),39 -1 'name': user.get_username(),40 -1 'displayName': user.get_full_name(),41 -1 },-1 28 PublicKeyCredentialUserEntity( -1 29 id=str(user.id).encode('utf-8'), -1 30 name=user.get_username(), -1 31 display_name=user.get_full_name(), -1 32 ), 42 33 get_credentials(user), 43 34 user_verification=settings.FIDO2_USER_VERIFICATION, 44 35 )45 -1 return encode(registration_data), state-1 36 return json.dumps(dict(registration_data)), state 46 37 47 38 48 39 def register_complete(state, request_data):49 -1 data = decode(request_data)50 -1 auth_data = fido2.register_complete(51 -1 state,52 -1 CollectedClientData(data['clientData']),53 -1 AttestationObject(data['attestationObject']),54 -1 )-1 40 auth_data = fido2.register_complete(state, json.loads(request_data)) 55 41 return websafe_encode(auth_data.credential_data) 56 42 57 43 @@ -61,16 +47,12 @@ def authenticate_begin(user): 61 47 credentials, 62 48 user_verification=settings.FIDO2_USER_VERIFICATION, 63 49 )64 -1 return encode(auth_data), state-1 50 return json.dumps(dict(auth_data)), state 65 51 66 52 67 53 def authenticate_complete(state, user, request_data):68 -1 data = decode(request_data)69 54 fido2.authenticate_complete( 70 55 state, 71 56 get_credentials(user),72 -1 data['credentialId'],73 -1 CollectedClientData(data['clientData']),74 -1 AuthenticatorData(data['authenticatorData']),75 -1 data['signature'],-1 57 json.loads(request_data), 76 58 )
diff --git a/mfa/static/mfa/fido2.js b/mfa/static/mfa/fido2.js
@@ -1,29 +1,16 @@1 -1 var encode = function(data) {2 -1 var buffer = CBOR.encode(data);3 -1 var arr = new Uint8Array(buffer);4 -1 return arr.reduce((s, b) => s + b.toString(16).padStart(2, '0'), '');5 -1 };6 -17 -1 var decode = function(hex) {8 -1 var arr = new Uint8Array(hex.length / 2);9 -1 for (var i = 0; i < arr.length; i += 1) {10 -1 arr[i] = parseInt(hex.substring(i * 2, i * 2 + 2), 16);11 -1 }12 -1 return CBOR.decode(arr.buffer);13 -1 };-1 1 import * as webauthnJSON from 'https://cdn.jsdelivr.net/npm/@github/webauthn-json@2.1.1/dist/esm/webauthn-json.browser-ponyfill.js'; 14 2 15 3 var initCreate = function() { 16 4 var form = document.querySelector('form[data-fido2-create]'); 17 5 if (form) {18 -1 var options = decode(form.dataset.fido2Create);-1 6 var options = webauthnJSON.parseCreationOptionsFromJSON( -1 7 JSON.parse(form.dataset.fido2Create) -1 8 ); 19 9 form.addEventListener('submit', function(event) { 20 10 event.preventDefault(); 21 1122 -1 navigator.credentials.create(options).then(attestation => {23 -1 this.code.value = encode({24 -1 'attestationObject': new Uint8Array(attestation.response.attestationObject),25 -1 'clientData': new Uint8Array(attestation.response.clientDataJSON),26 -1 });-1 12 webauthnJSON.create(options).then(attestation => { -1 13 this.code.value = JSON.stringify(attestation); 27 14 form.submit(); 28 15 }).catch(alert); 29 16 }); @@ -33,17 +20,14 @@ var initCreate = function() { 33 20 var initAuth = function() { 34 21 var form = document.querySelector('form[data-fido2-auth]'); 35 22 if (form) {36 -1 var options = decode(form.dataset.fido2Auth);-1 23 var options = webauthnJSON.parseRequestOptionsFromJSON( -1 24 JSON.parse(form.dataset.fido2Auth) -1 25 ); 37 26 form.addEventListener('submit', function(event) { 38 27 event.preventDefault(); 39 2840 -1 navigator.credentials.get(options).then(assertion => {41 -1 this.code.value = encode({42 -1 'credentialId': new Uint8Array(assertion.rawId),43 -1 'authenticatorData': new Uint8Array(assertion.response.authenticatorData),44 -1 'clientData': new Uint8Array(assertion.response.clientDataJSON),45 -1 'signature': new Uint8Array(assertion.response.signature),46 -1 });-1 29 webauthnJSON.get(options).then(assertion => { -1 30 this.code.value = JSON.stringify(assertion); 47 31 form.submit(); 48 32 }).catch(alert); 49 33 });
diff --git a/mfa/templates/mfa/auth_FIDO2.html b/mfa/templates/mfa/auth_FIDO2.html
@@ -14,5 +14,4 @@ 14 14 <a href="{% url 'mfa:auth' 'recovery' %}">Use recovery code instead</a> 15 15 </form> 16 1617 -1 <script src="{% static 'cbor-js/cbor.js' %}"></script>18 17 <script src="{% static 'mfa/fido2.js' %}" type="module"></script>
diff --git a/mfa/templates/mfa/create_FIDO2.html b/mfa/templates/mfa/create_FIDO2.html
@@ -15,5 +15,4 @@ 15 15 <button>Create</button> 16 16 </form> 17 1718 -1 <script src="{% static 'cbor-js/cbor.js' %}"></script>19 18 <script src="{% static 'mfa/fido2.js' %}" type="module"></script>
diff --git a/tests/tests.py b/tests/tests.py
@@ -187,12 +187,6 @@ class FIDO2Test(MFATestCase): 187 187 res = self.client.get('/mfa/create/FIDO2/') 188 188 self.assertEqual(res.status_code, 200) 189 189190 -1 def test_encode(self):191 -1 self.assertEqual(fido2.encode({'foo': [1, 2]}), 'a163666f6f820102')192 -1193 -1 def test_decode(self):194 -1 self.assertEqual(fido2.decode('a163666f6f820102'), {'foo': [1, 2]})195 -1196 190 def test_origin_https(self): 197 191 for domain, value, expected in [ 198 192 ('example.com', 'https://example.com', True),