Skip to content

Commit c931088

Browse files
committed
Merge #582: implement insecure-erase feature (was: Zeroize)
8fffbea implement "non_secure_erase" methods (kwantam) Pull request description: This PR adds [`Zeroize`](https://docs.rs/zeroize) derivations for the following structs: - `SecretKey` - `KeyPair` - `SharedSecret` - `Scalar` - `DisplaySecret` This is *only* a Zeroize impl, and does not make Zeroize happen automatically on drop (doing that would be a breaking change because it would preclude deriving `Copy`). But this is still useful, because it allows downstream libraries to implement `ZeroizeOnDrop` for structs that contain such secrets and/or simply to use the `Zeroizing` container struct. Because these new impls are never invoked automatically, performance impact should be zero. Safety-wise, the `Zeroize` library appears to be widely used in cryptographic code. For example, Supranational's [blst](https://github.com/supranational/blst) Rust bindings use it, and in turn are used in one of the most popular eth2 validator implementations. Thanks for maintaining a really great library! ACKs for top commit: tcharding: FWIW ACK 8fffbea apoelstra: ACK 8fffbea Tree-SHA512: 28d2cdcc6bd2d2d6330b67ae8635561882e869199d8fef9a3ebc3f368a7a0c2c00b818281190133f551b099e9c5226f104a56edc14c9b6f699ceba49e4b24563
2 parents 6ec968a + 8fffbea commit c931088

File tree

8 files changed

+83
-1
lines changed

8 files changed

+83
-1
lines changed

README.md

+10
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,16 @@ 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`)
44+
have a method called `non_secure_erase` that *attempts* to overwrite the contained secret. This
45+
method is provided to assist other libraries in building secure secret erasure. However, this
46+
library makes no guarantees about the security of using `non_secure_erase`. In particular,
47+
the compiler doesn't have any concept of secrets and in most cases can arbitrarily move or copy
48+
values anywhere it pleases. For more information, consult the [`zeroize`](https://docs.rs/zeroize)
49+
documentation.
50+
4151
## Fuzzing
4252

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

secp256k1-sys/Cargo.toml

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

+32
Original file line numberDiff line numberDiff line change
@@ -454,6 +454,38 @@ impl KeyPair {
454454
pk
455455
}
456456
}
457+
458+
/// Attempts to erase the contents of the underlying array.
459+
///
460+
/// Note, however, that the compiler is allowed to freely copy or move the
461+
/// contents of this array to other places in memory. Preventing this behavior
462+
/// is very subtle. For more discussion on this, please see the documentation
463+
/// of the [`zeroize`](https://docs.rs/zeroize) crate.
464+
#[inline]
465+
pub fn non_secure_erase(&mut self) {
466+
non_secure_erase_impl(&mut self.0, DUMMY_KEYPAIR);
467+
}
468+
}
469+
470+
// DUMMY_KEYPAIR is the internal repr of a valid key pair with secret key `[1u8; 32]`
471+
#[cfg(target_endian = "little")]
472+
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];
473+
#[cfg(all(target_endian = "big", target_pointer_width = "32"))]
474+
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];
475+
#[cfg(all(target_endian = "big", target_pointer_width = "64"))]
476+
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];
477+
478+
/// Does a best attempt at secure erasure using Rust intrinsics.
479+
///
480+
/// The implementation is based on the approach used by the [`zeroize`](https://docs.rs/zeroize) crate.
481+
#[inline(always)]
482+
pub fn non_secure_erase_impl<T>(dst: &mut T, src: T) {
483+
use core::sync::atomic;
484+
// overwrite using volatile value
485+
unsafe { ptr::write_volatile(dst, src); }
486+
487+
// prevent future accesses from being reordered to before erasure
488+
atomic::compiler_fence(atomic::Ordering::SeqCst);
457489
}
458490

459491
#[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_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

+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_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,15 @@ impl KeyPair {
992993
pub fn sign_schnorr(&self, msg: Message) -> schnorr::Signature {
993994
SECP256K1.sign_schnorr(&msg, self)
994995
}
996+
997+
/// Attempts to erase the secret within the underlying array.
998+
///
999+
/// Note, however, that the compiler is allowed to freely copy or move the contents
1000+
/// of this array to other places in memory. Preventing this behavior is very subtle.
1001+
/// For more discussion on this, please see the documentation of the
1002+
/// [`zeroize`](https://docs.rs/zeroize) crate.
1003+
#[inline]
1004+
pub fn non_secure_erase(&mut self) { self.0.non_secure_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(all(feature = "std", not(fuzzing)))]
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.non_secure_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_non_secure_erase {
95+
($thing:ident, $target:tt, $value:expr) => {
96+
impl $thing {
97+
/// Attempts to erase the contents of the underlying array.
98+
///
99+
/// Note, however, that the compiler is allowed to freely copy or move the
100+
/// contents of this array to other places in memory. Preventing this behavior
101+
/// is very subtle. For more discussion on this, please see the documentation
102+
/// of the [`zeroize`](https://docs.rs/zeroize) crate.
103+
#[inline]
104+
pub fn non_secure_erase(&mut self) {
105+
secp256k1_sys::non_secure_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_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

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

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

0 commit comments

Comments
 (0)