forked from Mirror/GodMode9
At least in the caller perspective. Also break down some functionalities into separate funcs, interally calling them on cert.c to avoid too many checks. And tried to avoid too much repeated code.
150 lines
5.4 KiB
C
150 lines
5.4 KiB
C
#include "tmd.h"
|
|
#include "unittype.h"
|
|
#include "cert.h"
|
|
#include "sha.h"
|
|
#include "rsa.h"
|
|
#include "ff.h"
|
|
|
|
u32 ValidateTmd(TitleMetaData* tmd) {
|
|
static const u8 magic[] = { TMD_SIG_TYPE };
|
|
if ((memcmp(tmd->sig_type, magic, sizeof(magic)) != 0) ||
|
|
((strncmp((char*) tmd->issuer, TMD_ISSUER, 0x40) != 0) &&
|
|
(strncmp((char*) tmd->issuer, TMD_ISSUER_DEV, 0x40) != 0)))
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
u32 ValidateTwlTmd(TitleMetaData* tmd) {
|
|
static const u8 magic[] = { TMD_SIG_TYPE_TWL };
|
|
if ((memcmp(tmd->sig_type, magic, sizeof(magic)) != 0) ||
|
|
(strncmp((char*) tmd->issuer, TMD_ISSUER_TWL, 0x40) != 0) ||
|
|
(getbe16(tmd->content_count) != 1))
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
u32 ValidateTmdSignature(TitleMetaData* tmd) {
|
|
Certificate cert;
|
|
u32 mod[2048/8];
|
|
u32 exp = 0;
|
|
|
|
// grab mod/exp from cert from cert.db
|
|
if (LoadCertFromCertDb(false, &cert, (char*)(tmd->issuer)) != 0 && LoadCertFromCertDb(true, &cert, (char*)(tmd->issuer)) != 0)
|
|
return 1;
|
|
|
|
// current code only expects RSA2048
|
|
u32 mod_size;
|
|
if (Certificate_GetModulusSize(&cert, &mod_size) != 0 ||
|
|
mod_size != 2048/8 ||
|
|
Certificate_GetModulus(&cert, &mod) != 0 ||
|
|
Certificate_GetExponent(&cert, &exp) != 0) {
|
|
Certificate_Cleanup(&cert);
|
|
return 1;
|
|
}
|
|
|
|
Certificate_Cleanup(&cert);
|
|
|
|
if (!RSA_setKey2048(3, mod, exp) ||
|
|
!RSA_verify2048((void*) &(tmd->signature), (void*) &(tmd->issuer), 0xC4))
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
u32 VerifyTmd(TitleMetaData* tmd) {
|
|
TmdContentChunk* content_list = (TmdContentChunk*) (tmd + 1);
|
|
u32 content_count = getbe16(tmd->content_count);
|
|
|
|
// TMD validation
|
|
if (ValidateTmd(tmd) != 0) return 1;
|
|
|
|
// check content info hash
|
|
if (sha_cmp(tmd->contentinfo_hash, (u8*)tmd->contentinfo, 64 * sizeof(TmdContentInfo), SHA256_MODE) != 0)
|
|
return 1;
|
|
|
|
// check hashes in content info
|
|
for (u32 i = 0, kc = 0; i < 64 && kc < content_count; i++) {
|
|
TmdContentInfo* info = tmd->contentinfo + i;
|
|
u32 k = getbe16(info->cmd_count);
|
|
if (sha_cmp(info->hash, content_list + kc, k * sizeof(TmdContentChunk), SHA256_MODE) != 0)
|
|
return 1;
|
|
kc += k;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
u32 GetTmdCtr(u8* ctr, TmdContentChunk* chunk) {
|
|
memset(ctr, 0, 16);
|
|
memcpy(ctr, chunk->index, 2);
|
|
return 0;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
u32 BuildFakeTmd(TitleMetaData* tmd, u8* title_id, u32 n_contents, u32 save_size, u32 twl_privsave_size, u8 twl_flag) {
|
|
static const u8 sig_type[4] = { TMD_SIG_TYPE };
|
|
// safety check: number of contents
|
|
if (n_contents > TMD_MAX_CONTENTS) return 1; // potential incompatibility here (!)
|
|
// set TMD all zero for a clean start
|
|
memset(tmd, 0x00, TMD_SIZE_N(n_contents));
|
|
// file TMD values
|
|
memcpy(tmd->sig_type, sig_type, 4);
|
|
memset(tmd->signature, 0xFF, 0x100);
|
|
snprintf((char*) tmd->issuer, 0x40, IS_DEVKIT ? TMD_ISSUER_DEV : TMD_ISSUER);
|
|
tmd->version = 0x01;
|
|
memcpy(tmd->title_id, title_id, 8);
|
|
tmd->title_type[3] = 0x40; // whatever
|
|
for (u32 i = 0; i < 4; i++) tmd->save_size[i] = (save_size >> (i*8)) & 0xFF; // le save size
|
|
for (u32 i = 0; i < 4; i++) tmd->twl_privsave_size[i] = (twl_privsave_size >> (i*8)) & 0xFF; // le privsave size
|
|
tmd->twl_flag = twl_flag;
|
|
tmd->content_count[0] = (u8) ((n_contents >> 8) & 0xFF);
|
|
tmd->content_count[1] = (u8) (n_contents & 0xFF);
|
|
memset(tmd->contentinfo_hash, 0xFF, 0x20); // placeholder (hash)
|
|
memcpy(tmd->contentinfo[0].cmd_count, tmd->content_count, 2);
|
|
memset(tmd->contentinfo[0].hash, 0xFF, 0x20); // placeholder (hash)
|
|
// nothing to do for content list (yet)
|
|
|
|
return 0;
|
|
}
|
|
|
|
u32 BuildTmdCert(u8* tmdcert) {
|
|
static const u8 cert_hash_expected[0x20] = {
|
|
0x91, 0x5F, 0x77, 0x3A, 0x07, 0x82, 0xD4, 0x27, 0xC4, 0xCE, 0xF5, 0x49, 0x25, 0x33, 0xE8, 0xEC,
|
|
0xF6, 0xFE, 0xA1, 0xEB, 0x8C, 0xCF, 0x59, 0x6E, 0x69, 0xBA, 0x2A, 0x38, 0x8D, 0x73, 0x8A, 0xE1
|
|
};
|
|
static const u8 cert_hash_expected_dev[0x20] = {
|
|
0x49, 0xC9, 0x41, 0x56, 0xCA, 0x86, 0xBD, 0x1F, 0x36, 0x51, 0x51, 0x6A, 0x4A, 0x9F, 0x54, 0xA1,
|
|
0xC2, 0xE9, 0xCA, 0x93, 0x94, 0xF4, 0x29, 0xA0, 0x38, 0x54, 0x75, 0xFF, 0xAB, 0x6E, 0x8E, 0x71
|
|
};
|
|
|
|
static const char* const retail_issuers[] = {"Root-CA00000003-CP0000000b", "Root-CA00000003"};
|
|
static const char* const dev_issuers[] = {"Root-CA00000004-CP0000000a", "Root-CA00000004"};
|
|
|
|
size_t size = TMD_CDNCERT_SIZE;
|
|
if (BuildRawCertBundleFromCertDb(tmdcert, &size, !IS_DEVKIT ? retail_issuers : dev_issuers, 2) ||
|
|
size != TMD_CDNCERT_SIZE) {
|
|
return 1;
|
|
}
|
|
|
|
// check the certificate hash
|
|
u8 cert_hash[0x20];
|
|
sha_quick(cert_hash, tmdcert, TMD_CDNCERT_SIZE, SHA256_MODE);
|
|
if (memcmp(cert_hash, IS_DEVKIT ? cert_hash_expected_dev : cert_hash_expected, 0x20) != 0)
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|