Expand description
§vitaminc-hmac
vitaminc-hmac is the local HMAC-SHA256 backend for
vitaminc-prf, the serde-style API for
deriving structured, domain-separated pseudorandom values.
Keys stay in Protected allocations, the keyed digest state lives behind
ProtectedDigest, and the RFC 2104 keying is performed with every key-derived
buffer in a wiped allocation, so no unwiped copy of the key material is left
behind by construction. Each leaf derives
HMAC-SHA256(key, PAE(encoding, context, input)).
§Keying and ownership
Construction goes through vitaminc_prf::PrfKeyInit. new takes a
Protected<[u8; 32]>, so the key length is guaranteed by the type and
construction cannot fail. Key material whose length is only known at runtime —
a KMS response, an environment variable — goes through try_from_bytes, which
rejects anything shorter than MIN_KEY_LEN with a WeakKeyError. HMAC itself
accepts a key of any length, including an empty one, which would silently
produce derivations anybody can recompute.
Both constructors take the key by value, and that is the whole ownership
story: the key moves into the PRF, lives there in one Protected allocation,
and is wiped when the PRF drops. HmacSha256Prf is deliberately not Clone,
so there is never a hidden second handle whose lifetime could postpone that
wipe. Derivation borrows the PRF (&prf), so one instance serves as many
derivations as you like; where a PRF genuinely has to be shared, wrap it in
an Arc so the sharing is visible at every call site (see the
HmacSha256Prf docs).
use vitaminc_hmac::{HmacSha256Prf, WeakKeyError};
use vitaminc_prf::PrfKeyInit;
use vitaminc_protected::Protected;
let from_kms: Vec<u8> = vec![7; 32];
let prf = HmacSha256Prf::try_from_bytes(Protected::new(from_kms))?;
let too_short = HmacSha256Prf::try_from_bytes(Protected::new(vec![7; 16]));
assert!(too_short.is_err());§Deriving a block
Built-in values return the backend’s raw block through prf. Use
prf_with_context to separate the same value across indexes or applications.
use vitaminc_prf::{PrfKeyInit, PrfValue};
use vitaminc_hmac::HmacSha256Prf;
use vitaminc_protected::Protected;
let prf = HmacSha256Prf::new(Protected::new([7; 32]));
let term = "alice@example.com"
.prf_with_context(&prf, "users/email/exact/v1")
.await?;
assert_eq!(term.len(), 32);
// The same instance keeps serving; nothing was consumed.
let again = "bob@example.com"
.prf_with_context(&prf, "users/email/exact/v1")
.await?;
assert_ne!(term, again);§Producing application-specific output
prf_visit lets a visitor interpret a raw block without placing Bloom, ORE,
or other policy in the PRF crates.
use vitaminc_prf::{PrfKeyInit, PrfValue, PrfVisitor, PrfVisitorError};
use vitaminc_hmac::HmacSha256Prf;
use vitaminc_protected::Protected;
struct BloomPositions {
count: usize,
modulus: i16,
}
impl<P> PrfVisitor<[u8; 32], P> for BloomPositions {
type Value = Vec<i16>;
fn visit_block(self, block: [u8; 32]) -> Result<Self::Value, PrfVisitorError> {
Ok(block
.chunks_exact(2)
.take(self.count)
.map(|chunk| {
i16::from_le_bytes([chunk[0], chunk[1]]).rem_euclid(self.modulus)
})
.collect())
}
}
let prf = HmacSha256Prf::new(Protected::new([7; 32]));
let positions = "alice@example.com"
.prf_visit(
&prf,
BloomPositions {
count: 4,
modulus: 2048,
},
)
.await?;
assert_eq!(positions.len(), 4);§Implementing PrfValue for a struct
Struct implementations describe their fields with a map driver. The final visitor receives resolved child nodes, so each field can produce a different owned output while a deferred backend still executes the structure as one batch.
use vitaminc_prf::{
BlockVisitor, IntoPrfContext, MapAccess, MapPrf, Prf, PrfKeyInit,
PrfValue, PrfVisitor, PrfVisitorError, SeqAccess,
};
use vitaminc_hmac::HmacSha256Prf;
use vitaminc_protected::Protected;
struct User {
email: String,
aliases: Vec<String>,
}
impl PrfValue for User {
fn prf_visit_with_context<'a, P, V, C>(
self,
prf: &P,
context: C,
visitor: V,
) -> P::Ok<V::Value>
where
P: Prf,
V: PrfVisitor<P::Block, P::Passthrough>,
C: IntoPrfContext<'a>,
{
let context = context.into_prf_context().into_owned();
prf.prf_map(Some(2))
.prf_entry("email", self.email, context.clone())
.prf_entry("aliases", self.aliases, context)
.end(visitor)
}
}
#[derive(Debug, PartialEq, Eq)]
struct UserTerms {
email: [u8; 32],
aliases: Vec<[u8; 32]>,
}
struct BlockListVisitor;
impl<P> PrfVisitor<[u8; 32], P> for BlockListVisitor {
type Value = Vec<[u8; 32]>;
fn visit_seq(self, seq: SeqAccess<[u8; 32], P>) -> Result<Self::Value, PrfVisitorError> {
seq.map(|node| node.visit(BlockVisitor)).collect()
}
}
struct UserTermsVisitor;
impl<P> PrfVisitor<[u8; 32], P> for UserTermsVisitor {
type Value = UserTerms;
fn visit_map(
self,
mut map: MapAccess<[u8; 32], P>,
) -> Result<Self::Value, PrfVisitorError> {
let (email_key, email) = map.next_entry().ok_or(PrfVisitorError::InvalidValue)?;
let (aliases_key, aliases) = map.next_entry().ok_or(PrfVisitorError::InvalidValue)?;
if email_key != "email" || aliases_key != "aliases" || map.next_entry().is_some() {
return Err(PrfVisitorError::InvalidValue);
}
Ok(UserTerms {
email: email.visit(BlockVisitor)?,
aliases: aliases.visit(BlockListVisitor)?,
})
}
}
let user = User {
email: "alice@example.com".into(),
aliases: vec!["alice".into(), "a.smith".into()],
};
let prf = HmacSha256Prf::new(Protected::new([7; 32]));
let terms = user
.prf_visit_with_context(&prf, "tenant/acme/users/v1", UserTermsVisitor)
.await?;
assert_eq!(terms.aliases.len(), 2);Structs§
- Hmac
MapPrf - Map driver borrowing its
HmacSha256Prffor one derivation. - Hmac
SeqPrf - Sequence driver borrowing its
HmacSha256Prffor one derivation. - Hmac
Sha256 Prf - Local HMAC-SHA256 structured PRF.
- Weak
KeyError - Key material offered to
PrfKeyInit::try_from_byteswas too short to key the PRF safely.
Constants§
- KEY_LEN
- Length of the key accepted by
PrfKeyInit::newonHmacSha256Prf. - MIN_
KEY_ LEN - Shortest key accepted by
PrfKeyInit::try_from_bytesonHmacSha256Prf.