From 86f6b6b7b2d930e8203114332b04a5c49a780b06 Mon Sep 17 00:00:00 2001 From: Liam Date: Thu, 10 Aug 2023 21:34:43 -0400 Subject: vfs: expand support for NCA reading --- src/core/file_sys/fssystem/fssystem_nca_reader.cpp | 542 +++++++++++++++++++++ 1 file changed, 542 insertions(+) create mode 100644 src/core/file_sys/fssystem/fssystem_nca_reader.cpp (limited to 'src/core/file_sys/fssystem/fssystem_nca_reader.cpp') diff --git a/src/core/file_sys/fssystem/fssystem_nca_reader.cpp b/src/core/file_sys/fssystem/fssystem_nca_reader.cpp new file mode 100644 index 000000000..cd4c49069 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_nca_reader.cpp @@ -0,0 +1,542 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "core/file_sys/fssystem/fssystem_aes_xts_storage.h" +#include "core/file_sys/fssystem/fssystem_nca_file_system_driver.h" +#include "core/file_sys/vfs_offset.h" + +namespace FileSys { + +namespace { + +constexpr inline u32 SdkAddonVersionMin = 0x000B0000; +constexpr inline size_t Aes128KeySize = 0x10; +constexpr const std::array ZeroKey{}; + +constexpr Result CheckNcaMagic(u32 magic) { + // Verify the magic is not a deprecated one. + R_UNLESS(magic != NcaHeader::Magic0, ResultUnsupportedSdkVersion); + R_UNLESS(magic != NcaHeader::Magic1, ResultUnsupportedSdkVersion); + R_UNLESS(magic != NcaHeader::Magic2, ResultUnsupportedSdkVersion); + + // Verify the magic is the current one. + R_UNLESS(magic == NcaHeader::Magic3, ResultInvalidNcaSignature); + + R_SUCCEED(); +} + +} // namespace + +NcaReader::NcaReader() + : m_body_storage(), m_header_storage(), m_is_software_aes_prioritized(false), + m_is_available_sw_key(false), m_header_encryption_type(NcaHeader::EncryptionType::Auto), + m_get_decompressor() { + 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)); +} + +NcaReader::~NcaReader() {} + +Result NcaReader::Initialize(VirtualFile base_storage, const NcaCryptoConfiguration& crypto_cfg, + const NcaCompressionConfiguration& compression_cfg) { + // Validate preconditions. + ASSERT(base_storage != nullptr); + ASSERT(m_body_storage == nullptr); + + // Create the work header storage storage. + VirtualFile work_header_storage; + + // We need to be able to generate keys. + R_UNLESS(crypto_cfg.generate_key != nullptr, ResultInvalidArgument); + + // Generate keys for header. + using AesXtsStorageForNcaHeader = AesXtsStorage; + + constexpr const s32 HeaderKeyTypeValues[NcaCryptoConfiguration::HeaderEncryptionKeyCount] = { + static_cast(KeyType::NcaHeaderKey1), + static_cast(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, HeaderKeyTypeValues[i]); + } + + // Create the header storage. + const u8 header_iv[AesXtsStorageForNcaHeader::IvSize] = {}; + work_header_storage = std::make_unique( + base_storage, header_decryption_keys[0], header_decryption_keys[1], + AesXtsStorageForNcaHeader::KeySize, header_iv, AesXtsStorageForNcaHeader::IvSize, + NcaHeader::XtsBlockSize); + + // Check that we successfully created the storage. + R_UNLESS(work_header_storage != nullptr, ResultAllocationMemoryFailedInNcaReaderA); + + // Read the header. + work_header_storage->ReadObject(std::addressof(m_header), 0); + + // Validate the magic. + if (const Result magic_result = CheckNcaMagic(m_header.magic); R_FAILED(magic_result)) { + // Try to use a plaintext header. + base_storage->ReadObject(std::addressof(m_header), 0); + R_UNLESS(R_SUCCEEDED(CheckNcaMagic(m_header.magic)), magic_result); + + // Configure to use the plaintext header. + auto base_storage_size = base_storage->GetSize(); + work_header_storage = std::make_shared(base_storage, base_storage_size, 0); + R_UNLESS(work_header_storage != nullptr, ResultAllocationMemoryFailedInNcaReaderA); + + // Set encryption type as plaintext. + m_header_encryption_type = NcaHeader::EncryptionType::None; + } + + // Validate the fixed key signature. + if (m_header.header1_signature_key_generation > + NcaCryptoConfiguration::Header1SignatureKeyGenerationMax) { + LOG_CRITICAL(Frontend, + "NcaCryptoConfiguration::Header1SignatureKeyGenerationMax = {}, " + "m_header.header1_signature_key_generation = {}", + NcaCryptoConfiguration::Header1SignatureKeyGenerationMax, + m_header.header1_signature_key_generation); + } + + R_UNLESS(m_header.header1_signature_key_generation <= + NcaCryptoConfiguration::Header1SignatureKeyGenerationMax, + ResultInvalidNcaHeader1SignatureKeyGeneration); + + // Verify the header sign1. + if (crypto_cfg.verify_sign1 != nullptr) { + const u8* sig = m_header.header_sign_1.data(); + const size_t sig_size = NcaHeader::HeaderSignSize; + const u8* msg = + static_cast(static_cast(std::addressof(m_header.magic))); + const size_t msg_size = + NcaHeader::Size - NcaHeader::HeaderSignSize * NcaHeader::HeaderSignCount; + + m_is_header_sign1_signature_valid = crypto_cfg.verify_sign1( + sig, sig_size, msg, msg_size, m_header.header1_signature_key_generation); + + if (!m_is_header_sign1_signature_valid) { + LOG_WARNING(Common_Filesystem, "Invalid NCA header sign1"); + } + } + + // Validate the sdk version. + R_UNLESS(m_header.sdk_addon_version >= SdkAddonVersionMin, ResultUnsupportedSdkVersion); + + // Validate the key index. + R_UNLESS(m_header.key_index < NcaCryptoConfiguration::KeyAreaEncryptionKeyIndexCount || + m_header.key_index == NcaCryptoConfiguration::KeyAreaEncryptionKeyIndexZeroKey, + ResultInvalidNcaKeyIndex); + + // Check if we have a rights id. + constexpr const std::array ZeroRightsId{}; + if (std::memcmp(ZeroRightsId.data(), m_header.rights_id.data(), NcaHeader::RightsIdSize) == 0) { + // If we don't, 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], Aes128KeySize, + m_header.encrypted_key_area.data() + NcaHeader::DecryptionKey_AesCtr * Aes128KeySize, + Aes128KeySize, GetKeyTypeValue(m_header.key_index, m_header.GetProperKeyGeneration())); + crypto_cfg.generate_key( + m_decryption_keys[NcaHeader::DecryptionKey_AesXts1], Aes128KeySize, + m_header.encrypted_key_area.data() + NcaHeader::DecryptionKey_AesXts1 * Aes128KeySize, + Aes128KeySize, GetKeyTypeValue(m_header.key_index, m_header.GetProperKeyGeneration())); + crypto_cfg.generate_key( + m_decryption_keys[NcaHeader::DecryptionKey_AesXts2], Aes128KeySize, + m_header.encrypted_key_area.data() + NcaHeader::DecryptionKey_AesXts2 * Aes128KeySize, + Aes128KeySize, GetKeyTypeValue(m_header.key_index, m_header.GetProperKeyGeneration())); + crypto_cfg.generate_key( + m_decryption_keys[NcaHeader::DecryptionKey_AesCtrEx], Aes128KeySize, + m_header.encrypted_key_area.data() + NcaHeader::DecryptionKey_AesCtrEx * Aes128KeySize, + Aes128KeySize, GetKeyTypeValue(m_header.key_index, m_header.GetProperKeyGeneration())); + + // Copy the hardware speed emulation key. + std::memcpy(m_decryption_keys[NcaHeader::DecryptionKey_AesCtrHw], + m_header.encrypted_key_area.data() + + NcaHeader::DecryptionKey_AesCtrHw * Aes128KeySize, + Aes128KeySize); + } + + // 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 decompressor function getter. + m_get_decompressor = compression_cfg.get_decompressor; + + // Set our storages. + m_header_storage = std::move(work_header_storage); + m_body_storage = std::move(base_storage); + + R_SUCCEED(); +} + +VirtualFile NcaReader::GetSharedBodyStorage() { + ASSERT(m_body_storage != nullptr); + return m_body_storage; +} + +u32 NcaReader::GetMagic() const { + ASSERT(m_body_storage != nullptr); + return m_header.magic; +} + +NcaHeader::DistributionType NcaReader::GetDistributionType() const { + ASSERT(m_body_storage != nullptr); + return m_header.distribution_type; +} + +NcaHeader::ContentType NcaReader::GetContentType() const { + ASSERT(m_body_storage != nullptr); + return m_header.content_type; +} + +u8 NcaReader::GetHeaderSign1KeyGeneration() const { + ASSERT(m_body_storage != nullptr); + return m_header.header1_signature_key_generation; +} + +u8 NcaReader::GetKeyGeneration() const { + ASSERT(m_body_storage != nullptr); + return m_header.GetProperKeyGeneration(); +} + +u8 NcaReader::GetKeyIndex() const { + ASSERT(m_body_storage != nullptr); + return m_header.key_index; +} + +u64 NcaReader::GetContentSize() const { + ASSERT(m_body_storage != nullptr); + return m_header.content_size; +} + +u64 NcaReader::GetProgramId() const { + ASSERT(m_body_storage != nullptr); + return m_header.program_id; +} + +u32 NcaReader::GetContentIndex() const { + ASSERT(m_body_storage != nullptr); + return m_header.content_index; +} + +u32 NcaReader::GetSdkAddonVersion() const { + ASSERT(m_body_storage != nullptr); + return m_header.sdk_addon_version; +} + +void NcaReader::GetRightsId(u8* dst, size_t dst_size) const { + ASSERT(dst != nullptr); + ASSERT(dst_size >= NcaHeader::RightsIdSize); + + std::memcpy(dst, m_header.rights_id.data(), NcaHeader::RightsIdSize); +} + +bool NcaReader::HasFsInfo(s32 index) const { + ASSERT(0 <= index && index < NcaHeader::FsCountMax); + return m_header.fs_info[index].start_sector != 0 || m_header.fs_info[index].end_sector != 0; +} + +s32 NcaReader::GetFsCount() const { + ASSERT(m_body_storage != nullptr); + for (s32 i = 0; i < NcaHeader::FsCountMax; i++) { + if (!this->HasFsInfo(i)) { + return i; + } + } + return NcaHeader::FsCountMax; +} + +const Hash& NcaReader::GetFsHeaderHash(s32 index) const { + ASSERT(m_body_storage != nullptr); + ASSERT(0 <= index && index < NcaHeader::FsCountMax); + return m_header.fs_header_hash[index]; +} + +void NcaReader::GetFsHeaderHash(Hash* dst, s32 index) const { + ASSERT(m_body_storage != nullptr); + ASSERT(0 <= index && index < NcaHeader::FsCountMax); + ASSERT(dst != nullptr); + std::memcpy(dst, std::addressof(m_header.fs_header_hash[index]), sizeof(*dst)); +} + +void NcaReader::GetFsInfo(NcaHeader::FsInfo* dst, s32 index) const { + ASSERT(m_body_storage != nullptr); + ASSERT(0 <= index && index < NcaHeader::FsCountMax); + ASSERT(dst != nullptr); + std::memcpy(dst, std::addressof(m_header.fs_info[index]), sizeof(*dst)); +} + +u64 NcaReader::GetFsOffset(s32 index) const { + ASSERT(m_body_storage != nullptr); + ASSERT(0 <= index && index < NcaHeader::FsCountMax); + return NcaHeader::SectorToByte(m_header.fs_info[index].start_sector); +} + +u64 NcaReader::GetFsEndOffset(s32 index) const { + ASSERT(m_body_storage != nullptr); + ASSERT(0 <= index && index < NcaHeader::FsCountMax); + return NcaHeader::SectorToByte(m_header.fs_info[index].end_sector); +} + +u64 NcaReader::GetFsSize(s32 index) const { + ASSERT(m_body_storage != nullptr); + ASSERT(0 <= index && index < NcaHeader::FsCountMax); + return NcaHeader::SectorToByte(m_header.fs_info[index].end_sector - + m_header.fs_info[index].start_sector); +} + +void NcaReader::GetEncryptedKey(void* dst, size_t size) const { + ASSERT(m_body_storage != nullptr); + ASSERT(dst != nullptr); + ASSERT(size >= NcaHeader::EncryptedKeyAreaSize); + + std::memcpy(dst, m_header.encrypted_key_area.data(), NcaHeader::EncryptedKeyAreaSize); +} + +const void* NcaReader::GetDecryptionKey(s32 index) const { + ASSERT(m_body_storage != nullptr); + ASSERT(0 <= index && index < NcaHeader::DecryptionKey_Count); + return m_decryption_keys[index]; +} + +bool NcaReader::HasValidInternalKey() const { + for (s32 i = 0; i < NcaHeader::DecryptionKey_Count; i++) { + if (std::memcmp(ZeroKey.data(), m_header.encrypted_key_area.data() + i * Aes128KeySize, + Aes128KeySize) != 0) { + return true; + } + } + return false; +} + +bool NcaReader::HasInternalDecryptionKeyForAesHw() const { + return std::memcmp(ZeroKey.data(), this->GetDecryptionKey(NcaHeader::DecryptionKey_AesCtrHw), + Aes128KeySize) != 0; +} + +bool NcaReader::IsSoftwareAesPrioritized() const { + return m_is_software_aes_prioritized; +} + +void NcaReader::PrioritizeSoftwareAes() { + m_is_software_aes_prioritized = true; +} + +bool NcaReader::IsAvailableSwKey() const { + return m_is_available_sw_key; +} + +bool NcaReader::HasExternalDecryptionKey() const { + return std::memcmp(ZeroKey.data(), this->GetExternalDecryptionKey(), Aes128KeySize) != 0; +} + +const void* NcaReader::GetExternalDecryptionKey() const { + return m_external_decryption_key; +} + +void NcaReader::SetExternalDecryptionKey(const void* src, size_t size) { + ASSERT(src != nullptr); + ASSERT(size == sizeof(m_external_decryption_key)); + + std::memcpy(m_external_decryption_key, src, sizeof(m_external_decryption_key)); +} + +void NcaReader::GetRawData(void* dst, size_t dst_size) const { + ASSERT(m_body_storage != nullptr); + ASSERT(dst != nullptr); + ASSERT(dst_size >= sizeof(NcaHeader)); + + std::memcpy(dst, std::addressof(m_header), sizeof(NcaHeader)); +} + +GetDecompressorFunction NcaReader::GetDecompressor() const { + ASSERT(m_get_decompressor != nullptr); + return m_get_decompressor; +} + +NcaHeader::EncryptionType NcaReader::GetEncryptionType() const { + return m_header_encryption_type; +} + +Result NcaReader::ReadHeader(NcaFsHeader* dst, s32 index) const { + ASSERT(dst != nullptr); + ASSERT(0 <= index && index < NcaHeader::FsCountMax); + + const s64 offset = sizeof(NcaHeader) + sizeof(NcaFsHeader) * index; + m_header_storage->ReadObject(dst, offset); + + R_SUCCEED(); +} + +bool NcaReader::GetHeaderSign1Valid() const { + return m_is_header_sign1_signature_valid; +} + +void NcaReader::GetHeaderSign2(void* dst, size_t size) const { + ASSERT(dst != nullptr); + ASSERT(size == NcaHeader::HeaderSignSize); + + std::memcpy(dst, m_header.header_sign_2.data(), size); +} + +Result NcaFsHeaderReader::Initialize(const NcaReader& reader, s32 index) { + // Reset ourselves to uninitialized. + m_fs_index = -1; + + // Read the header. + R_TRY(reader.ReadHeader(std::addressof(m_data), index)); + + // Set our index. + m_fs_index = index; + R_SUCCEED(); +} + +void NcaFsHeaderReader::GetRawData(void* dst, size_t dst_size) const { + ASSERT(this->IsInitialized()); + ASSERT(dst != nullptr); + ASSERT(dst_size >= sizeof(NcaFsHeader)); + + std::memcpy(dst, std::addressof(m_data), sizeof(NcaFsHeader)); +} + +NcaFsHeader::HashData& NcaFsHeaderReader::GetHashData() { + ASSERT(this->IsInitialized()); + return m_data.hash_data; +} + +const NcaFsHeader::HashData& NcaFsHeaderReader::GetHashData() const { + ASSERT(this->IsInitialized()); + return m_data.hash_data; +} + +u16 NcaFsHeaderReader::GetVersion() const { + ASSERT(this->IsInitialized()); + return m_data.version; +} + +s32 NcaFsHeaderReader::GetFsIndex() const { + ASSERT(this->IsInitialized()); + return m_fs_index; +} + +NcaFsHeader::FsType NcaFsHeaderReader::GetFsType() const { + ASSERT(this->IsInitialized()); + return m_data.fs_type; +} + +NcaFsHeader::HashType NcaFsHeaderReader::GetHashType() const { + ASSERT(this->IsInitialized()); + return m_data.hash_type; +} + +NcaFsHeader::EncryptionType NcaFsHeaderReader::GetEncryptionType() const { + ASSERT(this->IsInitialized()); + return m_data.encryption_type; +} + +NcaPatchInfo& NcaFsHeaderReader::GetPatchInfo() { + ASSERT(this->IsInitialized()); + return m_data.patch_info; +} + +const NcaPatchInfo& NcaFsHeaderReader::GetPatchInfo() const { + ASSERT(this->IsInitialized()); + return m_data.patch_info; +} + +const NcaAesCtrUpperIv NcaFsHeaderReader::GetAesCtrUpperIv() const { + ASSERT(this->IsInitialized()); + return m_data.aes_ctr_upper_iv; +} + +bool NcaFsHeaderReader::IsSkipLayerHashEncryption() const { + ASSERT(this->IsInitialized()); + return m_data.IsSkipLayerHashEncryption(); +} + +Result NcaFsHeaderReader::GetHashTargetOffset(s64* out) const { + ASSERT(out != nullptr); + ASSERT(this->IsInitialized()); + + R_RETURN(m_data.GetHashTargetOffset(out)); +} + +bool NcaFsHeaderReader::ExistsSparseLayer() const { + ASSERT(this->IsInitialized()); + return m_data.sparse_info.generation != 0; +} + +NcaSparseInfo& NcaFsHeaderReader::GetSparseInfo() { + ASSERT(this->IsInitialized()); + return m_data.sparse_info; +} + +const NcaSparseInfo& NcaFsHeaderReader::GetSparseInfo() const { + ASSERT(this->IsInitialized()); + return m_data.sparse_info; +} + +bool NcaFsHeaderReader::ExistsCompressionLayer() const { + ASSERT(this->IsInitialized()); + return m_data.compression_info.bucket.offset != 0 && m_data.compression_info.bucket.size != 0; +} + +NcaCompressionInfo& NcaFsHeaderReader::GetCompressionInfo() { + ASSERT(this->IsInitialized()); + return m_data.compression_info; +} + +const NcaCompressionInfo& NcaFsHeaderReader::GetCompressionInfo() const { + ASSERT(this->IsInitialized()); + return m_data.compression_info; +} + +bool NcaFsHeaderReader::ExistsPatchMetaHashLayer() const { + ASSERT(this->IsInitialized()); + return m_data.meta_data_hash_data_info.size != 0 && this->GetPatchInfo().HasIndirectTable(); +} + +NcaMetaDataHashDataInfo& NcaFsHeaderReader::GetPatchMetaDataHashDataInfo() { + ASSERT(this->IsInitialized()); + return m_data.meta_data_hash_data_info; +} + +const NcaMetaDataHashDataInfo& NcaFsHeaderReader::GetPatchMetaDataHashDataInfo() const { + ASSERT(this->IsInitialized()); + return m_data.meta_data_hash_data_info; +} + +NcaFsHeader::MetaDataHashType NcaFsHeaderReader::GetPatchMetaHashType() const { + ASSERT(this->IsInitialized()); + return m_data.meta_data_hash_type; +} + +bool NcaFsHeaderReader::ExistsSparseMetaHashLayer() const { + ASSERT(this->IsInitialized()); + return m_data.meta_data_hash_data_info.size != 0 && this->ExistsSparseLayer(); +} + +NcaMetaDataHashDataInfo& NcaFsHeaderReader::GetSparseMetaDataHashDataInfo() { + ASSERT(this->IsInitialized()); + return m_data.meta_data_hash_data_info; +} + +const NcaMetaDataHashDataInfo& NcaFsHeaderReader::GetSparseMetaDataHashDataInfo() const { + ASSERT(this->IsInitialized()); + return m_data.meta_data_hash_data_info; +} + +NcaFsHeader::MetaDataHashType NcaFsHeaderReader::GetSparseMetaHashType() const { + ASSERT(this->IsInitialized()); + return m_data.meta_data_hash_type; +} + +} // namespace FileSys -- cgit v1.2.3