def test_user_change_password(journalist_app, test_journo): """Test that a journalist can successfully login after changing their password""" with journalist_app.test_client() as app: _login_user(app, test_journo) # change password new_pw = 'another correct horse battery staply long password' assert new_pw != test_journo['password'] # precondition app.post('/account/new-password', data=dict(password=new_pw, current_password=test_journo['password'], token=TOTP(test_journo['otp_secret']).now())) # logout app.get('/logout') # start a new client/context to be sure we've cleared the session with journalist_app.test_client() as app: # login with new credentials should redirect to index page with InstrumentedApp(journalist_app) as ins: resp = app.post('/login', data=dict(username=test_journo['username'], password=new_pw, token=TOTP( test_journo['otp_secret']).now())) ins.assert_redirects(resp, '/')
def test_google_authenticator_delete(self): from talos.models import ValidationToken from talos.models import OneTimePasswordCredentialDirectory from talos.models import OneTimePasswordCredential from pyotp import TOTP self.create_user() self.login() self.add_evidence_sms() response = self.client.post(self.request_url, {}, format='json') self.assertResponseStatus(response, status.HTTP_403_FORBIDDEN) self.add_evidence_google() response = self.client.post(self.request_url, {}, format='json') self.assertResponseStatus(response, status.HTTP_200_OK) validation_token = ValidationToken.objects.last() self.assertEqual(validation_token.principal, self.principal) self.assertEqual(validation_token.type, 'otp_delete') sms_otp_directory = OneTimePasswordCredentialDirectory.objects.get( code='onetimepassword_internal_phone_sms') google_otp_directory = OneTimePasswordCredentialDirectory.objects.get( code='onetimepassword_internal_google_authenticator') sms_otp_credential = OneTimePasswordCredential.objects.get( principal=self.principal, directory=sms_otp_directory) google_otp_credential = OneTimePasswordCredential.objects.get( principal=self.principal, directory=google_otp_directory) totp = TOTP(sms_otp_credential.salt.decode()) sms_code = totp.now() totp = TOTP(google_otp_credential.salt) google_code = totp.now() data = { 'otp_code': sms_code, 'google_otp_code': google_code, 'password': self.password, 'token': validation_token.secret } response = self.client.post(self.confirm_url, data, format='json') self.assertResponseStatus(response, status.HTTP_200_OK) self.assertEqual( OneTimePasswordCredential.objects.filter( directory=google_otp_directory).count(), 0)
def is_valid(token): """Validate a token.""" try: TOTP(token).now() return True except (binascii.Error, ValueError): return False
def test_change_email_when_success(self): from pyotp import TOTP self.create_user() self.login() self.add_evidence_sms() self.generate_sms_code(self.principal) email_to_change = "*****@*****.**" validation_token = ValidationToken.objects.create( identifier='email', identifier_value=email_to_change, principal=self.principal, type='email_change', ) code = (OneTimePasswordCredential.objects.last()) totp = TOTP(code.salt.decode()) data = { 'otp_code': totp.now(), 'password': self.password, 'secret': validation_token.secret } response = self.client.put(self.email_change_insecure_url, data=data) changed_pricipal = Principal.objects.last() self.assertResponseStatus(response) self.assertEquals(changed_pricipal.email, email_to_change)
def test_totp_reuse_protections(journalist_app, test_journo, hardening): """Ensure that logging in twice with the same TOTP token fails. Also, ensure that last_token is updated accordingly. """ with totp_window(): token = TOTP(test_journo["otp_secret"]).now() with journalist_app.test_client() as app: login_user(app, test_journo) resp = app.get(url_for("main.logout"), follow_redirects=True) assert resp.status_code == 200 with journalist_app.app_context(): journo = Journalist.query.get(test_journo["id"]) assert journo.last_token == token with journalist_app.test_client() as app: resp = app.post( url_for("main.login"), data=dict(username=test_journo["username"], password=test_journo["password"], token=token), ) assert resp.status_code == 200 text = resp.data.decode("utf-8") assert "Login failed" in text
def main(): token = input("Please input your token found on your android: ") data = list(bytes.fromhex(token)) assert len(data) == len(masks) # xor every byte with masks for i in range(0, len(data)): b = data[i] m = masks[i] b = b ^ m data[i] = b data_str = bytes(data).decode() secret_hex = data_str[:40] serial = data_str[40:] # use base32 to encode the first 40 bytes to be used in totp secret = base64.b32encode(bytes.fromhex(secret_hex)).decode() from pyotp import TOTP totp = TOTP(secret, digits=8) key = totp.now() print(secret) print(serial) print(key) url = "otpauth://totp/{0}:{0}?secret={1}&issuer={0}&digits=8".format( serial, secret) print(url)
def test_password_change_secure(self): from talos.models import OneTimePasswordCredential from talos.models import Principal from pyotp import TOTP self.create_user() self.login() self.add_evidence_google() self.assertEqual(OneTimePasswordCredential.objects.all().count(), 1) google_otp_credential = OneTimePasswordCredential.objects.last() secret = google_otp_credential.salt totp = TOTP(secret) google_otp_code = totp.now() data = { 'password': self.password, 'new_password': '******', 'otp_code': google_otp_code } response = self.client.put(self.url, data, format='json') self.assertResponseStatus(response, status.HTTP_200_OK) principal = Principal.objects.last() self.assertFalse(principal.check_password(self.password)) self.assertTrue(principal.check_password('1234567'))
def test_change_email_when_wrong_password(self): from pyotp import TOTP self.create_user() self.login() self.add_evidence_sms() self.generate_sms_code(self.principal) validation_token = ValidationToken.objects.create( identifier='email', identifier_value=self.email, principal=self.principal, type='email_change', ) code = (OneTimePasswordCredential.objects.last()) totp = TOTP(code.salt.decode()) data = { 'sms_code': totp.now(), 'password': '******', 'secret': validation_token.secret } response = self.client.put(self.email_change_insecure_url, data=data) self.assertResponseStatus(response, status.HTTP_400_BAD_REQUEST) self.assertListEqual( response.data.get('error').get('password'), ['password_invalid']) self.assertListEqual( response.data.get('details').get('password'), ['Password is incorrect'])
def test_valid_user_can_get_an_api_token(journalist_app, test_journo): with journalist_app.test_client() as app: valid_token = TOTP(test_journo["otp_secret"]).now() response = app.post( url_for("api.get_token"), data=json.dumps( { "username": test_journo["username"], "passphrase": test_journo["password"], "one_time_code": valid_token, } ), headers=get_api_headers(), ) assert response.json["journalist_uuid"] == test_journo["uuid"] assert ( isinstance( Journalist.validate_api_token_and_get_user(response.json["token"]), Journalist ) is True ) assert response.status_code == 200 assert response.json["journalist_first_name"] == test_journo["first_name"] assert response.json["journalist_last_name"] == test_journo["last_name"] assert_valid_timestamp(response.json["expiration"])
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 is_correct_two_factor_code(self, code: str) -> bool: """Verify that a TOTP/backup code is correct.""" if not self.two_factor_secret: raise ValueError("User does not have 2FA enabled") totp = TOTP(self.two_factor_secret) code = code.strip().replace(" ", "").lower() # some possible user input (such as unicode) can cause an error in the totp # library, catch that and treat it the same as an invalid code try: is_valid_code = totp.verify(code) except TypeError: is_valid_code = False if is_valid_code: return True elif self.two_factor_backup_codes and code in self.two_factor_backup_codes: # Need to set the attribute so SQLAlchemy knows it changed self.two_factor_backup_codes = [ backup_code for backup_code in self.two_factor_backup_codes if backup_code != code ] return True return False
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 user = ObjectProxy(object_dn) factor_method = self.get_method_from_user(user) user_settings = self.__settings[ user.uuid] if user.uuid in self.__settings else {} if factor_method == "otp": totp = TOTP(user_settings.get('otp_secret')) return totp.verify(key) elif factor_method == "u2f": devices = [ DeviceRegistration.wrap(device) for device in user_settings.get('_u2f_devices_', []) ] challenge = user_settings.pop('_u2f_challenge_') data = loads(key) c, t = verify_authenticate(devices, challenge, data, [self.facet]) return {'touch': t, 'counter': c} elif factor_method is None: return True return False
def get_credentials(scss_dict): """Makes an API call to SCSS, returns credentials. Keyword Arguments: scss_dict - a dict() object containing the following keys with the correct corresponding values: api_key, otp, userid and url. Output: data - str(), the data returned from scss.""" log = getLogger(__name__) # Setting variables based on the data passed by the scss_dict. api_key = scss_dict['api_key'] otp = TOTP(scss_dict['otp']).now() userid = scss_dict['userid'] url = scss_dict['url'] user_agent = 'scss-client' # Building HTTP headers. headers = { 'User-Agent': user_agent, 'api-key': api_key, 'totp': otp, 'userid': userid } # Connecting to SCSS. If SSL verification fails, change verify to # false. This isn't recommended (as it defeats the purpose of # verification), but it will make the code work in an emergency. scss_response = post(url, headers=headers) if scss_response.status_code == 200: data = scss_response.json().get('gpg_pass') log.debug('Credentials successfully retrieved from SCSS') else: log.error('Unable to retrieve credentials from SCSS. The HTTP ' 'error code is %s', scss_response.status_code) exit(1) return data
def auth(): if not current_user.can_admin: abort(404) form = TOTPForm() try: user_secret = UserMetadata.get((UserMetadata.uid == current_user.uid) & (UserMetadata.key == 'totp_secret')) except UserMetadata.DoesNotExist: return engine.get_template('admin/totp.html').render({ 'authform': form, 'error': _('No TOTP secret found.') }) if form.validate_on_submit(): totp = TOTP(user_secret.value) if totp.verify(form.totp.data): session['apriv'] = time.time() return redirect(url_for('admin.index')) else: return engine.get_template('admin/totp.html').render({ 'authform': form, 'error': _('Invalid or expired token.') }) return engine.get_template('admin/totp.html').render({ 'authform': form, 'error': None })
def index(): if request.method == "POST": totp = TOTP(app.config["TOTP_SECRET"]) if "authcode" not in request.form: flash("Missing OTP", 'error') return redirect(request.url) if not totp.verify(request.form["authcode"]): flash("Incorrect OTP", "error") return redirect(request.url) if not request.files: flash("No files field", "error") return redirect(request.url) files = (f for f in request.files.getlist("files") if f.filename != "") if not files: flash("No files specified", "error") return redirect(request.url) for file in files: file.save(os.path.join(app.config["UPLOAD_DIR"], file.filename)) flash("Files uploaded correctly!", "success") return redirect("/drop") return render_template("drop.html")
def otp(self, otp: str) -> None: updated = False # Some sites give the secret in chunks split by spaces for easy reading # lets strip those as they'll produce an invalid secret. otp = otp.replace(" ", "") if not otp and self._otp: # Delete existing self._otp = None self._element.delete_custom_property("otp") self.updated() elif self._otp and self._otp.secret != otp: # Changing an existing OTP self._otp.secret = otp updated = True elif otp: # Creating brand new OTP. self._otp = TOTP(otp, issuer=self.name) updated = True if updated: self._element.set_custom_property("otp", self._otp.provisioning_uri()) self.updated()
def __init__(self, log_to_file, bank_channel_id, bank_role_id, guild_server_id, totp_secret): super().__init__() self._log_to_file = log_to_file self._bank_channel_id = bank_channel_id self._bank_role_id = bank_role_id self._guild_server_id = guild_server_id self._totp = TOTP(totp_secret)
def __init__(self, log_to_file, bank_channel_id, bank_role_id, totp_secret): super().__init__() self._log_to_file = log_to_file self._bank_channel_id = bank_channel_id self._bank_role_id = bank_role_id self._totp = TOTP(totp_secret) self.bank_roles = [] self.bank_channels = []
def get_google_url(seed, hostname): return "https://chart.googleapis.com/chart?" + urlencode( { "chs": "200x200", "chld": "M|0", "cht": "qr", "chl": TOTP(seed).provisioning_uri(hostname) })
def get_otpauth_url(serial: str, secret: str) -> str: """ Get the OTPAuth URL for the serial/secret pair https://github.com/google/google-authenticator/wiki/Key-Uri-Format """ totp = TOTP(secret, digits=8) return totp.provisioning_uri(serial, issuer_name="Blizzard")
def generatePin(data): # 3) INORDER TO GENERATE OTP FROM PYOTP MODULE WE NEED A BASE32 KEY encodedData = b32encode(data.encode("utf-8")) # 4) LET'S CONVERT THE EMAIL ID TO BASE32 SINCE EMAIL ID IS UNIQUE # 5) PASS THE CONVERTED STRING TO TOTP() TO GENERATE OTP otp = TOTP(encodedData.decode("utf-8")).now() # 6) GENERATE OTP return otp
def create(self): """ Create a tfa code """ try: self._totp = TOTP(self._token) self._secret_code = self._totp.now() except Exception as e: Logger.error("Couldn't generate two factor code : %s" % str(e))
def main(): parser = ArgumentParser(path.basename(__file__)) parser.add_argument('--source-url', required=True) parser.add_argument('--journo-url', required=True) args = parser.parse_args() totp = TOTP('JHCOGO7VCER3EJ4L') auth = UserPassOtp('journalist', 'WEjwn8ZyczDhQSK24YKM8C9a', totp.now()) client = Client(args.journo_url, auth)
def set_totp(self, totp_secret): """Set the secret for generating MFA tokens to authorize Args: totp_secret (str): The secret token set on an Application in the Gem Developer Console. """ self.totp = TOTP(totp_secret) return self
def __init__(self, master=None): super().__init__(master) self.master = master self.totp = TOTP(get_secret()) self.totp_qr = get_secret_qr(self.totp) self.create_widgets() self.pack() self.update_pin_label() self.set_scheduler()
def login(self, secret, username, password): r = self.get("session") data = get_form_data(r.content, action="/session") data['login'] = username data['password'] = password r = self.post("session", data=data) data = get_form_data(r.content, action="/sessions/two-factor") data['otp'] = TOTP(secret).now() r = self.post("sessions/two-factor", data=data)
def create(self): """ Create a tfa code """ try: self.totp = TOTP(self.secret_code) self.password = self.totp.now() except Exception as e: logging.error("Couldn't generate two factor code : %s" % str(e))
def find_and_submit_form(self, soup, email, password, mfa_secret=None): error = soup.find(id="message_error") if error: message = error.get_text() # Enter the characters as they are given in the challenge. raise Exception(message) form = soup.find(id="ap_signin_form") if not form: mfa_form = soup.find(id="auth-mfa-form") if mfa_form: raise Exception("accounts with Amazon MFA not supported") data = {'metadata1': self.session()._metadata1_generator.generate()} for field in form.find_all('input'): name = field.get('name') if not name: continue value = field.get('value') data[name] = value if "guess" in data: if not self.session()._captcha_solver: raise Exception("captcha solver required") captcha = soup.find(id="ap_captcha_img") img = captcha.find("img") src = img.get("src") guess_uuid = self.session()._captcha_solver.solve(url=src) while True: guess = self.session()._captcha_solver.result(guess_uuid) if guess is None: time.sleep(5) else: data["guess"] = guess break if "tokenCode" in data and mfa_secret: data['tokenCode'] = TOTP(mfa_secret).now() overrides = { "password": password, "email": email, } for k, v in data.items(): if v is None: _v = overrides.get(k) if _v: data[k] = _v return self.session()._post(form.get("action"), data=data)
def _login_user(app, user_dict): resp = app.post('/login', data={ 'username': user_dict['username'], 'password': user_dict['password'], 'token': TOTP(user_dict['otp_secret']).now() }, follow_redirects=True) assert resp.status_code == 200 assert hasattr(g, 'user') # ensure logged in
def journalist_api_token(journalist_app, test_journo): with journalist_app.test_client() as app: valid_token = TOTP(test_journo['otp_secret']).now() response = app.post(url_for('api.get_token'), data=json.dumps( {'username': test_journo['username'], 'passphrase': test_journo['password'], 'one_time_code': valid_token}), headers=utils.api_helper.get_api_headers()) return response.json['token']