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)
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)
def test_authenticate_complete_invalid_signature(self): rp = RelyingParty("example.com", "Example") server = Fido2Server(rp) state = { "challenge": "GAZPACHO!", "user_verification": USER_VERIFICATION.PREFERRED, } client_data_dict = { "challenge": "GAZPACHO!", "origin": "https://example.com", "type": WEBAUTHN_TYPE.GET_ASSERTION, } client_data = ClientData(json.dumps(client_data_dict).encode("utf-8")) _AUTH_DATA = a2b_hex( "A379A6F6EEAFB9A55E378C118034E2751E682FAB9F2D30AB13D2125586CE1947010000001D" ) with six.assertRaisesRegex(self, ValueError, "Invalid signature."): server.authenticate_complete( state, [AttestedCredentialData(_ATT_CRED_DATA)], _CRED_ID, client_data, AuthenticatorData(_AUTH_DATA), b"INVALID", )
def test_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, )
def test_register_begin_custom_challenge_too_short(self): rp = RelyingParty("example.com", "Example") server = Fido2Server(rp) challenge = b"123456789012345" with self.assertRaises(ValueError): request, state = server.register_begin({}, challenge=challenge)
def test_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, )
def test_register_begin_custom_challenge(self): rp = RelyingParty("example.com", "Example") server = Fido2Server(rp) challenge = b"1234567890123456" request, state = server.register_begin({}, challenge=challenge) self.assertEqual(request["publicKey"]["challenge"], challenge)
def test_register_begin_rp_no_icon(self): rp = RelyingParty('example.com', 'Example') server = Fido2Server(rp) request, state = server.register_begin({}) self.assertEqual(request['publicKey']['rp'], {'id': 'example.com', 'name': 'Example'})
def __init__(self, params): """ Create an instance of the class. :param params: Dictionary containing the following parameters db_path: Path to the user database rp_id: Relying Party identifier of the server. pre_share_eph_username: Indicates whether or not the server tries to establish an ephemeral user name for the next handshake db_encryption_key: The key the database is encrypted with. modes: List of allowed FIDO2 operation modes during authentication. force_fido2: Flag indicating whether or not to accept only FIDO2 authenticated users. """ # check arguments self._valid = False db_path = rp_id = encryption_key = None pre_share_eph_username = force_fido2 = False modes = FIDO2Mode.all if 'db_path' in params and isinstance(params['db_path'], str): db_path = params['db_path'] if 'rp_id' in params and isinstance(params['rp_id'], str): rp_id = params['rp_id'].lower() if not is_valid_hostname(rp_id): rp_id = None if 'db_encryption_key' in params and \ isinstance(params['db_encryption_key'], RSAKey): encryption_key = params['db_encryption_key'] if 'pre_share_eph_user_name' in params and \ isinstance(params['pre_share_eph_user_name'], bool): pre_share_eph_username = params['pre_share_eph_user_name'] if 'modes' in params and \ isinstance(params['modes'], list): modes = params['modes'] if 'force_fido2' in params and isinstance(params['force_fido2'], bool): force_fido2 = params['force_fido2'] # check if mandatory arguments are set if not db_path or not rp_id: return self.state = ServerState.init self.mode = None self.allowed_modes = modes self._db_connection = self._get_db_connection(db_path, encryption_key) relying_party = RelyingParty(rp_id) self._server = Fido2Server(relying_party) self.pre_share_eph_user_name = pre_share_eph_username self.force_fido2 = force_fido2 self._auth_state = None self._user_id = None self._eph_user_name_server_share = None self._allow_credentials = [] self._valid = bool(self._db_connection is not None)
def test_register_begin_rp_no_icon(self): rp = RelyingParty("example.com", "Example") server = Fido2Server(rp) request, state = server.register_begin({}) self.assertEqual( request["publicKey"]["rp"], {"id": "example.com", "name": "Example"} )
def test_register_begin_rp_icon(self): rp = RelyingParty('example.com', 'Example', 'http://example.com/icon.svg') server = Fido2Server(rp) request, state = server.register_begin({}) data = {'id': 'example.com', 'name': 'Example', 'icon': 'http://example.com/icon.svg'} self.assertEqual(request['publicKey']['rp'], data)
def test_register_begin_rp_icon(self): rp = RelyingParty("example.com", "Example", "http://example.com/icon.svg") server = Fido2Server(rp) request, state = server.register_begin({}) data = { "id": "example.com", "name": "Example", "icon": "http://example.com/icon.svg", } self.assertEqual(request["publicKey"]["rp"], data)
def begin(request): rp_host = urlparse(request.build_absolute_uri()).hostname rp = RelyingParty(rp_host, 'Demo server') server = Fido2Server(rp) existing_credentials = AttestedCredentialData.objects.filter( user=request.user).all() auth_data, state = server.authenticate_begin(existing_credentials) request.session['state'] = { 'challenge': state['challenge'], 'user_verification': state['user_verification'].value, } return Response(auth_data, content_type="application/cbor")
def 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)
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
def test_authenticate_complete_invalid_signature(self): rp = RelyingParty('example.com', 'Example') server = Fido2Server(rp) state = {'challenge': 'GAZPACHO!', 'user_verification': USER_VERIFICATION.PREFERRED} client_data_dict = {'challenge': 'GAZPACHO!', 'origin': 'https://example.com', 'type': WEBAUTHN_TYPE.GET_ASSERTION} client_data = ClientData(json.dumps(client_data_dict).encode('utf-8')) _AUTH_DATA = a2b_hex('A379A6F6EEAFB9A55E378C118034E2751E682FAB9F2D30AB13D2125586CE1947010000001D') # noqa with six.assertRaisesRegex(self, ValueError, 'Invalid signature.'): server.authenticate_complete( state, [AttestedCredentialData(_ATT_CRED_DATA)], _CRED_ID, client_data, AuthenticatorData(_AUTH_DATA), b'INVALID')
def authenticate(request): rp_host = urlparse(request.build_absolute_uri()).hostname rp = RelyingParty(rp_host, 'Demo server') server = Fido2Server(rp) data = request.data[0] credential_id = data['credentialId'] credentials = AttestedCredentialData.objects.filter( user=request.user, ).all() client_data = ClientData(data['clientDataJSON']) auth_data = AuthenticatorData(data['authenticatorData']) signature = data['signature'] state = request.session['state'] cred = server.authenticate_complete(state, credentials, credential_id, client_data, auth_data, signature) return cred
def 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'})
def getServer(): rp = RelyingParty(settings.FIDO_SERVER_ID, settings.FIDO_SERVER_NAME) return Fido2Server(rp)
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)
def get_server(): rp = RelyingParty(mf_settings['FIDO_SERVER_ID'], mf_settings['FIDO_SERVER_NAME'], mf_settings['FIDO_SERVER_ICON']) return Fido2Server(rp)
def get_webauthn_server(rp_id, name='eduID security API'): rp = RelyingParty(rp_id, name) return Fido2Server(rp)
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
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')
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'])
# -*- 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
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)
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
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'))
""" 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():