summaryrefslogtreecommitdiffstats
path: root/openpgp
diff options
context:
space:
mode:
authorNikhil Benesch <nikhil.benesch@gmail.com>2020-12-10 20:46:58 -0500
committerJustus Winter <justus@sequoia-pgp.org>2021-10-05 11:46:38 +0200
commit341fdd29a9863e793c560e2a7207989c4f61d772 (patch)
tree38ffda8be4d3ebce675e6561a3a619882934495e /openpgp
parentaa21e2404d9502eeea84ff39da03a85c971ea2d3 (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')
-rw-r--r--openpgp/Cargo.toml34
-rw-r--r--openpgp/NEWS2
-rw-r--r--openpgp/README.md7
-rw-r--r--openpgp/build.rs6
-rw-r--r--openpgp/src/crypto/aead.rs8
-rw-r--r--openpgp/src/crypto/backend.rs5
-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
12 files changed, 1107 insertions, 4 deletions
diff --git a/openpgp/Cargo.toml b/openpgp/Cargo.toml
index 1d1fa3de..cd285ebe 100644
--- a/openpgp/Cargo.toml
+++ b/openpgp/Cargo.toml
@@ -45,12 +45,33 @@ thiserror = "1.0.2"
backtrace = "0.3.3"
unicode-normalization = "0.1.9"
+# RustCrypto crates.
+aes = { version = "0.6.0", optional = true }
+block-modes = { version = "0.7.0", optional = true }
+block-padding = { version = "0.2.1", optional = true }
+blowfish = { version = "0.7.0", optional = true }
+cast5 = { version = "0.9.0", optional = true }
+cipher = { version = "0.2.5", optional = true, features = ["std"] }
+des = { version = "0.6.0", optional = true }
+digest = { version = "0.9.0", optional = true }
+eax = { version = "0.3.0", optional = true }
+ed25519-dalek = { version = "1", default-features = false, features = ["rand", "u64_backend"], optional = true }
+generic-array = { version = "0.14.4", optional = true }
+idea = { version = "0.3.0", optional = true }
+md-5 = { version = "0.9.1", optional = true }
+num-bigint-dig = { version = "0.6", default-features = false, optional = true }
+rand = { version = "0.7.3", optional = true }
+ripemd160 = { version = "0.9.1", optional = true }
+rsa = { version = "0.3.0", optional = true }
+sha-1 = { version = "0.9.2", optional = true }
+sha2 = { version = "0.9.2", optional = true }
+twofish = { version = "0.5.0", optional = true }
+typenum = { version = "1.12.0", optional = true }
+x25519-dalek = { version = "1.1.0", optional = true }
+
[target.'cfg(windows)'.dependencies]
win-crypto-ng = { version = "0.4", features = ["rand", "block-cipher"], optional = true }
-num-bigint-dig = { version = "0.6", default-features = false, optional = true }
-ed25519-dalek = { version = "1", default-features = false, features = ["rand", "u64_backend"], optional = true }
winapi = { version = "0.3.8", default-features = false, features = ["bcrypt"], optional = true }
-eax = "0.3"
[target.'cfg(all(target_arch = "wasm32", target_os = "unknown"))'.dependencies]
chrono = { version = "0.4.10", default-features = false, features = ["std"] }
@@ -68,7 +89,12 @@ criterion = { version = "0.3.4", features = ["html_reports"] }
default = ["compression", "crypto-nettle"]
# TODO(#333): Allow for/implement more backends
crypto-nettle = ["nettle"]
-crypto-cng = ["winapi", "win-crypto-ng", "ed25519-dalek", "num-bigint-dig"]
+crypto-rust = [
+ "aes", "block-modes", "block-padding", "blowfish", "cast5", "cipher", "des",
+ "digest", "eax", "ed25519-dalek", "generic-array", "idea", "md-5", "num-bigint-dig", "rand",
+ "ripemd160", "rsa", "sha-1", "sha2", "twofish", "typenum", "x25519-dalek"
+]
+crypto-cng = ["eax", "winapi", "win-crypto-ng", "ed25519-dalek", "num-bigint-dig"]
# Experimental and variable-time cryptographic backends opt-ins
allow-experimental-crypto = []
diff --git a/openpgp/NEWS b/openpgp/NEWS
index d17c9c08..739f53ec 100644
--- a/openpgp/NEWS
+++ b/openpgp/NEWS
@@ -3,6 +3,8 @@
#+STARTUP: content hidestars
* Changes in 1.4.0
+** New cryptographic backends
+ - We added a backend based on the RustCrypto crates.
** New functionality
- CipherSuite::is_supported
- MPI::value_padded
diff --git a/openpgp/README.md b/openpgp/README.md
index 3e0c81c2..c4bfa9e8 100644
--- a/openpgp/README.md
+++ b/openpgp/README.md
@@ -81,6 +81,13 @@ at compile time. Currently, these libraries are available:
include the `crypto-cng` feature to enable it. Currently, the CNG
backend requires at least Windows 10.
+ - The RustCrypto crates. To select this backend, use
+ `default-features = false`, and explicitly include the
+ `crypto-rust` feature to enable it. As of this writing, the
+ RustCrypto crates are not recommended for general use as they
+ cannot offer the same security guarantees as more mature
+ cryptographic libraries.
+
### Experimental and variable-time cryptographic backends
Some cryptographic backends are not yet considered mature enough for
diff --git a/openpgp/build.rs b/openpgp/build.rs
index 210483e5..aabf157d 100644
--- a/openpgp/build.rs
+++ b/openpgp/build.rs
@@ -62,6 +62,12 @@ fn crypto_backends_sanity_check() {
production_ready: true,
constant_time: true,
}),
+ (cfg!(feature = "crypto-rust"),
+ Backend {
+ name: "RustCrypto",
+ production_ready: false,
+ constant_time: false,
+ }),
].into_iter().filter_map(|(selected, backend)| {
if selected { Some(backend) } else { None }
}).collect::<Vec<_>>();
diff --git a/openpgp/src/crypto/aead.rs b/openpgp/src/crypto/aead.rs
index 3d55b0ab..95c172c1 100644
--- a/openpgp/src/crypto/aead.rs
+++ b/openpgp/src/crypto/aead.rs
@@ -815,6 +815,14 @@ mod tests {
SymmetricAlgorithm::Camellia256]
.iter()
.filter(|algo| algo.is_supported()) {
+
+ if cfg!(feature = "crypto-rust")
+ && sym_algo == &SymmetricAlgorithm::Twofish {
+ eprintln!("XXX: Skipping Twofish until Twofish \
+ implements Clone");
+ continue;
+ }
+
for aead in [
AEADAlgorithm::EAX,
AEADAlgorithm::OCB,
diff --git a/openpgp/src/crypto/backend.rs b/openpgp/src/crypto/backend.rs
index 11727d74..c0fd883f 100644
--- a/openpgp/src/crypto/backend.rs
+++ b/openpgp/src/crypto/backend.rs
@@ -8,6 +8,11 @@ mod nettle;
#[cfg(feature = "crypto-nettle")]
pub use self::nettle::*;
+#[cfg(feature = "crypto-rust")]
+mod rust;
+#[cfg(feature = "crypto-rust")]
+pub use self::rust::*;
+
#[cfg(feature = "crypto-cng")]
mod cng;
#[cfg(feature = "crypto-cng")]
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::wi