Skip to content

Commit

Permalink
feat: add Zeroize support to key types, and create new shared secre…
Browse files Browse the repository at this point in the history
…t type (#137)

* Add `Zeroize` support to key types

* Use zeroize-on-drop macro

* Ensure ECDH secrets are zeroized on drop

* Add new DHKE shared secret type
  • Loading branch information
AaronFeickert committed Oct 25, 2022
1 parent c658619 commit 532ccc0
Show file tree
Hide file tree
Showing 4 changed files with 82 additions and 26 deletions.
48 changes: 48 additions & 0 deletions src/dhke.rs
@@ -0,0 +1,48 @@
// Copyright 2022 The Tari Project
// SPDX-License-Identifier: BSD-3-Clause

//! The robotic innards of a Diffie-Hellman key exchange (DHKE) producing a shared secret.
//! Even though the result of a DHKE is the same type as a public key, it is typically treated as a secret value.
//! To make this work more safely, we ensure that a DHKE result is cleared after use (but beware of subsequent copies or moves).
//! Because a DHKE shared secret is intended to be used in further key derivation, the only visibility into it is as a byte array; it's not possible to directly extract the underlying public key type, and you probably shouldn't clone the byte array without a very good reason.
//! If you need the underlying public key itself, you probably should be using something else.

use std::ops::Mul;

use zeroize::Zeroize;

use crate::keys::PublicKey;

pub struct DiffieHellmanSharedSecret<P>(P)
where P: Zeroize;

impl<P> DiffieHellmanSharedSecret<P>
where
P: PublicKey + Zeroize,
for<'a> &'a <P as PublicKey>::K: Mul<&'a P, Output = P>,
{
/// Perform a Diffie-Hellman key exchange
pub fn new(sk: &P::K, pk: &P) -> Self {
Self(sk * pk)
}

/// Get the shared secret as a byte array
pub fn as_bytes(&self) -> &[u8] {
self.0.as_bytes()
}
}

impl<P> Zeroize for DiffieHellmanSharedSecret<P> where P: Zeroize {
/// Zeroize the shared secret's underlying public key
fn zeroize(&mut self) {
self.0.zeroize();
}
}

impl<P> Drop for DiffieHellmanSharedSecret<P> where P: Zeroize {
/// Zeroize the shared secret when out of scope or otherwise dropped
fn drop(&mut self) {
self.zeroize();
}
}

8 changes: 0 additions & 8 deletions src/keys.rs
Expand Up @@ -68,11 +68,3 @@ pub trait PublicKey:
(k, pk)
}
}

/// This trait provides a common mechanism to calculate a shared secret using the private and public key of two parties
pub trait DiffieHellmanSharedSecret: ByteArray + Clone + PartialEq + Eq + Add<Output = Self> + Default {
/// The type of public key
type PK: PublicKey;
/// Generate a shared secret from one party's private key and another party's public key
fn shared_secret(k: &<Self::PK as PublicKey>::K, pk: &Self::PK) -> Self::PK;
}
1 change: 1 addition & 0 deletions src/lib.rs
Expand Up @@ -9,6 +9,7 @@ extern crate lazy_static;
#[macro_use]
mod macros;
pub mod commitment;
pub mod dhke;
pub mod hash;
pub mod hashing;
pub mod keys;
Expand Down
51 changes: 33 additions & 18 deletions src/ristretto/ristretto_keys.rs
Expand Up @@ -26,7 +26,7 @@ use zeroize::Zeroize;
use crate::{
errors::HashingError,
hashing::{DerivedKeyDomain, DomainSeparatedHasher, DomainSeparation},
keys::{DiffieHellmanSharedSecret, PublicKey, SecretKey},
keys::{PublicKey, SecretKey},
};

/// The [SecretKey](trait.SecretKey.html) implementation for [Ristretto](https://ristretto.group) is a thin wrapper
Expand All @@ -49,7 +49,8 @@ use crate::{
/// let _k2 = RistrettoSecretKey::from_hex(&"100000002000000030000000040000000");
/// let _k3 = RistrettoSecretKey::random(&mut rng);
/// ```
#[derive(Eq, Clone, Default)]
#[derive(Eq, Clone, Default, Zeroize)]
#[zeroize(drop)]
pub struct RistrettoSecretKey(pub(crate) Scalar);

const SCALAR_LENGTH: usize = 32;
Expand All @@ -67,13 +68,6 @@ impl SecretKey for RistrettoSecretKey {
}
}

impl Drop for RistrettoSecretKey {
/// Clear the secret key value in memory when it goes out of scope
fn drop(&mut self) {
self.0.zeroize()
}
}

//------------------------------------- Ristretto Secret Key ByteArray ---------------------------------------------//

impl ByteArray for RistrettoSecretKey {
Expand Down Expand Up @@ -280,6 +274,18 @@ impl RistrettoPublicKey {
}
}

impl Zeroize for RistrettoPublicKey {
/// Zeroizes both the point and (if it exists) the compressed point
fn zeroize(&mut self) {
self.point.zeroize();

// Need to empty the cell
if let Some(mut compressed) = self.compressed.take() {
compressed.zeroize();
}
}
}

//--------------------------------------- Ristretto Hashing Applications ------------------------------------------//

/// The Domain Separation Tag type for the KDF algorithm, version 1
Expand Down Expand Up @@ -330,15 +336,6 @@ impl PublicKey for RistrettoPublicKey {
}
}

impl DiffieHellmanSharedSecret for RistrettoPublicKey {
type PK = RistrettoPublicKey;

/// Generate a shared secret from one party's private key and another party's public key
fn shared_secret(k: &<Self::PK as PublicKey>::K, pk: &Self::PK) -> Self::PK {
k * pk
}
}

// Requires custom Hashable implementation for RistrettoPublicKey as CompressedRistretto doesnt implement this trait
impl Hashable for RistrettoPublicKey {
fn hash(&self) -> Vec<u8> {
Expand Down Expand Up @@ -830,4 +827,22 @@ mod test {
let visible = format!("{:?}", key.reveal());
assert!(visible.contains("016c"));
}

#[test]
fn zeroize_test() {
let mut rng = rand::thread_rng();
let zeros = [0u8; 32];

// Zeroize scalar
let mut s = RistrettoSecretKey::random(&mut rng);
s.zeroize();
assert_eq!(s.as_bytes(), &zeros);

// Zeroize point
let mut p = RistrettoPublicKey::from_secret_key(&RistrettoSecretKey::random(&mut rng));
p.zeroize();
assert_eq!(p.compressed.get(), None); // no compressed point yet
assert_eq!(p.as_bytes(), &zeros); // this compresses the point
assert_eq!(p.compressed.get().unwrap().as_bytes(), &zeros); // check directly for good measure
}
}

0 comments on commit 532ccc0

Please sign in to comment.