195 lines
8.1 KiB
C
Raw Normal View History

#include "cia.h"
#include "ff.h"
#include "aes.h"
#include "sha.h"
u32 ValidateCiaHeader(CiaHeader* header) {
if ((header->size_header != CIA_HEADER_SIZE) ||
(header->size_cert != CIA_CERT_SIZE) ||
(header->size_ticket != CIA_TICKET_SIZE) ||
(header->size_tmd < CIA_TMD_SIZE_MIN) ||
(header->size_tmd > CIA_TMD_SIZE_MAX) ||
(header->size_content == 0) ||
((header->size_meta != 0) && (header->size_meta != CIA_META_SIZE)))
return 1;
return 0;
}
u32 GetCiaInfo(CiaInfo* info, CiaHeader* header) {
memcpy(info, header, 0x20); // take over first 0x20 byte
info->offset_cert = align(header->size_header, 64);
info->offset_ticket = info->offset_cert + align(header->size_cert, 64);
info->offset_tmd = info->offset_ticket + align(header->size_ticket, 64);
info->offset_content = info->offset_tmd + align(header->size_tmd, 64);
info->offset_meta = (header->size_meta) ? info->offset_content + align(header->size_content, 64) : 0;
info->offset_content_list = info->offset_tmd + sizeof(TitleMetaData);
info->size_content_list = info->size_tmd - sizeof(TitleMetaData);
info->size_cia = (header->size_meta) ? info->offset_meta + info->size_meta :
info->offset_content + info->size_content;
info->max_contents = (info->size_tmd - sizeof(TitleMetaData)) / sizeof(TmdContentChunk);
return 0;
}
u32 GetCiaContentInfo(CiaContentInfo* contents, TitleMetaData* tmd) {
TmdContentChunk* content_list = (TmdContentChunk*) (tmd + 1);
u32 content_count = getbe16(tmd->content_count);
u64 next_offset = 0;
for (u32 i = 0; (i < content_count) && (i < CIA_MAX_CONTENTS); i++) {
contents[i].offset = next_offset;
contents[i].size = getbe64(content_list[i].size);
contents[i].id = getbe32(content_list[i].id);
contents[i].index = getbe16(content_list[i].index);
contents[i].encrypted = getbe16(content_list[i].type) & 0x1;
next_offset += contents[i].size;
}
return 0;
}
u32 GetTitleKey(u8* titlekey, Ticket* ticket) {
// From https://github.com/profi200/Project_CTR/blob/master/makerom/pki/prod.h#L19
static const u8 common_keyy[6][16] = {
{0xD0, 0x7B, 0x33, 0x7F, 0x9C, 0xA4, 0x38, 0x59, 0x32, 0xA2, 0xE2, 0x57, 0x23, 0x23, 0x2E, 0xB9} , // 0 - eShop Titles
{0x0C, 0x76, 0x72, 0x30, 0xF0, 0x99, 0x8F, 0x1C, 0x46, 0x82, 0x82, 0x02, 0xFA, 0xAC, 0xBE, 0x4C} , // 1 - System Titles
{0xC4, 0x75, 0xCB, 0x3A, 0xB8, 0xC7, 0x88, 0xBB, 0x57, 0x5E, 0x12, 0xA1, 0x09, 0x07, 0xB8, 0xA4} , // 2
{0xE4, 0x86, 0xEE, 0xE3, 0xD0, 0xC0, 0x9C, 0x90, 0x2F, 0x66, 0x86, 0xD4, 0xC0, 0x6F, 0x64, 0x9F} , // 3
{0xED, 0x31, 0xBA, 0x9C, 0x04, 0xB0, 0x67, 0x50, 0x6C, 0x44, 0x97, 0xA3, 0x5B, 0x78, 0x04, 0xFC} , // 4
{0x5E, 0x66, 0x99, 0x8A, 0xB4, 0xE8, 0x93, 0x16, 0x06, 0x85, 0x0F, 0xD7, 0xA1, 0x6D, 0xD7, 0x55} , // 5
};
// From https://github.com/profi200/Project_CTR/blob/master/makerom/pki/dev.h#L21
/* static const u8 common_key_devkit[6][16] = { // unused atm!
{0x55, 0xA3, 0xF8, 0x72, 0xBD, 0xC8, 0x0C, 0x55, 0x5A, 0x65, 0x43, 0x81, 0x13, 0x9E, 0x15, 0x3B} , // 0 - eShop Titles
{0x44, 0x34, 0xED, 0x14, 0x82, 0x0C, 0xA1, 0xEB, 0xAB, 0x82, 0xC1, 0x6E, 0x7B, 0xEF, 0x0C, 0x25} , // 1 - System Titles
{0xF6, 0x2E, 0x3F, 0x95, 0x8E, 0x28, 0xA2, 0x1F, 0x28, 0x9E, 0xEC, 0x71, 0xA8, 0x66, 0x29, 0xDC} , // 2
{0x2B, 0x49, 0xCB, 0x6F, 0x99, 0x98, 0xD9, 0xAD, 0x94, 0xF2, 0xED, 0xE7, 0xB5, 0xDA, 0x3E, 0x27} , // 3
{0x75, 0x05, 0x52, 0xBF, 0xAA, 0x1C, 0x04, 0x07, 0x55, 0xC8, 0xD5, 0x9A, 0x55, 0xF9, 0xAD, 0x1F} , // 4
{0xAA, 0xDA, 0x4C, 0xA8, 0xF6, 0xE5, 0xA9, 0x77, 0xE0, 0xA0, 0xF9, 0xE4, 0x76, 0xCF, 0x0D, 0x63} , // 5
};*/
// setup key 0x3D
if (ticket->commonkey_idx >= 6) return 1;
setup_aeskeyY(0x3D, (void*) common_keyy[ticket->commonkey_idx >= 6]);
use_aeskey(0x3D);
// grab and decrypt the titlekey
u8 ctr[16] = { 0 };
memcpy(ctr, ticket->title_id, 8);
memcpy(titlekey, ticket->titlekey, 16);
set_ctr(ctr);
aes_decrypt(titlekey, titlekey, 1, AES_CNT_TITLEKEY_DECRYPT_MODE);
return 0;
}
u32 BuildCiaCert(u8* ciacert) {
const u8 cert_hash_expected[0x20] = {
0xC7, 0x2E, 0x1C, 0xA5, 0x61, 0xDC, 0x9B, 0xC8, 0x05, 0x58, 0x58, 0x9C, 0x63, 0x08, 0x1C, 0x8A,
0x10, 0x78, 0xDF, 0x42, 0x99, 0x80, 0x3A, 0x68, 0x58, 0xF0, 0x41, 0xF9, 0xCB, 0x10, 0xE6, 0x35
};
// open certs.db file on SysNAND
FIL db;
UINT bytes_read;
if (f_open(&db, "1:/dbs/certs.db", FA_READ | FA_OPEN_EXISTING) != FR_OK)
return 1;
// grab CIA cert from 4 offsets
f_lseek(&db, 0x0C10);
f_read(&db, ciacert + 0x000, 0x1F0, &bytes_read);
f_lseek(&db, 0x3A00);
f_read(&db, ciacert + 0x1F0, 0x210, &bytes_read);
f_lseek(&db, 0x3F10);
f_read(&db, ciacert + 0x400, 0x300, &bytes_read);
f_lseek(&db, 0x3C10);
f_read(&db, ciacert + 0x700, 0x300, &bytes_read);
f_close(&db);
// check the certificate hash
u8 cert_hash[0x20];
sha_quick(cert_hash, ciacert, CIA_CERT_SIZE, SHA256_MODE);
if (memcmp(cert_hash, cert_hash_expected, 0x20) != 0)
return 1;
return 0;
}
u32 BuildFakeTicket(Ticket* ticket, u8* title_id) {
const u8 sig_type[4] = { 0x00, 0x01, 0x00, 0x04 }; // RSA_2048 SHA256
const u8 ticket_cnt_index[] = { // whatever this is
0x00, 0x01, 0x00, 0x14, 0x00, 0x00, 0x00, 0xAC, 0x00, 0x00, 0x00, 0x14, 0x00, 0x01, 0x00, 0x14,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x84,
0x00, 0x00, 0x00, 0x84, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
};
// set ticket all zero for a clean start
memset(ticket, 0x00, sizeof(Ticket));
// fill ticket values
memcpy(ticket->sig_type, sig_type, 4);
memset(ticket->signature, 0xFF, 0x100);
snprintf((char*) ticket->issuer, 0x40, "Root-CA00000003-XS0000000c");
memset(ticket->ecdsa, 0xFF, 0x3C);
ticket->version = 0x01;
memset(ticket->titlekey, 0xFF, 16);
memcpy(ticket->title_id, title_id, 8);
ticket->commonkey_idx = 0x01;
ticket->audit = 0x01; // whatever
memcpy(ticket->content_index, ticket_cnt_index, sizeof(ticket_cnt_index));
return 0;
}
u32 BuildFakeTmd(TitleMetaData* tmd, u8* title_id, u32 n_contents) {
const u8 sig_type[4] = { 0x00, 0x01, 0x00, 0x04 }; // RSA_2048 SHA256
// safety check: number of contents
if (n_contents > 64) return 1; // !!!
// set TMD all zero for a clean start
memset(tmd, 0x00, sizeof(TitleMetaData) + (n_contents * sizeof(TmdContentChunk)));
// file TMD values
memcpy(tmd->sig_type, sig_type, 4);
memset(tmd->signature, 0xFF, 0x100);
snprintf((char*) tmd->issuer, 0x40, "Root-CA00000003-CP0000000b");
tmd->version = 0x01;
memcpy(tmd->title_id, title_id, 8);
tmd->title_type[3] = 0x40; // whatever
memset(tmd->save_size, 0x00, 4); // placeholder
tmd->content_count[1] = (u8) n_contents;
memset(tmd->contentinfo_hash, 0xFF, 0x20); // placeholder (hash)
tmd->contentinfo[0].cmd_count[1] = (u8) n_contents;
memset(tmd->contentinfo[0].hash, 0xFF, 0x20); // placeholder (hash)
// nothing to do for content list (yet)
return 0;
}
u32 LoadCiaStub(CiaStub* stub, const char* path) {
FIL file;
UINT bytes_read;
CiaInfo info;
if (f_open(&file, path, FA_READ | FA_OPEN_EXISTING) != FR_OK)
return 1;
// first 0x20 byte of CIA header
f_lseek(&file, 0);
if ((f_read(&file, stub, 0x20, &bytes_read) != FR_OK) || (bytes_read != 0x20) ||
(ValidateCiaHeader(&(stub->header)) != 0)) {
f_close(&file);
return 1;
}
GetCiaInfo(&info, &(stub->header));
// everything up till content offset
f_lseek(&file, 0);
if ((f_read(&file, stub, info.offset_content, &bytes_read) != FR_OK) || (bytes_read != info.offset_content)) {
f_close(&file);
return 1;
}
f_close(&file);
return 0;
}