nfc: Use existing secrets infrastructure for amiibo encryption. (#6652)

This commit is contained in:
Steveice10 2023-07-06 11:45:46 -07:00 committed by GitHub
parent 4383f6d80a
commit c00768d6d0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 68 additions and 80 deletions

View file

@ -12,9 +12,9 @@
#include <cryptopp/modes.h> #include <cryptopp/modes.h>
#include <cryptopp/sha.h> #include <cryptopp/sha.h>
#include "common/file_util.h"
#include "common/logging/log.h" #include "common/logging/log.h"
#include "core/hle/service/nfc/amiibo_crypto.h" #include "core/hle/service/nfc/amiibo_crypto.h"
#include "core/hw/aes/key.h"
namespace Service::NFC::AmiiboCrypto { namespace Service::NFC::AmiiboCrypto {
@ -159,35 +159,44 @@ HashSeed GetSeed(const NTAG215File& data) {
return seed; return seed;
} }
std::vector<u8> GenerateInternalKey(const InternalKey& key, const HashSeed& seed) { std::vector<u8> GenerateInternalKey(const HW::AES::NfcSecret& secret, const HashSeed& seed) {
const std::size_t seed_part1_len = sizeof(key.magic_bytes) - key.magic_length; static constexpr std::size_t FULL_SEED_LENGTH = 0x10;
const std::size_t string_size = key.type_string.size(); 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); std::vector<u8> output(string_size + seed_part1_len);
// Copy whole type string // 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); memcpy(output.data() + string_size, &seed, seed_part1_len);
// Append all bytes from magicBytes // Append all bytes from secret.seed
output.insert(output.end(), key.magic_bytes.begin(), output.insert(output.end(), secret.seed.begin(), secret.seed.end());
key.magic_bytes.begin() + key.magic_length);
output.insert(output.end(), seed.uid_1.begin(), seed.uid_1.end()); output.insert(output.end(), seed.uid_1.begin(), seed.uid_1.end());
output.emplace_back(seed.nintendo_id_1); output.emplace_back(seed.nintendo_id_1);
output.insert(output.end(), seed.uid_2.begin(), seed.uid_2.end()); output.insert(output.end(), seed.uid_2.begin(), seed.uid_2.end());
output.emplace_back(seed.nintendo_id_2); output.emplace_back(seed.nintendo_id_2);
for (std::size_t i = 0; i < sizeof(seed.keygen_salt); i++) { HW::AES::SelectDlpNfcKeyYIndex(HW::AES::DlpNfcKeyY::Nfc);
output.emplace_back(static_cast<u8>(seed.keygen_salt[i] ^ key.xor_pad[i])); 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; return output;
} }
void CryptoInit(CryptoCtx& ctx, CryptoPP::HMAC<CryptoPP::SHA256>& hmac_ctx, const HmacKey& hmac_key, void CryptoInit(CryptoCtx& ctx, CryptoPP::HMAC<CryptoPP::SHA256>& hmac_ctx,
const std::vector<u8>& seed) { std::span<const u8> hmac_key, std::span<const u8> seed) {
// Initialize context // Initialize context
ctx.used = false; ctx.used = false;
ctx.counter = 0; 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); 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); const auto seed = GetSeed(data);
// Generate internal seed // Generate internal seed
const std::vector<u8> internal_key = GenerateInternalKey(key, seed); const std::vector<u8> internal_key = GenerateInternalKey(secret, seed);
// Initialize context // Initialize context
CryptoCtx ctx{}; CryptoCtx ctx{};
CryptoPP::HMAC<CryptoPP::SHA256> hmac_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 // Generate derived keys
DerivedKeys 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; out_data.password = in_data.password;
} }
bool LoadKeys(InternalKey& locked_secret, InternalKey& unfixed_info) { static constexpr std::size_t HMAC_KEY_SIZE = 0x10;
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");
}
bool DecodeAmiibo(const EncryptedNTAG215File& encrypted_tag_data, NTAG215File& tag_data) { bool DecodeAmiibo(const EncryptedNTAG215File& encrypted_tag_data, NTAG215File& tag_data) {
InternalKey locked_secret{}; if (!HW::AES::NfcSecretsAvailable()) {
InternalKey unfixed_info{};
if (!LoadKeys(locked_secret, unfixed_info)) {
return false; return false;
} }
auto unfixed_info = HW::AES::GetNfcSecret(HW::AES::NfcSecretId::UnfixedInfo);
auto locked_secret = HW::AES::GetNfcSecret(HW::AES::NfcSecretId::LockedSecret);
// Generate keys // Generate keys
NTAG215File encoded_data = NfcDataToEncodedData(encrypted_tag_data); NTAG215File encoded_data = NfcDataToEncodedData(encrypted_tag_data);
const auto data_keys = GenerateKey(unfixed_info, encoded_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! // Regenerate tag HMAC. Note: order matters, data HMAC depends on tag HMAC!
constexpr std::size_t input_length = DYNAMIC_LOCK_START - UUID_START; 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), tag_hmac.CalculateDigest(reinterpret_cast<unsigned char*>(&tag_data.hmac_tag),
reinterpret_cast<const unsigned char*>(&tag_data.uid), input_length); reinterpret_cast<const unsigned char*>(&tag_data.uid), input_length);
// Regenerate data HMAC // Regenerate data HMAC
constexpr std::size_t input_length2 = DYNAMIC_LOCK_START - WRITE_COUNTER_START; 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), data_hmac.CalculateDigest(reinterpret_cast<unsigned char*>(&tag_data.hmac_data),
reinterpret_cast<const unsigned char*>(&tag_data.write_counter), reinterpret_cast<const unsigned char*>(&tag_data.write_counter),
input_length2); 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) { bool EncodeAmiibo(const NTAG215File& tag_data, EncryptedNTAG215File& encrypted_tag_data) {
InternalKey locked_secret{}; if (!HW::AES::NfcSecretsAvailable()) {
InternalKey unfixed_info{};
if (!LoadKeys(locked_secret, unfixed_info)) {
return false; return false;
} }
auto unfixed_info = HW::AES::GetNfcSecret(HW::AES::NfcSecretId::UnfixedInfo);
auto locked_secret = HW::AES::GetNfcSecret(HW::AES::NfcSecretId::LockedSecret);
// Generate keys // Generate keys
const auto data_keys = GenerateKey(unfixed_info, tag_data); const auto data_keys = GenerateKey(unfixed_info, tag_data);
const auto tag_keys = GenerateKey(locked_secret, 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 // Generate tag HMAC
constexpr std::size_t input_length = DYNAMIC_LOCK_START - UUID_START; constexpr std::size_t input_length = DYNAMIC_LOCK_START - UUID_START;
constexpr std::size_t input_length2 = HMAC_TAG_START - WRITE_COUNTER_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), tag_hmac.CalculateDigest(reinterpret_cast<unsigned char*>(&encoded_tag_data.hmac_tag),
reinterpret_cast<const unsigned char*>(&tag_data.uid), input_length); reinterpret_cast<const unsigned char*>(&tag_data.uid), input_length);
// Generate data HMAC // 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), data_hmac.Update(reinterpret_cast<const unsigned char*>(&tag_data.write_counter),
input_length2); input_length2);
data_hmac.Update(reinterpret_cast<unsigned char*>(&encoded_tag_data.hmac_tag), data_hmac.Update(reinterpret_cast<unsigned char*>(&encoded_tag_data.hmac_tag),

View file

@ -15,6 +15,10 @@ template <class T>
class HMAC; class HMAC;
} // namespace CryptoPP } // namespace CryptoPP
namespace HW::AES {
struct NfcSecret;
} // namespace HW::AES
namespace Service::NFC::AmiiboCrypto { namespace Service::NFC::AmiiboCrypto {
// Byte locations in Service::NFC::NTAG215File // Byte locations in Service::NFC::NTAG215File
constexpr std::size_t HMAC_DATA_START = 0x8; 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 UUID_START = 0x1D4;
constexpr std::size_t DYNAMIC_LOCK_START = 0x208; constexpr std::size_t DYNAMIC_LOCK_START = 0x208;
using HmacKey = std::array<u8, 0x10>;
using DrgbOutput = std::array<u8, 0x20>; using DrgbOutput = std::array<u8, 0x20>;
struct HashSeed { struct HashSeed {
@ -38,17 +41,6 @@ struct HashSeed {
}; };
static_assert(sizeof(HashSeed) == 0x40, "HashSeed is an invalid size"); 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 { struct CryptoCtx {
std::array<char, 480> buffer; std::array<char, 480> buffer;
bool used; bool used;
@ -79,27 +71,21 @@ EncryptedNTAG215File EncodedDataToNfcData(const NTAG215File& encoded_data);
HashSeed GetSeed(const NTAG215File& data); HashSeed GetSeed(const NTAG215File& data);
// Middle step on the generation of derived keys // 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 // Initializes mbedtls context
void CryptoInit(CryptoCtx& ctx, CryptoPP::HMAC<CryptoPP::SHA256>& hmac_ctx, const HmacKey& hmac_key, void CryptoInit(CryptoCtx& ctx, CryptoPP::HMAC<CryptoPP::SHA256>& hmac_ctx,
const std::vector<u8>& seed); std::span<const u8> hmac_key, std::span<const u8> seed);
// Feeds data to mbedtls context to generate the derived key // Feeds data to mbedtls context to generate the derived key
void CryptoStep(CryptoCtx& ctx, CryptoPP::HMAC<CryptoPP::SHA256>& hmac_ctx, DrgbOutput& output); void CryptoStep(CryptoCtx& ctx, CryptoPP::HMAC<CryptoPP::SHA256>& hmac_ctx, DrgbOutput& output);
// Generates the derived key from amiibo data // 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 // Encodes or decodes amiibo data
void Cipher(const DerivedKeys& keys, const NTAG215File& in_data, NTAG215File& out_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 /// Decodes encripted amiibo data returns true if output is valid
bool DecodeAmiibo(const EncryptedNTAG215File& encrypted_tag_data, NTAG215File& tag_data); bool DecodeAmiibo(const EncryptedNTAG215File& encrypted_tag_data, NTAG215File& tag_data);

View file

@ -13,6 +13,7 @@
#include "core/hle/kernel/shared_page.h" #include "core/hle/kernel/shared_page.h"
#include "core/hle/service/nfc/amiibo_crypto.h" #include "core/hle/service/nfc/amiibo_crypto.h"
#include "core/hle/service/nfc/nfc_device.h" #include "core/hle/service/nfc/nfc_device.h"
#include "core/hw/aes/key.h"
SERVICE_CONSTRUCT_IMPL(Service::NFC::NfcDevice) SERVICE_CONSTRUCT_IMPL(Service::NFC::NfcDevice)
@ -98,7 +99,7 @@ bool NfcDevice::LoadAmiibo(std::string filename) {
} }
// Fallback for encrypted amiibos without keys // Fallback for encrypted amiibos without keys
if (!AmiiboCrypto::IsKeyAvailable()) { if (!HW::AES::NfcSecretsAvailable()) {
LOG_INFO(Service_NFC, "Loading amiibo without keys"); LOG_INFO(Service_NFC, "Loading amiibo without keys");
memcpy(&encrypted_tag.raw, &tag.raw, sizeof(EncryptedNTAG215File)); memcpy(&encrypted_tag.raw, &tag.raw, sizeof(EncryptedNTAG215File));
tag.file = {}; tag.file = {};

View file

@ -593,8 +593,18 @@ void SelectDlpNfcKeyYIndex(u8 index) {
key_slots[KeySlotID::DLPNFCDataKey].SetKeyY(dlp_nfc_key_y_slots.at(index)); key_slots[KeySlotID::DLPNFCDataKey].SetKeyY(dlp_nfc_key_y_slots.at(index));
} }
const NfcSecret& GetNfcSecret(u8 index) { bool NfcSecretsAvailable() {
return nfc_secrets[index]; 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() { const AESIV& GetNfcIv() {

View file

@ -60,6 +60,11 @@ struct NfcSecret {
std::vector<u8> hmac_key; std::vector<u8> hmac_key;
}; };
enum NfcSecretId : std::size_t {
UnfixedInfo = 0,
LockedSecret = 1,
};
constexpr std::size_t MaxCommonKeySlot = 6; constexpr std::size_t MaxCommonKeySlot = 6;
constexpr std::size_t NumDlpNfcKeyYs = 2; constexpr std::size_t NumDlpNfcKeyYs = 2;
constexpr std::size_t NumNfcSecrets = 2; constexpr std::size_t NumNfcSecrets = 2;
@ -83,7 +88,8 @@ AESKey GetNormalKey(std::size_t slot_id);
void SelectCommonKeyIndex(u8 index); void SelectCommonKeyIndex(u8 index);
void SelectDlpNfcKeyYIndex(u8 index); void SelectDlpNfcKeyYIndex(u8 index);
const NfcSecret& GetNfcSecret(u8 index); bool NfcSecretsAvailable();
const NfcSecret& GetNfcSecret(NfcSecretId secret_id);
const AESIV& GetNfcIv(); const AESIV& GetNfcIv();
} // namespace HW::AES } // namespace HW::AES