mirror of
https://github.com/citra-emu/citra.git
synced 2024-11-15 02:30:05 +00:00
nfc: Use existing secrets infrastructure for amiibo encryption. (#6652)
This commit is contained in:
parent
4383f6d80a
commit
c00768d6d0
@ -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…
Reference in New Issue
Block a user