예제 #1
0
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"]')
예제 #2
0
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)
예제 #3
0
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"]')))
예제 #4
0
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
예제 #5
0
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
예제 #6
0
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
예제 #7
0
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')
예제 #8
0
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'
예제 #9
0
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')
예제 #10
0
    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__)
예제 #11
0
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')
예제 #12
0
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)
예제 #13
0
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()))
예제 #14
0
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)
예제 #15
0
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'}
예제 #16
0
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"]')))
예제 #17
0
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 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
예제 #19
0
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'])
예제 #20
0
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'}
예제 #21
0
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"]')
예제 #22
0
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()