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