Skip to main content

moq_e2ee/
credential.rs

1//! The out-of-band broadcast credential and the derivations scoped to it alone.
2
3use std::fmt;
4use std::sync::Arc;
5
6use aws_lc_rs::hkdf::{self, HKDF_SHA256};
7use bytes::Bytes;
8use zeroize::{Zeroize, ZeroizeOnDrop};
9
10use crate::epoch::Epoch;
11use crate::error::Result;
12use crate::generation::Generation;
13use crate::limits::{KEY_LEN, NAME_LEN, PATH_LABEL, PROFILE, SECRET_LEN, check_bytes, check_u53};
14use crate::name::encode;
15
16/// What the application distributes over its own authenticated channel.
17pub struct Config {
18	/// Opaque bytes both ends agree on as the broadcast's end-to-end identity.
19	pub context: Bytes,
20	/// Selects among the credentials the application retains; rotating the secret is a new kid.
21	pub kid: u64,
22	/// 32 bytes from a cryptographically secure random generator.
23	pub secret: [u8; SECRET_LEN],
24}
25
26#[derive(Zeroize, ZeroizeOnDrop)]
27struct Secret([u8; SECRET_LEN]);
28
29struct Inner {
30	context: Bytes,
31	kid: u64,
32	// Retained only so it is zeroized with the credential; every derivation uses the PRK.
33	_secret: Secret,
34	prk: hkdf::Prk,
35}
36
37/// An immutable broadcast credential: cheap to clone, never serialized, redacted from `Debug`.
38#[derive(Clone)]
39pub struct Credential(Arc<Inner>);
40
41impl fmt::Debug for Credential {
42	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
43		f.debug_struct("Credential")
44			.field("context_len", &self.0.context.len())
45			.field("kid", &self.0.kid)
46			.field("secret", &"<redacted>")
47			.finish()
48	}
49}
50
51impl Credential {
52	/// Check the bounds and extract the HKDF PRK.
53	///
54	/// # Errors
55	///
56	/// [`Error::Identity`](crate::Error::Identity) if `context` exceeds 65535 bytes or `kid` exceeds `2^53-1`.
57	pub fn new(config: Config) -> Result<Self> {
58		check_bytes(config.context.len())?;
59		check_u53(config.kid)?;
60		let secret = Secret(config.secret);
61		let prk = hkdf::Salt::new(HKDF_SHA256, PROFILE.as_bytes()).extract(&secret.0);
62		Ok(Self(Arc::new(Inner {
63			context: config.context,
64			kid: config.kid,
65			_secret: secret,
66			prk,
67		})))
68	}
69
70	/// Broadcast context bytes.
71	pub fn context(&self) -> &[u8] {
72		&self.0.context
73	}
74
75	/// Key identifier.
76	pub fn kid(&self) -> u64 {
77		self.0.kid
78	}
79
80	/// The opaque broadcast path for a semantic broadcast name; instances publish at `<path>/<epoch>`.
81	///
82	/// # Errors
83	///
84	/// [`Error::Identity`](crate::Error::Identity) if `semantic` exceeds 65535 bytes.
85	pub fn path(&self, semantic: &str) -> Result<moq_net::PathOwned> {
86		let material = self.expand(&self.path_info(semantic)?)?;
87		Ok(moq_net::Path::new(encode(material).as_str()).into_owned())
88	}
89
90	/// Bind an epoch, scoping every name and key derived from this credential.
91	pub fn generation(&self, epoch: Epoch) -> Generation {
92		Generation::new(self.clone(), epoch)
93	}
94
95	pub(crate) fn path_info(&self, semantic: &str) -> Result<Vec<u8>> {
96		let mut info = Vec::with_capacity(PATH_LABEL.len() + 2 + self.0.context.len() + 8 + 2 + semantic.len());
97		info.extend_from_slice(PATH_LABEL);
98		append_bytes(&mut info, &self.0.context)?;
99		info.extend_from_slice(&self.0.kid.to_be_bytes());
100		append_bytes(&mut info, semantic.as_bytes())?;
101		Ok(info)
102	}
103
104	/// HKDF-Expand 16 bytes of output for `info`.
105	pub(crate) fn expand(&self, info: &[u8]) -> Result<[u8; KEY_LEN]> {
106		const { assert!(KEY_LEN == NAME_LEN) };
107		let info = [info];
108		let okm = self.0.prk.expand(&info, Len16).map_err(|_| crate::Error::Identity)?;
109		let mut out = [0u8; KEY_LEN];
110		okm.fill(&mut out).map_err(|_| crate::Error::Identity)?;
111		Ok(out)
112	}
113
114	#[cfg(test)]
115	pub(crate) fn prk_bytes(&self) -> [u8; 32] {
116		use aws_lc_rs::hmac;
117		let key = hmac::Key::new(hmac::HMAC_SHA256, PROFILE.as_bytes());
118		let tag = hmac::sign(&key, &self.0._secret.0);
119		let mut out = [0u8; 32];
120		out.copy_from_slice(tag.as_ref());
121		out
122	}
123}
124
125struct Len16;
126
127impl hkdf::KeyType for Len16 {
128	fn len(&self) -> usize {
129		KEY_LEN
130	}
131}
132
133/// Append a `bytes` field: `u16(length) || data`.
134pub(crate) fn append_bytes(out: &mut Vec<u8>, data: &[u8]) -> Result<()> {
135	check_bytes(data.len())?;
136	out.extend_from_slice(&(data.len() as u16).to_be_bytes());
137	out.extend_from_slice(data);
138	Ok(())
139}