예제 #1
0
파일: validate.py 프로젝트: smartree/LinOTP
    def checkYubikeyPass(self, passw):
        """
        Checks the password of a yubikey in Yubico mode (44,48), where
        the first 12 or 16 characters are the tokenid

        :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
        """

        audit = context['audit']
        opt = None
        res = False

        tokenList = []

        # strip the yubico OTP and the PIN
        modhex_serial = passw[:-32][-16:]
        try:
            hex_serial = modhex_decode(modhex_serial)
            serialnum = "UBAM" + binascii.unhexlify(hex_serial).decode('utf-8')
        except TypeError as exx:
            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))

        for serial in serials:
            tokens = getTokens4UserOrSerial(serial=serial,
                                            read_for_update=True)
            tokenList.extend(tokens)

        if len(tokenList) == 0:
            audit['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) = self.checkTokenList(tokenList, passw)

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

        return res, opt
예제 #2
0
파일: validate.py 프로젝트: choth02/LinOTP
    def checkYubikeyPass(self, passw):
        """
        Checks the password of a yubikey in Yubico mode (44,48), where
        the first 12 or 16 characters are the tokenid

        :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
        """

        audit = self.context['audit']
        opt = None
        res = False

        tokenList = []

        # strip the yubico OTP and the PIN
        modhex_serial = passw[:-32][-16:]
        try:
            serialnum = "UBAM" + modhex_decode(modhex_serial)
        except TypeError as exx:
            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))

        for serial in serials:
            tokens = linotp.lib.token.getTokens4UserOrSerial(
                                        serial=serial, context=self.context)
            tokenList.extend(tokens)

        if len(tokenList) == 0:
            audit['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) = self.checkTokenList(tokenList, passw)

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

        return res, opt
예제 #3
0
    def checkOtp(self, otpVal, counter=None, window=None, options=None):
        """
        checkOtp - validate the token otp against a given otpvalue

        :param otpVal: the to be verified otpvalue
        :type otpVal:  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

        From: http://www.yubico.com/wp-content/uploads/2013/04/YubiKey-Manual-v3_1.pdf
                    6 Implementation details

        """
        res = -1

        if len(otpVal) < self.getOtpLen():
            return res

        serial = self.token.getSerial()
        secObj = self._get_secret_object()

        anOtpVal = otpVal.lower()

        # The prefix is the characters in front of the last 32 chars
        # We can also check the PREFIX! At the moment, we do not use it!
        yubi_prefix = anOtpVal[:-32]

        # verify the prefix if any
        enroll_prefix = self.getFromTokenInfo('public_uid', None)
        if enroll_prefix and enroll_prefix != yubi_prefix:
            return res

        # The variable otp val is the last 32 chars
        yubi_otp = anOtpVal[-32:]

        try:
            otp_bin = modhex_decode(yubi_otp)
            msg_bin = secObj.aes_decrypt(otp_bin)
        except KeyError:
            log.warning("failed to decode yubi_otp!")
            return res

        msg_hex = binascii.hexlify(msg_bin)

        uid = msg_hex[0:12]
        log.debug("[checkOtp] uid: %r" % uid)
        log.debug("[checkOtp] prefix: %r" %
                  binascii.hexlify(modhex_decode(yubi_prefix)))

        # usage_counter can go from 1 – 0x7fff
        usage_counter = msg_hex[12:16]

        # TODO: We also could check the timestamp
        # - the timestamp. see http://www.yubico.com/wp-content/uploads/2013/04/YubiKey-Manual-v3_1.pdf
        timestamp = msg_hex[16:22]

        # session counter can go from 00 to 0xff
        session_counter = msg_hex[22:24]
        random = msg_hex[24:28]

        log.debug("[checkOtp] decrypted: usage_count: %r, session_count: %r" %
                  (usage_counter, session_counter))

        # 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).
        crc = msg_hex[28:]
        log.debug("[checkOtp] calculated checksum (61624): %r" %
                  checksum(msg_hex))
        if checksum(msg_hex) != 0xf0b8:
            log.warning("[checkOtp] CRC checksum for token %r failed" % serial)
            return -3

        # 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('[checkOtp] decrypted counter: %r' % count_int)

        tokenid = self.getFromTokenInfo("yubikey.tokenid")
        if not tokenid:
            log.debug("[checkOtp] Got no tokenid for %r. Setting to %r." %
                      (serial, uid))
            tokenid = uid
            self.addToTokenInfo("yubikey.tokenid", tokenid)

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

        log.debug('[checkOtp] compare counter to LinOtpCount: %r' %
                  self.token.LinOtpCount)
        if count_int >= self.token.LinOtpCount:
            res = count_int

        return res
예제 #4
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,
                        'hmac_key' : xxxx,
                        'otplen' : xxx,
                        'description' : xxx
                         }
        }
    """
    TOKENS = {}
    log.debug("[parseYubicoCSV] starting to parse an yubico csv file.")

    csv_array = csv.split("\n")

    log.debug("[parseYubicoCSV] the file contains %i tokens." % 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 == "":
                    log.warning("No public ID in line %r" % line)
                    serial_int = int(binascii.hexlify(os.urandom(4)), 16)
                else:
                    serial_int = int(binascii.hexlify(modhex_decode(public_id)), 16)

                if typ.lower() == "yubico otp":
                    ttype = "yubikey"
                    otplen = 32 + len(public_id)
                    serial = "UBAM%08d_%s" % (serial_int, slot)
                    TOKENS[serial] = {"type": ttype, "hmac_key": key, "otplen": otplen, "description": public_id}
                elif typ.lower() == "oath-hotp":
                    """
                    TODO: this does not work out at the moment, since the 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 = "hmac"
                    otplen = 6
                    serial = "UBOM%08d_%s" % (serial_int, slot)
                    TOKENS[serial] = {"type": ttype, "hmac_key": key, "otplen": otplen, "description": public_id}
                else:
                    log.warning("[parseYubicoCSV] 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 = "hmac"
                    serial = "UBOM%s_%s" % (serial, slot)
                    otplen = 6
                elif l[2].strip() == "":
                    # Static
                    typ = "pw"
                    serial = "UBSM%s_%s" % (serial, slot)
                    key = create_static_password(key)
                    otplen = len(key)
                    log.warning(
                        "[parseYubcoCSV] 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%s_%s" % (serial, slot)
                    public_id = l[1].strip()
                    otplen = 32 + len(public_id)
                TOKENS[serial] = {"type": typ, "hmac_key": key, "otplen": otplen, "description": public_id}
        else:
            log.warning("[parseYubicoCSV] the line %r did not contain a enough values" % line)
            continue

    log.debug("[parseOATHcsv] read the following values: %s" % str(TOKENS))

    return TOKENS
예제 #5
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,
                        'hmac_key' : xxxx,
                        'otplen' : xxx,
                        'description' : xxx
                         }
        }
    '''
    TOKENS = {}
    log.debug("[parseYubicoCSV] starting to parse an yubico csv file.")

    csv_array = csv.split('\n')

    log.debug("[parseYubicoCSV] the file contains %i tokens." % 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 == "":
                    log.warning("No public ID in line %r" % line)
                    serial_int = int(binascii.hexlify(os.urandom(4)), 16)
                else:
                    serial_int = int(
                        binascii.hexlify(modhex_decode(public_id)), 16)

                if typ.lower() == "yubico otp":
                    ttype = "yubikey"
                    otplen = 32 + len(public_id)
                    serial = "UBAM%08d_%s" % (serial_int, slot)
                    TOKENS[serial] = {
                        'type': ttype,
                        'hmac_key': key,
                        'otplen': otplen,
                        'description': public_id
                    }
                elif typ.lower() == "oath-hotp":
                    '''
                    TODO: this does not work out at the moment, since the 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 = "hmac"
                    otplen = 6
                    serial = "UBOM%08d_%s" % (serial_int, slot)
                    TOKENS[serial] = {
                        'type': ttype,
                        'hmac_key': key,
                        'otplen': otplen,
                        'description': public_id
                    }
                else:
                    log.warning(
                        "[parseYubicoCSV] 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 = "hmac"
                    serial = "UBOM%s_%s" % (serial, slot)
                    otplen = 6
                elif l[2].strip() == "":
                    # Static
                    typ = "pw"
                    serial = "UBSM%s_%s" % (serial, slot)
                    key = create_static_password(key)
                    otplen = len(key)
                    log.warning(
                        "[parseYubcoCSV] 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%s_%s" % (serial, slot)
                    public_id = l[1].strip()
                    otplen = 32 + len(public_id)
                TOKENS[serial] = {
                    'type': typ,
                    'hmac_key': key,
                    'otplen': otplen,
                    'description': public_id
                }
        else:
            log.warning(
                "[parseYubicoCSV] the line %r did not contain a enough values"
                % line)
            continue

    log.debug("[parseOATHcsv] read the following values: %s" % str(TOKENS))

    return TOKENS
예제 #6
0
    def checkOtp(self, otpVal, counter=None, window=None, options=None):
        """
        checkOtp - validate the token otp against a given otpvalue

        :param otpVal: the to be verified otpvalue
        :type otpVal:  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

        From: http://www.yubico.com/wp-content/uploads/2013/04/YubiKey-Manual-v3_1.pdf
                    6 Implementation details

        """
        log.debug("[checkOtp] begin. Validate the token otp: otpVal: %r, counter: %r,  options: %r "
                  % (otpVal, counter, options))
        res = -1

        if len(otpVal) < self.getOtpLen():
            return res

        serial = self.token.getSerial()
        secObj = self._get_secret_object()

        anOtpVal = otpVal.lower()

        # The prefix is the characters in front of the last 32 chars
        # We can also check the PREFIX! At the moment, we do not use it!
        yubi_prefix = anOtpVal[:-32]

        # verify the prefix if any
        enroll_prefix = self.getFromTokenInfo('public_uid', None)
        if enroll_prefix and enroll_prefix != yubi_prefix:
            return res

        # The variable otp val is the last 32 chars
        yubi_otp = anOtpVal[-32:]

        try:
            otp_bin = modhex_decode(yubi_otp)
            msg_bin = secObj.aes_decrypt(otp_bin)
        except KeyError:
            log.warning("failed to decode yubi_otp!")
            return res

        msg_hex = binascii.hexlify(msg_bin)

        uid = msg_hex[0:12]
        log.debug("[checkOtp] uid: %r" % uid)
        log.debug("[checkOtp] prefix: %r" % binascii.hexlify(modhex_decode(yubi_prefix)))

        # usage_counter can go from 1 – 0x7fff
        usage_counter = msg_hex[12:16]

        # TODO: We also could check the timestamp
        # - the timestamp. see http://www.yubico.com/wp-content/uploads/2013/04/YubiKey-Manual-v3_1.pdf
        timestamp = msg_hex[16:22]


        # session counter can go from 00 to 0xff
        session_counter = msg_hex[22:24]
        random = msg_hex[24:28]

        log.debug("[checkOtp] decrypted: usage_count: %r, session_count: %r"
                  % (usage_counter, session_counter))

        # 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).
        crc = msg_hex[28:]
        log.debug("[checkOtp] calculated checksum (61624): %r" % checksum(msg_hex))
        if checksum(msg_hex) != 0xf0b8:
            log.warning("[checkOtp] CRC checksum for token %r failed" % serial)
            return -3

        # 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('[checkOtp] decrypted counter: %r' % count_int)

        tokenid = self.getFromTokenInfo("yubikey.tokenid")
        if not tokenid:
            log.debug("[checkOtp] Got no tokenid for %r. Setting to %r." % (serial, uid))
            tokenid = uid
            self.addToTokenInfo("yubikey.tokenid", tokenid)

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


        log.debug('[checkOtp] compare counter to LinOtpCount: %r' % self.token.LinOtpCount)
        if count_int >= self.token.LinOtpCount:
            res = count_int

        return res
예제 #7
0
def test_hex2mod():
    m = 'fifjgjgkhchb'
    h = '474858596061'
    assert modhex_decode(m) == h