use std::collections::HashMap;
use std::path::Path;
use crate::crypto::ecb;
use crate::error::{Error, Result};
const CRC32_CHECKSUMS: &[(&str, u32)] = &[
("aes_kek_generation_source", 2545229389),
("aes_key_generation_source", 459881589),
("titlekek_source", 3510501772),
("key_area_key_application_source", 4130296074),
("key_area_key_ocean_source", 3975316347),
("key_area_key_system_source", 4024798875),
("master_key_00", 3540309694),
("master_key_01", 3477638116),
("master_key_02", 2087460235),
("master_key_03", 4095912905),
("master_key_04", 3833085536),
("master_key_05", 2078263136),
("master_key_06", 2812171174),
("master_key_07", 1146095808),
("master_key_08", 1605958034),
("master_key_09", 3456782962),
("master_key_0a", 2012895168),
("master_key_0b", 3813624150),
("master_key_0c", 3881579466),
("master_key_0d", 723654444),
("master_key_0e", 2690905064),
("master_key_0f", 4082108335),
("master_key_10", 788455323),
("master_key_11", 1214507020),
("master_key_12", 1051942134),
("master_key_13", 2476807835),
("master_key_14", 2448653557),
("master_key_15", 4071812001),
];
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum KeyArea {
Application = 0,
Ocean = 1,
System = 2,
}
#[derive(Debug, Clone, Default)]
pub struct Keys {
raw: HashMap<String, Vec<u8>>,
title_keks: Vec<Option<[u8; 16]>>,
key_area_keys: Vec<[Option<[u8; 16]>; 3]>,
title_keys: HashMap<String, [u8; 16]>,
}
fn read_text(path: &Path) -> Result<String> {
std::fs::read_to_string(path)
.map_err(|e| std::io::Error::new(e.kind(), format!("{}: {e}", path.display())).into())
}
fn parse_line(line: &str) -> Option<(String, String)> {
let eq = line.find('=')?;
let key = line[..eq].trim();
let val = line[eq + 1..].trim();
if key.is_empty() || val.is_empty() {
return None;
}
let key_ok = key.chars().all(|c| c.is_ascii_alphanumeric() || c == '_');
let val_ok = val.chars().all(|c| c.is_ascii_hexdigit());
if key_ok && val_ok {
Some((key.to_string(), val.to_uppercase()))
} else {
None
}
}
impl Keys {
pub fn load(path: impl AsRef<Path>, verify_crc: bool) -> Result<Self> {
Self::from_str(&read_text(path.as_ref())?, verify_crc)
}
pub fn from_str(text: &str, verify_crc: bool) -> Result<Self> {
let mut raw = HashMap::new();
for line in text.lines() {
if let Some((k, v)) = parse_line(line) {
let bytes = hex::decode(&v)?;
raw.insert(k, bytes);
}
}
let get = |name: &str| -> Result<[u8; 16]> {
let bytes = raw
.get(name)
.cloned()
.or_else(|| raw.get(&name.replace("nca_", "")).cloned())
.ok_or_else(|| Error::MissingKey(name.to_string()))?;
if verify_crc
&& let Some(&(_, expected)) = CRC32_CHECKSUMS.iter().find(|(k, _)| *k == name)
{
let mut hasher = crc32fast::Hasher::new();
hasher.update(&bytes);
let got = hasher.finalize();
if got != expected {
return Err(Error::InvalidKey(format!(
"{name} crc32 mismatch (expected {expected}, got {got})"
)));
}
}
bytes
.try_into()
.map_err(|_| Error::InvalidKey(format!("{name} is not 16 bytes")))
};
let aes_kek_gen = get("aes_kek_generation_source")?;
let aes_key_gen = get("aes_key_generation_source")?;
let titlekek_source = get("titlekek_source")?;
let kaa = get("key_area_key_application_source")?;
let kao = get("key_area_key_ocean_source")?;
let kas = get("key_area_key_system_source")?;
let mut title_keks = vec![None; 32];
let mut key_area_keys = vec![[None, None, None]; 32];
for rev in 0..32 {
let name = format!("master_key_{rev:02x}");
let mk = match get(&name) {
Ok(m) => m,
Err(Error::MissingKey(_)) => continue,
Err(e) => return Err(e),
};
title_keks[rev] = Some(ecb_decrypt(&mk, titlekek_source));
key_area_keys[rev] =
[kaa, kao, kas].map(|src| Some(generate_kek(src, &mk, aes_kek_gen, aes_key_gen)));
}
Ok(Keys {
raw,
title_keks,
key_area_keys,
title_keys: HashMap::new(),
})
}
pub fn load_title_keys(&mut self, path: impl AsRef<Path>) -> Result<()> {
let text = read_text(path.as_ref())?;
for line in text.lines() {
if let Some((k, v)) = parse_line(line) {
let bytes = hex::decode(&v)?;
if bytes.len() == 16 {
let mut arr = [0u8; 16];
arr.copy_from_slice(&bytes);
self.title_keys.insert(k.to_lowercase(), arr);
}
}
}
Ok(())
}
pub fn get_raw(&self, name: &str) -> Option<&[u8]> {
self.raw.get(name).map(|v| v.as_slice())
}
pub fn has_master_key(&self, rev: usize) -> bool {
rev < 32 && self.raw.contains_key(&format!("master_key_{rev:02x}"))
}
pub fn title_kek(&self, rev: usize) -> Result<[u8; 16]> {
self.title_keks
.get(rev)
.and_then(|x| *x)
.ok_or_else(|| Error::MissingKey(format!("title_kek[{rev}]")))
}
pub fn key_area_key(&self, rev: usize, area: KeyArea) -> Result<[u8; 16]> {
self.key_area_keys
.get(rev)
.and_then(|a| a[area as usize])
.ok_or_else(|| Error::MissingKey(format!("key_area_key[{rev}][{area:?}]")))
}
pub fn decrypt_title_key(&self, wrapped: &[u8; 16], rev: usize) -> Result<[u8; 16]> {
Ok(ecb_decrypt(&self.title_kek(rev)?, *wrapped))
}
pub fn unwrap_title_key(&self, wrapped: &[u8; 16], key_generation: usize) -> Result<[u8; 16]> {
let kek = self.key_area_key(key_generation, KeyArea::Application)?;
Ok(ecb_decrypt(&kek, *wrapped))
}
pub fn wrap_title_key(&self, plain: &[u8; 16], key_generation: usize) -> Result<[u8; 16]> {
let mut out = *plain;
ecb::encrypt_block(
&self.key_area_key(key_generation, KeyArea::Application)?,
&mut out,
);
Ok(out)
}
pub fn header_key(&self) -> Result<[u8; 32]> {
self.get_raw("header_key")
.ok_or_else(|| Error::MissingKey("header_key".into()))?
.try_into()
.map_err(|_| Error::InvalidKey("header_key must be 32 bytes".into()))
}
pub fn title_key_by_rights(&self, rights_id: &str) -> Option<[u8; 16]> {
self.title_keys.get(&rights_id.to_lowercase()).copied()
}
}
fn generate_kek(
src: [u8; 16],
master: &[u8; 16],
kek_seed: [u8; 16],
key_seed: [u8; 16],
) -> [u8; 16] {
let kek = ecb_decrypt(master, kek_seed);
let src_kek = ecb_decrypt(&kek, src);
ecb_decrypt(&src_kek, key_seed)
}
fn ecb_decrypt(key: &[u8; 16], mut block: [u8; 16]) -> [u8; 16] {
ecb::decrypt_block(key, &mut block);
block
}