summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorWiktor Kwapisiewicz <wiktor@metacode.biz>2022-10-03 11:51:41 +0200
committerWiktor Kwapisiewicz <wiktor@metacode.biz>2022-12-09 10:27:40 +0100
commit7710d126eb99e8990039fccf088b1170f3830573 (patch)
tree48e92718acce16f30ae2a99520961be1abd8f517
parent06fbec72f0d8ea609469a97d7650da045971c213 (diff)
openpgp: Add OpenSSL cryptographic backend.wiktor/workwork2
- Adds the backend behind `crypto-openssl` feature. - The backend is considered experimental for now. - Add CI configuration to run tests with the new backend. - See #333.
-rw-r--r--.Makefile2
-rw-r--r--.gitlab-ci.yml14
-rw-r--r--Cargo.lock25
-rw-r--r--openpgp/Cargo.toml8
-rw-r--r--openpgp/README.md4
-rw-r--r--openpgp/build.rs19
-rw-r--r--openpgp/src/crypto/backend.rs5
-rw-r--r--openpgp/src/crypto/backend/openssl.rs80
-rw-r--r--openpgp/src/crypto/backend/openssl/aead.rs153
-rw-r--r--openpgp/src/crypto/backend/openssl/asymmetric.rs642
-rw-r--r--openpgp/src/crypto/backend/openssl/ecdh.rs123
-rw-r--r--openpgp/src/crypto/backend/openssl/hash.rs99
-rw-r--r--openpgp/src/crypto/backend/openssl/symmetric.rs166
13 files changed, 1334 insertions, 6 deletions
diff --git a/.Makefile b/.Makefile
index 0377017b..d35f90b0 100644
--- a/.Makefile
+++ b/.Makefile
@@ -149,5 +149,5 @@ clean:
.PHONY: codespell
codespell:
$(CODESPELL) $(CODESPELL_FLAGS) \
- -L "crate,ede,iff,mut,nd,te,uint,KeyServer,keyserver,Keyserver,keyservers,Keyservers,keypair,keypairs,KeyPair,fpr,dedup" \
+ -L "crate,ede,iff,mut,nd,te,uint,KeyServer,keyserver,Keyserver,keyservers,Keyservers,keypair,keypairs,KeyPair,fpr,dedup,deriver" \
-S "*.bin,*.gpg,*.pgp,./.git,data,highlight.js,*/target,Makefile"
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index b478e2ca..0387a36a 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -120,6 +120,20 @@ bookworm-crypto-rust:
CARGO_TARGET_DIR: /target
CARGO_HOME: /cargo
+bookworm-crypto-openssl:
+ tags:
+ - linux
+ stage: build
+ image: registry.gitlab.com/sequoia-pgp/build-docker-image/bookworm-prebuild:latest
+ dependencies:
+ - codespell
+ script:
+ - cargo run --manifest-path openpgp/Cargo.toml --no-default-features --features crypto-openssl,compression --example supported-algorithms
+ - cargo test --release --manifest-path openpgp/Cargo.toml --no-default-features --features crypto-openssl,compression
+ variables:
+ CARGO_TARGET_DIR: /target
+ CARGO_HOME: /cargo
+
benchmarks:
stage: test
image: registry.gitlab.com/sequoia-pgp/build-docker-image/bookworm:latest
diff --git a/Cargo.lock b/Cargo.lock
index 2bcf9c47..2cfb5bc5 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1971,19 +1971,31 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
[[package]]
name = "openssl"
-version = "0.10.38"
+version = "0.10.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0c7ae222234c30df141154f159066c5093ff73b63204dcda7121eb082fc56a95"
+checksum = "29d971fd5722fec23977260f6e81aa67d2f22cadbdc2aa049f1022d9a3be1566"
dependencies = [
"bitflags",
"cfg-if",
"foreign-types",
"libc",
"once_cell",
+ "openssl-macros",
"openssl-sys",
]
[[package]]
+name = "openssl-macros"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
name = "openssl-probe"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1991,9 +2003,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
[[package]]
name = "openssl-sys"
-version = "0.9.72"
+version = "0.9.79"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7e46109c383602735fa0a2e48dd2b7c892b048e1bf69e5c3b1d804b7d9c203cb"
+checksum = "5454462c0eced1e97f2ec09036abc8da362e66802f66fd20f86854d9d8cbcbc4"
dependencies = [
"autocfg 1.1.0",
"cc",
@@ -2781,6 +2793,7 @@ dependencies = [
"ecdsa",
"ed25519-dalek",
"flate2",
+ "foreign-types-shared",
"generic-array 0.14.5",
"getrandom 0.2.6",
"idea",
@@ -2793,6 +2806,8 @@ dependencies = [
"memsec",
"nettle",
"num-bigint-dig",
+ "openssl",
+ "openssl-sys",
"p256",
"quickcheck",
"rand 0.7.3",
@@ -2938,7 +2953,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f31bf4e9fe5cd8cea8e0887e2e4eb1b4d736ff11b776c8537bf0912a4b381285"
dependencies = [
"digest 0.9.0",
- "generic-array 0.12.4",
+ "generic-array 0.14.5",
]
[[package]]
diff --git a/openpgp/Cargo.toml b/openpgp/Cargo.toml
index ba65f97a..22a77157 100644
--- a/openpgp/Cargo.toml
+++ b/openpgp/Cargo.toml
@@ -44,6 +44,13 @@ 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.44 is needed due to many changes we contributed upstream
+openssl = { version = ">= 0.10.44", optional = true }
+# We need to directly depend on the sys crate so that the metadata produced
+# in its build script is passed to sequoia-openpgp's build script
+# see: https://doc.rust-lang.org/cargo/reference/build-scripts.html#the-links-manifest-key
+openssl-sys = { version = ">= 0.9.79", optional = true }
+foreign-types-shared = { version = "0.1.1", optional = true }
# RustCrypto crates.
aes = { version = "0.6.0", optional = true }
@@ -101,6 +108,7 @@ crypto-rust = [
"rand_core", "rand_core/getrandom", "ecdsa"
]
crypto-cng = ["eax", "winapi", "win-crypto-ng", "ed25519-dalek", "num-bigint-dig"]
+crypto-openssl = ["openssl", "openssl-sys", "foreign-types-shared"]
# Experimental and variable-time cryptographic backends opt-ins
allow-experimental-crypto = []
diff --git a/openpgp/README.md b/openpgp/README.md
index 00512b7a..2001a8bc 100644
--- a/openpgp/README.md
+++ b/openpgp/README.md
@@ -88,6 +88,10 @@ at compile time. Currently, these libraries are available:
cannot offer the same security guarantees as more mature
cryptographic libraries.
+ - The OpenSSL backend. To select this backend, use
+ `default-features = false`, and explicitly include the
+ `crypto-openssl` feature to enable it.
+
### 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 aabf157d..9de8fb76 100644
--- a/openpgp/build.rs
+++ b/openpgp/build.rs
@@ -6,10 +6,23 @@ use std::process::exit;
fn main() {
crypto_backends_sanity_check();
+ include_openssl_conf();
lalrpop::process_root().unwrap();
include_test_data().unwrap();
}
+/// Optionally include configuration passed from openssl-sys build
+/// script. This configuration is then exposed as a set of `osslconf`
+/// parameters and is used by OpenSSL backend to enable or disable
+/// algorithms available by the current environment.
+fn include_openssl_conf() {
+ if let Ok(vars) = env::var("DEP_OPENSSL_CONF") {
+ for var in vars.split(',') {
+ println!("cargo:rustc-cfg=osslconf=\"{}\"", var);
+ }
+ }
+}
+
/// Builds the index of the test data for use with the `::tests`
/// module.
fn include_test_data() -> io::Result<()> {
@@ -68,6 +81,12 @@ fn crypto_backends_sanity_check() {
production_ready: false,
constant_time: false,
}),
+ (cfg!(feature = "crypto-openssl"),
+ Backend {
+ name: "OpenSSL",
+ 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 c0fd883f..780b0bb1 100644
--- a/openpgp/src/crypto/backend.rs
+++ b/openpgp/src/crypto/backend.rs
@@ -17,3 +17,8 @@ pub use self::rust::*;
mod cng;
#[cfg(feature = "crypto-cng")]
pub use self::cng::*;
+
+#[cfg(feature = "crypto-openssl")]
+mod openssl;
+#[cfg(feature = "crypto-openssl")]
+pub use self::openssl::*;
diff --git a/openpgp/src/crypto/backend/openssl.rs b/openpgp/src/crypto/backend/openssl.rs
new file mode 100644
index 00000000..4e610bfe
--- /dev/null
+++ b/openpgp/src/crypto/backend/openssl.rs
@@ -0,0 +1,80 @@
+//! Implementation of Sequoia crypto API using the OpenSSL cryptographic library.
+use std::convert::TryFrom;
+
+use crate::types::*;
+
+pub mod aead;
+pub mod asymmetric;
+pub mod ecdh;
+pub mod hash;
+pub mod symmetric;
+
+/// Returns a short, human-readable description of the backend.
+pub fn backend() -> String {
+ "OpenSSL".to_string()
+}
+
+/// Fills the given buffer with random data.
+pub fn random(buf: &mut [u8]) {
+ // random is expected to always work or panic on wrong data.
+ // This is similar to what other backends do like CNG or Rust
+ // see: https://docs.rs/rand/latest/rand/trait.RngCore.html#tymethod.fill_bytes
+ openssl::rand::rand_bytes(buf).expect("rand_bytes to work");
+}
+
+impl PublicKeyAlgorithm {
+ pub(crate) fn is_supported_by_backend(&self) -> bool {
+ use PublicKeyAlgorithm::*;
+ #[allow(deprecated)]
+ match self {
+ RSAEncryptSign | RSAEncrypt | RSASign => true,
+ DSA => true,
+ ECDH | ECDSA | EdDSA => true,
+ _ => false,
+ }
+ }
+}
+
+impl Curve {
+ pub(crate) fn is_supported_by_backend(&self) -> bool {
+ if matches!(self, Curve::Ed25519 | Curve::Cv25519) {
+ // 25519-based algorithms are special-cased and supported
+ true
+ } else {
+ // the rest of EC algorithms are supported via the same
+ // codepath
+ openssl::nid::Nid::try_from(self).is_ok()
+ }
+ }
+}
+
+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 {
+ *self == AEADAlgorithm::OCB
+ }
+
+ #[cfg(test)]
+ pub(crate) fn supports_symmetric_algo(&self, algo: &SymmetricAlgorithm) -> bool {
+ match &self {
+ AEADAlgorithm::OCB =>
+ match algo {
+ // OpenSSL supports OCB only with AES
+ // see: https://wiki.openssl.org/index.php/OCB
+ SymmetricAlgorithm::AES128 |
+ SymmetricAlgorithm::AES192 |
+ SymmetricAlgorithm::AES256 => true,
+ _ => false,
+ },
+ _ => false
+ }
+ }
+}
diff --git a/openpgp/src/crypto/backend/openssl/aead.rs b/openpgp/src/crypto/backend/openssl/aead.rs
new file mode 100644
index 00000000..9139aa5a
--- /dev/null
+++ b/openpgp/src/crypto/backend/openssl/aead.rs
@@ -0,0 +1,153 @@
+//! Implementation of AEAD using OpenSSL cryptographic library.
+
+use crate::{Error, Result};
+
+use crate::crypto::aead::{Aead, CipherOp};
+use crate::types::{AEADAlgorithm, SymmetricAlgorithm};
+
+use openssl::cipher::Cipher;
+use openssl::cipher_ctx::{CipherCtx, CipherCtxRef};
+use openssl::error::ErrorStack;
+use openssl_sys::c_int;
+
+struct OpenSslContextEncrypt {
+ ctx: CipherCtx,
+ // The last chunk to be processed does not call `encrypt` thus
+ // leaves the crypter in non-finalized state. This makes the
+ // `get_tag` function of the crypter panic when calling `digest`.
+ // If this flag is set to `false` it means the crypter needs to be
+ // finalized.
+ finalized: bool,
+}
+
+fn cvt(r: c_int) -> std::result::Result<c_int, ErrorStack> {
+ if r <= 0 {
+ Err(ErrorStack::get())
+ } else {
+ Ok(r)
+ }
+}
+
+// Uses the same logic as `CipherCtxRef::cipher_update` to calculate the
+// required size of the output buffer. This fix will be upstreamed.
+fn safe_cipher_final(ctx: &CipherCtxRef, output: &mut [u8]) -> Result<usize> {
+ let min_output_size = ctx.minimal_output_size(0);
+ assert!(
+ output.len() >= min_output_size,
+ "Output buffer size should be at least {} bytes.",
+ min_output_size
+ );
+
+ let mut outl = 0;
+ unsafe {
+ use foreign_types_shared::ForeignTypeRef;
+ cvt(openssl_sys::EVP_CipherFinal(
+ ctx.as_ptr(),
+ output.as_mut_ptr(),
+ &mut outl,
+ ))?;
+ }
+
+ Ok(outl as usize)
+}
+
+impl Aead for OpenSslContextEncrypt {
+ fn update(&mut self, ad: &[u8]) -> Result<()> {
+ self.ctx.cipher_update(ad, None)?;
+ Ok(())
+ }
+
+ fn encrypt(&mut self, dst: &mut [u8], src: &[u8]) -> Result<()> {
+ let size = self.ctx.cipher_update(src, Some(dst))?;
+ safe_cipher_final(&self.ctx, &mut dst[size..])?;
+ self.finalized = true;
+ Ok(())
+ }
+
+ fn decrypt_verify(&mut self, _dst: &mut [u8], _src: &[u8], _valid_digest: &[u8]) -> Result<()> {
+ panic!("Decrypt called in encrypt context");
+ }
+
+ fn digest(&mut self, digest: &mut [u8]) -> Result<()> {
+ if !self.finalized {
+ safe_cipher_final(&self.ctx, &mut [])?;
+ }
+ self.ctx.tag(digest)?;
+ Ok(())
+ }
+
+ fn digest_size(&self) -> usize {
+ panic!("Unsupported op");
+ }
+}
+
+impl crate::seal::Sealed for OpenSslContextEncrypt {}
+
+struct OpenSslContextDecrypt {
+ ctx: CipherCtx,
+}
+
+impl Aead for OpenSslContextDecrypt {
+ fn update(&mut self, ad: &[u8]) -> Result<()> {
+ self.ctx.cipher_update(ad, None)?;
+ Ok(())
+ }
+
+ fn encrypt(&mut self, _dst: &mut [u8], _src: &[u8]) -> Result<()> {
+ panic!("Encrypt called in decrypt context");
+ }
+
+ fn decrypt_verify(&mut self, dst: &mut [u8], src: &[u8], valid_digest: &[u8]) -> Result<()> {
+ let size = self.ctx.cipher_update(src, Some(dst))?;
+ self.ctx.set_tag(valid_digest)?;
+ safe_cipher_final(&self.ctx, &mut dst[size..])?;
+ Ok(())
+ }
+
+ fn digest(&mut self, _digest: &mut [u8]) -> Result<()> {
+ panic!("Unsupported op, use decrypt_verify");
+ }
+
+ fn digest_size(&self) -> usize {
+ panic!("Unsupported operation");
+ }
+}
+
+impl crate::seal::Sealed for OpenSslContextDecrypt {}
+
+impl AEADAlgorithm {
+ pub(crate) fn context(
+ &self,
+ sym_algo: SymmetricAlgorithm,
+ key: &[u8],
+ nonce: &[u8],
+ op: CipherOp,
+ ) -> Result<Box<dyn Aead>> {
+ match self {
+ AEADAlgorithm::OCB => {
+ let cipher = match sym_algo {
+ SymmetricAlgorithm::AES128 => Cipher::aes_128_ocb(),
+ SymmetricAlgorithm::AES192 => Cipher::aes_192_ocb(),
+ SymmetricAlgorithm::AES256 => Cipher::aes_256_ocb(),
+ _ => return Err(Error::UnsupportedSymmetricAlgorithm(sym_algo).into()),
+ };
+ let mut ctx = CipherCtx::new()?;
+ ctx.set_padding(false);
+ match op {
+ CipherOp::Encrypt => {
+ ctx.encrypt_init(Some(cipher), Some(key), Some(nonce))?;
+ Ok(Box::new(OpenSslContextEncrypt {
+ ctx,
+ finalized: false,
+ }))
+ }
+ CipherOp::Decrypt => {
+ ctx.decrypt_init(Some(cipher), Some(key), Some(nonce))?;
+ Ok(Box::new(OpenSslContextDecrypt { ctx }))
+ }
+ }
+ }
+ _ => Err(Error::UnsupportedAEADAlgorithm(*self).into()),
+ }
+ }
+}
diff --git a/openpgp/src/crypto/backend/openssl/asymmetric.rs b/openpgp/src/crypto/backend/openssl/asymmetric.rs
new file mode 100644
index 00000000..aef777bf
--- /dev/null
+++ b/openpgp/src/crypto/backend/openssl/asymmetric.rs
@@ -0,0 +1,642 @@
+use crate::Result;
+
+use crate::crypto::asymmetric::{Decryptor, KeyPair, Signer};
+use crate::crypto::mpi;
+use crate::crypto::mpi::{ProtectedMPI, MPI};
+use crate::crypto::mem::Protected;
+use crate::crypto::SessionKey;
+use crate::packet::key::{Key4, SecretParts};
+use crate::packet::{key, Key};
+use crate::types::SymmetricAlgorithm;
+use crate::types::{Curve, HashAlgorithm, PublicKeyAlgorithm};
+use std::convert::{TryFrom, TryInto};
+use std::time::SystemTime;
+
+use openssl::bn::{BigNum, BigNumRef, BigNumContext};
+use openssl::ec::{EcGroup, EcKey, EcPoint, PointConversionForm};
+use openssl::ecdsa::EcdsaSig;
+use openssl::nid::Nid;
+use openssl::pkey::PKey;
+use openssl::pkey_ctx::PkeyCtx;
+use openssl::rsa::{Padding, Rsa, RsaPrivateKeyBuilder};
+use openssl::sign::Signer as OpenSslSigner;
+use openssl::sign::Verifier;
+
+impl TryFrom<&ProtectedMPI> for BigNum {
+ type Error = anyhow::Error;
+ fn try_from(mpi: &ProtectedMPI) -> std::result::Result<BigNum, anyhow::Error> {
+ let mut bn = BigNum::new_secure()?;
+ bn.copy_from_slice(mpi.value())?;
+ Ok(bn)
+ }
+}
+
+impl From<&BigNumRef> for ProtectedMPI {
+ fn from(bn: &BigNumRef) -> Self {
+ bn.to_vec().into()
+ }
+}
+
+impl From<BigNum> for ProtectedMPI {
+ fn from(bn: BigNum) -> Self {
+ bn.to_vec().into()
+ }
+}
+
+impl From<BigNum> for MPI {
+ fn from(bn: BigNum) -> Self {
+ bn.to_vec().into()
+ }
+}
+
+impl TryFrom<&MPI> for BigNum {
+ type Error = anyhow::Error;
+ fn try_from(mpi: &MPI) -> std::result::Result<BigNum, anyhow::Error> {
+ Ok(BigNum::from_slice(mpi.value())?)
+ }
+}
+
+impl From<&BigNumRef> for MPI {
+ fn from(bn: &BigNumRef) -> Self {
+ bn.to_vec().into()
+ }
+}
+
+impl TryFrom<&Curve> for Nid {
+ type Error = crate::Error;
+ fn try_from(curve: &Curve) -> std::result::Result<Nid, crate::Error> {
+ Ok(match curve {
+ Curve::NistP256 => Nid::X9_62_PRIME256V1,
+ Curve::NistP384 => Nid::SECP384R1,
+ Curve::NistP521 => Nid::SECP521R1,
+ Curve::BrainpoolP256 => Nid::BRAINPOOL_P256R1,
+ Curve::BrainpoolP512 => Nid::BRAINPOOL_P512R1,
+ _ => return Err(crate::Error::UnsupportedEllipticCurve(curve.clone()).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::*;
+ #[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 =
+ RsaPrivateKeyBuilder::new(n.try_into()?, e.try_into()?, d.try_into()?)?
+ .set_factors(p.try_into()?, q.try_into()?)?
+ .build();
+
+ let key = PKey::from_rsa(key)?;
+
+ let mut signature: Vec<u8> = vec![];
+
+ const MAX_OID_SIZE: usize = 20;
+ let mut v = Vec::with_capacity(MAX_OID_SIZE + digest.len());
+ v.extend(hash_algo.oid()?);
+ v.extend(digest);
+
+ let mut ctx = PkeyCtx::new(&key)?;
+ ctx.sign_init()?;
+ ctx.sign_to_vec(&v, &mut signature)?;
+
+ Ok(mpi::Signature::RSA {
+ s: signature.into(),
+ })
+ }
+ (
+ PublicKeyAlgorithm::DSA,
+ mpi::PublicKey::DSA { p, q, g, y },
+ mpi::SecretKeyMaterial::DSA { x },
+ ) => {
+ use openssl::dsa::{Dsa, DsaSig};
+ let dsa = Dsa::from_private_components(
+ p.try_into()?,
+ q.try_into()?,
+ g.try_into()?,
+ x.try_into()?,
+ y.try_into()?,
+ )?;
+ let key: PKey<_> = dsa.try_into()?;
+ let mut ctx = PkeyCtx::new(&key)?;
+ ctx.sign_init()?;
+ let mut signature = vec![];
+ ctx.sign_to_vec(&digest, &mut signature)?;
+ let signature = DsaSig::from_der(&signature)?;
+
+ Ok(mpi::Signature::DSA {
+ r: signature.r().to_vec().into(),
+ s: signature.s().to_vec().into(),
+ })
+ }
+ (
+ PublicKeyAlgorithm::ECDSA,
+ mpi::PublicKey::ECDSA { curve, q },
+ mpi::SecretKeyMaterial::ECDSA { scalar },
+ ) => {
+ let nid = curve.try_into()?;
+ let group = EcGroup::from_curve_name(nid)?;
+ let mut ctx = BigNumContext::new()?;
+ let point = EcPoint::from_bytes(&group, q.value(), &mut ctx)?;
+ let mut private = BigNum::new_secure()?;
+ private.copy_from_slice(scalar.value())?;
+ let key = EcKey::from_private_components(&group, &private, &point)?;
+ let sig = EcdsaSig::sign(digest, &key)?;
+ Ok(mpi::Signature::ECDSA {
+ r: sig.r().into(),
+ s: sig.s().into(),
+ })
+ }
+
+ (
+ EdDSA,
+ mpi::PublicKey::EdDSA { curve, .. },
+ mpi::SecretKeyMaterial::EdDSA { scalar },
+ ) => match curve {
+ Curve::Ed25519 => {
+ let scalar = scalar.value_padded(32);
+
+ let key =
+ PKey::private_key_from_raw_bytes(&scalar, openssl::pkey::Id::ED25519)?;
+
+ let mut signer = OpenSslSigner::new_without_digest(&key)?;
+ let signature = signer.sign_oneshot_to_vec(digest)?;
+
+ // https://tools.ietf.org/html/rfc8032#section-5.1.6
+ let (r, s) = signature.split_at(signature.len() / 2);
+ Ok(mpi::Signature::EdDSA {
+ r: r.to_vec().into(),
+ s: s.to_vec().into(),
+ })
+ }
+ _ => Err(crate::Error::UnsupportedEllipticCurve(curve.clone()).into()),
+ },
+
+ (pk_algo, _, _) => Err(crate::Error::InvalidOperation(format!(
+ "unsupported combination of algorithm {:?}, key {:?}, \
+ and secret key {:?} by OpenSSL backend",
+ 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> {
+ use crate::crypto::mpi::PublicKey;
+
+ self.secret().map(|secret| {
+ Ok(match (self.public().mpis(), secret, ciphertext) {
+ (
+ PublicKey::RSA { ref e, ref n },
+ mpi::SecretKeyMaterial::RSA {
+ ref p,
+ ref q,
+ ref d,
+ ..
+ },
+ mpi::Ciphertext::RSA { ref c },
+ ) => {
+ let key =
+ RsaPrivateKeyBuilder::new(n.try_into()?, e.try_into()?, d.try_into()?)?
+ .set_factors(p.try_into()?, q.try_into()?)?
+ .build();
+
+ let mut buf: Protected = vec![0; key.size().try_into()?].into();
+ let encrypted_len = key.private_decrypt(c.value(), &mut buf, Padding::PKCS1)?;
+ buf[..encrypted_len].into()
+ }
+
+ (
+ PublicKey::ECDH { .. },
+ mpi::SecretKeyMaterial::ECDH { .. },
+ mpi::Ciphertext::ECDH { .. },
+ ) => crate::crypto::ecdh::decrypt(self.public(), secret, ciphertext)?,
+
+ (public, secret, ciphertext) => {
+ return Err(crate::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(crate::Error::InvalidArgument(
+ "Plaintext data too large".into(),
+ )
+ .into());
+ }
+
+