// Copyright 2026 Simo Sorce
// See LICENSE.txt file for terms

use super::set_fips_error_state;
use crate::attribute::Attribute;
use crate::error::Result;
use crate::hmac::test_get_hmac;
use crate::mechanism::Verify;
use crate::native::hmac::HMACOperation;
use crate::native::tlskdf::TLSPRF;
use crate::object::Object;
use crate::pkcs11::*;
use std::sync::LazyLock;

/// Holds the result of the FIPS self-test
pub struct FIPSSelftest {
    pub result: CK_RV,
}

impl FIPSSelftest {
    fn fail() -> FIPSSelftest {
        set_fips_error_state();
        FIPSSelftest {
            result: CKR_FIPS_SELF_TEST_FAILED,
        }
    }
    fn pass() -> FIPSSelftest {
        FIPSSelftest { result: CKR_OK }
    }
}

/// Lazy code to run FIPS Known Answer Tests (KATs) on first use
///
/// Uses a test vector from OpenSSL.
///
/// If the calculated output does not match the expected output, it sets the
/// FIPS error state and stores `CKR_FIPS_SELF_TEST_FAILED` in
/// [FIPSSelfTest.result].
pub static HMAC_SELFTEST: LazyLock<FIPSSelftest> = LazyLock::new(|| {
    /* Test vector taken from OpenSSL selftest */
    let plaintext: [u8; 16] = [
        0xDD, 0x0C, 0x30, 0x33, 0x35, 0xF9, 0xE4, 0x2E, 0xC2, 0xEF, 0xCC, 0xBF,
        0x07, 0x95, 0xEE, 0xA2,
    ];
    let secret: Vec<u8> = vec![
        0xF4, 0x55, 0x66, 0x50, 0xAC, 0x31, 0xD3, 0x54, 0x61, 0x61, 0x0B, 0xAC,
        0x4E, 0xD8, 0x1B, 0x1A, 0x18, 0x1B, 0x2D, 0x8A, 0x43, 0xEA, 0x28, 0x54,
        0xCB, 0xAE, 0x22, 0xCA, 0x74, 0x56, 0x08, 0x13,
    ];
    let expect: [u8; 32] = [
        0xF5, 0xF5, 0xE5, 0xF2, 0x66, 0x49, 0xE2, 0x40, 0xFC, 0x9E, 0x85, 0x7F,
        0x2B, 0x9A, 0xBE, 0x28, 0x20, 0x12, 0x00, 0x92, 0x82, 0x21, 0x3E, 0x51,
        0x44, 0x5D, 0xE3, 0x31, 0x04, 0x01, 0x72, 0x6B,
    ];

    let mut hmac = match HMACOperation::internal(CKM_SHA256_HMAC, secret, 32) {
        Ok(h) => h,
        Err(_) => return FIPSSelftest::fail(),
    };
    if Verify::verify(&mut hmac, &plaintext, &expect).is_err() {
        return FIPSSelftest::fail();
    }
    FIPSSelftest::pass()
});

fn secret_key_object(secret: Vec<u8>) -> Result<Object> {
    let mut key = Object::new(CKO_SECRET_KEY);
    key.set_attr(Attribute::from_ulong(CKA_KEY_TYPE, CKK_GENERIC_SECRET))?;
    key.set_attr(Attribute::from_ulong(
        CKA_VALUE_LEN,
        secret.len() as CK_ULONG,
    ))?;
    key.set_attr(Attribute::from_bytes(CKA_VALUE, secret))?;
    key.set_attr(Attribute::from_bool(CKA_DERIVE, true))?;
    Ok(key)
}

/// Static Lazy variable to run FIPS Known Answer Tests (KATs) for the TLS PRF
/// on first use
///
/// Uses a test vector from OpenSSL.
///
/// If the calculated output does not match the expected output, it sets the
/// FIPS error state and stores `CKR_FIPS_SELF_TEST_FAILED` in
/// [FIPSSelfTest.result].
pub static TLS_PRF_SELFTEST: LazyLock<FIPSSelftest> = LazyLock::new(|| {
    /* Test vector taken from OpenSSL selftest */
    let prf: CK_MECHANISM_TYPE = CKM_SHA256_HMAC;
    let secret: Vec<u8> = vec![
        0x20, 0x2C, 0x88, 0xC0, 0x0F, 0x84, 0xA1, 0x7A, 0x20, 0x02, 0x70, 0x79,
        0x60, 0x47, 0x87, 0x46, 0x11, 0x76, 0x45, 0x55, 0x39, 0xE7, 0x05, 0xBE,
        0x73, 0x08, 0x90, 0x60, 0x2C, 0x28, 0x9A, 0x50, 0x01, 0xE3, 0x4E, 0xEB,
        0x3A, 0x04, 0x3E, 0x5D, 0x52, 0xA6, 0x5E, 0x66, 0x12, 0x51, 0x88, 0xBF,
    ];
    let seed: Vec<u8> = vec![
        b'k', b'e', b'y', b' ', b'e', b'x', b'p', b'a', b'n', b's', b'i', b'o',
        b'n', 0xAE, 0x6C, 0x80, 0x6F, 0x8A, 0xD4, 0xD8, 0x07, 0x84, 0x54, 0x9D,
        0xFF, 0x28, 0xA4, 0xB5, 0x8F, 0xD8, 0x37, 0x68, 0x1A, 0x51, 0xD9, 0x28,
        0xC3, 0xE3, 0x0E, 0xE5, 0xFF, 0x14, 0xF3, 0x98, 0x68, 0x62, 0xE1, 0xFD,
        0x91, 0xF2, 0x3F, 0x55, 0x8A, 0x60, 0x5F, 0x28, 0x47, 0x8C, 0x58, 0xCF,
        0x72, 0x63, 0x7B, 0x89, 0x78, 0x4D, 0x95, 0x9D, 0xF7, 0xE9, 0x46, 0xD3,
        0xF0, 0x7B, 0xD1, 0xB6, 0x16,
    ];

    let expect: Vec<u8> = vec![
        0xD0, 0x61, 0x39, 0x88, 0x9F, 0xFF, 0xAC, 0x1E, 0x3A, 0x71, 0x86, 0x5F,
        0x50, 0x4A, 0xA5, 0xD0, 0xD2, 0xA2, 0xE8, 0x95, 0x06, 0xC6, 0xF2, 0x27,
        0x9B, 0x67, 0x0C, 0x3E, 0x1B, 0x74, 0xF5, 0x31, 0x01, 0x6A, 0x25, 0x30,
        0xC5, 0x1A, 0x3A, 0x0F, 0x7E, 0x1D, 0x65, 0x90, 0xD0, 0xF0, 0x56, 0x6B,
        0x2F, 0x38, 0x7F, 0x8D, 0x11, 0xFD, 0x4F, 0x73, 0x1C, 0xDD, 0x57, 0x2D,
        0x2E, 0xAE, 0x92, 0x7F, 0x6F, 0x2F, 0x81, 0x41, 0x0B, 0x25, 0xE6, 0x96,
        0x0B, 0xE6, 0x89, 0x85, 0xAD, 0xD6, 0xC3, 0x84, 0x45, 0xAD, 0x9F, 0x8C,
        0x64, 0xBF, 0x80, 0x68, 0xBF, 0x9A, 0x66, 0x79, 0x48, 0x5D, 0x96, 0x6F,
        0x1A, 0xD6, 0xF6, 0x8B, 0x43, 0x49, 0x5B, 0x10, 0xA6, 0x83, 0x75, 0x5E,
        0xA2, 0xB8, 0x58, 0xD7, 0x0C, 0xCA, 0xC7, 0xEC, 0x8B, 0x05, 0x3C, 0x6B,
        0xD4, 0x1C, 0xA2, 0x99, 0xD4, 0xE5, 0x19, 0x28,
    ];

    /* mock key */
    let key = match secret_key_object(secret) {
        Ok(s) => s,
        Err(_) => return FIPSSelftest::fail(),
    };

    let mech = test_get_hmac(prf);

    let mut tlsprf = match TLSPRF::init(&key, &mech, prf) {
        Ok(a) => a,
        Err(_) => return FIPSSelftest::fail(),
    };
    let out = match tlsprf.finish(&seed, expect.len()) {
        Ok(a) => a,
        Err(_) => return FIPSSelftest::fail(),
    };
    if out == expect {
        FIPSSelftest::pass()
    } else {
        FIPSSelftest::fail()
    }
});
