From 72db601361ab570d3f3c741ee941a562a0b3f885 Mon Sep 17 00:00:00 2001 From: Justus Winter Date: Fri, 12 May 2023 11:36:37 +0200 Subject: openpgp: Add asymmetric encryption trait. - As first step, abstract over X25519. --- openpgp/src/crypto/backend/botan/asymmetric.rs | 38 +++++++++--- openpgp/src/crypto/backend/cng/asymmetric.rs | 75 +++++++++++++++++++----- openpgp/src/crypto/backend/interface.rs | 22 ++++++- openpgp/src/crypto/backend/nettle/asymmetric.rs | 40 +++++++++---- openpgp/src/crypto/backend/openssl/asymmetric.rs | 35 ++++++++--- openpgp/src/crypto/backend/rust/asymmetric.rs | 45 +++++++++++--- openpgp/src/crypto/mod.rs | 2 +- openpgp/src/packet/key.rs | 5 +- 8 files changed, 208 insertions(+), 54 deletions(-) diff --git a/openpgp/src/crypto/backend/botan/asymmetric.rs b/openpgp/src/crypto/backend/botan/asymmetric.rs index 0a3afbd1..0ff8b51b 100644 --- a/openpgp/src/crypto/backend/botan/asymmetric.rs +++ b/openpgp/src/crypto/backend/botan/asymmetric.rs @@ -17,6 +17,7 @@ use crate::{ Result, crypto::{ asymmetric::{KeyPair, Decryptor, Signer}, + backend::interface::Asymmetric, mem::Protected, mpi::{self, MPI, ProtectedMPI, PublicKey}, SessionKey, @@ -32,6 +33,36 @@ use crate::{ }, }; +impl Asymmetric for super::Backend { + fn x25519_generate_key() -> Result<(Protected, [u8; 32])> { + let mut rng = RandomNumberGenerator::new_userspace()?; + let secret = Privkey::create("Curve25519", "", &mut rng)?; + let mut public = [0u8; 32]; + public.copy_from_slice(&secret.pubkey()?.get_x25519_key()?); + let mut secret: Protected = secret.get_x25519_key()?.into(); + + // Clamp the scalar. X25519 does the clamping implicitly, but + // OpenPGP's ECDH over Curve25519 requires the secret to be + // clamped. + secret[0] &= 0b1111_1000; + secret[31] &= !0b1000_0000; + secret[31] |= 0b0100_0000; + + Ok((secret, public)) + } + + fn x25519_derive_public(secret: &Protected) -> Result<[u8; 32]> { + let secret = Privkey::load_x25519(secret)?; + Ok(<[u8; 32]>::try_from(&secret.pubkey()?.get_x25519_key()?[..])?) + } + + fn x25519_shared_point(secret: &Protected, public: &[u8; 32]) + -> Result { + let secret = Privkey::load_x25519(&secret)?; + Ok(secret.agree(public, 32, b"", "Raw")?.into()) + } +} + // CONFIDENTIALITY: Botan clears the MPIs after use. impl TryFrom<&ProtectedMPI> for botan::MPI { type Error = anyhow::Error; @@ -404,13 +435,6 @@ impl Key { impl Key4 where R: key::KeyRole, { - pub(crate) fn derive_cv25519_public_key(private_key: &Protected) -> Result<[u8; 32]> - { - let secret = Privkey::load_x25519(private_key)?; - - Ok(<[u8; 32]>::try_from(&secret.pubkey()?.get_x25519_key()?[..])?) - } - /// Creates a new OpenPGP secret key packet for an existing Ed25519 key. /// /// The ECDH key will use hash algorithm `hash` and symmetric diff --git a/openpgp/src/crypto/backend/cng/asymmetric.rs b/openpgp/src/crypto/backend/cng/asymmetric.rs index 3b541e55..f62f062f 100644 --- a/openpgp/src/crypto/backend/cng/asymmetric.rs +++ b/openpgp/src/crypto/backend/cng/asymmetric.rs @@ -7,6 +7,7 @@ use std::convert::TryInto; use crate::{Error, Result}; use crate::crypto::asymmetric::{Decryptor, KeyPair, Signer}; +use crate::crypto::backend::interface::Asymmetric; use crate::crypto::mem::Protected; use crate::crypto::mpi; use crate::crypto::SessionKey; @@ -21,6 +22,64 @@ use win_crypto_ng as cng; const CURVE25519_SIZE: usize = 32; +impl Asymmetric for super::Backend { + fn x25519_generate_key() -> Result<(Protected, [u8; 32])> { + use cng::asymmetric::{Ecdh, AsymmetricKey, Export}; + use cng::asymmetric::ecc::Curve25519; + + let pair = + AsymmetricKey::builder(Ecdh(Curve25519)).build()?.export()?; + + let mut public = [0u8; 32]; + public.copy_from_slice(pair.x()); + + Ok((pair.d().into(), public)) + } + + fn x25519_derive_public(secret: &Protected) -> Result<[u8; 32]> { + use cng::asymmetric::{AsymmetricAlgorithm, AsymmetricAlgorithmId, Ecdh, + Private, AsymmetricKey, Export}; + use cng::asymmetric::ecc::{Curve25519, NamedCurve}; + + let provider = AsymmetricAlgorithm::open( + AsymmetricAlgorithmId::Ecdh(NamedCurve::Curve25519) + )?; + let key = AsymmetricKey::, Private>::import_from_parts( + &provider, + secret, + )?; + Ok(<[u8; 32]>::try_from(&key.export()?.x()[..])?) + } + + fn x25519_shared_point(secret: &Protected, public: &[u8; 32]) + -> Result { + use cng::asymmetric::{Ecdh, AsymmetricKey, Public, Private, + AsymmetricAlgorithm, AsymmetricAlgorithmId}; + use cng::asymmetric::agreement::secret_agreement; + use cng::asymmetric::ecc::{NamedCurve, Curve25519}; + + let provider = + AsymmetricAlgorithm::open( + AsymmetricAlgorithmId::Ecdh(NamedCurve::Curve25519))?; + let public = + AsymmetricKey::, Public>::import_from_parts( + &provider, + public, + )?; + let secret = + AsymmetricKey::, Private>::import_from_parts( + &provider, + secret, + )?; + + let shared = secret_agreement(&secret, &public)?; + let mut shared = Protected::from(shared.derive_raw()?); + // Returned secret is little-endian, flip it to big-endian + shared.reverse(); + Ok(shared) + } +} + impl Signer for KeyPair { fn public(&self) -> &Key { KeyPair::public(self) @@ -693,22 +752,6 @@ impl Key4 where R: key::KeyRole, { - pub(crate) fn derive_cv25519_public_key(private_key: &Protected) -> Result<[u8; 32]> - { - use cng::asymmetric::{AsymmetricAlgorithm, AsymmetricAlgorithmId, Ecdh, Private}; - use cng::asymmetric::{AsymmetricKey, Export}; - use cng::asymmetric::ecc::{Curve25519, NamedCurve}; - - let provider = AsymmetricAlgorithm::open( - AsymmetricAlgorithmId::Ecdh(NamedCurve::Curve25519) - )?; - let key = AsymmetricKey::, Private>::import_from_parts( - &provider, - private_key - )?; - Ok(<[u8; 32]>::try_from(&key.export()?.x()[..])?) - } - /// Creates a new OpenPGP secret key packet for an existing Ed25519 key. /// /// The key will have it's creation date set to `ctime` or the current time diff --git a/openpgp/src/crypto/backend/interface.rs b/openpgp/src/crypto/backend/interface.rs index f96e9a6c..81343c6c 100644 --- a/openpgp/src/crypto/backend/interface.rs +++ b/openpgp/src/crypto/backend/interface.rs @@ -1,9 +1,12 @@ //! The crypto-backend abstraction. -use crate::Result; +use crate::{ + Result, + crypto::mem::Protected, +}; /// Abstracts over the cryptographic backends. -pub trait Backend { +pub trait Backend: Asymmetric { /// Returns a short, human-readable description of the backend. /// /// This starts with the name of the backend, possibly a version, @@ -19,3 +22,18 @@ pub trait Backend { /// long-term cryptographic keys from. fn random(buf: &mut [u8]) -> Result<()>; } + +/// Public-key cryptography interface. +pub trait Asymmetric { + /// Generates an X25519 key pair. + /// + /// Returns a tuple containing the secret and public key. + fn x25519_generate_key() -> Result<(Protected, [u8; 32])>; + + /// Computes the public key for a given secret key. + fn x25519_derive_public(secret: &Protected) -> Result<[u8; 32]>; + + /// Computes the shared point. + fn x25519_shared_point(secret: &Protected, public: &[u8; 32]) + -> Result; +} diff --git a/openpgp/src/crypto/backend/nettle/asymmetric.rs b/openpgp/src/crypto/backend/nettle/asymmetric.rs index c96ccaf0..52d93388 100644 --- a/openpgp/src/crypto/backend/nettle/asymmetric.rs +++ b/openpgp/src/crypto/backend/nettle/asymmetric.rs @@ -10,10 +10,37 @@ use crate::{Error, Result}; use crate::packet::{key, Key}; use crate::crypto::asymmetric::{KeyPair, Decryptor, Signer}; +use crate::crypto::backend::interface::Asymmetric; use crate::crypto::mpi::{self, MPI, PublicKey}; use crate::crypto::SessionKey; use crate::types::{Curve, HashAlgorithm}; +impl Asymmetric for super::Backend { + fn x25519_generate_key() -> Result<(Protected, [u8; 32])> { + debug_assert_eq!(curve25519::CURVE25519_SIZE, 32); + let mut rng = Yarrow::default(); + let secret = curve25519::private_key(&mut rng); + let mut public = [0; 32]; + curve25519::mul_g(&mut public, &secret)?; + Ok((secret.into(), public)) + } + + fn x25519_derive_public(secret: &Protected) -> Result<[u8; 32]> { + debug_assert_eq!(curve25519::CURVE25519_SIZE, 32); + let mut public = [0; 32]; + curve25519::mul_g(&mut public, secret)?; + Ok(public) + } + + fn x25519_shared_point(secret: &Protected, public: &[u8; 32]) + -> Result { + debug_assert_eq!(curve25519::CURVE25519_SIZE, 32); + let mut s: Protected = vec![0; 32].into(); + curve25519::mul(&mut s, secret, public)?; + Ok(s) + } +} + impl Signer for KeyPair { fn public(&self) -> &Key { KeyPair::public(self) @@ -333,13 +360,6 @@ use crate::types::PublicKeyAlgorithm; impl Key4 where R: key::KeyRole, { - pub(crate) fn derive_cv25519_public_key(private_key: &Protected) -> Result<[u8; 32]> - { - let mut public_key = [0; ed25519::ED25519_KEY_SIZE]; - curve25519::mul_g(&mut public_key, private_key)?; - Ok(public_key) - } - /// Creates a new OpenPGP secret key packet for an existing Ed25519 key. /// /// The ECDH key will use hash algorithm `hash` and symmetric @@ -449,10 +469,8 @@ impl Key4 } (Curve::Cv25519, false) => { - let mut public = [0; curve25519::CURVE25519_SIZE]; - let mut private: Protected = - curve25519::private_key(&mut rng).into(); - curve25519::mul_g(&mut public, &private)?; + let (mut private, public) = + super::Backend::x25519_generate_key()?; // Reverse the scalar. See // https://lists.gnupg.org/pipermail/gnupg-devel/2018-February/033437.html. diff --git a/openpgp/src/crypto/backend/openssl/asymmetric.rs b/openpgp/src/crypto/backend/openssl/asymmetric.rs index 3ba3ca64..baffde97 100644 --- a/openpgp/src/crypto/backend/openssl/asymmetric.rs +++ b/openpgp/src/crypto/backend/openssl/asymmetric.rs @@ -1,6 +1,7 @@ use crate::{Error, Result}; use crate::crypto::asymmetric::{Decryptor, KeyPair, Signer}; +use crate::crypto::backend::interface::Asymmetric; use crate::crypto::mpi; use crate::crypto::mpi::{ProtectedMPI, MPI}; use crate::crypto::mem::Protected; @@ -12,6 +13,7 @@ use std::convert::{TryFrom, TryInto}; use std::time::SystemTime; use openssl::bn::{BigNum, BigNumRef, BigNumContext}; +use openssl::derive::Deriver; use openssl::ec::{EcGroup, EcKey, EcPoint, PointConversionForm}; use openssl::ecdsa::EcdsaSig; use openssl::nid::Nid; @@ -21,6 +23,32 @@ use openssl::rsa::{Padding, Rsa, RsaPrivateKeyBuilder}; use openssl::sign::Signer as OpenSslSigner; use openssl::sign::Verifier; +impl Asymmetric for super::Backend { + fn x25519_generate_key() -> Result<(Protected, [u8; 32])> { + let pair = openssl::pkey::PKey::generate_x25519()?; + Ok((pair.raw_private_key()?.into(), + pair.raw_public_key()?.as_slice().try_into()?)) + } + + fn x25519_derive_public(secret: &Protected) -> Result<[u8; 32]> { + let key = PKey::private_key_from_raw_bytes( + secret, openssl::pkey::Id::X25519)?; + Ok(key.raw_public_key()?.as_slice().try_into()?) + } + + fn x25519_shared_point(secret: &Protected, public: &[u8; 32]) + -> Result { + let public = PKey::public_key_from_raw_bytes( + public, openssl::pkey::Id::X25519)?; + let secret = PKey::private_key_from_raw_bytes( + secret, openssl::pkey::Id::X25519)?; + + let mut deriver = Deriver::new(&secret)?; + deriver.set_peer(&public)?; + Ok(deriver.derive_to_vec()?.into()) + } +} + impl TryFrom<&ProtectedMPI> for BigNum { type Error = anyhow::Error; fn try_from(mpi: &ProtectedMPI) -> std::result::Result { @@ -399,13 +427,6 @@ impl Key4 where R: key::KeyRole, { - pub(crate) fn derive_cv25519_public_key(private_key: &Protected) -> Result<[u8; 32]> - { - let key = PKey::private_key_from_raw_bytes(private_key, openssl::pkey::Id::X25519)?; - let public = key.raw_public_key()?; - Ok(<[u8; 32]>::try_from(&public[..])?) - } - /// Creates a new OpenPGP secret key packet for an existing Ed25519 key. /// /// The ECDH key will use hash algorithm `hash` and symmetric diff --git a/openpgp/src/crypto/backend/rust/asymmetric.rs b/openpgp/src/crypto/backend/rust/asymmetric.rs index 38789926..1a558bfb 100644 --- a/openpgp/src/crypto/backend/rust/asymmetric.rs +++ b/openpgp/src/crypto/backend/rust/asymmetric.rs @@ -14,6 +14,7 @@ use rsa::{Pkcs1v15Encrypt, RsaPublicKey, RsaPrivateKey, Pkcs1v15Sign}; use crate::{Error, Result}; use crate::crypto::asymmetric::{KeyPair, Decryptor, Signer}; +use crate::crypto::backend::interface::Asymmetric; use crate::crypto::mem::Protected; use crate::crypto::mpi::{self, MPI, ProtectedMPI}; use crate::crypto::SessionKey; @@ -26,6 +27,42 @@ use super::GenericArrayExt; const CURVE25519_SIZE: usize = 32; +impl Asymmetric for super::Backend { + fn x25519_generate_key() -> Result<(Protected, [u8; 32])> { + use x25519_dalek::{StaticSecret, PublicKey}; + + // x25519_dalek v1.1 doesn't reexport OsRng. It + // depends on rand 0.8. + use rand::rngs::OsRng; + + let secret = StaticSecret::new(&mut OsRng); + let public = PublicKey::from(&secret); + let mut secret_bytes = secret.to_bytes(); + let secret = secret_bytes.as_ref().into(); + unsafe { + memsec::memzero(secret_bytes.as_mut_ptr(), secret_bytes.len()); + } + + Ok((secret, public.to_bytes())) + } + + fn x25519_derive_public(secret: &Protected) -> Result<[u8; 32]> { + use x25519_dalek::{PublicKey, StaticSecret}; + + let secret = StaticSecret::from(<[u8; 32]>::try_from(&secret[..])?); + Ok(*PublicKey::from(&secret).as_bytes()) + } + + fn x25519_shared_point(secret: &Protected, public: &[u8; 32]) + -> Result { + use x25519_dalek::{StaticSecret, PublicKey}; + + let secret = StaticSecret::from(<[u8; 32]>::try_from(&secret[..])?); + let public = PublicKey::from(public.clone()); + Ok((&secret.diffie_hellman(&public).as_bytes()[..]).into()) + } +} + fn pkcs1_padding(hash_algo: HashAlgorithm) -> Result { let hash = match hash_algo { HashAlgorithm::MD5 => Pkcs1v15Sign::new::(), @@ -375,14 +412,6 @@ impl Key { impl Key4 where R: key::KeyRole, { - pub(crate) fn derive_cv25519_public_key(private_key: &Protected) -> Result<[u8; 32]> - { - use x25519_dalek::{PublicKey, StaticSecret}; - - let secret = StaticSecret::from(<[u8; 32]>::try_from(&private_key[..])?); - Ok(*PublicKey::from(&secret).as_bytes()) - } - /// Creates a new OpenPGP secret key packet for an existing Ed25519 key. /// /// The ECDH key will use hash algorithm `hash` and symmetric diff --git a/openpgp/src/crypto/mod.rs b/openpgp/src/crypto/mod.rs index 30bf164b..74a5c860 100644 --- a/openpgp/src/crypto/mod.rs +++ b/openpgp/src/crypto/mod.rs @@ -31,7 +31,7 @@ use crate::{ pub(crate) mod aead; mod asymmetric; pub use self::asymmetric::{Signer, Decryptor, KeyPair}; -mod backend; +pub(crate) mod backend; pub mod ecdh; pub mod hash; pub mod mem; diff --git a/openpgp/src/packet/key.rs b/openpgp/src/packet/key.rs index a81583d3..2750e7a3 100644 --- a/openpgp/src/packet/key.rs +++ b/openpgp/src/packet/key.rs @@ -1066,9 +1066,10 @@ impl Key4 S: Into>, T: Into> { - let mut private_key = Protected::from(private_key); + use crate::crypto::backend::{Backend, interface::Asymmetric}; - let public_key = Self::derive_cv25519_public_key(&private_key)?; + let mut private_key = Protected::from(private_key); + let public_key = Backend::x25519_derive_public(&private_key)?; private_key.reverse(); -- cgit v1.2.3