From e0dbfc69a8e25ad7b75ff51537f1da4101db1d44 Mon Sep 17 00:00:00 2001 From: Michael Scire Date: Fri, 15 May 2020 14:58:45 -0700 Subject: [PATCH] exo2: implement SmcComputeAes, SmcGetResult, SmcGetResultData --- exosphere2/program/source/secmon_map.cpp | 54 +++++++ exosphere2/program/source/secmon_map.hpp | 3 + exosphere2/program/source/secmon_setup.cpp | 4 + .../program/source/smc/secmon_smc_aes.cpp | 85 ++++++++++- .../program/source/smc/secmon_smc_result.cpp | 82 ++++++++++- .../source/smc/secmon_user_page_mapper.cpp | 61 ++++++++ .../source/smc/secmon_user_page_mapper.hpp | 35 +++++ .../libexosphere/include/exosphere/gic.hpp | 2 + .../include/exosphere/se/se_aes.hpp | 4 + .../include/exosphere/se/se_management.hpp | 2 + .../exosphere/secmon/secmon_memory_layout.hpp | 1 + libraries/libexosphere/source/gic/gic_api.cpp | 4 + libraries/libexosphere/source/se/se_aes.cpp | 137 +++++++++++++++--- .../libexosphere/source/se/se_execute.cpp | 25 +++- .../libexosphere/source/se/se_execute.hpp | 3 + .../libexosphere/source/se/se_management.cpp | 8 + 16 files changed, 486 insertions(+), 24 deletions(-) create mode 100644 exosphere2/program/source/smc/secmon_user_page_mapper.cpp create mode 100644 exosphere2/program/source/smc/secmon_user_page_mapper.hpp diff --git a/exosphere2/program/source/secmon_map.cpp b/exosphere2/program/source/secmon_map.cpp index f3bd02792..f41d73ad1 100644 --- a/exosphere2/program/source/secmon_map.cpp +++ b/exosphere2/program/source/secmon_map.cpp @@ -15,6 +15,7 @@ */ #include #include "secmon_cache.hpp" +#include "secmon_setup.hpp" #include "secmon_map.hpp" namespace ams::secmon { @@ -24,8 +25,12 @@ namespace ams::secmon { constexpr inline const uintptr_t BootCodeAddress = MemoryRegionVirtualTzramBootCode.GetAddress(); constexpr inline const size_t BootCodeSize = MemoryRegionVirtualTzramBootCode.GetSize(); + constinit uintptr_t g_smc_user_page_physical_address = 0; + using namespace ams::mmu; + constexpr inline PageTableMappingAttribute MappingAttributesEl3NonSecureRwData = AddMappingAttributeIndex(PageTableMappingAttributes_El3NonSecureRwData, MemoryAttributeIndexNormal); + constexpr void UnmapBootCodeImpl(u64 *l1, u64 *l2, u64 *l3, uintptr_t boot_code, size_t boot_code_size) { /* Unmap the L3 entries corresponding to the boot code. */ InvalidateL3Entries(l3, boot_code, boot_code_size); @@ -42,6 +47,16 @@ namespace ams::secmon { InvalidateL1Entries(l1, MemoryRegionPhysical.GetAddress(), MemoryRegionPhysical.GetSize()); } + constexpr void MapSmcUserPageImpl(u64 *l3, uintptr_t address) { + /* Set the L3 entry. */ + SetL3BlockEntry(l3, MemoryRegionVirtualSmcUserPage.GetAddress(), address, MemoryRegionVirtualSmcUserPage.GetSize(), MappingAttributesEl3NonSecureRwData); + } + + constexpr void UnmapSmcUserPageImpl(u64 *l3) { + /* Unmap the L3 entry. */ + InvalidateL3Entries(l3, MemoryRegionVirtualSmcUserPage.GetAddress(), MemoryRegionVirtualSmcUserPage.GetSize()); + } + void ClearLow(uintptr_t address, size_t size) { /* Clear the low part. */ util::ClearMemory(reinterpret_cast(address), size / 2); @@ -85,4 +100,43 @@ namespace ams::secmon { secmon::EnsureMappingConsistency(); } + uintptr_t MapSmcUserPage(uintptr_t address) { + if (g_smc_user_page_physical_address != 0) { + if (!(MemoryRegionDram.GetAddress() <= address && address <= MemoryRegionDramHigh.GetEndAddress() - MemoryRegionVirtualSmcUserPage.GetSize())) { + return 0; + } + if (!util::IsAligned(address, 4_KB)) { + return 0; + } + + g_smc_user_page_physical_address = address; + + u64 * const l2_l3 = MemoryRegionVirtualTzramL2L3PageTable.GetPointer(); + + MapSmcUserPageImpl(l2_l3, address); + + /* Ensure the mappings are consistent. */ + secmon::EnsureMappingConsistency(MemoryRegionVirtualSmcUserPage.GetAddress()); + } else { + AMS_ABORT_UNLESS(address == g_smc_user_page_physical_address); + } + + return MemoryRegionVirtualSmcUserPage.GetAddress(); + } + + void UnmapSmcUserPage() { + if (g_smc_user_page_physical_address == 0) { + return; + } + + u64 * const l2_l3 = MemoryRegionVirtualTzramL2L3PageTable.GetPointer(); + + UnmapSmcUserPageImpl(l2_l3); + + /* Ensure the mappings are consistent. */ + secmon::EnsureMappingConsistency(MemoryRegionVirtualSmcUserPage.GetAddress()); + + g_smc_user_page_physical_address = 0; + } + } diff --git a/exosphere2/program/source/secmon_map.hpp b/exosphere2/program/source/secmon_map.hpp index aa8c7f32e..2d74d4a8e 100644 --- a/exosphere2/program/source/secmon_map.hpp +++ b/exosphere2/program/source/secmon_map.hpp @@ -20,4 +20,7 @@ namespace ams::secmon { void UnmapTzram(); + uintptr_t MapSmcUserPage(uintptr_t address); + void UnmapSmcUserPage(); + } \ No newline at end of file diff --git a/exosphere2/program/source/secmon_setup.cpp b/exosphere2/program/source/secmon_setup.cpp index da611c9bd..6748022bf 100644 --- a/exosphere2/program/source/secmon_setup.cpp +++ b/exosphere2/program/source/secmon_setup.cpp @@ -20,6 +20,7 @@ #include "secmon_cpu_context.hpp" #include "secmon_interrupt_handler.hpp" #include "secmon_misc.hpp" +#include "smc/secmon_random_cache.hpp" #include "smc/secmon_smc_power_management.hpp" #include "smc/secmon_smc_se_lock.hpp" @@ -938,6 +939,9 @@ namespace ams::secmon { ExitChargerHiZMode(); } + /* Refill the random cache, which is volatile and thus wiped on warmboot. */ + smc::FillRandomCache(); + /* Unlock the security engine. */ secmon::smc::UnlockSecurityEngine(); } diff --git a/exosphere2/program/source/smc/secmon_smc_aes.cpp b/exosphere2/program/source/smc/secmon_smc_aes.cpp index 3ca701edb..c004091ef 100644 --- a/exosphere2/program/source/smc/secmon_smc_aes.cpp +++ b/exosphere2/program/source/smc/secmon_smc_aes.cpp @@ -47,6 +47,13 @@ namespace ams::secmon::smc { KeyType_Count, }; + enum CipherMode { + CipherMode_CbcEncryption = 0, + CipherMode_CbcDecryption = 1, + CipherMode_Ctr = 2, + CipherMode_Cmac = 3, + }; + struct GenerateAesKekOption { using IsDeviceUnique = util::BitPack32::Field<0, 1, bool>; using KeyTypeIndex = util::BitPack32::Field<1, 4, KeyType>; @@ -54,6 +61,11 @@ namespace ams::secmon::smc { using Reserved = util::BitPack32::Field<8, 24, u32>; }; + struct ComputeAesOption { + using KeySlot = util::BitPack32::Field<0, 3, int>; + using CipherModeIndex = util::BitPack32::Field<4, 2, CipherMode>; + }; + constexpr const u8 SealKeySources[SealKey_Count][AesKeySize] = { [SealKey_LoadAesKey] = { 0xF4, 0x0C, 0x16, 0x26, 0x0D, 0x46, 0x3B, 0xE0, 0x8C, 0x6A, 0x56, 0xE5, 0x82, 0xD4, 0x1B, 0xF6 }, [SealKey_DecryptDeviceUniqueData] = { 0x7F, 0x54, 0x2C, 0x98, 0x1E, 0x54, 0x18, 0x3B, 0xBA, 0x63, 0xBD, 0x4C, 0x13, 0x5B, 0xF1, 0x06 }, @@ -81,6 +93,40 @@ namespace ams::secmon::smc { [SealKey_LoadEsClientCertKey] = { 0x89, 0x96, 0x43, 0x9A, 0x7C, 0xD5, 0x59, 0x55, 0x24, 0xD5, 0x24, 0x18, 0xAB, 0x6C, 0x04, 0x61 }, }; + constexpr uintptr_t LinkedListAddressMinimum = secmon::MemoryRegionDram.GetAddress(); + constexpr size_t LinkedListAddressRangeSize = 4_MB - 2_KB; + constexpr uintptr_t LinkedListAddressMaximum = LinkedListAddressMinimum + LinkedListAddressRangeSize; + + constexpr size_t LinkedListSize = 12; + + constexpr bool IsValidLinkedListAddress(uintptr_t address) { + return LinkedListAddressMinimum <= address && address <= (LinkedListAddressMaximum - LinkedListSize); + } + + constinit bool g_is_compute_aes_completed = false; + + void SecurityEngineDoneHandler() { + /* Check that the compute succeeded. */ + se::ValidateAesOperationResult(); + + /* End the asynchronous operation. */ + g_is_compute_aes_completed = true; + EndAsyncOperation(); + } + + SmcResult GetComputeAesResult(void *dst, size_t size) { + /* Arguments are unused. */ + AMS_UNUSED(dst); + AMS_UNUSED(size); + + /* Check that the operation is completed. */ + SMC_R_UNLESS(g_is_compute_aes_completed, Busy); + + /* Unlock the security engine and succeed. */ + UnlockSecurityEngine(); + return SmcResult::Success; + } + int PrepareMasterKey(int generation) { if (generation == GetKeyGeneration()) { return pkg1::AesKeySlot_Master; @@ -199,6 +245,42 @@ namespace ams::secmon::smc { return SmcResult::Success; } + SmcResult ComputeAesImpl(SmcArguments &args) { + /* Decode arguments. */ + u8 iv[se::AesBlockSize]; + + const util::BitPack32 option = { static_cast(args.r[1]) }; + std::memcpy(iv, std::addressof(args.r[2]), sizeof(iv)); + const u32 input_address = args.r[4]; + const u32 output_address = args.r[5]; + const u32 size = args.r[6]; + + const int slot = option.Get(); + const auto cipher_mode = option.Get(); + + /* Validate arguments. */ + SMC_R_UNLESS(pkg1::IsUserAesKeySlot(slot), InvalidArgument); + SMC_R_UNLESS(util::IsAligned(size, se::AesBlockSize), InvalidArgument); + SMC_R_UNLESS(IsValidLinkedListAddress(input_address), InvalidArgument); + SMC_R_UNLESS(IsValidLinkedListAddress(output_address), InvalidArgument); + + /* We're starting an aes operation, so reset the completion status. */ + g_is_compute_aes_completed = false; + + /* Dispatch the correct aes operation asynchronously. */ + switch (cipher_mode) { + case CipherMode_CbcEncryption: se::EncryptAes128CbcAsync(output_address, slot, input_address, size, iv, sizeof(iv), SecurityEngineDoneHandler); break; + case CipherMode_CbcDecryption: se::DecryptAes128CbcAsync(output_address, slot, input_address, size, iv, sizeof(iv), SecurityEngineDoneHandler); break; + case CipherMode_Ctr: se::ComputeAes128CtrAsync(output_address, slot, input_address, size, iv, sizeof(iv), SecurityEngineDoneHandler); break; + case CipherMode_Cmac: + return SmcResult::NotImplemented; + default: + return SmcResult::InvalidArgument; + } + + return SmcResult::Success; + } + } SmcResult SmcGenerateAesKek(SmcArguments &args) { @@ -210,8 +292,7 @@ namespace ams::secmon::smc { } SmcResult SmcComputeAes(SmcArguments &args) { - /* TODO */ - return SmcResult::NotImplemented; + return LockSecurityEngineAndInvokeAsync(args, ComputeAesImpl, GetComputeAesResult); } SmcResult SmcGenerateSpecificAesKey(SmcArguments &args) { diff --git a/exosphere2/program/source/smc/secmon_smc_result.cpp b/exosphere2/program/source/smc/secmon_smc_result.cpp index 5223ceca5..702f12ff2 100644 --- a/exosphere2/program/source/smc/secmon_smc_result.cpp +++ b/exosphere2/program/source/smc/secmon_smc_result.cpp @@ -16,17 +16,91 @@ #include #include "../secmon_error.hpp" #include "secmon_smc_result.hpp" +#include "secmon_user_page_mapper.hpp" namespace ams::secmon::smc { + namespace { + + constinit u64 g_async_key = InvalidAsyncKey; + constinit GetResultHandler g_async_handler = nullptr; + + u64 GenerateRandomU64() { + /* NOTE: This is one of the only places where Nintendo does not do data flushing. */ + /* to ensure coherency when doing random byte generation. */ + /* It is not clear why it is necessary elsewhere but not here. */ + /* TODO: Figure out why. */ + u64 v; + se::GenerateRandomBytes(std::addressof(v), sizeof(v)); + return v; + } + + } + + u64 BeginAsyncOperation(GetResultHandler handler) { + /* Only allow one async operation at a time. */ + if (g_async_key != InvalidAsyncKey) { + return InvalidAsyncKey; + } + + /* Generate a random async key. */ + g_async_key = GenerateRandomU64(); + g_async_handler = handler; + + return g_async_key; + } + + void CancelAsyncOperation(u64 async_key) { + if (async_key == g_async_key) { + g_async_key = InvalidAsyncKey; + } + } + + void EndAsyncOperation() { + gic::SetPending(SecurityEngineUserInterruptId); + } + SmcResult SmcGetResult(SmcArguments &args) { - /* TODO */ - return SmcResult::NotImplemented; + /* Decode arguments. */ + const u64 async_key = args.r[1]; + + /* Validate arguments. */ + SMC_R_UNLESS(g_async_key != InvalidAsyncKey, NoAsyncOperation); + SMC_R_UNLESS(g_async_key == async_key, InvalidAsyncOperation); + + /* Call the handler. */ + args.r[1] = static_cast(g_async_handler(nullptr, 0)); + g_async_key = InvalidAsyncKey; + + return SmcResult::Success; } SmcResult SmcGetResultData(SmcArguments &args) { - /* TODO */ - return SmcResult::NotImplemented; + /* Decode arguments. */ + const u64 async_key = args.r[1]; + const uintptr_t user_phys_addr = args.r[2]; + const size_t user_size = args.r[3]; + + /* Allocate a work buffer on the stack. */ + alignas(8) u8 work_buffer[1_KB]; + + /* Validate arguments. */ + SMC_R_UNLESS(g_async_key != InvalidAsyncKey, NoAsyncOperation); + SMC_R_UNLESS(g_async_key == async_key, InvalidAsyncOperation); + SMC_R_UNLESS(user_size <= sizeof(work_buffer), InvalidArgument); + + /* Call the handler. */ + args.r[1] = static_cast(g_async_handler(work_buffer, user_size)); + g_async_key = InvalidAsyncKey; + + /* Map the user buffer. */ + { + UserPageMapper mapper(user_phys_addr); + SMC_R_UNLESS(mapper.Map(), InvalidArgument); + SMC_R_UNLESS(mapper.CopyToUser(user_phys_addr, work_buffer, user_size), InvalidArgument); + } + + return SmcResult::Success; } } diff --git a/exosphere2/program/source/smc/secmon_user_page_mapper.cpp b/exosphere2/program/source/smc/secmon_user_page_mapper.cpp new file mode 100644 index 000000000..3748592db --- /dev/null +++ b/exosphere2/program/source/smc/secmon_user_page_mapper.cpp @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include +#include "../secmon_map.hpp" +#include "secmon_user_page_mapper.hpp" + +namespace ams::secmon::smc { + + bool UserPageMapper::Map() { + this->virtual_address = MapSmcUserPage(this->physical_address); + return this->virtual_address != 0; + } + + void *UserPageMapper::GetPointerTo(uintptr_t phys, size_t size) const { + /* Ensure we stay within the page. */ + if (util::AlignDown(phys, 4_KB) != this->physical_address) { + return nullptr; + } + if (size != 0) { + if (util::AlignDown(phys + size - 1, 4_KB) != this->physical_address) { + return nullptr; + } + } + + return reinterpret_cast(phys + (this->virtual_address - this->physical_address)); + } + + bool UserPageMapper::CopyToUser(uintptr_t dst_phys, const void *src, size_t size) const { + void * const dst = this->GetPointerTo(dst_phys, size); + if (dst == nullptr) { + return false; + } + + std::memcpy(dst, src, size); + return true; + } + + bool UserPageMapper::CopyFromUser(void *dst, uintptr_t src_phys, size_t size) const { + const void * const src = this->GetPointerTo(src_phys, size); + if (src == nullptr) { + return false; + } + + std::memcpy(dst, src, size); + return true; + } + +} diff --git a/exosphere2/program/source/smc/secmon_user_page_mapper.hpp b/exosphere2/program/source/smc/secmon_user_page_mapper.hpp new file mode 100644 index 000000000..cec6eb96d --- /dev/null +++ b/exosphere2/program/source/smc/secmon_user_page_mapper.hpp @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include +#include "secmon_smc_common.hpp" + +namespace ams::secmon::smc { + + class UserPageMapper { + private: + uintptr_t physical_address; + uintptr_t virtual_address; + public: + constexpr UserPageMapper(uintptr_t phys) : physical_address(util::AlignDown(phys, 4_KB)), virtual_address() { /* ... */ } + + bool Map(); + void *GetPointerTo(uintptr_t phys, size_t size) const; + bool CopyToUser(uintptr_t dst_phys, const void *src, size_t size) const; + bool CopyFromUser(void *dst, uintptr_t src_phys, size_t size) const; + }; + +} diff --git a/libraries/libexosphere/include/exosphere/gic.hpp b/libraries/libexosphere/include/exosphere/gic.hpp index 72e30432c..4b866d4e4 100644 --- a/libraries/libexosphere/include/exosphere/gic.hpp +++ b/libraries/libexosphere/include/exosphere/gic.hpp @@ -36,6 +36,8 @@ namespace ams::gic { void SetSpiTargetCpu(int interrupt_id, u32 cpu_mask); void SetSpiMode(int interrupt_id, InterruptMode mode); + void SetPending(int interrupt_id); + int GetInterruptRequestId(); void SetEndOfInterrupt(int interrupt_id); diff --git a/libraries/libexosphere/include/exosphere/se/se_aes.hpp b/libraries/libexosphere/include/exosphere/se/se_aes.hpp index a9bf3e67f..b78820416 100644 --- a/libraries/libexosphere/include/exosphere/se/se_aes.hpp +++ b/libraries/libexosphere/include/exosphere/se/se_aes.hpp @@ -35,4 +35,8 @@ namespace ams::se { void ComputeAes128Ctr(void *dst, size_t dst_size, int slot, const void *src, size_t src_size, const void *iv, size_t iv_size); + void EncryptAes128CbcAsync(u32 out_ll_address, int slot, u32 in_ll_address, u32 size, const void *iv, size_t iv_size, DoneHandler handler); + void DecryptAes128CbcAsync(u32 out_ll_address, int slot, u32 in_ll_address, u32 size, const void *iv, size_t iv_size, DoneHandler handler); + void ComputeAes128CtrAsync(u32 out_ll_address, int slot, u32 in_ll_address, u32 size, const void *iv, size_t iv_size, DoneHandler handler); + } diff --git a/libraries/libexosphere/include/exosphere/se/se_management.hpp b/libraries/libexosphere/include/exosphere/se/se_management.hpp index f5a8e6bea..05369d1fa 100644 --- a/libraries/libexosphere/include/exosphere/se/se_management.hpp +++ b/libraries/libexosphere/include/exosphere/se/se_management.hpp @@ -30,4 +30,6 @@ namespace ams::se { void HandleInterrupt(); + void ValidateAesOperationResult(); + } \ No newline at end of file diff --git a/libraries/libexosphere/include/exosphere/secmon/secmon_memory_layout.hpp b/libraries/libexosphere/include/exosphere/secmon/secmon_memory_layout.hpp index a7818c11c..38e6452bf 100644 --- a/libraries/libexosphere/include/exosphere/secmon/secmon_memory_layout.hpp +++ b/libraries/libexosphere/include/exosphere/secmon/secmon_memory_layout.hpp @@ -73,6 +73,7 @@ namespace ams::secmon { constexpr inline const MemoryRegion MemoryRegionVirtual = MemoryRegion(UINT64_C(0x1F0000000), 2_MB); constexpr inline const MemoryRegion MemoryRegionPhysical = MemoryRegion(UINT64_C( 0x40000000), 1_GB); constexpr inline const MemoryRegion MemoryRegionDram = MemoryRegion(UINT64_C( 0x80000000), 2_GB); + constexpr inline const MemoryRegion MemoryRegionDramHigh = MemoryRegion(MemoryRegionDram.GetEndAddress(), 2_GB); constexpr inline const MemoryRegion MemoryRegionDramGpuCarveout = MemoryRegion(UINT64_C(0x80020000), UINT64_C(0x40000)); static_assert(MemoryRegionDram.Contains(MemoryRegionDramGpuCarveout)); diff --git a/libraries/libexosphere/source/gic/gic_api.cpp b/libraries/libexosphere/source/gic/gic_api.cpp index 70e36002c..220713e79 100644 --- a/libraries/libexosphere/source/gic/gic_api.cpp +++ b/libraries/libexosphere/source/gic/gic_api.cpp @@ -206,6 +206,10 @@ namespace ams::gic { ReadWrite(g_distributor_address + offsetof(GicDistributor, icfgr), 2, interrupt_id, static_cast(mode) << 1); } + void SetPending(int interrupt_id) { + Write(g_distributor_address + offsetof(GicDistributor, ispendr), 1, interrupt_id, 1); + } + int GetInterruptRequestId() { return reg::Read(GetCpuInterface()->iar); } diff --git a/libraries/libexosphere/source/se/se_aes.cpp b/libraries/libexosphere/source/se/se_aes.cpp index 138e18cfb..ed0ec0e91 100644 --- a/libraries/libexosphere/source/se/se_aes.cpp +++ b/libraries/libexosphere/source/se/se_aes.cpp @@ -33,21 +33,37 @@ namespace ams::se { MemoryInterface_Mc = SE_CRYPTO_CONFIG_MEMIF_MCCIF, }; - constexpr inline u32 AesConfigEcb = reg::Encode(SE_REG_BITS_VALUE(CRYPTO_CONFIG_CTR_CNTN, 0), - SE_REG_BITS_ENUM (CRYPTO_CONFIG_KEYSCH_BYPASS, DISABLE), - SE_REG_BITS_ENUM (CRYPTO_CONFIG_IV_SELECT, ORIGINAL), - SE_REG_BITS_ENUM (CRYPTO_CONFIG_VCTRAM_SEL, MEMORY), - SE_REG_BITS_ENUM (CRYPTO_CONFIG_INPUT_SEL, MEMORY), - SE_REG_BITS_ENUM (CRYPTO_CONFIG_XOR_POS, BYPASS), - SE_REG_BITS_ENUM (CRYPTO_CONFIG_HASH_ENB, DISABLE)); + constexpr inline u32 AesConfigEcb = reg::Encode(SE_REG_BITS_VALUE(CRYPTO_CONFIG_CTR_CNTN, 0), + SE_REG_BITS_ENUM (CRYPTO_CONFIG_KEYSCH_BYPASS, DISABLE), + SE_REG_BITS_ENUM (CRYPTO_CONFIG_IV_SELECT, ORIGINAL), + SE_REG_BITS_ENUM (CRYPTO_CONFIG_VCTRAM_SEL, MEMORY), + SE_REG_BITS_ENUM (CRYPTO_CONFIG_INPUT_SEL, MEMORY), + SE_REG_BITS_ENUM (CRYPTO_CONFIG_XOR_POS, BYPASS), + SE_REG_BITS_ENUM (CRYPTO_CONFIG_HASH_ENB, DISABLE)); - constexpr inline u32 AesConfigCtr = reg::Encode(SE_REG_BITS_VALUE(CRYPTO_CONFIG_CTR_CNTN, 1), - SE_REG_BITS_ENUM (CRYPTO_CONFIG_KEYSCH_BYPASS, DISABLE), - SE_REG_BITS_ENUM (CRYPTO_CONFIG_IV_SELECT, ORIGINAL), - SE_REG_BITS_ENUM (CRYPTO_CONFIG_VCTRAM_SEL, MEMORY), - SE_REG_BITS_ENUM (CRYPTO_CONFIG_INPUT_SEL, LINEAR_CTR), - SE_REG_BITS_ENUM (CRYPTO_CONFIG_XOR_POS, BOTTOM), - SE_REG_BITS_ENUM (CRYPTO_CONFIG_HASH_ENB, DISABLE)); + constexpr inline u32 AesConfigCtr = reg::Encode(SE_REG_BITS_VALUE(CRYPTO_CONFIG_CTR_CNTN, 1), + SE_REG_BITS_ENUM (CRYPTO_CONFIG_KEYSCH_BYPASS, DISABLE), + SE_REG_BITS_ENUM (CRYPTO_CONFIG_IV_SELECT, ORIGINAL), + SE_REG_BITS_ENUM (CRYPTO_CONFIG_VCTRAM_SEL, MEMORY), + SE_REG_BITS_ENUM (CRYPTO_CONFIG_INPUT_SEL, LINEAR_CTR), + SE_REG_BITS_ENUM (CRYPTO_CONFIG_XOR_POS, BOTTOM), + SE_REG_BITS_ENUM (CRYPTO_CONFIG_HASH_ENB, DISABLE)); + + constexpr inline u32 AesConfigCbcEncrypt = reg::Encode(SE_REG_BITS_VALUE(CRYPTO_CONFIG_CTR_CNTN, 0), + SE_REG_BITS_ENUM (CRYPTO_CONFIG_KEYSCH_BYPASS, DISABLE), + SE_REG_BITS_ENUM (CRYPTO_CONFIG_IV_SELECT, ORIGINAL), + SE_REG_BITS_ENUM (CRYPTO_CONFIG_VCTRAM_SEL, INIT_AESOUT), + SE_REG_BITS_ENUM (CRYPTO_CONFIG_INPUT_SEL, MEMORY), + SE_REG_BITS_ENUM (CRYPTO_CONFIG_XOR_POS, TOP), + SE_REG_BITS_ENUM (CRYPTO_CONFIG_HASH_ENB, DISABLE)); + + constexpr inline u32 AesConfigCbcDecrypt = reg::Encode(SE_REG_BITS_VALUE(CRYPTO_CONFIG_CTR_CNTN, 0), + SE_REG_BITS_ENUM (CRYPTO_CONFIG_KEYSCH_BYPASS, DISABLE), + SE_REG_BITS_ENUM (CRYPTO_CONFIG_IV_SELECT, ORIGINAL), + SE_REG_BITS_ENUM (CRYPTO_CONFIG_VCTRAM_SEL, INIT_PREV_MEMORY), + SE_REG_BITS_ENUM (CRYPTO_CONFIG_INPUT_SEL, MEMORY), + SE_REG_BITS_ENUM (CRYPTO_CONFIG_XOR_POS, BOTTOM), + SE_REG_BITS_ENUM (CRYPTO_CONFIG_HASH_ENB, DISABLE)); void SetConfig(volatile SecurityEngineRegisters *SE, bool encrypt, SE_CONFIG_DST dst) { reg::Write(SE->SE_CONFIG, SE_REG_BITS_ENUM (CONFIG_ENC_MODE, AESMODE_KEY128), @@ -73,9 +89,9 @@ namespace ams::se { reg::ReadWrite(SE->SE_CONFIG, REG_BITS_VALUE(16, 16, mode)); } - // void UpdateMemoryInterface(volatile SecurityEngineRegisters *SE, MemoryInterface memif) { - // reg::ReadWrite(SE->SE_CRYPTO_CONFIG, SE_REG_BITS_VALUE(CRYPTO_CONFIG_MEMIF, memif)); - // } + void UpdateMemoryInterface(volatile SecurityEngineRegisters *SE, MemoryInterface memif) { + reg::ReadWrite(SE->SE_CRYPTO_CONFIG, SE_REG_BITS_VALUE(CRYPTO_CONFIG_MEMIF, memif)); + } void SetCounter(volatile SecurityEngineRegisters *SE, const void *ctr) { const u32 *ctr_32 = reinterpret_cast(ctr); @@ -87,6 +103,25 @@ namespace ams::se { reg::Write(SE->SE_CRYPTO_LINEAR_CTR[3], util::LoadLittleEndian(ctr_32 + 3)); } + void SetAesKeyIv(volatile SecurityEngineRegisters *SE, int slot, const void *iv, size_t iv_size) { + AMS_ABORT_UNLESS(0 <= slot && slot < AesKeySlotCount); + AMS_ABORT_UNLESS(iv_size <= AesBlockSize); + + /* Set each iv word in order. */ + const u32 *iv_u32 = static_cast(iv); + const int num_words = iv_size / sizeof(u32); + for (int i = 0; i < num_words; ++i) { + /* Select the keyslot. */ + reg::Write(SE->SE_CRYPTO_KEYTABLE_ADDR, SE_REG_BITS_VALUE(CRYPTO_KEYTABLE_ADDR_KEYIV_KEY_SLOT, slot), + SE_REG_BITS_ENUM (CRYPTO_KEYTABLE_ADDR_KEYIV_KEYIV_SEL, IV), + SE_REG_BITS_ENUM (CRYPTO_KEYTABLE_ADDR_KEYIV_IV_SEL, ORIGINAL_IV), + SE_REG_BITS_VALUE(CRYPTO_KEYTABLE_ADDR_KEYIV_KEY_WORD, i)); + + /* Set the key word. */ + SE->SE_CRYPTO_KEYTABLE_DATA = *(iv_u32++); + } + } + void SetEncryptedAesKey(int dst_slot, int kek_slot, const void *key, size_t key_size, AesMode mode) { AMS_ABORT_UNLESS(key_size <= AesKeySizeMax); AMS_ABORT_UNLESS(0 <= dst_slot && dst_slot < AesKeySlotCount); @@ -133,6 +168,29 @@ namespace ams::se { ExecuteOperationSingleBlock(SE, dst, dst_size, src, src_size); } + void ComputeAes128Async(u32 out_ll_address, int slot, u32 in_ll_address, u32 size, DoneHandler handler, u32 config, bool encrypt, volatile SecurityEngineRegisters *SE) { + /* If nothing to decrypt, succeed. */ + if (size == 0) { return; } + + /* Validate input. */ + AMS_ABORT_UNLESS(0 <= slot && slot < AesKeySlotCount); + + /* Configure for the specific operation. */ + SetConfig(SE, encrypt, SE_CONFIG_DST_MEMORY); + SetAesConfig(SE, slot, encrypt, config); + UpdateMemoryInterface(SE, MemoryInterface_Mc); + + /* Configure the number of blocks. */ + const int num_blocks = size / AesBlockSize; + SetBlockCount(SE, num_blocks); + + /* Set the done handler. */ + SetDoneHandler(SE, handler); + + /* Start the raw operation. */ + StartOperationRaw(SE, SE_OPERATION_OP_START, out_ll_address, in_ll_address); + } + } void ClearAesKeySlot(int slot) { @@ -270,4 +328,49 @@ namespace ams::se { } } + void EncryptAes128CbcAsync(u32 out_ll_address, int slot, u32 in_ll_address, u32 size, const void *iv, size_t iv_size, DoneHandler handler) { + /* Validate the iv. */ + AMS_ABORT_UNLESS(iv_size == AesBlockSize); + + /* Get the registers. */ + volatile auto *SE = GetRegisters(); + + /* Set the iv. */ + SetAesKeyIv(SE, slot, iv, iv_size); + + /* Perform the asynchronous aes operation. */ + ComputeAes128Async(out_ll_address, slot, in_ll_address, size, handler, AesConfigCbcEncrypt, true, SE); + } + + void DecryptAes128CbcAsync(u32 out_ll_address, int slot, u32 in_ll_address, u32 size, const void *iv, size_t iv_size, DoneHandler handler) { + /* Validate the iv. */ + AMS_ABORT_UNLESS(iv_size == AesBlockSize); + + /* Get the registers. */ + volatile auto *SE = GetRegisters(); + + /* Set the iv. */ + SetAesKeyIv(SE, slot, iv, iv_size); + + /* Perform the asynchronous aes operation. */ + ComputeAes128Async(out_ll_address, slot, in_ll_address, size, handler, AesConfigCbcDecrypt, false, SE); + } + + void ComputeAes128CtrAsync(u32 out_ll_address, int slot, u32 in_ll_address, u32 size, const void *iv, size_t iv_size, DoneHandler handler) { + /* Validate the iv. */ + AMS_ABORT_UNLESS(iv_size == AesBlockSize); + + /* Get the registers. */ + volatile auto *SE = GetRegisters(); + + /* Here Nintendo writes 1 to SE_SPARE. It's unclear why they do this, but we will do so as well. */ + SE->SE_SPARE = 0x1; + + /* Set the counter. */ + SetCounter(SE, iv); + + /* Perform the asynchronous aes operation. */ + ComputeAes128Async(out_ll_address, slot, in_ll_address, size, handler, AesConfigCtr, true, SE); + } + } diff --git a/libraries/libexosphere/source/se/se_execute.cpp b/libraries/libexosphere/source/se/se_execute.cpp index 35b18fcda..47252e772 100644 --- a/libraries/libexosphere/source/se/se_execute.cpp +++ b/libraries/libexosphere/source/se/se_execute.cpp @@ -64,6 +64,13 @@ namespace ams::se { reg::Write(SE->SE_OPERATION, SE_REG_BITS_VALUE(OPERATION_OP, op)); } + void EnsureOperationStarted(volatile SecurityEngineRegisters *SE) { + /* Read the operation register to make sure our write takes. */ + reg::Read(SE->SE_OPERATION); + + hw::DataSynchronizationBarrierInnerShareable(); + } + void WaitForOperationComplete(volatile SecurityEngineRegisters *SE) { /* Spin until the operation is done. */ while (reg::HasValue(SE->SE_INT_STATUS, SE_REG_BITS_ENUM(INT_STATUS_SE_OP_DONE, CLEAR))) { /* ... */ } @@ -124,6 +131,18 @@ namespace ams::se { std::memcpy(dst, aligned, dst_size); } + void StartOperationRaw(volatile SecurityEngineRegisters *SE, SE_OPERATION_OP op, u32 out_ll_address, u32 in_ll_address) { + /* Configure the linked list addresses. */ + reg::Write(SE->SE_IN_LL_ADDR, in_ll_address); + reg::Write(SE->SE_OUT_LL_ADDR, out_ll_address); + + /* Start the operation. */ + StartOperation(SE, op); + + /* Ensure the operation is started. */ + EnsureOperationStarted(SE); + } + void ValidateAesOperationResult(volatile SecurityEngineRegisters *SE) { /* Ensure no error occurred. */ AMS_ABORT_UNLESS(reg::HasValue(SE->SE_INT_STATUS, SE_REG_BITS_ENUM(INT_STATUS_ERR_STAT, CLEAR))); @@ -132,7 +151,11 @@ namespace ams::se { AMS_ABORT_UNLESS(reg::HasValue(SE->SE_STATUS, SE_REG_BITS_ENUM(STATUS_STATE, IDLE))); /* Ensure there is no error status. */ - AMS_ABORT_UNLESS(reg::Read(SE->SE_ERR_STATUS) == 0); + AMS_ABORT_UNLESS(reg::Read(SE->SE_ERR_STATUS) == 0); + } + + void ValidateAesOperationResult() { + return ValidateAesOperationResult(GetRegisters()); } } diff --git a/libraries/libexosphere/source/se/se_execute.hpp b/libraries/libexosphere/source/se/se_execute.hpp index 76b0a34af..5b242c597 100644 --- a/libraries/libexosphere/source/se/se_execute.hpp +++ b/libraries/libexosphere/source/se/se_execute.hpp @@ -23,6 +23,9 @@ namespace ams::se { void ExecuteOperation(volatile SecurityEngineRegisters *SE, SE_OPERATION_OP op, void *dst, size_t dst_size, const void *src, size_t src_size); void ExecuteOperationSingleBlock(volatile SecurityEngineRegisters *SE, void *dst, size_t dst_size, const void *src, size_t src_size); + void StartOperationRaw(volatile SecurityEngineRegisters *SE, SE_OPERATION_OP op, u32 out_ll_address, u32 in_ll_address); + void SetDoneHandler(volatile SecurityEngineRegisters *SE, DoneHandler handler); + void ValidateAesOperationResult(volatile SecurityEngineRegisters *SE); } diff --git a/libraries/libexosphere/source/se/se_management.cpp b/libraries/libexosphere/source/se/se_management.cpp index 8c145fd59..0c577a01b 100644 --- a/libraries/libexosphere/source/se/se_management.cpp +++ b/libraries/libexosphere/source/se/se_management.cpp @@ -105,4 +105,12 @@ namespace ams::se { } } + void SetDoneHandler(volatile SecurityEngineRegisters *SE, DoneHandler handler) { + /* Set the done handler. */ + g_done_handler = handler; + + /* Configure to trigger an interrupt when done. */ + reg::Write(SE->SE_INT_ENABLE, SE_REG_BITS_ENUM(INT_ENABLE_SE_OP_DONE, ENABLE)); + } + }