def checkPin(self, pin, options=None): ''' checkPin - test is the pin is matching :param pin: the pin :param options: additional optional parameters, which could be token specific :return: boolean ''' res = False hsm = context['hsm'] if self.token.isPinEncrypted(): # for comparison we encrypt the pin and do the comparison iv, encrypted_token_pin = self.token.get_encrypted_pin() encrypted_pin = SecretObj.encrypt_pin(pin, iv=iv, hsm=hsm) if encrypted_token_pin == encrypted_pin: res = True else: # for hashed pins we re-do the hash and compare the hashes iv, hashed_token_pin = self.token.get_hashed_pin() iv, hashed_pin = SecretObj.hash_pin(pin or '', iv, hsm=hsm) if hashed_pin == hashed_token_pin: res = True # special case of empty pin, where pin has never been set # especially in case of lost token with the pw token if len(hashed_token_pin) == 0 and len(pin) == 0: res = True return res
def setPin(self, pin, param=None): ''' set the PIN. The optional parameter "param" can hold the information, if the PIN is encrypted or hashed. :param pin: the pin value :param param: the additional request parameters, which could contain the 'encryptpin' value, that triggers, that the token secret are stored in an encrypted form :return: - nothing - ''' if param is None: param = {} hsm = context['hsm'] storeHashed = True enc = param.get("encryptpin", None) if enc is not None and "true" == enc.lower(): storeHashed = False if storeHashed is True: iv, hashed_pin = SecretObj.hash_pin(pin, hsm=hsm) self.token.set_hashed_pin(hashed_pin, iv) else: enc_pin = SecretObj.encrypt_pin(pin, hsm=hsm) iv = enc_pin.split(':')[0] self.token.set_encrypted_pin(enc_pin, binascii.unhexlify(iv))
def setPin(self, pin, param=None): """ set the PIN. The optional parameter "param" can hold the information, if the PIN is encrypted or hashed. :param pin: the pin value :param param: the additional request parameters, which could contain the 'encryptpin' value, that triggers, that the token secret are stored in an encrypted form :return: - nothing - """ if param is None: param = {} storeHashed = True enc = param.get("encryptpin", None) if enc is not None and "true" == enc.lower(): storeHashed = False if storeHashed is True: iv, hashed_pin = SecretObj.hash_pin(pin) self.token.set_hashed_pin(hashed_pin, iv) else: enc_pin = SecretObj.encrypt_pin(pin) iv = enc_pin.split(":")[0] self.token.set_encrypted_pin(enc_pin.encode("utf-8"), binascii.unhexlify(iv))
def checkPin(self, pin, options=None): """ checkPin - test is the pin is matching :param pin: the pin :param options: additional optional parameters, which could be token specific :return: boolean """ if self.token.isPinEncrypted(): # for comparison we encrypt the pin and do the comparison # iv is binary, while encrypted_token_pin is hexlified iv, encrypted_token_pin = self.token.get_encrypted_pin() return SecretObj.check_encrypted_pin(pin, encrypted_token_pin, iv) # hashed pin comparison iv, hashed_token_pin = self.token.get_hashed_pin() # special case of empty pin, where pin has never been set # especially in case of lost token with the pw token if len(hashed_token_pin) == 0 and len(pin) == 0: return True return SecretObj.check_hashed_pin(pin or "", hashed_token_pin, iv)
def set_token_data(self, token_data): serial = token_data["Serial"] tokens = Session.query(model_token).\ filter(model_token.LinOtpTokenSerialnumber == serial).all() token = tokens[0] if 'TokenPin' in token_data: enc_pin = token_data['TokenPin'] token_pin = self.crypter.decrypt(enc_pin, just_mac=serial + token.LinOtpPinHash) # prove, we can write enc_pin = SecretObj.encrypt_pin(token_pin) iv = enc_pin.split(':')[0] token.set_encrypted_pin(enc_pin, binascii.unhexlify(iv)) if 'TokenUserPin' in token_data: token_enc_user_pin = token_data['TokenUserPin'] user_pin = self.crypter.decrypt(token_enc_user_pin, just_mac=serial + token.LinOtpTokenPinUser) # prove, we can write iv, enc_user_pin = SecretObj.encrypt(user_pin, hsm=self.hsm) token.setUserPin(enc_user_pin, iv) # we put the current crypted seed in the mac to check if # something changed in meantime encKey = token.LinOtpKeyEnc enc_seed = token_data['TokenSeed'] token_seed = self.crypter.decrypt(enc_seed, just_mac=serial + encKey) # the encryption of the token seed is not part of the model anymore iv, enc_token_seed = SecretObj.encrypt(token_seed) token.set_encrypted_seed(enc_token_seed, iv, reset_failcount=False, reset_counter=False)
def setSoPin(self, soPin): """ set the soPin of the token the soPin is encrypted and the encrypte value is stored in the Token model :param soPin: the special so pin """ iv, enc_soPin = SecretObj.encrypt(soPin, hsm=context.get('hsm')) self.token.setSoPin(enc_soPin, iv)
def getPin(self): """ :return: the value of the pin- if it is stored encrypted """ pin = '' hsm = context['hsm'] if self.token.isPinEncrypted(): _iv, enc_pin = self.token.get_encrypted_pin() pin = SecretObj.decrypt_pin(enc_pin, hsm=hsm) return pin
def setSoPin(self, soPin): """ set the soPin of the token the soPin is encrypted and the encrypte value is stored in the Token model :param soPin: the special so pin """ iv, enc_soPin = SecretObj.encrypt(soPin, hsm=context.get("hsm")) self.token.setSoPin(enc_soPin, iv)
def getPin(self): """ :return: the value of the pin- if it is stored encrypted """ pin = "" hsm = context["hsm"] if self.token.isPinEncrypted(): _iv, enc_pin = self.token.get_encrypted_pin() pin = SecretObj.decrypt_pin(enc_pin, hsm=hsm) return pin
def set_token_data(self, token_data): serial = token_data["Serial"] tokens = Session.query(model_token).\ filter(model_token.LinOtpTokenSerialnumber == serial).all() token = tokens[0] if 'TokenPin' in token_data: enc_pin = token_data['TokenPin'] token_pin = self.crypter.decrypt( enc_pin, just_mac=serial + token.LinOtpPinHash) # prove, we can write enc_pin = SecretObj.encrypt_pin(token_pin) iv = enc_pin.split(':')[0] token.set_encrypted_pin(enc_pin, binascii.unhexlify(iv)) if 'TokenUserPin' in token_data: token_enc_user_pin = token_data['TokenUserPin'] user_pin = self.crypter.decrypt( token_enc_user_pin, just_mac=serial + token.LinOtpTokenPinUser) # prove, we can write iv, enc_user_pin = SecretObj.encrypt(user_pin, hsm=self.hsm) token.setUserPin(enc_user_pin, iv) # we put the current crypted seed in the mac to check if # something changed in meantime encKey = token.LinOtpKeyEnc enc_seed = token_data['TokenSeed'] token_seed = self.crypter.decrypt(enc_seed, just_mac=serial + encKey) # the encryption of the token seed is not part of the model anymore iv, enc_token_seed = SecretObj.encrypt(token_seed) token.set_encrypted_seed(enc_token_seed, iv, reset_failcount=False, reset_counter=False)
def _rollout_1(self, params): ''' do the rollout 1 step 1. https://linotpserver/admin/init? type=ocra& genkey=1& sharedsecret=1& user=BENUTZERNAME& session=SESSIONKEY =>> "serial" : SERIENNUMMER, "sharedsecret" : DATAOBJECT, "app_import" : IMPORTURL - genSharedSecret - vom HSM oder urandom ? - app_import : + linotp:// + ocrasuite ->> default aus dem config: (DefaultOcraSuite) + sharedsecret (Länge wie ???) + seriennummer - seriennummer: uuid ?? - token wird angelegt ist aber nicht aktiv!!! (counter == 0) ''' sharedSecret = params.get('sharedsecret', None) if sharedSecret == '1': # preserve the rollout state self.addToTokenInfo('rollout', '1') # preserve the current key as sharedSecret secObj = self._get_secret_object() key = secObj.getKey() encSharedSecret = SecretObj.encrypt_pin(key) self.addToTokenInfo('sharedSecret', encSharedSecret) info = {} uInfo = {} info['sharedsecret'] = key uInfo['sh'] = key info['ocrasuite'] = self.getOcraSuiteSuite() uInfo['os'] = self.getOcraSuiteSuite() info['serial'] = self.getSerial() uInfo['se'] = self.getSerial() info['app_import'] = 'lseqr://init?%s' % ( urllib.parse.urlencode(uInfo)) del info['ocrasuite'] self.info = info self.token.LinOtpIsactive = False return
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 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 setUserPin(self, userPin): """ set the userPin of the token the userPin is encrypted and the encrypte value is stored in the Token model :param userPin: the user pin """ iv, enc_user_pin = SecretObj.encrypt(userPin, hsm=context["hsm"]) self.token.setUserPin(enc_user_pin, iv)
def setUserPin(self, userPin): """ set the userPin of the token the userPin is encrypted and the encrypte value is stored in the Token model :param userPin: the user pin """ iv, enc_user_pin = SecretObj.encrypt(userPin, hsm=context['hsm']) self.token.setUserPin(enc_user_pin, iv)
def setOtpKey(self, otpKey, reset_failcount=True): """ set the token seed / secret the seed / secret is encrypted and the encrypte value is stored in the Token model :param otpKey: the token seed / secret :param reset_failcount: boolean, if the failcounter should be reseted """ iv, enc_otp_key = SecretObj.encrypt(otpKey, hsm=context['hsm']) self.token.set_encrypted_seed(enc_otp_key, iv, reset_failcount=reset_failcount)
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 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 _transform_action(action): """ transform the action, especialy the secret parameter of the url """ servers = [] name, _sep, values = action.partition("=") for value in values.split(" "): # decompose the server url to identify, if there is a secret inside parsed_server = urllib.parse.urlparse(value) # the urlparse has a bug,, where in elder versions, the # path is not split from the query if not parsed_server.query: path, _sep, query = parsed_server.path.partition("?") else: path = parsed_server.path query = parsed_server.query # in gereal url parsing allows mutiple entries per key # but we support here only one params = urllib.parse.parse_qs(query) for key, entry in list(params.items()): params[key] = entry[0] # finally we found the query parameters if "secret" in params: secret = params["secret"] params["encsecret"] = SecretObj.encrypt_pin(secret) del params["secret"] # build the server url with the encrypted param: # as the named tuple is not updateable, we have to convert this # into an list to make the update and then back to a tuple to # create an url from this parsed_list = list(parsed_server[:]) parsed_list[ForwardServerPolicy.Path_index] = path.strip() parsed_list[ForwardServerPolicy. Query_index] = urllib.parse.urlencode(params) server_url = urllib.parse.urlunparse(tuple(parsed_list)) servers.append(server_url) ret = "=".join([name, " ".join(servers)]) return ret
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 do_request(servers, env, user, passw, options): """ make the call to the foreign server """ log.debug("start request to foreign server: %r", servers) for server in servers.split(" "): parsed_server = urllib.parse.urlparse(server) # the urlparse has a bug,, where in elder versions, the # path is not split from the query if not parsed_server.query: path, _sep, query = parsed_server.path.partition("?") else: path = parsed_server.path query = parsed_server.query # finally we found the query parameters params = urllib.parse.parse_qs(query) for key, entry in list(params.items()): params[key] = entry[0] if "encsecret" in params: params["secret"] = SecretObj.decrypt_pin(params["encsecret"]) del params["encsecret"] parsed_list = list(parsed_server[:]) parsed_list[ForwardServerPolicy.Path_index] = path.strip() parsed_list[ForwardServerPolicy. Query_index] = urllib.parse.urlencode(params) server_url = urllib.parse.urlunparse(tuple(parsed_list)) if "radius://" in server_url: rad = RadiusRequest(server=server_url, env=env) res, opt = rad.do_request(user, passw, options) return res, opt elif "http://" in server_url or "https://" in server_url: http = HttpRequest(server=server_url, env=env) res, opt = http.do_request(user, passw, options) return res, opt
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 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 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 _rollout_2(self, params): ''' 2. https://linotpserver/admin/init? type=ocra& genkey=1& activationcode=AKTIVIERUNGSCODE& user=BENUTZERNAME& message=MESSAGE& session=SESSIONKEY =>> "serial" : SERIENNUMMER, "nonce" : DATAOBJECT, "transactionid" : "TRANSAKTIONSID, "app_import" : IMPORTURL - nonce - von HSM oder random ? - pkcs5 - kdf2 - es darf zur einer Zeit nur eine QR Token inaktiv (== im Ausrollzustand) sein !!!!! der Token wird über den User gefunden - seed = pdkdf2(nonce + activcode + shared secret) - challenge generiern - von urandom oder HSM ''' activationcode = params.get('activationcode', None) if activationcode is not None: # genkey might have created a new key, so we have to rely on encSharedSecret = self.getFromTokenInfo('sharedSecret', None) if encSharedSecret is None: raise Exception('missing shared secret of initialition' ' for token %r' % (self.getSerial())) sharedSecret = SecretObj.decrypt_pin(encSharedSecret) # we generate a nonce, which in the end is a challenge nonce = createNonce() self.addToTokenInfo('nonce', nonce) # create a new key from the ocrasuite key_len = 20 if self.ocraSuite.find('-SHA256'): key_len = 32 elif self.ocraSuite.find('-SHA512'): key_len = 64 newkey = kdf2(sharedSecret, nonce, activationcode, key_len) self.setOtpKey(binascii.hexlify(newkey)) # generate challenge, which is part of the app_import message = params.get('message', None) (transid, challenge, _ret, url) = self.challenge(message) # generate response info = {} uInfo = {} info['serial'] = self.getSerial() uInfo['se'] = self.getSerial() info['nonce'] = nonce uInfo['no'] = nonce info['transactionid'] = transid uInfo['tr'] = transid info['challenge'] = challenge uInfo['ch'] = challenge if message is not None: uInfo['me'] = str(message.encode("utf-8")) ustr = urllib.parse.urlencode({'u': str(url.encode("utf-8"))}) uInfo['u'] = ustr[2:] info['url'] = str(url.encode("utf-8")) app_import = 'lseqr://nonce?%s' % (urllib.parse.urlencode(uInfo)) # add a signature of the url signature = {'si': self.signData(app_import)} info['signature'] = signature.get('si') info['app_import'] = "%s&%s" % (app_import, urllib.parse.urlencode(signature)) self.info = info # setup new state self.addToTokenInfo('rollout', '2') self.enable(True) return
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 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)