Added support for dev WADs, Ticket now throws error for v1 Tickets

This commit is contained in:
Campbell 2025-03-20 10:38:33 -04:00
parent 5731b8d4d8
commit 561ada3d92
Signed by: NinjaCheetah
GPG Key ID: 39C2500E1778B156
4 changed files with 68 additions and 29 deletions

6
src/title/cert.rs Normal file
View File

@ -0,0 +1,6 @@
// title/cert.rs from rustii (c) 2025 NinjaCheetah & Contributors
// https://github.com/NinjaCheetah/rustii
//
// Implements the structures and methods required for validating the signatures of Wii titles.

View File

@ -15,20 +15,20 @@ fn title_id_to_iv(title_id: [u8; 8]) -> [u8; 16] {
} }
// Decrypt a Title Key using the specified common key. // Decrypt a Title Key using the specified common key.
pub fn decrypt_title_key(title_key_enc: [u8; 16], common_key_index: u8, title_id: [u8; 8]) -> [u8; 16] { pub fn decrypt_title_key(title_key_enc: [u8; 16], common_key_index: u8, title_id: [u8; 8], is_dev: Option<bool>) -> [u8; 16] {
let iv = title_id_to_iv(title_id); let iv = title_id_to_iv(title_id);
type Aes128CbcDec = cbc::Decryptor<aes::Aes128>; type Aes128CbcDec = cbc::Decryptor<aes::Aes128>;
let decryptor = Aes128CbcDec::new(&get_common_key(common_key_index, None).into(), &iv.into()); let decryptor = Aes128CbcDec::new(&get_common_key(common_key_index, is_dev).into(), &iv.into());
let mut title_key = title_key_enc; let mut title_key = title_key_enc;
decryptor.decrypt_padded_mut::<ZeroPadding>(&mut title_key).unwrap(); decryptor.decrypt_padded_mut::<ZeroPadding>(&mut title_key).unwrap();
title_key title_key
} }
// Encrypt a Title Key using the specified common key. // Encrypt a Title Key using the specified common key.
pub fn encrypt_title_key(title_key_dec: [u8; 16], common_key_index: u8, title_id: [u8; 8]) -> [u8; 16] { pub fn encrypt_title_key(title_key_dec: [u8; 16], common_key_index: u8, title_id: [u8; 8], is_dev: Option<bool>) -> [u8; 16] {
let iv = title_id_to_iv(title_id); let iv = title_id_to_iv(title_id);
type Aes128CbcEnc = cbc::Encryptor<aes::Aes128>; type Aes128CbcEnc = cbc::Encryptor<aes::Aes128>;
let encryptor = Aes128CbcEnc::new(&get_common_key(common_key_index, None).into(), &iv.into()); let encryptor = Aes128CbcEnc::new(&get_common_key(common_key_index, is_dev).into(), &iv.into());
let mut title_key = title_key_dec; let mut title_key = title_key_dec;
encryptor.encrypt_padded_mut::<ZeroPadding>(&mut title_key, 16).unwrap(); encryptor.encrypt_padded_mut::<ZeroPadding>(&mut title_key, 16).unwrap();
title_key title_key

View File

@ -9,6 +9,7 @@ pub mod crypto;
pub mod ticket; pub mod ticket;
pub mod tmd; pub mod tmd;
pub mod wad; pub mod wad;
mod cert;
use std::error::Error; use std::error::Error;
use std::fmt; use std::fmt;

View File

@ -3,10 +3,30 @@
// //
// Implements the structures and methods required for Ticket parsing and editing. // Implements the structures and methods required for Ticket parsing and editing.
use std::error::Error;
use std::fmt;
use std::io::{Cursor, Read, Write}; use std::io::{Cursor, Read, Write};
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
use crate::title::crypto::decrypt_title_key; use crate::title::crypto::decrypt_title_key;
#[derive(Debug)]
pub enum TicketError {
UnsupportedVersion,
IOError(std::io::Error),
}
impl fmt::Display for TicketError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let description = match *self {
TicketError::UnsupportedVersion => "The provided Ticket is not a supported version (only v0 is supported).",
TicketError::IOError(_) => "The provided Ticket data was invalid.",
};
f.write_str(description)
}
}
impl Error for TicketError {}
#[derive(Debug)] #[derive(Debug)]
#[derive(Copy)] #[derive(Copy)]
#[derive(Clone)] #[derive(Clone)]
@ -44,51 +64,55 @@ pub struct Ticket {
} }
impl Ticket { impl Ticket {
pub fn from_bytes(data: &[u8]) -> Result<Self, std::io::Error> { pub fn from_bytes(data: &[u8]) -> Result<Self, TicketError> {
let mut buf = Cursor::new(data); let mut buf = Cursor::new(data);
let signature_type = buf.read_u32::<BigEndian>()?; let signature_type = buf.read_u32::<BigEndian>().map_err(TicketError::IOError)?;
let mut signature = [0u8; 256]; let mut signature = [0u8; 256];
buf.read_exact(&mut signature)?; buf.read_exact(&mut signature).map_err(TicketError::IOError)?;
// Maybe this can be read differently? // Maybe this can be read differently?
let mut padding1 = [0u8; 60]; let mut padding1 = [0u8; 60];
buf.read_exact(&mut padding1)?; buf.read_exact(&mut padding1).map_err(TicketError::IOError)?;
let mut signature_issuer = [0u8; 64]; let mut signature_issuer = [0u8; 64];
buf.read_exact(&mut signature_issuer)?; buf.read_exact(&mut signature_issuer).map_err(TicketError::IOError)?;
let mut ecdh_data = [0u8; 60]; let mut ecdh_data = [0u8; 60];
buf.read_exact(&mut ecdh_data)?; buf.read_exact(&mut ecdh_data).map_err(TicketError::IOError)?;
let ticket_version = buf.read_u8()?; let ticket_version = buf.read_u8().map_err(TicketError::IOError)?;
// v1 Tickets are NOT supported (just like in libWiiPy).
if ticket_version != 0 {
return Err(TicketError::UnsupportedVersion);
}
let mut reserved1 = [0u8; 2]; let mut reserved1 = [0u8; 2];
buf.read_exact(&mut reserved1)?; buf.read_exact(&mut reserved1).map_err(TicketError::IOError)?;
let mut title_key = [0u8; 16]; let mut title_key = [0u8; 16];
buf.read_exact(&mut title_key)?; buf.read_exact(&mut title_key).map_err(TicketError::IOError)?;
let mut unknown1 = [0u8; 1]; let mut unknown1 = [0u8; 1];
buf.read_exact(&mut unknown1)?; buf.read_exact(&mut unknown1).map_err(TicketError::IOError)?;
let mut ticket_id = [0u8; 8]; let mut ticket_id = [0u8; 8];
buf.read_exact(&mut ticket_id)?; buf.read_exact(&mut ticket_id).map_err(TicketError::IOError)?;
let mut console_id = [0u8; 4]; let mut console_id = [0u8; 4];
buf.read_exact(&mut console_id)?; buf.read_exact(&mut console_id).map_err(TicketError::IOError)?;
let mut title_id = [0u8; 8]; let mut title_id = [0u8; 8];
buf.read_exact(&mut title_id)?; buf.read_exact(&mut title_id).map_err(TicketError::IOError)?;
let mut unknown2 = [0u8; 2]; let mut unknown2 = [0u8; 2];
buf.read_exact(&mut unknown2)?; buf.read_exact(&mut unknown2).map_err(TicketError::IOError)?;
let title_version = buf.read_u16::<BigEndian>()?; let title_version = buf.read_u16::<BigEndian>().map_err(TicketError::IOError)?;
let mut permitted_titles_mask = [0u8; 4]; let mut permitted_titles_mask = [0u8; 4];
buf.read_exact(&mut permitted_titles_mask)?; buf.read_exact(&mut permitted_titles_mask).map_err(TicketError::IOError)?;
let mut permit_mask = [0u8; 4]; let mut permit_mask = [0u8; 4];
buf.read_exact(&mut permit_mask)?; buf.read_exact(&mut permit_mask).map_err(TicketError::IOError)?;
let title_export_allowed = buf.read_u8()?; let title_export_allowed = buf.read_u8().map_err(TicketError::IOError)?;
let common_key_index = buf.read_u8()?; let common_key_index = buf.read_u8().map_err(TicketError::IOError)?;
let mut unknown3 = [0u8; 48]; let mut unknown3 = [0u8; 48];
buf.read_exact(&mut unknown3)?; buf.read_exact(&mut unknown3).map_err(TicketError::IOError)?;
let mut content_access_permission = [0u8; 64]; let mut content_access_permission = [0u8; 64];
buf.read_exact(&mut content_access_permission)?; buf.read_exact(&mut content_access_permission).map_err(TicketError::IOError)?;
let mut padding2 = [0u8; 2]; let mut padding2 = [0u8; 2];
buf.read_exact(&mut padding2)?; buf.read_exact(&mut padding2).map_err(TicketError::IOError)?;
// Build the array of title limits. // Build the array of title limits.
let mut title_limits: Vec<TitleLimit> = Vec::new(); let mut title_limits: Vec<TitleLimit> = Vec::new();
for _ in 0..8 { for _ in 0..8 {
let limit_type = buf.read_u32::<BigEndian>()?; let limit_type = buf.read_u32::<BigEndian>().map_err(TicketError::IOError)?;
let limit_max = buf.read_u32::<BigEndian>()?; let limit_max = buf.read_u32::<BigEndian>().map_err(TicketError::IOError)?;
title_limits.push(TitleLimit { limit_type, limit_max }); title_limits.push(TitleLimit { limit_type, limit_max });
} }
let title_limits = title_limits.try_into().unwrap(); let title_limits = title_limits.try_into().unwrap();
@ -150,6 +174,14 @@ impl Ticket {
} }
pub fn dec_title_key(&self) -> [u8; 16] { pub fn dec_title_key(&self) -> [u8; 16] {
decrypt_title_key(self.title_key, self.common_key_index, self.title_id) // Get the dev status of this Ticket so decrypt_title_key knows the right common key.
let is_dev = self.is_dev();
decrypt_title_key(self.title_key, self.common_key_index, self.title_id, Some(is_dev))
}
pub fn is_dev(&self) -> bool {
// Parse the signature issuer to determine if this is a dev Ticket or not.
let issuer_str = String::from_utf8(Vec::from(&self.signature_issuer)).unwrap_or_default();
issuer_str.contains("Root-CA00000002-XS00000004") || issuer_str.contains("Root-CA00000002-XS00000006")
} }
} }