x25519: use manual impls instead of zeroize_derive (#776)

The types involved are all simple 1-tuple newtypes where zeroization
only involves calling `zeroize` on the inner type, making all of the
involved impls relatively trivial.

Avoiding custom derive arguably improves auditability as you don't need
to expand a proc macro to see the resulting code. It decreases the
number of required dependencies in order for the `zeroize` feature to
work, where some of those dependencies are incredibly heavy
(particularly `syn`).
This commit is contained in:
Tony Arcieri 2025-07-07 08:14:51 -06:00 committed by GitHub
parent 25a9dbb811
commit 37d3ae99d3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 78 additions and 6 deletions

View file

@ -41,7 +41,7 @@ features = ["getrandom", "reusable_secrets", "serde", "static_secrets"]
curve25519-dalek = { version = "4", path = "../curve25519-dalek", default-features = false }
rand_core = { version = "0.6", default-features = false }
serde = { version = "1", default-features = false, optional = true, features = ["derive"] }
zeroize = { version = "1", default-features = false, optional = true, features = ["zeroize_derive"] }
zeroize = { version = "1", default-features = false, optional = true }
[dev-dependencies]
bincode = "1"

View file

@ -29,7 +29,6 @@ use zeroize::{Zeroize, ZeroizeOnDrop};
/// (in this crate) does *not* automatically happen, but either must be derived
/// for Drop or explicitly called.
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "zeroize", derive(Zeroize))]
#[derive(PartialEq, Eq, Hash, Copy, Clone, Debug)]
pub struct PublicKey(pub(crate) MontgomeryPoint);
@ -62,6 +61,13 @@ impl AsRef<[u8]> for PublicKey {
}
}
#[cfg(feature = "zeroize")]
impl Zeroize for PublicKey {
fn zeroize(&mut self) {
self.0.zeroize();
}
}
/// A short-lived Diffie-Hellman secret key that can only be used to compute a single
/// [`SharedSecret`].
///
@ -70,7 +76,6 @@ impl AsRef<[u8]> for PublicKey {
/// are no serialization methods defined. This means that [`EphemeralSecret`]s can only be
/// generated from fresh randomness where the compiler statically checks that the resulting
/// secret is used at most once.
#[cfg_attr(feature = "zeroize", derive(Zeroize, ZeroizeOnDrop))]
pub struct EphemeralSecret(pub(crate) [u8; 32]);
impl EphemeralSecret {
@ -111,6 +116,23 @@ impl<'a> From<&'a EphemeralSecret> for PublicKey {
}
}
impl Drop for EphemeralSecret {
fn drop(&mut self) {
#[cfg(feature = "zeroize")]
self.zeroize();
}
}
#[cfg(feature = "zeroize")]
impl Zeroize for EphemeralSecret {
fn zeroize(&mut self) {
self.0.zeroize();
}
}
#[cfg(feature = "zeroize")]
impl ZeroizeOnDrop for EphemeralSecret {}
/// A Diffie-Hellman secret key which may be used more than once, but is
/// purposefully not serialiseable in order to discourage key-reuse. This is
/// implemented to facilitate protocols such as Noise (e.g. Noise IK key usage,
@ -130,7 +152,6 @@ impl<'a> From<&'a EphemeralSecret> for PublicKey {
/// secret keys are never reused, which can have very serious security
/// implications for many protocols.
#[cfg(feature = "reusable_secrets")]
#[cfg_attr(feature = "zeroize", derive(Zeroize, ZeroizeOnDrop))]
#[derive(Clone)]
pub struct ReusableSecret(pub(crate) [u8; 32]);
@ -174,6 +195,24 @@ impl<'a> From<&'a ReusableSecret> for PublicKey {
}
}
#[cfg(feature = "reusable_secrets")]
impl Drop for ReusableSecret {
fn drop(&mut self) {
#[cfg(feature = "zeroize")]
self.zeroize();
}
}
#[cfg(all(feature = "reusable_secrets", feature = "zeroize"))]
impl Zeroize for ReusableSecret {
fn zeroize(&mut self) {
self.0.zeroize();
}
}
#[cfg(all(feature = "reusable_secrets", feature = "zeroize"))]
impl ZeroizeOnDrop for ReusableSecret {}
/// A Diffie-Hellman secret key that can be used to compute multiple [`SharedSecret`]s.
///
/// This type is identical to the [`EphemeralSecret`] type, except that the
@ -190,7 +229,6 @@ impl<'a> From<&'a ReusableSecret> for PublicKey {
/// implications for many protocols.
#[cfg(feature = "static_secrets")]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "zeroize", derive(Zeroize, ZeroizeOnDrop))]
#[derive(Clone)]
pub struct StaticSecret([u8; 32]);
@ -263,11 +301,28 @@ impl AsRef<[u8]> for StaticSecret {
}
}
#[cfg(feature = "static_secrets")]
impl Drop for StaticSecret {
fn drop(&mut self) {
#[cfg(feature = "zeroize")]
self.zeroize();
}
}
#[cfg(all(feature = "static_secrets", feature = "zeroize"))]
impl Zeroize for StaticSecret {
fn zeroize(&mut self) {
self.0.zeroize();
}
}
#[cfg(all(feature = "static_secrets", feature = "zeroize"))]
impl ZeroizeOnDrop for StaticSecret {}
/// The result of a Diffie-Hellman key exchange.
///
/// Each party computes this using their [`EphemeralSecret`] or [`StaticSecret`] and their
/// counterparty's [`PublicKey`].
#[cfg_attr(feature = "zeroize", derive(Zeroize, ZeroizeOnDrop))]
pub struct SharedSecret(pub(crate) MontgomeryPoint);
impl SharedSecret {
@ -330,6 +385,23 @@ impl AsRef<[u8]> for SharedSecret {
}
}
impl Drop for SharedSecret {
fn drop(&mut self) {
#[cfg(feature = "zeroize")]
self.zeroize();
}
}
#[cfg(feature = "zeroize")]
impl Zeroize for SharedSecret {
fn zeroize(&mut self) {
self.0.zeroize();
}
}
#[cfg(feature = "zeroize")]
impl ZeroizeOnDrop for SharedSecret {}
/// The bare, byte-oriented x25519 function, exactly as specified in RFC7748.
///
/// This can be used with [`X25519_BASEPOINT_BYTES`] for people who