def _get_secret_object(self): """ encapsulate the returning of the secret object the returning of the SecretObj to allow delayed access to the token seed eg. only when hmac is calculated, the secret will be decrypted :return: SecretObject, containing the token seed """ key, iv = self.token.get_encrypted_seed() secObj = SecretObj(key, iv, hsm=context["hsm"]) return secObj
def checkOtp(self, anOtpVal, counter, window, options=None): ''' checkOtp - validate the token otp against a given otpvalue :param anOtpVal: the to be verified otpvalue :type anOtpVal: string :param counter: the counter state, that shoule be verified :type counter: int :param window: the counter +window, which should be checked :type window: int :param options: the dict, which could contain token specific info :type options: dict :return: the counter state or -1 :rtype: int ''' otplen = self.token.LinOtpOtpLen #otime contains the previous verification time # the new one must be newer than this! otime = self.token.LinOtpCount secObj = self._get_secret_object() window = self.token.LinOtpCountWindow key, iv = self.token.getUserPin() secPinObj = SecretObj(key, iv, hsm=context.get('hsm')) mtimeOtp = mTimeOtp(secObj, secPinObj, otime, otplen) res = mtimeOtp.checkOtp(anOtpVal, window, options=options) if (res != -1): res = res - 1 ## later on this will be incremented by 1 if res == -1: msg = "verification failed" else: msg = "verifiction was successful" log.debug("[checkOtp] %s :res %r" % (msg, res)) return res
def test_compare_password(): """ test to verify the new password comparison in secret object used in the pw and lost token. """ # init the SecretObject enc_password = utils.crypt_password('password').encode('utf-8') sec_obj = SecretObj(val=enc_password, iv=b':1:') # run the comparison tests - positive test res = sec_obj.compare_password('password') assert res # negative test res = sec_obj.compare_password('Password') assert not res
def test_compare_password(self): """ test the new compare passwords - used in the pw and lost token """ # init the SecretObject sec_obj = SecretObj(val=libcrypt_password('password'), iv=':1:') # run the comparison tests - positive test res = sec_obj.compare_password('password') self.assertTrue(res) # negative test res = sec_obj.compare_password('Password') self.assertFalse(res) return
def get_token_data(self): """ get all tokens """ tokens = Session.query(model_token).all() for token in tokens: token_data = {} serial = token.LinOtpTokenSerialnumber token_data['Serial'] = serial if token.isPinEncrypted(): iv, enc_pin = token.get_encrypted_pin() pin = SecretObj.decrypt_pin(enc_pin, hsm=self.hsm) just_mac = serial + token.LinOtpPinHash enc_value = self.crypter.encrypt(input_data=pin, just_mac=just_mac) token_data['TokenPin'] = enc_value # the userpin is used in motp and ocra/ocra2 token if token.LinOtpTokenPinUser: key, iv = token.getUserPin() user_pin = SecretObj.decrypt(key, iv, hsm=self.hsm) just_mac = serial + token.LinOtpTokenPinUser enc_value = self.crypter.encrypt(input_data=user_pin, just_mac=just_mac) token_data['TokenUserPin'] = enc_value # then we retrieve as well the original value, # to identify changes encKey = token.LinOtpKeyEnc key, iv = token.get_encrypted_seed() secObj = SecretObj(key, iv, hsm=self.hsm) seed = secObj.getKey() enc_value = self.crypter.encrypt(input_data=seed, just_mac=serial + encKey) token_data['TokenSeed'] = enc_value # next we look for tokens, where the pin is encrypted yield token_data
def resync(self, otp1, otp2, options=None): ''' - for the resync to work, we take the last two transactions and their challenges - for each challenge, we search forward the sync window length ''' ret = False challenges = [] o_challenges = OcraTokenClass.getTransactions4serial(self.getSerial()) for challenge in o_challenges: challenges.append(challenge) # check if there are enough challenges around if len(challenges) < 2: return False challenge1 = {} challenge2 = {} if options is None: ch1 = challenges[0] challenge1['challenge'] = ch1.challenge challenge1['transid'] = ch1.transid challenge1['session'] = ch1.session ch2 = challenges[1] challenge2['challenge'] = ch2.challenge challenge2['transid'] = ch2.transid challenge2['session'] = ch2.session else: if 'challenge1' in options: challenge1['challenge'] = options.get('challenge1') if 'challenge2' in options: challenge2['challenge'] = options.get('challenge2') if len(challenge1) == 0 or len(challenge2) == 0: error = "No challenges found!" log.info('[OcraTokenClass:resync] %s' % (error)) raise Exception('[OcraTokenClass:resync] %s' % (error)) secObj = self._get_secret_object() ocraSuite = OcraSuite(self.getOcraSuiteSuite(), secObj) syncWindow = self.token.getSyncWindow() if ocraSuite.T is not None: syncWindow = syncWindow / 10 counter = self.token.getOtpCounter() # set the ocra token pin ocraPin = '' if ocraSuite.P is not None: key, iv = self.token.getUserPin() secObj = SecretObj(key, iv, hsm=context.get('hsm')) ocraPin = secObj.getKey() if ocraPin is None or len(ocraPin) == 0: ocraPin = '' timeShift = 0 if ocraSuite.T is not None: timeShift = int(self.getFromTokenInfo("timeShift", 0)) try: count_1 = ocraSuite.checkOtp(otp1, counter, syncWindow, challenge1, pin=ocraPin, timeshift=timeShift) if count_1 == -1: log.info('Ocra resync: lookup for first otp value failed!') ret = False else: count_2 = ocraSuite.checkOtp(otp2, counter, syncWindow, challenge2, pin=ocraPin, timeshift=timeShift) if count_2 == -1: log.info( 'Ocra resync: lookup for second otp value failed!') ret = False else: if ocraSuite.C is not None: if count_1 + 1 == count_2: self.setOtpCount(count_2) ret = True if ocraSuite.T is not None: if count_1 - count_2 <= ocraSuite.T * 2: # callculate the timeshift date = datetime.datetime.fromtimestamp(count_2) log.info('Ocra resync: Syncing token to new ' 'timestamp %r' % (date)) now = datetime.datetime.now() stime = now.strftime("%s") timeShift = count_2 - int(stime) self.addToTokenInfo('timeShift', timeShift) ret = True except Exception as ex: log.exception('[OcraTokenClass:resync] unknown error: %r' % (ex)) raise Exception('[OcraTokenClass:resync] unknown error: %s' % (ex)) return ret
def checkOtp(self, passw, counter, window, options=None): ''' checkOtp - standard callback of linotp to verify the token :param passw: the passw / otp, which has to be checked :type passw: string :param counter: the start counter :type counter: int :param window: the window, in which the token is valid :type window: int :param options: options contains the transaction id, eg. if check_t checks one transaction this will support assynchreonous otp checks (when check_t is used) :type options: dict :return: verification counter or -1 :rtype: int (-1) ''' ret = -1 secObj = self._get_secret_object() ocraSuite = OcraSuite(self.getOcraSuiteSuite(), secObj) # if we have no transactionid given through the options, # we have to retrieve the eldest challenge for this ocra token serial = self.getSerial() challenges = [] # set the ocra token pin ocraPin = '' if ocraSuite.P is not None: key, iv = self.token.getUserPin() secObj = SecretObj(key, iv, hsm=context.get('hsm')) ocraPin = secObj.getKey() if ocraPin is None or len(ocraPin) == 0: ocraPin = '' timeShift = 0 if ocraSuite.T is not None: defTimeWindow = int(getFromConfig("ocra.timeWindow", 180)) window = (int(self.getFromTokenInfo('timeWindow', defTimeWindow)) / ocraSuite.T) defTimeShift = int(getFromConfig("ocra.timeShift", 0)) timeShift = int(self.getFromTokenInfo("timeShift", defTimeShift)) if options is None: challenges = OcraTokenClass.getTransactions4serial( serial, currentOnly=True) elif options is not None: if type(options).__name__ != 'dict': err = ('[chekOtp] "options" not of type dict! %r' % type(options)) log.error(err) raise Exception(err) if 'transactionid' in options: transid = options.get('transactionid') challenges.append(OcraTokenClass.getTransaction(transid)) elif 'challenge' in options: challenges.append(options) # due to the added options in checkUserPass, we have to extend # the logic here: # if no challenges found in between but we have a serial, we catch # the open challenges by serial (s.o.) if len(challenges) == 0: challenges = OcraTokenClass.getTransactions4serial( serial, currentOnly=True) if len(challenges) == 0: # verify that there has already been a challenge challenges = OcraTokenClass.getTransactions4serial(serial) if len(challenges) > 0: err = 'No current transaction found!' ret = -1 return ret else: err = 'No open transaction found!' log.info(err) if type(options) == dict and 'transactionid' in options: raise Exception(err) ret = -1 return ret for ch in challenges: challenge = {} if isinstance(ch, dict): # transaction less checkOtp self.transId = 0 challenge.update(ch) elif type(ch) == OcraChallenge: # preserve transaction context, so we could use this in # the status callback self.transId = ch.transid challenge['challenge'] = ch.challenge challenge['transid'] = ch.transid challenge['session'] = ch.session ret = ocraSuite.checkOtp(passw, counter, window, challenge, pin=ocraPin, options=options, timeshift=timeShift) if ret != -1: break if -1 == ret: # autosync: test if two consecutive challenges + # it's counter match ret = self.autosync(ocraSuite, passw, challenge) return ret
def challenge(self, data, session='', typ='raw', challenge=None): ''' the challenge method is for creating an transaction / challenge object remark: the transaction has a maximum lifetime and a reference to the OcraSuite token (serial) :param data: data, which is the base for the challenge or None :type data: string or None :param session: session support for ocratokens :type session: string :type typ: define, which kind of challenge base should be used could be raw - take the data input as is (extract chars accordind challenge definition Q) or random - will generate a random input or hased - will take the hash of the input data :return: challenge response containing the transcation id and the challenge for the ocrasuite :rtype : tuple of (transId(string), challenge(string)) ''' s_data = 'None' s_session = 'None' s_challenge = 'None' if data is not None: s_data = data if session is not None: s_session = session if challenge is None: s_challenge = challenge secObj = self._get_secret_object() ocraSuite = OcraSuite(self.getOcraSuiteSuite(), secObj) if not data: typ = 'random' if challenge is None: if typ == 'raw': challenge = ocraSuite.data2rawChallenge(data) elif typ == 'random': challenge = ocraSuite.data2randomChallenge(data) elif typ == 'hash': challenge = ocraSuite.data2hashChallenge(data) serial = self.getSerial() counter = self.getOtpCount() # set the pin onyl in the compliant hashed mode pin = '' if ocraSuite.P is not None: key, iv = self.token.getUserPin() secObj = SecretObj(key, iv, hsm=context.get('hsm')) pin = secObj.getKey() try: param = {} param['C'] = counter param['Q'] = challenge param['P'] = pin param['S'] = session if ocraSuite.T is not None: now = datetime.datetime.now() stime = now.strftime("%s") itime = int(stime) param['T'] = itime ''' verify that the data is compliant with the OcraSuitesuite and the client is able to calc the otp ''' c_data = ocraSuite.combineData(**param) ocraSuite.compute(c_data) except Exception as ex: log.exception( "[OcraTokenClass] Failed to create ocrasuite challenge") raise Exception('[OcraTokenClass] Failed to create ocrasuite' 'challenge: %r' % (ex)) # save the object digits = '0123456789' transid = '' transactionIdLen = 12 try: transactionIdLen = int(getFromConfig("OcraDefaultSuite", '12')) except: transactionIdLen = 12 log.debug("[OcraTokenClass] Failed to set transactionId length" " from config - using fallback %d" % (transactionIdLen)) # create a non exisiting challenge try: while True: for _c in range(0, transactionIdLen): transid += urandom.choice(digits) chall = OcraTokenClass.getTransaction(transid) if chall is None: break ddata = '' if data is not None: ddata = data chall = OcraChallenge(transid=transid, tokenserial=serial, challenge=typ + ':' + challenge, data=typ + ':' + ddata) chall.save() except Exception as ex: # this might happen if we have a db problem or # the uniqnes constrain does not fit log.exception("[OcraTokenClass] Failed to create challenge") raise Exception('[OcraTokenClass] Failed to create challenge' ' object: %s' % (ex)) realms = [] tokenrealms = self.token.getRealms() for realm in tokenrealms: realms.append(realm.name) url = get_qrtan_url(realms) return (transid, challenge, True, url)
return res ## # AUTOSYNC starts here ## counter = self.token.getOtpCounter() syncWindow = self.token.getSyncWindow() if ocraSuite.T is not None: syncWindow = syncWindow / 10 # set the ocra token pin ocraPin = '' if ocraSuite.P is not None: key, iv = self.token.getUserPin() secObj = SecretObj(key, iv, hsm=context.get('hsm')) ocraPin = secObj.getKey() if ocraPin is None or len(ocraPin) == 0: ocraPin = '' timeShift = 0 if ocraSuite.T is not None: timeShift = int(self.getFromTokenInfo("timeShift", 0)) # timeStepping = int(ocraSuite.T) tinfo = self.getTokenInfo() # autosync does only work, if we have a token info, where the # last challenge and the last sync-counter is stored
def autosync(self, ocraSuite, passw, challenge): ''' try to resync a token automaticaly, if a former and the current request failed :param ocraSuite: the ocraSuite of the current Token :type ocraSuite: ocra object :param passw: ''' res = -1 autosync = False try: confsetting = getFromConfig("AutoResync") if confsetting is None: autosync = False elif "true" == confsetting.lower(): autosync = True elif "false" == confsetting.lower(): autosync = False except Exception as ex: log.exception('Ocra: autosync check undefined %r' % (ex)) return res ' if autosync is not enabled: do nothing ' if autosync is False: return res ## # AUTOSYNC starts here ## counter = self.token.getOtpCounter() syncWindow = self.token.getSyncWindow() if ocraSuite.T is not None: syncWindow = syncWindow / 10 # set the ocra token pin ocraPin = '' if ocraSuite.P is not None: key, iv = self.token.getUserPin() secObj = SecretObj(key, iv, hsm=context.get('hsm')) ocraPin = secObj.getKey() if ocraPin is None or len(ocraPin) == 0: ocraPin = '' timeShift = 0 if ocraSuite.T is not None: timeShift = int(self.getFromTokenInfo("timeShift", 0)) # timeStepping = int(ocraSuite.T) tinfo = self.getTokenInfo() # autosync does only work, if we have a token info, where the # last challenge and the last sync-counter is stored # if no tokeninfo, we start with a autosync request, thus start the # lookup in the sync window if 'lChallenge' not in tinfo: # run checkOtp, with sync window for the current challenge log.info('[OcraToken:autosync] initial sync') count_0 = -1 try: otp0 = passw count_0 = ocraSuite.checkOtp(otp0, counter, syncWindow, challenge, pin=ocraPin, timeshift=timeShift) except Exception as ex: log.exception('Ocra: Error during autosync0: %r' % (ex)) if count_0 != -1: tinfo['lChallenge'] = {'otpc': count_0} self.setTokenInfo(tinfo) log.info('[OcraToken:autosync] initial sync - success: %r' % count_0) res = -1 else: # run checkOtp, with sync window for the current challenge count_1 = -1 try: otp1 = passw count_1 = ocraSuite.checkOtp(otp1, counter, syncWindow, challenge, pin=ocraPin, timeshift=timeShift) except Exception as ex: log.exception('Ocra: Error during autosync1: %r' % (ex)) if count_1 == -1: del tinfo['lChallenge'] self.setTokenInfo(tinfo) log.info('[OcraToken:autosync] sync failed! Not a valid pass' ' in scope (%r)' % (otp1)) res = -1 else: # run checkOtp, with sync window for the old challenge lChallange = tinfo.get('lChallenge') count_0 = lChallange.get('otpc') if ocraSuite.C is not None: # sync the counter based ocra token if count_1 - count_0 < 2: self.setOtpCount(count_1) res = count_1 if ocraSuite.T is not None: # sync the timebased ocra token if count_1 - count_0 < ocraSuite.T * 2: # calc the new timeshift ! log.debug("[autosync] the counter %r matches: %r" % (count_1, datetime.datetime.fromtimestamp(count_1))) currenttime = int(time.time()) new_shift = (count_1 - currenttime) tinfo['timeShift'] = new_shift self.setOtpCount(count_1) res = count_1 # if we came here, the old challenge is not required anymore del tinfo['lChallenge'] self.setTokenInfo(tinfo) return res