diff --git a/libraries/libstratosphere/include/stratosphere/fssrv.hpp b/libraries/libstratosphere/include/stratosphere/fssrv.hpp index 5c8881371..d5477f68c 100644 --- a/libraries/libstratosphere/include/stratosphere/fssrv.hpp +++ b/libraries/libstratosphere/include/stratosphere/fssrv.hpp @@ -36,3 +36,4 @@ #include #include #include +#include diff --git a/libraries/libstratosphere/include/stratosphere/fssrv/impl/fssrv_external_key_manager.hpp b/libraries/libstratosphere/include/stratosphere/fssrv/impl/fssrv_external_key_manager.hpp new file mode 100644 index 000000000..0cd54c069 --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/fssrv/impl/fssrv_external_key_manager.hpp @@ -0,0 +1,147 @@ +/* + * 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::fssrv::impl { + + class ExternalKeyEntry : public util::IntrusiveListBaseNode, public ::ams::fs::impl::Newable { + private: + fs::RightsId m_rights_id; + spl::AccessKey m_access_key; + public: + ExternalKeyEntry(const fs::RightsId &rights_id, const spl::AccessKey &access_key) : m_rights_id(rights_id), m_access_key(access_key) { + /* ... */ + } + + bool Contains(const fs::RightsId &rights_id) const { + return crypto::IsSameBytes(std::addressof(m_rights_id), std::addressof(rights_id), sizeof(m_rights_id)); + } + + bool Contains(const void *key, size_t key_size) const { + AMS_ASSERT(key_size == sizeof(spl::AccessKey)); + return crypto::IsSameBytes(std::addressof(m_access_key), key, sizeof(m_access_key)); + } + + void CopyAccessKey(spl::AccessKey *out) const { + AMS_ASSERT(out != nullptr); + std::memcpy(out, std::addressof(m_access_key), sizeof(m_access_key)); + } + }; + + class ExternalKeyManager { + NON_COPYABLE(ExternalKeyManager); + NON_MOVEABLE(ExternalKeyManager); + private: + using ExternalKeyList = util::IntrusiveListBaseTraits::ListType; + private: + ExternalKeyList m_key_list; + os::SdkMutex m_mutex; + public: + constexpr ExternalKeyManager() : m_key_list(), m_mutex() { /* ... */ } + + Result Register(const fs::RightsId &rights_id, const spl::AccessKey &access_key) { + /* Acquire exclusive access to the key list */ + std::scoped_lock lk(m_mutex); + + /* Try to find an existing entry. */ + spl::AccessKey existing; + if (R_SUCCEEDED(this->FindCore(std::addressof(existing), rights_id))) { + /* Check the key matches what was previously registered. */ + R_UNLESS(crypto::IsSameBytes(std::addressof(existing), std::addressof(access_key), sizeof(access_key)), fs::ResultNcaExternalKeyInconsistent()); + } else { + /* Make a new entry. */ + auto *entry = new ExternalKeyEntry(rights_id, access_key); + R_UNLESS(entry != nullptr, fs::ResultAllocationFailure()); + + /* Add the entry to our list. */ + m_key_list.push_back(*entry); + } + + R_SUCCEED(); + } + + Result Unregister(const fs::RightsId &rights_id) { + /* Acquire exclusive access to the key list */ + std::scoped_lock lk(m_mutex); + + /* Find a matching entry. */ + for (auto it = m_key_list.begin(); it != m_key_list.end(); ++it) { + if (it->Contains(rights_id)) { + auto *entry = std::addressof(*it); + m_key_list.erase(it); + delete entry; + break; + } + } + + /* Always succeed. */ + R_SUCCEED(); + } + + Result UnregisterAll() { + /* Acquire exclusive access to the key list */ + std::scoped_lock lk(m_mutex); + + /* Remove all entries until our list is empty. */ + while (!m_key_list.empty()) { + auto *entry = std::addressof(*m_key_list.begin()); + m_key_list.erase(m_key_list.iterator_to(*entry)); + delete entry; + } + + R_SUCCEED(); + } + + bool IsAvailableAccessKey(const void *key, size_t key_size) { + /* Acquire exclusive access to the key list */ + std::scoped_lock lk(m_mutex); + + /* Check if any entry contains the key. */ + for (const auto &entry : m_key_list) { + if (entry.Contains(key, key_size)) { + return true; + } + } + + return false; + } + + Result Find(spl::AccessKey *out, const fs::RightsId &rights_id) { + /* Acquire exclusive access to the key list */ + std::scoped_lock lk(m_mutex); + + /* Try to find an entry with the desired rights id. */ + R_RETURN(this->FindCore(out, rights_id)); + } + private: + Result FindCore(spl::AccessKey *out, const fs::RightsId &rights_id) { + for (const auto &entry : m_key_list) { + if (entry.Contains(rights_id)) { + entry.CopyAccessKey(out); + R_SUCCEED(); + } + } + + R_THROW(fs::ResultNcaExternalKeyUnregistered()); + } + }; + +} diff --git a/libraries/libstratosphere/include/stratosphere/spl/spl_api.hpp b/libraries/libstratosphere/include/stratosphere/spl/spl_api.hpp index 3db4ed3dd..3d60504fe 100644 --- a/libraries/libstratosphere/include/stratosphere/spl/spl_api.hpp +++ b/libraries/libstratosphere/include/stratosphere/spl/spl_api.hpp @@ -105,4 +105,6 @@ namespace ams::spl { Result LoadPreparedAesKey(s32 slot, const AccessKey &access_key); + Result PrepareCommonEsTitleKey(AccessKey *out, const void *key_source, const size_t key_source_size, int generation); + } 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 0e3584adc..5a3eb5932 100644 --- a/libraries/libstratosphere/source/fssystem/fssystem_nca_file_system_driver.cpp +++ b/libraries/libstratosphere/source/fssystem/fssystem_nca_file_system_driver.cpp @@ -111,7 +111,7 @@ namespace ams::fssystem { u8 m_encrypted_key[KeySize]; public: AesCtrStorageExternal(std::shared_ptr bs, const void *enc_key, size_t enc_key_size, const void *iv, size_t iv_size, DecryptAesCtrFunction df, s32 kidx) : m_base_storage(std::move(bs)), m_decrypt_function(df), m_key_index(kidx) { - AMS_ASSERT(bs != nullptr); + AMS_ASSERT(m_base_storage != nullptr); AMS_ASSERT(enc_key_size == KeySize); AMS_ASSERT(iv != nullptr); AMS_ASSERT(iv_size == IvSize); 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 index 95945327a..fead3970e 100644 --- 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 @@ -47,6 +47,13 @@ namespace ams::spl::smc { KeyType_Count, }; + enum EsCommonKeyType { + EsCommonKeyType_TitleKey = 0, + EsCommonKeyType_ArchiveKey = 1, + + EsCommonKeyType_Count, + }; + struct GenerateAesKekOption { using IsDeviceUnique = util::BitPack32::Field<0, 1, bool>; using KeyTypeIndex = util::BitPack32::Field<1, 4, KeyType>; @@ -76,6 +83,11 @@ namespace ams::spl::smc { [SealKey_ImportEsClientCertKey] = { 0x89, 0x96, 0x43, 0x9A, 0x7C, 0xD5, 0x59, 0x55, 0x24, 0xD5, 0x24, 0x18, 0xAB, 0x6C, 0x04, 0x61 }, }; + constexpr const u8 EsCommonKeySources[EsCommonKeyType_Count][AesKeySize] = { + [EsCommonKeyType_TitleKey] = { 0x1E, 0xDC, 0x7B, 0x3B, 0x60, 0xE6, 0xB4, 0xD8, 0x78, 0xB8, 0x17, 0x15, 0x98, 0x5E, 0x62, 0x9B }, + [EsCommonKeyType_ArchiveKey] = { 0x3B, 0x78, 0xF2, 0x61, 0x0F, 0x9D, 0x5A, 0xE2, 0x7B, 0x4E, 0x45, 0xAF, 0xCB, 0x0B, 0x67, 0x4D }, + }; + constexpr u64 InvalidAsyncKey = 0; constinit os::SdkMutex g_crypto_lock; @@ -146,6 +158,22 @@ namespace ams::spl::smc { constinit KeySlotManager g_key_slot_manager; + void DecryptWithEsCommonKey(void *dst, size_t dst_size, const void *src, size_t src_size, EsCommonKeyType type, int generation) { + /* Validate pre-conditions. */ + AMS_ASSERT(dst_size == crypto::AesEncryptor128::KeySize); + AMS_ASSERT(src_size == crypto::AesEncryptor128::KeySize); + AMS_ASSERT(0 <= type && type < EsCommonKeyType_Count); + + /* Prepare the master key for the generation. */ + const int slot = g_key_slot_manager.PrepareMasterKey(generation); + + /* Derive the es common key. */ + g_key_slot_manager.SetEncryptedAesKey128(pkg1::AesKeySlot_Smc, slot, EsCommonKeySources[type], crypto::AesEncryptor128::KeySize); + + /* Decrypt the input using the common key. */ + g_key_slot_manager.DecryptAes128(dst, dst_size, pkg1::AesKeySlot_Smc, src, src_size); + } + } void PresetInternalKey(const AesKey *key, u32 generation, bool device) { @@ -243,7 +271,7 @@ namespace ams::spl::smc { 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(pkg1_generation < pkg1::KeyGeneration_Max, InvalidArgument); } SMC_R_UNLESS(0 <= key_type && key_type < KeyType_Count, InvalidArgument); @@ -411,19 +439,22 @@ namespace ams::spl::smc { 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]); - //} + Result PrepareCommonEsTitleKey(AccessKey *out, const KeySource &source, u32 generation) { + /* Decode arguments. */ + const int pkg1_gen = std::max(pkg1::KeyGeneration_1_0_0, static_cast(generation) - 1); + + /* Validate arguments. */ + SMC_R_UNLESS(pkg1_gen < pkg1::KeyGeneration_Max, InvalidArgument); + + /* Derive the key. */ + u8 key[crypto::AesEncryptor128::KeySize]; + DecryptWithEsCommonKey(key, sizeof(key), std::addressof(source), sizeof(source), EsCommonKeyType_TitleKey, pkg1_gen); + + /* Copy the access key to the output. */ + std::memcpy(out, key, sizeof(key)); + + return smc::Result::Success; + } // ///* Deprecated functions. */ diff --git a/libraries/libstratosphere/source/spl/spl_api.os.generic.cpp b/libraries/libstratosphere/source/spl/spl_api.os.generic.cpp index 4fee730d2..0009ef36f 100644 --- a/libraries/libstratosphere/source/spl/spl_api.os.generic.cpp +++ b/libraries/libstratosphere/source/spl/spl_api.os.generic.cpp @@ -103,4 +103,10 @@ namespace ams::spl { R_RETURN(impl::LoadPreparedAesKey(slot, access_key)); } + Result PrepareCommonEsTitleKey(AccessKey *out, const void *key_source, const size_t key_source_size, int generation) { + AMS_ASSERT(key_source_size == sizeof(KeySource)); + + R_RETURN(impl::PrepareCommonEsTitleKey(out, *static_cast(key_source), generation)); + } + } diff --git a/libraries/libvapours/include/vapours/results/fs_results.hpp b/libraries/libvapours/include/vapours/results/fs_results.hpp index 1b9ed6057..0df09509d 100644 --- a/libraries/libvapours/include/vapours/results/fs_results.hpp +++ b/libraries/libvapours/include/vapours/results/fs_results.hpp @@ -399,6 +399,8 @@ namespace ams::fs { R_DEFINE_ERROR_RESULT(PermissionDeniedForCreateHostFileSystem, 6403); R_DEFINE_ERROR_RESULT(PortAcceptableCountLimited, 6450); + R_DEFINE_ERROR_RESULT(NcaExternalKeyUnregistered, 6451); + R_DEFINE_ERROR_RESULT(NcaExternalKeyInconsistent, 6452); R_DEFINE_ERROR_RESULT(NeedFlush, 6454); R_DEFINE_ERROR_RESULT(FileNotClosed, 6455); R_DEFINE_ERROR_RESULT(DirectoryNotClosed, 6456);