1 unstable release

Uses new Rust 2024

new 0.1.0 May 13, 2026

#2 in #protected


Used in siegel-uniffi

MIT license

30KB
463 lines

๐Ÿงงsiegel

Siegel from the german word for seal

Siegel is a simple package that offers best-effort protected memory allocation for loading and using secrets.

Motivation

Loading secrets into memory always comes with risks. Using hardware-backed secure elements (e.g. Apple's Secure Enclave) will provide better security and should be used where possible. However, not all use cases can leverage devices' secure elements. Some examples include:

  • Unsupported curves (e.g. curve secp256k1 is currently unsupported on iOS).
  • Unsupported operations (e.g. specific hashing functions, key derivation functions).

For these use cases, Siegel provides a type-safe mechanism to loaad secrets into application memory and perform operations with them.

Siegel particularly focuses on secrets that must cross foreign boundaries. For example, if you have a zero-knowledge proof system relying on a secret stored in the device's keychain but the specific operations must be performed on the Rust side.

Design

The design focuses on making it harder to do unsafe behavior. For example, it takes the opinionated approach of secrets being one-time use so that secrets only live in application memory for the time they are actually required. In addition to this, when secrets are in memory they are:

  • mlocked to prevent swapping to disk.
  • mprotect-sealed to prevent reading outside of a very explicit scope.
  • Zeroized on drop.
  • Page-aligned and protected by a canary for page overflows.
  • Guarded at the beginning and end to prevent accidental over/underflows.

What's protected

  • โœ… Bugs within the process where memory where the secret is stored is accidentally read.
  • โœ… Stale pointer dereferences the sealed secret (PROT_NONE segfaults)
  • โœ… Secret swapped to disk (already not applicable on iOS).
  • โœ… UniFFI copies the secret into additional buffers for lowering/lifting which results in unzeroized copies of the secret.
  • โœ… Closure panics mid-operation. Secret is still zeroized as long as there is panic unwinding.
  • โœ… Makes accidental logging of secrets hard. Secret is only accessible within an explicit closure.

What's NOT Protected

  • ๐Ÿ”ด Compromised device where an attacker has arbitrary code execution within the app process. Memory permissions can simply be updated and the secret extracted.
  • ๐Ÿ”ด Jailbroken / rooted devices. Memory protections may be bypassed and memory location inspected.
  • ๐Ÿ”ด Memory hardware attacks.
  • ๐Ÿ”ด The brief window during the read and write inside closures. This window cannot be reduced.
  • ๐Ÿ”ด Mistakes by consumers of this package. Particularly, callers could accidentally log the secret, copy it elsewhere, etc.
  • ๐Ÿ”ด Panics where the process aborts without triggering Drops. The secret may live in memory until the OS clears it.

Example

use siegel::{Siegel, Empty};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let secret_bytes = [0x42; 32]; // load your secret from it's safe storage place
    let empty: Siegel<Empty> = Siegel::new(32)?;
    let loaded = empty.write(&secret_bytes)?;
    let signature = loaded.read_once(|secret| {
        // sign_something(secret, &payload)
    })?;
    Ok(())
}

Dependencies

~145โ€“560KB
~13K SLoC