From 8f6dce18f28141cbcc79dda90dc2e1f587f60f0f Mon Sep 17 00:00:00 2001 From: DrPeterVanNostrand Date: Sat, 4 Aug 2018 23:18:35 +0000 Subject: [PATCH] Added mlocking for secret types. --- Cargo.toml | 2 + src/error.rs | 28 +++ src/lib.rs | 318 +++++++++++++++++++++++++++------ src/poly.rs | 488 ++++++++++++++++++++++++++++++++++++++++++++------- 4 files changed, 717 insertions(+), 119 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 11920bd..3bbb495 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,9 +12,11 @@ categories = ["cryptography"] [dependencies] byteorder = "1.2.3" +errno = "0.2.4" failure = "0.1" init_with = "1.1.0" log = "0.4.1" +memsec = "0.5.4" pairing = { version = "0.14.2", features = ["u128-support"] } rand = "0.4.2" rand_derive = "0.3.1" diff --git a/src/error.rs b/src/error.rs index f040235..9e27319 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,5 +1,7 @@ //! Crypto errors. +use errno::Errno; + /// A crypto error. #[derive(Clone, Eq, PartialEq, Debug, Fail)] pub enum Error { @@ -7,6 +9,32 @@ pub enum Error { NotEnoughShares, #[fail(display = "Signature shares contain a duplicated index")] DuplicateEntry, + #[fail( + display = "Failed to `mlock` {} bytes starting at address: {}", + n_bytes, + addr + )] + MlockFailed { + // The errno set by the failed `mlock` syscall. + errno: Errno, + // The address for the first byte in the range of memory that was attempted to be locked. + addr: String, + // The number of bytes that were attempted to be locked. + n_bytes: usize, + }, + #[fail( + display = "Failed to `munlock` {} bytes starting at address: {}", + n_bytes, + addr + )] + MunlockFailed { + // The errno set by the failed `munlock` syscall. + errno: Errno, + // The address for the first byte in the range of memory that was attempted to be unlocked. + addr: String, + // The number of bytes that were attempted to be unlocked. + n_bytes: usize, + }, } unsafe impl Send for Error {} diff --git a/src/lib.rs b/src/lib.rs index 7afae85..7bcbdf4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,11 +5,13 @@ #[cfg(test)] extern crate bincode; extern crate byteorder; +extern crate errno; #[macro_use] extern crate failure; extern crate init_with; #[macro_use] extern crate log; +extern crate memsec; extern crate pairing; extern crate rand; #[macro_use] @@ -26,19 +28,47 @@ pub mod serde_impl; use std::fmt; use std::hash::{Hash, Hasher}; -use std::ptr::write_volatile; +use std::mem::size_of_val; +use std::ptr::{copy_nonoverlapping, write_volatile}; use byteorder::{BigEndian, ByteOrder}; +use errno::errno; use init_with::InitWith; +use memsec::{memzero, mlock, munlock}; use pairing::bls12_381::{Bls12, Fr, G1, G1Affine, G2, G2Affine}; use pairing::{CurveAffine, CurveProjective, Engine, Field}; -use rand::{ChaChaRng, OsRng, Rng, SeedableRng}; +use rand::{ChaChaRng, OsRng, Rng, Rand, SeedableRng}; use tiny_keccak::sha3_256; use error::{Error, Result}; use into_fr::IntoFr; use poly::{Commitment, Poly}; +/// Marks a type as containing one or more secret prime field elements. +pub(crate) trait ContainsSecret { + /// Calls the `mlock` system call on the region of memory allocated for the secret prime field + /// element or elements. This results in that region of memory not being being copied to disk, + /// either in a swap to disk or core dump. This method is called on every created instance of + /// a secret type. + /// + /// # Errors + /// + /// An `Error::MlockFailed` is returned if we failed to `mlock` the secret data. + fn mlock_secret_memory(&self) -> Result<()>; + + /// Undoes the `mlock` on the secret region of memory via the `munlock` system call. + /// + /// # Errors + /// + /// An `Error::MunlockFailed` is returned if we failed to `munlock` the secret data; this + /// method is called on each secret type when it goes out of scope. + fn munlock_secret_memory(&self) -> Result<()>; + + /// Overwrites the secret prime field element or elements with zeros; this method is called on + /// each each secret type when it goes out of scope. + fn zero_secret_memory(&self); +} + /// Wrapper for a byte array, whose `Debug` implementation outputs shortened hexadecimal strings. pub struct HexBytes<'a>(pub &'a [u8]); @@ -192,47 +222,170 @@ impl fmt::Debug for SignatureShare { } } -/// A secret key. -#[derive(Clone, PartialEq, Eq, Rand)] -pub struct SecretKey(Fr); - -impl fmt::Debug for SecretKey { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let uncomp = self.public_key().0.into_affine().into_uncompressed(); - let bytes = uncomp.as_ref(); - write!(f, "SecretKey({:?})", HexBytes(bytes)) - } -} +/// A secret key; wraps a single prime field element. The field element is +/// heap allocated to avoid any stack copying that result when passing +/// `SecretKey`s between stack frames. +#[derive(PartialEq, Eq)] +pub struct SecretKey(Box); +/// Creates a `SecretKey` containing the zero prime field element. +/// +/// # Panics +/// +/// Panics if we have hit the system's locked memory limit when `mlock`ing the new instance of +/// `SecretKey`.` impl Default for SecretKey { fn default() -> Self { - SecretKey(Fr::zero()) + let mut fr = Fr::zero(); + match SecretKey::from_mut_ptr(&mut fr as *mut Fr) { + Ok(sk) => sk, + Err(e) => panic!("Failed to create default `SecretKey`: {}", e), + } } } +/// Creates a random `SecretKey`. +/// +/// # Panics +/// +/// Panics if we have hit the system's locked memory limit when `mlock`ing the new instance of +/// `SecretKey`. +impl Rand for SecretKey { + fn rand(rng: &mut R) -> Self { + let mut fr = Fr::rand(rng); + match SecretKey::from_mut_ptr(&mut fr as *mut Fr) { + Ok(sk) => sk, + Err(e) => panic!("Failed to create random `SecretKey`: {}", e), + } + } +} + +/// Creates a new `SecretKey` by cloning another key's prime field element. +/// +/// # Panics +/// +/// Panics if we have hit the system's locked memory limit when `mlock`ing the new instance of +/// `SecretKey`. +impl Clone for SecretKey { + fn clone(&self) -> Self { + let mut fr = *self.0; + match SecretKey::from_mut_ptr(&mut fr as *mut Fr) { + Ok(sk) => sk, + Err(e) => panic!("Failed to clone a new `SecretKey`: {}", e), + } + } +} + +// A volatile overwrite of the prime field element's memory. +// +// # Panics +// +// Panics if we were unable to `munlock` the prime field element memory after it has been cleared. impl Drop for SecretKey { fn drop(&mut self) { - let ptr = self as *mut Self; + self.zero_secret_memory(); + if let Err(e) = self.munlock_secret_memory() { + panic!("Failed to drop `SecretKey`: {}", e); + } + } +} + +/// A debug statement where the secret prime field element is redacted. +impl fmt::Debug for SecretKey { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "SecretKey(...)") + } +} + +impl ContainsSecret for SecretKey { + fn mlock_secret_memory(&self) -> Result<()> { + let ptr = &*self.0 as *const Fr as *mut u8; + let n_bytes = size_of_val(&*self.0); + let mlock_succeeded = unsafe { mlock(ptr, n_bytes) }; + if mlock_succeeded { + Ok(()) + } else { + let e = Error::MlockFailed { + errno: errno(), + addr: format!("{:?}", ptr), + n_bytes, + }; + Err(e) + } + } + + fn munlock_secret_memory(&self) -> Result<()> { + let ptr = &*self.0 as *const Fr as *mut u8; + let n_bytes = size_of_val(&*self.0); + let munlock_succeeded = unsafe { munlock(ptr, n_bytes) }; + if munlock_succeeded { + Ok(()) + } else { + let e = Error::MunlockFailed { + errno: errno(), + addr: format!("{:?}", ptr), + n_bytes, + }; + Err(e) + } + } + + fn zero_secret_memory(&self) { + let ptr = &*self.0 as *const Fr as *mut u8; + let n_bytes = size_of_val(&*self.0); unsafe { - write_volatile(ptr, SecretKey::default()); + memzero(ptr, n_bytes); } } } impl SecretKey { - /// Creates a secret key from an existing value - pub fn from_value(f: Fr) -> Self { - SecretKey(f) + /// Creates a new `SecretKey` given a mutable raw pointer to a prime + /// field element. This constructor takes a pointer to avoid any + /// unnecessary stack copying/moving of secrets. The field element will + /// be copied bytewise onto the heap, the resulting `Box` is then + /// stored in the `SecretKey`. + /// + /// *WARNING* this constructor will overwrite the pointed to `Fr` element + /// with zeros after it has been copied onto the heap. + /// + /// # Errors + /// + /// Returns an `Error::MlockFailed` if we have reached the systems's + /// locked memory limit. + #[cfg_attr(feature = "cargo-clippy", allow(not_unsafe_ptr_arg_deref))] + pub fn from_mut_ptr(fr_ptr: *mut Fr) -> Result { + let mut boxed_fr = Box::new(Fr::zero()); + unsafe { + copy_nonoverlapping(fr_ptr, &mut *boxed_fr as *mut Fr, 1); + write_volatile(fr_ptr, Fr::zero()); + } + let sk = SecretKey(boxed_fr); + sk.mlock_secret_memory()?; + Ok(sk) + } + + /// Creates a new random instance of `SecretKey`. This is used + /// as a wrapper around: `let sk: SecretKey = rand::random();`. + /// + /// # Panics + /// + /// Panics if we have hit the system's locked memory limit when + /// `mlock`ing the new instance of `SecretKey`. + pub fn random() -> Self { + use rand::thread_rng; + let mut rng = thread_rng(); + SecretKey::rand(&mut rng) } /// Returns the matching public key. pub fn public_key(&self) -> PublicKey { - PublicKey(G1Affine::one().mul(self.0)) + PublicKey(G1Affine::one().mul(*self.0)) } /// Signs the given element of `G2`. pub fn sign_g2>(&self, hash: H) -> Signature { - Signature(hash.into().mul(self.0)) + Signature(hash.into().mul(*self.0)) } /// Signs the given message. @@ -246,9 +399,18 @@ impl SecretKey { return None; } let Ciphertext(ref u, ref v, _) = *ct; - let g = u.into_affine().mul(self.0); + let g = u.into_affine().mul(*self.0); Some(xor_vec(&hash_bytes(g, v.len()), v)) } + + /// Generates a non-redacted debug string. This method differs from + /// the `Debug` implementation in that it *does* leak the secret prime + /// field element. + pub fn reveal(&self) -> String { + let uncomp = self.public_key().0.into_affine().into_uncompressed(); + let bytes = uncomp.as_ref(); + format!("SecretKey({:?})", HexBytes(bytes)) + } } /// A secret key share. @@ -264,9 +426,21 @@ impl fmt::Debug for SecretKeyShare { } impl SecretKeyShare { - /// Creates a secret key share from an existing value - pub fn from_value(f: Fr) -> Self { - SecretKeyShare(SecretKey::from_value(f)) + /// Creates a secret key share from an existing value. This constructor + /// takes a pointer to avoid any unnecessary stack copying/moving of + /// secrets. The field element will be copied bytewise onto the heap, + /// the resulting `Box` is then stored in the `SecretKey` which is then + /// wrapped in a `SecretKeyShare`. + /// + /// *WARNING* this constructor will overwrite the pointed to `Fr` element + /// with zeros once it has been copied into a new `SecretKeyShare`. + /// + /// # Errors + /// + /// Returns an `Error::MlockFailed` if we have reached the systems's + /// locked memory limit. + pub fn from_mut_ptr(fr_ptr: *mut Fr) -> Result { + SecretKey::from_mut_ptr(fr_ptr).map(SecretKeyShare) } /// Returns the matching public key share. @@ -294,7 +468,16 @@ impl SecretKeyShare { /// Returns a decryption share, without validating the ciphertext. pub fn decrypt_share_no_verify(&self, ct: &Ciphertext) -> DecryptionShare { - DecryptionShare(ct.0.into_affine().mul((self.0).0)) + DecryptionShare(ct.0.into_affine().mul(*(self.0).0)) + } + + /// Generates a non-redacted debug string. This method differs from + /// the `Debug` implementation in that it *does* leak the secret prime + /// field element. + pub fn reveal(&self) -> String { + let uncomp = self.0.public_key().0.into_affine().into_uncompressed(); + let bytes = uncomp.as_ref(); + format!("SecretKeyShare({:?})", HexBytes(bytes)) } } @@ -411,10 +594,9 @@ impl From for SecretKeySet { impl SecretKeySet { /// Creates a set of secret key shares, where any `threshold + 1` of them can collaboratively /// sign and decrypt. - pub fn random(threshold: usize, rng: &mut R) -> Self { - SecretKeySet { - poly: Poly::random(threshold, rng), - } + pub fn random(threshold: usize, rng: &mut R) -> Result { + let poly = Poly::random(threshold, rng)?; + Ok(SecretKeySet { poly }) } /// Returns the threshold `t`: any set of `t + 1` signature shares can be combined into a full @@ -424,9 +606,9 @@ impl SecretKeySet { } /// Returns the `i`-th secret key share. - pub fn secret_key_share(&self, i: T) -> SecretKeyShare { - let value = self.poly.evaluate(into_fr_plus_1(i)); - SecretKeyShare(SecretKey(value)) + pub fn secret_key_share(&self, i: T) -> Result { + let mut fr = self.poly.evaluate(into_fr_plus_1(i)); + SecretKeyShare::from_mut_ptr(&mut fr as *mut Fr) } /// Returns the corresponding public key set. That information can be shared publicly. @@ -438,8 +620,9 @@ impl SecretKeySet { /// Returns the secret master key. #[cfg(test)] - fn secret_key(&self) -> SecretKey { - SecretKey(self.poly.evaluate(0)) + fn secret_key(&self) -> Result { + let mut fr = self.poly.evaluate(0); + SecretKey::from_mut_ptr(&mut fr as *mut Fr) } } @@ -546,25 +729,47 @@ mod tests { #[test] fn test_threshold_sig() { let mut rng = rand::thread_rng(); - let sk_set = SecretKeySet::random(3, &mut rng); + let sk_set = SecretKeySet::random(3, &mut rng).expect("Failed to create `SecretKeySet`"); let pk_set = sk_set.public_keys(); + let pk_master = pk_set.public_key(); // Make sure the keys are different, and the first coefficient is the main key. - assert_ne!(pk_set.public_key(), pk_set.public_key_share(0).0); - assert_ne!(pk_set.public_key(), pk_set.public_key_share(1).0); - assert_ne!(pk_set.public_key(), pk_set.public_key_share(2).0); + assert_ne!(pk_master, pk_set.public_key_share(0).0); + assert_ne!(pk_master, pk_set.public_key_share(1).0); + assert_ne!(pk_master, pk_set.public_key_share(2).0); // Make sure we don't hand out the main secret key to anyone. - assert_ne!(sk_set.secret_key(), sk_set.secret_key_share(0).0); - assert_ne!(sk_set.secret_key(), sk_set.secret_key_share(1).0); - assert_ne!(sk_set.secret_key(), sk_set.secret_key_share(2).0); + let sk_master = sk_set + .secret_key() + .expect("Failed to create master `SecretKey`"); + let sk_share_0 = sk_set + .secret_key_share(0) + .expect("Failed to create first `SecretKeyShare`") + .0; + let sk_share_1 = sk_set + .secret_key_share(1) + .expect("Failed to create second `SecretKeyShare`") + .0; + let sk_share_2 = sk_set + .secret_key_share(2) + .expect("Failed to create third `SecretKeyShare`") + .0; + assert_ne!(sk_master, sk_share_0); + assert_ne!(sk_master, sk_share_1); + assert_ne!(sk_master, sk_share_2); let msg = "Totally real news"; // The threshold is 3, so 4 signature shares will suffice to recreate the share. let sigs: BTreeMap<_, _> = [5, 8, 7, 10] - .into_iter() - .map(|i| (*i, sk_set.secret_key_share(*i).sign(msg))) + .iter() + .map(|&i| { + let sig = sk_set + .secret_key_share(i) + .unwrap_or_else(|_| panic!("Failed to create `SecretKeyShare` #{}", i)) + .sign(msg); + (i, sig) + }) .collect(); // Each of the shares is a valid signature matching its public key share. @@ -578,8 +783,14 @@ mod tests { // A different set of signatories produces the same signature. let sigs2: BTreeMap<_, _> = [42, 43, 44, 45] - .into_iter() - .map(|i| (*i, sk_set.secret_key_share(*i).sign(msg))) + .iter() + .map(|&i| { + let sig = sk_set + .secret_key_share(i) + .unwrap_or_else(|_| panic!("Failed to create `SecretKeyShare` #{}", i)) + .sign(msg); + (i, sig) + }) .collect(); let sig2 = pk_set.combine_signatures(&sigs2).expect("signatures match"); assert_eq!(sig, sig2); @@ -612,18 +823,21 @@ mod tests { #[test] fn test_threshold_enc() { let mut rng = rand::thread_rng(); - let sk_set = SecretKeySet::random(3, &mut rng); + let sk_set = SecretKeySet::random(3, &mut rng).expect("Failed to create to `SecretKeySet`"); let pk_set = sk_set.public_keys(); let msg = b"Totally real news"; let ciphertext = pk_set.public_key().encrypt(&msg[..]); // The threshold is 3, so 4 signature shares will suffice to decrypt. let shares: BTreeMap<_, _> = [5, 8, 7, 10] - .into_iter() - .map(|i| { - let ski = sk_set.secret_key_share(*i); - let share = ski.decrypt_share(&ciphertext).expect("ciphertext is valid"); - (*i, share) + .iter() + .map(|&i| { + let dec_share = sk_set + .secret_key_share(i) + .unwrap_or_else(|_| panic!("Failed to create `SecretKeyShare` #{}", i)) + .decrypt_share(&ciphertext) + .expect("ciphertext is valid"); + (i, dec_share) }) .collect(); diff --git a/src/poly.rs b/src/poly.rs index 420b450..e177a78 100644 --- a/src/poly.rs +++ b/src/poly.rs @@ -17,32 +17,74 @@ //! polynomials (in two variables) over a field `Fr`, as well as their _commitments_ in `G`. use std::borrow::Borrow; +use std::fmt::{self, Debug, Formatter}; use std::hash::{Hash, Hasher}; -use std::ptr::write_volatile; +use std::mem::{size_of, size_of_val}; use std::{cmp, iter, ops}; +use errno::errno; +use memsec::{memzero, mlock, munlock}; use pairing::bls12_381::{Fr, G1, G1Affine}; use pairing::{CurveAffine, CurveProjective, Field}; use rand::Rng; -use super::IntoFr; +use super::{ContainsSecret, Error, IntoFr, Result}; /// A univariate polynomial in the prime field. -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Serialize, Deserialize, PartialEq, Eq)] pub struct Poly { /// The coefficients of a polynomial. #[serde(with = "super::serde_impl::field_vec")] pub(super) coeff: Vec, } +/// Creates a new `Poly` with the same coefficients as another polynomial. +/// +/// # Panics +/// +/// Panics if we have hit the system's locked memory limit when `mlock`ing the new instance of +/// `Poly`. +impl Clone for Poly { + fn clone(&self) -> Self { + match Poly::new(self.coeff.clone()) { + Ok(poly) => poly, + Err(e) => panic!("Failed to clone `Poly`: {}", e), + } + } +} + +/// A debug statement where the `coeff` vector of prime field elements has been redacted. +impl Debug for Poly { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "Poly {{ coeff: ... }}") + } +} + +/// # Panics +/// +/// Panics if we hit the system's locked memory limit or if we fail to unlock memory that has been +/// truncated from the `coeff` vector. +#[cfg_attr(feature = "cargo-clippy", allow(suspicious_op_assign_impl))] impl> ops::AddAssign for Poly { fn add_assign(&mut self, rhs: B) { - let len = cmp::max(self.coeff.len(), rhs.borrow().coeff.len()); - self.coeff.resize(len, Fr::zero()); + let len = self.coeff.len(); + let rhs_len = rhs.borrow().coeff.len(); + if rhs_len > len { + self.coeff.resize(rhs_len, Fr::zero()); + let n_coeffs_added = rhs_len - len; + if let Err(e) = self.extend_mlock(n_coeffs_added) { + panic!( + "Failed to extend `Poly` memory lock during add-assign: {}", + e + ); + } + } for (self_c, rhs_c) in self.coeff.iter_mut().zip(&rhs.borrow().coeff) { self_c.add_assign(rhs_c); } - self.remove_zeros(); + if let Err(e) = self.remove_zeros() { + panic!("Failed to unlock `Poly` memory during add-assign: {}", e); + } } } @@ -63,6 +105,10 @@ impl> ops::Add for Poly { } } +/// # Panics +/// +/// Panics if we hit the system's locked memory limit or if we fail to unlock memory that has been +/// truncated from the `coeff` vector. impl<'a> ops::Add for Poly { type Output = Poly; @@ -70,10 +116,15 @@ impl<'a> ops::Add for Poly { if self.coeff.is_empty() { if !rhs.is_zero() { self.coeff.push(rhs); + if let Err(e) = self.extend_mlock(1) { + panic!("Failed to extend `Poly` memory lock during add: {}", e); + } } } else { self.coeff[0].add_assign(&rhs); - self.remove_zeros(); + if let Err(e) = self.remove_zeros() { + panic!("Failed to unlock `Poly` memory during add: {}", e); + } } self } @@ -87,14 +138,30 @@ impl<'a> ops::Add for Poly { } } +/// # Panics +/// +/// Panics if we hit the system's locked memory limit or if we fail to unlock memory that has been +/// truncated from the `coeff` vector. impl> ops::SubAssign for Poly { fn sub_assign(&mut self, rhs: B) { - let len = cmp::max(self.coeff.len(), rhs.borrow().coeff.len()); - self.coeff.resize(len, Fr::zero()); + let len = self.coeff.len(); + let rhs_len = rhs.borrow().coeff.len(); + if rhs_len > len { + self.coeff.resize(rhs_len, Fr::zero()); + let n_coeffs_added = rhs_len - len; + if let Err(e) = self.extend_mlock(n_coeffs_added) { + panic!( + "Failed to extend `Poly` memory lock during sub-assign: {}", + e + ); + } + } for (self_c, rhs_c) in self.coeff.iter_mut().zip(&rhs.borrow().coeff) { self_c.sub_assign(rhs_c); } - self.remove_zeros(); + if let Err(e) = self.remove_zeros() { + panic!("Failed to unlock `Poly` memory during sub-assign: {}", e); + } } } @@ -134,14 +201,19 @@ impl<'a> ops::Sub for Poly { } } +/// # Panics +/// +/// Panics if we hit the system's locked memory limit or if we fail to unlock memory that has been +/// truncated from the `coeff` vector. // Clippy thinks using any `+` and `-` in a `Mul` implementation is suspicious. #[cfg_attr(feature = "cargo-clippy", allow(suspicious_arithmetic_impl))] impl<'a, B: Borrow> ops::Mul for &'a Poly { type Output = Poly; fn mul(self, rhs: B) -> Self::Output { - let coeff = (0..(self.coeff.len() + rhs.borrow().coeff.len() - 1)) + let coeff: Vec = (0..(self.coeff.len() + rhs.borrow().coeff.len() - 1)) .map(|i| { + // TODO: clear these secrets from the stack. let mut c = Fr::zero(); for j in i.saturating_sub(rhs.borrow().degree())..(1 + cmp::min(i, self.degree())) { let mut s = self.coeff[j]; @@ -151,7 +223,11 @@ impl<'a, B: Borrow> ops::Mul for &'a Poly { c }) .collect(); - Poly { coeff } + + match Poly::new(coeff) { + Ok(poly) => poly, + Err(e) => panic!("Failed to create a new `Poly` duing muliplication: {}", e), + } } } @@ -169,11 +245,19 @@ impl> ops::MulAssign for Poly { } } +/// # Panics +/// +/// This operation may panic if: when multiplying the polynomial by a zero field element, we fail +/// to munlock the cleared `coeff` vector. impl<'a> ops::Mul for Poly { type Output = Poly; fn mul(mut self, rhs: Fr) -> Self::Output { if rhs.is_zero() { + self.zero_secret_memory(); + if let Err(e) = self.munlock_secret_memory() { + panic!("Failed to unlock `Poly` during multiplication: {}", e); + } self.coeff.clear(); } else { self.coeff.iter_mut().for_each(|c| c.mul_assign(&rhs)); @@ -190,59 +274,151 @@ impl<'a> ops::Mul for Poly { } } +/// # Panics +/// +/// Panics if we fail to munlock the `coeff` vector. impl Drop for Poly { fn drop(&mut self) { - let start = self.coeff.as_mut_ptr(); + self.zero_secret_memory(); + if let Err(e) = self.munlock_secret_memory() { + panic!("Failed to munlock `Poly` during drop: {}", e); + } + } +} + +impl ContainsSecret for Poly { + fn mlock_secret_memory(&self) -> Result<()> { + let ptr = self.coeff.as_ptr() as *mut u8; + let n_bytes = size_of_val(self.coeff.as_slice()); + if n_bytes == 0 { + return Ok(()); + } + let mlock_succeeded = unsafe { mlock(ptr, n_bytes) }; + if mlock_succeeded { + Ok(()) + } else { + let e = Error::MlockFailed { + errno: errno(), + addr: format!("{:?}", ptr), + n_bytes, + }; + Err(e) + } + } + + fn munlock_secret_memory(&self) -> Result<()> { + let ptr = self.coeff.as_ptr() as *mut u8; + let n_bytes = size_of_val(self.coeff.as_slice()); + if n_bytes == 0 { + return Ok(()); + } + let munlock_succeeded = unsafe { munlock(ptr, n_bytes) }; + if munlock_succeeded { + Ok(()) + } else { + let e = Error::MunlockFailed { + errno: errno(), + addr: format!("{:?}", ptr), + n_bytes, + }; + Err(e) + } + } + + fn zero_secret_memory(&self) { + let ptr = self.coeff.as_ptr() as *mut u8; + let n_bytes = size_of_val(self.coeff.as_slice()); unsafe { - for i in 0..self.coeff.len() { - let ptr = start.offset(i as isize); - write_volatile(ptr, Fr::zero()); - } + memzero(ptr, n_bytes); } } } impl Poly { + /// Creates a new `Poly` instance from a vector of prime field elements representing the + /// coefficients of the polynomial. The `mlock` system call is applied to the region of the + /// heap where the field elements are allocated. + /// + /// # Errors + /// + /// Returns an `Error::MlockFailed` if we have reached the systems's locked memory limit. + pub fn new(coeff: Vec) -> Result { + let poly = Poly { coeff }; + poly.mlock_secret_memory()?; + Ok(poly) + } + /// Creates a random polynomial. - pub fn random(degree: usize, rng: &mut R) -> Self { - Poly { - coeff: (0..(degree + 1)).map(|_| rng.gen()).collect(), - } + /// + /// # Errors + /// + /// Returns an `Error::MlockFailed` if we have reached the systems's locked memory limit. + pub fn random(degree: usize, rng: &mut R) -> Result { + let coeff: Vec = (0..=degree).map(|_| rng.gen()).collect(); + Poly::new(coeff) } /// Returns the polynomial with constant value `0`. - pub fn zero() -> Self { - Poly { coeff: Vec::new() } + /// + /// # Errors + /// + /// Returns an `Error::MlockFailed` if we have reached the systems's locked memory limit. + pub fn zero() -> Result { + Poly::new(vec![]) } /// Returns the polynomial with constant value `1`. - pub fn one() -> Self { + /// + /// # Errors + /// + /// Returns an `Error::MlockFailed` if we have reached the systems's locked memory limit. + pub fn one() -> Result { Self::monomial(0) } /// Returns the polynomial with constant value `c`. - pub fn constant(c: Fr) -> Self { - Poly { coeff: vec![c] } + /// + /// # Errors + /// + /// Returns an `Error::MlockFailed` if we have reached the systems's locked memory limit. + pub fn constant(c: Fr) -> Result { + let ptr = &c as *const Fr as *mut u8; + let res = Poly::new(vec![c]); + unsafe { + memzero(ptr, size_of::()); + } + res } /// Returns the identity function, i.e. the polynomial "`x`". - pub fn identity() -> Self { + /// + /// # Errors + /// + /// Returns an `Error::MlockFailed` if we have reached the systems's locked memory limit. + pub fn identity() -> Result { Self::monomial(1) } - /// Returns the (monic) monomial "`x.pow(degree)`". - pub fn monomial(degree: usize) -> Self { - Poly { - coeff: iter::repeat(Fr::zero()) - .take(degree) - .chain(iter::once(Fr::one())) - .collect(), - } + /// Returns the (monic) monomial "`x.pow(degree)`" + /// + /// # Errors + /// + /// Returns an `Error::MlockFailed` if we have reached the systems's locked memory limit. + pub fn monomial(degree: usize) -> Result { + let coeff: Vec = iter::repeat(Fr::zero()) + .take(degree) + .chain(iter::once(Fr::one())) + .collect(); + Poly::new(coeff) } /// Returns the unique polynomial `f` of degree `samples.len() - 1` with the given values /// `(x, f(x))`. - pub fn interpolate(samples_repr: I) -> Self + /// + /// # Errors + /// + /// Returns an `Error::MlockFailed` if we have reached the systems's locked memory limit. + pub fn interpolate(samples_repr: I) -> Result where I: IntoIterator, T: IntoFr, @@ -281,15 +457,26 @@ impl Poly { } /// Removes all trailing zero coefficients. - fn remove_zeros(&mut self) { + /// + /// # Errors + /// + /// An `Error::MunlockFailed` is returned if we failed to `munlock` the truncated portion of + /// the `coeff` vector. + fn remove_zeros(&mut self) -> Result<()> { let zeros = self.coeff.iter().rev().take_while(|c| c.is_zero()).count(); let len = self.coeff.len() - zeros; - self.coeff.truncate(len) + self.coeff.truncate(len); + self.truncate_mlock(zeros) } /// Returns the unique polynomial `f` of degree `samples.len() - 1` with the given values /// `(x, f(x))`. - fn compute_interpolation(samples: &[(Fr, Fr)]) -> Self { + /// + /// # Errors + /// + /// Returns an `Error::MlockFailed` if we hit the system's locked memory limit and failed to + /// `mlock` the new `Poly` instance. + fn compute_interpolation(samples: &[(Fr, Fr)]) -> Result { if samples.is_empty() { return Poly::zero(); } else if samples.len() == 1 { @@ -298,23 +485,79 @@ impl Poly { // The degree is at least 1 now. let degree = samples.len() - 1; // Interpolate all but the last sample. - let prev = Self::compute_interpolation(&samples[..degree]); + let prev = Self::compute_interpolation(&samples[..degree])?; let (x, mut y) = samples[degree]; // The last sample. y.sub_assign(&prev.evaluate(x)); - let step = Self::lagrange(x, &samples[..degree]); - prev + step * Self::constant(y) + let step = Self::lagrange(x, &samples[..degree])?; + Self::constant(y).map(|poly| poly * step + prev) } /// Returns the Lagrange base polynomial that is `1` in `p` and `0` in every `samples[i].0`. - fn lagrange(p: Fr, samples: &[(Fr, Fr)]) -> Self { - let mut result = Self::one(); + /// + /// # Errors + /// + /// Returns an `Error::MlockFailed` if we hit the system's locked memory limit. + fn lagrange(p: Fr, samples: &[(Fr, Fr)]) -> Result { + let mut result = Self::one()?; for &(sx, _) in samples { let mut denom = p; denom.sub_assign(&sx); denom = denom.inverse().expect("sample points must be distinct"); - result *= (Self::identity() - Self::constant(sx)) * Self::constant(denom); + result *= (Self::identity()? - Self::constant(sx)?) * Self::constant(denom)?; } - result + Ok(result) + } + + // Removes the `mlock` for `len` elements that have been truncated from the `coeff` vector. + fn truncate_mlock(&self, len: usize) -> Result<()> { + let n_bytes_truncated = len * size_of::(); + if n_bytes_truncated == 0 { + return Ok(()); + } + unsafe { + let ptr = self.coeff.as_ptr().offset(self.coeff.len() as isize) as *mut u8; + let munlock_succeeded = munlock(ptr, n_bytes_truncated); + if munlock_succeeded { + Ok(()) + } else { + let e = Error::MunlockFailed { + errno: errno(), + addr: format!("{:?}", ptr), + n_bytes: n_bytes_truncated, + }; + Err(e) + } + } + } + + // Extends the `mlock` on the `coeff` vector when `len` new elements are added. + fn extend_mlock(&self, len: usize) -> Result<()> { + let n_bytes_extended = len * size_of::(); + if n_bytes_extended == 0 { + return Ok(()); + } + let offset = (self.coeff.len() - len) as isize; + unsafe { + let ptr = self.coeff.as_ptr().offset(offset) as *mut u8; + let mlock_succeeded = mlock(ptr, n_bytes_extended); + if mlock_succeeded { + Ok(()) + } else { + let e = Error::MunlockFailed { + errno: errno(), + addr: format!("{:?}", ptr), + n_bytes: n_bytes_extended, + }; + Err(e) + } + } + } + + /// Generates a non-redacted debug string. This method differs from + /// the `Debug` implementation in that it *does* leak the secret prime + /// field elements. + pub fn reveal(&self) -> String { + format!("Poly {{ coeff: {:?} }}", self.coeff) } } @@ -395,7 +638,6 @@ impl Commitment { /// /// This can be used for Verifiable Secret Sharing and Distributed Key Generation. See the module /// documentation for details. -#[derive(Debug, Clone)] pub struct BivarPoly { /// The polynomial's degree in each of the two variables. degree: usize, @@ -404,28 +646,106 @@ pub struct BivarPoly { coeff: Vec, } +/// # Panics +/// +/// Panics if we have hit the system's locked memory limit when `mlock`ing the new instance of +/// `BivarPoly`. +impl Clone for BivarPoly { + fn clone(&self) -> Self { + let poly = BivarPoly { + degree: self.degree, + coeff: self.coeff.clone(), + }; + if let Err(e) = poly.mlock_secret_memory() { + panic!("Failed to clone `BivarPoly`: {}", e); + } + poly + } +} + +/// # Panics +/// +/// Panics if we fail to munlock the `coeff` vector. impl Drop for BivarPoly { fn drop(&mut self) { - let start = self.coeff.as_mut_ptr(); + self.zero_secret_memory(); + if let Err(e) = self.munlock_secret_memory() { + panic!("Failed to munlock `BivarPoly` during drop: {}", e); + } + } +} + +/// A debug statement where the `coeff` vector has been redacted. +impl Debug for BivarPoly { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "BivarPoly {{ degree: {}, coeff: ... }}", self.degree) + } +} + +impl ContainsSecret for BivarPoly { + fn mlock_secret_memory(&self) -> Result<()> { + let ptr = self.coeff.as_ptr() as *mut u8; + let n_bytes = size_of_val(self.coeff.as_slice()); + if n_bytes == 0 { + return Ok(()); + } + let mlock_succeeded = unsafe { mlock(ptr, n_bytes) }; + if mlock_succeeded { + Ok(()) + } else { + let e = Error::MlockFailed { + errno: errno(), + addr: format!("{:?}", ptr), + n_bytes, + }; + Err(e) + } + } + + fn munlock_secret_memory(&self) -> Result<()> { + let ptr = self.coeff.as_ptr() as *mut u8; + let n_bytes = size_of_val(self.coeff.as_slice()); + if n_bytes == 0 { + return Ok(()); + } + let munlock_succeeded = unsafe { munlock(ptr, n_bytes) }; + if munlock_succeeded { + Ok(()) + } else { + let e = Error::MunlockFailed { + errno: errno(), + addr: format!("{:?}", ptr), + n_bytes, + }; + Err(e) + } + } + + fn zero_secret_memory(&self) { + let ptr = self.coeff.as_ptr() as *mut u8; + let n_bytes = size_of_val(self.coeff.as_slice()); unsafe { - for i in 0..self.coeff.len() { - let ptr = start.offset(i as isize); - write_volatile(ptr, Fr::zero()); - } + memzero(ptr, n_bytes); } } } impl BivarPoly { /// Creates a random polynomial. - pub fn random(degree: usize, rng: &mut R) -> Self { - BivarPoly { + /// + /// # Errors + /// + /// Returns an `Error::MlockFailed` if we have reached the systems's locked memory limit. + pub fn random(degree: usize, rng: &mut R) -> Result { + let poly = BivarPoly { degree, coeff: (0..coeff_pos(degree + 1, 0)).map(|_| rng.gen()).collect(), - } + }; + poly.mlock_secret_memory()?; + Ok(poly) } - /// Returns the polynomial's degree: It is the same in both variables. + /// Returns the polynomial's degree; which is the same in both variables. pub fn degree(&self) -> usize { self.degree } @@ -448,10 +768,16 @@ impl BivarPoly { } /// Returns the `x`-th row, as a univariate polynomial. - pub fn row(&self, x: T) -> Poly { + /// + /// # Errors + /// + /// Returns an `Error::MlockFailed` if we have reached the systems's locked memory limit when + /// creating the new `Poly` instance. + pub fn row(&self, x: T) -> Result { let x_pow = self.powers(x); let coeff: Vec = (0..=self.degree) .map(|i| { + // TODO: clear these secrets from the stack. let mut result = Fr::zero(); for (j, x_pow_j) in x_pow.iter().enumerate() { let mut summand = self.coeff[coeff_pos(i, j)]; @@ -461,7 +787,7 @@ impl BivarPoly { result }) .collect(); - Poly { coeff } + Poly::new(coeff) } /// Returns the corresponding commitment. That information can be shared publicly. @@ -477,6 +803,16 @@ impl BivarPoly { fn powers(&self, x: T) -> Vec { powers(x, self.degree) } + + /// Generates a non-redacted debug string. This method differs from the + /// `Debug` implementation in that it *does* leak the the struct's + /// internal state. + pub fn reveal(&self) -> String { + format!( + "BivarPoly {{ degree: {}, coeff: {:?} }}", + self.degree, self.coeff + ) + } } /// A commitment to a symmetric bivariate polynomial. @@ -595,14 +931,18 @@ mod tests { #[test] fn poly() { // The polynomial 5 X³ + X - 2. - let poly = Poly::monomial(3) * 5 + Poly::monomial(1) - 2; + let x_pow_3 = Poly::monomial(3).expect("Failed to create monic polynomial of degree 3"); + let x_pow_1 = Poly::monomial(1).expect("Failed to create monic polynomial of degree 1"); + let poly = x_pow_3 * 5 + x_pow_1 - 2; + let coeff: Vec<_> = [-2, 1, 0, 5].into_iter().map(IntoFr::into_fr).collect(); assert_eq!(Poly { coeff }, poly); let samples = vec![(-1, -8), (2, 40), (3, 136), (5, 628)]; for &(x, y) in &samples { assert_eq!(y.into_fr(), poly.evaluate(x)); } - assert_eq!(Poly::interpolate(samples), poly); + let interp = Poly::interpolate(samples).expect("Failed to interpolate `Poly`"); + assert_eq!(interp, poly); } #[test] @@ -616,7 +956,10 @@ mod tests { // generates random bivariate polynomials and publicly commits to them. In partice, the // dealers can e.g. be any `faulty_num + 1` nodes. let bi_polys: Vec = (0..dealer_num) - .map(|_| BivarPoly::random(faulty_num, &mut rng)) + .map(|_| { + BivarPoly::random(faulty_num, &mut rng) + .expect("Failed to create random `BivarPoly`") + }) .collect(); let pub_bi_commits: Vec<_> = bi_polys.iter().map(BivarPoly::commitment).collect(); @@ -628,7 +971,9 @@ mod tests { for (bi_poly, bi_commit) in bi_polys.iter().zip(&pub_bi_commits) { for m in 1..=node_num { // Node `m` receives its row and verifies it. - let row_poly = bi_poly.row(m); + let row_poly = bi_poly + .row(m) + .unwrap_or_else(|_| panic!("Failed to create row #{}", m)); let row_commit = bi_commit.row(m); assert_eq!(row_poly.commitment(), row_commit); // Node `s` receives the `s`-th value and verifies it. @@ -641,7 +986,11 @@ mod tests { } // A cheating dealer who modified the polynomial would be detected. - let wrong_poly = row_poly.clone() + Poly::monomial(2) * Poly::constant(5.into_fr()); + let x_pow_2 = + Poly::monomial(2).expect("Failed to create monic polynomial of degree 2"); + let five = Poly::constant(5.into_fr()) + .expect("Failed to create polynomial with constant 5"); + let wrong_poly = row_poly.clone() + x_pow_2 * five; assert_ne!(wrong_poly.commitment(), row_commit); // If `2 * faulty_num + 1` nodes confirm that they received a valid row, then at @@ -655,7 +1004,8 @@ mod tests { .iter() .map(|&i| (i, bi_poly.evaluate(m, i))) .collect(); - let my_row = Poly::interpolate(received); + let my_row = + Poly::interpolate(received).expect("Failed to create `Poly` via interpolation"); assert_eq!(bi_poly.evaluate(m, 0), my_row.evaluate(0)); assert_eq!(row_poly, my_row); @@ -670,9 +1020,11 @@ mod tests { // The whole first column never gets added up in practice, because nobody has all the // information. We do it anyway here; entry `0` is the secret key that is not known to // anyone, neither a dealer, nor a node: - let mut sec_key_set = Poly::zero(); + let mut sec_key_set = Poly::zero().expect("Failed to create empty `Poly`"); for bi_poly in &bi_polys { - sec_key_set += bi_poly.row(0); + sec_key_set += bi_poly + .row(0) + .expect("Failed to create `Poly` from row #0 for `BivarPoly`"); } for m in 1..=node_num { assert_eq!(sec_key_set.evaluate(m), sec_keys[m - 1]); @@ -680,7 +1032,9 @@ mod tests { // The sum of the first rows of the public commitments is the commitment to the secret key // set. - let mut sum_commit = Poly::zero().commitment(); + let mut sum_commit = Poly::zero() + .expect("Failed to create empty `Poly`") + .commitment(); for bi_commit in &pub_bi_commits { sum_commit += bi_commit.row(0); }