From f35006f7179533b7ac3a7a9ee1d6932475b95cec Mon Sep 17 00:00:00 2001 From: Aaron Feickert <66188213+AaronFeickert@users.noreply.github.com> Date: Fri, 18 Nov 2022 09:05:14 +0200 Subject: [PATCH] feat: improve hidden data handling (#52) Some data, like cryptographic key material or passwords, needs special handling. Such data typically should: - be zeroized when it goes out of scope - never be displayed or written to debug logs - not typically be accessed except by (mutable) reference - have a strictly enforced type to avoid misuse This PR updates the `Hidden` type to handle this kind of data in a generic way. Hidden types can be instantiated using any underlying type that implements `Zeroize` and an optional differentiated type created by a macro that enforces context and prevents misuse. The hidden data is stored on the heap in a `Box` wrapper. This allows us to safely zeroize it on drop, and automatically prevents display and debug from revealing it unintentionally. Further, we only expose access to the data via immutable and mutable reference. The implementation supports `Clone` for cases where we need it, but does not support `Copy`. We also update `SafePassword` to be an instantiation of the updated `Hidden` type. This refactor is transparent to callers, as its existing API is unchanged. --- Cargo.toml | 4 +- src/hidden.rs | 250 +++++++++++++++++++++++++++++++++++------- src/lib.rs | 3 +- src/message_format.rs | 2 +- src/password.rs | 128 +++++++++++++++------ src/safe_array.rs | 170 ++++++++++++++++++++++++++++ 6 files changed, 486 insertions(+), 71 deletions(-) create mode 100644 src/safe_array.rs diff --git a/Cargo.toml b/Cargo.toml index 73a14c9..72dcc58 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,11 +13,13 @@ edition = "2018" base58-monero = { version = "0.3.2", default-features = false } base64 = "0.13.0" bincode = "1.3.3" +generic-array = "0.14.6" newtype-ops = "0.1.4" serde = { version = "1.0.0", features = ["derive"] } serde_json = "1.0.0" +subtle = "2.4.1" thiserror = "1.0.0" -zeroize = "1.3.0" +zeroize = { version = "1.3.0", features = ["zeroize_derive"] } [dev-dependencies] rand = "0.7.0" diff --git a/src/hidden.rs b/src/hidden.rs index b05ae71..a5d0de6 100644 --- a/src/hidden.rs +++ b/src/hidden.rs @@ -1,4 +1,4 @@ -// Copyright 2020. The Tari Project +// Copyright 2022. The Tari Project // // Redistribution and use in source and binary forms, with or without modification, are permitted provided that the // following conditions are met: @@ -20,74 +20,250 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -//! A wrapper to conceal secrets when output into logs or displayed. +//! Sometimes you need to handle sensitive data, like a passphrase or key material. +//! There are pitfalls here that you want to avoid: the data should be zeroized when it goes out of scope, shouldn't be +//! displayed or output to debug logs, shouldn't be unintentionally copied or moved, and often should have strict type +//! differentiation to avoid it being misused in an unintended context. This library provides a generic type and macro +//! that can help. -use std::fmt; +use std::{ + any::type_name, + fmt, + ops::{Deref, DerefMut}, +}; -use serde::{Deserialize, Serialize}; +use serde::Deserialize; +use zeroize::Zeroize; -/// A simple struct with a single inner value to wrap content of any type. -#[derive(Copy, Clone, Serialize, Deserialize, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] +/// This is a macro that produces a hidden type from an underlying data type. +/// +/// It is a thin wrapper around `Hidden` and retains its useful properties: +/// - The data is not subject to `Debug` or `Display` output, which are masked. +/// - The data can only be accessed by (immutable or mutable) reference. +/// - The data zeroizes when dropped, and can also be manually zeroized. +/// - Cloning is safe. +/// +/// The macro is a useful way to generate a hidden type that is subject to the compiler's type guarantees. +/// This can be useful if you need multiple hidden types that use the same underlying data type, but shouldn't be +/// confused for each other. +/// +/// Note that it may not be safe to dereference the hidden data if its type implements `Copy`. +/// If the type does not implement `Copy`, you should be fine. +/// If it does, avoid dereferencing. +/// +/// ```edition2018 +/// # #[macro_use] extern crate tari_utilities; +/// # use tari_utilities::Hidden; +/// # use zeroize::Zeroize; +/// # fn main() { +/// // Define a hidden type with a `[u8; 32]` data type +/// hidden_type!(MyHiddenType, [u8; 32]); +/// +/// // Instantiate using existing data +/// let mut example = MyHiddenType::from([1u8; 32]); +/// +/// // Access the data by immutable reference +/// assert_eq!(example.reveal(), &[1u8; 32]); +/// +/// // Access the data by mutable reference +/// let example_mut_ref = example.reveal_mut(); +/// *example_mut_ref = [42u8; 32]; +/// assert_eq!(example.reveal(), &[42u8; 32]); +/// +/// // Clone the data safely +/// let mut example_clone = example.clone(); +/// +/// // Zeroize the data manually +/// example_clone.zeroize(); +/// assert_eq!(example_clone.reveal(), &[0u8; 32]); +/// # } +/// ``` +#[macro_export] +macro_rules! hidden_type { + ($name:ident, $type:ty) => { + /// A hidden type + #[derive(Clone, Debug, Zeroize)] + pub struct $name { + data: Hidden<$type>, + } + + impl $name { + /// Get an immutable reference to the data + #[allow(dead_code)] + pub fn reveal(&self) -> &$type { + self.data.reveal() + } + + /// Get a mutable reference to the data + #[allow(dead_code)] + pub fn reveal_mut(&mut self) -> &mut $type { + self.data.reveal_mut() + } + } + + impl From<$type> for $name { + /// Hide existing data + fn from(t: $type) -> Self { + Self { + data: Hidden::hide(t), + } + } + } + }; +} + +/// A generic type for data that needs to be kept hidden and zeroized when out of scope, and is accessible only by +/// reference. +/// +/// You can define a hidden type using any underlying data type that implements `Zeroize`. +/// This is the case for most basic types that you probably care about. +/// +/// Hidden data has useful properties: +/// - The data is not subject to `Debug` or `Display` output, which are masked. +/// - The data can only be accessed by (immutable or mutable) reference. +/// - The data zeroizes when dropped, and can also be manually zeroized. +/// - Cloning is safe. +/// +/// Note that it may not be safe to dereference the hidden data if its type implements `Copy`. +/// If the type does not implement `Copy`, you should be fine. +/// If it does, avoid dereferencing. +/// +/// Hidden data supports transparent deserialization, but you'll need to implement serialization yourself if you need +/// it. +/// +/// ```edition2018 +/// # use tari_utilities::hidden::Hidden; +/// # use zeroize::Zeroize; +/// +/// // In this example, we need to handle secret data of type `[u8; 32]`. +/// +/// // We can create hidden data from existing data; in this case, it's the caller's responsibility to make sure the existing data is handled securely +/// let hidden_from_data = Hidden::<[u8; 32]>::hide([1u8; 32]); +/// +/// // We can access the hidden data as a reference, but not take ownership of it +/// assert_eq!(hidden_from_data.reveal(), &[1u8; 32]); +/// +/// // We can create default hidden data and then modify it as a mutable reference; this is common for functions that act on data in place +/// let mut hidden_in_place = Hidden::<[u8; 32]>::hide([0u8; 32]); +/// let hidden_in_place_mut_ref = hidden_in_place.reveal_mut(); +/// *hidden_in_place_mut_ref = [42u8; 32]; +/// assert_eq!(hidden_in_place.reveal(), &[42u8; 32]); +/// +/// // Cloning is safe to do +/// let mut clone = hidden_in_place.clone(); +/// assert_eq!(hidden_in_place.reveal(), clone.reveal()); +/// +/// // You can manually zeroize the data if you need to +/// clone.zeroize(); +/// assert_eq!(clone.reveal(), &[0u8; 32]); +/// ``` +#[derive(Clone, Deserialize)] #[serde(transparent)] -pub struct Hidden { - inner: T, +pub struct Hidden +where T: Zeroize +{ + inner: Box, } -impl Hidden { - /// Hides a value. +impl Hidden +where T: Zeroize +{ + /// Create new hidden data from the underlying type pub fn hide(inner: T) -> Self { - Self { inner } - } - - /// Returns ownership of the inner value discarding the wrapper. - pub fn into_inner(self) -> T { - self.inner + Self { inner: Box::new(inner) } } - /// Provides access to the inner value (immutable). + /// Reveal the hidden data as an immutable reference pub fn reveal(&self) -> &T { - &self.inner + self.inner.deref() } - /// Provides access to the inner value (mutable). + /// Reveal the hidden data as a mutable reference pub fn reveal_mut(&mut self) -> &mut T { - &mut self.inner + self.inner.deref_mut() } } -impl From for Hidden { - fn from(inner: T) -> Self { - Self::hide(inner) +/// Only output masked data for debugging, keeping the hidden data hidden +impl fmt::Debug for Hidden +where T: Zeroize +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Hidden<{}>", type_name::()) } } -/// Defines a masked value for the type to output as debug information. Concealing the secrets within. -impl fmt::Debug for Hidden { +/// Only display masked data, keeping the hidden data hidden +impl fmt::Display for Hidden +where T: Zeroize +{ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "Hidden<{}>", std::any::type_name::()) + write!(f, "Hidden<{}>", type_name::()) } } -/// Defines a masked value for the type to display. Concealing the secrets within. -impl fmt::Display for Hidden { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "Hidden<{}>", std::any::type_name::()) +/// Zeroize the hidden data +impl Zeroize for Hidden +where T: Zeroize +{ + fn zeroize(&mut self) { + self.inner.zeroize(); + } +} + +/// Zeroize the hidden data when dropped +impl Drop for Hidden +where T: Zeroize +{ + fn drop(&mut self) { + self.zeroize(); } } #[cfg(test)] -mod test { +mod tests { use super::*; #[test] - fn into_applies_wrapper_deref_removes_it() { - let wrapped: Hidden = Hidden::hide(42); - assert_eq!(42, *wrapped.reveal()); + fn references() { + // Check immutable reference + assert_eq!(Hidden::hide(0u8).reveal(), &0u8); + + // Check mutable reference + let mut hidden = Hidden::hide([0u8; 32]); + let hidden_mut_ref = hidden.reveal_mut(); + + *hidden_mut_ref = [42u8; 32]; + assert_eq!(hidden.reveal(), &[42u8; 32]); } #[test] - fn hidden_value_derived_trats() { - let wrapped: Hidden = 0.into(); - assert_eq!(wrapped, Hidden::default()); + fn deserialize() { + let value = 1u8; + let hidden = Hidden::::hide(value); + + let ser = value.to_string(); + + let deser: Hidden = serde_json::from_str(&ser).unwrap(); + assert_eq!(hidden.reveal(), deser.reveal()); + } + + #[test] + fn masking() { + let hidden = Hidden::::hide(1u8); + let formatted = format!("{}", hidden); + let expected = format!("Hidden<{}>", type_name::()); + assert_eq!(formatted, expected); + } + + #[test] + fn macro_types() { + hidden_type!(TypeA, [u8; 32]); + hidden_type!(TypeB, [u8; 32]); + let a = TypeA::from([1u8; 32]); + let b = TypeB::from([1u8; 32]); + + assert_eq!(a.reveal(), &[1u8; 32]); + assert_eq!(b.reveal(), &[1u8; 32]); } } diff --git a/src/lib.rs b/src/lib.rs index 69ed574..eedcde5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -30,10 +30,11 @@ pub mod fixed_set; pub mod hash; pub mod hex; #[macro_use] -pub mod locks; pub mod hidden; +pub mod locks; pub mod message_format; pub mod password; +pub mod safe_array; pub mod serde; pub use self::{ diff --git a/src/message_format.rs b/src/message_format.rs index 4809319..a315f5c 100644 --- a/src/message_format.rs +++ b/src/message_format.rs @@ -71,7 +71,7 @@ where T: DeserializeOwned + Serialize fn to_base64(&self) -> Result { let val = self.to_binary()?; - Ok(base64::encode(&val)) + Ok(base64::encode(val)) } fn from_binary(msg: &[u8]) -> Result { diff --git a/src/password.rs b/src/password.rs index 96eb170..9f13889 100644 --- a/src/password.rs +++ b/src/password.rs @@ -1,41 +1,78 @@ -//! A module with a safe password wrapper. +// Copyright 2022. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -use std::{error::Error, fmt, str::FromStr}; +//! A type for handling a passphrase safely. -use serde::{Deserialize, Serialize}; -use zeroize::Zeroize; +use std::{error::Error, fmt::Display, str::FromStr}; -use crate::Hidden; +use serde::{ser::SerializeSeq, Deserialize, Serialize, Serializer}; -/// A hidden string that implements [`Zeroize`]. -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] +use crate::hidden::Hidden; + +/// A representation of a passphrase that zeroizes on drop, prevents display and debug output, and limits access to +/// references +/// +/// The passphrase can be instantiated from a string or any type that can become a string. +/// It is converted to a byte array, which can be accessed as a mutable or immutable reference. +/// You can serialize and deserialize it transparently. +/// +/// ```edition2018 +/// # use tari_utilities::SafePassword; +/// +/// // Create a safe passphrase +/// let passphrase = SafePassword::from("my secret passphrase"); +/// +/// // We can also use a string directly +/// assert_eq!( +/// passphrase.reveal(), +/// SafePassword::from("my secret passphrase".to_string()).reveal() +/// ); +/// ``` +#[derive(Clone, Debug, Deserialize)] #[serde(transparent)] pub struct SafePassword { - password: Hidden>, + passphrase: Hidden>, } -impl> From for SafePassword { - fn from(s: S) -> Self { - Self { - password: Hidden::from(s.into().into_bytes().into_boxed_slice()), - } +impl SafePassword { + /// Get an immutable reference to the passphrase + pub fn reveal(&self) -> &Vec { + self.passphrase.reveal() } -} -impl Drop for SafePassword { - fn drop(&mut self) { - self.password.reveal_mut().zeroize(); + /// Get a mutable reference to the passphrase + pub fn reveal_mut(&mut self) -> &mut Vec { + self.passphrase.reveal_mut() } } -/// An error for parsing a password from string. +/// An error for parsing a password from a string #[derive(Debug)] pub struct PasswordError; impl Error for PasswordError {} -impl fmt::Display for PasswordError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { +impl Display for PasswordError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "PasswordError") } } @@ -44,26 +81,55 @@ impl FromStr for SafePassword { type Err = PasswordError; fn from_str(s: &str) -> Result { - Ok(Self::from(s.to_owned())) + Ok(Self { + passphrase: Hidden::>::hide(String::from(s).into_bytes()), + }) } } -impl SafePassword { - /// Gets a reference to bytes of a passphrase. - pub fn reveal(&self) -> &[u8] { - self.password.reveal() +impl> From for SafePassword { + fn from(s: S) -> Self { + Self { + passphrase: Hidden::>::hide(s.into().into_bytes()), + } + } +} + +impl Serialize for SafePassword { + fn serialize(&self, serializer: S) -> Result + where S: Serializer { + let mut seq = serializer.serialize_seq(Some(self.passphrase.reveal().len()))?; + for e in self.passphrase.reveal() { + seq.serialize_element(e)?; + } + seq.end() } } #[cfg(test)] mod tests { - use crate::SafePassword; + use std::str::FromStr; + + use super::SafePassword; #[test] - fn test_password() { - assert_eq!( - SafePassword::from("secret_must_match".to_string()), - SafePassword::from("secret_must_match") - ); + fn from_strings() { + let password = "password"; + + let from_str = SafePassword::from_str(password).unwrap(); + let from_string = SafePassword::from(password.to_string()); + let from_string_ref = SafePassword::from(password); + + assert_eq!(from_str.reveal(), from_string.reveal()); + assert_eq!(from_string.reveal(), from_string_ref.reveal()); + } + + #[test] + fn serialization() { + let safe_password = SafePassword::from("password"); + let ser = serde_json::to_string(&safe_password).unwrap(); + let deser: SafePassword = serde_json::from_str(&ser).unwrap(); + + assert_eq!(safe_password.reveal(), deser.reveal()); } } diff --git a/src/safe_array.rs b/src/safe_array.rs new file mode 100644 index 0000000..dc42d51 --- /dev/null +++ b/src/safe_array.rs @@ -0,0 +1,170 @@ +// Copyright 2022. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +//! An array-like type with safety features that make it suitable for cryptographic keys. + +use std::{ + fmt::Debug, + ops::{Deref, DerefMut}, +}; + +use subtle::ConstantTimeEq; +use zeroize::Zeroize; + +/// Sometimes it is not good that an array be used for a cryptographic key. +/// +/// For example, creating `Hidden` data out of such an array may cause copies to arise if the data is dereferenced. +/// Further, you likely want constant-time equality testing. +/// +/// A `SafeArray` is a useful generic type that looks like an array where it counts for keys. +/// It supports reference access by implementing `AsRef<[T]>` and `AsMut<[T]>`, but does not implement `Copy`. +/// It also supports `Deref` and `DerefMut` with `[T]` targets. +/// Further, you get `Default` for handy instantiation, as well as `Clone`. +/// It automatically handles equality checking in constant time. +/// +/// Under the hood, it's just `Vec`, but don't tell anybody. +/// +/// It's recommended that you use it as part of a `Hidden` type when you need a cryptographic key, like this: +/// +/// ```edition2018 +/// # #[macro_use] extern crate tari_utilities; +/// # use rand::rngs::OsRng; +/// # use rand::RngCore; +/// # use tari_utilities::{hidden_type, hidden::Hidden, safe_array::SafeArray}; +/// # use zeroize::Zeroize; +/// # fn main() { +/// // Use the hidden type macro to build a new type for a 32-byte cryptographic key +/// hidden_type!(CipherKey, SafeArray); +/// +/// // Create a new default key +/// let mut key = CipherKey::from(SafeArray::default()); +/// +/// // You can access the data as an array reference +/// assert_eq!(key.reveal().as_ref(), &[0u8; 32]); +/// +/// // Fill the key with random data, which requires `&mut [u8]` +/// let mut rng = OsRng; +/// rng.fill_bytes(key.reveal_mut()); +/// } +/// ``` +#[derive(Clone, Debug)] +pub struct SafeArray(Vec); + +impl SafeArray { + /// The fixed number of elements + pub const LEN: usize = N; +} + +impl AsRef<[T]> for SafeArray { + fn as_ref(&self) -> &[T] { + &self.0 + } +} + +impl AsMut<[T]> for SafeArray { + fn as_mut(&mut self) -> &mut [T] { + &mut self.0 + } +} + +impl Deref for SafeArray { + type Target = [T]; + + fn deref(&self) -> &Self::Target { + self.0.deref() + } +} + +impl DerefMut for SafeArray { + fn deref_mut(&mut self) -> &mut Self::Target { + self.0.deref_mut() + } +} + +impl Zeroize for SafeArray +where T: Zeroize +{ + fn zeroize(&mut self) { + self.0.zeroize(); + } +} + +impl Default for SafeArray +where T: Clone + Default +{ + fn default() -> Self { + Self(vec![T::default(); N]) + } +} + +impl ConstantTimeEq for SafeArray +where T: ConstantTimeEq +{ + fn ct_eq(&self, other: &Self) -> subtle::Choice { + self.0.ct_eq(&other.0) + } +} + +impl Eq for SafeArray where T: ConstantTimeEq {} +impl PartialEq for SafeArray +where T: ConstantTimeEq +{ + fn eq(&self, other: &Self) -> bool { + self.ct_eq(other).unwrap_u8() == 1u8 + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn reference() { + use rand::{rngs::OsRng, RngCore}; + use zeroize::Zeroize; + + use crate::{hidden::Hidden, hidden_type}; + + hidden_type!(CipherKey, SafeArray); + let key_a = CipherKey::from(SafeArray::::default()); + let key_b = CipherKey::from(SafeArray::::default()); + + // Test equality in constant time between the `SafeArray` types + assert_eq!(key_a.reveal(), key_b.reveal()); + + // Test equality (not in constant time) using an array reference + assert_eq!(key_a.reveal().as_ref(), &[0u8; 32]); + + // Test mutable reference access + let mut key_c = CipherKey::from(SafeArray::::default()); + let mut rng = OsRng; + rng.fill_bytes(key_c.reveal_mut()); + assert_ne!(key_c.reveal().as_ref(), &[0u8; 32]); + } + + #[test] + fn len() { + const N: usize = 64; + assert_eq!(SafeArray::::default().len(), N); + assert_eq!(SafeArray::::LEN, N); + } +}