2016-11-25 18:30:01 +01:00
|
|
|
#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;
|
2016-11-30 23:49:31 +01:00
|
|
|
|
|
|
|
info->max_contents = (info->size_tmd - sizeof(TitleMetaData)) / sizeof(TmdContentChunk);
|
2016-11-25 18:30:01 +01:00
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2016-12-09 15:33:04 +01:00
|
|
|
u32 GetTmdCtr(u8* ctr, TmdContentChunk* chunk) {
|
2016-12-08 22:06:47 +01:00
|
|
|
memset(ctr, 0, 16);
|
|
|
|
memcpy(ctr, chunk->index, 2);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2016-12-13 00:20:00 +01:00
|
|
|
u32 FixTmdHashes(TitleMetaData* tmd) {
|
|
|
|
TmdContentChunk* content_list = (TmdContentChunk*) (tmd + 1);
|
|
|
|
u32 content_count = getbe16(tmd->content_count);
|
|
|
|
// recalculate content info hashes
|
|
|
|
for (u32 i = 0, kc = 0; i < 64 && kc < content_count; i++) {
|
|
|
|
TmdContentInfo* info = tmd->contentinfo + i;
|
|
|
|
u32 k = getbe16(info->cmd_count);
|
|
|
|
sha_quick(info->hash, content_list + kc, k * sizeof(TmdContentChunk), SHA256_MODE);
|
|
|
|
kc += k;
|
|
|
|
}
|
|
|
|
sha_quick(tmd->contentinfo_hash, (u8*)tmd->contentinfo, 64 * sizeof(TmdContentInfo), SHA256_MODE);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2016-11-25 18:30:01 +01:00
|
|
|
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);
|
2016-12-09 15:33:04 +01:00
|
|
|
snprintf((char*) ticket->issuer, 0x40, CIA_TICKET_ISSUER);
|
2016-11-25 18:30:01 +01:00
|
|
|
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);
|
2016-12-09 15:33:04 +01:00
|
|
|
snprintf((char*) tmd->issuer, 0x40, CIA_TMD_ISSUER);
|
2016-11-25 18:30:01 +01:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2016-12-13 00:20:00 +01:00
|
|
|
u32 DecryptCiaContentSequential(u8* data, u32 size, u8* ctr, const u8* titlekey) {
|
2016-12-08 22:06:47 +01:00
|
|
|
// WARNING: size and offset of data have to be a multiple of 16
|
|
|
|
u8 tik[16] __attribute__((aligned(32)));
|
|
|
|
u32 mode = AES_CNT_TITLEKEY_DECRYPT_MODE;
|
|
|
|
memcpy(tik, titlekey, 16);
|
|
|
|
setup_aeskey(0x11, tik);
|
|
|
|
use_aeskey(0x11);
|
|
|
|
cbc_decrypt(data, data, size / 16, mode, ctr);
|
2016-11-25 18:30:01 +01:00
|
|
|
return 0;
|
|
|
|
}
|