diff --git a/arm9/src/install.c b/arm9/src/install.c index 020ccb6..6304660 100644 --- a/arm9/src/install.c +++ b/arm9/src/install.c @@ -3,7 +3,9 @@ #include "main.h" #include "message.h" #include "maketmd.h" +#include "nand/crypto.h" #include "nand/nandio.h" +#include "nand/ticket0.h" #include "rom.h" #include "storage.h" #include @@ -282,6 +284,72 @@ static void _createBannerSav(tDSiHeader* h, char* dataPath) } } +static void _createTicket(tDSiHeader *h, char* ticketPath) +{ + if (!h) return; + + iprintf("Forging ticket..."); + swiWaitForVBlank(); + + if (!ticketPath) + { + iprintf("\x1B[31m"); //red + iprintf("Failed\n"); + iprintf("\x1B[47m"); //white + } + else + { + const u32 encryptedSize = sizeof(ticket_v0_t) + 0x20; + u8 *buffer = (u8*)memalign(4, encryptedSize); //memalign might be needed for encryption, but not sure + memset(buffer, 0, encryptedSize); + ticket_v0_t *ticket = (ticket_v0_t*)buffer; + ticket->sig_type[0] = 0x00; + ticket->sig_type[1] = 0x01; + ticket->sig_type[2] = 0x00; + ticket->sig_type[3] = 0x01; + strcpy(ticket->issuer, "Root-CA00000001-XS00000006"); + PUT_UINT32_BE(h->tid_high, ticket->title_id, 0); + PUT_UINT32_BE(h->tid_low, ticket->title_id, 4); + memset(ticket->content_access_permissions, 0xFF, 0x20); + + // Encrypt + if (dsi_es_block_crypt(buffer, encryptedSize, ENCRYPT) != 0) + { + iprintf("\x1B[31m"); //red + iprintf("Failed\n"); + iprintf("\x1B[47m"); //white + free(buffer); + return; + } + + FILE *file = fopen(ticketPath, "wb"); + if (!file) + { + iprintf("\x1B[31m"); //red + iprintf("Failed\n"); + iprintf("\x1B[47m"); //white + free(buffer); + return; + } + + if (fwrite(buffer, 1, encryptedSize, file) != encryptedSize) + { + iprintf("\x1B[31m"); //red + iprintf("Failed\n"); + iprintf("\x1B[47m"); //white + } + else + { + iprintf("\x1B[42m"); //green + iprintf("Done\n"); + iprintf("\x1B[47m"); //white + } + + free(buffer); + fclose(file); + } +} + bool install(char* fpath, bool systemTitle) { bool result = false; @@ -360,12 +428,21 @@ bool install(char* fpath, bool systemTitle) clearScreen(&bottomScreen); iprintf("Installing %s\n\n", fpath); swiWaitForVBlank(); + //check for legit TMD, if found we'll generate a ticket which increases the size + int extensionPos = strrchr(fpath, '.') - fpath; + char tmdPath[PATH_MAX]; + strcpy(tmdPath, fpath); + strcpy(tmdPath + extensionPos, ".tmd"); + //DSi TMDs are 520, TMDs from NUS are 2,312. If 2,312 we can simply trim it to 520 + bool tmdFound = (getFileSizePath(tmdPath) >= 520) || (getFileSizePath(tmdPath) == 2312); + //get install size iprintf("Install Size: "); swiWaitForVBlank(); unsigned long long fileSize = getRomSize(fpath); unsigned long long installSize = fileSize + _getSaveDataSize(h); + if (tmdFound) installSize += 708; printBytes(installSize); iprintf("\n"); @@ -404,9 +481,7 @@ bool install(char* fpath, bool systemTitle) } } - //check for saves or legit tmd - int extensionPos = strrchr(fpath, '.') - fpath; - + //check for saves char pubPath[PATH_MAX]; strcpy(pubPath, fpath); strcpy(pubPath + extensionPos, ".pub"); @@ -443,11 +518,6 @@ bool install(char* fpath, bool systemTitle) goto error; } - char tmdPath[PATH_MAX]; - strcpy(tmdPath, fpath); - strcpy(tmdPath + extensionPos, ".tmd"); - bool tmdFound = getFileSizePath(tmdPath) == 520; - if (_iqueHack(h)) fixHeader = true; @@ -607,7 +677,7 @@ bool install(char* fpath, bool systemTitle) sprintf(newTmdPath, "%s/title.tmd", contentPath); if (tmdFound) { - if (copyFile(tmdPath, newTmdPath) != 0) + if (copyFilePart(tmdPath, 0, 520, newTmdPath) != 0) goto error; } else @@ -659,6 +729,23 @@ bool install(char* fpath, bool systemTitle) } } + //ticket folder /ticket/XXXXXXXX + if (tmdFound) + { + //ensure folders exist + char ticketPath[32]; + siprintf(ticketPath, "%s:/ticket", sdnandMode ? "sd" : "nand"); + mkdir(ticketPath, 0777); + siprintf(ticketPath, "%s/%08lx", ticketPath, h->tid_high); + mkdir(ticketPath, 0777); + + //actual tik path + siprintf(ticketPath, "%s/%08lx.tik", ticketPath, h->tid_low); + + if (access(ticketPath, F_OK) != 0 || choicePrint("Ticket already exists.\nKeep it?") == NO) + _createTicket(h, ticketPath); + } + //end result = true; iprintf("\x1B[42m"); //green diff --git a/arm9/src/nand/crypto.c b/arm9/src/nand/crypto.c index 93c0505..173cd46 100644 --- a/arm9/src/nand/crypto.c +++ b/arm9/src/nand/crypto.c @@ -11,7 +11,7 @@ static dsi_context nand_ctx ; static dsi_context boot2_ctx ; -static dsi_context es_ctx ; +static dsi_es_context es_ctx ; static uint8_t nand_ctr_iv[16]; static uint8_t boot2_ctr[16]; @@ -62,13 +62,17 @@ void dsi_crypt_init(const uint8_t *console_id_be, const uint8_t *emmc_cid, int i GET_UINT32_BE(console_id[1], console_id_be, 0); uint8_t key[16]; - generate_key(key, console_id, is3DS ? NAND_3DS : NAND); - dsi_set_key(&nand_ctx, key) ; + generate_key(key, console_id, is3DS ? NAND_3DS : NAND); + dsi_set_key(&nand_ctx, key) ; - generate_key(key, console_id, ES); - dsi_set_key(&es_ctx, key) ; + u32 normalkey[4]; + u32 tadsrl_keyX[4] = {0x4E00004A, 0x4A00004E, 0, 0}; + tadsrl_keyX[2] = console_id[1] ^ 0xC80C4B72; + tadsrl_keyX[3] = console_id[0]; + F_XY((u8 *)normalkey, (u8 *)tadsrl_keyX, DSi_ES_KEY_Y); + dsi_es_init(&es_ctx, (u8*)normalkey); - dsi_set_key(&boot2_ctx, DSi_BOOT2_KEY) ; + dsi_set_key(&boot2_ctx, DSi_BOOT2_KEY) ; swiSHA1Calc(nand_ctr_iv, emmc_cid, 16); @@ -99,7 +103,17 @@ void dsi_nand_crypt(uint8_t* out, const uint8_t* in, uint32_t offset, unsigned c u128_add32(ctr, 1); } } - + +int dsi_es_block_crypt(uint8_t *buf, unsigned buf_len, crypt_mode_t mode) +{ + if(mode == DECRYPT) + return dsi_es_decrypt(&es_ctx, buf, buf + buf_len - 0x20, buf_len - 0x20); + else + dsi_es_encrypt(&es_ctx, buf, buf + buf_len - 0x20, buf_len - 0x20); + + return 0; +} + void dsi_boot2_crypt_set_ctr(uint32_t size_r) { for (int i=0;i<4;i++) diff --git a/arm9/src/nand/ticket0.h b/arm9/src/nand/ticket0.h new file mode 100644 index 0000000..a143c6a --- /dev/null +++ b/arm9/src/nand/ticket0.h @@ -0,0 +1,132 @@ +#pragma once + +#include +#include + +#ifdef _MSC_VER +#pragma pack(push, 1) +#define PACKED +#elif ! defined PACKED +#define PACKED __attribute__ ((__packed__)) +#endif + +#define RSA_2048_LEN (2048/8) + +// most, if not all, are big endian + +// http://problemkaputt.de/gbatek.htm#dsisdmmcdsiwareticketsandtitlemetadata +// http://dsibrew.org/wiki/Ticket +// http://wiibrew.org/wiki/Ticket +typedef struct { + uint8_t sig_type[4]; + uint8_t sig[RSA_2048_LEN]; + uint8_t padding0[0x3c]; + char issuer[0x40]; + uint8_t ecdh[0x3c]; + uint8_t padding1[3]; + uint8_t encrypted_title_key[0x10]; + uint8_t unknown0; + uint8_t ticket_id[8]; + uint8_t console_id[4]; + uint8_t title_id[8]; + uint8_t unknown1[2]; + uint8_t version[2]; + uint8_t permitted_titles_mask[4]; + uint8_t permit_mask[4]; + uint8_t title_export_allowed; + uint8_t common_key_index; + uint8_t unknown[0x30]; + uint8_t content_access_permissions[0x40]; + uint8_t padding2[2]; + uint8_t time_limits[2 * 8 * sizeof(uint32_t)]; +} PACKED ticket_v0_t; + +static_assert(sizeof(ticket_v0_t) == 0x2a4, "invalid sizeof(ticket_v0_t)"); + +// http://dsibrew.org/wiki/Tmd +// http://wiibrew.org/wiki/Title_metadata +typedef struct { + uint8_t sig_type[4]; + uint8_t sig[RSA_2048_LEN]; + uint8_t padding0[0x3c]; + char issuer[0x40]; + uint8_t version; + uint8_t ca_crl_version; + uint8_t signer_crl_version; + uint8_t padding1; + uint8_t system_version[8]; + uint8_t title_id[8]; + uint8_t title_type[4]; + uint8_t group_id[2]; + uint8_t public_save_size[4]; + uint8_t private_save_size[4]; + uint8_t padding2[8]; + uint8_t parent_control[0x10]; + uint8_t padding3[0x1e]; + uint8_t access_rights[4]; + uint8_t title_version[2]; + uint8_t num_content[2]; + uint8_t boot_index[2]; + uint8_t padding4[2]; +} PACKED tmd_header_v0_t; + +static_assert(sizeof(tmd_header_v0_t) == 0x1e4, "invalid sizeof(tmd_header_v0_t)"); + +typedef struct { + uint8_t content_id[4]; + uint8_t index[2]; + uint8_t type[2]; + uint8_t size[8]; + uint8_t sha1[20]; +} PACKED tmd_content_v0_t; + +static_assert(sizeof(tmd_content_v0_t) == 0x24, "invalid sizeof(tmd_contend_v0_t)"); + +// used in ticket encryption +// http://problemkaputt.de/gbatek.htm#dsiesblockencryption + +#define AES_CCM_MAC_LEN 0x10 +#define AES_CCM_NONCE_LEN 0x0c + +typedef struct { + uint8_t ccm_mac[AES_CCM_MAC_LEN]; + union { + struct { + uint8_t fixed_3a; + uint8_t nonce[AES_CCM_NONCE_LEN]; + uint8_t len24be[3]; + }; + struct { + uint8_t padding[AES_CCM_NONCE_LEN]; + // defined for convenience, it's still big endian with only 24 effective bits + // read it as 32 bit big endian and discard the highest 8 bits + uint8_t len32be[4]; + }; + uint8_t encrypted[0x10]; + }; +} PACKED es_block_footer_t; + +static_assert(sizeof(es_block_footer_t) == 0x20, "invalid sizeof(es_block_footer_t)"); + +// used in cert.sys +// http://problemkaputt.de/gbatek.htm#dsisdmmcfirmwaredevkpandcertsyscertificatefiles +// "DSi SD/MMC Firmware dev.kp and cert.sys Certificate Files" +typedef struct { + uint32_t signature_type; + uint8_t signature[RSA_2048_LEN]; + uint8_t padding0[0x3c]; + char signature_name[0x40]; + uint32_t key_type; + char key_name[0x40]; + uint32_t key_flags; + uint8_t rsa_key[RSA_2048_LEN]; + uint8_t rsa_exp[4]; + uint8_t padding1[0x34]; +} PACKED cert_t; + +static_assert(sizeof(cert_t) == 0x300, "invalid sizeof(cert_t)"); + +#ifdef _MSC_VER +#pragma pack(pop) +#endif +#undef PACKED