1use alloc::vec::Vec;
2use core::fmt;
3
4use crate::aes128::Aes128;
5use crate::aes256::Aes256;
6use aes::cipher::{array::Array, BlockModeDecrypt};
7
8use crate::hmac::HmacSha256;
9use crate::pkcs7;
10use crate::sha256::Sha256;
11use crate::Rng;
12
13pub const TOKEN_OVERHEAD: usize = 48; #[derive(Debug, PartialEq)]
16pub enum TokenError {
17 InvalidKeyLength,
18 InvalidToken,
19 HmacMismatch,
20 DecryptionFailed,
21}
22
23impl fmt::Display for TokenError {
24 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
25 match self {
26 TokenError::InvalidKeyLength => write!(f, "Token key must be 32 or 64 bytes"),
27 TokenError::InvalidToken => write!(f, "Token too short"),
28 TokenError::HmacMismatch => write!(f, "Token HMAC was invalid"),
29 TokenError::DecryptionFailed => write!(f, "Could not decrypt token"),
30 }
31 }
32}
33
34#[derive(Clone)]
35enum AesMode {
36 Aes128(Aes128),
37 Aes256(Aes256),
38}
39
40#[allow(clippy::large_enum_variant)]
43enum CbcDecryptor {
44 Aes128(cbc::Decryptor<aes::Aes128>),
45 Aes256(cbc::Decryptor<aes::Aes256>),
46}
47
48impl CbcDecryptor {
49 fn decrypt_blocks(&mut self, buf: &mut [u8]) {
51 let (blocks, rest) = Array::slice_as_chunks_mut(buf);
52 debug_assert!(rest.is_empty());
53 match self {
54 CbcDecryptor::Aes128(dec) => dec.decrypt_blocks(blocks),
55 CbcDecryptor::Aes256(dec) => dec.decrypt_blocks(blocks),
56 }
57 }
58}
59
60pub struct Token {
61 mac: HmacSha256,
64 mode: AesMode,
65}
66
67impl Token {
68 pub fn new(key: &[u8]) -> Result<Self, TokenError> {
69 match key.len() {
70 32 => {
71 let signing_key = &key[..16];
72 let encryption_key: [u8; 16] = key[16..32].try_into().unwrap();
73 Ok(Token {
74 mac: HmacSha256::new(signing_key),
75 mode: AesMode::Aes128(Aes128::new(&encryption_key)),
76 })
77 }
78 64 => {
79 let signing_key = &key[..32];
80 let encryption_key: [u8; 32] = key[32..64].try_into().unwrap();
81 Ok(Token {
82 mac: HmacSha256::new(signing_key),
83 mode: AesMode::Aes256(Aes256::new(&encryption_key)),
84 })
85 }
86 _ => Err(TokenError::InvalidKeyLength),
87 }
88 }
89
90 pub fn encrypt(&self, plaintext: &[u8], rng: &mut dyn Rng) -> Vec<u8> {
91 let mut iv = [0u8; 16];
92 rng.fill_bytes(&mut iv);
93 self.encrypt_with_iv(plaintext, &iv)
94 }
95
96 pub fn token_len(plaintext_len: usize) -> usize {
99 16 + plaintext_len + (pkcs7::BLOCK_SIZE - plaintext_len % pkcs7::BLOCK_SIZE) + 32
100 }
101
102 pub fn encrypt_into(&self, plaintext: &[u8], rng: &mut dyn Rng, out: &mut [u8]) {
105 let mut iv = [0u8; 16];
106 rng.fill_bytes(&mut iv);
107 self.encrypt_with_iv_into(plaintext, &iv, out)
108 }
109
110 pub fn encrypt_with_iv_into(&self, plaintext: &[u8], iv: &[u8; 16], out: &mut [u8]) {
113 assert_eq!(
114 out.len(),
115 Self::token_len(plaintext.len()),
116 "token buffer length"
117 );
118 let (body, tag) = out.split_at_mut(out.len() - 32);
119 let padding = body.len() - 16 - plaintext.len();
120 body[..16].copy_from_slice(iv);
121 body[16..16 + plaintext.len()].copy_from_slice(plaintext);
122 body[16 + plaintext.len()..].fill(padding as u8);
123 match &self.mode {
124 AesMode::Aes128(aes) => aes.encrypt_cbc_in_place(&mut body[16..], iv),
125 AesMode::Aes256(aes) => aes.encrypt_cbc_in_place(&mut body[16..], iv),
126 }
127 .expect("PKCS7 padding always produces block-aligned plaintext");
128 tag.copy_from_slice(&self.tag(body));
129 }
130
131 pub fn encrypt_with_iv(&self, plaintext: &[u8], iv: &[u8; 16]) -> Vec<u8> {
132 let padding = pkcs7::BLOCK_SIZE - plaintext.len() % pkcs7::BLOCK_SIZE;
135 let mut token = Vec::with_capacity(16 + plaintext.len() + padding + 32);
136 token.extend_from_slice(iv);
137 token.extend_from_slice(plaintext);
138 token.resize(token.len() + padding, padding as u8);
139 match &self.mode {
140 AesMode::Aes128(aes) => aes.encrypt_cbc_in_place(&mut token[16..], iv),
141 AesMode::Aes256(aes) => aes.encrypt_cbc_in_place(&mut token[16..], iv),
142 }
143 .expect("PKCS7 padding always produces block-aligned plaintext");
144
145 let mac = self.tag(&token);
146 token.extend_from_slice(&mac);
147 token
148 }
149
150 pub fn encrypt_untagged(&self, plaintext: &[u8], rng: &mut dyn Rng) -> (Vec<u8>, HmacSha256) {
155 let mut iv = [0u8; 16];
156 rng.fill_bytes(&mut iv);
157 let mut token = alloc::vec![0u8; Self::token_len(plaintext.len())];
158 let signed = token.len() - 32;
159 let body = &mut token[..signed];
160 let padding = body.len() - 16 - plaintext.len();
161 body[..16].copy_from_slice(&iv);
162 body[16..16 + plaintext.len()].copy_from_slice(plaintext);
163 body[16 + plaintext.len()..].fill(padding as u8);
164 match &self.mode {
165 AesMode::Aes128(aes) => aes.encrypt_cbc_in_place(&mut body[16..], &iv),
166 AesMode::Aes256(aes) => aes.encrypt_cbc_in_place(&mut body[16..], &iv),
167 }
168 .expect("PKCS7 padding always produces block-aligned plaintext");
169 (token, self.mac.clone())
170 }
171
172 fn tag(&self, data: &[u8]) -> [u8; 32] {
174 let mut mac = self.mac.clone();
175 mac.update(data);
176 mac.finalize()
177 }
178
179 pub fn verify_hmac(&self, token: &[u8]) -> Result<bool, TokenError> {
180 if token.len() <= 32 {
181 return Err(TokenError::InvalidToken);
182 }
183 let received_hmac = &token[token.len() - 32..];
184 let mut mac = self.mac.clone();
185 mac.update(&token[..token.len() - 32]);
186 Ok(mac.verify(received_hmac))
187 }
188
189 pub fn decrypt_into(&self, token: &[u8], out: &mut Vec<u8>) -> Result<(), TokenError> {
192 out.clear();
193 if token.len() <= TOKEN_OVERHEAD {
194 return Err(TokenError::InvalidToken);
195 }
196 if !self
197 .verify_hmac(token)
198 .map_err(|_| TokenError::InvalidToken)?
199 {
200 return Err(TokenError::HmacMismatch);
201 }
202 let iv: [u8; 16] = token[..16].try_into().unwrap();
203 let ciphertext = &token[16..token.len() - 32];
204 if ciphertext.is_empty() || !ciphertext.len().is_multiple_of(16) {
205 return Err(TokenError::DecryptionFailed);
206 }
207 out.extend_from_slice(ciphertext);
208 let decrypted = match &self.mode {
209 AesMode::Aes128(aes) => aes.decrypt_cbc_in_place(out, &iv),
210 AesMode::Aes256(aes) => aes.decrypt_cbc_in_place(out, &iv),
211 };
212 let unpadded_len = decrypted
213 .ok()
214 .and_then(|()| pkcs7::unpad(out, 16).ok().map(<[u8]>::len));
215 match unpadded_len {
216 Some(len) => {
217 out.truncate(len);
218 Ok(())
219 }
220 None => {
221 out.clear();
222 Err(TokenError::DecryptionFailed)
223 }
224 }
225 }
226
227 pub fn decrypt(&self, token: &[u8]) -> Result<Vec<u8>, TokenError> {
228 if token.len() <= TOKEN_OVERHEAD {
229 return Err(TokenError::InvalidToken);
230 }
231
232 if !self
233 .verify_hmac(token)
234 .map_err(|_| TokenError::InvalidToken)?
235 {
236 return Err(TokenError::HmacMismatch);
237 }
238
239 let iv: [u8; 16] = token[..16].try_into().unwrap();
240 let ciphertext = &token[16..token.len() - 32];
241
242 if ciphertext.is_empty() || !ciphertext.len().is_multiple_of(16) {
244 return Err(TokenError::DecryptionFailed);
245 }
246
247 let mut decrypted = match &self.mode {
248 AesMode::Aes128(aes) => aes.decrypt_cbc(ciphertext, &iv),
249 AesMode::Aes256(aes) => aes.decrypt_cbc(ciphertext, &iv),
250 }
251 .map_err(|_| TokenError::DecryptionFailed)?;
252
253 let unpadded_len = pkcs7::unpad(&decrypted, 16)
254 .map_err(|_| TokenError::DecryptionFailed)?
255 .len();
256 decrypted.truncate(unpadded_len);
257 Ok(decrypted)
258 }
259
260 pub fn decrypt_chunks(&self, chunks: &[&[u8]], skip: usize) -> Result<Vec<u8>, TokenError> {
268 let total: usize = chunks.iter().map(|chunk| chunk.len()).sum();
269 let mut stream = self.decrypt_stream(skip);
270 stream.reserve(total.saturating_sub(TOKEN_OVERHEAD));
271 for chunk in chunks {
272 stream.update(chunk);
273 }
274 stream.finish()
275 }
276
277 pub fn decrypt_stream(&self, skip: usize) -> TokenDecryptStream {
280 assert!(skip <= 16, "decrypt_chunks skips at most one block");
281 TokenDecryptStream {
282 mac: self.mac.clone(),
283 mode: self.mode.clone(),
284 decryptor: None,
285 skip,
286 signed: 0,
287 iv: [0; 16],
288 tail: [0; 32],
289 tail_len: 0,
290 block: [0; 16],
291 block_len: 0,
292 first_block: true,
293 last_byte: 0,
294 plaintext: Vec::new(),
295 plain_hash: None,
296 }
297 }
298}
299
300pub struct TokenDecryptStream {
307 mac: HmacSha256,
308 mode: AesMode,
309 decryptor: Option<CbcDecryptor>,
310 skip: usize,
311 signed: usize,
313 iv: [u8; 16],
314 tail: [u8; 32],
316 tail_len: usize,
317 block: [u8; 16],
320 block_len: usize,
321 first_block: bool,
322 last_byte: u8,
323 plaintext: Vec<u8>,
324 plain_hash: Option<(Sha256, usize)>,
326}
327
328impl TokenDecryptStream {
329 pub fn hash_plaintext(&mut self) {
332 debug_assert!(self.signed == 0 && self.tail_len == 0);
333 self.plain_hash = Some((Sha256::new(), 0));
334 }
335
336 pub fn reserve(&mut self, additional: usize) {
338 self.plaintext.reserve(additional);
339 }
340
341 pub fn plaintext(&self) -> &[u8] {
345 &self.plaintext
346 }
347
348 pub fn update(&mut self, data: &[u8]) {
350 let total = self.tail_len + data.len();
351 if total <= 32 {
352 self.tail[self.tail_len..total].copy_from_slice(data);
353 self.tail_len = total;
354 return;
355 }
356 let release = total - 32;
358 let from_tail = release.min(self.tail_len);
359 if from_tail > 0 {
360 let tail = self.tail;
361 self.absorb_signed(&tail[..from_tail]);
362 }
363 let from_data = release - from_tail;
364 self.absorb_signed(&data[..from_data]);
365 let kept = self.tail_len - from_tail;
366 self.tail.copy_within(from_tail..self.tail_len, 0);
367 self.tail[kept..].copy_from_slice(&data[from_data..]);
368 self.tail_len = 32;
369 }
370
371 fn absorb_signed(&mut self, data: &[u8]) {
372 if data.is_empty() {
373 return;
374 }
375 self.decrypt_signed(data);
376 match self.plain_hash.as_mut() {
377 Some((hash, hashed)) => {
378 let settled = self.plaintext.len().saturating_sub(16).max(*hashed);
380 self.mac
381 .update_paired(data, hash, &self.plaintext[*hashed..settled]);
382 *hashed = settled;
383 }
384 None => self.mac.update(data),
385 }
386 }
387
388 fn decrypt_signed(&mut self, mut data: &[u8]) {
389 if self.signed < 16 {
390 let n = (16 - self.signed).min(data.len());
391 self.iv[self.signed..self.signed + n].copy_from_slice(&data[..n]);
392 self.signed += n;
393 data = &data[n..];
394 if data.is_empty() {
395 return;
396 }
397 }
398 self.signed += data.len();
399 let (mode, iv) = (&self.mode, &self.iv);
400 let decryptor = self.decryptor.get_or_insert_with(|| match mode {
401 AesMode::Aes128(aes) => CbcDecryptor::Aes128(aes.cbc_decryptor(iv)),
402 AesMode::Aes256(aes) => CbcDecryptor::Aes256(aes.cbc_decryptor(iv)),
403 });
404 while !data.is_empty() && (self.block_len > 0 || self.first_block) {
405 let n = (16 - self.block_len).min(data.len());
406 self.block[self.block_len..self.block_len + n].copy_from_slice(&data[..n]);
407 self.block_len += n;
408 data = &data[n..];
409 if self.block_len == 16 {
410 decryptor.decrypt_blocks(&mut self.block);
411 let from = if self.first_block { self.skip } else { 0 };
412 self.plaintext.extend_from_slice(&self.block[from..]);
413 self.last_byte = self.block[15];
414 self.first_block = false;
415 self.block_len = 0;
416 }
417 }
418 if !data.is_empty() {
419 let whole = data.len() - data.len() % 16;
420 if whole > 0 {
421 let at = self.plaintext.len();
422 self.plaintext.extend_from_slice(&data[..whole]);
423 decryptor.decrypt_blocks(&mut self.plaintext[at..]);
424 self.last_byte = self.plaintext[self.plaintext.len() - 1];
425 }
426 let rest = &data[whole..];
427 self.block[..rest.len()].copy_from_slice(rest);
428 self.block_len = rest.len();
429 }
430 }
431
432 pub fn finish_hashed(mut self) -> Result<(Vec<u8>, Option<Sha256>), TokenError> {
435 let plain_hash = self.plain_hash.take();
436 let plaintext = self.finish()?;
437 Ok(match plain_hash {
438 Some((mut hash, hashed)) => {
439 hash.update(&plaintext[hashed..]);
440 (plaintext, Some(hash))
441 }
442 None => (plaintext, None),
443 })
444 }
445
446 pub fn finish(self) -> Result<Vec<u8>, TokenError> {
449 if self.signed + self.tail_len <= TOKEN_OVERHEAD {
450 return Err(TokenError::InvalidToken);
451 }
452 if !self.mac.verify(&self.tail) {
453 return Err(TokenError::HmacMismatch);
454 }
455 if self.first_block || self.block_len != 0 {
458 return Err(TokenError::DecryptionFailed);
459 }
460 let mut plaintext = self.plaintext;
462 let padding = usize::from(self.last_byte);
463 if padding > 16 || padding > plaintext.len() {
464 return Err(TokenError::DecryptionFailed);
465 }
466 plaintext.truncate(plaintext.len() - padding);
467 Ok(plaintext)
468 }
469}
470
471#[cfg(test)]
472mod tests {
473 use super::*;
474 use crate::hmac::hmac_sha256;
475
476 #[test]
477 fn decrypting_into_a_reused_buffer_matches_decrypt() {
478 for key in [[7u8; 32].as_slice(), [9u8; 64].as_slice()] {
479 let token = Token::new(key).unwrap();
480 let other = Token::new(&[1u8; 32]).unwrap();
481 let mut out = vec![0x55; 3];
482 for len in [0usize, 1, 15, 16, 17, 100, 384] {
483 let plaintext: Vec<u8> = (0..len).map(|i| i as u8).collect();
484 let good = token.encrypt_with_iv(&plaintext, &[len as u8; 16]);
485 let mut tampered = good.clone();
486 tampered[20] ^= 1;
487 let foreign = other.encrypt_with_iv(&plaintext, &[0; 16]);
488 for input in [&good[..], &tampered[..], &foreign[..], &good[..40], &[][..]] {
489 let expected = token.decrypt(input);
490 let got = token.decrypt_into(input, &mut out);
491 assert_eq!(
492 format!("{:?}", got.map(|()| out.clone())),
493 format!("{expected:?}")
494 );
495 if expected.is_err() {
496 assert!(out.is_empty());
497 }
498 }
499 }
500 }
501 }
502
503 #[test]
504 fn encrypting_in_place_matches_encrypting_into_a_new_buffer() {
505 for key in [[7u8; 32].as_slice(), [9u8; 64].as_slice()] {
506 let token = Token::new(key).unwrap();
507 for len in [0usize, 1, 15, 16, 17, 31, 32, 33, 100, 383, 384] {
508 let plaintext: Vec<u8> = (0..len).map(|i| i as u8).collect();
509 let iv = [len as u8; 16];
510 let mut out = vec![0xaa; Token::token_len(len)];
511 token.encrypt_with_iv_into(&plaintext, &iv, &mut out);
512 assert_eq!(out, token.encrypt_with_iv(&plaintext, &iv));
513 assert_eq!(token.decrypt(&out).unwrap(), plaintext);
514 }
515 }
516 }
517 use crate::FixedRng;
518
519 #[test]
520 fn test_token_new_32byte_key() {
521 let key = [0u8; 32];
522 assert!(Token::new(&key).is_ok());
523 }
524
525 #[test]
526 fn test_token_new_64byte_key() {
527 let key = [0u8; 64];
528 assert!(Token::new(&key).is_ok());
529 }
530
531 #[test]
532 fn test_token_new_48byte_key() {
533 let key = [0u8; 48];
534 assert!(matches!(
535 Token::new(&key),
536 Err(TokenError::InvalidKeyLength)
537 ));
538 }
539
540 #[test]
541 fn test_token_roundtrip_32() {
542 let key = [0x42u8; 32];
543 let token = Token::new(&key).unwrap();
544 let mut rng = FixedRng::new(&[0xAA; 16]);
545 let plaintext = b"Hello, Reticulum!";
546 let encrypted = token.encrypt(plaintext, &mut rng);
547 let decrypted = token.decrypt(&encrypted).unwrap();
548 assert_eq!(decrypted, plaintext);
549 }
550
551 #[test]
552 fn test_token_roundtrip_64() {
553 let key = [0x42u8; 64];
554 let token = Token::new(&key).unwrap();
555 let mut rng = FixedRng::new(&[0xBB; 16]);
556 let plaintext = b"Hello, Reticulum!";
557 let encrypted = token.encrypt(plaintext, &mut rng);
558 let decrypted = token.decrypt(&encrypted).unwrap();
559 assert_eq!(decrypted, plaintext);
560 }
561
562 #[test]
563 fn test_token_hmac_reject_tampered() {
564 let key = [0x42u8; 64];
565 let token = Token::new(&key).unwrap();
566 let mut rng = FixedRng::new(&[0xCC; 16]);
567 let encrypted = token.encrypt(b"test", &mut rng);
568 let mut tampered = encrypted.clone();
569 tampered[20] ^= 0xFF; assert!(token.decrypt(&tampered).is_err());
571 }
572
573 #[test]
574 fn token_hmac_rejects_mismatches_across_the_entire_tag() {
575 let key = [0x42u8; 64];
576 let token = Token::new(&key).unwrap();
577 let mut rng = FixedRng::new(&[0xCC; 16]);
578 let encrypted = token.encrypt(b"timing regression", &mut rng);
579
580 assert_eq!(token.verify_hmac(&encrypted), Ok(true));
581 for tag_index in 0..32 {
582 let mut corrupted = encrypted.clone();
583 let index = corrupted.len() - 32 + tag_index;
584 corrupted[index] ^= 0x01;
585 assert_eq!(
586 token.verify_hmac(&corrupted),
587 Ok(false),
588 "tag mismatch at byte {tag_index} must fail"
589 );
590 }
591 }
592
593 #[test]
594 fn test_token_decrypt_truncated() {
595 let key = [0x42u8; 64];
596 let token = Token::new(&key).unwrap();
597 assert!(matches!(
598 token.decrypt(&[0u8; 10]),
599 Err(TokenError::InvalidToken)
600 ));
601 }
602
603 fn sign_token_parts(key: &[u8], iv_and_ciphertext: &[u8]) -> Vec<u8> {
604 let signing_key = &key[..key.len() / 2];
605 let mut token = iv_and_ciphertext.to_vec();
606 token.extend_from_slice(&hmac_sha256(signing_key, iv_and_ciphertext));
607 token
608 }
609
610 #[test]
611 fn test_token_decrypt_rejects_misaligned_ciphertext_with_valid_hmac() {
612 for key in [&[0x42u8; 32][..], &[0x42u8; 64][..]] {
613 let token = Token::new(key).unwrap();
614 for ciphertext_len in [1usize, 15, 17, 31, 33] {
615 let mut parts = alloc::vec![0xAAu8; 16];
616 parts.extend(core::iter::repeat_n(0x55u8, ciphertext_len));
617 let signed = sign_token_parts(key, &parts);
618 assert_eq!(token.verify_hmac(&signed), Ok(true));
619 assert_eq!(
620 token.decrypt(&signed),
621 Err(TokenError::DecryptionFailed),
622 "ciphertext length {ciphertext_len} must be rejected"
623 );
624 }
625 }
626 }
627
628 #[test]
629 fn test_token_well_formed_roundtrip_after_alignment_check() {
630 for key in [&[0x24u8; 32][..], &[0x24u8; 64][..]] {
631 let token = Token::new(key).unwrap();
632 for plaintext_len in [0usize, 1, 15, 16, 17, 32, 100] {
633 let plaintext = alloc::vec![0x5Au8; plaintext_len];
634 let encrypted = token.encrypt_with_iv(&plaintext, &[0x11; 16]);
635 assert_eq!((encrypted.len() - TOKEN_OVERHEAD) % 16, 0);
636 assert_eq!(token.decrypt(&encrypted).unwrap(), plaintext);
637 }
638 }
639 }
640
641 #[test]
642 fn test_token_overhead() {
643 assert_eq!(TOKEN_OVERHEAD, 48);
644 }
645
646 fn split_at_offsets<'a>(bytes: &'a [u8], offsets: &[usize]) -> Vec<&'a [u8]> {
648 let mut chunks = Vec::new();
649 let mut from = 0;
650 for &offset in offsets {
651 let to = offset.clamp(from, bytes.len());
652 chunks.push(&bytes[from..to]);
653 from = to;
654 }
655 chunks.push(&bytes[from..]);
656 chunks
657 }
658
659 fn chunked_as_joined(token: &Token, bytes: &[u8], chunks: &[&[u8]], skip: usize) {
660 let joined = token.decrypt(bytes);
661 let expected = match joined {
662 Ok(plaintext) if plaintext.len() >= skip => Ok(plaintext[skip..].to_vec()),
663 Ok(_) => Err(TokenError::DecryptionFailed),
664 Err(error) => Err(error),
665 };
666 assert_eq!(
667 token.decrypt_chunks(chunks, skip),
668 expected,
669 "{chunks:?} skip {skip}"
670 );
671 }
672
673 #[test]
674 fn chunked_decryption_matches_joined_decryption() {
675 let mut rng = FixedRng::new(&[0x5D; 64]);
676 for key in [[0x21u8; 32].to_vec(), [0x42u8; 64].to_vec()] {
677 let token = Token::new(&key).unwrap();
678 for len in [0usize, 1, 3, 4, 5, 12, 15, 16, 17, 33, 100, 1000] {
679 let plaintext: Vec<u8> = (0..len).map(|i| (i * 7) as u8).collect();
680 let bytes = token.encrypt(&plaintext, &mut rng);
681 let n = bytes.len();
682 let splits: Vec<Vec<usize>> = alloc::vec![
683 alloc::vec![],
684 alloc::vec![1],
685 alloc::vec![8, 16, 17],
686 alloc::vec![16, 20, 32, 47],
687 alloc::vec![n - 33, n - 32, n - 31],
688 alloc::vec![n - 40, n - 10],
689 (1..n).collect(),
690 (0..n).step_by(13).collect(),
691 alloc::vec![0, 0, 5, 5, n],
692 ];
693 for offsets in &splits {
694 let chunks = split_at_offsets(&bytes, offsets);
695 for skip in [0, 1, 4, 16] {
696 chunked_as_joined(&token, &bytes, &chunks, skip);
697 }
698 }
699 for at in 0..n {
701 let mut tampered = bytes.clone();
702 tampered[at] ^= 0x01;
703 let chunks = split_at_offsets(&tampered, &[at / 2, at, at + 1]);
704 assert_eq!(
705 token.decrypt_chunks(&chunks, 4),
706 Err(TokenError::HmacMismatch)
707 );
708 }
709 }
710 for ciphertext_len in [1usize, 15, 17, 31, 33] {
712 let mut parts = alloc::vec![0xAAu8; 16];
713 parts.extend(core::iter::repeat_n(0x55u8, ciphertext_len));
714 let signed = sign_token_parts(&key, &parts);
715 let chunks = split_at_offsets(&signed, &[7, 20, 40]);
716 chunked_as_joined(&token, &signed, &chunks, 4);
717 }
718 for fill in 0u8..64 {
720 for ciphertext_len in [16usize, 32] {
721 let parts: Vec<u8> = (0..16 + ciphertext_len)
722 .map(|i| (i as u8).wrapping_mul(fill).wrapping_add(fill))
723 .collect();
724 let signed = sign_token_parts(&key, &parts);
725 for offsets in [alloc::vec![], alloc::vec![3, 19, 30]] {
726 let chunks = split_at_offsets(&signed, &offsets);
727 for skip in [0, 4, 16] {
728 chunked_as_joined(&token, &signed, &chunks, skip);
729 }
730 }
731 }
732 }
733 for len in [0usize, 1, 48] {
734 let short = alloc::vec![0u8; len];
735 assert_eq!(
736 token.decrypt_chunks(&split_at_offsets(&short, &[1]), 0),
737 Err(TokenError::InvalidToken)
738 );
739 }
740 }
741 }
742
743 #[test]
744 fn untagged_encryption_with_its_mac_is_the_token() {
745 for key in [[0x21u8; 32].to_vec(), [0x42u8; 64].to_vec()] {
746 let token = Token::new(&key).unwrap();
747 for len in [0usize, 1, 15, 16, 100, 5000] {
748 let plaintext: Vec<u8> = (0..len).map(|i| (i * 5) as u8).collect();
749 let expected = token.encrypt(&plaintext, &mut FixedRng::new(&[0x77; 16]));
750 let (mut bytes, mut mac) =
751 token.encrypt_untagged(&plaintext, &mut FixedRng::new(&[0x77; 16]));
752 let signed = bytes.len() - 32;
753 assert!(bytes[signed..].iter().all(|&b| b == 0));
754 mac.update(&bytes[..signed]);
755 bytes[signed..].copy_from_slice(&mac.finalize());
756 assert_eq!(bytes, expected);
757 }
758 }
759 }
760
761 #[test]
762 fn streamed_plaintext_is_final_except_its_last_block() {
763 let mut rng = FixedRng::new(&[0x3C; 64]);
764 let token = Token::new(&[0x42u8; 64]).unwrap();
765 for len in [4usize, 15, 16, 17, 100, 1000, 5000] {
766 let plaintext: Vec<u8> = (0..len).map(|i| (i * 11) as u8).collect();
767 let bytes = token.encrypt(&plaintext, &mut rng);
768 for step in [1usize, 7, 16, 64, 500] {
769 let mut stream = token.decrypt_stream(4);
770 let mut partials = Vec::new();
771 for chunk in bytes.chunks(step) {
772 stream.update(chunk);
773 partials.push(stream.plaintext().to_vec());
774 }
775 let result = stream.finish().unwrap();
776 assert_eq!(result, plaintext[4..]);
777 let mut hashed = token.decrypt_stream(4);
778 hashed.hash_plaintext();
779 for chunk in bytes.chunks(step) {
780 hashed.update(chunk);
781 }
782 let (again, hash) = hashed.finish_hashed().unwrap();
783 assert_eq!(again, result);
784 assert_eq!(hash.unwrap().digest(), crate::sha256::sha256(&result));
785 for partial in partials {
786 let settled = partial.len().saturating_sub(16);
787 assert_eq!(partial[..settled], result[..settled]);
788 }
789 }
790 }
791 }
792
793 #[test]
794 fn single_buffer_encryption_matches_pad_encrypt_sign() {
795 for key in [[0x21u8; 32].to_vec(), [0x42u8; 64].to_vec()] {
796 let token = Token::new(&key).unwrap();
797 let iv = [0x17u8; 16];
798 for len in [0usize, 1, 15, 16, 17, 31, 32, 1000] {
799 let plaintext: Vec<u8> = (0..len).map(|i| i as u8).collect();
800 let padded = pkcs7::pad(&plaintext, 16);
801 let ciphertext = match &token.mode {
802 AesMode::Aes128(aes) => aes.encrypt_cbc(&padded, &iv),
803 AesMode::Aes256(aes) => aes.encrypt_cbc(&padded, &iv),
804 }
805 .unwrap();
806 let mut expected = iv.to_vec();
807 expected.extend_from_slice(&ciphertext);
808 let mac = hmac_sha256(&key[..key.len() / 2], &expected);
810 expected.extend_from_slice(&mac);
811 let encrypted = token.encrypt_with_iv(&plaintext, &iv);
812 assert_eq!(encrypted, expected, "key {} len {len}", key.len());
813 assert_eq!(encrypted.capacity(), encrypted.len());
814 assert_eq!(token.decrypt(&encrypted).unwrap(), plaintext);
815 }
816 }
817 }
818}