Ejemplo n.º 1
0
    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
Ejemplo n.º 2
0
    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
Ejemplo n.º 3
0
    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