194 lines
5.3 KiB
Rust
194 lines
5.3 KiB
Rust
//! 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<u8>,
|
|
}
|
|
|
|
/// 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<ParsedKeystore> {
|
|
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);
|
|
}
|
|
}
|
|
}
|