def test_translation_single_interpretation(self):
     otp_str1 = 'cccfgvgitchndibrrtuhdrgeufrdkrjfgutfjbnhhglv'
     otp_str2 = 'cccagvgitchndibrrtuhdrgeufrdkrjfgutfjbnhhglv'
     otp1 = OTP(otp_str1)
     otp2 = OTP(otp_str2)
     self.assertEqual(otp1.otp, otp_str1)
     self.assertEqual(otp2.otp, otp_str2)
    def test_otp_class(self):
        otp1 = OTP('tlerefhcvijlngibueiiuhkeibbcbecehvjiklltnbbl')
        otp2 = OTP('jjjjjjjjnhe.ngcgjeiuujjjdtgihjuecyixinxunkhj',
                   translate_otp=True)

        self.assertEqual(otp1.device_id, 'tlerefhcvijl')
        self.assertEqual(otp2.otp,
                         'ccccccccljdeluiucdgffccchkugjcfditgbglbflvjc')
Exemple #3
0
 def validate_code(self, code):
     otp = OTP(code)
     if not self.obj.secret or self.obj.secret != otp.device_id:
         return False
     if not self._validate_yubikey_otp(code):
         return False
     return True
Exemple #4
0
    def confirm_activation(self, code):
        """
        After successful confirmation store `device_id`
        in field `secret` MFAMethod model.
        """

        otp = OTP(code)
        self.obj.secret = otp.device_id
        self.obj.save(update_fields=('secret',))
    def verify_multi(self, otp_list, max_time_window=DEFAULT_MAX_TIME_WINDOW,
                     sl=None, timeout=None):
        """
        Verify a provided list of OTPs.

        :param max_time_window: Maximum number of seconds which can pass
                                between the first and last OTP generation for
                                the OTP to still be considered valid.
        :type max_time_window: ``int``
        """

        # Create the OTP objects
        otps = []
        for otp in otp_list:
            otps.append(OTP(otp, self.translate_otp))

        if len(otp_list) < 2:
            raise ValueError('otp_list needs to contain at least two OTPs')

        device_ids = set()
        for otp in otps:
            device_ids.add(otp.device_id)

        # Check that all the OTPs contain same device id
        if len(device_ids) != 1:
            raise Exception('OTPs contain different device ids')

        # Now we verify the OTPs and save the server response for each OTP.
        # We need the server response, to retrieve the timestamp.
        # It's possible to retrieve this value locally, without querying the
        # server but in this case, user would need to provide his AES key.
        for otp in otps:
            response = self.verify(otp.otp, True, sl, timeout,
                                   return_response=True)

            if not response:
                return False

            otp.timestamp = int(response['timestamp'])

        count = len(otps)
        delta = otps[count - 1].timestamp - otps[0].timestamp

        # OTPs have an 8Hz timestamp counter so we need to divide it to get
        # seconds
        delta = delta / 8

        if delta < 0:
            raise Exception('delta is smaller than zero. First OTP appears to '
                            'be older than the last one')

        if delta > max_time_window:
            raise Exception('More than %s seconds have passed between '
                            'generating the first and the last OTP.' %
                            (max_time_window))

        return True
Exemple #6
0
 def validate_code(self, code: str) -> bool:
     if (not self._mfa_method.secret
             or self._mfa_method.secret != OTP(code).device_id):
         return False
     return self._validate_yubikey_otp(code)
Exemple #7
0
 def confirm_activation(self, code: str) -> None:
     self._mfa_method.secret = OTP(code).device_id
     self._mfa_method.save(update_fields=("secret", ))
 def test_translation_multiple_interpretations(self):
     otp_str1 = 'vvbtbtndhtlfguefgluvbdcetnitidgkvfkbicevgcin'
     otp1 = OTP(otp_str1)
     self.assertEqual(otp1.otp, otp_str1)
Exemple #9
0
def offline_yubikey(monkeypatch, fake_yubikey):
    def mock_verify(*args, **kwargs):
        return True

    monkeypatch.setattr(target=Yubico, name="verify", value=mock_verify)
    assert OTP("123456").device_id == FAKE_YUBI_SECRET
Exemple #10
0
    def verify(self, otp, timestamp=False, sl=None, timeout=None,
               return_response=False):
        """
        Verify a provided OTP.

        :param otp: OTP to verify.
        :type otp: ``str``

        :param timestamp: True to include request timestamp and session counter
                          in the response. Defaults to False.
        :type timestamp: ``bool``

        :param sl: A value indicating percentage of syncing required by client.
        :type sl: ``int`` or ``str``

        :param timeout: Number of seconds to wait for sync responses.
        :type timeout: ``int``

        :param return_response: True to return a response object instead of the
                                status code. Defaults to False.
        :type return_response: ``bool``

        :return: True is the provided OTP is valid, False if the
        REPLAYED_OTP status value is returned or the response message signature
        verification failed and None for the rest of the status values.
        """
        ca_bundle_path = self._get_ca_bundle_path()

        otp = OTP(otp, self.translate_otp)
        rand_str = b(os.urandom(30))
        nonce = base64.b64encode(rand_str, b('xz'))[:25].decode('utf-8')
        query_string = self.generate_query_string(otp.otp, nonce, timestamp,
                                                  sl, timeout)

        threads = []
        timeout = timeout or DEFAULT_TIMEOUT
        for url in self.api_urls:
            thread = URLThread(url='%s?%s' % (url, query_string),
                               timeout=timeout,
                               verify_cert=self.verify_cert,
                               ca_bundle_path=ca_bundle_path,
                               max_retries=self.max_retries,
                               retry_delay=self.retry_delay)
            thread.start()
            threads.append(thread)

        # Wait for a first positive or negative response
        start_time = time.time()

        # If there's only one server to talk to, raise thread exceptions.
        # Otherwise we end up ignoring a good answer from a different
        # server later.
        raise_exceptions = (len(threads) == 1)

        # pylint: disable=too-many-nested-blocks
        while threads and (start_time + timeout) > time.time():
            for thread in threads:
                if not thread.is_alive():
                    if thread.exception and raise_exceptions:
                        raise thread.exception
                    elif thread.response:
                        status = self.verify_response(thread.response,
                                                      otp.otp, nonce,
                                                      return_response)

                        if status:
                            # pylint: disable=no-else-return
                            if return_response:
                                return status
                            else:
                                return True
                    threads.remove(thread)
            time.sleep(0.1)

        # Timeout or no valid response received
        raise Exception('NO_VALID_ANSWERS')