1use std::collections::HashMap;
16use std::path::Path;
17
18use salsa20::Salsa20;
19use salsa20::cipher::{KeyIvInit, StreamCipher};
20
21use crate::error::{CascError, Result};
22
23pub struct TactKeyStore {
29 keys: HashMap<u64, [u8; 16]>,
30}
31
32impl Default for TactKeyStore {
33 fn default() -> Self {
34 Self::new()
35 }
36}
37
38impl TactKeyStore {
39 pub fn new() -> Self {
41 Self {
42 keys: HashMap::new(),
43 }
44 }
45
46 pub fn with_known_keys() -> Self {
48 let mut keys = HashMap::new();
49 for (name, value) in known_keys() {
50 keys.insert(name, value);
51 }
52 Self { keys }
53 }
54
55 pub fn load_keyfile(path: &Path) -> Result<Self> {
60 let content = std::fs::read_to_string(path)?;
61 let mut keys = HashMap::new();
62
63 for line in content.lines() {
64 let trimmed = line.trim();
65 if trimmed.is_empty() || trimmed.starts_with('#') {
66 continue;
67 }
68
69 let parts: Vec<&str> = trimmed.split_whitespace().collect();
70 if parts.len() < 2 {
71 return Err(CascError::InvalidFormat(format!(
72 "invalid keyfile line: {}",
73 trimmed
74 )));
75 }
76
77 let key_name = u64::from_str_radix(parts[0], 16).map_err(|e| {
78 CascError::InvalidFormat(format!("invalid key name '{}': {}", parts[0], e))
79 })?;
80
81 let key_value = hex_to_key_result(parts[1])?;
82 keys.insert(key_name, key_value);
83 }
84
85 Ok(Self { keys })
86 }
87
88 pub fn merge(&mut self, other: &TactKeyStore) {
91 for (&name, &value) in &other.keys {
92 self.keys.insert(name, value);
93 }
94 }
95
96 pub fn get(&self, key_name: u64) -> Option<&[u8; 16]> {
98 self.keys.get(&key_name)
99 }
100
101 pub fn len(&self) -> usize {
103 self.keys.len()
104 }
105
106 pub fn is_empty(&self) -> bool {
108 self.keys.is_empty()
109 }
110}
111
112#[derive(Debug, PartialEq)]
118pub enum EncryptionAlgorithm {
119 Salsa20,
121 ARC4,
123}
124
125#[derive(Debug)]
127pub struct EncryptionHeader {
128 pub key_name: u64,
130 pub iv: Vec<u8>,
132 pub algorithm: EncryptionAlgorithm,
134}
135
136pub fn parse_encryption_header(data: &[u8]) -> Result<(EncryptionHeader, &[u8])> {
144 if data.is_empty() {
145 return Err(CascError::InvalidFormat(
146 "encryption header: empty data".into(),
147 ));
148 }
149
150 let mut pos = 0;
151
152 let key_count = data[pos] as usize;
154 pos += 1;
155
156 if key_count == 0 {
157 return Err(CascError::InvalidFormat(
158 "encryption header: key_count is 0".into(),
159 ));
160 }
161
162 let mut key_name: u64 = 0;
164 for i in 0..key_count {
165 if pos >= data.len() {
166 return Err(CascError::InvalidFormat(
167 "encryption header: truncated key_name_size".into(),
168 ));
169 }
170 let key_name_size = data[pos] as usize;
171 pos += 1;
172
173 if pos + key_name_size > data.len() {
174 return Err(CascError::InvalidFormat(
175 "encryption header: truncated key_name".into(),
176 ));
177 }
178
179 if i == 0 {
180 if key_name_size != 8 {
181 return Err(CascError::InvalidFormat(format!(
182 "encryption header: expected key_name_size 8, got {}",
183 key_name_size
184 )));
185 }
186 key_name = u64::from_le_bytes(data[pos..pos + 8].try_into().map_err(|_| {
187 CascError::InvalidFormat("encryption header: failed to read key_name".into())
188 })?);
189 }
190 pos += key_name_size;
191 }
192
193 if pos + 4 > data.len() {
195 return Err(CascError::InvalidFormat(
196 "encryption header: truncated IV size".into(),
197 ));
198 }
199 let iv_size = u32::from_le_bytes(data[pos..pos + 4].try_into().map_err(|_| {
200 CascError::InvalidFormat("encryption header: failed to read IV size".into())
201 })?) as usize;
202 pos += 4;
203
204 if pos + iv_size > data.len() {
206 return Err(CascError::InvalidFormat(
207 "encryption header: truncated IV".into(),
208 ));
209 }
210 let iv = data[pos..pos + iv_size].to_vec();
211 pos += iv_size;
212
213 if pos >= data.len() {
215 return Err(CascError::InvalidFormat(
216 "encryption header: missing encryption type".into(),
217 ));
218 }
219 let algorithm = match data[pos] {
220 b'S' => EncryptionAlgorithm::Salsa20,
221 b'A' => EncryptionAlgorithm::ARC4,
222 other => {
223 return Err(CascError::InvalidFormat(format!(
224 "encryption header: unknown algorithm 0x{:02X}",
225 other
226 )));
227 }
228 };
229 pos += 1;
230
231 Ok((
232 EncryptionHeader {
233 key_name,
234 iv,
235 algorithm,
236 },
237 &data[pos..],
238 ))
239}
240
241struct Arc4 {
246 s: [u8; 256],
247 i: u8,
248 j: u8,
249}
250
251impl Arc4 {
252 fn new(key: &[u8]) -> Self {
253 let mut s = [0u8; 256];
254 for (i, slot) in s.iter_mut().enumerate() {
255 *slot = i as u8;
256 }
257 let mut j: u8 = 0;
259 #[allow(clippy::needless_range_loop)]
260 for i in 0..256 {
261 j = j.wrapping_add(s[i]).wrapping_add(key[i % key.len()]);
262 s.swap(i, j as usize);
263 }
264 let mut arc4 = Arc4 { s, i: 0, j: 0 };
266 let mut discard = [0u8; 1024];
267 arc4.process(&mut discard);
268 arc4
269 }
270
271 fn process(&mut self, data: &mut [u8]) {
272 for byte in data.iter_mut() {
273 self.i = self.i.wrapping_add(1);
274 self.j = self.j.wrapping_add(self.s[self.i as usize]);
275 self.s.swap(self.i as usize, self.j as usize);
276 let k =
277 self.s[(self.s[self.i as usize].wrapping_add(self.s[self.j as usize])) as usize];
278 *byte ^= k;
279 }
280 }
281}
282
283fn decrypt_salsa20(key: &[u8; 16], iv: &[u8], data: &mut [u8]) {
288 let mut full_key = [0u8; 32];
290 full_key[..16].copy_from_slice(key);
291 full_key[16..].copy_from_slice(key);
292
293 let mut nonce = [0u8; 8];
295 let copy_len = iv.len().min(8);
296 nonce[..copy_len].copy_from_slice(&iv[..copy_len]);
297
298 let mut cipher = Salsa20::new(&full_key.into(), &nonce.into());
299 cipher.apply_keystream(data);
300}
301
302pub fn decrypt_block(data: &[u8], keystore: &TactKeyStore) -> Result<Vec<u8>> {
311 let (header, encrypted) = parse_encryption_header(data)?;
312
313 let key = keystore
314 .get(header.key_name)
315 .ok_or_else(|| CascError::EncryptionKeyMissing(format!("0x{:016X}", header.key_name)))?;
316
317 let mut output = encrypted.to_vec();
318 match header.algorithm {
319 EncryptionAlgorithm::Salsa20 => {
320 decrypt_salsa20(key, &header.iv, &mut output);
321 }
322 EncryptionAlgorithm::ARC4 => {
323 let mut cipher = Arc4::new(key);
324 cipher.process(&mut output);
325 }
326 }
327
328 Ok(output)
329}
330
331fn hex_to_key(hex_str: &str) -> [u8; 16] {
337 let bytes = hex::decode(hex_str).expect("invalid hex in known key");
338 let mut key = [0u8; 16];
339 key.copy_from_slice(&bytes);
340 key
341}
342
343fn hex_to_key_result(hex_str: &str) -> Result<[u8; 16]> {
345 let bytes = hex::decode(hex_str).map_err(|e| {
346 CascError::InvalidFormat(format!("invalid hex key value '{}': {}", hex_str, e))
347 })?;
348 if bytes.len() != 16 {
349 return Err(CascError::InvalidFormat(format!(
350 "key value must be 16 bytes, got {}",
351 bytes.len()
352 )));
353 }
354 let mut key = [0u8; 16];
355 key.copy_from_slice(&bytes);
356 Ok(key)
357}
358
359fn known_keys() -> Vec<(u64, [u8; 16])> {
360 vec![
361 (
362 0xFA505078126ACB3E,
363 hex_to_key("BDC51862ABED79B2DE48C8E7E66C6200"),
364 ),
365 (
366 0xFF813F7D062AC0BC,
367 hex_to_key("AA0B5C77F088CCC2D39049BD267F066D"),
368 ),
369 (
370 0xD1E9B5EDF9283668,
371 hex_to_key("8E4A2579894E38B4AB9058BA5C7328EE"),
372 ),
373 (
374 0xB76729641141CB34,
375 hex_to_key("9849D1AA7B1FD09819C5C66283A326EC"),
376 ),
377 (
378 0xFFB9469FF16E6BF8,
379 hex_to_key("D514BD1909A9E5DC8703F4B8BB1DFD9A"),
380 ),
381 (
382 0x23C5B5DF837A226C,
383 hex_to_key("1406E2D873B6FC99217A180881DA8D62"),
384 ),
385 (
386 0x3AE403EF40AC3037,
387 hex_to_key("EB31B554C67D603E2F10AA8C4584F1CE"),
388 ),
389 (
390 0xE2854509C471C381,
391 hex_to_key("A970FEF382CE86A53A1674C8F36C8F1B"),
392 ),
393 (
394 0x8EE2CB82178C995A,
395 hex_to_key("5FA43C8E204D2F1BFAF1FB26FFE5A34B"),
396 ),
397 (
398 0x5813810F4EC9B005,
399 hex_to_key("7F3DDA67B4A94DE6D3F3B8D4E45FC076"),
400 ),
401 (
402 0x7F3DDA67B4A94DE6,
403 hex_to_key("13AC5E1474618778916727B21F37B31E"),
404 ),
405 (
406 0x402CD9D8D6BFED98,
407 hex_to_key("AEB0EADFE24A0742C24B8FFC2DC28C69"),
408 ),
409 (
410 0xFB680CB6A8BF81F3,
411 hex_to_key("62D90EFA7F36D71C398AE2F1FE37C5F5"),
412 ),
413 (
414 0xDBD3371554F60306,
415 hex_to_key("34E397ACE6DD30EEFDC98A2AB093CD3C"),
416 ),
417 (
418 0x11A9203C9A2D0DC8,
419 hex_to_key("2E609EA137A31F85DE06A14A9FF04AA1"),
420 ),
421 (
422 0x279C3FFB7E3229BC,
423 hex_to_key("53D25B2053C58F053AA4A6EA4E2D1625"),
424 ),
425 (
426 0xC7459A25DC3B7A4C,
427 hex_to_key("C54CF38B19EA7ABCB17B1D5086423A90"),
428 ),
429 ]
430}
431
432#[cfg(test)]
437mod tests {
438 use super::*;
439
440 #[test]
445 fn keystore_new_is_empty() {
446 let ks = TactKeyStore::new();
447 assert!(ks.is_empty());
448 assert_eq!(ks.len(), 0);
449 }
450
451 #[test]
452 fn keystore_with_known_keys_not_empty() {
453 let ks = TactKeyStore::with_known_keys();
454 assert!(!ks.is_empty());
455 assert!(ks.len() >= 10);
456 }
457
458 #[test]
459 fn keystore_get_known_key() {
460 let ks = TactKeyStore::with_known_keys();
461 let key = ks.get(0xFA505078126ACB3E);
462 assert!(key.is_some());
463 assert_eq!(key.unwrap().len(), 16);
464 }
465
466 #[test]
467 fn keystore_get_unknown_returns_none() {
468 let ks = TactKeyStore::with_known_keys();
469 assert!(ks.get(0xDEADBEEFCAFEBABE).is_none());
470 }
471
472 #[test]
473 fn keystore_merge() {
474 let mut ks1 = TactKeyStore::new();
475 let mut ks2 = TactKeyStore::new();
476 ks2.keys.insert(0x1234, [0xAA; 16]);
477 ks1.merge(&ks2);
478 assert!(ks1.get(0x1234).is_some());
479 assert_eq!(ks1.get(0x1234).unwrap(), &[0xAA; 16]);
480 }
481
482 #[test]
483 fn keystore_load_keyfile() {
484 use std::io::Write;
485
486 let dir = std::env::temp_dir().join("casc_test_keyfile");
487 std::fs::create_dir_all(&dir).ok();
488 let path = dir.join("test.keys");
489 let mut f = std::fs::File::create(&path).unwrap();
490 writeln!(f, "# Comment line").unwrap();
491 writeln!(f, "FA505078126ACB3E BDC51862ABED79B2DE48C8E7E66C6200").unwrap();
492 writeln!(f).unwrap(); writeln!(f, "FF813F7D062AC0BC AA0B5C77F088CCC2D39049BD267F066D").unwrap();
494 drop(f);
495
496 let ks = TactKeyStore::load_keyfile(&path).unwrap();
497 assert_eq!(ks.len(), 2);
498 assert!(ks.get(0xFA505078126ACB3E).is_some());
499 assert!(ks.get(0xFF813F7D062AC0BC).is_some());
500
501 std::fs::remove_dir_all(&dir).ok();
502 }
503
504 #[test]
505 fn keystore_load_keyfile_missing_file_errors() {
506 let path = Path::new("nonexistent_keyfile.txt");
507 assert!(TactKeyStore::load_keyfile(path).is_err());
508 }
509
510 #[test]
511 fn keystore_load_keyfile_invalid_hex_errors() {
512 use std::io::Write;
513
514 let dir = std::env::temp_dir().join("casc_test_keyfile_bad");
515 std::fs::create_dir_all(&dir).ok();
516 let path = dir.join("bad.keys");
517 let mut f = std::fs::File::create(&path).unwrap();
518 writeln!(f, "ZZZZ INVALID_HEX_VALUE_HERE_TOO").unwrap();
519 drop(f);
520
521 assert!(TactKeyStore::load_keyfile(&path).is_err());
522 std::fs::remove_dir_all(&dir).ok();
523 }
524
525 #[test]
530 fn parse_encryption_header_salsa20() {
531 let mut data = Vec::new();
532 data.push(1u8); data.push(8u8); data.extend_from_slice(&0xFA505078126ACB3Eu64.to_le_bytes());
535 data.extend_from_slice(&4u32.to_le_bytes()); data.extend_from_slice(&[0x01, 0x02, 0x03, 0x04]); data.push(b'S'); data.extend_from_slice(b"encrypted_payload");
539
540 let (header, remaining) = parse_encryption_header(&data).unwrap();
541 assert_eq!(header.key_name, 0xFA505078126ACB3E);
542 assert_eq!(header.iv, vec![0x01, 0x02, 0x03, 0x04]);
543 assert_eq!(header.algorithm, EncryptionAlgorithm::Salsa20);
544 assert_eq!(remaining, b"encrypted_payload");
545 }
546
547 #[test]
548 fn parse_encryption_header_arc4() {
549 let mut data = Vec::new();
550 data.push(1u8);
551 data.push(8u8);
552 data.extend_from_slice(&0xDEADBEEFu64.to_le_bytes());
553 data.extend_from_slice(&4u32.to_le_bytes());
554 data.extend_from_slice(&[0x0A, 0x0B, 0x0C, 0x0D]);
555 data.push(b'A'); data.extend_from_slice(b"payload");
557
558 let (header, remaining) = parse_encryption_header(&data).unwrap();
559 assert_eq!(header.algorithm, EncryptionAlgorithm::ARC4);
560 assert_eq!(header.key_name, 0xDEADBEEF);
561 assert_eq!(remaining, b"payload");
562 }
563
564 #[test]
565 fn parse_encryption_header_empty_errors() {
566 assert!(parse_encryption_header(&[]).is_err());
567 }
568
569 #[test]
570 fn parse_encryption_header_unknown_algo_errors() {
571 let mut data = Vec::new();
572 data.push(1u8);
573 data.push(8u8);
574 data.extend_from_slice(&0u64.to_le_bytes());
575 data.extend_from_slice(&4u32.to_le_bytes());
576 data.extend_from_slice(&[0; 4]);
577 data.push(b'X'); assert!(parse_encryption_header(&data).is_err());
580 }
581
582 #[test]
587 fn arc4_round_trip() {
588 let key = b"test_key_16bytes"; let plaintext = b"Hello World! This is a test of ARC4 encryption.";
590
591 let mut encrypted = plaintext.to_vec();
592 let mut cipher1 = Arc4::new(key);
593 cipher1.process(&mut encrypted);
594
595 assert_ne!(&encrypted[..], &plaintext[..]);
597
598 let mut decrypted = encrypted.clone();
600 let mut cipher2 = Arc4::new(key);
601 cipher2.process(&mut decrypted);
602
603 assert_eq!(&decrypted[..], &plaintext[..]);
604 }
605
606 #[test]
607 fn arc4_empty_data() {
608 let key = b"some_key_16bytes";
609 let mut data = Vec::new();
610 let mut cipher = Arc4::new(key);
611 cipher.process(&mut data);
612 assert!(data.is_empty());
613 }
614
615 #[test]
620 fn salsa20_round_trip() {
621 let key = [0x42u8; 16];
622 let iv = [0x01, 0x02, 0x03, 0x04];
623 let plaintext = b"test salsa20 encryption data";
624
625 let mut data = plaintext.to_vec();
627 decrypt_salsa20(&key, &iv, &mut data);
628 assert_ne!(&data[..], &plaintext[..]);
629
630 let mut roundtrip = data.clone();
632 decrypt_salsa20(&key, &iv, &mut roundtrip);
633 assert_eq!(&roundtrip[..], &plaintext[..]);
634 }
635
636 #[test]
641 fn decrypt_block_missing_key_errors() {
642 let ks = TactKeyStore::new(); let mut data = Vec::new();
644 data.push(1u8); data.push(8u8); data.extend_from_slice(&0xDEADBEEFu64.to_le_bytes());
647 data.extend_from_slice(&4u32.to_le_bytes());
648 data.extend_from_slice(&[0; 4]);
649 data.push(b'S');
650 data.extend_from_slice(b"encrypted");
651
652 let result = decrypt_block(&data, &ks);
653 assert!(result.is_err());
654 match result.unwrap_err() {
655 CascError::EncryptionKeyMissing(_) => {}
656 e => panic!("Expected EncryptionKeyMissing, got: {:?}", e),
657 }
658 }
659
660 #[test]
661 fn decrypt_block_salsa20_round_trip() {
662 let key_name: u64 = 0xFA505078126ACB3E;
663 let ks = TactKeyStore::with_known_keys();
664 let key = ks.get(key_name).unwrap();
665
666 let plaintext = b"Nhello world inner content";
668 let iv_bytes = [0x10, 0x20, 0x30, 0x40];
669
670 let mut encrypted_payload = plaintext.to_vec();
672 decrypt_salsa20(key, &iv_bytes, &mut encrypted_payload);
673
674 let mut block_data = Vec::new();
676 block_data.push(1u8);
677 block_data.push(8u8);
678 block_data.extend_from_slice(&key_name.to_le_bytes());
679 block_data.extend_from_slice(&4u32.to_le_bytes());
680 block_data.extend_from_slice(&iv_bytes);
681 block_data.push(b'S');
682 block_data.extend_from_slice(&encrypted_payload);
683
684 let decrypted = decrypt_block(&block_data, &ks).unwrap();
686 assert_eq!(&decrypted[..], &plaintext[..]);
687 }
688
689 #[test]
690 fn decrypt_block_arc4_round_trip() {
691 let key_name: u64 = 0xFA505078126ACB3E;
692 let ks = TactKeyStore::with_known_keys();
693 let key = ks.get(key_name).unwrap();
694
695 let plaintext = b"Zcompressed inner data here";
696
697 let mut encrypted_payload = plaintext.to_vec();
699 let mut cipher = Arc4::new(key);
700 cipher.process(&mut encrypted_payload);
701
702 let mut block_data = Vec::new();
704 block_data.push(1u8);
705 block_data.push(8u8);
706 block_data.extend_from_slice(&key_name.to_le_bytes());
707 block_data.extend_from_slice(&4u32.to_le_bytes());
708 block_data.extend_from_slice(&[0x01, 0x02, 0x03, 0x04]);
709 block_data.push(b'A');
710 block_data.extend_from_slice(&encrypted_payload);
711
712 let decrypted = decrypt_block(&block_data, &ks).unwrap();
713 assert_eq!(&decrypted[..], &plaintext[..]);
714 }
715
716 #[test]
717 fn hex_to_key_result_valid() {
718 let key = hex_to_key_result("BDC51862ABED79B2DE48C8E7E66C6200").unwrap();
719 assert_eq!(key.len(), 16);
720 assert_eq!(key[0], 0xBD);
721 assert_eq!(key[15], 0x00);
722 }
723
724 #[test]
725 fn hex_to_key_result_invalid_hex_errors() {
726 assert!(hex_to_key_result("ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ").is_err());
727 }
728
729 #[test]
730 fn hex_to_key_result_wrong_length_errors() {
731 assert!(hex_to_key_result("AABB").is_err());
732 }
733
734 #[test]
735 fn known_keys_all_valid() {
736 let keys = known_keys();
737 assert!(keys.len() >= 15);
738 for (name, value) in &keys {
739 assert_ne!(*name, 0, "key name should not be zero");
740 assert_ne!(*value, [0u8; 16], "key value should not be all zeros");
741 }
742 }
743}