summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJustus Winter <justus@sequoia-pgp.org>2023-03-08 11:21:41 +0100
committerJustus Winter <justus@sequoia-pgp.org>2023-03-08 13:06:44 +0100
commit336a1562bfe3ad5fe32b5e3017c88197f5bad0fa (patch)
treeec7f433a8ab5cf97a508848c38be5cc6ae9685cf
parent07bb232c30851bff0b0f315fbc088af6f8e6911a (diff)
openpgp: Add a new backend based on the Botan cryptographic library.
-rw-r--r--.gitlab-ci.yml18
-rw-r--r--Cargo.lock16
-rw-r--r--openpgp/Cargo.toml5
-rw-r--r--openpgp/NEWS2
-rw-r--r--openpgp/README.md6
-rw-r--r--openpgp/build.rs8
-rw-r--r--openpgp/src/crypto/backend.rs9
-rw-r--r--openpgp/src/crypto/backend/botan.rs101
-rw-r--r--openpgp/src/crypto/backend/botan/aead.rs63
-rw-r--r--openpgp/src/crypto/backend/botan/asymmetric.rs670
-rw-r--r--openpgp/src/crypto/backend/botan/ecdh.rs150
-rw-r--r--openpgp/src/crypto/backend/botan/hash.rs103
-rw-r--r--openpgp/src/crypto/backend/botan/symmetric.rs178
13 files changed, 1329 insertions, 0 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 825db3ed..2d3ae64f 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -113,6 +113,24 @@ fedora-crypto-openssl:
CARGO_TARGET_DIR: /target
CARGO_HOME: /cargo
+rust-stable-crypto-botan:
+ tags:
+ - linux
+ stage: build
+ interruptible: true
+ image: 192.168.122.1:5000/sequoia-pgp/build-docker-image/rust-stable-prebuild:latest
+ before_script:
+ - *before_script_start
+ - apt -y install libbotan-2-dev
+ - rustup override set stable
+ - *before_script_end
+ script:
+ - cargo run --manifest-path openpgp/Cargo.toml --no-default-features --features crypto-botan,compression --example supported-algorithms
+ - cargo test --manifest-path openpgp/Cargo.toml --no-default-features --features crypto-botan,compression
+ variables:
+ CARGO_TARGET_DIR: /target
+ CARGO_HOME: /cargo
+
benchmarks:
stage: test
interruptible: true
diff --git a/Cargo.lock b/Cargo.lock
index 8b22110e..7ba2fd59 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -213,6 +213,21 @@ dependencies = [
]
[[package]]
+name = "botan"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ca148eb4be2a336a57efb859f664847cb1d268649e4a6032c5b16f21e8fd5027"
+dependencies = [
+ "botan-sys",
+]
+
+[[package]]
+name = "botan-sys"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b986825430fa1dff571f2dc36562a7a228b98c42e9f9552727c60a4a16de6555"
+
+[[package]]
name = "bstr"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2325,6 +2340,7 @@ dependencies = [
"block-modes",
"block-padding",
"blowfish",
+ "botan",
"buffered-reader",
"bzip2",
"cast5",
diff --git a/openpgp/Cargo.toml b/openpgp/Cargo.toml
index 0407d482..d8575d05 100644
--- a/openpgp/Cargo.toml
+++ b/openpgp/Cargo.toml
@@ -44,6 +44,7 @@ regex-syntax = "0.6"
sha1collisiondetection = { version = "0.2.3", default-features = false, features = ["std"] }
thiserror = "1.0.2"
xxhash-rust = { version = "0.8", features = ["xxh3"] }
+
# At least 0.10.45 is needed due to CipherCtx::cipher_final_unchecked
openssl = { version = ">= 0.10.45", optional = true }
# We need to directly depend on the sys crate so that the metadata produced
@@ -52,6 +53,9 @@ openssl = { version = ">= 0.10.45", optional = true }
openssl-sys = { version = ">= 0.9.80", optional = true }
foreign-types-shared = { version = "0.1.1", optional = true }
+# Botan.
+botan = { version = "0.10", optional = true }
+
# RustCrypto crates.
aes = { version = "0.6.0", optional = true }
block-modes = { version = "0.7.0", optional = true }
@@ -109,6 +113,7 @@ crypto-rust = [
]
crypto-cng = ["eax", "winapi", "win-crypto-ng", "ed25519-dalek", "num-bigint-dig"]
crypto-openssl = ["openssl", "openssl-sys", "foreign-types-shared"]
+crypto-botan = ["botan"]
__implicit-crypto-backend-for-tests = []
# Experimental and variable-time cryptographic backends opt-ins
diff --git a/openpgp/NEWS b/openpgp/NEWS
index 7a6080ed..cfabfe07 100644
--- a/openpgp/NEWS
+++ b/openpgp/NEWS
@@ -4,6 +4,8 @@
#+STARTUP: content hidestars
* Changes in 1.14.0
+** New cryptographic backends
+ - We added a backend that uses Botan.
** New functionality
- crypto::mem::Protected::new
- crypto::mpi::SecretKeyMaterial::from_bytes
diff --git a/openpgp/README.md b/openpgp/README.md
index fcaaed89..ecf3eb61 100644
--- a/openpgp/README.md
+++ b/openpgp/README.md
@@ -80,6 +80,12 @@ at compile time. Currently, these libraries are available:
`default-features = false`, and explicitly include the
`crypto-openssl` feature to enable it.
+ - The Botan backend. To select this backend, use
+ `default-features = false`, and explicitly include the
+ `crypto-botan` feature to enable it.
+
+ **Note**: Using the Botan backend raises the MSRV to 1.64.
+
- The Windows Cryptography API: Next Generation (CNG). To select
this backend, use `default-features = false`, and explicitly
include the `crypto-cng` feature to enable it. Currently, the CNG
diff --git a/openpgp/build.rs b/openpgp/build.rs
index 60f07620..2a2aa9a1 100644
--- a/openpgp/build.rs
+++ b/openpgp/build.rs
@@ -66,6 +66,7 @@ fn crypto_backends_sanity_check() {
(cfg!(all(feature = "crypto-nettle",
not(all(feature = "__implicit-crypto-backend-for-tests",
any(feature = "crypto-openssl",
+ feature = "crypto-botan",
feature = "crypto-rust"))))),
Backend {
name: "Nettle",
@@ -76,6 +77,7 @@ fn crypto_backends_sanity_check() {
not(all(feature = "__implicit-crypto-backend-for-tests",
any(feature = "crypto-nettle",
feature = "crypto-openssl",
+ feature = "crypto-botan",
feature = "crypto-rust"))))),
Backend {
name: "Windows CNG",
@@ -94,6 +96,12 @@ fn crypto_backends_sanity_check() {
production_ready: true,
constant_time: true,
}),
+ (cfg!(feature = "crypto-botan"),
+ Backend {
+ name: "Botan",
+ production_ready: true,
+ constant_time: true,
+ }),
].into_iter().filter_map(|(selected, backend)| {
if selected { Some(backend) } else { None }
}).collect::<Vec<_>>();
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, beca