def server(self) -> Fido2Server: """Return FIDO 2 server instance.""" rp = PublicKeyCredentialRpEntity(self.get_rp_id(), self.get_rp_name()) if AttestationVerifier is None: return Fido2Server(rp, attestation=self.attestation, attestation_types=self.attestation_types) elif self.verify_attestation is None: if self.attestation_types is not None: warnings.warn('You have defined `attestation_types` but not `verify_attestation`, this means that the ' '`attestation_types` setting is being iognored.', DeprecationWarning) return Fido2Server(rp, attestation=self.attestation) else: return Fido2Server(rp, attestation=self.attestation, verify_attestation=self.verify_attestation(self.attestation_types))
def fido2_api_register_begin(request): rp = PublicKeyCredentialRpEntity(get_domain(request), settings.FIDO2_RP_NAME) fido2 = Fido2Server(rp) all_devices = Fido2Device.objects.filter(user=request.user) registration_data, state = fido2.register_begin( { "id": request.user.email.encode(), "name": request.user.email, "displayName": request.user.email, "icon": "", }, # Pass existing fido2 credentials to prevent duplicate credentials=[ AttestedCredentialData(cbor2.loads(d.authenticator_data)) for d in all_devices ], user_verification="discouraged", authenticator_attachment="cross-platform", ) request.session[FIDO2_REGISTER_STATE] = state return HttpResponse(cbor2.dumps(registration_data), content_type="application/octet-stream")
def test_authenticate_complete_invalid_signature(self): rp = PublicKeyCredentialRpEntity("Example", "example.com") server = Fido2Server(rp) state = { "challenge": "GAZPACHO!", "user_verification": UserVerificationRequirement.PREFERRED, } client_data = CollectedClientData.create( CollectedClientData.TYPE.GET, "GAZPACHO!", "https://example.com", ) _AUTH_DATA = bytes.fromhex( "A379A6F6EEAFB9A55E378C118034E2751E682FAB9F2D30AB13D2125586CE1947010000001D" ) with self.assertRaisesRegex(ValueError, "Invalid signature."): server.authenticate_complete( state, [AttestedCredentialData(_ATT_CRED_DATA)], _CRED_ID, client_data, AuthenticatorData(_AUTH_DATA), b"INVALID", )
def test_register_begin_custom_challenge_too_short(self): rp = PublicKeyCredentialRpEntity("Example", "example.com") server = Fido2Server(rp) challenge = b"123456789012345" with self.assertRaises(ValueError): request, state = server.register_begin(USER, challenge=challenge)
def verify_token(self, token): data = cbor2.loads(base64.b64decode(token["token"])) credential_id = data["credentialId"] client_data = ClientData(data["clientDataJSON"]) auth_data = AuthenticatorData(data["authenticatorData"]) signature = data["signature"] state = token["state"] domain = token["domain"] credentials_query = Fido2Device.objects.filter(user=self.user) credentials = [ AttestedCredentialData(cbor2.loads(c.authenticator_data)) for c in credentials_query ] rp = PublicKeyCredentialRpEntity(domain, settings.FIDO2_RP_NAME) fido2 = Fido2Server(rp) try: fido2.authenticate_complete( state, credentials, credential_id, client_data, auth_data, signature, ) return True except ValueError: logger.exception("Error in FIDO2 final authentication") return False
def test_authenticate_complete_invalid_signature(self): rp = RelyingParty("example.com", "Example") server = Fido2Server(rp) state = { "challenge": "GAZPACHO!", "user_verification": USER_VERIFICATION.PREFERRED, } client_data_dict = { "challenge": "GAZPACHO!", "origin": "https://example.com", "type": WEBAUTHN_TYPE.GET_ASSERTION, } client_data = ClientData(json.dumps(client_data_dict).encode("utf-8")) _AUTH_DATA = a2b_hex( "A379A6F6EEAFB9A55E378C118034E2751E682FAB9F2D30AB13D2125586CE1947010000001D" ) with six.assertRaisesRegex(self, ValueError, "Invalid signature."): server.authenticate_complete( state, [AttestedCredentialData(_ATT_CRED_DATA)], _CRED_ID, client_data, AuthenticatorData(_AUTH_DATA), b"INVALID", )
def test_register_begin_custom_challenge_too_short(self): rp = RelyingParty("example.com", "Example") server = Fido2Server(rp) challenge = b"123456789012345" with self.assertRaises(ValueError): request, state = server.register_begin({}, challenge=challenge)
def test_register_begin_custom_challenge(self): rp = PublicKeyCredentialRpEntity("Example", "example.com") server = Fido2Server(rp) challenge = b"1234567890123456" request, state = server.register_begin(USER, challenge=challenge) self.assertEqual(request["publicKey"]["challenge"], challenge)
def test_register_begin_custom_challenge(self): rp = RelyingParty("example.com", "Example") server = Fido2Server(rp) challenge = b"1234567890123456" request, state = server.register_begin({}, challenge=challenge) self.assertEqual(request["publicKey"]["challenge"], challenge)
def test_register_begin_rp_no_icon(self): rp = RelyingParty('example.com', 'Example') server = Fido2Server(rp) request, state = server.register_begin({}) self.assertEqual(request['publicKey']['rp'], {'id': 'example.com', 'name': 'Example'})
def test_register_begin_rp_no_icon(self): rp = RelyingParty("example.com", "Example") server = Fido2Server(rp) request, state = server.register_begin({}) self.assertEqual( request["publicKey"]["rp"], {"id": "example.com", "name": "Example"} )
def main(): server = Fido2Server({ "id": "example.com", "name": "Example RP" }, attestation="direct") client = get_client() # add_credentials(server, client) authenticate_device(server, client)
def __init__(self, params): """ Create an instance of the class. :param params: Dictionary containing the following parameters db_path: Path to the user database rp_id: Relying Party identifier of the server. pre_share_eph_username: Indicates whether or not the server tries to establish an ephemeral user name for the next handshake db_encryption_key: The key the database is encrypted with. modes: List of allowed FIDO2 operation modes during authentication. force_fido2: Flag indicating whether or not to accept only FIDO2 authenticated users. """ # check arguments self._valid = False db_path = rp_id = encryption_key = None pre_share_eph_username = force_fido2 = False modes = FIDO2Mode.all if 'db_path' in params and isinstance(params['db_path'], str): db_path = params['db_path'] if 'rp_id' in params and isinstance(params['rp_id'], str): rp_id = params['rp_id'].lower() if not is_valid_hostname(rp_id): rp_id = None if 'db_encryption_key' in params and \ isinstance(params['db_encryption_key'], RSAKey): encryption_key = params['db_encryption_key'] if 'pre_share_eph_user_name' in params and \ isinstance(params['pre_share_eph_user_name'], bool): pre_share_eph_username = params['pre_share_eph_user_name'] if 'modes' in params and \ isinstance(params['modes'], list): modes = params['modes'] if 'force_fido2' in params and isinstance(params['force_fido2'], bool): force_fido2 = params['force_fido2'] # check if mandatory arguments are set if not db_path or not rp_id: return self.state = ServerState.init self.mode = None self.allowed_modes = modes self._db_connection = self._get_db_connection(db_path, encryption_key) relying_party = RelyingParty(rp_id) self._server = Fido2Server(relying_party) self.pre_share_eph_user_name = pre_share_eph_username self.force_fido2 = force_fido2 self._auth_state = None self._user_id = None self._eph_user_name_server_share = None self._allow_credentials = [] self._valid = bool(self._db_connection is not None)
def test_register_begin_rp(self): rp = PublicKeyCredentialRpEntity("Example", "example.com") server = Fido2Server(rp) request, state = server.register_begin(USER) self.assertEqual(request["publicKey"]["rp"], { "id": "example.com", "name": "Example" })
def setUp(self): self.u2f = U2fInterface() self.login_as(user=self.user) rp = PublicKeyCredentialRpEntity("richardmasentry.ngrok.io", "Sentry") self.test_registration_server = Fido2Server(rp, verify_origin=verifiy_origin) self.test_authentication_server = U2FFido2Server( app_id="http://richardmasentry.ngrok.io/auth/2fa/u2fappid.json", rp={"id": "richardmasentry.ngrok.io", "name": "Sentry"}, verify_u2f_origin=verifiy_origin, )
def test_register_begin_rp_icon(self): rp = RelyingParty('example.com', 'Example', 'http://example.com/icon.svg') server = Fido2Server(rp) request, state = server.register_begin({}) data = {'id': 'example.com', 'name': 'Example', 'icon': 'http://example.com/icon.svg'} self.assertEqual(request['publicKey']['rp'], data)
def _get_fido2server(credentials, fido2rp): # See if any of the credentials is a legacy U2F credential with an app-id # (assume all app-ids are the same - authenticating with a mix of different # app-ids isn't supported in current Webauthn) app_id = None for k, v in credentials.items(): if v['app_id']: app_id = v['app_id'] break if app_id: return U2FFido2Server(app_id, fido2rp) return Fido2Server(fido2rp)
def test_register_begin_rp_icon(self): rp = RelyingParty("example.com", "Example", "http://example.com/icon.svg") server = Fido2Server(rp) request, state = server.register_begin({}) data = { "id": "example.com", "name": "Example", "icon": "http://example.com/icon.svg", } self.assertEqual(request["publicKey"]["rp"], data)
def begin(request): rp_host = urlparse(request.build_absolute_uri()).hostname rp = RelyingParty(rp_host, 'Demo server') server = Fido2Server(rp) existing_credentials = AttestedCredentialData.objects.filter( user=request.user).all() auth_data, state = server.authenticate_begin(existing_credentials) request.session['state'] = { 'challenge': state['challenge'], 'user_verification': state['user_verification'].value, } return Response(auth_data, content_type="application/cbor")
def do_register_user(user, rp_id, resident_key=False): """ FIDO2 registration process :param user: The user to register :param rp_id: Relying Party identifier :param resident_key: Boolean indicating whether or not to store a resident key :return: Newly created credentials """ # begin registration relying_part = RelyingParty(rp_id) server = Fido2Server(relying_part) registration_data, state = server.register_begin(user) # make credential dev = next(CtapHidDevice.list_devices(), None) if not dev: print('No FIDO device found') sys.exit(1) client = Fido2Client(dev, 'https://' + rp_id) rp = {'id': rp_id, 'name': rp_id} challenge = websafe_encode(registration_data['publicKey']['challenge']) if resident_key: user['name'] = "." user_string = "(id: {0})".format(user['id'].hex()) else: user_string = "(name: {0}, display name: {1})".format( user['name'], user['displayName']) print("\nRegistration request for user: "******"From service: (Address: {0}, Name: {1})".format(rp['id'], rp['name'])) print('Touch your authenticator device now to consent to registration...\n') try: attestation_object, client_data = client.make_credential( rp, user, challenge, rk=resident_key) except Exception as e: print("Registration failed") raise e # complete registration registration_data = server.register_complete(state, client_data, attestation_object) credential = registration_data.credential_data print("Registration complete") return credential
class TestFido2GeneralAuthenticationBackend(TestCase): """Test `Fido2GeneralAuthenticationBackend` class.""" backend = Fido2GeneralAuthenticationBackend() server = Fido2Server(PublicKeyCredentialRpEntity(HOSTNAME, HOSTNAME)) state = { 'challenge': AUTHENTICATION_CHALLENGE, 'user_verification': UserVerificationRequirement.PREFERRED } fido2_response = { 'client_data': ClientData(base64.b64decode(AUTHENTICATION_CLIENT_DATA)), 'credential_id': base64.b64decode(CREDENTIAL_ID), 'authenticator_data': AuthenticatorData(base64.b64decode(AUTHENTICATOR_DATA)), 'signature': base64.b64decode(SIGNATURE) } def setUp(self): self.user = User.objects.create_user(USERNAME, password=PASSWORD) self.device = Authenticator.objects.create( user=self.user, credential_id_data=CREDENTIAL_ID, attestation_data=ATTESTATION_OBJECT) def test_authenticate(self): authenticated_user = self.backend.authenticate(sentinel.request, USERNAME, PASSWORD, self.server, self.state, self.fido2_response) self.assertEqual(authenticated_user, self.user) self.assertQuerysetEqual(Authenticator.objects.values_list( 'user', 'counter'), [(self.user.pk, 152)], transform=tuple) def test_authenticate_wrong_password(self): authenticated_user = self.backend.authenticate(sentinel.request, USERNAME, 'wrong_password', self.server, self.state, self.fido2_response) self.assertEqual(authenticated_user, None) self.assertQuerysetEqual(Authenticator.objects.values_list( 'user', 'counter'), [(self.user.pk, 0)], transform=tuple)
def test_authenticate_complete_invalid_signature(self): rp = RelyingParty('example.com', 'Example') server = Fido2Server(rp) state = {'challenge': 'GAZPACHO!', 'user_verification': USER_VERIFICATION.PREFERRED} client_data_dict = {'challenge': 'GAZPACHO!', 'origin': 'https://example.com', 'type': WEBAUTHN_TYPE.GET_ASSERTION} client_data = ClientData(json.dumps(client_data_dict).encode('utf-8')) _AUTH_DATA = a2b_hex('A379A6F6EEAFB9A55E378C118034E2751E682FAB9F2D30AB13D2125586CE1947010000001D') # noqa with six.assertRaisesRegex(self, ValueError, 'Invalid signature.'): server.authenticate_complete( state, [AttestedCredentialData(_ATT_CRED_DATA)], _CRED_ID, client_data, AuthenticatorData(_AUTH_DATA), b'INVALID')
def test_authenticate(): """test authentication""" device = SoftWebauthnDevice() device.cred_init('example.org', b'randomhandle') registered_credential = device.cred_as_attested() server = Fido2Server( PublicKeyCredentialRpEntity('example.org', 'test server')) options, state = server.authenticate_begin([registered_credential]) assertion = device.get(options, 'https://example.org') server.authenticate_complete( state, [registered_credential], assertion['rawId'], ClientData(assertion['response']['clientDataJSON']), AuthenticatorData(assertion['response']['authenticatorData']), assertion['response']['signature'])
def fido2_api_login_begin(request): user = request.user credentials_query = Fido2Device.objects.filter(user=user) credentials = [ AttestedCredentialData(cbor2.loads(c.authenticator_data)) for c in credentials_query ] rp = PublicKeyCredentialRpEntity(get_domain(request), settings.FIDO2_RP_NAME) fido2 = Fido2Server(rp) auth_data, state = fido2.authenticate_begin( credentials, user_verification="discouraged") request.session["fido2_state"] = state request.session["fido2_domain"] = get_domain(request) return HttpResponse(cbor2.dumps(auth_data), content_type="application/cbor")
def authenticate(request): rp_host = urlparse(request.build_absolute_uri()).hostname rp = RelyingParty(rp_host, 'Demo server') server = Fido2Server(rp) data = request.data[0] credential_id = data['credentialId'] credentials = AttestedCredentialData.objects.filter( user=request.user, ).all() client_data = ClientData(data['clientDataJSON']) auth_data = AuthenticatorData(data['authenticatorData']) signature = data['signature'] state = request.session['state'] cred = server.authenticate_complete(state, credentials, credential_id, client_data, auth_data, signature) return cred
def test_register(): """test registering generated credential""" device = SoftWebauthnDevice() server = Fido2Server( PublicKeyCredentialRpEntity('example.org', 'test server')) exclude_credentials = [] options, state = server.register_begin( { 'id': b'randomhandle', 'name': 'username', 'displayName': 'User Name' }, exclude_credentials) attestation = device.create(options, 'https://example.org') auth_data = server.register_complete( state, ClientData(attestation['response']['clientDataJSON']), AttestationObject(attestation['response']['attestationObject'])) assert isinstance(auth_data, AuthenticatorData)
def fido2_api_register_finish(request): data = cbor2.loads(request.body) client_data = ClientData(data["clientDataJSON"]) att_obj = AttestationObject(data["attestationObject"]) rp = PublicKeyCredentialRpEntity(get_domain(request), settings.FIDO2_RP_NAME) fido2 = Fido2Server(rp) auth_data = fido2.register_complete(request.session[FIDO2_REGISTER_STATE], client_data, att_obj) device = Fido2Device( authenticator_data=cbor2.dumps(auth_data.credential_data)) device.user = request.user device.name = data["name"] or "Fido key" device.confirmed = True device.save() return HttpResponse(cbor2.dumps({"status": "OK"}), content_type="application/cbor")
def complete(request): rp_host = urlparse(request.build_absolute_uri()).hostname rp = RelyingParty(rp_host, 'Demo server') server = Fido2Server(rp) data = request.data[0] client_data = ClientData(data['clientDataJSON']) att_obj = AttestationObject(data['attestationObject']) state = request.session['state'] auth_data = server.register_complete(state, client_data, att_obj) cred = AttestedCredentialData.objects.create( aaguid=auth_data.credential_data.aaguid, credential_id=auth_data.credential_data.credential_id, public_key=cbor.dump_dict(auth_data.credential_data.public_key), user=request.user, ) verify(request, cred, backend='apps.fido.auth.backends.FIDO2Backend') return Response({'status': 'OK'})
# Set up a FIDO 2 client using the origin https://example.com client = Fido2Client(dev, "https://example.com") # Prefer UV if supported if client.info.options.get("uv"): uv = "preferred" print("Authenticator supports User Verification") elif client.info.options.get("clientPin"): # Prompt for PIN if needed pin = getpass("Please enter PIN: ") else: print("PIN not set, won't use") server = Fido2Server({ "id": "example.com", "name": "Example RP" }, attestation="direct") user = {"id": b"user_id", "name": "A. User"} # Prepare parameters for makeCredential create_options, state = server.register_begin( user, resident_key=True, user_verification=uv, authenticator_attachment="cross-platform", ) # Create a credential if use_prompt:
def getServer(): rp = RelyingParty(settings.FIDO_SERVER_ID, settings.FIDO_SERVER_NAME) return Fido2Server(rp)