diff options
author | bunnei <bunneidev@gmail.com> | 2017-02-25 06:12:33 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-02-25 06:12:33 +0100 |
commit | 892888ed9e7c683150493c7c17f567d3c569e62e (patch) | |
tree | fffbfb393fc1da4b6c69a9c0593d2fe8ef9a10a7 | |
parent | Merge pull request #2421 from Subv/timers (diff) | |
parent | externals: remove -march=native for crypto++ (diff) | |
download | yuzu-892888ed9e7c683150493c7c17f567d3c569e62e.tar yuzu-892888ed9e7c683150493c7c17f567d3c569e62e.tar.gz yuzu-892888ed9e7c683150493c7c17f567d3c569e62e.tar.bz2 yuzu-892888ed9e7c683150493c7c17f567d3c569e62e.tar.lz yuzu-892888ed9e7c683150493c7c17f567d3c569e62e.tar.xz yuzu-892888ed9e7c683150493c7c17f567d3c569e62e.tar.zst yuzu-892888ed9e7c683150493c7c17f567d3c569e62e.zip |
-rw-r--r-- | externals/cryptopp/CMakeLists.txt | 9 | ||||
-rw-r--r-- | src/common/common_paths.h | 1 | ||||
-rw-r--r-- | src/common/logging/backend.cpp | 1 | ||||
-rw-r--r-- | src/common/logging/log.h | 1 | ||||
-rw-r--r-- | src/core/CMakeLists.txt | 6 | ||||
-rw-r--r-- | src/core/hle/service/apt/apt.cpp | 103 | ||||
-rw-r--r-- | src/core/hle/service/apt/apt.h | 40 | ||||
-rw-r--r-- | src/core/hle/service/apt/apt_a.cpp | 4 | ||||
-rw-r--r-- | src/core/hle/service/apt/apt_s.cpp | 4 | ||||
-rw-r--r-- | src/core/hle/service/apt/apt_u.cpp | 4 | ||||
-rw-r--r-- | src/core/hw/aes/arithmetic128.cpp | 47 | ||||
-rw-r--r-- | src/core/hw/aes/arithmetic128.h | 17 | ||||
-rw-r--r-- | src/core/hw/aes/ccm.cpp | 95 | ||||
-rw-r--r-- | src/core/hw/aes/ccm.h | 40 | ||||
-rw-r--r-- | src/core/hw/aes/key.cpp | 173 | ||||
-rw-r--r-- | src/core/hw/aes/key.h | 35 | ||||
-rw-r--r-- | src/core/hw/hw.cpp | 2 |
17 files changed, 568 insertions, 14 deletions
diff --git a/externals/cryptopp/CMakeLists.txt b/externals/cryptopp/CMakeLists.txt index bbac71bb9..653af1e4b 100644 --- a/externals/cryptopp/CMakeLists.txt +++ b/externals/cryptopp/CMakeLists.txt @@ -3,6 +3,7 @@ # The differences are: # - removed support for legacy CMake versions # - removed support for 32-bit +# - removed -march=native flag # - removed rdrand module.asm as a workaround for an issue (see below) # - added prefix "CRYPTOPP_" to all option names # - disabled testing @@ -96,14 +97,6 @@ if ((NOT CRYPTOPP_CROSS_COMPILE) AND (NOT (WINDOWS OR WINDOWS_STORE OR WINDOWS_P endif() endif() -# -march=native for GCC, Clang and ICC in any version that does support it. -if ((NOT CRYPTOPP_DISABLE_CXXFLAGS_OPTIMIZATIONS) AND (NOT CRYPTOPP_CROSS_COMPILE) AND (CMAKE_CXX_COMPILER_ID MATCHES "Clang|GNU|Intel")) - CHECK_CXX_COMPILER_FLAG("-march=native" COMPILER_OPT_ARCH_NATIVE_SUPPORTED) - if (COMPILER_OPT_ARCH_NATIVE_SUPPORTED AND NOT CMAKE_CXX_FLAGS MATCHES "-march=") - SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=native") - endif() -endif() - # Link is driven through the compiler, but CXXFLAGS are not used. Also see # http://public.kitware.com/pipermail/cmake/2003-June/003967.html if (NOT (WINDOWS OR WINDOWS_STORE OR WINDOWS_PHONE)) 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 0f0354821..737e1d57f 100644 --- a/src/common/logging/backend.cpp +++ b/src/common/logging/backend.cpp @@ -63,6 +63,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 f0ec922d2..4b0f8ff03 100644 --- a/src/common/logging/log.h +++ b/src/common/logging/log.h @@ -80,6 +80,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 5332e35a3..8334fece9 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 @@ -343,6 +346,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/hle/service/apt/apt.cpp b/src/core/hle/service/apt/apt.cpp index 615fe31ea..e57b19c2d 100644 --- a/src/core/hle/service/apt/apt.cpp +++ b/src/core/hle/service/apt/apt.cpp @@ -18,6 +18,8 @@ #include "core/hle/service/fs/archive.h" #include "core/hle/service/ptm/ptm.h" #include "core/hle/service/service.h" +#include "core/hw/aes/ccm.h" +#include "core/hw/aes/key.h" namespace Service { namespace APT { @@ -470,6 +472,107 @@ void GetStartupArgument(Service::Interface* self) { cmd_buff[2] = 0; } +void Wrap(Service::Interface* self) { + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x46, 4, 4); + const u32 output_size = rp.Pop<u32>(); + const u32 input_size = rp.Pop<u32>(); + const u32 nonce_offset = rp.Pop<u32>(); + u32 nonce_size = rp.Pop<u32>(); + size_t desc_size; + IPC::MappedBufferPermissions desc_permission; + const VAddr input = rp.PopMappedBuffer(&desc_size, &desc_permission); + ASSERT(desc_size == input_size && desc_permission == IPC::MappedBufferPermissions::R); + const VAddr output = rp.PopMappedBuffer(&desc_size, &desc_permission); + ASSERT(desc_size == output_size && desc_permission == IPC::MappedBufferPermissions::W); + + // Note: real 3DS still returns SUCCESS when the sizes don't match. It seems that it doesn't + // check the buffer size and writes data with potential overflow. + ASSERT_MSG(output_size == input_size + HW::AES::CCM_MAC_SIZE, + "input_size (%d) doesn't match to output_size (%d)", input_size, output_size); + + LOG_DEBUG(Service_APT, "called, output_size=%u, input_size=%u, nonce_offset=%u, nonce_size=%u", + output_size, input_size, nonce_offset, nonce_size); + + // Note: This weird nonce size modification is verified against real 3DS + nonce_size = std::min<u32>(nonce_size & ~3, HW::AES::CCM_NONCE_SIZE); + + // Reads nonce and concatenates the rest of the input as plaintext + HW::AES::CCMNonce nonce{}; + Memory::ReadBlock(input + nonce_offset, nonce.data(), nonce_size); + u32 pdata_size = input_size - nonce_size; + std::vector<u8> pdata(pdata_size); + Memory::ReadBlock(input, pdata.data(), nonce_offset); + Memory::ReadBlock(input + nonce_offset + nonce_size, pdata.data() + nonce_offset, + pdata_size - nonce_offset); + + // Encrypts the plaintext using AES-CCM + auto cipher = HW::AES::EncryptSignCCM(pdata, nonce, HW::AES::KeySlotID::APTWrap); + + // Puts the nonce to the beginning of the output, with ciphertext followed + Memory::WriteBlock(output, nonce.data(), nonce_size); + Memory::WriteBlock(output + nonce_size, cipher.data(), cipher.size()); + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 4); + rb.Push(RESULT_SUCCESS); + + // Unmap buffer + rb.PushMappedBuffer(input, input_size, IPC::MappedBufferPermissions::R); + rb.PushMappedBuffer(output, output_size, IPC::MappedBufferPermissions::W); +} + +void Unwrap(Service::Interface* self) { + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x47, 4, 4); + const u32 output_size = rp.Pop<u32>(); + const u32 input_size = rp.Pop<u32>(); + const u32 nonce_offset = rp.Pop<u32>(); + u32 nonce_size = rp.Pop<u32>(); + size_t desc_size; + IPC::MappedBufferPermissions desc_permission; + const VAddr input = rp.PopMappedBuffer(&desc_size, &desc_permission); + ASSERT(desc_size == input_size && desc_permission == IPC::MappedBufferPermissions::R); + const VAddr output = rp.PopMappedBuffer(&desc_size, &desc_permission); + ASSERT(desc_size == output_size && desc_permission == IPC::MappedBufferPermissions::W); + + // Note: real 3DS still returns SUCCESS when the sizes don't match. It seems that it doesn't + // check the buffer size and writes data with potential overflow. + ASSERT_MSG(output_size == input_size - HW::AES::CCM_MAC_SIZE, + "input_size (%d) doesn't match to output_size (%d)", input_size, output_size); + + LOG_DEBUG(Service_APT, "called, output_size=%u, input_size=%u, nonce_offset=%u, nonce_size=%u", + output_size, input_size, nonce_offset, nonce_size); + + // Note: This weird nonce size modification is verified against real 3DS + nonce_size = std::min<u32>(nonce_size & ~3, HW::AES::CCM_NONCE_SIZE); + + // Reads nonce and cipher text + HW::AES::CCMNonce nonce{}; + Memory::ReadBlock(input, nonce.data(), nonce_size); + u32 cipher_size = input_size - nonce_size; + std::vector<u8> cipher(cipher_size); + Memory::ReadBlock(input + nonce_size, cipher.data(), cipher_size); + + // Decrypts the ciphertext using AES-CCM + auto pdata = HW::AES::DecryptVerifyCCM(cipher, nonce, HW::AES::KeySlotID::APTWrap); + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + if (!pdata.empty()) { + // Splits the plaintext and put the nonce in between + Memory::WriteBlock(output, pdata.data(), nonce_offset); + Memory::WriteBlock(output + nonce_offset, nonce.data(), nonce_size); + Memory::WriteBlock(output + nonce_offset + nonce_size, pdata.data() + nonce_offset, + pdata.size() - nonce_offset); + rb.Push(RESULT_SUCCESS); + } else { + LOG_ERROR(Service_APT, "Failed to decrypt data"); + rb.Push(ResultCode(static_cast<ErrorDescription>(1), ErrorModule::PS, + ErrorSummary::WrongArgument, ErrorLevel::Status)); + } + + // Unmap buffer + rb.PushMappedBuffer(input, input_size, IPC::MappedBufferPermissions::R); + rb.PushMappedBuffer(output, output_size, IPC::MappedBufferPermissions::W); +} + void CheckNew3DSApp(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); diff --git a/src/core/hle/service/apt/apt.h b/src/core/hle/service/apt/apt.h index 80325361f..e63b61450 100644 --- a/src/core/hle/service/apt/apt.h +++ b/src/core/hle/service/apt/apt.h @@ -137,6 +137,46 @@ void Initialize(Service::Interface* self); void GetSharedFont(Service::Interface* self); /** + * APT::Wrap service function + * Inputs: + * 1 : Output buffer size + * 2 : Input buffer size + * 3 : Nonce offset to the input buffer + * 4 : Nonce size + * 5 : Buffer mapping descriptor ((input_buffer_size << 4) | 0xA) + * 6 : Input buffer address + * 7 : Buffer mapping descriptor ((input_buffer_size << 4) | 0xC) + * 8 : Output buffer address + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + * 2 : Buffer unmapping descriptor ((input_buffer_size << 4) | 0xA) + * 3 : Input buffer address + * 4 : Buffer unmapping descriptor ((input_buffer_size << 4) | 0xC) + * 5 : Output buffer address + */ +void Wrap(Service::Interface* self); + +/** + * APT::Unwrap service function + * Inputs: + * 1 : Output buffer size + * 2 : Input buffer size + * 3 : Nonce offset to the output buffer + * 4 : Nonce size + * 5 : Buffer mapping descriptor ((input_buffer_size << 4) | 0xA) + * 6 : Input buffer address + * 7 : Buffer mapping descriptor ((input_buffer_size << 4) | 0xC) + * 8 : Output buffer address + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + * 2 : Buffer unmapping descriptor ((input_buffer_size << 4) | 0xA) + * 3 : Input buffer address + * 4 : Buffer unmapping descriptor ((input_buffer_size << 4) | 0xC) + * 5 : Output buffer address + */ +void Unwrap(Service::Interface* self); + +/** * APT::NotifyToWait service function * Inputs: * 1 : AppID diff --git a/src/core/hle/service/apt/apt_a.cpp b/src/core/hle/service/apt/apt_a.cpp index 62dc2d61d..c496cba8d 100644 --- a/src/core/hle/service/apt/apt_a.cpp +++ b/src/core/hle/service/apt/apt_a.cpp @@ -78,8 +78,8 @@ const Interface::FunctionInfo FunctionTable[] = { {0x00430040, NotifyToWait, "NotifyToWait"}, {0x00440000, GetSharedFont, "GetSharedFont"}, {0x00450040, nullptr, "GetWirelessRebootInfo"}, - {0x00460104, nullptr, "Wrap"}, - {0x00470104, nullptr, "Unwrap"}, + {0x00460104, Wrap, "Wrap"}, + {0x00470104, Unwrap, "Unwrap"}, {0x00480100, nullptr, "GetProgramInfo"}, {0x00490180, nullptr, "Reboot"}, {0x004A0040, nullptr, "GetCaptureInfo"}, diff --git a/src/core/hle/service/apt/apt_s.cpp b/src/core/hle/service/apt/apt_s.cpp index effd23dce..ec5668d05 100644 --- a/src/core/hle/service/apt/apt_s.cpp +++ b/src/core/hle/service/apt/apt_s.cpp @@ -78,8 +78,8 @@ const Interface::FunctionInfo FunctionTable[] = { {0x00430040, NotifyToWait, "NotifyToWait"}, {0x00440000, GetSharedFont, "GetSharedFont"}, {0x00450040, nullptr, "GetWirelessRebootInfo"}, - {0x00460104, nullptr, "Wrap"}, - {0x00470104, nullptr, "Unwrap"}, + {0x00460104, Wrap, "Wrap"}, + {0x00470104, Unwrap, "Unwrap"}, {0x00480100, nullptr, "GetProgramInfo"}, {0x00490180, nullptr, "Reboot"}, {0x004A0040, nullptr, "GetCaptureInfo"}, diff --git a/src/core/hle/service/apt/apt_u.cpp b/src/core/hle/service/apt/apt_u.cpp index e06084a1e..9dd002590 100644 --- a/src/core/hle/service/apt/apt_u.cpp +++ b/src/core/hle/service/apt/apt_u.cpp @@ -78,8 +78,8 @@ const Interface::FunctionInfo FunctionTable[] = { {0x00430040, NotifyToWait, "NotifyToWait"}, {0x00440000, GetSharedFont, "GetSharedFont"}, {0x00450040, nullptr, "GetWirelessRebootInfo"}, - {0x00460104, nullptr, "Wrap"}, - {0x00470104, nullptr, "Unwrap"}, + {0x00460104, Wrap, "Wrap"}, + {0x00470104, Unwrap, "Unwrap"}, {0x00480100, nullptr, "GetProgramInfo"}, {0x00490180, nullptr, "Reboot"}, {0x004A0040, nullptr, "GetCaptureInfo"}, 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 <algorithm> +#include <functional> +#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<u8>(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 <algorithm> +#include <cryptopp/aes.h> +#include <cryptopp/ccm.h> +#include <cryptopp/cryptlib.h> +#include <cryptopp/filters.h> +#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 <bool T_IsEncryption> +class CCM_3DSVariant_Final : public CCM_Final<AES, CCM_MAC_SIZE, T_IsEncryption> { +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<true>; + using Decryption = CCM_3DSVariant_Final<false>; +}; + +} // namespace + +std::vector<u8> EncryptSignCCM(const std::vector<u8>& 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<u8> 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<u8> DecryptVerifyCCM(const std::vector<u8>& 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<u8> 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 <array> +#include <cstddef> +#include <vector> +#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<u8, CCM_NONCE_SIZE>; + +/** + * 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<u8> EncryptSignCCM(const std::vector<u8>& 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<u8> DecryptVerifyCCM(const std::vector<u8>& 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 <algorithm> +#include <exception> +#include <sstream> +#include <boost/optional.hpp> +#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<AESKey> generator_constant; + +struct KeySlot { + boost::optional<AESKey> x; + boost::optional<AESKey> y; + boost::optional<AESKey> 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<KeySlot, KeySlotID::MaxKeySlotID> 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<u8>(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<std::string> 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 <array> +#include <cstddef> +#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<u8, AES_BLOCK_SIZE>; + +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"); |