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 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 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
## # 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
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)
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
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)