#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <openssl/bn.h>
#include <openssl/sha.h>

static void die(const char *msg)
{
    fprintf(stderr, "%s\n", msg);
    exit(1);
}

static unsigned char *read_binary_file(const char *path, long *len_out)
{
    FILE *fp = fopen(path, "rb");
    if (!fp) die("Failed to open binary file.");

    if (fseek(fp, 0, SEEK_END) != 0) die("fseek failed.");
    long len = ftell(fp);
    if (len < 0) die("ftell failed.");
    rewind(fp);

    unsigned char *buf = (unsigned char *)malloc(len);
    if (!buf) die("malloc failed.");

    if (fread(buf, 1, len, fp) != (size_t)len) die("fread failed.");
    fclose(fp);

    *len_out = len;
    return buf;
}

static char *read_hex_file_strip(const char *path)
{
    FILE *fp = fopen(path, "r");
    if (!fp) die("Failed to open hex file.");

    fseek(fp, 0, SEEK_END);
    long len = ftell(fp);
    if (len < 0) die("ftell failed.");
    rewind(fp);

    char *raw = (char *)malloc(len + 1);
    if (!raw) die("malloc failed.");
    if (fread(raw, 1, len, fp) != (size_t)len) die("fread failed.");
    fclose(fp);
    raw[len] = '\0';

    char *clean = (char *)malloc(len + 1);
    if (!clean) die("malloc failed.");

    int j = 0;
    for (long i = 0; i < len; i++) {
        if (isxdigit((unsigned char)raw[i])) {
            clean[j++] = raw[i];
        }
    }
    clean[j] = '\0';
    free(raw);

    return clean;
}

static void print_hex_bytes(const char *msg, const unsigned char *buf, int len)
{
    printf("%s", msg);
    for (int i = 0; i < len; i++) {
        printf("%02X", buf[i]);
    }
    printf("\n");
}

int main(int argc, char *argv[])
{
    if (argc != 5) {
        fprintf(stderr, "Usage: %s c0_body.bin signature.hex modulus.hex exponent_decimal\n", argv[0]);
        return 1;
    }

    const char *body_path = argv[1];
    const char *sig_path  = argv[2];
    const char *mod_path  = argv[3];
    const char *exp_dec   = argv[4];

    long body_len = 0;
    unsigned char *body = read_binary_file(body_path, &body_len);

    unsigned char digest[SHA256_DIGEST_LENGTH];
    SHA256(body, body_len, digest);

    char *sig_hex = read_hex_file_strip(sig_path);
    char *mod_hex = read_hex_file_strip(mod_path);

    BN_CTX *ctx = BN_CTX_new();
    if (!ctx) die("BN_CTX_new failed.");

    BIGNUM *sig = BN_new();
    BIGNUM *n   = BN_new();
    BIGNUM *e   = BN_new();
    BIGNUM *m   = BN_new();

    if (!sig || !n || !e || !m) die("BN_new failed.");

    BN_hex2bn(&sig, sig_hex);
    BN_hex2bn(&n, mod_hex);
    BN_dec2bn(&e, exp_dec);

    BN_mod_exp(m, sig, e, n, ctx);

    int k = BN_num_bytes(n);
    unsigned char *em = (unsigned char *)malloc(k);
    if (!em) die("malloc failed.");

    if (BN_bn2binpad(m, em, k) != k) die("BN_bn2binpad failed.");

    print_hex_bytes("Computed SHA-256(body) = ", digest, SHA256_DIGEST_LENGTH);
    print_hex_bytes("Decrypted signature block = ", em, k);

    const unsigned char sha256_der_prefix[] = {
        0x30,0x31,0x30,0x0d,0x06,0x09,0x60,0x86,0x48,0x01,
        0x65,0x03,0x04,0x02,0x01,0x05,0x00,0x04,0x20
    };

    int valid = 1;

    if (k < 2 || em[0] != 0x00 || em[1] != 0x01) {
        valid = 0;
    }

    int i = 2;
    while (i < k && em[i] == 0xFF) i++;

    if (i < 10) {
        valid = 0;
    }

    if (i >= k || em[i] != 0x00) {
        valid = 0;
    }

    i++;

    int remaining = k - i;
    int expected_len = (int)sizeof(sha256_der_prefix) + SHA256_DIGEST_LENGTH;

    if (remaining != expected_len) {
        valid = 0;
    }

    if (valid && memcmp(em + i, sha256_der_prefix, sizeof(sha256_der_prefix)) != 0) {
        valid = 0;
    }

    if (valid && memcmp(em + i + sizeof(sha256_der_prefix), digest, SHA256_DIGEST_LENGTH) != 0) {
        valid = 0;
    }

    if (valid) {
        printf("Certificate signature is VALID.\n");
    } else {
        printf("Certificate signature is INVALID.\n");
    }

    free(body);
    free(sig_hex);
    free(mod_hex);
    free(em);

    BN_free(sig);
    BN_free(n);
    BN_free(e);
    BN_free(m);
    BN_CTX_free(ctx);

    return 0;
}
