def checkOtp(self, passwd, counter, window, options=None): """ checks if the supplied challenge response is correct. :param passwd: The challenge response :param options: A dictionary of parameters passed by the upper layer (used for transaction_id in this context) :param counter: legacy API (unused) :param window: legacy API (unused) :raises TokenStateError: If token state is not 'active' or 'pairing_challenge_sent' :returns: -1 for failure, 1 for success """ valid_states = ['pairing_challenge_sent', 'active'] self.ensure_state_is_in(valid_states) # ------------------------------------------------------------------ -- # new pushtoken protocoll supports the keyword based accept or deny. # the old 'passwd' argument is not supported anymore try: signature_accept = passwd.get('accept', None) signature_reject = passwd.get('reject', None) except AttributeError: # will be raised with a get() on a str object raise Exception('Pushtoken version %r requires "accept" or' ' "reject" as parameter' % CHALLENGE_URL_VERSION) if signature_accept is not None and signature_reject is not None: raise Exception('Pushtoken version %r requires "accept" or' ' "reject" as parameter' % CHALLENGE_URL_VERSION) # ------------------------------------------------------------------ -- filtered_challenges = [] serial = self.getSerial() if options is None: options = {} max_fail = int(getFromConfig('PushMaxChallenges', '3')) # ------------------------------------------------------------------ -- if 'transactionid' in options: # -------------------------------------------------------------- -- # fetch all challenges that match the transaction id or serial transaction_id = options.get('transactionid') challenges = Challenges.lookup_challenges(serial=serial, transid=transaction_id, filter_open=True) # -------------------------------------------------------------- -- # filter into filtered_challenges for challenge in challenges: (received_tan, tan_is_valid) = challenge.getTanStatus() fail_counter = challenge.getTanCount() # if we iterate over matching challenges (that is: challenges # with the correct transaction id) we either find a fresh # challenge, that didn't receive a TAN at all (first case) # or a challenge, that already received a number of wrong # TANs but still has tries left (second case). if not received_tan: filtered_challenges.append(challenge) elif not tan_is_valid and fail_counter <= max_fail: filtered_challenges.append(challenge) # ------------------------------------------------------------------ -- if not filtered_challenges: return -1 if len(filtered_challenges) > 1: log.error('multiple challenges for one transaction and for one' ' token found!') return -1 # for the serial and the transaction id there could always be only # at max one challenge matching. This is even true for sub transactions challenge = filtered_challenges[0] # client verifies the challenge by signing the challenge # plaintext. we retrieve the original plaintext (saved # in createChallenge) and check for a match data = challenge.getData() data_to_verify = b64decode(data['sig_base']) b64_dsa_public_key = self.getFromTokenInfo('user_dsa_public_key') user_dsa_public_key = b64decode(b64_dsa_public_key) # -------------------------------------------------------------- -- # handle the accept case if signature_accept is not None: accept_signature_as_bytes = decode_base64_urlsafe(signature_accept) accept_data_to_verify_as_bytes = ( struct.pack('<b', CHALLENGE_URL_VERSION) + b'ACCEPT\0' + data_to_verify) try: verify_sig(accept_signature_as_bytes, accept_data_to_verify_as_bytes, user_dsa_public_key) challenge.add_session_info({'accept': True}) return 1 except ValueError: challenge.add_session_info({'accept': False}) log.error("accept signature mismatch!") return -1 # -------------------------------------------------------------- -- # handle the reject case elif signature_reject is not None: reject_signature_as_bytes = decode_base64_urlsafe(signature_reject) reject_data_to_verify_as_bytes = ( struct.pack('<b', CHALLENGE_URL_VERSION) + b'DENY\0' + data_to_verify) try: verify_sig(reject_signature_as_bytes, reject_data_to_verify_as_bytes, user_dsa_public_key) challenge.add_session_info({'reject': True}) return 1 except ValueError: challenge.add_session_info({'reject': False}) log.error("reject signature mismatch!") return -1 return -1
def checkOtp(self, passwd, counter, window, options=None): """ checks if the supplied challenge response is correct. :param passwd: The challenge response :param options: A dictionary of parameters passed by the upper layer (used for transaction_id in this context) :param counter: legacy API (unused) :param window: legacy API (unused) :raises TokenStateError: If token state is not 'active' or 'pairing_challenge_sent' :returns: -1 for failure, 1 for success """ valid_states = ['pairing_challenge_sent', 'active'] self.ensure_state_is_in(valid_states) # ------------------------------------------------------------------- -- filtered_challenges = [] serial = self.getSerial() if options is None: options = {} max_fail = int(getFromConfig('PushMaxChallenges', '3')) # ------------------------------------------------------------------- -- if 'transactionid' in options: # --------------------------------------------------------------- -- # fetch all challenges that match the transaction id or serial transaction_id = options.get('transaction_id') challenges = Challenges.lookup_challenges(serial, transaction_id) # --------------------------------------------------------------- -- # filter into filtered_challenges for challenge in challenges: (received_tan, tan_is_valid) = challenge.getTanStatus() fail_counter = challenge.getTanCount() # if we iterate over matching challenges (that is: challenges # with the correct transaction id) we either find a fresh # challenge, that didn't receive a TAN at all (first case) # or a challenge, that already received a number of wrong # TANs but still has tries left (second case). if not received_tan: filtered_challenges.append(challenge) elif not tan_is_valid and fail_counter <= max_fail: filtered_challenges.append(challenge) # ------------------------------------------------------------------- -- if not filtered_challenges: return -1 for challenge in filtered_challenges: # client verifies the challenge by signing the challenge # plaintext. we retrieve the original plaintext (saved # in createChallenge) and check for a match b64_dsa_public_key = self.getFromTokenInfo('user_dsa_public_key') user_dsa_public_key = b64decode(b64_dsa_public_key) data = challenge.getData() sig_base = data['sig_base'] passwd_as_bytes = decode_base64_urlsafe(passwd) sig_base_as_bytes = b64decode(sig_base) try: verify_sig(passwd_as_bytes, sig_base_as_bytes, user_dsa_public_key) return 1 except ValueError: # signature mismatch return -1 return -1
def checkOtp(self, passwd, counter, window, options=None): """ checks if the supplied challenge response is correct. :param passwd: The challenge response :param options: A dictionary of parameters passed by the upper layer (used for transaction_id in this context) :param counter: legacy API (unused) :param window: legacy API (unused) :raises TokenStateError: If token state is not 'active' or 'pairing_challenge_sent' :returns: -1 for failure, 1 for success """ valid_states = ['pairing_challenge_sent', 'active'] self.ensure_state_is_in(valid_states) # ------------------------------------------------------------------ -- # new pushtoken protocoll supports the keyword based accept or deny. # the old 'passwd' argument is not supported anymore try: signature_accept = passwd.get('accept', None) signature_reject = passwd.get('reject', None) except AttributeError: # will be raised with a get() on a str object raise Exception('Pushtoken version %r requires "accept" or' ' "reject" as parameter' % CHALLENGE_URL_VERSION) if signature_accept is not None and signature_reject is not None: raise Exception('Pushtoken version %r requires "accept" or' ' "reject" as parameter' % CHALLENGE_URL_VERSION) # ------------------------------------------------------------------ -- filtered_challenges = [] serial = self.getSerial() if options is None: options = {} max_fail = int(getFromConfig('PushMaxChallenges', '3')) # ------------------------------------------------------------------ -- if 'transactionid' in options: # -------------------------------------------------------------- -- # fetch all challenges that match the transaction id or serial transaction_id = options.get('transactionid') challenges = Challenges.lookup_challenges(serial=serial, transid=transaction_id, filter_open=True) # -------------------------------------------------------------- -- # filter into filtered_challenges for challenge in challenges: (received_tan, tan_is_valid) = challenge.getTanStatus() fail_counter = challenge.getTanCount() # if we iterate over matching challenges (that is: challenges # with the correct transaction id) we either find a fresh # challenge, that didn't receive a TAN at all (first case) # or a challenge, that already received a number of wrong # TANs but still has tries left (second case). if not received_tan: filtered_challenges.append(challenge) elif not tan_is_valid and fail_counter <= max_fail: filtered_challenges.append(challenge) # ------------------------------------------------------------------ -- if not filtered_challenges: return -1 if len(filtered_challenges) > 1: log.error('multiple challenges for one transaction and for one' ' token found!') return -1 # for the serial and the transaction id there could always be only # at max one challenge matching. This is even true for sub transactions challenge = filtered_challenges[0] # client verifies the challenge by signing the challenge # plaintext. we retrieve the original plaintext (saved # in createChallenge) and check for a match data = challenge.getData() data_to_verify = b64decode(data['sig_base']) b64_dsa_public_key = self.getFromTokenInfo('user_dsa_public_key') user_dsa_public_key = b64decode(b64_dsa_public_key) # -------------------------------------------------------------- -- # handle the accept case if signature_accept is not None: accept_signature_as_bytes = decode_base64_urlsafe( signature_accept) accept_data_to_verify_as_bytes = ( struct.pack('<b', CHALLENGE_URL_VERSION) + b'ACCEPT\0' + data_to_verify) try: verify_sig(accept_signature_as_bytes, accept_data_to_verify_as_bytes, user_dsa_public_key) challenge.add_session_info({'accept': True}) return 1 except ValueError: challenge.add_session_info({'accept': False}) log.error("accept signature mismatch!") return -1 # -------------------------------------------------------------- -- # handle the reject case elif signature_reject is not None: reject_signature_as_bytes = decode_base64_urlsafe( signature_reject) reject_data_to_verify_as_bytes = ( struct.pack('<b', CHALLENGE_URL_VERSION) + b'DENY\0' + data_to_verify) try: verify_sig(reject_signature_as_bytes, reject_data_to_verify_as_bytes, user_dsa_public_key) challenge.add_session_info({'reject': True}) return 1 except ValueError: challenge.add_session_info({'reject': False}) log.error("reject signature mismatch!") return -1 return -1