Exemple #1
0
def decrypt_secret(b64str, pincode):
    # split the secret into components
    try:
        (scheme, salt, ciphertext) = b64str.split('$')

        salt = base64.b64decode(salt)
        ciphertext = base64.b64decode(ciphertext)

    except (ValueError, TypeError):
        raise totpcgi.UserSecretError('Failed to parse encrypted secret')

    key = pbkdf2_hmac('sha256', pincode, salt, KDF_ITER, KEY_SIZE * 2)

    aes_key = key[:KEY_SIZE]
    hmac_key = key[KEY_SIZE:]

    sig_size = hashlib.sha256().digest_size
    sig = ciphertext[-sig_size:]
    data = ciphertext[:-sig_size]

    # verify hmac sig first
    if hmac.new(hmac_key, data, hashlib.sha256).digest() != sig:
        raise totpcgi.UserSecretError('Failed to verify hmac!')

    iv_bytes = data[:AES_BLOCK_SIZE]
    data = data[AES_BLOCK_SIZE:]

    cypher = AES.new(aes_key, AES.MODE_CBC, iv_bytes)
    data = cypher.decrypt(data)
    padlen = bytearray((data[-1], ))[0]
    secret = data[:-padlen].decode('utf-8')

    logger.debug('Decryption successful')

    return secret
Exemple #2
0
    def get_user_secret(self, user, pincode=None):

        totp_file = os.path.join(self.secrets_dir, user) + '.totp'
        logger.debug('Examining user secret file: %s' % totp_file)

        if not os.access(totp_file, os.R_OK):
            raise totpcgi.UserNotFound('%s.totp does not exist or is not readable' % user)

        fh = open(totp_file, 'r')
        lockf(fh, LOCK_SH)

        # secret is always the first entry
        secret = fh.readline()
        secret = secret.strip()

        using_encrypted_secret = False
        if secret.find('aes256+hmac256') == 0:
            using_encrypted_secret = True
            if pincode is not None:
                secret = totpcgi.utils.decrypt_secret(secret, pincode)
            else:
                raise totpcgi.UserSecretError('Secret is encrypted, but no pincode provided')

        gaus = totpcgi.GAUserSecret(secret)

        while True:
            line = fh.readline()

            if line == '':
                break

            line = line.strip()

            if len(line) and line[0] == '"':
                if line[2:12] == 'RATE_LIMIT':
                    (tries, seconds) = line[13:].split(' ')
                    gaus.rate_limit = (int(tries), int(seconds))
                    logger.debug('rate_limit=%s' % str(gaus.rate_limit))

                elif line[2:13] == 'WINDOW_SIZE':
                    window_size = int(line[14:])
                    if 0 < window_size < 3:
                        window_size = 3
                    gaus.window_size = window_size
                    logger.debug('window_size=%s' % window_size)

                elif line[2:14] == 'HOTP_COUNTER':
                    # This will most likely be overriden by user state, but load it up anyway,
                    # as this will trigger HOTP mode.
                    try:
                        gaus.set_hotp(int(line[15:]))
                    except ValueError:
                        gaus.set_hotp(0)

                    logger.debug('hotp_counter=%s' % gaus.counter)

            # Scratch code tokens are 8-digit
            # We ignore scratch tokens if we're using encrypted secret
            elif len(line) == 8 and not using_encrypted_secret:
                try:
                    gaus.scratch_tokens.append(int(line))
                    logger.debug('Found a scratch-code token, adding it')
                except ValueError:
                    logger.debug('Non-numeric scratch token found')
                    # don't fail, just pretend we didn't see it
                    continue

        lockf(fh, LOCK_UN)
        fh.close()

        # Make sure that we have a window_size defined
        # The topt configuration many not have had one, if not we need
        # to make sure we set it to the default of 3
        if not hasattr(gaus, 'window_size'):
                gaus.window_size = 3

        return gaus