/*---------------------------------------------------------------------------* Project: TwlSDK - CARD - libraries File: card_hash.c Copyright 2008 Nintendo. All rights reserved. These coded instructions, statements, and computer programs contain proprietary information of Nintendo of America Inc. and/or Nintendo Company Ltd., and are protected by Federal copyright law. They may not be disclosed to third parties or copied or duplicated in any form, in whole or in part, without the prior written consent of Nintendo. $Date:: 2009-01-30#$ $Rev: 9944 $ $Author: yosizaki $ *---------------------------------------------------------------------------*/ #include #include "../include/card_rom.h" // Use MI_CpuCopy, which uses a larger size, only in LIMITED mode to prevent it from being linked in a NITRO environment. #if defined(SDK_TWLLTD) #define CARDi_CopyMemory MI_CpuCopy #else #define CARDi_CopyMemory MI_CpuCopy8 #endif // Enable this flag when making HMAC-SHA1 calculation high-speed #if defined(SDK_TWL) #define CARD_SUPPORT_SHA1OPTIMIZE #endif #include /*---------------------------------------------------------------------------*/ /* Constants */ // Constants determined to be appropriate by the Card library at present. // Proper changes are acceptable depending on the result of the performance calculation static const u32 CARD_ROM_HASH_BLOCK_MAX = 4; static const u32 CARD_ROM_HASH_SECTOR_MAX = 32; #if defined(CARD_SUPPORT_SHA1OPTIMIZE) static SVCSHA1Context sha1_ipad_def; static SVCSHA1Context sha1_opad_def; #endif static u8 CARDiHmacKey[] ATTRIBUTE_ALIGN(4) = { 0x21, 0x06, 0xc0, 0xde, 0xba, 0x98, 0xce, 0x3f, 0xa6, 0x92, 0xe3, 0x9d, 0x46, 0xf2, 0xed, 0x01, 0x76, 0xe3, 0xcc, 0x08, 0x56, 0x23, 0x63, 0xfa, 0xca, 0xd4, 0xec, 0xdf, 0x9a, 0x62, 0x78, 0x34, 0x8f, 0x6d, 0x63, 0x3c, 0xfe, 0x22, 0xca, 0x92, 0x20, 0x88, 0x97, 0x23, 0xd2, 0xcf, 0xae, 0xc2, 0x32, 0x67, 0x8d, 0xfe, 0xca, 0x83, 0x64, 0x98, 0xac, 0xfd, 0x3e, 0x37, 0x87, 0x46, 0x58, 0x24, }; /*---------------------------------------------------------------------------*/ /* Functions */ /*---------------------------------------------------------------------------* Name: CARD_CalcRomHashBufferLength Description: Gets the buffer size required in ROM hashing context. Arguments: header: ROM header buffer that held the ROM information necessary for management Returns: The buffer size required for the ROM hash context. *---------------------------------------------------------------------------*/ u32 CARD_CalcRomHashBufferLength(const CARDRomHeaderTWL *header) { u32 length = 0; length += MATH_ROUNDUP(header->digest_tabel2.length, 32); length += MATH_ROUNDUP(header->digest_table1_size * CARD_ROM_HASH_SECTOR_MAX, 32); length += MATH_ROUNDUP(MATH_ROUNDUP(CARD_ROM_HASH_SIZE * header->digest_table2_sectors, CARD_ROM_PAGE_SIZE * 3) * CARD_ROM_HASH_BLOCK_MAX, 32); length += MATH_ROUNDUP(sizeof(CARDRomHashSector) * CARD_ROM_HASH_SECTOR_MAX, 32); length += MATH_ROUNDUP(sizeof(CARDRomHashBlock) * CARD_ROM_HASH_BLOCK_MAX, 32); return length; } /*---------------------------------------------------------------------------* Name: CARDi_CompareHash Description: Validity check using hashing (HMAC-SHA1) Arguments: hash: Hash value to be used as the standard for comparison src: Image to be checked len: Size of the subject Returns: None. *---------------------------------------------------------------------------*/ static void CARDi_CompareHash(const void *hash, const void *buffer, u32 length) { #if defined(CARD_SUPPORT_SHA1OPTIMIZE) // Copy the entire structure of the ipad/opad that was calculated in advance . u8 tmphash[CARD_ROM_HASH_SIZE]; const u32 *h1 = (const u32 *)hash; const u32 *h2 = (const u32 *)tmphash; u32 dif = 0; SVCSHA1Context tmpcxt; CARDi_CopyMemory(&sha1_ipad_def, &tmpcxt, sizeof(tmpcxt)); SVC_SHA1Update(&tmpcxt, buffer, length); SVC_SHA1GetHash(&tmpcxt, tmphash); CARDi_CopyMemory(&sha1_opad_def, &tmpcxt, sizeof(tmpcxt)); SVC_SHA1Update(&tmpcxt, tmphash, sizeof(tmphash)); SVC_SHA1GetHash(&tmpcxt, tmphash); dif |= (h1[0] ^ h2[0]); dif |= (h1[1] ^ h2[1]); dif |= (h1[2] ^ h2[2]); dif |= (h1[3] ^ h2[3]); dif |= (h1[4] ^ h2[4]); if (dif != 0) #else u8 tmphash[CARD_ROM_HASH_SIZE]; #ifdef SDK_TWL if (OS_IsRunOnTwl()) { SVC_CalcHMACSHA1(tmphash, buffer, length, CARDiHmacKey, sizeof(CARDiHmacKey)); } else #endif { MATH_CalcHMACSHA1(tmphash, buffer, length, CARDiHmacKey, sizeof(CARDiHmacKey)); } if (MI_CpuComp8(hash, tmphash, sizeof(tmphash)) != 0) #endif { OS_TPanic("ROM-hash comparation error!\n"); } } /*---------------------------------------------------------------------------* Name: CARDi_GetHashSectorIndex Description: Gets sector number where the specified ROM offset belongs. Arguments: context: CARDRomHashContext structure offset: ROM offset Returns: Sector number where the specified ROM offset belongs. *---------------------------------------------------------------------------*/ SDK_INLINE u32 CARDi_GetHashSectorIndex(const CARDRomHashContext *context, u32 offset) { offset -= context->area_ntr.offset; if (offset >= context->area_ntr.length) { offset += (context->area_ntr.offset - context->area_ltd.offset); if (offset < context->area_ltd.length) { offset += context->area_ntr.length; } else { OS_TPanic("specified ROM address is outof-range.(unsafe without secure hash)\n"); } } return offset / context->bytes_per_sector; } /*---------------------------------------------------------------------------* Name: CARDi_StartLoading Description: Starts transfers for only one sector waiting for media load. Arguments: context: CARDRomHashContext structure Returns: None. *---------------------------------------------------------------------------*/ static void CARDi_StartLoading(CARDRomHashContext *context) { // Start the asynchronous process here if not already running void *buffer = NULL; u32 offset = 0; u32 length = 0; OSIntrMode bak_cpsr = OS_DisableInterrupts(); if (context->recent_load == NULL) { if (context->loading_block) { // A larger buffer has been allocated than actually needed so that asynchronous DMA transfer is possible. // Align offset of the reference target to the table position that is actually required. CARDRomHashBlock *block = context->loading_block; u32 pos = block->offset; u32 mod = (pos & (CARD_ROM_PAGE_SIZE - 1UL)); block->hash = &block->hash_aligned[mod]; context->recent_load = block; buffer = block->hash_aligned; offset = pos - mod; length = MATH_ROUNDUP(mod + CARD_ROM_HASH_SIZE * context->sectors_per_block, CARD_ROM_PAGE_SIZE); } else if (context->loading_sector) { CARDRomHashSector *sector = context->loading_sector; context->recent_load = sector; buffer = sector->image; offset = sector->offset; length = context->bytes_per_sector; } } (void)OS_RestoreInterrupts(bak_cpsr); // Perform synchronous transfer here if the asynchronous tranfser is not supported or currently unusable if (buffer != NULL) { if ((*context->ReadAsync)(context->userdata, buffer, offset, length) == 0) { (void)(*context->ReadSync)(context->userdata, buffer, offset, length); CARD_NotifyRomHashReadAsync(context); } } } /*---------------------------------------------------------------------------* Name: CARD_NotifyRomHashReadAsync Description: Notifies completion of the device asynchronous read. Arguments: context: CARDRomHashContext structure Returns: None. *---------------------------------------------------------------------------*/ void CARD_NotifyRomHashReadAsync(CARDRomHashContext *context) { OSIntrMode bak_cpsr = OS_DisableInterrupts(); // Move list from "waiting to load" to "waiting to verify" if (context->recent_load == context->loading_sector) { CARDRomHashSector *sector = context->loading_sector; context->loading_sector = sector->next; sector->next = context->loaded_sector; context->loaded_sector = sector; } else { CARDRomHashBlock *block = context->loading_block; context->loading_block = block->next; block->next = context->loaded_block; context->loaded_block = block; } context->recent_load = NULL; (void)OS_RestoreInterrupts(bak_cpsr); // Notify wait thread that "waiting to verify" list was added. OS_WakeupThreadDirect(context->loader); // Start transfer of next "waiting to load" list, if there is one. CARDi_StartLoading(context); } /*---------------------------------------------------------------------------* Name: CARDi_TouchRomHashBlock Description: Loads hash block data that includes the specified offset. Does nothing if already loaded. Arguments: context: CARDRomHashContext structure sector: Sector to access Returns: Corresponding hash block data. *---------------------------------------------------------------------------*/ static CARDRomHashBlock* CARDi_TouchRomHashBlock(CARDRomHashContext *context, u32 sector) { // Note that the block region is handled by linking the NTR and LTD regions // (Do not insert any extra padding or alignment) u32 index = sector / context->sectors_per_block; // Determine whether the corresponding block data is already loaded. CARDRomHashBlock **pp = &context->valid_block; CARDRomHashBlock *block = NULL; for (;; pp = &(*pp)->next) { block = *pp; if (block->index == index) { // Move the touched block data to the top of the list. *pp = block->next; block->next = context->valid_block; context->valid_block = block; break; } // If not loaded, destroy the oldest block data and reload. else if (block->next == NULL) { // Move to end of "waiting to load" list OSIntrMode bak_cpsr = OS_DisableInterrupts(); CARDRomHashBlock **tail = pp; CARDRomHashBlock *loading = block; // Do nothing if the same block is already waiting to load or waiting to verify. for (pp = &context->loaded_block; *pp; pp = &(*pp)->next) { if ((*pp)->index == index) { block = (*pp); loading = NULL; break; } } if (loading) { for (pp = &context->loading_block; *pp; pp = &(*pp)->next) { if ((*pp)->index == index) { block = (*pp); loading = NULL; break; } } if (loading) { *tail = NULL; *pp = loading; loading->index = index; loading->offset = context->sector_hash.offset + index * (CARD_ROM_HASH_SIZE * context->sectors_per_block); } } (void)OS_RestoreInterrupts(bak_cpsr); if (loading) { // Check the start timing of the asynchronous load CARDi_StartLoading(context); } break; } } return block; } /*---------------------------------------------------------------------------* Name: CARDi_TouchRomHashSector Description: Loads the specified sector on the block. Does nothing if already loaded. Arguments: context: CARDRomHashContext structure block: CARDRomHashBlock structure offset: ROM offset to access Returns: Corresponding sector image or NULL. *---------------------------------------------------------------------------*/ static void* CARDi_TouchRomHashSector(CARDRomHashContext *context, u32 offset) { void *image = NULL; CARDRomHashSector **pp = &context->valid_sector; CARDRomHashSector *sector = NULL; u32 index = CARDi_GetHashSectorIndex(context, offset); for (pp = &context->valid_sector; ; pp = &(*pp)->next) { // If the corresponding sector data is already loaded, returns that image if ((*pp)->index == index) { // Move the touched sector data to the top of the list sector = *pp; *pp = (*pp)->next; sector->next = context->valid_sector; context->valid_sector = sector; image = sector->image; break; } // If not loaded, destroy the oldest sector data and reload else if ((*pp)->next == NULL) { // Move to end of "waiting to load" list OSIntrMode bak_cpsr = OS_DisableInterrupts(); CARDRomHashSector *loading = *pp; *pp = NULL; for (pp = &context->loading_sector; *pp; pp = &(*pp)->next) { } *pp = loading; loading->index = index; loading->offset = MATH_ROUNDDOWN(offset, context->bytes_per_sector); (void)OS_RestoreInterrupts(bak_cpsr); // Search for the block that will be required for validity test (void)CARDi_TouchRomHashBlock(context, index); // Check the start timing of the asynchronous load CARDi_StartLoading(context); break; } } return image; } /*---------------------------------------------------------------------------* Name: CARD_InitRomHashContext Description: Initializes the ROM hashing context. Arguments: context: CARDRomHashContext structure that must be initialized header: ROM header buffer that held the ROM information necessary for management (No need to keep this because this is referenced only within the function) buffer: Buffer for management information used in the context length: Size of management information buffer (Can be calculated using the CARD_CalcRomHashBufferLength function) sync: Device synchronous read callback function async: Device asynchronous read callback function (if not supported, NULL) userdata: Arbitrary user-defined function passed to the device read callback Returns: None. *---------------------------------------------------------------------------*/ void CARD_InitRomHashContext(CARDRomHashContext *context, const CARDRomHeaderTWL *header, void *buffer, u32 length, MIDeviceReadFunction sync, MIDeviceReadFunction async, void *userdata) { // Constant differs by the build const u32 bytes_per_sector = header->digest_table1_size; const u32 sectors_per_block = header->digest_table2_sectors; const u32 master_hash_size = header->digest_tabel2.length; // Calculate whether the required variable length memory has been allocated u8 *lo = (u8 *)MATH_ROUNDUP((u32)buffer, 32); u8 *hi = (u8 *)MATH_ROUNDDOWN((u32)&lo[length], 32); u8 *cur = lo; context->master_hash = (u8 *)cur; cur += MATH_ROUNDUP(master_hash_size, 32); context->images = (u8 *)cur; cur += MATH_ROUNDUP(bytes_per_sector * CARD_ROM_HASH_SECTOR_MAX, 32); context->hashes = (u8 *)cur; cur += MATH_ROUNDUP(MATH_ROUNDUP(CARD_ROM_HASH_SIZE * sectors_per_block, CARD_ROM_PAGE_SIZE * 3) * CARD_ROM_HASH_BLOCK_MAX, 32); context->sectors = (CARDRomHashSector *)cur; cur += MATH_ROUNDUP(sizeof(*context->sectors) * CARD_ROM_HASH_SECTOR_MAX, 32); context->blocks = (CARDRomHashBlock *)cur; cur += MATH_ROUNDUP(sizeof(*context->blocks) * CARD_ROM_HASH_BLOCK_MAX, 32); if (cur > hi) { OS_TPanic("cannot allocate memory for ROM-hash from ARENA"); } else { // Initialize the basic settings if the memory can be allocated MI_CpuClear32(lo, (u32)(cur - lo)); context->area_ntr = header->digest_area_ntr; context->area_ltd = header->digest_area_ltd; context->sector_hash = header->digest_tabel1; context->block_hash = header->digest_tabel2; context->bytes_per_sector = bytes_per_sector; context->sectors_per_block = sectors_per_block; context->block_max = CARD_ROM_HASH_BLOCK_MAX; context->sector_max = CARD_ROM_HASH_SECTOR_MAX; // Initialize the device reader context->ReadSync = sync; context->ReadAsync = async; context->userdata = userdata; #if defined(CARD_SUPPORT_SHA1OPTIMIZE) // Calculates the ipad/opad for hash calculation in advance { u8 ipad[MATH_HASH_BLOCK_SIZE]; u8 opad[MATH_HASH_BLOCK_SIZE]; int i; for (i = 0; i < MATH_HASH_BLOCK_SIZE; ++i) { ipad[i] = (u8)(CARDiHmacKey[i] ^ 0x36); opad[i] = (u8)(CARDiHmacKey[i] ^ 0x5c); } SVC_SHA1Init(&sha1_ipad_def); SVC_SHA1Init(&sha1_opad_def); SVC_SHA1Update(&sha1_ipad_def, ipad, sizeof(ipad)); SVC_SHA1Update(&sha1_opad_def, opad, sizeof(opad)); } #endif // Determine validity by loading the master hash (void)(*context->ReadSync)(context->userdata, context->master_hash, context->block_hash.offset, context->block_hash.length); CARDi_CompareHash(header->digest_tabel2_digest, context->master_hash, context->block_hash.length); // Initialize the sector data list { CARDRomHashSector *sectors = context->sectors; int sector_index = 0; int i; for (i = 0; i < context->sector_max; ++i) { sectors[i].next = §ors[i - 1]; sectors[i].image = &context->images[i * context->bytes_per_sector]; sectors[i].index = 0xFFFFFFFF; } context->sectors[0].next = NULL; context->valid_sector = &context->sectors[context->sector_max - 1]; context->loading_sector = NULL; context->loaded_sector = NULL; } // Initialize the block data list { CARDRomHashBlock *blocks = context->blocks; const u32 unit = MATH_ROUNDUP(CARD_ROM_HASH_SIZE * sectors_per_block, CARD_ROM_PAGE_SIZE * 3); int i; for (i = 0; i < context->block_max; ++i) { blocks[i].next = &blocks[i + 1]; blocks[i].index = 0xFFFFFFFF; blocks[i].hash_aligned = &context->hashes[i * unit]; } context->valid_block = &context->blocks[0]; context->blocks[context->block_max - 1].next = NULL; context->loading_block = NULL; context->loaded_block = NULL; } } } /*---------------------------------------------------------------------------* Name: CARDi_ReadRomHashImageDirect Description: Directly copies to the transfer destination without caching to the hash context. Arguments: context: CARDRomHashContext structure buffer: The buffer to transfer to (Must be 4-byte aligned) offset: ROM offset to access length: Transfer size Returns: None. *---------------------------------------------------------------------------*/ static void CARDi_ReadRomHashImageDirect(CARDRomHashContext *context, void *buffer, u32 offset, u32 length) { const u32 sectunit = context->bytes_per_sector; const u32 blckunit = context->sectors_per_block; u32 position = offset; u32 end = length + offset; u32 sector = CARDi_GetHashSectorIndex(context, position); while (position < end) { // Synchronously load images of minimum units that can be obtained this time u32 available = (u32)(*context->ReadSync)(context->userdata, buffer, position, end - position); // Access blocks in advance that may be needed in verification this time (void)CARDi_TouchRomHashBlock(context, sector); // Request load preparation for the portion that will be required next if (context->ReadAsync && (position + available < end)) { (void)(*context->ReadAsync)(context->userdata, NULL, position + available, end - (position + available)); } // Test validity of obtained image while (available >= sectunit) { CARDRomHashBlock *block = CARDi_TouchRomHashBlock(context, sector); u32 slot = sector - block->index * blckunit; while ((slot < blckunit) && (available >= sectunit)) { // Verify the hash table in block units here if required if (block != context->valid_block) { OSIntrMode bak_cpsr = OS_DisableInterrupts(); while (context->loading_block) { OS_SleepThread(NULL); } if (block == context->loaded_block) { context->loaded_block = block->next; } (void)OS_RestoreInterrupts(bak_cpsr); CARDi_CompareHash(&context->master_hash[block->index * CARD_ROM_HASH_SIZE], block->hash, CARD_ROM_HASH_SIZE * blckunit); block->next = context->valid_block; context->valid_block = block; } // Calculate the image hash CARDi_CompareHash(&block->hash[slot * CARD_ROM_HASH_SIZE], buffer, sectunit); position += sectunit; available -= sectunit; buffer = ((u8 *)buffer) + sectunit; slot += 1; sector += 1; } } } } /*---------------------------------------------------------------------------* Name: CARDi_ReadRomHashImageCaching Description: Directly copies to the transfer destination while caching to the hash context. Arguments: context: CARDRomHashContext structure buffer: The buffer to transfer to offset: ROM offset to access length: Transfer size Returns: None. *---------------------------------------------------------------------------*/ static void CARDi_ReadRomHashImageCaching(CARDRomHashContext *context, void *buffer, u32 offset, u32 length) { while (length > 0) { // Determine whether the next sector has undergone validity test void *image = CARDi_TouchRomHashSector(context, offset); if (image) { // If verified, copy memory as is and go to next u32 max = context->bytes_per_sector; u32 mod = offset - MATH_ROUNDDOWN(offset, max); u32 len = (u32)MATH_MIN(length, (max - mod)); CARDi_CopyMemory((u8*)image + mod, buffer, len); buffer = (u8 *)buffer + len; length -= len; offset += len; } else { // If no hit was found, request to load the sector in advance as much as the capacity allows so that all unverified lists can be processed together. // Related block hash is automatically read first, and if possible the asynchronous transfer has already started. // // u32 hit = MATH_ROUNDDOWN(offset, context->bytes_per_sector); while (context->valid_sector && context->valid_block) { hit += context->bytes_per_sector; if (hit >= offset + length) { break; } (void)CARDi_TouchRomHashSector(context, hit); } for (;;) { // Idle until a verification wait list of a load-complete state is generated OSIntrMode bak_cpsr = OS_DisableInterrupts(); CARDRomHashBlock *block = NULL; CARDRomHashSector *sector = NULL; while ((context->loading_sector && !context->loaded_sector) || (context->loading_block && !context->loaded_block)) { OS_SleepThread(NULL); } // Process the block before the sector considering dependency relationships block = context->loaded_block; if (block) { context->loaded_block = block->next; } else { sector = context->loaded_sector; if (sector) { context->loaded_sector = sector->next; } } (void)OS_RestoreInterrupts(bak_cpsr); // Compare with the higher table and verify by calculating block or sector hash if (block) { u32 len = CARD_ROM_HASH_SIZE * context->sectors_per_block; CARDi_CompareHash(&context->master_hash[block->index * CARD_ROM_HASH_SIZE], block->hash, len); // Move the touched block data to the top of the list block->next = context->valid_block; context->valid_block = block; } else if (sector) { CARDRomHashBlock *block = CARDi_TouchRomHashBlock(context, sector->index); u32 slot = sector->index - block->index * context->sectors_per_block; CARDi_CompareHash(&block->hash[slot * CARD_ROM_HASH_SIZE], sector->image, context->bytes_per_sector); // Move the touched sector data to the top of the list sector->next = context->valid_sector; context->valid_sector = sector; } // If there is nothing that must be asynchronously verified, end this loop else { break; } } } } } /*---------------------------------------------------------------------------* Name: CARD_ReadRomHashImage Description: Reads the hash-verified ROM image from the specified offset Arguments: context: CARDRomHashContext structure offset: ROM offset to access Returns: None. *---------------------------------------------------------------------------*/ void CARD_ReadRomHashImage(CARDRomHashContext *context, void *buffer, u32 offset, u32 length) { // Initialize notification destination from the DMA context->loader = OS_GetCurrentThread(); context->recent_load = NULL; // If conducting a large-scale read where more than half of the sector cache could be lost all at once, employ a direct transfer mode that is high-speed and protects the cache // if ((length >= context->bytes_per_sector * (CARD_ROM_HASH_SECTOR_MAX / 2)) // TODO: // For the time being, do not use the direct transfer mode with a card boot. // (No particular attention needs to be paid to the word boundary alignment of the transfer source and transfer destination, but when transferring ROM, when there are synchronized versions, performance actually drops, so conditions should be added when necessary.) // // && (OS_GetBootType() != OS_BOOTTYPE_ROM)) { // Split the leading end and trailing end portions without aligning the sector boundaries const u32 sectmask = (u32)(context->bytes_per_sector - 1UL); const u32 headlen = (u32)((context->bytes_per_sector - offset) & sectmask); const u32 bodylen = (u32)((length - headlen) & ~sectmask); // Leading End Portion if (headlen > 0) { CARDi_ReadRomHashImageCaching(context, buffer, offset, headlen); offset += headlen; length -= headlen; buffer = ((u8 *)buffer) + headlen; } // Middle Portion if (bodylen > 0) { CARDi_ReadRomHashImageDirect(context, buffer, offset, bodylen); offset += bodylen; length -= bodylen; buffer = ((u8 *)buffer) + bodylen; } } // Process the remainder that could not be used with the direct transfer mode CARDi_ReadRomHashImageCaching(context, buffer, offset, length); } #include