Skip to content

Commit

Permalink
Merge pull request #35 from roblabla/keygen
Browse files Browse the repository at this point in the history
Keygen
  • Loading branch information
roblabla committed Jan 25, 2019
2 parents f497d4f + 4c57e2c commit 246b853
Show file tree
Hide file tree
Showing 5 changed files with 214 additions and 8 deletions.
37 changes: 37 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
19 changes: 19 additions & 0 deletions src/bin/linkle_clap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<PathBuf>,
}
}

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> {
Expand Down Expand Up @@ -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<U: ?Sized, T: AsRef<U>>(s: &Option<T>) -> Option<&U> {
s.as_ref().map(AsRef::as_ref)
}
Expand All @@ -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 {
Expand Down
8 changes: 8 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down Expand Up @@ -80,3 +82,9 @@ impl From<FromUtf8Error> 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())
}
}
157 changes: 149 additions & 8 deletions src/pki.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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(())
}
}
}
}

Expand All @@ -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<EncryptedKeyblob, Error> {
let mut encrypted_keyblob = [0; 0xB0];
encrypted_keyblob[0x20..].copy_from_slice(&self.0);

let mut crypter = Ctr128::<Aes128, ZeroPadding>::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::<Aes128>::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<Keyblob, Error> {
fn decrypt(&self, key: &Aes128Key, mac_key: &Aes128Key, keyblob_id: usize) -> Result<Keyblob, Error> {
let mut keyblob = [0; 0x90];
keyblob.copy_from_slice(&self.0[0x20..]);

let mut cmac = Cmac::<Aes128>::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::<Aes128, ZeroPadding>::new_fixkey(GenericArray::from_slice(&key.0), GenericArray::from_slice(&self.0[0x10..0x20]));
crypter.decrypt_nopad(&mut keyblob)?;

Expand Down Expand Up @@ -150,6 +179,71 @@ pub struct Keys {
package2_fixed_key_modulus: Option<Modulus>,
}

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 {
Expand All @@ -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)));
}
}
Expand All @@ -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)));
}
}
Expand Down Expand Up @@ -359,6 +457,47 @@ impl Keys {
Ok(())
}

pub fn write<W: 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 */
Expand All @@ -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
}
Expand Down

0 comments on commit 246b853

Please sign in to comment.