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 = "%s%s" % (otpval, pin) if get_from_config("PrependPin") == "True": combined = "%s%s" % (pin, otpval) return 1, pin, otpval, combined
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 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 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 __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: basestring :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 = str(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))
class OCRA(object): 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 create_data_input(self, question, pin=None, pin_hash=None, counter=None, timesteps=None): """ Create the data_input to be used in the HMAC function In case of QN the question would be "111111" In case of QA the question would be "123ASD" In case of QH the question would be "BEEF" The question is transformed internally. :param question: The question can be :type question: str :param pin_hash: The hash of the pin :type pin_hash: basestring (hex) :param timesteps: timestemps :type timesteps: hex string :return: data_input :rtype: bytes """ # In case the ocrasuite comes as a unicode (like from the webui) we # need to convert it! data_input = to_bytes(self.ocrasuite) + b'\0' # Check for counter if self.ocrasuite_obj.counter == "C": if counter: counter = int(counter) counter = struct.pack('>Q', int(counter)) data_input += counter else: raise Exception( "The ocrasuite {0!s} requires a counter".format( self.ocrasuite)) # Check for Question if self.ocrasuite_obj.challenge_type == "QN": # question contains only numeric values hex_q = '{0:x}'.format(int(question)) hex_q += '0' * (len(hex_q) % 2) bin_q = binascii.unhexlify(hex_q) bin_q += b'\x00' * (128 - len(bin_q)) data_input += bin_q elif self.ocrasuite_obj.challenge_type == "QA": # question contains alphanumeric characters bin_q = to_bytes(question) bin_q += b'\x00' * (128 - len(bin_q)) data_input += bin_q elif self.ocrasuite_obj.challenge_type == "QH": # qustion contains hex values bin_q = binascii.unhexlify(question) bin_q += b'\x00' * (128 - len(bin_q)) data_input += bin_q # in case of PIN if self.ocrasuite_obj.signature_type == "P": if pin_hash: data_input += binascii.unhexlify(pin_hash) elif pin: pin_hash = SHA_FUNC.get(self.ocrasuite_obj.signature_hash)( to_bytes(pin)).digest() data_input += pin_hash else: raise Exception("The ocrasuite {0!s} requires a PIN!".format( self.ocrasuite)) elif self.ocrasuite_obj.signature_type == "T": if not timesteps: raise Exception( "The ocrasuite {0!s} requires timesteps".format( self.ocrasuite)) # In case of Time timesteps = int(timesteps, 16) timesteps = struct.pack('>Q', int(timesteps)) data_input += timesteps elif self.ocrasuite_obj.signature_type == "S": # pragma: no cover # In case of session # TODO: Session not yet implemented raise NotImplementedError("OCRA Session not implemented, yet.") return data_input def get_response(self, question, pin=None, pin_hash=None, counter=None, timesteps=None): """ Create an OTP response from the given input values. :param question: :param pin: :param pin_hash: :param counter: :return: """ data_input = self.create_data_input(question, pin=pin, pin_hash=pin_hash, counter=counter, timesteps=timesteps) r = self.hmac_obj.generate(key=self.key, challenge=binascii.hexlify(data_input)) return r def check_response(self, response, question=None, pin=None, pin_hash=None, counter=None, timesteps=None): """ Check the given *response* if it is the correct response to the challenge/question. :param response: :param question: :param pin: :param pin_hash: :param counter: :param timesteps: :return: """ r = self.get_response(question, pin=pin, pin_hash=pin_hash, counter=counter, timesteps=timesteps) if r == response: return 1 else: return -1
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: %r, syncWindow: %r, timeShift: %r" % (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): %i" % counter) oCount = self.get_otp_count() log.debug("tokenCounter: %r" % oCount) log.debug("now checking window %s, timeStepping %s" % (self.timewindow, self.timestep)) # check 2nd value hmac2Otp = HmacOtp(secretHOtp, counter, otplen, self.get_hashlib(self.hashlib)) log.debug("%s in otpkey: %s " % (otp2, secretHOtp)) res2 = hmac2Otp.checkOtp(otp2, int(self.timewindow / self.timestep), symetric=True) # TEST -remove the 10 log.debug("res 2: %r" % res2) # check 1st value hmac2Otp = HmacOtp(secretHOtp, counter - 1, otplen, self.get_hashlib(self.hashlib)) log.debug("%s in otpkey: %s " % (otp1, secretHOtp)) res1 = hmac2Otp.checkOtp(otp1, int(self.timewindow / self.timestep), symetric=True) # TEST -remove the 10 log.debug("res 1: %r" % 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 %r and %r matched. New shift: %r" % (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. %s: ret: %r" % (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) # and we reset the fail counter self.reset() # 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 : %r" % lastauthDt) log.debug("tokentime : %r" % tokenDt) log.debug("now : %r" % nowDt) log.debug("delta : %r" % (tokentime - inow)) new_shift = (tokentime - inow) log.debug("the counter %r matched. New shift: %r" % (res, new_shift)) self.add_tokeninfo('timeShift', new_shift) return res
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_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)
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")
class OCRA(object): 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: basestring :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 = str(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 create_data_input(self, question, pin=None, pin_hash=None, counter=None, timesteps=None): """ Create the data_input to be used in the HMAC function In case of QN the question would be "111111" In case of QA the question would be "123ASD" In case of QH the question would be "BEEF" The question is transformed internally. :param question: The question can be :type question: basestring :param pin_hash: The hash of the pin :type pin_hash: basestring (hex) :param timesteps: timestemps :type timesteps: hex string :return: data_input :rytpe: binary """ # In case the ocrasuite comes as a unicode (like from the webui) we # need to convert it! data_input = str(self.ocrasuite) + b'\0' # Check for counter if self.ocrasuite_obj.counter == "C": if counter: counter = int(counter) counter = struct.pack('>Q', int(counter)) data_input += counter else: raise Exception("The ocrasuite {0!s} requires a counter".format( self.ocrasuite)) # Check for Question if self.ocrasuite_obj.challenge_type == "QN": # In case of QN question = '{0:x}'.format(int(question)) question += '0' * (len(question) % 2) question = binascii.unhexlify(question) question += '\0' * (128-len(question)) data_input += question elif self.ocrasuite_obj.challenge_type == "QA": question += '\0' * (128-len(question)) data_input += question elif self.ocrasuite_obj.challenge_type == "QH": # pragma: no cover question = binascii.unhexlify(question) question += '\0' * (128-len(question)) data_input += question # in case of PIN if self.ocrasuite_obj.signature_type == "P": if pin_hash: data_input += binascii.unhexlify(pin_hash) elif pin: pin_hash = SHA_FUNC.get(self.ocrasuite_obj.signature_hash)( pin).digest() data_input += pin_hash else: raise Exception("The ocrasuite {0!s} requires a PIN!".format( self.ocrasuite)) elif self.ocrasuite_obj.signature_type == "T": if not timesteps: raise Exception("The ocrasuite {0!s} requires timesteps".format( self.ocrasuite)) # In case of Time timesteps = int(timesteps, 16) timesteps = struct.pack('>Q', int(timesteps)) data_input += timesteps elif self.ocrasuite_obj.signature_type == "S": # pragma: no cover # In case of session # TODO: Session not yet implemented raise NotImplementedError("OCRA Session not implemented, yet.") return data_input def get_response(self, question, pin=None, pin_hash=None, counter=None, timesteps=None): """ Create an OTP response from the given input values. :param question: :param pin: :param pin_hash: :param counter: :return: """ data_input = self.create_data_input(question, pin=pin, pin_hash=pin_hash, counter=counter, timesteps=timesteps) r = self.hmac_obj.generate(key=self.key, challenge=binascii.hexlify(data_input)) return r def check_response(self, response, question=None, pin=None, pin_hash=None, counter=None, timesteps=None): """ Check the given *response* if it is the correct response to the challenge/question. :param response: :param question: :param pin: :param pin_hash: :param counter: :param timesteps: :return: """ r = self.get_response(question, pin=pin, pin_hash=pin_hash, counter=counter, timesteps=timesteps) if r == response: return 1 else: return -1