Exemplo n.º 1
0
    def test_u2f(self):
        rp = RelyingParty('example.com', 'Example',
                          'http://example.com/icon.svg')
        app_id = b'https://example.com'
        server = U2FFido2Server(app_id=app_id.decode('ascii'), rp=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'))

        param = b'TOMATO GIVES '

        device = U2FDevice(param, app_id)
        auth_data = AttestedCredentialData.from_ctap1(param,
                                                      device.public_key_bytes)
        authenticator_data, signature = device.sign(client_data)

        server.authenticate_complete(state, [auth_data], device.credential_id,
                                     client_data, authenticator_data,
                                     signature)
Exemplo n.º 2
0
 def test_id_hash(self):
     rp = RelyingParty("example.com")
     rp_id_hash = (
         b"\xa3y\xa6\xf6\xee\xaf\xb9\xa5^7\x8c\x11\x804\xe2u\x1eh/"
         b"\xab\x9f-0\xab\x13\xd2\x12U\x86\xce\x19G"
     )
     self.assertEqual(rp.id_hash, rp_id_hash)
Exemplo n.º 3
0
    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",
            )
Exemplo n.º 4
0
    def test_u2f(self):
        rp = RelyingParty("example.com", "Example",
                          "http://example.com/icon.svg")
        app_id = b"https://example.com"
        server = U2FFido2Server(app_id=app_id.decode("ascii"), rp=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"))

        param = b"TOMATO GIVES "

        device = U2FDevice(param, app_id)
        auth_data = AttestedCredentialData.from_ctap1(param,
                                                      device.public_key_bytes)
        authenticator_data, signature = device.sign(client_data)

        server.authenticate_complete(
            state,
            [auth_data],
            device.credential_id,
            client_data,
            authenticator_data,
            signature,
        )
Exemplo n.º 5
0
    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)
Exemplo n.º 6
0
    def test_u2f_facets(self):
        rp = RelyingParty("example.com", "Example",
                          "http://example.com/icon.svg")
        app_id = b"https://www.example.com/facets.json"

        def verify_u2f_origin(origin):
            return origin in ("https://oauth.example.com",
                              "https://admin.example.com")

        server = U2FFido2Server(app_id=app_id.decode("ascii"),
                                rp=rp,
                                verify_u2f_origin=verify_u2f_origin)

        state = {
            "challenge": "GAZPACHO!",
            "user_verification": USER_VERIFICATION.PREFERRED,
        }
        client_data_dict = {
            "challenge": "GAZPACHO!",
            "origin": "https://oauth.example.com",
            "type": WEBAUTHN_TYPE.GET_ASSERTION,
        }
        client_data = ClientData(json.dumps(client_data_dict).encode("utf-8"))

        param = b"TOMATO GIVES "

        device = U2FDevice(param, app_id)
        auth_data = AttestedCredentialData.from_ctap1(param,
                                                      device.public_key_bytes)
        authenticator_data, signature = device.sign(client_data)

        server.authenticate_complete(
            state,
            [auth_data],
            device.credential_id,
            client_data,
            authenticator_data,
            signature,
        )

        # Now with something not whitelisted
        client_data_dict = {
            "challenge": "GAZPACHO!",
            "origin": "https://publicthingy.example.com",
            "type": WEBAUTHN_TYPE.GET_ASSERTION,
        }
        client_data = ClientData(json.dumps(client_data_dict).encode("utf-8"))

        authenticator_data, signature = device.sign(client_data)

        with six.assertRaisesRegex(self, ValueError, "Invalid origin in "
                                   "ClientData."):
            server.authenticate_complete(
                state,
                [auth_data],
                device.credential_id,
                client_data,
                authenticator_data,
                signature,
            )
Exemplo n.º 7
0
    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)
Exemplo n.º 8
0
    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'})
Exemplo n.º 9
0
    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)
Exemplo n.º 10
0
    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"}
        )
Exemplo n.º 11
0
    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)
Exemplo n.º 12
0
    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)
Exemplo n.º 13
0
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")
Exemplo n.º 14
0
    def test_u2f_facets(self):
        rp = RelyingParty('example.com', 'Example',
                          'http://example.com/icon.svg')
        app_id = b'https://www.example.com/facets.json'

        def verify_u2f_origin(origin):
            return origin in ('https://oauth.example.com',
                              'https://admin.example.com')

        server = U2FFido2Server(app_id=app_id.decode('ascii'),
                                rp=rp,
                                verify_u2f_origin=verify_u2f_origin)

        state = {
            'challenge': 'GAZPACHO!',
            'user_verification': USER_VERIFICATION.PREFERRED
        }
        client_data_dict = {
            'challenge': 'GAZPACHO!',
            'origin': 'https://oauth.example.com',
            'type': WEBAUTHN_TYPE.GET_ASSERTION
        }
        client_data = ClientData(json.dumps(client_data_dict).encode('utf-8'))

        param = b'TOMATO GIVES '

        device = U2FDevice(param, app_id)
        auth_data = AttestedCredentialData.from_ctap1(param,
                                                      device.public_key_bytes)
        authenticator_data, signature = device.sign(client_data)

        server.authenticate_complete(state, [auth_data], device.credential_id,
                                     client_data, authenticator_data,
                                     signature)

        # Now with something not whitelisted
        client_data_dict = {
            'challenge': 'GAZPACHO!',
            'origin': 'https://publicthingy.example.com',
            'type': WEBAUTHN_TYPE.GET_ASSERTION
        }
        client_data = ClientData(json.dumps(client_data_dict).encode('utf-8'))

        authenticator_data, signature = device.sign(client_data)

        with six.assertRaisesRegex(self, ValueError, 'Invalid origin in '
                                   'ClientData.'):
            server.authenticate_complete(state, [auth_data],
                                         device.credential_id, client_data,
                                         authenticator_data, signature)
Exemplo n.º 15
0
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
Exemplo n.º 16
0
    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')
Exemplo n.º 17
0
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
Exemplo n.º 18
0
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'})
Exemplo n.º 19
0
def getServer():
    rp = RelyingParty(settings.FIDO_SERVER_ID, settings.FIDO_SERVER_NAME)
    return Fido2Server(rp)
Exemplo n.º 20
0
class Config(object):
    # URL of admin app
    ADMIN_BASE_URL = os.getenv('ADMIN_BASE_URL', 'http://*****:*****@notifications.service.gov.uk',
        '*****@*****.**',
        '*****@*****.**',
    )

    SIMULATED_SMS_NUMBERS = ('+16132532222', '+16132532223', '+16132532224')

    DVLA_BUCKETS = {
        'job':
        '{}-dvla-file-per-job'.format(
            os.getenv('NOTIFY_ENVIRONMENT', 'development')),
        'notification':
        '{}-dvla-letter-api-files'.format(
            os.getenv('NOTIFY_ENVIRONMENT', 'development'))
    }

    FREE_SMS_TIER_FRAGMENT_COUNT = 250000

    SMS_INBOUND_WHITELIST = json.loads(os.getenv('SMS_INBOUND_WHITELIST',
                                                 '[]'))
    FIRETEXT_INBOUND_SMS_AUTH = json.loads(
        os.getenv('FIRETEXT_INBOUND_SMS_AUTH', '[]'))
    MMG_INBOUND_SMS_AUTH = json.loads(os.getenv('MMG_INBOUND_SMS_AUTH', '[]'))
    MMG_INBOUND_SMS_USERNAME = json.loads(
        os.getenv('MMG_INBOUND_SMS_USERNAME', '[]'))

    ROUTE_SECRET_KEY_1 = os.getenv('ROUTE_SECRET_KEY_1', '')
    ROUTE_SECRET_KEY_2 = os.getenv('ROUTE_SECRET_KEY_2', '')

    # Format is as follows:
    # {"dataset_1": "token_1", ...}
    PERFORMANCE_PLATFORM_ENDPOINTS = json.loads(
        os.getenv('PERFORMANCE_PLATFORM_ENDPOINTS', '{}'))

    TEMPLATE_PREVIEW_API_HOST = os.getenv('TEMPLATE_PREVIEW_API_HOST',
                                          'http://localhost:6013')
    TEMPLATE_PREVIEW_API_KEY = os.getenv('TEMPLATE_PREVIEW_API_KEY',
                                         'my-secret-key')

    DOCUMENT_DOWNLOAD_API_HOST = os.getenv('DOCUMENT_DOWNLOAD_API_HOST',
                                           'http://localhost:7000')
    DOCUMENT_DOWNLOAD_API_KEY = os.getenv('DOCUMENT_DOWNLOAD_API_KEY',
                                          'auth-token')

    MMG_URL = os.getenv("MMG_URL", "https://api.mmg.co.uk/json/api.php")
    FIRETEXT_URL = os.getenv("FIRETEXT_URL",
                             "https://www.firetext.co.uk/api/sendsms/json")

    AWS_REGION = os.getenv("AWS_REGION", "us-east-1")
    NOTIFY_LOG_PATH = ''

    FIDO2_SERVER = Fido2Server(RelyingParty(
        os.getenv('FIDO2_DOMAIN', 'localhost'), 'Notification'),
                               verify_origin=lambda x: True)
Exemplo n.º 21
0
def get_server():
    rp = RelyingParty(mf_settings['FIDO_SERVER_ID'],
                      mf_settings['FIDO_SERVER_NAME'],
                      mf_settings['FIDO_SERVER_ICON'])
    return Fido2Server(rp)
Exemplo n.º 22
0
def get_webauthn_server(rp_id, name='eduID security API'):
    rp = RelyingParty(rp_id, name)
    return Fido2Server(rp)
Exemplo n.º 23
0
    def get_config_for_bundle(self, action):
        if action.old_format:
            userid = action.user_id
            user = current_app.central_userdb.get_user_by_id(
                userid, raise_on_missing=False)
        else:
            eppn = action.eppn
            user = current_app.central_userdb.get_user_by_eppn(
                eppn, raise_on_missing=False)
        current_app.logger.debug('Loaded User {} from db'.format(user))
        if not user:
            raise self.ActionError('mfa.user-not-found')

        credentials = _get_user_credentials(user)
        current_app.logger.debug('FIDO credentials for user {}:\n{}'.format(
            user, pprint.pformat(credentials)))

        # CTAP1/U2F
        # TODO: Only make U2F challenges for U2F tokens?
        challenge = None
        if current_app.config.get('GENERATE_U2F_CHALLENGES') is True:
            u2f_tokens = [v['u2f'] for v in credentials.values()]
            try:
                challenge = begin_authentication(
                    current_app.config['U2F_APP_ID'], u2f_tokens)
                current_app.logger.debug('U2F challenge:\n{}'.format(
                    pprint.pformat(challenge)))
            except ValueError:
                # there is no U2F key registered for this user
                pass

        # CTAP2/Webauthn
        # TODO: Only make Webauthn challenges for Webauthn tokens?
        webauthn_credentials = [v['webauthn'] for v in credentials.values()]
        fido2rp = RelyingParty(current_app.config['FIDO2_RP_ID'], 'eduID')
        fido2server = _get_fido2server(credentials, fido2rp)
        raw_fido2data, fido2state = fido2server.authenticate_begin(
            webauthn_credentials)
        current_app.logger.debug('FIDO2 authentication data:\n{}'.format(
            pprint.pformat(raw_fido2data)))
        fido2data = base64.urlsafe_b64encode(
            cbor.dumps(raw_fido2data)).decode('ascii')
        fido2data = fido2data.rstrip('=')

        config = {'u2fdata': '{}', 'webauthn_options': fido2data}

        # Save the challenge to be used when validating the signature in perform_action() below
        if challenge is not None:
            session[self.PACKAGE_NAME + '.u2f.challenge'] = challenge.json
            config['u2fdata'] = json.dumps(challenge.data_for_client)
            current_app.logger.debug(
                f'FIDO1/U2F challenge for user {user}: {challenge.data_for_client}'
            )

        current_app.logger.debug(
            f'FIDO2/Webauthn state for user {user}: {fido2state}')
        session[self.PACKAGE_NAME + '.webauthn.state'] = json.dumps(fido2state)

        # Explicit check for boolean True
        if current_app.config.get('MFA_TESTING') is True:
            current_app.logger.info('MFA test mode is enabled')
            config['testing'] = True
        else:
            config['testing'] = False

        # Add config for external mfa auth
        config['eidas_url'] = current_app.config['EIDAS_URL']
        config['mfa_authn_idp'] = current_app.config['MFA_AUTHN_IDP']

        return config
Exemplo n.º 24
0
    def perform_step(self, action):
        current_app.logger.debug('Performing MFA step')
        if current_app.config['MFA_TESTING']:
            current_app.logger.debug('Test mode is on, faking authentication')
            return {
                'success': True,
                'testing': True,
            }

        if action.old_format:
            userid = action.user_id
            user = current_app.central_userdb.get_user_by_id(
                userid, raise_on_missing=False)
        else:
            eppn = action.eppn
            user = current_app.central_userdb.get_user_by_eppn(
                eppn, raise_on_missing=False)
        current_app.logger.debug(
            'Loaded User {} from db (in perform_action)'.format(user))

        # Third party service MFA
        if session.mfa_action.success is True:  # Explicit check that success is the boolean True
            issuer = session.mfa_action.issuer
            authn_instant = session.mfa_action.authn_instant
            authn_context = session.mfa_action.authn_context
            current_app.logger.info(
                'User {} logged in using external mfa service {}'.format(
                    user, issuer))
            action.result = {
                'success': True,
                'issuer': issuer,
                'authn_instant': authn_instant,
                'authn_context': authn_context
            }
            current_app.actions_db.update_action(action)
            return action.result

        req_json = request.get_json()
        if not req_json:
            current_app.logger.error(
                'No data in request to authn {}'.format(user))
            raise self.ActionError('mfa.no-request-data')

        # Process POSTed data
        if 'tokenResponse' in req_json:
            # CTAP1/U2F
            token_response = request.get_json().get('tokenResponse', '')
            current_app.logger.debug(
                'U2F token response: {}'.format(token_response))

            challenge = session.get(self.PACKAGE_NAME + '.u2f.challenge')
            current_app.logger.debug('Challenge: {!r}'.format(challenge))

            device, counter, touch = complete_authentication(
                challenge, token_response,
                current_app.config['U2F_VALID_FACETS'])
            current_app.logger.debug('U2F authentication data: {}'.format({
                'keyHandle':
                device['keyHandle'],
                'touch':
                touch,
                'counter':
                counter,
            }))

            for this in user.credentials.filter(U2F).to_list():
                if this.keyhandle == device['keyHandle']:
                    current_app.logger.info(
                        'User {} logged in using U2F token {} (touch: {}, counter {})'
                        .format(user, this, touch, counter))
                    action.result = {
                        'success': True,
                        'touch': touch,
                        'counter': counter,
                        RESULT_CREDENTIAL_KEY_NAME: this.key,
                    }
                    current_app.actions_db.update_action(action)
                    return action.result
        elif 'authenticatorData' in req_json:
            # CTAP2/Webauthn
            req = {}
            for this in [
                    'credentialId', 'clientDataJSON', 'authenticatorData',
                    'signature'
            ]:
                try:
                    req_json[this] += ('=' * (len(req_json[this]) % 4))
                    req[this] = base64.urlsafe_b64decode(req_json[this])
                except:
                    current_app.logger.error(
                        'Failed to find/b64decode Webauthn parameter {}: {}'.
                        format(this, req_json.get(this)))
                    raise self.ActionError(
                        'mfa.bad-token-response'
                    )  # XXX add bad-token-response to frontend
            current_app.logger.debug(
                'Webauthn request after decoding:\n{}'.format(
                    pprint.pformat(req)))
            client_data = ClientData(req['clientDataJSON'])
            auth_data = AuthenticatorData(req['authenticatorData'])

            credentials = _get_user_credentials(user)
            fido2state = json.loads(session[self.PACKAGE_NAME +
                                            '.webauthn.state'])

            rp_id = current_app.config['FIDO2_RP_ID']
            fido2rp = RelyingParty(rp_id, 'eduID')
            fido2server = _get_fido2server(credentials, fido2rp)
            matching_credentials = [
                (v['webauthn'], k) for k, v in credentials.items()
                if v['webauthn'].credential_id == req['credentialId']
            ]

            if not matching_credentials:
                current_app.logger.error(
                    'Could not find webauthn credential {} on user {}'.format(
                        req['credentialId'], user))
                raise self.ActionError('mfa.unknown-token')

            authn_cred = fido2server.authenticate_complete(
                fido2state,
                [mc[0] for mc in matching_credentials],
                req['credentialId'],
                client_data,
                auth_data,
                req['signature'],
            )
            current_app.logger.debug(
                'Authenticated Webauthn credential: {}'.format(authn_cred))

            cred_key = [mc[1] for mc in matching_credentials][0]

            touch = auth_data.flags
            counter = auth_data.counter
            current_app.logger.info(
                'User {} logged in using Webauthn token {} (touch: {}, counter {})'
                .format(user, cred_key, touch, counter))
            action.result = {
                'success':
                True,
                'touch':
                auth_data.is_user_present() or auth_data.is_user_verified(),
                'user_present':
                auth_data.is_user_present(),
                'user_verified':
                auth_data.is_user_verified(),
                'counter':
                counter,
                RESULT_CREDENTIAL_KEY_NAME:
                cred_key,
            }
            current_app.actions_db.update_action(action)
            return action.result

        else:
            current_app.logger.error(
                'Neither U2F nor Webauthn data in request to authn {}'.format(
                    user))
            current_app.logger.debug('Request: {}'.format(req_json))
            raise self.ActionError('mfa.no-token-response')

        raise self.ActionError('mfa.unknown-token')
Exemplo n.º 25
0
from __future__ import print_function, absolute_import, unicode_literals

from fido2.client import ClientData
from fido2.server import U2FFido2Server, RelyingParty
from fido2.ctap2 import AttestationObject, AuthenticatorData
from fido2.ctap1 import RegistrationData
from fido2.utils import sha256, websafe_encode
from fido2 import cbor
from flask import Flask, session, request, redirect, abort

import os

app = Flask(__name__, static_url_path='')
app.secret_key = os.urandom(32)  # Used for session.

rp = RelyingParty('localhost', 'Demo server')
# By using the U2FFido2Server class, we can support existing credentials
# registered by the legacy u2f.register API for an appId.
server = U2FFido2Server('https://*****:*****@app.route('/')
def index():
    return redirect('/index-u2f.html')


@app.route('/api/register/begin', methods=['POST'])
Exemplo n.º 26
0
# -*- coding: utf-8 -*-

import logging
from flask import Flask
from flask_ldapconn import LDAPConn

app = Flask(__name__)
app.config.from_pyfile('config.py')
logging.basicConfig(level=app.config['RP_LOGLEVEL'])

ldap = LDAPConn(app)
from fido2.server import RelyingParty
rp = RelyingParty(app.config['RP_HOST'], app.config['RP_NAME'])

from . import views
from . import filters
from . import models
from . import cli
Exemplo n.º 27
0
            self.send_response(200)
            self.send_header('Set-Cookie', cookie.output(header=''))
            if header != 0:
                self.send_header(header[0], header[1])
            #self.send_header('Fido-User', "test")
            # TODO sem připsat hlavičku s uživatelem
            self.end_headers()
            self.wfile.write(bytes(json.dumps({'status': 'ok'}), 'UTF-8'))


if len(sys.argv) > 1 and sys.argv[1] == "save-client":
    host = sys.argv[2]
    client_data = ClientData(base64.b64decode(sys.argv[3]))
    attestation_object = AttestationObject(base64.b64decode(sys.argv[4]))

    rp = RelyingParty(host, 'NGINX Auth Server')
    server = U2FFido2Server('https://' + host, rp)

    with open(LASTCHALLENGE) as f:
        auth_data = server.register_complete(json.loads(f.read()), client_data,
                                             attestation_object)
        with open(CREDENTIALS_DIR + "/" + sys.argv[5], 'wb') as f:
            f.write(auth_data.credential_data)

    print("Credentials saved successfully")

else:
    socketserver.TCPServer.allow_reuse_address = True
    httpd = socketserver.TCPServer(("", PORT), AuthHandler)
    try:
        print("serving at port", PORT)
Exemplo n.º 28
0
from django.conf import settings
from fido2.server import Fido2Server, RelyingParty

from .models import User, WebAuthnPublicKey

rp = RelyingParty(settings.RELYING_PARTY_DOMAIN, settings.RELYING_PARTY_NAME)
server = Fido2Server(rp)


class WebAuthnBackend:
    def authenticate(self, request, username, credential_id, state,
                     client_data, auth_data, signature):
        try:
            user = User.objects.get(username=username)
        except User.DoesNotExist:
            return None
        credentials = (WebAuthnPublicKey.objects.credentials(username))
        try:
            server.authenticate_complete(state, credentials, credential_id,
                                         client_data, auth_data, signature)
        except ValueError:
            return None

        return user

    def get_user(self, user_id):
        try:
            return User.objects.get(pk=user_id)
        except User.DoesNotExist:
            return None
Exemplo n.º 29
0
    def do_POST(self):
        origin = self.headers.get('Origin')
        host = origin[len('https://'):]

        rp = RelyingParty(host, 'NGINX Auth Server')
        server = U2FFido2Server(origin, rp)

        if self.path == HTTP_PREFIX + "/get_challenge_for_new_key":
            registration_data, state = server.register_begin({
                'id':
                b'default',
                'name':
                "Default user",
                'displayName':
                "Default user"
            })
            registration_data["publicKey"]["challenge"] = str(
                base64.b64encode(registration_data["publicKey"]["challenge"]),
                'utf-8')
            registration_data["publicKey"]["user"]["id"] = str(
                base64.b64encode(registration_data["publicKey"]["user"]["id"]),
                'utf-8')

            self.send_response(200)
            self.send_header('Content-type', 'application/json')
            self.end_headers()
            # Save this challenge to a file so you can kill the host to add the lient via CLI
            with open(LASTCHALLENGE, 'w') as f:
                f.write(json.dumps(state))
            self.wfile.write(bytes(json.dumps(registration_data), 'UTF-8'))
            return

        if self.path == HTTP_PREFIX + "/register":
            self.send_response(401)
            self.send_header('Content-type', 'application/json')
            self.end_headers()
            self.wfile.write(
                bytes(json.dumps({'error': 'not_configured'}), 'UTF-8'))
            return

        creds = []
        files = glob.glob(CREDENTIALS_DIR + "/*")
        for file in files:
            public_key = ""
            with open(file, 'rb') as f:
                cred, _ = AttestedCredentialData.unpack_from(f.read())
                # TODO add more public-keys which must be stored in AttestedCredentialData
                creds.append(cred)
                auth_data, state = server.authenticate_begin([cred])
                public_key = str(
                    base64.b64encode(auth_data["publicKey"]["allowCredentials"]
                                     [0]["id"]))[2:-1]
            header_file = HEADERS_DIR + "/" + file.split("/")[-1]
            if os.path.exists(header_file):
                with open(header_file, 'r') as f:
                    HEADER_MANAGER[public_key] = [
                        "Fido-User", "\t".join(f.read().splitlines())
                    ]

        if self.path == HTTP_PREFIX + "/get_challenge_for_existing_key":
            auth_data, state = server.authenticate_begin(creds)
            auth_data["publicKey"]["challenge"] = str(
                base64.b64encode(auth_data["publicKey"]["challenge"]), 'utf-8')
            for el in auth_data["publicKey"]["allowCredentials"]:
                el["id"] = str(base64.b64encode(el["id"]), 'utf-8')
            #auth_data["publicKey"]["allowCredentials"][0]["id"] = str(base64.b64encode(auth_data["publicKey"]["allowCredentials"][0]["id"]), 'utf-8')

            CHALLENGE.update(state)

            self.send_response(200)
            self.send_header('Content-type', 'application/json')
            self.end_headers()
            self.wfile.write(bytes(json.dumps(auth_data), 'UTF-8'))

        if self.path == HTTP_PREFIX + "/complete_challenge_for_existing_key":
            data = json.loads(
                self.rfile.read(int(self.headers.get('Content-Length'))))

            credential_id = base64.b64decode(data['id'])
            client_data = ClientData(base64.b64decode(data['clientDataJSON']))
            auth_data = AuthenticatorData(
                base64.b64decode(data['authenticatorData']))
            signature = base64.b64decode(data['signature'])

            with open(LASTCHALLENGE) as f:
                server.authenticate_complete(CHALLENGE, creds, credential_id,
                                             client_data, auth_data, signature)

            cookie = http.cookies.SimpleCookie()
            header = HEADER_MANAGER.get(data['id'], ["", ""])
            token = TOKEN_MANAGER.generate()
            cookie["token"] = token
            TOKEN_MANAGER.set_header(token, header)
            cookie["token"]["path"] = "/"
            cookie["token"]["secure"] = True

            self.send_response(200)
            self.send_header('Set-Cookie', cookie.output(header=''))
            if header != 0:
                self.send_header(header[0], header[1])
            #self.send_header('Fido-User', "test")
            # TODO sem připsat hlavičku s uživatelem
            self.end_headers()
            self.wfile.write(bytes(json.dumps({'status': 'ok'}), 'UTF-8'))
Exemplo n.º 30
0
"""
from __future__ import print_function, absolute_import, unicode_literals

from fido2.client import ClientData
from fido2.server import Fido2Server, RelyingParty
from fido2.ctap2 import AttestationObject, AuthenticatorData
from fido2 import cbor
from flask import Flask, session, request, redirect, abort

import os


app = Flask(__name__, static_url_path="")
app.secret_key = os.urandom(32)  # Used for session.

rp = RelyingParty("localhost", "Demo server")
server = Fido2Server(rp)


# Registered credentials are stored globally, in memory only. Single user
# support, state is lost when the server terminates.
credentials = []


@app.route("/")
def index():
    return redirect("/index.html")


@app.route("/api/register/begin", methods=["POST"])
def register_begin():