Skip to main content

rns_crypto/
token.rs

1use alloc::vec::Vec;
2use core::fmt;
3
4use crate::aes128::Aes128;
5use crate::aes256::Aes256;
6use crate::hmac::hmac_sha256;
7use crate::pkcs7;
8use crate::Rng;
9
10pub const TOKEN_OVERHEAD: usize = 48; // 16 IV + 32 HMAC
11
12#[derive(Debug, PartialEq)]
13pub enum TokenError {
14    InvalidKeyLength,
15    InvalidToken,
16    HmacMismatch,
17    DecryptionFailed,
18}
19
20impl fmt::Display for TokenError {
21    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
22        match self {
23            TokenError::InvalidKeyLength => write!(f, "Token key must be 32 or 64 bytes"),
24            TokenError::InvalidToken => write!(f, "Token too short"),
25            TokenError::HmacMismatch => write!(f, "Token HMAC was invalid"),
26            TokenError::DecryptionFailed => write!(f, "Could not decrypt token"),
27        }
28    }
29}
30
31enum AesMode {
32    Aes128(Aes128),
33    Aes256(Aes256),
34}
35
36pub struct Token {
37    signing_key: Vec<u8>,
38    mode: AesMode,
39}
40
41impl Token {
42    pub fn new(key: &[u8]) -> Result<Self, TokenError> {
43        match key.len() {
44            32 => {
45                let signing_key = key[..16].to_vec();
46                let encryption_key: [u8; 16] = key[16..32].try_into().unwrap();
47                Ok(Token {
48                    signing_key,
49                    mode: AesMode::Aes128(Aes128::new(&encryption_key)),
50                })
51            }
52            64 => {
53                let signing_key = key[..32].to_vec();
54                let encryption_key: [u8; 32] = key[32..64].try_into().unwrap();
55                Ok(Token {
56                    signing_key,
57                    mode: AesMode::Aes256(Aes256::new(&encryption_key)),
58                })
59            }
60            _ => Err(TokenError::InvalidKeyLength),
61        }
62    }
63
64    pub fn encrypt(&self, plaintext: &[u8], rng: &mut dyn Rng) -> Vec<u8> {
65        let mut iv = [0u8; 16];
66        rng.fill_bytes(&mut iv);
67        self.encrypt_with_iv(plaintext, &iv)
68    }
69
70    pub fn encrypt_with_iv(&self, plaintext: &[u8], iv: &[u8; 16]) -> Vec<u8> {
71        let padded = pkcs7::pad(plaintext, 16);
72        let ciphertext = match &self.mode {
73            AesMode::Aes128(aes) => aes.encrypt_cbc(&padded, iv),
74            AesMode::Aes256(aes) => aes.encrypt_cbc(&padded, iv),
75        };
76
77        let mut signed_parts = Vec::with_capacity(16 + ciphertext.len());
78        signed_parts.extend_from_slice(iv);
79        signed_parts.extend_from_slice(&ciphertext);
80
81        let mac = hmac_sha256(&self.signing_key, &signed_parts);
82
83        let mut result = Vec::with_capacity(signed_parts.len() + 32);
84        result.extend_from_slice(&signed_parts);
85        result.extend_from_slice(&mac);
86        result
87    }
88
89    pub fn verify_hmac(&self, token: &[u8]) -> Result<bool, TokenError> {
90        if token.len() <= 32 {
91            return Err(TokenError::InvalidToken);
92        }
93        let received_hmac = &token[token.len() - 32..];
94        let expected_hmac = hmac_sha256(&self.signing_key, &token[..token.len() - 32]);
95        Ok(received_hmac == expected_hmac)
96    }
97
98    pub fn decrypt(&self, token: &[u8]) -> Result<Vec<u8>, TokenError> {
99        if token.len() <= TOKEN_OVERHEAD {
100            return Err(TokenError::InvalidToken);
101        }
102
103        if !self
104            .verify_hmac(token)
105            .map_err(|_| TokenError::InvalidToken)?
106        {
107            return Err(TokenError::HmacMismatch);
108        }
109
110        let iv: [u8; 16] = token[..16].try_into().unwrap();
111        let ciphertext = &token[16..token.len() - 32];
112
113        let decrypted = match &self.mode {
114            AesMode::Aes128(aes) => aes.decrypt_cbc(ciphertext, &iv),
115            AesMode::Aes256(aes) => aes.decrypt_cbc(ciphertext, &iv),
116        };
117
118        pkcs7::unpad(&decrypted, 16)
119            .map(|s| s.to_vec())
120            .map_err(|_| TokenError::DecryptionFailed)
121    }
122}
123
124#[cfg(test)]
125mod tests {
126    use super::*;
127    use crate::FixedRng;
128
129    #[test]
130    fn test_token_new_32byte_key() {
131        let key = [0u8; 32];
132        assert!(Token::new(&key).is_ok());
133    }
134
135    #[test]
136    fn test_token_new_64byte_key() {
137        let key = [0u8; 64];
138        assert!(Token::new(&key).is_ok());
139    }
140
141    #[test]
142    fn test_token_new_48byte_key() {
143        let key = [0u8; 48];
144        assert!(matches!(
145            Token::new(&key),
146            Err(TokenError::InvalidKeyLength)
147        ));
148    }
149
150    #[test]
151    fn test_token_roundtrip_32() {
152        let key = [0x42u8; 32];
153        let token = Token::new(&key).unwrap();
154        let mut rng = FixedRng::new(&[0xAA; 16]);
155        let plaintext = b"Hello, Reticulum!";
156        let encrypted = token.encrypt(plaintext, &mut rng);
157        let decrypted = token.decrypt(&encrypted).unwrap();
158        assert_eq!(decrypted, plaintext);
159    }
160
161    #[test]
162    fn test_token_roundtrip_64() {
163        let key = [0x42u8; 64];
164        let token = Token::new(&key).unwrap();
165        let mut rng = FixedRng::new(&[0xBB; 16]);
166        let plaintext = b"Hello, Reticulum!";
167        let encrypted = token.encrypt(plaintext, &mut rng);
168        let decrypted = token.decrypt(&encrypted).unwrap();
169        assert_eq!(decrypted, plaintext);
170    }
171
172    #[test]
173    fn test_token_hmac_reject_tampered() {
174        let key = [0x42u8; 64];
175        let token = Token::new(&key).unwrap();
176        let mut rng = FixedRng::new(&[0xCC; 16]);
177        let encrypted = token.encrypt(b"test", &mut rng);
178        let mut tampered = encrypted.clone();
179        tampered[20] ^= 0xFF; // flip a bit in ciphertext
180        assert!(token.decrypt(&tampered).is_err());
181    }
182
183    #[test]
184    fn test_token_decrypt_truncated() {
185        let key = [0x42u8; 64];
186        let token = Token::new(&key).unwrap();
187        assert!(matches!(
188            token.decrypt(&[0u8; 10]),
189            Err(TokenError::InvalidToken)
190        ));
191    }
192
193    #[test]
194    fn test_token_overhead() {
195        assert_eq!(TOKEN_OVERHEAD, 48);
196    }
197}