diff --git a/externals/dynarmic b/externals/dynarmic index 459d7d1ba..358cf7c32 160000 --- a/externals/dynarmic +++ b/externals/dynarmic @@ -1 +1 @@ -Subproject commit 459d7d1bafcf85677c989b7cb260d3789aa813e2 +Subproject commit 358cf7c32205a5114964865c86a8455daf810732 diff --git a/src/common/common_paths.h b/src/common/common_paths.h index b56105306..d5b510cdb 100644 --- a/src/common/common_paths.h +++ b/src/common/common_paths.h @@ -45,3 +45,4 @@ // Sys files #define SHARED_FONT "shared_font.bin" +#define AES_KEYS "aes_keys.txt" diff --git a/src/common/logging/backend.cpp b/src/common/logging/backend.cpp index 2ef3e6b05..0ea65e817 100644 --- a/src/common/logging/backend.cpp +++ b/src/common/logging/backend.cpp @@ -62,6 +62,7 @@ namespace Log { SUB(HW, Memory) \ SUB(HW, LCD) \ SUB(HW, GPU) \ + SUB(HW, AES) \ CLS(Frontend) \ CLS(Render) \ SUB(Render, Software) \ diff --git a/src/common/logging/log.h b/src/common/logging/log.h index 4330ef879..a590d39ff 100644 --- a/src/common/logging/log.h +++ b/src/common/logging/log.h @@ -79,6 +79,7 @@ enum class Class : ClassType { HW_Memory, ///< Memory-map and address translation HW_LCD, ///< LCD register emulation HW_GPU, ///< GPU control emulation + HW_AES, ///< AES engine emulation Frontend, ///< Emulator UI Render, ///< Emulator video output and hardware acceleration Render_Software, ///< Software renderer backend diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 1da10afab..da3d024bb 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -159,6 +159,9 @@ set(SRCS hle/service/y2r_u.cpp hle/shared_page.cpp hle/svc.cpp + hw/aes/arithmetic128.cpp + hw/aes/ccm.cpp + hw/aes/key.cpp hw/gpu.cpp hw/hw.cpp hw/lcd.cpp @@ -342,6 +345,9 @@ set(HEADERS hle/service/y2r_u.h hle/shared_page.h hle/svc.h + hw/aes/arithmetic128.h + hw/aes/ccm.h + hw/aes/key.h hw/gpu.h hw/hw.h hw/lcd.h diff --git a/src/core/hw/aes/arithmetic128.cpp b/src/core/hw/aes/arithmetic128.cpp new file mode 100644 index 000000000..55b954a52 --- /dev/null +++ b/src/core/hw/aes/arithmetic128.cpp @@ -0,0 +1,47 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include +#include "core/hw/aes/arithmetic128.h" + +namespace HW { +namespace AES { + +AESKey Lrot128(const AESKey& in, u32 rot) { + AESKey out; + rot %= 128; + const u32 byte_shift = rot / 8; + const u32 bit_shift = rot % 8; + + for (u32 i = 0; i < 16; i++) { + const u32 wrap_index_a = (i + byte_shift) % 16; + const u32 wrap_index_b = (i + byte_shift + 1) % 16; + out[i] = ((in[wrap_index_a] << bit_shift) | (in[wrap_index_b] >> (8 - bit_shift))) & 0xFF; + } + return out; +} + +AESKey Add128(const AESKey& a, const AESKey& b) { + AESKey out; + u32 carry = 0; + u32 sum = 0; + + for (int i = 15; i >= 0; i--) { + sum = a[i] + b[i] + carry; + carry = sum >> 8; + out[i] = static_cast(sum & 0xff); + } + + return out; +} + +AESKey Xor128(const AESKey& a, const AESKey& b) { + AESKey out; + std::transform(a.cbegin(), a.cend(), b.cbegin(), out.begin(), std::bit_xor<>()); + return out; +} + +} // namespace AES +} // namespace HW diff --git a/src/core/hw/aes/arithmetic128.h b/src/core/hw/aes/arithmetic128.h new file mode 100644 index 000000000..d670e2ce2 --- /dev/null +++ b/src/core/hw/aes/arithmetic128.h @@ -0,0 +1,17 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "common/common_types.h" +#include "core/hw/aes/key.h" + +namespace HW { +namespace AES { +AESKey Lrot128(const AESKey& in, u32 rot); +AESKey Add128(const AESKey& a, const AESKey& b); +AESKey Xor128(const AESKey& a, const AESKey& b); + +} // namspace AES +} // namespace HW diff --git a/src/core/hw/aes/ccm.cpp b/src/core/hw/aes/ccm.cpp new file mode 100644 index 000000000..dc7035ab6 --- /dev/null +++ b/src/core/hw/aes/ccm.cpp @@ -0,0 +1,95 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include +#include +#include +#include +#include "common/alignment.h" +#include "common/logging/log.h" +#include "core/hw/aes/ccm.h" +#include "core/hw/aes/key.h" + +namespace HW { +namespace AES { + +namespace { + +// 3DS uses a non-standard AES-CCM algorithm, so we need to derive a sub class from the standard one +// and override with the non-standard part. +using CryptoPP::lword; +using CryptoPP::AES; +using CryptoPP::CCM_Final; +using CryptoPP::CCM_Base; +template +class CCM_3DSVariant_Final : public CCM_Final { +public: + void UncheckedSpecifyDataLengths(lword header_length, lword message_length, + lword footer_length) override { + // 3DS uses the aligned size to generate B0 for authentication, instead of the original size + lword aligned_message_length = Common::AlignUp(message_length, AES_BLOCK_SIZE); + CCM_Base::UncheckedSpecifyDataLengths(header_length, aligned_message_length, footer_length); + CCM_Base::m_messageLength = message_length; // restore the actual message size + } +}; + +class CCM_3DSVariant { +public: + using Encryption = CCM_3DSVariant_Final; + using Decryption = CCM_3DSVariant_Final; +}; + +} // namespace + +std::vector EncryptSignCCM(const std::vector& pdata, const CCMNonce& nonce, + size_t slot_id) { + if (!IsNormalKeyAvailable(slot_id)) { + LOG_ERROR(HW_AES, "Key slot %d not available. Will use zero key.", slot_id); + } + const AESKey normal = GetNormalKey(slot_id); + std::vector cipher(pdata.size() + CCM_MAC_SIZE); + + try { + CCM_3DSVariant::Encryption e; + e.SetKeyWithIV(normal.data(), AES_BLOCK_SIZE, nonce.data(), CCM_NONCE_SIZE); + e.SpecifyDataLengths(0, pdata.size(), 0); + CryptoPP::ArraySource as(pdata.data(), pdata.size(), true, + new CryptoPP::AuthenticatedEncryptionFilter( + e, new CryptoPP::ArraySink(cipher.data(), cipher.size()))); + } catch (const CryptoPP::Exception& e) { + LOG_ERROR(HW_AES, "FAILED with: %s", e.what()); + } + return cipher; +} + +std::vector DecryptVerifyCCM(const std::vector& cipher, const CCMNonce& nonce, + size_t slot_id) { + if (!IsNormalKeyAvailable(slot_id)) { + LOG_ERROR(HW_AES, "Key slot %d not available. Will use zero key.", slot_id); + } + const AESKey normal = GetNormalKey(slot_id); + const std::size_t pdata_size = cipher.size() - CCM_MAC_SIZE; + std::vector pdata(pdata_size); + + try { + CCM_3DSVariant::Decryption d; + d.SetKeyWithIV(normal.data(), AES_BLOCK_SIZE, nonce.data(), CCM_NONCE_SIZE); + d.SpecifyDataLengths(0, pdata_size, 0); + CryptoPP::AuthenticatedDecryptionFilter df( + d, new CryptoPP::ArraySink(pdata.data(), pdata_size)); + CryptoPP::ArraySource as(cipher.data(), cipher.size(), true, new CryptoPP::Redirector(df)); + if (!df.GetLastResult()) { + LOG_ERROR(HW_AES, "FAILED"); + return {}; + } + } catch (const CryptoPP::Exception& e) { + LOG_ERROR(HW_AES, "FAILED with: %s", e.what()); + return {}; + } + return pdata; +} + +} // namespace AES +} // namespace HW diff --git a/src/core/hw/aes/ccm.h b/src/core/hw/aes/ccm.h new file mode 100644 index 000000000..bf4146e80 --- /dev/null +++ b/src/core/hw/aes/ccm.h @@ -0,0 +1,40 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include +#include "common/common_types.h" + +namespace HW { +namespace AES { + +constexpr size_t CCM_NONCE_SIZE = 12; +constexpr size_t CCM_MAC_SIZE = 16; + +using CCMNonce = std::array; + +/** + * Encrypts and adds a MAC to the given data using AES-CCM algorithm. + * @param pdata The plain text data to encrypt + * @param nonce The nonce data to use for encryption + * @param slot_id The slot ID of the key to use for encryption + * @returns a vector of u8 containing the encrypted data with MAC at the end + */ +std::vector EncryptSignCCM(const std::vector& pdata, const CCMNonce& nonce, size_t slot_id); + +/** + * Decrypts and verify the MAC of the given data using AES-CCM algorithm. + * @param cipher The cipher text data to decrypt, with MAC at the end to verify + * @param nonce The nonce data to use for decryption + * @param slot_id The slot ID of the key to use for decryption + * @returns a vector of u8 containing the decrypted data; an empty vector if the verification fails + */ +std::vector DecryptVerifyCCM(const std::vector& cipher, const CCMNonce& nonce, + size_t slot_id); + +} // namespace AES +} // namespace HW diff --git a/src/core/hw/aes/key.cpp b/src/core/hw/aes/key.cpp new file mode 100644 index 000000000..4e8a8a59a --- /dev/null +++ b/src/core/hw/aes/key.cpp @@ -0,0 +1,173 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include +#include +#include +#include "common/common_paths.h" +#include "common/file_util.h" +#include "common/logging/log.h" +#include "common/string_util.h" +#include "core/hw/aes/arithmetic128.h" +#include "core/hw/aes/key.h" + +namespace HW { +namespace AES { + +namespace { + +boost::optional generator_constant; + +struct KeySlot { + boost::optional x; + boost::optional y; + boost::optional normal; + + void SetKeyX(const AESKey& key) { + x = key; + if (y && generator_constant) { + GenerateNormalKey(); + } + } + + void SetKeyY(const AESKey& key) { + y = key; + if (x && generator_constant) { + GenerateNormalKey(); + } + } + + void SetNormalKey(const AESKey& key) { + normal = key; + } + + void GenerateNormalKey() { + normal = Lrot128(Add128(Xor128(Lrot128(*x, 2), *y), *generator_constant), 87); + } + + void Clear() { + x.reset(); + y.reset(); + normal.reset(); + } +}; + +std::array key_slots; + +void ClearAllKeys() { + for (KeySlot& slot : key_slots) { + slot.Clear(); + } + generator_constant.reset(); +} + +AESKey HexToKey(const std::string& hex) { + if (hex.size() < 32) { + throw std::invalid_argument("hex string is too short"); + } + + AESKey key; + for (size_t i = 0; i < key.size(); ++i) { + key[i] = static_cast(std::stoi(hex.substr(i * 2, 2), 0, 16)); + } + + return key; +} + +void LoadPresetKeys() { + const std::string filepath = FileUtil::GetUserPath(D_SYSDATA_IDX) + AES_KEYS; + FileUtil::CreateFullPath(filepath); // Create path if not already created + std::ifstream file; + OpenFStream(file, filepath, std::ios_base::in); + if (!file) { + return; + } + + while (!file.eof()) { + std::string line; + std::getline(file, line); + std::vector parts; + Common::SplitString(line, '=', parts); + if (parts.size() != 2) { + LOG_ERROR(HW_AES, "Failed to parse %s", line.c_str()); + continue; + } + + const std::string& name = parts[0]; + AESKey key; + try { + key = HexToKey(parts[1]); + } catch (const std::logic_error& e) { + LOG_ERROR(HW_AES, "Invalid key %s: %s", parts[1].c_str(), e.what()); + continue; + } + + if (name == "generator") { + generator_constant = key; + continue; + } + + size_t slot_id; + char key_type; + if (std::sscanf(name.c_str(), "slot0x%zXKey%c", &slot_id, &key_type) != 2) { + LOG_ERROR(HW_AES, "Invalid key name %s", name.c_str()); + continue; + } + + if (slot_id >= MaxKeySlotID) { + LOG_ERROR(HW_AES, "Out of range slot ID 0x%zX", slot_id); + continue; + } + + switch (key_type) { + case 'X': + key_slots.at(slot_id).SetKeyX(key); + break; + case 'Y': + key_slots.at(slot_id).SetKeyY(key); + break; + case 'N': + key_slots.at(slot_id).SetNormalKey(key); + break; + default: + LOG_ERROR(HW_AES, "Invalid key type %c", key_type); + break; + } + } +} + +} // namespace + +void InitKeys() { + ClearAllKeys(); + LoadPresetKeys(); +} + +void SetGeneratorConstant(const AESKey& key) { + generator_constant = key; +} + +void SetKeyX(size_t slot_id, const AESKey& key) { + key_slots.at(slot_id).SetKeyX(key); +} + +void SetKeyY(size_t slot_id, const AESKey& key) { + key_slots.at(slot_id).SetKeyY(key); +} + +void SetNormalKey(size_t slot_id, const AESKey& key) { + key_slots.at(slot_id).SetNormalKey(key); +} + +bool IsNormalKeyAvailable(size_t slot_id) { + return key_slots.at(slot_id).normal.is_initialized(); +} + +AESKey GetNormalKey(size_t slot_id) { + return key_slots.at(slot_id).normal.value_or(AESKey{}); +} + +} // namespace AES +} // namespace HW diff --git a/src/core/hw/aes/key.h b/src/core/hw/aes/key.h new file mode 100644 index 000000000..b01d04f13 --- /dev/null +++ b/src/core/hw/aes/key.h @@ -0,0 +1,35 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include "common/common_types.h" + +namespace HW { +namespace AES { + +enum KeySlotID : size_t { + APTWrap = 0x31, + + MaxKeySlotID = 0x40, +}; + +constexpr size_t AES_BLOCK_SIZE = 16; + +using AESKey = std::array; + +void InitKeys(); + +void SetGeneratorConstant(const AESKey& key); +void SetKeyX(size_t slot_id, const AESKey& key); +void SetKeyY(size_t slot_id, const AESKey& key); +void SetNormalKey(size_t slot_id, const AESKey& key); + +bool IsNormalKeyAvailable(size_t slot_id); +AESKey GetNormalKey(size_t slot_id); + +} // namspace AES +} // namespace HW diff --git a/src/core/hw/hw.cpp b/src/core/hw/hw.cpp index 9ff8825b2..8499f2ce6 100644 --- a/src/core/hw/hw.cpp +++ b/src/core/hw/hw.cpp @@ -4,6 +4,7 @@ #include "common/common_types.h" #include "common/logging/log.h" +#include "core/hw/aes/key.h" #include "core/hw/gpu.h" #include "core/hw/hw.h" #include "core/hw/lcd.h" @@ -85,6 +86,7 @@ void Update() {} /// Initialize hardware void Init() { + AES::InitKeys(); GPU::Init(); LCD::Init(); LOG_DEBUG(HW, "initialized OK");