Skip to content

Commit 6b0961c

Browse files
committed
implement insecure-erase feature
1 parent 6ec968a commit 6b0961c

File tree

9 files changed

+74
-1
lines changed

9 files changed

+74
-1
lines changed

Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ global-context = ["std"]
3535
# if you are doing a no-std build, then this feature does nothing
3636
# and is not necessary.)
3737
global-context-less-secure = ["global-context"]
38+
insecure-erase = ["secp256k1-sys/insecure-erase"]
3839

3940
[dependencies]
4041
secp256k1-sys = { version = "0.8.0", default-features = false, path = "./secp256k1-sys" }

README.md

+4
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 the `insecure-erase` feature
42+
43+
The `insecure-erase` feature is provided to assist other libraries in building secure secret erasure. When the `insecure-erase` feature is enabled, secret types (`SecretKey`, `KeyPair`, `SharedSecret`, `Scalar`, and `DisplaySecret`) have a method called `insecure_erase` that *attempts* to overwrite the contained secret. This library makes no guarantees about the security of using `insecure_erase`. In particular, since all of these types are `Copy`, the compiler is free to move and copy them, thereby making additional copies in memory. 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

+1-1
Original file line numberDiff line numberDiff line change
@@ -32,4 +32,4 @@ recovery = []
3232
lowmemory = []
3333
std = ["alloc"]
3434
alloc = []
35-
35+
insecure-erase = []

secp256k1-sys/src/lib.rs

+27
Original file line numberDiff line numberDiff line change
@@ -454,6 +454,33 @@ 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 [`zeroize`] crate.
462+
/// [`zeroize`]: https://docs.rs/zeroize
463+
#[cfg(feature = "insecure-erase")]
464+
#[inline]
465+
pub fn insecure_erase(&mut self) {
466+
// DUMMY is a valid key pair with secret key `[1u8; 32]`
467+
const DUMMY: [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];
468+
insecure_erase_impl(&mut self.0, DUMMY);
469+
}
470+
}
471+
472+
/// This function implements a best attempt at secure erasure using Rust intrinsics.
473+
/// The implementation is based on the approach used by the [`zeroize`] crate.
474+
/// [`zeroize`]: https://docs.rs/zeroize
475+
#[cfg(feature = "insecure-erase")]
476+
#[inline(always)]
477+
pub fn insecure_erase_impl<T>(dst: &mut T, src: T) {
478+
use core::sync::atomic;
479+
// overwrite using volatile value
480+
unsafe { ptr::write_volatile(dst, src); }
481+
482+
// prevent future accesses from being reordered to before erasure
483+
atomic::compiler_fence(atomic::Ordering::SeqCst);
457484
}
458485

459486
#[cfg(not(fuzzing))]

src/ecdh.rs

+1
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_insecure_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

+21
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_insecure_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,15 @@ 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 [`zeroize`] crate.
1001+
/// [`zeroize`]: https://docs.rs/zeroize
1002+
#[inline]
1003+
#[cfg(feature = "insecure-erase")]
1004+
pub fn insecure_erase(&mut self) { self.0.insecure_erase(); }
9951005
}
9961006

9971007
impl From<KeyPair> for SecretKey {
@@ -1603,6 +1613,17 @@ mod test {
16031613
assert_eq!(PublicKey::from_slice(&pk1.serialize_uncompressed()[..]), Ok(pk1));
16041614
}
16051615

1616+
#[test]
1617+
#[cfg(feature = "insecure-erase")]
1618+
fn erased_keypair_is_valid() {
1619+
let s = Secp256k1::new();
1620+
let kp = KeyPair::from_seckey_slice(&s, &[1u8; constants::SECRET_KEY_SIZE])
1621+
.expect("valid secret key");
1622+
let mut kp2 = kp;
1623+
kp2.insecure_erase();
1624+
assert!(kp.eq_fast_unstable(&kp2));
1625+
}
1626+
16061627
#[test]
16071628
#[rustfmt::skip]
16081629
fn invalid_secret_key() {

src/macros.rs

+17
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,23 @@ macro_rules! impl_pretty_debug {
9191
};
9292
}
9393

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

src/scalar.rs

+1
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_insecure_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

+1
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_insecure_erase!(DisplaySecret, secret, [0u8; SECRET_KEY_SIZE]);
8990

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

0 commit comments

Comments
 (0)