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 ACK 8fffbeab13
  apoelstra:
    ACK 8fffbeab13

Tree-SHA512: 28d2cdcc6bd2d2d6330b67ae8635561882e869199d8fef9a3ebc3f368a7a0c2c00b818281190133f551b099e9c5226f104a56edc14c9b6f699ceba49e4b24563
This commit is contained in:
Andrew Poelstra 2023-02-22 15:52:37 +00:00
commit c9310884b6
No known key found for this signature in database
GPG Key ID: C588D63CE41B97C1
8 changed files with 83 additions and 1 deletions

View File

@ -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
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
If you want to fuzz this library, or any library which depends on it, you will

View File

@ -32,4 +32,3 @@ recovery = []
lowmemory = []
std = ["alloc"]
alloc = []

View File

@ -454,6 +454,38 @@ impl KeyPair {
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))]

View File

@ -46,6 +46,7 @@ const SHARED_SECRET_SIZE: usize = constants::SECRET_KEY_SIZE;
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct SharedSecret([u8; SHARED_SECRET_SIZE]);
impl_display_secret!(SharedSecret);
impl_non_secure_erase!(SharedSecret, 0, [0u8; SHARED_SECRET_SIZE]);
impl SharedSecret {
/// Creates a new shared secret from a pubkey and secret key.

View File

@ -66,6 +66,7 @@ use crate::{hashes, ThirtyTwoByteHash};
#[derive(Copy, Clone)]
pub struct SecretKey([u8; constants::SECRET_KEY_SIZE]);
impl_display_secret!(SecretKey);
impl_non_secure_erase!(SecretKey, 0, [1u8; constants::SECRET_KEY_SIZE]);
impl PartialEq for SecretKey {
/// 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 {
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 {
@ -1603,6 +1613,17 @@ mod test {
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]
#[rustfmt::skip]
fn invalid_secret_key() {

View File

@ -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
/// because `e.source()` is only available in std builds, without this macro the error source is
/// lost for no-std builds.

View File

@ -23,6 +23,7 @@ use crate::constants;
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
pub struct Scalar([u8; 32]);
impl_pretty_debug!(Scalar);
impl_non_secure_erase!(Scalar, 0, [0u8; 32]);
const MAX_RAW: [u8; 32] = [
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE,

View File

@ -86,6 +86,7 @@ macro_rules! impl_display_secret {
pub struct DisplaySecret {
secret: [u8; SECRET_KEY_SIZE],
}
impl_non_secure_erase!(DisplaySecret, secret, [0u8; SECRET_KEY_SIZE]);
impl fmt::Debug for DisplaySecret {
#[inline]