def get_multi_otp(self, count=0, epoch_start=0, epoch_end=0, curTime=None, timestamp=None): """ return a dictionary of multiple future OTP values of the HOTP/HMAC token :param count: how many otp values should be returned :type count: int :param epoch_start: not implemented :param epoch_end: not implemented :param curTime: Simulate the servertime :type curTime: datetime :param timestamp: Simulate the servertime :type timestamp: epoch time :return: tuple of status: boolean, error: text and the OTP dictionary """ otp_dict = {"type": "TOTP", "otp": {}} ret = False error = "No count specified" otplen = int(self.token.otplen) secretHOtp = self.token.get_otpkey() hmac2Otp = HmacOtp(secretHOtp, self.get_otp_count(), otplen, self.get_hashlib(self.hashlib)) if curTime: # datetime object provided for simulation tCounter = self._time2float(curTime) elif timestamp: # epoch time provided for simulation tCounter = int(timestamp) else: # use the current server time tCounter = self._time2float(datetime.datetime.now()) # we don't need to round here as we have alread float counter = int(((tCounter - self.timeshift) / self.timestep)) otp_dict["shift"] = self.timeshift otp_dict["timeStepping"] = self.timeshift if count > 0: error = "OK" for i in range(0, count): otpval = hmac2Otp.generate(counter=counter + i, inc_counter=False) timeCounter = ((counter + i) * self.timestep) + self.timeshift val_time = datetime.datetime.\ fromtimestamp(timeCounter).strftime("%Y-%m-%d %H:%M:%S") otp_dict["otp"][counter + i] = {'otpval': otpval, 'time': val_time} ret = True return ret, error, otp_dict
def test_04_no_2stepinit(self): set_policy( name="disallow_2step", action=["enrollHOTP=1", "delete"], # no 2step policy => disallow by default scope=SCOPE.ADMIN, ) with self.app.test_request_context( '/token/init', method='POST', data={ "type": "hotp", "genkey": "1", "2stepinit": "1", # will be ignored "2step_serversize": "5", "2step_clientsize": "16", "2step_difficulty": "17898", }, headers={'Authorization': self.at}): res = self.app.full_dispatch_request() self.assertTrue(res.status_code == 200, res) result = res.json.get("result") self.assertTrue(result.get("status") is True, result) self.assertTrue(result.get("value") is True, result) detail = res.json.get("detail") serial = detail.get("serial") otpkey_url = detail.get("otpkey", {}).get("value") otpkey_bin = binascii.unhexlify(otpkey_url.split("/")[2]) self.assertEqual(detail.get("rollout_state"), "") # Now try to authenticate otp_value = HmacOtp().generate(key=otpkey_bin, counter=1) with self.app.test_request_context('/validate/check', method='POST', data={ "serial": serial, "pass": otp_value }): res = self.app.full_dispatch_request() self.assertTrue(res.status_code == 200, res) result = res.json.get("result") self.assertEqual(result.get("status"), True) self.assertEqual(result.get("value"), True) delete_policy("disallow_2step")
def get_otp(self, current_time=None, do_truncation=True, time_seconds=None, challenge=None): """ get the next OTP value :param current_time: the current time, for which the OTP value should be calculated for. :type current_time: datetime object :param time_seconds: the current time, for which the OTP value should be calculated for (date +%s) :type: time_seconds: int, unix system time seconds :return: next otp value, and PIN, if possible :rtype: tuple """ otplen = int(self.token.otplen) secretHOtp = self.token.get_otpkey() hmac2Otp = HmacOtp(secretHOtp, self.get_otp_count(), otplen, self.get_hashlib(self.hashlib)) if time_seconds is None: time_seconds = self._time2float(datetime.datetime.now()) if current_time: time_seconds = self._time2float(current_time) # we don't need to round here as we have already float counter = int(((time_seconds - self.timeshift) / self.timestep)) otpval = hmac2Otp.generate(counter=counter, inc_counter=False, do_truncation=do_truncation, challenge=challenge) pin = self.token.get_pin() combined = "{0!s}{1!s}".format(otpval, pin) if get_from_config("PrependPin") == "True": combined = "{0!s}{1!s}".format(pin, otpval) return 1, pin, otpval, combined
def __init__(self, ocrasuite, key=None, security_object=None): """ Creates an OCRA Object that can be used to calculate OTP response or verify a response. :param ocrasuite: The ocrasuite description :type ocrasuite: str :param security_object: A privacyIDEA security object, that can be used to look up the key in the database :type security_object: secObject as defined in privacyidea.lib.crypto :param key: The HMAC Key :type key: binary :return: OCRA Object """ self.ocrasuite_obj = OCRASuite(ocrasuite) self.ocrasuite = ocrasuite self.key = key self.security_obj = security_object digits = self.ocrasuite_obj.truncation self.hmac_obj = HmacOtp(secObj=self.security_obj, digits=digits, hashfunc=SHA_FUNC.get(self.ocrasuite_obj.sha))
def resync(self, otp1, otp2, options=None): """ resync the token based on two otp values external method to do the resync of the token :param otp1: the first otp value :type otp1: string :param otp2: the second otp value :type otp2: string :param options: optional token specific parameters :type options: dict or None :return: counter or -1 if otp does not exist :rtype: int """ ret = False options = options or {} otplen = int(self.token.otplen) secretHOtp = self.token.get_otpkey() log.debug( "timestep: {0!r}, syncWindow: {1!r}, timeShift: {2!r}".format( self.timestep, self.timewindow, self.timeshift)) initTime = int(options.get('initTime', -1)) if initTime != -1: server_time = int(initTime) else: server_time = time.time() + self.timeshift counter = int((server_time / self.timestep) + 0.5) log.debug("counter (current time): {0:d}".format(counter)) oCount = self.get_otp_count() log.debug("tokenCounter: {0!r}".format(oCount)) log.debug("now checking window {0!s}, timeStepping {1!s}".format( self.timewindow, self.timestep)) # check 2nd value hmac2Otp = HmacOtp(secretHOtp, counter, otplen, self.get_hashlib(self.hashlib)) log.debug("{0!s} in otpkey: {1!s} ".format(otp2, secretHOtp)) res2 = hmac2Otp.checkOtp(otp2, int(self.timewindow / self.timestep), symetric=True) # TEST -remove the 10 log.debug("res 2: {0!r}".format(res2)) # check 1st value hmac2Otp = HmacOtp(secretHOtp, counter - 1, otplen, self.get_hashlib(self.hashlib)) log.debug("{0!s} in otpkey: {1!s} ".format(otp1, secretHOtp)) res1 = hmac2Otp.checkOtp(otp1, int(self.timewindow / self.timestep), symetric=True) # TEST -remove the 10 log.debug("res 1: {0!r}".format(res1)) if res1 < oCount: # A previous OTP value was used again! log.warning("a previous OTP value was used again! tokencounter: " "%i, presented counter %i" % (oCount, res1)) res1 = -1 if res1 != -1 and res1 + 1 == res2: # here we calculate the new drift/shift between the server time # and the tokentime tokentime = (res2 + 0.5) * self.timestep currenttime = server_time - self.timeshift new_shift = (tokentime - currenttime) log.debug("the counters {0!r} and {1!r} matched. New shift: {2!r}". format(res1, res2, new_shift)) self.add_tokeninfo('timeShift', new_shift) # The OTP value that was used for resync must not be used again! self.set_otp_count(res2 + 1) ret = True if ret is True: msg = "resync was successful" else: msg = "resync was not successful" log.debug("end. {0!s}: ret: {1!r}".format(msg, ret)) return ret
def check_otp(self, anOtpVal, counter=None, window=None, options=None): """ validate the token otp against a given otpvalue :param anOtpVal: the to be verified otpvalue :type anOtpVal: string :param counter: the counter state, that should be verified. For TOTP this is the unix system time (seconds) divided by 30/60 :type counter: int :param window: the counter +window (sec), which should be checked :type window: int :param options: the dict, which could contain token specific info :type options: dict :return: the counter or -1 :rtype: int """ otplen = int(self.token.otplen) options = options or {} secretHOtp = self.token.get_otpkey() # oldCounter we have to remove one, as the normal otp handling will # increment # TODO: Migration: Really? # oCount = self.get_otp_count() - 1 oCount = self.get_otp_count() inow = int(time.time()) window = window or self.timewindow initTime = int(options.get('initTime', -1)) if initTime != -1: server_time = int(initTime) else: server_time = time.time() + self.timeshift # If we have a counter from the parameter list if not counter: # No counter, so we take the current token_time counter = self._time2counter(server_time, timeStepping=self.timestep) otime = self._getTimeFromCounter(oCount, timeStepping=self.timestep) ttime = self._getTimeFromCounter(counter, timeStepping=self.timestep) hmac2Otp = HmacOtp(secretHOtp, counter, otplen, self.get_hashlib(self.hashlib)) res = hmac2Otp.checkOtp(anOtpVal, int(window / self.timestep), symetric=True) if res != -1 and oCount != 0 and res <= oCount: log.warning("a previous OTP value was used again! former " "tokencounter: %i, presented counter %i" % (oCount, res)) res = -1 return res if -1 == res: # _autosync: test if two consecutive otps have been provided res = self._autosync(hmac2Otp, anOtpVal) if res != -1: # on success, we have to save the last attempt self.set_otp_count(res) # We could also store it temporarily # self.auth_details["matched_otp_counter"] = res # here we calculate the new drift/shift between the server time # and the tokentime tokentime = self._counter2time(res, self.timestep) tokenDt = datetime.datetime.fromtimestamp(tokentime / 1.0) nowDt = datetime.datetime.fromtimestamp(inow / 1.0) lastauth = self._counter2time(oCount, self.timestep) lastauthDt = datetime.datetime.fromtimestamp(lastauth / 1.0) log.debug("last auth : {0!r}".format(lastauthDt)) log.debug("tokentime : {0!r}".format(tokenDt)) log.debug("now : {0!r}".format(nowDt)) log.debug("delta : {0!r}".format((tokentime - inow))) new_shift = (tokentime - inow) log.debug("the counter {0!r} matched. New shift: {1!r}".format( res, new_shift)) self.add_tokeninfo('timeShift', new_shift) return res
def test_06_force_totp_parameters(self): set_policy( name="force_2step", action=["totp_2step=force", "enrollTOTP=1", "delete"], scope=SCOPE.ADMIN, ) set_policy( name="2step_params", action=[ "totp_2step_difficulty=12345", "totp_2step_serversize=33", "totp_2step_clientsize=11" ], scope=SCOPE.ENROLL, ) with self.app.test_request_context( '/token/init', method='POST', data={ "type": "totp", "genkey": "1", "2stepinit": "0", # will be forced nevertheless "2step_serversize": "3", "2step_clientsize": "4", "2step_difficulty": "33333", "timeStep": "60", "hashlib": "sha512", "otplen": "8", }, headers={'Authorization': self.at}): res = self.app.full_dispatch_request() self.assertTrue(res.status_code == 200, res) result = res.json.get("result") self.assertTrue(result.get("status") is True, result) self.assertTrue(result.get("value") is True, result) detail = res.json.get("detail") serial = detail.get("serial") otpkey_url = detail.get("otpkey", {}).get("value") server_component = binascii.unhexlify(otpkey_url.split("/")[2]) google_url = detail["googleurl"]["value"] self.assertIn('2step_difficulty=12345', google_url) self.assertIn('2step_salt=11', google_url) self.assertIn('2step_output=64', google_url) # Authentication does not work yet! wrong_otp_value = HmacOtp(digits=8, hashfunc=hashlib.sha512).generate( key=server_component, counter=int(time.time() // 60)) with self.app.test_request_context('/validate/check', method='POST', data={ "serial": serial, "pass": wrong_otp_value }): res = self.app.full_dispatch_request() self.assertTrue(res.status_code == 200, res) result = res.json self.assertTrue(result.get("result").get("status")) self.assertFalse(result.get("result").get("value")) self.assertEqual( result.get("detail").get("message"), u'matching 1 tokens, Token is disabled') client_component = b"wrongsize" # 9 bytes hex_client_component = binascii.hexlify(client_component) # Supply a client secret of incorrect size with self.app.test_request_context('/token/init', method='POST', data={ "type": "totp", "serial": serial, "otpkey": hex_client_component, }, headers={'Authorization': self.at}): res = self.app.full_dispatch_request() self.assertTrue(res.status_code == 400, res) result = res.json.get("result") self.assertFalse(result.get("status")) client_component = b"correctsize" # 11 bytes hex_client_component = binascii.hexlify(client_component) # Now doing the correct 2nd step with self.app.test_request_context( '/token/init', method='POST', data={ "type": "totp", "serial": serial, "otpkey": hex_client_component, "2step_serversize": "3", # will have no effect "2step_clientsize": "4", "2step_difficulty": "33333" }, headers={'Authorization': self.at}): res = self.app.full_dispatch_request() self.assertTrue(res.status_code == 200, res) result = res.json.get("result") self.assertTrue(result.get("status") is True, result) self.assertTrue(result.get("value") is True, result) detail = res.json.get("detail") otpkey_url = detail.get("otpkey", {}).get("value") otpkey = otpkey_url.split("/")[2] # Now try to authenticate otpkey_bin = binascii.unhexlify(otpkey) otp_value = HmacOtp(digits=8, hashfunc=hashlib.sha512).generate( key=otpkey_bin, counter=int(time.time() // 60)) with self.app.test_request_context('/validate/check', method='POST', data={ "serial": serial, "pass": otp_value }): res = self.app.full_dispatch_request() self.assertTrue(res.status_code == 200, res) result = res.json.get("result") self.assertEqual(result.get("status"), True) self.assertEqual(result.get("value"), True) # Check serversize self.assertEqual(len(server_component), 33) # Check that the OTP key is what we expected it to be expected_secret = pbkdf2_hmac('sha1', binascii.hexlify(server_component), client_component, 12345, 64) self.assertEqual(otpkey_bin, expected_secret) with self.app.test_request_context('/token/' + serial, method='DELETE', headers={'Authorization': self.at}): res = self.app.full_dispatch_request() self.assertTrue(res.status_code == 200, res) delete_policy("force_2step") delete_policy("2step_params")
def test_05_init_totp_token(self): set_policy( name="allow_2step", action=["totp_2step=allow", "enrollTOTP=1", "delete"], scope=SCOPE.ADMIN, ) with self.app.test_request_context('/token/init', method='POST', data={ "type": "totp", "genkey": "1", "2stepinit": "1" }, headers={'Authorization': self.at}): res = self.app.full_dispatch_request() self.assertTrue(res.status_code == 200, res) result = res.json.get("result") self.assertTrue(result.get("status") is True, result) self.assertTrue(result.get("value") is True, result) detail = res.json.get("detail") serial = detail.get("serial") otpkey_url = detail.get("otpkey", {}).get("value") server_component = binascii.unhexlify(otpkey_url.split("/")[2]) google_url = detail["googleurl"]["value"] self.assertIn('2step_difficulty=10000', google_url) self.assertIn('2step_salt=8', google_url) self.assertIn('2step_output=20', google_url) self.assertEqual(detail['2step_difficulty'], 10000) self.assertEqual(detail['2step_salt'], 8) self.assertEqual(detail['2step_output'], 20) client_component = b"VRYSECRT" checksum = hashlib.sha1(client_component).digest()[:4] base32check_client_component = base64.b32encode( checksum + client_component).strip(b"=") # Try to do a 2stepinit on a second step will raise an error with self.app.test_request_context('/token/init', method='POST', data={ "type": "totp", "2stepinit": "1", "serial": serial, "otpkey": base32check_client_component, "otpkeyformat": "base32check" }, headers={'Authorization': self.at}): res = self.app.full_dispatch_request() self.assertEqual(res.status_code, 400) result = res.json.get("result") self.assertIn( '2stepinit is only to be used in the first initialization step', result.get("error").get("message")) # Invalid base32check will raise an error with self.app.test_request_context( '/token/init', method='POST', data={ "type": "totp", "2stepinit": "1", "serial": serial, "otpkey": b"A" + base32check_client_component[1:], "otpkeyformat": "base32check" }, headers={'Authorization': self.at}): res = self.app.full_dispatch_request() self.assertEqual(res.status_code, 400) result = res.json.get("result") self.assertIn('Malformed base32check data: Incorrect checksum', result.get("error").get("message")) # Authentication does not work yet! wrong_otp_value = HmacOtp().generate(key=server_component, counter=int(time.time() // 30)) with self.app.test_request_context('/validate/check', method='POST', data={ "serial": serial, "pass": wrong_otp_value }): res = self.app.full_dispatch_request() self.assertTrue(res.status_code == 200, res) result = res.json self.assertTrue(result.get("result").get("status")) self.assertFalse(result.get("result").get("value")) self.assertEqual( result.get("detail").get("message"), u'matching 1 tokens, Token is disabled') # Now doing the correct 2nd step with self.app.test_request_context('/token/init', method='POST', data={ "type": "totp", "serial": serial, "otpkey": base32check_client_component, "otpkeyformat": "base32check" }, headers={'Authorization': self.at}): res = self.app.full_dispatch_request() self.assertTrue(res.status_code == 200, res) result = res.json.get("result") self.assertTrue(result.get("status") is True, result) self.assertTrue(result.get("value") is True, result) detail = res.json.get("detail") otpkey_url = detail.get("otpkey", {}).get("value") otpkey = otpkey_url.split("/")[2] self.assertNotIn('2step', detail) # Now try to authenticate otpkey_bin = binascii.unhexlify(otpkey) otp_value = HmacOtp().generate(key=otpkey_bin, counter=int(time.time() // 30)) with self.app.test_request_context('/validate/check', method='POST', data={ "serial": serial, "pass": otp_value }): res = self.app.full_dispatch_request() self.assertTrue(res.status_code == 200, res) result = res.json.get("result") self.assertEqual(result.get("status"), True) self.assertEqual(result.get("value"), True) # Check that the OTP key is what we expected it to be expected_secret = pbkdf2_hmac('sha1', binascii.hexlify(server_component), client_component, 10000, 20) self.assertEqual(otpkey_bin, expected_secret) with self.app.test_request_context('/token/' + serial, method='DELETE', headers={'Authorization': self.at}): res = self.app.full_dispatch_request() self.assertTrue(res.status_code == 200, res) delete_policy("allow_2step")
def test_03_custom_parameters(self): set_policy( name="enrollhotp", action=["enrollHOTP=1", "delete", "hotp_2step=allow"], scope=SCOPE.ADMIN, ) with self.app.test_request_context( '/token/init', method='POST', data={ "type": "hotp", "genkey": "1", "2stepinit": "1", "2step_serversize": "5", "2step_clientsize": "16", "2step_difficulty": "17898", "hashlib": "sha512", # force 64-byte secret "otplen": "8", }, headers={'Authorization': self.at}): res = self.app.full_dispatch_request() self.assertTrue(res.status_code == 200, res) result = res.json.get("result") self.assertTrue(result.get("status") is True, result) self.assertTrue(result.get("value") is True, result) detail = res.json.get("detail") serial = detail.get("serial") otpkey_url = detail.get("otpkey", {}).get("value") server_component = binascii.unhexlify(otpkey_url.split("/")[2]) client_component = b"wrongsize0" # 10 bytes hex_client_component = binascii.hexlify(client_component) # Supply a client secret of incorrect size with self.app.test_request_context('/token/init', method='POST', data={ "type": "hotp", "serial": serial, "otpkey": hex_client_component, }, headers={'Authorization': self.at}): res = self.app.full_dispatch_request() self.assertTrue(res.status_code == 400, res) result = res.json.get("result") self.assertFalse(result.get("status")) client_component = b"correctsizeABCDE" # 16 bytes hex_client_component = binascii.hexlify(client_component) # Now doing the correct 2nd step with self.app.test_request_context( '/token/init', method='POST', data={ "type": "hotp", "serial": serial, "otpkey": hex_client_component, "2step_serversize": "3", # will have no effect "2step_clientsize": "4", "2step_difficulty": "33333" }, headers={'Authorization': self.at}): res = self.app.full_dispatch_request() self.assertTrue(res.status_code == 200, res) result = res.json.get("result") self.assertTrue(result.get("status") is True, result) self.assertTrue(result.get("value") is True, result) detail = res.json.get("detail") otpkey_url = detail.get("otpkey", {}).get("value") otpkey = otpkey_url.split("/")[2] # Now try to authenticate otpkey_bin = binascii.unhexlify(otpkey) otp_value = HmacOtp(digits=8, hashfunc=hashlib.sha512).generate(key=otpkey_bin, counter=1) with self.app.test_request_context('/validate/check', method='POST', data={ "serial": serial, "pass": otp_value }): res = self.app.full_dispatch_request() self.assertTrue(res.status_code == 200, res) result = res.json.get("result") self.assertEqual(result.get("status"), True) self.assertEqual(result.get("value"), True) # Check serversize self.assertEqual(len(server_component), 5) # Check that the OTP key is what we expected it to be expected_secret = pbkdf2_hmac('sha1', binascii.hexlify(server_component), client_component, 17898, 64) self.assertEqual(otpkey_bin, expected_secret) with self.app.test_request_context('/token/' + serial, method='DELETE', headers={'Authorization': self.at}): res = self.app.full_dispatch_request() self.assertTrue(res.status_code == 200, res) delete_policy("enrollhotp")
def test_01_init_token(self): with self.app.test_request_context('/token/init', method='POST', data={ "type": "hotp", "genkey": "1", "2stepinit": "1" }, headers={'Authorization': self.at}): res = self.app.full_dispatch_request() self.assertTrue(res.status_code == 200, res) result = json.loads(res.data).get("result") self.assertTrue(result.get("status") is True, result) self.assertTrue(result.get("value") is True, result) detail = json.loads(res.data).get("detail") serial = detail.get("serial") otpkey_url = detail.get("otpkey", {}).get("value") server_component = otpkey_url.split("/")[2] client_component = "AAAAAAAA" # Try to do a 2stepinit on a second step will raise an error with self.app.test_request_context('/token/init', method='POST', data={ "type": "hotp", "2stepinit": "1", "serial": serial, "otpkey": client_component }, headers={'Authorization': self.at}): res = self.app.full_dispatch_request() self.assertTrue(res.status_code == 400, res) result = json.loads(res.data).get("result") self.assertEqual( result.get("error").get("message"), u'ERR905: 2stepinit is only to be used ' u'in the first initialization step.') # Now doing the correct 2nd step with self.app.test_request_context('/token/init', method='POST', data={ "type": "hotp", "serial": serial, "otpkey": client_component }, headers={'Authorization': self.at}): res = self.app.full_dispatch_request() self.assertTrue(res.status_code == 200, res) result = json.loads(res.data).get("result") self.assertTrue(result.get("status") is True, result) self.assertTrue(result.get("value") is True, result) detail = json.loads(res.data).get("detail") otpkey_url = detail.get("otpkey", {}).get("value") otpkey = otpkey_url.split("/")[2] # Now try to authenticate otpkey_bin = binascii.unhexlify(otpkey) otp_value = HmacOtp().generate(key=otpkey_bin, counter=1) with self.app.test_request_context('/validate/check', method='POST', data={ "serial": serial, "pass": otp_value }): res = self.app.full_dispatch_request() self.assertTrue(res.status_code == 200, res) result = json.loads(res.data).get("result") self.assertEqual(result.get("status"), True) self.assertEqual(result.get("value"), True) with self.app.test_request_context('/token/' + serial, method='DELETE', headers={'Authorization': self.at}): res = self.app.full_dispatch_request() self.assertTrue(res.status_code == 200, res)