#!/opt/local/bin/python3.13

import argparse
import logging
import sys

__VERSION__ = '1.0.0'

# Set up logging
logger = logging.getLogger("keybag_logger")
handler = logging.StreamHandler()
formatter = logging.Formatter('%(message)s')
handler.setFormatter(formatter)
logger.addHandler(handler)

class Keybag:
    def __init__(self, file_obj):
        self.size = 0
        self.uuid = ''
        self.version = 0
        self.type = 0
        self.hmackey = ''
        self.wrap = 0
        self.salt = ''
        self.iterations = 0
        

        self._read_header(file_obj)
        self.class_keys = self._read_class_keys(file_obj)

    def _read_header(self, file_obj):
        while True:
            tag = file_obj.read(4).decode('ascii')
            if tag == 'DATA': # DATA
                self.size = int.from_bytes(file_obj.read(4), byteorder='big')
            else:
                length = int.from_bytes(file_obj.read(4), byteorder='big')
                data = file_obj.read(length)

            if tag == 'VERS': # VERS
                self.version = int.from_bytes(data, byteorder='big')
            elif tag == 'TYPE':
                self.type = int.from_bytes(data, byteorder='big')
            elif tag == 'UUID':
                if not self.uuid:
                    self.uuid = data.hex()
                else:
                    file_obj.seek(-length - 8, 1)
                    break
            elif tag == 'HMCK':
                self.hmackey = data.hex()
            elif tag == 'WRAP':
                self.wrap = int.from_bytes(data, byteorder='big')
            elif tag == 'SALT':
                self.salt = data.hex()
            elif tag == 'ITER':
                self.iterations = int.from_bytes(data, byteorder='big')

    def _read_class_keys(self, file_obj):
        class_keys = {}

        for x in range(0, 10):
            stop = False
            while stop != True:
                tag = file_obj.read(4).decode('ascii')
                length = int.from_bytes(file_obj.read(4), byteorder='big')
                data = file_obj.read(length)
                # new class key
                if tag == 'UUID':
                    if class_keys.get(x):
                        if class_keys[x].get('UUID'):
                            file_obj.seek(-length - 8, 1)
                            stop = True
                        else:
                            class_keys[x] = {}
                    else:
                        class_keys[x] = {}
                if tag == 'WRAP' or tag == 'CLAS' or tag == 'KTYP':
                    class_keys[x][tag] = int.from_bytes(data, byteorder='big')
                else:
                    class_keys[x][tag] = data.hex()
                if file_obj.tell() > self.size:
                    stop = True
        return class_keys

    
    def print_keybag(self):
        logger.debug(f'SIZE: {self.size}')
        logger.debug(f'VERSION: {self.version}')
        logger.debug(f'TYPE: {self.type}')
        logger.debug(f'UUID: {self.uuid}')
        logger.debug(f'HMACKEY: {self.hmackey}')
        logger.debug(f'SALT: {self.salt}')
        logger.debug(f'ITERATIONS: {self.iterations}')
        for x, class_key in self.class_keys.items():
            logger.debug(f'{x}:')
            for key, value in class_key.items():
                logger.debug(f'    {key}: {value}')

def main():
    # Create the argument parser
    parser = argparse.ArgumentParser(description="Process a keybag file with a specified UID.")

    # Add the UID argument
    parser.add_argument(
        '--uid',
        type=str,
        required=True,
        help="Specify the device UID."
    )

    # Add the keybag file argument
    parser.add_argument(
        'keybag', 
        type=str, 
        help="Path to the keybag file."
    )

    # Add the debug flag
    parser.add_argument(
        '--debug', 
        action='store_true', 
        help="Enable debug logging."
    )

    # Parse the arguments
    args = parser.parse_args()

    if args.debug:
        logger.setLevel(logging.DEBUG)
    else:
        logger.setLevel(logging.WARNING)

    # Access the arguments
    uid = args.uid[0:32]
    keybag_path = args.keybag

    logger.debug(f'keybag2hashcat - version {__VERSION__}')

    with open(keybag_path, 'br') as keybag_file:
        kb = Keybag(keybag_file)
        kb.print_keybag()
        if not kb.version:
            logger.error('Unable to detect version of keybag, exiting.')
            sys.exit(1)
        if not kb.salt:
            logger.error('Unable to detect salt, exiting.')
            sys.exit(1)
        if not kb.iterations:
            logger.error('Unable to detect iterations, exiting.')
            sys.exit(1)
        if not kb.version in [3, 4]:
            logger.error(f'This script has not been tested with version {kb.version}.')
            sys.exit(1)
        if not kb.class_keys:
            logger.error(f'Unable to parse class keys, exiting.')
            sys.exit(1)
        classkey1 = 0
        for x, class_key in kb.class_keys.items():
            if class_key.get('WRAP') == 3:
                class_type = class_key.get('CLAS')
                if class_type == 1 or class_type == 33:
                    classkey1 = class_key.get('WPKY')
        
        if not classkey1:
            logger.error(f'Unable to find a classkey of class NSFileProtectionComplete.')
            logger.error(f'You could try to get another class key, make sure it is ktyp 0 and wrap 3.')
            exit(1)
    print(f'$uido${uid}${kb.salt}${kb.iterations}${classkey1}')
        

if __name__ == "__main__":
    main()
