mirror of
https://github.com/NinjaCheetah/rustii.git
synced 2026-03-04 11:55:27 -05:00
Added signature verification, info command now has WiiPy feature parity
Some checks are pending
Build rustii / build-linux-x86_64 (push) Waiting to run
Build rustii / build-macos-arm64 (push) Waiting to run
Build rustii / build-macos-x86_64 (push) Waiting to run
Build rustii / build-windows-x86_64 (push) Waiting to run
Some checks are pending
Build rustii / build-linux-x86_64 (push) Waiting to run
Build rustii / build-macos-arm64 (push) Waiting to run
Build rustii / build-macos-x86_64 (push) Waiting to run
Build rustii / build-windows-x86_64 (push) Waiting to run
rustii CLI info command now displays the signing status of TMDs/Tickets/WADs like WiiPy does, and displays the ASCII TID for a title when applicable. This means that this command now has full feature parity with WiiPy.
This commit is contained in:
@@ -1,43 +1,44 @@
|
||||
// Sample file for testing rustii library stuff.
|
||||
|
||||
use std::fs;
|
||||
use rustii::title::{content, crypto, wad};
|
||||
use rustii::title::{wad, cert};
|
||||
use rustii::title;
|
||||
|
||||
fn main() {
|
||||
let data = fs::read("boot2.wad").unwrap();
|
||||
let mut title = title::Title::from_bytes(&data).unwrap();
|
||||
let data = fs::read("sm.wad").unwrap();
|
||||
let title = title::Title::from_bytes(&data).unwrap();
|
||||
println!("Title ID from WAD via Title object: {}", hex::encode(title.tmd.title_id));
|
||||
|
||||
let wad = wad::WAD::from_bytes(&data).unwrap();
|
||||
println!("size of tmd: {:?}", wad.tmd().len());
|
||||
println!("num content records: {:?}", title.tmd.content_records.len());
|
||||
println!("first record data: {:?}", title.tmd.content_records.first().unwrap());
|
||||
if !title.tmd.is_fakesigned() {
|
||||
title.tmd.fakesign().unwrap();
|
||||
}
|
||||
println!("TMD is fakesigned: {:?}",title.tmd.is_fakesigned());
|
||||
|
||||
println!("title version from ticket is: {:?}", title.ticket.title_version);
|
||||
println!("title key (enc): {:?}", title.ticket.title_key);
|
||||
println!("title key (dec): {:?}", title.ticket.dec_title_key());
|
||||
if !title.ticket.is_fakesigned() {
|
||||
title.ticket.fakesign().unwrap();
|
||||
}
|
||||
println!("ticket is fakesigned: {:?}", title.ticket.is_fakesigned());
|
||||
|
||||
println!("title is fakesigned: {:?}", title.is_fakesigned());
|
||||
|
||||
let content_region = content::ContentRegion::from_bytes(&wad.content(), title.tmd.content_records).unwrap();
|
||||
assert_eq!(wad.content(), content_region.to_bytes().unwrap());
|
||||
println!("content OK");
|
||||
|
||||
let content_dec = content_region.get_content_by_index(0, title.ticket.dec_title_key()).unwrap();
|
||||
println!("content dec from index: {:?}", content_dec);
|
||||
|
||||
let content = content_region.get_enc_content_by_index(0).unwrap();
|
||||
assert_eq!(content, crypto::encrypt_content(&content_dec, title.ticket.dec_title_key(), 0, content_region.content_records[0].content_size));
|
||||
println!("content re-encrypted OK");
|
||||
|
||||
println!("wad header: {:?}", wad.header);
|
||||
|
||||
let cert_chain = &title.cert_chain;
|
||||
println!("cert chain OK");
|
||||
let result = cert::verify_ca_cert(&cert_chain.ca_cert()).unwrap();
|
||||
println!("CA cert {} verified successfully: {}", cert_chain.ca_cert().child_cert_identity(), result);
|
||||
|
||||
let result = cert::verify_child_cert(&cert_chain.ca_cert(), &cert_chain.tmd_cert()).unwrap();
|
||||
println!("TMD cert {} verified successfully: {}", cert_chain.tmd_cert().child_cert_identity(), result);
|
||||
let result = cert::verify_tmd(&cert_chain.tmd_cert(), &title.tmd).unwrap();
|
||||
println!("TMD verified successfully: {}", result);
|
||||
|
||||
let result = cert::verify_child_cert(&cert_chain.ca_cert(), &cert_chain.ticket_cert()).unwrap();
|
||||
println!("Ticket cert {} verified successfully: {}", cert_chain.ticket_cert().child_cert_identity(), result);
|
||||
let result = cert::verify_ticket(&cert_chain.ticket_cert(), &title.ticket).unwrap();
|
||||
println!("Ticket verified successfully: {}", result);
|
||||
|
||||
let result = title.verify().unwrap();
|
||||
println!("full title verified successfully: {}", result);
|
||||
}
|
||||
|
||||
@@ -5,13 +5,27 @@
|
||||
|
||||
use std::{str, fs};
|
||||
use std::path::Path;
|
||||
use rustii::{title, title::tmd, title::ticket, title::wad, title::versions};
|
||||
use rustii::{title, title::cert, title::tmd, title::ticket, title::wad, title::versions};
|
||||
use crate::filetypes::{WiiFileType, identify_file_type};
|
||||
|
||||
fn print_tmd_info(tmd: tmd::TMD) {
|
||||
fn tid_to_ascii(tid: [u8; 8]) -> Option<String> {
|
||||
let tid = String::from_utf8_lossy(&tid[4..]).trim_end_matches('\0').trim_start_matches('\0').to_owned();
|
||||
if tid.len() == 4 {
|
||||
Some(tid)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn print_tmd_info(tmd: tmd::TMD, cert: Option<cert::Certificate>) {
|
||||
// Print all important keys from the TMD.
|
||||
println!("Title Info");
|
||||
println!(" Title ID: {}", hex::encode(tmd.title_id).to_uppercase());
|
||||
let ascii_tid = tid_to_ascii(tmd.title_id);
|
||||
if ascii_tid.is_some() {
|
||||
println!(" Title ID: {} ({})", hex::encode(tmd.title_id).to_uppercase(), ascii_tid.unwrap());
|
||||
} else {
|
||||
println!(" Title ID: {}", hex::encode(tmd.title_id).to_uppercase());
|
||||
}
|
||||
if hex::encode(tmd.title_id)[..8].eq("00000001") {
|
||||
if hex::encode(tmd.title_id).eq("0000000100000001") {
|
||||
println!(" Title Version: {} (boot2v{})", tmd.title_version, tmd.title_version);
|
||||
@@ -67,7 +81,24 @@ fn print_tmd_info(tmd: tmd::TMD) {
|
||||
println!(" vWii Title: {}", tmd.is_vwii != 0);
|
||||
println!(" DVD Video Access: {}", tmd.check_access_right(tmd::AccessRight::DVDVideo));
|
||||
println!(" AHB Access: {}", tmd.check_access_right(tmd::AccessRight::AHB));
|
||||
println!(" Fakesigned: {}", tmd.is_fakesigned());
|
||||
if cert.is_some() {
|
||||
let signing_str = match cert::verify_tmd(&cert.unwrap(), &tmd) {
|
||||
Ok(result) => match result {
|
||||
true => "Valid (Unmodified TMD)",
|
||||
false => {
|
||||
if tmd.is_fakesigned() {
|
||||
"Fakesigned"
|
||||
} else {
|
||||
"Invalid (Modified TMD)"
|
||||
}
|
||||
},
|
||||
},
|
||||
Err(_) => "Invalid (Modified TMD)"
|
||||
};
|
||||
println!(" Signature: {}", signing_str);
|
||||
} else {
|
||||
println!(" Fakesigned: {}", tmd.is_fakesigned());
|
||||
}
|
||||
println!("\nContent Info");
|
||||
println!(" Total Contents: {}", tmd.num_contents);
|
||||
println!(" Boot Content Index: {}", tmd.boot_index);
|
||||
@@ -81,10 +112,15 @@ fn print_tmd_info(tmd: tmd::TMD) {
|
||||
}
|
||||
}
|
||||
|
||||
fn print_ticket_info(ticket: ticket::Ticket) {
|
||||
fn print_ticket_info(ticket: ticket::Ticket, cert: Option<cert::Certificate>) {
|
||||
// Print all important keys from the Ticket.
|
||||
println!("Ticket Info");
|
||||
println!(" Title ID: {}", hex::encode(ticket.title_id).to_uppercase());
|
||||
let ascii_tid = tid_to_ascii(ticket.title_id);
|
||||
if ascii_tid.is_some() {
|
||||
println!(" Title ID: {} ({})", hex::encode(ticket.title_id).to_uppercase(), ascii_tid.unwrap());
|
||||
} else {
|
||||
println!(" Title ID: {}", hex::encode(ticket.title_id).to_uppercase());
|
||||
}
|
||||
if hex::encode(ticket.title_id)[..8].eq("00000001") {
|
||||
if hex::encode(ticket.title_id).eq("0000000100000001") {
|
||||
println!(" Title Version: {} (boot2v{})", ticket.title_version, ticket.title_version);
|
||||
@@ -120,7 +156,24 @@ fn print_ticket_info(ticket: ticket::Ticket) {
|
||||
println!(" Decryption Key: {}", key);
|
||||
println!(" Title Key (Encrypted): {}", hex::encode(ticket.title_key));
|
||||
println!(" Title Key (Decrypted): {}", hex::encode(ticket.dec_title_key()));
|
||||
println!(" Fakesigned: {}", ticket.is_fakesigned());
|
||||
if cert.is_some() {
|
||||
let signing_str = match cert::verify_ticket(&cert.unwrap(), &ticket) {
|
||||
Ok(result) => match result {
|
||||
true => "Valid (Unmodified Ticket)",
|
||||
false => {
|
||||
if ticket.is_fakesigned() {
|
||||
"Fakesigned"
|
||||
} else {
|
||||
"Invalid (Modified Ticket)"
|
||||
}
|
||||
},
|
||||
},
|
||||
Err(_) => "Invalid (Modified Ticket)"
|
||||
};
|
||||
println!(" Signature: {}", signing_str);
|
||||
} else {
|
||||
println!(" Fakesigned: {}", ticket.is_fakesigned());
|
||||
}
|
||||
}
|
||||
|
||||
fn print_wad_info(wad: wad::WAD) {
|
||||
@@ -147,11 +200,28 @@ fn print_wad_info(wad: wad::WAD) {
|
||||
}
|
||||
println!(" Has Meta/Footer: {}", wad.meta_size() != 0);
|
||||
println!(" Has CRL: {}", wad.crl_size() != 0);
|
||||
println!(" Fakesigned: {}", title.is_fakesigned());
|
||||
let signing_str = match title.verify() {
|
||||
Ok(result) => match result {
|
||||
true => "Legitimate (Unmodified TMD + Ticket)",
|
||||
false => {
|
||||
if title.is_fakesigned() {
|
||||
"Fakesigned"
|
||||
} else if cert::verify_tmd(&title.cert_chain.tmd_cert(), &title.tmd).unwrap() {
|
||||
"Piratelegit (Unmodified TMD, Modified Ticket)"
|
||||
} else if cert::verify_ticket(&title.cert_chain.ticket_cert(), &title.ticket).unwrap() {
|
||||
"Edited (Modified TMD, Unmodified Ticket)"
|
||||
} else {
|
||||
"Illegitimate (Modified TMD + Ticket)"
|
||||
}
|
||||
},
|
||||
},
|
||||
Err(_) => "Illegitimate (Modified TMD + Ticket)"
|
||||
};
|
||||
println!(" Signing Status: {}", signing_str);
|
||||
println!();
|
||||
print_ticket_info(title.ticket);
|
||||
print_ticket_info(title.ticket, Some(title.cert_chain.ticket_cert()));
|
||||
println!();
|
||||
print_tmd_info(title.tmd);
|
||||
print_tmd_info(title.tmd, Some(title.cert_chain.tmd_cert()));
|
||||
}
|
||||
|
||||
pub fn info(input: &str) {
|
||||
@@ -162,11 +232,11 @@ pub fn info(input: &str) {
|
||||
match identify_file_type(input) {
|
||||
Some(WiiFileType::Tmd) => {
|
||||
let tmd = tmd::TMD::from_bytes(fs::read(in_path).unwrap().as_slice()).unwrap();
|
||||
print_tmd_info(tmd);
|
||||
print_tmd_info(tmd, None);
|
||||
},
|
||||
Some(WiiFileType::Ticket) => {
|
||||
let ticket = ticket::Ticket::from_bytes(fs::read(in_path).unwrap().as_slice()).unwrap();
|
||||
print_ticket_info(ticket);
|
||||
print_ticket_info(ticket, None);
|
||||
},
|
||||
Some(WiiFileType::Wad) => {
|
||||
let wad = wad::WAD::from_bytes(fs::read(in_path).unwrap().as_slice()).unwrap();
|
||||
|
||||
@@ -7,7 +7,7 @@ use std::{str, fs};
|
||||
use std::path::{Path, PathBuf};
|
||||
use clap::Subcommand;
|
||||
use glob::glob;
|
||||
use rustii::title::{tmd, ticket, content, wad};
|
||||
use rustii::title::{cert, tmd, ticket, content, wad};
|
||||
use rustii::title;
|
||||
|
||||
#[derive(Subcommand)]
|
||||
@@ -59,7 +59,7 @@ pub fn pack_wad(input: &str, output: &str) {
|
||||
} else if cert_files.len() > 1 {
|
||||
panic!("Error: More than one Cert file found in the source directory.")
|
||||
}
|
||||
let cert_chain = fs::read(&cert_files[0]).expect("could not read cert chain file");
|
||||
let cert_chain = cert::CertificateChain::from_bytes(&fs::read(&cert_files[0]).expect("could not read cert chain file")).unwrap();
|
||||
// Read footer, if one exists (only accept one file).
|
||||
let footer_files: Vec<PathBuf> = glob(&format!("{}/*.footer", in_path.display()))
|
||||
.expect("failed to read glob pattern")
|
||||
@@ -106,7 +106,7 @@ pub fn unpack_wad(input: &str, output: &str) {
|
||||
let ticket_file_name = format!("{}.tik", tid);
|
||||
fs::write(Path::join(out_path, ticket_file_name), title.ticket.to_bytes().unwrap()).expect("could not write Ticket file");
|
||||
let cert_file_name = format!("{}.cert", tid);
|
||||
fs::write(Path::join(out_path, cert_file_name), title.cert_chain()).expect("could not write Cert file");
|
||||
fs::write(Path::join(out_path, cert_file_name), title.cert_chain.to_bytes().unwrap()).expect("could not write Cert file");
|
||||
let meta_file_name = format!("{}.footer", tid);
|
||||
fs::write(Path::join(out_path, meta_file_name), title.meta()).expect("could not write footer file");
|
||||
// Iterate over contents, decrypt them, and write them out.
|
||||
|
||||
Reference in New Issue
Block a user