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