diff --git a/arm9/source/game/cert.c b/arm9/source/game/cert.c index 8027376..c1d07c8 100644 --- a/arm9/source/game/cert.c +++ b/arm9/source/game/cert.c @@ -1,21 +1,313 @@ #include "cert.h" -#include "ff.h" +#include "disadiff.h" -u32 LoadCertFromCertDb(u64 offset, Certificate* cert, u32* mod, u32* exp) { - Certificate cert_local; - FIL db; - UINT bytes_read; +typedef struct { + char magic[4]; // "CERT" + u8 unk[4]; // afaik, always 0 + u8 used_size[4]; // size used after this header + u8 garbage[4]; // literally garbage values +} PACKED_STRUCT CertsDbPartitionHeader; - // not much in terms of error checking here - if (f_open(&db, "1:/dbs/certs.db", FA_READ | FA_OPEN_EXISTING) != FR_OK) +static void GetCertDBPath(char* path, bool emunand) { + path[0] = emunand ? '4' : '1'; + strcpy(&path[1], ":/dbs/certs.db"); +} + +bool Certificate_IsValid(const Certificate* cert) { + if (!cert || !cert->sig || !cert->data) + return false; + + u32 sig_type = getbe32(cert->sig->sig_type); + if (sig_type < 0x10000 || sig_type > 0x10005) + return false; + + u32 keytype = getbe32(cert->data->keytype); + if (keytype > 2) + return false; + + size_t issuer_len = strnlen(cert->data->issuer, 0x40); + size_t name_len = strnlen(cert->data->name, 0x40); + // if >= 0x40, cert can't fit as issuer for other objects later + // since later objects using the certificate as their issuer will have them use it as certissuer-certname + if (!issuer_len || !name_len || (issuer_len + name_len + 1) >= 0x40) + return false; + + return true; +} + +bool Certificate_IsRSA(const Certificate* cert) { + if (!Certificate_IsValid(cert)) return false; + if (getbe32(cert->data->keytype) >= 2) return false; + return true; +} + +bool Certificate_IsECC(const Certificate* cert) { + if (!Certificate_IsValid(cert)) return false; + if (getbe32(cert->data->keytype) != 2) return false; + return true; +} + +u32 Certificate_GetSignatureSize(const Certificate* cert, u32* size) { + if (!size || !Certificate_IsValid(cert)) return 1; + + u32 sig_type = getbe32(cert->sig->sig_type); + + if (sig_type == 0x10000 || sig_type == 0x10003) + *size = 0x200; + else if (sig_type == 0x10001 || sig_type == 0x10004) + *size = 0x100; + else if (sig_type == 0x10002 || sig_type == 0x10005) + *size = 0x3C; + else return 1; - f_lseek(&db, offset); - if (!cert) cert = &cert_local; - f_read(&db, cert, CERT_SIZE, &bytes_read); - f_close(&db); - - if (mod) memcpy(mod, cert->mod, 0x100); - if (exp) *exp = getle32(cert->exp); return 0; } + +u32 Certificate_GetModulusSize(const Certificate* cert, u32* size) { + if (!size || !Certificate_IsRSA(cert)) return 1; + + u32 keytype = getbe32(cert->data->keytype); + + if (keytype == 0) + *size = 4096 / 8; + else if (keytype == 1) + *size = 2048 / 8; + else return 1; + + return 0; +} + +u32 Certificate_GetModulus(const Certificate* cert, void* mod) { + u32 size; + if (!mod || Certificate_GetModulusSize(cert, &size)) return 1; + + memcpy(mod, cert->data->pub_key_data, size); + + return 0; +} + +u32 Certificate_GetExponent(const Certificate* cert, void* exp) { + u32 size; + if (!exp || Certificate_GetModulusSize(cert, &size)) return 1; + + memcpy(exp, &cert->data->pub_key_data[size], 4); + + return 0; +} + +u32 Certificate_GetEccSingleCoordinateSize(const Certificate* cert, u32* size) { + if (!size || !Certificate_IsECC(cert)) return 1; + + u32 keytype = getbe32(cert->data->keytype); + + if (keytype == 2) + *size = 0x3C / 2; + else return 1; + + return 0; +} + +u32 Certificate_GetEccXY(const Certificate* cert, void* X, void* Y) { + u32 size; + if (!X || !Y || Certificate_GetEccSingleCoordinateSize(cert, &size)) return 1; + + memcpy(X, cert->data->pub_key_data, size); + memcpy(Y, &cert->data->pub_key_data[size], size); + + return 0; +} + +static inline u32 _Certificate_GetSignatureChunkSizeFromType(u32 sig_type) { + if (sig_type == 0x10000 || sig_type == 0x10003) + return CERT_RSA4096_SIG_SIZE; + else if (sig_type == 0x10001 || sig_type == 0x10004) + return CERT_RSA2048_SIG_SIZE; + else if (sig_type == 0x10002 || sig_type == 0x10005) + return CERT_ECC_SIG_SIZE; + return 0; +} + +u32 Certificate_GetSignatureChunkSize(const Certificate* cert, u32* size) { + if (!size || !Certificate_IsValid(cert)) return 1; + + u32 _size = _Certificate_GetSignatureChunkSizeFromType(getbe32(cert->sig->sig_type)); + + if (_size == 0) return 1; + + *size = _size; + + return 0; +} + +static inline u32 _Certificate_GetDataChunkSizeFromType(u32 keytype) { + if (keytype == 0) + return CERT_RSA4096_BODY_SIZE; + else if (keytype == 1) + return CERT_RSA2048_BODY_SIZE; + else if (keytype == 2) + return CERT_ECC_BODY_SIZE; + return 0; +} + +u32 Certificate_GetDataChunkSize(const Certificate* cert, u32* size) { + if (!size || !Certificate_IsValid(cert)) return 1; + + u32 _size = _Certificate_GetDataChunkSizeFromType(getbe32(cert->data->keytype)); + + if (_size == 0) return 1; + + *size = _size; + + return 0; +} + +u32 Certificate_GetFullSize(const Certificate* cert, u32* size) { + if (!size || !Certificate_IsValid(cert)) return 1; + + 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; + + *size = sig_size + data_size; + + return 0; +} + +u32 Certificate_RawCopy(const Certificate* cert, void* raw) { + if (!raw || !Certificate_IsValid(cert)) return 1; + + 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; + + memcpy(raw, cert->sig, sig_size); + memcpy(&((u8*)raw)[sig_size], cert->data, data_size); + + return 0; +} + +u32 Certificate_Cleanup(Certificate* cert) { + if (!cert) return 1; + + free(cert->sig); + free(cert->data); + cert->sig = NULL; + cert->data = NULL; + + return 0; +} + +u32 LoadCertFromCertDb(bool emunand, Certificate* cert, const char* issuer) { + if (!issuer || !cert) return 1; + + 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); + 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; + + // certs.db has no filesystem.. its pretty plain, certificates after another + // but also, certificates are not equally sized + // 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]; + + 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) + break; + + if (!strcmp(full_issuer, issuer)) { + ret = 0; + break; + } + + Certificate_Cleanup(&cert_local); + + offset += full_size; + } + + if (ret) { + Certificate_Cleanup(&cert_local); + } + + *cert = cert_local; + + free(cache); + return ret; +} diff --git a/arm9/source/game/cert.h b/arm9/source/game/cert.h index b195d24..3d47930 100644 --- a/arm9/source/game/cert.h +++ b/arm9/source/game/cert.h @@ -2,21 +2,48 @@ #include "common.h" -#define CERT_SIZE sizeof(Certificate) +#define CERT_MAX_SIZE (sizeof(CertificateSignature) + 0x23C + sizeof(CertificateBody) + 0x238) + +#define CERT_RSA4096_SIG_SIZE (sizeof(CertificateSignature) + 0x23C) +#define CERT_RSA2048_SIG_SIZE (sizeof(CertificateSignature) + 0x13C) +#define CERT_ECC_SIG_SIZE (sizeof(CertificateSignature) + 0x7C) +#define CERT_RSA4096_BODY_SIZE (sizeof(CertificateBody) + 0x238) +#define CERT_RSA2048_BODY_SIZE (sizeof(CertificateBody) + 0x138) +#define CERT_ECC_BODY_SIZE (sizeof(CertificateBody) + 0x78) // from: http://3dbrew.org/wiki/Certificates // all numbers in big endian typedef struct { - u8 sig_type[4]; // expected: 0x010004 / RSA_2048 SHA256 - u8 signature[0x100]; - u8 padding0[0x3C]; - u8 issuer[0x40]; - u8 keytype[4]; // expected: 0x01 / RSA_2048 - u8 name[0x40]; - u8 unknown[4]; - u8 mod[0x100]; - u8 exp[0x04]; - u8 padding1[0x34]; -} PACKED_STRUCT Certificate; + u8 sig_type[4]; + u8 signature[]; +} PACKED_ALIGN(1) CertificateSignature; -u32 LoadCertFromCertDb(u64 offset, Certificate* cert, u32* mod, u32* exp); +typedef struct { + char issuer[0x40]; + u8 keytype[4]; + char name[0x40]; + u8 expiration[4]; + u8 pub_key_data[]; +} PACKED_ALIGN(1) CertificateBody; + +typedef struct { + CertificateSignature* sig; + CertificateBody* data; +} Certificate; + +bool Certificate_IsValid(const Certificate* cert); +bool Certificate_IsRSA(const Certificate* cert); +bool Certificate_IsECC(const Certificate* cert); +u32 Certificate_GetSignatureSize(const Certificate* cert, u32* size); +u32 Certificate_GetModulusSize(const Certificate* cert, u32* size); +u32 Certificate_GetModulus(const Certificate* cert, void* mod); +u32 Certificate_GetExponent(const Certificate* cert, void* exp); +u32 Certificate_GetEccSingleCoordinateSize(const Certificate* cert, u32* size); +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_RawCopy(const Certificate* cert, void* raw); +u32 Certificate_Cleanup(Certificate* cert); + +u32 LoadCertFromCertDb(bool emunand, Certificate* cert, const char* issuer); diff --git a/arm9/source/game/cia.c b/arm9/source/game/cia.c index 630e838..b941ab6 100644 --- a/arm9/source/game/cia.c +++ b/arm9/source/game/cia.c @@ -4,6 +4,7 @@ #include "ff.h" #include "aes.h" #include "sha.h" +#include "cert.h" u32 ValidateCiaHeader(CiaHeader* header) { if ((header->size_header != CIA_HEADER_SIZE) || @@ -59,21 +60,49 @@ u32 BuildCiaCert(u8* ciacert) { 0x18, 0x83, 0xAF, 0xE0, 0xF4, 0xE5, 0x62, 0xBA, 0x69, 0xEE, 0x72, 0x2A, 0xC2, 0x4E, 0x95, 0xB3 }; - // 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) + 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"; + + // 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; - // 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); + + if (LoadCertFromCertDb(false, &cert_xs, issuer_xs) != 0 && LoadCertFromCertDb(true, &cert_xs, issuer_xs) != 0) { + Certificate_Cleanup(&cert_ca); + 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]; diff --git a/arm9/source/game/ticket.c b/arm9/source/game/ticket.c index ccb427c..721ed61 100644 --- a/arm9/source/game/ticket.c +++ b/arm9/source/game/ticket.c @@ -28,17 +28,26 @@ u32 ValidateTwlTicket(Ticket* ticket) { } u32 ValidateTicketSignature(Ticket* ticket) { - static bool got_modexp = false; - static u32 mod[0x100 / 0x4] = { 0 }; - static u32 exp = 0; + Certificate cert; + u32 mod[2048/8]; + u32 exp = 0; - if (!got_modexp) { - // grab mod/exp from cert from cert.db - if (LoadCertFromCertDb(0x3F10, NULL, mod, &exp) == 0) - got_modexp = true; - else return 1; + // grab mod/exp from cert from cert.db + if (LoadCertFromCertDb(false, &cert, (char*)(ticket->issuer)) != 0 && LoadCertFromCertDb(true, &cert, (char*)(ticket->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*) &(ticket->signature), (void*) &(ticket->issuer), GetTicketSize(ticket) - 0x140)) return 1; @@ -89,19 +98,35 @@ u32 BuildTicketCert(u8* tickcert) { 0xC6, 0x4B, 0xD4, 0x8F, 0xDF, 0x13, 0x21, 0x3D, 0xFC, 0x72, 0xFC, 0x8D, 0x9F, 0xDD, 0x01, 0x0E }; - // 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) + const char* issuer_xs = !IS_DEVKIT ? "Root-CA00000003-XS0000000c" : "Root-CA00000004-XS00000009"; + const char* issuer_ca = !IS_DEVKIT ? "Root-CA00000003" : "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; - // grab ticket cert from 3 offsets - f_lseek(&db, 0x3F10); - f_read(&db, tickcert + 0x000, 0x300, &bytes_read); - f_lseek(&db, 0x0C10); - f_read(&db, tickcert + 0x300, 0x1F0, &bytes_read); - f_lseek(&db, 0x3A00); - f_read(&db, tickcert + 0x4F0, 0x210, &bytes_read); - f_close(&db); + + if (LoadCertFromCertDb(false, &cert_ca, issuer_ca) != 0 && LoadCertFromCertDb(true, &cert_ca, issuer_ca) != 0) { + Certificate_Cleanup(&cert_xs); + 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]; diff --git a/arm9/source/game/tmd.c b/arm9/source/game/tmd.c index d030ecf..bc15fa5 100644 --- a/arm9/source/game/tmd.c +++ b/arm9/source/game/tmd.c @@ -24,17 +24,26 @@ u32 ValidateTwlTmd(TitleMetaData* tmd) { } u32 ValidateTmdSignature(TitleMetaData* tmd) { - static bool got_modexp = false; - static u32 mod[0x100 / 4] = { 0 }; - static u32 exp = 0; + Certificate cert; + u32 mod[2048/8]; + u32 exp = 0; - if (!got_modexp) { - // grab mod/exp from cert from cert.db - if (LoadCertFromCertDb(0x3C10, NULL, mod, &exp) == 0) - got_modexp = true; - else return 1; + // 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; @@ -121,19 +130,35 @@ u32 BuildTmdCert(u8* tmdcert) { 0xC2, 0xE9, 0xCA, 0x93, 0x94, 0xF4, 0x29, 0xA0, 0x38, 0x54, 0x75, 0xFF, 0xAB, 0x6E, 0x8E, 0x71 }; - // 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) + const char* issuer_cp = !IS_DEVKIT ? "Root-CA00000003-CP0000000b" : "Root-CA00000004-CP0000000a"; + const char* issuer_ca = !IS_DEVKIT ? "Root-CA00000003" : "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; - // grab TMD cert from 3 offsets - f_lseek(&db, 0x3C10); - f_read(&db, tmdcert + 0x000, 0x300, &bytes_read); - f_lseek(&db, 0x0C10); - f_read(&db, tmdcert + 0x300, 0x1F0, &bytes_read); - f_lseek(&db, 0x3A00); - f_read(&db, tmdcert + 0x4F0, 0x210, &bytes_read); - f_close(&db); + + if (LoadCertFromCertDb(false, &cert_ca, issuer_ca) != 0 && LoadCertFromCertDb(true, &cert_ca, issuer_ca) != 0) { + Certificate_Cleanup(&cert_cp); + 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];