mirror of
https://github.com/DarkMatterCore/nxdumptool.git
synced 2024-11-08 11:51:48 +00:00
Crypto changes.
* Implemented RSA-2048-PSS + SHA256 signature verification. * Refactored RSA-2048-OAEP decryption steps to use mbedtls function calls. * Implemented NCA header main signature verification. * Replaced Björn Samuelsson's CRC32 algorithm with the hardware accelerated CRC32 checksum calculation from libnx (latest commit with support for calculation in blocks).
This commit is contained in:
parent
f82d7a3db4
commit
f526d4e6f4
12 changed files with 222 additions and 249 deletions
|
@ -23,7 +23,6 @@
|
|||
#include "gamecard.h"
|
||||
#include "usb.h"
|
||||
#include "title.h"
|
||||
#include "crc32_fast.h"
|
||||
|
||||
#define BLOCK_SIZE USB_TRANSFER_BUFFER_SIZE
|
||||
|
||||
|
@ -446,7 +445,7 @@ static bool sendGameCardKeyAreaViaUsb(void)
|
|||
|
||||
if (!dumpGameCardKeyArea(&gc_key_area) || !filename) goto end;
|
||||
|
||||
crc32FastCalculate(&(gc_key_area.initial_data), sizeof(GameCardInitialData), &crc);
|
||||
crc = crc32Calculate(&(gc_key_area.initial_data), sizeof(GameCardInitialData));
|
||||
snprintf(path, MAX_ELEMENTS(path), "%s (Initial Data) (%08X).bin", filename, crc);
|
||||
|
||||
if (!sendFileData(path, &(gc_key_area.initial_data), sizeof(GameCardInitialData))) goto end;
|
||||
|
@ -484,7 +483,7 @@ static bool sendGameCardCertificateViaUsb(void)
|
|||
|
||||
consolePrint("get gamecard certificate ok\n");
|
||||
|
||||
crc32FastCalculate(&gc_cert, sizeof(FsGameCardCertificate), &crc);
|
||||
crc = crc32Calculate(&gc_cert, sizeof(FsGameCardCertificate));
|
||||
snprintf(path, MAX_ELEMENTS(path), "%s (Certificate) (%08X).bin", filename, crc);
|
||||
|
||||
if (!sendFileData(path, &gc_cert, sizeof(FsGameCardCertificate))) goto end;
|
||||
|
@ -549,9 +548,15 @@ static bool sendGameCardImageViaUsb(void)
|
|||
if (g_appendKeyArea)
|
||||
{
|
||||
gc_size += sizeof(GameCardKeyArea);
|
||||
|
||||
if (!dumpGameCardKeyArea(&gc_key_area)) goto end;
|
||||
if (g_calcCrc) crc32FastCalculate(&gc_key_area, sizeof(GameCardKeyArea), &key_area_crc);
|
||||
shared_data.full_xci_crc = key_area_crc;
|
||||
|
||||
if (g_calcCrc)
|
||||
{
|
||||
key_area_crc = crc32Calculate(&gc_key_area, sizeof(GameCardKeyArea));
|
||||
if (g_appendKeyArea) shared_data.full_xci_crc = key_area_crc;
|
||||
}
|
||||
|
||||
consolePrint("gamecard size (with key area): 0x%lX\n", gc_size);
|
||||
}
|
||||
|
||||
|
@ -730,8 +735,8 @@ static void read_thread_func(void *arg)
|
|||
/* Update checksum */
|
||||
if (g_calcCrc)
|
||||
{
|
||||
crc32FastCalculate(buf, blksize, &(shared_data->xci_crc));
|
||||
if (g_appendKeyArea) crc32FastCalculate(buf, blksize, &(shared_data->full_xci_crc));
|
||||
shared_data->xci_crc = crc32CalculateWithSeed(shared_data->xci_crc, buf, blksize);
|
||||
if (g_appendKeyArea) shared_data->full_xci_crc = crc32CalculateWithSeed(shared_data->full_xci_crc, buf, blksize);
|
||||
}
|
||||
|
||||
/* Wait until the previous data chunk has been written */
|
||||
|
|
|
@ -1,42 +0,0 @@
|
|||
/*
|
||||
* crc32_fast.h
|
||||
*
|
||||
* Based on the standard CRC32 checksum fast public domain implementation for
|
||||
* little-endian architecures by Björn Samuelsson (http://home.thep.lu.se/~bjorn/crc).
|
||||
*
|
||||
* Copyright (c) 2020-2021, 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.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifndef __CRC32_FAST_H__
|
||||
#define __CRC32_FAST_H__
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/// Calculates a CRC32 checksum over the provided input buffer. Checksum calculation in chunks is supported.
|
||||
/// CRC32 calculation state is both read from and saved to 'crc', which should be zero during the first call to this function.
|
||||
void crc32FastCalculate(const void *data, u64 n_bytes, u32 *crc);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* __CRC32_FAST_H__ */
|
|
@ -37,6 +37,9 @@ bool keysLoadNcaKeyset(void);
|
|||
/// Returns a pointer to the AES-128-XTS NCA header key, or NULL if keydata hasn't been loaded.
|
||||
const u8 *keysGetNcaHeaderKey(void);
|
||||
|
||||
/// Returns a pointer to the RSA-2048-PSS modulus for the NCA header main signature, using the provided key generation value.
|
||||
const u8 *keysGetNcaMainSignatureModulus(u8 key_generation);
|
||||
|
||||
/// Decrypts 'src' into 'dst' using the provided key area encryption key index and key generation values. Runtime sealed keydata from the SMC AES engine is used to achieve this.
|
||||
/// Both 'dst' and 'src' buffers must have a size of at least AES_128_KEY_SIZE.
|
||||
/// Returns false if an error occurs or if keydata hasn't been loaded.
|
||||
|
|
|
@ -53,7 +53,7 @@ extern "C" {
|
|||
|
||||
#define NCA_AES_XTS_SECTOR_SIZE 0x200
|
||||
|
||||
#define NCA_ACID_SIGNATURE_AREA_SIZE 0x200 /* Signature is calculated starting at the NCA header magic word. */
|
||||
#define NCA_SIGNATURE_AREA_SIZE 0x200 /* Signature is calculated starting at the NCA header magic word. */
|
||||
|
||||
typedef enum {
|
||||
NcaDistributionType_Download = 0,
|
||||
|
@ -369,14 +369,17 @@ typedef struct {
|
|||
u8 id_offset; ///< Retrieved from NcmContentInfo.
|
||||
bool rights_id_available;
|
||||
bool titlekey_retrieved;
|
||||
bool valid_main_signature;
|
||||
u8 titlekey[AES_128_KEY_SIZE]; ///< Decrypted titlekey from the ticket.
|
||||
NcaHeader header; ///< Plaintext NCA header.
|
||||
u8 header_hash[SHA256_HASH_SIZE]; ///< Plaintext NCA header hash. Used to determine if it's necessary to replace the NCA header while dumping this NCA.
|
||||
NcaHeader encrypted_header; ///< Encrypted NCA header. If the plaintext NCA header is modified, this will hold an encrypted copy of it.
|
||||
///< Otherwise, this holds the unmodified, encrypted NCA header.
|
||||
bool header_written; ///< Set to true after the NCA header and the FS section headers have been written to an output dump.
|
||||
NcaFsSectionContext fs_ctx[NCA_FS_HEADER_COUNT];
|
||||
NcaDecryptedKeyArea decrypted_key_area;
|
||||
NcaFsSectionContext fs_ctx[NCA_FS_HEADER_COUNT];
|
||||
|
||||
///< NSP-related fields.
|
||||
bool header_written; ///< Set to true after the NCA header and the FS section headers have been written to an output dump.
|
||||
void *content_type_ctx; ///< Pointer to a content type context (e.g. ContentMetaContext, ProgramInfoContext, NacpContext, LegalInfoContext). Set to NULL if unused.
|
||||
bool content_type_ctx_patch; ///< Set to true if a NCA patch generated by the content type context is needed and hasn't been completely writen yet.
|
||||
u32 content_type_ctx_data_idx; ///< Start index for the data generated by the content type context. Used while creating NSPs.
|
||||
|
|
|
@ -30,20 +30,31 @@
|
|||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define RSA2048_SIG_SIZE 0x100
|
||||
#define RSA2048_PUBKEY_SIZE RSA2048_SIG_SIZE
|
||||
#define RSA2048_BYTES 0x100
|
||||
#define RSA2048_BITS (RSA2048_BYTES * 8)
|
||||
|
||||
#define RSA2048_SIG_SIZE RSA2048_BYTES
|
||||
#define RSA2048_PUBKEY_SIZE RSA2048_BYTES
|
||||
|
||||
/// Returns a pointer to the RSA-2048 public key that can be used to verify signatures generated by rsa2048GenerateSha256BasedPssSignature().
|
||||
/// Suitable to replace the ACID public key in a NPDM.
|
||||
const u8 *rsa2048GetCustomPublicKey(void);
|
||||
|
||||
/// Verifies a RSA-2048-PSS with SHA-256 signature.
|
||||
/// The provided signature and modulus should have sizes of at least RSA2048_SIG_SIZE and RSA2048_PUBKEY_SIZE, respectively.
|
||||
bool rsa2048VerifySha256BasedPssSignature(const void *data, size_t data_size, const void *signature, const void *modulus, const void *public_exponent, size_t public_exponent_size);
|
||||
|
||||
/// Generates a RSA-2048-PSS with SHA-256 signature using a custom RSA-2048 private key.
|
||||
/// Suitable to replace the ACID signature in a Program NCA header.
|
||||
/// Destination buffer size should be at least RSA2048_SIG_SIZE.
|
||||
bool rsa2048GenerateSha256BasedPssSignature(void *dst, const void *src, size_t size);
|
||||
|
||||
/// Returns a pointer to the RSA-2048 public key that can be used to verify signatures generated by rsa2048GenerateSha256BasedPssSignature().
|
||||
/// Suitable to replace the ACID public key in a NPDM.
|
||||
const u8 *rsa2048GetCustomPublicKey(void);
|
||||
|
||||
/// Performs RSA-2048-OAEP decryption and verification. Used to decrypt the titlekey block from tickets with personalized crypto.
|
||||
bool rsa2048OaepDecryptAndVerify(void *dst, size_t dst_size, const void *signature, const void *modulus, const void *exponent, size_t exponent_size, const void *label_hash, size_t *out_size);
|
||||
/// Performs RSA-2048-OAEP decryption.
|
||||
/// Suitable to decrypt the titlekey block from tickets with personalized crypto.
|
||||
/// The provided signature and modulus should have sizes of at least RSA2048_SIG_SIZE and RSA2048_PUBKEY_SIZE, respectively.
|
||||
/// The label and label_size arguments are optional - these may be set to NULL and 0 if not needed, respectively.
|
||||
bool rsa2048OaepDecrypt(void *dst, size_t dst_size, const void *signature, const void *modulus, const void *public_exponent, size_t public_exponent_size, const void *private_exponent, \
|
||||
size_t private_exponent_size, const void *label, size_t label_size, size_t *out_size);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
|
|
@ -1,63 +0,0 @@
|
|||
/*
|
||||
* crc32_fast.c
|
||||
*
|
||||
* Based on the standard CRC32 checksum fast public domain implementation for
|
||||
* little-endian architecures by Björn Samuelsson (http://home.thep.lu.se/~bjorn/crc).
|
||||
*
|
||||
* Copyright (c) 2020-2021, 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.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "nxdt_utils.h"
|
||||
|
||||
static u32 crc32FastGetTableValueByIndex(u32 r)
|
||||
{
|
||||
for(u32 j = 0; j < 8; ++j) r = ((r & 1 ? 0 : (u32)0xEDB88320) ^ r >> 1);
|
||||
return (r ^ (u32)0xFF000000);
|
||||
}
|
||||
|
||||
static void crc32FastInitializeTables(u32 *table, u32 *wtable)
|
||||
{
|
||||
for(u32 i = 0; i < 0x100; ++i) table[i] = crc32FastGetTableValueByIndex(i);
|
||||
|
||||
for(u32 k = 0; k < 4; ++k)
|
||||
{
|
||||
for(u32 w, i = 0; i < 0x100; ++i)
|
||||
{
|
||||
for(u32 j = w = 0; j < 4; ++j) w = (table[(u8)(j == k ? (w ^ i) : w)] ^ w >> 8);
|
||||
wtable[(k << 8) + i] = (w ^ (k ? wtable[0] : 0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void crc32FastCalculate(const void *data, u64 n_bytes, u32 *crc)
|
||||
{
|
||||
if (!data || !n_bytes || !crc) return;
|
||||
|
||||
static u32 table[0x100] = {0}, wtable[0x400] = {0};
|
||||
u64 n_accum = (n_bytes / 4);
|
||||
|
||||
if (!*table) crc32FastInitializeTables(table, wtable);
|
||||
|
||||
for(u64 i = 0; i < n_accum; ++i)
|
||||
{
|
||||
u32 a = (*crc ^ ((const u32*)data)[i]);
|
||||
for(u32 j = *crc = 0; j < 4; ++j) *crc ^= wtable[(j << 8) + (u8)(a >> 8 * j)];
|
||||
}
|
||||
|
||||
for(u64 i = (n_accum * 4); i < n_bytes; ++i) *crc = (table[(u8)*crc ^ ((const u8*)data)[i]] ^ *crc >> 8);
|
||||
}
|
|
@ -76,10 +76,10 @@ typedef struct {
|
|||
/// Used to parse the eTicket RSA device key retrieved from PRODINFO via setcalGetEticketDeviceKey().
|
||||
/// Everything after the AES CTR is encrypted using the eTicket RSA device key encryption key.
|
||||
typedef struct {
|
||||
u8 ctr[0x10];
|
||||
u8 exponent[0x100];
|
||||
u8 modulus[0x100];
|
||||
u32 public_exponent; ///< Must match ETICKET_RSA_DEVICE_KEY_PUBLIC_EXPONENT. Stored using big endian byte order.
|
||||
u8 ctr[AES_128_KEY_SIZE];
|
||||
u8 private_exponent[RSA2048_BYTES];
|
||||
u8 modulus[RSA2048_BYTES];
|
||||
u32 public_exponent; ///< Must match ETICKET_RSA_DEVICE_KEY_PUBLIC_EXPONENT. Stored using big endian byte order.
|
||||
u8 padding[0x14];
|
||||
u64 device_id;
|
||||
u8 ghash[0x10];
|
||||
|
@ -116,12 +116,6 @@ static Mutex g_ncaKeysetMutex = 0;
|
|||
|
||||
static SetCalRsa2048DeviceKey g_eTicketRsaDeviceKey = {0};
|
||||
|
||||
/// Used during the RSA-OAEP titlekey decryption steps.
|
||||
static const u8 g_nullHash[0x20] = {
|
||||
0xE3, 0xB0, 0xC4, 0x42, 0x98, 0xFC, 0x1C, 0x14, 0x9A, 0xFB, 0xF4, 0xC8, 0x99, 0x6F, 0xB9, 0x24,
|
||||
0x27, 0xAE, 0x41, 0xE4, 0x64, 0x9B, 0x93, 0x4C, 0xA4, 0x95, 0x99, 0x1B, 0x78, 0x52, 0xB8, 0x55
|
||||
};
|
||||
|
||||
static KeysMemoryInfo g_fsRodataMemoryInfo = {
|
||||
.location = {
|
||||
.program_id = FS_SYSMODULE_TID,
|
||||
|
@ -304,6 +298,33 @@ const u8 *keysGetNcaHeaderKey(void)
|
|||
return ret;
|
||||
}
|
||||
|
||||
const u8 *keysGetNcaMainSignatureModulus(u8 key_generation)
|
||||
{
|
||||
if (key_generation > NcaMainSignatureKeyGeneration_Current)
|
||||
{
|
||||
LOG_MSG("Invalid key generation value! (0x%02X).", key_generation);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bool dev_unit = utilsIsDevelopmentUnit();
|
||||
const u8 *ret = NULL, null_modulus[RSA2048_PUBKEY_SIZE] = {0};
|
||||
|
||||
SCOPED_LOCK(&g_ncaKeysetMutex)
|
||||
{
|
||||
if (!g_ncaKeysetLoaded) break;
|
||||
|
||||
ret = (const u8*)(dev_unit ? g_ncaKeyset.nca_main_signature_moduli_dev[key_generation] : g_ncaKeyset.nca_main_signature_moduli_prod[key_generation]);
|
||||
|
||||
if (!memcmp(ret, null_modulus, RSA2048_PUBKEY_SIZE))
|
||||
{
|
||||
LOG_MSG("%s NCA header main signature modulus 0x%02X unavailable.", dev_unit ? "Development" : "Retail", key_generation);
|
||||
ret = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool keysDecryptNcaKeyAreaEntry(u8 kaek_index, u8 key_generation, void *dst, const void *src)
|
||||
{
|
||||
bool ret = false;
|
||||
|
@ -379,14 +400,15 @@ bool keysDecryptRsaOaepWrappedTitleKey(const void *rsa_wrapped_titlekey, void *o
|
|||
if (!g_ncaKeysetLoaded) break;
|
||||
|
||||
size_t out_keydata_size = 0;
|
||||
u8 out_keydata[0x100] = {0};
|
||||
u8 out_keydata[RSA2048_BYTES] = {0};
|
||||
|
||||
/* Get eTicket RSA device key. */
|
||||
EticketRsaDeviceKey *eticket_rsa_key = (EticketRsaDeviceKey*)g_eTicketRsaDeviceKey.key;
|
||||
|
||||
/* Perform a RSA-OAEP unwrap operation to get the encrypted titlekey. */
|
||||
ret = (rsa2048OaepDecryptAndVerify(out_keydata, sizeof(out_keydata), rsa_wrapped_titlekey, eticket_rsa_key->modulus, eticket_rsa_key->exponent, sizeof(eticket_rsa_key->exponent), \
|
||||
g_nullHash, &out_keydata_size) && out_keydata_size >= AES_128_KEY_SIZE);
|
||||
/* ES uses a NULL string as the label. */
|
||||
ret = (rsa2048OaepDecrypt(out_keydata, sizeof(out_keydata), rsa_wrapped_titlekey, eticket_rsa_key->modulus, &(eticket_rsa_key->public_exponent), sizeof(eticket_rsa_key->public_exponent), \
|
||||
eticket_rsa_key->private_exponent, sizeof(eticket_rsa_key->private_exponent), NULL, 0, &out_keydata_size) && out_keydata_size >= AES_128_KEY_SIZE);
|
||||
if (ret)
|
||||
{
|
||||
/* Copy RSA-OAEP unwrapped titlekey. */
|
||||
|
@ -859,7 +881,7 @@ static bool keysGetDecryptedEticketRsaDeviceKey(void)
|
|||
/* Decrypt eTicket RSA device key. */
|
||||
eticket_rsa_key = (EticketRsaDeviceKey*)g_eTicketRsaDeviceKey.key;
|
||||
aes128CtrContextCreate(&eticket_aes_ctx, eticket_rsa_kek, eticket_rsa_key->ctr);
|
||||
aes128CtrCrypt(&eticket_aes_ctx, &(eticket_rsa_key->exponent), &(eticket_rsa_key->exponent), sizeof(EticketRsaDeviceKey) - sizeof(eticket_rsa_key->ctr));
|
||||
aes128CtrCrypt(&eticket_aes_ctx, &(eticket_rsa_key->private_exponent), &(eticket_rsa_key->private_exponent), sizeof(EticketRsaDeviceKey) - sizeof(eticket_rsa_key->ctr));
|
||||
|
||||
/* Public exponent value must be 0x10001. */
|
||||
/* It is stored using big endian byte order. */
|
||||
|
@ -871,7 +893,7 @@ static bool keysGetDecryptedEticketRsaDeviceKey(void)
|
|||
}
|
||||
|
||||
/* Test RSA key pair. */
|
||||
if (!keysTestEticketRsaDeviceKey(&(eticket_rsa_key->public_exponent), eticket_rsa_key->exponent, eticket_rsa_key->modulus))
|
||||
if (!keysTestEticketRsaDeviceKey(&(eticket_rsa_key->public_exponent), eticket_rsa_key->private_exponent, eticket_rsa_key->modulus))
|
||||
{
|
||||
LOG_MSG("eTicket RSA device key test failed! Wrong keys?");
|
||||
return false;
|
||||
|
@ -889,7 +911,7 @@ static bool keysTestEticketRsaDeviceKey(const void *e, const void *d, const void
|
|||
}
|
||||
|
||||
Result rc = 0;
|
||||
u8 x[0x100] = {0}, y[0x100] = {0}, z[0x100] = {0};
|
||||
u8 x[RSA2048_BYTES] = {0}, y[RSA2048_BYTES] = {0}, z[RSA2048_BYTES] = {0};
|
||||
|
||||
/* 0xCAFEBABE. */
|
||||
x[0xFC] = 0xCA;
|
||||
|
@ -897,7 +919,7 @@ static bool keysTestEticketRsaDeviceKey(const void *e, const void *d, const void
|
|||
x[0xFE] = 0xBA;
|
||||
x[0xFF] = 0xBE;
|
||||
|
||||
rc = splUserExpMod(x, n, d, 0x100, y);
|
||||
rc = splUserExpMod(x, n, d, RSA2048_BYTES, y);
|
||||
if (R_FAILED(rc))
|
||||
{
|
||||
LOG_MSG("splUserExpMod failed! (#1) (0x%08X).", rc);
|
||||
|
@ -911,7 +933,7 @@ static bool keysTestEticketRsaDeviceKey(const void *e, const void *d, const void
|
|||
return false;
|
||||
}
|
||||
|
||||
if (memcmp(x, z, 0x100) != 0)
|
||||
if (memcmp(x, z, RSA2048_BYTES) != 0)
|
||||
{
|
||||
LOG_MSG("Invalid RSA key pair!");
|
||||
return false;
|
||||
|
|
|
@ -34,20 +34,25 @@
|
|||
static u8 *g_ncaCryptoBuffer = NULL;
|
||||
static Mutex g_ncaCryptoBufferMutex = 0;
|
||||
|
||||
/// Used to verify if the key area from a NCA0 is encrypted.
|
||||
static const u8 g_nca0KeyAreaHash[SHA256_HASH_SIZE] = {
|
||||
0x9A, 0xBB, 0xD2, 0x11, 0x86, 0x00, 0x21, 0x9D, 0x7A, 0xDC, 0x5B, 0x43, 0x95, 0xF8, 0x4E, 0xFD,
|
||||
0xFF, 0x6B, 0x25, 0xEF, 0x9F, 0x96, 0x85, 0x28, 0x18, 0x9E, 0x76, 0xB0, 0x92, 0xF0, 0x6A, 0xCB
|
||||
};
|
||||
|
||||
/// Used to verify the NCA header main signature.
|
||||
static const u8 g_ncaHeaderMainSignaturePublicExponent[3] = { 0x01, 0x00, 0x01 };
|
||||
|
||||
/* Function prototypes. */
|
||||
|
||||
NX_INLINE bool ncaIsFsInfoEntryValid(NcaFsInfo *fs_info);
|
||||
|
||||
static bool ncaReadDecryptedHeader(NcaContext *ctx);
|
||||
static bool ncaDecryptKeyArea(NcaContext *ctx);
|
||||
|
||||
static bool ncaEncryptKeyArea(NcaContext *ctx);
|
||||
|
||||
static bool ncaVerifyMainSignature(NcaContext *ctx);
|
||||
|
||||
NX_INLINE bool ncaIsVersion0KeyAreaEncrypted(NcaContext *ctx);
|
||||
NX_INLINE u8 ncaGetKeyGenerationValue(NcaContext *ctx);
|
||||
NX_INLINE bool ncaCheckRightsIdAvailability(NcaContext *ctx);
|
||||
|
@ -597,6 +602,7 @@ static bool ncaReadDecryptedHeader(NcaContext *ctx)
|
|||
ctx->key_generation = ncaGetKeyGenerationValue(ctx);
|
||||
ctx->rights_id_available = ncaCheckRightsIdAvailability(ctx);
|
||||
sha256CalculateHash(ctx->header_hash, &(ctx->header), sizeof(NcaHeader));
|
||||
ctx->valid_main_signature = ncaVerifyMainSignature(ctx);
|
||||
|
||||
/* Decrypt NCA key area (if needed). */
|
||||
if (!ctx->rights_id_available && !ncaDecryptKeyArea(ctx))
|
||||
|
@ -734,6 +740,23 @@ static bool ncaEncryptKeyArea(NcaContext *ctx)
|
|||
return true;
|
||||
}
|
||||
|
||||
static bool ncaVerifyMainSignature(NcaContext *ctx)
|
||||
{
|
||||
if (!ctx)
|
||||
{
|
||||
LOG_MSG("Invalid NCA context!");
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Retrieve modulus for the NCA main signature. */
|
||||
const u8 *modulus = keysGetNcaMainSignatureModulus(ctx->header.main_signature_key_generation);
|
||||
if (!modulus) return false;
|
||||
|
||||
/* Verify NCA signature. */
|
||||
return rsa2048VerifySha256BasedPssSignature(&(ctx->header.magic), NCA_SIGNATURE_AREA_SIZE, ctx->header.main_signature, modulus, g_ncaHeaderMainSignaturePublicExponent, \
|
||||
sizeof(g_ncaHeaderMainSignaturePublicExponent));
|
||||
}
|
||||
|
||||
NX_INLINE bool ncaIsVersion0KeyAreaEncrypted(NcaContext *ctx)
|
||||
{
|
||||
if (!ctx || ctx->format_version != NcaVersion_Nca0) return false;
|
||||
|
|
|
@ -316,7 +316,7 @@ bool npdmGenerateNcaPatch(NpdmContext *npdm_ctx)
|
|||
}
|
||||
|
||||
/* Update NCA ACID signature. */
|
||||
if (!rsa2048GenerateSha256BasedPssSignature(nca_ctx->header.acid_signature, &(nca_ctx->header.magic), NCA_ACID_SIGNATURE_AREA_SIZE))
|
||||
if (!rsa2048GenerateSha256BasedPssSignature(nca_ctx->header.acid_signature, &(nca_ctx->header.magic), NCA_SIGNATURE_AREA_SIZE))
|
||||
{
|
||||
LOG_MSG("Failed to generate RSA-2048-PSS NCA ACID signature!");
|
||||
return false;
|
||||
|
|
|
@ -24,11 +24,10 @@
|
|||
#include "nxdt_utils.h"
|
||||
#include "rsa.h"
|
||||
|
||||
#include <mbedtls/rsa.h>
|
||||
#include <mbedtls/entropy.h>
|
||||
#include <mbedtls/ctr_drbg.h>
|
||||
#include <mbedtls/md.h>
|
||||
#include <mbedtls/rsa.h>
|
||||
#include <mbedtls/x509.h>
|
||||
#include <mbedtls/pk.h>
|
||||
|
||||
/* Global variables. */
|
||||
|
||||
|
@ -84,7 +83,53 @@ static const u8 g_rsa2048CustomPublicKey[] = {
|
|||
|
||||
/* Function prototypes. */
|
||||
|
||||
static void rsaCalculateMgf1AndXor(void *data, size_t data_size, const void *h_src, size_t h_src_size);
|
||||
const u8 *rsa2048GetCustomPublicKey(void)
|
||||
{
|
||||
return g_rsa2048CustomPublicKey;
|
||||
}
|
||||
|
||||
bool rsa2048VerifySha256BasedPssSignature(const void *data, size_t data_size, const void *signature, const void *modulus, const void *public_exponent, size_t public_exponent_size)
|
||||
{
|
||||
if (!data || !data_size || !signature || !modulus || !public_exponent || !public_exponent_size)
|
||||
{
|
||||
LOG_MSG("Invalid parameters!");
|
||||
return false;
|
||||
}
|
||||
|
||||
int mbedtls_ret = 0;
|
||||
mbedtls_rsa_context rsa;
|
||||
u8 hash[SHA256_HASH_SIZE] = {0};
|
||||
bool ret = false;
|
||||
|
||||
/* Initialize RSA context. */
|
||||
mbedtls_rsa_init(&rsa, MBEDTLS_RSA_PKCS_V21, MBEDTLS_MD_SHA256);
|
||||
|
||||
/* Import RSA parameters. */
|
||||
mbedtls_ret = mbedtls_rsa_import_raw(&rsa, (const u8*)modulus, RSA2048_BYTES, NULL, 0, NULL, 0, NULL, 0, (const u8*)public_exponent, public_exponent_size);
|
||||
if (mbedtls_ret != 0)
|
||||
{
|
||||
LOG_MSG("mbedtls_rsa_import_raw failed! (%d).", mbedtls_ret);
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* Calculate SHA-256 checksum for the input data. */
|
||||
sha256CalculateHash(hash, data, data_size);
|
||||
|
||||
/* Verify signature. */
|
||||
mbedtls_ret = mbedtls_rsa_rsassa_pss_verify(&rsa, NULL, NULL, MBEDTLS_RSA_PUBLIC, MBEDTLS_MD_SHA256, SHA256_HASH_SIZE, hash, (const u8*)signature);
|
||||
if (mbedtls_ret != 0)
|
||||
{
|
||||
LOG_MSG("mbedtls_rsa_rsassa_pss_verify failed! (%d).", mbedtls_ret);
|
||||
goto end;
|
||||
}
|
||||
|
||||
ret = true;
|
||||
|
||||
end:
|
||||
mbedtls_rsa_free(&rsa);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool rsa2048GenerateSha256BasedPssSignature(void *dst, const void *src, size_t size)
|
||||
{
|
||||
|
@ -94,39 +139,38 @@ bool rsa2048GenerateSha256BasedPssSignature(void *dst, const void *src, size_t s
|
|||
return false;
|
||||
}
|
||||
|
||||
u8 hash[SHA256_HASH_SIZE] = {0};
|
||||
u8 buf[MBEDTLS_MPI_MAX_SIZE] = {0};
|
||||
const char *pers = "rsa_sign_pss";
|
||||
size_t olen = 0;
|
||||
|
||||
int ret;
|
||||
bool success = false;
|
||||
|
||||
mbedtls_ctr_drbg_context ctr_drbg;
|
||||
mbedtls_ctr_drbg_init(&ctr_drbg);
|
||||
|
||||
mbedtls_entropy_context entropy;
|
||||
mbedtls_entropy_init(&entropy);
|
||||
|
||||
mbedtls_ctr_drbg_context ctr_drbg;
|
||||
mbedtls_pk_context pk;
|
||||
|
||||
size_t olen = 0;
|
||||
int mbedtls_ret = 0;
|
||||
const char *pers = __func__;
|
||||
u8 hash[SHA256_HASH_SIZE] = {0}, buf[MBEDTLS_MPI_MAX_SIZE] = {0};
|
||||
|
||||
bool ret = false;
|
||||
|
||||
/* Initialize contexts. */
|
||||
mbedtls_entropy_init(&entropy);
|
||||
mbedtls_ctr_drbg_init(&ctr_drbg);
|
||||
mbedtls_pk_init(&pk);
|
||||
|
||||
/* Calculate SHA-256 checksum for the input data. */
|
||||
sha256CalculateHash(hash, src, size);
|
||||
|
||||
/* Seed the random number generator. */
|
||||
ret = mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func, &entropy, (const u8*)pers, strlen(pers));
|
||||
if (ret != 0)
|
||||
mbedtls_ret = mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func, &entropy, (const u8*)pers, strlen(pers));
|
||||
if (mbedtls_ret != 0)
|
||||
{
|
||||
LOG_MSG("mbedtls_ctr_drbg_seed failed! (%d).", ret);
|
||||
LOG_MSG("mbedtls_ctr_drbg_seed failed! (%d).", mbedtls_ret);
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* Parse private key. */
|
||||
ret = mbedtls_pk_parse_key(&pk, (const u8*)g_rsa2048CustomPrivateKey, strlen(g_rsa2048CustomPrivateKey) + 1, NULL, 0);
|
||||
if (ret != 0)
|
||||
mbedtls_ret = mbedtls_pk_parse_key(&pk, (const u8*)g_rsa2048CustomPrivateKey, strlen(g_rsa2048CustomPrivateKey) + 1, NULL, 0);
|
||||
if (mbedtls_ret != 0)
|
||||
{
|
||||
LOG_MSG("mbedtls_pk_parse_key failed! (%d).", ret);
|
||||
LOG_MSG("mbedtls_pk_parse_key failed! (%d).", mbedtls_ret);
|
||||
goto end;
|
||||
}
|
||||
|
||||
|
@ -134,119 +178,86 @@ bool rsa2048GenerateSha256BasedPssSignature(void *dst, const void *src, size_t s
|
|||
mbedtls_rsa_set_padding(mbedtls_pk_rsa(pk), MBEDTLS_RSA_PKCS_V21, MBEDTLS_MD_SHA256);
|
||||
|
||||
/* Calculate hash signature. */
|
||||
ret = mbedtls_pk_sign(&pk, MBEDTLS_MD_SHA256, hash, 0, buf, &olen, mbedtls_ctr_drbg_random, &ctr_drbg);
|
||||
if (ret != 0)
|
||||
mbedtls_ret = mbedtls_pk_sign(&pk, MBEDTLS_MD_SHA256, hash, 0, buf, &olen, mbedtls_ctr_drbg_random, &ctr_drbg);
|
||||
if (mbedtls_ret != 0 || olen < RSA2048_SIG_SIZE)
|
||||
{
|
||||
LOG_MSG("mbedtls_pk_sign failed! (%d).", ret);
|
||||
LOG_MSG("mbedtls_pk_sign failed! (%d, %lu).", mbedtls_ret, olen);
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* Copy signature to output buffer. */
|
||||
memcpy(dst, buf, RSA2048_SIG_SIZE);
|
||||
success = true;
|
||||
ret = true;
|
||||
|
||||
end:
|
||||
mbedtls_pk_free(&pk);
|
||||
mbedtls_entropy_free(&entropy);
|
||||
mbedtls_ctr_drbg_free(&ctr_drbg);
|
||||
mbedtls_entropy_free(&entropy);
|
||||
|
||||
return success;
|
||||
return ret;
|
||||
}
|
||||
|
||||
const u8 *rsa2048GetCustomPublicKey(void)
|
||||
bool rsa2048OaepDecrypt(void *dst, size_t dst_size, const void *signature, const void *modulus, const void *public_exponent, size_t public_exponent_size, const void *private_exponent, \
|
||||
size_t private_exponent_size, const void *label, size_t label_size, size_t *out_size)
|
||||
{
|
||||
return g_rsa2048CustomPublicKey;
|
||||
}
|
||||
|
||||
bool rsa2048OaepDecryptAndVerify(void *dst, size_t dst_size, const void *signature, const void *modulus, const void *exponent, size_t exponent_size, const void *label_hash, size_t *out_size)
|
||||
{
|
||||
if (!dst || !dst_size || !signature || !modulus || !exponent || !exponent_size || !label_hash || !out_size)
|
||||
if (!dst || !dst_size || !signature || !modulus || !public_exponent || !public_exponent_size || !private_exponent || !private_exponent_size || (!label && label_size) || (label && !label_size) || \
|
||||
!out_size)
|
||||
{
|
||||
LOG_MSG("Invalid parameters!");
|
||||
return false;
|
||||
}
|
||||
|
||||
Result rc = 0;
|
||||
u8 m_buf[RSA2048_SIG_SIZE] = {0};
|
||||
mbedtls_entropy_context entropy;
|
||||
mbedtls_ctr_drbg_context ctr_drbg;
|
||||
mbedtls_rsa_context rsa;
|
||||
|
||||
rc = splUserExpMod(signature, modulus, exponent, exponent_size, m_buf);
|
||||
if (R_FAILED(rc))
|
||||
const char *pers = __func__;
|
||||
int mbedtls_ret = 0;
|
||||
bool ret = false;
|
||||
|
||||
/* Initialize contexts. */
|
||||
mbedtls_entropy_init(&entropy);
|
||||
mbedtls_ctr_drbg_init(&ctr_drbg);
|
||||
mbedtls_rsa_init(&rsa, MBEDTLS_RSA_PKCS_V21, MBEDTLS_MD_SHA256);
|
||||
|
||||
/* Seed the random number generator. */
|
||||
mbedtls_ret = mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func, &entropy, (const u8*)pers, strlen(pers));
|
||||
if (mbedtls_ret != 0)
|
||||
{
|
||||
LOG_MSG("splUserExpMod failed! (0x%08X).", rc);
|
||||
return false;
|
||||
LOG_MSG("mbedtls_ctr_drbg_seed failed! (%d).", mbedtls_ret);
|
||||
goto end;
|
||||
}
|
||||
|
||||
if (m_buf[0] != 0)
|
||||
/* Import RSA parameters. */
|
||||
mbedtls_ret = mbedtls_rsa_import_raw(&rsa, (const u8*)modulus, RSA2048_BYTES, NULL, 0, NULL, 0, (const u8*)private_exponent, private_exponent_size, (const u8*)public_exponent, public_exponent_size);
|
||||
if (mbedtls_ret != 0)
|
||||
{
|
||||
LOG_MSG("Invalid PSS!");
|
||||
return false;
|
||||
LOG_MSG("mbedtls_rsa_import_raw failed! (%d).", mbedtls_ret);
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* Unmask salt. */
|
||||
rsaCalculateMgf1AndXor(m_buf + 1, 0x20, m_buf + 0x21, RSA2048_SIG_SIZE - 0x21);
|
||||
|
||||
/* Unmask DB. */
|
||||
rsaCalculateMgf1AndXor(m_buf + 0x21, RSA2048_SIG_SIZE - 0x21, m_buf + 1, 0x20);
|
||||
|
||||
/* Validate label hash. */
|
||||
const u8 *db = (const u8*)(m_buf + 0x21);
|
||||
if (memcmp(db, label_hash, SHA256_HASH_SIZE) != 0)
|
||||
/* Derive RSA prime factors. */
|
||||
mbedtls_ret = mbedtls_rsa_complete(&rsa);
|
||||
if (mbedtls_ret != 0)
|
||||
{
|
||||
LOG_MSG("Label hash validation failed! Wrong decryption keys?");
|
||||
return false;
|
||||
LOG_MSG("mbedtls_rsa_complete failed! (%d).", mbedtls_ret);
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* Validate message prefix. */
|
||||
const u8 *data = (const u8*)(db + 0x20);
|
||||
size_t remaining = (RSA2048_SIG_SIZE - 0x41);
|
||||
|
||||
while(!*data && remaining)
|
||||
/* Perform RSA-OAEP decryption. */
|
||||
mbedtls_ret = mbedtls_rsa_rsaes_oaep_decrypt(&rsa, mbedtls_ctr_drbg_random, &ctr_drbg, MBEDTLS_RSA_PRIVATE, (const u8*)label, label_size, out_size, (const u8*)signature, (u8*)dst, dst_size);
|
||||
if (mbedtls_ret != 0)
|
||||
{
|
||||
data++;
|
||||
remaining--;
|
||||
LOG_MSG("mbedtls_rsa_rsaes_oaep_decrypt failed! (%d).", mbedtls_ret);
|
||||
goto end;
|
||||
}
|
||||
|
||||
if (!remaining || *data++ != 1)
|
||||
{
|
||||
LOG_MSG("Message prefix validation failed! Wrong decryption keys?");
|
||||
return false;
|
||||
}
|
||||
ret = true;
|
||||
|
||||
remaining--;
|
||||
*out_size = remaining;
|
||||
end:
|
||||
mbedtls_rsa_free(&rsa);
|
||||
mbedtls_ctr_drbg_free(&ctr_drbg);
|
||||
mbedtls_entropy_free(&entropy);
|
||||
|
||||
if (remaining > dst_size) remaining = dst_size;
|
||||
memcpy(dst, data, remaining);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void rsaCalculateMgf1AndXor(void *data, size_t data_size, const void *h_src, size_t h_src_size)
|
||||
{
|
||||
if (!data || !data_size || !h_src || !h_src_size || h_src_size > RSA2048_SIG_SIZE)
|
||||
{
|
||||
LOG_MSG("Invalid parameters!");
|
||||
return;
|
||||
}
|
||||
|
||||
u32 seed = 0;
|
||||
size_t i, offset = 0;
|
||||
u8 *data_u8 = (u8*)data;
|
||||
|
||||
u8 mgf1_buf[SHA256_HASH_SIZE] = {0};
|
||||
u8 h_buf[RSA2048_SIG_SIZE] = {0};
|
||||
|
||||
memcpy(h_buf, h_src, h_src_size);
|
||||
|
||||
while(offset < data_size)
|
||||
{
|
||||
for(i = 0; i < 4; i++) h_buf[h_src_size + 3 - i] = ((seed >> (8 * i)) & 0xFF);
|
||||
|
||||
sha256CalculateHash(mgf1_buf, h_buf, h_src_size + 4);
|
||||
|
||||
for(i = offset; i < data_size && i < (offset + 0x20); i++) data_u8[i] ^= mgf1_buf[i - offset];
|
||||
|
||||
seed++;
|
||||
offset += 0x20;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
|
|
@ -2076,7 +2076,7 @@ static TitleInfo *_titleGetInfoFromStorageByTitleId(u8 storage_id, u64 title_id)
|
|||
}
|
||||
}
|
||||
|
||||
if (!info) LOG_MSG("Unable to find title info entry with ID \"%016lX\"! (storage ID %u).", title_id, storage_id);
|
||||
//if (!info) LOG_MSG("Unable to find title info entry with ID \"%016lX\"! (storage ID %u).", title_id, storage_id);
|
||||
|
||||
end:
|
||||
return info;
|
||||
|
|
2
todo.txt
2
todo.txt
|
@ -3,7 +3,6 @@ todo:
|
|||
log: verbosity levels
|
||||
log: nxlink output for advanced users
|
||||
|
||||
nca: signature verification
|
||||
nca: support for compressed fs sections?
|
||||
nca: support for sparse sections?
|
||||
|
||||
|
@ -16,6 +15,7 @@ todo:
|
|||
title: parse the update partition from gamecards (if available) to generate ncmcontentinfo data for all update titles
|
||||
|
||||
gamecard: functions to display filelist
|
||||
gamecard: check cardinfo's lafw version
|
||||
|
||||
pfs0: functions to display filelist
|
||||
|
||||
|
|
Loading…
Reference in a new issue