// Copyright 2020 Contributors to the Parsec project.
// SPDX-License-Identifier: Apache-2.0
use std::convert::{TryFrom, TryInto};
use tss_esapi::{
    abstraction::ek,
    abstraction::transient::{KeyParams, ObjectWrapper, TransientKeyContextBuilder},
    constants::response_code::Tss2ResponseCodeKind,
    interface_types::{
        algorithm::{
            AsymmetricAlgorithm, EccSchemeAlgorithm, HashingAlgorithm, RsaSchemeAlgorithm,
        },
        ecc::EccCurve,
        key_bits::RsaKeyBits,
        resource_handles::Hierarchy,
    },
    structures::{
        Auth, CreateKeyResult, Digest, EccScheme, Public, PublicKeyRsa, RsaExponent, RsaScheme,
        RsaSignature, Signature, SymmetricDefinitionObject,
    },
    utils::{create_restricted_decryption_rsa_public, PublicKey},
    Error, TransientKeyContext, WrapperErrorKind as ErrorKind,
};

use crate::common::create_tcti;

const HASH: [u8; 32] = [
    0x69, 0x3E, 0xDB, 0x1B, 0x22, 0x79, 0x03, 0xF4, 0xC0, 0xBF, 0xD6, 0x91, 0x76, 0x37, 0x84, 0xA2,
    0x94, 0x8E, 0x92, 0x50, 0x35, 0xC2, 0x8C, 0x5C, 0x3C, 0xCA, 0xFE, 0x18, 0xE8, 0x81, 0x37, 0x78,
];

fn create_ctx() -> TransientKeyContext {
    TransientKeyContextBuilder::new()
        .with_tcti(create_tcti())
        .build()
        .unwrap()
}

#[test]
fn wrong_key_sizes() {
    assert_eq!(
        TransientKeyContextBuilder::new()
            .with_tcti(create_tcti())
            .with_root_key_size(1023)
            .build()
            .unwrap_err(),
        Error::WrapperError(ErrorKind::InvalidParam)
    );
    assert_eq!(
        TransientKeyContextBuilder::new()
            .with_tcti(create_tcti())
            .with_root_key_size(1025)
            .build()
            .unwrap_err(),
        Error::WrapperError(ErrorKind::InvalidParam)
    );
    assert_eq!(
        TransientKeyContextBuilder::new()
            .with_tcti(create_tcti())
            .with_root_key_size(2047)
            .build()
            .unwrap_err(),
        Error::WrapperError(ErrorKind::InvalidParam)
    );
    assert_eq!(
        TransientKeyContextBuilder::new()
            .with_tcti(create_tcti())
            .with_root_key_size(2049)
            .build()
            .unwrap_err(),
        Error::WrapperError(ErrorKind::InvalidParam)
    );
}

#[test]
fn wrong_auth_size() {
    assert_eq!(
        TransientKeyContextBuilder::new()
            .with_tcti(create_tcti())
            .with_root_key_auth_size(33)
            .build()
            .unwrap_err(),
        Error::WrapperError(ErrorKind::WrongParamSize)
    );
}

#[test]
fn load_bad_sized_key() {
    let mut ctx = create_ctx();
    let key_params = KeyParams::Rsa {
        size: RsaKeyBits::Rsa1024,
        scheme: RsaScheme::create(RsaSchemeAlgorithm::RsaSsa, Some(HashingAlgorithm::Sha256))
            .expect("Failed to create RSA scheme"),
        pub_exponent: RsaExponent::default(),
    };
    let _ = ctx
        .load_external_public_key(PublicKey::Rsa(vec![0xDE, 0xAD, 0xBE, 0xEF]), key_params)
        .unwrap_err();
}

#[test]
fn load_with_invalid_params() {
    let pub_key = vec![
        0x96, 0xDC, 0x72, 0x77, 0x49, 0x82, 0xFD, 0x2D, 0x06, 0x65, 0x8C, 0xE5, 0x3A, 0xCD, 0xED,
        0xBD, 0x50, 0xD7, 0x6F, 0x3B, 0xE5, 0x6A, 0x76, 0xED, 0x3E, 0xD8, 0xF9, 0x93, 0x40, 0x55,
        0x86, 0x6F, 0xBE, 0x76, 0x60, 0xD2, 0x03, 0x23, 0x59, 0x19, 0x8D, 0xFC, 0x51, 0x6A, 0x95,
        0xC8, 0x5D, 0x5A, 0x89, 0x4D, 0xE5, 0xEA, 0x44, 0x78, 0x29, 0x62, 0xDB, 0x3F, 0xF0, 0xF7,
        0x49, 0x15, 0xA5, 0xAE, 0x6D, 0x81, 0x8F, 0x06, 0x7B, 0x0B, 0x50, 0x7A, 0x2F, 0xEB, 0x00,
        0xB6, 0x12, 0xF3, 0x10, 0xAF, 0x4D, 0x4A, 0xA9, 0xD9, 0x81, 0xBB, 0x1E, 0x2B, 0xDF, 0xB9,
        0x33, 0x3D, 0xD6, 0xB7, 0x8D, 0x23, 0x7C, 0x7F, 0xE7, 0x12, 0x48, 0x4F, 0x26, 0x73, 0xAF,
        0x63, 0x51, 0xA9, 0xDB, 0xA4, 0xAB, 0xB7, 0x27, 0x00, 0xD7, 0x1C, 0xFC, 0x2F, 0x61, 0x2A,
        0xB9, 0x5B, 0x66, 0xA0, 0xE0, 0xD8, 0xF3, 0xD9,
    ];

    let key_params = KeyParams::Ecc {
        curve: EccCurve::NistP256,
        scheme: EccScheme::create(
            EccSchemeAlgorithm::EcDsa,
            Some(HashingAlgorithm::Sha256),
            None,
        )
        .expect("Failed to create ecc scheme"),
    };
    let mut ctx = create_ctx();
    let _ = ctx
        .load_external_public_key(PublicKey::Rsa(pub_key), key_params)
        .unwrap_err();
}

#[test]
fn verify() {
    let pub_key = vec![
        0x96, 0xDC, 0x72, 0x77, 0x49, 0x82, 0xFD, 0x2D, 0x06, 0x65, 0x8C, 0xE5, 0x3A, 0xCD, 0xED,
        0xBD, 0x50, 0xD7, 0x6F, 0x3B, 0xE5, 0x6A, 0x76, 0xED, 0x3E, 0xD8, 0xF9, 0x93, 0x40, 0x55,
        0x86, 0x6F, 0xBE, 0x76, 0x60, 0xD2, 0x03, 0x23, 0x59, 0x19, 0x8D, 0xFC, 0x51, 0x6A, 0x95,
        0xC8, 0x5D, 0x5A, 0x89, 0x4D, 0xE5, 0xEA, 0x44, 0x78, 0x29, 0x62, 0xDB, 0x3F, 0xF0, 0xF7,
        0x49, 0x15, 0xA5, 0xAE, 0x6D, 0x81, 0x8F, 0x06, 0x7B, 0x0B, 0x50, 0x7A, 0x2F, 0xEB, 0x00,
        0xB6, 0x12, 0xF3, 0x10, 0xAF, 0x4D, 0x4A, 0xA9, 0xD9, 0x81, 0xBB, 0x1E, 0x2B, 0xDF, 0xB9,
        0x33, 0x3D, 0xD6, 0xB7, 0x8D, 0x23, 0x7C, 0x7F, 0xE7, 0x12, 0x48, 0x4F, 0x26, 0x73, 0xAF,
        0x63, 0x51, 0xA9, 0xDB, 0xA4, 0xAB, 0xB7, 0x27, 0x00, 0xD7, 0x1C, 0xFC, 0x2F, 0x61, 0x2A,
        0xB9, 0x5B, 0x66, 0xA0, 0xE0, 0xD8, 0xF3, 0xD9,
    ];

    // "Les carottes sont cuites." hashed with SHA256
    let digest = Digest::try_from(vec![
        0x02, 0x2b, 0x26, 0xb1, 0xc3, 0x18, 0xdb, 0x73, 0x36, 0xef, 0x6f, 0x50, 0x9c, 0x35, 0xdd,
        0xaa, 0xe1, 0x3d, 0x21, 0xdf, 0x83, 0x68, 0x0f, 0x48, 0xae, 0x5d, 0x8a, 0x5d, 0x37, 0x3c,
        0xc1, 0x05,
    ])
    .unwrap();

    let signature = Signature::RsaSsa(
        RsaSignature::create(
            HashingAlgorithm::Sha256,
            PublicKeyRsa::try_from(vec![
                0x8c, 0xf8, 0x87, 0x3a, 0xb2, 0x9a, 0x18, 0xf9, 0xe0, 0x2e, 0xb9, 0x2d, 0xe7, 0xc8,
                0x32, 0x12, 0xd6, 0xd9, 0x2d, 0x98, 0xec, 0x9e, 0x47, 0xb7, 0x5b, 0x26, 0x86, 0x9d,
                0xf5, 0xa2, 0x6b, 0x8b, 0x6f, 0x00, 0xd3, 0xbb, 0x68, 0x88, 0xe1, 0xad, 0xcf, 0x1c,
                0x09, 0x81, 0x91, 0xbf, 0xee, 0xce, 0x4f, 0xb5, 0x83, 0x3c, 0xf5, 0xb0, 0xfa, 0x68,
                0x69, 0xde, 0x7b, 0xe8, 0x49, 0x69, 0x40, 0xad, 0x90, 0xf1, 0x7f, 0x31, 0xf2, 0x75,
                0x4e, 0x1c, 0x52, 0x92, 0x72, 0x2e, 0x0b, 0x06, 0xe7, 0x32, 0xb4, 0x5e, 0x82, 0x8b,
                0x39, 0x72, 0x24, 0x5f, 0xee, 0x17, 0xae, 0x2d, 0x77, 0x53, 0xff, 0x1a, 0xad, 0x12,
                0x83, 0x4f, 0xb5, 0x52, 0x92, 0x6e, 0xda, 0xb2, 0x55, 0x77, 0xa7, 0x58, 0xcc, 0x10,
                0xa6, 0x7f, 0xc5, 0x26, 0x4e, 0x5b, 0x75, 0x9d, 0x83, 0x05, 0x9f, 0x99, 0xde, 0xc6,
                0xf5, 0x12,
            ])
            .expect("Failed to create Public RSA key structure for RSA signature"),
        )
        .expect("Failed to create RSA signature"),
    );

    let mut ctx = create_ctx();
    let key_params = KeyParams::Rsa {
        size: RsaKeyBits::Rsa1024,
        scheme: RsaScheme::create(RsaSchemeAlgorithm::RsaSsa, Some(HashingAlgorithm::Sha256))
            .expect("Failed to create RSA scheme"),
        pub_exponent: RsaExponent::default(),
    };
    let pub_key = ctx
        .load_external_public_key(PublicKey::Rsa(pub_key), key_params)
        .unwrap();
    let _ = ctx
        .verify_signature(pub_key, key_params, digest, signature)
        .expect("the signature should be valid");
}

#[test]
fn sign_with_bad_auth() {
    let mut ctx = create_ctx();
    let key_params = KeyParams::Rsa {
        size: RsaKeyBits::Rsa2048,
        scheme: RsaScheme::create(RsaSchemeAlgorithm::RsaSsa, Some(HashingAlgorithm::Sha256))
            .expect("Failed to create RSA scheme"),
        pub_exponent: RsaExponent::default(),
    };
    let (key, key_auth) = ctx.create_key(key_params, 16).unwrap();
    let auth_value = key_auth.unwrap();
    let mut bad_auth_values = auth_value.value().to_vec();
    bad_auth_values[6..10].copy_from_slice(&[0xDE, 0xAD, 0xBE, 0xEF]);
    ctx.sign(
        key,
        key_params,
        Some(Auth::try_from(bad_auth_values).unwrap()),
        Digest::try_from(HASH.to_vec()).unwrap(),
    )
    .unwrap_err();
}

#[test]
fn sign_with_no_auth() {
    let mut ctx = create_ctx();
    let key_params = KeyParams::Rsa {
        size: RsaKeyBits::Rsa2048,
        scheme: RsaScheme::create(RsaSchemeAlgorithm::RsaSsa, Some(HashingAlgorithm::Sha256))
            .expect("Failed to create RSA scheme"),
        pub_exponent: RsaExponent::default(),
    };
    let (key, _) = ctx.create_key(key_params, 16).unwrap();
    ctx.sign(
        key,
        key_params,
        None,
        Digest::try_from(HASH.to_vec()).unwrap(),
    )
    .unwrap_err();
}

#[test]
fn encrypt_decrypt() {
    let mut ctx = create_ctx();
    let key_params = KeyParams::Rsa {
        size: RsaKeyBits::Rsa2048,
        scheme: RsaScheme::create(RsaSchemeAlgorithm::Oaep, Some(HashingAlgorithm::Sha256))
            .expect("Failed to create RSA scheme"),
        pub_exponent: RsaExponent::default(),
    };
    let (dec_key, auth) = ctx.create_key(key_params, 16).unwrap();
    let enc_key = ctx
        .load_external_public_key(dec_key.public().clone(), key_params)
        .unwrap();
    let message = vec![0x1, 0x2, 0x3];

    let ciphertext = ctx
        .rsa_encrypt(
            enc_key,
            key_params,
            None,
            PublicKeyRsa::try_from(message.clone()).unwrap(),
            None,
        )
        .unwrap();
    assert_ne!(message, ciphertext.value());

    let plaintext = ctx
        .rsa_decrypt(dec_key, key_params, auth, ciphertext, None)
        .unwrap();
    assert_eq!(message, plaintext.value());
}

#[test]
fn two_signatures_different_digest() {
    let mut ctx = create_ctx();
    let key_params1 = KeyParams::Rsa {
        size: RsaKeyBits::Rsa2048,
        scheme: RsaScheme::create(RsaSchemeAlgorithm::RsaSsa, Some(HashingAlgorithm::Sha256))
            .expect("Failed to create RSA scheme"),
        pub_exponent: RsaExponent::default(),
    };
    let (key1, auth1) = ctx.create_key(key_params1, 16).unwrap();
    let key_params2 = KeyParams::Rsa {
        size: RsaKeyBits::Rsa2048,
        scheme: RsaScheme::create(RsaSchemeAlgorithm::RsaSsa, Some(HashingAlgorithm::Sha256))
            .expect("Failed to create RSA scheme"),
        pub_exponent: RsaExponent::default(),
    };
    let (key2, auth2) = ctx.create_key(key_params2, 16).unwrap();
    let signature1 = ctx
        .sign(
            key1,
            key_params1,
            auth1,
            Digest::try_from(HASH.to_vec()).unwrap(),
        )
        .unwrap();
    let signature2 = ctx
        .sign(
            key2,
            key_params2,
            auth2,
            Digest::try_from(HASH.to_vec()).unwrap(),
        )
        .unwrap();

    if let Signature::RsaSsa(rsa_signature_1) = signature1 {
        if let Signature::RsaSsa(rsa_signature_2) = signature2 {
            assert!(
                rsa_signature_1.signature().value().to_vec()
                    != rsa_signature_2.signature().value().to_vec()
            );
        } else {
            panic!("Unexpected signature for signature 2");
        }
    } else {
        panic!("Unexpected singature for signature 1");
    }
}

#[test]
fn verify_wrong_key() {
    let mut ctx = create_ctx();
    let key_params1 = KeyParams::Rsa {
        size: RsaKeyBits::Rsa2048,
        scheme: RsaScheme::create(RsaSchemeAlgorithm::RsaSsa, Some(HashingAlgorithm::Sha256))
            .expect("Failed to create RSA scheme"),
        pub_exponent: RsaExponent::default(),
    };
    let (key1, auth1) = ctx.create_key(key_params1, 16).unwrap();

    let key_params2 = KeyParams::Rsa {
        size: RsaKeyBits::Rsa2048,
        scheme: RsaScheme::create(RsaSchemeAlgorithm::RsaSsa, Some(HashingAlgorithm::Sha256))
            .expect("Failed to create RSA scheme"),
        pub_exponent: RsaExponent::default(),
    };
    let (key2, _) = ctx.create_key(key_params2, 16).unwrap();

    // Sign with the first key
    let signature = ctx
        .sign(
            key1,
            key_params1,
            auth1,
            Digest::try_from(HASH.to_vec()).unwrap(),
        )
        .unwrap();

    // Import and verify with the second key
    let pub_key = ctx
        .load_external_public_key(key2.public().clone(), key_params2)
        .unwrap();
    if let Error::Tss2Error(error) = ctx
        .verify_signature(
            pub_key,
            key_params2,
            Digest::try_from(HASH.to_vec()).unwrap(),
            signature,
        )
        .unwrap_err()
    {
        assert_eq!(error.kind(), Some(Tss2ResponseCodeKind::Signature));
    } else {
        panic!("The signature verification should have failed with an invalid signature error.");
    }
}
#[test]
fn verify_wrong_digest() {
    let mut ctx = create_ctx();
    let key_params = KeyParams::Rsa {
        size: RsaKeyBits::Rsa2048,
        scheme: RsaScheme::create(RsaSchemeAlgorithm::RsaSsa, Some(HashingAlgorithm::Sha256))
            .expect("Failed to create RSA scheme"),
        pub_exponent: RsaExponent::default(),
    };
    let (key, auth) = ctx.create_key(key_params, 16).unwrap();

    let signature = ctx
        .sign(
            key.clone(),
            key_params,
            auth,
            Digest::try_from(HASH.to_vec()).unwrap(),
        )
        .unwrap();
    let pub_key = ctx
        .load_external_public_key(key.public().clone(), key_params)
        .unwrap();

    let mut digest_values = HASH.to_vec();
    digest_values[0..4].copy_from_slice(&[0xDE, 0xAD, 0xBE, 0xEF]);
    if let Error::Tss2Error(error) = ctx
        .verify_signature(
            pub_key,
            key_params,
            Digest::try_from(digest_values).unwrap(),
            signature,
        )
        .unwrap_err()
    {
        assert_eq!(error.kind(), Some(Tss2ResponseCodeKind::Signature));
    } else {
        panic!("The signature verification should have failed with an invalid signature error.");
    }
}

#[test]
fn full_test() {
    let mut ctx = create_ctx();
    for _ in 0..4 {
        let key_params = KeyParams::Rsa {
            size: RsaKeyBits::Rsa2048,
            scheme: RsaScheme::create(RsaSchemeAlgorithm::RsaSsa, Some(HashingAlgorithm::Sha256))
                .expect("Failed to create RSA scheme"),
            pub_exponent: RsaExponent::default(),
        };
        let (key, auth) = ctx.create_key(key_params, 16).unwrap();
        let signature = ctx
            .sign(
                key.clone(),
                key_params,
                auth,
                Digest::try_from(HASH.to_vec()).unwrap(),
            )
            .unwrap();
        let pub_key = ctx
            .load_external_public_key(key.public().clone(), key_params)
            .unwrap();
        let _ = ctx
            .verify_signature(
                pub_key,
                key_params,
                Digest::try_from(HASH.to_vec()).unwrap(),
                signature,
            )
            .unwrap();
    }
}

#[test]
fn create_ecc_key() {
    let mut ctx = create_ctx();
    let _ = ctx
        .create_key(
            KeyParams::Ecc {
                curve: EccCurve::NistP256,
                scheme: EccScheme::create(
                    EccSchemeAlgorithm::EcDsa,
                    Some(HashingAlgorithm::Sha256),
                    None,
                )
                .expect("Failed to create ecc scheme"),
            },
            16,
        )
        .unwrap();
}

#[test]
fn create_ecc_key_decryption_scheme() {
    let mut ctx = create_ctx();
    let _ = ctx
        .create_key(
            KeyParams::Ecc {
                curve: EccCurve::NistP256,
                scheme: EccScheme::create(
                    EccSchemeAlgorithm::EcDh,
                    Some(HashingAlgorithm::Sha256),
                    None,
                )
                .expect("Failed to create ecc scheme"),
            },
            16,
        )
        .unwrap_err();
}

#[test]
fn full_ecc_test() {
    let mut ctx = create_ctx();
    let key_params = KeyParams::Ecc {
        curve: EccCurve::NistP256,
        scheme: EccScheme::create(
            EccSchemeAlgorithm::EcDsa,
            Some(HashingAlgorithm::Sha256),
            None,
        )
        .expect("Failed to create ecc scheme"),
    };
    for _ in 0..4 {
        let (key, auth) = ctx.create_key(key_params, 16).unwrap();
        let signature = ctx
            .sign(
                key.clone(),
                key_params,
                auth,
                Digest::try_from(HASH.to_vec()).unwrap(),
            )
            .unwrap();
        let pub_key = ctx
            .load_external_public_key(key.public().clone(), key_params)
            .unwrap();
        let _ = ctx
            .verify_signature(
                pub_key,
                key_params,
                Digest::try_from(HASH.to_vec()).unwrap(),
                signature,
            )
            .unwrap();
    }
}

#[test]
fn ctx_migration_test() {
    // Create two key contexts using `Context`, one for an RSA keypair,
    // one for just the public part of the key
    let mut basic_ctx = crate::common::create_ctx_with_session();
    let random_digest = basic_ctx.get_random(16).unwrap();
    let key_auth = Auth::try_from(random_digest.value().to_vec()).unwrap();
    let prim_key_handle = basic_ctx
        .create_primary(
            Hierarchy::Owner,
            create_restricted_decryption_rsa_public(
                SymmetricDefinitionObject::AES_256_CFB,
                RsaKeyBits::Rsa2048,
                RsaExponent::create(0).unwrap(),
            )
            .unwrap(),
            Some(key_auth.clone()),
            None,
            None,
            None,
        )
        .unwrap()
        .key_handle;

    let result = basic_ctx
        .create(
            prim_key_handle,
            crate::common::signing_key_pub(),
            Some(key_auth.clone()),
            None,
            None,
            None,
        )
        .unwrap();

    let key_handle = basic_ctx
        .load(
            prim_key_handle,
            result.out_private.clone(),
            result.out_public.clone(),
        )
        .unwrap();
    let key_context = basic_ctx.context_save(key_handle.into()).unwrap();

    let pub_key_handle = basic_ctx
        .load_external_public(result.out_public.clone(), Hierarchy::Owner)
        .unwrap();
    let pub_key_context = basic_ctx.context_save(pub_key_handle.into()).unwrap();

    // Drop the `Context` to free the comms channel to the TPM
    std::mem::drop(basic_ctx);

    // Migrate the keys and attempt to use them
    let mut ctx = create_ctx();
    let key = ctx
        .migrate_key_from_ctx(key_context, Some(key_auth.clone()))
        .unwrap();
    let pub_key = ctx
        .migrate_key_from_ctx(pub_key_context, Some(key_auth.clone()))
        .unwrap();

    let key_params = KeyParams::Rsa {
        scheme: RsaScheme::create(RsaSchemeAlgorithm::RsaSsa, Some(HashingAlgorithm::Sha256))
            .expect("Failed to create RSA scheme"),
        size: RsaKeyBits::Rsa2048,
        pub_exponent: RsaExponent::default(),
    };
    let signature = ctx
        .sign(
            key.clone(),
            key_params,
            Some(key_auth),
            Digest::try_from(HASH.to_vec()).unwrap(),
        )
        .unwrap();
    let _ = ctx
        .verify_signature(
            pub_key.clone(),
            key_params,
            Digest::try_from(HASH.to_vec()).unwrap(),
            signature,
        )
        .expect("the signature should be valid");

    // Check that the public key is identical across the migration
    if let CreateKeyResult {
        out_public: Public::Rsa { unique, .. },
        ..
    } = result
    {
        assert_eq!(
            PublicKey::Rsa(unique.value().to_vec()),
            pub_key.public().clone()
        );
        assert_eq!(
            PublicKey::Rsa(unique.value().to_vec()),
            key.public().clone()
        );
    } else {
        panic!("Got wrong type of key from TPM");
    }
}

#[test]
fn activate_credential() {
    // create a Transient key context, generate a key and
    // obtain the Make Credential parameters
    let mut ctx = create_ctx();
    let params = KeyParams::Ecc {
        curve: EccCurve::NistP256,
        scheme: EccScheme::create(
            EccSchemeAlgorithm::EcDsa,
            Some(HashingAlgorithm::Sha256),
            None,
        )
        .expect("Failed to create ecc scheme"),
    };
    let (material, auth) = ctx.create_key(params, 16).unwrap();
    let obj = ObjectWrapper {
        material,
        params,
        auth,
    };
    let make_cred_params = ctx.get_make_cred_params(obj.clone(), None).unwrap();

    drop(ctx);

    // create a normal Context and make the credential
    let mut basic_ctx = crate::common::create_ctx_with_session();

    // the public part of the EK is used, so we retrieve the parameters
    let key_pub =
        ek::create_ek_public_from_default_template(AsymmetricAlgorithm::Rsa, None).unwrap();
    let key_pub = if let Public::Rsa {
        object_attributes,
        name_hashing_algorithm,
        auth_policy,
        parameters,
        ..
    } = key_pub
    {
        Public::Rsa {
            object_attributes,
            name_hashing_algorithm,
            auth_policy,
            parameters,
            unique: if let PublicKey::Rsa(val) = make_cred_params.attesting_key_pub {
                PublicKeyRsa::try_from(val).unwrap()
            } else {
                panic!("Wrong public key type");
            },
        }
    } else {
        panic!("Wrong Public type");
    };
    let pub_handle = basic_ctx
        .load_external_public(key_pub, Hierarchy::Owner)
        .unwrap();

    // Credential to expect back as proof for attestation
    let credential = vec![0x53; 16];

    let (cred, secret) = basic_ctx
        .make_credential(
            pub_handle,
            credential.clone().try_into().unwrap(),
            make_cred_params.name.try_into().unwrap(),
        )
        .unwrap();

    drop(basic_ctx);

    // Create a new Transient key context and activate the credential
    let mut ctx = create_ctx();
    let cred_back = ctx
        .activate_credential(obj, None, cred.value().to_vec(), secret.value().to_vec())
        .unwrap();

    assert_eq!(cred_back, credential);
}

#[test]
fn make_cred_params_name() {
    // create a Transient key context, generate a key and
    // obtain the Make Credential parameters
    let mut ctx = create_ctx();
    let params = KeyParams::Ecc {
        curve: EccCurve::NistP256,
        scheme: EccScheme::create(
            EccSchemeAlgorithm::EcDsa,
            Some(HashingAlgorithm::Sha256),
            None,
        )
        .expect("Failed to create ecc scheme"),
    };
    let (material, auth) = ctx.create_key(params, 16).unwrap();
    let obj = ObjectWrapper {
        material,
        params,
        auth,
    };
    let make_cred_params = ctx.get_make_cred_params(obj, None).unwrap();

    // Verify that the name provided in the parameters is
    // consistent with the public buffer
    use sha2::Digest;
    let mut hasher = sha2::Sha256::new();
    hasher.update(make_cred_params.public);
    let hash = hasher.finalize();
    // The first 2 bytes of the name represent the hash algorithm used
    assert_eq!(make_cred_params.name[2..], hash[..]);
}

#[test]
fn activate_credential_wrong_key() {
    // create a Transient key context, generate two keys and
    // obtain the Make Credential parameters for the first one
    let mut ctx = create_ctx();
    let params = KeyParams::Ecc {
        curve: EccCurve::NistP256,
        scheme: EccScheme::create(
            EccSchemeAlgorithm::EcDsa,
            Some(HashingAlgorithm::Sha256),
            None,
        )
        .expect("Failed to create ecc scheme"),
    };
    // "Good" key (for which the credential will be generated)
    let (material, auth) = ctx.create_key(params, 16).unwrap();
    let obj = ObjectWrapper {
        material,
        params,
        auth,
    };
    let make_cred_params = ctx.get_make_cred_params(obj, None).unwrap();

    // "Wrong" key (which will be used instead of the good key in attestation)
    let (material, auth) = ctx.create_key(params, 16).unwrap();
    let wrong_obj = ObjectWrapper {
        material,
        params,
        auth,
    };

    drop(ctx);

    // create a normal Context and make the credential
    let mut basic_ctx = crate::common::create_ctx_with_session();

    // the public part of the EK is used, so we retrieve the parameters
    let key_pub =
        ek::create_ek_public_from_default_template(AsymmetricAlgorithm::Rsa, None).unwrap();
    let key_pub = if let Public::Rsa {
        object_attributes,
        name_hashing_algorithm,
        auth_policy,
        parameters,
        ..
    } = key_pub
    {
        Public::Rsa {
            object_attributes,
            name_hashing_algorithm,
            auth_policy,
            parameters,
            unique: if let PublicKey::Rsa(val) = make_cred_params.attesting_key_pub {
                PublicKeyRsa::try_from(val).unwrap()
            } else {
                panic!("Wrong public key type");
            },
        }
    } else {
        panic!("Wrong Public type");
    };
    let pub_handle = basic_ctx
        .load_external_public(key_pub, Hierarchy::Owner)
        .unwrap();

    // Credential to expect back as proof for attestation
    let credential = vec![0x53; 16];

    let (cred, secret) = basic_ctx
        .make_credential(
            pub_handle,
            credential.try_into().unwrap(),
            make_cred_params.name.try_into().unwrap(),
        )
        .unwrap();

    drop(basic_ctx);

    // Create a new Transient key context and activate the credential
    // Validation fails within the TPM because the credential HMAC is
    // associated with a different object (so the integrity check fails).
    let mut ctx = create_ctx();
    let e = ctx
        .activate_credential(
            wrong_obj,
            None,
            cred.value().to_vec(),
            secret.value().to_vec(),
        )
        .unwrap_err();
    if let Error::Tss2Error(e) = e {
        assert_eq!(e.kind(), Some(Tss2ResponseCodeKind::Integrity));
    } else {
        panic!("Got crate error ({}) when expecting an error from TPM.", e);
    }
}

#[test]
fn activate_credential_wrong_data() {
    let mut ctx = create_ctx();
    let params = KeyParams::Ecc {
        curve: EccCurve::NistP256,
        scheme: EccScheme::create(
            EccSchemeAlgorithm::EcDsa,
            Some(HashingAlgorithm::Sha256),
            None,
        )
        .expect("Failed to create ecc scheme"),
    };
    // "Good" key (for which the credential will be generated)
    let (material, auth) = ctx.create_key(params, 16).unwrap();
    let obj = ObjectWrapper {
        material,
        params,
        auth,
    };

    // No data (essentially wrong size)
    let e = ctx
        .activate_credential(obj.clone(), None, vec![], vec![])
        .unwrap_err();
    if let Error::Tss2Error(e) = e {
        assert_eq!(e.kind(), Some(Tss2ResponseCodeKind::Size));
    } else {
        panic!("Got crate error ({}) when expecting an error from TPM.", e);
    }

    // Correct size but gibberish
    let e = ctx
        .activate_credential(obj, None, vec![0xaa; 52], vec![0x55; 256])
        .unwrap_err();
    if let Error::Tss2Error(e) = e {
        // IBM software TPM returns Value, swtpm returns Failure...
        assert!(matches!(
            e.kind(),
            Some(Tss2ResponseCodeKind::Value) | Some(Tss2ResponseCodeKind::Failure)
        ));
    } else {
        panic!("Got crate error ({}) when expecting an error from TPM.", e);
    }
}

#[test]
fn get_random_from_tkc() {
    // Check that we can convert a reference from TKC to Context
    let mut ctx = create_ctx();
    let _rand_bytes = ctx
        .as_mut()
        .execute_without_session(|ctx| ctx.get_random(16))
        .expect("Failed to get random bytes");
}
