From f526d4e6f4c24b80ce9d82cdd1d28b718dbc7cde Mon Sep 17 00:00:00 2001 From: Pablo Curiel Date: Fri, 21 May 2021 09:34:43 -0400 Subject: [PATCH] Crypto changes. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 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). --- code_templates/usb_gc_dumper.c | 19 +-- include/core/crc32_fast.h | 42 ------- include/core/keys.h | 3 + include/core/nca.h | 9 +- include/core/rsa.h | 27 ++-- source/core/crc32_fast.c | 63 ---------- source/core/keys.c | 58 ++++++--- source/core/nca.c | 25 +++- source/core/npdm.c | 2 +- source/core/rsa.c | 219 +++++++++++++++++---------------- source/core/title.c | 2 +- todo.txt | 2 +- 12 files changed, 222 insertions(+), 249 deletions(-) delete mode 100644 include/core/crc32_fast.h delete mode 100644 source/core/crc32_fast.c diff --git a/code_templates/usb_gc_dumper.c b/code_templates/usb_gc_dumper.c index ea328a4..2859028 100644 --- a/code_templates/usb_gc_dumper.c +++ b/code_templates/usb_gc_dumper.c @@ -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 */ diff --git a/include/core/crc32_fast.h b/include/core/crc32_fast.h deleted file mode 100644 index a8d17e5..0000000 --- a/include/core/crc32_fast.h +++ /dev/null @@ -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 . - * - * 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 . - */ - -#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__ */ diff --git a/include/core/keys.h b/include/core/keys.h index 844acc5..1ed9a73 100644 --- a/include/core/keys.h +++ b/include/core/keys.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. diff --git a/include/core/nca.h b/include/core/nca.h index 7a54ec1..39806cd 100644 --- a/include/core/nca.h +++ b/include/core/nca.h @@ -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. diff --git a/include/core/rsa.h b/include/core/rsa.h index 6085177..b30aaa7 100644 --- a/include/core/rsa.h +++ b/include/core/rsa.h @@ -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 } diff --git a/source/core/crc32_fast.c b/source/core/crc32_fast.c deleted file mode 100644 index a0d177c..0000000 --- a/source/core/crc32_fast.c +++ /dev/null @@ -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 . - * - * 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 . - */ - -#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); -} diff --git a/source/core/keys.c b/source/core/keys.c index 8383fb3..4c67693 100644 --- a/source/core/keys.c +++ b/source/core/keys.c @@ -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; diff --git a/source/core/nca.c b/source/core/nca.c index d19a769..46c29d3 100644 --- a/source/core/nca.c +++ b/source/core/nca.c @@ -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; diff --git a/source/core/npdm.c b/source/core/npdm.c index 37d4602..c676270 100644 --- a/source/core/npdm.c +++ b/source/core/npdm.c @@ -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; diff --git a/source/core/rsa.c b/source/core/rsa.c index e5f501e..7b1d25b 100644 --- a/source/core/rsa.c +++ b/source/core/rsa.c @@ -24,11 +24,10 @@ #include "nxdt_utils.h" #include "rsa.h" +#include #include #include -#include -#include -#include +#include /* 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; } diff --git a/source/core/title.c b/source/core/title.c index 454b869..01dbd0c 100644 --- a/source/core/title.c +++ b/source/core/title.c @@ -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; diff --git a/todo.txt b/todo.txt index 46cfe17..f612489 100644 --- a/todo.txt +++ b/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