Skip to content

Commit bf7fb6c

Browse files
committed
fido2: use JSON encoding
1 parent f00fb60 commit bf7fb6c

File tree

6 files changed

+24
-67
lines changed

6 files changed

+24
-67
lines changed

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ pip install django-mfa3
2929
`settings.py` for a full list of settings.
3030
4. Register URLs: `path('mfa/', include('mfa.urls', namespace='mfa')`
3131
5. The included templates are just examples, so you should [replace them](https://docs.djangoproject.com/en/stable/howto/overriding-templates/) with your own
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 [cbor-js](https://www.npmjs.com/package/cbor-js)).
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)).
3333
7. Somewhere in your app, add a link to `'mfa:list'`
3434

3535
## Enforce MFA

mfa/methods/fido2.py

+15-30
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,44 @@
1-
from fido2 import cbor
1+
import json
2+
3+
from fido2.features import webauthn_json_mapping
24
from fido2.server import Fido2Server
35
from fido2.utils import websafe_decode
46
from fido2.utils import websafe_encode
5-
from fido2.webauthn import AttestationObject
67
from fido2.webauthn import AttestedCredentialData
7-
from fido2.webauthn import AuthenticatorData
8-
from fido2.webauthn import CollectedClientData
98
from fido2.webauthn import PublicKeyCredentialRpEntity
9+
from fido2.webauthn import PublicKeyCredentialUserEntity
1010

1111
from .. import settings
1212

1313
name = 'FIDO2'
1414

15+
webauthn_json_mapping.enabled = True
16+
1517
fido2 = Fido2Server(
1618
PublicKeyCredentialRpEntity(id=settings.DOMAIN, name=settings.SITE_TITLE),
1719
)
1820

1921

20-
def encode(data):
21-
return cbor.encode(data).hex()
22-
23-
24-
def decode(s):
25-
return cbor.decode(bytes.fromhex(s))
26-
27-
2822
def get_credentials(user):
2923
keys = user.mfakey_set.filter(method=name)
3024
return [AttestedCredentialData(websafe_decode(key.secret)) for key in keys]
3125

3226

3327
def register_begin(user):
3428
registration_data, state = fido2.register_begin(
35-
{
36-
'id': str(user.id).encode('utf-8'),
37-
'name': user.get_username(),
38-
'displayName': user.get_full_name(),
39-
},
29+
PublicKeyCredentialUserEntity(
30+
id=str(user.id).encode('utf-8'),
31+
name=user.get_username(),
32+
display_name=user.get_full_name(),
33+
),
4034
get_credentials(user),
4135
user_verification=settings.FIDO2_USER_VERIFICATION,
4236
)
43-
return encode(registration_data), state
37+
return json.dumps(dict(registration_data)), state
4438

4539

4640
def register_complete(state, request_data):
47-
data = decode(request_data)
48-
auth_data = fido2.register_complete(
49-
state,
50-
CollectedClientData(data['clientData']),
51-
AttestationObject(data['attestationObject']),
52-
)
41+
auth_data = fido2.register_complete(state, json.loads(request_data))
5342
return websafe_encode(auth_data.credential_data)
5443

5544

@@ -59,16 +48,12 @@ def authenticate_begin(user):
5948
credentials,
6049
user_verification=settings.FIDO2_USER_VERIFICATION,
6150
)
62-
return encode(auth_data), state
51+
return json.dumps(dict(auth_data)), state
6352

6453

6554
def authenticate_complete(state, user, request_data):
66-
data = decode(request_data)
6755
fido2.authenticate_complete(
6856
state,
6957
get_credentials(user),
70-
data['credentialId'],
71-
CollectedClientData(data['clientData']),
72-
AuthenticatorData(data['authenticatorData']),
73-
data['signature'],
58+
json.loads(request_data),
7459
)

mfa/static/mfa/fido2.js

+6-28
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,13 @@
11
(function() {
2-
var encode = function(data) {
3-
var buffer = CBOR.encode(data);
4-
var arr = new Uint8Array(buffer);
5-
return arr.reduce((s, b) => s + b.toString(16).padStart(2, '0'), '');
6-
};
7-
8-
var decode = function(hex) {
9-
var arr = new Uint8Array(hex.length / 2);
10-
for (var i = 0; i < arr.length; i += 1) {
11-
arr[i] = parseInt(hex.substring(i * 2, i * 2 + 2), 16);
12-
}
13-
return CBOR.decode(arr.buffer);
14-
};
15-
162
var initCreate = function() {
173
var form = document.querySelector('form[data-fido2-create]');
184
if (form) {
19-
var options = decode(form.dataset.fido2Create);
5+
var options = webauthnJSON.parseCreationOptionsFromJSON(form.dataset.fido2Create);
206
form.addEventListener('submit', function(event) {
217
event.preventDefault();
228

23-
navigator.credentials.create(options).then(attestation => {
24-
this.code.value = encode({
25-
'attestationObject': new Uint8Array(attestation.response.attestationObject),
26-
'clientData': new Uint8Array(attestation.response.clientDataJSON),
27-
});
9+
webauthnJSON.create(options).then(attestation => {
10+
this.code.value = JSON.stringify(attestation);
2811
form.submit();
2912
}).catch(alert);
3013
});
@@ -34,17 +17,12 @@
3417
var initAuth = function() {
3518
var form = document.querySelector('form[data-fido2-auth]');
3619
if (form) {
37-
var options = decode(form.dataset.fido2Auth);
20+
var options = webauthnJSON.parseRequestOptionsFromJSON(form.dataset.fido2Auth);
3821
form.addEventListener('submit', function(event) {
3922
event.preventDefault();
4023

41-
navigator.credentials.get(options).then(assertion => {
42-
this.code.value = encode({
43-
'credentialId': new Uint8Array(assertion.rawId),
44-
'authenticatorData': new Uint8Array(assertion.response.authenticatorData),
45-
'clientData': new Uint8Array(assertion.response.clientDataJSON),
46-
'signature': new Uint8Array(assertion.response.signature),
47-
});
24+
webauthnJSON.get(options).then(assertion => {
25+
this.code.value = JSON.stringify(assertion);
4826
form.submit();
4927
}).catch(alert);
5028
});

mfa/templates/mfa/auth_FIDO2.html

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,5 @@ <h1>Two-factor authentication</h1>
1414
<a href="{% url 'mfa:auth' 'recovery' %}">Use recovery code instead</a>
1515
</form>
1616

17-
<script src="{% static 'cbor-js/cbor.js' %}"></script>
17+
<script src="{% static '@github/webauthn-json/dist/browser-global/webauthn-json.browser-global.js' %}"></script>
1818
<script src="{% static 'mfa/fido2.js' %}"></script>

mfa/templates/mfa/create_FIDO2.html

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,5 @@
1515
<button>Create</button>
1616
</form>
1717

18-
<script src="{% static 'cbor-js/cbor.js' %}"></script>
18+
<script src="{% static '@github/webauthn-json/dist/browser-global/webauthn-json.browser-global.js' %}"></script>
1919
<script src="{% static 'mfa/fido2.js' %}"></script>

tests/tests.py

-6
Original file line numberDiff line numberDiff line change
@@ -187,12 +187,6 @@ def test_create(self):
187187
res = self.client.get('/mfa/create/FIDO2/')
188188
self.assertEqual(res.status_code, 200)
189189

190-
def test_encode(self):
191-
self.assertEqual(fido2.encode({'foo': [1, 2]}), 'a163666f6f820102')
192-
193-
def test_decode(self):
194-
self.assertEqual(fido2.decode('a163666f6f820102'), {'foo': [1, 2]})
195-
196190
def test_origin_https(self):
197191
for domain, value, expected in [
198192
('example.com', 'https://example.com', True),

0 commit comments

Comments
 (0)