pub struct Dek { /* private fields */ }Expand description
Per-user 32-byte DEK material. The byte buffer is
wiped on Drop via the zeroize crate; callers obtain a Dek from
an HSM unwrap — the runtime never derives key material directly
(envelope encryption).
A per-DEK monotonic counter drives the deterministic 96-bit nonce
for AES-GCM / AES-GCM-SIV under the NIST SP 800-38D §8.2.1
construction (4-byte invocation field = DekConfig::replica_id +
8-byte counter, big-endian). XChaCha20-Poly1305 uses a 192-bit
random nonce and leaves the counter alone. Counter exhaustion at
u64::MAX surfaces PiiError::DekExhausted so the operator
rotates the DEK before any nonce reuse.
replica_id is held by the DEK itself and is immutable after
construction — changing it post-hoc would violate L0 A11 pure
compute (replay determinism depends on stable nonce bytes). Default
single-writer deployments use replica_id = 0; federation builds
populate a per-replica id via Dek::with_config.
Dek is intentionally not Clone — two copies of the same key
material with independent counters would collide their nonces under
AES-GCM (catastrophic integrity loss). Callers must hold a single
owner per key; rotation yields a fresh Dek via Dek::from_bytes
whose counter starts at 0.
The interior Cell<u64> counter makes Dek implicitly !Sync, so
the compiler refuses naive cross-thread sharing. If a deployment
genuinely needs to share a DEK across task boundaries (e.g., an
L2 async runtime feeding multiple encrypt handlers), wrap it in
Arc<Mutex<Dek>> or Arc<parking_lot::Mutex<Dek>> at the caller
site — the mutex guards the counter advance and keeps the
deterministic-nonce invariant intact. Single-writer deployments
(L0 A2 single-thread) hold a plain owned Dek without
synchronisation.
Implementations§
Source§impl Dek
impl Dek
Sourcepub fn from_bytes(material: [u8; 32]) -> Self
pub fn from_bytes(material: [u8; 32]) -> Self
Construct from a 32-byte buffer using default configuration
(replica_id = 0, single-writer). The input is copied; callers
remain responsible for wiping their own buffer. Counter starts
at 0.
Sourcepub fn with_config(material: [u8; 32], config: DekConfig) -> Self
pub fn with_config(material: [u8; 32], config: DekConfig) -> Self
Construct from a 32-byte buffer with an explicit DekConfig.
Federation path consumes this entry point with a non-zero
replica_id; single-writer deployments use Dek::from_bytes.
Sourcepub fn try_from_slice(bytes: &[u8]) -> Result<Self, PiiError>
pub fn try_from_slice(bytes: &[u8]) -> Result<Self, PiiError>
Construct from a byte slice. Rejects the call with
PiiError::InvalidKeyLength when bytes.len() != 32.
Counter starts at 0 with default DekConfig.
The length check is a single usize compare against the
constant 32 — no byte-by-byte value comparison happens on
the reject path, so there is no timing side-channel the
subtle crate would mitigate. copy_from_slice runs in
time dependent on the buffer length, not its contents.