#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <CoreFoundation/CoreFoundation.h>
#include <CommonCrypto/CommonCryptor.h> 
#include <CommonCrypto/CommonHMAC.h>
#include "IOKit.h"
#include "IOAESAccelerator.h"
#include "AppleEffaceableStorage.h"
#include "AppleKeyStore.h"
#include "util.h"
#include "bsdcrypto/rijndael.h"
#include "bsdcrypto/key_wrap.h"

CFDictionaryRef AppleKeyStore_loadKeyBag(const char* folder, const char* filename)
{
    char keybagPath[100];
    struct BAG1Locker bag1_locker={0};
    //unsigned char buffer_bag1[52] = {0};
    
    snprintf(keybagPath, 99, "%s/%s.kb", folder, filename);

    CFURLRef url = CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault,
                                                           (const UInt8*)keybagPath,
                                                           strlen(keybagPath),
                                                           0);
    
    if (url == NULL)
        return NULL;
    
    CFReadStreamRef stream = CFReadStreamCreateWithFile (kCFAllocatorDefault, url);
    
    if (stream == NULL)
        return NULL;
    
    if (CFReadStreamOpen(stream) != TRUE)
        return NULL;
    
    CFPropertyListRef plist =  CFPropertyListCreateWithStream (kCFAllocatorDefault, 
                                                               stream,
                                                               0,
                                                               kCFPropertyListImmutable,
                                                               NULL,
                                                               NULL
                                                               );
    if (plist == NULL)
        return NULL;
    
    CFDataRef data = CFDictionaryGetValue(plist, CFSTR("_MKBPAYLOAD"));
    
    if (data == NULL)
        return NULL;
    
    uint8_t* mkbpayload = valloc(CFDataGetLength(data));
    CFDataGetBytes(data, CFRangeMake(0,CFDataGetLength(data)), mkbpayload);
    int length = CFDataGetLength(data);
    
    if (length < 16)
    {
        free(mkbpayload);
        return NULL;
    }
    
    if (AppleEffaceableStorage__getLocker(LOCKER_BAG1, (uint8_t*) &bag1_locker, sizeof(struct BAG1Locker)))
    {
        free(mkbpayload);
        return NULL;
    }
    
    if (bag1_locker.magic != 'BAG1')
        fprintf(stderr, "AppleKeyStore_loadKeyBag: bad BAG1 magic\n");

    size_t decryptedSize = 0; 

    CCCryptorStatus cryptStatus = CCCrypt(kCCDecrypt, 
                                          kCCAlgorithmAES128, 
                                          kCCOptionPKCS7Padding, 
                                          bag1_locker.key, 
                                          kCCKeySizeAES256, 
                                          bag1_locker.iv,
                                          mkbpayload,
                                          length,
                                          mkbpayload,
                                          length,
                                          &decryptedSize); 

    if (cryptStatus != kCCSuccess)
    { 
        fprintf(stderr, "AppleKeyStore_loadKeyBag CCCrypt kCCDecrypt with BAG1 key failed, return code=%x\n", cryptStatus);
        free(mkbpayload);
        return NULL;
    }
        
    CFDataRef data2 = CFDataCreate(kCFAllocatorDefault,
                                  mkbpayload,
                                  decryptedSize
                                  );
    
    if (data2 == NULL)
    {
        free(mkbpayload);
        return NULL;
    }

    CFErrorRef e=NULL;
    CFPropertyListRef plist2 = CFPropertyListCreateWithData(kCFAllocatorDefault, data2, kCFPropertyListImmutable, NULL, &e);
    if (plist2 == NULL)
    {
        fprintf(stderr, "AppleKeyStore_loadKeyBag failed to create plist, AES fail ? decryptedSize=%zx\n", decryptedSize);
        
        CFShow(e);
    }
    free(mkbpayload);
    CFRelease(data2);
    return plist2;
}

int AppleKeyStoreKeyBagInit()
{
    uint64_t out = 0;
    uint32_t one = 1;
    return IOKit_call("AppleKeyStore",
                      kAppleKeyStoreInitUserClient,
                      NULL,
                      0,
                      NULL,
                      0,
                      &out,
                      &one,
                      NULL,
                      NULL);
}

int AppleKeyStoreKeyBagCreateWithData(CFDataRef data, uint64_t* keybagId)
{
    uint32_t outCnt = 1;
    aes_key_wrap_ctx ctx;
    uint8_t hmckkey[32] = {0};

    int retcode = IOKit_call("AppleKeyStore",
                      kAppleKeyStoreKeyBagCreateWithData,
                      NULL,
                      0,
                      CFDataGetBytePtr(data),
                      CFDataGetLength(data),
                      keybagId,
                      &outCnt,
                      NULL,
                      NULL
                      );
    if (retcode != 0xE00002C9)
        return retcode;
    //HAX to load new iOS 7 keybags on previous iOS kernels
    uint32_t* kbdata = (uint32_t*) CFDataGetBytePtr(data);
    if ((kbdata[2] == 'SREV') && (kbdata[4] == 0x04000000))
    {
        printf("Patching iOS 7 keybag VERS 4 signature for older kernels\n");

        aes_key_wrap_set_key(&ctx, IOAES_key835(), 16);
        assert(kbdata[0x38/4] == 'KCMH');
        assert(!aes_key_unwrap(&ctx, (const uint8_t*) &kbdata[0x38/4 + 2], hmckkey, 4));

        assert(kbdata[CFSwapInt32BigToHost(kbdata[1])/4 + 2] == 'NGIS');

        CCHmac(kCCHmacAlgSHA1,
                (const void *) &kbdata[2],
                CFSwapInt32BigToHost(kbdata[1]),
                hmckkey,
                32,
                &kbdata[CFSwapInt32BigToHost(kbdata[1])/4 + 2 + 2]);
    }
    outCnt = 1;
    return IOKit_call("AppleKeyStore",
                      kAppleKeyStoreKeyBagCreateWithData,
                      NULL,
                      0,
                      CFDataGetBytePtr(data),
                      CFDataGetLength(data),
                      keybagId,
                      &outCnt,
                      NULL,
                      NULL
                      );
}

int AppleKeyStoreKeyBagSetSystem(uint64_t keybagId)
{
    return IOKit_call("AppleKeyStore",
                      kAppleKeyStoreKeyBagSetSystem,
                      &keybagId,
                      1,
                      NULL,
                      0,
                      NULL,
                      NULL,
                      NULL,
                      NULL);
}

int AppleKeyStoreUnlockDevice(io_connect_t conn, CFDataRef passcode)
{
    return IOConnectCallMethod(conn,
                      kAppleKeyStoreUnlockDevice,
                      NULL,
                      0,
                      CFDataGetBytePtr(passcode),
                      CFDataGetLength(passcode),
                      NULL,
                      NULL,
                      NULL,
                      NULL);
}

KeyBag* AppleKeyStore_parseBinaryKeyBag(CFDataRef kb)
{
    const uint8_t* ptr = CFDataGetBytePtr(kb);
    unsigned int len = CFDataGetLength(kb);
    struct KeyBagBlobItem* p = (struct KeyBagBlobItem*) ptr;
    const uint8_t* end;
    
    if (p->tag != 'ATAD') {
        printf("Keybag does not start with DATA\n");
        return NULL;
    }
    if (8 + CFSwapInt32BigToHost(p->len) > len) {
        return NULL;
    }
    
    KeyBag* keybag = malloc(sizeof(KeyBag));
    if (keybag == NULL)
        return NULL;

    memset(keybag, 0, sizeof(KeyBag));
    
    end = ptr + 8 + CFSwapInt32BigToHost(p->len);
    p = (struct KeyBagBlobItem*) p->data.bytes;
    int kbuuid=0;
    int i = -1;
    
    while ((uint8_t*)p < end) {
        //printf("%x\n", p->tag);
        len = CFSwapInt32BigToHost(p->len);
        
        if (p->tag == 'SREV') {
            keybag->version = CFSwapInt32BigToHost(p->data.intvalue);
        }
        else if (p->tag == 'TLAS') {
            memcpy(keybag->salt, p->data.bytes, 20);
        }
        else if (p->tag == 'RETI') {
            keybag->iter = CFSwapInt32BigToHost(p->data.intvalue);
        }
        else if (p->tag == 'DIUU') {
            if (!kbuuid)
            {
                memcpy(keybag->uuid, p->data.bytes, 16);
                kbuuid = 1;
            }
            else
            {
                i++;
                if (i >= MAX_CLASS_KEYS)
                    break;
                memcpy(keybag->keys[i].uuid, p->data.bytes, 16);
            }
        }
        else if (p->tag == 'SALC')
        {
            keybag->keys[i].clas = CFSwapInt32BigToHost(p->data.intvalue);
        }
        else if (p->tag == 'PARW' && kbuuid)
        {
            keybag->keys[i].wrap = CFSwapInt32BigToHost(p->data.intvalue);
        }
        else if (p->tag == 'YKPW')
        {
            memcpy(keybag->keys[i].wpky, p->data.bytes, (len > 40)  ? 40 : len);
        }
        p = (struct KeyBagBlobItem*) &p->data.bytes[len];
    }
    keybag->numKeys = i + 1;
    
    return keybag;
}

void AppleKeyStore_printKeyBag(KeyBag* kb)
{
    int i;
    printf("Keybag version : %d\n", kb->version);
    printf("Keybag keys : %d\n", kb->numKeys);
    printf("Class\tWrap\tKey\n");
    for (i=0; i < kb->numKeys; i++)
    {
        printf("%d\t%d\t", kb->keys[i].clas, kb->keys[i].wrap);
        printBytesToHex(kb->keys[i].wpky, kb->keys[i].wrap & 2 ? 40 : 32);
        printf("\n");
    }
    printf("\n");
}

CFMutableDictionaryRef AppleKeyStore_getClassKeys(KeyBag* kb)
{
    int i;
    CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorDefault, kb->numKeys, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
    CFStringRef key;
    
    for (i=0; i < kb->numKeys; i++)
    {
        if(kb->keys[i].wrap == 0)
        {
            key = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%d"), kb->keys[i].clas);
            addHexaString(dict, key, kb->keys[i].wpky, 32);
            CFRelease(key);
        }
    }
    return dict;
}
