1use 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
16pub struct Config {
18 pub context: Bytes,
20 pub kid: u64,
22 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 _secret: Secret,
34 prk: hkdf::Prk,
35}
36
37#[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 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 pub fn context(&self) -> &[u8] {
72 &self.0.context
73 }
74
75 pub fn kid(&self) -> u64 {
77 self.0.kid
78 }
79
80 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 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 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
133pub(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}