def test_28_yubikey_utils(self):
        self.assertEquals(modhex_encode(b'\x47'), 'fi')
        self.assertEquals(modhex_encode(b'\xba\xad\xf0\x0d'), 'nlltvcct')
        self.assertEquals(modhex_encode(binascii.unhexlify('0123456789abcdef')),
                          'cbdefghijklnrtuv')
        self.assertEquals(modhex_encode('Hallo'), 'fjhbhrhrhv')
        # and the other way around
        self.assertEquals(modhex_decode('fi'), b'\x47')
        self.assertEquals(modhex_decode('nlltvcct'), b'\xba\xad\xf0\x0d')
        self.assertEquals(modhex_decode('cbdefghijklnrtuv'),
                          binascii.unhexlify('0123456789abcdef'))
        self.assertEquals(modhex_decode('fjhbhrhrhv'), b'Hallo')

        # now test the crc function
        self.assertEquals(checksum(b'\x01\x02\x03\x04'), 0xc66e)
        self.assertEquals(checksum(b'\x01\x02\x03\x04\x919'), 0xf0b8)
Beispiel #2
0
    def test_28_yubikey_utils(self):
        self.assertEquals(modhex_encode(b'\x47'), 'fi')
        self.assertEquals(modhex_encode(b'\xba\xad\xf0\x0d'), 'nlltvcct')
        self.assertEquals(modhex_encode(binascii.unhexlify('0123456789abcdef')),
                          'cbdefghijklnrtuv')
        self.assertEquals(modhex_encode('Hallo'), 'fjhbhrhrhv')
        # and the other way around
        self.assertEquals(modhex_decode('fi'), b'\x47')
        self.assertEquals(modhex_decode('nlltvcct'), b'\xba\xad\xf0\x0d')
        self.assertEquals(modhex_decode('cbdefghijklnrtuv'),
                          binascii.unhexlify('0123456789abcdef'))
        self.assertEquals(modhex_decode('fjhbhrhrhv'), b'Hallo')

        # now test the crc function
        self.assertEquals(checksum(b'\x01\x02\x03\x04'), 0xc66e)
        self.assertEquals(checksum(b'\x01\x02\x03\x04\x919'), 0xf0b8)
Beispiel #3
0
    def check_yubikey_pass(passw):
        """
        if the Token has set a PIN the user must also enter the PIN for
        authentication!

        This checks the output of a yubikey in AES mode without providing
        the serial number.
        The first 12 (of 44) or 16 of 48) characters are the tokenid, which is
        stored in the tokeninfo yubikey.tokenid or the prefix yubikey.prefix.

        :param passw: The password that consist of the static yubikey prefix and
            the otp
        :type passw: string

        :return: True/False and the User-Object of the token owner
        :rtype: dict
        """
        opt = {}
        res = False

        token_list = []

        # strip the yubico OTP and the PIN
        prefix = passw[:-32][-16:]

        from privacyidea.lib.token import get_tokens
        from privacyidea.lib.token import check_token_list

        # See if the prefix matches the serial number
        if prefix[:2] != "vv" and prefix[:2] != "cc":
            try:
                # Keep the backward compatibility
                serialnum = "UBAM" + modhex_decode(prefix)
                for i in range(1, 3):
                    s = "{0!s}_{1!s}".format(serialnum, i)
                    toks = get_tokens(serial=s, tokentype='yubikey')
                    token_list.extend(toks)
            except TypeError as exx:  # pragma: no cover
                log.error("Failed to convert serialnumber: {0!r}".format(exx))

        # Now, we see, if the prefix matches the new version
        if not token_list:
            # If we did not find the token via the serial number, we also
            # search for the yubikey.prefix in the tokeninfo.
            token_candidate_list = get_tokens(
                tokentype='yubikey', tokeninfo={"yubikey.prefix": prefix})
            token_list.extend(token_candidate_list)

        if not token_list:
            opt['action_detail'] = (
                "The prefix {0!s} could not be found!".format(prefix))
            return res, opt

        (res, opt) = check_token_list(token_list,
                                      passw,
                                      allow_reset_all_tokens=True)
        return res, opt
    def check_yubikey_pass(passw):
        """
        if the Token has set a PIN the user must also enter the PIN for
        authentication!

        This checks the output of a yubikey in AES mode without providing
        the serial number.
        The first 12 (of 44) or 16 of 48) characters are the tokenid, which is
        stored in the tokeninfo yubikey.tokenid or the prefix yubikey.prefix.

        :param passw: The password that consist of the static yubikey prefix and
            the otp
        :type passw: string

        :return: True/False and the User-Object of the token owner
        :rtype: dict
        """
        opt = {}
        res = False

        token_list = []

        # strip the yubico OTP and the PIN
        prefix = passw[:-32][-16:]

        from privacyidea.lib.token import get_tokens
        from privacyidea.lib.token import check_token_list

        # See if the prefix matches the serial number
        if prefix[:2] != "vv" and prefix[:2] != "cc":
            try:
                # Keep the backward compatibility
                serialnum = "UBAM" + modhex_decode(prefix)
                for i in range(1, 3):
                    s = "{0!s}_{1!s}".format(serialnum, i)
                    toks = get_tokens(serial=s, tokentype='yubikey')
                    token_list.extend(toks)
            except TypeError as exx:  # pragma: no cover
                log.error("Failed to convert serialnumber: {0!r}".format(exx))

        # Now, we see, if the prefix matches the new version
        if not token_list:
            # If we did not find the token via the serial number, we also
            # search for the yubikey.prefix in the tokeninfo.
            token_candidate_list = get_tokens(tokentype='yubikey',
                                              tokeninfo={"yubikey.prefix":
                                                             prefix})
            token_list.extend(token_candidate_list)

        if not token_list:
            opt['action_detail'] = ("The prefix {0!s} could not be found!".format(
                                    prefix))
            return res, opt

        (res, opt) = check_token_list(token_list, passw, allow_reset_all_tokens=True)
        return res, opt
Beispiel #5
0
    def check_yubikey_pass(passw):
        """
        if the Token has set a PIN the user must also enter the PIN for
        authentication!

        This checks the output of a yubikey in AES mode without providing
        the serial number.
        The first 12 (of 44) or 16 of 48) characters are the tokenid, which is
        stored in the tokeninfo.

        :param passw: The password that consist of the static yubikey prefix and
            the otp
        :type passw: string

        :return: True/False and the User-Object of the token owner
        :rtype: dict
        """
        opt = {}
        res = False

        token_list = []

        # strip the yubico OTP and the PIN
        modhex_serial = passw[:-32][-16:]
        try:
            serialnum = "UBAM" + modhex_decode(modhex_serial)
        except TypeError as exx:  # pragma: no cover
            log.error("Failed to convert serialnumber: %r" % exx)
            return res, opt

        # build list of possible yubikey tokens
        serials = [serialnum]
        for i in range(1, 3):
            serials.append("%s_%s" % (serialnum, i))

        from privacyidea.lib.token import get_tokens
        from privacyidea.lib.token import check_token_list
        for serial in serials:
            tokenobject_list = get_tokens(serial=serial)
            token_list.extend(tokenobject_list)

        if len(token_list) == 0:
            opt['action_detail'] = ("The serial %s could not be found!" %
                                    serialnum)
            return res, opt

        (res, opt) = check_token_list(token_list, passw)
        return res, opt
Beispiel #6
0
    def check_yubikey_pass(passw):
        """
        if the Token has set a PIN the user must also enter the PIN for
        authentication!

        This checks the output of a yubikey in AES mode without providing
        the serial number.
        The first 12 (of 44) or 16 of 48) characters are the tokenid, which is
        stored in the tokeninfo.

        :param passw: The password that consist of the static yubikey prefix and
            the otp
        :type passw: string

        :return: True/False and the User-Object of the token owner
        :rtype: dict
        """
        opt = {}
        res = False

        token_list = []

        # strip the yubico OTP and the PIN
        modhex_serial = passw[:-32][-16:]
        try:
            serialnum = "UBAM" + modhex_decode(modhex_serial)
        except TypeError as exx:  # pragma: no cover
            log.error("Failed to convert serialnumber: %r" % exx)
            return res, opt

        # build list of possible yubikey tokens
        serials = [serialnum]
        for i in range(1, 3):
            serials.append("%s_%s" % (serialnum, i))

        from privacyidea.lib.token import get_tokens
        from privacyidea.lib.token import check_token_list
        for serial in serials:
            tokenobject_list = get_tokens(serial=serial)
            token_list.extend(tokenobject_list)

        if not token_list:
            opt['action_detail'] = ("The serial %s could not be found!" %
                                    serialnum)
            return res, opt

        (res, opt) = check_token_list(token_list, passw)
        return res, opt
Beispiel #7
0
def parseYubicoCSV(csv):
    '''
    This function reads the CSV data as created by the Yubico personalization
    GUI.

    Traditional Format:
    Yubico OTP,12/11/2013 11:10,1,vvgutbiedkvi,
            ab86c04de6a3,d26a7c0f85fdda28bd816e406342b214,,,0,0,0,0,0,0,0,0,0,0
    OATH-HOTP,11.12.13 18:55,1,cccccccccccc,,
            916821d3a138bf855e70069605559a206ba854cd,,,0,0,0,6,0,0,0,0,0,0
    Static Password,11.12.13 19:08,1,,d5a3d50327dc,
            0e8e37b0e38b314a56748c030f58d21d,,,0,0,0,0,0,0,0,0,0,0

    Yubico Format:
    # OATH mode
    508326,,0,69cfb9202438ca68964ec3244bfa4843d073a43b,,2013-12-12T08:41:07,
    1382042,,0,bf7efc1c8b6f23604930a9ce693bdd6c3265be00,,2013-12-12T08:41:17,
    # Yubico mode
    508326,cccccccccccc,83cebdfb7b93,a47c5bf9c152202f577be6721c0113af,,
            2013-12-12T08:43:17,
    # static mode
    508326,,,9e2fd386224a7f77e9b5aee775464033,,2013-12-12T08:44:34,

    column 0: serial
    column 1: public ID in yubico mode
    column 2: private ID in yubico mode, 0 in OATH mode, blank in static mode
    column 3: AES key

    BUMMER: The Yubico Format does not contain the information,
    which slot of the token was written.

    If now public ID or serial is given, we can not import the token, as the
    returned dictionary needs the token serial as a key.

    It returns a dictionary with the new tokens to be created:

        {
            serial: {   'type' : yubico,
                        'otpkey' : xxxx,
                        'otplen' : xxx,
                        'description' : xxx
                         }
        }
    '''
    TOKENS = {}
    csv_array = csv.split('\n')

    log.debug("the file contains {0:d} tokens.".format(len(csv_array)))
    for line in csv_array:
        l = line.split(',')
        serial = ""
        key = ""
        otplen = 32
        public_id = ""
        slot = ""
        if len(l) >= 6:
            first_column = l[0].strip()
            if first_column.lower() in ["yubico otp",
                                        "oath-hotp",
                                        "static password"]:
                # traditional format
                typ = l[0].strip()
                slot = l[2].strip()
                public_id = l[3].strip()
                key = l[5].strip()

                if public_id == "":
                    # Usually a "static password" does not have a public ID!
                    # So we would bail out here for static passwords.
                    log.warning("No public ID in line {0!r}".format(line))
                    continue

                serial_int = int(binascii.hexlify(modhex_decode(public_id)),
                                 16)

                if typ.lower() == "yubico otp":
                    ttype = "yubikey"
                    otplen = 32 + len(public_id)
                    serial = "UBAM{0:08d}_{1!s}".format(serial_int, slot)
                    TOKENS[serial] = {'type': ttype,
                                      'otpkey': key,
                                      'otplen': otplen,
                                      'description': public_id
                                      }
                elif typ.lower() == "oath-hotp":
                    '''
                    WARNING: this does not work out at the moment, since the
                    Yubico GUI either
                    1. creates a serial in the CSV, but then the serial is
                       always prefixed! We can not authenticate with this!
                    2. if it does not prefix the serial there is no serial in
                       the CSV! We can not import and assign the token!
                    '''
                    ttype = "hotp"
                    otplen = 6
                    serial = "UBOM{0:08d}_{1!s}".format(serial_int, slot)
                    TOKENS[serial] = {'type': ttype,
                                      'otpkey': key,
                                      'otplen': otplen,
                                      'description': public_id
                                      }
                else:
                    log.warning("at the moment we do only support Yubico OTP"
                                " and HOTP: %r" % line)
                    continue
            elif first_column.isdigit():
                # first column is a number, (serial number), so we are
                # in the yubico format
                serial = first_column
                # the yubico format does not specify a slot
                slot = "X"
                key = l[3].strip()
                if l[2].strip() == "0":
                    # HOTP
                    typ = "hotp"
                    serial = "UBOM{0!s}_{1!s}".format(serial, slot)
                    otplen = 6
                elif l[2].strip() == "":
                    # Static
                    typ = "pw"
                    serial = "UBSM{0!s}_{1!s}".format(serial, slot)
                    key = _create_static_password(key)
                    otplen = len(key)
                    log.warning("We can not enroll a static mode, since we do"
                                " not know the private identify and so we do"
                                " not know the static password.")
                    continue
                else:
                    # Yubico
                    typ = "yubikey"
                    serial = "UBAM{0!s}_{1!s}".format(serial, slot)
                    public_id = l[1].strip()
                    otplen = 32 + len(public_id)
                TOKENS[serial] = {'type': typ,
                                  'otpkey': key,
                                  'otplen': otplen,
                                  'description': public_id
                                  }
        else:
            log.warning("the line {0!r} did not contain a enough values".format(line))
            continue

    return TOKENS
Beispiel #8
0
def parseYubicoCSV(csv):
    '''
    This function reads the CSV data as created by the Yubico personalization
    GUI.

    Traditional Format:
    Yubico OTP,12/11/2013 11:10,1,vvgutbiedkvi,
            ab86c04de6a3,d26a7c0f85fdda28bd816e406342b214,,,0,0,0,0,0,0,0,0,0,0
    OATH-HOTP,11.12.13 18:55,1,cccccccccccc,,
            916821d3a138bf855e70069605559a206ba854cd,,,0,0,0,6,0,0,0,0,0,0
    Static Password,11.12.13 19:08,1,,d5a3d50327dc,
            0e8e37b0e38b314a56748c030f58d21d,,,0,0,0,0,0,0,0,0,0,0

    Yubico Format:
    # OATH mode
    508326,,0,69cfb9202438ca68964ec3244bfa4843d073a43b,,2013-12-12T08:41:07,
    1382042,,0,bf7efc1c8b6f23604930a9ce693bdd6c3265be00,,2013-12-12T08:41:17,
    # Yubico mode
    508326,cccccccccccc,83cebdfb7b93,a47c5bf9c152202f577be6721c0113af,,
            2013-12-12T08:43:17,
    # static mode
    508326,,,9e2fd386224a7f77e9b5aee775464033,,2013-12-12T08:44:34,

    column 0: serial
    column 1: public ID in yubico mode
    column 2: private ID in yubico mode, 0 in OATH mode, blank in static mode
    column 3: AES key

    BUMMER: The Yubico Format does not contain the information,
    which slot of the token was written.

    If now public ID or serial is given, we can not import the token, as the
    returned dictionary needs the token serial as a key.

    It returns a dictionary with the new tokens to be created:

        {
            serial: {   'type' : yubico,
                        'otpkey' : xxxx,
                        'otplen' : xxx,
                        'description' : xxx
                         }
        }
    '''
    TOKENS = {}
    csv_array = csv.split('\n')

    log.debug("the file contains {0:d} tokens.".format(len(csv_array)))
    for line in csv_array:
        l = line.split(',')
        serial = ""
        key = ""
        otplen = 32
        public_id = ""
        slot = ""
        if len(l) >= 6:
            first_column = l[0].strip()
            if first_column.lower() in [
                    "yubico otp", "oath-hotp", "static password"
            ]:
                # traditional format
                typ = l[0].strip()
                slot = l[2].strip()
                public_id = l[3].strip()
                key = l[5].strip()

                if public_id == "":
                    # Usually a "static password" does not have a public ID!
                    # So we would bail out here for static passwords.
                    log.warning("No public ID in line {0!r}".format(line))
                    continue

                serial_int = int(binascii.hexlify(modhex_decode(public_id)),
                                 16)

                if typ.lower() == "yubico otp":
                    ttype = "yubikey"
                    otplen = 32 + len(public_id)
                    serial = "UBAM{0:08d}_{1!s}".format(serial_int, slot)
                    TOKENS[serial] = {
                        'type': ttype,
                        'otpkey': key,
                        'otplen': otplen,
                        'description': public_id
                    }
                elif typ.lower() == "oath-hotp":
                    '''
                    WARNING: this does not work out at the moment, since the
                    Yubico GUI either
                    1. creates a serial in the CSV, but then the serial is
                       always prefixed! We can not authenticate with this!
                    2. if it does not prefix the serial there is no serial in
                       the CSV! We can not import and assign the token!
                    '''
                    ttype = "hotp"
                    otplen = 6
                    serial = "UBOM{0:08d}_{1!s}".format(serial_int, slot)
                    TOKENS[serial] = {
                        'type': ttype,
                        'otpkey': key,
                        'otplen': otplen,
                        'description': public_id
                    }
                else:
                    log.warning("at the moment we do only support Yubico OTP"
                                " and HOTP: %r" % line)
                    continue
            elif first_column.isdigit():
                # first column is a number, (serial number), so we are
                # in the yubico format
                serial = first_column
                # the yubico format does not specify a slot
                slot = "X"
                key = l[3].strip()
                if l[2].strip() == "0":
                    # HOTP
                    typ = "hotp"
                    serial = "UBOM{0!s}_{1!s}".format(serial, slot)
                    otplen = 6
                elif l[2].strip() == "":
                    # Static
                    typ = "pw"
                    serial = "UBSM{0!s}_{1!s}".format(serial, slot)
                    key = _create_static_password(key)
                    otplen = len(key)
                    log.warning("We can not enroll a static mode, since we do"
                                " not know the private identify and so we do"
                                " not know the static password.")
                    continue
                else:
                    # Yubico
                    typ = "yubikey"
                    serial = "UBAM{0!s}_{1!s}".format(serial, slot)
                    public_id = l[1].strip()
                    otplen = 32 + len(public_id)
                TOKENS[serial] = {
                    'type': typ,
                    'otpkey': key,
                    'otplen': otplen,
                    'description': public_id
                }
        else:
            log.warning(
                "the line {0!r} did not contain a enough values".format(line))
            continue

    return TOKENS
Beispiel #9
0
    def check_otp(self, anOtpVal, counter=None, window=None, options=None):
        """
        validate the token otp against a given otpvalue

        :param anOtpVal: the to be verified otpvalue
        :type anOtpVal:  string

        :param counter: the counter state. It is not used by the Yubikey
            because the current counter value is sent encrypted inside the
            OTP value
        :type counter: int

        :param window: the counter +window, which is not used in the Yubikey
            because the current counter value is sent encrypted inside the
            OTP, allowing a simple comparison between the encrypted counter
            value and the stored counter value
        :type window: int

        :param options: the dict, which could contain token specific info
        :type options: dict

        :return: the counter state or an error code (< 0):
        -1 if the OTP is old (counter < stored counter)
        -2 if the private_uid sent in the OTP is wrong (different from the one stored with the token)
        -3 if the CRC verification fails
        :rtype: int

        """
        res = -1

        serial = self.token.serial
        secret = self.token.get_otpkey()

        # The prefix is the characters in front of the last 32 chars
        yubi_prefix = anOtpVal[:-32]
        # The variable otp val is the last 32 chars
        yubi_otp = anOtpVal[-32:]

        try:
            otp_bin = modhex_decode(yubi_otp)
        except KeyError:
            # The OTP value is no yubikey aes otp value and can not be decoded
            return -4

        msg_bin = secret.aes_decrypt(otp_bin)
        msg_hex = binascii.hexlify(msg_bin)

        # The checksum is a CRC-16 (16-bit ISO 13239 1st complement) that
        # occupies the last 2 bytes of the decrypted OTP value. Calculating the
        # CRC-16 checksum of the whole decrypted OTP should give a fixed
        # residual
        # of 0xf0b8 (see Yubikey-Manual - Chapter 6: Implementation details).
        log.debug("calculated checksum (61624): {0!r}".format(
            checksum(msg_hex)))
        if checksum(msg_hex) != 0xf0b8:  # pragma: no cover
            log.warning("CRC checksum for token {0!r} failed".format(serial))
            return -3

        uid = msg_hex[0:12]
        log.debug("uid: {0!r}".format(uid))
        log.debug("prefix: {0!r}".format(
            binascii.hexlify(modhex_decode(yubi_prefix))))
        # usage_counter can go from 1 – 0x7fff
        usage_counter = msg_hex[12:16]
        timestamp = msg_hex[16:22]
        # session counter can go from 00 to 0xff
        session_counter = msg_hex[22:24]
        random = msg_hex[24:28]
        crc = msg_hex[28:]
        log.debug("decrypted: usage_count: {0!r}, session_count: {1!r}".format(
            usage_counter, session_counter))

        # create the counter as integer
        # Note: The usage counter is stored LSB!

        count_hex = usage_counter[2:4] + usage_counter[0:2] + session_counter
        count_int = int(count_hex, 16)
        log.debug('decrypted counter: {0!r}'.format(count_int))

        tokenid = self.get_tokeninfo("yubikey.tokenid")
        if not tokenid:
            log.debug("Got no tokenid for {0!r}. Setting to {1!r}.".format(
                serial, uid))
            tokenid = uid
            self.add_tokeninfo("yubikey.tokenid", tokenid)

        prefix = self.get_tokeninfo("yubikey.prefix")
        if not prefix:
            log.debug("Got no prefix for {0!r}. Setting to {1!r}.".format(
                serial, yubi_prefix))
            self.add_tokeninfo("yubikey.prefix", yubi_prefix)

        if tokenid != uid:
            # wrong token!
            log.warning("The wrong token was presented for %r. "
                        "Got %r, expected %r." % (serial, uid, tokenid))
            return -2

        # TODO: We also could check the timestamp
        # see http://www.yubico.com/wp-content/uploads/2013/04/YubiKey-Manual-v3_1.pdf
        log.debug('compare counter to database counter: {0!r}'.format(
            self.token.count))
        if count_int >= self.token.count:
            res = count_int
            # on success we save the used counter
            self.inc_otp_counter(res)

        return res
Beispiel #10
0
    def check_otp(self, anOtpVal, counter=None, window=None, options=None):
        """
        validate the token otp against a given otpvalue

        :param anOtpVal: the to be verified otpvalue
        :type anOtpVal:  string

        :param counter: the counter state. It is not used by the Yubikey
            because the current counter value is sent encrypted inside the
            OTP value
        :type counter: int

        :param window: the counter +window, which is not used in the Yubikey
            because the current counter value is sent encrypted inside the
            OTP, allowing a simple comparison between the encrypted counter
            value and the stored counter value
        :type window: int

        :param options: the dict, which could contain token specific info
        :type options: dict

        :return: the counter state or an error code (< 0):
        -1 if the OTP is old (counter < stored counter)
        -2 if the private_uid sent in the OTP is wrong (different from the one stored with the token)
        -3 if the CRC verification fails
        :rtype: int

        """
        res = -1

        serial = self.token.serial
        secret = self.token.get_otpkey()

        # The prefix is the characters in front of the last 32 chars
        yubi_prefix = anOtpVal[:-32]
        # The variable otp val is the last 32 chars
        yubi_otp = anOtpVal[-32:]

        # TODO: We can also check the PREFIX! At the moment, we do not use it!

        try:
            otp_bin = modhex_decode(yubi_otp)
        except KeyError:
            # The OTP value is no yubikey aes otp value and can not be decoded
            return -4

        msg_bin = secret.aes_decrypt(otp_bin)
        msg_hex = binascii.hexlify(msg_bin)

        # The checksum is a CRC-16 (16-bit ISO 13239 1st complement) that
        # occupies the last 2 bytes of the decrypted OTP value. Calculating the
        # CRC-16 checksum of the whole decrypted OTP should give a fixed
        # residual
        # of 0xf0b8 (see Yubikey-Manual - Chapter 6: Implementation details).
        log.debug("calculated checksum (61624): %r" % checksum(msg_hex))
        if checksum(msg_hex) != 0xf0b8:  # pragma: no cover
            log.warning("CRC checksum for token %r failed" % serial)
            return -3

        uid = msg_hex[0:12]
        log.debug("uid: %r" % uid)
        log.debug("prefix: %r" % binascii.hexlify(modhex_decode(yubi_prefix)))
        # usage_counter can go from 1 – 0x7fff
        usage_counter = msg_hex[12:16]
        timestamp = msg_hex[16:22]
        # session counter can go from 00 to 0xff
        session_counter = msg_hex[22:24]
        random = msg_hex[24:28]
        crc = msg_hex[28:]
        log.debug("decrypted: usage_count: %r, session_count: %r" %
                  (usage_counter, session_counter))

        # create the counter as integer
        # Note: The usage counter is stored LSB!

        count_hex = usage_counter[2:4] + usage_counter[0:2] + session_counter
        count_int = int(count_hex, 16)
        log.debug('decrypted counter: %r' % count_int)

        tokenid = self.get_tokeninfo("yubikey.tokenid")
        if not tokenid:
            log.debug("Got no tokenid for %r. Setting to %r." % (serial, uid))
            tokenid = uid
            self.add_tokeninfo("yubikey.tokenid", tokenid)

        if tokenid != uid:
            # wrong token!
            log.warning("The wrong token was presented for %r. Got %r, expected %r."
                        % (serial, uid, tokenid))
            return -2


        # TODO: We also could check the timestamp
        # see http://www.yubico.com/wp-content/uploads/2013/04/YubiKey-Manual-v3_1.pdf
        log.debug('compare counter to database counter: %r' % self.token.count)
        if count_int >= self.token.count:
            res = count_int
            # on success we save the used counter
            self.inc_otp_counter(res)

        return res
Beispiel #11
0
def check_yubikey_pass(passw):
    """
    This only works without a PIN!

    This checks the output of a yubikey in AES mode without providing
    the serial number.
    The first 12 (of 44) or 16 of 48) characters are the tokenid, which is
    stored in the tokeninfo.

    :param passw: The password that consist of the static yubikey prefix and
        the otp
    :type passw: string

    :return: True/False and the User-Object of the token owner
    :rtype: dict
    """
    opt = {}
    res = False

    token_list = []

    # strip the yubico OTP and the PIN
    modhex_serial = passw[:-32][-16:]
    try:
        serialnum = "UBAM" + modhex_decode(modhex_serial)
    except TypeError as exx:  # pragma: no cover
        log.error("Failed to convert serialnumber: %r" % exx)
        return res, opt

    # build list of possible yubikey tokens
    serials = [serialnum]
    for i in range(1, 3):
        serials.append("%s_%s" % (serialnum, i))

    from privacyidea.lib.token import get_tokens
    from privacyidea.lib.token import check_token_list
    for serial in serials:
        tokenobject_list = get_tokens(serial=serial)
        token_list.extend(tokenobject_list)

    if len(token_list) == 0:
        opt['action_detail'] = ("The serial %s could not be found!" % serialnum)
        return res, opt

    # FIXME if the Token has set a PIN and the User does not want
    # to enter the PIN
    # for authentication, we need to do something different here...
    # and avoid PIN checking in __checkToken.
    # We could pass an "option" to __checkToken.
    (res, opt) = check_token_list(token_list, passw)

    # Now we need to get the user
    # TODO: Migration
    #if res is not False and 'serial' in c.audit:
    #    serial = c.audit.get('serial', None)
    #    if serial is not None:
    #        user = getTokenOwner(serial)
    #        c.audit['user'] = user.login
    #        c.audit['realm'] = user.realm
    #        opt = {}
    #        opt['user'] = user.login
    #        opt['realm'] = user.realm
    #        opt['serial'] = serial

    return res, opt