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(&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;