solidity_bindgen/secrets.rs
1use secp256k1::key::{SecretKey, ONE_KEY};
2
3use sodiumoxide::utils::{mlock, munlock};
4use std::convert::TryFrom;
5use std::fmt;
6use std::ops::Deref;
7use std::pin::Pin;
8use std::slice;
9use zeroize::{DefaultIsZeroes, Zeroize};
10
11/// Securely stores a secret key in memory that is zeroized on drop. Care is
12/// taken so that when this struct is constructed or moved that additional
13/// copies of the secret are not made in memory or disk.
14/// https://github.com/veorq/cryptocoding#clean-memory-of-secret-data
15///
16/// Unfortunately the SafeSecretKey is not magic, there are some things to be
17/// aware of when using it...
18/// * The memory used when constructing the secret key must also be zeroized,
19/// but this is left as an exercise to the caller.
20/// * If you mem::forget the SafeSecretKey or otherwise don't drop it, the
21/// secret will not be zeroized.
22/// * When the caller lends out a reference to the SecretKey (available for
23/// example via Deref) it is the responsibility of the caller to not Clone
24/// the SecretKey or otherwise make a copy of it's memory
25pub struct SafeSecretKey {
26 safe: Pin<Box<ZeroizedSecretKey>>,
27}
28
29impl fmt::Debug for SafeSecretKey {
30 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
31 f.debug_struct("SecretKey").finish()
32 }
33}
34
35#[derive(Copy, Clone)]
36struct ZeroizedSecretKey(SecretKey);
37
38impl DefaultIsZeroes for ZeroizedSecretKey {}
39impl Default for ZeroizedSecretKey {
40 fn default() -> Self {
41 Self(ONE_KEY)
42 }
43}
44
45impl ZeroizedSecretKey {
46 fn as_mut_bytes(&mut self) -> &mut [u8] {
47 unsafe {
48 let ptr = self.0.as_mut_ptr();
49 slice::from_raw_parts_mut(ptr, self.0.len())
50 }
51 }
52}
53
54impl SafeSecretKey {
55 fn new(secret: &SecretKey) -> Result<Self, ()> {
56 // Allocate to a fixed location in memory
57 let mut safe = Pin::new(Box::<ZeroizedSecretKey>::default());
58
59 // Tell the OS that it's not ok to page this memory to disk
60 let mem = safe.as_mut_bytes();
61 mlock(mem)?;
62
63 // Copy to that location without moving secret onto the stack
64 safe.0 = *secret;
65
66 // Now the only copy of the memory managed by Self is committed to be zeroized later.
67 // This operation (and subsequent) don't make copies because it's the Pin that moves,
68 // not the underlying allocation.
69 Ok(Self { safe })
70 }
71}
72
73impl<'a> TryFrom<&'a SecretKey> for SafeSecretKey {
74 type Error = ();
75 fn try_from(secret: &'a SecretKey) -> Result<Self, ()> {
76 Self::new(secret)
77 }
78}
79
80impl Drop for SafeSecretKey {
81 fn drop(&mut self) {
82 self.safe.zeroize();
83 let mem = self.safe.as_mut_bytes();
84 // If the OS cannot unlock this memory, it's not a problem. There is no
85 // reasonable way to propagate an error so just ignore it.
86 let _ignore = munlock(mem);
87 }
88}
89
90impl Deref for SafeSecretKey {
91 type Target = SecretKey;
92 fn deref(&self) -> &Self::Target {
93 &self.safe.0
94 }
95}
96
97#[cfg(test)]
98mod tests {
99 use super::*;
100 use secp256k1::key::ONE_KEY;
101
102 /// It's a bit hard to verify the inner workings like zeroing without relying on undefined behavior.
103 /// But, we can check that this at least runs
104 #[test]
105 pub fn no_panic() {
106 let key = ONE_KEY;
107 let safe = SafeSecretKey::new(&key).unwrap();
108 drop(safe);
109 }
110}