#include <stdio.h>
#include <string.h>

#include <openssl/err.h>
#include <openssl/obj_mac.h>
#include <openssl/ec.h>

#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/errno.h>

#include <argon2.h>

extern uint8_t key_hashes[150388][6];

extern size_t CustomBase32Decode(const char* __restrict src, size_t len,
                                 void* __restrict out_buf, size_t out_len);

const uint8_t OfficialPublicKey[2][14] = {
    0x47, 0xcf, 0xb5, 0xf7, 0xe8, 0x93, 0x1e, 0xc9, 0x3d, 0x42, 0xd1, 0x22, 0x1e, 0x7f,
    0x98, 0x5d, 0x74, 0xaf, 0x45, 0x53, 0x70, 0xf3, 0x47, 0x39, 0x8e, 0x1b, 0x1d, 0x3e
};

const uint8_t salt_for_short_product_key[16] = {
    0x06, 0x9c, 0x5e, 0xf4, 0x90, 0x67, 0x39, 0x4c,
    0x7b, 0x61, 0xa7, 0xee, 0x36, 0x97, 0xc6, 0x02
};

const uint8_t salt_for_long_product_key[16] = {
    0xa1, 0x38, 0x11, 0x98, 0x12, 0x2f, 0x28, 0xee,
    0x2c, 0x3a, 0xa0, 0x57, 0xbd, 0xcf, 0x2d, 0x83
};

const uint8_t InverseSubstitutionTable[256] = {
    0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
    0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
    0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
    0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0xff,0xff,0xff,0xff,0xff,0xff,
    0xff,0x0a,0xff,0x0b,0x0c,0x0d,0x0e,0x0f,0x10,0xff,0x11,0x12,0x13,0x14,0x15,0xff,
    0x16,0x17,0x18,0xff,0x19,0x1a,0x1b,0x1c,0x1d,0x1e,0x1f,0xff,0xff,0xff,0xff,0xff,
    0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
    0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
    0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
    0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
    0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
    0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
    0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
    0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
    0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
    0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff
};

int IsTheKeyRecorded(uint8_t Hash[6]) {
    int start = 0;
    int end = 150388;
    while (start != end) {
        int mid = (start + end) / 2;
        int j = 0;
        for (; j < 6; ++j) {
            if (Hash[j] < key_hashes[mid][j]) {
                end = mid;
                break;
            } else if (Hash[j] > key_hashes[mid][j]) {
                start = mid + 1;
                break;
            }
        }
        if (j == 6)
            return 1;
    }
    return 0;
}

int DecodeShortProductKey(const char* lpcszProductKey, uint8_t data[20]) {
    if (strlen(lpcszProductKey) != 23)
        return 0;

    data[0] = InverseSubstitutionTable[(uint8_t)lpcszProductKey[22]];
    data[1] = InverseSubstitutionTable[(uint8_t)lpcszProductKey[21]];
    data[2] = InverseSubstitutionTable[(uint8_t)lpcszProductKey[20]];
    data[3] = InverseSubstitutionTable[(uint8_t)lpcszProductKey[19]];
    data[4] = InverseSubstitutionTable[(uint8_t)lpcszProductKey[18]];

    data[5] = InverseSubstitutionTable[(uint8_t)lpcszProductKey[16]];
    data[6] = InverseSubstitutionTable[(uint8_t)lpcszProductKey[15]];
    data[7] = InverseSubstitutionTable[(uint8_t)lpcszProductKey[14]];
    data[8] = InverseSubstitutionTable[(uint8_t)lpcszProductKey[13]];
    data[9] = InverseSubstitutionTable[(uint8_t)lpcszProductKey[12]];

    data[10] = InverseSubstitutionTable[(uint8_t)lpcszProductKey[10]];
    data[11] = InverseSubstitutionTable[(uint8_t)lpcszProductKey[9]];
    data[12] = InverseSubstitutionTable[(uint8_t)lpcszProductKey[8]];
    data[13] = InverseSubstitutionTable[(uint8_t)lpcszProductKey[7]];
    data[14] = InverseSubstitutionTable[(uint8_t)lpcszProductKey[6]];

    data[15] = InverseSubstitutionTable[(uint8_t)lpcszProductKey[4]];
    data[16] = InverseSubstitutionTable[(uint8_t)lpcszProductKey[3]];
    data[17] = InverseSubstitutionTable[(uint8_t)lpcszProductKey[2]];
    data[18] = InverseSubstitutionTable[(uint8_t)lpcszProductKey[1]];
    data[19] = InverseSubstitutionTable[(uint8_t)lpcszProductKey[0]];

    for (int i = 0; i < 20; ++i)
        if (data[i] >= 32)
            return 0;
    return 1;
}

int DecodeLongProductKey(const char* lpcszProductKey, uint8_t data[14 + 5]) {
    if (strlen(lpcszProductKey) != 34)
        return 0;

    char prekey_str[30 + 1] = {};
    prekey_str[0] = lpcszProductKey[33];
    prekey_str[1] = lpcszProductKey[32];
    prekey_str[2] = lpcszProductKey[31];
    prekey_str[3] = lpcszProductKey[30];
    prekey_str[4] = lpcszProductKey[29];
    prekey_str[5] = lpcszProductKey[28];

    prekey_str[6] = lpcszProductKey[26];
    prekey_str[7] = lpcszProductKey[25];
    prekey_str[8] = lpcszProductKey[24];
    prekey_str[9] = lpcszProductKey[23];
    prekey_str[10] = lpcszProductKey[22];
    prekey_str[11] = lpcszProductKey[21];

    prekey_str[12] = lpcszProductKey[19];
    prekey_str[13] = lpcszProductKey[18];
    prekey_str[14] = lpcszProductKey[17];
    prekey_str[15] = lpcszProductKey[16];
    prekey_str[16] = lpcszProductKey[15];
    prekey_str[17] = lpcszProductKey[14];

    prekey_str[18] = lpcszProductKey[12];
    prekey_str[19] = lpcszProductKey[11];
    prekey_str[20] = lpcszProductKey[10];
    prekey_str[21] = lpcszProductKey[9];
    prekey_str[22] = lpcszProductKey[8];
    prekey_str[23] = lpcszProductKey[7];

    prekey_str[24] = lpcszProductKey[5];
    prekey_str[25] = lpcszProductKey[4];
    prekey_str[26] = lpcszProductKey[3];
    prekey_str[27] = lpcszProductKey[2];
    prekey_str[28] = lpcszProductKey[1];
    prekey_str[29] = lpcszProductKey[0];

    return CustomBase32Decode(prekey_str, 30, data, 14 + 5) ? 1 : 0;
}

// return key_type,
// short product key if return 1 or 2
// long product key if return 3
// otherwise invalid
int GetKeyType(const char* lpcszProductKey) {
    size_t KeyLength = strlen(lpcszProductKey);
    if (KeyLength == 23) {
        uint8_t data[20] = {};
        if (!DecodeShortProductKey(lpcszProductKey, data))
            return 0;

        uint64_t keydata_a = 0, keydata_b = 0;
        keydata_b |= (uint64_t)data[0];
        keydata_b |= (uint64_t)data[1] << 5;
        keydata_b |= (uint64_t)data[2] << 10;
        keydata_b |= (uint64_t)data[3] << 15;
        keydata_b |= (uint64_t)data[4] << 20;
        keydata_b |= (uint64_t)data[5] << 25;
        keydata_b |= (uint64_t)data[6] << 30;
        keydata_b |= (uint64_t)data[7] << 35;
        keydata_b |= (uint64_t)data[8] << 40;
        keydata_b |= (uint64_t)data[9] << 45;
        keydata_b |= (uint64_t)data[10] << 50;
        keydata_b |= (uint64_t)data[11] << 55;
        keydata_b |= (uint64_t)data[12] << 60;
        keydata_a |= (uint64_t)data[12] >> 4;
        keydata_a |= (uint64_t)data[13] << 1;
        keydata_a |= (uint64_t)data[14] << 6;
        keydata_a |= (uint64_t)data[15] << 11;
        keydata_a |= (uint64_t)data[16] << 16;
        keydata_a |= (uint64_t)data[17] << 21;
        keydata_a |= (uint64_t)data[18] << 26;
        keydata_a |= (uint64_t)data[19] << 31;

        int checksum = 0;
        if ((keydata_a & 0xc8b85c312ull) == 0x882818002ull && (keydata_b & 0x30688298668b32aull) == 0x306880904202200ull) {
            for (int i = 0; i < 20; ++i) {
                if (i == 12)
                    continue;
                checksum += data[i];
            }
            if (checksum % 32 == data[12])
                return 1;
        } else if ((keydata_a & 0x6306a9612ull) == 0x430228000ull && (keydata_b & 0xc4d0ac3165804a43ull) == 0x4050a01165800a42ull) {
            for (int i = 0; i < 20; ++i) {
                if (i == 3)
                    continue;
                checksum += data[i];
            }
            if (checksum % 32 == data[3])
                return 2;
        }
    } else if (KeyLength == 34) {
        return 3;
    }

    return 0;
}

void help() {
    printf("Usage:\n");
    printf("    ./TuxeraNTFS-verifier [path to tuxera_key.bin] <Product Key>\n");
    printf("\n");
}

int VerifyShortProductKey(const char* lpcszProductKey) {
    if (strlen(lpcszProductKey) != 23)
        return 0;

    char buf[25] = {};
    uint8_t Hash[6] = {};
    sprintf(buf, "%s\n", lpcszProductKey);
    argon2_hash(1,
                1 << 16,
                1,
                buf,
                sizeof(buf),
                salt_for_short_product_key,
                sizeof(salt_for_short_product_key),
                Hash,
                sizeof(Hash),
                NULL,
                0,
                Argon2_d,
                ARGON2_VERSION_13);
    return IsTheKeyRecorded(Hash);
}

int VerifyLongProductKey(uint8_t key_data[14 + 5], const EC_POINT* PublicKey) {
    int status = 0;
    BIGNUM* s = NULL;
    BIGNUM* h = NULL;
    EC_GROUP* ec_curve = NULL;
    EC_POINT* R = NULL;
    BIGNUM* Rx = NULL;
    BIGNUM* Ry = NULL;
    uint8_t bin_R[2][14] = {};
    uint8_t Hash[5] = {};

    s = BN_bin2bn(key_data, 14, NULL);
    if (s == NULL)
        goto On_VerifyLongProductKey_Error;

    h = BN_bin2bn(key_data + 14, 5, NULL);
    if (h == NULL)
        goto On_VerifyLongProductKey_Error;

    ec_curve = EC_GROUP_new_by_curve_name(NID_secp112r1);
    if (ec_curve == NULL)
        goto On_VerifyLongProductKey_Error;

    R = EC_POINT_new(ec_curve);
    if (R == NULL)
        goto On_VerifyLongProductKey_Error;

    Rx = BN_new();
    if (Rx == NULL)
        goto On_VerifyLongProductKey_Error;

    Ry = BN_new();
    if (Ry == NULL)
        goto On_VerifyLongProductKey_Error;

    if (!EC_POINT_mul(ec_curve, R, s, PublicKey, h, NULL))
        goto On_VerifyLongProductKey_Error;

    if (!EC_POINT_get_affine_coordinates_GFp(ec_curve, R, Rx, Ry, NULL))
        goto On_VerifyLongProductKey_Error;

    BN_bn2bin(Rx, bin_R[0] + 14 - BN_num_bytes(Rx));
    BN_bn2bin(Ry, bin_R[1] + 14 - BN_num_bytes(Ry));

    argon2_hash(1,
                1 << 16,
                1,
                bin_R,
                sizeof(bin_R),
                salt_for_long_product_key,
                sizeof(salt_for_long_product_key),
                Hash,
                sizeof(Hash),
                NULL,
                0,
                Argon2_d,
                ARGON2_VERSION_13);

    Hash[4] &= 0xFC;

    status = memcmp(key_data + 14, Hash, 5) == 0 ? 1 : 0;

On_VerifyLongProductKey_Error:
    if (Ry)
        BN_free(Ry);
    if (Rx)
        BN_free(Rx);
    if (R)
        EC_POINT_free(R);
    if (ec_curve)
        EC_GROUP_free(ec_curve);
    if (h)
        BN_free(h);
    if (s)
        BN_free(s);
    return status;
}

EC_POINT* LoadPublicKey(const char* key_file) {
    EC_GROUP* ec_curve = NULL;
    EC_POINT* PublicKey = NULL;
    uint8_t binPublicKey[2][14] = {};
    BIGNUM* Px = NULL;
    BIGNUM* Py = NULL;

    ec_curve = EC_GROUP_new_by_curve_name(NID_secp112r1);
    if (ec_curve == NULL)
        goto On_LoadPublicKey_Error;

    PublicKey = EC_POINT_new(ec_curve);
    if (PublicKey == NULL)
        goto On_LoadPublicKey_Error;

    if (key_file == NULL) {
        memcpy(binPublicKey, OfficialPublicKey, sizeof(OfficialPublicKey));
    } else {
        int fd = -1;
        fd = open(key_file, O_RDONLY);
        if (fd == -1) {
            EC_POINT_free(PublicKey);
            PublicKey = NULL;
            goto On_LoadPublicKey_Error;
        }

        read(fd, binPublicKey, 14);
        if (14 != read(fd, binPublicKey[0], 14)) {
            EC_POINT_free(PublicKey);
            PublicKey = NULL;
            goto On_LoadPublicKey_Error;
        }
        if (14 != read(fd, binPublicKey[1], 14)) {
            EC_POINT_free(PublicKey);
            PublicKey = NULL;
            goto On_LoadPublicKey_Error;
        }
    }

    Px = BN_bin2bn(binPublicKey[0], sizeof(binPublicKey[0]), NULL);
    if (Px == NULL) {
        EC_POINT_free(PublicKey);
        PublicKey = NULL;
        goto On_LoadPublicKey_Error;
    }

    Py = BN_bin2bn(binPublicKey[1], sizeof(binPublicKey[1]), NULL);
    if (Py == NULL) {
        EC_POINT_free(PublicKey);
        PublicKey = NULL;
        goto On_LoadPublicKey_Error;
    }

    if (!EC_POINT_set_affine_coordinates_GFp(ec_curve, PublicKey, Px, Py, NULL)) {
        EC_POINT_free(PublicKey);
        PublicKey = NULL;
        goto On_LoadPublicKey_Error;
    }

On_LoadPublicKey_Error:
    if (Py)
        BN_free(Py);
    if (Px)
        BN_free(Px);
    if (ec_curve)
        EC_GROUP_free(ec_curve);
    return PublicKey;
}

int main(int argc, char* argv[]) {
    if (argc != 2 && argc != 3) {
        help();
        return 0;
    }

    int KeyType = GetKeyType(argv[argc - 1]);
    switch (KeyType) {
        case 1:
        case 2:
            printf("%s\n", VerifyShortProductKey(argv[argc - 1]) ? "Valid Key" : "Invalid Key");
            break;
        case 3: {
            uint8_t key_data[14 + 5] = {};
            EC_POINT* PublicKey = NULL;

            PublicKey = LoadPublicKey(argc == 3 ? argv[1] : NULL);
            if (PublicKey == NULL) {
                printf("Failed to load public key.\n");
                return 0;
            }

            if (!DecodeLongProductKey(argv[argc - 1], key_data)) {
                printf("Invalid Key\n");
                EC_POINT_free(PublicKey);
                return 0;
            }

            printf("%s\n", VerifyLongProductKey(key_data, PublicKey) ? "Valid Key" : "Invalid Key");
            EC_POINT_free(PublicKey);
        }
            break;
        default:
            printf("Invalid Key\n");
            break;
    }
    return 0;
}
