Skip to content

Commit ead3d4f

Browse files
committed
implement "non_secure_erase" methods
This PR implements a `non_secure_erase()` method on SecretKey, KeyPair, SharedSecret, Scalar, and DisplaySecret. The purpose of this method is to (attempt to) overwrite secret data with valid default values. This method can be used by libraries to implement Zeroize on structs containing secret values. `non_secure_erase()` attempts to avoid being optimized away or reordered using the same mechanism as the zeroize crate: first, using `std::ptr::write_volatile` (which will not be optimized away) to overwrite the memory, then using a memory fence to prevent subtle issues due to load or store reordering. Note, however, that this method is *very unlikely* to do anything useful on its own. Effective use involves carefully placing these values inside non-Copy structs and pinning those structs in place. See the [`zeroize`](https://docs.rs/zeroize) documentation for tips and tricks, and for further discussion.
1 parent 6ec968a commit ead3d4f

File tree

8 files changed

+73
-1
lines changed

8 files changed

+73
-1
lines changed

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,10 @@ Alternatively add symlinks in your `.git/hooks` directory to any of the githooks
3838
We use a custom Rust compiler configuration conditional to guard the bench mark code. To run the
3939
bench marks use: `RUSTFLAGS='--cfg=bench' cargo +nightly bench --features=recovery`.
4040

41+
### A note on `non_secure_erase`
42+
43+
This crate's secret types (`SecretKey`, `KeyPair`, `SharedSecret`, `Scalar`, and `DisplaySecret`) have a method called `non_secure_erase` that *attempts* to overwrite the contained secret. This method is provided to assist other libraries in building secure secret erasure. However, this library makes no guarantees about the security of using `non_secure_erase`. In particular, the compiler doesn't have any concept of secrets and in most cases can arbitrarily move or copy values anywhere it pleases. For more information, consult the [`zeroize`](https://docs.rs/zeroize) documentation.
44+
4145
## Fuzzing
4246

4347
If you want to fuzz this library, or any library which depends on it, you will

secp256k1-sys/Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,4 +32,3 @@ recovery = []
3232
lowmemory = []
3333
std = ["alloc"]
3434
alloc = []
35-

secp256k1-sys/src/lib.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -454,6 +454,36 @@ impl KeyPair {
454454
pk
455455
}
456456
}
457+
458+
/// This function attempts to erase the contents of the underlying array.
459+
/// Note, however, that since this type is `Copy`, ensuring that the contents
460+
/// of this array don't end up elsewhere in memory is very subtle. For more
461+
/// discussion on this, please see the documentation of the
462+
/// [`zeroize`](https://docs.rs/zeroize) crate.
463+
#[inline]
464+
pub fn non_secure_erase(&mut self) {
465+
non_secure_erase_impl(&mut self.0, DUMMY_KEYPAIR);
466+
}
467+
}
468+
469+
// DUMMY_KEYPAIR is the internal repr of a valid key pair with secret key `[1u8; 32]`
470+
#[cfg(target_endian = "little")]
471+
const DUMMY_KEYPAIR: [c_uchar; 96] = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 143, 7, 221, 213, 233, 245, 23, 156, 255, 25, 72, 96, 52, 24, 30, 215, 101, 5, 186, 170, 213, 62, 93, 153, 64, 100, 18, 123, 86, 197, 132, 27, 209, 232, 168, 105, 122, 212, 34, 81, 222, 57, 246, 167, 32, 129, 223, 223, 66, 171, 197, 66, 166, 214, 254, 7, 21, 84, 139, 88, 143, 175, 190, 112];
472+
#[cfg(all(target_endian = "big", target_pointer_width = "32"))]
473+
const DUMMY_KEYPAIR: [c_uchar; 96] = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 213, 221, 7, 143, 156, 23, 245, 233, 96, 72, 25, 255, 215, 30, 24, 52, 170, 186, 5, 101, 153, 93, 62, 213, 123, 18, 100, 64, 27, 132, 197, 86, 105, 168, 232, 209, 81, 34, 212, 122, 167, 246, 57, 222, 223, 223, 129, 32, 66, 197, 171, 66, 7, 254, 214, 166, 88, 139, 84, 21, 112, 190, 175, 143];
474+
#[cfg(all(target_endian = "big", target_pointer_width = "64"))]
475+
const DUMMY_KEYPAIR: [c_uchar; 96] = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 156, 23, 245, 233, 213, 221, 7, 143, 215, 30, 24, 52, 96, 72, 25, 255, 153, 93, 62, 213, 170, 186, 5, 101, 27, 132, 197, 86, 123, 18, 100, 64, 81, 34, 212, 122, 105, 168, 232, 209, 223, 223, 129, 32, 167, 246, 57, 222, 7, 254, 214, 166, 66, 197, 171, 66, 112, 190, 175, 143, 88, 139, 84, 21];
476+
477+
/// This function implements a best attempt at secure erasure using Rust intrinsics.
478+
/// The implementation is based on the approach used by the [`zeroize`](https://docs.rs/zeroize) crate.
479+
#[inline(always)]
480+
pub fn non_secure_erase_impl<T>(dst: &mut T, src: T) {
481+
use core::sync::atomic;
482+
// overwrite using volatile value
483+
unsafe { ptr::write_volatile(dst, src); }
484+
485+
// prevent future accesses from being reordered to before erasure
486+
atomic::compiler_fence(atomic::Ordering::SeqCst);
457487
}
458488

459489
#[cfg(not(fuzzing))]

src/ecdh.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ const SHARED_SECRET_SIZE: usize = constants::SECRET_KEY_SIZE;
4646
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
4747
pub struct SharedSecret([u8; SHARED_SECRET_SIZE]);
4848
impl_display_secret!(SharedSecret);
49+
impl_non_secure_erase!(SharedSecret, 0, [0u8; SHARED_SECRET_SIZE]);
4950

5051
impl SharedSecret {
5152
/// Creates a new shared secret from a pubkey and secret key.

src/key.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ use crate::{hashes, ThirtyTwoByteHash};
6666
#[derive(Copy, Clone)]
6767
pub struct SecretKey([u8; constants::SECRET_KEY_SIZE]);
6868
impl_display_secret!(SecretKey);
69+
impl_non_secure_erase!(SecretKey, 0, [1u8; constants::SECRET_KEY_SIZE]);
6970

7071
impl PartialEq for SecretKey {
7172
/// This implementation is designed to be constant time to help prevent side channel attacks.
@@ -992,6 +993,14 @@ impl KeyPair {
992993
pub fn sign_schnorr(&self, msg: Message) -> schnorr::Signature {
993994
SECP256K1.sign_schnorr(&msg, self)
994995
}
996+
997+
/// This function attempts to erase the contents of the underlying array.
998+
/// Note, however, that since this type is `Copy`, ensuring that the contents
999+
/// of this array don't end up elsewhere in memory is very subtle. For more
1000+
/// discussion on this, please see the documentation of the
1001+
/// [`zeroize`](https://docs.rs/zeroize) crate.
1002+
#[inline]
1003+
pub fn non_secure_erase(&mut self) { self.0.non_secure_erase(); }
9951004
}
9961005

9971006
impl From<KeyPair> for SecretKey {
@@ -1603,6 +1612,17 @@ mod test {
16031612
assert_eq!(PublicKey::from_slice(&pk1.serialize_uncompressed()[..]), Ok(pk1));
16041613
}
16051614

1615+
#[test]
1616+
#[cfg(all(feature = "std", not(fuzzing)))]
1617+
fn erased_keypair_is_valid() {
1618+
let s = Secp256k1::new();
1619+
let kp = KeyPair::from_seckey_slice(&s, &[1u8; constants::SECRET_KEY_SIZE])
1620+
.expect("valid secret key");
1621+
let mut kp2 = kp;
1622+
kp2.non_secure_erase();
1623+
assert!(kp.eq_fast_unstable(&kp2));
1624+
}
1625+
16061626
#[test]
16071627
#[rustfmt::skip]
16081628
fn invalid_secret_key() {

src/macros.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,22 @@ macro_rules! impl_pretty_debug {
9191
};
9292
}
9393

94+
macro_rules! impl_non_secure_erase {
95+
($thing:ident, $target:tt, $value:expr) => {
96+
impl $thing {
97+
/// This function attempts to erase the contents of the underlying array.
98+
/// Note, however, that since this type is `Copy`, ensuring that the contents
99+
/// of this array don't end up elsewhere in memory is very subtle. For more
100+
/// discussion on this, please see the documentation of the
101+
/// [`zeroize`](https://docs.rs/zeroize) crate.
102+
#[inline]
103+
pub fn non_secure_erase(&mut self) {
104+
secp256k1_sys::non_secure_erase_impl(&mut self.$target, $value);
105+
}
106+
}
107+
};
108+
}
109+
94110
/// Formats error. If `std` feature is OFF appends error source (delimited by `: `). We do this
95111
/// because `e.source()` is only available in std builds, without this macro the error source is
96112
/// lost for no-std builds.

src/scalar.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ use crate::constants;
2323
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
2424
pub struct Scalar([u8; 32]);
2525
impl_pretty_debug!(Scalar);
26+
impl_non_secure_erase!(Scalar, 0, [0u8; 32]);
2627

2728
const MAX_RAW: [u8; 32] = [
2829
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE,

src/secret.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ macro_rules! impl_display_secret {
8686
pub struct DisplaySecret {
8787
secret: [u8; SECRET_KEY_SIZE],
8888
}
89+
impl_non_secure_erase!(DisplaySecret, secret, [0u8; SECRET_KEY_SIZE]);
8990

9091
impl fmt::Debug for DisplaySecret {
9192
#[inline]

0 commit comments

Comments
 (0)