1
0
Fork 0
mirror of https://github.com/DarkMatterCore/nxdumptool.git synced 2024-11-27 04:32:18 +00:00
nxdumptool/source/core/cert.c

385 lines
11 KiB
C
Raw Normal View History

2020-04-16 01:06:41 +01:00
/*
* cert.c
2020-04-16 01:06:41 +01:00
*
2023-04-08 12:42:22 +01:00
* Copyright (c) 2020-2023, DarkMatterCore <pabloacurielz@gmail.com>.
*
* This file is part of nxdumptool (https://github.com/DarkMatterCore/nxdumptool).
*
* nxdumptool is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
2020-04-16 01:06:41 +01:00
*
* nxdumptool is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
2020-04-16 01:06:41 +01:00
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
2020-04-16 01:06:41 +01:00
*/
2021-03-26 04:35:14 +00:00
#include "nxdt_utils.h"
2020-04-11 06:28:26 +01:00
#include "cert.h"
#include "save.h"
#include "gamecard.h"
2020-04-11 06:28:26 +01:00
#define CERT_SAVEFILE_PATH BIS_SYSTEM_PARTITION_MOUNT_NAME "/save/80000000000000e0"
2020-04-11 06:28:26 +01:00
#define CERT_SAVEFILE_STORAGE_BASE_PATH "/certificate/"
#define CERT_TYPE(sig) (pub_key_type == CertPubKeyType_Rsa4096 ? CertType_Sig##sig##_PubKeyRsa4096 : \
(pub_key_type == CertPubKeyType_Rsa2048 ? CertType_Sig##sig##_PubKeyRsa2048 : CertType_Sig##sig##_PubKeyEcc480))
2020-04-15 21:50:07 +01:00
/* Global variables. */
static save_ctx_t *g_esCertSaveCtx = NULL;
2020-05-03 00:40:50 +01:00
static Mutex g_esCertSaveMutex = 0;
2020-04-15 21:50:07 +01:00
/* Function prototypes. */
2020-04-11 06:28:26 +01:00
static bool certOpenEsCertSaveFile(void);
static void certCloseEsCertSaveFile(void);
static bool _certRetrieveCertificateByName(Certificate *dst, const char *name);
static u8 certGetCertificateType(void *data, u64 data_size);
static bool _certRetrieveCertificateChainBySignatureIssuer(CertificateChain *dst, const char *issuer);
2020-04-11 06:28:26 +01:00
static u32 certGetCertificateCountInSignatureIssuer(const char *issuer);
2020-04-11 06:28:26 +01:00
static u64 certCalculateRawCertificateChainSize(const CertificateChain *chain);
static void certCopyCertificateChainDataToMemoryBuffer(void *dst, const CertificateChain *chain);
bool certRetrieveCertificateByName(Certificate *dst, const char *name)
{
if (!dst || !name || !*name)
2020-04-11 06:28:26 +01:00
{
LOG_MSG_ERROR("Invalid parameters!");
return false;
2020-04-11 06:28:26 +01:00
}
bool ret = false;
SCOPED_LOCK(&g_esCertSaveMutex)
{
if (!certOpenEsCertSaveFile()) break;
ret = _certRetrieveCertificateByName(dst, name);
certCloseEsCertSaveFile();
}
return ret;
}
bool certRetrieveCertificateChainBySignatureIssuer(CertificateChain *dst, const char *issuer)
{
if (!dst || !issuer || strncmp(issuer, "Root-", 5) != 0)
2020-04-11 06:28:26 +01:00
{
LOG_MSG_ERROR("Invalid parameters!");
return false;
2020-04-11 06:28:26 +01:00
}
bool ret = false;
SCOPED_LOCK(&g_esCertSaveMutex)
{
if (!certOpenEsCertSaveFile()) break;
ret = _certRetrieveCertificateChainBySignatureIssuer(dst, issuer);
certCloseEsCertSaveFile();
}
return ret;
2020-04-11 06:28:26 +01:00
}
u8 *certGenerateRawCertificateChainBySignatureIssuer(const char *issuer, u64 *out_size)
2020-04-19 23:44:22 +01:00
{
if (!issuer || !*issuer || !out_size)
2020-04-19 23:44:22 +01:00
{
LOG_MSG_ERROR("Invalid parameters!");
2020-04-19 23:44:22 +01:00
return NULL;
}
CertificateChain chain = {0};
u8 *raw_chain = NULL;
u64 raw_chain_size = 0;
if (!certRetrieveCertificateChainBySignatureIssuer(&chain, issuer))
2020-04-19 23:44:22 +01:00
{
LOG_MSG_ERROR("Error retrieving certificate chain for \"%s\"!", issuer);
return NULL;
}
raw_chain_size = certCalculateRawCertificateChainSize(&chain);
raw_chain = malloc(raw_chain_size);
if (!raw_chain)
{
LOG_MSG_ERROR("Unable to allocate memory for raw \"%s\" certificate chain! (0x%lX).", issuer, raw_chain_size);
goto end;
2020-04-19 23:44:22 +01:00
}
certCopyCertificateChainDataToMemoryBuffer(raw_chain, &chain);
*out_size = raw_chain_size;
end:
certFreeCertificateChain(&chain);
return raw_chain;
2020-04-19 23:44:22 +01:00
}
u8 *certRetrieveRawCertificateChainFromGameCardByRightsId(const FsRightsId *id, u64 *out_size)
2020-04-11 06:28:26 +01:00
{
if (!id || !out_size)
2020-04-11 06:28:26 +01:00
{
LOG_MSG_ERROR("Invalid parameters!");
2020-04-11 06:28:26 +01:00
return NULL;
}
char raw_chain_filename[0x30] = {0};
u64 raw_chain_offset = 0, raw_chain_size = 0;
2020-04-11 06:28:26 +01:00
u8 *raw_chain = NULL;
bool success = false;
utilsGenerateHexStringFromData(raw_chain_filename, sizeof(raw_chain_filename), id->c, sizeof(id->c), false);
strcat(raw_chain_filename, ".cert");
I'm a terrible person and an even worse developer. And I don't need anyone to tell me so, thank you very much. * PoC: remove gc_dumper and nsp_dumper PoC; create nxdt_rw_poc with all gc_dumper and nsp_dumper capabilities + standalone ticket dumping + raw NCA dumping; use ftruncate() to set output file sizes whenever possible. PoC code is a mess, as always. Expect the features from the rest of the PoCs to be implemented into nxdt_rw_poc soon. * workflow: temporarily disable borealis build generation; comment out manual installation of up-to-date packages from Leseratte's mirrors because the latest devkitA64 Docker image has them all. * borealis: update to fix building issues with latest devkitA64. * bfttf: error out on invalid NCA signatures. * config: save configuration to the current working directory; parse and validate new "gamecard/write_raw_hfs_partition" flag. * defines: remove CONFIG_PATH macro; rename CONFIG_FILE_NAME. * gamecard: rename fs_ctx -> hfs_ctx everywhere; use HFS function calls to retrieve partition names. * hfs: move GameCardHashFileSystemPartitionType enum from gamecard.h and rename it to HashFileSystemPartitionType; add hfsIsValidContext(); add hfsGetPartitionNameString(). * nca/npdm: update comments to reflect latest HOS version. * nxdt_bfsar: always generate absolute SD card paths with the device name; error out on an invalid NCA signature. * nxdt_includes: include dirent.h; refactor Version struct to make it a union of all known *Version structs. * nxdt_log: don't write session separator if the logfile is empty. * nxdt_utils: log appletIsGamePlayRecordingSupported() errors; add utilsDeleteDirectoryRecursively(). * rsa: provide clearer function descriptions in header file. * services: handle usb:ds initialization. * tik: update tikConvertPersonalizedTicketToCommonTicket() to allow NULL input pointers as raw certificate chain arguments (much needed for standalone ticket dumping). * title: add titleGetApplicationIdByMetaKey(). * usb: refactor interface (de)initialization code; slightly improve ABI usage (console-side only); redefine ABI version field in StartSession command blocks; upgrade ABI to v1.1. * FatFs: rename DIR -> FDIR to avoid conflicts with definitions from stdlib's dirent.h. * gamecard_tab: display package ID from the inserted gamecard; fix displayed version numbers from bundled system updates below 3.0.0. * todo: add notes about creating devoptab devices for HFS/PFS/RomFS file tree dumping.
2023-05-24 20:05:34 +01:00
if (!gamecardGetHashFileSystemEntryInfoByName(HashFileSystemPartitionType_Secure, raw_chain_filename, &raw_chain_offset, &raw_chain_size))
2020-04-11 06:28:26 +01:00
{
LOG_MSG_ERROR("Error retrieving offset and size for \"%s\" entry in secure hash FS partition!", raw_chain_filename);
2020-04-11 06:28:26 +01:00
return NULL;
}
if (raw_chain_size < SIGNED_CERT_MIN_SIZE)
{
LOG_MSG_ERROR("Invalid size for \"%s\"! (0x%lX).", raw_chain_filename, raw_chain_size);
return NULL;
}
2020-04-11 06:28:26 +01:00
raw_chain = malloc(raw_chain_size);
if (!raw_chain)
{
LOG_MSG_ERROR("Unable to allocate memory for raw \"%s\" certificate chain! (0x%lX).", raw_chain_filename, raw_chain_size);
return NULL;
}
if (!gamecardReadStorage(raw_chain, raw_chain_size, raw_chain_offset))
{
LOG_MSG_ERROR("Failed to read \"%s\" data from the inserted gamecard!", raw_chain_filename);
goto end;
2020-04-11 06:28:26 +01:00
}
2020-04-11 06:28:26 +01:00
*out_size = raw_chain_size;
success = true;
end:
if (!success && raw_chain)
{
free(raw_chain);
raw_chain = NULL;
}
2020-04-11 06:28:26 +01:00
return raw_chain;
}
static bool certOpenEsCertSaveFile(void)
{
if (g_esCertSaveCtx) return true;
g_esCertSaveCtx = save_open_savefile(CERT_SAVEFILE_PATH, 0);
if (!g_esCertSaveCtx)
{
LOG_MSG_ERROR("Failed to open ES certificate system savefile!");
return false;
}
return true;
}
static void certCloseEsCertSaveFile(void)
{
if (!g_esCertSaveCtx) return;
save_close_savefile(g_esCertSaveCtx);
g_esCertSaveCtx = NULL;
}
static bool _certRetrieveCertificateByName(Certificate *dst, const char *name)
{
if (!g_esCertSaveCtx)
{
LOG_MSG_ERROR("ES certificate savefile not opened!");
return false;
}
u64 cert_size = 0;
char cert_path[SAVE_FS_LIST_MAX_NAME_LENGTH] = {0};
allocation_table_storage_ctx_t fat_storage = {0};
snprintf(cert_path, SAVE_FS_LIST_MAX_NAME_LENGTH, CERT_SAVEFILE_STORAGE_BASE_PATH "%s", name);
if (!save_get_fat_storage_from_file_entry_by_path(g_esCertSaveCtx, cert_path, &fat_storage, &cert_size))
{
LOG_MSG_ERROR("Failed to locate certificate \"%s\" in ES certificate system save!", name);
return false;
}
if (cert_size < SIGNED_CERT_MIN_SIZE || cert_size > SIGNED_CERT_MAX_SIZE)
{
LOG_MSG_ERROR("Invalid size for certificate \"%s\"! (0x%lX).", name, cert_size);
return false;
}
dst->size = cert_size;
u64 br = save_allocation_table_storage_read(&fat_storage, dst->data, 0, dst->size);
if (br != dst->size)
{
LOG_MSG_ERROR("Failed to read 0x%lX bytes from certificate \"%s\"! Read 0x%lX bytes.", dst->size, name, br);
return false;
}
dst->type = certGetCertificateType(dst->data, dst->size);
2020-04-19 23:44:22 +01:00
if (dst->type == CertType_None)
{
LOG_MSG_ERROR("Invalid certificate type for \"%s\"!", name);
return false;
}
return true;
}
static u8 certGetCertificateType(void *data, u64 data_size)
2020-04-11 06:28:26 +01:00
{
CertCommonBlock *cert_common_block = NULL;
u32 sig_type = 0, pub_key_type = 0;
u64 signed_cert_size = 0;
u8 type = CertType_None;
if (!data || data_size < SIGNED_CERT_MIN_SIZE || data_size > SIGNED_CERT_MAX_SIZE)
2020-04-11 06:28:26 +01:00
{
LOG_MSG_ERROR("Invalid parameters!");
return type;
2020-04-11 06:28:26 +01:00
}
if (!(cert_common_block = certGetCommonBlock(data)) || !(signed_cert_size = certGetSignedCertificateSize(data)) || signed_cert_size > data_size)
{
LOG_MSG_ERROR("Input buffer doesn't hold a valid signed certificate!");
return type;
}
sig_type = signatureGetSigType(data, true);
pub_key_type = __builtin_bswap32(cert_common_block->pub_key_type);
2020-04-11 06:28:26 +01:00
switch(sig_type)
{
case SignatureType_Rsa4096Sha1:
case SignatureType_Rsa4096Sha256:
type = CERT_TYPE(Rsa4096);
2020-04-11 06:28:26 +01:00
break;
case SignatureType_Rsa2048Sha1:
case SignatureType_Rsa2048Sha256:
type = CERT_TYPE(Rsa2048);
2020-04-11 06:28:26 +01:00
break;
case SignatureType_Ecc480Sha1:
case SignatureType_Ecc480Sha256:
type = CERT_TYPE(Ecc480);
break;
case SignatureType_Hmac160Sha1:
type = CERT_TYPE(Hmac160);
2020-04-11 06:28:26 +01:00
break;
default:
break;
}
2020-04-11 06:28:26 +01:00
return type;
}
static bool _certRetrieveCertificateChainBySignatureIssuer(CertificateChain *dst, const char *issuer)
{
if (!g_esCertSaveCtx)
{
LOG_MSG_ERROR("ES certificate savefile not opened!");
return false;
}
u32 i = 0;
char issuer_copy[0x40] = {0}, *pch = NULL, *state = NULL;
bool success = true;
dst->count = certGetCertificateCountInSignatureIssuer(issuer);
if (!dst->count)
{
LOG_MSG_ERROR("Invalid signature issuer string!");
return false;
}
dst->certs = calloc(dst->count, sizeof(Certificate));
if (!dst->certs)
{
LOG_MSG_ERROR("Unable to allocate memory for the certificate chain! (0x%lX).", dst->count * sizeof(Certificate));
return false;
}
/* Copy string to avoid problems with strtok_r(). */
/* The "Root-" parent from the issuer string is skipped. */
snprintf(issuer_copy, sizeof(issuer_copy), "%s", issuer + 5);
pch = strtok_r(issuer_copy, "-", &state);
while(pch)
{
if (!_certRetrieveCertificateByName(&(dst->certs[i]), pch))
{
LOG_MSG_ERROR("Unable to retrieve certificate \"%s\"!", pch);
success = false;
break;
}
i++;
pch = strtok_r(NULL, "-", &state);
}
if (!success) certFreeCertificateChain(dst);
return success;
}
2020-04-11 06:28:26 +01:00
static u32 certGetCertificateCountInSignatureIssuer(const char *issuer)
{
if (!issuer || !*issuer) return 0;
2020-04-11 06:28:26 +01:00
u32 count = 0;
char issuer_copy[0x40] = {0}, *pch = NULL, *state = NULL;
/* Copy string to avoid problems with strtok_r(). */
/* The "Root-" parent from the issuer string is skipped. */
snprintf(issuer_copy, sizeof(issuer_copy), "%s", issuer + 5);
pch = strtok_r(issuer_copy, "-", &state);
while(pch)
2020-04-11 06:28:26 +01:00
{
count++;
pch = strtok_r(NULL, "-", &state);
2020-04-11 06:28:26 +01:00
}
2020-04-11 06:28:26 +01:00
return count;
}
static u64 certCalculateRawCertificateChainSize(const CertificateChain *chain)
{
if (!chain || !chain->count || !chain->certs) return 0;
2020-04-11 06:28:26 +01:00
u64 chain_size = 0;
for(u32 i = 0; i < chain->count; i++) chain_size += chain->certs[i].size;
return chain_size;
}
static void certCopyCertificateChainDataToMemoryBuffer(void *dst, const CertificateChain *chain)
{
if (!chain || !chain->count || !chain->certs) return;
2020-04-11 06:28:26 +01:00
u8 *dst_u8 = (u8*)dst;
for(u32 i = 0; i < chain->count; i++)
{
2020-10-14 19:58:33 +01:00
Certificate *cert = &(chain->certs[i]);
memcpy(dst_u8, cert->data, cert->size);
dst_u8 += cert->size;
2020-04-11 06:28:26 +01:00
}
}