1
0
Fork 0
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:
Pablo Curiel 2021-05-21 09:34:43 -04:00
parent f82d7a3db4
commit f526d4e6f4
12 changed files with 222 additions and 249 deletions

View file

@ -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 */

View file

@ -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__ */

View file

@ -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.

View file

@ -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.

View file

@ -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
}

View file

@ -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);
}

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;
}

View file

@ -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;

View file

@ -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