def post(self, request, *args, **kwargs): r""" Verify a registration challenge and register a 2-FA key. Called asynchronously by JavaScript. :param request: The current request :type request: ~django.http.HttpResponse :param \*args: The supplied arguments :type \*args: list :param \**kwargs: The supplied keyword arguments :type \**kwargs: dict :return: The JSON response :rtype: ~django.http.JsonResponse """ json_data = json.loads(request.body) webauthn_registration_response = verify_registration_response( credential=RegistrationCredential.parse_raw(request.body), expected_rp_id=settings.HOSTNAME, expected_origin=settings.BASE_URL, expected_challenge=base64url_to_bytes( request.session["mfa_registration_challenge"] ), ) existing_key = request.user.mfa_keys.filter(name=json_data["name"]) if existing_key.exists(): return JsonResponse( {"success": False, "error": _("This key name has already been used")} ) existing_key = request.user.mfa_keys.filter( key_id=webauthn_registration_response.credential_id ) if existing_key.exists(): return JsonResponse( {"success": False, "error": _("You already registered this key")} ) new_key = UserMfaKey( user=request.user, name=json_data["name"], key_id=webauthn_registration_response.credential_id, public_key=webauthn_registration_response.credential_public_key, sign_count=webauthn_registration_response.sign_count, ) new_key.save() messages.success( request, _( 'The 2-factor authentication key "{}" was successfully registered.' ).format(new_key.name), ) # Determine success url based on current region kwargs = {"region_slug": request.region.slug} if request.region else {} success_url = reverse("user_settings", kwargs=kwargs) return JsonResponse({"success": True, "successUrl": success_url})
def post(self, request): """ :param request: The current request :type request: ~django.http.HttpResponse :return: The mfa challenge as JSON :rtype: ~django.http.JsonResponse """ if "mfa_user_id" not in request.session: return JsonResponse( {"success": False, "error": _("You need to log in first")}, status=403 ) user = get_user_model().objects.get(id=request.session["mfa_user_id"]) challenge = request.session["challenge"] assertion_response = json.loads(request.body) credential_id = assertion_response["id"] key = user.mfa_keys.get(key_id=base64url_to_bytes(credential_id)) try: authentication_verification = verify_authentication_response( credential=AuthenticationCredential.parse_raw(request.body), expected_challenge=base64url_to_bytes(challenge), expected_rp_id=settings.HOSTNAME, expected_origin=settings.BASE_URL, credential_public_key=key.public_key, credential_current_sign_count=key.sign_count, ) except InvalidAuthenticationResponse as e: logger.exception(e) return JsonResponse( {"success": False, "error": "Authentication rejected"}, status=403 ) # Update counter. key.sign_count = authentication_verification.new_sign_count key.last_usage = datetime.datetime.now() key.save() auth_login(request, user, backend="django.contrib.auth.backends.ModelBackend") return JsonResponse({"success": True})
) print("\n[Registration Options - Complex]") print(options_to_json(complex_registration_options)) # Registration Response Verification registration_verification = verify_registration_response( credential=RegistrationCredential.parse_raw("""{ "id": "ZoIKP1JQvKdrYj1bTUPJ2eTUsbLeFkv-X5xJQNr4k6s", "rawId": "ZoIKP1JQvKdrYj1bTUPJ2eTUsbLeFkv-X5xJQNr4k6s", "response": { "attestationObject": "o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YVkBZ0mWDeWIDoxodDQXD2R2YFuP5K65ooYyx5lc87qDHZdjRQAAAAAAAAAAAAAAAAAAAAAAAAAAACBmggo_UlC8p2tiPVtNQ8nZ5NSxst4WS_5fnElA2viTq6QBAwM5AQAgWQEA31dtHqc70D_h7XHQ6V_nBs3Tscu91kBL7FOw56_VFiaKYRH6Z4KLr4J0S12hFJ_3fBxpKfxyMfK66ZMeAVbOl_wemY4S5Xs4yHSWy21Xm_dgWhLJjZ9R1tjfV49kDPHB_ssdvP7wo3_NmoUPYMgK-edgZ_ehttp_I6hUUCnVaTvn_m76b2j9yEPReSwl-wlGsabYG6INUhTuhSOqG-UpVVQdNJVV7GmIPHCA2cQpJBDZBohT4MBGme_feUgm4sgqVCWzKk6CzIKIz5AIVnspLbu05SulAVnSTB3NxTwCLNJR_9v9oSkvphiNbmQBVQH1tV_psyi9HM1Jtj9VJVKMeyFDAQAB", "clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoiQ2VUV29nbWcwY2NodWlZdUZydjhEWFhkTVpTSVFSVlpKT2dhX3hheVZWRWNCajBDdzN5NzN5aEQ0RmtHU2UtUnJQNmhQSkpBSW0zTFZpZW40aFhFTGciLCJvcmlnaW4iOiJodHRwOi8vbG9jYWxob3N0OjUwMDAiLCJjcm9zc09yaWdpbiI6ZmFsc2V9" }, "type": "public-key", "clientExtensionResults": {}, "transports": ["internal"] }"""), expected_challenge=base64url_to_bytes( "CeTWogmg0cchuiYuFrv8DXXdMZSIQRVZJOga_xayVVEcBj0Cw3y73yhD4FkGSe-RrP6hPJJAIm3LVien4hXELg" ), expected_origin="http://localhost:5000", expected_rp_id="localhost", require_user_verification=True, ) print("\n[Registration Verification - None]") print(registration_verification.json(indent=2)) assert registration_verification.credential_id == base64url_to_bytes( "ZoIKP1JQvKdrYj1bTUPJ2eTUsbLeFkv-X5xJQNr4k6s")
def base64url_to_memoryview(data: str) -> memoryview: data_bytes = base64url_to_bytes(data) return memoryview(data_bytes)
def __new__(cls, data: str): data_bytes = base64url_to_bytes(data) self = bytes.__new__(cls, memoryview(data_bytes).tobytes()) return self
print(options_to_json(complex_authentication_options)) # Authentication Response Verification authentication_verification = verify_authentication_response( credential=AuthenticationCredential.parse_raw("""{ "id": "ZoIKP1JQvKdrYj1bTUPJ2eTUsbLeFkv-X5xJQNr4k6s", "rawId": "ZoIKP1JQvKdrYj1bTUPJ2eTUsbLeFkv-X5xJQNr4k6s", "response": { "authenticatorData": "SZYN5YgOjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2MFAAAAAQ", "clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uZ2V0IiwiY2hhbGxlbmdlIjoiaVBtQWkxUHAxWEw2b0FncTNQV1p0WlBuWmExekZVRG9HYmFRMF9LdlZHMWxGMnMzUnRfM280dVN6Y2N5MHRtY1RJcFRUVDRCVTFULUk0bWFhdm5kalEiLCJvcmlnaW4iOiJodHRwOi8vbG9jYWxob3N0OjUwMDAiLCJjcm9zc09yaWdpbiI6ZmFsc2V9", "signature": "iOHKX3erU5_OYP_r_9HLZ-CexCE4bQRrxM8WmuoKTDdhAnZSeTP0sjECjvjfeS8MJzN1ArmvV0H0C3yy_FdRFfcpUPZzdZ7bBcmPh1XPdxRwY747OrIzcTLTFQUPdn1U-izCZtP_78VGw9pCpdMsv4CUzZdJbEcRtQuRS03qUjqDaovoJhOqEBmxJn9Wu8tBi_Qx7A33RbYjlfyLm_EDqimzDZhyietyop6XUcpKarKqVH0M6mMrM5zTjp8xf3W7odFCadXEJg-ERZqFM0-9Uup6kJNLbr6C5J4NDYmSm3HCSA6lp2iEiMPKU8Ii7QZ61kybXLxsX4w4Dm3fOLjmDw", "userHandle": "T1RWa1l6VXdPRFV0WW1NNVlTMDBOVEkxTFRnd056Z3RabVZpWVdZNFpEVm1ZMk5p" }, "type": "public-key", "clientExtensionResults": {} }"""), expected_challenge=base64url_to_bytes( "iPmAi1Pp1XL6oAgq3PWZtZPnZa1zFUDoGbaQ0_KvVG1lF2s3Rt_3o4uSzccy0tmcTIpTTT4BU1T-I4maavndjQ" ), expected_rp_id="localhost", expected_origin="http://localhost:5000", credential_public_key=base64url_to_bytes( "pAEDAzkBACBZAQDfV20epzvQP-HtcdDpX-cGzdOxy73WQEvsU7Dnr9UWJophEfpngouvgnRLXaEUn_d8HGkp_HIx8rrpkx4BVs6X_B6ZjhLlezjIdJbLbVeb92BaEsmNn1HW2N9Xj2QM8cH-yx28_vCjf82ahQ9gyAr552Bn96G22n8jqFRQKdVpO-f-bvpvaP3IQ9F5LCX7CUaxptgbog1SFO6FI6ob5SlVVB00lVXsaYg8cIDZxCkkENkGiFPgwEaZ7995SCbiyCpUJbMqToLMgojPkAhWeyktu7TlK6UBWdJMHc3FPAIs0lH_2_2hKS-mGI1uZAFVAfW1X-mzKL0czUm2P1UlUox7IUMBAAE" ), credential_current_sign_count=0, require_user_verification=True, ) print("\n[Authentication Verification]") print(authentication_verification.json(indent=2)) assert authentication_verification.new_sign_count == 1