Example #1
0
    def authenticate_complete_kdf(self, state, pkconfs, credential_id,
                                  client_data, auth_data, signature):
        if credential_id not in pkconfs:
            raise Exception('Unrecognized credential id')
        pkconf = pkconfs[credential_id]
        message = auth_data + client_data.hash

        pycapos, pycaneg = ecdsa_recover_pubkey(signature, message, NISTP256,
                                                hashes.SHA256())
        pkpos = ES256.from_cryptography_key(pycapos)
        pkneg = ES256.from_cryptography_key(pycaneg)

        def confirm(pkcbor):
            return bytes_eq(pkconf, sha256(b'FIDOKDF1' + pkcbor).digest())

        def complete(public_key, pkcbor):
            credentials = [
                AttestedCredentialData.create(b'\0' * 16, credential_id,
                                              public_key)
            ]
            self.authenticate_complete(state, credentials, credential_id,
                                       client_data, auth_data, signature)
            return sha256(b'FIDOKDF2' + pkcbor).digest()

        pkposcbor = cbor.encode(pkpos)
        if confirm(pkposcbor):
            return complete(pkpos, pkposcbor)

        pknegcbor = cbor.encode(pkneg)
        if confirm(pknegcbor):
            return complete(pkneg, pknegcbor)

        raise Exception('Key mismatch')
Example #2
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"]')))
Example #3
0
    def test_send_cbor_ok(self):
        ctap = self.mock_ctap()
        ctap.device.call.return_value = b"\0" + cbor.encode({1: b"response"})

        self.assertEqual({1: b"response"}, ctap.send_cbor(2, b"foobar"))
        ctap.device.call.assert_called_with(0x10,
                                            b"\2" + cbor.encode(b"foobar"),
                                            mock.ANY, None)
Example #4
0
    def test_send_cbor_ok(self):
        ctap = CTAP2(mock.MagicMock())
        ctap.device.call.return_value = b'\0' + cbor.encode({1: b'response'})

        self.assertEqual({1: b'response'}, ctap.send_cbor(2, b'foobar'))
        ctap.device.call.assert_called_with(0x10,
                                            b'\2' + cbor.encode(b'foobar'),
                                            None, None)
Example #5
0
    def authenticator_make_credential(self, args_cbor):
        args = cbor.decode(args_cbor)
        clientDataJSON_hash = args[0x01]
        rp_id = args[0x02]['id']
        extension_inputs = args[0x06]
        rp_id_hash = sha256(rp_id.encode('utf-8'))
        flags = 0b11000001  # ED, AT, UP
        sign_count = 0

        credential_id = secrets.token_bytes(32)
        (credential_private_key, credential_public_key) = fastecdsa.keys.gen_keypair(P256)

        self._credentials[credential_id] = credential_private_key

        attested_cred_data = pack_attested_credential_data(
            self._aaguid,
            credential_id,
            credential_public_key,
        )

        authData_without_extensions = struct.pack(
            f'>32sBL{len(attested_cred_data)}s',
            rp_id_hash,
            flags,
            sign_count,
            attested_cred_data,
        )

        extensions = {}

        if "recovery" in extension_inputs:
            extensions["recovery"] = self.process_recovery_extension(
                rp_id,
                authData_without_extensions,
                clientDataJSON_hash,
                extension_inputs["recovery"],
            )

        authData = authData_without_extensions + cbor.encode(extensions)
        attStmt = {
            0x01: 'packed',
            0x02: authData,
            0x03: {
                'alg': -7,
                'sig': DEREncoder.encode_signature(
                    *ecdsa.sign(
                        authData + clientDataJSON_hash,
                        credential_private_key,
                        hashfunc=hashlib.sha256
                    )
                ),
            },
        }
        return cbor.encode(attStmt)
Example #6
0
    def create(self, options, origin):
        """create credential and return PublicKeyCredential object aka attestation"""

        if {
                'alg': -7,
                'type': 'public-key'
        } not in options['publicKey']['pubKeyCredParams']:
            raise ValueError(
                'Requested pubKeyCredParams does not contain supported type')

        if ('attestation' in options['publicKey']) and (
                options['publicKey']['attestation'] != 'none'):
            raise ValueError('Only none attestation supported')

        # prepare new key
        self.cred_init(options['publicKey']['rp']['id'],
                       options['publicKey']['user']['id'])

        # generate credential response
        client_data = {
            'type':
            'webauthn.create',
            'challenge':
            urlsafe_b64encode(
                options['publicKey']['challenge']).decode('ascii'),
            'origin':
            origin
        }

        rp_id_hash = sha256(self.rp_id.encode('ascii'))
        flags = b'\x41'  # attested_data + user_present
        sign_count = pack('>I', self.sign_count)
        credential_id_length = pack('>H', len(self.credential_id))
        cose_key = cbor.encode(
            ES256.from_cryptography_key(self.private_key.public_key()))
        attestation_object = {
            'authData':
            rp_id_hash + flags + sign_count + self.aaguid +
            credential_id_length + self.credential_id + cose_key,
            'fmt':
            'none',
            'attStmt': {}
        }

        return {
            'id': urlsafe_b64encode(self.credential_id),
            'rawId': self.credential_id,
            'response': {
                'clientDataJSON': json.dumps(client_data).encode('utf-8'),
                'attestationObject': cbor.encode(attestation_object)
            },
            'type': 'public-key'
        }
Example #7
0
def authenticate_complete():
    token = request.cookies.get("token")
    user_identity = current_user.get_id()
    database_id, remember_me = get_current_user_info(token, user_identity)
    if database_id is None:
        abort(401)

    user = User.query.filter_by(did=database_id).first()

    credentials = get_credentials(database_id)
    if not credentials:
        abort(401)

    data = cbor.decode(request.get_data())
    credential_id = data["credentialId"]
    client_data = ClientData(data["clientDataJSON"])
    auth_data = AuthenticatorData(data["authenticatorData"])
    signature = data["signature"]

    server.authenticate_complete(
        session.pop("state"),
        credentials,
        credential_id,
        client_data,
        auth_data,
        signature,
    )

    current_counter = int(auth_data.counter)

    keys = Key.query.filter_by(user_id=database_id).all()

    for key in keys:
        if key.credential_id == credential_id:
            last_counter = key.counter
            if last_counter is None or last_counter >= current_counter:
                # Cloned => untrusted key!
                return cbor.encode({
                    "status": "error",
                    "reason": "invalid counter"
                })
            key.last_access = datetime.utcnow()
            key.counter = current_counter
            break
    db.session.add(key)
    db.session.commit()

    login_user(user, remember=remember_me)

    return cbor.encode({"status": "OK"})
Example #8
0
def profile_webauthn_register_route():
    """register credential for current user"""

    user = User.query.get(current_user.id)
    form = WebauthnRegisterForm()
    if form.validate_on_submit():
        try:
            attestation = cbor.decode(b64decode(form.attestation.data))
            auth_data = webauthn.register_complete(
                session.pop('webauthn_register_state'),
                ClientData(attestation['clientDataJSON']),
                AttestationObject(attestation['attestationObject']))

            db.session.add(WebauthnCredential(
                user_id=user.id,
                user_handle=session.pop('webauthn_register_user_handle'),
                credential_data=cbor.encode(auth_data.credential_data.__dict__),
                name=form.name.data))
            db.session.commit()

            return redirect(url_for('auth.profile_route'))
        except (KeyError, ValueError) as e:
            current_app.logger.exception(e)
            flash('Error during registration.', 'error')

    return render_template('auth/profile/webauthn_register.html', form=form)
Example #9
0
def authenticate_begin():
    if not credentials:
        abort(404)

    auth_data, state = server.authenticate_begin(credentials)
    session["state"] = state
    return cbor.encode(auth_data)
Example #10
0
def authenticate_complete(request):
    data = cbor.decode(request.body)
    credential_id = data["credentialId"]
    client_data = ClientData(data["clientDataJSON"])
    auth_data = AuthenticatorData(data["authenticatorData"])
    signature = data["signature"]
    credentials = [
        row.credential for row in User.objects.get(
            pk=request.session['user']).fidocredential_set.all()
    ]

    fido_server.authenticate_complete(
        request.session.pop("state"),
        credentials,
        credential_id,
        client_data,
        auth_data,
        signature,
    )

    try:
        user = User.objects.get(pk=request.session.pop('user'))
    except User.DoesNotExist:
        raise Http404
    else:
        # If we're here, we've already confirmed the user has the
        # username and the password.
        login(request, user)

    return HttpResponse(
        cbor.encode({"status": "OK"}),
        content_type='application/cbor',
    )
Example #11
0
def basic_get_assertion(authenticator, clientDataJSON_hash, allowList):
    authenticator.authenticator_get_assertion(cbor.encode({
        0x01: RP_ID,
        0x02: clientDataJSON_hash,
        0x03: allowList,
        0x04: {},
    }))
Example #12
0
    def export_recovery_seed(self, allow_algs):
        for alg in allow_algs:
            if alg == 0:
                if self._recovery_seed_pri_key is None:
                    self._initialize_recovery_seed()

                S = fastecdsa.keys.get_public_key(self._recovery_seed_pri_key, P256)
                S_enc = encode_pub(S)

                signed_data = struct.pack('>B16s65s', alg, self._aaguid, S_enc)
                sig = DEREncoder.encode_signature(
                    *ecdsa.sign(
                        signed_data,
                        self._attestation_key,
                        hashfunc=hashlib.sha256
                    )
                )
                payload = {
                    1: alg,
                    2: [],
                    3: self._aaguid,
                    4: sig,
                    -1: S_enc,
                }
                return cbor.encode(payload)

        raise UnknownKeyAgreementScheme(allow_algs)
Example #13
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
Example #14
0
    def activate(self, request: Request, is_webauthn_signin_ff_enabled):
        if not is_webauthn_signin_ff_enabled:
            challenge = dict(u2f.begin_authentication(self.u2f_app_id, self.get_u2f_devices()))
            # XXX: Upgrading python-u2flib-server to 5.0.0 changes the response
            # format. Our current js u2f library expects the old format, so
            # massaging the data to include the old `authenticateRequests` key here.

            authenticate_requests = []
            for registered_key in challenge["registeredKeys"]:
                authenticate_requests.append(
                    {
                        "challenge": challenge["challenge"],
                        "version": registered_key["version"],
                        "keyHandle": registered_key["keyHandle"],
                        "appId": registered_key["appId"],
                    }
                )
            challenge["authenticateRequests"] = authenticate_requests

            return ActivationChallengeResult(challenge=challenge)

        credentials = []

        for device in self.get_u2f_devices():
            if type(device) == AuthenticatorData:
                credentials.append(device.credential_data)
            else:
                credentials.append(create_credential_object(device))
        challenge, state = self.webauthn_authentication_server.authenticate_begin(
            credentials=credentials
        )
        request.session["webauthn_authentication_state"] = state

        return ActivationChallengeResult(challenge=cbor.encode(challenge["publicKey"]))
def authenticate_complete():
    if not User.query.count():
        abort(404)

    try:
        data = cbor.decode(request.get_data())
        credential_id = data["credentialId"]
        client_data = ClientData(data["clientDataJSON"])
        auth_data = AuthenticatorData(data["authenticatorData"])
        signature = data["signature"]
    except IndexError:
        abort(400)

    user = User.get_by(credential_id)
    if user is None:
        abort(401)

    server.authenticate_complete(
        session.pop("state"),
        [user.create_data],
        credential_id,
        client_data,
        auth_data,
        signature,
    )
    return cbor.encode({"status": "OK"})
Example #16
0
    def register_complete_kdf(self, state, client_data, attestation_object):
        auth_data = self.register_complete(state, client_data,
                                           attestation_object)
        credential_id = auth_data.credential_data.credential_id
        public_key = auth_data.credential_data.public_key

        if not isinstance(public_key, dict):
            raise Exception('Invalid public key')
        if set(public_key.keys()) != {1, 3, -1, -2, -3}:
            raise Exception('Malformed public key')
        if public_key[1] != 2:  # kty = verify
            raise Exception('Inappropriate public key type')
        if public_key[3] != ES256.ALGORITHM:  # alg = ES256, ECDSA w/ SHA-256
            raise Exception('Unsupported signature algorithm')
        if public_key[-1] != 1:  # curve = NIST P-256
            raise Exception('Unsupported ECDSA curve')
        if not isinstance(public_key[-2], bytes):
            raise Exception('Invalid x coordinate')
        if not isinstance(public_key[-3], bytes):
            raise Exception('Invalid y coordinate')

        pkcbor = cbor.encode(public_key)
        pkconf = sha256(b'FIDOKDF1' + pkcbor).digest()
        key = sha256(b'FIDOKDF2' + pkcbor).digest()

        return {credential_id: pkconf}, key
Example #17
0
def get_assertion(authenticator, request_json):
    request = json.loads(request_json)
    pkcro = request['publicKeyCredentialRequestOptions']
    collectedClientData = {
        'type': 'webauthn.get',
        'challenge': pkcro['challenge'],
        'origin': 'https://localhost:8443',
    }
    clientDataJSON = json.dumps(collectedClientData, indent=None).encode('utf-8')
    clientDataJSON_hash = sha256(clientDataJSON)
    authenticator_response = cbor.decode(authenticator.authenticator_get_assertion(cbor.encode({
        0x01: pkcro['rpId'],
        0x02: clientDataJSON_hash,
        0x03: [{'id': base64.urlsafe_b64decode(c['id']), 'type': 'public-key'} for c in pkcro['allowCredentials']],
        0x04: pkcro['extensions'],
    })))
    authenticatorData = authenticator_response[0x02]
    sig = authenticator_response[0x03]
    credential = {
        'type': 'public-key',
        'id': base64.urlsafe_b64encode(authenticator_response[0x01]['id']).decode('utf-8'),
        'response': {
            'authenticatorData': base64.urlsafe_b64encode(authenticatorData).decode('utf-8'),
            'clientDataJSON': base64.urlsafe_b64encode(clientDataJSON).decode('utf-8'),
            'signature': base64.urlsafe_b64encode(sig).decode('utf-8'),
        },
        'clientExtensionResults': {},
    }
    print(json.dumps(credential, indent=2))
Example #18
0
def create_credential(authenticator, request_json):
    request = json.loads(request_json)
    pkcco = request['publicKeyCredentialCreationOptions']
    collectedClientData = {
        'type': 'webauthn.create',
        'challenge': pkcco['challenge'],
        'origin': 'https://localhost:8443',
    }
    clientDataJSON = json.dumps(collectedClientData, indent=None).encode('utf-8')
    clientDataJSON_hash = sha256(clientDataJSON)

    if 'extensions' in pkcco and 'recovery' in pkcco['extensions'] and 'allowCredentials' in pkcco['extensions']['recovery']:
        for cred in pkcco['extensions']['recovery']['allowCredentials']:
            cred['id'] = b64d(cred['id'])

    attObj_bytes = authenticator.authenticator_make_credential(cbor.encode({
        0x01: clientDataJSON_hash,
        0x02: pkcco['rp'],
        0x06: pkcco['extensions'],
    }))
    attObj = ctap2.AttestationObject(attObj_bytes)
    credential_id = attObj.auth_data.credential_data.credential_id
    credential = {
        'type': 'public-key',
        'id': base64.urlsafe_b64encode(credential_id).decode('utf-8'),
        'response': {
            'attestationObject': base64.urlsafe_b64encode(ctap2_to_webauthn_attestation_object(attObj_bytes)).decode('utf-8'),
            'clientDataJSON': base64.urlsafe_b64encode(clientDataJSON).decode('utf-8'),
        },
        'clientExtensionResults': {},
    }
    print(json.dumps(credential, indent=2))
Example #19
0
def ctap2_to_webauthn_attestation_object(attObj_cbor):
    attObj = cbor.decode(attObj_cbor)
    return cbor.encode({
        'fmt': attObj[0x01],
        'authData': attObj[0x02],
        'attStmt': attObj[0x03],
    })
Example #20
0
def assertion_response():
    app.logger.debug("/assertion/response")
    data = cbor.decode(request.get_data())
    app.logger.debug('post_data:\n%s', pp.pformat(data))
    credential_id = data['id']
    raw_id = data['rawId']
    client_data = ClientData(data['clientDataJSON'])
    app.logger.debug('client_data:\n%s', pp.pformat(client_data))
    auth_data = AuthenticatorData(data['authenticatorData'])
    app.logger.debug('auth_data:\n%s', pp.pformat(auth_data))
    signature = data['signature']
    credential = Credential.get(credential_id)
    if not credential:
        abort(404)
    credential_data = credential.to_credential_data()
    server.authenticate_complete(session.pop('state'), [credential_data],
                                 raw_id, client_data, auth_data, signature)
    # Checking signCount
    if auth_data.counter > credential.counter:
        # TODO: flask_ldapconn is not allow to store integer value
        credential.counter = str(auth_data.counter)
        credential.save()
    else:
        app.logger.warn('wrong counter: stored counter=%d but %d asserted',
                        credential.counter, auth_data.counter)
        abort(404)
    user = credential.user
    if not user:
        abort(404)
    login_user(user)
    return cbor.encode({'status': 'OK'})
Example #21
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"]')))
Example #22
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
Example #23
0
    def buildFIDO2Registration(self, user):
        try:
            self.credentials[user] = {}
            self.credentials[user]["credentials"] = []
            registration_data, state = self.server.register_begin(
                {
                    "id": user.encode('utf-8'), #Required
                    "name": user, # Optional
                    #"displayName": "First Last", #Optional
                    #"icon": "https://domain.com/user_avatar.png" #Optional
                },
                self.credentials[user]["credentials"],
                # User Verification Mode
                # discouraged - passwordless devices can be used, required - User password required for device to be used, 
                # preferred - User password not required but preferred
                # Note - Windows will still require a PIN be created for devices that support it if using 'preferred'
                user_verification="discouraged",

                # Supported Authenticator Types
                # cross-platform - e.g. usb yubikey, platform - e.g. Windows Hello, omit/None - Any type
                # If using USB tokens like Yubikeys, it's recommended cross-platform is set, Windows Hello will prompt before a Yubikey
                #authenticator_attachment="cross-platform", 
            )

            self.credentials[user]["state"] = state
            ec_data = cbor.encode(registration_data)
            b64reg = base64.b64encode(ec_data).decode('utf-8')
            b64user = base64encode(user)
            reply = "CRV1:FIDO2,R:reg:%s:%s" % (b64user, b64reg)
            self.saveCredentials(self.credsfile)
            return reply
        except Exception as e:
            print("Failed buildFIDO2Registration")
            print(e)
        return None
Example #24
0
def get_cbor_resp(data=None, redirect=None):
    resp = HttpResponse(content_type="application/cbor")
    if data:
        resp.write(cbor.encode(data))
    if redirect:
        resp.write(redirect)
    return resp
Example #25
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"]')
Example #26
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
Example #27
0
def register_options():
    app.logger.debug('/register/response')
    if 'resident_key' in request.json:
        resident_key = request.json['resident_key']
    else:
        regident_key = False
    credentials = []
    user_verification = app.config.get('RP_USER_VERIFICATION', "discouraged")
    user_id = UUID(current_user.entryUUID).bytes
    if current_user.description:
        displayName = current_user.description
    else:
        displayName = current_user.uid
    registration_data, state = server.register_begin(
        {
            'id': user_id,
            'name': current_user.uid,
            'displayName': displayName,
            'icon': 'https://example.com/'
        },
        credentials,
        user_verification=user_verification,
        resident_key=resident_key)
    session['state'] = state
    app.logger.debug('registration_data:\n%s', pp.pformat(registration_data))
    app.logger.debug('state:\n%s', pp.pformat(state))
    return cbor.encode(registration_data)
 def test_failure_on_non_dict_cbor_data(self):
     res = self._dev.call(
         CTAPHID.CBOR,
         data=ctap2.Ctap2.CMD.MAKE_CREDENTIAL.to_bytes(1, 'big')
         + cbor.encode('str'),
     )
     assert res == CtapError.ERR.INVALID_CBOR.to_bytes(1, 'big')
Example #29
0
def registration_begin(user, authenticator):
    user_webauthn_tokens = user.credentials.filter(FidoCredential)
    if user_webauthn_tokens.count >= current_app.config.webauthn_max_allowed_tokens:
        current_app.logger.error(
            'User tried to register more than {} tokens.'.format(current_app.config.webauthn_max_allowed_tokens)
        )
        return error_response(message=SecurityMsg.max_webauthn)

    creds = make_credentials(user_webauthn_tokens.to_list())
    server = get_webauthn_server(current_app.config.fido2_rp_id)
    if user.given_name is None or user.surname is None or user.display_name is None:
        return error_response(message=SecurityMsg.no_pdata)

    registration_data, state = server.register_begin(
        {
            'id': str(user.eppn).encode('ascii'),
            'name': "{} {}".format(user.given_name, user.surname),
            'displayName': user.display_name,
        },
        credentials=creds,
        user_verification=USER_VERIFICATION.DISCOURAGED,
        authenticator_attachment=authenticator,
    )
    session['_webauthn_state_'] = state

    current_app.logger.info('User {} has started registration of a webauthn token'.format(user))
    current_app.logger.debug('Webauthn Registration data: {}.'.format(registration_data))
    current_app.stats.count(name='webauthn_register_begin')

    encoded_data = base64.urlsafe_b64encode(cbor.encode(registration_data)).decode('ascii')
    encoded_data = encoded_data.rstrip('=')
    return {'csrf_token': session.new_csrf_token(), 'registration_data': encoded_data}
Example #30
0
def authenticate_begin(username, **_):
    """
    Begin authentication procedure

    Variables:
    username     user name of the user you want to login with

    Arguments:
    None

    Data Block:
    None

    Result example:
    <WEBAUTHN_AUTHENTICATION_DATA>
    """
    user = STORAGE.user.get(username, as_obj=False)
    if not user:
        return make_api_response({'success': False}, err="Bad Request", status_code=400)

    session.pop('state', None)
    security_tokens = user.get('security_tokens', {}) or {}
    credentials = [AttestedCredentialData(websafe_decode(x)) for x in security_tokens.values()]

    auth_data, state = server.authenticate_begin(credentials)
    session['state'] = state

    return make_api_response(list(cbor.encode(auth_data)))