diff --git a/webauthn_demo.py b/webauthn_demo.py index e20f3e02745ea7b7904f7e80ffe139e9044ee8d7..0672451b911cede1de3ed0429f41bd755931509d 100755 --- a/webauthn_demo.py +++ b/webauthn_demo.py @@ -14,6 +14,7 @@ from webauthn import ( from webauthn.helpers.structs import AuthenticationCredential, RegistrationCredential HOSTNAME="diseasedmind.com" +REQUIRE_USER_PRESENCE=False # False to support Yubikeys, no PIN required # Our database is just this global dictionary so everything is forgotton # when the server restarts. @@ -118,11 +119,12 @@ def POST_register(response): else: user_name, user_id, opts = database["reg_requests"][challenge] try: - verified_reg = verify_reg(opts, dumps(response), user_name) - if not verified_reg.user_verified: - raise Exception("Registration signature not valid") + verified_reg = verify_reg(opts, dumps(response), user_name, + REQUIRE_USER_PRESENCE) + if REQUIRE_USER_PRESENCE and not verified_reg.user_verified: + raise Exception("User presence required but not attested") except Exception as e: - print("Exception: %s" % e) + print("Exception (%s): %s" % (type(e), e)) status = "400 Bad Request" content = b"Error %s" % str(e).encode() @@ -155,7 +157,7 @@ def generate_registration(hostname, user_id, user_name, provider_id=None): return generate_registration_options(rp_id=hostname, rp_name=provider_id, user_id=user_id, user_name=user_name) -def verify_reg(opts, response, user_name): +def verify_reg(opts, response, user_name, user_verification_required): """ Verifies the response from the browser. If the user was verified, their credential_id, credential_type, and credential_public_key should all be @@ -178,6 +180,10 @@ def verify_reg(opts, response, user_name): :param user_name: A user-friendly, readable name for the Relying Party. This is shown to the user on their hardware device. :type user_name: string + :param user_verification_required: Flag controlling whether the user must + be verified (e.g. enter a PIN). This must + be False to support Yubikey. + :type user_verification_required: boolean :returns: Information about a verified attestation of which an RP can make use. :rtype: `py:VerifiedRegistration` """ @@ -192,9 +198,12 @@ def verify_reg(opts, response, user_name): expected_challenge=base64url_to_bytes(urlsafe_b64encode(expected_challenge).decode()), expected_origin="https://"+opts.rp.id, expected_rp_id=opts.rp.id, - require_user_verification=True + require_user_verification=user_verification_required ) - if verified_reg.user_verified: + + # If user verification is not required, or it is and we have it, register + # the user's device + if not user_verification_required or verified_reg.user_verified: register_device(verified_reg, opts, user_name) return verified_reg @@ -280,7 +289,8 @@ def POST_auth(response): else: user_name, opts = database["auth_requests"][challenge] try: - verified_auth = verify_auth_response(opts, dumps(response), user_name) + verified_auth = verify_auth_response(opts, dumps(response), + user_name, REQUIRE_USER_PRESENCE) except Exception as e: print("Exception: %s" % e) status = "400 Bad Request" @@ -305,7 +315,7 @@ def generate_authentication(credentials, hostname): return generate_authentication_options(rp_id=hostname, allow_credentials=credentials) -def verify_auth_response(opts, response, user_name): +def verify_auth_response(opts, response, user_name, user_verification_required): """ Verifies the authentication response is valid. Raises an exception if the key used to sign the challenge was not in the list of allowed keys for this @@ -323,6 +333,10 @@ def verify_auth_response(opts, response, user_name): :param user_name: A user-friendly, readable name for the Relying Party. This is shown to the user on their hardware device. :type user_name: string + :param user_verification_required: Flag controlling whether the user must + be verified (e.g. enter a PIN). This must + be False to support Yubikey. + :type user_verification_required: boolean :returns: Verified authentication object :rtype: `py:VerifiedAuthentication` """ @@ -356,7 +370,7 @@ def verify_auth_response(opts, response, user_name): expected_origin="https://"+opts.rp_id, credential_public_key=pubkey, credential_current_sign_count=0, - require_user_verification=True, + require_user_verification=user_verification_required, )