diff --git a/arm9/source/game/cert.c b/arm9/source/game/cert.c index d2e461f..3b96ee8 100644 --- a/arm9/source/game/cert.c +++ b/arm9/source/game/cert.c @@ -8,7 +8,7 @@ typedef struct { u8 garbage[4]; // literally garbage values } PACKED_STRUCT CertsDbPartitionHeader; -static void GetCertDBPath(char* path, bool emunand) { +static inline void GetCertDBPath(char* path, bool emunand) { path[0] = emunand ? '4' : '1'; strcpy(&path[1], ":/dbs/certs.db"); } @@ -65,6 +65,8 @@ static struct { } }; +static inline void _Certificate_CleanupImpl(Certificate* cert); + bool Certificate_IsValid(const Certificate* cert) { if (!cert || !cert->sig || !cert->data) return false; @@ -228,9 +230,34 @@ u32 Certificate_GetFullSize(const Certificate* cert, u32* size) { return 0; } -u32 Certificate_RawCopy(const Certificate* cert, void* raw) { - if (!raw || !Certificate_IsValid(cert)) return 1; +static u32 _Certificate_AllocCopyOutImpl(const Certificate* cert, Certificate* out_cert) { + u32 sig_size = _Certificate_GetSignatureChunkSizeFromType(getbe32(cert->sig->sig_type)); + u32 data_size = _Certificate_GetDataChunkSizeFromType(getbe32(cert->data->keytype)); + if (sig_size == 0 || data_size == 0) + return 1; + + out_cert->sig = (CertificateSignature*)malloc(sig_size); + out_cert->data = (CertificateBody*)malloc(data_size); + + if (!out_cert->sig || !out_cert->data) { + _Certificate_CleanupImpl(out_cert); + return 1; + } + + memcpy(out_cert->sig, cert->sig, sig_size); + memcpy(out_cert->data, cert->data, data_size); + + return 0; +} + +u32 Certificate_AllocCopyOut(const Certificate* cert, Certificate* out_cert) { + if (!out_cert || !Certificate_IsValid(cert)) return 1; + + return _Certificate_AllocCopyOutImpl(cert, out_cert); +} + +static u32 _Certificate_RawCopyImpl(const Certificate* cert, void* raw) { u32 sig_size = _Certificate_GetSignatureChunkSizeFromType(getbe32(cert->sig->sig_type)); u32 data_size = _Certificate_GetDataChunkSizeFromType(getbe32(cert->data->keytype)); @@ -243,6 +270,12 @@ u32 Certificate_RawCopy(const Certificate* cert, void* raw) { return 0; } +u32 Certificate_RawCopy(const Certificate* cert, void* raw) { + if (!raw || !Certificate_IsValid(cert)) return 1; + + return _Certificate_RawCopyImpl(cert, raw); +} + // ptr free check, to not free if ptr is pointing to static storage!! static inline void _Certificate_SafeFree(void* ptr) { if ((u32)ptr >= (u32)&_CommonCertsStorage && (u32)ptr < (u32)&_CommonCertsStorage + sizeof(_CommonCertsStorage)) @@ -251,13 +284,17 @@ static inline void _Certificate_SafeFree(void* ptr) { free(ptr); } -u32 Certificate_Cleanup(Certificate* cert) { - if (!cert) return 1; - +static inline void _Certificate_CleanupImpl(Certificate* cert) { _Certificate_SafeFree(cert->sig); _Certificate_SafeFree(cert->data); cert->sig = NULL; cert->data = NULL; +} + +u32 Certificate_Cleanup(Certificate* cert) { + if (!cert) return 1; + + _Certificate_CleanupImpl(cert); return 0; } @@ -400,11 +437,99 @@ static void _SaveToCertStorage(const Certificate* cert, u32 ident) { if (sig_size + data_size != raw_size) return; - if (!Certificate_RawCopy(cert, raw_space)) { + if (!_Certificate_RawCopyImpl(cert, raw_space)) { _CommonCertsStorage.loaded_certs_flg |= ident; } } +// grumble grumble, gotta avoid repeated code when possible or at least if significant enough + +static u32 _DisaOpenCertDb(char (*path)[16], bool emunand, DisaDiffRWInfo* info, u8** cache, u32* offset, u32* max_offset) { + GetCertDBPath(*path, emunand); + + u8* _cache = NULL; + if (GetDisaDiffRWInfo(*path, info, false) != 0) return 1; + _cache = (u8*)malloc(info->size_dpfs_lvl2); + if (!_cache) return 1; + if (BuildDisaDiffDpfsLvl2Cache(*path, info, _cache, info->size_dpfs_lvl2) != 0) { + free(_cache); + return 1; + } + + CertsDbPartitionHeader header; + + if (ReadDisaDiffIvfcLvl4(*path, info, 0, sizeof(CertsDbPartitionHeader), &header) != sizeof(CertsDbPartitionHeader)) { + free(_cache); + return 1; + } + + if (getbe32(header.magic) != 0x43455254 /* 'CERT' */ || + getbe32(header.unk) != 0 || + getle32(header.used_size) & 0xFF) { + free(_cache); + return 1; + } + + *cache = _cache; + + *offset = sizeof(CertsDbPartitionHeader); + *max_offset = getle32(header.used_size) + sizeof(CertsDbPartitionHeader); + + return 0; +} + +static u32 _ProcessNextCertDbEntry(const char* path, DisaDiffRWInfo* info, Certificate* cert, u32 *full_size, char (*full_issuer)[0x41], u32* offset, u32 max_offset) { + u8 sig_type_data[4]; + u8 keytype_data[4]; + + if (*offset + 4 > max_offset) return 1; + + if (ReadDisaDiffIvfcLvl4(path, info, *offset, 4, sig_type_data) != 4) + return 1; + + u32 sig_type = getbe32(sig_type_data); + + if (sig_type == 0x10002 || sig_type == 0x10005) return 1; // ECC signs not allowed on db + + u32 sig_size = _Certificate_GetSignatureChunkSizeFromType(sig_type); + if (sig_size == 0) return 1; + + u32 keytype_off = *offset + sig_size + offsetof(CertificateBody, keytype); + if (keytype_off + 4 > max_offset) return 1; + + if (ReadDisaDiffIvfcLvl4(path, info, keytype_off, 4, keytype_data) != 4) + return 1; + + u32 keytype = getbe32(keytype_data); + + if (keytype == 2) return 1; // ECC keys not allowed on db + + u32 data_size = _Certificate_GetDataChunkSizeFromType(keytype); + if (data_size == 0) return 1; + + *full_size = sig_size + data_size; + if (*offset + *full_size > max_offset) return 1; + + cert->sig = (CertificateSignature*)malloc(sig_size); + cert->data = (CertificateBody*)malloc(data_size); + if (!cert->sig || !cert->data) + return 1; + + if (ReadDisaDiffIvfcLvl4(path, info, *offset, sig_size, cert->sig) != sig_size) + return 1; + + if (ReadDisaDiffIvfcLvl4(path, info, *offset + sig_size, data_size, cert->data) != data_size) + return 1; + + if (!Certificate_IsValid(cert)) + return 1; + + if (snprintf(*full_issuer, 0x41, "%s-%s", cert->data->issuer, cert->data->name) > 0x40) + return 1; + + return 0; +} + u32 LoadCertFromCertDb(bool emunand, Certificate* cert, const char* issuer) { if (!issuer || !cert) return 1; @@ -416,34 +541,13 @@ u32 LoadCertFromCertDb(bool emunand, Certificate* cert, const char* issuer) { Certificate cert_local = {NULL, NULL}; char path[16]; - GetCertDBPath(path, emunand); - DisaDiffRWInfo info; - u8* cache = NULL; - if (GetDisaDiffRWInfo(path, &info, false) != 0) return 1; - cache = malloc(info.size_dpfs_lvl2); - if (!cache) return 1; - if (BuildDisaDiffDpfsLvl2Cache(path, &info, cache, info.size_dpfs_lvl2) != 0) { - free(cache); + u8* cache; + + u32 offset, max_offset; + + if (_DisaOpenCertDb(&path, emunand, &info, &cache, &offset, &max_offset)) return 1; - } - - CertsDbPartitionHeader header; - - if (ReadDisaDiffIvfcLvl4(path, &info, 0, sizeof(CertsDbPartitionHeader), &header) != sizeof(CertsDbPartitionHeader)) { - free(cache); - return 1; - } - - if (getbe32(header.magic) != 0x43455254 /* 'CERT' */ || - getbe32(header.unk) != 0 || - getle32(header.used_size) & 0xFF) { - free(cache); - return 1; - } - - u32 offset = sizeof(CertsDbPartitionHeader); - u32 max_offset = getle32(header.used_size) + sizeof(CertsDbPartitionHeader); u32 ret = 1; @@ -452,52 +556,9 @@ u32 LoadCertFromCertDb(bool emunand, Certificate* cert, const char* issuer) { // so most cases of bad data, leads to giving up while (offset < max_offset) { char full_issuer[0x41]; - u8 sig_type_data[4]; - u8 keytype_data[4]; + u32 full_size; - if (offset + 4 > max_offset) break; - - if (ReadDisaDiffIvfcLvl4(path, &info, offset, 4, sig_type_data) != 4) - break; - - u32 sig_type = getbe32(sig_type_data); - - if (sig_type == 0x10002 || sig_type == 0x10005) break; // ECC signs not allowed on db - - u32 sig_size = _Certificate_GetSignatureChunkSizeFromType(sig_type); - if (sig_size == 0) break; - - u32 keytype_off = offset + sig_size + offsetof(CertificateBody, keytype); - if (keytype_off + 4 > max_offset) break; - - if (ReadDisaDiffIvfcLvl4(path, &info, keytype_off, 4, keytype_data) != 4) - break; - - u32 keytype = getbe32(keytype_data); - - if (keytype == 2) break; // ECC keys not allowed on db - - u32 data_size = _Certificate_GetDataChunkSizeFromType(keytype); - if (data_size == 0) break; - - u32 full_size = sig_size + data_size; - if (offset + full_size > max_offset) break; - - cert_local.sig = (CertificateSignature*)malloc(sig_size); - cert_local.data = (CertificateBody*)malloc(data_size); - if (!cert_local.sig || !cert_local.data) - break; - - if (ReadDisaDiffIvfcLvl4(path, &info, offset, sig_size, cert_local.sig) != sig_size) - break; - - if (ReadDisaDiffIvfcLvl4(path, &info, offset + sig_size, data_size, cert_local.data) != data_size) - break; - - if (!Certificate_IsValid(&cert_local)) - break; - - if (snprintf(full_issuer, 0x41, "%s-%s", cert_local.data->issuer, cert_local.data->name) > 0x40) + if (_ProcessNextCertDbEntry(path, &info, &cert_local, &full_size, &full_issuer, &offset, max_offset)) break; if (!strcmp(full_issuer, issuer)) { @@ -505,13 +566,13 @@ u32 LoadCertFromCertDb(bool emunand, Certificate* cert, const char* issuer) { break; } - Certificate_Cleanup(&cert_local); + _Certificate_CleanupImpl(&cert_local); offset += full_size; } if (ret) { - Certificate_Cleanup(&cert_local); + _Certificate_CleanupImpl(&cert_local); } else { _SaveToCertStorage(&cert_local, _ident); } @@ -521,3 +582,122 @@ u32 LoadCertFromCertDb(bool emunand, Certificate* cert, const char* issuer) { free(cache); return ret; } + +// I dont expect many certs on a cert bundle, so I'll cap it to 8 +u32 BuildRawCertBundleFromCertDb(void* rawout, size_t* size, const char* const* cert_issuers, int count) { + if (!rawout || !size || !cert_issuers || count < 0 || count > 8) return 1; + if (!*size && count) return 1; + if (!count) { // *shrug* + *size = 0; + return 0; + } + + for (int i = 0; i < count; ++i) { + if (!cert_issuers[i]) + return 1; + } + + Certificate certs[8]; + u8 certs_loaded = 0; + + memset(certs, 0, sizeof(certs)); + + int loaded_count = 0; + + // search static storage first + for (int i = 0; i < count; ++i) { + u32 _ident = _Issuer_To_StorageIdent(cert_issuers[i]); + if (_LoadFromCertStorage(&certs[i], _ident)) { + certs_loaded |= BIT(i); + ++loaded_count; + } + } + + int ret = 0; + + for (int i = 0; i < 2 && loaded_count != count && !ret; ++i) { + Certificate cert_local = {NULL, NULL}; + + char path[16]; + DisaDiffRWInfo info; + u8* cache; + + u32 offset, max_offset; + + if (_DisaOpenCertDb(&path, i ? true : false, &info, &cache, &offset, &max_offset)) + continue; + + while (offset < max_offset) { + char full_issuer[0x41]; + u32 full_size; + + if (_ProcessNextCertDbEntry(path, &info, &cert_local, &full_size, &full_issuer, &offset, max_offset)) + break; + + for (int j = 0; j < count; j++) { + if (certs_loaded & BIT(j)) continue; + if (!strcmp(full_issuer, cert_issuers[j])) { + ret = _Certificate_AllocCopyOutImpl(&cert_local, &certs[j]); + if (ret) break; + certs_loaded |= BIT(j); + ++loaded_count; + } + } + + // while at it, try to save to static storage, if applicable + u32 _ident = _Issuer_To_StorageIdent(full_issuer); + _SaveToCertStorage(&cert_local, _ident); + + _Certificate_CleanupImpl(&cert_local); + + if (loaded_count == count || ret) // early exit + break; + + offset += full_size; + } + + free(cache); + } + + if (!ret && loaded_count == count) { + u8* out = (u8*)rawout; + size_t limit = *size, written = 0; + + for (int i = 0; i < count; ++i) { + u32 sig_size = _Certificate_GetSignatureChunkSizeFromType(getbe32(certs[i].sig->sig_type)); + u32 data_size = _Certificate_GetDataChunkSizeFromType(getbe32(certs[i].data->keytype)); + + if (sig_size == 0 || data_size == 0) { + ret = 1; + break; + } + + u32 full_size = sig_size + data_size; + + if (written + full_size > limit) { + ret = 1; + break; + } + + if (_Certificate_RawCopyImpl(&certs[i], out)) { + ret = 1; + break; + } + + out += full_size; + written += full_size; + } + + if (!ret) + *size = written; + } else { + ret = 1; + } + + for (int i = 0; i < count; ++i) { + if (certs_loaded & BIT(i)) + _Certificate_CleanupImpl(&certs[i]); + } + + return ret; +} diff --git a/arm9/source/game/cert.h b/arm9/source/game/cert.h index 3d47930..4e8b3c2 100644 --- a/arm9/source/game/cert.h +++ b/arm9/source/game/cert.h @@ -43,7 +43,9 @@ u32 Certificate_GetEccXY(const Certificate* cert, void* X, void* Y); u32 Certificate_GetSignatureChunkSize(const Certificate* cert, u32* size); u32 Certificate_GetDataChunkSize(const Certificate* cert, u32* size); u32 Certificate_GetFullSize(const Certificate* cert, u32* size); +u32 Certificate_AllocCopyOut(const Certificate* cert, Certificate* out_cert); u32 Certificate_RawCopy(const Certificate* cert, void* raw); u32 Certificate_Cleanup(Certificate* cert); u32 LoadCertFromCertDb(bool emunand, Certificate* cert, const char* issuer); +u32 BuildRawCertBundleFromCertDb(void* rawout, size_t* size, const char* const* cert_issuers, int count); diff --git a/arm9/source/game/cia.c b/arm9/source/game/cia.c index b941ab6..7f52575 100644 --- a/arm9/source/game/cia.c +++ b/arm9/source/game/cia.c @@ -60,50 +60,15 @@ u32 BuildCiaCert(u8* ciacert) { 0x18, 0x83, 0xAF, 0xE0, 0xF4, 0xE5, 0x62, 0xBA, 0x69, 0xEE, 0x72, 0x2A, 0xC2, 0x4E, 0x95, 0xB3 }; - const char* issuer_ca = !IS_DEVKIT ? "Root-CA00000003" : "Root-CA00000004"; - const char* issuer_xs = !IS_DEVKIT ? "Root-CA00000003-XS0000000c" : "Root-CA00000004-XS00000009"; - const char* issuer_cp = !IS_DEVKIT ? "Root-CA00000003-CP0000000b" : "Root-CA00000004-CP0000000a"; + static const char* const retail_issuers[] = {"Root-CA00000003", "Root-CA00000003-XS0000000c", "Root-CA00000003-CP0000000b"}; + static const char* const dev_issuers[] = {"Root-CA00000004", "Root-CA00000004-XS00000009", "Root-CA00000004-CP0000000a"}; - // open certs.db file on SysNAND or EmuNAND - Certificate cert_ca; - Certificate cert_xs; - Certificate cert_cp; - if (LoadCertFromCertDb(false, &cert_ca, issuer_ca) != 0 && LoadCertFromCertDb(true, &cert_ca, issuer_ca) != 0) - return 1; - - if (LoadCertFromCertDb(false, &cert_xs, issuer_xs) != 0 && LoadCertFromCertDb(true, &cert_xs, issuer_xs) != 0) { - Certificate_Cleanup(&cert_ca); + size_t size = CIA_CERT_SIZE; + if (BuildRawCertBundleFromCertDb(ciacert, &size, !IS_DEVKIT ? retail_issuers : dev_issuers, 3) || + size != CIA_CERT_SIZE) { return 1; } - if (LoadCertFromCertDb(false, &cert_cp, issuer_cp) != 0 && LoadCertFromCertDb(true, &cert_cp, issuer_cp) != 0) { - Certificate_Cleanup(&cert_ca); - Certificate_Cleanup(&cert_xs); - return 1; - } - - u32 cert_size_ca; - u32 cert_size_xs; - u32 cert_size_cp; - if (Certificate_GetFullSize(&cert_ca, &cert_size_ca) != 0 || - cert_size_ca != 0x400 || - Certificate_GetFullSize(&cert_xs, &cert_size_xs) != 0 || - cert_size_xs != 0x300 || - Certificate_GetFullSize(&cert_cp, &cert_size_cp) != 0 || - cert_size_cp != 0x300 || - Certificate_RawCopy(&cert_ca, ciacert) != 0 || - Certificate_RawCopy(&cert_xs, &ciacert[0x400]) != 0 || - Certificate_RawCopy(&cert_cp, &ciacert[0x700]) != 0) { - Certificate_Cleanup(&cert_ca); - Certificate_Cleanup(&cert_xs); - Certificate_Cleanup(&cert_cp); - return 1; - } - - Certificate_Cleanup(&cert_ca); - Certificate_Cleanup(&cert_xs); - Certificate_Cleanup(&cert_cp); - // check the certificate hash u8 cert_hash[0x20]; sha_quick(cert_hash, ciacert, CIA_CERT_SIZE, SHA256_MODE); diff --git a/arm9/source/game/ticket.c b/arm9/source/game/ticket.c index 721ed61..fbf5139 100644 --- a/arm9/source/game/ticket.c +++ b/arm9/source/game/ticket.c @@ -98,36 +98,15 @@ u32 BuildTicketCert(u8* tickcert) { 0xC6, 0x4B, 0xD4, 0x8F, 0xDF, 0x13, 0x21, 0x3D, 0xFC, 0x72, 0xFC, 0x8D, 0x9F, 0xDD, 0x01, 0x0E }; - const char* issuer_xs = !IS_DEVKIT ? "Root-CA00000003-XS0000000c" : "Root-CA00000004-XS00000009"; - const char* issuer_ca = !IS_DEVKIT ? "Root-CA00000003" : "Root-CA00000004"; + static const char* const retail_issuers[] = {"Root-CA00000003-XS0000000c", "Root-CA00000003"}; + static const char* const dev_issuers[] = {"Root-CA00000004-XS00000009", "Root-CA00000004"}; - // open certs.db file on SysNAND or EmuNAND - Certificate cert_xs; - Certificate cert_ca; - if (LoadCertFromCertDb(false, &cert_xs, issuer_xs) != 0 && LoadCertFromCertDb(true, &cert_xs, issuer_xs) != 0) - return 1; - - if (LoadCertFromCertDb(false, &cert_ca, issuer_ca) != 0 && LoadCertFromCertDb(true, &cert_ca, issuer_ca) != 0) { - Certificate_Cleanup(&cert_xs); + size_t size = TICKET_CDNCERT_SIZE; + if (BuildRawCertBundleFromCertDb(tickcert, &size, !IS_DEVKIT ? retail_issuers : dev_issuers, 2) || + size != TICKET_CDNCERT_SIZE) { return 1; } - u32 cert_size_xs; - u32 cert_size_ca; - if (Certificate_GetFullSize(&cert_xs, &cert_size_xs) != 0 || - cert_size_xs != 0x300 || - Certificate_GetFullSize(&cert_ca, &cert_size_ca) != 0 || - cert_size_ca != 0x400 || - Certificate_RawCopy(&cert_xs, tickcert) != 0 || - Certificate_RawCopy(&cert_ca, &tickcert[0x300]) != 0) { - Certificate_Cleanup(&cert_xs); - Certificate_Cleanup(&cert_ca); - return 1; - } - - Certificate_Cleanup(&cert_xs); - Certificate_Cleanup(&cert_ca); - // check the certificate hash u8 cert_hash[0x20]; sha_quick(cert_hash, tickcert, TICKET_CDNCERT_SIZE, SHA256_MODE); diff --git a/arm9/source/game/tmd.c b/arm9/source/game/tmd.c index bc15fa5..8d78526 100644 --- a/arm9/source/game/tmd.c +++ b/arm9/source/game/tmd.c @@ -130,36 +130,15 @@ u32 BuildTmdCert(u8* tmdcert) { 0xC2, 0xE9, 0xCA, 0x93, 0x94, 0xF4, 0x29, 0xA0, 0x38, 0x54, 0x75, 0xFF, 0xAB, 0x6E, 0x8E, 0x71 }; - const char* issuer_cp = !IS_DEVKIT ? "Root-CA00000003-CP0000000b" : "Root-CA00000004-CP0000000a"; - const char* issuer_ca = !IS_DEVKIT ? "Root-CA00000003" : "Root-CA00000004"; + static const char* const retail_issuers[] = {"Root-CA00000003-CP0000000b", "Root-CA00000003"}; + static const char* const dev_issuers[] = {"Root-CA00000004-CP0000000a", "Root-CA00000004"}; - // open certs.db file on SysNAND or EmuNAND - Certificate cert_cp; - Certificate cert_ca; - if (LoadCertFromCertDb(false, &cert_cp, issuer_cp) != 0 && LoadCertFromCertDb(true, &cert_cp, issuer_cp) != 0) - return 1; - - if (LoadCertFromCertDb(false, &cert_ca, issuer_ca) != 0 && LoadCertFromCertDb(true, &cert_ca, issuer_ca) != 0) { - Certificate_Cleanup(&cert_cp); + size_t size = TMD_CDNCERT_SIZE; + if (BuildRawCertBundleFromCertDb(tmdcert, &size, !IS_DEVKIT ? retail_issuers : dev_issuers, 2) || + size != TMD_CDNCERT_SIZE) { return 1; } - u32 cert_size_cp; - u32 cert_size_ca; - if (Certificate_GetFullSize(&cert_cp, &cert_size_cp) != 0 || - cert_size_cp != 0x300 || - Certificate_GetFullSize(&cert_ca, &cert_size_ca) != 0 || - cert_size_ca != 0x400 || - Certificate_RawCopy(&cert_cp, tmdcert) != 0 || - Certificate_RawCopy(&cert_ca, &tmdcert[0x300]) != 0) { - Certificate_Cleanup(&cert_cp); - Certificate_Cleanup(&cert_ca); - return 1; - } - - Certificate_Cleanup(&cert_cp); - Certificate_Cleanup(&cert_ca); - // check the certificate hash u8 cert_hash[0x20]; sha_quick(cert_hash, tmdcert, TMD_CDNCERT_SIZE, SHA256_MODE);