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<'a>(
162 &self,
163 token: &'a [u8],
164 ) -> SecurityResult<(AdvancedTokenManager, &'a [u8])> {
165 match &self.source {
166 SecretSource::Static(secret) => self.manager(secret).map(|manager| (manager, token)),
167 SecretSource::Keyring(keyring) => {
168 let (key_id, inner) = unwrap_keyring_token(token)?;
169 let material = keyring
170 .resolve_for_validation(key_id, unix_time_ms())
171 .map_err(|_| SecurityError::VerificationFailed)?;
172 self.manager(&material.secret)
173 .map(|manager| (manager, inner))
174 }
175 #[cfg(windows)]
176 SecretSource::WindowsDpapiKeyring(keyring) => {
177 let (key_id, inner) = unwrap_keyring_token(token)?;
178 let material = keyring
179 .resolve_for_validation(key_id, unix_time_ms())
180 .map_err(|_| SecurityError::VerificationFailed)?;
181 self.manager(&material.secret)
182 .map(|manager| (manager, inner))
183 }
184 }
185 }
186
187 fn generate_options(claims: &TokenClaims) -> GenerateTokenOptions<'_> {
188 let expires_in = if claims.ttl_ms == 0 {
189 None
190 } else {
191 Some(claims.ttl_ms.div_ceil(1_000))
192 };
193 GenerateTokenOptions {
194 expires_in,
195 issuer: Some(claims.issuer.as_str()),
196 audience: Some(claims.audience.as_str()),
197 ..Default::default()
198 }
199 }
200
201 fn validate_options(claims: &TokenClaims) -> ValidateTokenOptions<'_> {
202 ValidateTokenOptions {
203 issuer: Some(claims.issuer.as_str()),
204 audience: Some(claims.audience.as_str()),
205 ..Default::default()
206 }
207 }
208}
209
210fn validate_material(secret: &[u8], salts: &[Vec<u8>]) -> SecurityResult<()> {
211 if secret.len() < MIN_TOKEN_SECRET_BYTES || salts.is_empty() || salts.iter().any(Vec::is_empty)
212 {
213 return Err(SecurityError::InvalidToken);
214 }
215 Ok(())
216}
217
218impl TokenProvider for HashTokenProvider {
219 fn seal(&self, payload: &[u8], claims: &TokenClaims) -> SecurityResult<Vec<u8>> {
220 let (mut manager, key_id) = self.active_manager()?;
221 let token = manager
222 .seal_token_bytes(payload, Self::generate_options(claims))
223 .map(|token| token.into_bytes())
224 .map_err(|_| SecurityError::InvalidToken)?;
225 Ok(wrap_keyring_token(key_id.as_deref(), token))
226 }
227
228 fn open(&self, token: &[u8], claims: &TokenClaims) -> SecurityResult<Vec<u8>> {
229 let (manager, token) = self.validation_manager(token)?;
230 let token = std::str::from_utf8(token).map_err(|_| SecurityError::InvalidToken)?;
231 manager
232 .open_token_bytes(token, Self::validate_options(claims))
233 .map(|verified| verified.payload)
234 .map_err(|_| SecurityError::InvalidToken)
235 }
236
237 fn sign(&self, payload: &[u8], claims: &TokenClaims) -> SecurityResult<Vec<u8>> {
238 let (mut manager, key_id) = self.active_manager()?;
239 let token = manager
240 .generate_token_bytes(payload, Self::generate_options(claims))
241 .map(|token| token.into_bytes())
242 .map_err(|_| SecurityError::InvalidToken)?;
243 Ok(wrap_keyring_token(key_id.as_deref(), token))
244 }
245
246 fn verify(&self, payload: &[u8], signature: &[u8], claims: &TokenClaims) -> SecurityResult<()> {
247 let (manager, signature) = self.validation_manager(signature)?;
248 let signature = std::str::from_utf8(signature).map_err(|_| SecurityError::InvalidToken)?;
249 let verified = manager
250 .validate_token_bytes(signature, Self::validate_options(claims))
251 .map_err(|_| SecurityError::VerificationFailed)?;
252 if verified.payload == payload {
253 return Ok(());
254 }
255 Err(SecurityError::VerificationFailed)
256 }
257}
258
259fn wrap_keyring_token(key_id: Option<&str>, token: Vec<u8>) -> Vec<u8> {
260 let Some(key_id) = key_id else {
261 return token;
262 };
263 let mut wrapped =
264 Vec::with_capacity(KEYRING_TOKEN_PREFIX.len() + key_id.len() + token.len() + 1);
265 wrapped.extend_from_slice(KEYRING_TOKEN_PREFIX);
266 wrapped.extend_from_slice(key_id.as_bytes());
267 wrapped.push(b'.');
268 wrapped.extend_from_slice(&token);
269 wrapped
270}
271
272fn unwrap_keyring_token(token: &[u8]) -> SecurityResult<(&str, &[u8])> {
273 let remainder = token
274 .strip_prefix(KEYRING_TOKEN_PREFIX)
275 .ok_or(SecurityError::InvalidToken)?;
276 let separator = remainder
277 .iter()
278 .position(|byte| *byte == b'.')
279 .ok_or(SecurityError::InvalidToken)?;
280 let key_id =
281 std::str::from_utf8(&remainder[..separator]).map_err(|_| SecurityError::InvalidToken)?;
282 if key_id.is_empty()
283 || remainder
284 .get(separator + 1..)
285 .is_none_or(|inner| inner.is_empty())
286 {
287 return Err(SecurityError::InvalidToken);
288 }
289 Ok((key_id, &remainder[separator + 1..]))
290}
291
292fn unix_time_ms() -> u64 {
293 SystemTime::now()
294 .duration_since(UNIX_EPOCH)
295 .map(|duration| duration.as_millis() as u64)
296 .unwrap_or(0)
297}
298
299#[cfg(test)]
300#[path = "hashtoken_tests.rs"]
301mod tests;