mirror of
				https://github.com/PabloMK7/citra.git
				synced 2025-10-30 21:30:04 +00:00 
			
		
		
		
	Load RSA slot_0 data from bootrom; load and decrypt data from native firm for NCCHSecure2 keyslot
This commit is contained in:
		
							parent
							
								
									5b54a99f96
								
							
						
					
					
						commit
						ebeea43fb8
					
				
					 4 changed files with 204 additions and 4 deletions
				
			
		|  | @ -421,6 +421,8 @@ add_library(core STATIC | ||||||
|     hw/hw.h |     hw/hw.h | ||||||
|     hw/lcd.cpp |     hw/lcd.cpp | ||||||
|     hw/lcd.h |     hw/lcd.h | ||||||
|  |     hw/rsa/rsa.cpp | ||||||
|  |     hw/rsa/rsa.h | ||||||
|     hw/y2r.cpp |     hw/y2r.cpp | ||||||
|     hw/y2r.h |     hw/y2r.h | ||||||
|     loader/3dsx.cpp |     loader/3dsx.cpp | ||||||
|  |  | ||||||
|  | @ -8,6 +8,7 @@ | ||||||
| #include <sstream> | #include <sstream> | ||||||
| #include <cryptopp/aes.h> | #include <cryptopp/aes.h> | ||||||
| #include <cryptopp/modes.h> | #include <cryptopp/modes.h> | ||||||
|  | #include <cryptopp/sha.h> | ||||||
| #include <fmt/format.h> | #include <fmt/format.h> | ||||||
| #include "common/common_paths.h" | #include "common/common_paths.h" | ||||||
| #include "common/file_util.h" | #include "common/file_util.h" | ||||||
|  | @ -17,6 +18,7 @@ | ||||||
| #include "core/hle/service/fs/archive.h" | #include "core/hle/service/fs/archive.h" | ||||||
| #include "core/hw/aes/arithmetic128.h" | #include "core/hw/aes/arithmetic128.h" | ||||||
| #include "core/hw/aes/key.h" | #include "core/hw/aes/key.h" | ||||||
|  | #include "core/hw/rsa/rsa.h" | ||||||
| 
 | 
 | ||||||
| namespace HW::AES { | namespace HW::AES { | ||||||
| 
 | 
 | ||||||
|  | @ -26,7 +28,7 @@ namespace { | ||||||
| // normal key dumped from a Wii U solving the equation:
 | // normal key dumped from a Wii U solving the equation:
 | ||||||
| // NormalKey = (((KeyX ROL 2) XOR KeyY) + constant) ROL 87
 | // NormalKey = (((KeyX ROL 2) XOR KeyY) + constant) ROL 87
 | ||||||
| // On a real 3DS the generation for the normal key is hardware based, and thus the constant can't
 | // On a real 3DS the generation for the normal key is hardware based, and thus the constant can't
 | ||||||
| // get dumped . generated normal keys are also not accesible on a 3DS. The used formula for
 | // get dumped. Generated normal keys are also not accesible on a 3DS. The used formula for
 | ||||||
| // calculating the constant is a software implementation of what the hardware generator does.
 | // calculating the constant is a software implementation of what the hardware generator does.
 | ||||||
| constexpr AESKey generator_constant = {{0x1F, 0xF9, 0xE9, 0xAA, 0xC5, 0xFE, 0x04, 0x08, 0x02, 0x45, | constexpr AESKey generator_constant = {{0x1F, 0xF9, 0xE9, 0xAA, 0xC5, 0xFE, 0x04, 0x08, 0x02, 0x45, | ||||||
|                                         0x91, 0xDC, 0x5D, 0x52, 0x76, 0x8A}}; |                                         0x91, 0xDC, 0x5D, 0x52, 0x76, 0x8A}}; | ||||||
|  | @ -203,13 +205,69 @@ void LoadBootromKeys() { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void LoadNativeFirmKeysOld3DS() { | void LoadNativeFirmKeysOld3DS() { | ||||||
|  |     constexpr u64 native_firm_id = 0x00040138'00000002; | ||||||
|  |     FileSys::NCCHArchive archive(native_firm_id, Service::FS::MediaType::NAND); | ||||||
|  |     std::array<char, 8> exefs_filepath = {'.', 'f', 'i', 'r', 'm', 0, 0, 0}; | ||||||
|  |     FileSys::Path file_path = FileSys::MakeNCCHFilePath( | ||||||
|  |         FileSys::NCCHFileOpenType::NCCHData, 0, FileSys::NCCHFilePathType::ExeFS, exefs_filepath); | ||||||
|  |     FileSys::Mode open_mode = {}; | ||||||
|  |     open_mode.read_flag.Assign(1); | ||||||
|  |     auto file_result = archive.OpenFile(file_path, open_mode); | ||||||
|  |     if (file_result.Failed()) | ||||||
|  |         return; | ||||||
|  | 
 | ||||||
|  |     auto firm = std::move(file_result).Unwrap(); | ||||||
|  |     const std::size_t size = firm->GetSize(); | ||||||
|  |     if (size != 966656) { | ||||||
|  |         LOG_ERROR(HW_AES, "native firm has wrong size {}", size); | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     auto rsa = RSA::GetSlot(0); | ||||||
|  |     if (!rsa) { | ||||||
|  |         LOG_ERROR(HW_AES, "RSA slot is missing"); | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     std::vector<u8> firm_buffer(size); | ||||||
|  |     firm->Read(0, firm_buffer.size(), firm_buffer.data()); | ||||||
|  |     firm->Close(); | ||||||
|  | 
 | ||||||
|  |     constexpr std::size_t SLOT_0x25_KEY_X_SECRET_OFFSET = 934444; | ||||||
|  |     constexpr std::size_t SLOT_0x25_KEY_X_SECRET_SIZE = 64; | ||||||
|  |     std::vector<u8> secret_data(SLOT_0x25_KEY_X_SECRET_SIZE); | ||||||
|  |     std::memcpy(secret_data.data(), firm_buffer.data() + SLOT_0x25_KEY_X_SECRET_OFFSET, | ||||||
|  |                 secret_data.size()); | ||||||
|  | 
 | ||||||
|  |     auto asn1 = RSA::CreateASN1Message(secret_data); | ||||||
|  |     auto result = rsa.GetSignature(asn1); | ||||||
|  |     if (result.size() < 0x100) { | ||||||
|  |         std::vector<u8> temp; | ||||||
|  |         temp.resize(0x100 - result.size()); | ||||||
|  |         std::fill(temp.begin(), temp.end(), 0); | ||||||
|  |         std::copy(result.begin(), result.end(), std::back_inserter(temp)); | ||||||
|  |         result = temp; | ||||||
|  |     } else if (result.size() > 0x100) { | ||||||
|  |         std::vector<u8> temp(result.begin(), result.begin() + 0x100); | ||||||
|  |         result = temp; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     CryptoPP::SHA256 sha; | ||||||
|  |     std::array<u8, CryptoPP::SHA256::DIGESTSIZE> hash_result; | ||||||
|  |     sha.CalculateDigest(hash_result.data(), result.data(), result.size()); | ||||||
|  |     AESKey key; | ||||||
|  |     std::memcpy(key.data(), hash_result.data(), sizeof(key)); | ||||||
|  |     key_slots.at(0x2F).SetKeyY(key); | ||||||
|  |     std::memcpy(key.data(), hash_result.data() + sizeof(key), sizeof(key)); | ||||||
|  |     key_slots.at(0x25).SetKeyX(key); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void LoadSaveModeNativeFirmKeysOld3DS() { | ||||||
|     // Use the save mode native firm instead of the normal mode since there are only 2 version of it
 |     // Use the save mode native firm instead of the normal mode since there are only 2 version of it
 | ||||||
|     // and thus we can use fixed offsets
 |     // and thus we can use fixed offsets
 | ||||||
| 
 | 
 | ||||||
|     constexpr u64 save_mode_native_firm_id = 0x00040138'00000003; |     constexpr u64 save_mode_native_firm_id = 0x00040138'00000003; | ||||||
| 
 | 
 | ||||||
|     // TODO(B3N30): Add the 0x25 KeyX that gets initalized by native_firm
 |  | ||||||
| 
 |  | ||||||
|     FileSys::NCCHArchive archive(save_mode_native_firm_id, Service::FS::MediaType::NAND); |     FileSys::NCCHArchive archive(save_mode_native_firm_id, Service::FS::MediaType::NAND); | ||||||
|     std::array<char, 8> exefs_filepath = {'.', 'f', 'i', 'r', 'm', 0, 0, 0}; |     std::array<char, 8> exefs_filepath = {'.', 'f', 'i', 'r', 'm', 0, 0, 0}; | ||||||
|     FileSys::Path file_path = FileSys::MakeNCCHFilePath( |     FileSys::Path file_path = FileSys::MakeNCCHFilePath( | ||||||
|  | @ -443,11 +501,13 @@ void InitKeys() { | ||||||
|     static bool initialized = false; |     static bool initialized = false; | ||||||
|     if (initialized) |     if (initialized) | ||||||
|         return; |         return; | ||||||
|  |     initialized = true; | ||||||
|  |     HW::RSA::InitSlots(); | ||||||
|     LoadBootromKeys(); |     LoadBootromKeys(); | ||||||
|     LoadNativeFirmKeysOld3DS(); |     LoadNativeFirmKeysOld3DS(); | ||||||
|  |     LoadSaveModeNativeFirmKeysOld3DS(); | ||||||
|     LoadNativeFirmKeysNew3DS(); |     LoadNativeFirmKeysNew3DS(); | ||||||
|     LoadPresetKeys(); |     LoadPresetKeys(); | ||||||
|     initialized = true; |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void SetKeyX(std::size_t slot_id, const AESKey& key) { | void SetKeyX(std::size_t slot_id, const AESKey& key) { | ||||||
|  |  | ||||||
							
								
								
									
										102
									
								
								src/core/hw/rsa/rsa.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										102
									
								
								src/core/hw/rsa/rsa.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,102 @@ | ||||||
|  | // Copyright 2020 Citra Emulator Project
 | ||||||
|  | // Licensed under GPLv2 or any later version
 | ||||||
|  | // Refer to the license.txt file included.
 | ||||||
|  | 
 | ||||||
|  | #include <sstream> | ||||||
|  | #include <cryptopp/integer.h> | ||||||
|  | #include <cryptopp/nbtheory.h> | ||||||
|  | #include <cryptopp/sha.h> | ||||||
|  | #include <fmt/format.h> | ||||||
|  | #include "common/common_paths.h" | ||||||
|  | #include "common/file_util.h" | ||||||
|  | #include "common/logging/log.h" | ||||||
|  | #include "common/string_util.h" | ||||||
|  | #include "core/hw/rsa/rsa.h" | ||||||
|  | 
 | ||||||
|  | namespace HW::RSA { | ||||||
|  | 
 | ||||||
|  | namespace { | ||||||
|  | std::vector<u8> HexToBytes(const std::string& hex) { | ||||||
|  |     std::vector<u8> bytes; | ||||||
|  | 
 | ||||||
|  |     for (unsigned int i = 0; i < hex.length(); i += 2) { | ||||||
|  |         std::string byteString = hex.substr(i, 2); | ||||||
|  |         u8 byte = (u8)strtol(byteString.c_str(), NULL, 16); | ||||||
|  |         bytes.push_back(byte); | ||||||
|  |     } | ||||||
|  |     return bytes; | ||||||
|  | }; | ||||||
|  | } // namespace
 | ||||||
|  | 
 | ||||||
|  | constexpr std::size_t SlotSize = 4; | ||||||
|  | std::array<RsaSlot, SlotSize> rsa_slots; | ||||||
|  | 
 | ||||||
|  | std::vector<u8> RsaSlot::GetSignature(const std::vector<u8>& message) { | ||||||
|  |     CryptoPP::Integer sig = | ||||||
|  |         CryptoPP::ModularExponentiation(CryptoPP::Integer(message.data(), message.size()), | ||||||
|  |                                         CryptoPP::Integer(exponent.data(), exponent.size()), | ||||||
|  |                                         CryptoPP::Integer(modulus.data(), modulus.size())); | ||||||
|  |     std::stringstream ss; | ||||||
|  |     ss << std::hex << sig; | ||||||
|  |     return HexToBytes(ss.str()); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void InitSlots() { | ||||||
|  |     static bool initialized = false; | ||||||
|  |     if (initialized) | ||||||
|  |         return; | ||||||
|  |     initialized = true; | ||||||
|  | 
 | ||||||
|  |     const std::string filepath = FileUtil::GetUserPath(FileUtil::UserPath::SysDataDir) + BOOTROM9; | ||||||
|  |     auto file = FileUtil::IOFile(filepath, "rb"); | ||||||
|  |     if (!file) { | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     const std::size_t length = file.GetSize(); | ||||||
|  |     if (length != 65536) { | ||||||
|  |         LOG_ERROR(HW_AES, "Bootrom9 size is wrong: {}", length); | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     constexpr std::size_t RSA_MODULUS_POS = 0xB3E0; | ||||||
|  |     file.Seek(RSA_MODULUS_POS, SEEK_SET); | ||||||
|  |     std::vector<u8> modulus; | ||||||
|  |     modulus.resize(256); | ||||||
|  |     file.ReadArray(modulus.data(), modulus.size()); | ||||||
|  | 
 | ||||||
|  |     constexpr std::size_t RSA_EXPONENT_POS = 0xB4E0; | ||||||
|  |     file.Seek(RSA_EXPONENT_POS, SEEK_SET); | ||||||
|  |     std::vector<u8> exponent; | ||||||
|  |     exponent.resize(256); | ||||||
|  |     file.ReadArray(exponent.data(), exponent.size()); | ||||||
|  | 
 | ||||||
|  |     rsa_slots[0] = RsaSlot(exponent, modulus); | ||||||
|  |     // TODO(B3N30): Initalize the other slots. But since they aren't used at all, we can skip them
 | ||||||
|  |     // for now
 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | RsaSlot GetSlot(std::size_t slot_id) { | ||||||
|  |     if (slot_id >= rsa_slots.size()) | ||||||
|  |         return RsaSlot{}; | ||||||
|  |     return rsa_slots[slot_id]; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | std::vector<u8> CreateASN1Message(const std::vector<u8>& data) { | ||||||
|  |     static constexpr auto asn1_header = | ||||||
|  |         "0001FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" | ||||||
|  |         "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" | ||||||
|  |         "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" | ||||||
|  |         "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" | ||||||
|  |         "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" | ||||||
|  |         "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" | ||||||
|  |         "FFFFFFFFFFFFFFFFFFFFFFFF003031300D060960864801650304020105000420"; | ||||||
|  |     std::vector<u8> message = HexToBytes(asn1_header); | ||||||
|  |     CryptoPP::SHA256 sha; | ||||||
|  |     std::array<u8, CryptoPP::SHA256::DIGESTSIZE> hash; | ||||||
|  |     sha.CalculateDigest(hash.data(), data.data(), data.size()); | ||||||
|  |     std::copy(hash.begin(), hash.end(), std::back_inserter(message)); | ||||||
|  |     return message; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | } // namespace HW::RSA
 | ||||||
							
								
								
									
										36
									
								
								src/core/hw/rsa/rsa.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								src/core/hw/rsa/rsa.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,36 @@ | ||||||
|  | // Copyright 2020 Citra Emulator Project
 | ||||||
|  | // Licensed under GPLv2 or any later version
 | ||||||
|  | // Refer to the license.txt file included.
 | ||||||
|  | 
 | ||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include <vector> | ||||||
|  | #include "common/common_types.h" | ||||||
|  | 
 | ||||||
|  | namespace HW::RSA { | ||||||
|  | 
 | ||||||
|  | class RsaSlot { | ||||||
|  | public: | ||||||
|  |     RsaSlot() : init(false) {} | ||||||
|  |     RsaSlot(const std::vector<u8>& exponent, const std::vector<u8>& modulus) | ||||||
|  |         : init(true), exponent(exponent), modulus(modulus) {} | ||||||
|  |     std::vector<u8> GetSignature(const std::vector<u8>& message); | ||||||
|  | 
 | ||||||
|  |     operator bool() const { | ||||||
|  |         // TODO(B3N30): Maybe check if exponent and modulus are vailid
 | ||||||
|  |         return init; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | private: | ||||||
|  |     bool init; | ||||||
|  |     std::vector<u8> exponent; | ||||||
|  |     std::vector<u8> modulus; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | void InitSlots(); | ||||||
|  | 
 | ||||||
|  | RsaSlot GetSlot(std::size_t slot_id); | ||||||
|  | 
 | ||||||
|  | std::vector<u8> CreateASN1Message(const std::vector<u8>& data); | ||||||
|  | 
 | ||||||
|  | } // namespace HW::RSA
 | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue