Skip to main content

appcore_security/
hashtoken.rs

1// =============================================================================
2//        #######
3//     ###       ###     F: hashtoken.rs
4//    ##   ## ##   ##    P: AppCore-Runtime
5//         ## ##
6//                       C: 2026/05/31 13:38:42 by dnettoRaw
7//    ##   ## ##   ##    U: 2026/07/23 23:50:45 by dnettoRaw
8//      ###########      S: 1.0.1-rc.8
9// =============================================================================
10
11//! `HashToken` adapter implementation for internal signed and sealed tokens.
12
13use 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/// HashToken-based token provider for internal runtime trust.
43#[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    /// Creates a provider from at least 16 bytes of secret material.
77    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    /// Creates a provider with explicit salts and `HashToken` algorithm.
86    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    /// Creates a SHA-256 provider with explicit secret and salts.
100    pub fn with_secret(secret: Vec<u8>, salts: Vec<Vec<u8>>) -> SecurityResult<Self> {
101        Self::with_material(secret, salts, Algorithm::Sha256)
102    }
103
104    /// Creates a rotation-aware provider backed by a durable file keyring.
105    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    /// Creates a rotation-aware provider backed by the Windows DPAPI user keyring.
119    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;