axess_events/encryption.rs
1//! Payload-level encryption primitives.
2//!
3//! The envelope itself does not enforce encryption. Producers that
4//! want broker-opaque payloads wrap their value in an [`EncryptedBlob`]
5//! and place it in [`EventBody::Encrypted`](crate::EventBody::Encrypted);
6//! subscribers without the key still see envelope metadata (id,
7//! kind, time, tenant, subject, actor, status) for routing and
8//! auditing without ever decrypting the payload contents.
9//!
10//! Field-level tokenization for sensitive data inside an otherwise
11//! clear payload (PAN, email, factor secrets) is handled separately
12//! by axess's `PiiToken` / `DevicePiiStore` primitives; orthogonal
13//! to the envelope's encryption choice.
14
15use axess_strings::ShortString;
16
17/// Reference to a key in a KMS / key registry. The envelope crate
18/// stores only the id; key material lives wherever your KMS does.
19#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
20#[cfg_attr(
21 feature = "rkyv",
22 derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
23)]
24#[derive(Clone, Debug, PartialEq, Eq, Hash, Default)]
25pub struct KeyId(pub ShortString);
26
27impl KeyId {
28 /// Construct from any string slice.
29 #[inline]
30 pub fn new(s: &str) -> Self {
31 Self(ShortString::new(s))
32 }
33
34 /// Construct from a `&'static str` without allocating.
35 #[inline]
36 pub const fn from_static(s: &'static str) -> Self {
37 Self(ShortString::from_static(s))
38 }
39
40 /// Borrow as `&str`.
41 #[inline]
42 pub fn as_str(&self) -> &str {
43 self.0.as_str()
44 }
45}
46
47/// AEAD algorithm identifier on an encrypted payload. Two values
48/// today; extend via a new variant + `envelope_version` bump when a
49/// third is added.
50#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
51#[cfg_attr(
52 feature = "rkyv",
53 derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
54)]
55#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
56pub enum AeadAlgorithm {
57 /// AES-256 in GCM mode (NIST SP 800-38D). Hardware-accelerated on
58 /// most modern CPUs.
59 Aes256Gcm,
60 /// ChaCha20 with Poly1305 (RFC 8439). Constant-time without
61 /// dedicated hardware; preferred on platforms lacking AES-NI.
62 ChaCha20Poly1305,
63}
64
65/// Encrypted payload bytes. Replaces a clear payload in the
66/// [`EventBody`](crate::EventBody) for events that should remain
67/// opaque to brokers and unauthorised subscribers.
68///
69/// Decryption produces the plaintext bytes; the recipient knows what
70/// type to deserialise as via the envelope's
71/// [`kind`](crate::Event::kind) field.
72#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
73#[cfg_attr(
74 feature = "rkyv",
75 derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
76)]
77#[derive(Clone, Debug, PartialEq, Eq)]
78pub struct EncryptedBlob {
79 /// KMS reference for the key the payload was encrypted under.
80 pub key_id: KeyId,
81 /// AEAD algorithm in use.
82 pub algorithm: AeadAlgorithm,
83 /// AEAD nonce (96 bits; standard for both supported algorithms).
84 pub nonce: [u8; 12],
85 /// Authenticated ciphertext, including the AEAD tag.
86 pub ciphertext: Vec<u8>,
87}