def test_login_webauthn(live_server, selenium, webauthn_credential_factory): # pylint: disable=unused-argument """test login by webauthn""" device = SoftWebauthnDevice() device.cred_init(webauthn.rp.id, b'randomhandle') wncred = webauthn_credential_factory.create(initialized_device=device) # factory post_generate does not call commit to propagate self.attr changes, that messes the actual db state when # accessing from different process such as real browser db.session.commit() selenium.get(url_for('auth.login_route', _external=True)) selenium.find_element_by_xpath( '//form//input[@name="username"]').send_keys(wncred.user.username) selenium.find_element_by_xpath('//form//input[@type="submit"]').click() # some javascript code must be emulated webdriver_waituntil(selenium, js_variable_ready('window.pkcro_raw')) pkcro = cbor.decode( b64decode( selenium.execute_script('return window.pkcro_raw;').encode( 'utf-8'))) assertion = device.get(pkcro, 'https://%s' % webauthn.rp.id) selenium.execute_script( 'authenticate_assertion(CBOR.decode(Sner.base64_to_array_buffer("%s")));' % b64encode(cbor.encode(assertion)).decode('utf-8')) # and back to standard test codeflow webdriver_waituntil( selenium, EC.presence_of_element_located((By.XPATH, '//a[text()="Logout"]')))
def test_login_webauthn(client, webauthn_credential_factory): """test login by webauthn""" device = SoftWebauthnDevice() device.cred_init(webauthn.rp.id, b'randomhandle') wncred = webauthn_credential_factory.create(initialized_device=device) form = client.get(url_for('auth.login_route')).form form['username'] = wncred.user.username response = form.submit() assert response.status_code == HTTPStatus.FOUND response = response.follow() # some javascript code muset be emulated pkcro = cbor.decode( b64decode( client.post(url_for('auth.login_webauthn_pkcro_route'), { 'csrf_token': get_csrf_token(client) }).body)) assertion = device.get(pkcro, f'https://{webauthn.rp.id}') assertion_data = { 'credentialRawId': assertion['rawId'], 'authenticatorData': assertion['response']['authenticatorData'], 'clientDataJSON': assertion['response']['clientDataJSON'], 'signature': assertion['response']['signature'], 'userHandle': assertion['response']['userHandle'] } form = response.form form['assertion'] = b64encode(cbor.encode(assertion_data)) response = form.submit() # and back to standard test codeflow assert response.status_code == HTTPStatus.FOUND response = client.get(url_for('index_route')) assert response.lxml.xpath('//a[text()="Logout"]')
def test_webauthn_register_new_key(test_client, init_database): sign_in_response = sign_in(test_client, "dave", "wselfknskjdksdaiujlj") device = SoftWebauthnDevice() begin_register_response = test_client.post("/webauthn/register/begin", data=json.dumps( {"resident": False})) pkcco = cbor.decode(begin_register_response.data) attestation = device.create(pkcco, f"https://{TestConfig.RP_ID}") attestation_data = cbor.encode({ "clientDataJSON": attestation["response"]["clientDataJSON"], "attestationObject": attestation["response"]["attestationObject"], }) raw_response = test_client.post( "/webauthn/register/complete", input_stream=BytesIO(attestation_data), content_type="application/cbor", ) registration_response = cbor.decode(raw_response.data) assert registration_response == {"status": "OK"} user = User.query.filter_by(username="******").first() webauthn = Webauthn.query.filter_by(user_id=user.did).first() assert webauthn assert webauthn.number == 1 assert webauthn.is_enabled is False
def test_as_attested_cred(): """test straight credential generation and access""" device = SoftWebauthnDevice() device.cred_init('rpid', b'randomhandle') assert isinstance(device.cred_as_attested(), AttestedCredentialData)
def test_webauthn_register_route(cl_user): """register new credential for user""" device = SoftWebauthnDevice() response = cl_user.get(url_for('app.webauthn_register_route')) # some javascript code must be emulated pkcco = cbor.decode( b64decode( cl_user.post(url_for('app.webauthn_pkcco_route'), { 'csrf_token': get_csrf_token(cl_user) }).body)) attestation = device.create(pkcco, 'https://%s' % webauthn.rp.ident) attestation_data = { 'clientDataJSON': attestation['response']['clientDataJSON'], 'attestationObject': attestation['response']['attestationObject'] } form = response.form form['attestation'] = b64encode(cbor.encode(attestation_data)) # and back to standard test codeflow form['name'] = 'pytest token' response = form.submit() assert response.status_code == HTTPStatus.FOUND user = User.query.filter(User.username == 'pytest_user').one() assert user.webauthn_credentials
def test_profile_webauthn_register_route(live_server, sl_user): # pylint: disable=unused-argument """register new credential for user""" device = SoftWebauthnDevice() sl_user.get(url_for('auth.profile_webauthn_register_route', _external=True)) # some javascript code must be emulated webdriver_waituntil(sl_user, js_variable_ready('window.pkcco_raw')) pkcco = cbor.decode( b64decode( sl_user.execute_script('return window.pkcco_raw;').encode( 'utf-8'))) attestation = device.create(pkcco, 'https://%s' % webauthn.rp.id) sl_user.execute_script( 'pack_attestation(CBOR.decode(Sner.base64_to_array_buffer("%s")));' % b64encode(cbor.encode(attestation)).decode('utf-8')) # and back to standard test codeflow sl_user.find_element_by_xpath( '//form[@id="webauthn_register_form"]//input[@name="name"]').send_keys( 'pytest token') sl_user.find_element_by_xpath( '//form[@id="webauthn_register_form"]//input[@type="submit"]').click() user = User.query.filter(User.username == 'pytest_user').one() assert user.webauthn_credentials
def test_login_webauthn(live_server, selenium, test_user): # pylint: disable=unused-argument """test login by webauthn""" device = SoftWebauthnDevice() device.cred_init(webauthn.rp.id, b'randomhandle') persist_and_detach( WebauthnCredential(user=test_user, user_handle=device.user_handle, credential_data=cbor.encode( device.cred_as_attested().__dict__))) selenium.get(url_for('auth.login_route', _external=True)) selenium.find_element_by_xpath( '//form//input[@name="username"]').send_keys(test_user.username) selenium.find_element_by_xpath('//form//input[@type="submit"]').click() # some javascript code must be emulated webdriver_waituntil(selenium, js_variable_ready('window.pkcro_raw')) pkcro = cbor.decode( b64decode( selenium.execute_script('return window.pkcro_raw;').encode( 'utf-8'))) assertion = device.get(pkcro, 'https://%s' % webauthn.rp.id) selenium.execute_script( 'authenticate_assertion(CBOR.decode(Sner.base64_to_array_buffer("%s")));' % b64encode(cbor.encode(assertion)).decode('utf-8')) # and back to standard test codeflow webdriver_waituntil( selenium, EC.presence_of_element_located((By.XPATH, '//a[text()="Logout"]')))
def test_create_not_supported_attestation(): """test for internal class check""" device = SoftWebauthnDevice() pkcco = copy.deepcopy(PKCCO) pkcco['publicKey']['attestation'] = 'direct' with pytest.raises(ValueError): device.create(pkcco, 'https://example.org')
def test_create_not_supported_type(): """test for internal class check""" device = SoftWebauthnDevice() pkcco = copy.deepcopy(PKCCO) pkcco['publicKey']['pubKeyCredParams'][0]['alg'] = -8 with pytest.raises(ValueError): device.create(pkcco, 'https://example.org')
def test_create(): """test create""" device = SoftWebauthnDevice() attestation = device.create(PKCCO, 'https://example.org') assert attestation assert device.private_key assert device.rp_id == 'example.org'
def test_get_not_matching_rpid(): """test get not mathcing rpid""" device = SoftWebauthnDevice() device.cred_init('rpid', b'randomhandle') pkcro = copy.deepcopy(PKCRO) pkcro['publicKey']['rpId'] = 'another_rpid' with pytest.raises(ValueError): device.get(pkcro, 'https://example.org')
def initialized_device(self, create, extracted, **kwargs): # pylint: disable=unused-argument """DI or self initialize device""" if extracted: device = extracted else: device = SoftWebauthnDevice() device.cred_init(webauthn.rp.id, b'randomhandle') self.user_handle = device.user_handle self.credential_data = cbor.encode(device.cred_as_attested().__dict__)
def create_test_wncred(a_test_user): """test webauthn credential""" device = SoftWebauthnDevice() device.cred_init(webauthn.rp.id, b'randomhandle') return WebauthnCredential(user_id=a_test_user.id, user=a_test_user, user_handle=device.user_handle, credential_data=cbor.encode( device.cred_as_attested().__dict__), name='testcredential')
def test_wncred(test_user): # pylint: disable=redefined-outer-name """persistent test registered webauthn credential""" device = SoftWebauthnDevice() device.cred_init(webauthn.rp.ident, b'randomhandle') wncred = WebauthnCredential(user_id=test_user.id, user=test_user, user_handle=device.user_handle, credential_data=cbor.encode( device.cred_as_attested().__dict__), name='testcredential') yield persist_and_detach(wncred)
def test_get(): """test get""" device = SoftWebauthnDevice() device.cred_init(PKCRO['publicKey']['rpId'], b'randomhandle') assertion = device.get(PKCRO, 'https://example.org') assert assertion device.private_key.public_key().verify( assertion['response']['signature'], assertion['response']['authenticatorData'] + sha256(assertion['response']['clientDataJSON']), ec.ECDSA(hashes.SHA256()))
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 test_webauthn_authenticate(test_client, init_database): sign_in_response = sign_in(test_client, "jennie", "9df1c362e4df3e51edd1acde9") device = SoftWebauthnDevice() user = User.query.filter_by(username="******").first() webauthn = Webauthn.query.filter_by(user_id=user.did).first() user_handle = webauthn.user_identifier device.cred_init(TestConfig.RP_ID, user_handle) device.private_key = KeyList.priv_one user5_first_security_key_public_key = ES256.from_cryptography_key( device.private_key.public_key()) key = (Key.query.filter_by(user_id=user.did).filter_by( public_key=cbor.encode(user5_first_security_key_public_key)).first()) device.credential_id = key.credential_id pkcro = cbor.decode(test_client.post("/webauthn/authenticate/begin").data) assertion = device.get(pkcro, f"https://{TestConfig.RP_ID}") assertion_data = cbor.encode({ "credentialId": assertion["rawId"], "clientDataJSON": assertion["response"]["clientDataJSON"], "authenticatorData": assertion["response"]["authenticatorData"], "signature": assertion["response"]["signature"], "userHandle": assertion["response"]["userHandle"], }) raw_response = test_client.post( "/webauthn/authenticate/complete", input_stream=BytesIO(assertion_data), content_type="application/cbor", ) authentication_response = cbor.decode(raw_response.data) assert authentication_response == {"status": "OK"} settings_response = test_client.get("/settings") assert settings_response.status_code == 200
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 test_example_server_registration(client): # pylint: disable=redefined-outer-name """Registration example""" # User holds an authenticator. # # NOTE: SoftWebauthnDevice emulates mixed client and authenticator behavior # and can be used for testing Webauthn/FIDO2 enabled applications during # continuous integration test-cases. device = SoftWebauthnDevice() # The browser starts registration process by requesting # publicKeyCredentialCreationOptions (pkcco) from the server/web # application/relying party (RP). # # NOTE: The transfer encoding and format is not in scope of Webauthn spec, # Yubico fido2 example server uses application/cbor. pkcco = cbor.decode(client.post('/api/register/begin').data) print('publicKeyCredentialCreationOptions: ', pkcco) # publicKeyCredentialCreationOptions object is passed from browser/client # to the authenticator. Authenticator will generate new credential and # return credential object (attestation). attestation = device.create(pkcco, 'https://localhost') print('new credential attestation: ', attestation) # Browser conveys the attestation data to the RP for registration. attestation_data = cbor.encode({ 'clientDataJSON': attestation['response']['clientDataJSON'], 'attestationObject': attestation['response']['attestationObject'] }) raw_response = client.post( '/api/register/complete', input_stream=BytesIO(attestation_data), content_type='application/cbor' ) registration_response = cbor.decode(raw_response.data) print('registration response:', registration_response) # After verification, RP stores attested credential data associated with # user account for later authentication. assert registration_response == {'status': 'OK'}
def test_example_server_authentication(client): # pylint: disable=redefined-outer-name """Authentication example""" # Already registered credential is typicaly part of fixture test-case code. # # NOTE: the storage of the credential data on the RP side is not in scope # of Webauthn spec. Yubico example server uses module scoped variable. device = SoftWebauthnDevice() device.cred_init('localhost', b'randomhandle') tests.example_server.credentials = [device.cred_as_attested()] # Browser starts authentication by requesting # publicKeyCredentialRequestOptions (pkcro) from the RP. pkcro = cbor.decode(client.post('/api/authenticate/begin').data) print('publicKeyCredentialRequestOptions: ', pkcro) # publicKeyCredentialRequestOptions object is passed to the authenticator, # which performs requester user verification and return credential object # (assertion). assertion = device.get(pkcro, 'https://localhost') print('credential assertion: ', assertion) # Browser conveys assertion data to the RP for authentication. assertion_data = cbor.encode({ 'credentialId': assertion['rawId'], 'clientDataJSON': assertion['response']['clientDataJSON'], 'authenticatorData': assertion['response']['authenticatorData'], 'signature': assertion['response']['signature'], 'userHandle': assertion['response']['userHandle'] }) raw_response = client.post( '/api/authenticate/complete', input_stream=BytesIO(assertion_data), content_type='application/cbor' ) authentication_response = cbor.decode(raw_response.data) print('authentication response:', authentication_response) # RP will verify assertion and on success proceeds with user logon. assert authentication_response == {'status': 'OK'}
def test_webauthn_login_route(client, test_user): """test login by webauthn""" device = SoftWebauthnDevice() device.cred_init(webauthn.rp.ident, b'randomhandle') persist_and_detach( WebauthnCredential(user=test_user, user_handle=device.user_handle, credential_data=cbor.encode( device.cred_as_attested().__dict__))) form = client.get(url_for('app.login_route')).form form['username'] = test_user.username response = form.submit() assert response.status_code == HTTPStatus.FOUND response = response.follow() # some javascript code muset be emulated pkcro = cbor.decode( b64decode( client.post(url_for('app.webauthn_pkcro_route'), { 'csrf_token': get_csrf_token(client) }).body)) assertion = device.get(pkcro, 'https://%s' % webauthn.rp.ident) assertion_data = { 'credentialRawId': assertion['rawId'], 'authenticatorData': assertion['response']['authenticatorData'], 'clientDataJSON': assertion['response']['clientDataJSON'], 'signature': assertion['response']['signature'], 'userHandle': assertion['response']['userHandle'] } form = response.form form['assertion'] = b64encode(cbor.encode(assertion_data)) response = form.submit() # and back to standard test codeflow assert response.status_code == HTTPStatus.FOUND response = client.get(url_for('app.index_route')) assert response.lxml.xpath('//a[text()="Logout"]')
def init_database(): db.create_all() user1 = User(username="******", email="*****@*****.**") user1.set_password("[email protected]<") user2 = User(username="******", email="*****@*****.**") user2.set_password("m7ZTbjQdwuUFU/Zy6la+k6uUtniBExIgEhmBPduKexM=") user3 = User(username="******", email="*****@*****.**") user3.set_password("wselfknskjdksdaiujlj") db.session.add(user1) db.session.add(user2) db.session.add(user3) user4 = User(username="******", email="*****@*****.**") user4.set_password("c1c149afbf4c8996fb92427ae41e4649b934ca") user5 = User(username="******", email="*****@*****.**") user5.set_password("9df1c362e4df3e51edd1acde9") user6 = User(username="******", email="*****@*****.**") user6.set_password("ukehjwqbjhwqkbejw") user7 = User(username="******", email="*****@*****.**") user7.set_password("qghjoiwjiklwek") user8 = User(username="******", email="*****@*****.**") user8.set_password("2398wqshjduiwd8932") db.session.add(user4) db.session.add(user5) db.session.add(user6) db.session.add(user7) db.session.add(user8) db.session.commit() got_user4 = User.query.filter_by(username="******").first() webauthn_for_user4 = Webauthn( number=0, is_enabled=True, user_id=got_user4.did ) got_user5 = User.query.filter_by(username="******").first() webauthn_for_user5 = Webauthn( number=1, is_enabled=True, user_identifier=b"\x7e" + os.urandom(31), user_id=got_user5.did, ) device = SoftWebauthnDevice() pkcco = cbor.decode( cbor.encode( { "publicKey": { "rp": {"id": TestConfig.RP_ID, "name": "Demo server"}, "user": { "id": webauthn_for_user5.user_identifier, "icon": "https://example.com/image.png", "name": got_user5.username, "displayName": f"Tests - {got_user5.username}", }, "timeout": 30000, "challenge": ( b"\xcc\x8e\x03\x04\xdb6bd\xa0d\x98\xa9Vz0p.x" b"\xa4\xf5\xd4\xf6%\xf8\x86zt\x1d\ny\xf9<" ), "pubKeyCredParams": [ {"alg": -7, "type": "public-key"}, {"alg": -8, "type": "public-key"}, {"alg": -37, "type": "public-key"}, {"alg": -257, "type": "public-key"}, ], "excludeCredentials": [], "authenticatorSelection": { "userVerification": "discouraged", "authenticatorAttachment": "cross-platform", }, } } ) ) attestation = device.create(pkcco, f"https://{TestConfig.RP_ID}") KeyList.priv_one = device.private_key att_obj = AttestationObject(attestation["response"]["attestationObject"]) client_data = ClientData(attestation["response"]["clientDataJSON"]) auth_data = att_obj.auth_data key_for_user5 = Key( name="Key 1", aaguid=auth_data.credential_data.aaguid, credential_id=auth_data.credential_data.credential_id, client_data_hash=hashlib.sha256(client_data).digest(), public_key=cbor.encode(auth_data.credential_data.public_key), counter=att_obj.auth_data.counter, attestation=attestation["response"]["attestationObject"], info="TODO", last_access=datetime.utcnow(), created=datetime.utcnow(), user_id=got_user5.did, ) db.session.add(webauthn_for_user4) db.session.add(webauthn_for_user5) db.session.add(key_for_user5) # Users for activating Webauthn got_user6 = User.query.filter_by(username="******").first() webauthn_for_user6 = Webauthn( number=2, is_enabled=False, user_id=got_user6.did ) got_user7 = User.query.filter_by(username="******").first() webauthn_for_user7 = Webauthn( number=1, is_enabled=False, user_id=got_user7.did ) db.session.add(webauthn_for_user6) db.session.add(webauthn_for_user7) got_user8 = User.query.filter_by(username="******").first() webauthn_for_user8 = Webauthn( number=1, is_enabled=False, user_id=got_user8.did ) first_key_for_user8 = Key( name="Key 1", aaguid=b"", credential_id=b"againnotrealbutrequiredtolistkeyproperly", client_data_hash=hashlib.sha256(b"a").digest(), public_key=b"", counter=0, attestation=b"", info="TODO", last_access=datetime.utcnow(), created=datetime.utcnow(), user_id=got_user8.did, ) second_key_for_user8 = Key( name="Key 2", aaguid=b"", credential_id=b"notrealbutnecessarytodelete", client_data_hash=hashlib.sha256(b"a").digest(), public_key=b"", counter=0, attestation=b"", info="TODO", last_access=datetime.utcnow(), created=datetime.utcnow(), user_id=got_user8.did, ) db.session.add(webauthn_for_user8) db.session.add(first_key_for_user8) db.session.add(second_key_for_user8) db.session.commit() yield db db.drop_all()