context_authenticator/
lib.rs1use fidl_fuchsia_pkg as fpkg;
6use hmac::Mac as _;
7
8#[derive(Clone, Debug)]
11pub struct ContextAuthenticator {
12 hmac: hmac::Hmac<sha2::Sha256>,
13}
14
15const SECRET_LEN: usize = 64;
17const TAG_LEN: usize = 32;
19
20impl ContextAuthenticator {
21 pub fn new() -> Self {
23 let mut secret = [0; SECRET_LEN];
24 let () = rand::fill(&mut secret[..]);
25 Self::from_secret(secret)
26 }
27
28 fn from_secret(secret: [u8; SECRET_LEN]) -> Self {
29 Self { hmac: hmac::Hmac::<sha2::Sha256>::new(secret.as_slice().into()) }
30 }
31
32 pub fn create(mut self, hash: &fuchsia_hash::Hash) -> fpkg::ResolutionContext {
35 let () = self.hmac.update(hash.as_bytes());
36 let mut bytes = self.hmac.finalize().into_bytes().to_vec();
37 bytes.extend_from_slice(hash.as_bytes());
38 fpkg::ResolutionContext { bytes }
39 }
40
41 pub fn authenticate(
44 mut self,
45 context: fpkg::ResolutionContext,
46 ) -> Result<fuchsia_hash::Hash, ContextAuthenticatorError> {
47 let context: &[u8; TAG_LEN + fuchsia_hash::HASH_SIZE] = context
48 .bytes
49 .as_slice()
50 .try_into()
51 .map_err(|_| ContextAuthenticatorError::InvalidLength(context.bytes.len()))?;
52 let (tag, hash) = context.split_at(TAG_LEN);
53 let () = self.hmac.update(hash);
54 let () =
55 self.hmac.verify_slice(tag).map_err(ContextAuthenticatorError::AuthenticationFailed)?;
56 fuchsia_hash::Hash::try_from(hash)
59 .map_err(|_| ContextAuthenticatorError::InvalidLength(context.len()))
60 }
61}
62
63impl Default for ContextAuthenticator {
64 fn default() -> Self {
65 Self::new()
66 }
67}
68
69#[derive(thiserror::Error, Debug)]
70pub enum ContextAuthenticatorError {
71 #[error("expected context length {} found {}", TAG_LEN + fuchsia_hash::HASH_SIZE, .0)]
72 InvalidLength(usize),
73
74 #[error("authentication failed")]
75 AuthenticationFailed(#[source] hmac::digest::MacError),
76}
77
78#[cfg(test)]
79mod tests {
80 use super::*;
81 use assert_matches::assert_matches;
82
83 fn hash() -> fuchsia_hash::Hash {
84 [0; 32].into()
85 }
86
87 #[fuchsia::test]
88 fn tag_len() {
89 let authenticator = ContextAuthenticator::new();
90 assert_eq!(authenticator.hmac.finalize().into_bytes().len(), TAG_LEN);
91 }
92
93 #[fuchsia::test]
94 fn success() {
95 let authenticator = ContextAuthenticator::from_secret([0u8; SECRET_LEN]);
96 let context = authenticator.clone().create(&hash());
97 assert_eq!(authenticator.authenticate(context).unwrap(), hash());
98 }
99
100 #[fuchsia::test]
101 fn invalid_context_length() {
102 let authenticator = ContextAuthenticator::from_secret([0u8; SECRET_LEN]);
103 assert_matches!(
104 authenticator.authenticate(fpkg::ResolutionContext { bytes: vec![] }),
105 Err(ContextAuthenticatorError::InvalidLength(0))
106 );
107 }
108
109 #[fuchsia::test]
110 fn authentication_fails() {
111 let authenticator = ContextAuthenticator::from_secret([0u8; SECRET_LEN]);
112 let mut context = authenticator.clone().create(&hash());
113 context.bytes[0] = !context.bytes[0];
114 assert_matches!(
115 authenticator.authenticate(context),
116 Err(ContextAuthenticatorError::AuthenticationFailed(_))
117 );
118 }
119}