//! This module defines self-tests. Running tests in this module (i.e., calling [`self_tests()`](self::self_tests()))
//! at runtime can help with [FIPS 140-3] compliance.
//!
//! [FIPS 140-3]: (https://csrc.nist.gov/csrc/media/Projects/cryptographic-module-validation-program/documents/fips%20140-3/FIPS%20140-3%20IG.pdf)

use rustls::crypto::{
    tls12::{Prf, PrfUsingHmac},
    tls13::{expand, Hkdf, HkdfUsingHmac},
    ActiveKeyExchange,
};
use std::vec::Vec;

// test copied from rustls repo
/// TLS 1.2 SHA256 PRF test
#[cfg(feature = "tls12")]
pub fn tls12_sha256_prf_test_1() {
    let secret = b"\x9b\xbe\x43\x6b\xa9\x40\xf0\x17\xb1\x76\x52\x84\x9a\x71\xdb\x35";
    let seed = b"\xa0\xba\x9f\x93\x6c\xda\x31\x18\x27\xa6\xf7\x96\xff\xd5\x19\x8c";
    let label = b"test label";
    let expect = include_bytes!("../testdata/prf-result.1.bin");
    let mut output = [0u8; 100];

    let prf = PrfUsingHmac(&super::hmac::HMAC_SHA256);
    prf.for_secret(&mut output, secret, label, seed);

    assert_eq!(expect.len(), output.len());
    assert_eq!(&expect[..], &output[..]);
}

/// TLS 1.2 SHA256 PRF test with `"extended master secret"`
#[cfg(feature = "tls12")]
pub fn tls12_sha256_prf_test_2() {
    let secret = b"\x9b\xbe\x43\x6b\xa9\x40\xf0\x17\xb1\x76\x52\x84\x9a\x71\xdb\x35";
    let session_hash: &[u8; 32] = b"\xa0\xba\x9f\x93\x6c\xda\x31\x18\x27\xa6\xf7\x96\xff\xd5\x19\x8c\
                                    \xb1\x7c\x53\x8e\xcd\x16\x73\x8a\x59\x18\xf7\xd2\xff\x21\xbb\x81";
    let label = b"extended master secret";
    let expect = [
        0x39, 0x10, 0x1e, 0xdf, 0x15, 0x16, 0xb8, 0xbf, 0x3b, 0xbb, 0x0b, 0x62, 0x9b, 0xf8, 0x05, 0x47, 0x5b, 0xef, 0x76, 0x1b,
        0x3d, 0x62, 0xc1, 0x0d, 0x99, 0x06, 0x44, 0x87, 0xaa, 0x7a, 0xee, 0x09, 0x55, 0x6c, 0xb1, 0xa8, 0x24, 0xd6, 0xfc, 0x24,
        0x99, 0x2b, 0x98, 0x22, 0x7f, 0x30, 0xc1, 0x19, 0x4b, 0x91, 0xf2, 0xbb, 0xed, 0x40, 0xee, 0xe1, 0x05, 0x36, 0xcb, 0x49,
        0x93, 0xc4, 0x93, 0x21, 0x7f, 0x31, 0x01, 0xc9, 0x23, 0x7c, 0x31, 0x8c, 0x12, 0x93, 0xe4, 0xb8, 0xcf, 0x0b, 0xcb, 0x9f,
        0xb6, 0x5e, 0x80, 0x83, 0xfe, 0xd3, 0x70, 0x5b, 0x7d, 0x86, 0x7a, 0x02, 0xe1, 0x7c, 0x4a, 0x7f, 0xf5, 0x30, 0x29, 0xee,
    ];
    let mut output = [0u8; 100];

    let prf = PrfUsingHmac(&super::hmac::HMAC_SHA256);
    prf.for_secret(&mut output, secret, label, session_hash);

    assert_eq!(expect.len(), output.len());
    assert_eq!(&expect[..], &output[..]);
}

// test copied from rustls repo
/// TLS 1.2 SHA384 PRF test
#[cfg(feature = "tls12")]
pub fn tls12_sha384_prf_test_1() {
    let secret = b"\xb8\x0b\x73\x3d\x6c\xee\xfc\xdc\x71\x56\x6e\xa4\x8e\x55\x67\xdf";
    let seed = b"\xcd\x66\x5c\xf6\xa8\x44\x7d\xd6\xff\x8b\x27\x55\x5e\xdb\x74\x65";
    let label = b"test label";
    let expect = include_bytes!("../testdata/prf-result.3.bin");
    let mut output = [0u8; 148];

    let prf = PrfUsingHmac(&super::hmac::HMAC_SHA384);
    prf.for_secret(&mut output, secret, label, seed);

    assert_eq!(expect.len(), output.len());
    assert_eq!(&expect[..], &output[..]);
}

/// TLS 1.2 SHA384 PRF test with `"extended master secret"`
#[cfg(feature = "tls12")]
pub fn tls12_sha384_prf_test_2() {
    let secret = b"\xb8\x0b\x73\x3d\x6c\xee\xfc\xdc\x71\x56\x6e\xa4\x8e\x55\x67\xdf";
    let session_hash: &[u8; 48] = b"\xd9\x00\xfc\x5a\x86\xf0\x3d\xc6\x57\xc8\x5d\x24\x17\xfd\xb2\xfa\
                                    \x03\xaf\x25\x40\x76\xde\xc5\x94\x71\xac\x2a\x09\xf1\xb3\x6b\xd3\
                                    \xa2\x71\xa8\x54\x50\xe1\xf2\x06\x03\x8f\xe8\x88\xfe\x95\x2a\xba";
    let label = b"extended master secret";
    let expect = [
        0xbb, 0x98, 0xf0, 0x38, 0x1e, 0xcc, 0xea, 0xde, 0xb5, 0x5e, 0xd1, 0xea, 0xfb, 0x3a, 0xf7, 0x65, 0xfe, 0x1f, 0xbe, 0x07,
        0xc6, 0x9e, 0xd9, 0x96, 0xf3, 0x7f, 0xea, 0x1c, 0x9e, 0x36, 0xfd, 0xa8, 0xd7, 0xe8, 0x73, 0x61, 0xc4, 0xb6, 0x78, 0x76,
        0xfc, 0xf4, 0xac, 0xa5, 0xbf, 0x32, 0x0a, 0x9a, 0x3a, 0x41, 0x68, 0x2e, 0x19, 0x59, 0xd0, 0xb7, 0x18, 0x6d, 0x99, 0x90,
        0x3b, 0x58, 0x40, 0xd9, 0x64, 0x33, 0x91, 0x74, 0x8b, 0xca, 0x0b, 0xfd, 0x3f, 0xe1, 0xaf, 0xa7, 0x6d, 0x12, 0x01, 0xf2,
        0xb5, 0x17, 0x38, 0xc1, 0xae, 0xed, 0x17, 0x99, 0x42, 0xbb, 0xad, 0x41, 0x1d, 0x32, 0x37, 0xa4, 0x55, 0x2e, 0x82, 0x86,
        0x88, 0x1a, 0x42, 0x4d, 0xc5, 0x02, 0xc7, 0x02, 0x50, 0x6a, 0x70, 0xfe, 0xf8, 0x62, 0xaf, 0xc1, 0xcf, 0x38, 0x26, 0xf3,
        0x28, 0x05, 0x63, 0x5f, 0xaf, 0x08, 0x74, 0x48, 0x4e, 0xa6, 0x0d, 0x6f, 0x97, 0x64, 0x13, 0xb1, 0x3a, 0xea, 0x57, 0x60,
        0x6e, 0x3a, 0x6b, 0x3b, 0x65, 0x34, 0x47, 0xfb,
    ];
    let mut output = [0u8; 148];

    let prf = PrfUsingHmac(&super::hmac::HMAC_SHA384);
    prf.for_secret(&mut output, secret, label, session_hash);

    assert_eq!(expect.len(), output.len());
    assert_eq!(&expect[..], &output[..]);
}

// test copied from rustls repo
/// TLS 1.3 KDF [test case 1](https://datatracker.ietf.org/doc/html/rfc5869#appendix-A.1).
pub fn tls13_kdf_test_case_1() {
    let hkdf = HkdfUsingHmac(&super::hmac::HMAC_SHA256);
    let ikm = &[0x0b; 22];
    let salt = &[0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c];
    let info: &[&[u8]] = &[&[0xf0, 0xf1, 0xf2], &[0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9]];

    let output: [u8; 42] = expand(
        hkdf.extract_from_secret(Some(salt), ikm)
            .as_ref(),
        info,
    );

    assert_eq!(
        &output,
        &[
            0x3c, 0xb2, 0x5f, 0x25, 0xfa, 0xac, 0xd5, 0x7a, 0x90, 0x43, 0x4f, 0x64, 0xd0, 0x36, 0x2f, 0x2a, 0x2d, 0x2d, 0x0a,
            0x90, 0xcf, 0x1a, 0x5a, 0x4c, 0x5d, 0xb0, 0x2d, 0x56, 0xec, 0xc4, 0xc5, 0xbf, 0x34, 0x00, 0x72, 0x08, 0xd5, 0xb8,
            0x87, 0x18, 0x58, 0x65
        ]
    );
}

// test copied from rustls repo
/// TLS 1.3 KDF [test case 2](https://datatracker.ietf.org/doc/html/rfc5869#appendix-A.2).
pub fn tls13_kdf_test_case_2() {
    let hkdf = HkdfUsingHmac(&super::hmac::HMAC_SHA256);
    let ikm: Vec<u8> = (0x00u8..=0x4f).collect();
    let salt: Vec<u8> = (0x60u8..=0xaf).collect();
    let info: Vec<u8> = (0xb0u8..=0xff).collect();

    let output: [u8; 82] = expand(
        hkdf.extract_from_secret(Some(&salt), &ikm)
            .as_ref(),
        &[&info],
    );

    assert_eq!(
        &output,
        &[
            0xb1, 0x1e, 0x39, 0x8d, 0xc8, 0x03, 0x27, 0xa1, 0xc8, 0xe7, 0xf7, 0x8c, 0x59, 0x6a, 0x49, 0x34, 0x4f, 0x01, 0x2e,
            0xda, 0x2d, 0x4e, 0xfa, 0xd8, 0xa0, 0x50, 0xcc, 0x4c, 0x19, 0xaf, 0xa9, 0x7c, 0x59, 0x04, 0x5a, 0x99, 0xca, 0xc7,
            0x82, 0x72, 0x71, 0xcb, 0x41, 0xc6, 0x5e, 0x59, 0x0e, 0x09, 0xda, 0x32, 0x75, 0x60, 0x0c, 0x2f, 0x09, 0xb8, 0x36,
            0x77, 0x93, 0xa9, 0xac, 0xa3, 0xdb, 0x71, 0xcc, 0x30, 0xc5, 0x81, 0x79, 0xec, 0x3e, 0x87, 0xc1, 0x4c, 0x01, 0xd5,
            0xc1, 0xf3, 0x43, 0x4f, 0x1d, 0x87
        ]
    );
}

// test copied from rustls repo
/// TLS 1.3 KDF [test case 3](https://datatracker.ietf.org/doc/html/rfc5869#appendix-A.3).
pub fn tls13_kdf_test_case_3() {
    let hkdf = HkdfUsingHmac(&super::hmac::HMAC_SHA256);
    let ikm = &[0x0b; 22];
    let salt = &[];
    let info = &[];

    let output: [u8; 42] = expand(
        hkdf.extract_from_secret(Some(salt), ikm)
            .as_ref(),
        info,
    );

    assert_eq!(
        &output,
        &[
            0x8d, 0xa4, 0xe7, 0x75, 0xa5, 0x63, 0xc1, 0x8f, 0x71, 0x5f, 0x80, 0x2a, 0x06, 0x3c, 0x5a, 0x31, 0xb8, 0xa1, 0x1f,
            0x5c, 0x5e, 0xe1, 0x87, 0x9e, 0xc3, 0x45, 0x4e, 0x5f, 0x3c, 0x73, 0x8d, 0x2d, 0x9d, 0x20, 0x13, 0x95, 0xfa, 0xa4,
            0xb6, 0x1a, 0x96, 0xc8
        ]
    );
}

/// Known answer Cryptographic Algorithm Self Test (CAST) for FFDHE.
/// This is required by [FIPS 140-3 IG D.F]:
///
/// > The CAST for a solution complying with path (1) above shall
/// > consist of verifying the correctness of the computation of the
/// > shared secret Z in at least two of the schemes listed in Section 6
/// > of [SP 800-56Arev3]: one CAST for the Finite Field Cryptography
/// > (FFC) methods and one CAST for the Elliptic Curve Cryptography
/// > methods, if both the FFC and ECC methods are implemented;
/// > otherwise, just one. No separate CASTs are required to test
/// > Diffie-Hellman and MQV schemes.
///
/// [FIPS 140-3 IG D.F]:
///     https://csrc.nist.gov/projects/cryptographic-module-validation-program/fips-140-3-ig-announcements
/// [SP 800-56Arev3]: https://csrc.nist.gov/pubs/sp/800/56/a/r3/final
pub fn ffdhe_crypto_algo_self_test() {
    // known binary variables
    let server_private_key_vec = [
        0x4f, 0x51, 0xa9, 0x0b, 0x6b, 0x56, 0x38, 0x26, 0x2a, 0x55, 0x13, 0xf4, 0x5b, 0x91, 0x4c, 0x1a, 0x3c, 0x40, 0x4e, 0xfa,
        0x95, 0xf1, 0x4c, 0xc3, 0x3a, 0x53, 0xf0, 0x02, 0xb3, 0xcc, 0x07, 0xd3, 0x35, 0xd4, 0x2c, 0x27, 0x2e, 0xb0, 0x4e, 0x30,
        0x4c, 0x64, 0xb8, 0x7d, 0x1c, 0xfc, 0x07, 0xf6,
    ];
    let peer_public_key_vec = [
        0x62, 0x56, 0x4a, 0x73, 0x21, 0x0d, 0x76, 0xa1, 0xcf, 0xef, 0x6c, 0x99, 0xf7, 0x58, 0xf9, 0x41, 0xb3, 0xcd, 0x69, 0xca,
        0x2f, 0xfd, 0x84, 0xdd, 0xc6, 0xf1, 0x30, 0xe2, 0x94, 0xb9, 0xe0, 0xa0, 0x9b, 0x45, 0xa5, 0xbd, 0xf0, 0x5a, 0x6c, 0xf3,
        0xd9, 0x8e, 0x73, 0x2e, 0x99, 0x15, 0x7a, 0xd5, 0x27, 0xba, 0x18, 0x18, 0x46, 0x47, 0xd7, 0x78, 0xa4, 0xb1, 0x94, 0xdd,
        0x23, 0xe6, 0x43, 0x20, 0xee, 0x91, 0x19, 0x47, 0x64, 0x51, 0x1b, 0x49, 0x8f, 0xe5, 0x2c, 0xd5, 0x56, 0x3c, 0x41, 0x62,
        0xa5, 0x0f, 0xd2, 0x1f, 0xec, 0x09, 0x19, 0x0f, 0xde, 0x68, 0xff, 0x27, 0x33, 0x14, 0xa4, 0x5d, 0xb9, 0xb4, 0xa1, 0x8a,
        0xc4, 0x49, 0x5a, 0x07, 0x30, 0xed, 0xed, 0x6b, 0xb4, 0x69, 0xc7, 0x63, 0x5b, 0x7c, 0x53, 0x34, 0xc8, 0xac, 0x3d, 0x4e,
        0xdd, 0x97, 0x13, 0x93, 0x3b, 0x63, 0xbb, 0xcf, 0x13, 0x09, 0x03, 0x58, 0x8a, 0xdb, 0x8d, 0xd9, 0x05, 0xe9, 0x63, 0x26,
        0x9b, 0x6d, 0x61, 0xb6, 0xe2, 0xfd, 0xb8, 0x57, 0x86, 0x6f, 0x10, 0xdd, 0xff, 0x90, 0xe6, 0x4b, 0xfd, 0x35, 0x83, 0xeb,
        0x0a, 0x2d, 0x56, 0xfa, 0x09, 0x88, 0x1a, 0x54, 0x06, 0x6d, 0x9e, 0x5c, 0x2d, 0x2f, 0x70, 0xd9, 0x22, 0xcf, 0x2c, 0x32,
        0xb8, 0x9e, 0x5c, 0x5b, 0xc5, 0xa9, 0x4c, 0xb5, 0x3c, 0x5f, 0x67, 0x62, 0x91, 0x4f, 0x39, 0xb1, 0xa7, 0x46, 0x35, 0xeb,
        0xab, 0x90, 0xd9, 0x0c, 0x2b, 0x9d, 0x2f, 0x42, 0x46, 0x2d, 0x43, 0xae, 0x29, 0x89, 0xda, 0x87, 0x3b, 0x1c, 0x48, 0x44,
        0x86, 0x1f, 0x11, 0xc5, 0x10, 0xc9, 0x09, 0x0c, 0x1f, 0xb7, 0x8e, 0x7a, 0x5e, 0xfc, 0xd6, 0x09, 0x2b, 0xf2, 0xc6, 0xb2,
        0xe3, 0xff, 0x40, 0xdd, 0x11, 0x61, 0xd9, 0xed, 0xb2, 0xda, 0x6c, 0xe3, 0xba, 0x1b, 0xb3, 0x75,
    ];
    let expected_shared_secret = [
        0x99, 0x8e, 0xa6, 0x14, 0x62, 0x67, 0x20, 0x37, 0x5f, 0xa3, 0x61, 0x3f, 0xa7, 0xcb, 0xf0, 0x92, 0x1e, 0xec, 0xbb, 0x89,
        0xf2, 0x5a, 0x52, 0x55, 0xb9, 0xe1, 0xcd, 0x4f, 0x34, 0x8b, 0xf6, 0x5d, 0x1e, 0x22, 0x93, 0x58, 0xd7, 0xae, 0xcf, 0xac,
        0x50, 0xff, 0xff, 0x5b, 0x3d, 0x22, 0x1e, 0x32, 0xd0, 0xfc, 0xfc, 0xa3, 0xd1, 0xe0, 0x3b, 0x8c, 0x04, 0xf9, 0x4a, 0x7b,
        0x61, 0x3e, 0x61, 0xda, 0x5f, 0x81, 0x7e, 0xd7, 0x6f, 0xbc, 0xf6, 0xdd, 0x30, 0x7d, 0xfe, 0x91, 0x6c, 0x4c, 0x84, 0x55,
        0xdf, 0xa5, 0x89, 0x5a, 0x4e, 0xba, 0x4f, 0x08, 0xe9, 0xff, 0xf7, 0x2d, 0xc9, 0xbe, 0x22, 0xf9, 0xfe, 0xd5, 0x03, 0xbc,
        0x4c, 0xee, 0xfc, 0x24, 0x70, 0x40, 0xd3, 0x3d, 0x7e, 0x8e, 0x8f, 0x57, 0xf2, 0x19, 0xd5, 0x0d, 0x8a, 0x44, 0xf8, 0x1e,
        0x47, 0x89, 0x93, 0xc5, 0x5d, 0xce, 0x00, 0x07, 0x07, 0xa6, 0x45, 0xe9, 0x57, 0x85, 0x5d, 0x63, 0x58, 0xac, 0xa8, 0xa9,
        0xfe, 0x34, 0x0d, 0x72, 0x5e, 0x5b, 0xc8, 0x69, 0x66, 0x96, 0x96, 0x2a, 0x57, 0x50, 0xb5, 0x61, 0xd2, 0xae, 0x0d, 0x9f,
        0x9d, 0x94, 0x8b, 0x38, 0x3a, 0xcc, 0xc3, 0x4e, 0xf2, 0xdb, 0xdf, 0x79, 0x7a, 0xd5, 0x21, 0xb3, 0xab, 0x2d, 0x43, 0x19,
        0x82, 0x1a, 0x41, 0x22, 0x01, 0xe3, 0xe5, 0x87, 0x21, 0x0b, 0xf5, 0x2e, 0x79, 0x1f, 0x33, 0x2c, 0x6b, 0x90, 0x9c, 0xf4,
        0xd2, 0x28, 0x1c, 0xe6, 0xf1, 0xd3, 0x45, 0x05, 0xe8, 0x5e, 0x96, 0x7f, 0x24, 0xb9, 0x68, 0x4a, 0xbf, 0x3d, 0x7d, 0x0c,
        0xad, 0x59, 0x25, 0xc1, 0x4a, 0xd2, 0x97, 0x2a, 0xc0, 0xb7, 0x64, 0x26, 0x20, 0x44, 0xa2, 0x5a, 0xc4, 0x46, 0xe6, 0x1d,
        0x95, 0xe1, 0xdb, 0x82, 0x55, 0x3c, 0x8b, 0xd8, 0xfa, 0x2a, 0x2a, 0x5e, 0x04, 0x30, 0x76, 0xf8,
    ];
    // Create a FFDHE2048 DheActiveKeyExchange object based on a known private key
    let named_group = rustls::NamedGroup::FFDHE2048;
    let group = rustls::ffdhe_groups::FFDHE2048;
    let g = mbedtls::bignum::Mpi::from_binary(group.g).unwrap();
    let p = mbedtls::bignum::Mpi::from_binary(group.p).unwrap();
    let self_private_key = mbedtls::bignum::Mpi::from_binary(&server_private_key_vec).unwrap();
    let self_public_key = g
        .mod_exp(&self_private_key, &p)
        .unwrap();
    let self_public_key_vec = self_public_key
        .to_binary_padded(group.p.len())
        .unwrap();
    let dhe_kx = Box::new(crate::kx::DheActiveKeyExchangeImpl::new(
        named_group,
        group,
        std::sync::Mutex::new(p),
        std::sync::Mutex::new(self_private_key),
        self_public_key_vec,
    ));
    // Compute share secret again a known peer public key
    let shared_secret = dhe_kx
        .complete(&peer_public_key_vec)
        .unwrap();
    // Check result
    assert_eq!(&expected_shared_secret, shared_secret.secret_bytes());
}

/// Run all the self_tests. If any test fails, this function will panic.
///
/// If `verbose` is true, print messages about tests that have been executed.
pub fn self_tests(verbose: bool) {
    macro_rules! print_msg {
        ($($tt: tt)*) => {
            if verbose { std::println!($($tt)*) }
        };
    }

    #[cfg(feature = "tls12")]
    {
        tls12_sha256_prf_test_1();
        print_msg!("tls12_sha256_prf_test_1 passed.");
        tls12_sha256_prf_test_2();
        print_msg!("tls12_sha256_prf_test_2 passed.");
        tls12_sha384_prf_test_1();
        print_msg!("tls12_sha384_prf_test_1 passed.");
        tls12_sha384_prf_test_2();
        print_msg!("tls12_sha384_prf_test_2 passed.");
    }

    tls13_kdf_test_case_1();
    print_msg!("tls13_kdf_test_case_1 passed.");
    tls13_kdf_test_case_2();
    print_msg!("tls13_kdf_test_case_2 passed.");
    tls13_kdf_test_case_3();
    print_msg!("tls13_kdf_test_case_3 passed.");
    ffdhe_crypto_algo_self_test();
    print_msg!("ffdhe_crypto_algo_self_test passed.");

    print_msg!("All rustls-mbedcrypto-provider self-tests passed.");
}

#[test]
fn self_tests_succeed() {
    self_tests(true)
}
