diff options
author | Nikhil Benesch <nikhil.benesch@gmail.com> | 2020-12-10 20:46:58 -0500 |
---|---|---|
committer | Justus Winter <justus@sequoia-pgp.org> | 2021-10-05 11:46:38 +0200 |
commit | 341fdd29a9863e793c560e2a7207989c4f61d772 (patch) | |
tree | 38ffda8be4d3ebce675e6561a3a619882934495e /openpgp/src/crypto/backend | |
parent | aa21e2404d9502eeea84ff39da03a85c971ea2d3 (diff) |
openpgp: Add a RustCrypto backend.
- This adds a cryptographic backend based on the RustCrypto crates.
The backend is marked as experimental, as the RustCrypto crates'
authors state that they have not been audited and may not perform
computations in constant time. Nevertheless, it may be useful in
certain environments, e.g. WebAssembly.
- The backend implements RSA, EdDSA and ECDH over Curve25519, IDEA,
3DES, CAST5, Blowfish, AES, Twofish, EAX, MD5, SHA1, RipeMD160, and
the SHA2 family.
- Notably missing are DSA, ElGamal, and ECDSA and ECDH over the NIST
curves.
- See #333.
Diffstat (limited to 'openpgp/src/crypto/backend')
-rw-r--r-- | openpgp/src/crypto/backend/rust.rs | 64 | ||||
-rw-r--r-- | openpgp/src/crypto/backend/rust/aead.rs | 134 | ||||
-rw-r--r-- | openpgp/src/crypto/backend/rust/asymmetric.rs | 473 | ||||
-rw-r--r-- | openpgp/src/crypto/backend/rust/ecdh.rs | 99 | ||||
-rw-r--r-- | openpgp/src/crypto/backend/rust/hash.rs | 80 | ||||
-rw-r--r-- | openpgp/src/crypto/backend/rust/symmetric.rs | 199 |
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 |