appcore_security/
hashtoken.rs1use crate::secret_keyring::FileSecretKeyring;
14use crate::token::{SecurityError, SecurityResult, TokenClaims, TokenProvider};
15use hash_token_rust::{
16 AdvancedTokenManager, Algorithm, GenerateTokenOptions, ValidateTokenOptions,
17};
18use std::time::{SystemTime, UNIX_EPOCH};
19use zeroize::Zeroize;
20
21const KEYRING_TOKEN_PREFIX: &[u8] = b"appcore-keyring-v1.";
22
23#[derive(Clone)]
24enum SecretSource {
25 Static(Vec<u8>),
26 Keyring(FileSecretKeyring),
27}
28
29impl Drop for SecretSource {
30 fn drop(&mut self) {
31 if let Self::Static(secret) = self {
32 secret.zeroize();
33 }
34 }
35}
36
37#[derive(Clone)]
39pub struct HashTokenProvider {
40 source: SecretSource,
41 salts: Vec<Vec<u8>>,
42 algorithm: Algorithm,
43}
44
45impl std::fmt::Debug for HashTokenProvider {
46 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
47 formatter
48 .debug_struct("HashTokenProvider")
49 .field(
50 "source",
51 &match &self.source {
52 SecretSource::Static(_) => "static(REDACTED)",
53 SecretSource::Keyring(_) => "file-keyring-v1",
54 },
55 )
56 .field("salts", &self.salts.len())
57 .field("algorithm", &self.algorithm)
58 .finish()
59 }
60}
61
62impl Drop for HashTokenProvider {
63 fn drop(&mut self) {
64 self.salts.zeroize();
65 }
66}
67
68impl HashTokenProvider {
69 pub fn from_secret(secret: Vec<u8>) -> SecurityResult<Self> {
71 if secret.len() < 16 {
72 return Err(SecurityError::InvalidToken);
73 }
74 Ok(Self {
75 source: SecretSource::Static(secret),
76 salts: vec![b"appcore-salt-1".to_vec(), b"appcore-salt-2".to_vec()],
77 algorithm: Algorithm::Sha256,
78 })
79 }
80
81 pub fn with_material(secret: Vec<u8>, salts: Vec<Vec<u8>>, algorithm: Algorithm) -> Self {
83 Self {
84 source: SecretSource::Static(secret),
85 salts,
86 algorithm,
87 }
88 }
89
90 pub fn with_secret(secret: Vec<u8>, salts: Vec<Vec<u8>>) -> Self {
92 Self {
93 source: SecretSource::Static(secret),
94 salts,
95 algorithm: Algorithm::Sha256,
96 }
97 }
98
99 pub fn from_keyring(keyring: FileSecretKeyring, salts: Vec<Vec<u8>>) -> SecurityResult<Self> {
101 keyring
102 .resolve_active(unix_time_ms())
103 .map_err(|_| SecurityError::SecretUnavailable)?;
104 Ok(Self {
105 source: SecretSource::Keyring(keyring),
106 salts,
107 algorithm: Algorithm::Sha256,
108 })
109 }
110
111 fn manager(&self, secret: &[u8]) -> SecurityResult<AdvancedTokenManager> {
112 let salt_slices: Vec<&[u8]> = self.salts.iter().map(Vec::as_slice).collect();
113 AdvancedTokenManager::new(secret, &salt_slices, self.algorithm)
114 .map_err(|_| SecurityError::InvalidToken)
115 }
116
117 fn active_manager(&self) -> SecurityResult<(AdvancedTokenManager, Option<String>)> {
118 match &self.source {
119 SecretSource::Static(secret) => self.manager(secret).map(|manager| (manager, None)),
120 SecretSource::Keyring(keyring) => {
121 let material = keyring
122 .resolve_active(unix_time_ms())
123 .map_err(|_| SecurityError::SecretUnavailable)?;
124 self.manager(&material.secret)
125 .map(|manager| (manager, Some(material.metadata.key_id.clone())))
126 }
127 }
128 }
129
130 fn validation_manager(&self, token: &[u8]) -> SecurityResult<(AdvancedTokenManager, Vec<u8>)> {
131 match &self.source {
132 SecretSource::Static(secret) => self
133 .manager(secret)
134 .map(|manager| (manager, token.to_vec())),
135 SecretSource::Keyring(keyring) => {
136 let (key_id, inner) = unwrap_keyring_token(token)?;
137 let material = keyring
138 .resolve_for_validation(key_id, unix_time_ms())
139 .map_err(|_| SecurityError::VerificationFailed)?;
140 self.manager(&material.secret)
141 .map(|manager| (manager, inner.to_vec()))
142 }
143 }
144 }
145
146 fn generate_options(claims: &TokenClaims) -> GenerateTokenOptions<'_> {
147 let expires_in = if claims.ttl_ms == 0 {
148 None
149 } else {
150 Some(claims.ttl_ms.div_ceil(1_000))
151 };
152 GenerateTokenOptions {
153 expires_in,
154 issuer: Some(claims.issuer.as_str()),
155 audience: Some(claims.audience.as_str()),
156 ..Default::default()
157 }
158 }
159
160 fn validate_options(claims: &TokenClaims) -> ValidateTokenOptions<'_> {
161 ValidateTokenOptions {
162 issuer: Some(claims.issuer.as_str()),
163 audience: Some(claims.audience.as_str()),
164 ..Default::default()
165 }
166 }
167}
168
169impl TokenProvider for HashTokenProvider {
170 fn seal(&self, payload: &[u8], claims: &TokenClaims) -> SecurityResult<Vec<u8>> {
171 let (mut manager, key_id) = self.active_manager()?;
172 let token = manager
173 .seal_token_bytes(payload, Self::generate_options(claims))
174 .map(|token| token.into_bytes())
175 .map_err(|_| SecurityError::InvalidToken)?;
176 Ok(wrap_keyring_token(key_id.as_deref(), token))
177 }
178
179 fn open(&self, token: &[u8], claims: &TokenClaims) -> SecurityResult<Vec<u8>> {
180 let (manager, token) = self.validation_manager(token)?;
181 let token = std::str::from_utf8(&token).map_err(|_| SecurityError::InvalidToken)?;
182 manager
183 .open_token_bytes(token, Self::validate_options(claims))
184 .map(|verified| verified.payload)
185 .map_err(|_| SecurityError::InvalidToken)
186 }
187
188 fn sign(&self, payload: &[u8], claims: &TokenClaims) -> SecurityResult<Vec<u8>> {
189 let (mut manager, key_id) = self.active_manager()?;
190 let token = manager
191 .generate_token_bytes(payload, Self::generate_options(claims))
192 .map(|token| token.into_bytes())
193 .map_err(|_| SecurityError::InvalidToken)?;
194 Ok(wrap_keyring_token(key_id.as_deref(), token))
195 }
196
197 fn verify(&self, payload: &[u8], signature: &[u8], claims: &TokenClaims) -> SecurityResult<()> {
198 let (manager, signature) = self.validation_manager(signature)?;
199 let signature = std::str::from_utf8(&signature).map_err(|_| SecurityError::InvalidToken)?;
200 let verified = manager
201 .validate_token_bytes(signature, Self::validate_options(claims))
202 .map_err(|_| SecurityError::VerificationFailed)?;
203 if verified.payload == payload {
204 return Ok(());
205 }
206 Err(SecurityError::VerificationFailed)
207 }
208}
209
210fn wrap_keyring_token(key_id: Option<&str>, token: Vec<u8>) -> Vec<u8> {
211 let Some(key_id) = key_id else {
212 return token;
213 };
214 let mut wrapped =
215 Vec::with_capacity(KEYRING_TOKEN_PREFIX.len() + key_id.len() + token.len() + 1);
216 wrapped.extend_from_slice(KEYRING_TOKEN_PREFIX);
217 wrapped.extend_from_slice(key_id.as_bytes());
218 wrapped.push(b'.');
219 wrapped.extend_from_slice(&token);
220 wrapped
221}
222
223fn unwrap_keyring_token(token: &[u8]) -> SecurityResult<(&str, &[u8])> {
224 let remainder = token
225 .strip_prefix(KEYRING_TOKEN_PREFIX)
226 .ok_or(SecurityError::InvalidToken)?;
227 let separator = remainder
228 .iter()
229 .position(|byte| *byte == b'.')
230 .ok_or(SecurityError::InvalidToken)?;
231 let key_id =
232 std::str::from_utf8(&remainder[..separator]).map_err(|_| SecurityError::InvalidToken)?;
233 if key_id.is_empty()
234 || remainder
235 .get(separator + 1..)
236 .is_none_or(|inner| inner.is_empty())
237 {
238 return Err(SecurityError::InvalidToken);
239 }
240 Ok((key_id, &remainder[separator + 1..]))
241}
242
243fn unix_time_ms() -> u64 {
244 SystemTime::now()
245 .duration_since(UNIX_EPOCH)
246 .map(|duration| duration.as_millis() as u64)
247 .unwrap_or(0)
248}
249
250#[cfg(test)]
251#[path = "hashtoken_tests.rs"]
252mod tests;