Resident Key is done
This commit is contained in:
42
mfa/FIDO2.py
42
mfa/FIDO2.py
@@ -28,18 +28,25 @@ def recheck(request):
|
||||
|
||||
def getServer():
|
||||
"""Get Server Info from settings and returns a Fido2Server"""
|
||||
from mfa import AttestationPreference
|
||||
rp = PublicKeyCredentialRpEntity(id=settings.FIDO_SERVER_ID, name=settings.FIDO_SERVER_NAME)
|
||||
return Fido2Server(rp)
|
||||
attestation= getattr(settings,'MFA_FIDO2_ATTESTATION_PREFERENCE', AttestationPreference.NONE )
|
||||
return Fido2Server(rp,attestation=attestation)
|
||||
|
||||
|
||||
def begin_registeration(request):
|
||||
"""Starts registering a new FIDO Device, called from API"""
|
||||
server = getServer()
|
||||
from mfa import ResidentKey
|
||||
resident_key = getattr(settings,'MFA_FIDO2_RESIDENT_KEY', ResidentKey.DISCOURAGED)
|
||||
auth_attachment = getattr(settings,'MFA_FIDO2_AUTHENTICATOR_ATTACHMENT', None)
|
||||
user_verification = getattr(settings,'MFA_FIDO2_USER_VERIFICATION', None)
|
||||
registration_data, state = server.register_begin({
|
||||
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),user_verification = user_verification,
|
||||
resident_key_requirement = resident_key, authenticator_attachment = auth_attachment)
|
||||
request.session['fido_state'] = state
|
||||
|
||||
return HttpResponse(cbor.encode(registration_data), content_type = 'application/octet-stream')
|
||||
@@ -67,6 +74,8 @@ def complete_reg(request):
|
||||
uk.properties = {"device": encoded, "type": att_obj.fmt, }
|
||||
uk.owned_by_enterprise = getattr(settings, "MFA_OWNED_BY_ENTERPRISE", False)
|
||||
uk.key_type = "FIDO2"
|
||||
if auth_data.credential_data.credential_id:
|
||||
uk.user_handle = auth_data.credential_data.credential_id
|
||||
uk.save()
|
||||
if getattr(settings, 'MFA_ENFORCE_RECOVERY_METHOD', False) and not User_Keys.objects.filter(key_type = "RECOVERY", username=request.user.username).exists():
|
||||
request.session["mfa_reg"] = {"method":"FIDO2","name": getattr(settings, "MFA_RENAME_METHODS", {}).get("FIDO2", "FIDO2")}
|
||||
@@ -107,7 +116,14 @@ def auth(request):
|
||||
|
||||
def authenticate_begin(request):
|
||||
server = getServer()
|
||||
credentials = getUserCredentials(request.session.get("base_username", request.user.username))
|
||||
credentials=[]
|
||||
username = None
|
||||
if "base_username" in request.session:
|
||||
username = request.session["base_username"]
|
||||
if request.user.is_authenticated:
|
||||
username = request.user.username
|
||||
if username:
|
||||
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")
|
||||
@@ -117,11 +133,22 @@ def authenticate_begin(request):
|
||||
def authenticate_complete(request):
|
||||
try:
|
||||
credentials = []
|
||||
username = request.session.get("base_username", request.user.username)
|
||||
username = None
|
||||
keys = None
|
||||
if "base_username" in request.session:
|
||||
username = request.session["base_username"]
|
||||
if request.user.is_authenticated:
|
||||
username = request.user.username
|
||||
server = getServer()
|
||||
credentials = getUserCredentials(username)
|
||||
data = cbor.decode(request.body)
|
||||
credential_id = data['credentialId']
|
||||
if credential_id and username is None:
|
||||
keys = User_Keys.objects.filter(user_handle = credential_id)
|
||||
if keys.exists():
|
||||
credentials=[AttestedCredentialData(websafe_decode(keys[0].properties["device"]))]
|
||||
else:
|
||||
credentials = getUserCredentials(username)
|
||||
|
||||
client_data = CollectedClientData(data['clientDataJSON'])
|
||||
auth_data = AuthenticatorData(data['authenticatorData'])
|
||||
signature = data['signature']
|
||||
@@ -155,7 +182,8 @@ def authenticate_complete(request):
|
||||
content_type = "application/json")
|
||||
else:
|
||||
import random
|
||||
keys = User_Keys.objects.filter(username = username, key_type = "FIDO2", enabled = 1)
|
||||
if keys is None:
|
||||
keys = User_Keys.objects.filter(username = username, key_type = "FIDO2", enabled = 1)
|
||||
for k in keys:
|
||||
if AttestedCredentialData(websafe_decode(k.properties["device"])).credential_id == cred.credential_id:
|
||||
k.last_used = timezone.now()
|
||||
@@ -170,7 +198,7 @@ def authenticate_complete(request):
|
||||
except:
|
||||
authenticated = request.user.is_authenticated()
|
||||
if not authenticated:
|
||||
res = login(request)
|
||||
res = login(request,k.username)
|
||||
if not "location" in res: return reset_cookie(request)
|
||||
return HttpResponse(simplejson.dumps({'status': "OK", "redirect": res["location"]}),
|
||||
content_type = "application/json")
|
||||
|
||||
@@ -1 +1,5 @@
|
||||
__version__="2.2.0"
|
||||
__version__="2.7.1"
|
||||
from fido2.webauthn import ResidentKeyRequirement as ResidentKey
|
||||
from fido2.webauthn import AuthenticatorAttachment as Attachment
|
||||
from fido2.webauthn import UserVerificationRequirement as UserVerification
|
||||
from fido2.webauthn import AttestationConveyancePreference as AttestationPreference
|
||||
17
mfa/migrations/0012_user_keys_userhandle.py
Normal file
17
mfa/migrations/0012_user_keys_userhandle.py
Normal file
@@ -0,0 +1,17 @@
|
||||
# Generated by Django 2.2 on 2022-10-16 14:15
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('mfa', '0011_auto_20210530_0622'),
|
||||
]
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='user_keys',
|
||||
name='user_handle',
|
||||
field=models.CharField(blank=True, default=None, max_length=255, null=True),
|
||||
),
|
||||
]
|
||||
@@ -16,6 +16,7 @@ class User_Keys(models.Model):
|
||||
expires=models.DateTimeField(null=True,default=None,blank=True)
|
||||
last_used=models.DateTimeField(null=True,default=None,blank=True)
|
||||
owned_by_enterprise=models.BooleanField(default=None,null=True,blank=True)
|
||||
user_handle = models.CharField(default = None, null = True, blank = True, max_length = 255)
|
||||
|
||||
def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
|
||||
if self.key_type == "Trusted Device" and self.properties.get("signature","") == "":
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
}
|
||||
throw new Error('Error getting registration data!');
|
||||
}).then(CBOR.decode).then(function(options) {
|
||||
options.publicKey.attestation="direct"
|
||||
//options.publicKey.attestation="direct"
|
||||
console.log(options)
|
||||
|
||||
return navigator.credentials.create(options);
|
||||
|
||||
71
mfa/templates/FIDO2/Auth_JS.html
Normal file
71
mfa/templates/FIDO2/Auth_JS.html
Normal file
@@ -0,0 +1,71 @@
|
||||
{% load static %}
|
||||
<script type="application/javascript" src="{% static 'mfa/js/cbor.js' %}"></script>
|
||||
<script type="application/javascript" src="{% static 'mfa/js/ua-parser.min.js' %}"></script>
|
||||
<script type="text/javascript">
|
||||
function authen()
|
||||
{
|
||||
fetch('{% url 'fido2_begin_auth' %}', {
|
||||
method: 'GET',
|
||||
}).then(function(response) {
|
||||
if(response.ok) return response.arrayBuffer();
|
||||
throw new Error('No credential available to authenticate!');
|
||||
}).then(CBOR.decode).then(function(options) {
|
||||
console.log(options)
|
||||
return navigator.credentials.get(options);
|
||||
}).then(function(assertion) {
|
||||
res=CBOR.encode({
|
||||
"credentialId": new Uint8Array(assertion.rawId),
|
||||
"authenticatorData": new Uint8Array(assertion.response.authenticatorData),
|
||||
"clientDataJSON": new Uint8Array(assertion.response.clientDataJSON),
|
||||
"signature": new Uint8Array(assertion.response.signature)
|
||||
});
|
||||
|
||||
return fetch('{% url 'fido2_complete_auth' %}', {
|
||||
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/cbor'},
|
||||
body:res,
|
||||
|
||||
}).then(function (response) {if (response.ok) return res = response.json()}).then(function (res) {
|
||||
if (res.status=="OK")
|
||||
{
|
||||
$("#msgdiv").addClass("alert alert-success").removeClass("alert-danger")
|
||||
$("#msgdiv").html("Verified....please wait")
|
||||
{% if mode == "auth" or mode == None %}
|
||||
window.location.href=res.redirect;
|
||||
{% elif mode == "recheck" %}
|
||||
mfa_success_function();
|
||||
{% endif %}
|
||||
}
|
||||
else {
|
||||
$("#msgdiv").addClass("alert alert-danger").removeClass("alert-success")
|
||||
$("#msgdiv").html("Verification Failed as " + res.message + ", <a href='javascript:void(0)' onclick='authen())'> try again</a> or <a href='javascript:void(0)' onclick='history.back()'> Go Back</a>")
|
||||
|
||||
{% if mode == "auth" %}
|
||||
|
||||
{% elif mode == "recheck" %}
|
||||
|
||||
mfa_failed_function();
|
||||
{% endif %}
|
||||
}
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
}
|
||||
$(document).ready(function () {
|
||||
if (location.protocol != 'https:') {
|
||||
$("#main_paragraph").addClass("alert alert-danger")
|
||||
$("#main_paragraph").html("FIDO2 must work under secure context")
|
||||
} else {
|
||||
ua=new UAParser().getResult()
|
||||
if (ua.browser.name == "Safari" || ua.browser.name == "Mobile Safari" || ua.os.name == "iOS" || ua.os.name == "iPadOS")
|
||||
$("#res").html("<button class='btn btn-success' onclick='authen()'>Authenticate...</button>")
|
||||
else
|
||||
authen()
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
</script>
|
||||
@@ -1,6 +1,4 @@
|
||||
{% load static %}
|
||||
<script type="application/javascript" src="{% static 'mfa/js/cbor.js' %}"></script>
|
||||
<script type="application/javascript" src="{% static 'mfa/js/ua-parser.min.js' %}"></script>
|
||||
<div class="row">
|
||||
|
||||
<div class="col-sm-10 col-sm-offset-1 col-xs-12 col-md-10 col-md-offset-1 col-lg-8 col-lg-offset-2 offset-2 col-8">
|
||||
@@ -47,71 +45,4 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
function authen()
|
||||
{
|
||||
fetch('{% url 'fido2_begin_auth' %}', {
|
||||
method: 'GET',
|
||||
}).then(function(response) {
|
||||
if(response.ok) return response.arrayBuffer();
|
||||
throw new Error('No credential available to authenticate!');
|
||||
}).then(CBOR.decode).then(function(options) {
|
||||
console.log(options)
|
||||
return navigator.credentials.get(options);
|
||||
}).then(function(assertion) {
|
||||
res=CBOR.encode({
|
||||
"credentialId": new Uint8Array(assertion.rawId),
|
||||
"authenticatorData": new Uint8Array(assertion.response.authenticatorData),
|
||||
"clientDataJSON": new Uint8Array(assertion.response.clientDataJSON),
|
||||
"signature": new Uint8Array(assertion.response.signature)
|
||||
});
|
||||
|
||||
return fetch('{% url 'fido2_complete_auth' %}', {
|
||||
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/cbor'},
|
||||
body:res,
|
||||
|
||||
}).then(function (response) {if (response.ok) return res = response.json()}).then(function (res) {
|
||||
if (res.status=="OK")
|
||||
{
|
||||
$("#msgdiv").addClass("alert alert-success").removeClass("alert-danger")
|
||||
$("#msgdiv").html("Verified....please wait")
|
||||
{% if mode == "auth" %}
|
||||
window.location.href=res.redirect;
|
||||
{% elif mode == "recheck" %}
|
||||
mfa_success_function();
|
||||
{% endif %}
|
||||
}
|
||||
else {
|
||||
$("#msgdiv").addClass("alert alert-danger").removeClass("alert-success")
|
||||
$("#msgdiv").html("Verification Failed as " + res.message + ", <a href='javascript:void(0)' onclick='authen())'> try again</a> or <a href='javascript:void(0)' onclick='history.back()'> Go Back</a>")
|
||||
|
||||
{% if mode == "auth" %}
|
||||
|
||||
{% elif mode == "recheck" %}
|
||||
|
||||
mfa_failed_function();
|
||||
{% endif %}
|
||||
}
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
}
|
||||
$(document).ready(function () {
|
||||
if (location.protocol != 'https:') {
|
||||
$("#main_paragraph").addClass("alert alert-danger")
|
||||
$("#main_paragraph").html("FIDO2 must work under secure context")
|
||||
} else {
|
||||
ua=new UAParser().getResult()
|
||||
if (ua.browser.name == "Safari" || ua.browser.name == "Mobile Safari" || ua.os.name == "iOS" || ua.os.name == "iPadOS")
|
||||
$("#res").html("<button class='btn btn-success' onclick='authen()'>Authenticate...</button>")
|
||||
else
|
||||
authen()
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
</script>
|
||||
{% include 'FIDO2/Auth_JS.html' %}
|
||||
|
||||
@@ -60,11 +60,13 @@ def reset_cookie(request):
|
||||
response.delete_cookie("base_username")
|
||||
return response
|
||||
|
||||
def login(request):
|
||||
def login(request,username = None):
|
||||
from django.contrib import auth
|
||||
from django.conf import settings
|
||||
callable_func = __get_callable_function__(settings.MFA_LOGIN_CALLBACK)
|
||||
return callable_func(request,username=request.session["base_username"])
|
||||
if not username:
|
||||
username = request.session["base_username"]
|
||||
return callable_func(request,username=username)
|
||||
|
||||
|
||||
@login_required
|
||||
|
||||
Reference in New Issue
Block a user