// Copyright (c) Open Enclave SDK contributors.
// Licensed under the MIT License.

#ifdef _WIN32
#include <ws2tcpip.h>
#else
#include <stdio.h>
#include <time.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <netinet/in.h>
#include <resolv.h>
#include <sys/socket.h>
#endif

#include <openenclave/attestation/sgx/report.h>
#include <openenclave/attestation/verifier.h>
#include <openssl/bio.h>
#include <openssl/err.h>
#include <openssl/x509_vfy.h>
#include <string.h>
#include "cert_verify_config.h"
#include "common.h"
#include "verify_callback.h"

// 简易日志函数：追加写入 /tmp/libencryptsql.log
static void log_verify(const char* fmt, ...)
{
    FILE* fp = fopen("/tmp/libencryptsql.log", "a");
    if (!fp)
        return;

    time_t now = time(nullptr);
    struct tm tm_now;
    localtime_r(&now, &tm_now);

    fprintf(fp, "[%04d-%02d-%02d %02d:%02d:%02d] ",
            tm_now.tm_year + 1900,
            tm_now.tm_mon + 1,
            tm_now.tm_mday,
            tm_now.tm_hour,
            tm_now.tm_min,
            tm_now.tm_sec);

    va_list ap;
    va_start(ap, fmt);
    vfprintf(fp, fmt, ap);
    va_end(ap);

    fputc('\n', fp);
    fclose(fp);
}


/**
 * Helper function used to make the claim-finding process more convenient. Given
 * the claim name, claim list, and its size, returns the claim with that claim
 * name in the list.
 */
static const oe_claim_t* find_claim(
    const oe_claim_t* claims,
    size_t claims_size,
    const char* name)
{
    for (size_t i = 0; i < claims_size; i++)
    {
        if (strcmp(claims[i].name, name) == 0)
            return &(claims[i]);
    }
    return nullptr;
}

static bool verify_signer_id(
    const char* pem_key_buffer,
    size_t pem_key_buffer_len,
    uint8_t* expected_signer,
    size_t expected_signer_size)
{
    printf("\n" TLS_CLIENT "verify connecting server's identity\n");

    uint8_t calculated_signer[OE_SIGNER_ID_SIZE];
    size_t calculated_signer_size = sizeof(calculated_signer);
    if (oe_sgx_get_signer_id_from_public_key(
            pem_key_buffer,
            pem_key_buffer_len,
            calculated_signer,
            &calculated_signer_size) != OE_OK)
    {
        printf("oe_sgx_get_signer_id_from_public_key failed\n");
        return false;
    }

    // validate against
    if (memcmp(calculated_signer, expected_signer, expected_signer_size) != 0)
    {
        printf("signer_id is not equal\n");
        for (size_t i = 0; i < expected_signer_size; i++)
        {
            printf(
                "0x%x - 0x%x\n",
                (uint8_t)expected_signer[i],
                (uint8_t)calculated_signer[i]);
        }
        return false;
    }
    printf("signer_id was successfully validated\n");
    return true;
}

// This is the evidence claims validation callback. A TLS connecting party
// (client or server) can verify the passed in "identity" information to decide
// whether to accept the connection request from a tls server running inside a
// specific enclave. In a real app, custom identity validation should be done
// inside this routine.
oe_result_t enclave_claims_verifier(
    oe_claim_t* claims,
    size_t claims_length,
    void* arg)
{
    oe_result_t result = OE_VERIFY_FAILED;
    const oe_claim_t* claim;

    (void)arg;
    printf(TLS_CLIENT "enclave_claims_verifier is called with the claims from "
                      "the evidence:\n");

    // Dump an identity information: unique ID, signer ID and Product ID
    // They are MRENCLAVE, MRSIGNER and ISVPRODID for SGX enclaves.

    // Enclave's security version
    if ((claim = find_claim(
             claims, claims_length, OE_CLAIM_SECURITY_VERSION)) == nullptr)
    {
        printf(TLS_SERVER "could not find OE_CLAIM_SECURITY_VERSION\n");
        goto done;
    }
    if (claim->value_size != sizeof(uint32_t))
    {
        printf(
            "security_version size(%zu) checking failed\n", claim->value_size);
        goto done;
    }
    printf(TLS_SERVER "\nsecurity_version = %d\n", *claim->value);

    // The unique ID for the enclave, for SGX enclaves, this is the MRENCLAVE
    // value
    if ((claim = find_claim(claims, claims_length, OE_CLAIM_UNIQUE_ID)) ==
        nullptr)
    {
        printf(TLS_CLIENT "could not find OE_CLAIM_UNIQUE_ID\n");
        goto done;
    }
    if (claim->value_size != OE_UNIQUE_ID_SIZE)
    {
        printf(
            TLS_CLIENT "unique_id size(%zu) checking failed\n",
            claim->value_size);
        goto done;
    }

    if (verify_claim_value(claim) != OE_OK)
    {
        printf(TLS_ENCLAVE "failed: unique_id not equal\n");
        goto done;
    }

    // The Product ID for the enclave, for SGX enclaves, this is the ISVPRODID
    // value
    if ((claim = find_claim(claims, claims_length, OE_CLAIM_PRODUCT_ID)) ==
        nullptr)
    {
        printf(TLS_CLIENT "could not find OE_CLAIM_PRODUCT_ID\n");
        goto done;
    }
    if (claim->value_size != OE_PRODUCT_ID_SIZE)
    {
        printf(
            TLS_CLIENT "product_id size(%zu) checking failed\n",
            claim->value_size);
        goto done;
    }
    printf(TLS_CLIENT "\nproduct_id :\n");
    for (size_t i = 0; i < claim->value_size; i++)
        printf("0x%0x ", (uint8_t)claim->value[i]);
    printf("\n");

    // The signer ID for the enclave, for SGX enclaves, this is the MRSIGNER
    // value
    if ((claim = find_claim(claims, claims_length, OE_CLAIM_SIGNER_ID)) ==
        nullptr)
    {
        printf(TLS_CLIENT "could not find OE_CLAIM_SIGNER_ID\n");
        goto done;
    }
    if (claim->value_size != OE_SIGNER_ID_SIZE)
    {
        printf(
            TLS_CLIENT "signer_id size(%zu) checking failed\n",
            claim->value_size);
        goto done;
    }
    printf(TLS_CLIENT "\nverify signer_id:\n");
    for (size_t i = 0; i < claim->value_size; i++)
        printf("0x%0x ", (uint8_t)claim->value[i]);

    // In this sample, only signer_id validation is shown
    if (!verify_signer_id(
            (char*)OTHER_ENCLAVE_PUBLIC_KEY,
            sizeof(OTHER_ENCLAVE_PUBLIC_KEY),
            claim->value,
            claim->value_size))
    {
        printf(TLS_CLIENT "failed: signer_id not equal\n");
        goto done;
    }
    printf(TLS_CLIENT "signer_id validation passed.\n");

    printf(TLS_CLIENT "enclave_claims_verifier returned success\n");
    result = OE_OK;
done:
    return result;
}

// The return value of verify_callback controls the strategy of the further
// verification process. If verify_callback returns 0, the verification process
// is immediately stopped with "verification failed" state and A verification
// failure alert is sent to the peer and the TLS/SSL handshake is terminated. If
// verify_callback returns 1, the verification process is continued.
// int verify_callback(int preverify_ok, X509_STORE_CTX* ctx)
// {
//     int ret = 0;
//     int certificate_size = 0;
//     unsigned char* certificate = nullptr;
//     unsigned char* buffer = nullptr;
//     oe_claim_t* claims = nullptr;
//     size_t claims_length = 0;
//     oe_result_t result = OE_FAILURE;
//     X509* crt = nullptr;
//     int err = X509_V_ERR_UNSPECIFIED;

//     fprintf(stderr,
//             "[DEK TLS] verify_callback called with preverify_ok=%d\n",
//             preverify_ok);

//     crt = X509_STORE_CTX_get_current_cert(ctx);
//     if (crt == nullptr)
//     {
//         fprintf(stderr, "[DEK TLS] failed to retrieve certificate\n");
//         goto done;
//     }

//     if (preverify_ok == 0)
//     {
//         err = X509_STORE_CTX_get_error(ctx);
//         if (err == X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT)
//         {
//             fprintf(stderr,
//                     "[DEK TLS] self-signed certificate detected (continue)\n");
//             ret = 1;
//             goto done;
//         }
//     }

//     certificate_size = i2d_X509(crt, nullptr);
//     buffer = static_cast<unsigned char*>(
//         malloc(static_cast<size_t>(certificate_size)));
//     if (buffer == nullptr)
//     {
//         fprintf(stderr,
//                 "[DEK TLS] malloc failed (certificate_size=%d)\n",
//                 certificate_size);
//         goto done;
//     }

//     certificate = buffer;
//     certificate_size = i2d_X509(crt, &buffer);
//     if (certificate_size < 0)
//     {
//         fprintf(stderr,
//                 "[DEK TLS] i2d_X509 failed (certificate_size=%d)\n",
//                 certificate_size);
//         goto done;
//     }

//     fprintf(stderr,
//             "[DEK TLS] certificate=%p buffer=%p buffer moved by %d offset "
//             "certificate_size=%d\n",
//             static_cast<void*>(certificate),
//             static_cast<void*>(buffer),
//             static_cast<int>(buffer - certificate),
//             certificate_size);

//     fprintf(stderr, "[DEK TLS] verifying certificate start\n");

//     oe_verifier_initialize();
//     result = oe_verify_attestation_certificate_with_evidence_v2(
//         certificate,
//         static_cast<size_t>(certificate_size),
//         nullptr,
//         0,
//         nullptr,
//         0,
//         &claims,
//         &claims_length);

//     // 绕过TCB问题
//     if (result == OE_TCB_LEVEL_INVALID)
//     {
//         fprintf(stderr,
//                 "[DEK TLS] WARNING: platform TCB marked OutOfDate; "
//                 "continuing for test\n");
//         result = OE_OK;
//     }
//     else if (result != OE_OK)
//     {
//         fprintf(stderr,
//                 "[DEK TLS] oe_verify_attestation_certificate_with_evidence_v2 "
//                 "failed: %s\n",
//                 oe_result_str(result));
//         goto done;
//     }

//     result = enclave_claims_verifier(claims, claims_length, nullptr);
//     if (result != OE_OK)
//     {
//         fprintf(stderr,
//                 "[DEK TLS] enclave_claims_verifier failed: %s\n",
//                 oe_result_str(result));
//         goto done;
//     }

//     fprintf(stderr, "[DEK TLS] verifying certificate end\n");
//     ret = 1;

// done:
//     free(certificate);
//     oe_free_claims(claims, claims_length);

//     if (err != X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT)
//     {
//         fprintf(stderr,
//                 "[DEK TLS] verifying SGX certificate extensions ... %s\n",
//                 ret ? "succeeded" : "failed");
//     }

//     oe_verifier_shutdown();
//     return ret;
// }

int verify_callback(int preverify_ok, X509_STORE_CTX* ctx)
{
    log_verify("verify_callback entered");
    int ret = 0;
    int certificate_size = 0;
    unsigned char* certificate = nullptr;
    unsigned char* buffer = nullptr;
    oe_claim_t* claims = nullptr;
    size_t claims_length = 0;
    oe_result_t result = OE_FAILURE;
    X509* crt = nullptr;
    int err = X509_V_ERR_UNSPECIFIED;

    log_verify("verify_callback called with preverify_ok=%d", preverify_ok);

    crt = X509_STORE_CTX_get_current_cert(ctx);
    if (!crt)
    {
        log_verify("failed to retrieve certificate");
        goto done;
    }

    if (preverify_ok == 0)
    {
        err = X509_STORE_CTX_get_error(ctx);
        if (err == X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT)
        {
            log_verify("self-signed certificate detected, continue");
            ret = 1;
            goto done;
        }
    }

    certificate_size = i2d_X509(crt, nullptr);
    buffer = static_cast<unsigned char*>(
        malloc(static_cast<size_t>(certificate_size)));
    if (!buffer)
    {
        log_verify("malloc failed (certificate_size=%d)", certificate_size);
        goto done;
    }

    certificate = buffer;
    certificate_size = i2d_X509(crt, &buffer);
    if (certificate_size < 0)
    {
        log_verify("i2d_X509 failed (certificate_size=%d)", certificate_size);
        goto done;
    }

    log_verify("certificate=%p buffer=%p moved=%d size=%d",
               static_cast<void*>(certificate),
               static_cast<void*>(buffer),
               static_cast<int>(buffer - certificate),
               certificate_size);

    log_verify("verifying certificate start");
    oe_verifier_initialize();

    result = oe_verify_attestation_certificate_with_evidence_v2(
        certificate,
        static_cast<size_t>(certificate_size),
        nullptr,
        0,
        nullptr,
        0,
        &claims,
        &claims_length);

    // 若平台 TCB 已过期，只做测试允许继续
    if (result == OE_TCB_LEVEL_INVALID)
    {
        log_verify("WARNING: platform TCB marked OutOfDate; continuing for test");
        result = OE_OK;
    }
    else if (result != OE_OK)
    {
        log_verify("oe_verify_attestation_certificate_with_evidence_v2 failed: %s",
                   oe_result_str(result));
        goto done;
    }

    result = enclave_claims_verifier(claims, claims_length, nullptr);
    if (result != OE_OK)
    {
        log_verify("enclave_claims_verifier failed: %s", oe_result_str(result));
        goto done;
    }

    log_verify("verifying certificate end");
    ret = 1;

done:
    free(certificate);
    oe_free_claims(claims, claims_length);

    if (err != X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT)
        log_verify("verifying SGX certificate extensions ... %s",
                   ret ? "succeeded" : "failed");

    oe_verifier_shutdown();
    log_verify("verify_callback returning %d", ret);
    return ret;
}