summaryrefslogtreecommitdiffstats
path: root/openpgp/src/crypto/backend
diff options
context:
space:
mode:
Diffstat (limited to 'openpgp/src/crypto/backend')
-rw-r--r--openpgp/src/crypto/backend/rust.rs64
-rw-r--r--openpgp/src/crypto/backend/rust/aead.rs134
-rw-r--r--openpgp/src/crypto/backend/rust/asymmetric.rs473
-rw-r--r--openpgp/src/crypto/backend/rust/ecdh.rs99
-rw-r--r--openpgp/src/crypto/backend/rust/hash.rs80
-rw-r--r--openpgp/src/crypto/backend/rust/symmetric.rs199
6 files changed, 1049 insertions, 0 deletions
diff --git a/openpgp/src/crypto/backend/rust.rs b/openpgp/src/crypto/backend/rust.rs
new file mode 100644
index 00000000..a661c14e
--- /dev/null
+++ b/openpgp/src/crypto/backend/rust.rs
@@ -0,0 +1,64 @@
+//! Implementation of Sequoia crypto API using pure Rust cryptographic
+//! libraries.
+
+use crate::types::*;
+
+pub mod aead;
+pub mod asymmetric;
+pub mod ecdh;
+pub mod hash;
+pub mod symmetric;
+
+/// Fills the given buffer with random data.
+///
+/// Fills the given buffer with random data produced by a
+/// cryptographically secure pseudorandom number generator (CSPRNG).
+/// The output may be used as session keys or to derive long-term
+/// cryptographic keys from.
+pub fn random<B: AsMut<[u8]>>(mut buf: B) {
+ use rand::rngs::OsRng;
+ use rand::RngCore;
+
+ OsRng.fill_bytes(buf.as_mut())
+}
+
+impl PublicKeyAlgorithm {
+ pub(crate) fn is_supported_by_backend(&self) -> bool {
+ use PublicKeyAlgorithm::*;
+ #[allow(deprecated)]
+ match &self {
+ RSAEncryptSign | RSAEncrypt | RSASign | ECDH | EdDSA
+ => true,
+ DSA | ECDSA
+ => false,
+ ElGamalEncrypt | ElGamalEncryptSign | Private(_) | Unknown(_)
+ => false,
+ }
+ }
+}
+
+impl Curve {
+ pub(crate) fn is_supported_by_backend(&self) -> bool {
+ use self::Curve::*;
+ match &self {
+ NistP256 | NistP384 | NistP521
+ => false,
+ Ed25519 | Cv25519
+ => true,
+ BrainpoolP256 | BrainpoolP512 | Unknown(_)
+ => false,
+ }
+ }
+}
+
+impl AEADAlgorithm {
+ pub(crate) fn is_supported_by_backend(&self) -> bool {
+ use self::AEADAlgorithm::*;
+ match &self {
+ EAX
+ => true,
+ OCB | Private(_) | Unknown(_)
+ => false,
+ }
+ }
+}
diff --git a/openpgp/src/crypto/backend/rust/aead.rs b/openpgp/src/crypto/backend/rust/aead.rs
new file mode 100644
index 00000000..ab2ca208
--- /dev/null
+++ b/openpgp/src/crypto/backend/rust/aead.rs
@@ -0,0 +1,134 @@
+//! Implementation of AEAD using pure Rust cryptographic libraries.
+
+use std::cmp;
+
+use cipher::{BlockCipher, NewBlockCipher};
+use cipher::block::Block;
+use cipher::consts::U16;
+use eax::online::{Eax, Encrypt, Decrypt};
+use generic_array::{ArrayLength, GenericArray};
+
+use crate::{Error, Result};
+use crate::crypto::aead::{Aead, CipherOp};
+use crate::seal;
+use crate::types::{AEADAlgorithm, SymmetricAlgorithm};
+
+trait GenericArrayExt {
+ const LEN: usize;
+}
+
+impl<T, N: ArrayLength<T>> GenericArrayExt for GenericArray<T, N> {
+ const LEN: usize = N::USIZE;
+}
+
+impl<Cipher> Aead for Eax<Cipher, Encrypt>
+where
+ Cipher: BlockCipher<BlockSize = U16> + NewBlockCipher + Clone,
+ Cipher::ParBlocks: ArrayLength<Block<Cipher>>,
+{
+ fn update(&mut self, ad: &[u8]) {
+ self.update_assoc(ad)
+ }
+
+ fn digest_size(&self) -> usize {
+ eax::Tag::LEN
+ }
+
+ fn digest(&mut self, digest: &mut [u8]) {
+ let tag = self.tag_clone();
+ digest[..tag.len()].copy_from_slice(&tag[..]);
+ }
+
+ fn encrypt(&mut self, dst: &mut [u8], src: &[u8]) {
+ let len = cmp::min(dst.len(), src.len());
+ dst[..len].copy_from_slice(&src[..len]);
+ Self::encrypt(self, &mut dst[..len])
+ }
+
+ fn decrypt(&mut self, _dst: &mut [u8], _src: &[u8]) {
+ panic!("AEAD decryption called in the encryption context")
+ }
+}
+
+impl<Cipher> Aead for Eax<Cipher, Decrypt>
+where
+ Cipher: BlockCipher<BlockSize = U16> + NewBlockCipher + Clone,
+ Cipher::ParBlocks: ArrayLength<Block<Cipher>>,
+{
+ fn update(&mut self, ad: &[u8]) {
+ self.update_assoc(ad)
+ }
+
+ fn digest_size(&self) -> usize {
+ eax::Tag::LEN
+ }
+
+ fn digest(&mut self, digest: &mut [u8]) {
+ let tag = self.tag_clone();
+ digest[..tag.len()].copy_from_slice(&tag[..]);
+ }
+
+ fn encrypt(&mut self, _dst: &mut [u8], _src: &[u8]) {
+ panic!("AEAD encryption called in the decryption context")
+ }
+
+ fn decrypt(&mut self, dst: &mut [u8], src: &[u8]) {
+ let len = core::cmp::min(dst.len(), src.len());
+ dst[..len].copy_from_slice(&src[..len]);
+ self.decrypt_unauthenticated_hazmat(&mut dst[..len])
+ }
+}
+
+impl<Cipher, Op> seal::Sealed for Eax<Cipher, Op>
+where
+ Cipher: BlockCipher<BlockSize = U16> + NewBlockCipher + Clone,
+ Cipher::ParBlocks: ArrayLength<Block<Cipher>>,
+ Op: eax::online::CipherOp,
+{}
+
+impl AEADAlgorithm {
+ pub(crate) fn context(
+ &self,
+ sym_algo: SymmetricAlgorithm,
+ key: &[u8],
+ nonce: &[u8],
+ op: CipherOp,
+ ) -> Result<Box<dyn Aead>> {
+ match self {
+ AEADAlgorithm::EAX => match sym_algo {
+ SymmetricAlgorithm::AES128 => match op {
+ CipherOp::Encrypt => Ok(Box::new(
+ Eax::<aes::Aes128, Encrypt>::with_key_and_nonce(key.into(), nonce.into()))),
+ CipherOp::Decrypt => Ok(Box::new(
+ Eax::<aes::Aes128, Decrypt>::with_key_and_nonce(key.into(), nonce.into()))),
+ }
+ SymmetricAlgorithm::AES192 => match op {
+ CipherOp::Encrypt => Ok(Box::new(
+ Eax::<aes::Aes192, Encrypt>::with_key_and_nonce(key.into(), nonce.into()))),
+ CipherOp::Decrypt => Ok(Box::new(
+ Eax::<aes::Aes192, Decrypt>::with_key_and_nonce(key.into(), nonce.into()))),
+ }
+ SymmetricAlgorithm::AES256 => match op {
+ CipherOp::Encrypt => Ok(Box::new(
+ Eax::<aes::Aes256, Encrypt>::with_key_and_nonce(key.into(), nonce.into()))),
+ CipherOp::Decrypt => Ok(Box::new(
+ Eax::<aes::Aes256, Decrypt>::with_key_and_nonce(key.into(), nonce.into()))),
+ }
+ | SymmetricAlgorithm::IDEA
+ | SymmetricAlgorithm::TripleDES
+ | SymmetricAlgorithm::CAST5
+ | SymmetricAlgorithm::Blowfish
+ | SymmetricAlgorithm::Twofish
+ | SymmetricAlgorithm::Camellia128
+ | SymmetricAlgorithm::Camellia192
+ | SymmetricAlgorithm::Camellia256
+ | SymmetricAlgorithm::Private(_)
+ | SymmetricAlgorithm::Unknown(_)
+ | SymmetricAlgorithm::Unencrypted =>
+ Err(Error::UnsupportedSymmetricAlgorithm(sym_algo).into()),
+ },
+ AEADAlgorithm::OCB | AEADAlgorithm::Private(_) | AEADAlgorithm::Unknown(_) =>
+ Err(Error::UnsupportedAEADAlgorithm(*self).into()),
+ }
+ }
+}
diff --git a/openpgp/src/crypto/backend/rust/asymmetric.rs b/openpgp/src/crypto/backend/rust/asymmetric.rs
new file mode 100644
index 00000000..b09f6214
--- /dev/null
+++ b/openpgp/src/crypto/backend/rust/asymmetric.rs
@@ -0,0 +1,473 @@
+//! Holds the implementation of [`Signer`] and [`Decryptor`] for [`KeyPair`].
+//!
+//! [`Signer`]: ../../asymmetric/trait.Signer.html
+//! [`Decryptor`]: ../../asymmetric/trait.Decryptor.html
+//! [`KeyPair`]: ../../asymmetric/struct.KeyPair.html
+
+use std::convert::TryFrom;
+use std::time::SystemTime;
+
+use num_bigint_dig::{traits::ModInverse, BigUint};
+use rand::rngs::OsRng;
+use rsa::{PaddingScheme, RSAPublicKey, RSAPrivateKey, PublicKey, PublicKeyParts, Hash};
+
+use crate::{Error, Result};
+use crate::crypto::asymmetric::{KeyPair, Decryptor, Signer};
+use crate::crypto::mem::Protected;
+use crate::crypto::mpi::{self, MPI, ProtectedMPI};
+use crate::crypto::SessionKey;
+use crate::packet::{key, Key};
+use crate::packet::key::{Key4, SecretParts};
+use crate::types::{Curve, HashAlgorithm, PublicKeyAlgorithm, SymmetricAlgorithm};
+
+const CURVE25519_SIZE: usize = 32;
+
+fn pkcs1_padding(hash_algo: HashAlgorithm) -> Result<PaddingScheme> {
+ let hash = match hash_algo {
+ HashAlgorithm::MD5 => Hash::MD5,
+ HashAlgorithm::SHA1 => Hash::SHA1,
+ HashAlgorithm::SHA224 => Hash::SHA2_224,
+ HashAlgorithm::SHA256 => Hash::SHA2_256,
+ HashAlgorithm::SHA384 => Hash::SHA2_384,
+ HashAlgorithm::SHA512 => Hash::SHA2_512,
+ HashAlgorithm::RipeMD => Hash::RIPEMD160,
+ _ => return Err(Error::InvalidArgument(format!(
+ "Algorithm {:?} not representable", hash_algo)).into()),
+ };
+ Ok(PaddingScheme::PKCS1v15Sign {
+ hash: Some(hash)
+ })
+}
+
+fn rsa_public_key(e: &MPI, n: &MPI) -> Result<RSAPublicKey> {
+ let n = BigUint::from_bytes_be(n.value());
+ let e = BigUint::from_bytes_be(e.value());
+ Ok(RSAPublicKey::new(n, e)?)
+}
+
+fn rsa_private_key(e: &MPI, n: &MPI, p: &ProtectedMPI, q: &ProtectedMPI, d: &ProtectedMPI)
+ -> RSAPrivateKey
+{
+ let n = BigUint::from_bytes_be(n.value());
+ let e = BigUint::from_bytes_be(e.value());
+ let p = BigUint::from_bytes_be(p.value());
+ let q = BigUint::from_bytes_be(q.value());
+ let d = BigUint::from_bytes_be(d.value());
+ RSAPrivateKey::from_components(n, e, d, vec![p, q])
+}
+
+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::*;
+ #[allow(deprecated)]
+ self.secret().map(|secret| match (self.public().pk_algo(), self.public().mpis(), secret) {
+ (RSAEncryptSign,
+ mpi::PublicKey::RSA { e, n },
+ mpi::SecretKeyMaterial::RSA { p, q, d, .. }) |
+ (RSASign,
+ mpi::PublicKey::RSA { e, n },
+ mpi::SecretKeyMaterial::RSA { p, q, d, .. }) => {
+ let key = rsa_private_key(e, n, p, q, d);
+ let padding = pkcs1_padding(hash_algo)?;
+ let sig = key.sign(padding, digest)?;
+ Ok(mpi::Signature::RSA {
+ s: mpi::MPI::new(&sig),
+ })
+ },
+
+ (PublicKeyAlgorithm::DSA,
+ mpi:: PublicKey::DSA { .. },
+ mpi::SecretKeyMaterial::DSA { .. }) => {
+ Err(Error::UnsupportedPublicKeyAlgorithm(PublicKeyAlgorithm::DSA).into())
+ },
+
+ (PublicKeyAlgorithm::ECDSA,
+ mpi::PublicKey::ECDSA { .. },
+ mpi::SecretKeyMaterial::ECDSA { .. }) => {
+ Err(Error::UnsupportedPublicKeyAlgorithm(PublicKeyAlgorithm::ECDSA).into())
+ },
+
+ (EdDSA,
+ mpi::PublicKey::EdDSA { curve, q },
+ mpi::SecretKeyMaterial::EdDSA { scalar }) => match curve
+ {
+ Curve::Ed25519 => {
+ use ed25519_dalek::{Keypair, Signer};
+ use ed25519_dalek::{PUBLIC_KEY_LENGTH, SECRET_KEY_LENGTH};
+
+ let (public, ..) = q.decode_point(&Curve::Ed25519)?;
+ assert_eq!(public.len(), PUBLIC_KEY_LENGTH);
+
+ // It's expected for the private key to be exactly
+ // SECRET_KEY_LENGTH bytes long but OpenPGP allows leading
+ // zeros to be stripped.
+ // Padding has to be unconditional; otherwise we have a
+ // secret-dependent branch.
+ let mut keypair = Protected::from(
+ vec![0u8; SECRET_KEY_LENGTH + PUBLIC_KEY_LENGTH]
+ );
+ keypair.as_mut()[..SECRET_KEY_LENGTH]
+ .copy_from_slice(
+ &scalar.value_padded(SECRET_KEY_LENGTH));
+ keypair.as_mut()[SECRET_KEY_LENGTH..]
+ .copy_from_slice(&public);
+ let pair = Keypair::from_bytes(&keypair)?;
+
+ let sig = pair.sign(digest).to_bytes();
+
+ // https://tools.ietf.org/html/rfc8032#section-5.1.6
+ let (r, s) = sig.split_at(sig.len() / 2);
+ Ok(mpi::Signature::EdDSA {
+ r: mpi::MPI::new(r),
+ s: mpi::MPI::new(s),
+ })
+ },
+ _ => Err(Error::UnsupportedEllipticCurve(curve.clone()).into()),
+ },
+
+ (pk_algo, _, _) => Err(Error::InvalidOperation(format!(
+ "unsupported combination of algorithm {:?}, key {:?}, \
+ and secret key {:?}",
+ pk_algo, self.public(), self.secret())))?,
+ })
+ }
+}
+
+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>
+ {
+ use crate::PublicKeyAlgorithm::*;
+ self.secret().map(|secret| match (self.public().mpis(), secret, ciphertext) {
+ (mpi::PublicKey::RSA { e, n },
+ mpi::SecretKeyMaterial::RSA { p, q, d, .. },
+ mpi::Ciphertext::RSA { c }) => {
+ let key = rsa_private_key(e, n, p, q, d);
+ let padding = PaddingScheme::PKCS1v15Encrypt;
+ let decrypted = key.decrypt(padding, c.value())?;
+ Ok(SessionKey::from(decrypted))
+ }
+
+ (mpi::PublicKey::ElGamal { .. },
+ mpi::SecretKeyMaterial::ElGamal { .. },
+ mpi::Ciphertext::ElGamal { .. }) =>
+ Err(Error::UnsupportedPublicKeyAlgorithm(ElGamalEncrypt).into()),
+
+ (mpi::PublicKey::ECDH { .. },
+ mpi::SecretKeyMaterial::ECDH { .. },
+ mpi::Ciphertext::ECDH { .. }) =>
+ crate::crypto::ecdh::decrypt(self.public(), secret, ciphertext),
+
+ (public, secret, ciphertext) =>
+ 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 PublicKeyAlgorithm::*;
+ #[allow(deprecated)]
+ match self.pk_algo() {
+ RSAEncryptSign | RSAEncrypt => match self.mpis() {
+ 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 key = rsa_public_key(e, n)?;
+ let padding = PaddingScheme::PKCS1v15Encrypt;
+ let ciphertext = key.encrypt(&mut OsRng, padding, data.as_ref())?;
+ Ok(mpi::Ciphertext::RSA {
+ c: mpi::MPI::new(&ciphertext)
+ })
+ }
+ pk => Err(Error::MalformedPacket(format!(
+ "Key: Expected RSA public key, got {:?}", pk)).into())
+ }
+ ECDH => crate::crypto::ecdh::encrypt(self.parts_as_public(), data),
+ algo => Err(Error::UnsupportedPublicKeyAlgorithm(algo).into()),
+ }
+ }
+
+ /// Verifies the given signature.
+ pub fn verify(&self, sig: &mpi::Signature, hash_algo: HashAlgorithm,
+ digest: &[u8]) -> Result<()>
+ {
+ match (self.mpis(), sig) {
+ (mpi::PublicKey::RSA { e, n }, mpi::Signature::RSA { s }) => {
+ let key = rsa_public_key(e, n)?;
+ let padding = pkcs1_padding(hash_algo)?;
+ key.verify(padding, digest, s.value())?;
+ Ok(())
+ }
+ (mpi::PublicKey::DSA { .. },
+ mpi::Signature::DSA { .. }) => {
+ Err(Error::UnsupportedPublicKeyAlgorithm(PublicKeyAlgorithm::DSA).into())
+ },
+ (mpi::PublicKey::ECDSA { .. },
+ mpi::Signature::ECDSA { .. }) => {
+ Err(Error::UnsupportedPublicKeyAlgorithm(PublicKeyAlgorithm::ECDSA).into())
+ },
+ (mpi::PublicKey::EdDSA { curve, q },
+ mpi::Signature::EdDSA { r, s }) => match curve {
+ Curve::Ed25519 => {
+ use ed25519_dalek::{PublicKey, Signature, SIGNATURE_LENGTH};
+ use ed25519_dalek::{Verifier};
+
+ let (public, ..) = q.decode_point(&Curve::Ed25519)?;
+ assert_eq!(public.len(), 32);
+
+ let key = PublicKey::from_bytes(public).map_err(|e| {
+ Error::InvalidKey(e.to_string())
+ })?;
+
+ // OpenPGP encodes R and S separately, but our
+ // cryptographic library expects them to be
+ // concatenated.
+ let mut sig_bytes = [0u8; SIGNATURE_LENGTH];
+
+ // We need to zero-pad them at the front, because
+ // the MPI encoding drops leading zero bytes.
+ let half = SIGNATURE_LENGTH / 2;
+ sig_bytes[..half].copy_from_slice(
+ &r.value_padded(half).map_err(bad)?);
+ sig_bytes[half..].copy_from_slice(
+ &s.value_padded(half).map_err(bad)?);
+
+ let signature = Signature::from(sig_bytes);
+
+ key.verify(digest, &signature)
+ .map_err(|e| Error::BadSignature(e.to_string()))?;
+ Ok(())
+ },
+ _ => Err(Error::UnsupportedEllipticCurve(curve.clone()).into()),
+ },
+ _ => Err(Error::MalformedPacket(format!(
+ "unsupported combination of key {} and signature {:?}.",
+ self.pk_algo(), sig)).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>>
+ {
+ use x25519_dalek::{PublicKey, StaticSecret};
+
+ let secret = StaticSecret::from(<[u8; 32]>::try_from(private_key)?);
+ let public_key = PublicKey::from(&secret);
+
+ let mut private_key = Vec::from(private_key);
+ private_key.reverse();
+
+ Self::with_secret(
+ ctime.into().unwrap_or_else(SystemTime::now),
+ PublicKeyAlgorithm::ECDH,
+ mpi::PublicKey::ECDH {
+ curve: Curve::Cv25519,
+ hash: hash.into().unwrap_or(HashAlgorithm::SHA512),
+ sym: sym.into().unwrap_or(SymmetricAlgorithm::AES256),
+ q: MPI::new_compressed_point(&*public_key.as_bytes()),
+ },
+ mpi::SecretKeyMaterial::ECDH {
+ scalar: private_key.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>>
+ {
+ use ed25519_dalek::{PublicKey, SecretKey};
+
+ let private = SecretKey::from_bytes(private_key).map_err(|e| {
+ Error::InvalidKey(e.to_string())
+ })?;
+
+ // Mark MPI as compressed point with 0x40 prefix. See
+ // https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-07#section-13.2.
+ let mut public = [0u8; 1 + CURVE25519_SIZE];
+ public[0] = 0x40;
+ &mut public[1..].copy_from_slice(Into::<PublicKey>::into(&private).as_bytes());
+
+ Self::with_secret(
+ ctime.into().unwrap_or_else(SystemTime::now),
+ PublicKeyAlgorithm::EdDSA,
+ mpi::PublicKey::EdDSA {
+ curve: Curve::Ed25519,
+ q: mpi::MPI::new(&public)
+ },
+ mpi::SecretKeyMaterial::EdDSA {
+ scalar: mpi::MPI::new(&private_key).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.
+ pub fn import_secret_rsa<T>(d: &[u8], p: &[u8], q: &[u8], ctime: T)
+ -> Result<Self> where T: Into<Option<SystemTime>>
+ {
+ // RFC 4880: `p < q`
+ let (p, q) = if p < q { (p, q) } else { (q, p) };
+
+ // RustCrypto can't compute the public key from the private one, so do it ourselves
+ let big_p = BigUint::from_bytes_be(p);
+ let big_q = BigUint::from_bytes_be(q);
+ let n = big_p.clone() * big_q.clone();
+
+ let big_d = BigUint::from_bytes_be(d);
+ let big_phi = (big_p.clone() - 1u32) * (big_q.clone() - 1u32);
+ let e = big_d.mod_inverse(big_phi) // e ≡ d⁻¹ (mod 𝜙)
+ .and_then(|x| x.to_biguint())
+ .ok_or_else(|| Error::MalformedMPI("RSA: `d` and `(p-1)(q-1)` aren't coprime".into()))?;
+
+ let u: BigUint = big_p.mod_inverse(big_q) // RFC 4880: u ≡ p⁻¹ (mod q)
+ .and_then(|x| x.to_biguint())
+ .ok_or_else(|| Error::MalformedMPI("RSA: `p` and `q` aren't coprime".into()))?;
+
+ Self::with_secret(
+ ctime.into().unwrap_or_else(SystemTime::now),
+ PublicKeyAlgorithm::RSAEncryptSign,
+ mpi::PublicKey::RSA {
+ e: mpi::MPI::new(&e.to_bytes_be()),
+ n: mpi::MPI::new(&n.to_bytes_be()),
+ },
+ mpi::SecretKeyMaterial::RSA {
+ d: mpi::MPI::new(d).into(),
+ p: mpi::MPI::new(p).into(),
+ q: mpi::MPI::new(q).into(),
+ u: mpi::MPI::new(&u.to_bytes_be()).into(),
+ }.into()
+ )
+ }
+
+ /// Generates a new RSA key with a public modulos of size `bits`.
+ pub fn generate_rsa(bits: usize) -> Result<Self> {
+ let key = RSAPrivateKey::new(&mut OsRng, bits)?;
+ let (p, q) = match key.primes() {
+ [p, q] => (p, q),
+ _ => panic!("RSA key generation resulted in wrong number of primes"),
+ };
+ let u = p.mod_inverse(q) // RFC 4880: u ≡ p⁻¹ (mod q)
+ .and_then(|x| x.to_biguint())
+ .expect("rsa crate did not generate coprime p and q");
+
+ let public = mpi::PublicKey::RSA {
+ e: mpi::MPI::new(&key.to_public_key().e().to_bytes_be()),
+ n: mpi::MPI::new(&key.to_public_key().n().to_bytes_be()),
+ };
+
+ let private = mpi::SecretKeyMaterial::RSA {
+ p: mpi::MPI::new(&p.to_bytes_be()).into(),
+ q: mpi::MPI::new(&q.to_bytes_be()).into(),
+ d: mpi::MPI::new(&key.d().to_bytes_be()).into(),
+ u: mpi::MPI::new(&u.to_bytes_be()).into(),
+ };
+
+ Self::with_secret(
+ SystemTime::now(),
+ PublicKeyAlgorithm::RSAEncryptSign,
+ public,
+ private.into(),
+ )
+ }
+
+ /// Generates a new ECC key over `curve`.
+ ///
+ /// If `for_signing` is false a ECDH key, if it's true either a
+ /// EdDSA or ECDSA key is generated. Giving `for_signing == true` and
+ /// `curve == Cv25519` will produce an error. Likewise
+ /// `for_signing == false` and `curve == Ed25519` will produce an error.
+ pub fn generate_ecc(for_signing: bool, curve: Curve) -> Result<Self> {
+ let (algo, public, private) = match (&curve, for_signing) {
+ (Curve::Ed25519, true) => {
+ use ed25519_dalek::Keypair;
+
+ let Keypair { public, secret } = Keypair::generate(&mut OsRng);
+
+ let secret: Protected = secret.as_bytes().as_ref().into();
+
+ // Mark MPI as compressed point with 0x40 prefix. See
+ // https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-07#section-13.2.
+ let mut compressed_public = [0u8; 1 + CURVE25519_SIZE];
+ compressed_public[0] = 0x40;
+ &mut compressed_public[1..].copy_from_slice(public.as_bytes());
+
+ (
+ PublicKeyAlgorithm::EdDSA,
+ mpi::PublicKey::EdDSA { curve, q: mpi::MPI::new(&compressed_public) },
+ mpi::SecretKeyMaterial::EdDSA { scalar: secret.into() },
+ )
+ }
+
+ (Curve::Cv25519, false) => {
+ use x25519_dalek::{StaticSecret, PublicKey};
+
+ let private_key = StaticSecret::new(OsRng);
+ let public_key = PublicKey::from(&private_key);
+
+ let mut private_key = Vec::from(private_key.to_bytes());
+ private_key.reverse();
+
+ let public_mpis = mpi::PublicKey::ECDH {
+ curve: Curve::Cv25519,
+ q: MPI::new_compressed_point(&*public_key.as_bytes()),
+ hash: HashAlgorithm::SHA256,
+ sym: SymmetricAlgorithm::AES256,
+ };
+ let private_mpis = mpi::SecretKeyMaterial::ECDH {
+ scalar: private_key.into(),
+ };
+
+ (PublicKeyAlgorithm::ECDH, public_mpis, private_mpis.into())
+ }
+
+ _ => {
+ return Err(Error::UnsupportedEllipticCurve(curve).into());
+ }
+ };
+ Self::with_secret(SystemTime::now(), algo, public, private.into())
+ }
+}
+
diff --git a/openpgp/src/crypto/backend/rust/ecdh.rs b/openpgp/src/crypto/backend/rust/ecdh.rs
new file mode 100644
index 00000000..519782f4
--- /dev/null
+++ b/