def login(): devices = [doc['device'] for doc in DB.u2f.find()] u2f_enabled = True if devices else False if request.method == 'POST': csrf.protect() pwd = request.form.get('pass') if pwd and verify_pass(pwd): if devices: resp = json.loads(request.form.get('resp')) print(resp) try: u2f.complete_authentication(session['challenge'], resp) except ValueError as exc: print('failed', exc) abort(401) return finally: session['challenge'] = None session['logged_in'] = True return redirect(request.args.get('redirect') or '/admin') else: abort(401) payload = None if devices: payload = u2f.begin_authentication(ID, devices) session['challenge'] = payload return render_template( 'login.html', u2f_enabled=u2f_enabled, me=ME, payload=payload, )
def validate_2fa(username, otp_token, u2f_challenge, u2f_response, storage): u2f_enabled = False otp_enabled = False u2f_error = False otp_error = False report_errors = False # Get user user_data = storage.get_user(username) # Test u2f if config.auth.get('allow_u2f', True): registered_keys = user_data.get('u2f_devices', []) if registered_keys: # U2F is enabled for user u2f_enabled = True report_errors = True if u2f_challenge and u2f_response: try: complete_authentication(u2f_challenge, u2f_response, [APP_ID]) return except Exception: u2f_error = True # Test OTP if config.auth.get('allow_2fa', True): otp_sk = user_data.get('otp_sk', None) if otp_sk: # OTP is enabled for user otp_enabled = True report_errors = True if otp_token: if get_totp_token(otp_sk) != otp_token: otp_error = True else: return if report_errors: if u2f_error: # Wrong response to challenge raise AuthenticationException("Wrong U2F Security Token") elif otp_error: # Wrong token provided raise AuthenticationException("Wrong OTP token") elif u2f_enabled: # No challenge/response provided and U2F is enabled raise AuthenticationException("Wrong U2F Security Token") elif otp_enabled: # No OTP Token provided and OTP is enabled raise AuthenticationException("Wrong OTP token") # This should never hit raise AuthenticationException("Unknown 2FA Authentication error")
def test_authenticate_single_soft_u2f(self): # Register device, token = register_token() # Authenticate request = begin_authentication(APP_ID, [device]) data = request.data_for_client response = token.getAssertion(FACET, data['appId'], data['challenge'], data['registeredKeys'][0]) complete_authentication(request.json, response)
def verify_token(self, token): client_data = json.loads(websafe_decode(token['clientData']).decode()) try: u2f_challenge = U2FChallenge.objects.get( challenge=client_data['challenge'], device=self) except U2FChallenge.DoesNotExist: return False try: complete_authentication(u2f_challenge.challenge_data, token, [settings.OTP_U2F_APP_ID]) except ValueError: return False u2f_challenge.delete() return True
def verify(self, user_name, object_dn, key): # Do we have read permissions for the requested attribute self.__check_acl(user_name, object_dn, "r") # Get the object for the given dn uuid = self.__dn_to_uuid(object_dn) factor_method = self.get_method_from_user(uuid) user_settings = self.__settings[uuid] if uuid in self.__settings else {} if factor_method == "otp": totp = TOTP(user_settings.get('otp_secret')) return totp.verify(key) elif factor_method == "u2f": challenge = user_settings.pop('_u2f_challenge_') data = loads(key) device, c, t = complete_authentication(challenge, data, [self.facet]) return { 'keyHandle': device['keyHandle'], 'touch': t, 'counter': c } elif factor_method is None: return True return False
def validate(request, username): import datetime, random data = simplejson.loads(request.POST["response"]) res = check_errors(request, data) if res != True: return res challenge = request.session.pop('_u2f_challenge_') device, c, t = complete_authentication(challenge, data, [settings.U2F_APPID]) key = User_Keys.objects.get(username=username, properties__shas="$.device.publicKey=%s" % device["publicKey"]) key.last_used = timezone.now() key.save() mfa = {"verified": True, "method": "U2F", "id": key.id} if getattr(settings, "MFA_RECHECK", False): mfa["next_check"] = int((datetime.datetime.now() + datetime.timedelta( seconds=random.randint(settings.MFA_RECHECK_MIN, settings.MFA_RECHECK_MAX))).strftime("%s")) request.session["mfa"] = mfa return True
def validate_2fa(name): form = forms.SignTokenForm() if form.validate_on_submit(): errorCode = json.loads(form.response.data)['errorCode'] if errorCode != 0: flash("Token authentication failed", "error") return redirect(url_for('select_2fa')) device, c, t = complete_authentication(session['u2f_sign'], form.response.data, app_id) if t != 1: flash("Token authentication failed", "error") return redirect(url_for('select_2fa')) # Log in the user user = load_user(session['user']) flask_login.login_user(user) flash("Login complete", "success") return redirect(url_for('index')) key = models.U2FCredentials.query.filter_by(owner=session['user'], name=name).first() sign = begin_authentication(app_id, [key.device]) session['u2f_sign'] = sign.json challenge = sign['challenge'] registeredKeys = json.loads(sign['registeredKeys'][0]) version = registeredKeys['version'] keyHandle = registeredKeys['keyHandle'] return render_template('validate_2fa.html', challenge=challenge, version=version, keyHandle=keyHandle, app_id=app_id, form=form, key=key)
def verify(self, user_name, object_dn, key): # Do we have read permissions for the requested attribute self.__check_acl(user_name, object_dn, "r") # Get the object for the given dn uuid = self.__dn_to_uuid(object_dn) factor_method = self.get_method_from_user(uuid) user_settings = self.__settings[ uuid] if uuid in self.__settings else {} if factor_method == "otp": totp = TOTP(user_settings.get('otp_secret')) return totp.verify(key) elif factor_method == "u2f": challenge = user_settings.pop('_u2f_challenge_') data = loads(key) device, c, t = complete_authentication(challenge, data, [self.facet]) return {'keyHandle': device['keyHandle'], 'touch': t, 'counter': c} elif factor_method is None: return True return False
def validate(request, username): data = json.loads(request.POST["response"]) res = check_errors(request, data) if res is not True: return res challenge = request.session.pop('_u2f_challenge_') device, c, t = complete_authentication(challenge, data, [settings.U2F_APPID]) key = UserKey.objects.get( username=username, properties__device__publicKey=device["publicKey"] ) key.last_used = timezone.now() key.save() mfa = { "verified": True, "method": "U2F", "id": key.id } if getattr(settings, "MFA_RECHECK", False): mfa["next_check"] = next_check() request.session["mfa"] = mfa return True
def test_authenticate_single_soft_u2f(self): # Register device, token = register_token() # Authenticate request = begin_authentication(APP_ID, [device]) data = request.data_for_client response = token.getAssertion( FACET, data['appId'], data['challenge'], data['registeredKeys'][0] ) complete_authentication(request.json, response)
def _sign_response(user_id, response_data): client = get_client() user = get_user(user_id) sign_response = response_data.signResponse challenge = sign_response.clientData.challenge request_data = store.retrieve(client.id, user_id, challenge) if request_data is None: raise exc.NotFoundException('Transaction not found') request_data = json.loads(request_data) device, counter, presence = complete_authentication( request_data, sign_response, client.valid_facets) dev = user.devices[request_data['handleMap'][device['keyHandle']]] if dev.compromised: raise exc.DeviceCompromisedException('Device is compromised', dev.get_descriptor()) if presence == 0: raise exc.BadInputException('User presence byte not set') if counter > (dev.counter or -1): dev.counter = counter dev.authenticated_at = datetime.now() dev.update_properties(request_data['properties']) dev.update_properties(response_data.properties) db.session.commit() return dev.get_descriptor(get_metadata(dev)) else: dev.compromised = True db.session.commit() raise exc.DeviceCompromisedException('Device counter mismatch', dev.get_descriptor())
def admin_login() -> _Response: if session.get("logged_in") is True: return redirect(url_for("admin.admin_notifications")) devices = [doc["device"] for doc in DB.u2f.find()] u2f_enabled = True if devices else False if request.method == "POST": csrf.protect() # 1. Check regular password login flow pwd = request.form.get("pass") if pwd: if verify_pass(pwd): session.permanent = True session["logged_in"] = True return redirect( request.args.get("redirect") or url_for("admin.admin_notifications")) else: abort(403) # 2. Check for U2F payload, if any elif devices: resp = json.loads(request.form.get("resp")) # type: ignore try: u2f.complete_authentication(session["challenge"], resp) except ValueError as exc: print("failed", exc) abort(403) return finally: session["challenge"] = None session.permanent = True session["logged_in"] = True return redirect( request.args.get("redirect") or url_for("admin.admin_notifications")) else: abort(401) payload = None if devices: payload = u2f.begin_authentication(ID, devices) session["challenge"] = payload return htmlify( render_template("login.html", u2f_enabled=u2f_enabled, payload=payload))
def clean(self): cleaned_data = super().clean() u2f_challenge = self.session["u2f_challenge"] token_response = cleaned_data["token_response"] device, _, _ = complete_authentication(u2f_challenge, token_response, [zentral_settings["api"]["tls_hostname"]]) if not device: raise forms.ValidationError("Could not complete the U2F authentication") return cleaned_data
def verify(self, username, data): user = self.users[username] challenge = user.pop('_u2f_challenge_') device, c, t = complete_authentication(challenge, data, [self.facet]) return json.dumps({ 'keyHandle': device['keyHandle'], 'touch': t, 'counter': c })
def clean(self): if isinstance(self.initial_device, U2FDevice): try: response = json.loads(self.cleaned_data['otp_token']) request = self.request.session['u2f_sign_request'] device, login_counter, _ = u2f.complete_authentication(request, response) except (ValueError, TypeError): self.add_error('__all__', 'U2F validation failed -- bad signature.') else: self.clean_otp(self.user) return self.cleaned_data
def verify(): app_id = get_origin(request.environ) username = request.args.get('username', 'user') data = request.data.decode() user = users[username] challenge = user.pop('_u2f_challenge_') device, c, t = complete_authentication(challenge, data, [app_id]) return json.dumps({ 'keyHandle': device['keyHandle'], 'touch': t, 'counter': c })
def post(self, request, *args, **kwargs): data = json.loads(request.POST["response"]) if data.get("errorCode", 0) != 0: messages.error(request, "Invalid security key response.") return super().get(request, *args, **kwargs) challenge = request.session.pop('_u2f_challenge_') device, c, t = u2f.complete_authentication(challenge, data, [mf_settings['U2F_APPID']]) key = self.keys.get(properties__device__publicKey=device["publicKey"]) write_session(request, key) return login(request)
def validate_second_factor(self): response = json.loads(self.cleaned_data['response']) try: device, login_counter, _ = u2f.complete_authentication(self.sign_request, response) # TODO: store login_counter and verify it's increasing device = self.user.u2f_keys.get(key_handle=device['keyHandle']) device.last_used_at = timezone.now() device.save() del self.request.session['u2f_sign_request'] return True except ValueError: self.add_error('__all__', 'U2F validation failed -- bad signature.') return False
def validate_response(self, request: Request, challenge, response, has_webauthn_register): try: if not has_webauthn_register: u2f.complete_authentication(challenge, response, self.u2f_facets) return True credentials = [] for device in self.get_u2f_devices(): if type(device) == AuthenticatorData: credentials.append(device.credential_data) else: credentials.append(create_credential_object(device)) self.webauthn_authentication_server.authenticate_complete( state=request.session["webauthn_authentication_state"], credentials=credentials, credential_id=websafe_decode(response["keyHandle"]), client_data=ClientData(websafe_decode(response["clientData"])), auth_data=AuthenticatorData(websafe_decode(response["authenticatorData"])), signature=websafe_decode(response["signatureData"]), ) except (InvalidSignature, InvalidKey, StopIteration): return False return True
def verify(user, key_handle, signature_data, client_data): challenge = session.pop('_u2f_challenge_') if not challenge: current_app.logger.error('Found no U2F challenge data in session.') return {'_error': True, 'message': 'security.u2f.missing_challenge_data'} data = { 'keyHandle': key_handle, 'signatureData': signature_data, 'clientData': client_data } device, c, t = complete_authentication(challenge, data, current_app.config['U2F_FACETS']) current_app.stats.count(name='u2f_verify') return {'key_handle': device['keyHandle'], 'counter': c, 'touch': t}
def perform_verify(self, cleaned_data): challenge = cleaned_data['u2f_challenge_signed'] data = cleaned_data['u2f_verify'] try: device, c, t = complete_authentication(challenge, data, [APP_ID]) except ValueError as e: raise forms.ValidationError("Authentication failed. {}.".format( str(e))) cleaned_data.update({ 'keyHandle': device['keyHandle'], 'touch': t, 'counter': c, 'token': self.get_token(device) })
def u2f_check_credentials(self, last_challenge, last_response): if self._u2f_get_device(): baseurl = self.env['ir.config_parameter'].sudo().get_param( 'web.base.url') try: device, c, t = complete_authentication(last_challenge, last_response, [baseurl]) except Exception: _logger.info('Exception during U2F authentication.', exc_info=True) raise U2FAuthenticationError() _logger.debug('Successful U2F auth with: %s, %s, %s', device, c, t) return True
def verify(user, key_handle, signature_data, client_data): challenge = session.pop('_u2f_challenge_') if not challenge: current_app.logger.error('Found no U2F challenge data in session.') return error_response(message=SecurityMsg.no_challenge) data = { 'keyHandle': key_handle, 'signatureData': signature_data, 'clientData': client_data } device, c, t = complete_authentication(challenge, data, current_app.config.u2f_facets) current_app.stats.count(name='u2f_verify') return {'key_handle': device['keyHandle'], 'counter': c, 'touch': t}
def validate_second_factor(self): response = json.loads(self.cleaned_data['response']) try: device, login_counter, _ = u2f.complete_authentication( self.sign_request, response) # TODO: store login_counter and verify it's increasing device = self.user.u2f_keys.get(key_handle=device['keyHandle']) device.last_used_at = timezone.now() device.save() del self.request.session['u2f_sign_request'] return True except ValueError: self.add_error('__all__', 'U2F validation failed -- bad signature.') return False
def authenticate(self, request, user, u2f_request, u2f_response): """Authenticate using U2F.""" try: device, counter, user_presence = u2f.complete_authentication(u2f_request, u2f_response) except (TypeError, ValueError, KeyError) as error: _LOGGER.info("U2F authentication failed with error: %r", error) return None u2f_device = user.u2f_devices.get(key_handle=device['keyHandle']) try: self.mark_device_used(u2f_device, counter) except ValueError: # Raise `PermissionDenied` to stop the authentication process and skip remaining backends. messages.error(request, self.counter_error_message) raise PermissionDenied("Counter didn't increase.") return user
def finish_login(request): username = request.data.get('username') response = request.data.get('response') user = get_object_or_404(User, username=username) auth_req = get_object_or_404(U2FAuthenticationRequest, user=user) json_auth_req = json.loads(auth_req.body) try: device, counter, _ = u2f.complete_authentication( json_auth_req, response) # TODO: store login_counter and verify it's increasing device = user.u2f_key.get(keyHandle=device['keyHandle']) device.last_used_at = timezone.now() device.save() auth_req.delete() except ValueError: return Response({"error": "U2F validation failed -- bad signature"}, status=HTTP_401_UNAUTHORIZED) token, _ = Token.objects.get_or_create(user=user) return Response({"token": token.key})
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')
def _complete_authentication(request, u2f_key): u2f_response = request.POST['response'] device, login_counter, _ = u2f.complete_authentication( request.session['u2f_request'], u2f_response) u2f_key.last_used_at = timezone.now() u2f_key.save()
def validate_response(self, request, challenge, response): try: u2f.complete_authentication(challenge, response, self.u2f_facets) except (InvalidSignature, InvalidKey, StopIteration): return False return True
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')