diff --git a/libraries/libstratosphere/include/stratosphere/fssystem/buffers/fssystem_file_system_buddy_heap.hpp b/libraries/libstratosphere/include/stratosphere/fssystem/buffers/fssystem_file_system_buddy_heap.hpp index 150785acc..4b236d6b8 100644 --- a/libraries/libstratosphere/include/stratosphere/fssystem/buffers/fssystem_file_system_buddy_heap.hpp +++ b/libraries/libstratosphere/include/stratosphere/fssystem/buffers/fssystem_file_system_buddy_heap.hpp @@ -125,7 +125,7 @@ namespace ams::fssystem { size_t GetBytesFromOrder(s32 order) const { AMS_ASSERT(m_free_lists != nullptr); AMS_ASSERT(0 <= order); - AMS_ASSERT(order < this->GetOrderMax()); + AMS_ASSERT(order <= this->GetOrderMax()); return (this->GetBlockSize() << order); } diff --git a/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_nca_file_system_driver.hpp b/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_nca_file_system_driver.hpp index 99a308d96..a1c87870d 100644 --- a/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_nca_file_system_driver.hpp +++ b/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_nca_file_system_driver.hpp @@ -192,7 +192,11 @@ namespace ams::fssystem { class NcaFileSystemDriver : public ::ams::fs::impl::Newable { NON_COPYABLE(NcaFileSystemDriver); NON_MOVEABLE(NcaFileSystemDriver); + #if defined(ATMOSPHERE_BOARD_NINTENDO_NX) private: + #else + public: + #endif struct StorageContext { bool open_raw_storage; std::shared_ptr body_substorage; @@ -209,8 +213,11 @@ namespace ams::fssystem { std::shared_ptr fs_data_storage; std::shared_ptr compressed_storage_meta_storage; std::shared_ptr compressed_storage; - }; + /* For tools. */ + std::shared_ptr external_original_storage; + }; + private: enum AlignmentStorageRequirement { /* TODO */ AlignmentStorageRequirement_CacheBlockSize = 0, @@ -235,7 +242,15 @@ namespace ams::fssystem { AMS_ASSERT(m_hash_generator_factory_selector != nullptr); } - Result OpenStorage(std::shared_ptr *out, std::shared_ptr *out_splitter, NcaFsHeaderReader *out_header_reader, s32 fs_index); + Result OpenStorageWithContext(std::shared_ptr *out, std::shared_ptr *out_splitter, NcaFsHeaderReader *out_header_reader, s32 fs_index, StorageContext *ctx); + + Result OpenStorage(std::shared_ptr *out, std::shared_ptr *out_splitter, NcaFsHeaderReader *out_header_reader, s32 fs_index) { + /* Create a storage context. */ + StorageContext ctx{}; + + /* Open the storage. */ + R_RETURN(OpenStorageWithContext(out, out_splitter, out_header_reader, fs_index, std::addressof(ctx))); + } private: Result OpenStorageImpl(std::shared_ptr *out, NcaFsHeaderReader *out_header_reader, s32 fs_index, StorageContext *ctx); diff --git a/libraries/libstratosphere/include/stratosphere/spl/smc/spl_secure_monitor_api.hpp b/libraries/libstratosphere/include/stratosphere/spl/smc/spl_secure_monitor_api.hpp index e2ff504c3..6ba24ca0d 100644 --- a/libraries/libstratosphere/include/stratosphere/spl/smc/spl_secure_monitor_api.hpp +++ b/libraries/libstratosphere/include/stratosphere/spl/smc/spl_secure_monitor_api.hpp @@ -37,7 +37,7 @@ namespace ams::spl::smc { Result GenerateRandomBytes(void *out, size_t size); Result GenerateAesKek(AccessKey *out, const KeySource &source, u32 generation, u32 option); Result LoadAesKey(u32 keyslot, const AccessKey &access_key, const KeySource &source); - Result ComputeAes(AsyncOperationKey *out_op, u32 dst_addr, u32 mode, const IvCtr &iv_ctr, u32 src_addr, size_t size); + Result ComputeAes(AsyncOperationKey *out_op, u64 dst_addr, u32 mode, const IvCtr &iv_ctr, u64 src_addr, size_t size); Result GenerateSpecificAesKey(AesKey *out_key, const KeySource &source, u32 generation, u32 which); Result ComputeCmac(Cmac *out_mac, u32 keyslot, const void *data, size_t size); Result ReencryptDeviceUniqueData(void *data, size_t size, const AccessKey &access_key_dec, const KeySource &source_dec, const AccessKey &access_key_enc, const KeySource &source_enc, u32 option); @@ -68,4 +68,8 @@ namespace ams::spl::smc { return SetConfig(key, std::addressof(value), 1); } + #if !defined(ATMOSPHERE_OS_HORIZON) + void PresetInternalKey(const AesKey *key, u32 generation, bool device); + #endif + } diff --git a/libraries/libstratosphere/libstratosphere.mk b/libraries/libstratosphere/libstratosphere.mk index e854123b6..57f47a647 100644 --- a/libraries/libstratosphere/libstratosphere.mk +++ b/libraries/libstratosphere/libstratosphere.mk @@ -146,8 +146,11 @@ hos_stratosphere_api.o: CXXFLAGS += -fno-lto init_operator_new.o: CXXFLAGS += -fno-lto init_libnx_shim.os.horizon.o: CXXFLAGS += -fno-lto +spl_secure_monitor_api.os.generic.o: CXXFLAGS += -I$(ATMOSPHERE_LIBRARIES_DIR)/libexosphere/include + ifeq ($(ATMOSPHERE_OS_NAME),windows) os_%.o: CXXFLAGS += -fno-lto +fssystem_%.o: CXXFLAGS += -fno-lto endif #--------------------------------------------------------------------------------- diff --git a/libraries/libstratosphere/source/fssystem/fssystem_aes_xts_storage.cpp b/libraries/libstratosphere/source/fssystem/fssystem_aes_xts_storage.cpp index 962d31385..bd2bf733d 100644 --- a/libraries/libstratosphere/source/fssystem/fssystem_aes_xts_storage.cpp +++ b/libraries/libstratosphere/source/fssystem/fssystem_aes_xts_storage.cpp @@ -31,7 +31,7 @@ namespace ams::fssystem { template AesXtsStorage::AesXtsStorage(BasePointer base, const void *key1, const void *key2, size_t key_size, const void *iv, size_t iv_size, size_t block_size) : m_base_storage(std::move(base)), m_block_size(block_size), m_mutex() { - AMS_ASSERT(base != nullptr); + AMS_ASSERT(m_base_storage != nullptr); AMS_ASSERT(key1 != nullptr); AMS_ASSERT(key2 != nullptr); AMS_ASSERT(iv != nullptr); diff --git a/libraries/libstratosphere/source/fssystem/fssystem_nca_file_system_driver.cpp b/libraries/libstratosphere/source/fssystem/fssystem_nca_file_system_driver.cpp index c468deb40..a745c57ab 100644 --- a/libraries/libstratosphere/source/fssystem/fssystem_nca_file_system_driver.cpp +++ b/libraries/libstratosphere/source/fssystem/fssystem_nca_file_system_driver.cpp @@ -302,16 +302,13 @@ namespace ams::fssystem { } - Result NcaFileSystemDriver::OpenStorage(std::shared_ptr *out, std::shared_ptr *out_splitter, NcaFsHeaderReader *out_header_reader, s32 fs_index) { - /* Create a storage context. */ - StorageContext ctx{}; - + Result NcaFileSystemDriver::OpenStorageWithContext(std::shared_ptr *out, std::shared_ptr *out_splitter, NcaFsHeaderReader *out_header_reader, s32 fs_index, StorageContext *ctx) { /* Open storage. */ - R_TRY(this->OpenStorageImpl(out, out_header_reader, fs_index, std::addressof(ctx))); + R_TRY(this->OpenStorageImpl(out, out_header_reader, fs_index, ctx)); /* If we have a compressed storage, use it as splitter. */ - if (ctx.compressed_storage != nullptr) { - *out_splitter = std::move(ctx.compressed_storage); + if (ctx->compressed_storage != nullptr) { + *out_splitter = std::move(ctx->compressed_storage); } else { /* Otherwise, allocate a default splitter. */ *out_splitter = fssystem::AllocateShared(); @@ -427,6 +424,9 @@ namespace ams::fssystem { /* Open original indirectable storage. */ R_TRY(original_driver.OpenIndirectableStorageAsOriginal(std::addressof(original_indirectable_storage), std::addressof(original_header_reader), ctx)); + } else if (ctx != nullptr && ctx->external_original_storage != nullptr) { + /* Use the external original storage. */ + original_indirectable_storage = ctx->external_original_storage; } else { /* Allocate a dummy memory storage as original storage. */ original_indirectable_storage = fssystem::AllocateShared(nullptr, 0); diff --git a/libraries/libstratosphere/source/spl/impl/spl_api_impl.cpp b/libraries/libstratosphere/source/spl/impl/spl_api_impl.cpp index 91a27718f..e476d80f6 100644 --- a/libraries/libstratosphere/source/spl/impl/spl_api_impl.cpp +++ b/libraries/libstratosphere/source/spl/impl/spl_api_impl.cpp @@ -229,8 +229,7 @@ namespace ams::spl::impl { os::InitializeInterruptEvent(std::addressof(g_interrupt), g_interrupt_name, os::EventClearMode_AutoClear); #else - AMS_UNUSED(g_interrupt_name); - AMS_ABORT("TODO: How should this work?"); + AMS_UNUSED(g_interrupt_name, g_interrupt); #endif } @@ -277,7 +276,9 @@ namespace ams::spl::impl { } void WaitOperation() { + #if defined(ATMOSPHERE_OS_HORIZON) os::WaitInterruptEvent(std::addressof(g_interrupt)); + #endif } smc::Result WaitAndGetResult(smc::AsyncOperationKey op_key) { @@ -310,6 +311,7 @@ namespace ams::spl::impl { u8 out_buffer[crypto::AesEncryptor128::BlockSize]; }; + #if defined(ATMOSPHERE_OS_HORIZON) auto &layout = *reinterpret_cast(g_work_buffer); layout.crypt_ctx.in.num_entries = 0; @@ -342,8 +344,33 @@ namespace ams::spl::impl { } } os::FlushDataCache(std::addressof(layout.out_buffer), sizeof(layout.out_buffer)); - std::memcpy(dst, layout.out_buffer, sizeof(layout.out_buffer)); + #else + { + /* Set up buffers. */ + u8 in_buffer[crypto::AesEncryptor128::BlockSize]; + u8 out_buffer[crypto::AesEncryptor128::BlockSize]; + std::memcpy(in_buffer, src, sizeof(in_buffer)); + + std::scoped_lock lk(g_operation_lock); + + /* On generic os, we don't worry about the security engine. */ + smc::AsyncOperationKey op_key; + const IvCtr iv_ctr = {}; + const u32 mode = smc::GetComputeAesMode(smc::CipherMode::CbcDecrypt, GetPhysicalAesKeySlot(keyslot, true)); + smc::Result res = smc::ComputeAes(std::addressof(op_key), reinterpret_cast(out_buffer), mode, iv_ctr, reinterpret_cast(in_buffer), sizeof(in_buffer)); + if (res != smc::Result::Success) { + return res; + } + + res = WaitAndGetResult(op_key); + if (res != smc::Result::Success) { + return res; + } + + std::memcpy(dst, out_buffer, sizeof(out_buffer)); + } + #endif return smc::Result::Success; } @@ -642,6 +669,7 @@ namespace ams::spl::impl { R_UNLESS(src_size <= dst_size, spl::ResultInvalidBufferSize()); R_UNLESS(util::IsAligned(src_size, AesBlockSize), spl::ResultInvalidBufferSize()); + #if defined(ATMOSPHERE_OS_HORIZON) /* We can only map 4_MB aligned buffers for the SE, so determine where to map our buffers. */ const uintptr_t src_addr = reinterpret_cast(src); const uintptr_t dst_addr = reinterpret_cast(dst); @@ -697,6 +725,25 @@ namespace ams::spl::impl { } } os::FlushDataCache(dst, dst_size); + #else + { + std::scoped_lock lk(g_operation_lock); + + const u32 mode = smc::GetComputeAesMode(smc::CipherMode::Ctr, GetPhysicalAesKeySlot(keyslot, true)); + + /* On generic os, we don't worry about the security engine. */ + smc::AsyncOperationKey op_key; + smc::Result res = smc::ComputeAes(std::addressof(op_key), reinterpret_cast(dst), mode, iv_ctr, reinterpret_cast(src), src_size); + if (res != smc::Result::Success) { + return smc::ConvertResult(res); + } + + res = WaitAndGetResult(op_key); + if (res != smc::Result::Success) { + return smc::ConvertResult(res); + } + } + #endif return ResultSuccess(); } diff --git a/libraries/libstratosphere/source/spl/smc/spl_secure_monitor_api.os.generic.cpp b/libraries/libstratosphere/source/spl/smc/spl_secure_monitor_api.os.generic.cpp new file mode 100644 index 000000000..224c13781 --- /dev/null +++ b/libraries/libstratosphere/source/spl/smc/spl_secure_monitor_api.os.generic.cpp @@ -0,0 +1,499 @@ +/* + * Copyright (c) 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 + +namespace ams::spl::smc { + + #define SMC_R_SUCCEEEDED(res) (res == smc::Result::Success) + #define SMC_R_FAILED(res) (res != smc::Result::Success) + + #define SMC_R_TRY(res_expr) ({ const auto _tmp_r_try_rc = (res_expr); if (SMC_R_FAILED(_tmp_r_try_rc)) { return _tmp_r_try_rc; } }) + #define SMC_R_UNLESS(cond, RES) ({ if (!(cond)) { return smc::Result::RES; }}) + + namespace { + + enum SealKey { + SealKey_LoadAesKey = 0, + SealKey_DecryptDeviceUniqueData = 1, + SealKey_ImportLotusKey = 2, + SealKey_ImportEsDeviceKey = 3, + SealKey_ReencryptDeviceUniqueData = 4, + SealKey_ImportSslKey = 5, + SealKey_ImportEsClientCertKey = 6, + + SealKey_Count, + }; + + enum KeyType { + KeyType_Default = 0, + KeyType_NormalOnly = 1, + KeyType_RecoveryOnly = 2, + KeyType_NormalAndRecovery = 3, + + KeyType_Count, + }; + + struct GenerateAesKekOption { + using IsDeviceUnique = util::BitPack32::Field<0, 1, bool>; + using KeyTypeIndex = util::BitPack32::Field<1, 4, KeyType>; + using SealKeyIndex = util::BitPack32::Field<5, 3, SealKey>; + 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 KeyTypeSources[KeyType_Count][crypto::AesEncryptor128::KeySize] = { + [KeyType_Default] = { 0x4D, 0x87, 0x09, 0x86, 0xC4, 0x5D, 0x20, 0x72, 0x2F, 0xBA, 0x10, 0x53, 0xDA, 0x92, 0xE8, 0xA9 }, + [KeyType_NormalOnly] = { 0x25, 0x03, 0x31, 0xFB, 0x25, 0x26, 0x0B, 0x79, 0x8C, 0x80, 0xD2, 0x69, 0x98, 0xE2, 0x22, 0x77 }, + [KeyType_RecoveryOnly] = { 0x76, 0x14, 0x1D, 0x34, 0x93, 0x2D, 0xE1, 0x84, 0x24, 0x7B, 0x66, 0x65, 0x55, 0x04, 0x65, 0x81 }, + [KeyType_NormalAndRecovery] = { 0xAF, 0x3D, 0xB7, 0xF3, 0x08, 0xA2, 0xD8, 0xA2, 0x08, 0xCA, 0x18, 0xA8, 0x69, 0x46, 0xC9, 0x0B }, + }; + + constexpr const u8 SealKeyMasks[SealKey_Count][crypto::AesEncryptor128::KeySize] = { + [SealKey_LoadAesKey] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, + [SealKey_DecryptDeviceUniqueData] = { 0xA2, 0xAB, 0xBF, 0x9C, 0x92, 0x2F, 0xBB, 0xE3, 0x78, 0x79, 0x9B, 0xC0, 0xCC, 0xEA, 0xA5, 0x74 }, + [SealKey_ImportLotusKey] = { 0x57, 0xE2, 0xD9, 0x45, 0xE4, 0x92, 0xF4, 0xFD, 0xC3, 0xF9, 0x86, 0x38, 0x89, 0x78, 0x9F, 0x3C }, + [SealKey_ImportEsDeviceKey] = { 0xE5, 0x4D, 0x9A, 0x02, 0xF0, 0x4F, 0x5F, 0xA8, 0xAD, 0x76, 0x0A, 0xF6, 0x32, 0x95, 0x59, 0xBB }, + [SealKey_ReencryptDeviceUniqueData] = { 0x59, 0xD9, 0x31, 0xF4, 0xA7, 0x97, 0xB8, 0x14, 0x40, 0xD6, 0xA2, 0x60, 0x2B, 0xED, 0x15, 0x31 }, + [SealKey_ImportSslKey] = { 0xFD, 0x6A, 0x25, 0xE5, 0xD8, 0x38, 0x7F, 0x91, 0x49, 0xDA, 0xF8, 0x59, 0xA8, 0x28, 0xE6, 0x75 }, + [SealKey_ImportEsClientCertKey] = { 0x89, 0x96, 0x43, 0x9A, 0x7C, 0xD5, 0x59, 0x55, 0x24, 0xD5, 0x24, 0x18, 0xAB, 0x6C, 0x04, 0x61 }, + }; + + constexpr u64 InvalidAsyncKey = 0; + + constinit os::SdkMutex g_crypto_lock; + constinit u64 g_async_key = InvalidAsyncKey; + constinit u8 g_async_result_buffer[1_KB]; + + u64 GenerateRandomU64() { + /* TODO: Can/should we make this cryptographically secure? */ + u64 v = -1; + os::GenerateRandomBytes(std::addressof(v), sizeof(v)); + return v; + } + + constinit u8 g_master_keys[pkg1::KeyGeneration_Max][crypto::AesEncryptor128::KeySize]{}; + constinit u8 g_device_keys[pkg1::KeyGeneration_Max][crypto::AesEncryptor128::KeySize]{}; + + class KeySlotManager { + private: + u8 m_key_slot_contents[pkg1::AesKeySlot_Count][crypto::AesEncryptor256::KeySize]; + public: + constexpr KeySlotManager() : m_key_slot_contents{} { /* ... */ } + public: + const u8 *GetKey(s32 slot) const { + return m_key_slot_contents[slot]; + } + + void LoadAesKey(s32 slot, const AccessKey &access_key, const KeySource &key_source) { + crypto::AesDecryptor128 aes; + aes.Initialize(std::addressof(access_key), sizeof(access_key)); + aes.DecryptBlock(m_key_slot_contents[slot], crypto::AesEncryptor128::KeySize, std::addressof(key_source), sizeof(key_source)); + } + + void LoadPreparedAesKey(s32 slot, const AccessKey &access_key) { + this->SetAesKey128(slot, std::addressof(access_key), sizeof(access_key)); + } + + s32 PrepareDeviceMasterKey(s32 generation) { + constexpr s32 Slot = pkg1::AesKeySlot_Smc; + this->SetAesKey128(Slot, g_device_keys[generation], crypto::AesEncryptor128::KeySize); + return Slot; + } + + s32 PrepareMasterKey(s32 generation) { + constexpr s32 Slot = pkg1::AesKeySlot_Smc; + this->SetAesKey128(Slot, g_master_keys[generation], crypto::AesEncryptor128::KeySize); + return Slot; + } + + void SetAesKey128(s32 slot, const void *key, size_t key_size) { + std::memcpy(m_key_slot_contents[slot], key, key_size); + } + + void SetEncryptedAesKey128(s32 dst, s32 src, const void *key_source, size_t key_source_size) { + crypto::AesDecryptor128 aes; + aes.Initialize(this->GetKey(src), crypto::AesDecryptor128::KeySize); + aes.DecryptBlock(m_key_slot_contents[dst], crypto::AesEncryptor128::KeySize, key_source, key_source_size); + } + + void DecryptAes128(void *dst, size_t dst_size, s32 slot, const void *src, size_t src_size) { + crypto::AesDecryptor128 aes; + aes.Initialize(this->GetKey(slot), crypto::AesDecryptor128::KeySize); + aes.DecryptBlock(dst, dst_size, src, src_size); + } + }; + + constexpr bool IsUserAesKeySlot(s32 slot) { + return pkg1::IsUserAesKeySlot(slot); + } + + constinit KeySlotManager g_key_slot_manager; + + } + + void PresetInternalKey(const AesKey *key, u32 generation, bool device) { + if (device) { + std::memcpy(g_device_keys[generation], key, sizeof(*key)); + } else { + std::memcpy(g_master_keys[generation], key, sizeof(*key)); + } + } + + //Result SetConfig(AsyncOperationKey *out_op, spl::ConfigItem key, const u64 *value, size_t num_qwords, const void *sign) { + // svc::SecureMonitorArguments args; + // + // args.r[0] = static_cast(FunctionId::SetConfig); + // args.r[1] = static_cast(key); + // args.r[2] = reinterpret_cast(sign); + // + // for (size_t i = 0; i < std::min(static_cast(4), num_qwords); i++) { + // args.r[3 + i] = value[i]; + // } + // svc::CallSecureMonitor(std::addressof(args)); + // + // out_op->value = args.r[1]; + // return static_cast(args.r[0]); + //} + + //Result GetConfig(u64 *out, size_t num_qwords, spl::ConfigItem key) { + // svc::SecureMonitorArguments args; + // + // args.r[0] = static_cast(FunctionId::GetConfig); + // args.r[1] = static_cast(key); + // svc::CallSecureMonitor(std::addressof(args)); + // + // for (size_t i = 0; i < std::min(static_cast(4), num_qwords); i++) { + // out[i] = args.r[1 + i]; + // } + // return static_cast(args.r[0]); + //} + + Result GetResult(Result *out, AsyncOperationKey op) { + SMC_R_UNLESS(g_async_key != InvalidAsyncKey, NoAsyncOperation); + SMC_R_UNLESS(g_async_key == op.value, InvalidAsyncOperation); + + g_async_key = InvalidAsyncKey; + + *out = smc::Result::Success; + return smc::Result::Success; + } + + Result GetResultData(Result *out, void *out_buf, size_t out_buf_size, AsyncOperationKey op) { + SMC_R_UNLESS(g_async_key != InvalidAsyncKey, NoAsyncOperation); + SMC_R_UNLESS(g_async_key == op.value, InvalidAsyncOperation); + SMC_R_UNLESS(out_buf_size <= sizeof(g_async_result_buffer), InvalidArgument); + + g_async_key = InvalidAsyncKey; + std::memcpy(out_buf, g_async_result_buffer, out_buf_size); + + *out = smc::Result::Success; + return smc::Result::Success; + } + + //Result ModularExponentiate(AsyncOperationKey *out_op, const void *base, const void *exp, size_t exp_size, const void *mod) { + // svc::SecureMonitorArguments args; + // + // args.r[0] = static_cast(FunctionId::ModularExponentiate); + // args.r[1] = reinterpret_cast(base); + // args.r[2] = reinterpret_cast(exp); + // args.r[3] = reinterpret_cast(mod); + // args.r[4] = exp_size; + // svc::CallSecureMonitor(std::addressof(args)); + // + // out_op->value = args.r[1]; + // return static_cast(args.r[0]); + //} + + Result GenerateRandomBytes(void *out, size_t size) { + /* TODO: Cryptographically secure? */ + os::GenerateRandomBytes(out, size); + return smc::Result::Success; + } + + Result GenerateAesKek(AccessKey *out, const KeySource &source, u32 user_generation, u32 option_value) { + std::scoped_lock lk(g_crypto_lock); + + const int pkg1_generation = std::max(static_cast(user_generation) - 1, pkg1::KeyGeneration_1_0_0); + + const util::BitPack32 option = { option_value }; + const bool is_device_unique = option.Get(); + const auto key_type = option.Get(); + const auto seal_key = option.Get(); + const u32 reserved = option.Get(); + + /* Validate arguments. */ + SMC_R_UNLESS(reserved == 0, InvalidArgument); + + if (is_device_unique) { + SMC_R_UNLESS(pkg1::IsValidDeviceUniqueKeyGeneration(pkg1_generation), InvalidArgument); + } else { + SMC_R_UNLESS(pkg1_generation <= pkg1::KeyGeneration_Max, InvalidArgument); + } + + SMC_R_UNLESS(0 <= key_type && key_type < KeyType_Count, InvalidArgument); + SMC_R_UNLESS(0 <= seal_key && seal_key < SealKey_Count, InvalidArgument); + + /* Here N might check if key type is normal or recovery only, but we're not going to enforce that. */ + u8 static_source[crypto::AesEncryptor128::KeySize]; + + /* Derive the static source. */ + for (size_t i = 0; i < sizeof(static_source); ++i) { + static_source[i] = KeyTypeSources[key_type][i] ^ SealKeyMasks[seal_key][i]; + } + + /* Get the slot. */ + const int slot = is_device_unique ? g_key_slot_manager.PrepareDeviceMasterKey(pkg1_generation) : g_key_slot_manager.PrepareMasterKey(pkg1_generation); + + /* Derive a static generation kek. */ + g_key_slot_manager.SetEncryptedAesKey128(pkg1::AesKeySlot_Smc, slot, static_source, sizeof(static_source)); + + /* Decrypt the input using the static-derived key. */ + g_key_slot_manager.DecryptAes128(out, sizeof(*out), pkg1::AesKeySlot_Smc, std::addressof(source), sizeof(source)); + + return smc::Result::Success; + } + + Result LoadAesKey(u32 keyslot, const AccessKey &access_key, const KeySource &source) { + std::scoped_lock lk(g_crypto_lock); + + /* Check args. */ + SMC_R_UNLESS(IsUserAesKeySlot(keyslot), InvalidArgument); + + /* Unseal the access key. */ + g_key_slot_manager.SetAesKey128(pkg1::AesKeySlot_Smc, std::addressof(access_key), sizeof(access_key)); + + /* Derive the key. */ + g_key_slot_manager.SetEncryptedAesKey128(keyslot, pkg1::AesKeySlot_Smc, std::addressof(source), sizeof(source)); + + return smc::Result::Success; + } + + Result ComputeAes(AsyncOperationKey *out_op, u64 dst_addr, u32 mode, const IvCtr &iv_ctr, u64 src_addr, size_t size) { + std::scoped_lock lk(g_crypto_lock); + + /* Check size. */ + SMC_R_UNLESS(util::IsAligned(size, crypto::AesEncryptor128::BlockSize), InvalidArgument); + + const util::BitPack32 option = { mode }; + const int slot = option.Get(); + const auto cipher_mode = option.Get(); + SMC_R_UNLESS(IsUserAesKeySlot(slot), InvalidArgument); + + /* Set a random async key. */ + g_async_key = GenerateRandomU64(); + + switch (cipher_mode) { + case CipherMode::CbcEncrypt: crypto::EncryptAes128Cbc(reinterpret_cast(dst_addr), size, g_key_slot_manager.GetKey(slot), crypto::AesEncryptor128::KeySize, iv_ctr.data, sizeof(iv_ctr.data), reinterpret_cast(src_addr), size); break; + case CipherMode::CbcDecrypt: crypto::DecryptAes128Cbc(reinterpret_cast(dst_addr), size, g_key_slot_manager.GetKey(slot), crypto::AesEncryptor128::KeySize, iv_ctr.data, sizeof(iv_ctr.data), reinterpret_cast(src_addr), size); break; + case CipherMode::Ctr: crypto::EncryptAes128Ctr(reinterpret_cast(dst_addr), size, g_key_slot_manager.GetKey(slot), crypto::AesEncryptor128::KeySize, iv_ctr.data, sizeof(iv_ctr.data), reinterpret_cast(src_addr), size); break; + default: + return smc::Result::InvalidArgument; + } + + *out_op = AsyncOperationKey{g_async_key}; + return smc::Result::Success; + } + + //Result GenerateSpecificAesKey(AesKey *out_key, const KeySource &source, u32 generation, u32 which) { + // svc::SecureMonitorArguments args; + // + // args.r[0] = static_cast(FunctionId::GenerateSpecificAesKey); + // args.r[1] = source.data64[0]; + // args.r[2] = source.data64[1]; + // args.r[3] = generation; + // args.r[4] = which; + // svc::CallSecureMonitor(std::addressof(args)); + // + // out_key->data64[0] = args.r[1]; + // out_key->data64[1] = args.r[2]; + // return static_cast(args.r[0]); + //} + + //Result ComputeCmac(Cmac *out_mac, u32 keyslot, const void *data, size_t size) { + // svc::SecureMonitorArguments args; + // + // args.r[0] = static_cast(FunctionId::ComputeCmac); + // args.r[1] = keyslot; + // args.r[2] = reinterpret_cast(data); + // args.r[3] = size; + // svc::CallSecureMonitor(std::addressof(args)); + // + // out_mac->data64[0] = args.r[1]; + // out_mac->data64[1] = args.r[2]; + // return static_cast(args.r[0]); + //} + + //Result ReencryptDeviceUniqueData(void *data, size_t size, const AccessKey &access_key_dec, const KeySource &source_dec, const AccessKey &access_key_enc, const KeySource &source_enc, u32 option) { + // svc::SecureMonitorArguments args; + // + // args.r[0] = static_cast(FunctionId::ReencryptDeviceUniqueData); + // args.r[1] = reinterpret_cast(std::addressof(access_key_dec)); + // args.r[2] = reinterpret_cast(std::addressof(access_key_enc)); + // args.r[3] = option; + // args.r[4] = reinterpret_cast(data); + // args.r[5] = size; + // args.r[6] = reinterpret_cast(std::addressof(source_dec)); + // args.r[7] = reinterpret_cast(std::addressof(source_enc)); + // svc::CallSecureMonitor(std::addressof(args)); + // + // return static_cast(args.r[0]); + //} + + //Result DecryptDeviceUniqueData(void *data, size_t size, const AccessKey &access_key, const KeySource &source, DeviceUniqueDataMode mode) { + // svc::SecureMonitorArguments args; + // + // args.r[0] = static_cast(FunctionId::DecryptDeviceUniqueData); + // args.r[1] = access_key.data64[0]; + // args.r[2] = access_key.data64[1]; + // args.r[3] = static_cast(mode); + // args.r[4] = reinterpret_cast(data); + // args.r[5] = size; + // args.r[6] = source.data64[0]; + // args.r[7] = source.data64[1]; + // svc::CallSecureMonitor(std::addressof(args)); + // + // return static_cast(args.r[0]); + //} + + //Result ModularExponentiateWithStorageKey(AsyncOperationKey *out_op, const void *base, const void *mod, ModularExponentiateWithStorageKeyMode mode) { + // svc::SecureMonitorArguments args; + // + // args.r[0] = static_cast(FunctionId::ModularExponentiateWithStorageKey); + // args.r[1] = reinterpret_cast(base); + // args.r[2] = reinterpret_cast(mod); + // args.r[3] = static_cast(mode); + // svc::CallSecureMonitor(std::addressof(args)); + // + // out_op->value = args.r[1]; + // return static_cast(args.r[0]); + //} + + //Result PrepareEsDeviceUniqueKey(AsyncOperationKey *out_op, const void *base, const void *mod, const void *label_digest, size_t label_digest_size, u32 option) { + // svc::SecureMonitorArguments args; + // + // args.r[0] = static_cast(FunctionId::PrepareEsDeviceUniqueKey); + // args.r[1] = reinterpret_cast(base); + // args.r[2] = reinterpret_cast(mod); + // std::memset(std::addressof(args.r[3]), 0, 4 * sizeof(args.r[3])); + // std::memcpy(std::addressof(args.r[3]), label_digest, std::min(static_cast(4 * sizeof(args.r[3])), label_digest_size)); + // args.r[7] = option; + // svc::CallSecureMonitor(std::addressof(args)); + // + // out_op->value = args.r[1]; + // return static_cast(args.r[0]); + //} + + Result LoadPreparedAesKey(u32 keyslot, const AccessKey &access_key) { + std::scoped_lock lk(g_crypto_lock); + + /* Check args. */ + SMC_R_UNLESS(IsUserAesKeySlot(keyslot), InvalidArgument); + + /* Unseal the key. */ + g_key_slot_manager.SetAesKey128(keyslot, std::addressof(access_key), sizeof(access_key)); + + return smc::Result::Success; + } + + //Result PrepareCommonEsTitleKey(AccessKey *out, const KeySource &source, u32 generation) { + // svc::SecureMonitorArguments args; + // + // args.r[0] = static_cast(FunctionId::PrepareCommonEsTitleKey); + // args.r[1] = source.data64[0]; + // args.r[2] = source.data64[1]; + // args.r[3] = generation; + // svc::CallSecureMonitor(std::addressof(args)); + // + // out->data64[0] = args.r[1]; + // out->data64[1] = args.r[2]; + // return static_cast(args.r[0]); + //} + + // + ///* Deprecated functions. */ + //Result LoadEsDeviceKey(const void *data, size_t size, const AccessKey &access_key, const KeySource &source, u32 option) { + // svc::SecureMonitorArguments args; + // + // args.r[0] = static_cast(FunctionId::LoadEsDeviceKey); + // args.r[1] = access_key.data64[0]; + // args.r[2] = access_key.data64[1]; + // args.r[3] = option; + // args.r[4] = reinterpret_cast(data); + // args.r[5] = size; + // args.r[6] = source.data64[0]; + // args.r[7] = source.data64[1]; + // svc::CallSecureMonitor(std::addressof(args)); + // + // return static_cast(args.r[0]); + //} + + //Result DecryptDeviceUniqueData(size_t *out_size, void *data, size_t size, const AccessKey &access_key, const KeySource &source, u32 option) { + // svc::SecureMonitorArguments args; + // + // args.r[0] = static_cast(FunctionId::DecryptDeviceUniqueData); + // args.r[1] = access_key.data64[0]; + // args.r[2] = access_key.data64[1]; + // args.r[3] = option; + // args.r[4] = reinterpret_cast(data); + // args.r[5] = size; + // args.r[6] = source.data64[0]; + // args.r[7] = source.data64[1]; + // svc::CallSecureMonitor(std::addressof(args)); + // + // *out_size = static_cast(args.r[1]); + // return static_cast(args.r[0]); + //} + + //Result DecryptAndStoreGcKey(const void *data, size_t size, const AccessKey &access_key, const KeySource &source, u32 option) { + // svc::SecureMonitorArguments args; + // + // args.r[0] = static_cast(FunctionId::DecryptAndStoreGcKey); + // args.r[1] = access_key.data64[0]; + // args.r[2] = access_key.data64[1]; + // args.r[3] = option; + // args.r[4] = reinterpret_cast(data); + // args.r[5] = size; + // args.r[6] = source.data64[0]; + // args.r[7] = source.data64[1]; + // svc::CallSecureMonitor(std::addressof(args)); + // + // return static_cast(args.r[0]); + //} + + Result AtmosphereCopyToIram(uintptr_t, const void *, size_t ) { + AMS_ABORT("AtmosphereCopyToIram not supported on generic SecureMonitor api."); + } + + Result AtmosphereCopyFromIram(void *, uintptr_t, size_t) { + AMS_ABORT("AtmosphereCopyToIram not supported on generic SecureMonitor api."); + } + + Result AtmosphereReadWriteRegister(uint64_t, uint32_t, uint32_t, uint32_t *) { + AMS_ABORT("AtmosphereReadWriteRegister not supported on generic SecureMonitor api."); + } + + Result AtmosphereGetEmummcConfig(void *out_config, void *out_paths, u32 storage_id) { + /* TODO: We actually probably should support this one on generic? */ + AMS_UNUSED(out_config, out_paths, storage_id); + AMS_ABORT("AtmosphereGetEmummcConfig not supported on generic SecureMonitor api."); + } + +} diff --git a/libraries/libstratosphere/source/spl/smc/spl_secure_monitor_api.os.horizon.cpp b/libraries/libstratosphere/source/spl/smc/spl_secure_monitor_api.os.horizon.cpp index 439bb15eb..129d7c192 100644 --- a/libraries/libstratosphere/source/spl/smc/spl_secure_monitor_api.os.horizon.cpp +++ b/libraries/libstratosphere/source/spl/smc/spl_secure_monitor_api.os.horizon.cpp @@ -126,15 +126,15 @@ namespace ams::spl::smc { return static_cast(args.r[0]); } - Result ComputeAes(AsyncOperationKey *out_op, u32 dst_addr, u32 mode, const IvCtr &iv_ctr, u32 src_addr, size_t size) { + Result ComputeAes(AsyncOperationKey *out_op, u64 dst_addr, u32 mode, const IvCtr &iv_ctr, u64 src_addr, size_t size) { svc::SecureMonitorArguments args; args.r[0] = static_cast(FunctionId::ComputeAes); args.r[1] = mode; args.r[2] = iv_ctr.data64[0]; args.r[3] = iv_ctr.data64[1]; - args.r[4] = src_addr; - args.r[5] = dst_addr; + args.r[4] = static_cast(src_addr); + args.r[5] = static_cast(dst_addr); args.r[6] = size; svc::CallSecureMonitor(std::addressof(args)); diff --git a/libraries/libstratosphere/source/spl/spl_api.os.generic.cpp b/libraries/libstratosphere/source/spl/spl_api.os.generic.cpp new file mode 100644 index 000000000..4fee730d2 --- /dev/null +++ b/libraries/libstratosphere/source/spl/spl_api.os.generic.cpp @@ -0,0 +1,106 @@ +/* + * Copyright (c) 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 + +namespace ams::spl { + + namespace { + + bool InitializeImpl() { + /* Initialize implementation api. */ + impl::Initialize(); + return true; + } + + void EnsureInitialized() { + AMS_FUNCTION_LOCAL_STATIC(bool, s_initialized, InitializeImpl()); + AMS_ABORT_UNLESS(s_initialized); + } + + Result WaitAvailableKeySlotAndExecute(auto f) { + os::SystemEvent *event = nullptr; + while (true) { + R_TRY_CATCH(static_cast<::ams::Result>(f())) { + R_CATCH(spl::ResultNoAvailableKeySlot) { + if (event == nullptr) { + event = impl::GetAesKeySlotAvailableEvent(); + } + event->Wait(); + continue; + } + } R_END_TRY_CATCH; + + R_SUCCEED(); + } + } + + } + + Result AllocateAesKeySlot(s32 *out_slot) { + EnsureInitialized(); + + R_RETURN(WaitAvailableKeySlotAndExecute([&]() -> Result { + R_RETURN(impl::AllocateAesKeySlot(out_slot)); + })); + } + + Result DeallocateAesKeySlot(s32 slot) { + EnsureInitialized(); + + R_RETURN(impl::DeallocateAesKeySlot(slot)); + } + + Result GenerateAesKek(AccessKey *out_access_key, const void *key_source, size_t key_source_size, s32 generation, u32 option) { + EnsureInitialized(); + + /* Check key size (assumed valid). */ + AMS_ASSERT(key_source_size == sizeof(KeySource)); + AMS_UNUSED(key_source_size); + + /* AccessKey *out_access_key, const KeySource &key_source, u32 generation, u32 option */ + R_RETURN(impl::GenerateAesKek(out_access_key, *static_cast(key_source), generation, option)); + } + + Result LoadAesKey(s32 slot, const AccessKey &access_key, const void *key_source, size_t key_source_size) { + AMS_ASSERT(key_source_size == sizeof(KeySource)); + AMS_UNUSED(key_source_size); + + R_RETURN(impl::LoadAesKey(slot, access_key, *static_cast(key_source))); + } + + Result GenerateAesKey(void *dst, size_t dst_size, const AccessKey &access_key, const void *key_source, size_t key_source_size) { + AMS_ASSERT(dst_size >= sizeof(AesKey)); + AMS_ASSERT(key_source_size == sizeof(KeySource)); + AMS_UNUSED(dst_size, key_source_size); + + R_RETURN(WaitAvailableKeySlotAndExecute([&]() -> Result { + R_RETURN(impl::GenerateAesKey(static_cast(dst), access_key, *static_cast(key_source))); + })); + } + + Result ComputeCtr(void *dst, size_t dst_size, s32 slot, const void *src, size_t src_size, const void *iv, size_t iv_size) { + AMS_ASSERT(iv_size >= sizeof(IvCtr)); + AMS_UNUSED(iv_size); + AMS_ASSERT(dst_size >= src_size); + + R_RETURN(impl::ComputeCtr(dst, dst_size, slot, src, src_size, *static_cast(iv))); + } + + Result LoadPreparedAesKey(s32 slot, const AccessKey &access_key) { + R_RETURN(impl::LoadPreparedAesKey(slot, access_key)); + } + +} diff --git a/libraries/libstratosphere/source/spl/spl_api.cpp b/libraries/libstratosphere/source/spl/spl_api.os.horizon.cpp similarity index 99% rename from libraries/libstratosphere/source/spl/spl_api.cpp rename to libraries/libstratosphere/source/spl/spl_api.os.horizon.cpp index 525ccd081..e296579c1 100644 --- a/libraries/libstratosphere/source/spl/spl_api.cpp +++ b/libraries/libstratosphere/source/spl/spl_api.os.horizon.cpp @@ -17,9 +17,6 @@ namespace ams::spl { - /* BIG TODO: How to deal with this? */ - #if defined(ATMOSPHERE_OS_HORIZON) - namespace { enum class InitializeMode { @@ -298,6 +295,4 @@ namespace ams::spl { } } - #endif - } diff --git a/libraries/libvapours/include/vapours/crypto.hpp b/libraries/libvapours/include/vapours/crypto.hpp index db768d96c..45dd81e18 100644 --- a/libraries/libvapours/include/vapours/crypto.hpp +++ b/libraries/libvapours/include/vapours/crypto.hpp @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include diff --git a/libraries/libvapours/include/vapours/crypto/crypto_aes_cbc_encryptor_decryptor.hpp b/libraries/libvapours/include/vapours/crypto/crypto_aes_cbc_encryptor_decryptor.hpp new file mode 100644 index 000000000..6fe5c3901 --- /dev/null +++ b/libraries/libvapours/include/vapours/crypto/crypto_aes_cbc_encryptor_decryptor.hpp @@ -0,0 +1,81 @@ +/* + * Copyright (c) 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 +#include +#include +#include +#include + +namespace ams::crypto { + + namespace impl { + + template typename _CbcImpl, typename _AesImpl> + class AesCbcCryptor { + NON_COPYABLE(AesCbcCryptor); + NON_MOVEABLE(AesCbcCryptor); + private: + using AesImpl = _AesImpl; + using CbcImpl = _CbcImpl; + public: + static constexpr size_t KeySize = AesImpl::KeySize; + static constexpr size_t BlockSize = CbcImpl::BlockSize; + static constexpr size_t IvSize = CbcImpl::BlockSize; + private: + AesImpl m_aes_impl; + CbcImpl m_cbc_impl; + public: + AesCbcCryptor() { /* ... */ } + + void Initialize(const void *key, size_t key_size, const void *iv, size_t iv_size) { + AMS_ASSERT(key_size == KeySize); + AMS_ASSERT(iv_size == IvSize); + + m_aes_impl.Initialize(key, key_size); + m_cbc_impl.Initialize(std::addressof(m_aes_impl), iv, iv_size); + } + + size_t Update(void *dst, size_t dst_size, const void *src, size_t src_size) { + return m_cbc_impl.Update(dst, dst_size, src, src_size); + } + + size_t GetBufferedDataSize() const { + return m_cbc_impl.GetBufferedDataSize(); + } + }; + + } + + using Aes128CbcEncryptor = impl::AesCbcCryptor; + using Aes192CbcEncryptor = impl::AesCbcCryptor; + using Aes256CbcEncryptor = impl::AesCbcCryptor; + + using Aes128CbcDecryptor = impl::AesCbcCryptor; + using Aes192CbcDecryptor = impl::AesCbcCryptor; + using Aes256CbcDecryptor = impl::AesCbcCryptor; + + size_t EncryptAes128Cbc(void *dst, size_t dst_size, const void *key, size_t key_size, const void *iv, size_t iv_size, const void *src, size_t src_size); + size_t EncryptAes192Cbc(void *dst, size_t dst_size, const void *key, size_t key_size, const void *iv, size_t iv_size, const void *src, size_t src_size); + size_t EncryptAes256Cbc(void *dst, size_t dst_size, const void *key, size_t key_size, const void *iv, size_t iv_size, const void *src, size_t src_size); + + size_t DecryptAes128Cbc(void *dst, size_t dst_size, const void *key, size_t key_size, const void *iv, size_t iv_size, const void *src, size_t src_size); + size_t DecryptAes192Cbc(void *dst, size_t dst_size, const void *key, size_t key_size, const void *iv, size_t iv_size, const void *src, size_t src_size); + size_t DecryptAes256Cbc(void *dst, size_t dst_size, const void *key, size_t key_size, const void *iv, size_t iv_size, const void *src, size_t src_size); + +} diff --git a/libraries/libvapours/include/vapours/crypto/crypto_cbc_decryptor.hpp b/libraries/libvapours/include/vapours/crypto/crypto_cbc_decryptor.hpp new file mode 100644 index 000000000..b9fca26bd --- /dev/null +++ b/libraries/libvapours/include/vapours/crypto/crypto_cbc_decryptor.hpp @@ -0,0 +1,55 @@ +/* + * Copyright (c) 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 +#include +#include + +namespace ams::crypto { + + /* TODO: C++20 BlockCipher concept */ + + template + class CbcDecryptor { + NON_COPYABLE(CbcDecryptor); + NON_MOVEABLE(CbcDecryptor); + private: + using Impl = impl::CbcModeImpl; + public: + static constexpr size_t KeySize = Impl::KeySize; + static constexpr size_t BlockSize = Impl::BlockSize; + static constexpr size_t IvSize = Impl::IvSize; + private: + Impl m_impl; + public: + CbcDecryptor() { /* ... */ } + + void Initialize(const BlockCipher *cipher, const void *iv, size_t iv_size) { + m_impl.Initialize(cipher, iv, iv_size); + } + + size_t Update(void *dst, size_t dst_size, const void *src, size_t src_size) { + return m_impl.UpdateDecryption(dst, dst_size, src, src_size); + } + + size_t GetBufferedDataSize() const { + return m_impl.GetBufferedDataSize(); + } + }; + +} diff --git a/libraries/libvapours/include/vapours/crypto/crypto_cbc_encryptor.hpp b/libraries/libvapours/include/vapours/crypto/crypto_cbc_encryptor.hpp new file mode 100644 index 000000000..96c07619b --- /dev/null +++ b/libraries/libvapours/include/vapours/crypto/crypto_cbc_encryptor.hpp @@ -0,0 +1,55 @@ +/* + * Copyright (c) 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 +#include +#include + +namespace ams::crypto { + + /* TODO: C++20 BlockCipher concept */ + + template + class CbcEncryptor { + NON_COPYABLE(CbcEncryptor); + NON_MOVEABLE(CbcEncryptor); + private: + using Impl = impl::CbcModeImpl; + public: + static constexpr size_t KeySize = Impl::KeySize; + static constexpr size_t BlockSize = Impl::BlockSize; + static constexpr size_t IvSize = Impl::IvSize; + private: + Impl m_impl; + public: + CbcEncryptor() { /* ... */ } + + void Initialize(const BlockCipher *cipher, const void *iv, size_t iv_size) { + m_impl.Initialize(cipher, iv, iv_size); + } + + size_t Update(void *dst, size_t dst_size, const void *src, size_t src_size) { + return m_impl.UpdateEncryption(dst, dst_size, src, src_size); + } + + size_t GetBufferedDataSize() const { + return m_impl.GetBufferedDataSize(); + } + }; + +} diff --git a/libraries/libvapours/include/vapours/crypto/impl/crypto_aes_impl.hpp b/libraries/libvapours/include/vapours/crypto/impl/crypto_aes_impl.hpp index 3f662c5ad..066687771 100644 --- a/libraries/libvapours/include/vapours/crypto/impl/crypto_aes_impl.hpp +++ b/libraries/libvapours/include/vapours/crypto/impl/crypto_aes_impl.hpp @@ -13,11 +13,11 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ - #pragma once #include #include #include +#include namespace ams::crypto::impl { @@ -50,6 +50,8 @@ namespace ams::crypto::impl { #endif }; - /* static_assert(HashFunction); */ + static_assert(BlockCipher>); + static_assert(BlockCipher>); + static_assert(BlockCipher>); } diff --git a/libraries/libvapours/include/vapours/crypto/impl/crypto_block_cipher.hpp b/libraries/libvapours/include/vapours/crypto/impl/crypto_block_cipher.hpp new file mode 100644 index 000000000..0e6ac760b --- /dev/null +++ b/libraries/libvapours/include/vapours/crypto/impl/crypto_block_cipher.hpp @@ -0,0 +1,33 @@ +/* + * Copyright (c) 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 +#include +#include +#include + +namespace ams::crypto::impl { + + template + concept BlockCipher = requires(T &t, const void *cv, void *v, size_t sz, bool b) { + { T::BlockSize } -> std::convertible_to; + { t.EncryptBlock(v, sz, cv, sz) } -> std::same_as; + { t.DecryptBlock(v, sz, cv, sz) } -> std::same_as; + }; + +} diff --git a/libraries/libvapours/include/vapours/crypto/impl/crypto_cbc_mode_impl.hpp b/libraries/libvapours/include/vapours/crypto/impl/crypto_cbc_mode_impl.hpp new file mode 100644 index 000000000..eccd232f9 --- /dev/null +++ b/libraries/libvapours/include/vapours/crypto/impl/crypto_cbc_mode_impl.hpp @@ -0,0 +1,200 @@ +/* + * Copyright (c) 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 +#include +#include +#include + +namespace ams::crypto::impl { + + template + class CbcModeImpl { + NON_COPYABLE(CbcModeImpl); + NON_MOVEABLE(CbcModeImpl); + public: + static constexpr size_t KeySize = BlockCipher::KeySize; + static constexpr size_t BlockSize = BlockCipher::BlockSize; + static constexpr size_t IvSize = BlockCipher::BlockSize; + private: + enum State { + State_None, + State_Initialized, + }; + private: + const BlockCipher *m_block_cipher; + u8 m_iv[IvSize]; + u8 m_buffer[BlockSize]; + size_t m_buffered_bytes; + State m_state; + public: + CbcModeImpl() : m_state(State_None) { /* ... */ } + + ~CbcModeImpl() { + ClearMemory(this, sizeof(*this)); + } + + void Initialize(const BlockCipher *block_cipher, const void *iv, size_t iv_size) { + AMS_ASSERT(iv_size == IvSize); + AMS_UNUSED(iv_size); + + m_block_cipher = block_cipher; + std::memcpy(m_iv, iv, IvSize); + m_buffered_bytes = 0; + + m_state = State_Initialized; + } + + size_t GetBufferedDataSize() const { + return m_buffered_bytes; + } + + size_t UpdateEncryption(void *dst, size_t dst_size, const void *src, size_t src_size) { + AMS_ASSERT(dst_size >= ((src_size + this->GetBufferedDataSize()) / BlockSize) * BlockSize); + AMS_ASSERT(m_state == State_Initialized); + + return this->Update(dst, dst_size, src, src_size, [&] (u8 *d, u8 *i, const u8 *s, size_t n) ALWAYS_INLINE_LAMBDA { + this->EncryptBlocks(d, i, s, n); + }); + } + + size_t UpdateDecryption(void *dst, size_t dst_size, const void *src, size_t src_size) { + AMS_ASSERT(dst_size >= ((src_size + this->GetBufferedDataSize()) / BlockSize) * BlockSize); + AMS_ASSERT(m_state == State_Initialized); + + return this->Update(dst, dst_size, src, src_size, [&] (u8 *d, u8 *i, const u8 *s, size_t n) ALWAYS_INLINE_LAMBDA { + this->DecryptBlocks(d, i, s, n); + }); + } + private: + size_t Update(void *_dst, size_t dst_size, const void *_src, size_t src_size, auto ProcessBlocks) { + AMS_UNUSED(dst_size); + + u8 *dst = static_cast(_dst); + const u8 *src = static_cast(_src); + size_t remaining = src_size; + size_t processed = 0; + + if (m_buffered_bytes > 0) { + const size_t copy_size = std::min(BlockSize - m_buffered_bytes, remaining); + + std::memcpy(m_buffer + m_buffered_bytes, src, copy_size); + src += copy_size; + remaining -= copy_size; + m_buffered_bytes += copy_size; + + if (m_buffered_bytes == BlockSize) { + ProcessBlocks(dst, m_iv, m_buffer, 1); + processed += BlockSize; + dst += BlockSize; + + m_buffered_bytes = 0; + } + } + + if (remaining >= BlockSize) { + const size_t num_blocks = remaining / BlockSize; + + ProcessBlocks(dst, m_iv, src, num_blocks); + + const size_t processed_size = num_blocks * BlockSize; + dst += processed_size; + src += processed_size; + remaining -= processed_size; + processed += processed_size; + } + + if (remaining > 0) { + std::memcpy(m_buffer, src, remaining); + m_buffered_bytes = remaining; + } + + return processed; + } + + void EncryptBlocks(u8 *dst, u8 *iv, const u8 *src, size_t num_blocks) { + const u8 *cur_iv = iv; + + u8 block[BlockSize]; + while (num_blocks--) { + for (size_t i = 0; i < BlockSize; ++i) { + block[i] = src[i] ^ cur_iv[i]; + } + + m_block_cipher->EncryptBlock(dst, BlockSize, block, BlockSize); + + cur_iv = dst; + src += BlockSize; + dst += BlockSize; + } + + if (iv != cur_iv) { + std::memcpy(iv, cur_iv, BlockSize); + } + } + + void DecryptBlocks(u8 *dst, u8 *iv, const u8 *src, size_t num_blocks) { + u8 next_iv[BlockSize]; + std::memcpy(next_iv, src + ((num_blocks - 1) * BlockSize), BlockSize); + + if (src == dst) { + src = src + ((num_blocks - 1) * BlockSize); + dst = dst + ((num_blocks - 1) * BlockSize); + + const u8 *cur_iv = (num_blocks == 1) ? iv : src - BlockSize; + + while (num_blocks-- > 1) { + m_block_cipher->DecryptBlock(dst, BlockSize, src, BlockSize); + + for (size_t i = 0; i < BlockSize; ++i) { + dst[i] ^= cur_iv[i]; + } + + cur_iv -= BlockSize; + src -= BlockSize; + dst -= BlockSize; + } + + m_block_cipher->DecryptBlock(dst, BlockSize, src, BlockSize); + + for (size_t i = 0; i < BlockSize; ++i) { + dst[i] ^= iv[i]; + } + } else { + const u8 *cur_iv = iv; + + while (num_blocks-- > 0) { + m_block_cipher->DecryptBlock(dst, BlockSize, src, BlockSize); + + for (size_t i = 0; i < BlockSize; ++i) { + dst[i] ^= cur_iv[i]; + } + + cur_iv = src; + src += BlockSize; + dst += BlockSize; + } + } + + std::memcpy(iv, next_iv, BlockSize); + } + }; + + /* TODO: Optimized AES cbc impl specializations. */ + +} diff --git a/libraries/libvapours/include/vapours/crypto/impl/crypto_hmac_impl.hpp b/libraries/libvapours/include/vapours/crypto/impl/crypto_hmac_impl.hpp index d8f7a44fc..4fc8acabb 100644 --- a/libraries/libvapours/include/vapours/crypto/impl/crypto_hmac_impl.hpp +++ b/libraries/libvapours/include/vapours/crypto/impl/crypto_hmac_impl.hpp @@ -23,7 +23,7 @@ namespace ams::crypto::impl { - template /* requires HashFunction */ + template class HmacImpl { NON_COPYABLE(HmacImpl); NON_MOVEABLE(HmacImpl); @@ -61,7 +61,7 @@ namespace ams::crypto::impl { void GetMac(void *dst, size_t dst_size); }; - template + template inline void HmacImpl::Initialize(const void *key, size_t key_size) { /* Clear the key storage. */ std::memset(m_key, 0, sizeof(m_key)); @@ -88,14 +88,14 @@ namespace ams::crypto::impl { m_state = State_Initialized; } - template + template inline void HmacImpl::Update(const void *data, size_t data_size) { AMS_ASSERT(m_state == State_Initialized); m_hash_function.Update(data, data_size); } - template + template inline void HmacImpl::GetMac(void *dst, size_t dst_size) { AMS_ASSERT(m_state == State_Initialized || m_state == State_Done); AMS_ASSERT(dst_size >= MacSize); diff --git a/libraries/libvapours/include/vapours/crypto/impl/crypto_md5_impl.hpp b/libraries/libvapours/include/vapours/crypto/impl/crypto_md5_impl.hpp index 4705f42e6..4d37abf7a 100644 --- a/libraries/libvapours/include/vapours/crypto/impl/crypto_md5_impl.hpp +++ b/libraries/libvapours/include/vapours/crypto/impl/crypto_md5_impl.hpp @@ -56,6 +56,6 @@ namespace ams::crypto::impl { void ProcessLastBlock(); }; - /* static_assert(HashFunction); */ + static_assert(HashFunction); } diff --git a/libraries/libvapours/include/vapours/crypto/impl/crypto_rsa_oaep_impl.hpp b/libraries/libvapours/include/vapours/crypto/impl/crypto_rsa_oaep_impl.hpp index 6948b5a36..b45ac962f 100644 --- a/libraries/libvapours/include/vapours/crypto/impl/crypto_rsa_oaep_impl.hpp +++ b/libraries/libvapours/include/vapours/crypto/impl/crypto_rsa_oaep_impl.hpp @@ -22,7 +22,7 @@ namespace ams::crypto::impl { - template requires HashFunction + template class RsaOaepImpl { NON_COPYABLE(RsaOaepImpl); NON_MOVEABLE(RsaOaepImpl); diff --git a/libraries/libvapours/include/vapours/crypto/impl/crypto_rsa_pss_impl.hpp b/libraries/libvapours/include/vapours/crypto/impl/crypto_rsa_pss_impl.hpp index e5f38285c..73f7ada9a 100644 --- a/libraries/libvapours/include/vapours/crypto/impl/crypto_rsa_pss_impl.hpp +++ b/libraries/libvapours/include/vapours/crypto/impl/crypto_rsa_pss_impl.hpp @@ -22,7 +22,7 @@ namespace ams::crypto::impl { - template requires HashFunction + template class RsaPssImpl { NON_COPYABLE(RsaPssImpl); NON_MOVEABLE(RsaPssImpl); diff --git a/libraries/libvapours/include/vapours/crypto/impl/crypto_sha1_impl.hpp b/libraries/libvapours/include/vapours/crypto/impl/crypto_sha1_impl.hpp index 10339bc48..4404c8248 100644 --- a/libraries/libvapours/include/vapours/crypto/impl/crypto_sha1_impl.hpp +++ b/libraries/libvapours/include/vapours/crypto/impl/crypto_sha1_impl.hpp @@ -55,6 +55,6 @@ namespace ams::crypto::impl { void ProcessLastBlock(); }; - /* static_assert(HashFunction); */ + static_assert(HashFunction); } diff --git a/libraries/libvapours/source/crypto/crypto_aes_cbc_encryptor_decryptor.cpp b/libraries/libvapours/source/crypto/crypto_aes_cbc_encryptor_decryptor.cpp new file mode 100644 index 000000000..14e0113a2 --- /dev/null +++ b/libraries/libvapours/source/crypto/crypto_aes_cbc_encryptor_decryptor.cpp @@ -0,0 +1,56 @@ +/* + * Copyright (c) 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 + +namespace ams::crypto { + + size_t EncryptAes128Cbc(void *dst, size_t dst_size, const void *key, size_t key_size, const void *iv, size_t iv_size, const void *src, size_t src_size) { + Aes128CbcEncryptor aes; + aes.Initialize(key, key_size, iv, iv_size); + return aes.Update(dst, dst_size, src, src_size); + } + + size_t EncryptAes192Cbc(void *dst, size_t dst_size, const void *key, size_t key_size, const void *iv, size_t iv_size, const void *src, size_t src_size) { + Aes192CbcEncryptor aes; + aes.Initialize(key, key_size, iv, iv_size); + return aes.Update(dst, dst_size, src, src_size); + } + + size_t EncryptAes256Cbc(void *dst, size_t dst_size, const void *key, size_t key_size, const void *iv, size_t iv_size, const void *src, size_t src_size) { + Aes256CbcEncryptor aes; + aes.Initialize(key, key_size, iv, iv_size); + return aes.Update(dst, dst_size, src, src_size); + } + + size_t DecryptAes128Cbc(void *dst, size_t dst_size, const void *key, size_t key_size, const void *iv, size_t iv_size, const void *src, size_t src_size) { + Aes128CbcDecryptor aes; + aes.Initialize(key, key_size, iv, iv_size); + return aes.Update(dst, dst_size, src, src_size); + } + + size_t DecryptAes192Cbc(void *dst, size_t dst_size, const void *key, size_t key_size, const void *iv, size_t iv_size, const void *src, size_t src_size) { + Aes192CbcDecryptor aes; + aes.Initialize(key, key_size, iv, iv_size); + return aes.Update(dst, dst_size, src, src_size); + } + + size_t DecryptAes256Cbc(void *dst, size_t dst_size, const void *key, size_t key_size, const void *iv, size_t iv_size, const void *src, size_t src_size) { + Aes256CbcDecryptor aes; + aes.Initialize(key, key_size, iv, iv_size); + return aes.Update(dst, dst_size, src, src_size); + } + +} diff --git a/libraries/libvapours/source/crypto/crypto_memory_compare.arch.generic.cpp b/libraries/libvapours/source/crypto/crypto_memory_compare.arch.generic.cpp new file mode 100644 index 000000000..46f65b57b --- /dev/null +++ b/libraries/libvapours/source/crypto/crypto_memory_compare.arch.generic.cpp @@ -0,0 +1,33 @@ +/* + * Copyright (c) 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 + +namespace ams::crypto { + + bool IsSameBytes(const void *lhs, const void *rhs, size_t size) { + /* TODO: Should the generic impl be constant time? */ + volatile u8 diff = 0; + + const volatile u8 *lhs8 = static_cast(lhs); + const volatile u8 *rhs8 = static_cast(rhs); + for (size_t i = 0; i < size; ++i) { + diff = diff | (lhs8[i] ^ rhs8[i]); + } + + return diff == 0; + } + +} \ No newline at end of file diff --git a/libraries/libvapours/source/crypto/impl/crypto_xts_mode_impl.arch.generic.cpp b/libraries/libvapours/source/crypto/impl/crypto_xts_mode_impl.arch.generic.cpp new file mode 100644 index 000000000..510d107c2 --- /dev/null +++ b/libraries/libvapours/source/crypto/impl/crypto_xts_mode_impl.arch.generic.cpp @@ -0,0 +1,57 @@ +/* + * Copyright (c) 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 "crypto_update_impl.hpp" + +namespace ams::crypto::impl { + + size_t XtsModeImpl::UpdateGeneric(void *dst, size_t dst_size, const void *src, size_t src_size) { + AMS_ASSERT(m_state == State_Initialized || m_state == State_Processing); + + return UpdateImpl(this, dst, dst_size, src, src_size); + } + + size_t XtsModeImpl::ProcessBlocksGeneric(u8 *dst, const u8 *src, size_t num_blocks) { + size_t processed = BlockSize * (num_blocks - 1); + + if (m_state == State_Processing) { + this->ProcessBlock(dst, m_last_block); + dst += BlockSize; + processed += BlockSize; + } + + while ((--num_blocks) > 0) { + this->ProcessBlock(dst, src); + dst += BlockSize; + src += BlockSize; + } + + std::memcpy(m_last_block, src, BlockSize); + + m_state = State_Processing; + + return processed; + } + + template<> size_t XtsModeImpl::Update(void *dst, size_t dst_size, const void *src, size_t src_size) { return this->UpdateGeneric(dst, dst_size, src, src_size); } + template<> size_t XtsModeImpl::Update(void *dst, size_t dst_size, const void *src, size_t src_size) { return this->UpdateGeneric(dst, dst_size, src, src_size); } + template<> size_t XtsModeImpl::Update(void *dst, size_t dst_size, const void *src, size_t src_size) { return this->UpdateGeneric(dst, dst_size, src, src_size); } + + template<> size_t XtsModeImpl::Update(void *dst, size_t dst_size, const void *src, size_t src_size) { return this->UpdateGeneric(dst, dst_size, src, src_size); } + template<> size_t XtsModeImpl::Update(void *dst, size_t dst_size, const void *src, size_t src_size) { return this->UpdateGeneric(dst, dst_size, src, src_size); } + template<> size_t XtsModeImpl::Update(void *dst, size_t dst_size, const void *src, size_t src_size) { return this->UpdateGeneric(dst, dst_size, src, src_size); } + +} diff --git a/tests/TestFs/source/test.cpp b/tests/TestFs/source/test.cpp index 3d159603a..96c20fb72 100644 --- a/tests/TestFs/source/test.cpp +++ b/tests/TestFs/source/test.cpp @@ -1,3 +1,18 @@ +/* + * Copyright (c) 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 namespace ams { diff --git a/tests/TestOsEvents/source/test.cpp b/tests/TestOsEvents/source/test.cpp index e3533efe9..4ff3442bf 100644 --- a/tests/TestOsEvents/source/test.cpp +++ b/tests/TestOsEvents/source/test.cpp @@ -1,3 +1,18 @@ +/* + * Copyright (c) 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 namespace ams {