From c00768d6d046ac718505ebb946380e4741cb42d4 Mon Sep 17 00:00:00 2001 From: Steveice10 <1269164+Steveice10@users.noreply.github.com> Date: Thu, 6 Jul 2023 11:45:46 -0700 Subject: [PATCH] nfc: Use existing secrets infrastructure for amiibo encryption. (#6652) --- src/core/hle/service/nfc/amiibo_crypto.cpp | 93 +++++++++------------- src/core/hle/service/nfc/amiibo_crypto.h | 30 ++----- src/core/hle/service/nfc/nfc_device.cpp | 3 +- src/core/hw/aes/key.cpp | 14 +++- src/core/hw/aes/key.h | 8 +- 5 files changed, 68 insertions(+), 80 deletions(-) diff --git a/src/core/hle/service/nfc/amiibo_crypto.cpp b/src/core/hle/service/nfc/amiibo_crypto.cpp index d57d80aba..dc90f0855 100644 --- a/src/core/hle/service/nfc/amiibo_crypto.cpp +++ b/src/core/hle/service/nfc/amiibo_crypto.cpp @@ -12,9 +12,9 @@ #include #include -#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 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 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 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(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::Decryption d; + d.SetKeyWithIV(nfc_key.data(), nfc_key.size(), nfc_iv.data(), nfc_iv.size()); + std::array decrypted_salt{}; + d.ProcessData(reinterpret_cast(decrypted_salt.data()), + reinterpret_cast(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& hmac_ctx, const HmacKey& hmac_key, - const std::vector& seed) { +void CryptoInit(CryptoCtx& ctx, CryptoPP::HMAC& hmac_ctx, + std::span hmac_key, std::span seed) { // Initialize context ctx.used = false; ctx.counter = 0; @@ -216,16 +225,16 @@ void CryptoStep(CryptoCtx& ctx, CryptoPP::HMAC& hmac_ctx, Drgb output.data(), reinterpret_cast(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 internal_key = GenerateInternalKey(key, seed); + const std::vector internal_key = GenerateInternalKey(secret, seed); // Initialize context CryptoCtx ctx{}; CryptoPP::HMAC 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 tag_hmac(tag_keys.hmac_key.data(), sizeof(HmacKey)); + CryptoPP::HMAC tag_hmac(tag_keys.hmac_key.data(), HMAC_KEY_SIZE); tag_hmac.CalculateDigest(reinterpret_cast(&tag_data.hmac_tag), reinterpret_cast(&tag_data.uid), input_length); // Regenerate data HMAC constexpr std::size_t input_length2 = DYNAMIC_LOCK_START - WRITE_COUNTER_START; - CryptoPP::HMAC data_hmac(data_keys.hmac_key.data(), sizeof(HmacKey)); + CryptoPP::HMAC data_hmac(data_keys.hmac_key.data(), HMAC_KEY_SIZE); data_hmac.CalculateDigest(reinterpret_cast(&tag_data.hmac_data), reinterpret_cast(&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 tag_hmac(tag_keys.hmac_key.data(), sizeof(HmacKey)); + CryptoPP::HMAC tag_hmac(tag_keys.hmac_key.data(), HMAC_KEY_SIZE); tag_hmac.CalculateDigest(reinterpret_cast(&encoded_tag_data.hmac_tag), reinterpret_cast(&tag_data.uid), input_length); // Generate data HMAC - CryptoPP::HMAC data_hmac(data_keys.hmac_key.data(), sizeof(HmacKey)); + CryptoPP::HMAC data_hmac(data_keys.hmac_key.data(), HMAC_KEY_SIZE); data_hmac.Update(reinterpret_cast(&tag_data.write_counter), input_length2); data_hmac.Update(reinterpret_cast(&encoded_tag_data.hmac_tag), diff --git a/src/core/hle/service/nfc/amiibo_crypto.h b/src/core/hle/service/nfc/amiibo_crypto.h index f253b7372..ef3f5b749 100644 --- a/src/core/hle/service/nfc/amiibo_crypto.h +++ b/src/core/hle/service/nfc/amiibo_crypto.h @@ -15,6 +15,10 @@ template 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; using DrgbOutput = std::array; 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 type_string; - u8 reserved; - u8 magic_length; - std::array magic_bytes; - std::array xor_pad; -}; -static_assert(sizeof(InternalKey) == 0x50, "InternalKey is an invalid size"); -static_assert(std::is_trivially_copyable_v, "InternalKey must be trivially copyable."); - struct CryptoCtx { std::array 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 GenerateInternalKey(const InternalKey& key, const HashSeed& seed); +std::vector GenerateInternalKey(const HW::AES::NfcSecret& secret, const HashSeed& seed); // Initializes mbedtls context -void CryptoInit(CryptoCtx& ctx, CryptoPP::HMAC& hmac_ctx, const HmacKey& hmac_key, - const std::vector& seed); +void CryptoInit(CryptoCtx& ctx, CryptoPP::HMAC& hmac_ctx, + std::span hmac_key, std::span seed); // Feeds data to mbedtls context to generate the derived key void CryptoStep(CryptoCtx& ctx, CryptoPP::HMAC& 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); diff --git a/src/core/hle/service/nfc/nfc_device.cpp b/src/core/hle/service/nfc/nfc_device.cpp index 63f862c3b..e9ef3cb77 100644 --- a/src/core/hle/service/nfc/nfc_device.cpp +++ b/src/core/hle/service/nfc/nfc_device.cpp @@ -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 = {}; diff --git a/src/core/hw/aes/key.cpp b/src/core/hw/aes/key.cpp index 991fa5a5e..6614d29a7 100644 --- a/src/core/hw/aes/key.cpp +++ b/src/core/hw/aes/key.cpp @@ -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() { diff --git a/src/core/hw/aes/key.h b/src/core/hw/aes/key.h index 5131882d7..b12e250bf 100644 --- a/src/core/hw/aes/key.h +++ b/src/core/hw/aes/key.h @@ -60,6 +60,11 @@ struct NfcSecret { std::vector 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