//! Embedded keystore: binary blob of protocol keys (KIA, Star Line, VAG). //! Matches ProtoPirate key types (keys.c: KIA_KEY1..4). Loaded at startup instead of keystore_dir. mod embedded; use std::convert::TryInto; /// Type IDs that are KeeLoq manufacturer keys (64-bit keys used for keeloq_decrypt). const KEELOQ_MF_TYPES: &[u32] = &[0, 1, 2, 10, 20]; /// Names for each key entry in the blob (same order as entries before VAG). Used for fallback decode display. const KEY_ENTRY_NAMES: &[&str] = &[ "KGB/Subaru", "Magic_2", "IronLogic", "Stilmatic", "Pandora_Test_Debug_2", "Rosh", "Dea_Mio", "Leopard", "Sheriff", "Cenmax", "Centurion", "Guard_RF-311A", "Pandora_PRO", "Cardin_S449", "Pandora_DEA", "Pandora_GIBIDI", "Pandora_MCODE", "Pandora_Unknown_1", "Pandora_SUZUKI", "Harpoon", "Gibidi", "Pandora_Unknown_2", "Jarolift", "KEY", "Novoferm", "Pandora_SUBARU", "Pecinin", "Merlin", "Pandora_M101", "IL-100(Smart)", "Pantera_CLK", "Kingates_Stylo4k", "Monarch", "Aprimatic", "NICE_MHOUSE", "BFT", "Pantera", "KIAV5", "FAAC_SLH", "Alligator_S-275", "NICE_Smilo", "SL_A6-A9/Tomahawk_9010", "Motorline", "KIAV6A", "Tomahawk_TZ-9030", "Cenmax_St-7", "Pantera_XS/Jaguar", "APS-1100_APS-2550", "Reff", "KIAV6B", "Beninca_ARC", "Tomahawk_Z,X_3-5", "Sommer", "Pandora_PRO2", "DoorHan", "EcoStar", "JCM_Tech", "Faraon", "Came_Space", "Magic_4", "SL_A2-A4", "Elmes_Poland", "Normstahl", "KIA", "GSN", "Mutanco_Mutancode", "Cenmax_St-5", "Magic_3", "Partisan_RX", "Steelmate", "Teco", "ZX-730-750-1055", "Jolly_Motors", "Beninca", "Mongoose", "Alligator", "Comunello", "SL_B6,B9_dop", "FAAC_RC,XT", "Genius_Bravo", "Rossi", "DTM_Neo", "Star Line", ]; const MAGIC: &[u8; 4] = b"KATK"; const VAG_TAG: &[u8; 4] = b"VAG "; const VAG_SIZE: usize = 64; const ENTRY_SIZE: usize = 4 + 8; // u32 type + u64 key /// Parsed result from the embedded blob: (type_id, key) pairs and raw VAG bytes. pub struct ParsedKeystore { pub entries: Vec<(u32, u64)>, pub vag_bytes: Vec, } /// Parse the embedded keystore blob. Returns KIA/Star Line entries and VAG raw bytes. /// /// Keys are stored as 8 bytes **little-endian** per entry; we load with `u64::from_le_bytes`. /// The resulting u64 value matches the reference/Pandora hex (e.g. KIA = 0xA8F5DFFC8DAA5CDB), /// which is typically written MSB-first in docs; KeeLoq uses this value as a 64-bit key. pub fn parse_blob(blob: &[u8]) -> Option { if blob.len() < 4 || &blob[0..4] != MAGIC { return None; } let n = u16::from_le_bytes(blob[4..6].try_into().ok()?) as usize; let mut off = 6; let mut entries = Vec::with_capacity(n); for _ in 0..n { if off + ENTRY_SIZE > blob.len() { return None; } let ty = u32::from_le_bytes(blob[off..off + 4].try_into().ok()?); let key = u64::from_le_bytes(blob[off + 4..off + 12].try_into().ok()?); entries.push((ty, key)); off += ENTRY_SIZE; } if off + 4 + VAG_SIZE > blob.len() || &blob[off..off + 4] != VAG_TAG { return Some(ParsedKeystore { entries, vag_bytes: Vec::new(), }); } off += 4; let vag_bytes = blob[off..off + VAG_SIZE].to_vec(); Some(ParsedKeystore { entries, vag_bytes, }) } /// Return all KeeLoq manufacturer keys from the embedded blob with display names. /// Used when an unknown signal is tried as KeeLoq with every key; on success we show "Keeloq (name)". /// Only includes entry types that are KeeLoq MF keys (0, 1, 2, 10, 20). pub fn keeloq_mf_keys_with_names() -> Vec<(String, u64)> { let blob = embedded_blob(); let Some(parsed) = parse_blob(blob) else { return Vec::new(); }; parsed .entries .iter() .enumerate() .filter(|(_, (ty, _))| KEELOQ_MF_TYPES.contains(ty)) .filter_map(|(i, (_, key))| { let name = KEY_ENTRY_NAMES.get(i).copied().unwrap_or("?").to_string(); if *key == 0 { None } else { Some((name, *key)) } }) .collect() } /// Return the embedded keystore blob for loading. pub fn embedded_blob() -> &'static [u8] { embedded::KEYSTORE_BLOB } /// Return all (type_id, key_u64) from the embedded blob for comparison with external key lists (e.g. Pandora). /// Key is stored LE in blob; returned as u64. Format as 16-char hex with format!("{:016X}", key) to match Pandora. #[cfg(test)] pub fn embedded_entries_for_compare() -> Vec<(u32, u64)> { parse_blob(embedded_blob()).map(|p| p.entries).unwrap_or_default() } #[cfg(test)] mod tests { use super::*; #[test] fn dump_keystore_keys_for_pandora_compare() { let entries = embedded_entries_for_compare(); eprintln!("KAT embedded keystore keys (type, hex, name):"); for (i, (ty, key)) in entries.iter().enumerate() { let name = KEY_ENTRY_NAMES.get(i).copied().unwrap_or("?"); eprintln!(" type {} {} {}", ty, format!("{:016X}", key), name); } } }