From 41e105b45bd179aa37e0f1ed342dde161524dcb3 Mon Sep 17 00:00:00 2001 From: Mohamed ElKalioby Date: Tue, 16 Nov 2021 16:59:20 +0300 Subject: [PATCH] Resident Key Done #58 --- README.md | 3 ++- example/example/auth.py | 1 + example/example/settings.py | 3 ++- example/example/templates/login.html | 4 ++++ mfa/FIDO2.py | 27 ++++++++++++++++++++++----- mfa/templates/FIDO2/Add.html | 2 ++ mfa/templates/FIDO2/recheck.html | 3 +++ 7 files changed, 36 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 32c18a2..dc45a31 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ A Django app that handles MFA, it supports TOTP, U2F, FIDO2 U2F (Web Authn), Ema Web Authencation API (WebAuthn) is state-of-the art techology that is expected to replace passwords. -![Andriod Fingerprint](https://cdn-images-1.medium.com/max/800/1*1FWkRE8D7NTA2Kn1DrPjPA.png) +![Android Fingerprint](https://cdn-images-1.medium.com/max/800/1*1FWkRE8D7NTA2Kn1DrPjPA.png) For FIDO2, the following are supported * **security keys** (Firefox 60+, Chrome 67+, Edge 18+, Safari 13 on Mac OS, Chrome on Andriod, Safari on iOS 13.3+), @@ -75,6 +75,7 @@ Depends on MFA_RECHECK_MIN=10 # Minimum interval in seconds MFA_RECHECK_MAX=30 # Maximum in seconds MFA_QUICKLOGIN=True # Allow quick login for returning users by provide only their 2FA + MFA_RESIDENT_KEY = None # Use Resident Key (Only supported in Chromimum based browsers) MFA_HIDE_DISABLE=('FIDO2',) # Can the user disable his key (Added in 1.2.0). MFA_OWNED_BY_ENTERPRISE = FALSE # Who owns security keys diff --git a/example/example/auth.py b/example/example/auth.py index afd587b..b5059f0 100644 --- a/example/example/auth.py +++ b/example/example/auth.py @@ -3,6 +3,7 @@ from django.http import HttpResponseRedirect from django.urls import reverse from django.contrib.auth import authenticate,login,logout from django.contrib.auth.models import User + def loginView(request): context={} if request.method=="POST": diff --git a/example/example/settings.py b/example/example/settings.py index cc9633c..3807667 100644 --- a/example/example/settings.py +++ b/example/example/settings.py @@ -142,9 +142,10 @@ MFA_QUICKLOGIN=True # Allow quick login for returning users by provide on MFA_HIDE_DISABLE=('',) # Can the user disable his key (Added in 1.2.0). MFA_REDIRECT_AFTER_REGISTRATION="registered" MFA_SUCCESS_REGISTRATION_MSG="Go to Home" +MFA_RESIDENT_KEY = True TOKEN_ISSUER_NAME="PROJECT_NAME" #TOTP Issuer name U2F_APPID="https://localhost" #URL For U2F -FIDO_SERVER_ID=u"local.mkalioby.com" # Server rp id for FIDO2, it the full domain of your project +FIDO_SERVER_ID=u"localhost" # Server rp id for FIDO2, it the full domain of your project FIDO_SERVER_NAME=u"TestApp" diff --git a/example/example/templates/login.html b/example/example/templates/login.html index 139c77f..419c8f1 100644 --- a/example/example/templates/login.html +++ b/example/example/templates/login.html @@ -45,6 +45,10 @@ +
+ OR +
+ diff --git a/mfa/FIDO2.py b/mfa/FIDO2.py index 98a6e5e..e1af5b9 100644 --- a/mfa/FIDO2.py +++ b/mfa/FIDO2.py @@ -39,7 +39,7 @@ def begin_registeration(request): u'id': request.user.username.encode("utf8"), u'name': (request.user.first_name + " " + request.user.last_name), u'displayName': request.user.username, - }, getUserCredentials(request.user.username)) + }, getUserCredentials(request.user.username),resident_key=getattr(settings,'MFA_RESIDENT_KEY',None)) request.session['fido_state'] = state return HttpResponse(cbor.encode(registration_data), content_type = 'application/octet-stream') @@ -63,6 +63,8 @@ def complete_reg(request): uk = User_Keys() uk.username = request.user.username uk.properties = {"device": encoded, "type": att_obj.fmt, } + if data.get('userHandle'): + uk.properties["userHandle"] = data['userHandle'] uk.owned_by_enterprise = getattr(settings, "MFA_OWNED_BY_ENTERPRISE", False) uk.key_type = "FIDO2" uk.save() @@ -97,7 +99,9 @@ def auth(request): def authenticate_begin(request): server = getServer() - credentials = getUserCredentials(request.session.get("base_username", request.user.username)) + credentials=None + if not getattr(settings,'MFA_RESIDENT_KEY',None): + credentials = getUserCredentials(request.session.get("base_username", request.user.username)) auth_data, state = server.authenticate_begin(credentials) request.session['fido_state'] = state return HttpResponse(cbor.encode(auth_data), content_type = "application/octet-stream") @@ -107,13 +111,26 @@ def authenticate_begin(request): def authenticate_complete(request): try: credentials = [] - username = request.session.get("base_username", request.user.username) + data = cbor.decode(request.body) + + if data.get("userHandle"): + keys = User_Keys.objects.filter(key_type="FIDO2", properties__icontains='"userHandle": "%s"'%data["userHandle"]) + if keys.count()==1: + username = keys[0].username + request.session["base_username"]=username + request.session.update = 1 + + else: + username = request.session.get("base_username", request.user.username) + + server = getServer() credentials = getUserCredentials(username) - data = cbor.decode(request.body) + auth_data = AuthenticatorData(data['authenticatorData']) + credential_id = data['credentialId'] client_data = ClientData(data['clientDataJSON']) - auth_data = AuthenticatorData(data['authenticatorData']) + signature = data['signature'] try: cred = server.authenticate_complete( diff --git a/mfa/templates/FIDO2/Add.html b/mfa/templates/FIDO2/Add.html index 5340c45..cfbc0ec 100644 --- a/mfa/templates/FIDO2/Add.html +++ b/mfa/templates/FIDO2/Add.html @@ -17,10 +17,12 @@ return navigator.credentials.create(options); }).then(function(attestation) { + console.log(attestation) return fetch('{% url 'fido2_complete_reg' %}', { method: 'POST', headers: {'Content-Type': 'application/cbor'}, body: CBOR.encode({ + "userHandle":attestation.id, "attestationObject": new Uint8Array(attestation.response.attestationObject), "clientDataJSON": new Uint8Array(attestation.response.clientDataJSON), }) diff --git a/mfa/templates/FIDO2/recheck.html b/mfa/templates/FIDO2/recheck.html index e5d8e79..6c592eb 100644 --- a/mfa/templates/FIDO2/recheck.html +++ b/mfa/templates/FIDO2/recheck.html @@ -58,8 +58,11 @@ }).then(CBOR.decode).then(function(options) { console.log(options) return navigator.credentials.get(options); + }).then(function(assertion) { + console.log(assertion) res=CBOR.encode({ + "userHandle":assertion.id, "credentialId": new Uint8Array(assertion.rawId), "authenticatorData": new Uint8Array(assertion.response.authenticatorData), "clientDataJSON": new Uint8Array(assertion.response.clientDataJSON),