summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJustus Winter <justus@sequoia-pgp.org>2023-05-12 11:36:37 +0200
committerJustus Winter <justus@sequoia-pgp.org>2023-05-22 11:59:48 +0200
commit72db601361ab570d3f3c741ee941a562a0b3f885 (patch)
treed99fa4e083b45cc8854c1288334560bd8ba0bf67
parent8553d278249bd183517d8a497adb32144772ad50 (diff)
openpgp: Add asymmetric encryption trait.
- As first step, abstract over X25519.
-rw-r--r--openpgp/src/crypto/backend/botan/asymmetric.rs38
-rw-r--r--openpgp/src/crypto/backend/cng/asymmetric.rs75
-rw-r--r--openpgp/src/crypto/backend/interface.rs22
-rw-r--r--openpgp/src/crypto/backend/nettle/asymmetric.rs40
-rw-r--r--openpgp/src/crypto/backend/openssl/asymmetric.rs35
-rw-r--r--openpgp/src/crypto/backend/rust/asymmetric.rs45
-rw-r--r--openpgp/src/crypto/mod.rs2
-rw-r--r--openpgp/src/packet/key.rs5
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<Protected> {
+ 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<P: key::KeyParts, R: key::KeyRole> Key<P, R> {
impl<R> Key4<SecretParts, R>
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::<Ecdh<Curve25519>, Private>::import_from_parts(
+ &provider,
+ secret,
+ )?;
+ Ok(<[u8; 32]>::try_from(&key.export()?.x()[..])?)
+ }
+
+ fn x25519_shared_point(secret: &Protected, public: &[u8; 32])
+ -> Result<Protected> {
+ 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::<Ecdh<Curve25519>, Public>::import_from_parts(
+ &provider,
+ public,
+ )?;
+ let secret =
+ AsymmetricKey::<Ecdh<Curve25519>, 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<key::PublicParts, key::UnspecifiedRole> {
KeyPair::public(self)
@@ -693,22 +752,6 @@ impl<R> Key4<SecretParts, R>
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::<Ecdh<Curve25519>, 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<Protected>;
+}
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<Protected> {
+ 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<key::PublicParts, key::UnspecifiedRole> {
KeyPair::public(self)
@@ -333,13 +360,6 @@ use crate::types::PublicKeyAlgorithm;
impl<R> Key4<SecretParts, R>
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<R> Key4<SecretParts, R>
}
(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<Protected> {
+ 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<BigNum, anyhow::Error> {
@@ -399,13 +427,6 @@ impl<R> Key4<SecretParts, R>
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<Protected> {
+ 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<Pkcs1v15Sign> {
let hash = match hash_algo {
HashAlgorithm::MD5 => Pkcs1v15Sign::new::<md5::Md5>(),
@@ -375,14 +412,6 @@ impl<P: key::KeyParts, R: key::KeyRole> Key<P, R> {
impl<R> Key4<SecretParts, R>
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<R> Key4<SecretParts, R>
S: Into<Option<SymmetricAlgorithm>>,
T: Into<Option<std::time::SystemTime>>
{
- 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();