diff options
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_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/openpgp/src/crypto/backend/rust/ecdh.rs @@ -0,0 +1,99 @@ +//! Elliptic Curve Diffie-Hellman. + +use std::convert::TryInto; + +use rand::rngs::OsRng; |