mirror of
				https://github.com/PabloMK7/citra.git
				synced 2025-10-30 21:30:04 +00:00 
			
		
		
		
	nfc: Use existing secrets infrastructure for amiibo encryption. (#6652)
This commit is contained in:
		
							parent
							
								
									4383f6d80a
								
							
						
					
					
						commit
						c00768d6d0
					
				
					 5 changed files with 68 additions and 80 deletions
				
			
		|  | @ -12,9 +12,9 @@ | |||
| #include <cryptopp/modes.h> | ||||
| #include <cryptopp/sha.h> | ||||
| 
 | ||||
| #include "common/file_util.h" | ||||
| #include "common/logging/log.h" | ||||
| #include "core/hle/service/nfc/amiibo_crypto.h" | ||||
| #include "core/hw/aes/key.h" | ||||
| 
 | ||||
| namespace Service::NFC::AmiiboCrypto { | ||||
| 
 | ||||
|  | @ -159,35 +159,44 @@ HashSeed GetSeed(const NTAG215File& data) { | |||
|     return seed; | ||||
| } | ||||
| 
 | ||||
| std::vector<u8> GenerateInternalKey(const InternalKey& key, const HashSeed& seed) { | ||||
|     const std::size_t seed_part1_len = sizeof(key.magic_bytes) - key.magic_length; | ||||
|     const std::size_t string_size = key.type_string.size(); | ||||
| std::vector<u8> GenerateInternalKey(const HW::AES::NfcSecret& secret, const HashSeed& seed) { | ||||
|     static constexpr std::size_t FULL_SEED_LENGTH = 0x10; | ||||
|     const std::size_t seed_part1_len = FULL_SEED_LENGTH - secret.seed.size(); | ||||
|     const std::size_t string_size = secret.phrase.size(); | ||||
|     std::vector<u8> output(string_size + seed_part1_len); | ||||
| 
 | ||||
|     // Copy whole type string
 | ||||
|     memccpy(output.data(), key.type_string.data(), '\0', string_size); | ||||
|     memccpy(output.data(), secret.phrase.data(), '\0', string_size); | ||||
| 
 | ||||
|     // Append (16 - magic_length) from the input seed
 | ||||
|     // Append (FULL_SEED_LENGTH - secret.seed.size()) from the input seed
 | ||||
|     memcpy(output.data() + string_size, &seed, seed_part1_len); | ||||
| 
 | ||||
|     // Append all bytes from magicBytes
 | ||||
|     output.insert(output.end(), key.magic_bytes.begin(), | ||||
|                   key.magic_bytes.begin() + key.magic_length); | ||||
|     // Append all bytes from secret.seed
 | ||||
|     output.insert(output.end(), secret.seed.begin(), secret.seed.end()); | ||||
| 
 | ||||
|     output.insert(output.end(), seed.uid_1.begin(), seed.uid_1.end()); | ||||
|     output.emplace_back(seed.nintendo_id_1); | ||||
|     output.insert(output.end(), seed.uid_2.begin(), seed.uid_2.end()); | ||||
|     output.emplace_back(seed.nintendo_id_2); | ||||
| 
 | ||||
|     for (std::size_t i = 0; i < sizeof(seed.keygen_salt); i++) { | ||||
|         output.emplace_back(static_cast<u8>(seed.keygen_salt[i] ^ key.xor_pad[i])); | ||||
|     } | ||||
|     HW::AES::SelectDlpNfcKeyYIndex(HW::AES::DlpNfcKeyY::Nfc); | ||||
|     auto nfc_key = HW::AES::GetNormalKey(HW::AES::KeySlotID::DLPNFCDataKey); | ||||
|     auto nfc_iv = HW::AES::GetNfcIv(); | ||||
| 
 | ||||
|     // Decrypt the keygen salt using the NFC key and IV.
 | ||||
|     CryptoPP::CTR_Mode<CryptoPP::AES>::Decryption d; | ||||
|     d.SetKeyWithIV(nfc_key.data(), nfc_key.size(), nfc_iv.data(), nfc_iv.size()); | ||||
|     std::array<u8, sizeof(seed.keygen_salt)> decrypted_salt{}; | ||||
|     d.ProcessData(reinterpret_cast<unsigned char*>(decrypted_salt.data()), | ||||
|                   reinterpret_cast<const unsigned char*>(seed.keygen_salt.data()), | ||||
|                   seed.keygen_salt.size()); | ||||
|     output.insert(output.end(), decrypted_salt.begin(), decrypted_salt.end()); | ||||
| 
 | ||||
|     return output; | ||||
| } | ||||
| 
 | ||||
| void CryptoInit(CryptoCtx& ctx, CryptoPP::HMAC<CryptoPP::SHA256>& hmac_ctx, const HmacKey& hmac_key, | ||||
|                 const std::vector<u8>& seed) { | ||||
| void CryptoInit(CryptoCtx& ctx, CryptoPP::HMAC<CryptoPP::SHA256>& hmac_ctx, | ||||
|                 std::span<const u8> hmac_key, std::span<const u8> seed) { | ||||
|     // Initialize context
 | ||||
|     ctx.used = false; | ||||
|     ctx.counter = 0; | ||||
|  | @ -216,16 +225,16 @@ void CryptoStep(CryptoCtx& ctx, CryptoPP::HMAC<CryptoPP::SHA256>& hmac_ctx, Drgb | |||
|         output.data(), reinterpret_cast<const unsigned char*>(ctx.buffer.data()), ctx.buffer_size); | ||||
| } | ||||
| 
 | ||||
| DerivedKeys GenerateKey(const InternalKey& key, const NTAG215File& data) { | ||||
| DerivedKeys GenerateKey(const HW::AES::NfcSecret& secret, const NTAG215File& data) { | ||||
|     const auto seed = GetSeed(data); | ||||
| 
 | ||||
|     // Generate internal seed
 | ||||
|     const std::vector<u8> internal_key = GenerateInternalKey(key, seed); | ||||
|     const std::vector<u8> internal_key = GenerateInternalKey(secret, seed); | ||||
| 
 | ||||
|     // Initialize context
 | ||||
|     CryptoCtx ctx{}; | ||||
|     CryptoPP::HMAC<CryptoPP::SHA256> hmac_ctx; | ||||
|     CryptoInit(ctx, hmac_ctx, key.hmac_key, internal_key); | ||||
|     CryptoInit(ctx, hmac_ctx, secret.hmac_key, internal_key); | ||||
| 
 | ||||
|     // Generate derived keys
 | ||||
|     DerivedKeys derived_keys{}; | ||||
|  | @ -264,40 +273,16 @@ void Cipher(const DerivedKeys& keys, const NTAG215File& in_data, NTAG215File& ou | |||
|     out_data.password = in_data.password; | ||||
| } | ||||
| 
 | ||||
| bool LoadKeys(InternalKey& locked_secret, InternalKey& unfixed_info) { | ||||
|     const auto citra_keys_dir = FileUtil::GetUserPath(FileUtil::UserPath::SysDataDir); | ||||
|     auto keys_file = FileUtil::IOFile(citra_keys_dir + "key_retail.bin", "rb"); | ||||
| 
 | ||||
|     if (!keys_file.IsOpen()) { | ||||
|         LOG_ERROR(Service_NFC, "No keys detected"); | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     if (keys_file.ReadBytes(&unfixed_info, sizeof(InternalKey)) != sizeof(InternalKey)) { | ||||
|         LOG_ERROR(Service_NFC, "Failed to read unfixed_info"); | ||||
|         return false; | ||||
|     } | ||||
|     if (keys_file.ReadBytes(&locked_secret, sizeof(InternalKey)) != sizeof(InternalKey)) { | ||||
|         LOG_ERROR(Service_NFC, "Failed to read locked-secret"); | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| bool IsKeyAvailable() { | ||||
|     const auto citra_keys_dir = FileUtil::GetUserPath(FileUtil::UserPath::SysDataDir); | ||||
|     return FileUtil::Exists(citra_keys_dir + "key_retail.bin"); | ||||
| } | ||||
| static constexpr std::size_t HMAC_KEY_SIZE = 0x10; | ||||
| 
 | ||||
| bool DecodeAmiibo(const EncryptedNTAG215File& encrypted_tag_data, NTAG215File& tag_data) { | ||||
|     InternalKey locked_secret{}; | ||||
|     InternalKey unfixed_info{}; | ||||
| 
 | ||||
|     if (!LoadKeys(locked_secret, unfixed_info)) { | ||||
|     if (!HW::AES::NfcSecretsAvailable()) { | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     auto unfixed_info = HW::AES::GetNfcSecret(HW::AES::NfcSecretId::UnfixedInfo); | ||||
|     auto locked_secret = HW::AES::GetNfcSecret(HW::AES::NfcSecretId::LockedSecret); | ||||
| 
 | ||||
|     // Generate keys
 | ||||
|     NTAG215File encoded_data = NfcDataToEncodedData(encrypted_tag_data); | ||||
|     const auto data_keys = GenerateKey(unfixed_info, encoded_data); | ||||
|  | @ -308,13 +293,13 @@ bool DecodeAmiibo(const EncryptedNTAG215File& encrypted_tag_data, NTAG215File& t | |||
| 
 | ||||
|     // Regenerate tag HMAC. Note: order matters, data HMAC depends on tag HMAC!
 | ||||
|     constexpr std::size_t input_length = DYNAMIC_LOCK_START - UUID_START; | ||||
|     CryptoPP::HMAC<CryptoPP::SHA256> tag_hmac(tag_keys.hmac_key.data(), sizeof(HmacKey)); | ||||
|     CryptoPP::HMAC<CryptoPP::SHA256> tag_hmac(tag_keys.hmac_key.data(), HMAC_KEY_SIZE); | ||||
|     tag_hmac.CalculateDigest(reinterpret_cast<unsigned char*>(&tag_data.hmac_tag), | ||||
|                              reinterpret_cast<const unsigned char*>(&tag_data.uid), input_length); | ||||
| 
 | ||||
|     // Regenerate data HMAC
 | ||||
|     constexpr std::size_t input_length2 = DYNAMIC_LOCK_START - WRITE_COUNTER_START; | ||||
|     CryptoPP::HMAC<CryptoPP::SHA256> data_hmac(data_keys.hmac_key.data(), sizeof(HmacKey)); | ||||
|     CryptoPP::HMAC<CryptoPP::SHA256> data_hmac(data_keys.hmac_key.data(), HMAC_KEY_SIZE); | ||||
|     data_hmac.CalculateDigest(reinterpret_cast<unsigned char*>(&tag_data.hmac_data), | ||||
|                               reinterpret_cast<const unsigned char*>(&tag_data.write_counter), | ||||
|                               input_length2); | ||||
|  | @ -333,13 +318,13 @@ bool DecodeAmiibo(const EncryptedNTAG215File& encrypted_tag_data, NTAG215File& t | |||
| } | ||||
| 
 | ||||
| bool EncodeAmiibo(const NTAG215File& tag_data, EncryptedNTAG215File& encrypted_tag_data) { | ||||
|     InternalKey locked_secret{}; | ||||
|     InternalKey unfixed_info{}; | ||||
| 
 | ||||
|     if (!LoadKeys(locked_secret, unfixed_info)) { | ||||
|     if (!HW::AES::NfcSecretsAvailable()) { | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     auto unfixed_info = HW::AES::GetNfcSecret(HW::AES::NfcSecretId::UnfixedInfo); | ||||
|     auto locked_secret = HW::AES::GetNfcSecret(HW::AES::NfcSecretId::LockedSecret); | ||||
| 
 | ||||
|     // Generate keys
 | ||||
|     const auto data_keys = GenerateKey(unfixed_info, tag_data); | ||||
|     const auto tag_keys = GenerateKey(locked_secret, tag_data); | ||||
|  | @ -349,12 +334,12 @@ bool EncodeAmiibo(const NTAG215File& tag_data, EncryptedNTAG215File& encrypted_t | |||
|     // Generate tag HMAC
 | ||||
|     constexpr std::size_t input_length = DYNAMIC_LOCK_START - UUID_START; | ||||
|     constexpr std::size_t input_length2 = HMAC_TAG_START - WRITE_COUNTER_START; | ||||
|     CryptoPP::HMAC<CryptoPP::SHA256> tag_hmac(tag_keys.hmac_key.data(), sizeof(HmacKey)); | ||||
|     CryptoPP::HMAC<CryptoPP::SHA256> tag_hmac(tag_keys.hmac_key.data(), HMAC_KEY_SIZE); | ||||
|     tag_hmac.CalculateDigest(reinterpret_cast<unsigned char*>(&encoded_tag_data.hmac_tag), | ||||
|                              reinterpret_cast<const unsigned char*>(&tag_data.uid), input_length); | ||||
| 
 | ||||
|     // Generate data HMAC
 | ||||
|     CryptoPP::HMAC<CryptoPP::SHA256> data_hmac(data_keys.hmac_key.data(), sizeof(HmacKey)); | ||||
|     CryptoPP::HMAC<CryptoPP::SHA256> data_hmac(data_keys.hmac_key.data(), HMAC_KEY_SIZE); | ||||
|     data_hmac.Update(reinterpret_cast<const unsigned char*>(&tag_data.write_counter), | ||||
|                      input_length2); | ||||
|     data_hmac.Update(reinterpret_cast<unsigned char*>(&encoded_tag_data.hmac_tag), | ||||
|  |  | |||
|  | @ -15,6 +15,10 @@ template <class T> | |||
| class HMAC; | ||||
| } // namespace CryptoPP
 | ||||
| 
 | ||||
| namespace HW::AES { | ||||
| struct NfcSecret; | ||||
| } // namespace HW::AES
 | ||||
| 
 | ||||
| namespace Service::NFC::AmiiboCrypto { | ||||
| // Byte locations in Service::NFC::NTAG215File
 | ||||
| constexpr std::size_t HMAC_DATA_START = 0x8; | ||||
|  | @ -24,7 +28,6 @@ constexpr std::size_t HMAC_TAG_START = 0x1B4; | |||
| constexpr std::size_t UUID_START = 0x1D4; | ||||
| constexpr std::size_t DYNAMIC_LOCK_START = 0x208; | ||||
| 
 | ||||
| using HmacKey = std::array<u8, 0x10>; | ||||
| using DrgbOutput = std::array<u8, 0x20>; | ||||
| 
 | ||||
| struct HashSeed { | ||||
|  | @ -38,17 +41,6 @@ struct HashSeed { | |||
| }; | ||||
| static_assert(sizeof(HashSeed) == 0x40, "HashSeed is an invalid size"); | ||||
| 
 | ||||
| struct InternalKey { | ||||
|     HmacKey hmac_key; | ||||
|     std::array<char, 0xE> type_string; | ||||
|     u8 reserved; | ||||
|     u8 magic_length; | ||||
|     std::array<u8, 0x10> magic_bytes; | ||||
|     std::array<u8, 0x20> xor_pad; | ||||
| }; | ||||
| static_assert(sizeof(InternalKey) == 0x50, "InternalKey is an invalid size"); | ||||
| static_assert(std::is_trivially_copyable_v<InternalKey>, "InternalKey must be trivially copyable."); | ||||
| 
 | ||||
| struct CryptoCtx { | ||||
|     std::array<char, 480> buffer; | ||||
|     bool used; | ||||
|  | @ -79,27 +71,21 @@ EncryptedNTAG215File EncodedDataToNfcData(const NTAG215File& encoded_data); | |||
| HashSeed GetSeed(const NTAG215File& data); | ||||
| 
 | ||||
| // Middle step on the generation of derived keys
 | ||||
| std::vector<u8> GenerateInternalKey(const InternalKey& key, const HashSeed& seed); | ||||
| std::vector<u8> GenerateInternalKey(const HW::AES::NfcSecret& secret, const HashSeed& seed); | ||||
| 
 | ||||
| // Initializes mbedtls context
 | ||||
| void CryptoInit(CryptoCtx& ctx, CryptoPP::HMAC<CryptoPP::SHA256>& hmac_ctx, const HmacKey& hmac_key, | ||||
|                 const std::vector<u8>& seed); | ||||
| void CryptoInit(CryptoCtx& ctx, CryptoPP::HMAC<CryptoPP::SHA256>& hmac_ctx, | ||||
|                 std::span<const u8> hmac_key, std::span<const u8> seed); | ||||
| 
 | ||||
| // Feeds data to mbedtls context to generate the derived key
 | ||||
| void CryptoStep(CryptoCtx& ctx, CryptoPP::HMAC<CryptoPP::SHA256>& hmac_ctx, DrgbOutput& output); | ||||
| 
 | ||||
| // Generates the derived key from amiibo data
 | ||||
| DerivedKeys GenerateKey(const InternalKey& key, const NTAG215File& data); | ||||
| DerivedKeys GenerateKey(const HW::AES::NfcSecret& secret, const NTAG215File& data); | ||||
| 
 | ||||
| // Encodes or decodes amiibo data
 | ||||
| void Cipher(const DerivedKeys& keys, const NTAG215File& in_data, NTAG215File& out_data); | ||||
| 
 | ||||
| /// Loads both amiibo keys from key_retail.bin
 | ||||
| bool LoadKeys(InternalKey& locked_secret, InternalKey& unfixed_info); | ||||
| 
 | ||||
| /// Returns true if key_retail.bin exist
 | ||||
| bool IsKeyAvailable(); | ||||
| 
 | ||||
| /// Decodes encripted amiibo data returns true if output is valid
 | ||||
| bool DecodeAmiibo(const EncryptedNTAG215File& encrypted_tag_data, NTAG215File& tag_data); | ||||
| 
 | ||||
|  |  | |||
|  | @ -13,6 +13,7 @@ | |||
| #include "core/hle/kernel/shared_page.h" | ||||
| #include "core/hle/service/nfc/amiibo_crypto.h" | ||||
| #include "core/hle/service/nfc/nfc_device.h" | ||||
| #include "core/hw/aes/key.h" | ||||
| 
 | ||||
| SERVICE_CONSTRUCT_IMPL(Service::NFC::NfcDevice) | ||||
| 
 | ||||
|  | @ -98,7 +99,7 @@ bool NfcDevice::LoadAmiibo(std::string filename) { | |||
|     } | ||||
| 
 | ||||
|     // Fallback for encrypted amiibos without keys
 | ||||
|     if (!AmiiboCrypto::IsKeyAvailable()) { | ||||
|     if (!HW::AES::NfcSecretsAvailable()) { | ||||
|         LOG_INFO(Service_NFC, "Loading amiibo without keys"); | ||||
|         memcpy(&encrypted_tag.raw, &tag.raw, sizeof(EncryptedNTAG215File)); | ||||
|         tag.file = {}; | ||||
|  |  | |||
|  | @ -593,8 +593,18 @@ void SelectDlpNfcKeyYIndex(u8 index) { | |||
|     key_slots[KeySlotID::DLPNFCDataKey].SetKeyY(dlp_nfc_key_y_slots.at(index)); | ||||
| } | ||||
| 
 | ||||
| const NfcSecret& GetNfcSecret(u8 index) { | ||||
|     return nfc_secrets[index]; | ||||
| bool NfcSecretsAvailable() { | ||||
|     auto missing_secret = | ||||
|         std::find_if(nfc_secrets.begin(), nfc_secrets.end(), [](auto& nfc_secret) { | ||||
|             return nfc_secret.phrase.empty() || nfc_secret.seed.empty() || | ||||
|                    nfc_secret.hmac_key.empty(); | ||||
|         }); | ||||
|     SelectDlpNfcKeyYIndex(DlpNfcKeyY::Nfc); | ||||
|     return IsNormalKeyAvailable(KeySlotID::DLPNFCDataKey) && missing_secret == nfc_secrets.end(); | ||||
| } | ||||
| 
 | ||||
| const NfcSecret& GetNfcSecret(NfcSecretId secret_id) { | ||||
|     return nfc_secrets[secret_id]; | ||||
| } | ||||
| 
 | ||||
| const AESIV& GetNfcIv() { | ||||
|  |  | |||
|  | @ -60,6 +60,11 @@ struct NfcSecret { | |||
|     std::vector<u8> hmac_key; | ||||
| }; | ||||
| 
 | ||||
| enum NfcSecretId : std::size_t { | ||||
|     UnfixedInfo = 0, | ||||
|     LockedSecret = 1, | ||||
| }; | ||||
| 
 | ||||
| constexpr std::size_t MaxCommonKeySlot = 6; | ||||
| constexpr std::size_t NumDlpNfcKeyYs = 2; | ||||
| constexpr std::size_t NumNfcSecrets = 2; | ||||
|  | @ -83,7 +88,8 @@ AESKey GetNormalKey(std::size_t slot_id); | |||
| void SelectCommonKeyIndex(u8 index); | ||||
| void SelectDlpNfcKeyYIndex(u8 index); | ||||
| 
 | ||||
| const NfcSecret& GetNfcSecret(u8 index); | ||||
| bool NfcSecretsAvailable(); | ||||
| const NfcSecret& GetNfcSecret(NfcSecretId secret_id); | ||||
| const AESIV& GetNfcIv(); | ||||
| 
 | ||||
| } // namespace HW::AES
 | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue