diff options
author | Justus Winter <justus@sequoia-pgp.org> | 2023-03-08 11:21:41 +0100 |
---|---|---|
committer | Justus Winter <justus@sequoia-pgp.org> | 2023-03-08 13:06:44 +0100 |
commit | 336a1562bfe3ad5fe32b5e3017c88197f5bad0fa (patch) | |
tree | ec7f433a8ab5cf97a508848c38be5cc6ae9685cf /openpgp/src | |
parent | 07bb232c30851bff0b0f315fbc088af6f8e6911a (diff) |
openpgp: Add a new backend based on the Botan cryptographic library.
Diffstat (limited to 'openpgp/src')
-rw-r--r-- | openpgp/src/crypto/backend.rs | 9 | ||||
-rw-r--r-- | openpgp/src/crypto/backend/botan.rs | 101 | ||||
-rw-r--r-- | openpgp/src/crypto/backend/botan/aead.rs | 63 | ||||
-rw-r--r-- | openpgp/src/crypto/backend/botan/asymmetric.rs | 670 | ||||
-rw-r--r-- | openpgp/src/crypto/backend/botan/ecdh.rs | 150 | ||||
-rw-r--r-- | openpgp/src/crypto/backend/botan/hash.rs | 103 | ||||
-rw-r--r-- | openpgp/src/crypto/backend/botan/symmetric.rs | 178 |
7 files changed, 1274 insertions, 0 deletions
diff --git a/openpgp/src/crypto/backend.rs b/openpgp/src/crypto/backend.rs index 28fcfe14..7cae8a8a 100644 --- a/openpgp/src/crypto/backend.rs +++ b/openpgp/src/crypto/backend.rs @@ -14,11 +14,13 @@ pub(crate) mod sha1cd; #[cfg(all(feature = "crypto-nettle", not(all(feature = "__implicit-crypto-backend-for-tests", any(feature = "crypto-openssl", + feature = "crypto-botan", feature = "crypto-rust")))))] mod nettle; #[cfg(all(feature = "crypto-nettle", not(all(feature = "__implicit-crypto-backend-for-tests", any(feature = "crypto-openssl", + feature = "crypto-botan", feature = "crypto-rust")))))] pub use self::nettle::*; @@ -34,12 +36,14 @@ pub use self::nettle::*; not(all(feature = "__implicit-crypto-backend-for-tests", any(feature = "crypto-nettle", feature = "crypto-openssl", + feature = "crypto-botan", feature = "crypto-rust")))))] mod cng; #[cfg(all(feature = "crypto-cng", not(all(feature = "__implicit-crypto-backend-for-tests", any(feature = "crypto-nettle", feature = "crypto-openssl", + feature = "crypto-botan", feature = "crypto-rust")))))] pub use self::cng::*; @@ -52,3 +56,8 @@ pub use self::rust::*; mod openssl; #[cfg(feature = "crypto-openssl")] pub use self::openssl::*; + +#[cfg(feature = "crypto-botan")] +mod botan; +#[cfg(feature = "crypto-botan")] +pub use self::botan::*; diff --git a/openpgp/src/crypto/backend/botan.rs b/openpgp/src/crypto/backend/botan.rs new file mode 100644 index 00000000..30f42a67 --- /dev/null +++ b/openpgp/src/crypto/backend/botan.rs @@ -0,0 +1,101 @@ +//! Implementation of Sequoia crypto API using the Botan cryptographic library. + +use crate::types::*; + +pub mod aead; +#[allow(unused_variables)] +pub mod asymmetric; +#[allow(unused_variables)] +pub mod ecdh; +pub mod hash; +pub mod symmetric; + +/// Returns a short, human-readable description of the backend. +pub fn backend() -> String { + "Botan".to_string() +} + +/// Fills the given buffer with random data. +pub fn random(buf: &mut [u8]) { + let mut rng = botan::RandomNumberGenerator::new_system().unwrap(); + rng.fill(buf).unwrap(); +} + +impl PublicKeyAlgorithm { + pub(crate) fn is_supported_by_backend(&self) -> bool { + use PublicKeyAlgorithm::*; + #[allow(deprecated)] + match &self { + RSAEncryptSign | RSAEncrypt | RSASign | DSA | ECDH | ECDSA | EdDSA | + ElGamalEncrypt | ElGamalEncryptSign + => true, + Private(_) | Unknown(_) + => false, + } + } +} + +impl Curve { + pub(crate) fn is_supported_by_backend(&self) -> bool { + use self::Curve::*; + match &self { + NistP256 | NistP384 | NistP521 | Ed25519 | Cv25519 | + BrainpoolP256 | BrainpoolP512 + => true, + Unknown(_) if self.is_brainpoolp384() // XXX + => true, + Unknown(_) + => false, + } + } +} + +impl AEADAlgorithm { + /// Returns the best AEAD mode supported by the backend. + /// + /// This SHOULD return OCB, which is the mandatory-to-implement + /// algorithm and the most performing one, but fall back to any + /// supported algorithm. + pub(crate) const fn const_default() -> AEADAlgorithm { + AEADAlgorithm::OCB + } + + pub(crate) fn is_supported_by_backend(&self) -> bool { + use self::AEADAlgorithm::*; + match &self { + EAX | OCB + => true, + Private(_) | Unknown(_) + => false, + } + } + + #[cfg(test)] + pub(crate) fn supports_symmetric_algo(&self, algo: &SymmetricAlgorithm) -> bool { + match &self { + AEADAlgorithm::EAX => + match algo { + SymmetricAlgorithm::AES128 | + SymmetricAlgorithm::AES192 | + SymmetricAlgorithm::AES256 | + SymmetricAlgorithm::Twofish | + SymmetricAlgorithm::Camellia128 | + SymmetricAlgorithm::Camellia192 | + SymmetricAlgorithm::Camellia256 => true, + _ => false, + }, + AEADAlgorithm::OCB => + match algo { + SymmetricAlgorithm::AES128 | + SymmetricAlgorithm::AES192 | + SymmetricAlgorithm::AES256 | + SymmetricAlgorithm::Twofish | + SymmetricAlgorithm::Camellia128 | + SymmetricAlgorithm::Camellia192 | + SymmetricAlgorithm::Camellia256 => true, + _ => false, + }, + _ => false + } + } +} diff --git a/openpgp/src/crypto/backend/botan/aead.rs b/openpgp/src/crypto/backend/botan/aead.rs new file mode 100644 index 00000000..c7f4bbf2 --- /dev/null +++ b/openpgp/src/crypto/backend/botan/aead.rs @@ -0,0 +1,63 @@ +//! Implementation of AEAD using the Botan cryptographic library. + +use crate::{Error, Result}; + +use crate::crypto::aead::{Aead, CipherOp}; +use crate::seal; +use crate::types::{AEADAlgorithm, SymmetricAlgorithm}; + +struct Cipher(botan::Cipher, usize); + +impl seal::Sealed for Cipher {} +impl Aead for Cipher { + fn encrypt_seal(&mut self, dst: &mut [u8], src: &[u8]) -> Result<()> { + debug_assert_eq!(dst.len(), src.len() + self.digest_size()); + self.0.finish_into(src, dst)?; + Ok(()) + } + + fn decrypt_verify(&mut self, dst: &mut [u8], src: &[u8]) -> Result<()> { + debug_assert_eq!(dst.len() + self.digest_size(), src.len()); + self.0.finish_into(src, dst)?; + Ok(()) + } + fn digest_size(&self) -> usize { + self.1 + } +} + +impl AEADAlgorithm { + /// Returns the name of the algorithm for use with Botan's + /// constructor. + fn botan_name(self) -> Result<&'static str> { + match self { + AEADAlgorithm::EAX => Ok("EAX"), + AEADAlgorithm::OCB => Ok("OCB"), + _ => Err(Error::UnsupportedAEADAlgorithm(self).into()), + } + } + + pub(crate) fn context(&self, + sym_algo: SymmetricAlgorithm, + key: &[u8], + aad: &[u8], + nonce: &[u8], + op: CipherOp) + -> Result<Box<dyn Aead>> + { + let mut cipher = botan::Cipher::new( + &format!("{}/{}", sym_algo.botan_name()?, self.botan_name()?), + match op { + CipherOp::Encrypt => botan::CipherDirection::Encrypt, + CipherOp::Decrypt => botan::CipherDirection::Decrypt, + }) + // XXX it could be the cipher that is not supported. + .map_err(|_| Error::UnsupportedAEADAlgorithm(*self))?; + + cipher.set_key(key)?; + cipher.set_associated_data(aad)?; + cipher.start(nonce)?; + + Ok(Box::new(Cipher(cipher, self.digest_size()?))) + } +} diff --git a/openpgp/src/crypto/backend/botan/asymmetric.rs b/openpgp/src/crypto/backend/botan/asymmetric.rs new file mode 100644 index 00000000..4cc2da5b --- /dev/null +++ b/openpgp/src/crypto/backend/botan/asymmetric.rs @@ -0,0 +1,670 @@ +//! Hold the implementation of [`Signer`] and [`Decryptor`] for [`KeyPair`]. +//! +//! [`Signer`]: super::super::asymmetric::Signer +//! [`Decryptor`]: super::super::asymmetric::Decryptor +//! [`KeyPair`]: super::super::asymmetric::KeyPair + +use std::time::SystemTime; + +use botan::{ + RandomNumberGenerator, + Pubkey, + Privkey, +}; + +use crate::{ + Error, + Result, + crypto::{ + asymmetric::{KeyPair, Decryptor, Signer}, + mem::Protected, + mpi::{self, MPI, ProtectedMPI, PublicKey}, + SessionKey, + }, + packet::{ + key::{self, Key4, SecretParts}, + Key, + }, + types::{ + Curve, + HashAlgorithm, + PublicKeyAlgorithm, + SymmetricAlgorithm, + }, +}; + +// CONFIDENTIALITY: Botan clears the MPIs after use. +impl TryFrom<&ProtectedMPI> for botan::MPI { + type Error = anyhow::Error; + fn try_from(mpi: &ProtectedMPI) -> anyhow::Result<botan::MPI> { + Ok(botan::MPI::new_from_bytes(mpi.value())?) + } +} + +impl TryFrom<&botan::MPI> for ProtectedMPI { + type Error = anyhow::Error; + fn try_from(bn: &botan::MPI) -> anyhow::Result<Self> { + Ok(bn.to_bin()?.into()) + } +} + +impl TryFrom<botan::MPI> for ProtectedMPI { + type Error = anyhow::Error; + fn try_from(bn: botan::MPI) -> anyhow::Result<Self> { + Ok(bn.to_bin()?.into()) + } +} + +impl TryFrom<&MPI> for botan::MPI { + type Error = anyhow::Error; + fn try_from(mpi: &MPI) -> anyhow::Result<botan::MPI> { + Ok(botan::MPI::new_from_bytes(mpi.value())?) + } +} + +impl TryFrom<&botan::MPI> for MPI { + type Error = anyhow::Error; + fn try_from(bn: &botan::MPI) -> anyhow::Result<Self> { + Ok(bn.to_bin()?.into()) + } +} + +impl TryFrom<botan::MPI> for MPI { + type Error = anyhow::Error; + fn try_from(bn: botan::MPI) -> anyhow::Result<Self> { + Ok(bn.to_bin()?.into()) + } +} + +impl Signer for KeyPair { + fn public(&self) -> &Key<key::PublicParts, key::UnspecifiedRole> { + KeyPair::public(self) + } + + fn sign(&mut self, hash_algo: HashAlgorithm, digest: &[u8]) + -> Result<mpi::Signature> + { + use crate::PublicKeyAlgorithm::*; + + let mut rng = RandomNumberGenerator::new_userspace()?; + + self.secret().map(|secret| { + #[allow(deprecated)] + match (self.public().pk_algo(), self.public().mpis(), secret) + { + (RSASign, + PublicKey::RSA { e, .. }, + mpi::SecretKeyMaterial::RSA { p, q, .. }) | + (RSAEncryptSign, + PublicKey::RSA { e, .. }, + mpi::SecretKeyMaterial::RSA { p, q, .. }) => { + let secret = Privkey::load_rsa(&p.try_into()?, &q.try_into()?, + &e.try_into()?)?; + let sig = secret.sign( + digest, + &format!("PKCS1v15(Raw,{})", hash_algo.botan_name()?), + &mut rng)?; + Ok(mpi::Signature::RSA { + s: MPI::new(&sig), + }) + }, + + (DSA, + PublicKey::DSA { p, q, g, .. }, + mpi::SecretKeyMaterial::DSA { x }) => { + let secret = Privkey::load_dsa(&p.try_into()?, &q.try_into()?, + &g.try_into()?, &x.try_into()?)?; + let size = q.value().len(); + let truncated_digest = &digest[..size.min(digest.len())]; + let sig = secret.sign(truncated_digest, "Raw", &mut rng)?; + + if sig.len() != size * 2 { + return Err(Error::MalformedMPI( + format!("Expected signature with length {}, got {}", + size * 2, sig.len())).into()); + } + + Ok(mpi::Signature::DSA { + r: MPI::new(&sig[..size]), + s: MPI::new(&sig[size..]), + }) + }, + + (EdDSA, + PublicKey::EdDSA { curve, .. }, + mpi::SecretKeyMaterial::EdDSA { scalar }) => match curve { + Curve::Ed25519 => { + let size = 32; + let scalar = scalar.value_padded(size); + let secret = Privkey::load_ed25519(&scalar)?; + let sig = secret.sign(digest, "", &mut rng)?; + + if sig.len() != size * 2 { + return Err(Error::MalformedMPI( + format!("Expected signature with length {}, got {}", + size * 2, sig.len())).into()); + } + + Ok(mpi::Signature::EdDSA { + r: MPI::new(&sig[..size]), + s: MPI::new(&sig[size..]), + }) + }, + _ => Err( + Error::UnsupportedEllipticCurve(curve.clone()).into()), + }, + + (ECDSA, + PublicKey::ECDSA { curve, .. }, + mpi::SecretKeyMaterial::ECDSA { scalar }) => { + let size = curve.field_size()?; + let secret = Privkey::load_ecdsa( + &scalar.try_into()?, curve.botan_name()?)?; + let sig = secret.sign(digest, "Raw", &mut rng)?; + + if sig.len() != size * 2 { + return Err(Error::MalformedMPI( + format!("Expected signature with length {}, got {}", + size * 2, sig.len())).into()); + } + + Ok(mpi::Signature::ECDSA { + r: MPI::new(&sig[..size]), + s: MPI::new(&sig[size..]), + }) + }, + + (pk_algo, _, _) => Err(Error::InvalidOperation(format!( + "unsupported combination of algorithm {:?}, key {:?}, \ + and secret key {:?}", + pk_algo, self.public(), self.secret())).into()), + }}) + } +} + +impl Decryptor for KeyPair { + fn public(&self) -> &Key<key::PublicParts, key::UnspecifiedRole> { + KeyPair::public(self) + } + + fn decrypt(&mut self, ciphertext: &mpi::Ciphertext, + plaintext_len: Option<usize>) + -> Result<SessionKey> + { + fn bad(e: impl ToString) -> anyhow::Error { + // XXX: Not a great error to return. + Error::MalformedMessage(e.to_string()).into() + } + + self.secret().map( + |secret| Ok(match (self.public().mpis(), secret, ciphertext) + { + (PublicKey::RSA { e, .. }, + mpi::SecretKeyMaterial::RSA { p, q, .. }, + mpi::Ciphertext::RSA { c }) => { + let secret = Privkey::load_rsa(&p.try_into()?, &q.try_into()?, + &e.try_into()?)?; + secret.decrypt(c.value(), "PKCS1v15")?.into() + }, + + (PublicKey::ElGamal{ p, g, .. }, + mpi::SecretKeyMaterial::ElGamal{ x }, + mpi::Ciphertext::ElGamal{ e, c }) => { + // OpenPGP encodes E and C separately, but our + // cryptographic library expects them to be + // concatenated. + let size = p.value().len(); + let mut ctxt = Vec::with_capacity(2 * size); + + // We need to zero-pad them at the front, because + // the MPI encoding drops leading zero bytes. + ctxt.extend_from_slice(&e.value_padded(size).map_err(bad)?); + ctxt.extend_from_slice(&c.value_padded(size).map_err(bad)?); + + let secret = + Privkey::load_elgamal(&p.try_into()?, &g.try_into()?, + &x.try_into()?)?; + secret.decrypt(&ctxt, "PKCS1v15")?.into() + }, + + (PublicKey::ECDH{ .. }, + mpi::SecretKeyMaterial::ECDH { .. }, + mpi::Ciphertext::ECDH { .. }) => + crate::crypto::ecdh::decrypt(self.public(), secret, ciphertext)?, + + (public, secret, ciphertext) => + return Err(Error::InvalidOperation(format!( + "unsupported combination of key pair {:?}/{:?} \ + and ciphertext {:?}", + public, secret, ciphertext)).into()), + })) + } +} + + +impl<P: key::KeyParts, R: key::KeyRole> Key<P, R> { + /// Encrypts the given data with this key. + pub fn encrypt(&self, data: &SessionKey) -> Result<mpi::Ciphertext> { + use crate::PublicKeyAlgorithm::*; + + #[allow(deprecated)] + match (self.pk_algo(), self.mpis()) { + (RSAEncryptSign, mpi::PublicKey::RSA { e, n }) | + (RSAEncrypt, mpi::PublicKey::RSA { e, n }) => { + // The ciphertext has the length of the modulus. + let ciphertext_len = n.value().len(); + if data.len() + 11 > ciphertext_len { + return Err(Error::InvalidArgument( + "Plaintext data too large".into()).into()); + } + + let mut rng = RandomNumberGenerator::new_userspace()?; + let pk = + Pubkey::load_rsa(&n.try_into()?, &e.try_into()?)?; + let esk = pk.encrypt(data, "PKCS1v15", &mut rng)?; + Ok(mpi::Ciphertext::RSA { + c: MPI::new(&esk), + }) + }, + + (ElGamalEncryptSign, mpi::PublicKey::ElGamal { p, g, y }) | + (ElGamalEncrypt, mpi::PublicKey::ElGamal { p, g, y }) => { + // OpenPGP encodes E and C separately, but our + // cryptographic library concatenates them. + let size = p.value().len(); + + let mut rng = RandomNumberGenerator::new_userspace()?; + let pk = + Pubkey::load_elgamal(&p.try_into()?, &g.try_into()?, + &y.try_into()?)?; + let esk = pk.encrypt(data, "PKCS1v15", &mut rng)?; + + if esk.len() != size * 2 { + return Err(Error::MalformedMPI( + format!("Expected ciphertext with length {}, got {}", + size * 2, esk.len())).into()); + } + + Ok(mpi::Ciphertext::ElGamal { + e: MPI::new(&esk[..size]), + c: MPI::new(&esk[size..]), + }) + }, + + (ECDH, mpi::PublicKey::ECDH { .. }) => + crate::crypto::ecdh::encrypt(self.parts_as_public(), data), + + _ => return Err(Error::MalformedPacket(format!( + "unsupported combination of key {} and mpis {:?}.", + self.pk_algo(), self.mpis())).into()), + } + } + + /// Verifies the given signature. + pub fn verify(&self, sig: &mpi::Signature, hash_algo: HashAlgorithm, + digest: &[u8]) -> Result<()> + { + use crate::crypto::mpi::Signature; + + fn bad(e: impl ToString) -> anyhow::Error { + Error::BadSignature(e.to_string()).into() + } + + let ok = match (self.mpis(), sig) { + (PublicKey::RSA { e, n }, Signature::RSA { s }) => { + let pk = Pubkey::load_rsa(&n.try_into()?, &e.try_into()?)?; + pk.verify(digest, s.value(), + &format!("PKCS1v15(Raw,{})", hash_algo.botan_name()?))? + }, + (PublicKey::DSA { y, q, p, g }, Signature::DSA { s, r }) => { + // OpenPGP encodes R and S separately, but our + // cryptographic library expects them to be + // concatenated. + let size = q.value().len(); + let mut sig = Vec::with_capacity(2 * size); + + // We need to zero-pad them at the front, because + // the MPI encoding drops leading zero bytes. + sig.extend_from_slice(&r.value_padded(size).map_err(bad)?); + sig.extend_from_slice(&s.value_padded(size).map_err(bad)?); + + let pk = Pubkey::load_dsa(&p.try_into()?, &q.try_into()?, + &g.try_into()?, &y.try_into()?)?; + let truncated_digest = &digest[..size.min(digest.len())]; + pk.verify(truncated_digest, &sig, "Raw").unwrap() + }, + (PublicKey::EdDSA { curve, q }, Signature::EdDSA { r, s }) => + match curve { + Curve::Ed25519 => { + if q.value().get(0).map(|&b| b != 0x40).unwrap_or(true) { + return Err(Error::MalformedPacket( + "Invalid point encoding".into()).into()); + } + + // OpenPGP encodes R and S separately, but our + // cryptographic library expects them to be + // concatenated. + let mut sig = Vec::with_capacity(64); + + // We need to zero-pad them at the front, because + // the MPI encoding drops leading zero bytes. + sig.extend_from_slice(&r.value_padded(32).map_err(bad)?); + sig.extend_from_slice(&s.value_padded(32).map_err(bad)?); + + let pk = Pubkey::load_ed25519(&q.value()[1..])?; + pk.verify(digest, &sig, "")? + }, + _ => return + Err(Error::UnsupportedEllipticCurve(curve.clone()).into()), + }, + (PublicKey::ECDSA { curve, q }, Signature::ECDSA { s, r }) => + { + // OpenPGP encodes R and S separately, but our + // cryptographic library expects them to be + // concatenated. + let size = curve.field_size()?; + let mut sig = Vec::with_capacity(2 * size); + + // We need to zero-pad them at the front, because + // the MPI encoding drops leading zero bytes. + sig.extend_from_slice(&r.value_padded(size).map_err(bad)?); + sig.extend_from_slice(&s.value_padded(size).map_err(bad)?); + + let (x, y) = q.decode_point(curve)?; + let pk = Pubkey::load_ecdsa(&botan::MPI::new_from_bytes(&x)?, + &botan::MPI::new_from_bytes(&y)?, + curve.botan_name()?)?; + pk.verify(digest, &sig, "Raw")? + }, + _ => return Err(Error::MalformedPacket(format!( + "unsupported combination of key {} and signature {:?}.", + self.pk_algo(), sig)).into()), + }; + + if ok { + Ok(()) + } else { + Err(Error::ManipulatedMessage.into()) + } + } +} + +impl<R> Key4<SecretParts, R> + where R: key::KeyRole, +{ + /// Creates a new OpenPGP secret key packet for an existing X25519 key. + /// + /// The ECDH key will use hash algorithm `hash` and symmetric + /// algorithm `sym`. If one or both are `None` secure defaults + /// will be used. The key will have it's creation date set to + /// `ctime` or the current time if `None` is given. + pub fn import_secret_cv25519<H, S, T>(private_key: &[u8], + hash: H, sym: S, ctime: T) + -> Result<Self> where H: Into<Option<HashAlgorithm>>, + S: Into<Option<SymmetricAlgorithm>>, + T: Into<Option<SystemTime>> + { + let secret = Privkey::load_x25519(private_key)?; + let public = secret.pubkey()?.get_x25519_key()?; + let mut secret = secret.get_x25519_key()?; + + // OpenPGP stores the secret in reverse order. + secret.reverse(); + + use crate::crypto::ecdh; + Self::with_secret( + ctime.into().unwrap_or_else(crate::now), + PublicKeyAlgorithm::ECDH, + mpi::PublicKey::ECDH { + curve: Curve::Cv25519, + hash: hash.into().unwrap_or_else( + || ecdh::default_ecdh_kdf_hash(&Curve::Cv25519)), + sym: sym.into().unwrap_or_else( + || ecdh::default_ecdh_kek_cipher(&Curve::Cv25519)), + q: MPI::new_compressed_point(&public), + }, + mpi::SecretKeyMaterial::ECDH { + scalar: secret.into(), + }.into()) + } + + /// Creates a new OpenPGP secret key packet for an existing Ed25519 key. + /// + /// The ECDH key will use hash algorithm `hash` and symmetric + /// algorithm `sym`. If one or both are `None` secure defaults + /// will be used. The key will have it's creation date set to + /// `ctime` or the current time if `None` is given. + pub fn import_secret_ed25519<T>(private_key: &[u8], ctime: T) + -> Result<Self> where T: Into<Option<SystemTime>> + { + let secret = Privkey::load_ed25519(private_key)?; + let (public, secret) = secret.get_ed25519_key()?; + + Self::with_secret( + ctime.into().unwrap_or_else(crate::now), + PublicKeyAlgorithm::EdDSA, + mpi::PublicKey::EdDSA { + curve: Curve::Ed25519, + q: MPI::new_compressed_point(&public), + }, + mpi::SecretKeyMaterial::EdDSA { + scalar: secret.into(), + }.into()) + } + + /// Creates a new OpenPGP public key packet for an existing RSA key. + /// + /// The RSA key will use public exponent `e` and modulo `n`. The key will + /// have it's creation date set to `ctime` or the current time if `None` + /// is given. + #[allow(clippy::many_single_char_names)] + pub fn import_secret_rsa<T>(d: &[u8], p: &[u8], q: &[u8], ctime: T) + -> Result<Self> where T: Into<Option<SystemTime>> + { + let d = botan::MPI::new_from_bytes(d)?; + let p = botan::MPI::new_from_bytes(p)?; + let q = botan::MPI::new_from_bytes(q)?; + + // Compute e ≡ d⁻¹ (mod 𝜙). + let phi = p.mp_sub_u32(1)?.mp_mul(&q.mp_sub_u32(1)?)?; + let e = botan::MPI::modular_inverse(&d, &phi)?; + + let secret = Privkey::load_rsa(&p.try_into()?, &q.try_into()?, + &e.try_into()?)?; + + let (public, secret) = rsa_rfc4880(secret)?; + Self::with_secret( + crate::now(), + PublicKeyAlgorit |