use anyhow::Context as _;

use cryptoki::{
    object::{
        Attribute,
        KeyType,
        MlDsaParameterSetType,
        MlKemParameterSetType,
        ObjectClass,
        SlhDsaParameterSetType,
    },
    session::Session,
};

use sequoia_openpgp::{
    crypto,
    Profile,
    packet,
    packet::key,
    types::Curve,
    types::HashAlgorithm,
    types::SymmetricAlgorithm,
};

use crate::Context;
use crate::Key;
use crate::PublicKeyAlgorithm;
use crate::Result;
use crate::Token;
use crate::keyid::Bundling;

/// Whether a key is for signing or encryption.
///
/// This is used with [`Context::import`].
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum KeyCapability {
    Signing,
    Encryption,
}

const TRACE: bool = false;

/// Key import.
impl Context {
    /// Import a key to the specified token.
    ///
    /// The key must already be unencrypted.  (See
    /// [Key::decrypt_secret].)
    ///
    /// [Key::decrypt_secret]: https://docs.rs/sequoia-openpgp/latest/sequoia_openpgp/packet/key/enum.Key.html#method.decrypt_secret
    pub fn import(&self,
                  session: &Session,
                  token: Token,
                  key_label: Option<&str>,
                  key: &packet::Key<key::SecretParts,
                                    key::UnspecifiedRole>,
                  capability: KeyCapability)
                  -> Result<Key>
    {
        tracer!(TRACE, "Context::import");

        let skm = match key.secret() {
            key::SecretKeyMaterial::Unencrypted(skm) => skm,
            key::SecretKeyMaterial::Encrypted(_skm) => {
                // XXX: Prompt for the password, if not provided.

                // skm.decrypt(&key, password)

                return Err(anyhow::anyhow!(
                    "Can't import {}, it is password protected",
                    key.fingerprint()));
            }
        };

        let profile = match key.version() {
            4 => Profile::RFC4880,
            6 => Profile::RFC9580,
            version => {
                return Err(anyhow::anyhow!(
                    "v{} keys are not supported", version));
            }
        };

	let algo = PublicKeyAlgorithm::try_from(key)?;
        t!("Importing a {} ({}) key", key.pk_algo(), u8::from(key.pk_algo()));

        let (public_handle, constituent) = skm.map(|skm| match (key.mpis(), skm) {
            (crypto::mpi::PublicKey::ECDH { q, hash, sym, .. },
             crypto::mpi::SecretKeyMaterial::ECDH { scalar }) =>
            {
                if capability != KeyCapability::Encryption {
                    return Err(anyhow::anyhow!(
                        "ECDH keys cannot be used for signing"));
                }

                // Import a v4 ECDH secret key.

                // Version 4 ECDH public keys include two parameters:
                // a hash algorithm and a symmetric algorithm.  We
                // need to add those to the key ID so that we can
                // recreate the OpenPGP key.
                let key_id_extra = {
                    let hash_algo = match hash {
                        HashAlgorithm::SHA256 => "sha256",
                        HashAlgorithm::SHA384 => "sha384",
                        HashAlgorithm::SHA512 => "sha512",
                        algo => {
                            return Err(anyhow::anyhow!(
                                "{} cannot be used with a cv25519 key", algo));
                        }
                    };
                    let sym_algo = match sym {
                        SymmetricAlgorithm::AES128 => "aes128",
                        SymmetricAlgorithm::AES192 => "aes192",
                        SymmetricAlgorithm::AES256 => "aes256",
                        algo => {
                            return Err(anyhow::anyhow!(
                                "{} cannot be used with a cv25519 key", algo));
                        }
                    };

                    Some(format!("{}-{}", hash_algo, sym_algo))
                };

                let (key_id, key_label) = self.generate_key_id(
                    algo.clone(), Profile::RFC4880, Bundling::Standalone,
                    Some(key.creation_time()),
                    key_id_extra.as_deref(), key_label)?;

                // Note: the secret key material is stored in big
                // endian form, but PKCS#11 expects it to be in little
                // endian form:
                //
                //   5.5.5.6.1.1. Curve25519Legacy ECDH Secret Key
                //   Material (Deprecated)
                //
                //   A Curve25519Legacy secret key is stored as a
                //   standard integer in big-endian MPI
                //   form. Curve25519Legacy MUST NOT be used in key
                //   packets version 6 or above. Note that this form
                //   is in reverse octet order from the little-endian
                //   "native" form found in [RFC7748].
                //
                //   https://www.rfc-editor.org/rfc/rfc9580.html#section-5.5.5.6.1
                let mut scalar = scalar.value().to_vec();
                scalar.reverse();

                let template = [
                    Attribute::Class(ObjectClass::PRIVATE_KEY),
                    Attribute::KeyType(KeyType::EC_MONTGOMERY),
                    Attribute::EcParams(vec![
                        0x13, 0x0a, 0x63, 0x75, 0x72, 0x76, 0x65, 0x32,
                        0x35, 0x35, 0x31, 0x39
                    ]),
                    Attribute::Value(scalar),
                    Attribute::Id(key_id.as_bytes().to_vec()),
                    Attribute::Label(key_label.as_bytes().to_vec()),
                    // Prohibit exporting the secret key material.
                    Attribute::Sensitive(true),
                    // Persist it on the token.
                    Attribute::Token(true),
                    // Decryption is done by deriving a KEK from a
                    // shared secret.  For PKCS#11, this means we use
                    // the CKM_ECDH1_DERIVE mechanism.
                    Attribute::Derive(true),
                ];

                let _priv_handle = session.create_object(&template)?;

                // Extract the public key.  In OpenPGP it is encoded
                // as follows:
                //
                //   11.2.2. Prefixed Native EC Point Wire Format
                //
                //   For a custom compressed point, the content of the
                //   MPI is:
                //
                //     B = 40 || p
                //
                //   where p is the public key of the point encoded
                //   using the rules defined for the specified
                //   curve. This format is used for ECDH keys based on
                //   curves expressed in Montgomery form and for
                //   points when using EdDSA.
                //
                // https://www.rfc-editor.org/rfc/rfc9580.html#ec-point-prefixed-native
                let ec_point = q.value();
                if ec_point.len() != 33 || ec_point[0] != 0x40 {
                    return Err(anyhow::anyhow!(
                        "Invalid public key material (should be 33 bytes \
                         where the first byte is 0x40)"));
                }
                let ec_point = ec_point[1..].to_vec();

                let template = [
                    Attribute::Class(ObjectClass::PUBLIC_KEY),
                    Attribute::KeyType(KeyType::EC_MONTGOMERY),
                    Attribute::EcParams(crate::oids::X25519.into()),
                    Attribute::EcPoint(ec_point),
                    Attribute::Id(key_id.as_bytes().to_vec()),
                    Attribute::Label(key_label.as_bytes().to_vec()),
                    // Persist it on the token.
                    Attribute::Token(true),
                    Attribute::Derive(true),
                ];

                let public_handle = session.create_object(&template)?;

                Ok((public_handle, None))
            }
            (crypto::mpi::PublicKey::MLKEM768_X25519 { ecdh, mlkem, },
             crypto::mpi::SecretKeyMaterial::MLKEM768_X25519 {
                 ecdh: ecdh_secret, mlkem: mlkem_seed,
             }) => {
                let (key_id, key_label) = self.generate_key_id(
                    PublicKeyAlgorithm::MLKEM768_X25519,
                    profile, Bundling::PostQuantumHalf,
                    Some(key.creation_time()),
                    None, key_label)?;

                let template = [
                    Attribute::Class(ObjectClass::PRIVATE_KEY),
                    Attribute::KeyType(KeyType::ML_KEM),
                    Attribute::ParameterSet(
                        MlKemParameterSetType::ML_KEM_768.into()),
                    Attribute::Seed(mlkem_seed.to_vec()),
                    Attribute::Id(key_id.as_bytes().to_vec()),
                    Attribute::Label(key_label.as_bytes().to_vec()),
                    // Prohibit exporting the secret key material.
                    Attribute::Sensitive(true),
                    // Persist it on the token.
                    Attribute::Token(true),
                    Attribute::Decapsulate(true),
                ];

                let _priv_handle = session.create_object(&template)
                    .context("Importing ML-KEM private key (perhaps the \
                              token doesn't support ML-KEM keys, or importing \
                              by seed is not supported)")?;

                let template = [
                    Attribute::Class(ObjectClass::PUBLIC_KEY),
                    Attribute::KeyType(KeyType::ML_KEM),
                    Attribute::ParameterSet(
                        MlKemParameterSetType::ML_KEM_768.into()),
                    Attribute::Value(mlkem.to_vec()),
                    Attribute::Id(key_id.as_bytes().to_vec()),
                    Attribute::Label(key_label.as_bytes().to_vec()),
                    // Persist it on the token.
                    Attribute::Token(true),
                    Attribute::Encapsulate(true),
                ];

                let public_handle = session.create_object(&template)
                    .context("Importing ML-KEM public key")?;

                let constituent = Key::from_object(
                    token.clone(), session, public_handle)?;

                let key_label = constituent.key_label().map(|label| {
                    String::from_utf8(label.to_vec())
                        .expect("label is valid UTF-8")
                });

                let (key_id, key_label) = self.generate_key_id(
                    PublicKeyAlgorithm::MLKEM768_X25519,
                    profile, Bundling::TraditionalHalf,
                    Some(key.creation_time()),
                    None, key_label.as_deref())?;

                let template = [
                    Attribute::Class(ObjectClass::PRIVATE_KEY),
                    Attribute::KeyType(KeyType::EC_MONTGOMERY),
                    Attribute::EcParams(crate::oids::X25519.into()),
                    Attribute::Value(ecdh_secret.to_vec()),
                    Attribute::Id(key_id.as_bytes().to_vec()),
                    Attribute::Label(key_label.as_bytes().to_vec()),
                    // Prohibit exporting the secret key material.
                    Attribute::Sensitive(true),
                    // Persist it on the token.
                    Attribute::Token(true),
                    Attribute::Derive(true),
                ];

                let _priv_handle2 = session.create_object(&template)
                    .context("Importing X25519 private key")?;

                let template = [
                    Attribute::Class(ObjectClass::PUBLIC_KEY),
                    Attribute::KeyType(KeyType::EC_MONTGOMERY),
                    Attribute::Id(key_id.as_bytes().to_vec()),
                    Attribute::EcParams(crate::oids::X25519.into()),
                    Attribute::EcPoint(ecdh.to_vec()),
                    Attribute::Label(key_label.as_bytes().to_vec()),
                    // Persist it on the token.
                    Attribute::Token(true),
                    Attribute::Derive(true),
                ];

                let public_handle2 = session.create_object(&template)
                    .context("Importing X25519 public key")?;

                Ok((public_handle2, Some(constituent)))
            }
            (crypto::mpi::PublicKey::MLKEM1024_X448 { ecdh, mlkem, },
             crypto::mpi::SecretKeyMaterial::MLKEM1024_X448 {
                 ecdh: ecdh_secret, mlkem: mlkem_seed,
             }) => {
                let (key_id, key_label) = self.generate_key_id(
                    PublicKeyAlgorithm::MLKEM1024_X448,
                    profile, Bundling::PostQuantumHalf,
                    Some(key.creation_time()),
                    None, key_label)?;

                let template = [
                    Attribute::Class(ObjectClass::PRIVATE_KEY),
                    Attribute::KeyType(KeyType::ML_KEM),
                    Attribute::ParameterSet(
                        MlKemParameterSetType::ML_KEM_1024.into()),
                    Attribute::Seed(mlkem_seed.to_vec()),
                    Attribute::Id(key_id.as_bytes().to_vec()),
                    Attribute::Label(key_label.as_bytes().to_vec()),
                    // Prohibit exporting the secret key material.
                    Attribute::Sensitive(true),
                    // Persist it on the token.
                    Attribute::Token(true),
                    Attribute::Decapsulate(true),
                ];

                let _priv_handle = session.create_object(&template)
                    .context("Importing ML-KEM private key (perhaps the \
                              token doesn't support ML-KEM keys, or importing \
                              by seed is not supported)")?;

                let template = [
                    Attribute::Class(ObjectClass::PUBLIC_KEY),
                    Attribute::KeyType(KeyType::ML_KEM),
                    Attribute::ParameterSet(
                        MlKemParameterSetType::ML_KEM_1024.into()),
                    Attribute::Value(mlkem.to_vec()),
                    Attribute::Id(key_id.as_bytes().to_vec()),
                    Attribute::Label(key_label.as_bytes().to_vec()),
                    // Persist it on the token.
                    Attribute::Token(true),
                    Attribute::Encapsulate(true),
                ];

                let public_handle = session.create_object(&template)
                    .context("Importing ML-KEM public key")?;

                let constituent = Key::from_object(
                    token.clone(), session, public_handle)?;

                let key_label = constituent.key_label().map(|label| {
                    String::from_utf8(label.to_vec())
                        .expect("label is valid UTF-8")
                });

                let (key_id, key_label) = self.generate_key_id(
                    PublicKeyAlgorithm::MLKEM1024_X448,
                    profile, Bundling::TraditionalHalf,
                    Some(key.creation_time()),
                    None, key_label.as_deref())?;

                let template = [
                    Attribute::Class(ObjectClass::PRIVATE_KEY),
                    Attribute::KeyType(KeyType::EC_MONTGOMERY),
                    Attribute::EcParams(crate::oids::X448.into()),
                    Attribute::Value(ecdh_secret.to_vec()),
                    Attribute::Id(key_id.as_bytes().to_vec()),
                    Attribute::Label(key_label.as_bytes().to_vec()),
                    // Prohibit exporting the secret key material.
                    Attribute::Sensitive(true),
                    // Persist it on the token.
                    Attribute::Token(true),
                    Attribute::Derive(true),
                ];

                let _priv_handle2 = session.create_object(&template)
                    .context("Importing X448 private key")?;

                let template = [
                    Attribute::Class(ObjectClass::PUBLIC_KEY),
                    Attribute::KeyType(KeyType::EC_MONTGOMERY),
                    Attribute::Id(key_id.as_bytes().to_vec()),
                    Attribute::EcParams(crate::oids::X448.into()),
                    Attribute::EcPoint(ecdh.to_vec()),
                    Attribute::Label(key_label.as_bytes().to_vec()),
                    // Persist it on the token.
                    Attribute::Token(true),
                    Attribute::Derive(true),
                ];

                let public_handle2 = session.create_object(&template)
                    .context("Importing X448 public key")?;

                Ok((public_handle2, Some(constituent)))
            }
            (crypto::mpi::PublicKey::X25519 { u },
             crypto::mpi::SecretKeyMaterial::X25519 { x }) =>
            {
                let (key_id, key_label) = self.generate_key_id(
                    PublicKeyAlgorithm::X25519,
                    profile, Bundling::Standalone,
                    Some(key.creation_time()),
                    None, key_label)?;

                let template = [
                    Attribute::Class(ObjectClass::PRIVATE_KEY),
                    Attribute::KeyType(KeyType::EC_MONTGOMERY),
                    Attribute::EcParams(crate::oids::X25519.into()),
                    Attribute::Value(x.to_vec()),
                    Attribute::Id(key_id.as_bytes().to_vec()),
                    Attribute::Label(key_label.as_bytes().to_vec()),
                    // Prohibit exporting the secret key material.
                    Attribute::Sensitive(true),
                    // Persist it on the token.
                    Attribute::Token(true),
                    Attribute::Derive(true),
                ];

                let _priv_handle = session.create_object(&template)?;

                let template = [
                    Attribute::Class(ObjectClass::PUBLIC_KEY),
                    Attribute::KeyType(KeyType::EC_MONTGOMERY),
                    Attribute::Id(key_id.as_bytes().to_vec()),
                    Attribute::EcParams(crate::oids::X25519.into()),
                    Attribute::EcPoint(u.to_vec()),
                    Attribute::Label(key_label.as_bytes().to_vec()),
                    // Persist it on the token.
                    Attribute::Token(true),
                    Attribute::Derive(true),
                ];

                let public_handle = session.create_object(&template)?;

                Ok((public_handle, None))
            }
            (crypto::mpi::PublicKey::X448 { u },
             crypto::mpi::SecretKeyMaterial::X448 { x }) =>
            {
                let (key_id, key_label) = self.generate_key_id(
                    PublicKeyAlgorithm::X448,
                    profile, Bundling::Standalone,
                    Some(key.creation_time()),
                    None, key_label)?;

                let template = [
                    Attribute::Class(ObjectClass::PRIVATE_KEY),
                    Attribute::KeyType(KeyType::EC_MONTGOMERY),
                    Attribute::EcParams(crate::oids::X448.into()),
                    Attribute::Value(x.to_vec()),
                    Attribute::Id(key_id.as_bytes().to_vec()),
                    Attribute::Label(key_label.as_bytes().to_vec()),
                    // Prohibit exporting the secret key material.
                    Attribute::Sensitive(true),
                    // Persist it on the token.
                    Attribute::Token(true),
                    Attribute::Derive(true),
                ];

                let _priv_handle = session.create_object(&template)?;

                let template = [
                    Attribute::Class(ObjectClass::PUBLIC_KEY),
                    Attribute::KeyType(KeyType::EC_MONTGOMERY),
                    Attribute::Id(key_id.as_bytes().to_vec()),
                    Attribute::EcParams(crate::oids::X448.into()),
                    Attribute::EcPoint(u.to_vec()),
                    Attribute::Label(key_label.as_bytes().to_vec()),
                    // Persist it on the token.
                    Attribute::Token(true),
                    Attribute::Derive(true),
                ];

                let public_handle = session.create_object(&template)?;

                Ok((public_handle, None))
            }
            (crypto::mpi::PublicKey::EdDSA { curve, q },
             crypto::mpi::SecretKeyMaterial::EdDSA { scalar }) =>
            {
                t!("Key uses curve {}", curve);

                if capability != KeyCapability::Signing {
                    return Err(anyhow::anyhow!(
                        "EdDSA keys cannot be used for encryption"));
                }

                let (key_id, key_label) = self.generate_key_id(
                    PublicKeyAlgorithm::EdDSA,
                    Profile::RFC4880, Bundling::Standalone,
                    Some(key.creation_time()),
                    None, key_label)?;

                // Section 5.5.5.4. Algorithm-Specific Part for ECDSA Keys
                //
                // For ECDSA keys, the public key consists of this
                // series of values:
                //
                // - A variable-length field containing a curve OID,
                //   which is formatted as follows:
                //
                //   - A 1-octet size of the following field; values 0
                //     and 0xFF are reserved for future extensions.
                //
                //   - The octets representing a curve OID, as defined
                //     in Section 9.2.
                //
                // - An MPI of an EC point representing a public key.
                //
                // The secret key consists of this single
                // multiprecision integer:
                //
                // - An MPI of an integer representing the secret key,
                //   which is a scalar of the public EC point.
                //
                // https://www.rfc-editor.org/rfc/rfc9580.html#name-algorithm-specific-part-for-ec

                match curve {
                    Curve::Ed25519 => (),
                    curve => {
                        return Err(anyhow::anyhow!(
                            "{} keys using curve {} are not yet supported",
                            key.pk_algo(), curve));
                    }
                }

                let template = [
                    Attribute::Class(ObjectClass::PRIVATE_KEY),
                    Attribute::KeyType(KeyType::EC_EDWARDS),
                    Attribute::Id(key_id.as_bytes().to_vec()),
                    Attribute::Label(key_label.as_bytes().to_vec()),
                    // Prohibit exporting the secret key material.
                    Attribute::Sensitive(true),
                    // Persist it on the token.
                    Attribute::Token(true),
                    Attribute::Sign(true),
                    Attribute::EcParams(crate::oids::Ed25519.to_vec()),
                    Attribute::Value(scalar.value().to_vec()),
                ];

                let _priv_handle = session.create_object(&template)?;

                // 11.2.2. Prefixed Native EC Point Wire Format
                //
                // For a custom compressed point, the content of the
                // MPI is:
                //
                //   B = 40 || p
                //
                // where p is the public key of the point encoded
                // using the rules defined for the specified
                // curve. This format is used for ECDH keys based on
                // curves expressed in Montgomery form and for points
                // when using EdDSA.
                //
                // https://www.rfc-editor.org/rfc/rfc9580.html#section-11.2.2

                let ec_point = q.value();
                if ec_point.len() != 33 || ec_point[0] != 0x40 {
                    return Err(anyhow::anyhow!(
                        "Invalid public key material (should be 33 bytes \
                         where the first byte is 0x40)"));
                }
                let ec_point = ec_point[1..].to_vec();

                let template = [
                    Attribute::Class(ObjectClass::PUBLIC_KEY),
                    Attribute::KeyType(KeyType::EC_EDWARDS),
                    Attribute::Id(key_id.as_bytes().to_vec()),
                    Attribute::Label(key_label.as_bytes().to_vec()),
                    // Persist it on the token.
                    Attribute::Token(true),
                    Attribute::Verify(true),
                    Attribute::EcParams(crate::oids::Ed25519.into()),
                    Attribute::EcPoint(ec_point),
                ];

                let public_handle = session.create_object(&template)?;

                Ok((public_handle, None))
            }
            (crypto::mpi::PublicKey::Ed25519 { a },
             crypto::mpi::SecretKeyMaterial::Ed25519 { x }) =>
            {
                let (key_id, key_label) = self.generate_key_id(
                    PublicKeyAlgorithm::Ed25519,
                    profile, Bundling::Standalone,
                    Some(key.creation_time()),
                    None, key_label)?;

                let template = [
                    Attribute::Class(ObjectClass::PRIVATE_KEY),
                    Attribute::KeyType(KeyType::EC_EDWARDS),
                    Attribute::EcParams(crate::oids::Ed25519.into()),
                    Attribute::Value(x.to_vec()),
                    Attribute::Id(key_id.as_bytes().to_vec()),
                    Attribute::Label(key_label.as_bytes().to_vec()),
                    // Prohibit exporting the secret key material.
                    Attribute::Sensitive(true),
                    // Persist it on the token.
                    Attribute::Token(true),
                    Attribute::Sign(true),
                ];

                let _priv_handle = session.create_object(&template)?;

                let template = [
                    Attribute::Class(ObjectClass::PUBLIC_KEY),
                    Attribute::KeyType(KeyType::EC_EDWARDS),
                    Attribute::Id(key_id.as_bytes().to_vec()),
                    Attribute::EcParams(crate::oids::Ed25519.into()),
                    Attribute::EcPoint(a.to_vec()),
                    Attribute::Label(key_label.as_bytes().to_vec()),
                    // Persist it on the token.
                    Attribute::Token(true),
                    Attribute::Verify(true),
                ];

                let public_handle = session.create_object(&template)?;

                Ok((public_handle, None))
            }
            (crypto::mpi::PublicKey::Ed448 { a },
             crypto::mpi::SecretKeyMaterial::Ed448 { x }) =>
            {
                let (key_id, key_label) = self.generate_key_id(
                    PublicKeyAlgorithm::Ed448,
                    profile, Bundling::Standalone,
                    Some(key.creation_time()),
                    None, key_label)?;

                let template = [
                    Attribute::Class(ObjectClass::PRIVATE_KEY),
                    Attribute::KeyType(KeyType::EC_EDWARDS),
                    Attribute::EcParams(crate::oids::Ed448.into()),
                    Attribute::Value(x.to_vec()),
                    Attribute::Id(key_id.as_bytes().to_vec()),
                    Attribute::Label(key_label.as_bytes().to_vec()),
                    // Prohibit exporting the secret key material.
                    Attribute::Sensitive(true),
                    // Persist it on the token.
                    Attribute::Token(true),
                    Attribute::Sign(true),
                ];

                let _priv_handle = session.create_object(&template)?;

                let template = [
                    Attribute::Class(ObjectClass::PUBLIC_KEY),
                    Attribute::KeyType(KeyType::EC_EDWARDS),
                    Attribute::Id(key_id.as_bytes().to_vec()),
                    Attribute::EcParams(crate::oids::Ed448.into()),
                    Attribute::EcPoint(a.to_vec()),
                    Attribute::Label(key_label.as_bytes().to_vec()),
                    // Persist it on the token.
                    Attribute::Token(true),
                    Attribute::Verify(true),
                ];

                let public_handle = session.create_object(&template)?;

                Ok((public_handle, None))
            }
            (crypto::mpi::PublicKey::MLDSA65_Ed25519 { eddsa, mldsa },
             crypto::mpi::SecretKeyMaterial::MLDSA65_Ed25519 {
                 eddsa: eddsa_secret,
                 mldsa: mldsa_seed,
             }) =>
            {
                let (key_id, key_label) = self.generate_key_id(
                    PublicKeyAlgorithm::MLDSA65_Ed25519,
                    profile, Bundling::PostQuantumHalf,
                    Some(key.creation_time()),
                    None, key_label)?;

                let template = [
                    Attribute::Class(ObjectClass::PRIVATE_KEY),
                    Attribute::KeyType(KeyType::ML_DSA),
                    Attribute::ParameterSet(
                        MlDsaParameterSetType::ML_DSA_65.into()),
                    Attribute::Seed(mldsa_seed.to_vec()),
                    Attribute::Id(key_id.as_bytes().to_vec()),
                    Attribute::Label(key_label.as_bytes().to_vec()),
                    // Prohibit exporting the secret key material.
                    Attribute::Sensitive(true),
                    // Persist it on the token.
                    Attribute::Token(true),
                    Attribute::Sign(true),
                ];

                let _priv_handle = session.create_object(&template)
                    .context("Importing ML-DSA private key (perhaps the \
                              token doesn't support ML-DSA keys, or importing \
                              by seed is not supported)")?;

                let template = [
                    Attribute::Class(ObjectClass::PUBLIC_KEY),
                    Attribute::KeyType(KeyType::ML_DSA),
                    Attribute::ParameterSet(
                        MlDsaParameterSetType::ML_DSA_65.into()),
                    Attribute::Value(mldsa.to_vec()),
                    Attribute::Id(key_id.as_bytes().to_vec()),
                    Attribute::Label(key_label.as_bytes().to_vec()),
                    // Persist it on the token.
                    Attribute::Token(true),
                    Attribute::Verify(true),
                ];

                let public_handle = session.create_object(&template)
                    .context("Importing ML-DSA public key")?;

                let constituent = Key::from_object(
                    token.clone(), session, public_handle)?;

                let key_label = constituent.key_label().map(|label| {
                    String::from_utf8(label.to_vec())
                        .expect("label is valid UTF-8")
                });

                let (key_id, key_label) = self.generate_key_id(
                    PublicKeyAlgorithm::MLDSA65_Ed25519,
                    profile, Bundling::TraditionalHalf,
                    Some(key.creation_time()),
                    None, key_label.as_deref())?;

                let template = [
                    Attribute::Class(ObjectClass::PRIVATE_KEY),
                    Attribute::KeyType(KeyType::EC_EDWARDS),
                    Attribute::EcParams(crate::oids::Ed25519.into()),
                    Attribute::Value(eddsa_secret.to_vec()),
                    Attribute::Id(key_id.as_bytes().to_vec()),
                    Attribute::Label(key_label.as_bytes().to_vec()),
                    // Prohibit exporting the secret key material.
                    Attribute::Sensitive(true),
                    // Persist it on the token.
                    Attribute::Token(true),
                    Attribute::Sign(true),
                ];

                let _priv_handle2 = session.create_object(&template)
                    .context("Importing Ed25519 public key")?;

                let template = [
                    Attribute::Class(ObjectClass::PUBLIC_KEY),
                    Attribute::KeyType(KeyType::EC_EDWARDS),
                    Attribute::Id(key_id.as_bytes().to_vec()),
                    Attribute::EcParams(crate::oids::Ed25519.into()),
                    Attribute::EcPoint(eddsa.to_vec()),
                    Attribute::Label(key_label.as_bytes().to_vec()),
                    // Persist it on the token.
                    Attribute::Token(true),
                    Attribute::Verify(true),
                ];

                let public_handle2 = session.create_object(&template)
                    .context("Importing Ed25519 public key")?;

                Ok((public_handle2, Some(constituent)))
            }
            (crypto::mpi::PublicKey::MLDSA87_Ed448 { eddsa, mldsa },
             crypto::mpi::SecretKeyMaterial::MLDSA87_Ed448 {
                 eddsa: eddsa_secret,
                 mldsa: mldsa_seed,
             }) =>
            {
                let (key_id, key_label) = self.generate_key_id(
                    PublicKeyAlgorithm::MLDSA87_Ed448,
                    profile, Bundling::PostQuantumHalf,
                    Some(key.creation_time()),
                    None, key_label)?;

                let template = [
                    Attribute::Class(ObjectClass::PRIVATE_KEY),
                    Attribute::KeyType(KeyType::ML_DSA),
                    Attribute::ParameterSet(
                        MlDsaParameterSetType::ML_DSA_87.into()),
                    Attribute::Seed(mldsa_seed.to_vec()),
                    Attribute::Id(key_id.as_bytes().to_vec()),
                    Attribute::Label(key_label.as_bytes().to_vec()),
                    // Prohibit exporting the secret key material.
                    Attribute::Sensitive(true),
                    // Persist it on the token.
                    Attribute::Token(true),
                    Attribute::Sign(true),
                ];

                let _priv_handle = session.create_object(&template)
                    .context("Importing ML-DSA private key (perhaps the \
                              token doesn't support ML-DSA keys, or importing \
                              by seed is not supported)")?;

                let template = [
                    Attribute::Class(ObjectClass::PUBLIC_KEY),
                    Attribute::KeyType(KeyType::ML_DSA),
                    Attribute::ParameterSet(
                        MlDsaParameterSetType::ML_DSA_87.into()),
                    Attribute::Value(mldsa.to_vec()),
                    Attribute::Id(key_id.as_bytes().to_vec()),
                    Attribute::Label(key_label.as_bytes().to_vec()),
                    // Persist it on the token.
                    Attribute::Token(true),
                    Attribute::Verify(true),
                ];

                let public_handle = session.create_object(&template)
                    .context("Importing ML-DSA public key")?;

                let constituent = Key::from_object(
                    token.clone(), session, public_handle)?;

                let key_label = constituent.key_label().map(|label| {
                    String::from_utf8(label.to_vec())
                        .expect("label is valid UTF-8")
                });

                let (key_id, key_label) = self.generate_key_id(
                    PublicKeyAlgorithm::MLDSA87_Ed448,
                    profile, Bundling::TraditionalHalf,
                    Some(key.creation_time()),
                    None, key_label.as_deref())?;

                let template = [
                    Attribute::Class(ObjectClass::PRIVATE_KEY),
                    Attribute::KeyType(KeyType::EC_EDWARDS),
                    Attribute::EcParams(crate::oids::Ed448.into()),
                    Attribute::Value(eddsa_secret.to_vec()),
                    Attribute::Id(key_id.as_bytes().to_vec()),
                    Attribute::Label(key_label.as_bytes().to_vec()),
                    // Prohibit exporting the secret key material.
                    Attribute::Sensitive(true),
                    // Persist it on the token.
                    Attribute::Token(true),
                    Attribute::Sign(true),
                ];

                let _priv_handle2 = session.create_object(&template)
                    .context("Importing Ed448 private key")?;

                let template = [
                    Attribute::Class(ObjectClass::PUBLIC_KEY),
                    Attribute::KeyType(KeyType::EC_EDWARDS),
                    Attribute::Id(key_id.as_bytes().to_vec()),
                    Attribute::EcParams(crate::oids::Ed448.into()),
                    Attribute::EcPoint(eddsa.to_vec()),
                    Attribute::Label(key_label.as_bytes().to_vec()),
                    // Persist it on the token.
                    Attribute::Token(true),
                    Attribute::Verify(true),
                ];

                let public_handle2 = session.create_object(&template)
                    .context("Importing Ed448 public key")?;

                Ok((public_handle2, Some(constituent)))
            }
            (crypto::mpi::PublicKey::SLHDSA128s { public },
             crypto::mpi::SecretKeyMaterial::SLHDSA128s { secret }) =>
            {
                let (key_id, key_label) = self.generate_key_id(
                    PublicKeyAlgorithm::SLHDSA128s,
                    profile, Bundling::Standalone,
                    Some(key.creation_time()),
                    None, key_label)?;

                let template = [
                    Attribute::Class(ObjectClass::PRIVATE_KEY),
                    Attribute::KeyType(KeyType::SLH_DSA),
                    Attribute::ParameterSet(
                        SlhDsaParameterSetType::SHAKE_128S.into()),
                    Attribute::Value(secret.to_vec()),
                    Attribute::Id(key_id.as_bytes().to_vec()),
                    Attribute::Label(key_label.as_bytes().to_vec()),
                    // Prohibit exporting the secret key material.
                    Attribute::Sensitive(true),
                    // Persist it on the token.
                    Attribute::Token(true),
                    Attribute::Sign(true),
                ];

                let _priv_handle = session.create_object(&template)?;

                let template = [
                    Attribute::Class(ObjectClass::PUBLIC_KEY),
                    Attribute::KeyType(KeyType::SLH_DSA),
                    Attribute::ParameterSet(
                        SlhDsaParameterSetType::SHAKE_128S.into()),
                    Attribute::Value(public.to_vec()),
                    Attribute::Id(key_id.as_bytes().to_vec()),
                    Attribute::Label(key_label.as_bytes().to_vec()),
                    // Persist it on the token.
                    Attribute::Token(true),
                    Attribute::Verify(true),
                ];

                let public_handle = session.create_object(&template)?;

                Ok((public_handle, None))
            }
            (crypto::mpi::PublicKey::SLHDSA128f { public },
             crypto::mpi::SecretKeyMaterial::SLHDSA128f { secret }) =>
            {
                let (key_id, key_label) = self.generate_key_id(
                    PublicKeyAlgorithm::SLHDSA128f,
                    profile, Bundling::Standalone,
                    Some(key.creation_time()),
                    None, key_label)?;

                let template = [
                    Attribute::Class(ObjectClass::PRIVATE_KEY),
                    Attribute::KeyType(KeyType::SLH_DSA),
                    Attribute::ParameterSet(
                        SlhDsaParameterSetType::SHAKE_128F.into()),
                    Attribute::Value(secret.to_vec()),
                    Attribute::Id(key_id.as_bytes().to_vec()),
                    Attribute::Label(key_label.as_bytes().to_vec()),
                    // Prohibit exporting the secret key material.
                    Attribute::Sensitive(true),
                    // Persist it on the token.
                    Attribute::Token(true),
                    Attribute::Sign(true),
                ];

                let _priv_handle = session.create_object(&template)?;

                let template = [
                    Attribute::Class(ObjectClass::PUBLIC_KEY),
                    Attribute::KeyType(KeyType::SLH_DSA),
                    Attribute::ParameterSet(
                        SlhDsaParameterSetType::SHAKE_128F.into()),
                    Attribute::Value(public.to_vec()),
                    Attribute::Id(key_id.as_bytes().to_vec()),
                    Attribute::Label(key_label.as_bytes().to_vec()),
                    // Persist it on the token.
                    Attribute::Token(true),
                    Attribute::Verify(true),
                ];

                let public_handle = session.create_object(&template)?;

                Ok((public_handle, None))
            }
            (crypto::mpi::PublicKey::SLHDSA256s { public },
             crypto::mpi::SecretKeyMaterial::SLHDSA256s { secret }) =>
            {
                let (key_id, key_label) = self.generate_key_id(
                    PublicKeyAlgorithm::SLHDSA256s,
                    profile, Bundling::Standalone,
                    Some(key.creation_time()),
                    None, key_label)?;

                let template = [
                    Attribute::Class(ObjectClass::PRIVATE_KEY),
                    Attribute::KeyType(KeyType::SLH_DSA),
                    Attribute::ParameterSet(
                        SlhDsaParameterSetType::SHAKE_256S.into()),
                    Attribute::Value(secret.to_vec()),
                    Attribute::Id(key_id.as_bytes().to_vec()),
                    Attribute::Label(key_label.as_bytes().to_vec()),
                    // Prohibit exporting the secret key material.
                    Attribute::Sensitive(true),
                    // Persist it on the token.
                    Attribute::Token(true),
                    Attribute::Sign(true),
                ];

                let _priv_handle = session.create_object(&template)?;

                let template = [
                    Attribute::Class(ObjectClass::PUBLIC_KEY),
                    Attribute::KeyType(KeyType::SLH_DSA),
                    Attribute::ParameterSet(
                        SlhDsaParameterSetType::SHAKE_256S.into()),
                    Attribute::Value(public.to_vec()),
                    Attribute::Id(key_id.as_bytes().to_vec()),
                    Attribute::Label(key_label.as_bytes().to_vec()),
                    // Persist it on the token.
                    Attribute::Token(true),
                    Attribute::Verify(true),
                ];

                let public_handle = session.create_object(&template)?;

                Ok((public_handle, None))
            }
            (crypto::mpi::PublicKey::RSA { e, n },
             crypto::mpi::SecretKeyMaterial::RSA { d, p, q, u }) =>
            {
                let (key_id, key_label) = self.generate_key_id(
		    PublicKeyAlgorithm::RSA,
                    profile, Bundling::Standalone,
                    Some(key.creation_time()),
                    None, key_label)?;

                let template = [
                    Attribute::Class(ObjectClass::PRIVATE_KEY),
                    Attribute::KeyType(KeyType::RSA),
                    Attribute::Id(key_id.as_bytes().to_vec()),
                    Attribute::Label(key_label.as_bytes().to_vec()),
                    // Prohibit exporting the secret key material.
                    Attribute::Sensitive(true),
                    // Persist it on the token.
                    Attribute::Token(true),
                    if capability == KeyCapability::Signing {
                        Attribute::Sign(true)
                    } else {
                        Attribute::Decrypt(true)
                    },
                    Attribute::Modulus(n.value().to_vec()),
                    Attribute::PublicExponent(e.value().to_vec()),
                    Attribute::PrivateExponent(d.value().to_vec()),
                    Attribute::Prime1(p.value().to_vec()),
                    Attribute::Prime2(q.value().to_vec()),
                    Attribute::Coefficient(u.value().to_vec()),
                ];

                let _priv_handle = session.create_object(&template)?;

                let template = [
                    Attribute::Class(ObjectClass::PUBLIC_KEY),
                    Attribute::KeyType(KeyType::RSA),
                    Attribute::Id(key_id.as_bytes().to_vec()),
                    Attribute::Label(key_label.as_bytes().to_vec()),
                    // Persist it on the token.
                    Attribute::Token(true),
                    if capability == KeyCapability::Signing {
                        Attribute::Verify(true)
                    } else {
                        Attribute::Encrypt(true)
                    },
                    Attribute::Modulus(n.value().to_vec()),
                    Attribute::PublicExponent(e.value().to_vec()),
                ];

                let public_handle = session.create_object(&template)?;

                Ok((public_handle, None))
            }
            _ => {
                return Err(anyhow::anyhow!(
                    "{} keys are not yet supported",
                    key.pk_algo()));
            }
        })?;

        let mut key = Key::from_object(token, session, public_handle)?;
        if let Some(constituent) = constituent {
            key.combine_with(constituent);
        }

        Ok(key)
    }
}
