From 3bfb9ef6ec64a28d21bf3d7439cf0fa6e21988a0 Mon Sep 17 00:00:00 2001 From: luigoalma Date: Thu, 6 May 2021 04:58:29 +0100 Subject: [PATCH] Make cert bundle building nicer 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. --- arm9/source/game/cert.c | 340 +++++++++++++++++++++++++++++--------- arm9/source/game/cert.h | 2 + arm9/source/game/cia.c | 45 +---- arm9/source/game/ticket.c | 31 +--- arm9/source/game/tmd.c | 31 +--- 5 files changed, 277 insertions(+), 172 deletions(-) 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);