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;
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.";
22const MIN_TOKEN_SECRET_BYTES: usize = 16;
23
24#[derive(Clone)]
25enum SecretSource {
26    Static(Vec<u8>),
27    Keyring(FileSecretKeyring),
28}
29
30impl Drop for SecretSource {
31    fn drop(&mut self) {
32        if let Self::Static(secret) = self {
33            secret.zeroize();
34        }
35    }
36}
37
38/// HashToken-based token provider for internal runtime trust.
39#[derive(Clone)]
40pub struct HashTokenProvider {
41    source: SecretSource,
42    salts: Vec<Vec<u8>>,
43    algorithm: Algorithm,
44}
45
46impl std::fmt::Debug for HashTokenProvider {
47    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
48        formatter
49            .debug_struct("HashTokenProvider")
50            .field(
51                "source",
52                &match &self.source {
53                    SecretSource::Static(_) => "static(REDACTED)",
54                    SecretSource::Keyring(_) => "file-keyring-v1",
55                },
56            )
57            .field("salts", &self.salts.len())
58            .field("algorithm", &self.algorithm)
59            .finish()
60    }
61}
62
63impl Drop for HashTokenProvider {
64    fn drop(&mut self) {
65        self.salts.zeroize();
66    }
67}
68
69impl HashTokenProvider {
70    /// Creates a provider from at least 16 bytes of secret material.
71    pub fn from_secret(secret: Vec<u8>) -> SecurityResult<Self> {
72        Self::with_material(
73            secret,
74            vec![b"appcore-salt-1".to_vec(), b"appcore-salt-2".to_vec()],
75            Algorithm::Sha256,
76        )
77    }
78
79    /// Creates a provider with explicit salts and HashToken algorithm.
80    pub fn with_material(
81        secret: Vec<u8>,
82        salts: Vec<Vec<u8>>,
83        algorithm: Algorithm,
84    ) -> SecurityResult<Self> {
85        validate_material(&secret, &salts)?;
86        Ok(Self {
87            source: SecretSource::Static(secret),
88            salts,
89            algorithm,
90        })
91    }
92
93    /// Creates a SHA-256 provider with explicit secret and salts.
94    pub fn with_secret(secret: Vec<u8>, salts: Vec<Vec<u8>>) -> SecurityResult<Self> {
95        Self::with_material(secret, salts, Algorithm::Sha256)
96    }
97
98    /// Creates a rotation-aware provider backed by a durable file keyring.
99    pub fn from_keyring(keyring: FileSecretKeyring, salts: Vec<Vec<u8>>) -> SecurityResult<Self> {
100        let material = keyring
101            .resolve_active(unix_time_ms())
102            .map_err(|_| SecurityError::SecretUnavailable)?;
103        validate_material(&material.secret, &salts)?;
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
169fn validate_material(secret: &[u8], salts: &[Vec<u8>]) -> SecurityResult<()> {
170    if secret.len() < MIN_TOKEN_SECRET_BYTES || salts.is_empty() || salts.iter().any(Vec::is_empty)
171    {
172        return Err(SecurityError::InvalidToken);
173    }
174    Ok(())
175}
176
177impl TokenProvider for HashTokenProvider {
178    fn seal(&self, payload: &[u8], claims: &TokenClaims) -> SecurityResult<Vec<u8>> {
179        let (mut manager, key_id) = self.active_manager()?;
180        let token = manager
181            .seal_token_bytes(payload, Self::generate_options(claims))
182            .map(|token| token.into_bytes())
183            .map_err(|_| SecurityError::InvalidToken)?;
184        Ok(wrap_keyring_token(key_id.as_deref(), token))
185    }
186
187    fn open(&self, token: &[u8], claims: &TokenClaims) -> SecurityResult<Vec<u8>> {
188        let (manager, token) = self.validation_manager(token)?;
189        let token = std::str::from_utf8(&token).map_err(|_| SecurityError::InvalidToken)?;
190        manager
191            .open_token_bytes(token, Self::validate_options(claims))
192            .map(|verified| verified.payload)
193            .map_err(|_| SecurityError::InvalidToken)
194    }
195
196    fn sign(&self, payload: &[u8], claims: &TokenClaims) -> SecurityResult<Vec<u8>> {
197        let (mut manager, key_id) = self.active_manager()?;
198        let token = manager
199            .generate_token_bytes(payload, Self::generate_options(claims))
200            .map(|token| token.into_bytes())
201            .map_err(|_| SecurityError::InvalidToken)?;
202        Ok(wrap_keyring_token(key_id.as_deref(), token))
203    }
204
205    fn verify(&self, payload: &[u8], signature: &[u8], claims: &TokenClaims) -> SecurityResult<()> {
206        let (manager, signature) = self.validation_manager(signature)?;
207        let signature = std::str::from_utf8(&signature).map_err(|_| SecurityError::InvalidToken)?;
208        let verified = manager
209            .validate_token_bytes(signature, Self::validate_options(claims))
210            .map_err(|_| SecurityError::VerificationFailed)?;
211        if verified.payload == payload {
212            return Ok(());
213        }
214        Err(SecurityError::VerificationFailed)
215    }
216}
217
218fn wrap_keyring_token(key_id: Option<&str>, token: Vec<u8>) -> Vec<u8> {
219    let Some(key_id) = key_id else {
220        return token;
221    };
222    let mut wrapped =
223        Vec::with_capacity(KEYRING_TOKEN_PREFIX.len() + key_id.len() + token.len() + 1);
224    wrapped.extend_from_slice(KEYRING_TOKEN_PREFIX);
225    wrapped.extend_from_slice(key_id.as_bytes());
226    wrapped.push(b'.');
227    wrapped.extend_from_slice(&token);
228    wrapped
229}
230
231fn unwrap_keyring_token(token: &[u8]) -> SecurityResult<(&str, &[u8])> {
232    let remainder = token
233        .strip_prefix(KEYRING_TOKEN_PREFIX)
234        .ok_or(SecurityError::InvalidToken)?;
235    let separator = remainder
236        .iter()
237        .position(|byte| *byte == b'.')
238        .ok_or(SecurityError::InvalidToken)?;
239    let key_id =
240        std::str::from_utf8(&remainder[..separator]).map_err(|_| SecurityError::InvalidToken)?;
241    if key_id.is_empty()
242        || remainder
243            .get(separator + 1..)
244            .is_none_or(|inner| inner.is_empty())
245    {
246        return Err(SecurityError::InvalidToken);
247    }
248    Ok((key_id, &remainder[separator + 1..]))
249}
250
251fn unix_time_ms() -> u64 {
252    SystemTime::now()
253        .duration_since(UNIX_EPOCH)
254        .map(|duration| duration.as_millis() as u64)
255        .unwrap_or(0)
256}
257
258#[cfg(test)]
259#[path = "hashtoken_tests.rs"]
260mod tests;