//! Key generation.

use std::{
    borrow::Cow,
    time::SystemTime,
};

use anyhow::Context as _;

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

use sequoia_openpgp::{
    Profile,
    types::HashAlgorithm,
    types::SymmetricAlgorithm,
};

use crate::{
    Context,
    Key,
    PublicKeyAlgorithm,
    Result,
    Token,
    keyid::Bundling,
};

/// Key generation.
impl Context {
    /// Generates a new Ed25519 key.
    ///
    /// `bundling` indicates whether the key is a standalone key or
    /// half of a composite key.
    fn generate_ed25519_internal(&self,
                                 session: &Session,
                                 token: Token,
                                 algo: PublicKeyAlgorithm,
                                 profile: Profile,
                                 bundling: Bundling,
                                 creation_time: Option<SystemTime>,
                                 key_label: Option<&str>)
        -> Result<Key>
    {
        assert!(bundling == Bundling::TraditionalHalf
                || bundling == Bundling::Standalone);

        self.generate_key(
            session, Cow::Owned(token), algo, profile, bundling,
            creation_time, key_label, None,
            Mechanism::EccEdwardsKeyPairGen,
            vec![
                Attribute::Class(ObjectClass::PUBLIC_KEY),
                Attribute::KeyType(KeyType::EC_EDWARDS),
                // DER encoding of the OID and some other parameters.
                // See PKCS#11, v3.2, draft 13, Section 6.3.5, Edwards
                // Elliptic Curve public key objects
                Attribute::EcParams(crate::oids::Ed25519.into()),
                Attribute::Verify(true),
            ],
            vec![
                Attribute::Class(ObjectClass::PRIVATE_KEY),
                Attribute::KeyType(KeyType::EC_EDWARDS),
                Attribute::Sensitive(true),
                Attribute::Sign(true),
            ],
        )
    }

    /// Generates a new EdDSA key.
    ///
    /// [EdDSA keys] are based on elliptical curve cryptography, and
    /// can be used for signing.  They are only defined for version
    /// 4 keys.
    ///
    /// [EdDSA keys]: https://www.rfc-editor.org/rfc/rfc9580.html#name-ecc-curves
    /// [RFC 9580, section 11.5.1. ECDH Parameters]: https://www.rfc-editor.org/rfc/rfc9580.html#ecdh
    ///
    /// To generate a version 6 key, use `Context::generate_ed25519`.
    pub fn generate_eddsa<T>(&self,
                             session: &Session,
                             token: Token,
                             creation_time: T,
                             key_label: Option<&str>)
        -> Result<Key>
    where
        T: Into<Option<SystemTime>>,
    {
        self.generate_ed25519_internal(
            session,
            token,
            PublicKeyAlgorithm::EdDSA,
            Profile::RFC4880,
            // This is a standalone key, and not a (post quantum)
            // composite key.
            Bundling::Standalone,
            creation_time.into(),
            key_label)
    }

    /// Generates a new Ed25519 key.
    pub fn generate_ed25519<T>(&self,
                               session: &Session,
                               token: Token,
                               profile: Profile,
                               creation_time: T,
                               key_label: Option<&str>)
                               -> Result<Key>
    where
        T: Into<Option<SystemTime>>,
    {
        self.generate_ed25519_internal(
            session,
            token,
            PublicKeyAlgorithm::Ed25519,
            profile,
            // This is a standalone key, and not a (post
            // quantum) composite key.
            Bundling::Standalone,
            creation_time.into(),
            key_label)
    }

    /// Generates a new Ed448 key.
    ///
    /// `bundling` indicates whether the key is a standalone key or
    /// half of a composite key.
    fn generate_ed448_internal(&self,
                               session: &Session,
                               token: Token,
                               algo: PublicKeyAlgorithm,
                               profile: Profile,
                               bundling: Bundling,
                               creation_time: Option<SystemTime>,
                               key_label: Option<&str>)
                               -> Result<Key>
    {
        assert!(bundling == Bundling::TraditionalHalf
                || bundling == Bundling::Standalone);

        self.generate_key(
            session, Cow::Owned(token), algo, profile, bundling,
            creation_time, key_label, None,
            Mechanism::EccEdwardsKeyPairGen,
            vec![
                Attribute::Class(ObjectClass::PUBLIC_KEY),
                Attribute::KeyType(KeyType::EC_EDWARDS),
                // DER encoding of the OID and some other parameters.
                // See PKCS#11, v3.2, draft 13, Section 6.3.5, Edwards
                // Elliptic Curve public key objects
                Attribute::EcParams(crate::oids::Ed448.into()),
                Attribute::Verify(true),
            ],
            vec![
                Attribute::Class(ObjectClass::PRIVATE_KEY),
                Attribute::KeyType(KeyType::EC_EDWARDS),
                Attribute::Sensitive(true),
                Attribute::Sign(true),
            ],
        )
    }

    /// Generates a new Ed448 key.
    pub fn generate_ed448<T>(&self,
                             session: &Session,
                             token: Token,
                             profile: Profile,
                             creation_time: T,
                             key_label: Option<&str>)
                             -> Result<Key>
    where
        T: Into<Option<SystemTime>>,
    {
        self.generate_ed448_internal(
            session,
            token,
            PublicKeyAlgorithm::Ed448,
            profile,
            // This is a standalone key, and not a (post
            // quantum) composite key.
            Bundling::Standalone,
            creation_time.into(),
            key_label)
    }

    /// Generates a new cv25519 key.
    ///
    /// [cv25519 keys] are based on elliptical curve cryptography, and
    /// can be used for encryption.  They are only defined for version
    /// 4 keys, and they take two parameters: a hash function, and a
    /// symmetric algorithm.  If either parameter is `None`, we use
    /// the recommended values as per [RFC 9580, section 11.5.1. ECDH
    /// Parameters].
    ///
    /// [cv25519 keys]: https://www.rfc-editor.org/rfc/rfc9580.html#name-ecc-curves
    /// [RFC 9580, section 11.5.1. ECDH Parameters]: https://www.rfc-editor.org/rfc/rfc9580.html#ecdh
    ///
    /// To generate a version 6 key, use `Context::generate_x25519`.
    pub fn generate_legacy_cv25519<T>(&self,
                                      session: &Session,
                                      token: Token,
                                      creation_time: T,
                                      hash_algo: Option<HashAlgorithm>,
                                      sym_algo: Option<SymmetricAlgorithm>,
                                      key_label: Option<&str>)
        -> Result<Key>
    where
        T: Into<Option<SystemTime>>,
    {
        self.generate_x25519_internal(
            session, token, PublicKeyAlgorithm::Cv25519,
            Profile::RFC4880, Bundling::Standalone,
            creation_time.into(), hash_algo, sym_algo, key_label)
    }

    /// Generates a new MLDSA65+Ed25519 key.
    ///
    /// This generates two PKCS#11 keys: an MLDSA65 key and an Ed25519
    /// key.  The two keys are linked via their key ID.  Hence, either
    /// can be specified and the other will be found automatically.
    pub fn generate_mldsa65_ed25519<T>(&self,
                                       session: &Session,
                                       token: Token,
                                       profile: Profile,
                                       creation_time: T,
                                       key_label: Option<&str>)
                                       -> Result<Key>
    where
        T: Into<Option<SystemTime>>,
    {
        if profile != Profile::RFC9580 {
            return Err(anyhow::anyhow!(
                "ML-DSA is only usable with the RFC 9580 profile"));
        }

        let creation_time = creation_time.into();
        // Resolve now to ensure the keys have the same creation time.
        let creation_time = creation_time.unwrap_or_else(SystemTime::now);

        let mut pq = self.generate_key(
            session, Cow::Borrowed(&token), PublicKeyAlgorithm::MLDSA65_Ed25519,
            profile, Bundling::PostQuantumHalf,
            Some(creation_time.clone()), key_label, None,
            Mechanism::MlDsaKeyPairGen,
            vec![
                Attribute::Class(ObjectClass::PUBLIC_KEY),
                Attribute::KeyType(KeyType::ML_DSA),
                Attribute::ParameterSet(MlDsaParameterSetType::ML_DSA_65.into()),
                Attribute::Verify(true),
            ],
            vec![
                Attribute::Class(ObjectClass::PRIVATE_KEY),
                Attribute::KeyType(KeyType::ML_DSA),
                Attribute::ParameterSet(MlDsaParameterSetType::ML_DSA_65.into()),
                Attribute::Sensitive(true),
                Attribute::Sign(true),
            ],
        )?;

        let key_label
            = String::from_utf8(pq.key_label().expect("set label").to_vec())
            .expect("set a UTF-8 label");

        // Now create the traditional half.
        let ed25519 = match self.generate_ed25519_internal(
            session,
            token,
            PublicKeyAlgorithm::MLDSA65_Ed25519,
            profile,
            // This is a composite key, and not a standalone key.
            Bundling::TraditionalHalf,
            Some(creation_time),
            Some(&key_label))
        {
            Ok(key) => key,
            Err(err) => {
                // XXX: Destroy the PQ key.
                return Err(err);
            }
        };

        pq.combine_with(ed25519);
        Ok(pq)
    }

    /// Generates a new MLDSA87+Ed448 key.
    ///
    /// This generates two PKCS#11 keys: an MLDSA87 key and an ED448
    /// key.  The two keys are linked via their key ID.  Hence, either
    /// can be specified and the other will be found automatically.
    pub fn generate_mldsa87_ed448<T>(&self,
                                     session: &Session,
                                     token: Token,
                                     profile: Profile,
                                     creation_time: T,
                                     key_label: Option<&str>)
                                     -> Result<Key>
    where
        T: Into<Option<SystemTime>>,
    {
        if profile != Profile::RFC9580 {
            return Err(anyhow::anyhow!(
                "ML-DSA is only usable with the RFC 9580 profile"));
        }

        let creation_time = creation_time.into();
        // Resolve now to ensure the keys have the same creation time.
        let creation_time = creation_time.unwrap_or_else(SystemTime::now);

        // Create the PQ half first.  The token is more likely to not
        // support PQ algorithms.
        let mut pq = self.generate_key(
            session, Cow::Borrowed(&token), PublicKeyAlgorithm::MLDSA87_Ed448,
            profile, Bundling::PostQuantumHalf,
            Some(creation_time.clone()), key_label, None,
            Mechanism::MlDsaKeyPairGen,
            vec![
                Attribute::Class(ObjectClass::PUBLIC_KEY),
                Attribute::KeyType(KeyType::ML_DSA),
                Attribute::ParameterSet(MlDsaParameterSetType::ML_DSA_87.into()),
                Attribute::Verify(true),
            ],
            vec![
                Attribute::Class(ObjectClass::PRIVATE_KEY),
                Attribute::KeyType(KeyType::ML_DSA),
                Attribute::ParameterSet(MlDsaParameterSetType::ML_DSA_87.into()),
                Attribute::Sensitive(true),
                Attribute::Sign(true),
            ],
        )?;

        let key_label
            = String::from_utf8(pq.key_label().expect("set label").to_vec())
            .expect("set a UTF-8 label");

        // Now create the traditional half.
        let ed448 = match self.generate_ed448_internal(
            session,
            token,
            PublicKeyAlgorithm::MLDSA87_Ed448,
            profile,
            // This is a composite key, and not a standalone key.
            Bundling::TraditionalHalf,
            Some(creation_time),
            Some(&key_label))
        {
            Ok(key) => key,
            Err(err) => {
                // XXX: Destroy the PQ key.
                return Err(err);
            }
        };

        pq.combine_with(ed448);
        Ok(pq)
    }

    /// Generates a new SLH-DSA key.
    fn generate_slh_dsa_internal(&self,
                                 session: &Session,
                                 token: Token,
                                 algo: PublicKeyAlgorithm,
                                 profile: Profile,
                                 params: SlhDsaParameterSetType,
                                 creation_time: Option<SystemTime>,
                                 key_label: Option<&str>)
        -> Result<Key>
    {
        if profile != Profile::RFC9580 {
            return Err(anyhow::anyhow!(
                "SLH-DSA is only usable with the RFC 9580 profile"));
        }

        self.generate_key(
            session, Cow::Owned(token), algo, profile, Bundling::Standalone,
            creation_time, key_label, None,
            Mechanism::SlhDsaKeyPairGen,
            vec![
                Attribute::Class(ObjectClass::PUBLIC_KEY),
                Attribute::KeyType(KeyType::SLH_DSA),
                Attribute::ParameterSet(params.into()),
                Attribute::Verify(true),
            ],
            vec![
                Attribute::Class(ObjectClass::PRIVATE_KEY),
                Attribute::KeyType(KeyType::SLH_DSA),
                Attribute::ParameterSet(params.into()),
                Attribute::Sensitive(true),
                Attribute::Sign(true),
            ],
        )
    }

    /// Generates a new SLH-DSA-128s key.
    pub fn generate_slh_dsa_128s<T>(&self,
                                    session: &Session,
                                    token: Token,
                                    profile: Profile,
                                    creation_time: T,
                                    key_label: Option<&str>)
        -> Result<Key>
    where
        T: Into<Option<SystemTime>>,
    {
        self.generate_slh_dsa_internal(
            session, token, PublicKeyAlgorithm::SLHDSA128s,
            profile, SlhDsaParameterSetType::SHAKE_128S,
            creation_time.into(), key_label)
    }

    /// Generates a new SLH-DSA-128f key.
    pub fn generate_slh_dsa_128f<T>(&self,
                                    session: &Session,
                                    token: Token,
                                    profile: Profile,
                                    creation_time: T,
                                    key_label: Option<&str>)
        -> Result<Key>
    where
        T: Into<Option<SystemTime>>,
    {
        self.generate_slh_dsa_internal(
            session, token, PublicKeyAlgorithm::SLHDSA128f,
            profile, SlhDsaParameterSetType::SHAKE_128F,
            creation_time.into(), key_label)
    }

    /// Generates a new SLH-DSA-256s key.
    pub fn generate_slh_dsa_256s<T>(&self,
                                    session: &Session,
                                    token: Token,
                                    profile: Profile,
                                    creation_time: T,
                                    key_label: Option<&str>)
        -> Result<Key>
    where
        T: Into<Option<SystemTime>>,
    {
        self.generate_slh_dsa_internal(
            session, token, PublicKeyAlgorithm::SLHDSA256s,
            profile, SlhDsaParameterSetType::SHAKE_256S,
            creation_time.into(), key_label)
    }

    /// Generates a new X25519 key.
    ///
    /// [x25519 keys] are based on elliptical curve cryptography, and
    /// can be used for encryption.
    ///
    /// If `profile` is `RFC4880`, then a version 4 key is created
    /// using the default parameters.  See
    /// [`Context::generate_legacy_cv25519`] for more information
    /// about version 4 keys.  Note: version 6 keys don't have any
    /// free parameters.
    pub fn generate_x25519<T>(&self,
                              session: &Session,
                              token: Token,
                              profile: Profile,
                              creation_time: T,
                              key_label: Option<&str>)
                              -> Result<Key>
    where
        T: Into<Option<SystemTime>>,
    {
        self.generate_x25519_internal(
            session, token, PublicKeyAlgorithm::X25519,
            profile, Bundling::Standalone,
            creation_time.into(), None, None, key_label)
    }

    /// hash_algo and sym_algo are only relevant if `profile` is
    /// `RFC4880` and `bundling` is `Standalone`.
    fn generate_x25519_internal(&self,
                                session: &Session,
                                token: Token,
                                algo: PublicKeyAlgorithm,
                                profile: Profile,
                                bundling: Bundling,
                                creation_time: Option<SystemTime>,
                                hash_algo: Option<HashAlgorithm>,
                                sym_algo: Option<SymmetricAlgorithm>,
                                key_label: Option<&str>)
        -> Result<Key>
    {
	let base_key_id_extra = match algo {
	    PublicKeyAlgorithm::Cv25519 => {
		assert_eq!(profile, Profile::RFC4880);
		assert_eq!(bundling, Bundling::Standalone);

		let hash_algo = match hash_algo {
                    Some(HashAlgorithm::SHA256) | None => "sha256",
                    Some(HashAlgorithm::SHA384) => "sha384",
                    Some(HashAlgorithm::SHA512) => "sha512",
                    Some(algo) => {
			return Err(anyhow::anyhow!(
                            "{} cannot be used with a cv25519 key", algo));
                    }
		};
		let sym_algo = match sym_algo {
                    Some(SymmetricAlgorithm::AES128) | None => "aes128",
                    Some(SymmetricAlgorithm::AES192) => "aes192",
                    Some(SymmetricAlgorithm::AES256) => "aes256",
                    Some(algo) => {
			return Err(anyhow::anyhow!(
                            "{} cannot be used with a cv25519 key", algo));
                    }
		};

		Some(format!("{}-{}", hash_algo, sym_algo))
	    }
	    PublicKeyAlgorithm::X25519 => {
		assert_eq!(bundling, Bundling::Standalone);
		assert!(hash_algo.is_none());
		assert!(sym_algo.is_none());
		None
	    }
	    PublicKeyAlgorithm::MLKEM768_X25519 => {
		assert_eq!(bundling, Bundling::TraditionalHalf);
		assert!(hash_algo.is_none());
		assert!(sym_algo.is_none());
		None
	    }
	    algo => {
		panic!("Internal error: generate_x25519_internal called for {}",
		       algo);
	    }
	};

        self.generate_key(
            session, Cow::Owned(token), algo, profile, bundling,
            creation_time.into(), key_label, base_key_id_extra.as_deref(),
            Mechanism::EccMontgomeryKeyPairGen,
            vec![
                Attribute::Class(ObjectClass::PUBLIC_KEY),
                Attribute::KeyType(KeyType::EC_MONTGOMERY),
                // DER encoding of the OID and some other parameters.
                // See PKCS#11, v3.2, draft 13, Section 6.3.5, Edwards
                // Elliptic Curve public key objects
                Attribute::EcParams(crate::oids::X25519.into()),
                Attribute::Encrypt(true),
                Attribute::Wrap(true),
            ],
            vec![
                Attribute::Class(ObjectClass::PRIVATE_KEY),
                Attribute::KeyType(KeyType::EC_MONTGOMERY),
                Attribute::Sensitive(true),
                Attribute::Decrypt(true),
                Attribute::Unwrap(true),
                Attribute::Derive(true),
            ],
        )
    }

    /// Generates a new MLKEM768+X25519 key.
    ///
    /// This generates two PKCS#11 keys: an MLKEM768 key and a X25519
    /// key.  The two keys are linked via their key ID.  Hence, either
    /// can be specified and the other will be found automatically.
    pub fn generate_mlkem768_x25519<T>(&self,
                                       session: &Session,
                                       token: Token,
                                       profile: Profile,
                                       creation_time: T,
                                       key_label: Option<&str>)
                                       -> Result<Key>
    where
        T: Into<Option<SystemTime>>,
    {
        let creation_time = creation_time.into();
        // Resolve now to ensure the keys have the same creation time.
        let creation_time = creation_time.unwrap_or_else(SystemTime::now);

        let mut pq = self.generate_key(
            session, Cow::Borrowed(&token), PublicKeyAlgorithm::MLKEM768_X25519,
            profile, Bundling::PostQuantumHalf,
            Some(creation_time.clone()), key_label, None,
            Mechanism::MlKemKeyPairGen,
            vec![
                Attribute::Class(ObjectClass::PUBLIC_KEY),
                Attribute::KeyType(KeyType::ML_KEM),
                Attribute::ParameterSet(MlKemParameterSetType::ML_KEM_768.into()),
                Attribute::Encapsulate(true),
            ],
            vec![
                Attribute::Class(ObjectClass::PRIVATE_KEY),
                Attribute::KeyType(KeyType::ML_KEM),
                Attribute::ParameterSet(MlKemParameterSetType::ML_KEM_768.into()),
                Attribute::Sensitive(true),
                Attribute::Decapsulate(true),
            ],
        ).context("Generating ML-KEM-768 key")?;

        let key_label
            = String::from_utf8(pq.key_label().expect("set label").to_vec())
            .expect("set a UTF-8 label");

        // Now create the traditional half.
        let x25519 = match self.generate_x25519_internal(
            session,
            token,
            PublicKeyAlgorithm::MLKEM768_X25519,
            profile,
            // This is a composite key, and not a standalone key.
            Bundling::TraditionalHalf,
            Some(creation_time),
            None, None,
            Some(&key_label))
        {
            Ok(key) => key,
            Err(err) => {
                // XXX: Destroy the PQ key.
                return Err(err).context("Generating X25519 key");
            }
        };

        pq.combine_with(x25519);
        Ok(pq)
    }

    /// Generates a new X448 key.
    fn generate_x448_internal<T>(&self,
                                 session: &Session,
                                 token: Token,
                                 algo: PublicKeyAlgorithm,
                                 profile: Profile,
                                 bundling: Bundling,
                                 creation_time: T,
                                 key_label: Option<&str>)
        -> Result<Key>
    where
        T: Into<Option<SystemTime>>,
    {
        self.generate_key(
            session, Cow::Owned(token), algo, profile, bundling,
            creation_time.into(), key_label, None,
            Mechanism::EccMontgomeryKeyPairGen,
            vec![
                Attribute::Class(ObjectClass::PUBLIC_KEY),
                Attribute::KeyType(KeyType::EC_MONTGOMERY),
                // DER encoding of the OID and some other parameters.
                // See PKCS#11, v3.2, draft 13, Section 6.3.5, Edwards
                // Elliptic Curve public key objects
                Attribute::EcParams(crate::oids::X448.into()),
                Attribute::Encrypt(true),
                Attribute::Wrap(true),
            ],
            vec![
                Attribute::Class(ObjectClass::PRIVATE_KEY),
                Attribute::KeyType(KeyType::EC_MONTGOMERY),
                Attribute::Sensitive(true),
                Attribute::Decrypt(true),
                Attribute::Unwrap(true),
                Attribute::Derive(true),
            ],
        )
    }

    /// Generates a new X448 key.
    pub fn generate_x448<T>(&self,
                            session: &Session,
                            token: Token,
                            profile: Profile,
                            creation_time: T,
                            key_label: Option<&str>)
                            -> Result<Key>
    where
        T: Into<Option<SystemTime>>,
    {
        self.generate_x448_internal(
            session, token, PublicKeyAlgorithm::X448,
            profile, Bundling::Standalone,
            creation_time, key_label)
    }

    /// Generates a new MLKEM1024+X448 key.
    ///
    /// This generates two PKCS#11 keys: an MLKEM1024 key and a X448
    /// key.  The two keys are linked via their key ID.  Hence, either
    /// can be specified and the other will be found automatically.
    pub fn generate_mlkem1024_x448<T>(&self,
                                       session: &Session,
                                       token: Token,
                                       profile: Profile,
                                       creation_time: T,
                                       key_label: Option<&str>)
                                       -> Result<Key>
    where
        T: Into<Option<SystemTime>>,
    {
        let creation_time = creation_time.into();
        // Resolve now to ensure the keys have the same creation time.
        let creation_time = creation_time.unwrap_or_else(SystemTime::now);

        let mut pq = self.generate_key(
            session, Cow::Borrowed(&token), PublicKeyAlgorithm::MLKEM1024_X448,
            profile, Bundling::PostQuantumHalf,
            Some(creation_time.clone()), key_label, None,
            Mechanism::MlKemKeyPairGen,
            vec![
                Attribute::Class(ObjectClass::PUBLIC_KEY),
                Attribute::KeyType(KeyType::ML_KEM),
                Attribute::ParameterSet(MlKemParameterSetType::ML_KEM_1024.into()),
                Attribute::Encapsulate(true),
            ],
            vec![
                Attribute::Class(ObjectClass::PRIVATE_KEY),
                Attribute::KeyType(KeyType::ML_KEM),
                Attribute::ParameterSet(MlKemParameterSetType::ML_KEM_1024.into()),
                Attribute::Sensitive(true),
                Attribute::Decapsulate(true),
            ],
        ).context("Generating ML-KEM-1024 key")?;

        let key_label
            = String::from_utf8(pq.key_label().expect("set label").to_vec())
            .expect("set a UTF-8 label");

        // Now create the traditional half.
        let x448 = match self.generate_x448_internal(
            session,
            token,
            PublicKeyAlgorithm::MLKEM1024_X448,
            profile,
            // This is a composite key, and not a standalone key.
            Bundling::TraditionalHalf,
            Some(creation_time),
            Some(&key_label))
        {
            Ok(key) => key,
            Err(err) => {
                // XXX: Destroy the PQ key.
                return Err(err).context("Generating X448 key");
            }
        };

        pq.combine_with(x448);
        Ok(pq)
    }

    /// Generates a new signing-capable RSA key.
    pub fn generate_rsa_for_signing<T>(&self,
                                       session: &Session,
                                       token: Token,
                                       profile: Profile,
                                       bits: usize,
                                       creation_time: T,
                                       key_label: Option<&str>)
        -> Result<Key>
    where
        T: Into<Option<SystemTime>>,
    {
        self.generate_key(
            session, Cow::Owned(token),
            PublicKeyAlgorithm::RSA,
            profile, Bundling::Standalone,
            creation_time.into(), key_label, None,
            Mechanism::RsaPkcsKeyPairGen,
            vec![
                Attribute::Class(ObjectClass::PUBLIC_KEY),
                Attribute::KeyType(KeyType::RSA),
                Attribute::ModulusBits(bits.try_into()?),
                Attribute::Verify(true),
            ],
            vec![
                Attribute::Class(ObjectClass::PRIVATE_KEY),
                Attribute::KeyType(KeyType::RSA),
                Attribute::Sensitive(true),
                Attribute::Sign(true),
            ],
        )
    }

    /// Generates a new encryption-capable RSA key.
    pub fn generate_rsa_for_encryption<T>(&self,
                                          session: &Session,
                                          token: Token,
                                          profile: Profile,
                                          bits: usize,
                                          creation_time: T,
                                          key_label: Option<&str>)
        -> Result<Key>
    where
        T: Into<Option<SystemTime>>,
    {
        self.generate_key(
            session, Cow::Owned(token),
            PublicKeyAlgorithm::RSA,
            profile, Bundling::Standalone,
            creation_time.into(), key_label, None,
            Mechanism::RsaPkcsKeyPairGen,
            vec![
                Attribute::Class(ObjectClass::PUBLIC_KEY),
                Attribute::KeyType(KeyType::RSA),
                Attribute::ModulusBits(bits.try_into()?),
                Attribute::Encrypt(true),
            ],
            vec![
                Attribute::Class(ObjectClass::PRIVATE_KEY),
                Attribute::KeyType(KeyType::RSA),
                Attribute::Sensitive(true),
                Attribute::Decrypt(true),
            ],
        )
    }

    /// Generates a key with the given parameters.
    fn generate_key(&self,
                    session: &Session,
                    token: Cow<Token>,
                    algo: PublicKeyAlgorithm,
                    profile: Profile,
                    bundling: Bundling,
                    creation_time: Option<SystemTime>,
                    key_label: Option<&str>,
                    key_id_extra: Option<&str>,
                    mechanism: Mechanism,
                    mut public_key_parameters: Vec<Attribute>,
                    mut private_key_parameters: Vec<Attribute>)
                    -> Result<Key>
    {
        let (key_id, key_label) = self.generate_key_id(
            algo, profile, bundling, creation_time, key_id_extra, key_label)
            .context("Constructing key ID")?;

        let common_attributes = [
            // The common ID.
            Attribute::Id(key_id.clone().into()),
            // Persist the object on the token.
            Attribute::Token(true),
            Attribute::Label(key_label.as_bytes().to_vec()),
        ];

        public_key_parameters.extend(common_attributes.iter().cloned());
        private_key_parameters.extend(common_attributes.into_iter());

        // Generate the key pair.
        let (public, _secret) = session.generate_key_pair(
            &mechanism, &public_key_parameters, &private_key_parameters)
            .context("Generating key pair")?;

        Key::from_object(token.into_owned(), session, public)
    }
}
