def finish_challenge_token(self): """ processing of the challenge tokens """ challenge_tokens = self.challenge_tokens options = self.options if not options: options = {} action_detail = 'challenge created' if len(challenge_tokens) == 1: challenge_token = challenge_tokens[0] _res, reply = Challenges.create_challenge( challenge_token, options=options) return (False, reply, action_detail) # processing of multiple challenges else: # for each token, who can submit a challenge, we have to # create the challenge. To mark the challenges as depending # the transaction id will have an id that all sub transaction share # and a postfix with their enumaration. Finally the result is # composed by the top level transaction id and the message # and below in a dict for each token a challenge description - # the key is the token type combined with its token serial number all_reply = {'challenges': {}} challenge_count = 0 transactionid = '' challenge_id = "" for challenge_token in challenge_tokens: challenge_count += 1 id_postfix = ".%02d" % challenge_count if transactionid: challenge_id = "%s%s" % (transactionid, id_postfix) (_res, reply) = Challenges.create_challenge( challenge_token, options=options, challenge_id=challenge_id, id_postfix=id_postfix ) transactionid = reply.get('transactionid').rsplit('.')[0] # add token type and serial to ease the type specific processing reply['linotp_tokentype'] = challenge_token.type reply['linotp_tokenserial'] = challenge_token.getSerial() key = challenge_token.getSerial() all_reply['challenges'][key] = reply # finally add the root challenge response with top transaction id # and message, that indicates that 'multiple challenges have been # submitted all_reply['transactionid'] = transactionid all_reply['message'] = "Multiple challenges submitted." log.debug("Multiple challenges submitted: %d", len(challenge_tokens)) return (False, all_reply, action_detail)
def finish_challenge_token(self): """ processing of the challenge tokens """ challenge_tokens = self.challenge_tokens options = self.options if not options: options = {} action_detail = 'challenge created' if len(challenge_tokens) == 1: challenge_token = challenge_tokens[0] _res, reply = Challenges.create_challenge(challenge_token, options=options) return (False, reply, action_detail) # processing of multiple challenges else: # for each token, who can submit a challenge, we have to # create the challenge. To mark the challenges as depending # the transaction id will have an id that all sub transaction share # and a postfix with their enumeration. Finally the result is # composed by the top level transaction id and the message # and below in a dict for each token a challenge description - # the key is the token type combined with its token serial number all_reply = {'challenges': {}} challenge_count = 0 transactionid = '' challenge_id = "" for challenge_token in challenge_tokens: challenge_count += 1 id_postfix = ".%02d" % challenge_count if transactionid: challenge_id = "%s%s" % (transactionid, id_postfix) (_res, reply) = Challenges.create_challenge( challenge_token, options=options, challenge_id=challenge_id, id_postfix=id_postfix) transactionid = reply.get('transactionid').rsplit('.')[0] key = challenge_token.getSerial() all_reply['challenges'][key] = reply # finally add the root challenge response with top transaction id # and message, that indicates that 'multiple challenges have been # submitted all_reply['transactionid'] = transactionid all_reply['message'] = "Multiple challenges submitted." log.debug("Multiple challenges submitted: %d", len(challenge_tokens)) return (False, all_reply, action_detail)
def checkSerialPass(self, serial, passw, options=None, user=None): """ This function checks the otp for a given serial :attention: the parameter user must be set, as the pin policy==1 will verify the user pin """ token_type = options.get("token_type", None) tokenList = getTokens4UserOrSerial(None, serial, token_type=token_type, read_for_update=True) if passw is None: # other than zero or one token should not happen, as serial is # unique if len(tokenList) == 1: theToken = tokenList[0] tok = theToken.token realms = tok.getRealmNames() if realms is None or len(realms) == 0: realm = getDefaultRealm() elif len(realms) > 0: realm = realms[0] userInfo = getUserInfo( tok.LinOtpUserid, tok.LinOtpIdResolver, tok.LinOtpIdResClass, ) user = User(login=userInfo.get("username"), realm=realm) user.info = userInfo if theToken.is_challenge_request(passw, user, options=options): (res, opt) = Challenges.create_challenge(theToken, options) res = False else: raise ParameterError("Missing parameter: pass", id=905) else: raise Exception("No token found: " "unable to create challenge for %s" % serial) else: (res, opt) = self.checkTokenList(tokenList, passw, user=user, options=options) return (res, opt)
def checkSerialPass(self, serial, passw, options=None, user=None): """ This function checks the otp for a given serial :attention: the parameter user must be set, as the pin policy==1 will verify the user pin """ log.debug('checking for serial %r' % serial) tokenList = linotp.lib.token.getTokens4UserOrSerial( None, serial) if passw is None: # other than zero or one token should not happen, as serial is # unique if len(tokenList) == 1: theToken = tokenList[0] tok = theToken.token realms = tok.getRealmNames() if realms is None or len(realms) == 0: realm = getDefaultRealm() elif len(realms) > 0: realm = realms[0] userInfo = getUserInfo(tok.LinOtpUserid, tok.LinOtpIdResolver, tok.LinOtpIdResClass) user = User(login=userInfo.get('username'), realm=realm) user.info = userInfo if theToken.is_challenge_request(passw, user, options=options): (res, opt) = Challenges.create_challenge( theToken, options) res = False else: raise ParameterError('Missing parameter: pass', id=905) else: raise Exception('No token found: ' 'unable to create challenge for %s' % serial) else: log.debug('checking len(pass)=%r for serial %r' % (len(passw), serial)) (res, opt) = self.checkTokenList( tokenList, passw, user=user, options=options) return (res, opt)
def checkSerialPass(self, serial, passw, options=None, user=None): """ This function checks the otp for a given serial :attention: the parameter user must be set, as the pin policy==1 will verify the user pin """ log.debug('checking for serial %r' % serial) tokenList = linotp.lib.token.getTokens4UserOrSerial(None, serial) if passw is None: # other than zero or one token should not happen, as serial is # unique if len(tokenList) == 1: theToken = tokenList[0] tok = theToken.token realms = tok.getRealmNames() if realms is None or len(realms) == 0: realm = getDefaultRealm() elif len(realms) > 0: realm = realms[0] userInfo = getUserInfo(tok.LinOtpUserid, tok.LinOtpIdResolver, tok.LinOtpIdResClass) user = User(login=userInfo.get('username'), realm=realm) user.info = userInfo if theToken.is_challenge_request(passw, user, options=options): (res, opt) = Challenges.create_challenge(theToken, options) res = False else: raise ParameterError('Missing parameter: pass', id=905) else: raise Exception('No token found: ' 'unable to create challenge for %s' % serial) else: log.debug('checking len(pass)=%r for serial %r' % (len(passw), serial)) (res, opt) = self.checkTokenList(tokenList, passw, user=user, options=options) return (res, opt)
def update(self, params): param_keys = set(params.keys()) init_rollout_state_keys = set(['type', 'hashlib', 'serial', 'key_size', 'user.login', 'user.realm', 'session']) # ---------------------------------------------------------------------- if param_keys.issubset(init_rollout_state_keys): # if param keys are in {'type', 'hashlib'} the token is # initialized for the first time. this is e.g. done on the # manage web ui. since the token doesn't exist in the database # yet, its rollout state must be None (that is: they data for # the rollout state doesn't exist yet) self.ensure_state(None) # ------------------------------------------------------------------ # collect data used for generating the pairing url serial = params.get('serial') hash_algorithm = params.get('hashlib') pub_key = get_qrtoken_public_key() cb_url = get_single_auth_policy('qrtoken_pairing_callback_url') cb_sms = get_single_auth_policy('qrtoken_pairing_callback_sms') # TODO: read from config otp_pin_length = None # ------------------------------------------------------------------ pairing_url = generate_pairing_url('qrtoken', server_public_key=pub_key, serial=serial, callback_url=cb_url, callback_sms_number=cb_sms, otp_pin_length=otp_pin_length, hash_algorithm=hash_algorithm) # ------------------------------------------------------------------ self.addToInfo('pairing_url', pairing_url) # we set the the active state of the token to False, because # it should not be allowed to use it for validation before the # pairing process is done self.token.LinOtpIsactive = False # ------------------------------------------------------------------ self.change_state('pairing_url_sent') # ---------------------------------------------------------------------- elif 'pairing_response' in params: # if a pairing response is in the parameters, we guess, # that the request refers to a token in the state # 'pairing_url_sent' self.ensure_state('pairing_url_sent') # ------------------------------------------------------------------ # adding the user's public key to the token info # as well as the user_token_id, which is used to # identify the token on the user's side self.addToTokenInfo('user_token_id', params['user_token_id']) # user public key arrives in the bytes format. # we must convert to a string in order to be # able to dump it as json in the db b64_user_public_key = b64encode(params['user_public_key']) self.addToTokenInfo('user_public_key', b64_user_public_key) # ------------------------------------------------------------------ # create challenge through the challenge factory # add the content type and the challenge data to the params # (needed in the createChallenge method) params['content_type'] = CONTENT_TYPE_PAIRING params['data'] = self.getSerial() self.change_state('pairing_response_received') success, challenge_dict = Challenges.create_challenge(self, params) if not success: raise Exception('Unable to create challenge from ' 'pairing response %s' % params['pairing_response']) challenge_url = challenge_dict['message'] # ------------------------------------------------------------------ self.addToInfo('pairing_challenge_url', challenge_url) # ------------------------------------------------------------------ self.change_state('pairing_challenge_sent')
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)) ''' log.debug('[challenge] %r: %r: %r' % (data, session, challenge)) secretHOtp = self.token.getHOtpKey() ocraSuite = OcraSuite(self.getOcraSuiteSuite(), secretHOtp) if data is None or len(data) == 0: 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) log.debug('[Ocra2TokenClass] challenge: %r ' % (challenge)) counter = self.getOtpCount() ## set the pin onyl in the compliant hashed mode pin = '' if ocraSuite.P is not None: pinObj = self.token.getUserPin() pin = pinObj.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: raise Exception('[Ocra2TokenClass] Failed to create ocrasuite ' 'challenge: %r' % (ex)) ## create a non exisiting challenge try: (res, opt) = Challenges.create_challenge(self, self.context, options={'messgae': data}) transid = opt.get('transactionid') challenge = opt.get('challenge') except Exception as ex: ## this might happen if we have a db problem or ## the uniqnes constrain does not fit log.exception("[Ocra2TokenClass] %r" % ex) raise Exception('[Ocra2TokenClass] Failed to create ' 'challenge object: %s' % (ex)) realm = None realms = self.token.getRealms() if len(realms) > 0: realm = realms[0] url = '' if realm is not None: url = get_qrtan_url(realm.name, context=self.context) log.debug('[challenge]: %r: %r: %r' % (transid, challenge, url)) return (transid, challenge, True, url)
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 ''' log.debug('[_rollout_2] %r ' % (params)) 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 = decryptPin(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) #self.createChallenge() (res, opt) = Challenges.create_challenge(self, self.context, options=params) challenge = opt.get('challenge') url = opt.get('url') transid = opt.get('transactionid') ## 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.urlencode({'u':str(url.encode("utf-8"))}) uInfo['u'] = ustr[2:] info['url'] = str(url.encode("utf-8")) app_import = 'lseqr://nonce?%s' % (urllib.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.urlencode(signature)) self.info = info ## setup new state self.addToTokenInfo('rollout', '2') self.enable(True) log.debug('[_rollout_2]:') return