Merge rust-bitcoin/rust-secp256k1#582: implement `insecure-erase` feature (was: `Zeroize`)
8fffbeab13
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 ACK8fffbeab13
apoelstra: ACK8fffbeab13
Tree-SHA512: 28d2cdcc6bd2d2d6330b67ae8635561882e869199d8fef9a3ebc3f368a7a0c2c00b818281190133f551b099e9c5226f104a56edc14c9b6f699ceba49e4b24563
This commit is contained in:
commit
c9310884b6
10
README.md
10
README.md
|
@ -38,6 +38,16 @@ Alternatively add symlinks in your `.git/hooks` directory to any of the githooks
|
||||||
We use a custom Rust compiler configuration conditional to guard the bench mark code. To run the
|
We use a custom Rust compiler configuration conditional to guard the bench mark code. To run the
|
||||||
bench marks use: `RUSTFLAGS='--cfg=bench' cargo +nightly bench --features=recovery`.
|
bench marks use: `RUSTFLAGS='--cfg=bench' cargo +nightly bench --features=recovery`.
|
||||||
|
|
||||||
|
### A note on `non_secure_erase`
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
## Fuzzing
|
## Fuzzing
|
||||||
|
|
||||||
If you want to fuzz this library, or any library which depends on it, you will
|
If you want to fuzz this library, or any library which depends on it, you will
|
||||||
|
|
|
@ -32,4 +32,3 @@ recovery = []
|
||||||
lowmemory = []
|
lowmemory = []
|
||||||
std = ["alloc"]
|
std = ["alloc"]
|
||||||
alloc = []
|
alloc = []
|
||||||
|
|
||||||
|
|
|
@ -454,6 +454,38 @@ impl KeyPair {
|
||||||
pk
|
pk
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Attempts to erase the contents of the underlying array.
|
||||||
|
///
|
||||||
|
/// Note, however, that the compiler is allowed to freely copy or move the
|
||||||
|
/// contents of this array to other places in memory. Preventing this behavior
|
||||||
|
/// is very subtle. For more discussion on this, please see the documentation
|
||||||
|
/// of the [`zeroize`](https://docs.rs/zeroize) crate.
|
||||||
|
#[inline]
|
||||||
|
pub fn non_secure_erase(&mut self) {
|
||||||
|
non_secure_erase_impl(&mut self.0, DUMMY_KEYPAIR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DUMMY_KEYPAIR is the internal repr of a valid key pair with secret key `[1u8; 32]`
|
||||||
|
#[cfg(target_endian = "little")]
|
||||||
|
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];
|
||||||
|
#[cfg(all(target_endian = "big", target_pointer_width = "32"))]
|
||||||
|
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];
|
||||||
|
#[cfg(all(target_endian = "big", target_pointer_width = "64"))]
|
||||||
|
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];
|
||||||
|
|
||||||
|
/// Does a best attempt at secure erasure using Rust intrinsics.
|
||||||
|
///
|
||||||
|
/// The implementation is based on the approach used by the [`zeroize`](https://docs.rs/zeroize) crate.
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn non_secure_erase_impl<T>(dst: &mut T, src: T) {
|
||||||
|
use core::sync::atomic;
|
||||||
|
// overwrite using volatile value
|
||||||
|
unsafe { ptr::write_volatile(dst, src); }
|
||||||
|
|
||||||
|
// prevent future accesses from being reordered to before erasure
|
||||||
|
atomic::compiler_fence(atomic::Ordering::SeqCst);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(fuzzing))]
|
#[cfg(not(fuzzing))]
|
||||||
|
|
|
@ -46,6 +46,7 @@ const SHARED_SECRET_SIZE: usize = constants::SECRET_KEY_SIZE;
|
||||||
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
pub struct SharedSecret([u8; SHARED_SECRET_SIZE]);
|
pub struct SharedSecret([u8; SHARED_SECRET_SIZE]);
|
||||||
impl_display_secret!(SharedSecret);
|
impl_display_secret!(SharedSecret);
|
||||||
|
impl_non_secure_erase!(SharedSecret, 0, [0u8; SHARED_SECRET_SIZE]);
|
||||||
|
|
||||||
impl SharedSecret {
|
impl SharedSecret {
|
||||||
/// Creates a new shared secret from a pubkey and secret key.
|
/// Creates a new shared secret from a pubkey and secret key.
|
||||||
|
|
21
src/key.rs
21
src/key.rs
|
@ -66,6 +66,7 @@ use crate::{hashes, ThirtyTwoByteHash};
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone)]
|
||||||
pub struct SecretKey([u8; constants::SECRET_KEY_SIZE]);
|
pub struct SecretKey([u8; constants::SECRET_KEY_SIZE]);
|
||||||
impl_display_secret!(SecretKey);
|
impl_display_secret!(SecretKey);
|
||||||
|
impl_non_secure_erase!(SecretKey, 0, [1u8; constants::SECRET_KEY_SIZE]);
|
||||||
|
|
||||||
impl PartialEq for SecretKey {
|
impl PartialEq for SecretKey {
|
||||||
/// This implementation is designed to be constant time to help prevent side channel attacks.
|
/// This implementation is designed to be constant time to help prevent side channel attacks.
|
||||||
|
@ -992,6 +993,15 @@ impl KeyPair {
|
||||||
pub fn sign_schnorr(&self, msg: Message) -> schnorr::Signature {
|
pub fn sign_schnorr(&self, msg: Message) -> schnorr::Signature {
|
||||||
SECP256K1.sign_schnorr(&msg, self)
|
SECP256K1.sign_schnorr(&msg, self)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Attempts to erase the secret within the underlying array.
|
||||||
|
///
|
||||||
|
/// Note, however, that the compiler is allowed to freely copy or move the contents
|
||||||
|
/// of this array to other places in memory. Preventing this behavior is very subtle.
|
||||||
|
/// For more discussion on this, please see the documentation of the
|
||||||
|
/// [`zeroize`](https://docs.rs/zeroize) crate.
|
||||||
|
#[inline]
|
||||||
|
pub fn non_secure_erase(&mut self) { self.0.non_secure_erase(); }
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<KeyPair> for SecretKey {
|
impl From<KeyPair> for SecretKey {
|
||||||
|
@ -1603,6 +1613,17 @@ mod test {
|
||||||
assert_eq!(PublicKey::from_slice(&pk1.serialize_uncompressed()[..]), Ok(pk1));
|
assert_eq!(PublicKey::from_slice(&pk1.serialize_uncompressed()[..]), Ok(pk1));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(all(feature = "std", not(fuzzing)))]
|
||||||
|
fn erased_keypair_is_valid() {
|
||||||
|
let s = Secp256k1::new();
|
||||||
|
let kp = KeyPair::from_seckey_slice(&s, &[1u8; constants::SECRET_KEY_SIZE])
|
||||||
|
.expect("valid secret key");
|
||||||
|
let mut kp2 = kp;
|
||||||
|
kp2.non_secure_erase();
|
||||||
|
assert!(kp.eq_fast_unstable(&kp2));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
fn invalid_secret_key() {
|
fn invalid_secret_key() {
|
||||||
|
|
|
@ -91,6 +91,23 @@ macro_rules! impl_pretty_debug {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
macro_rules! impl_non_secure_erase {
|
||||||
|
($thing:ident, $target:tt, $value:expr) => {
|
||||||
|
impl $thing {
|
||||||
|
/// Attempts to erase the contents of the underlying array.
|
||||||
|
///
|
||||||
|
/// Note, however, that the compiler is allowed to freely copy or move the
|
||||||
|
/// contents of this array to other places in memory. Preventing this behavior
|
||||||
|
/// is very subtle. For more discussion on this, please see the documentation
|
||||||
|
/// of the [`zeroize`](https://docs.rs/zeroize) crate.
|
||||||
|
#[inline]
|
||||||
|
pub fn non_secure_erase(&mut self) {
|
||||||
|
secp256k1_sys::non_secure_erase_impl(&mut self.$target, $value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/// Formats error. If `std` feature is OFF appends error source (delimited by `: `). We do this
|
/// Formats error. If `std` feature is OFF appends error source (delimited by `: `). We do this
|
||||||
/// because `e.source()` is only available in std builds, without this macro the error source is
|
/// because `e.source()` is only available in std builds, without this macro the error source is
|
||||||
/// lost for no-std builds.
|
/// lost for no-std builds.
|
||||||
|
|
|
@ -23,6 +23,7 @@ use crate::constants;
|
||||||
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
|
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
|
||||||
pub struct Scalar([u8; 32]);
|
pub struct Scalar([u8; 32]);
|
||||||
impl_pretty_debug!(Scalar);
|
impl_pretty_debug!(Scalar);
|
||||||
|
impl_non_secure_erase!(Scalar, 0, [0u8; 32]);
|
||||||
|
|
||||||
const MAX_RAW: [u8; 32] = [
|
const MAX_RAW: [u8; 32] = [
|
||||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE,
|
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE,
|
||||||
|
|
|
@ -86,6 +86,7 @@ macro_rules! impl_display_secret {
|
||||||
pub struct DisplaySecret {
|
pub struct DisplaySecret {
|
||||||
secret: [u8; SECRET_KEY_SIZE],
|
secret: [u8; SECRET_KEY_SIZE],
|
||||||
}
|
}
|
||||||
|
impl_non_secure_erase!(DisplaySecret, secret, [0u8; SECRET_KEY_SIZE]);
|
||||||
|
|
||||||
impl fmt::Debug for DisplaySecret {
|
impl fmt::Debug for DisplaySecret {
|
||||||
#[inline]
|
#[inline]
|
||||||
|
|
Loading…
Reference in New Issue