mirror of
https://github.com/Atmosphere-NX/Atmosphere.git
synced 2024-11-30 18:34:38 +01:00
fssrv: add ExternalKeyManager (rights-id crypto) for hac2l
This commit is contained in:
parent
f8409680c2
commit
502a89e1e3
@ -36,3 +36,4 @@
|
|||||||
#include <stratosphere/fssrv/fssrv_program_registry_impl.hpp>
|
#include <stratosphere/fssrv/fssrv_program_registry_impl.hpp>
|
||||||
#include <stratosphere/fssrv/fssrv_program_registry_service.hpp>
|
#include <stratosphere/fssrv/fssrv_program_registry_service.hpp>
|
||||||
#include <stratosphere/fssrv/fssrv_nca_file_system_service_impl.hpp>
|
#include <stratosphere/fssrv/fssrv_nca_file_system_service_impl.hpp>
|
||||||
|
#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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
#include <vapours.hpp>
|
||||||
|
#include <stratosphere/ncm/ncm_ids.hpp>
|
||||||
|
#include <stratosphere/fs/fs_rights_id.hpp>
|
||||||
|
#include <stratosphere/fs/impl/fs_newable.hpp>
|
||||||
|
#include <stratosphere/spl/spl_types.hpp>
|
||||||
|
|
||||||
|
namespace ams::fssrv::impl {
|
||||||
|
|
||||||
|
class ExternalKeyEntry : public util::IntrusiveListBaseNode<ExternalKeyEntry>, 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<ExternalKeyEntry>::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());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
@ -105,4 +105,6 @@ namespace ams::spl {
|
|||||||
|
|
||||||
Result LoadPreparedAesKey(s32 slot, const AccessKey &access_key);
|
Result LoadPreparedAesKey(s32 slot, const AccessKey &access_key);
|
||||||
|
|
||||||
|
Result PrepareCommonEsTitleKey(AccessKey *out, const void *key_source, const size_t key_source_size, int generation);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -111,7 +111,7 @@ namespace ams::fssystem {
|
|||||||
u8 m_encrypted_key[KeySize];
|
u8 m_encrypted_key[KeySize];
|
||||||
public:
|
public:
|
||||||
AesCtrStorageExternal(std::shared_ptr<fs::IStorage> 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) {
|
AesCtrStorageExternal(std::shared_ptr<fs::IStorage> 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(enc_key_size == KeySize);
|
||||||
AMS_ASSERT(iv != nullptr);
|
AMS_ASSERT(iv != nullptr);
|
||||||
AMS_ASSERT(iv_size == IvSize);
|
AMS_ASSERT(iv_size == IvSize);
|
||||||
|
@ -47,6 +47,13 @@ namespace ams::spl::smc {
|
|||||||
KeyType_Count,
|
KeyType_Count,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum EsCommonKeyType {
|
||||||
|
EsCommonKeyType_TitleKey = 0,
|
||||||
|
EsCommonKeyType_ArchiveKey = 1,
|
||||||
|
|
||||||
|
EsCommonKeyType_Count,
|
||||||
|
};
|
||||||
|
|
||||||
struct GenerateAesKekOption {
|
struct GenerateAesKekOption {
|
||||||
using IsDeviceUnique = util::BitPack32::Field<0, 1, bool>;
|
using IsDeviceUnique = util::BitPack32::Field<0, 1, bool>;
|
||||||
using KeyTypeIndex = util::BitPack32::Field<1, 4, KeyType>;
|
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 },
|
[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;
|
constexpr u64 InvalidAsyncKey = 0;
|
||||||
|
|
||||||
constinit os::SdkMutex g_crypto_lock;
|
constinit os::SdkMutex g_crypto_lock;
|
||||||
@ -146,6 +158,22 @@ namespace ams::spl::smc {
|
|||||||
|
|
||||||
constinit KeySlotManager g_key_slot_manager;
|
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) {
|
void PresetInternalKey(const AesKey *key, u32 generation, bool device) {
|
||||||
@ -243,7 +271,7 @@ namespace ams::spl::smc {
|
|||||||
if (is_device_unique) {
|
if (is_device_unique) {
|
||||||
SMC_R_UNLESS(pkg1::IsValidDeviceUniqueKeyGeneration(pkg1_generation), InvalidArgument);
|
SMC_R_UNLESS(pkg1::IsValidDeviceUniqueKeyGeneration(pkg1_generation), InvalidArgument);
|
||||||
} else {
|
} 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);
|
SMC_R_UNLESS(0 <= key_type && key_type < KeyType_Count, InvalidArgument);
|
||||||
@ -411,19 +439,22 @@ namespace ams::spl::smc {
|
|||||||
return smc::Result::Success;
|
return smc::Result::Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
//Result PrepareCommonEsTitleKey(AccessKey *out, const KeySource &source, u32 generation) {
|
Result PrepareCommonEsTitleKey(AccessKey *out, const KeySource &source, u32 generation) {
|
||||||
// svc::SecureMonitorArguments args;
|
/* Decode arguments. */
|
||||||
//
|
const int pkg1_gen = std::max<int>(pkg1::KeyGeneration_1_0_0, static_cast<int>(generation) - 1);
|
||||||
// args.r[0] = static_cast<u64>(FunctionId::PrepareCommonEsTitleKey);
|
|
||||||
// args.r[1] = source.data64[0];
|
/* Validate arguments. */
|
||||||
// args.r[2] = source.data64[1];
|
SMC_R_UNLESS(pkg1_gen < pkg1::KeyGeneration_Max, InvalidArgument);
|
||||||
// args.r[3] = generation;
|
|
||||||
// svc::CallSecureMonitor(std::addressof(args));
|
/* Derive the key. */
|
||||||
//
|
u8 key[crypto::AesEncryptor128::KeySize];
|
||||||
// out->data64[0] = args.r[1];
|
DecryptWithEsCommonKey(key, sizeof(key), std::addressof(source), sizeof(source), EsCommonKeyType_TitleKey, pkg1_gen);
|
||||||
// out->data64[1] = args.r[2];
|
|
||||||
// return static_cast<Result>(args.r[0]);
|
/* Copy the access key to the output. */
|
||||||
//}
|
std::memcpy(out, key, sizeof(key));
|
||||||
|
|
||||||
|
return smc::Result::Success;
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
///* Deprecated functions. */
|
///* Deprecated functions. */
|
||||||
|
@ -103,4 +103,10 @@ namespace ams::spl {
|
|||||||
R_RETURN(impl::LoadPreparedAesKey(slot, access_key));
|
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<const KeySource *>(key_source), generation));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -399,6 +399,8 @@ namespace ams::fs {
|
|||||||
R_DEFINE_ERROR_RESULT(PermissionDeniedForCreateHostFileSystem, 6403);
|
R_DEFINE_ERROR_RESULT(PermissionDeniedForCreateHostFileSystem, 6403);
|
||||||
|
|
||||||
R_DEFINE_ERROR_RESULT(PortAcceptableCountLimited, 6450);
|
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(NeedFlush, 6454);
|
||||||
R_DEFINE_ERROR_RESULT(FileNotClosed, 6455);
|
R_DEFINE_ERROR_RESULT(FileNotClosed, 6455);
|
||||||
R_DEFINE_ERROR_RESULT(DirectoryNotClosed, 6456);
|
R_DEFINE_ERROR_RESULT(DirectoryNotClosed, 6456);
|
||||||
|
Loading…
Reference in New Issue
Block a user