Skip to main content

Crate secure_gate

Crate secure_gate 

Source
Expand description

Secure wrappers for secrets with explicit access and mandatory zeroization — a no_std-compatible, zero-overhead library with audit-friendly access patterns.

Secrets are automatically zeroized on drop (the inner type must implement Zeroize). No Deref, no accidental leaks — callers access the inner secret only via RevealSecret / RevealSecretMut. Debug always prints [REDACTED]. All access follows a 3-tier model: scoped closures (preferred), direct references (escape hatch), and owned extraction (consumption).

§Which type should I use?

TypeAllocationUse caseFeature
Fixed<T>StackKeys, nonces, tokens — compile-time-known sizeAlways available
Dynamic<T>HeapPasswords, API keys, ciphertexts — variable lengthalloc (default)

Both types share the same RevealSecret / RevealSecretMut access API.

§Quick start

use secure_gate::{Fixed, RevealSecret};

// Wrap a 32-byte key
let key = Fixed::new([0x42u8; 32]);

// Tier 1 — scoped access (preferred): secret ref cannot escape the closure
let first = key.with_secret(|bytes| bytes[0]);
assert_eq!(first, 0x42);

// Tier 2 — direct reference (escape hatch for FFI / third-party APIs)
assert_eq!(key.expose_secret().len(), 32);

// Debug is always redacted
assert_eq!(format!("{:?}", key), "[REDACTED]");
// key is zeroized when dropped
use secure_gate::{Dynamic, RevealSecret};

let password: Dynamic<String> = Dynamic::new(String::from("hunter2"));
let len = password.with_secret(|s: &String| s.len());
assert_eq!(len, 7);
use secure_gate::{fixed_alias, RevealSecret};

fixed_alias!(pub Aes256Key, 32);

let key: Aes256Key = [0xABu8; 32].into();
key.with_secret(|b| assert_eq!(b.len(), 32));

§Module structure

secure_gate (lib.rs)
├── Fixed<T>              ← always available, stack-allocated
├── Dynamic<T>            ← requires `alloc`, heap-allocated
├── traits/
│   ├── RevealSecret      ← immutable access (always available)
│   ├── RevealSecretMut   ← mutable access (always available)
│   ├── revealed_secrets/
│   │   ├── InnerSecret<T>    ← owned extraction wrapper
│   │   └── EncodedSecret     ← zeroizing encoded string wrapper (alloc)
│   ├── ConstantTimeEq    ← ct-eq feature
│   ├── CloneableSecret   ← cloneable feature
│   ├── SerializableSecret← serde-serialize feature
│   ├── encoding/         ← ToHex, ToBase64Url, ToBech32, ToBech32m
│   └── decoding/         ← FromHexStr, FromBase64UrlStr, FromBech32Str, FromBech32mStr
├── macros/               ← fixed_alias!, dynamic_alias!, etc.
└── error                 ← FromSliceError, HexError, Base64Error, Bech32Error, DecodingError

All public items are re-exported at the crate root. Use secure_gate::Fixed, not secure_gate::fixed::Fixed.

§Import paths

// ✅ Correct — always import from the crate root
use secure_gate::{Fixed, RevealSecret};

// ❌ Wrong — these internal paths compile but are not the public API
// use secure_gate::traits::reveal_secret::RevealSecret;
// use secure_gate::traits::encoding::hex::ToHex;

§Method resolution: wrapper methods vs trait methods

Encoding methods exist at two levels — both produce identical results:

Call styleExampleAppears in audit sweep?
Wrapper inherent (ergonomic)key.to_hex()No — grep for to_hex directly
Trait via scoped access (audit-friendly)key.with_secret(|b| b.to_hex())Yes — with_secret is grep-able

The wrapper methods (Fixed::to_hex, Dynamic::to_hex) internally call self.with_secret(|s| s.to_hex()) — they are convenience shorthands, not separate implementations.

§Feature flags

FeatureDefaultDescription
allocyesHeap types (Dynamic<T>), Vec/String zeroization
stdnoFull std support (implies alloc)
Cryptographic
ct-eqnoConstantTimeEq via subtle — timing-safe comparison
randnofrom_random() / from_rng()no_std for Fixed
Serialization
serde-serializenoSerde Serialize (requires SerializableSecret marker)
serde-deserializenoSerde Deserialize with 1 MiB default limit
serdenoBoth directions
Encoding
encoding-hexnoToHex / FromHexStr via base16ct (constant-time)
encoding-base64noToBase64Url / FromBase64UrlStr via base64ct (constant-time)
encoding-bech32noToBech32 / FromBech32Str — BIP-173, extended ~5 KB limit
encoding-bech32mnoToBech32m / FromBech32mStr — BIP-350, standard 90-byte limit
encodingnoAll encoding features
Meta
cloneablenoCloneableSecret opt-in cloning
fullnoEverything

§What’s available without alloc?

With default-features = false:

Not available without alloc: Dynamic<T>, EncodedSecret, encoding traits (ToHex, etc.), decoding traits (FromHexStr, etc.), dynamic_alias!, dynamic_generic_alias!, serde support.

§no_std

no_std compatible. Fixed<T> works without alloc. Enable alloc (default) for Dynamic<T>. For pure stack / embedded builds, use default-features = false. MSRV: 1.85 (Rust edition 2024).

§Security

This crate has not undergone an independent security audit. No unsafe code — enforced with #![forbid(unsafe_code)]. Prefer scoped access (RevealSecret::with_secret) over direct references. Prefer zeroizing encoding variants (to_hex_zeroizing, etc.) when the encoded form is sensitive. See SECURITY.md for the full threat model.

See the README and SECURITY.md for full details.

Re-exports§

pub use traits::CloneableSecret;
pub use traits::ConstantTimeEq;
pub use traits::RevealSecret;
pub use traits::RevealSecretMut;
pub use traits::InnerSecret;
pub use traits::EncodedSecret;
pub use traits::SerializableSecret;
pub use traits::FromBase64UrlStr;
pub use traits::FromBech32Str;
pub use traits::FromBech32mStr;
pub use traits::FromHexStr;
pub use traits::ToBase64Url;
pub use traits::ToBech32;
pub use traits::ToBech32m;
pub use traits::ToHex;
pub use traits::SecureDecoding;
pub use traits::SecureEncoding;

Modules§

traits
Core traits for wrapper polymorphism - always available. Traits for polymorphic secret handling.

Macros§

dynamic_alias
Creates a type alias for Dynamic<T>.
dynamic_generic_alias
Creates a generic type alias Name<T> for Dynamic<T>.
fixed_alias
Creates a type alias for Fixed<[u8; N]>.
fixed_generic_alias
Creates a const-generic type alias Name<const N: usize> for Fixed<[u8; N]>.

Structs§

Dynamic
Heap-allocated secret wrapper with explicit access and automatic zeroization on drop.
DynamicReader
Cursor-like reader over Dynamic<Vec<u8>> — see Dynamic::as_reader. Cursor-like reader over a Dynamic<Vec<u8>>.
Fixed
Stack-allocated secret wrapper with explicit access and automatic zeroization on drop.

Enums§

Base64Error
Errors from Base64url decoding. Debug builds include detailed context; release builds use generic messages. Errors produced when decoding base64url strings.
Bech32Error
Errors from Bech32 (BIP-173) and Bech32m (BIP-350) decoding. Debug builds include detailed context; release builds use generic messages. Errors produced when decoding Bech32 (BIP-173) or Bech32m (BIP-350) strings.
DecodingError
Unified error type wrapping format-specific decoding errors (HexError, Base64Error, Bech32Error). Always available; variants depend on enabled features. Unified error type for multi-format decoding operations.
FromSliceError
Error returned when a byte slice cannot be converted to Fixed<[u8; N]> due to length mismatch. Produced by Fixed::try_from(&[u8]). Error returned when a byte slice cannot be converted to a fixed-size array.
HexError
Errors from hex decoding. Debug builds include detailed context; release builds use generic messages. Errors produced when decoding hexadecimal strings.

Constants§

MAX_DESERIALIZE_BYTES
Default maximum byte length for Dynamic<Vec<u8>> / Dynamic<String> deserialization (1 MiB).