secure-types 0.4.0

Secure data types that protect sensitive data in memory via locking and zeroization.
Documentation
secure-types-0.4.0 has been yanked.

Secure Types

The goal of this crate is to provide a simple way to properly handle sensitive data in memory (eg. passwords, private keys, etc).

Currently there are 3 types:

  • SecureString: For working with strings.
  • SecureVec: For working with Vec<T>.
  • SecureArray: For working with &[T; LENGTH].

Features

  • Zeroization on Drop: Memory is wiped when dropped.
  • Memory Locking: (OS-only) The allocation is mlocked (Windows: VirtualLock) and excluded from core dumps (MADV_DONTDUMP), so it cannot be swapped out or captured in a crash dump. While no unlock* scope is active the pages are also mprotected PROT_NONE, which is what keeps the contents away from other processes. On Linux the allocation is backed by memfd_secret when the kernel supports it.
  • Safe Scoped Access: Direct access on these types is not possible, data is protected by default and only accessible within safe blocks.
  • Send, not Sync: Values can be moved to another thread. Sharing one instance across threads requires an explicit lock (Arc<Mutex<_>>). Concurrent unlock would race on page protection.
  • no_std Support: For embedded and Web environments (with zeroization only). Select it by turning off the default features — see Feature Flags.
  • Serde Support: Optional serialization/deserialization for SecureString, SecureVec<u8> and SecureArray<u8, LENGTH>.

How memory is locked

Locking is best-effort in one respect: memsec discards the return value of mlock, so exhausting RLIMIT_MEMLOCK does not fail construction — the allocation is still mprotected. The constructors return Error::LockFailed when that mprotect fails, and a failed re-lock after an unlock* scope panics in every profile rather than silently leaving the memory readable.

Usage

SecureString

use secure_types::SecureString;

 // Create a SecureString
let mut secret = SecureString::from("my_super_secret");

// The memory is locked here

// Safely append more data.
secret.push_str("_password");

// The memory is locked here.

// Use a scope to safely access the content as a &str.
secret.unlock_str(|exposed_str| {
     assert_eq!(exposed_str, "my_super_secret_password");
 });

 // When `secret` is dropped, its data zeroized.

SecureVec

use secure_types::SecureVec;

// Create a new, empty secure vector.
let mut secret_key: SecureVec<u8> = SecureVec::new().unwrap();

// Push some sensitive data into it.
secret_key.push(0);
secret_key.push(1);
secret_key.push(2);

// The memory is locked here.

// Use a scope to safely access the contents as a slice.
secret_key.unlock_slice(|unlocked_slice| {
     assert_eq!(unlocked_slice, &[0, 1, 2]);
 });

SecureArray

use secure_types::SecureArray;

let exposed_array: &mut [u8; 3] = &mut [1, 2, 3];
let mut secure_array = SecureArray::from_slice_mut(exposed_array).unwrap();


secure_array.unlock_mut(|unlocked_slice| {
    assert_eq!(unlocked_slice, &[1, 2, 3]);
});

See also the examples.

Feature Flags

  • use_os (default): Enables all OS-level security features.
  • no_os: No-op, kept for backwards compatibility. no_std is selected by disabling the default features (--no-default-features), which leaves only the zeroize-on-drop guarantee.
  • serde: Enables serialization/deserialization.
  • serde_json: Adds serialize_json_into_secure_bytes / serialize_json_into_secure_string, which serialize straight into a SecureVec<u8> / SecureString instead of an ordinary Vec/String. Implies serde and requires use_os.
  • expose-ptr: For testing purposes. Exposes the locked memory region pointer.

Security notes

  • Serialization writes plaintext. Serialize cannot wipe the buffer the serializer builds for it: serde_json::to_string/to_vec leave the plaintext in an ordinary String/Vec that nothing zeroizes, so zeroize that buffer yourself if you call them. zeroized on drop. Prefer serialize_json_into_secure_bytes (feature serde_json) when the JSON is going to be compressed or encrypted, or serialize_json_into_secure_string if you want the text form or wire any serializer around SecureBytesWriter the plaintext then only ever lives in locked memory that is zeroized on drop.
  • Deserializing reads from a buffer you own. serde_json::from_str/from_slice take a plain &str/&[u8], and nothing can wipe that input for you. Parse from inside the locked buffer instead — secure_json.unlock_str(|json| serde_json::from_str::<Vault>(json)) — so the plaintext is unlocked only for the duration of the parse. Note that when a JSON string contains escape sequences, serde_json unescapes it into an internal scratch buffer of its own before handing it over; that copy is not ours to erase (strings without escapes are read straight out of your input).
  • Owned buffers a deserializer hands over are wiped. When a format gives up ownership of a String/Vec<u8> (visit_string/visit_byte_buf), the contents are copied into locked memory and the buffer is zeroized before it is released, instead of being dropped with the plaintext still inside.
  • Leaking a Drain still skips drops. SecureVec::drain unlocks the memory only while an item is read and while the iterator compacts the vector, so a core::mem::forgetped iterator leaves the memory locked but the elements left in the drained range are never dropped or zeroized, and the length stays at the drain start. Consume or drop the iterator.
  • clear() does not wipe. SecureVec::clear only sets the length to zero the bytes are still there. Use erase() to zeroize the contents.
  • SecureArray::empty() has a strict contract. Only the elements that were actually written are tracked as initialized, so dropping a partially-filled array never reads the unwritten slots. Those slots are not valid Ts though: fill the whole array (for example via unlock_mut) before reading it.

Running tests

cargo test --features serde,expose-ptr

License

Licensed under the MIT license.

Credits