mirror of
				https://github.com/PabloMK7/citra.git
				synced 2025-10-30 05:10:03 +00:00 
			
		
		
		
	Merge pull request #5108 from B3n30/load_aes_key_X0x25_from_nativeFirm
Load NCCHSecure2 keyX from native firm
This commit is contained in:
		
						commit
						ab998b04c8
					
				
					 4 changed files with 220 additions and 12 deletions
				
			
		|  | @ -421,6 +421,8 @@ add_library(core STATIC | |||
|     hw/hw.h | ||||
|     hw/lcd.cpp | ||||
|     hw/lcd.h | ||||
|     hw/rsa/rsa.cpp | ||||
|     hw/rsa/rsa.h | ||||
|     hw/y2r.cpp | ||||
|     hw/y2r.h | ||||
|     loader/3dsx.cpp | ||||
|  |  | |||
|  | @ -8,6 +8,7 @@ | |||
| #include <sstream> | ||||
| #include <cryptopp/aes.h> | ||||
| #include <cryptopp/modes.h> | ||||
| #include <cryptopp/sha.h> | ||||
| #include <fmt/format.h> | ||||
| #include "common/common_paths.h" | ||||
| #include "common/file_util.h" | ||||
|  | @ -17,6 +18,7 @@ | |||
| #include "core/hle/service/fs/archive.h" | ||||
| #include "core/hw/aes/arithmetic128.h" | ||||
| #include "core/hw/aes/key.h" | ||||
| #include "core/hw/rsa/rsa.h" | ||||
| 
 | ||||
| namespace HW::AES { | ||||
| 
 | ||||
|  | @ -26,7 +28,7 @@ namespace { | |||
| // normal key dumped from a Wii U solving the equation:
 | ||||
| // 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
 | ||||
| // 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.
 | ||||
| constexpr AESKey generator_constant = {{0x1F, 0xF9, 0xE9, 0xAA, 0xC5, 0xFE, 0x04, 0x08, 0x02, 0x45, | ||||
|                                         0x91, 0xDC, 0x5D, 0x52, 0x76, 0x8A}}; | ||||
|  | @ -203,14 +205,67 @@ void LoadBootromKeys() { | |||
| } | ||||
| 
 | ||||
| void LoadNativeFirmKeysOld3DS() { | ||||
|     // Use the save mode native firm instead of the normal mode since there are only 2 version of it
 | ||||
|     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 = 933480; | ||||
|     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(0x100); | ||||
|         std::copy(result.begin(), result.end(), temp.end() - result.size()); | ||||
|         result = temp; | ||||
|     } else if (result.size() > 0x100) { | ||||
|         result.resize(0x100); | ||||
|     } | ||||
| 
 | ||||
|     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 LoadSafeModeNativeFirmKeysOld3DS() { | ||||
|     // Use the safe mode native firm instead of the normal mode since there are only 2 version of it
 | ||||
|     // and thus we can use fixed offsets
 | ||||
| 
 | ||||
|     constexpr u64 save_mode_native_firm_id = 0x00040138'00000003; | ||||
|     constexpr u64 safe_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(safe_mode_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); | ||||
|  | @ -223,7 +278,7 @@ void LoadNativeFirmKeysOld3DS() { | |||
|     auto firm = std::move(file_result).Unwrap(); | ||||
|     const std::size_t size = firm->GetSize(); | ||||
|     if (size != 843776) { | ||||
|         LOG_ERROR(HW_AES, "save mode native firm has wrong size {}", size); | ||||
|         LOG_ERROR(HW_AES, "safe mode native firm has wrong size {}", size); | ||||
|         return; | ||||
|     } | ||||
|     std::vector<u8> firm_buffer(size); | ||||
|  | @ -265,16 +320,16 @@ void LoadNativeFirmKeysNew3DS() { | |||
|     AESKey secret_key; | ||||
|     secret.ReadArray(secret_key.data(), secret_key.size()); | ||||
| 
 | ||||
|     // Use the save mode native firm instead of the normal mode since there are only 1 version of it
 | ||||
|     // Use the safe mode native firm instead of the normal mode since there are only 1 version of it
 | ||||
|     // and thus we can use fixed offsets
 | ||||
|     constexpr u64 save_mode_native_firm_id = 0x00040138'20000003; | ||||
|     constexpr u64 safe_mode_native_firm_id = 0x00040138'20000003; | ||||
| 
 | ||||
|     // TODO(B3N30): Add the 0x25 KeyX that gets initalized by native_firm
 | ||||
| 
 | ||||
|     // TODO(B3N30): Add the 0x18 - 0x1F KeyX that gets initalized by native_firm. This probably
 | ||||
|     // requires the normal native firm with version > 9.6.0-X
 | ||||
| 
 | ||||
|     FileSys::NCCHArchive archive(save_mode_native_firm_id, Service::FS::MediaType::NAND); | ||||
|     FileSys::NCCHArchive archive(safe_mode_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); | ||||
|  | @ -296,7 +351,7 @@ void LoadNativeFirmKeysNew3DS() { | |||
|         return a | b << 8 | c << 16 | d << 24; | ||||
|     }; | ||||
|     if (MakeMagic('F', 'I', 'R', 'M') != header.magic) { | ||||
|         LOG_ERROR(HW_AES, "N3DS SAVE MODE Native Firm has wrong header {}", header.magic); | ||||
|         LOG_ERROR(HW_AES, "N3DS SAFE MODE Native Firm has wrong header {}", header.magic); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|  | @ -443,11 +498,13 @@ void InitKeys() { | |||
|     static bool initialized = false; | ||||
|     if (initialized) | ||||
|         return; | ||||
|     initialized = true; | ||||
|     HW::RSA::InitSlots(); | ||||
|     LoadBootromKeys(); | ||||
|     LoadNativeFirmKeysOld3DS(); | ||||
|     LoadSafeModeNativeFirmKeysOld3DS(); | ||||
|     LoadNativeFirmKeysNew3DS(); | ||||
|     LoadPresetKeys(); | ||||
|     initialized = true; | ||||
| } | ||||
| 
 | ||||
| void SetKeyX(std::size_t slot_id, const AESKey& key) { | ||||
|  |  | |||
							
								
								
									
										113
									
								
								src/core/hw/rsa/rsa.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										113
									
								
								src/core/hw/rsa/rsa.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,113 @@ | |||
| // Copyright 2020 Citra Emulator Project
 | ||||
| // Licensed under GPLv2 or any later version
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #include <sstream> | ||||
| #include <cryptopp/hex.h> | ||||
| #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 "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 = static_cast<u8>(std::strtol(byteString.c_str(), nullptr, 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; | ||||
|     CryptoPP::HexDecoder decoder; | ||||
|     decoder.Put(reinterpret_cast<unsigned char*>(ss.str().data()), ss.str().size()); | ||||
|     decoder.MessageEnd(); | ||||
|     std::vector<u8> result(decoder.MaxRetrievable()); | ||||
|     decoder.Get(result.data(), result.size()); | ||||
|     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; | ||||
|     FileUtil::IOFile file(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(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(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 std::array<u8, 224> asn1_header = { | ||||
|         {0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, | ||||
|          0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, | ||||
|          0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, | ||||
|          0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, | ||||
|          0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, | ||||
|          0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, | ||||
|          0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, | ||||
|          0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, | ||||
|          0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, | ||||
|          0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, | ||||
|          0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, | ||||
|          0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, | ||||
|          0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, | ||||
|          0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x30, 0x31, 0x30, 0x0D, 0x06, | ||||
|          0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05, 0x00, 0x04, 0x20}}; | ||||
| 
 | ||||
|     std::vector<u8> message(asn1_header.begin(), asn1_header.end()); | ||||
|     CryptoPP::SHA256 sha; | ||||
|     message.resize(message.size() + CryptoPP::SHA256::DIGESTSIZE); | ||||
|     sha.CalculateDigest(message.data() + asn1_header.size(), data.data(), data.size()); | ||||
|     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