protocol updates
This commit is contained in:
@@ -6,9 +6,14 @@
|
||||
//! - KIA V3/V4: kia_mf_key (manufacturer key for KeeLoq)
|
||||
//! - KIA V5: kia_v5_key (custom mixer cipher key)
|
||||
//! - KIA V6: kia_v6_a_key, kia_v6_b_key (AES-128 XOR mask keys)
|
||||
//! - Star Line: star_line_mf_key (manufacturer key for KeeLoq)
|
||||
//! - VAG: AUT64 keys loaded from keystore files
|
||||
//!
|
||||
//! Keys are loaded from `~/.config/KAT/keystore/keystore.ini` at startup.
|
||||
//! See [`load_keystore_from_dir`] for the file format.
|
||||
|
||||
use super::aut64::{self, Aut64Key, AUT64_KEY_STRUCT_PACKED_SIZE};
|
||||
use configparser::ini::Ini;
|
||||
use std::path::Path;
|
||||
use std::sync::{OnceLock, RwLock};
|
||||
use tracing::{info, warn, error};
|
||||
@@ -18,6 +23,7 @@ const KIA_KEY1: u32 = 10; // kia_mf_key
|
||||
const KIA_KEY2: u32 = 11; // kia_v6_a_key
|
||||
const KIA_KEY3: u32 = 12; // kia_v6_b_key
|
||||
const KIA_KEY4: u32 = 13; // kia_v5_key
|
||||
const STAR_LINE_KEY: u32 = 20; // star_line_mf_key
|
||||
|
||||
/// Maximum number of VAG AUT64 keys
|
||||
const VAG_KEYS_COUNT: usize = 3;
|
||||
@@ -32,6 +38,8 @@ pub struct KeyStore {
|
||||
pub kia_v6_b_key: u64,
|
||||
/// KIA V5 mixer key
|
||||
pub kia_v5_key: u64,
|
||||
/// Star Line manufacturer key (for KeeLoq)
|
||||
pub star_line_mf_key: u64,
|
||||
/// VAG AUT64 keys
|
||||
pub vag_keys: Vec<Aut64Key>,
|
||||
/// Whether VAG keys have been loaded
|
||||
@@ -45,6 +53,7 @@ impl Default for KeyStore {
|
||||
kia_v6_a_key: 0,
|
||||
kia_v6_b_key: 0,
|
||||
kia_v5_key: 0,
|
||||
star_line_mf_key: 0,
|
||||
vag_keys: Vec::new(),
|
||||
vag_keys_loaded: false,
|
||||
}
|
||||
@@ -66,6 +75,7 @@ impl KeyStore {
|
||||
KIA_KEY2 => self.kia_v6_a_key = key_value,
|
||||
KIA_KEY3 => self.kia_v6_b_key = key_value,
|
||||
KIA_KEY4 => self.kia_v5_key = key_value,
|
||||
STAR_LINE_KEY => self.star_line_mf_key = key_value,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
@@ -146,6 +156,11 @@ impl KeyStore {
|
||||
pub fn get_kia_v5_key(&self) -> u64 {
|
||||
self.kia_v5_key
|
||||
}
|
||||
|
||||
/// Get the Star Line manufacturer key
|
||||
pub fn get_star_line_mf_key(&self) -> u64 {
|
||||
self.star_line_mf_key
|
||||
}
|
||||
}
|
||||
|
||||
/// Global singleton keystore
|
||||
@@ -175,3 +190,171 @@ pub fn load_vag_keys(path: &str) {
|
||||
let mut store = get_keystore_mut();
|
||||
store.load_vag_keys_from_file(path);
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Keystore file loading from ~/.config/KAT/keystore/
|
||||
// =============================================================================
|
||||
|
||||
/// Parse a hex string (with or without "0x" prefix) into a u64.
|
||||
fn parse_hex_u64(s: &str) -> Option<u64> {
|
||||
let s = s.trim();
|
||||
let s = s.strip_prefix("0x").or_else(|| s.strip_prefix("0X")).unwrap_or(s);
|
||||
u64::from_str_radix(s, 16).ok()
|
||||
}
|
||||
|
||||
/// Default keystore.ini template content.
|
||||
/// Written to disk on first run so users know which keys to configure.
|
||||
const DEFAULT_KEYSTORE_INI: &str = r#"; KAT Keystore — Protocol Encryption Keys
|
||||
;
|
||||
; Place your protocol keys here. Key values should be in hexadecimal
|
||||
; with a 0x prefix (e.g. 0x0123456789ABCDEF).
|
||||
;
|
||||
; Keys left at 0x0000000000000000 or omitted are treated as "not loaded"
|
||||
; and the corresponding protocol will decode without decryption
|
||||
; (serial/button still visible, but counter may not validate).
|
||||
;
|
||||
; This file corresponds to protopirate's keystore/encrypted and keystore/vag
|
||||
; assets. Since KAT runs on a PC (not a Flipper), keys must be provided
|
||||
; in plaintext here rather than in the Flipper's encrypted keystore format.
|
||||
|
||||
[kia]
|
||||
; KIA V3/V4: KeeLoq manufacturer key (used for hop-code decryption)
|
||||
mf_key = 0x0000000000000000
|
||||
|
||||
; KIA V5: Custom mixer cipher key
|
||||
v5_key = 0x0000000000000000
|
||||
|
||||
; KIA V6: AES-128 key components (XOR-masked before use)
|
||||
v6_a_key = 0x0000000000000000
|
||||
v6_b_key = 0x0000000000000000
|
||||
|
||||
[star_line]
|
||||
; Star Line: KeeLoq manufacturer key (used for hop-code decryption)
|
||||
mf_key = 0x0000000000000000
|
||||
|
||||
[vag]
|
||||
; VAG: Path to AUT64 binary key file (raw packed keys, 16 bytes each).
|
||||
; Can be absolute or relative to the keystore directory.
|
||||
; Leave empty or commented out if you don't have VAG keys.
|
||||
; keys_file = vag.bin
|
||||
"#;
|
||||
|
||||
/// Write the default keystore.ini template if it doesn't exist yet.
|
||||
pub fn create_default_keystore(keystore_dir: &Path) {
|
||||
let ini_path = keystore_dir.join("keystore.ini");
|
||||
if ini_path.exists() {
|
||||
return;
|
||||
}
|
||||
|
||||
match std::fs::write(&ini_path, DEFAULT_KEYSTORE_INI) {
|
||||
Ok(_) => info!("Created default keystore template at {:?}", ini_path),
|
||||
Err(e) => warn!("Could not create default keystore.ini: {}", e),
|
||||
}
|
||||
}
|
||||
|
||||
/// Load all keys from the keystore directory.
|
||||
///
|
||||
/// Reads `keystore.ini` from the given directory and populates the global
|
||||
/// [`KeyStore`] with:
|
||||
///
|
||||
/// - KIA V3/V4 manufacturer key (`[kia] mf_key`)
|
||||
/// - KIA V5 mixer key (`[kia] v5_key`)
|
||||
/// - KIA V6 AES keys (`[kia] v6_a_key`, `[kia] v6_b_key`)
|
||||
/// - Star Line manufacturer key (`[star_line] mf_key`)
|
||||
/// - VAG AUT64 keys from binary file (`[vag] keys_file`)
|
||||
///
|
||||
/// Keys that are missing, zeroed, or unparseable are silently skipped.
|
||||
pub fn load_keystore_from_dir(keystore_dir: &Path) {
|
||||
let ini_path = keystore_dir.join("keystore.ini");
|
||||
|
||||
if !ini_path.exists() {
|
||||
info!("No keystore.ini found at {:?} — keys not loaded", ini_path);
|
||||
return;
|
||||
}
|
||||
|
||||
let mut ini = Ini::new();
|
||||
if let Err(e) = ini.load(ini_path.to_string_lossy().as_ref()) {
|
||||
error!("Failed to parse keystore.ini: {}", e);
|
||||
return;
|
||||
}
|
||||
|
||||
let mut store = get_keystore_mut();
|
||||
let mut loaded_count = 0u32;
|
||||
|
||||
// ── KIA keys ──────────────────────────────────────────────────────────
|
||||
if let Some(val) = ini.get("kia", "mf_key").and_then(|s| parse_hex_u64(&s)) {
|
||||
if val != 0 {
|
||||
store.kia_mf_key = val;
|
||||
loaded_count += 1;
|
||||
info!("Loaded KIA V3/V4 manufacturer key");
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(val) = ini.get("kia", "v5_key").and_then(|s| parse_hex_u64(&s)) {
|
||||
if val != 0 {
|
||||
store.kia_v5_key = val;
|
||||
loaded_count += 1;
|
||||
info!("Loaded KIA V5 mixer key");
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(val) = ini.get("kia", "v6_a_key").and_then(|s| parse_hex_u64(&s)) {
|
||||
if val != 0 {
|
||||
store.kia_v6_a_key = val;
|
||||
loaded_count += 1;
|
||||
info!("Loaded KIA V6 AES key A");
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(val) = ini.get("kia", "v6_b_key").and_then(|s| parse_hex_u64(&s)) {
|
||||
if val != 0 {
|
||||
store.kia_v6_b_key = val;
|
||||
loaded_count += 1;
|
||||
info!("Loaded KIA V6 AES key B");
|
||||
}
|
||||
}
|
||||
|
||||
// ── Star Line keys ────────────────────────────────────────────────────
|
||||
if let Some(val) = ini.get("star_line", "mf_key").and_then(|s| parse_hex_u64(&s)) {
|
||||
if val != 0 {
|
||||
store.star_line_mf_key = val;
|
||||
loaded_count += 1;
|
||||
info!("Loaded Star Line manufacturer key");
|
||||
}
|
||||
}
|
||||
|
||||
// ── VAG AUT64 keys (binary file) ──────────────────────────────────────
|
||||
if let Some(vag_file) = ini.get("vag", "keys_file") {
|
||||
let vag_file = vag_file.trim().to_string();
|
||||
if !vag_file.is_empty() {
|
||||
let vag_path = if Path::new(&vag_file).is_absolute() {
|
||||
std::path::PathBuf::from(&vag_file)
|
||||
} else {
|
||||
keystore_dir.join(&vag_file)
|
||||
};
|
||||
|
||||
if vag_path.exists() {
|
||||
match std::fs::read(&vag_path) {
|
||||
Ok(data) => {
|
||||
store.load_vag_keys_from_data(&data);
|
||||
if store.vag_keys_loaded {
|
||||
loaded_count += store.vag_keys.len() as u32;
|
||||
info!("Loaded {} VAG AUT64 keys from {:?}", store.vag_keys.len(), vag_path);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Failed to read VAG key file {:?}: {}", vag_path, e);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
warn!("VAG key file not found: {:?}", vag_path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if loaded_count > 0 {
|
||||
info!("Keystore loaded: {} key(s) from {:?}", loaded_count, keystore_dir);
|
||||
} else {
|
||||
info!("Keystore loaded but no non-zero keys found. Edit keystore.ini to add your keys.");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user