Service/AM: handle encrypted CIA
This commit is contained in:
		| @@ -6,6 +6,8 @@ | ||||
| #include <cinttypes> | ||||
| #include <cstddef> | ||||
| #include <cstring> | ||||
| #include <cryptopp/aes.h> | ||||
| #include <cryptopp/modes.h> | ||||
| #include <fmt/format.h> | ||||
| #include "common/file_util.h" | ||||
| #include "common/logging/log.h" | ||||
| @@ -74,13 +76,31 @@ struct TicketInfo { | ||||
|  | ||||
| static_assert(sizeof(TicketInfo) == 0x18, "Ticket info structure size is wrong"); | ||||
|  | ||||
| class CIAFile::DecryptionState { | ||||
| public: | ||||
|     std::vector<CryptoPP::CBC_Mode<CryptoPP::AES>::Decryption> content; | ||||
| }; | ||||
|  | ||||
| CIAFile::CIAFile(Service::FS::MediaType media_type) | ||||
|     : media_type(media_type), decryption_state(std::make_unique<DecryptionState>()) {} | ||||
|  | ||||
| CIAFile::~CIAFile() { | ||||
|     Close(); | ||||
| } | ||||
|  | ||||
| ResultVal<std::size_t> CIAFile::Read(u64 offset, std::size_t length, u8* buffer) const { | ||||
|     UNIMPLEMENTED(); | ||||
|     return MakeResult<std::size_t>(length); | ||||
| } | ||||
|  | ||||
| ResultVal<std::size_t> CIAFile::WriteTitleMetadata(u64 offset, std::size_t length, | ||||
|                                                    const u8* buffer) { | ||||
| ResultCode CIAFile::WriteTicket() { | ||||
|     container.LoadTicket(data, container.GetTicketOffset()); | ||||
|  | ||||
|     install_state = CIAInstallState::TicketLoaded; | ||||
|     return RESULT_SUCCESS; | ||||
| } | ||||
|  | ||||
| ResultCode CIAFile::WriteTitleMetadata() { | ||||
|     container.LoadTitleMetadata(data, container.GetTitleMetadataOffset()); | ||||
|     FileSys::TitleMetadata tmd = container.GetTitleMetadata(); | ||||
|     tmd.Print(); | ||||
| @@ -109,10 +129,22 @@ ResultVal<std::size_t> CIAFile::WriteTitleMetadata(u64 offset, std::size_t lengt | ||||
|                       &app_folder, nullptr, nullptr); | ||||
|     FileUtil::CreateFullPath(app_folder); | ||||
|  | ||||
|     content_written.resize(container.GetTitleMetadata().GetContentCount()); | ||||
|     auto content_count = container.GetTitleMetadata().GetContentCount(); | ||||
|     content_written.resize(content_count); | ||||
|  | ||||
|     auto title_key = container.GetTicket().GetTitleKey(); | ||||
|     if (title_key) { | ||||
|         decryption_state->content.resize(content_count); | ||||
|         for (std::size_t i = 0; i < content_count; ++i) { | ||||
|             auto ctr = tmd.GetContentCTRByIndex(i); | ||||
|             decryption_state->content[i].SetKeyWithIV(title_key->data(), title_key->size(), | ||||
|                                                       ctr.data()); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     install_state = CIAInstallState::TMDLoaded; | ||||
|  | ||||
|     return MakeResult<std::size_t>(length); | ||||
|     return RESULT_SUCCESS; | ||||
| } | ||||
|  | ||||
| ResultVal<std::size_t> CIAFile::WriteContentData(u64 offset, std::size_t length, const u8* buffer) { | ||||
| @@ -144,7 +176,15 @@ ResultVal<std::size_t> CIAFile::WriteContentData(u64 offset, std::size_t length, | ||||
|             if (!file.IsOpen()) | ||||
|                 return FileSys::ERROR_INSUFFICIENT_SPACE; | ||||
|  | ||||
|             file.WriteBytes(buffer + (range_min - offset), available_to_write); | ||||
|             std::vector<u8> temp(buffer + (range_min - offset), | ||||
|                                  buffer + (range_min - offset) + available_to_write); | ||||
|  | ||||
|             if (tmd.GetContentTypeByIndex(static_cast<u16>(i)) & | ||||
|                 FileSys::TMDContentTypeFlag::Encrypted) { | ||||
|                 decryption_state->content[i].ProcessData(temp.data(), temp.data(), temp.size()); | ||||
|             } | ||||
|  | ||||
|             file.WriteBytes(temp.data(), temp.size()); | ||||
|  | ||||
|             // Keep tabs on how much of this content ID has been written so new range_min | ||||
|             // values can be calculated. | ||||
| @@ -207,8 +247,12 @@ ResultVal<std::size_t> CIAFile::Write(u64 offset, std::size_t length, bool flush | ||||
|     // The end of our TMD is at the beginning of Content data, so ensure we have that much | ||||
|     // buffered before trying to parse. | ||||
|     if (written >= container.GetContentOffset() && install_state != CIAInstallState::TMDLoaded) { | ||||
|         auto result = WriteTitleMetadata(offset, length, buffer); | ||||
|         if (result.Failed()) | ||||
|         auto result = WriteTicket(); | ||||
|         if (result.IsError()) | ||||
|             return result; | ||||
|  | ||||
|         result = WriteTitleMetadata(); | ||||
|         if (result.IsError()) | ||||
|             return result; | ||||
|     } | ||||
|  | ||||
| @@ -296,9 +340,12 @@ InstallStatus InstallCIA(const std::string& path, | ||||
|         Service::AM::CIAFile installFile( | ||||
|             Service::AM::GetTitleMediaType(container.GetTitleMetadata().GetTitleID())); | ||||
|  | ||||
|         bool title_key_available = container.GetTicket().GetTitleKey().is_initialized(); | ||||
|  | ||||
|         for (std::size_t i = 0; i < container.GetTitleMetadata().GetContentCount(); i++) { | ||||
|             if (container.GetTitleMetadata().GetContentTypeByIndex(static_cast<u16>(i)) & | ||||
|                 FileSys::TMDContentTypeFlag::Encrypted) { | ||||
|             if ((container.GetTitleMetadata().GetContentTypeByIndex(static_cast<u16>(i)) & | ||||
|                  FileSys::TMDContentTypeFlag::Encrypted) && | ||||
|                 !title_key_available) { | ||||
|                 LOG_ERROR(Service_AM, "File {} is encrypted! Aborting...", path); | ||||
|                 return InstallStatus::ErrorEncrypted; | ||||
|             } | ||||
|   | ||||
| @@ -6,6 +6,7 @@ | ||||
|  | ||||
| #include <array> | ||||
| #include <functional> | ||||
| #include <memory> | ||||
| #include <string> | ||||
| #include <vector> | ||||
| #include "common/common_types.h" | ||||
| @@ -61,13 +62,12 @@ using ProgressCallback = void(std::size_t, std::size_t); | ||||
| // A file handled returned for CIAs to be written into and subsequently installed. | ||||
| class CIAFile final : public FileSys::FileBackend { | ||||
| public: | ||||
|     explicit CIAFile(Service::FS::MediaType media_type) : media_type(media_type) {} | ||||
|     ~CIAFile() { | ||||
|         Close(); | ||||
|     } | ||||
|     explicit CIAFile(Service::FS::MediaType media_type); | ||||
|     ~CIAFile(); | ||||
|  | ||||
|     ResultVal<std::size_t> Read(u64 offset, std::size_t length, u8* buffer) const override; | ||||
|     ResultVal<std::size_t> WriteTitleMetadata(u64 offset, std::size_t length, const u8* buffer); | ||||
|     ResultCode WriteTicket(); | ||||
|     ResultCode WriteTitleMetadata(); | ||||
|     ResultVal<std::size_t> WriteContentData(u64 offset, std::size_t length, const u8* buffer); | ||||
|     ResultVal<std::size_t> Write(u64 offset, std::size_t length, bool flush, | ||||
|                                  const u8* buffer) override; | ||||
| @@ -89,6 +89,9 @@ private: | ||||
|     std::vector<u8> data; | ||||
|     std::vector<u64> content_written; | ||||
|     Service::FS::MediaType media_type; | ||||
|  | ||||
|     class DecryptionState; | ||||
|     std::unique_ptr<DecryptionState> decryption_state; | ||||
| }; | ||||
|  | ||||
| /** | ||||
|   | ||||
		Reference in New Issue
	
	Block a user