Skip to content

Commit d12edb6

Browse files
committed
Obfuscate SharedSecret when printing
Currently printing the `SharedSecret` using `Display` or `Debug` prints the real secret, this is sub-optimal. We have a solution for other secrets in the project where printing is obfuscated and we provide a `display_secret` method for explicitly printing. Mirror the logic for other secrets and obfuscate the `SharedSecret` when printing.
1 parent c841696 commit d12edb6

File tree

1 file changed

+70
-2
lines changed

1 file changed

+70
-2
lines changed

src/ecdh.rs

Lines changed: 70 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,13 @@
1616
//! Support for shared secret computations
1717
//!
1818
19-
use core::ptr;
19+
use core::{fmt, ptr};
2020
use core::ops::{FnMut, Deref};
2121

2222
use key::{SecretKey, PublicKey};
2323
use ffi::{self, CPtr};
2424
use secp256k1_sys::types::{c_int, c_uchar, c_void};
25+
use to_hex;
2526

2627
/// The buffer size (in bytes) used by `SharedSecret`.
2728
const BUF_SIZE: usize = 256;
@@ -48,7 +49,7 @@ pub struct SharedSecret {
4849
data: [u8; BUF_SIZE],
4950
len: usize,
5051
}
51-
impl_raw_debug!(SharedSecret);
52+
impl_display_secret!(SharedSecret);
5253

5354
// This implements `From<N>` for all `[u8; N]` arrays from 128bits(16 byte) to 2048bits allowing known hash lengths.
5455
// Lower than 128 bits isn't resistant to collisions any more.
@@ -88,6 +89,11 @@ impl SharedSecret {
8889
debug_assert!(len <= self.data.len());
8990
self.len = len;
9091
}
92+
93+
/// Serializes the whole shared secret buffer as byte value.
94+
pub(crate) fn serialize_secret(&self) -> [u8; BUF_SIZE] {
95+
self.data
96+
}
9197
}
9298

9399
impl PartialEq for SharedSecret {
@@ -183,6 +189,68 @@ impl SharedSecret {
183189
y.copy_from_slice(&xy[32..]);
184190
hash_function(x, y)
185191
}
192+
193+
/// Formats the explicit byte value of the shared secret kept inside the type as a
194+
/// little-endian hexadecimal string using the provided formatter.
195+
///
196+
/// This is the only method that outputs the actual shared secret value, and, thus,
197+
/// should be used with extreme precaution.
198+
///
199+
/// # Example
200+
///
201+
/// ```
202+
/// # #[cfg(all(feature = "std", not(feature = "bitcoin_hashes")))] {
203+
/// # use std::str::FromStr;
204+
/// # use secp256k1::{SecretKey, PublicKey};
205+
/// use secp256k1::ecdh::SharedSecret;
206+
/// # let pk = PublicKey::from_slice(&[3, 23, 183, 225, 206, 31, 159, 148, 195, 42, 67, 115, 146, 41, 248, 140, 11, 3, 51, 41, 111, 180, 110, 143, 114, 134, 88, 73, 198, 174, 52, 184, 78]).expect("hard coded slice should parse correctly");
207+
/// # let sk = SecretKey::from_str("57f0148f94d13095cfda539d0da0d1541304b678d8b36e243980aab4e1b7cead").unwrap();
208+
/// let secret = SharedSecret::new(&pk, &sk);
209+
/// // Normal display hides the value.
210+
/// assert_eq!(format!("{:?}", secret), "SharedSecret(#6b778f6da1cd7d46)");
211+
/// // Here we explicitly display the secret value:
212+
/// assert_eq!(
213+
/// format!("{}", secret.display_secret()),
214+
/// "cf05ae7da039ddce6d56dd57d3000c6dd91c6f1695eae47e05389f11e24670430000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000");
215+
/// // Can explicitly display with `{}` and `{:?}`.
216+
/// assert_eq!(
217+
/// format!("{:?}", secret.display_secret()),
218+
/// format!("DisplaySecret(\"{}\")", secret.display_secret()));
219+
/// # }
220+
/// ```
221+
#[inline]
222+
pub fn display_secret(&self) -> DisplaySecret {
223+
DisplaySecret { secret: self.serialize_secret() }
224+
}
225+
}
226+
227+
/// Helper struct for safely printing a shared secret.
228+
///
229+
/// Formats the explicit byte value of the secret as a little-endian hexadecimal string using the
230+
/// provided formatter.
231+
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
232+
pub struct DisplaySecret {
233+
secret: [u8; BUF_SIZE]
234+
}
235+
236+
impl fmt::Debug for DisplaySecret {
237+
#[inline]
238+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
239+
let mut slice = [0u8; BUF_SIZE * 2];
240+
let hex = to_hex(&self.secret, &mut slice).expect("fixed-size hex serializer failed");
241+
f.debug_tuple("DisplaySecret")
242+
.field(&hex)
243+
.finish()
244+
}
245+
}
246+
247+
impl fmt::Display for DisplaySecret {
248+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
249+
for i in &self.secret {
250+
write!(f, "{:02x}", i)?;
251+
}
252+
Ok(())
253+
}
186254
}
187255

188256
#[cfg(test)]

0 commit comments

Comments
 (0)