fs: update nca drivers (and dependents/callees) for 14.0.0 changes

This commit is contained in:
Michael Scire 2022-03-25 09:48:24 -07:00
parent 20e53fcd82
commit ec44eaa263
24 changed files with 1491 additions and 457 deletions

View file

@ -39,6 +39,9 @@
#include <stratosphere/fssystem/fssystem_crypto_configuration.hpp>
#include <stratosphere/fssystem/fssystem_compression_configuration.hpp>
#include <stratosphere/fssystem/fssystem_aes_ctr_counter_extended_storage.hpp>
#include <stratosphere/fssystem/fssystem_aes_ctr_storage_external.hpp>
#include <stratosphere/fssystem/fssystem_aes_xts_storage_external.hpp>
#include <stratosphere/fssystem/fssystem_switch_storage.hpp>
#include <stratosphere/fssystem/buffers/fssystem_buffer_manager_utils.hpp>
#include <stratosphere/fssystem/buffers/fssystem_file_system_buffer_manager.hpp>
#include <stratosphere/fssystem/fssystem_pooled_buffer.hpp>

View file

@ -32,7 +32,7 @@ namespace ams::fssystem {
using IAllocator = BucketTree::IAllocator;
using DecryptFunction = void(*)(void *dst, size_t dst_size, s32 index, const void *enc_key, size_t enc_key_size, const void *iv, size_t iv_size, const void *src, size_t src_size);
using DecryptFunction = void(*)(void *dst, size_t dst_size, u8 index, u8 gen, const void *enc_key, size_t enc_key_size, const void *iv, size_t iv_size, const void *src, size_t src_size);
class IDecryptor {
public:
@ -72,7 +72,7 @@ namespace ams::fssystem {
return BucketTree::QueryEntryStorageSize(NodeSize, sizeof(Entry), entry_count);
}
static Result CreateExternalDecryptor(std::unique_ptr<IDecryptor> *out, DecryptFunction func, s32 key_index);
static Result CreateExternalDecryptor(std::unique_ptr<IDecryptor> *out, DecryptFunction func, s32 key_index, s32 key_generation);
static Result CreateSoftwareDecryptor(std::unique_ptr<IDecryptor> *out);
private:
BucketTree m_table;

View file

@ -0,0 +1,50 @@
/*
* 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/fs/fs_istorage.hpp>
#include <stratosphere/fs/impl/fs_newable.hpp>
#include <stratosphere/fssystem/fssystem_nca_file_system_driver.hpp>
namespace ams::fssystem {
/* ACCURATE_TO_VERSION: 14.3.0.0 */
class AesCtrStorageExternal : public ::ams::fs::IStorage, public ::ams::fs::impl::Newable {
NON_COPYABLE(AesCtrStorageExternal);
NON_MOVEABLE(AesCtrStorageExternal);
public:
static constexpr size_t BlockSize = crypto::Aes128CtrEncryptor::BlockSize;
static constexpr size_t KeySize = crypto::Aes128CtrEncryptor::KeySize;
static constexpr size_t IvSize = crypto::Aes128CtrEncryptor::IvSize;
private:
std::shared_ptr<fs::IStorage> m_base_storage;
u8 m_iv[IvSize];
DecryptAesCtrFunction m_decrypt_function;
s32 m_key_index;
s32 m_key_generation;
u8 m_encrypted_key[KeySize];
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, s32 kgen);
virtual Result Read(s64 offset, void *buffer, size_t size) override;
virtual Result OperateRange(void *dst, size_t dst_size, fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) override;
virtual Result GetSize(s64 *out) override;
virtual Result Flush() override;
virtual Result Write(s64 offset, const void *buffer, size_t size) override;
virtual Result SetSize(s64 size) override;
};
}

View file

@ -0,0 +1,54 @@
/*
* 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/fs/fs_istorage.hpp>
#include <stratosphere/fs/impl/fs_newable.hpp>
#include <stratosphere/fssystem/fssystem_nca_file_system_driver.hpp>
namespace ams::fssystem {
/* ACCURATE_TO_VERSION: 14.3.0.0 */
template<fs::PointerToStorage BasePointer>
class AesXtsStorageExternal : public ::ams::fs::IStorage, public ::ams::fs::impl::Newable {
NON_COPYABLE(AesXtsStorageExternal);
NON_MOVEABLE(AesXtsStorageExternal);
public:
static constexpr size_t AesBlockSize = crypto::Aes128XtsEncryptor::BlockSize;
static constexpr size_t KeySize = crypto::Aes128XtsEncryptor::KeySize;
static constexpr size_t IvSize = crypto::Aes128XtsEncryptor::IvSize;
private:
BasePointer m_base_storage;
char m_key[2][KeySize];
char m_iv[IvSize];
const size_t m_block_size;
CryptAesXtsFunction m_encrypt_function;
CryptAesXtsFunction m_decrypt_function;
public:
AesXtsStorageExternal(BasePointer bs, const void *key1, const void *key2, size_t key_size, const void *iv, size_t iv_size, size_t block_size, CryptAesXtsFunction ef, CryptAesXtsFunction df);
virtual Result Read(s64 offset, void *buffer, size_t size) override;
virtual Result OperateRange(void *dst, size_t dst_size, fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) override;
virtual Result GetSize(s64 *out) override;
virtual Result Flush() override;
virtual Result Write(s64 offset, const void *buffer, size_t size) override;
virtual Result SetSize(s64 size) override;
};
using AesXtsStorageExternalByPointer = AesXtsStorageExternal<fs::IStorage *>;
using AesXtsStorageExternalBySharedPointer = AesXtsStorageExternal<std::shared_ptr<fs::IStorage>>;
}

View file

@ -99,13 +99,13 @@ namespace ams::fssystem {
size_t m_verification_block_shift;
s32 m_flags;
s32 m_buffer_level;
fs::StorageType m_storage_type;
BlockCacheManager m_block_cache_manager;
bool m_is_writable;
public:
BlockCacheBufferedStorage();
virtual ~BlockCacheBufferedStorage() override;
Result Initialize(fs::IBufferManager *bm, os::SdkRecursiveMutex *mtx, IStorage *data, s64 data_size, size_t verif_block_size, s32 max_cache_entries, bool is_real_data, s8 buffer_level, bool is_keep_burst_mode, fs::StorageType storage_type);
Result Initialize(fs::IBufferManager *bm, os::SdkRecursiveMutex *mtx, IStorage *data, s64 data_size, size_t verif_block_size, s32 max_cache_entries, bool is_real_data, s8 buffer_level, bool is_keep_burst_mode, bool is_writable);
void Finalize();
virtual Result Read(s64 offset, void *buffer, size_t size) override;

View file

@ -24,7 +24,7 @@
namespace ams::fssystem {
/* ACCURATE_TO_VERSION: Unknown */
/* ACCURATE_TO_VERSION: 14.3.0.0 */
struct HierarchicalIntegrityVerificationLevelInformation {
fs::Int64 offset;
@ -151,20 +151,25 @@ namespace ams::fssystem {
os::SdkRecursiveMutex *m_mutex;
IntegrityVerificationStorage m_verify_storages[MaxLayers - 1];
BlockCacheBufferedStorage m_buffer_storages[MaxLayers - 1];
os::Semaphore *m_read_semaphore;
os::Semaphore *m_write_semaphore;
s64 m_data_size;
s32 m_max_layers;
bool m_is_written_for_rollback;
public:
HierarchicalIntegrityVerificationStorage() : m_buffers(nullptr), m_mutex(nullptr), m_data_size(-1), m_is_written_for_rollback(false) { /* ... */ }
HierarchicalIntegrityVerificationStorage() : m_buffers(nullptr), m_mutex(nullptr), m_data_size(-1) { /* ... */ }
virtual ~HierarchicalIntegrityVerificationStorage() override { this->Finalize(); }
Result Initialize(const HierarchicalIntegrityVerificationInformation &info, HierarchicalStorageInformation storage, FileSystemBufferManagerSet *bufs, IHash256GeneratorFactory *hgf, os::SdkRecursiveMutex *mtx, fs::StorageType storage_type);
Result Initialize(const HierarchicalIntegrityVerificationInformation &info, HierarchicalStorageInformation storage, FileSystemBufferManagerSet *bufs, IHash256GeneratorFactory *hgf, bool hash_salt_enabled, os::SdkRecursiveMutex *mtx, os::Semaphore *read_sema, os::Semaphore *write_sema, int max_data_cache_entries, int max_hash_cache_entries, s8 buffer_level, bool is_writable, bool allow_cleared_blocks);
Result Initialize(const HierarchicalIntegrityVerificationInformation &info, HierarchicalStorageInformation storage, FileSystemBufferManagerSet *bufs, IHash256GeneratorFactory *hgf, bool hash_salt_enabled, os::SdkRecursiveMutex *mtx, int max_data_cache_entries, int max_hash_cache_entries, s8 buffer_level, bool is_writable, bool allow_cleared_blocks) {
R_RETURN(this->Initialize(info, storage, bufs, hgf, hash_salt_enabled, mtx, nullptr, nullptr, max_data_cache_entries, max_hash_cache_entries, buffer_level, is_writable, allow_cleared_blocks));
}
void Finalize();
virtual Result Read(s64 offset, void *buffer, size_t size) override;
virtual Result Write(s64 offset, const void *buffer, size_t size) override;
virtual Result SetSize(s64 size) override { AMS_UNUSED(size); return fs::ResultUnsupportedSetSizeForHierarchicalIntegrityVerificationStorage(); }
virtual Result SetSize(s64 size) override { AMS_UNUSED(size); R_THROW(fs::ResultUnsupportedSetSizeForHierarchicalIntegrityVerificationStorage()); }
virtual Result GetSize(s64 *out) override;
virtual Result Flush() override;
@ -179,10 +184,6 @@ namespace ams::fssystem {
return m_data_size >= 0;
}
bool IsWrittenForRollback() const {
return m_is_written_for_rollback;
}
FileSystemBufferManagerSet *GetBuffers() {
return m_buffers;
}
@ -201,6 +202,10 @@ namespace ams::fssystem {
fs::SubStorage GetL1HashStorage() {
return fs::SubStorage(std::addressof(m_buffer_storages[m_max_layers - 3]), 0, util::DivideUp(m_data_size, this->GetL1HashVerificationBlockSize()));
}
public:
static constexpr s8 GetDefaultDataCacheBufferLevel(u32 max_layers) {
return 16 + max_layers - 2;
}
};
}

View file

@ -39,33 +39,33 @@ namespace ams::fssystem {
IntegrityRomFsStorage() : m_mutex() { /* ... */ }
virtual ~IntegrityRomFsStorage() override { this->Finalize(); }
Result Initialize(HierarchicalIntegrityVerificationInformation level_hash_info, Hash master_hash, HierarchicalIntegrityVerificationStorage::HierarchicalStorageInformation storage_info, fs::IBufferManager *bm, IHash256GeneratorFactory *hgf);
Result Initialize(HierarchicalIntegrityVerificationInformation level_hash_info, Hash master_hash, HierarchicalIntegrityVerificationStorage::HierarchicalStorageInformation storage_info, fs::IBufferManager *bm, int max_data_cache_entries, int max_hash_cache_entries, s8 buffer_level, IHash256GeneratorFactory *hgf);
void Finalize();
virtual Result Read(s64 offset, void *buffer, size_t size) override {
return m_integrity_storage.Read(offset, buffer, size);
R_RETURN(m_integrity_storage.Read(offset, buffer, size));
}
virtual Result Write(s64 offset, const void *buffer, size_t size) override {
return m_integrity_storage.Write(offset, buffer, size);
R_RETURN(m_integrity_storage.Write(offset, buffer, size));
}
virtual Result SetSize(s64 size) override { AMS_UNUSED(size); return fs::ResultUnsupportedSetSizeForIntegrityRomFsStorage(); }
virtual Result SetSize(s64 size) override { AMS_UNUSED(size); R_THROW(fs::ResultUnsupportedSetSizeForIntegrityRomFsStorage()); }
virtual Result GetSize(s64 *out) override {
return m_integrity_storage.GetSize(out);
R_RETURN(m_integrity_storage.GetSize(out));
}
virtual Result Flush() override {
return m_integrity_storage.Flush();
R_RETURN(m_integrity_storage.Flush());
}
virtual Result OperateRange(void *dst, size_t dst_size, fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) override {
return m_integrity_storage.OperateRange(dst, dst_size, op_id, offset, size, src, src_size);
R_RETURN(m_integrity_storage.OperateRange(dst, dst_size, op_id, offset, size, src, src_size));
}
Result Commit() {
return m_integrity_storage.Commit();
R_RETURN(m_integrity_storage.Commit());
}
FileSystemBufferManagerSet *GetBuffers() {

View file

@ -23,7 +23,7 @@
namespace ams::fssystem {
/* ACCURATE_TO_VERSION: Unknown */
/* ACCURATE_TO_VERSION: 14.3.0.0 */
class IntegrityVerificationStorage : public ::ams::fs::IStorage {
NON_COPYABLE(IntegrityVerificationStorage);
@ -43,15 +43,16 @@ namespace ams::fssystem {
s64 m_upper_layer_verification_block_size;
s64 m_upper_layer_verification_block_order;
fs::IBufferManager *m_buffer_manager;
fs::HashSalt m_salt;
util::optional<fs::HashSalt> m_salt;
bool m_is_real_data;
fs::StorageType m_storage_type;
fssystem::IHash256GeneratorFactory *m_hash_generator_factory;
bool m_is_writable;
bool m_allow_cleared_blocks;
public:
IntegrityVerificationStorage() : m_verification_block_size(0), m_verification_block_order(0), m_upper_layer_verification_block_size(0), m_upper_layer_verification_block_order(0), m_buffer_manager(nullptr) { /* ... */ }
IntegrityVerificationStorage() : m_verification_block_size(0), m_verification_block_order(0), m_upper_layer_verification_block_size(0), m_upper_layer_verification_block_order(0), m_buffer_manager(nullptr), m_salt(util::nullopt) { /* ... */ }
virtual ~IntegrityVerificationStorage() override { this->Finalize(); }
Result Initialize(fs::SubStorage hs, fs::SubStorage ds, s64 verif_block_size, s64 upper_layer_verif_block_size, fs::IBufferManager *bm, fssystem::IHash256GeneratorFactory *hgf, const fs::HashSalt &salt, bool is_real_data, fs::StorageType storage_type);
Result Initialize(fs::SubStorage hs, fs::SubStorage ds, s64 verif_block_size, s64 upper_layer_verif_block_size, fs::IBufferManager *bm, fssystem::IHash256GeneratorFactory *hgf, const util::optional<fs::HashSalt> &salt, bool is_real_data, bool is_writable, bool allow_cleared_blocks);
void Finalize();
virtual Result Read(s64 offset, void *buffer, size_t size) override;

View file

@ -25,7 +25,7 @@
namespace ams::fssystem {
/* ACCURATE_TO_VERSION: 13.4.0.0 */
/* ACCURATE_TO_VERSION: 14.3.0.0 */
class CompressedStorage;
class AesCtrCounterExtendedStorage;
@ -34,8 +34,11 @@ namespace ams::fssystem {
struct NcaCryptoConfiguration;
using KeyGenerationFunction = void (*)(void *dst_key, size_t dst_key_size, const void *src_key, size_t src_key_size, s32 key_type, const NcaCryptoConfiguration &cfg);
using DecryptAesCtrFunction = void (*)(void *dst, size_t dst_size, s32 key_type, const void *src_key, size_t src_key_size, const void *iv, size_t iv_size, const void *src, size_t src_size);
using KeyGenerationFunction = void (*)(void *dst_key, size_t dst_key_size, const void *src_key, size_t src_key_size, s32 key_type);
using DecryptAesCtrFunction = void (*)(void *dst, size_t dst_size, u8 key_index, u8 key_generation, const void *src_key, size_t src_key_size, const void *iv, size_t iv_size, const void *src, size_t src_size);
using CryptAesXtsFunction = Result (*)(void *dst, size_t dst_size, const void *key1, const void *key2, size_t key_size, const void *iv, size_t iv_size, const void *src, size_t src_size);
using VerifySign1Function = bool (*)(const void *sig, size_t sig_size, const void *data, size_t data_size, u8 generation, const NcaCryptoConfiguration &cfg);
struct NcaCryptoConfiguration {
static constexpr size_t Rsa2048KeyModulusSize = crypto::Rsa2048PssSha256Verifier::ModulusSize;
@ -49,6 +52,8 @@ namespace ams::fssystem {
static constexpr s32 KeyAreaEncryptionKeyIndexCount = 3;
static constexpr s32 HeaderEncryptionKeyCount = 2;
static constexpr u8 KeyAreaEncryptionKeyIndexZeroKey = 0xFF;
static constexpr size_t KeyGenerationMax = 32;
const u8 *header_1_sign_key_moduli[Header1SignatureKeyGenerationMax + 1];
@ -57,9 +62,13 @@ namespace ams::fssystem {
u8 header_encryption_key_source[Aes128KeySize];
u8 header_encrypted_encryption_keys[HeaderEncryptionKeyCount][Aes128KeySize];
KeyGenerationFunction generate_key;
CryptAesXtsFunction decrypt_aes_xts_external;
CryptAesXtsFunction encrypt_aes_xts_external;
DecryptAesCtrFunction decrypt_aes_ctr;
DecryptAesCtrFunction decrypt_aes_ctr_external;
VerifySign1Function verify_sign1;
bool is_plaintext_header_available;
bool is_available_sw_key;
#if !defined(ATMOSPHERE_BOARD_NINTENDO_NX)
bool is_unsigned_header_available_for_host_tool;
@ -72,30 +81,35 @@ namespace ams::fssystem {
};
static_assert(util::is_pod<NcaCompressionConfiguration>::value);
constexpr inline s32 KeyAreaEncryptionKeyCount = NcaCryptoConfiguration::KeyAreaEncryptionKeyIndexCount * NcaCryptoConfiguration::KeyGenerationMax;
enum class KeyType : s32 {
ZeroKey = -2,
InvalidKey = -1,
NcaHeaderKey1 = KeyAreaEncryptionKeyCount + 0,
NcaHeaderKey2 = KeyAreaEncryptionKeyCount + 1,
NcaExternalKey = KeyAreaEncryptionKeyCount + 2,
SaveDataDeviceUniqueMac = KeyAreaEncryptionKeyCount + 3,
SaveDataSeedUniqueMac = KeyAreaEncryptionKeyCount + 4,
SaveDataTransferMac = KeyAreaEncryptionKeyCount + 5,
};
constexpr inline bool IsInvalidKeyTypeValue(s32 key_type) {
return key_type < 0;
}
constexpr inline s32 GetKeyTypeValue(u8 key_index, u8 key_generation) {
constexpr s32 InvalidKeyTypeValue = -1;
static_assert(IsInvalidKeyTypeValue(InvalidKeyTypeValue));
if (key_index == NcaCryptoConfiguration::KeyAreaEncryptionKeyIndexZeroKey) {
return util::ToUnderlying(KeyType::ZeroKey);
}
if (key_index >= NcaCryptoConfiguration::KeyAreaEncryptionKeyIndexCount) {
return InvalidKeyTypeValue;
return util::ToUnderlying(KeyType::InvalidKey);
}
return NcaCryptoConfiguration::KeyAreaEncryptionKeyIndexCount * key_generation + key_index;
}
constexpr inline s32 KeyAreaEncryptionKeyCount = NcaCryptoConfiguration::KeyAreaEncryptionKeyIndexCount * NcaCryptoConfiguration::KeyGenerationMax;
enum class KeyType : s32 {
NcaHeaderKey = KeyAreaEncryptionKeyCount + 0,
NcaExternalKey = KeyAreaEncryptionKeyCount + 1,
SaveDataDeviceUniqueMac = KeyAreaEncryptionKeyCount + 2,
SaveDataSeedUniqueMac = KeyAreaEncryptionKeyCount + 3,
};
class NcaReader : public ::ams::fs::impl::Newable {
NON_COPYABLE(NcaReader);
NON_MOVEABLE(NcaReader);
@ -108,6 +122,7 @@ namespace ams::fssystem {
DecryptAesCtrFunction m_decrypt_aes_ctr;
DecryptAesCtrFunction m_decrypt_aes_ctr_external;
bool m_is_software_aes_prioritized;
bool m_is_available_sw_key;
NcaHeader::EncryptionType m_header_encryption_type;
bool m_is_header_sign1_signature_valid;
GetDecompressorFunction m_get_decompressor;
@ -144,6 +159,7 @@ namespace ams::fssystem {
bool HasInternalDecryptionKeyForAesHw() const;
bool IsSoftwareAesPrioritized() const;
void PrioritizeSoftwareAes();
bool IsAvailableSwKey() const;
bool HasExternalDecryptionKey() const;
const void *GetExternalDecryptionKey() const;
void SetExternalDecryptionKey(const void *src, size_t size);
@ -191,12 +207,27 @@ namespace ams::fssystem {
NcaPatchInfo &GetPatchInfo();
const NcaPatchInfo &GetPatchInfo() const;
const NcaAesCtrUpperIv GetAesCtrUpperIv() const;
bool IsSkipLayerHashEncryption() const;
Result GetHashTargetOffset(s64 *out) const;
bool ExistsSparseLayer() const;
NcaSparseInfo &GetSparseInfo();
const NcaSparseInfo &GetSparseInfo() const;
bool ExistsCompressionLayer() const;
NcaCompressionInfo &GetCompressionInfo();
const NcaCompressionInfo &GetCompressionInfo() const;
bool ExistsPatchMetaHashLayer() const;
NcaMetaDataHashDataInfo &GetPatchMetaDataHashDataInfo();
const NcaMetaDataHashDataInfo &GetPatchMetaDataHashDataInfo() const;
NcaFsHeader::MetaDataHashType GetPatchMetaHashType() const;
bool ExistsSparseMetaHashLayer() const;
NcaMetaDataHashDataInfo &GetSparseMetaDataHashDataInfo();
const NcaMetaDataHashDataInfo &GetSparseMetaDataHashDataInfo() const;
NcaFsHeader::MetaDataHashType GetSparseMetaHashType() const;
};
class NcaFileSystemDriver : public ::ams::fs::impl::Newable {
@ -224,6 +255,9 @@ namespace ams::fssystem {
std::shared_ptr<fs::IStorage> compressed_storage_meta_storage;
std::shared_ptr<fssystem::CompressedStorage> compressed_storage;
std::shared_ptr<fs::IStorage> patch_layer_info_storage;
std::shared_ptr<fs::IStorage> sparse_layer_info_storage;
/* For tools. */
std::shared_ptr<fs::IStorage> external_original_storage;
};
@ -282,15 +316,24 @@ namespace ams::fssystem {
Result CreateSparseStorageCore(std::shared_ptr<fssystem::SparseStorage> *out, std::shared_ptr<fs::IStorage> base_storage, s64 base_size, std::shared_ptr<fs::IStorage> meta_storage, const NcaSparseInfo &sparse_info, bool external_info);
Result CreateSparseStorage(std::shared_ptr<fs::IStorage> *out, s64 *out_fs_data_offset, std::shared_ptr<fssystem::SparseStorage> *out_sparse_storage, std::shared_ptr<fs::IStorage> *out_meta_storage, s32 index, const NcaAesCtrUpperIv &upper_iv, const NcaSparseInfo &sparse_info);
Result CreateAesCtrExMetaStorage(std::shared_ptr<fs::IStorage> *out, std::shared_ptr<fs::IStorage> base_storage, s64 offset, const NcaAesCtrUpperIv &upper_iv, const NcaPatchInfo &patch_info);
Result CreateSparseStorageMetaStorageWithVerification(std::shared_ptr<fs::IStorage> *out, std::shared_ptr<fs::IStorage> *out_verification, std::shared_ptr<fs::IStorage> base_storage, s64 offset, const NcaAesCtrUpperIv &upper_iv, const NcaSparseInfo &sparse_info, const NcaMetaDataHashDataInfo &meta_data_hash_data_info, IHash256GeneratorFactory *hgf);
Result CreateSparseStorageWithVerification(std::shared_ptr<fs::IStorage> *out, s64 *out_fs_data_offset, std::shared_ptr<fssystem::SparseStorage> *out_sparse_storage, std::shared_ptr<fs::IStorage> *out_meta_storage, std::shared_ptr<fs::IStorage> *out_verification, s32 index, const NcaAesCtrUpperIv &upper_iv, const NcaSparseInfo &sparse_info, const NcaMetaDataHashDataInfo &meta_data_hash_data_info, NcaFsHeader::MetaDataHashType meta_data_hash_type);
Result CreateAesCtrExStorageMetaStorage(std::shared_ptr<fs::IStorage> *out, std::shared_ptr<fs::IStorage> base_storage, s64 offset, NcaFsHeader::EncryptionType encryption_type, const NcaAesCtrUpperIv &upper_iv, const NcaPatchInfo &patch_info);
Result CreateAesCtrExStorage(std::shared_ptr<fs::IStorage> *out, std::shared_ptr<fssystem::AesCtrCounterExtendedStorage> *out_ext, std::shared_ptr<fs::IStorage> base_storage, std::shared_ptr<fs::IStorage> meta_storage, s64 counter_offset, const NcaAesCtrUpperIv &upper_iv, const NcaPatchInfo &patch_info);
Result CreateIndirectStorageMetaStorage(std::shared_ptr<fs::IStorage> *out, std::shared_ptr<fs::IStorage> base_storage, const NcaPatchInfo &patch_info);
Result CreateIndirectStorage(std::shared_ptr<fs::IStorage> *out, std::shared_ptr<fssystem::IndirectStorage> *out_ind, std::shared_ptr<fs::IStorage> base_storage, std::shared_ptr<fs::IStorage> original_data_storage, std::shared_ptr<fs::IStorage> meta_storage, const NcaPatchInfo &patch_info);
Result CreatePatchMetaStorage(std::shared_ptr<fs::IStorage> *out_aes_ctr_ex_meta, std::shared_ptr<fs::IStorage> *out_indirect_meta, std::shared_ptr<fs::IStorage> *out_verification, std::shared_ptr<fs::IStorage> base_storage, s64 offset, const NcaAesCtrUpperIv &upper_iv, const NcaPatchInfo &patch_info, const NcaMetaDataHashDataInfo &meta_data_hash_data_info, IHash256GeneratorFactory *hgf);
Result CreateSha256Storage(std::shared_ptr<fs::IStorage> *out, std::shared_ptr<fs::IStorage> base_storage, const NcaFsHeader::HashData::HierarchicalSha256Data &sha256_data, IHash256GeneratorFactory *hgf);
Result CreateIntegrityVerificationStorage(std::shared_ptr<fs::IStorage> *out, std::shared_ptr<fs::IStorage> base_storage, const NcaFsHeader::HashData::IntegrityMetaInfo &meta_info, IHash256GeneratorFactory *hgf);
Result CreateIntegrityVerificationStorageForMeta(std::shared_ptr<fs::IStorage> *out, std::shared_ptr<fs::IStorage> *out_verification, std::shared_ptr<fs::IStorage> base_storage, s64 offset, const NcaMetaDataHashDataInfo &meta_data_hash_data_info, IHash256GeneratorFactory *hgf);
Result CreateIntegrityVerificationStorageImpl(std::shared_ptr<fs::IStorage> *out, std::shared_ptr<fs::IStorage> base_storage, const NcaFsHeader::HashData::IntegrityMetaInfo &meta_info, s64 layer_info_offset, int max_data_cache_entries, int max_hash_cache_entries, s8 buffer_level, IHash256GeneratorFactory *hgf);
Result CreateRegionSwitchStorage(std::shared_ptr<fs::IStorage> *out, const NcaFsHeaderReader *header_reader, std::shared_ptr<fs::IStorage> inside_storage, std::shared_ptr<fs::IStorage> outside_storage);
Result CreateCompressedStorage(std::shared_ptr<fs::IStorage> *out, std::shared_ptr<fssystem::CompressedStorage> *out_cmp, std::shared_ptr<fs::IStorage> *out_meta, std::shared_ptr<fs::IStorage> base_storage, const NcaCompressionInfo &compression_info);
public:

View file

@ -15,13 +15,14 @@
*/
#pragma once
#include <vapours.hpp>
#include <stratosphere/fssystem/fssystem_i_hash_256_generator.hpp>
namespace ams::fssystem {
/* ACCURATE_TO_VERSION: 13.4.0.0 */
/* ACCURATE_TO_VERSION: 14.3.0.0 */
struct Hash {
static constexpr size_t Size = crypto::Sha256Generator::HashSize;
static constexpr size_t Size = IHash256Generator::HashSize;
u8 value[Size];
};
static_assert(sizeof(Hash) == Hash::Size);
@ -185,9 +186,17 @@ namespace ams::fssystem {
struct NcaCompressionInfo {
NcaBucketInfo bucket;
u8 reserved[8];
};
static_assert(util::is_pod<NcaCompressionInfo>::value);
struct NcaMetaDataHashDataInfo {
fs::Int64 offset;
fs::Int64 size;
Hash hash;
};
static_assert(util::is_pod<NcaMetaDataHashDataInfo>::value);
struct NcaFsHeader {
static constexpr size_t Size = 0x200;
static constexpr size_t HashDataOffset = 0x8;
@ -209,6 +218,8 @@ namespace ams::fssystem {
AesXts = 2,
AesCtr = 3,
AesCtrEx = 4,
AesCtrSkipLayerHash = 5,
AesCtrExSkipLayerHash = 6,
};
enum class HashType : u8 {
@ -216,6 +227,14 @@ namespace ams::fssystem {
None = 1,
HierarchicalSha256Hash = 2,
HierarchicalIntegrityHash = 3,
AutoSha3 = 4,
HierarchicalSha3256Hash = 5,
HierarchicalIntegritySha3Hash = 6,
};
enum class MetaDataHashType : u8 {
None = 0,
HierarchicalIntegrity = 1,
};
union HashData {
@ -265,13 +284,34 @@ namespace ams::fssystem {
FsType fs_type;
HashType hash_type;
EncryptionType encryption_type;
u8 reserved[3];
MetaDataHashType meta_data_hash_type;
u8 reserved[2];
HashData hash_data;
NcaPatchInfo patch_info;
NcaAesCtrUpperIv aes_ctr_upper_iv;
NcaSparseInfo sparse_info;
NcaCompressionInfo compression_info;
u8 pad[0x68];
NcaMetaDataHashDataInfo meta_data_hash_data_info;
u8 pad[0x30];
bool IsSkipLayerHashEncryption() const {
return this->encryption_type == EncryptionType::AesCtrSkipLayerHash || this->encryption_type == EncryptionType::AesCtrExSkipLayerHash;
}
Result GetHashTargetOffset(s64 *out) const {
switch (this->hash_type) {
case HashType::HierarchicalIntegrityHash:
case HashType::HierarchicalIntegritySha3Hash:
*out = this->hash_data.integrity_meta_info.level_hash_info.info[this->hash_data.integrity_meta_info.level_hash_info.max_layers - 2].offset;
R_SUCCEED();
case HashType::HierarchicalSha256Hash:
case HashType::HierarchicalSha3256Hash:
*out = this->hash_data.hierarchical_sha256_data.hash_layer_region[this->hash_data.hierarchical_sha256_data.hash_layer_count - 1].offset;
R_SUCCEED();
default:
R_THROW(fs::ResultInvalidNcaFsHeader());
}
}
};
static_assert(sizeof(NcaFsHeader) == NcaFsHeader::Size);
static_assert(util::is_pod<NcaFsHeader>::value);
@ -280,4 +320,11 @@ namespace ams::fssystem {
inline constexpr const size_t NcaFsHeader::HashData::HierarchicalSha256Data::MasterHashOffset = AMS_OFFSETOF(NcaFsHeader, hash_data.hierarchical_sha256_data.fs_data_master_hash);
inline constexpr const size_t NcaFsHeader::HashData::IntegrityMetaInfo::MasterHashOffset = AMS_OFFSETOF(NcaFsHeader, hash_data.integrity_meta_info.master_hash);
struct NcaMetaDataHashData {
s64 layer_info_offset;
NcaFsHeader::HashData::IntegrityMetaInfo integrity_meta_info;
};
static_assert(sizeof(NcaMetaDataHashData) == sizeof(NcaFsHeader::HashData::IntegrityMetaInfo) + sizeof(s64));
static_assert(util::is_pod<NcaMetaDataHashData>::value);
}

View file

@ -0,0 +1,218 @@
/*
* 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/fs/fs_istorage.hpp>
#include <stratosphere/fs/impl/fs_newable.hpp>
namespace ams::fssystem {
/* ACCURATE_TO_VERSION: 14.3.0.0 */
template<typename F>
class SwitchStorage : public ::ams::fs::IStorage, public ::ams::fs::impl::Newable {
NON_COPYABLE(SwitchStorage);
NON_MOVEABLE(SwitchStorage);
private:
std::shared_ptr<fs::IStorage> m_true_storage;
std::shared_ptr<fs::IStorage> m_false_storage;
F m_truth_function;
private:
ALWAYS_INLINE std::shared_ptr<fs::IStorage> &SelectStorage() {
return m_truth_function() ? m_true_storage : m_false_storage;
}
public:
SwitchStorage(std::shared_ptr<fs::IStorage> t, std::shared_ptr<fs::IStorage> f, F func) : m_true_storage(std::move(t)), m_false_storage(std::move(f)), m_truth_function(func) { /* ... */ }
virtual Result Read(s64 offset, void *buffer, size_t size) override {
R_RETURN(this->SelectStorage()->Read(offset, buffer, size));
}
virtual Result OperateRange(void *dst, size_t dst_size, fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) override {
switch (op_id) {
case fs::OperationId::Invalidate:
{
R_TRY(m_true_storage->OperateRange(dst, dst_size, op_id, offset, size, src, src_size));
R_TRY(m_false_storage->OperateRange(dst, dst_size, op_id, offset, size, src, src_size));
R_SUCCEED();
}
case fs::OperationId::QueryRange:
{
R_TRY(this->SelectStorage()->OperateRange(dst, dst_size, op_id, offset, size, src, src_size));
R_SUCCEED();
}
default:
R_THROW(fs::ResultUnsupportedOperateRangeForSwitchStorage());
}
}
virtual Result GetSize(s64 *out) override {
R_RETURN(this->SelectStorage()->GetSize(out));
}
virtual Result Flush() override {
R_RETURN(this->SelectStorage()->Flush());
}
virtual Result Write(s64 offset, const void *buffer, size_t size) override {
R_RETURN(this->SelectStorage()->Write(offset, buffer, size));
}
virtual Result SetSize(s64 size) override {
R_RETURN(this->SelectStorage()->SetSize(size));
}
};
class RegionSwitchStorage : public ::ams::fs::IStorage, public ::ams::fs::impl::Newable {
NON_COPYABLE(RegionSwitchStorage);
NON_MOVEABLE(RegionSwitchStorage);
public:
struct Region {
s64 offset;
s64 size;
};
private:
std::shared_ptr<fs::IStorage> m_inside_region_storage;
std::shared_ptr<fs::IStorage> m_outside_region_storage;
Region m_region;
public:
RegionSwitchStorage(std::shared_ptr<fs::IStorage> &&i, std::shared_ptr<fs::IStorage> &&o, Region r) : m_inside_region_storage(std::move(i)), m_outside_region_storage(std::move(o)), m_region(r) {
/* ... */
}
virtual Result Read(s64 offset, void *buffer, size_t size) override {
/* Process until we're done. */
size_t processed = 0;
while (processed < size) {
/* Process on the appropriate storage. */
s64 cur_size = 0;
if (this->CheckRegions(std::addressof(cur_size), offset + processed, size - processed)) {
R_TRY(m_inside_region_storage->Read(offset + processed, static_cast<u8 *>(buffer) + processed, cur_size));
} else {
R_TRY(m_outside_region_storage->Read(offset + processed, static_cast<u8 *>(buffer) + processed, cur_size));
}
/* Advance. */
processed += cur_size;
}
R_SUCCEED();
}
virtual Result Write(s64 offset, const void *buffer, size_t size) override {
/* Process until we're done. */
size_t processed = 0;
while (processed < size) {
/* Process on the appropriate storage. */
s64 cur_size = 0;
if (this->CheckRegions(std::addressof(cur_size), offset + processed, size - processed)) {
R_TRY(m_inside_region_storage->Write(offset + processed, static_cast<const u8 *>(buffer) + processed, cur_size));
} else {
R_TRY(m_outside_region_storage->Write(offset + processed, static_cast<const u8 *>(buffer) + processed, cur_size));
}
/* Advance. */
processed += cur_size;
}
R_SUCCEED();
}
virtual Result OperateRange(void *dst, size_t dst_size, fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) override {
switch (op_id) {
case fs::OperationId::Invalidate:
{
R_TRY(m_inside_region_storage->OperateRange(dst, dst_size, op_id, offset, size, src, src_size));
R_TRY(m_outside_region_storage->OperateRange(dst, dst_size, op_id, offset, size, src, src_size));
R_SUCCEED();
}
case fs::OperationId::QueryRange:
{
/* Create a new info. */
fs::QueryRangeInfo merged_info;
merged_info.Clear();
/* Process until we're done. */
s64 processed = 0;
while (processed < size) {
/* Process on the appropriate storage. */
s64 cur_size = 0;
fs::QueryRangeInfo cur_info;
if (this->CheckRegions(std::addressof(cur_size), offset + processed, size - processed)) {
R_TRY(m_inside_region_storage->OperateRange(std::addressof(cur_info), sizeof(cur_info), op_id, offset + processed, cur_size, src, src_size));
} else {
R_TRY(m_outside_region_storage->OperateRange(std::addressof(cur_info), sizeof(cur_info), op_id, offset + processed, cur_size, src, src_size));
}
/* Merge the current info. */
merged_info.Merge(cur_info);
/* Advance. */
processed += cur_size;
}
/* Write the merged info. */
*reinterpret_cast<fs::QueryRangeInfo *>(dst) = merged_info;
R_SUCCEED();
}
default:
R_THROW(fs::ResultUnsupportedOperateRangeForRegionSwitchStorage());
}
}
virtual Result GetSize(s64 *out) override {
R_RETURN(m_inside_region_storage->GetSize(out));
}
virtual Result Flush() override {
/* Flush both storages. */
R_TRY(m_inside_region_storage->Flush());
R_TRY(m_outside_region_storage->Flush());
R_SUCCEED();
}
virtual Result SetSize(s64 size) override {
/* Set size for both storages. */
R_TRY(m_inside_region_storage->SetSize(size));
R_TRY(m_outside_region_storage->SetSize(size));
R_SUCCEED();
}
private:
bool CheckRegions(s64 *out_current_size, s64 offset, s64 size) const {
/* Check if our region contains the access. */
if (m_region.offset <= offset) {
if (offset < m_region.offset + m_region.size) {
if (m_region.offset + m_region.size <= offset + size) {
*out_current_size = m_region.offset + m_region.size - offset;
} else {
*out_current_size = size;
}
return true;
} else {
*out_current_size = size;
return false;
}
} else {
if (m_region.offset <= offset + size) {
*out_current_size = m_region.offset - offset;
} else {
*out_current_size = size;
}
return false;
}
}
};
}

View file

@ -128,14 +128,26 @@ namespace ams::fssrv {
/* Key Generation Function */
nullptr,
/* Decrypt Aes Xts Eternal Function */
nullptr,
/* Encrypt Aes Xts Eternal Function */
nullptr,
/* Decrypt Aes Ctr Function */
nullptr,
/* Decrypt Aes Ctr External Function */
nullptr,
/* Verify Sign1 Function */
nullptr,
/* Plaintext Header Available */
false,
/* Software Key Available */
true,
};
constexpr inline const ::ams::fssystem::NcaCryptoConfiguration DefaultNcaCryptoConfigurationProd = {
@ -169,14 +181,26 @@ namespace ams::fssrv {
/* Key Generation Function */
nullptr,
/* Decrypt Aes Xts Eternal Function */
nullptr,
/* Encrypt Aes Xts Eternal Function */
nullptr,
/* Decrypt Aes Ctr Function */
nullptr,
/* Decrypt Aes Ctr External Function */
nullptr,
/* Verify Sign1 Function */
nullptr,
/* Plaintext Header Available */
false,
/* Software Key Available */
true,
};
}

View file

@ -33,8 +33,9 @@ namespace ams::fssystem {
private:
AesCtrCounterExtendedStorage::DecryptFunction m_decrypt_function;
s32 m_key_index;
s32 m_key_generation;
public:
ExternalDecryptor(AesCtrCounterExtendedStorage::DecryptFunction df, s32 key_idx) : m_decrypt_function(df), m_key_index(key_idx) {
ExternalDecryptor(AesCtrCounterExtendedStorage::DecryptFunction df, s32 key_idx, s32 key_gen) : m_decrypt_function(df), m_key_index(key_idx), m_key_generation(key_gen) {
AMS_ASSERT(m_decrypt_function != nullptr);
}
public:
@ -44,8 +45,8 @@ namespace ams::fssystem {
}
Result AesCtrCounterExtendedStorage::CreateExternalDecryptor(std::unique_ptr<IDecryptor> *out, DecryptFunction func, s32 key_index) {
std::unique_ptr<IDecryptor> decryptor = std::make_unique<ExternalDecryptor>(func, key_index);
Result AesCtrCounterExtendedStorage::CreateExternalDecryptor(std::unique_ptr<IDecryptor> *out, DecryptFunction func, s32 key_index, s32 key_generation) {
std::unique_ptr<IDecryptor> decryptor = std::make_unique<ExternalDecryptor>(func, key_index, key_generation);
R_UNLESS(decryptor != nullptr, fs::ResultAllocationMemoryFailedInAesCtrCounterExtendedStorageA());
*out = std::move(decryptor);
return ResultSuccess();
@ -280,7 +281,7 @@ namespace ams::fssystem {
size_t cur_size = std::min(pooled_buffer.GetSize(), remaining_size);
u8 *dst = static_cast<u8 *>(buf) + cur_offset;
m_decrypt_function(pooled_buffer.GetBuffer(), cur_size, m_key_index, enc_key, enc_key_size, ctr, IvSize, dst, cur_size);
m_decrypt_function(pooled_buffer.GetBuffer(), cur_size, m_key_index, m_key_generation, enc_key, enc_key_size, ctr, IvSize, dst, cur_size);
std::memcpy(dst, pooled_buffer.GetBuffer(), cur_size);

View file

@ -0,0 +1,130 @@
/*
* 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/>.
*/
#include <stratosphere.hpp>
namespace ams::fssystem {
AesCtrStorageExternal::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, s32 kgen) : m_base_storage(std::move(bs)), m_decrypt_function(df), m_key_index(kidx), m_key_generation(kgen) {
AMS_ASSERT(m_base_storage != nullptr);
AMS_ASSERT(enc_key_size == KeySize);
AMS_ASSERT(iv != nullptr);
AMS_ASSERT(iv_size == IvSize);
AMS_UNUSED(iv_size);
std::memcpy(m_iv, iv, IvSize);
std::memcpy(m_encrypted_key, enc_key, enc_key_size);
}
Result AesCtrStorageExternal::Read(s64 offset, void *buffer, size_t size) {
/* Allow zero size. */
R_SUCCEED_IF(size == 0);
/* Validate arguments. */
/* NOTE: For some reason, Nintendo uses InvalidArgument instead of InvalidOffset/InvalidSize here. */
R_UNLESS(buffer != nullptr, fs::ResultNullptrArgument());
R_UNLESS(util::IsAligned(offset, BlockSize), fs::ResultInvalidArgument());
R_UNLESS(util::IsAligned(size, BlockSize), fs::ResultInvalidArgument());
/* Read the data. */
R_TRY(m_base_storage->Read(offset, buffer, size));
/* Temporarily increase our thread priority. */
ScopedThreadPriorityChanger cp(+1, ScopedThreadPriorityChanger::Mode::Relative);
/* Allocate a pooled buffer for decryption. */
PooledBuffer pooled_buffer;
pooled_buffer.AllocateParticularlyLarge(size, BlockSize);
AMS_ASSERT(pooled_buffer.GetSize() >= BlockSize);
/* Setup the counter. */
u8 ctr[IvSize];
std::memcpy(ctr, m_iv, IvSize);
AddCounter(ctr, IvSize, offset / BlockSize);
/* Setup tracking. */
size_t remaining_size = size;
s64 cur_offset = 0;
while (remaining_size > 0) {
/* Get the current size to process. */
size_t cur_size = std::min(pooled_buffer.GetSize(), remaining_size);
char *dst = static_cast<char *>(buffer) + cur_offset;
/* Decrypt into the temporary buffer */
m_decrypt_function(pooled_buffer.GetBuffer(), cur_size, m_key_index, m_key_generation, m_encrypted_key, KeySize, ctr, IvSize, dst, cur_size);
/* Copy to the destination. */
std::memcpy(dst, pooled_buffer.GetBuffer(), cur_size);
/* Update tracking. */
cur_offset += cur_size;
remaining_size -= cur_size;
if (remaining_size > 0) {
AddCounter(ctr, IvSize, cur_size / BlockSize);
}
}
R_SUCCEED();
}
Result AesCtrStorageExternal::OperateRange(void *dst, size_t dst_size, fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) {
switch (op_id) {
case fs::OperationId::QueryRange:
{
/* Validate that we have an output range info. */
R_UNLESS(dst != nullptr, fs::ResultNullptrArgument());
R_UNLESS(dst_size == sizeof(fs::QueryRangeInfo), fs::ResultInvalidSize());
/* Operate on our base storage. */
R_TRY(m_base_storage->OperateRange(dst, dst_size, op_id, offset, size, src, src_size));
/* Add in new flags. */
fs::QueryRangeInfo new_info;
new_info.Clear();
new_info.aes_ctr_key_type = static_cast<s32>(m_key_index >= 0 ? fs::AesCtrKeyTypeFlag::InternalKeyForHardwareAes : fs::AesCtrKeyTypeFlag::ExternalKeyForHardwareAes);
/* Merge the new info in. */
reinterpret_cast<fs::QueryRangeInfo *>(dst)->Merge(new_info);
R_SUCCEED();
}
default:
{
/* Operate on our base storage. */
R_RETURN(m_base_storage->OperateRange(dst, dst_size, op_id, offset, size, src, src_size));
}
}
}
Result AesCtrStorageExternal::GetSize(s64 *out) {
R_RETURN(m_base_storage->GetSize(out));
}
Result AesCtrStorageExternal::Flush() {
R_SUCCEED();
}
Result AesCtrStorageExternal::Write(s64 offset, const void *buffer, size_t size) {
AMS_UNUSED(offset, buffer, size);
R_THROW(fs::ResultUnsupportedWriteForAesCtrStorageExternal());
}
Result AesCtrStorageExternal::SetSize(s64 size) {
AMS_UNUSED(size);
R_THROW(fs::ResultUnsupportedSetSizeForAesCtrStorageExternal());
}
}

View file

@ -108,7 +108,7 @@ namespace ams::fssystem {
AddCounter(ctr, IvSize, 1);
}
return ResultSuccess();
R_SUCCEED();
}
template<typename BasePointer>
@ -206,36 +206,39 @@ namespace ams::fssystem {
remaining -= write_size;
}
return ResultSuccess();
R_SUCCEED();
}
template<typename BasePointer>
Result AesXtsStorage<BasePointer>::Flush() {
return m_base_storage->Flush();
R_RETURN(m_base_storage->Flush());
}
template<typename BasePointer>
Result AesXtsStorage<BasePointer>::SetSize(s64 size) {
R_UNLESS(util::IsAligned(size, AesBlockSize), fs::ResultUnexpectedInAesXtsStorageA());
return m_base_storage->SetSize(size);
R_RETURN(m_base_storage->SetSize(size));
}
template<typename BasePointer>
Result AesXtsStorage<BasePointer>::GetSize(s64 *out) {
return m_base_storage->GetSize(out);
R_RETURN(m_base_storage->GetSize(out));
}
template<typename BasePointer>
Result AesXtsStorage<BasePointer>::OperateRange(void *dst, size_t dst_size, fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) {
/* Unless invalidating cache, check the arguments. */
if (op_id != fs::OperationId::Invalidate) {
/* Handle the zero size case. */
R_SUCCEED_IF(size == 0);
/* Ensure alignment. */
R_UNLESS(util::IsAligned(offset, AesBlockSize), fs::ResultInvalidArgument());
R_UNLESS(util::IsAligned(size, AesBlockSize), fs::ResultInvalidArgument());
}
return m_base_storage->OperateRange(dst, dst_size, op_id, offset, size, src, src_size);
R_RETURN(m_base_storage->OperateRange(dst, dst_size, op_id, offset, size, src, src_size));
}
template class AesXtsStorage<fs::IStorage *>;

View file

@ -0,0 +1,233 @@
/*
* 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/>.
*/
#include <stratosphere.hpp>
namespace ams::fssystem {
template<typename BasePointer>
AesXtsStorageExternal<BasePointer>::AesXtsStorageExternal(BasePointer bs, const void *key1, const void *key2, size_t key_size, const void *iv, size_t iv_size, size_t block_size, CryptAesXtsFunction ef, CryptAesXtsFunction df) : m_base_storage(std::move(bs)), m_block_size(block_size), m_encrypt_function(ef), m_decrypt_function(df) {
AMS_ASSERT(key_size == KeySize);
AMS_ASSERT(iv_size == IvSize);
AMS_UNUSED(key_size, iv_size);
if (key1 != nullptr) {
std::memcpy(m_key[0], key1, KeySize);
}
if (key2 != nullptr) {
std::memcpy(m_key[1], key2, KeySize);
}
std::memcpy(m_iv, iv, IvSize);
}
template<typename BasePointer>
Result AesXtsStorageExternal<BasePointer>::Read(s64 offset, void *buffer, size_t size) {
/* Allow zero size. */
R_SUCCEED_IF(size == 0);
/* Ensure buffer is valid. */
R_UNLESS(buffer != nullptr, fs::ResultNullptrArgument());
/* Ensure we can decrypt. */
R_UNLESS(m_decrypt_function != nullptr, fs::ResultNullptrArgument());
/* We can only read at block aligned offsets. */
R_UNLESS(util::IsAligned(offset, AesBlockSize), fs::ResultInvalidArgument());
R_UNLESS(util::IsAligned(size, AesBlockSize), fs::ResultInvalidArgument());
/* Read the data. */
R_TRY(m_base_storage->Read(offset, buffer, size));
/* Temporarily increase our thread priority. */
ScopedThreadPriorityChanger cp(+1, ScopedThreadPriorityChanger::Mode::Relative);
/* Setup the counter. */
char ctr[IvSize];
std::memcpy(ctr, m_iv, IvSize);
AddCounter(ctr, IvSize, offset / m_block_size);
/* Handle any unaligned data before the start. */
size_t processed_size = 0;
if ((offset % m_block_size) != 0) {
/* Determine the size of the pre-data read. */
const size_t skip_size = static_cast<size_t>(offset - util::AlignDown(offset, m_block_size));
const size_t data_size = std::min(size, m_block_size - skip_size);
/* Decrypt into a pooled buffer. */
{
PooledBuffer tmp_buf(m_block_size, m_block_size);
AMS_ASSERT(tmp_buf.GetSize() >= m_block_size);
std::memset(tmp_buf.GetBuffer(), 0, skip_size);
std::memcpy(tmp_buf.GetBuffer() + skip_size, buffer, data_size);
/* Decrypt. */
R_TRY(m_decrypt_function(tmp_buf.GetBuffer(), m_block_size, m_key[0], m_key[1], KeySize, ctr, IvSize, tmp_buf.GetBuffer(), m_block_size));
std::memcpy(buffer, tmp_buf.GetBuffer() + skip_size, data_size);
}
AddCounter(ctr, IvSize, 1);
processed_size += data_size;
AMS_ASSERT(processed_size == std::min(size, m_block_size - skip_size));
}
/* Decrypt aligned chunks. */
char *cur = static_cast<char *>(buffer) + processed_size;
size_t remaining = size - processed_size;
while (remaining > 0) {
const size_t cur_size = std::min(m_block_size, remaining);
R_TRY(m_decrypt_function(cur, cur_size, m_key[0], m_key[1], KeySize, ctr, IvSize, cur, cur_size));
remaining -= cur_size;
cur += cur_size;
AddCounter(ctr, IvSize, 1);
}
R_SUCCEED();
}
template<typename BasePointer>
Result AesXtsStorageExternal<BasePointer>::Write(s64 offset, const void *buffer, size_t size) {
/* Allow zero-size writes. */
R_SUCCEED_IF(size == 0);
/* Ensure buffer is valid. */
R_UNLESS(buffer != nullptr, fs::ResultNullptrArgument());
/* Ensure we can encrypt. */
R_UNLESS(m_decrypt_function != nullptr, fs::ResultNullptrArgument());
/* We can only write at block aligned offsets. */
R_UNLESS(util::IsAligned(offset, AesBlockSize), fs::ResultInvalidArgument());
R_UNLESS(util::IsAligned(size, AesBlockSize), fs::ResultInvalidArgument());
/* Get a pooled buffer. */
PooledBuffer pooled_buffer;
const bool use_work_buffer = !IsDeviceAddress(buffer);
if (use_work_buffer) {
pooled_buffer.Allocate(size, m_block_size);
}
/* Setup the counter. */
char ctr[IvSize];
std::memcpy(ctr, m_iv, IvSize);
AddCounter(ctr, IvSize, offset / m_block_size);
/* Handle any unaligned data before the start. */
size_t processed_size = 0;
if ((offset % m_block_size) != 0) {
/* Determine the size of the pre-data read. */
const size_t skip_size = static_cast<size_t>(offset - util::AlignDown(offset, m_block_size));
const size_t data_size = std::min(size, m_block_size - skip_size);
/* Encrypt into a pooled buffer. */
{
/* NOTE: Nintendo allocates a second pooled buffer here despite having one already allocated above. */
PooledBuffer tmp_buf(m_block_size, m_block_size);
AMS_ASSERT(tmp_buf.GetSize() >= m_block_size);
std::memset(tmp_buf.GetBuffer(), 0, skip_size);
std::memcpy(tmp_buf.GetBuffer() + skip_size, buffer, data_size);
R_TRY(m_encrypt_function(tmp_buf.GetBuffer(), m_block_size, m_key[0], m_key[1], KeySize, ctr, IvSize, tmp_buf.GetBuffer(), m_block_size));
R_TRY(m_base_storage->Write(offset, tmp_buf.GetBuffer() + skip_size, data_size));
}
AddCounter(ctr, IvSize, 1);
processed_size += data_size;
AMS_ASSERT(processed_size == std::min(size, m_block_size - skip_size));
}
/* Encrypt aligned chunks. */
size_t remaining = size - processed_size;
s64 cur_offset = offset + processed_size;
while (remaining > 0) {
/* Determine data we're writing and where. */
const size_t write_size = use_work_buffer ? std::min(pooled_buffer.GetSize(), remaining) : remaining;
/* Encrypt the data, with temporarily increased priority. */
{
ScopedThreadPriorityChanger cp(+1, ScopedThreadPriorityChanger::Mode::Relative);
size_t remaining_write = write_size;
size_t encrypt_offset = 0;
while (remaining_write > 0) {
const size_t cur_size = std::min(remaining_write, m_block_size);
const void *src = static_cast<const char *>(buffer) + processed_size + encrypt_offset;
void *dst = use_work_buffer ? pooled_buffer.GetBuffer() + encrypt_offset : const_cast<void *>(src);
R_TRY(m_encrypt_function(dst, cur_size, m_key[0], m_key[1], KeySize, ctr, IvSize, src, cur_size));
AddCounter(ctr, IvSize, 1);
encrypt_offset += cur_size;
remaining_write -= cur_size;
}
}
/* Write the encrypted data. */
const void *write_buf = use_work_buffer ? pooled_buffer.GetBuffer() : static_cast<const char *>(buffer) + processed_size;
R_TRY(m_base_storage->Write(cur_offset, write_buf, write_size));
/* Advance. */
cur_offset += write_size;
processed_size += write_size;
remaining -= write_size;
}
R_SUCCEED();
}
template<typename BasePointer>
Result AesXtsStorageExternal<BasePointer>::OperateRange(void *dst, size_t dst_size, fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) {
/* Unless invalidating cache, check the arguments. */
if (op_id != fs::OperationId::Invalidate) {
/* Handle the zero size case. */
R_SUCCEED_IF(size == 0);
/* Ensure alignment. */
R_UNLESS(util::IsAligned(offset, AesBlockSize), fs::ResultInvalidArgument());
R_UNLESS(util::IsAligned(size, AesBlockSize), fs::ResultInvalidArgument());
}
R_RETURN(m_base_storage->OperateRange(dst, dst_size, op_id, offset, size, src, src_size));
}
template<typename BasePointer>
Result AesXtsStorageExternal<BasePointer>::GetSize(s64 *out) {
R_RETURN(m_base_storage->GetSize(out));
}
template<typename BasePointer>
Result AesXtsStorageExternal<BasePointer>::Flush() {
R_RETURN(m_base_storage->Flush());
}
template<typename BasePointer>
Result AesXtsStorageExternal<BasePointer>::SetSize(s64 size) {
R_UNLESS(util::IsAligned(size, AesBlockSize), fs::ResultUnexpectedInAesXtsStorageA());
R_RETURN(m_base_storage->SetSize(size));
}
template class AesXtsStorageExternal<fs::IStorage *>;
template class AesXtsStorageExternal<std::shared_ptr<fs::IStorage>>;
}

View file

@ -17,8 +17,7 @@
namespace ams::fssystem {
BlockCacheBufferedStorage::BlockCacheBufferedStorage() : m_mutex(), m_data_storage(), m_last_result(ResultSuccess()), m_data_size(), m_verification_block_size(), m_verification_block_shift(), m_flags(), m_buffer_level(-1), m_block_cache_manager()
{
BlockCacheBufferedStorage::BlockCacheBufferedStorage() : m_mutex(), m_data_storage(), m_last_result(ResultSuccess()), m_data_size(), m_verification_block_size(), m_verification_block_shift(), m_flags(), m_buffer_level(-1), m_block_cache_manager() {
/* ... */
}
@ -26,7 +25,7 @@ namespace ams::fssystem {
this->Finalize();
}
Result BlockCacheBufferedStorage::Initialize(fs::IBufferManager *bm, os::SdkRecursiveMutex *mtx, IStorage *data, s64 data_size, size_t verif_block_size, s32 max_cache_entries, bool is_real_data, s8 buffer_level, bool is_keep_burst_mode, fs::StorageType storage_type) {
Result BlockCacheBufferedStorage::Initialize(fs::IBufferManager *bm, os::SdkRecursiveMutex *mtx, IStorage *data, s64 data_size, size_t verif_block_size, s32 max_cache_entries, bool is_real_data, s8 buffer_level, bool is_keep_burst_mode, bool is_writable) {
/* Validate preconditions. */
AMS_ASSERT(data != nullptr);
AMS_ASSERT(bm != nullptr);
@ -46,7 +45,7 @@ namespace ams::fssystem {
m_last_result = ResultSuccess();
m_flags = 0;
m_buffer_level = buffer_level;
m_storage_type = storage_type;
m_is_writable = is_writable;
/* Calculate block shift. */
m_verification_block_shift = ILog2(static_cast<u32>(verif_block_size));
@ -385,24 +384,20 @@ namespace ams::fssystem {
switch (op_id) {
case fs::OperationId::FillZero:
{
R_TRY(this->FillZeroImpl(offset, size));
R_SUCCEED();
R_RETURN(this->FillZeroImpl(offset, size));
}
case fs::OperationId::DestroySignature:
{
R_TRY(this->DestroySignatureImpl(offset, size));
R_SUCCEED();
R_RETURN(this->DestroySignatureImpl(offset, size));
}
case fs::OperationId::Invalidate:
{
R_UNLESS(m_storage_type != fs::StorageType_SaveData, fs::ResultUnsupportedOperateRangeForNonSaveDataBlockCacheBufferedStorage());
R_TRY(this->InvalidateImpl());
R_SUCCEED();
R_UNLESS(!m_is_writable, fs::ResultUnsupportedOperateRangeForWritableBlockCacheBufferedStorage());
R_RETURN(this->InvalidateImpl());
}
case fs::OperationId::QueryRange:
{
R_TRY(this->QueryRangeImpl(dst, dst_size, offset, size));
R_SUCCEED();
R_RETURN(this->QueryRangeImpl(dst, dst_size, offset, size));
}
default:
R_THROW(fs::ResultUnsupportedOperateRangeForBlockCacheBufferedStorage());

View file

@ -120,24 +120,24 @@ namespace ams::fssystem {
AMS_FUNCTION_LOCAL_STATIC_CONSTINIT(spl::AccessKey, s_nca_header_kek_access_key);
AMS_FUNCTION_LOCAL_STATIC_CONSTINIT(spl::AccessKey, s_invalid_nca_kek_access_key);
if (key_type > static_cast<s32>(KeyType::NcaHeaderKey) || IsInvalidKeyTypeValue(key_type)) {
if (key_type > static_cast<s32>(KeyType::NcaHeaderKey2) || IsInvalidKeyTypeValue(key_type)) {
return s_invalid_nca_kek_access_key;
} else if (key_type == static_cast<s32>(KeyType::NcaHeaderKey)) {
} else if (key_type == static_cast<s32>(KeyType::NcaHeaderKey1) || key_type == static_cast<s32>(KeyType::NcaHeaderKey2)) {
return s_nca_header_kek_access_key;
} else {
return s_nca_kek_access_key_array[key_type];
}
}
void GenerateNcaKey(void *dst, size_t dst_size, const void *src, size_t src_size, s32 key_type, const NcaCryptoConfiguration &cfg) {
AMS_UNUSED(cfg);
void GenerateNcaKey(void *dst, size_t dst_size, const void *src, size_t src_size, s32 key_type) {
R_ABORT_UNLESS(spl::GenerateAesKey(dst, dst_size, GetNcaKekAccessKey(key_type), src, src_size));
}
void DecryptAesCtr(void *dst, size_t dst_size, s32 key_type, const void *enc_key, size_t enc_key_size, const void *iv, size_t iv_size, const void *src, size_t src_size) {
void DecryptAesCtr(void *dst, size_t dst_size, u8 key_index, u8 key_generation, const void *enc_key, size_t enc_key_size, const void *iv, size_t iv_size, const void *src, size_t src_size) {
std::unique_ptr<KeySlotCacheAccessor> accessor;
const s32 key_type = GetKeyTypeValue(key_index, key_generation);
R_TRY_CATCH(g_key_slot_cache.Find(std::addressof(accessor), enc_key, enc_key_size, key_type)) {
R_CATCH(fs::ResultTargetNotFound) {
R_ABORT_UNLESS(g_key_slot_cache.AllocateHighPriority(std::addressof(accessor), enc_key, enc_key_size, key_type));
@ -148,10 +148,11 @@ namespace ams::fssystem {
R_ABORT_UNLESS(spl::ComputeCtr(dst, dst_size, accessor->GetKeySlotIndex(), src, src_size, iv, iv_size));
}
void DecryptAesCtrForPreparedKey(void *dst, size_t dst_size, s32 key_type, const void *enc_key, size_t enc_key_size, const void *iv, size_t iv_size, const void *src, size_t src_size) {
void DecryptAesCtrForPreparedKey(void *dst, size_t dst_size, u8 key_index, u8 key_generation, const void *enc_key, size_t enc_key_size, const void *iv, size_t iv_size, const void *src, size_t src_size) {
std::unique_ptr<KeySlotCacheAccessor> accessor;
key_type = static_cast<s32>(KeyType::NcaExternalKey);
AMS_UNUSED(key_index, key_generation);
const s32 key_type = static_cast<s32>(KeyType::NcaExternalKey);
R_TRY_CATCH(g_key_slot_cache.Find(std::addressof(accessor), enc_key, enc_key_size, key_type)) {
R_CATCH(fs::ResultTargetNotFound) {
@ -167,6 +168,15 @@ namespace ams::fssystem {
R_ABORT_UNLESS(spl::ComputeCtr(dst, dst_size, accessor->GetKeySlotIndex(), src, src_size, iv, iv_size));
}
bool VerifySign1(const void *sig, size_t sig_size, const void *data, size_t data_size, u8 generation, const NcaCryptoConfiguration &cfg) {
const u8 *mod = cfg.header_1_sign_key_moduli[generation];
const size_t mod_size = NcaCryptoConfiguration::Rsa2048KeyModulusSize;
const u8 *exp = cfg.header_1_sign_key_public_exponent;
const size_t exp_size = NcaCryptoConfiguration::Rsa2048KeyPublicExponentSize;
return crypto::VerifyRsa2048PssSha256(sig, sig_size, mod, mod_size, exp, exp_size, data, data_size);
}
}
const ::ams::fssystem::NcaCryptoConfiguration *GetNcaCryptoConfiguration(bool prod) {
@ -176,9 +186,13 @@ namespace ams::fssystem {
/* Set the key generation functions. */
cfg->generate_key = GenerateNcaKey;
cfg->decrypt_aes_xts_external = nullptr;
cfg->encrypt_aes_xts_external = nullptr;
cfg->decrypt_aes_ctr = DecryptAesCtr;
cfg->decrypt_aes_ctr_external = DecryptAesCtrForPreparedKey;
cfg->verify_sign1 = VerifySign1;
cfg->is_plaintext_header_available = !prod;
cfg->is_available_sw_key = true;
/* TODO: Should this default to false for host tools with api to set explicitly? */
#if !defined(ATMOSPHERE_BOARD_NINTENDO_NX)
@ -204,7 +218,7 @@ namespace ams::fssystem {
}
/* Setup the header encryption key. */
R_ABORT_UNLESS(spl::GenerateAesKek(std::addressof(GetNcaKekAccessKey(static_cast<s32>(KeyType::NcaHeaderKey))), nca_crypto_cfg->header_encryption_key_source, KeySize, 0, Option));
R_ABORT_UNLESS(spl::GenerateAesKek(std::addressof(GetNcaKekAccessKey(static_cast<s32>(KeyType::NcaHeaderKey1))), nca_crypto_cfg->header_encryption_key_source, KeySize, 0, Option));
}
/* TODO FS-REIMPL: Save stuff. */

View file

@ -23,11 +23,6 @@ namespace ams::fssystem {
constexpr inline u32 IntegrityVerificationStorageVersion = 0x00020000;
constexpr inline u32 IntegrityVerificationStorageVersionMask = 0xFFFF0000;
constexpr inline auto MaxSaveDataFsDataCacheEntryCount = 32;
constexpr inline auto MaxSaveDataFsHashCacheEntryCount = 4;
constexpr inline auto MaxRomFsDataCacheEntryCount = 24;
constexpr inline auto MaxRomFsHashCacheEntryCount = 8;
constexpr inline auto AccessCountMax = 5;
constexpr inline auto AccessTimeout = TimeSpan::FromMilliSeconds(10);
@ -92,7 +87,7 @@ namespace ams::fssystem {
out->layered_hash_sizes[level - 1] = level_size[level];
}
return ResultSuccess();
R_SUCCEED();
}
Result HierarchicalIntegrityVerificationStorageControlArea::Expand(fs::SubStorage meta_storage, const HierarchicalIntegrityVerificationMetaInformation &meta) {
@ -122,7 +117,7 @@ namespace ams::fssystem {
R_TRY(meta_storage.Write(0, std::addressof(meta), sizeof(meta)));
R_TRY(meta_storage.Flush());
return ResultSuccess();
R_SUCCEED();
}
Result HierarchicalIntegrityVerificationStorageControlArea::Initialize(fs::SubStorage meta_storage) {
@ -143,14 +138,14 @@ namespace ams::fssystem {
/* Validate the meta version. */
R_UNLESS((m_meta.version & IntegrityVerificationStorageVersionMask) == (IntegrityVerificationStorageVersion & IntegrityVerificationStorageVersionMask), fs::ResultUnsupportedVersion());
return ResultSuccess();
R_SUCCEED();
}
void HierarchicalIntegrityVerificationStorageControlArea::Finalize() {
m_storage = fs::SubStorage();
}
Result HierarchicalIntegrityVerificationStorage::Initialize(const HierarchicalIntegrityVerificationInformation &info, HierarchicalStorageInformation storage, FileSystemBufferManagerSet *bufs, IHash256GeneratorFactory *hgf, os::SdkRecursiveMutex *mtx, fs::StorageType storage_type) {
Result HierarchicalIntegrityVerificationStorage::Initialize(const HierarchicalIntegrityVerificationInformation &info, HierarchicalStorageInformation storage, FileSystemBufferManagerSet *bufs, IHash256GeneratorFactory *hgf, bool hash_salt_enabled, os::SdkRecursiveMutex *mtx, os::Semaphore *read_sema, os::Semaphore *write_sema, int max_data_cache_entries, int max_hash_cache_entries, s8 buffer_level, bool is_writable, bool allow_cleared_blocks) {
/* Validate preconditions. */
AMS_ASSERT(bufs != nullptr);
AMS_ASSERT(IntegrityMinLayerCount <= info.max_layers && info.max_layers <= IntegrityMaxLayerCount);
@ -159,20 +154,21 @@ namespace ams::fssystem {
m_max_layers = info.max_layers;
m_buffers = bufs;
m_mutex = mtx;
m_read_semaphore = read_sema;
m_write_semaphore = write_sema;
/* Determine our cache counts. */
const auto max_data_cache_entry_count = (storage_type == fs::StorageType_SaveData) ? MaxSaveDataFsDataCacheEntryCount : MaxRomFsDataCacheEntryCount;
const auto max_hash_cache_entry_count = (storage_type == fs::StorageType_SaveData) ? MaxSaveDataFsHashCacheEntryCount : MaxRomFsHashCacheEntryCount;
/* Initialize the top level verification storage. */
{
fs::HashSalt mac;
crypto::GenerateHmacSha256(mac.value, sizeof(mac), info.seed.value, sizeof(info.seed), KeyArray[0].key, KeyArray[0].size);
m_verify_storages[0].Initialize(storage[HierarchicalStorageInformation::MasterStorage], storage[HierarchicalStorageInformation::Layer1Storage], static_cast<s64>(1) << info.info[0].block_order, HashSize, m_buffers->buffers[m_max_layers - 2], hgf, mac, false, storage_type);
/* If hash salt is enabled, generate it. */
util::optional<fs::HashSalt> hash_salt = util::nullopt;
if (hash_salt_enabled) {
hash_salt.emplace();
crypto::GenerateHmacSha256(hash_salt->value, sizeof(hash_salt->value), info.seed.value, sizeof(info.seed), KeyArray[0].key, KeyArray[0].size);
}
/* Initialize the top level verification storage. */
m_verify_storages[0].Initialize(storage[HierarchicalStorageInformation::MasterStorage], storage[HierarchicalStorageInformation::Layer1Storage], static_cast<s64>(1) << info.info[0].block_order, HashSize, m_buffers->buffers[m_max_layers - 2], hgf, hash_salt, false, is_writable, allow_cleared_blocks);
/* Ensure we don't leak state if further initialization goes wrong. */
auto top_verif_guard = SCOPE_GUARD {
ON_RESULT_FAILURE {
m_verify_storages[0].Finalize();
m_data_size = -1;
@ -181,14 +177,14 @@ namespace ams::fssystem {
};
/* Initialize the top level buffer storage. */
R_TRY(m_buffer_storages[0].Initialize(m_buffers->buffers[0], m_mutex, std::addressof(m_verify_storages[0]), info.info[0].size, static_cast<s64>(1) << info.info[0].block_order, max_hash_cache_entry_count, false, 0x10, false, storage_type));
auto top_buffer_guard = SCOPE_GUARD { m_buffer_storages[0].Finalize(); };
R_TRY(m_buffer_storages[0].Initialize(m_buffers->buffers[0], m_mutex, std::addressof(m_verify_storages[0]), info.info[0].size, static_cast<s64>(1) << info.info[0].block_order, max_hash_cache_entries, false, 0x10, false, is_writable));
ON_RESULT_FAILURE_2 { m_buffer_storages[0].Finalize(); };
/* Prepare to initialize the level storages. */
s32 level = 0;
/* Ensure we don't leak state if further initialization goes wrong. */
auto level_guard = SCOPE_GUARD {
ON_RESULT_FAILURE_2 {
m_verify_storages[level + 1].Finalize();
for (/* ... */; level > 0; --level) {
m_buffer_storages[level].Finalize();
@ -198,40 +194,43 @@ namespace ams::fssystem {
/* Initialize the level storages. */
for (/* ... */; level < m_max_layers - 3; ++level) {
/* Initialize the verification storage. */
{
fs::SubStorage buffer_storage(std::addressof(m_buffer_storages[level]), 0, info.info[level].size);
fs::HashSalt mac;
crypto::GenerateHmacSha256(mac.value, sizeof(mac), info.seed.value, sizeof(info.seed), KeyArray[level + 1].key, KeyArray[level + 1].size);
m_verify_storages[level + 1].Initialize(buffer_storage, storage[level + 2], static_cast<s64>(1) << info.info[level + 1].block_order, static_cast<s64>(1) << info.info[level].block_order, m_buffers->buffers[m_max_layers - 2], hgf, mac, false, storage_type);
/* If hash salt is enabled, generate it. */
util::optional<fs::HashSalt> hash_salt = util::nullopt;
if (hash_salt_enabled) {
hash_salt.emplace();
crypto::GenerateHmacSha256(hash_salt->value, sizeof(hash_salt->value), info.seed.value, sizeof(info.seed), KeyArray[level + 1].key, KeyArray[level + 1].size);
}
/* Initialize the verification storage. */
fs::SubStorage buffer_storage(std::addressof(m_buffer_storages[level]), 0, info.info[level].size);
m_verify_storages[level + 1].Initialize(buffer_storage, storage[level + 2], static_cast<s64>(1) << info.info[level + 1].block_order, static_cast<s64>(1) << info.info[level].block_order, m_buffers->buffers[m_max_layers - 2], hgf, hash_salt, false, is_writable, allow_cleared_blocks);
/* Initialize the buffer storage. */
R_TRY(m_buffer_storages[level + 1].Initialize(m_buffers->buffers[level + 1], m_mutex, std::addressof(m_verify_storages[level + 1]), info.info[level + 1].size, static_cast<s64>(1) << info.info[level + 1].block_order, max_hash_cache_entry_count, false, 0x11 + static_cast<s8>(level), false, storage_type));
R_TRY(m_buffer_storages[level + 1].Initialize(m_buffers->buffers[level + 1], m_mutex, std::addressof(m_verify_storages[level + 1]), info.info[level + 1].size, static_cast<s64>(1) << info.info[level + 1].block_order, max_hash_cache_entries, false, 0x11 + static_cast<s8>(level), false, is_writable));
}
/* Initialize the final level storage. */
{
/* Initialize the verification storage. */
{
fs::SubStorage buffer_storage(std::addressof(m_buffer_storages[level]), 0, info.info[level].size);
fs::HashSalt mac;
crypto::GenerateHmacSha256(mac.value, sizeof(mac), info.seed.value, sizeof(info.seed), KeyArray[level + 1].key, KeyArray[level + 1].size);
m_verify_storages[level + 1].Initialize(buffer_storage, storage[level + 2], static_cast<s64>(1) << info.info[level + 1].block_order, static_cast<s64>(1) << info.info[level].block_order, m_buffers->buffers[m_max_layers - 2], hgf, mac, true, storage_type);
/* If hash salt is enabled, generate it. */
util::optional<fs::HashSalt> hash_salt = util::nullopt;
if (hash_salt_enabled) {
hash_salt.emplace();
crypto::GenerateHmacSha256(hash_salt->value, sizeof(hash_salt->value), info.seed.value, sizeof(info.seed), KeyArray[level + 1].key, KeyArray[level + 1].size);
}
/* Initialize the verification storage. */
fs::SubStorage buffer_storage(std::addressof(m_buffer_storages[level]), 0, info.info[level].size);
m_verify_storages[level + 1].Initialize(buffer_storage, storage[level + 2], static_cast<s64>(1) << info.info[level + 1].block_order, static_cast<s64>(1) << info.info[level].block_order, m_buffers->buffers[m_max_layers - 2], hgf, hash_salt, true, is_writable, allow_cleared_blocks);
/* Initialize the buffer storage. */
R_TRY(m_buffer_storages[level + 1].Initialize(m_buffers->buffers[level + 1], m_mutex, std::addressof(m_verify_storages[level + 1]), info.info[level + 1].size, static_cast<s64>(1) << info.info[level + 1].block_order, max_data_cache_entry_count, true, 0x11 + static_cast<s8>(level), true, storage_type));
R_TRY(m_buffer_storages[level + 1].Initialize(m_buffers->buffers[level + 1], m_mutex, std::addressof(m_verify_storages[level + 1]), info.info[level + 1].size, static_cast<s64>(1) << info.info[level + 1].block_order, max_data_cache_entries, true, buffer_level, true, is_writable));
}
/* Set the data size. */
m_data_size = info.info[level + 1].size;
/* We succeeded. */
level_guard.Cancel();
top_buffer_guard.Cancel();
top_verif_guard.Cancel();
return ResultSuccess();
R_SUCCEED();
}
void HierarchicalIntegrityVerificationStorage::Finalize() {
@ -260,7 +259,11 @@ namespace ams::fssystem {
/* Validate arguments. */
R_UNLESS(buffer != nullptr, fs::ResultNullptrArgument());
/* Acquire access to the read semaphore. */
/* If we have a read semaphore, acquire it. */
if (m_read_semaphore != nullptr) { m_read_semaphore->Acquire(); }
ON_SCOPE_EXIT { if (m_read_semaphore != nullptr) { m_read_semaphore->Release(); } };
/* Acquire access to the global read semaphore. */
if (!g_read_semaphore.TimedAcquire(AccessTimeout)) {
for (auto level = m_max_layers - 2; level >= 0; --level) {
R_TRY(m_buffer_storages[level].Flush());
@ -272,8 +275,7 @@ namespace ams::fssystem {
ON_SCOPE_EXIT { g_read_semaphore.Release(); };
/* Read the data. */
R_TRY(m_buffer_storages[m_max_layers - 2].Read(offset, buffer, size));
return ResultSuccess();
R_RETURN(m_buffer_storages[m_max_layers - 2].Read(offset, buffer, size));
}
Result HierarchicalIntegrityVerificationStorage::Write(s64 offset, const void *buffer, size_t size) {
@ -286,6 +288,10 @@ namespace ams::fssystem {
/* Validate arguments. */
R_UNLESS(buffer != nullptr, fs::ResultNullptrArgument());
/* If we have a write semaphore, acquire it. */
if (m_write_semaphore != nullptr) { m_write_semaphore->Acquire(); }
ON_SCOPE_EXIT { if (m_write_semaphore != nullptr) { m_write_semaphore->Release(); } };
/* Acquire access to the write semaphore. */
if (!g_write_semaphore.TimedAcquire(AccessTimeout)) {
for (auto level = m_max_layers - 2; level >= 0; --level) {
@ -298,20 +304,18 @@ namespace ams::fssystem {
ON_SCOPE_EXIT { g_write_semaphore.Release(); };
/* Write the data. */
R_TRY(m_buffer_storages[m_max_layers - 2].Write(offset, buffer, size));
m_is_written_for_rollback = true;
return ResultSuccess();
R_RETURN(m_buffer_storages[m_max_layers - 2].Write(offset, buffer, size));
}
Result HierarchicalIntegrityVerificationStorage::GetSize(s64 *out) {
AMS_ASSERT(out != nullptr);
AMS_ASSERT(m_data_size >= 0);
*out = m_data_size;
return ResultSuccess();
R_SUCCEED();
}
Result HierarchicalIntegrityVerificationStorage::Flush() {
return ResultSuccess();
R_SUCCEED();
}
Result HierarchicalIntegrityVerificationStorage::OperateRange(void *dst, size_t dst_size, fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) {
@ -320,17 +324,16 @@ namespace ams::fssystem {
case fs::OperationId::DestroySignature:
{
R_TRY(m_buffer_storages[m_max_layers - 2].OperateRange(dst, dst_size, op_id, offset, size, src, src_size));
m_is_written_for_rollback = true;
return ResultSuccess();
R_SUCCEED();
}
case fs::OperationId::Invalidate:
case fs::OperationId::QueryRange:
{
R_TRY(m_buffer_storages[m_max_layers - 2].OperateRange(dst, dst_size, op_id, offset, size, src, src_size));
return ResultSuccess();
R_SUCCEED();
}
default:
return fs::ResultUnsupportedOperateRangeForHierarchicalIntegrityVerificationStorage();
R_THROW(fs::ResultUnsupportedOperateRangeForHierarchicalIntegrityVerificationStorage());
}
}
@ -338,15 +341,14 @@ namespace ams::fssystem {
for (s32 level = m_max_layers - 2; level >= 0; --level) {
R_TRY(m_buffer_storages[level].Commit());
}
return ResultSuccess();
R_SUCCEED();
}
Result HierarchicalIntegrityVerificationStorage::OnRollback() {
for (s32 level = m_max_layers - 2; level >= 0; --level) {
R_TRY(m_buffer_storages[level].OnRollback());
}
m_is_written_for_rollback = false;
return ResultSuccess();
R_SUCCEED();
}
}

View file

@ -17,7 +17,7 @@
namespace ams::fssystem {
Result IntegrityRomFsStorage::Initialize(HierarchicalIntegrityVerificationInformation level_hash_info, Hash master_hash, HierarchicalIntegrityVerificationStorage::HierarchicalStorageInformation storage_info, fs::IBufferManager *bm, IHash256GeneratorFactory *hgf) {
Result IntegrityRomFsStorage::Initialize(HierarchicalIntegrityVerificationInformation level_hash_info, Hash master_hash, HierarchicalIntegrityVerificationStorage::HierarchicalStorageInformation storage_info, fs::IBufferManager *bm, int max_data_cache_entries, int max_hash_cache_entries, s8 buffer_level, IHash256GeneratorFactory *hgf) {
/* Validate preconditions. */
AMS_ASSERT(bm != nullptr);
@ -35,7 +35,7 @@ namespace ams::fssystem {
}
/* Initialize our integrity storage. */
return m_integrity_storage.Initialize(level_hash_info, storage_info, std::addressof(m_buffers), hgf, std::addressof(m_mutex), fs::StorageType_RomFs);
R_RETURN(m_integrity_storage.Initialize(level_hash_info, storage_info, std::addressof(m_buffers), hgf, false, std::addressof(m_mutex), max_data_cache_entries, max_hash_cache_entries, buffer_level, false, false));
}
void IntegrityRomFsStorage::Finalize() {

View file

@ -17,7 +17,7 @@
namespace ams::fssystem {
Result IntegrityVerificationStorage::Initialize(fs::SubStorage hs, fs::SubStorage ds, s64 verif_block_size, s64 upper_layer_verif_block_size, fs::IBufferManager *bm, fssystem::IHash256GeneratorFactory *hgf, const fs::HashSalt &salt, bool is_real_data, fs::StorageType storage_type) {
Result IntegrityVerificationStorage::Initialize(fs::SubStorage hs, fs::SubStorage ds, s64 verif_block_size, s64 upper_layer_verif_block_size, fs::IBufferManager *bm, fssystem::IHash256GeneratorFactory *hgf, const util::optional<fs::HashSalt> &salt, bool is_real_data, bool is_writable, bool allow_cleared_blocks) {
/* Validate preconditions. */
AMS_ASSERT(verif_block_size >= HashSize);
AMS_ASSERT(bm != nullptr);
@ -49,18 +49,19 @@ namespace ams::fssystem {
s64 hash_size = 0;
s64 data_size = 0;
AMS_ASSERT(R_SUCCEEDED(m_hash_storage.GetSize(std::addressof(hash_size))));
AMS_ASSERT(R_SUCCEEDED(m_data_storage.GetSize(std::addressof(hash_size))));
AMS_ASSERT(R_SUCCEEDED(m_data_storage.GetSize(std::addressof(data_size))));
AMS_ASSERT(((hash_size / HashSize) * m_verification_block_size) >= data_size);
AMS_UNUSED(hash_size, data_size);
}
/* Set salt. */
std::memcpy(m_salt.value, salt.value, fs::HashSalt::Size);
m_salt = salt;
/* Set data and storage type. */
/* Set data, writable, and allow cleared. */
m_is_real_data = is_real_data;
m_storage_type = storage_type;
return ResultSuccess();
m_is_writable = is_writable;
m_allow_cleared_blocks = allow_cleared_blocks;
R_SUCCEED();
}
void IntegrityVerificationStorage::Finalize() {
@ -146,7 +147,7 @@ namespace ams::fssystem {
std::memset(cur_buf, 0, m_verification_block_size);
/* Set the result if we should. */
if (!fs::ResultClearedRealDataVerificationFailed::Includes(cur_result) && m_storage_type != fs::StorageType_Authoring) {
if (!fs::ResultClearedRealDataVerificationFailed::Includes(cur_result) && !m_allow_cleared_blocks) {
verify_hash_result = cur_result;
}
@ -157,14 +158,14 @@ namespace ams::fssystem {
/* If we failed, clear and return. */
if (R_FAILED(cur_result)) {
std::memset(buffer, 0, size);
return cur_result;
R_THROW(cur_result);
}
/* Advance. */
verified_count += cur_count;
}
return verify_hash_result;
R_RETURN(verify_hash_result);
}
Result IntegrityVerificationStorage::Write(s64 offset, const void *buffer, size_t size) {
@ -246,30 +247,32 @@ namespace ams::fssystem {
/* Write the data. */
R_TRY(m_data_storage.Write(offset, buffer, std::min(write_size, updated_count << m_verification_block_order)));
return update_result;
R_RETURN(update_result);
}
Result IntegrityVerificationStorage::GetSize(s64 *out) {
return m_data_storage.GetSize(out);
R_RETURN(m_data_storage.GetSize(out));
}
Result IntegrityVerificationStorage::Flush() {
/* Flush both storages. */
R_TRY(m_hash_storage.Flush());
R_TRY(m_data_storage.Flush());
return ResultSuccess();
R_SUCCEED();
}
Result IntegrityVerificationStorage::OperateRange(void *dst, size_t dst_size, fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) {
/* Validate preconditions. */
if (op_id != fs::OperationId::Invalidate) {
AMS_ASSERT(util::IsAligned(offset, static_cast<size_t>(m_verification_block_size)));
AMS_ASSERT(util::IsAligned(size, static_cast<size_t>(m_verification_block_size)));
}
switch (op_id) {
case fs::OperationId::FillZero:
{
/* Clear should only be called for save data. */
AMS_ASSERT(m_storage_type == fs::StorageType_SaveData);
/* FillZero should only be called for writable storages. */
AMS_ASSERT(m_is_writable);
/* Validate the range. */
s64 data_size = 0;
@ -297,12 +300,12 @@ namespace ams::fssystem {
remaining_size -= cur_size;
}
return ResultSuccess();
R_SUCCEED();
}
case fs::OperationId::DestroySignature:
{
/* Clear Signature should only be called for save data. */
AMS_ASSERT(m_storage_type == fs::StorageType_SaveData);
/* DestroySignature should only be called for save data. */
AMS_ASSERT(m_is_writable);
/* Validate the range. */
s64 data_size = 0;
@ -327,19 +330,19 @@ namespace ams::fssystem {
}
/* Write the cleared signature. */
return m_hash_storage.Write(sign_offset, buf.get(), sign_size);
R_RETURN(m_hash_storage.Write(sign_offset, buf.get(), sign_size));
}
case fs::OperationId::Invalidate:
{
/* Only allow cache invalidation for RomFs. */
R_UNLESS(m_storage_type != fs::StorageType_SaveData, fs::ResultUnsupportedOperateRangeForNonSaveDataIntegrityVerificationStorage());
/* Only allow cache invalidation read-only storages. */
R_UNLESS(!m_is_writable, fs::ResultUnsupportedOperateRangeForWritableIntegrityVerificationStorage());
/* Operate on our storages. */
R_TRY(m_hash_storage.OperateRange(dst, dst_size, op_id, 0, std::numeric_limits<s64>::max(), src, src_size));
R_TRY(m_data_storage.OperateRange(dst, dst_size, op_id, offset, size, src, src_size));
R_TRY(m_hash_storage.OperateRange(op_id, 0, std::numeric_limits<s64>::max()));
R_TRY(m_data_storage.OperateRange(op_id, offset, size));
return ResultSuccess();
R_SUCCEED();
}
case fs::OperationId::QueryRange:
{
@ -352,31 +355,37 @@ namespace ams::fssystem {
const auto actual_size = std::min(size, data_size - offset);
/* Query the data storage. */
R_TRY(m_data_storage.OperateRange(dst, dst_size, op_id, offset, actual_size, src, src_size));
return ResultSuccess();
R_RETURN(m_data_storage.OperateRange(dst, dst_size, op_id, offset, actual_size, src, src_size));
}
default:
return fs::ResultUnsupportedOperateRangeForIntegrityVerificationStorage();
R_THROW(fs::ResultUnsupportedOperateRangeForIntegrityVerificationStorage());
}
}
void IntegrityVerificationStorage::CalcBlockHash(BlockHash *out, const void *buffer, size_t block_size, std::unique_ptr<fssystem::IHash256Generator> &generator) const {
/* Hash procedure depends on whether or not we're writable. */
if (m_is_writable) {
/* Compute the hash with or without the hash salt, if we have one. */
if (m_salt.has_value()) {
/* Initialize the generator. */
generator->Initialize();
/* If calculating for save data, hash the salt. */
if (m_storage_type == fs::StorageType_SaveData) {
generator->Update(m_salt.value, sizeof(m_salt));
}
/* Hash the salt. */
generator->Update(m_salt->value, sizeof(m_salt->value));
/* Update with the buffer and get the hash. */
generator->Update(buffer, block_size);
generator->GetHash(out, sizeof(*out));
} else {
/* If we have no hash salt, just calculate the hash. */
m_hash_generator_factory->GenerateHash(out, sizeof(*out), buffer, block_size);
}
/* Set the validation bit, if the hash is for save data. */
if (m_storage_type == fs::StorageType_SaveData) {
/* Set the validation bit. */
SetValidationBit(out);
} else {
/* If we're not writable, just calculate the hash. */
m_hash_generator_factory->GenerateHash(out, sizeof(*out), buffer, block_size);
}
}
@ -407,7 +416,7 @@ namespace ams::fssystem {
/* We succeeded. */
clear_guard.Cancel();
return ResultSuccess();
R_SUCCEED();
}
Result IntegrityVerificationStorage::WriteBlockSignature(const void *src, size_t src_size, s64 offset, size_t size) {
@ -425,7 +434,7 @@ namespace ams::fssystem {
R_TRY(m_hash_storage.Write(sign_offset, src, sign_size));
/* We succeeded. */
return ResultSuccess();
R_SUCCEED();
}
Result IntegrityVerificationStorage::VerifyHash(const void *buf, BlockHash *hash, std::unique_ptr<fssystem::IHash256Generator> &generator) {
@ -436,8 +445,8 @@ namespace ams::fssystem {
/* Get the comparison hash. */
auto &cmp_hash = *hash;
/* If save data, check if the data is uninitialized. */
if (m_storage_type == fs::StorageType_SaveData) {
/* If writable, check if the data is uninitialized. */
if (m_is_writable) {
bool is_cleared = false;
R_TRY(this->IsCleared(std::addressof(is_cleared), cmp_hash));
R_UNLESS(!is_cleared, fs::ResultClearedRealDataVerificationFailed());
@ -454,19 +463,19 @@ namespace ams::fssystem {
/* Return the appropriate result. */
if (m_is_real_data) {
return fs::ResultUnclearedRealDataVerificationFailed();
R_THROW(fs::ResultUnclearedRealDataVerificationFailed());
} else {
return fs::ResultNonRealDataVerificationFailed();
R_THROW(fs::ResultNonRealDataVerificationFailed());
}
}
return ResultSuccess();
R_SUCCEED();
}
Result IntegrityVerificationStorage::IsCleared(bool *is_cleared, const BlockHash &hash) {
/* Validate preconditions. */
AMS_ASSERT(is_cleared != nullptr);
AMS_ASSERT(m_storage_type == fs::StorageType_SaveData);
AMS_ASSERT(m_is_writable);
/* Default to uncleared. */
*is_cleared = false;
@ -481,7 +490,7 @@ namespace ams::fssystem {
/* Set cleared. */
*is_cleared = true;
return ResultSuccess();
R_SUCCEED();
}
}

View file

@ -31,6 +31,12 @@ namespace ams::fssystem {
constexpr inline s32 SparseTableCacheBlockSize = SparseStorage::NodeSize;
constexpr inline s32 SparseTableCacheCount = 4;
constexpr inline s32 IntegrityDataCacheCount = 24;
constexpr inline s32 IntegrityHashCacheCount = 8;
constexpr inline s32 IntegrityDataCacheCountForMeta = 16;
constexpr inline s32 IntegrityHashCacheCountForMeta = 2;
//TODO: Better names for these?
//constexpr inline s32 CompressedDataBlockSize = 64_KB;
//constexpr inline s32 CompressedContinuousReadingSizeMax = 640_KB;
@ -56,21 +62,21 @@ namespace ams::fssystem {
AMS_ASSERT(m_storage != nullptr);
/* Read from the base storage. */
return m_storage->Read(offset, buffer, size);
R_RETURN(m_storage->Read(offset, buffer, size));
}
virtual Result GetSize(s64 *out) override {
/* Validate pre-conditions. */
AMS_ASSERT(m_storage != nullptr);
return m_storage->GetSize(out);
R_RETURN(m_storage->GetSize(out));
}
virtual Result Flush() override {
/* Validate pre-conditions. */
AMS_ASSERT(m_storage != nullptr);
return m_storage->Flush();
R_RETURN(m_storage->Flush());
}
virtual Result Write(s64 offset, const void *buffer, size_t size) override {
@ -78,200 +84,21 @@ namespace ams::fssystem {
AMS_ASSERT(m_storage != nullptr);
/* Read from the base storage. */
return m_storage->Write(offset, buffer, size);
R_RETURN(m_storage->Write(offset, buffer, size));
}
virtual Result SetSize(s64 size) override {
/* Validate pre-conditions. */
AMS_ASSERT(m_storage != nullptr);
return m_storage->SetSize(size);
R_RETURN(m_storage->SetSize(size));
}
virtual Result OperateRange(void *dst, size_t dst_size, fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) override {
/* Validate pre-conditions. */
AMS_ASSERT(m_storage != nullptr);
return m_storage->OperateRange(dst, dst_size, op_id, offset, size, src, src_size);
}
};
class AesCtrStorageExternal : public ::ams::fs::IStorage, public ::ams::fs::impl::Newable {
NON_COPYABLE(AesCtrStorageExternal);
NON_MOVEABLE(AesCtrStorageExternal);
public:
static constexpr size_t BlockSize = crypto::Aes128CtrEncryptor::BlockSize;
static constexpr size_t KeySize = crypto::Aes128CtrEncryptor::KeySize;
static constexpr size_t IvSize = crypto::Aes128CtrEncryptor::IvSize;
private:
std::shared_ptr<fs::IStorage> m_base_storage;
u8 m_iv[IvSize];
DecryptAesCtrFunction m_decrypt_function;
s32 m_key_index;
u8 m_encrypted_key[KeySize];
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) {
AMS_ASSERT(m_base_storage != nullptr);
AMS_ASSERT(enc_key_size == KeySize);
AMS_ASSERT(iv != nullptr);
AMS_ASSERT(iv_size == IvSize);
AMS_UNUSED(iv_size);
std::memcpy(m_iv, iv, IvSize);
std::memcpy(m_encrypted_key, enc_key, enc_key_size);
}
virtual Result Read(s64 offset, void *buffer, size_t size) override {
/* Allow zero size. */
R_SUCCEED_IF(size == 0);
/* Validate arguments. */
/* NOTE: For some reason, Nintendo uses InvalidArgument instead of InvalidOffset/InvalidSize here. */
R_UNLESS(buffer != nullptr, fs::ResultNullptrArgument());
R_UNLESS(util::IsAligned(offset, BlockSize), fs::ResultInvalidArgument());
R_UNLESS(util::IsAligned(size, BlockSize), fs::ResultInvalidArgument());
/* Read the data. */
R_TRY(m_base_storage->Read(offset, buffer, size));
/* Temporarily increase our thread priority. */
ScopedThreadPriorityChanger cp(+1, ScopedThreadPriorityChanger::Mode::Relative);
/* Allocate a pooled buffer for decryption. */
PooledBuffer pooled_buffer;
pooled_buffer.AllocateParticularlyLarge(size, BlockSize);
AMS_ASSERT(pooled_buffer.GetSize() >= BlockSize);
/* Setup the counter. */
u8 ctr[IvSize];
std::memcpy(ctr, m_iv, IvSize);
AddCounter(ctr, IvSize, offset / BlockSize);
/* Setup tracking. */
size_t remaining_size = size;
s64 cur_offset = 0;
while (remaining_size > 0) {
/* Get the current size to process. */
size_t cur_size = std::min(pooled_buffer.GetSize(), remaining_size);
char *dst = static_cast<char *>(buffer) + cur_offset;
/* Decrypt into the temporary buffer */
m_decrypt_function(pooled_buffer.GetBuffer(), cur_size, m_key_index, m_encrypted_key, KeySize, ctr, IvSize, dst, cur_size);
/* Copy to the destination. */
std::memcpy(dst, pooled_buffer.GetBuffer(), cur_size);
/* Update tracking. */
cur_offset += cur_size;
remaining_size -= cur_size;
if (remaining_size > 0) {
AddCounter(ctr, IvSize, cur_size / BlockSize);
}
}
return ResultSuccess();
}
virtual Result OperateRange(void *dst, size_t dst_size, fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) override {
switch (op_id) {
case fs::OperationId::QueryRange:
{
/* Validate that we have an output range info. */
R_UNLESS(dst != nullptr, fs::ResultNullptrArgument());
R_UNLESS(dst_size == sizeof(fs::QueryRangeInfo), fs::ResultInvalidSize());
/* Operate on our base storage. */
R_TRY(m_base_storage->OperateRange(dst, dst_size, op_id, offset, size, src, src_size));
/* Add in new flags. */
fs::QueryRangeInfo new_info;
new_info.Clear();
new_info.aes_ctr_key_type = static_cast<s32>(m_key_index >= 0 ? fs::AesCtrKeyTypeFlag::InternalKeyForHardwareAes : fs::AesCtrKeyTypeFlag::ExternalKeyForHardwareAes);
/* Merge the new info in. */
reinterpret_cast<fs::QueryRangeInfo *>(dst)->Merge(new_info);
return ResultSuccess();
}
default:
{
/* Operate on our base storage. */
R_TRY(m_base_storage->OperateRange(dst, dst_size, op_id, offset, size, src, src_size));
return ResultSuccess();
}
}
}
virtual Result GetSize(s64 *out) override {
return m_base_storage->GetSize(out);
}
virtual Result Flush() override {
return ResultSuccess();
}
virtual Result Write(s64 offset, const void *buffer, size_t size) override {
AMS_UNUSED(offset, buffer, size);
return fs::ResultUnsupportedWriteForAesCtrStorageExternal();
}
virtual Result SetSize(s64 size) override {
AMS_UNUSED(size);
return fs::ResultUnsupportedSetSizeForAesCtrStorageExternal();
}
};
template<typename F>
class SwitchStorage : public ::ams::fs::IStorage, public ::ams::fs::impl::Newable {
NON_COPYABLE(SwitchStorage);
NON_MOVEABLE(SwitchStorage);
private:
std::shared_ptr<fs::IStorage> m_true_storage;
std::shared_ptr<fs::IStorage> m_false_storage;
F m_truth_function;
private:
ALWAYS_INLINE std::shared_ptr<fs::IStorage> &SelectStorage() {
return m_truth_function() ? m_true_storage : m_false_storage;
}
public:
SwitchStorage(std::shared_ptr<fs::IStorage> t, std::shared_ptr<fs::IStorage> f, F func) : m_true_storage(std::move(t)), m_false_storage(std::move(f)), m_truth_function(func) { /* ... */ }
virtual Result Read(s64 offset, void *buffer, size_t size) override {
return this->SelectStorage()->Read(offset, buffer, size);
}
virtual Result OperateRange(void *dst, size_t dst_size, fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) override {
switch (op_id) {
case fs::OperationId::Invalidate:
{
R_TRY(m_true_storage->OperateRange(dst, dst_size, op_id, offset, size, src, src_size));
R_TRY(m_false_storage->OperateRange(dst, dst_size, op_id, offset, size, src, src_size));
return ResultSuccess();
}
case fs::OperationId::QueryRange:
{
R_TRY(this->SelectStorage()->OperateRange(dst, dst_size, op_id, offset, size, src, src_size));
return ResultSuccess();
}
default:
return fs::ResultUnsupportedOperateRangeForSwitchStorage();
}
}
virtual Result GetSize(s64 *out) override {
return this->SelectStorage()->GetSize(out);
}
virtual Result Flush() override {
return this->SelectStorage()->Flush();
}
virtual Result Write(s64 offset, const void *buffer, size_t size) override {
return this->SelectStorage()->Write(offset, buffer, size);
}
virtual Result SetSize(s64 size) override {
return this->SelectStorage()->SetSize(size);
R_RETURN(m_storage->OperateRange(dst, dst_size, op_id, offset, size, src, src_size));
}
};
@ -307,7 +134,7 @@ namespace ams::fssystem {
R_UNLESS(*out_splitter != nullptr, fs::ResultAllocationMemoryFailedAllocateShared());
}
return ResultSuccess();
R_SUCCEED();
}
Result NcaFileSystemDriver::OpenStorageImpl(std::shared_ptr<fs::IStorage> *out, NcaFsHeaderReader *out_header_reader, s32 fs_index, StorageContext *ctx) {
@ -328,8 +155,17 @@ namespace ams::fssystem {
/* Process sparse layer. */
s64 fs_data_offset = 0;
if (out_header_reader->ExistsSparseLayer()) {
/* Get the sparse info. */
const auto &sparse_info = out_header_reader->GetSparseInfo();
/* Create based on whether we have a meta hash layer. */
if (out_header_reader->ExistsSparseMetaHashLayer()) {
/* Create the sparse storage with verification. */
R_TRY(this->CreateSparseStorageWithVerification(std::addressof(storage), std::addressof(fs_data_offset), ctx != nullptr ? std::addressof(ctx->current_sparse_storage) : nullptr, ctx != nullptr ? std::addressof(ctx->sparse_storage_meta_storage) : nullptr, ctx != nullptr ? std::addressof(ctx->sparse_layer_info_storage) : nullptr, fs_index, out_header_reader->GetAesCtrUpperIv(), sparse_info, out_header_reader->GetSparseMetaDataHashDataInfo(), out_header_reader->GetSparseMetaHashType()));
} else {
/* Create the sparse storage. */
R_TRY(this->CreateSparseStorage(std::addressof(storage), std::addressof(fs_data_offset), ctx != nullptr ? std::addressof(ctx->current_sparse_storage) : nullptr, ctx != nullptr ? std::addressof(ctx->sparse_storage_meta_storage) : nullptr, fs_index, out_header_reader->GetAesCtrUpperIv(), out_header_reader->GetSparseInfo()));
R_TRY(this->CreateSparseStorage(std::addressof(storage), std::addressof(fs_data_offset), ctx != nullptr ? std::addressof(ctx->current_sparse_storage) : nullptr, ctx != nullptr ? std::addressof(ctx->sparse_storage_meta_storage) : nullptr, fs_index, out_header_reader->GetAesCtrUpperIv(), sparse_info));
}
} else {
/* Get the data offsets. */
fs_data_offset = GetFsOffset(*m_reader, fs_index);
@ -350,13 +186,28 @@ namespace ams::fssystem {
/* Process patch layer. */
const auto &patch_info = out_header_reader->GetPatchInfo();
std::shared_ptr<fs::IStorage> patch_meta_aes_ctr_ex_meta_storage;
std::shared_ptr<fs::IStorage> patch_meta_indirect_meta_storage;
if (out_header_reader->ExistsPatchMetaHashLayer()) {
/* Check the meta hash type. */
R_UNLESS(out_header_reader->GetPatchMetaHashType() == NcaFsHeader::MetaDataHashType::HierarchicalIntegrity, fs::ResultRomNcaInvalidPatchMetaDataHashType());
/* Create the patch meta storage. */
R_TRY(this->CreatePatchMetaStorage(std::addressof(patch_meta_aes_ctr_ex_meta_storage), std::addressof(patch_meta_indirect_meta_storage), ctx != nullptr ? std::addressof(ctx->patch_layer_info_storage) : nullptr, storage, fs_data_offset, out_header_reader->GetAesCtrUpperIv(), patch_info, out_header_reader->GetPatchMetaDataHashDataInfo(), m_hash_generator_factory_selector->GetFactory(fssystem::HashAlgorithmType_Sha2)));
}
if (patch_info.HasAesCtrExTable()) {
/* Check the encryption type. */
AMS_ASSERT(out_header_reader->GetEncryptionType() == NcaFsHeader::EncryptionType::AesCtrEx);
AMS_ASSERT(out_header_reader->GetEncryptionType() == NcaFsHeader::EncryptionType::None || out_header_reader->GetEncryptionType() == NcaFsHeader::EncryptionType::AesCtrEx || out_header_reader->GetEncryptionType() == NcaFsHeader::EncryptionType::AesCtrExSkipLayerHash);
/* Create the ex meta storage. */
std::shared_ptr<fs::IStorage> aes_ctr_ex_storage_meta_storage;
R_TRY(this->CreateAesCtrExMetaStorage(std::addressof(aes_ctr_ex_storage_meta_storage), storage, fs_data_offset, out_header_reader->GetAesCtrUpperIv(), patch_info));
std::shared_ptr<fs::IStorage> aes_ctr_ex_storage_meta_storage = patch_meta_aes_ctr_ex_meta_storage;
if (aes_ctr_ex_storage_meta_storage == nullptr) {
/* If we don't have a meta storage, we must not have a patch meta hash layer. */
AMS_ASSERT(!out_header_reader->ExistsPatchMetaHashLayer());
R_TRY(this->CreateAesCtrExStorageMetaStorage(std::addressof(aes_ctr_ex_storage_meta_storage), storage, fs_data_offset, out_header_reader->GetEncryptionType(), out_header_reader->GetAesCtrUpperIv(), patch_info));
}
/* Create the ex storage. */
std::shared_ptr<fs::IStorage> aes_ctr_ex_storage;
@ -383,8 +234,18 @@ namespace ams::fssystem {
case NcaFsHeader::EncryptionType::AesCtr:
R_TRY(this->CreateAesCtrStorage(std::addressof(storage), std::move(storage), fs_data_offset, out_header_reader->GetAesCtrUpperIv(), AlignmentStorageRequirement_None));
break;
case NcaFsHeader::EncryptionType::AesCtrSkipLayerHash:
{
/* Create the aes ctr storage. */
std::shared_ptr<fs::IStorage> aes_ctr_storage;
R_TRY(this->CreateAesCtrStorage(std::addressof(aes_ctr_storage), storage, fs_data_offset, out_header_reader->GetAesCtrUpperIv(), AlignmentStorageRequirement_None));
/* Create region switch storage. */
R_TRY(this->CreateRegionSwitchStorage(std::addressof(storage), out_header_reader, std::move(storage), std::move(aes_ctr_storage)));
}
break;
default:
return fs::ResultInvalidNcaFsHeaderEncryptionType();
R_THROW(fs::ResultInvalidNcaFsHeaderEncryptionType());
}
/* Potentially save storages to our context. */
@ -396,8 +257,13 @@ namespace ams::fssystem {
/* Process indirect layer. */
if (patch_info.HasIndirectTable()) {
/* Create the indirect meta storage */
std::shared_ptr<fs::IStorage> indirect_storage_meta_storage;
std::shared_ptr<fs::IStorage> indirect_storage_meta_storage = patch_meta_indirect_meta_storage;
if (indirect_storage_meta_storage == nullptr) {
/* If we don't have a meta storage, we must not have a patch meta hash layer. */
AMS_ASSERT(!out_header_reader->ExistsPatchMetaHashLayer());
R_TRY(this->CreateIndirectStorageMetaStorage(std::addressof(indirect_storage_meta_storage), storage, patch_info));
}
/* Potentially save the indirect meta storage to our context. */
if (ctx != nullptr) {
@ -436,7 +302,7 @@ namespace ams::fssystem {
/* Check if we're sparse or requested to skip the integrity layer. */
if (out_header_reader->ExistsSparseLayer() || (ctx != nullptr && ctx->open_raw_storage)) {
*out = std::move(storage);
return ResultSuccess();
R_SUCCEED();
}
/* Create the non-raw storage. */
@ -456,7 +322,7 @@ namespace ams::fssystem {
R_TRY(this->CreateIntegrityVerificationStorage(std::addressof(storage), std::move(storage), header_reader->GetHashData().integrity_meta_info, m_hash_generator_factory_selector->GetFactory(fssystem::HashAlgorithmType_Sha2)));
break;
default:
return fs::ResultInvalidNcaFsHeaderHashType();
R_THROW(fs::ResultInvalidNcaFsHeaderHashType());
}
/* Process compression layer. */
@ -466,7 +332,7 @@ namespace ams::fssystem {
/* Set output storage. */
*out = std::move(storage);
return ResultSuccess();
R_SUCCEED();
}
Result NcaFileSystemDriver::OpenIndirectableStorageAsOriginal(std::shared_ptr<fs::IStorage> *out, const NcaFsHeaderReader *header_reader, StorageContext *ctx) {
@ -479,8 +345,17 @@ namespace ams::fssystem {
/* Process sparse layer. */
s64 fs_data_offset = 0;
if (header_reader->ExistsSparseLayer()) {
/* Get the sparse info. */
const auto &sparse_info = header_reader->GetSparseInfo();
/* Create based on whether we have a meta hash layer. */
if (header_reader->ExistsSparseMetaHashLayer()) {
/* Create the sparse storage with verification. */
R_TRY(this->CreateSparseStorageWithVerification(std::addressof(storage), std::addressof(fs_data_offset), ctx != nullptr ? std::addressof(ctx->original_sparse_storage) : nullptr, ctx != nullptr ? std::addressof(ctx->sparse_storage_meta_storage) : nullptr, ctx != nullptr ? std::addressof(ctx->sparse_layer_info_storage) : nullptr, fs_index, header_reader->GetAesCtrUpperIv(), sparse_info, header_reader->GetSparseMetaDataHashDataInfo(), header_reader->GetSparseMetaHashType()));
} else {
/* Create the sparse storage. */
R_TRY(this->CreateSparseStorage(std::addressof(storage), std::addressof(fs_data_offset), ctx != nullptr ? std::addressof(ctx->original_sparse_storage) : nullptr, ctx != nullptr ? std::addressof(ctx->sparse_storage_meta_storage) : nullptr, fs_index, header_reader->GetAesCtrUpperIv(), header_reader->GetSparseInfo()));
R_TRY(this->CreateSparseStorage(std::addressof(storage), std::addressof(fs_data_offset), ctx != nullptr ? std::addressof(ctx->original_sparse_storage) : nullptr, ctx != nullptr ? std::addressof(ctx->sparse_storage_meta_storage) : nullptr, fs_index, header_reader->GetAesCtrUpperIv(), sparse_info));
}
} else {
/* Get the data offsets. */
fs_data_offset = GetFsOffset(*m_reader, fs_index);
@ -506,12 +381,12 @@ namespace ams::fssystem {
R_TRY(this->CreateAesCtrStorage(std::addressof(storage), std::move(storage), fs_data_offset, header_reader->GetAesCtrUpperIv(), AlignmentStorageRequirement_CacheBlockSize));
break;
default:
return fs::ResultInvalidNcaFsHeaderEncryptionType();
R_THROW(fs::ResultInvalidNcaFsHeaderEncryptionType());
}
/* Set output storage. */
*out = std::move(storage);
return ResultSuccess();
R_SUCCEED();
}
Result NcaFileSystemDriver::CreateBodySubStorage(std::shared_ptr<fs::IStorage> *out, s64 offset, s64 size) {
@ -532,7 +407,7 @@ namespace ams::fssystem {
/* Set the output storage. */
*out = std::move(body_substorage);
return ResultSuccess();
R_SUCCEED();
}
Result NcaFileSystemDriver::CreateAesCtrStorage(std::shared_ptr<fs::IStorage> *out, std::shared_ptr<fs::IStorage> base_storage, s64 offset, const NcaAesCtrUpperIv &upper_iv, AlignmentStorageRequirement alignment_storage_requirement) {
@ -575,7 +450,7 @@ namespace ams::fssystem {
/* Create the ctr storage. */
std::shared_ptr<fs::IStorage> aes_ctr_storage;
if (m_reader->HasExternalDecryptionKey()) {
aes_ctr_storage = fssystem::AllocateShared<AesCtrStorageExternal>(std::move(base_storage), m_reader->GetExternalDecryptionKey(), AesCtrStorageExternal::KeySize, iv, AesCtrStorageExternal::IvSize, m_reader->GetExternalDecryptAesCtrFunctionForExternalKey(), -1);
aes_ctr_storage = fssystem::AllocateShared<AesCtrStorageExternal>(std::move(base_storage), m_reader->GetExternalDecryptionKey(), AesCtrStorageExternal::KeySize, iv, AesCtrStorageExternal::IvSize, m_reader->GetExternalDecryptAesCtrFunctionForExternalKey(), -1, -1);
R_UNLESS(aes_ctr_storage != nullptr, fs::ResultAllocationMemoryFailedAllocateShared());
} else {
/* Create software decryption storage. */
@ -584,7 +459,7 @@ namespace ams::fssystem {
/* If we have a hardware key and should use it, make the hardware decryption storage. */
if (m_reader->HasInternalDecryptionKeyForAesHw() && !m_reader->IsSoftwareAesPrioritized()) {
auto hw_storage = fssystem::AllocateShared<AesCtrStorageExternal>(base_storage, m_reader->GetDecryptionKey(NcaHeader::DecryptionKey_AesCtrHw), AesCtrStorageExternal::KeySize, iv, AesCtrStorageExternal::IvSize, m_reader->GetExternalDecryptAesCtrFunction(), GetKeyTypeValue(m_reader->GetKeyIndex(), m_reader->GetKeyGeneration()));
auto hw_storage = fssystem::AllocateShared<AesCtrStorageExternal>(base_storage, m_reader->GetDecryptionKey(NcaHeader::DecryptionKey_AesCtrHw), AesCtrStorageExternal::KeySize, iv, AesCtrStorageExternal::IvSize, m_reader->GetExternalDecryptAesCtrFunction(), m_reader->GetKeyIndex(), m_reader->GetKeyGeneration());
R_UNLESS(hw_storage != nullptr, fs::ResultAllocationMemoryFailedAllocateShared());
/* Create the selection storage. */
@ -605,7 +480,7 @@ namespace ams::fssystem {
/* Set the out storage. */
*out = std::move(aligned_storage);
return ResultSuccess();
R_SUCCEED();
}
Result NcaFileSystemDriver::CreateAesXtsStorage(std::shared_ptr<fs::IStorage> *out, std::shared_ptr<fs::IStorage> base_storage, s64 offset) {
@ -629,7 +504,7 @@ namespace ams::fssystem {
/* Set the out storage. */
*out = std::move(aligned_storage);
return ResultSuccess();
R_SUCCEED();
}
Result NcaFileSystemDriver::CreateSparseStorageMetaStorage(std::shared_ptr<fs::IStorage> *out, std::shared_ptr<fs::IStorage> base_storage, s64 offset, const NcaAesCtrUpperIv &upper_iv, const NcaSparseInfo &sparse_info) {
@ -663,7 +538,7 @@ namespace ams::fssystem {
/* Set the output. */
*out = std::move(meta_storage);
return ResultSuccess();
R_SUCCEED();
}
Result NcaFileSystemDriver::CreateSparseStorageCore(std::shared_ptr<fssystem::SparseStorage> *out, std::shared_ptr<fs::IStorage> base_storage, s64 base_size, std::shared_ptr<fs::IStorage> meta_storage, const NcaSparseInfo &sparse_info, bool external_info) {
@ -700,7 +575,7 @@ namespace ams::fssystem {
/* Set the output. */
*out = std::move(sparse_storage);
return ResultSuccess();
R_SUCCEED();
}
Result NcaFileSystemDriver::CreateSparseStorage(std::shared_ptr<fs::IStorage> *out, s64 *out_fs_data_offset, std::shared_ptr<fssystem::SparseStorage> *out_sparse_storage, std::shared_ptr<fs::IStorage> *out_meta_storage, s32 index, const NcaAesCtrUpperIv &upper_iv, const NcaSparseInfo &sparse_info) {
@ -757,13 +632,125 @@ namespace ams::fssystem {
/* Set the output storage. */
*out = std::move(sparse_storage);
return ResultSuccess();
R_SUCCEED();
}
Result NcaFileSystemDriver::CreateAesCtrExMetaStorage(std::shared_ptr<fs::IStorage> *out, std::shared_ptr<fs::IStorage> base_storage, s64 offset, const NcaAesCtrUpperIv &upper_iv, const NcaPatchInfo &patch_info) {
Result NcaFileSystemDriver::CreateSparseStorageMetaStorageWithVerification(std::shared_ptr<fs::IStorage> *out, std::shared_ptr<fs::IStorage> *out_layer_info_storage, std::shared_ptr<fs::IStorage> base_storage, s64 offset, const NcaAesCtrUpperIv &upper_iv, const NcaSparseInfo &sparse_info, const NcaMetaDataHashDataInfo &meta_data_hash_data_info, IHash256GeneratorFactory *hgf) {
/* Validate preconditions. */
AMS_ASSERT(out != nullptr);
AMS_ASSERT(base_storage != nullptr);
AMS_ASSERT(hgf != nullptr);
/* Get the base storage size. */
s64 base_size = 0;
R_TRY(base_storage->GetSize(std::addressof(base_size)));
/* Get the meta extents. */
const auto meta_offset = sparse_info.bucket.offset;
const auto meta_size = sparse_info.bucket.size;
R_UNLESS(meta_offset + meta_size - offset <= base_size, fs::ResultNcaBaseStorageOutOfRangeB());
/* Get the meta data hash data extents. */
const s64 meta_data_hash_data_offset = meta_data_hash_data_info.offset;
const s64 meta_data_hash_data_size = util::AlignUp<s64>(meta_data_hash_data_info.size, NcaHeader::CtrBlockSize);
R_UNLESS(meta_data_hash_data_offset + meta_data_hash_data_size <= base_size, fs::ResultNcaBaseStorageOutOfRangeB());
/* Check that the meta is before the hash data. */
R_UNLESS(meta_offset + meta_size <= meta_data_hash_data_offset, fs::ResultRomNcaInvalidSparseMetaDataHashDataOffset());
/* Check that offsets are appropriately aligned. */
R_UNLESS(util::IsAligned<s64>(meta_data_hash_data_offset, NcaHeader::CtrBlockSize), fs::ResultRomNcaInvalidSparseMetaDataHashDataOffset());
R_UNLESS(util::IsAligned<s64>(meta_offset, NcaHeader::CtrBlockSize), fs::ResultInvalidNcaFsHeader());
/* Create the meta storage. */
auto enc_storage = fssystem::AllocateShared<fs::SubStorage>(std::move(base_storage), meta_offset, meta_data_hash_data_offset + meta_data_hash_data_size - meta_offset);
R_UNLESS(enc_storage != nullptr, fs::ResultAllocationMemoryFailedAllocateShared());
/* Create the decrypted storage. */
std::shared_ptr<fs::IStorage> decrypted_storage;
R_TRY(this->CreateAesCtrStorage(std::addressof(decrypted_storage), std::move(enc_storage), offset + meta_offset, sparse_info.MakeAesCtrUpperIv(upper_iv), AlignmentStorageRequirement_None));
/* Create the verification storage. */
std::shared_ptr<fs::IStorage> integrity_storage;
R_TRY_CATCH(this->CreateIntegrityVerificationStorageForMeta(std::addressof(integrity_storage), out_layer_info_storage, std::move(decrypted_storage), meta_offset, meta_data_hash_data_info, hgf)) {
R_CONVERT(fs::ResultInvalidNcaMetaDataHashDataSize, fs::ResultRomNcaInvalidSparseMetaDataHashDataSize())
R_CONVERT(fs::ResultInvalidNcaMetaDataHashDataHash, fs::ResultRomNcaInvalidSparseMetaDataHashDataHash())
} R_END_TRY_CATCH;
/* Create the meta storage. */
auto meta_storage = fssystem::AllocateShared<fs::SubStorage>(std::move(integrity_storage), 0, meta_size);
R_UNLESS(meta_storage != nullptr, fs::ResultAllocationMemoryFailedAllocateShared());
/* Set the output. */
*out = std::move(meta_storage);
R_SUCCEED();
}
Result NcaFileSystemDriver::CreateSparseStorageWithVerification(std::shared_ptr<fs::IStorage> *out, s64 *out_fs_data_offset, std::shared_ptr<fssystem::SparseStorage> *out_sparse_storage, std::shared_ptr<fs::IStorage> *out_meta_storage, std::shared_ptr<fs::IStorage> *out_layer_info_storage, s32 index, const NcaAesCtrUpperIv &upper_iv, const NcaSparseInfo &sparse_info, const NcaMetaDataHashDataInfo &meta_data_hash_data_info, NcaFsHeader::MetaDataHashType meta_data_hash_type) {
/* Validate preconditions. */
AMS_ASSERT(out != nullptr);
AMS_ASSERT(out_fs_data_offset != nullptr);
/* Check the sparse info generation. */
R_UNLESS(sparse_info.generation != 0, fs::ResultInvalidNcaHeader());
/* Read and verify the bucket tree header. */
BucketTree::Header header;
std::memcpy(std::addressof(header), sparse_info.bucket.header, sizeof(header));
R_TRY(header.Verify());
/* Determine the storage extents. */
const auto fs_offset = GetFsOffset(*m_reader, index);
const auto fs_end_offset = GetFsEndOffset(*m_reader, index);
const auto fs_size = fs_end_offset - fs_offset;
/* Create the sparse storage. */
std::shared_ptr<fssystem::SparseStorage> sparse_storage;
if (header.entry_count != 0) {
/* Create the body substorage. */
std::shared_ptr<fs::IStorage> body_substorage;
R_TRY(this->CreateBodySubStorage(std::addressof(body_substorage), sparse_info.physical_offset, util::AlignUp<s64>(static_cast<s64>(meta_data_hash_data_info.offset) + static_cast<s64>(meta_data_hash_data_info.size), NcaHeader::CtrBlockSize)));
/* Check the meta data hash type. */
R_UNLESS(meta_data_hash_type == NcaFsHeader::MetaDataHashType::HierarchicalIntegrity, fs::ResultRomNcaInvalidSparseMetaDataHashType());
/* Create the meta storage. */
std::shared_ptr<fs::IStorage> meta_storage;
R_TRY(this->CreateSparseStorageMetaStorageWithVerification(std::addressof(meta_storage), out_layer_info_storage, body_substorage, sparse_info.physical_offset, upper_iv, sparse_info, meta_data_hash_data_info, m_hash_generator_factory_selector->GetFactory(fssystem::HashAlgorithmType_Sha2)));
/* Potentially set the output meta storage. */
if (out_meta_storage != nullptr) {
*out_meta_storage = meta_storage;
}
/* Create the sparse storage. */
R_TRY(this->CreateSparseStorageCore(std::addressof(sparse_storage), body_substorage, sparse_info.GetPhysicalSize(), std::move(meta_storage), sparse_info, false));
} else {
/* If there are no entries, there's nothing to actually do. */
sparse_storage = fssystem::AllocateShared<fssystem::SparseStorage>();
R_UNLESS(sparse_storage != nullptr, fs::ResultAllocationMemoryFailedAllocateShared());
sparse_storage->Initialize(fs_size);
}
/* Potentially set the output sparse storage. */
if (out_sparse_storage != nullptr) {
*out_sparse_storage = sparse_storage;
}
/* Set the output fs data offset. */
*out_fs_data_offset = fs_offset;
/* Set the output storage. */
*out = std::move(sparse_storage);
R_SUCCEED();
}
Result NcaFileSystemDriver::CreateAesCtrExStorageMetaStorage(std::shared_ptr<fs::IStorage> *out, std::shared_ptr<fs::IStorage> base_storage, s64 offset, NcaFsHeader::EncryptionType encryption_type, const NcaAesCtrUpperIv &upper_iv, const NcaPatchInfo &patch_info) {
/* Validate preconditions. */
AMS_ASSERT(out != nullptr);
AMS_ASSERT(base_storage != nullptr);
AMS_ASSERT(encryption_type == NcaFsHeader::EncryptionType::None || encryption_type == NcaFsHeader::EncryptionType::AesCtrEx || encryption_type == NcaFsHeader::EncryptionType::AesCtrExSkipLayerHash);
AMS_ASSERT(patch_info.HasAesCtrExTable());
/* Validate patch info extents. */
@ -786,7 +773,12 @@ namespace ams::fssystem {
/* Create the decrypted storage. */
std::shared_ptr<fs::IStorage> decrypted_storage;
if (encryption_type != NcaFsHeader::EncryptionType::None) {
R_TRY(this->CreateAesCtrStorage(std::addressof(decrypted_storage), std::move(enc_storage), offset + meta_offset, upper_iv, AlignmentStorageRequirement_None));
} else {
/* If encryption type is none, don't do any decryption. */
decrypted_storage = std::move(enc_storage);
}
/* Create meta storage. */
auto meta_storage = fssystem::AllocateShared<BufferedStorage>();
@ -802,7 +794,7 @@ namespace ams::fssystem {
/* Set the output. */
*out = std::move(aligned_storage);
return ResultSuccess();
R_SUCCEED();
}
Result NcaFileSystemDriver::CreateAesCtrExStorage(std::shared_ptr<fs::IStorage> *out, std::shared_ptr<fssystem::AesCtrCounterExtendedStorage> *out_ext, std::shared_ptr<fs::IStorage> base_storage, std::shared_ptr<fs::IStorage> meta_storage, s64 counter_offset, const NcaAesCtrUpperIv &upper_iv, const NcaPatchInfo &patch_info) {
@ -839,7 +831,7 @@ namespace ams::fssystem {
if (m_reader->HasExternalDecryptionKey()) {
/* Create the decryptor. */
std::unique_ptr<AesCtrCounterExtendedStorage::IDecryptor> decryptor;
R_TRY(AesCtrCounterExtendedStorage::CreateExternalDecryptor(std::addressof(decryptor), m_reader->GetExternalDecryptAesCtrFunctionForExternalKey(), -1));
R_TRY(AesCtrCounterExtendedStorage::CreateExternalDecryptor(std::addressof(decryptor), m_reader->GetExternalDecryptAesCtrFunctionForExternalKey(), -1, -1));
/* Create the aes ctr ex storage. */
auto impl_storage = fssystem::AllocateShared<AesCtrCounterExtendedStorage>();
@ -876,7 +868,7 @@ namespace ams::fssystem {
if (m_reader->HasInternalDecryptionKeyForAesHw() && !m_reader->IsSoftwareAesPrioritized()) {
/* Create the hardware decryptor. */
std::unique_ptr<AesCtrCounterExtendedStorage::IDecryptor> hw_decryptor;
R_TRY(AesCtrCounterExtendedStorage::CreateExternalDecryptor(std::addressof(hw_decryptor), m_reader->GetExternalDecryptAesCtrFunction(), GetKeyTypeValue(m_reader->GetKeyIndex(), m_reader->GetKeyGeneration())));
R_TRY(AesCtrCounterExtendedStorage::CreateExternalDecryptor(std::addressof(hw_decryptor), m_reader->GetExternalDecryptAesCtrFunction(), m_reader->GetKeyIndex(), m_reader->GetKeyGeneration()));
/* Create the hardware storage. */
auto hw_storage = fssystem::AllocateShared<AesCtrCounterExtendedStorage>();
@ -904,7 +896,7 @@ namespace ams::fssystem {
/* Set the output. */
*out = std::move(aligned_storage);
return ResultSuccess();
R_SUCCEED();
}
Result NcaFileSystemDriver::CreateIndirectStorageMetaStorage(std::shared_ptr<fs::IStorage> *out, std::shared_ptr<fs::IStorage> base_storage, const NcaPatchInfo &patch_info) {
@ -929,7 +921,7 @@ namespace ams::fssystem {
/* Set the output. */
*out = std::move(meta_storage);
return ResultSuccess();
R_SUCCEED();
}
Result NcaFileSystemDriver::CreateIndirectStorage(std::shared_ptr<fs::IStorage> *out, std::shared_ptr<fssystem::IndirectStorage> *out_ind, std::shared_ptr<fs::IStorage> base_storage, std::shared_ptr<fs::IStorage> original_data_storage, std::shared_ptr<fs::IStorage> meta_storage, const NcaPatchInfo &patch_info) {
@ -985,7 +977,64 @@ namespace ams::fssystem {
/* Set the output. */
*out = std::move(indirect_storage);
return ResultSuccess();
R_SUCCEED();
}
Result NcaFileSystemDriver::CreatePatchMetaStorage(std::shared_ptr<fs::IStorage> *out_aes_ctr_ex_meta, std::shared_ptr<fs::IStorage> *out_indirect_meta, std::shared_ptr<fs::IStorage> *out_layer_info_storage, std::shared_ptr<fs::IStorage> base_storage, s64 offset, const NcaAesCtrUpperIv &upper_iv, const NcaPatchInfo &patch_info, const NcaMetaDataHashDataInfo &meta_data_hash_data_info, IHash256GeneratorFactory *hgf) {
/* Validate preconditions. */
AMS_ASSERT(out_aes_ctr_ex_meta != nullptr);
AMS_ASSERT(out_indirect_meta != nullptr);
AMS_ASSERT(base_storage != nullptr);
AMS_ASSERT(patch_info.HasAesCtrExTable());
AMS_ASSERT(patch_info.HasIndirectTable());
AMS_ASSERT(util::IsAligned<s64>(patch_info.aes_ctr_ex_size, NcaHeader::XtsBlockSize));
/* Validate patch info extents. */
R_UNLESS(patch_info.indirect_size > 0, fs::ResultInvalidNcaPatchInfoIndirectSize());
R_UNLESS(patch_info.aes_ctr_ex_size >= 0, fs::ResultInvalidNcaPatchInfoAesCtrExSize());
R_UNLESS(patch_info.indirect_size + patch_info.indirect_offset <= patch_info.aes_ctr_ex_offset, fs::ResultInvalidNcaPatchInfoAesCtrExOffset());
R_UNLESS(patch_info.aes_ctr_ex_offset + patch_info.aes_ctr_ex_size <= meta_data_hash_data_info.offset, fs::ResultRomNcaInvalidPatchMetaDataHashDataOffset());
/* Get the base storage size. */
s64 base_size;
R_TRY(base_storage->GetSize(std::addressof(base_size)));
/* Check that extents remain within range. */
R_UNLESS(patch_info.indirect_offset + patch_info.indirect_size <= base_size, fs::ResultNcaBaseStorageOutOfRangeE());
R_UNLESS(patch_info.aes_ctr_ex_offset + patch_info.aes_ctr_ex_size <= base_size, fs::ResultNcaBaseStorageOutOfRangeB());
/* Check that metadata hash data extents remain within range. */
const s64 meta_data_hash_data_offset = meta_data_hash_data_info.offset;
const s64 meta_data_hash_data_size = util::AlignUp<s64>(meta_data_hash_data_info.size, NcaHeader::CtrBlockSize);
R_UNLESS(meta_data_hash_data_offset + meta_data_hash_data_size <= base_size, fs::ResultNcaBaseStorageOutOfRangeB());
/* Create the encrypted storage. */
auto enc_storage = fssystem::AllocateShared<fs::SubStorage>(std::move(base_storage), patch_info.indirect_offset, meta_data_hash_data_offset + meta_data_hash_data_size - patch_info.indirect_offset);
R_UNLESS(enc_storage != nullptr, fs::ResultAllocationMemoryFailedAllocateShared());
/* Create the decrypted storage. */
std::shared_ptr<fs::IStorage> decrypted_storage;
R_TRY(this->CreateAesCtrStorage(std::addressof(decrypted_storage), std::move(enc_storage), offset + patch_info.indirect_offset, upper_iv, AlignmentStorageRequirement_None));
/* Create the verification storage. */
std::shared_ptr<fs::IStorage> integrity_storage;
R_TRY_CATCH(this->CreateIntegrityVerificationStorageForMeta(std::addressof(integrity_storage), out_layer_info_storage, std::move(decrypted_storage), patch_info.indirect_offset, meta_data_hash_data_info, hgf)) {
R_CONVERT(fs::ResultInvalidNcaMetaDataHashDataSize, fs::ResultRomNcaInvalidPatchMetaDataHashDataSize())
R_CONVERT(fs::ResultInvalidNcaMetaDataHashDataHash, fs::ResultRomNcaInvalidPatchMetaDataHashDataHash())
} R_END_TRY_CATCH;
/* Create the indirect meta storage. */
auto indirect_meta_storage = fssystem::AllocateShared<fs::SubStorage>(integrity_storage, patch_info.indirect_offset - patch_info.indirect_offset, patch_info.indirect_size);
R_UNLESS(indirect_meta_storage != nullptr, fs::ResultAllocationMemoryFailedAllocateShared());
/* Create the aes ctr ex meta storage. */
auto aes_ctr_ex_meta_storage = fssystem::AllocateShared<fs::SubStorage>(integrity_storage, patch_info.aes_ctr_ex_offset - patch_info.indirect_offset, patch_info.aes_ctr_ex_size);
R_UNLESS(aes_ctr_ex_meta_storage != nullptr, fs::ResultAllocationMemoryFailedAllocateShared());
/* Set the output. */
*out_aes_ctr_ex_meta = std::move(aes_ctr_ex_meta_storage);
*out_indirect_meta = std::move(indirect_meta_storage);
R_SUCCEED();
}
Result NcaFileSystemDriver::CreateSha256Storage(std::shared_ptr<fs::IStorage> *out, std::shared_ptr<fs::IStorage> base_storage, const NcaFsHeader::HashData::HierarchicalSha256Data &hash_data, IHash256GeneratorFactory *hgf) {
@ -1052,13 +1101,50 @@ namespace ams::fssystem {
/* Set the output. */
*out = std::move(aligned_storage);
return ResultSuccess();
R_SUCCEED();
}
Result NcaFileSystemDriver::CreateIntegrityVerificationStorage(std::shared_ptr<fs::IStorage> *out, std::shared_ptr<fs::IStorage> base_storage, const NcaFsHeader::HashData::IntegrityMetaInfo &meta_info, IHash256GeneratorFactory *hgf) {
R_RETURN(this->CreateIntegrityVerificationStorageImpl(out, base_storage, meta_info, 0, IntegrityDataCacheCount, IntegrityHashCacheCount, fssystem::HierarchicalIntegrityVerificationStorage::GetDefaultDataCacheBufferLevel(meta_info.level_hash_info.max_layers), hgf));
}
Result NcaFileSystemDriver::CreateIntegrityVerificationStorageForMeta(std::shared_ptr<fs::IStorage> *out, std::shared_ptr<fs::IStorage> *out_layer_info_storage, std::shared_ptr<fs::IStorage> base_storage, s64 offset, const NcaMetaDataHashDataInfo &meta_data_hash_data_info, IHash256GeneratorFactory *hgf) {
/* Validate preconditions. */
AMS_ASSERT(out != nullptr);
/* Check the meta data hash data size. */
R_UNLESS(meta_data_hash_data_info.size == sizeof(NcaMetaDataHashData), fs::ResultInvalidNcaMetaDataHashDataSize());
/* Read the meta data hash data. */
NcaMetaDataHashData meta_data_hash_data;
R_TRY(base_storage->Read(meta_data_hash_data_info.offset - offset, std::addressof(meta_data_hash_data), sizeof(meta_data_hash_data)));
/* Check the meta data hash data hash. */
u8 meta_data_hash_data_hash[IHash256Generator::HashSize];
m_hash_generator_factory_selector->GetFactory(fssystem::HashAlgorithmType_Sha2)->GenerateHash(meta_data_hash_data_hash, sizeof(meta_data_hash_data_hash), std::addressof(meta_data_hash_data), sizeof(meta_data_hash_data));
R_UNLESS(crypto::IsSameBytes(meta_data_hash_data_hash, std::addressof(meta_data_hash_data_info.hash), sizeof(meta_data_hash_data_hash)), fs::ResultInvalidNcaMetaDataHashDataHash());
/* Set the out layer info storage, if necessary. */
if (out_layer_info_storage != nullptr) {
auto layer_info_storage = fssystem::AllocateShared<fs::SubStorage>(base_storage, meta_data_hash_data.layer_info_offset - offset, meta_data_hash_data_info.offset + meta_data_hash_data_info.size - meta_data_hash_data.layer_info_offset);
R_UNLESS(layer_info_storage != nullptr, fs::ResultAllocationMemoryFailedAllocateShared());
*out_layer_info_storage = std::move(layer_info_storage);
}
/* Create the meta storage. */
auto meta_storage = fssystem::AllocateShared<fs::SubStorage>(std::move(base_storage), 0, meta_data_hash_data_info.offset - offset);
R_UNLESS(meta_storage != nullptr, fs::ResultAllocationMemoryFailedAllocateShared());
/* Create the integrity verification storage. */
R_RETURN(this->CreateIntegrityVerificationStorageImpl(out, std::move(meta_storage), meta_data_hash_data.integrity_meta_info, meta_data_hash_data.layer_info_offset - offset, IntegrityDataCacheCountForMeta, IntegrityHashCacheCountForMeta, 0, hgf));
}
Result NcaFileSystemDriver::CreateIntegrityVerificationStorageImpl(std::shared_ptr<fs::IStorage> *out, std::shared_ptr<fs::IStorage> base_storage, const NcaFsHeader::HashData::IntegrityMetaInfo &meta_info, s64 layer_info_offset, int max_data_cache_entries, int max_hash_cache_entries, s8 buffer_level, IHash256GeneratorFactory *hgf) {
/* Validate preconditions. */
AMS_ASSERT(out != nullptr);
AMS_ASSERT(base_storage != nullptr);
AMS_ASSERT(layer_info_offset >= 0);
/* Define storage types. */
using VerificationStorage = HierarchicalIntegrityVerificationStorage;
@ -1079,30 +1165,52 @@ namespace ams::fssystem {
StorageInfo storage_info;
for (s32 i = 0; i < static_cast<s32>(level_hash_info.max_layers - 2); ++i) {
const auto &layer_info = level_hash_info.info[i];
R_UNLESS(layer_info.offset + layer_info.size <= base_storage_size, fs::ResultNcaBaseStorageOutOfRangeD());
R_UNLESS(layer_info_offset + layer_info.offset + layer_info.size <= base_storage_size, fs::ResultNcaBaseStorageOutOfRangeD());
storage_info[i + 1] = fs::SubStorage(base_storage, layer_info.offset, layer_info.size);
storage_info[i + 1] = fs::SubStorage(base_storage, layer_info_offset + layer_info.offset, layer_info.size);
}
/* Set the last layer info. */
const auto &layer_info = level_hash_info.info[level_hash_info.max_layers - 2];
R_UNLESS(layer_info.offset + layer_info.size <= base_storage_size, fs::ResultNcaBaseStorageOutOfRangeD());
storage_info.SetDataStorage(fs::SubStorage(std::move(base_storage), layer_info.offset, layer_info.size));
const s64 last_layer_info_offset = layer_info_offset > 0 ? 0 : layer_info.offset;
R_UNLESS(last_layer_info_offset + layer_info.size <= base_storage_size, fs::ResultNcaBaseStorageOutOfRangeD());
if (layer_info_offset > 0) {
R_UNLESS(last_layer_info_offset + layer_info.size <= layer_info_offset, fs::ResultRomNcaInvalidIntegrityLayerInfoOffset());
}
storage_info.SetDataStorage(fs::SubStorage(std::move(base_storage), last_layer_info_offset, layer_info.size));
/* Make the integrity romfs storage. */
auto integrity_storage = fssystem::AllocateShared<fssystem::IntegrityRomFsStorage>();
R_UNLESS(integrity_storage != nullptr, fs::ResultAllocationMemoryFailedAllocateShared());
/* Initialize the integrity storage. */
R_TRY(integrity_storage->Initialize(level_hash_info, meta_info.master_hash, storage_info, m_buffer_manager, hgf));
R_TRY(integrity_storage->Initialize(level_hash_info, meta_info.master_hash, storage_info, m_buffer_manager, max_data_cache_entries, max_hash_cache_entries, buffer_level, hgf));
/* Set the output. */
*out = std::move(integrity_storage);
return ResultSuccess();
R_SUCCEED();
}
Result NcaFileSystemDriver::CreateRegionSwitchStorage(std::shared_ptr<fs::IStorage> *out, const NcaFsHeaderReader *header_reader, std::shared_ptr<fs::IStorage> inside_storage, std::shared_ptr<fs::IStorage> outside_storage) {
/* Check pre-conditions. */
AMS_ASSERT(header_reader->GetHashType() == NcaFsHeader::HashType::HierarchicalIntegrityHash);
/* Create the region. */
fssystem::RegionSwitchStorage::Region region = {};
R_TRY(header_reader->GetHashTargetOffset(std::addressof(region.size)));
/* Create the region switch storage. */
auto region_switch_storage = fssystem::AllocateShared<fssystem::RegionSwitchStorage>(std::move(inside_storage), std::move(outside_storage), region);
R_UNLESS(region_switch_storage != nullptr, fs::ResultAllocationMemoryFailedAllocateShared());
/* Set the output. */
*out = std::move(region_switch_storage);
R_SUCCEED();
}
Result NcaFileSystemDriver::CreateCompressedStorage(std::shared_ptr<fs::IStorage> *out, std::shared_ptr<fssystem::CompressedStorage> *out_cmp, std::shared_ptr<fs::IStorage> *out_meta, std::shared_ptr<fs::IStorage> base_storage, const NcaCompressionInfo &compression_info) {
return this->CreateCompressedStorage(out, out_cmp, out_meta, std::move(base_storage), compression_info, m_reader->GetDecompressor(), m_allocator, m_buffer_manager);
R_RETURN(this->CreateCompressedStorage(out, out_cmp, out_meta, std::move(base_storage), compression_info, m_reader->GetDecompressor(), m_allocator, m_buffer_manager));
}
Result NcaFileSystemDriver::CreateCompressedStorage(std::shared_ptr<fs::IStorage> *out, std::shared_ptr<fssystem::CompressedStorage> *out_cmp, std::shared_ptr<fs::IStorage> *out_meta, std::shared_ptr<fs::IStorage> base_storage, const NcaCompressionInfo &compression_info, GetDecompressorFunction get_decompressor, MemoryResource *allocator, fs::IBufferManager *buffer_manager) {
@ -1145,7 +1253,7 @@ namespace ams::fssystem {
/* Set the output. */
*out = std::move(compressed_storage);
return ResultSuccess();
R_SUCCEED();
}
}

View file

@ -35,7 +35,7 @@ namespace ams::fssystem {
}
NcaReader::NcaReader() : m_body_storage(), m_header_storage(), m_decrypt_aes_ctr(), m_decrypt_aes_ctr_external(), m_is_software_aes_prioritized(false), m_header_encryption_type(NcaHeader::EncryptionType::Auto), m_get_decompressor(), m_hash_generator_factory_selector() {
NcaReader::NcaReader() : m_body_storage(), m_header_storage(), m_decrypt_aes_ctr(), m_decrypt_aes_ctr_external(), m_is_software_aes_prioritized(false), m_is_available_sw_key(false), m_header_encryption_type(NcaHeader::EncryptionType::Auto), m_get_decompressor(), m_hash_generator_factory_selector() {
std::memset(std::addressof(m_header), 0, sizeof(m_header));
std::memset(std::addressof(m_decryption_keys), 0, sizeof(m_decryption_keys));
std::memset(std::addressof(m_external_decryption_key), 0, sizeof(m_external_decryption_key));
@ -52,19 +52,42 @@ namespace ams::fssystem {
AMS_ASSERT(m_body_storage == nullptr);
/* Check that the crypto config is valid. */
R_UNLESS(crypto_cfg.verify_sign1 != nullptr, fs::ResultInvalidArgument());
/* Create the work header storage storage. */
std::unique_ptr<fs::IStorage> work_header_storage;
if (crypto_cfg.is_available_sw_key) {
/* If software key is available, we need to be able to generate keys. */
R_UNLESS(crypto_cfg.generate_key != nullptr, fs::ResultInvalidArgument());
/* Generate keys for header. */
using AesXtsStorageForNcaHeader = AesXtsStorageBySharedPointer;
constexpr const s32 HeaderKeyTypeValues[NcaCryptoConfiguration::HeaderEncryptionKeyCount] = {
static_cast<s32>(KeyType::NcaHeaderKey1),
static_cast<s32>(KeyType::NcaHeaderKey2),
};
u8 header_decryption_keys[NcaCryptoConfiguration::HeaderEncryptionKeyCount][NcaCryptoConfiguration::Aes128KeySize];
for (size_t i = 0; i < NcaCryptoConfiguration::HeaderEncryptionKeyCount; i++) {
crypto_cfg.generate_key(header_decryption_keys[i], AesXtsStorageForNcaHeader::KeySize, crypto_cfg.header_encrypted_encryption_keys[i], AesXtsStorageForNcaHeader::KeySize, static_cast<s32>(KeyType::NcaHeaderKey), crypto_cfg);
crypto_cfg.generate_key(header_decryption_keys[i], AesXtsStorageForNcaHeader::KeySize, crypto_cfg.header_encrypted_encryption_keys[i], AesXtsStorageForNcaHeader::KeySize, HeaderKeyTypeValues[i]);
}
/* Create the header storage. */
const u8 header_iv[AesXtsStorageForNcaHeader::IvSize] = {};
std::unique_ptr<fs::IStorage> work_header_storage = std::make_unique<AesXtsStorageForNcaHeader>(base_storage, header_decryption_keys[0], header_decryption_keys[1], AesXtsStorageForNcaHeader::KeySize, header_iv, AesXtsStorageForNcaHeader::IvSize, NcaHeader::XtsBlockSize);
work_header_storage = std::make_unique<AesXtsStorageForNcaHeader>(base_storage, header_decryption_keys[0], header_decryption_keys[1], AesXtsStorageForNcaHeader::KeySize, header_iv, AesXtsStorageForNcaHeader::IvSize, NcaHeader::XtsBlockSize);
} else {
/* Software key isn't available, so we need to be able to decrypt externally. */
R_UNLESS(crypto_cfg.decrypt_aes_xts_external, fs::ResultInvalidArgument());
/* Create the header storage. */
using AesXtsStorageExternalForNcaHeader = AesXtsStorageExternalByPointer;
const u8 header_iv[AesXtsStorageExternalForNcaHeader::IvSize] = {};
work_header_storage = std::make_unique<AesXtsStorageExternalForNcaHeader>(base_storage.get(), nullptr, nullptr, AesXtsStorageExternalForNcaHeader::KeySize, header_iv, AesXtsStorageExternalForNcaHeader::IvSize, NcaHeader::XtsBlockSize, crypto_cfg.encrypt_aes_xts_external, crypto_cfg.decrypt_aes_xts_external);
}
/* Check that we successfully created the storage. */
R_UNLESS(work_header_storage != nullptr, fs::ResultAllocationMemoryFailedInNcaReaderA());
/* Read the header. */
@ -91,19 +114,15 @@ namespace ams::fssystem {
/* Validate the fixed key signature. */
R_UNLESS(m_header.header1_signature_key_generation <= NcaCryptoConfiguration::Header1SignatureKeyGenerationMax, fs::ResultInvalidNcaHeader1SignatureKeyGeneration());
const u8 *header_1_sign_key_modulus = crypto_cfg.header_1_sign_key_moduli[m_header.header1_signature_key_generation];
AMS_ABORT_UNLESS(header_1_sign_key_modulus != nullptr);
/* Verify the header sign1. */
{
const u8 *sig = m_header.header_sign_1;
const size_t sig_size = NcaHeader::HeaderSignSize;
const u8 *mod = header_1_sign_key_modulus;
const size_t mod_size = NcaCryptoConfiguration::Rsa2048KeyModulusSize;
const u8 *exp = crypto_cfg.header_1_sign_key_public_exponent;
const size_t exp_size = NcaCryptoConfiguration::Rsa2048KeyPublicExponentSize;
const u8 *msg = static_cast<const u8 *>(static_cast<const void *>(std::addressof(m_header.magic)));
const size_t msg_size = NcaHeader::Size - NcaHeader::HeaderSignSize * NcaHeader::HeaderSignCount;
m_is_header_sign1_signature_valid = crypto::VerifyRsa2048PssSha256(sig, sig_size, mod, mod_size, exp, exp_size, msg, msg_size);
m_is_header_sign1_signature_valid = crypto_cfg.verify_sign1(sig, sig_size, msg, msg_size, m_header.header1_signature_key_generation, crypto_cfg);
#if defined(ATMOSPHERE_BOARD_NINTENDO_NX)
R_UNLESS(m_is_header_sign1_signature_valid, fs::ResultNcaHeaderSignature1VerificationFailed());
@ -116,7 +135,7 @@ namespace ams::fssystem {
R_UNLESS(m_header.sdk_addon_version >= SdkAddonVersionMin, fs::ResultUnsupportedSdkVersion());
/* Validate the key index. */
R_UNLESS(m_header.key_index < NcaCryptoConfiguration::KeyAreaEncryptionKeyIndexCount, fs::ResultInvalidNcaKeyIndex());
R_UNLESS(m_header.key_index < NcaCryptoConfiguration::KeyAreaEncryptionKeyIndexCount || m_header.key_index == NcaCryptoConfiguration::KeyAreaEncryptionKeyIndexZeroKey, fs::ResultInvalidNcaKeyIndex());
/* Set our hash generator factory selector. */
m_hash_generator_factory_selector = hgf_selector;
@ -124,15 +143,17 @@ namespace ams::fssystem {
/* Check if we have a rights id. */
constexpr const u8 ZeroRightsId[NcaHeader::RightsIdSize] = {};
if (crypto::IsSameBytes(ZeroRightsId, m_header.rights_id, NcaHeader::RightsIdSize)) {
/* If we do, then we don't have an external key, so we need to generate decryption keys. */
crypto_cfg.generate_key(m_decryption_keys[NcaHeader::DecryptionKey_AesCtr], crypto::AesDecryptor128::KeySize, m_header.encrypted_key_area + NcaHeader::DecryptionKey_AesCtr * crypto::AesDecryptor128::KeySize, crypto::AesDecryptor128::KeySize, GetKeyTypeValue(m_header.key_index, m_header.GetProperKeyGeneration()), crypto_cfg);
/* If we do, then we don't have an external key, so we need to generate decryption keys if software keys are available. */
if (crypto_cfg.is_available_sw_key) {
crypto_cfg.generate_key(m_decryption_keys[NcaHeader::DecryptionKey_AesCtr], crypto::AesDecryptor128::KeySize, m_header.encrypted_key_area + NcaHeader::DecryptionKey_AesCtr * crypto::AesDecryptor128::KeySize, crypto::AesDecryptor128::KeySize, GetKeyTypeValue(m_header.key_index, m_header.GetProperKeyGeneration()));
/* If we're building for non-nx board (i.e., a host tool), generate all keys for debug. */
#if !defined(ATMOSPHERE_BOARD_NINTENDO_NX)
crypto_cfg.generate_key(m_decryption_keys[NcaHeader::DecryptionKey_AesXts1], crypto::AesDecryptor128::KeySize, m_header.encrypted_key_area + NcaHeader::DecryptionKey_AesXts1 * crypto::AesDecryptor128::KeySize, crypto::AesDecryptor128::KeySize, GetKeyTypeValue(m_header.key_index, m_header.GetProperKeyGeneration()), crypto_cfg);
crypto_cfg.generate_key(m_decryption_keys[NcaHeader::DecryptionKey_AesXts2], crypto::AesDecryptor128::KeySize, m_header.encrypted_key_area + NcaHeader::DecryptionKey_AesXts2 * crypto::AesDecryptor128::KeySize, crypto::AesDecryptor128::KeySize, GetKeyTypeValue(m_header.key_index, m_header.GetProperKeyGeneration()), crypto_cfg);
crypto_cfg.generate_key(m_decryption_keys[NcaHeader::DecryptionKey_AesCtrEx], crypto::AesDecryptor128::KeySize, m_header.encrypted_key_area + NcaHeader::DecryptionKey_AesCtrEx * crypto::AesDecryptor128::KeySize, crypto::AesDecryptor128::KeySize, GetKeyTypeValue(m_header.key_index, m_header.GetProperKeyGeneration()), crypto_cfg);
crypto_cfg.generate_key(m_decryption_keys[NcaHeader::DecryptionKey_AesXts1], crypto::AesDecryptor128::KeySize, m_header.encrypted_key_area + NcaHeader::DecryptionKey_AesXts1 * crypto::AesDecryptor128::KeySize, crypto::AesDecryptor128::KeySize, GetKeyTypeValue(m_header.key_index, m_header.GetProperKeyGeneration()));
crypto_cfg.generate_key(m_decryption_keys[NcaHeader::DecryptionKey_AesXts2], crypto::AesDecryptor128::KeySize, m_header.encrypted_key_area + NcaHeader::DecryptionKey_AesXts2 * crypto::AesDecryptor128::KeySize, crypto::AesDecryptor128::KeySize, GetKeyTypeValue(m_header.key_index, m_header.GetProperKeyGeneration()));
crypto_cfg.generate_key(m_decryption_keys[NcaHeader::DecryptionKey_AesCtrEx], crypto::AesDecryptor128::KeySize, m_header.encrypted_key_area + NcaHeader::DecryptionKey_AesCtrEx * crypto::AesDecryptor128::KeySize, crypto::AesDecryptor128::KeySize, GetKeyTypeValue(m_header.key_index, m_header.GetProperKeyGeneration()));
#endif
}
/* Copy the hardware speed emulation key. */
std::memcpy(m_decryption_keys[NcaHeader::DecryptionKey_AesCtrHw], m_header.encrypted_key_area + NcaHeader::DecryptionKey_AesCtrHw * crypto::AesDecryptor128::KeySize, crypto::AesDecryptor128::KeySize);
@ -141,6 +162,9 @@ namespace ams::fssystem {
/* Clear the external decryption key. */
std::memset(m_external_decryption_key, 0, sizeof(m_external_decryption_key));
/* Set software key availability. */
m_is_available_sw_key = crypto_cfg.is_available_sw_key;
/* Set our decryptor functions. */
m_decrypt_aes_ctr = crypto_cfg.decrypt_aes_ctr;
m_decrypt_aes_ctr_external = crypto_cfg.decrypt_aes_ctr_external;
@ -309,6 +333,10 @@ namespace ams::fssystem {
m_is_software_aes_prioritized = true;
}
bool NcaReader::IsAvailableSwKey() const {
return m_is_available_sw_key;
}
bool NcaReader::HasExternalDecryptionKey() const {
constexpr const u8 ZeroKey[crypto::AesDecryptor128::KeySize] = {};
return !crypto::IsSameBytes(ZeroKey, this->GetExternalDecryptionKey(), crypto::AesDecryptor128::KeySize);
@ -469,6 +497,18 @@ namespace ams::fssystem {
return m_data.aes_ctr_upper_iv;
}
bool NcaFsHeaderReader::IsSkipLayerHashEncryption() const {
AMS_ASSERT(this->IsInitialized());
return m_data.IsSkipLayerHashEncryption();
}
Result NcaFsHeaderReader::GetHashTargetOffset(s64 *out) const {
AMS_ASSERT(out != nullptr);
AMS_ASSERT(this->IsInitialized());
return m_data.GetHashTargetOffset(out);
}
bool NcaFsHeaderReader::ExistsSparseLayer() const {
AMS_ASSERT(this->IsInitialized());
return m_data.sparse_info.generation != 0;
@ -499,4 +539,44 @@ namespace ams::fssystem {
return m_data.compression_info;
}
bool NcaFsHeaderReader::ExistsPatchMetaHashLayer() const {
AMS_ASSERT(this->IsInitialized());
return m_data.meta_data_hash_data_info.size != 0 && this->GetPatchInfo().HasIndirectTable();
}
NcaMetaDataHashDataInfo &NcaFsHeaderReader::GetPatchMetaDataHashDataInfo() {
AMS_ASSERT(this->IsInitialized());
return m_data.meta_data_hash_data_info;
}
const NcaMetaDataHashDataInfo &NcaFsHeaderReader::GetPatchMetaDataHashDataInfo() const {
AMS_ASSERT(this->IsInitialized());
return m_data.meta_data_hash_data_info;
}
NcaFsHeader::MetaDataHashType NcaFsHeaderReader::GetPatchMetaHashType() const {
AMS_ASSERT(this->IsInitialized());
return m_data.meta_data_hash_type;
}
bool NcaFsHeaderReader::ExistsSparseMetaHashLayer() const {
AMS_ASSERT(this->IsInitialized());
return m_data.meta_data_hash_data_info.size != 0 && this->ExistsSparseLayer();
}
NcaMetaDataHashDataInfo &NcaFsHeaderReader::GetSparseMetaDataHashDataInfo() {
AMS_ASSERT(this->IsInitialized());
return m_data.meta_data_hash_data_info;
}
const NcaMetaDataHashDataInfo &NcaFsHeaderReader::GetSparseMetaDataHashDataInfo() const {
AMS_ASSERT(this->IsInitialized());
return m_data.meta_data_hash_data_info;
}
NcaFsHeader::MetaDataHashType NcaFsHeaderReader::GetSparseMetaHashType() const {
AMS_ASSERT(this->IsInitialized());
return m_data.meta_data_hash_type;
}
}

View file

@ -279,6 +279,17 @@ namespace ams::fs {
R_DEFINE_ERROR_RESULT(RomHierarchicalSha256BaseStorageTooLarge, 4074);
R_DEFINE_ERROR_RESULT(RomHierarchicalSha256HashVerificationFailed, 4075);
R_DEFINE_ERROR_RESULT(RomNcaInvalidCompressionInfo, 4083);
R_DEFINE_ERROR_RESULT(RomNcaInvalidPatchMetaDataHashType, 4084);
R_DEFINE_ERROR_RESULT(RomNcaInvalidIntegrityLayerInfoOffset, 4085);
R_DEFINE_ERROR_RESULT(RomNcaInvalidPatchMetaDataHashDataSize, 4086);
R_DEFINE_ERROR_RESULT(RomNcaInvalidPatchMetaDataHashDataOffset, 4087);
R_DEFINE_ERROR_RESULT(RomNcaInvalidPatchMetaDataHashDataHash, 4088);
R_DEFINE_ERROR_RESULT(RomNcaInvalidSparseMetaDataHashType, 4089);
R_DEFINE_ERROR_RESULT(RomNcaInvalidSparseMetaDataHashDataSize, 4090);
R_DEFINE_ERROR_RESULT(RomNcaInvalidSparseMetaDataHashDataOffset, 4091);
R_DEFINE_ERROR_RESULT(RomNcaInvalidSparseMetaDataHashDataHash, 4092);
R_DEFINE_ERROR_RANGE(RomIntegrityVerificationStorageCorrupted, 4141, 4179);
R_DEFINE_ERROR_RESULT(IncorrectRomIntegrityVerificationMagic, 4142);
R_DEFINE_ERROR_RESULT(InvalidRomZeroHash, 4143);
@ -350,6 +361,8 @@ namespace ams::fs {
/* TODO: Range? */
R_DEFINE_ERROR_RESULT(InvalidCompressedStorageSize, 4547);
R_DEFINE_ERROR_RESULT(InvalidNcaMetaDataHashDataSize, 4548);
R_DEFINE_ERROR_RESULT(InvalidNcaMetaDataHashDataHash, 4549);
R_DEFINE_ERROR_RANGE(IntegrityVerificationStorageCorrupted, 4601, 4639);
R_DEFINE_ERROR_RESULT(IncorrectIntegrityVerificationMagic, 4602);
@ -463,10 +476,10 @@ namespace ams::fs {
R_DEFINE_ERROR_RESULT(UnsupportedSetSizeForHierarchicalIntegrityVerificationStorage, 6316);
R_DEFINE_ERROR_RESULT(UnsupportedOperateRangeForHierarchicalIntegrityVerificationStorage, 6317);
R_DEFINE_ERROR_RESULT(UnsupportedSetSizeForIntegrityVerificationStorage, 6318);
R_DEFINE_ERROR_RESULT(UnsupportedOperateRangeForNonSaveDataIntegrityVerificationStorage, 6319);
R_DEFINE_ERROR_RESULT(UnsupportedOperateRangeForWritableIntegrityVerificationStorage, 6319);
R_DEFINE_ERROR_RESULT(UnsupportedOperateRangeForIntegrityVerificationStorage, 6320);
R_DEFINE_ERROR_RESULT(UnsupportedSetSizeForBlockCacheBufferedStorage, 6321);
R_DEFINE_ERROR_RESULT(UnsupportedOperateRangeForNonSaveDataBlockCacheBufferedStorage, 6322);
R_DEFINE_ERROR_RESULT(UnsupportedOperateRangeForWritableBlockCacheBufferedStorage, 6322);
R_DEFINE_ERROR_RESULT(UnsupportedOperateRangeForBlockCacheBufferedStorage, 6323);
R_DEFINE_ERROR_RESULT(UnsupportedWriteForIndirectStorage, 6324);
R_DEFINE_ERROR_RESULT(UnsupportedSetSizeForIndirectStorage, 6325);
@ -531,6 +544,7 @@ namespace ams::fs {
R_DEFINE_ERROR_RESULT(UnsupportedSetSizeForZeroBitmapHashStorageFile, 6386);
R_DEFINE_ERROR_RESULT(UnsupportedWriteForCompressedStorage, 6387);
R_DEFINE_ERROR_RESULT(UnsupportedOperateRangeForCompressedStorage, 6388);
R_DEFINE_ERROR_RESULT(UnsupportedOperateRangeForRegionSwitchStorage, 6397);
R_DEFINE_ERROR_RANGE(PermissionDenied, 6400, 6449);