diff --git a/Cargo.lock b/Cargo.lock index ca3465a..4a3a627 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -214,11 +214,38 @@ dependencies = [ "vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "cmac" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "block-cipher-trait 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", + "crypto-mac 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "dbl 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "constant_time_eq" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "crypto-mac" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "generic-array 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", + "subtle 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "dbl" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "generic-array 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "derive_more" version = "0.13.0" @@ -384,6 +411,7 @@ dependencies = [ "byteorder 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)", "cargo_metadata 0.6.0 (git+https://github.com/roblabla/cargo_metadata)", "clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cmac 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "derive_more 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)", "dirs 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", "elf 0.0.10 (registry+https://github.com/rust-lang/crates.io-index)", @@ -663,6 +691,11 @@ dependencies = [ "syn 0.15.22 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "subtle" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "syn" version = "0.14.7" @@ -834,7 +867,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum cc 1.0.24 (registry+https://github.com/rust-lang/crates.io-index)" = "70f2a88c2e69ceee91c209d8ef25b81fc1a65f42c7f14dfd59d1fed189e514d1" "checksum cfg-if 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "0c4e7bb64a8ebb0d856483e1e682ea3422f883c5f5615a90d51a2c82fe87fdd3" "checksum clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b957d88f4b6a63b9d70d5f454ac8011819c6efa7727858f458ab71c756ce2d3e" +"checksum cmac 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6f4a435124bcc292eba031f1f725d7abacdaf13cbf9f935450e8c45aa9e96cad" "checksum constant_time_eq 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8ff012e225ce166d4422e0e78419d901719760f62ae2b7969ca6b564d1b54a9e" +"checksum crypto-mac 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4434400df11d95d556bac068ddfedd482915eb18fe8bea89bc80b6e4b1c179e5" +"checksum dbl 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6c40b13b561e11560d7b12785e74113a3163df617e2fbce60ce1764e0b270eaa" "checksum derive_more 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3f57d78cf3bd45270dad4e70c21ec77a960b36c7a841ff9db76aaa775a8fb871" "checksum digest 0.7.5 (registry+https://github.com/rust-lang/crates.io-index)" = "5b29c278aa8fd30796bd977169e8004b4aa88cdcd2f32a6eb22bc2d5d38df94a" "checksum dirs 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "88972de891f6118092b643d85a0b28e0678e0f948d7f879aa32f2d5aafe97d2a" @@ -889,6 +925,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4f380125926a99e52bc279241539c018323fab05ad6368b56f93d9369ff550" "checksum structopt 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)" = "41c4a2479a078509940d82773d90ff824a8c89533ab3b59cd3ce8b0c0e369c02" "checksum structopt-derive 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)" = "5352090cfae7a2c85e1a31146268b53396106c88ca5d6ccee2e3fae83b6e35c2" +"checksum subtle 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2d67a5a62ba6e01cb2192ff309324cb4875d0c451d55fe2319433abe7a05a8ee" "checksum syn 0.14.7 (registry+https://github.com/rust-lang/crates.io-index)" = "e2e13df71f29f9440b50261a5882c86eac334f1badb3134ec26f0de2f1418e44" "checksum syn 0.15.22 (registry+https://github.com/rust-lang/crates.io-index)" = "ae8b29eb5210bc5cf63ed6149cbf9adfc82ac0be023d8735c176ee74a2db4da7" "checksum synstructure 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "73687139bf99285483c96ac0add482c3776528beac1d97d444f6e91f203a2015" diff --git a/Cargo.toml b/Cargo.toml index 0b67223..d3e1cdc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,6 +38,7 @@ block-modes = "0.2" aes = "0.3" num-traits = "0.2" derive_more = "0.13" +cmac = "0.2.0" [features] binaries = ["structopt", "cargo_metadata", "scroll", "goblin", "clap"] diff --git a/src/bin/linkle_clap.rs b/src/bin/linkle_clap.rs index 1da02c7..fce2351 100644 --- a/src/bin/linkle_clap.rs +++ b/src/bin/linkle_clap.rs @@ -74,6 +74,17 @@ enum Opt { #[structopt(parse(from_os_str))] output_file: PathBuf, }, + /// Print all the keys generated from our keyfile. + #[structopt(name = "keygen")] + Keygen { + /// Use development keys instead of retail + #[structopt(short = "d", long = "dev")] + dev: bool, + + /// Key file to use + #[structopt(parse(from_os_str), short = "k", long = "keyset")] + keyfile: Option, + } } fn create_nxo(format: &str, input_file: &str, output_file: &str, icon_file: Option<&str>, romfs_dir: Option<&str>, nacp_file: Option<&str>) -> Result<(), linkle::error::Error> { @@ -153,6 +164,13 @@ fn create_romfs(input_directory: &Path, output_file: &Path) -> Result<(), linkle Ok(()) } +fn print_keys(is_dev: bool, key_path: Option<&Path>) -> Result<(), linkle::error::Error> { + let keys = linkle::pki::Keys::new_retail(key_path).unwrap(); + + keys.write(&mut std::io::stdout()).unwrap(); + Ok(()) +} + fn to_opt_ref>(s: &Option) -> Option<&U> { s.as_ref().map(AsRef::as_ref) } @@ -165,6 +183,7 @@ fn process_args(app: &Opt) { Opt::Pfs0Extract { ref input_file, ref output_directory } => extract_pfs0(input_file, output_directory), Opt::Nacp { ref input_file, ref output_file } => create_nacp(input_file, output_file), Opt::Romfs { ref input_directory, ref output_file } => create_romfs(input_directory, output_file), + Opt::Keygen { dev, ref keyfile } => print_keys(*dev, to_opt_ref(keyfile)), }; if let Err(e) = res { diff --git a/src/error.rs b/src/error.rs index 3d41bda..a345ce1 100644 --- a/src/error.rs +++ b/src/error.rs @@ -20,6 +20,8 @@ pub enum Error { Ini(#[cause] ini::ini::Error, Backtrace), #[display(fmt = "Key derivation error: {}", _0)] Crypto(String, Backtrace), + #[display(fmt = "Invalid keyblob {}: {}.", _1, _0)] + MacError(cmac::crypto_mac::MacError, usize, Backtrace), #[display(fmt = "Invalid PFS0: {}.", _0)] InvalidPfs0(&'static str, Backtrace), #[display(fmt = "Failed to convert filename to UTF8: {}.", _0)] @@ -80,3 +82,9 @@ impl From for Error { Error::Utf8Conversion(String::from_utf8_lossy(err.as_bytes()).into_owned(), err.utf8_error(), Backtrace::new()) } } + +impl From<(usize, cmac::crypto_mac::MacError)> for Error { + fn from((id, err): (usize, cmac::crypto_mac::MacError)) -> Error { + Error::MacError(err, id, Backtrace::new()) + } +} diff --git a/src/pki.rs b/src/pki.rs index c1931be..fbc8d2d 100644 --- a/src/pki.rs +++ b/src/pki.rs @@ -2,10 +2,12 @@ use std::fmt; use ini::{self, ini::Properties}; use failure::Backtrace; use std::fs::File; -use std::io::{self, ErrorKind}; +use std::io::{self, Write, ErrorKind}; use std::path::Path; use crate::error::Error; use aes::Aes128; +use cmac::Cmac; +use cmac::crypto_mac::Mac; use block_modes::{Ctr128, BlockModeIv, BlockMode}; use block_modes::block_padding::ZeroPadding; use aes::block_cipher_trait::generic_array::GenericArray; @@ -27,6 +29,14 @@ macro_rules! impl_debug { Ok(()) } } + impl fmt::Display for $for { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + for byte in &self.0[..] { + write!(f, "{:02X}", byte)?; + } + Ok(()) + } + } } } @@ -36,11 +46,30 @@ impl_debug!(EncryptedKeyblob); impl_debug!(Keyblob); impl_debug!(Modulus); +impl Keyblob { + fn encrypt(&self, key: &Aes128Key, mac_key: &Aes128Key, keyblob_id: usize) -> Result { + let mut encrypted_keyblob = [0; 0xB0]; + encrypted_keyblob[0x20..].copy_from_slice(&self.0); + + let mut crypter = Ctr128::::new_fixkey(GenericArray::from_slice(&key.0), GenericArray::from_slice(&encrypted_keyblob[0x10..0x20])); + crypter.encrypt_nopad(&mut encrypted_keyblob[0x20..])?; + + let mut cmac = Cmac::::new_varkey(&mac_key.0[..]).unwrap(); + cmac.input(&encrypted_keyblob[0x10..]); + encrypted_keyblob[..0x10].copy_from_slice(cmac.result().code().as_slice()); + Ok(EncryptedKeyblob(encrypted_keyblob)) + } +} + impl EncryptedKeyblob { - fn decrypt(&self, key: &Aes128Key) -> Result { + fn decrypt(&self, key: &Aes128Key, mac_key: &Aes128Key, keyblob_id: usize) -> Result { let mut keyblob = [0; 0x90]; keyblob.copy_from_slice(&self.0[0x20..]); + let mut cmac = Cmac::::new_varkey(&mac_key.0[..]).unwrap(); + cmac.input(&self.0[0x10..]); + cmac.verify(&self.0[..0x10]).map_err(|err| (keyblob_id, err))?; + let mut crypter = Ctr128::::new_fixkey(GenericArray::from_slice(&key.0), GenericArray::from_slice(&self.0[0x10..0x20])); crypter.decrypt_nopad(&mut keyblob)?; @@ -150,6 +179,71 @@ pub struct Keys { package2_fixed_key_modulus: Option, } +macro_rules! make_key_macros_write { + ($self:ident, $w:ident) => { + macro_rules! single_key { + ($keyname:tt) => { + if let Some(key) = &$self.$keyname { + writeln!($w, "{} = {}", stringify!($keyname), key)?; + } + } + } + + macro_rules! single_key_xts { + ($keyname:tt) => { + if let Some(key) = &$self.$keyname { + writeln!($w, "{} = {}", stringify!($keyname), key)?; + } + } + } + + macro_rules! multi_key { + ($keyname:tt) => { + for (idx, v) in $self.$keyname.iter().enumerate() { + if let Some(key) = v { + // remove trailing s + let mut name = String::from(stringify!($keyname)); + if name.bytes().last() == Some(b's') { + name.pop(); + } + writeln!($w, "{}_{:02x} = {}", name, idx, key)?; + } + } + } + } + + macro_rules! multi_keyblob { + ($keyname:tt) => { + for (idx, v) in $self.$keyname.iter().enumerate() { + if let Some(key) = v { + // remove trailing s + let mut name = String::from(stringify!($keyname)); + if name.bytes().last() == Some(b's') { + name.pop(); + } + writeln!($w, "{}_{:02x} = {}", name, idx, key)?; + } + } + } + } + + macro_rules! multi_encrypted_keyblob { + ($keyname:tt) => { + for (idx, v) in $self.$keyname.iter().enumerate() { + if let Some(key) = v { + // remove trailing s + let mut name = String::from(stringify!($keyname)); + if name.bytes().last() == Some(b's') { + name.pop(); + } + writeln!($w, "{}_{:02x} = {}", name, idx, key)?; + } + } + } + } + } +} + macro_rules! make_key_macros { ($self:ident, $section:ident) => { macro_rules! single_key { @@ -172,7 +266,9 @@ macro_rules! make_key_macros { let mut key = [0; 0x10]; // remove trailing s let mut name = String::from(stringify!($keyname)); - name.pop(); + if name.bytes().last() == Some(b's') { + name.pop(); + } v.or_in(key_to_aes_array($section, &name, idx, &mut key)?.map(|()| Aes128Key(key))); } } @@ -198,7 +294,9 @@ macro_rules! make_key_macros { let mut key = [0; 0xB0]; // remove trailing s let mut name = String::from(stringify!($keyname)); - name.pop(); + if name.bytes().last() == Some(b's') { + name.pop(); + } v.or_in(key_to_aes_array($section, &name, idx, &mut key)?.map(|()| EncryptedKeyblob(key))); } } @@ -359,6 +457,47 @@ impl Keys { Ok(()) } + pub fn write(&self, w: &mut W) -> io::Result<()> { + make_key_macros_write!(self, w); + single_key!(secure_boot_key); + single_key!(tsec_key); + multi_key!(keyblob_keys); + multi_key!(keyblob_mac_keys); + multi_key!(keyblob_key_sources); + multi_encrypted_keyblob!(encrypted_keyblobs); + multi_keyblob!(keyblobs); + single_key!(keyblob_mac_key_source); + single_key!(tsec_root_key); + multi_key!(master_kek_sources); + multi_key!(master_keks); + single_key!(master_key_source); + multi_key!(master_keys); + multi_key!(package1_keys); + multi_key!(package2_keys); + single_key!(package2_key_source); + single_key!(aes_kek_generation_source); + single_key!(aes_key_generation_source); + single_key!(key_area_key_application_source); + single_key!(key_area_key_ocean_source); + single_key!(key_area_key_system_source); + single_key!(titlekek_source); + single_key!(header_kek_source); + single_key!(sd_card_kek_source); + single_key_xts!(sd_card_save_key_source); + single_key_xts!(sd_card_nca_key_source); + single_key!(save_mac_kek_source); + single_key!(save_mac_key_source); + single_key_xts!(header_key_source); + single_key_xts!(header_key); + multi_key!(titlekeks); + multi_key!(key_area_key_application); + multi_key!(key_area_key_ocean); + multi_key!(key_area_key_system); + single_key_xts!(sd_card_save_key); + single_key_xts!(sd_card_nca_key); + Ok(()) + } + pub fn derive_keys(&mut self) -> Result<(), Error> { for i in 0..6 { /* Derive the keyblob_keys */ @@ -380,10 +519,12 @@ impl Keys { } } for i in 0..6 { - match (&self.keyblob_keys[i], &self.keyblob_mac_keys[i], &self.encrypted_keyblobs[i]) { - (Some(keyblob_key), Some(_keyblob_mac_key), Some(encrypted_keyblob)) => { - // TODO: Calculate cmac - self.keyblobs[i] = Some(encrypted_keyblob.decrypt(keyblob_key)?); + match (&self.keyblob_keys[i], &self.keyblob_mac_keys[i], &mut self.encrypted_keyblobs[i], &mut self.keyblobs[i]) { + (Some(keyblob_key), Some(keyblob_mac_key), Some(encrypted_keyblob), ref mut keyblob @ None) => { + **keyblob = Some(encrypted_keyblob.decrypt(keyblob_key, keyblob_mac_key, i)?); + }, + (Some(keyblob_key), Some(keyblob_mac_key), ref mut encrypted_keyblob @ None, Some(keyblob)) => { + **encrypted_keyblob = Some(keyblob.encrypt(keyblob_key, keyblob_mac_key, i)?); }, _ => continue }