From b70e2bce58a8f7e5d77b8027b337e99e35a56364 Mon Sep 17 00:00:00 2001 From: Weiyi Wang Date: Tue, 4 Sep 2018 12:27:48 -0400 Subject: [PATCH 1/3] HW/AES: add common key loading and selecting --- src/core/hw/aes/key.cpp | 35 +++++++++++++++++++++++++---------- src/core/hw/aes/key.h | 5 +++++ 2 files changed, 30 insertions(+), 10 deletions(-) diff --git a/src/core/hw/aes/key.cpp b/src/core/hw/aes/key.cpp index 8d20ee781..f7493015c 100644 --- a/src/core/hw/aes/key.cpp +++ b/src/core/hw/aes/key.cpp @@ -25,26 +25,26 @@ struct KeySlot { boost::optional y; boost::optional normal; - void SetKeyX(const AESKey& key) { + void SetKeyX(boost::optional key) { x = key; - if (y && generator_constant) { - GenerateNormalKey(); - } + GenerateNormalKey(); } - void SetKeyY(const AESKey& key) { + void SetKeyY(boost::optional key) { y = key; - if (x && generator_constant) { - GenerateNormalKey(); - } + GenerateNormalKey(); } - void SetNormalKey(const AESKey& key) { + void SetNormalKey(boost::optional key) { normal = key; } void GenerateNormalKey() { - normal = Lrot128(Add128(Xor128(Lrot128(*x, 2), *y), *generator_constant), 87); + if (x && y && generator_constant) { + normal = Lrot128(Add128(Xor128(Lrot128(*x, 2), *y), *generator_constant), 87); + } else { + normal = boost::none; + } } void Clear() { @@ -55,6 +55,7 @@ struct KeySlot { }; std::array key_slots; +std::array, 6> common_key_y_slots; AESKey HexToKey(const std::string& hex) { if (hex.size() < 32) { @@ -102,6 +103,16 @@ void LoadPresetKeys() { continue; } + std::size_t common_key_index; + if (std::sscanf(name.c_str(), "common%zd", &common_key_index) == 1) { + if (common_key_index >= common_key_y_slots.size()) { + LOG_ERROR(HW_AES, "Invalid common key index {}", common_key_index); + } else { + common_key_y_slots[common_key_index] = key; + } + continue; + } + std::size_t slot_id; char key_type; if (std::sscanf(name.c_str(), "slot0x%zXKey%c", &slot_id, &key_type) != 2) { @@ -165,5 +176,9 @@ AESKey GetNormalKey(std::size_t slot_id) { return key_slots.at(slot_id).normal.value_or(AESKey{}); } +void SelectCommonKeyIndex(u8 index) { + key_slots[KeySlotID::TicketCommonKey].SetKeyY(common_key_y_slots.at(index)); +} + } // namespace AES } // namespace HW diff --git a/src/core/hw/aes/key.h b/src/core/hw/aes/key.h index b2c7311af..3d6664fd2 100644 --- a/src/core/hw/aes/key.h +++ b/src/core/hw/aes/key.h @@ -28,6 +28,9 @@ enum KeySlotID : std::size_t { // AES keyslot used for APT:Wrap/Unwrap functions APTWrap = 0x31, + // AES keyslot used for decrypting ticket title key + TicketCommonKey = 0x3D, + MaxKeySlotID = 0x40, }; @@ -45,5 +48,7 @@ void SetNormalKey(std::size_t slot_id, const AESKey& key); bool IsNormalKeyAvailable(std::size_t slot_id); AESKey GetNormalKey(std::size_t slot_id); +void SelectCommonKeyIndex(u8 index); + } // namespace AES } // namespace HW From df7749193835e7bb7b590bb2030dcf5069dc15d3 Mon Sep 17 00:00:00 2001 From: Weiyi Wang Date: Tue, 4 Sep 2018 12:28:38 -0400 Subject: [PATCH 2/3] FileSys/cia: add ticket parsing --- src/core/CMakeLists.txt | 3 ++ src/core/file_sys/cia_common.h | 40 +++++++++++++++++++ src/core/file_sys/cia_container.cpp | 9 +++++ src/core/file_sys/cia_container.h | 4 ++ src/core/file_sys/ticket.cpp | 56 +++++++++++++++++++++++++++ src/core/file_sys/ticket.h | 58 ++++++++++++++++++++++++++++ src/core/file_sys/title_metadata.cpp | 25 ++++-------- src/core/file_sys/title_metadata.h | 10 +---- 8 files changed, 178 insertions(+), 27 deletions(-) create mode 100644 src/core/file_sys/cia_common.h create mode 100644 src/core/file_sys/ticket.cpp create mode 100644 src/core/file_sys/ticket.h diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index b1a14c782..1ce61ad40 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -50,6 +50,7 @@ add_library(core STATIC file_sys/archive_source_sd_savedata.h file_sys/archive_systemsavedata.cpp file_sys/archive_systemsavedata.h + file_sys/cia_common.h file_sys/cia_container.cpp file_sys/cia_container.h file_sys/directory_backend.h @@ -68,6 +69,8 @@ add_library(core STATIC file_sys/romfs_reader.h file_sys/savedata_archive.cpp file_sys/savedata_archive.h + file_sys/ticket.cpp + file_sys/ticket.h file_sys/title_metadata.cpp file_sys/title_metadata.h frontend/applets/default_applets.cpp diff --git a/src/core/file_sys/cia_common.h b/src/core/file_sys/cia_common.h new file mode 100644 index 000000000..900c82af6 --- /dev/null +++ b/src/core/file_sys/cia_common.h @@ -0,0 +1,40 @@ +// Copyright 2018 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "common/assert.h" +#include "common/common_types.h" + +namespace FileSys { + +enum TMDSignatureType : u32 { + Rsa4096Sha1 = 0x10000, + Rsa2048Sha1 = 0x10001, + EllipticSha1 = 0x10002, + Rsa4096Sha256 = 0x10003, + Rsa2048Sha256 = 0x10004, + EcdsaSha256 = 0x10005 +}; + +inline u32 GetSignatureSize(u32 signature_type) { + switch (signature_type) { + case Rsa4096Sha1: + case Rsa4096Sha256: + return 0x200; + + case Rsa2048Sha1: + case Rsa2048Sha256: + return 0x100; + + case EllipticSha1: + case EcdsaSha256: + return 0x3C; + } + + UNREACHABLE(); + return 0; +} + +} // namespace FileSys diff --git a/src/core/file_sys/cia_container.cpp b/src/core/file_sys/cia_container.cpp index 84aee77c7..a53e04d4b 100644 --- a/src/core/file_sys/cia_container.cpp +++ b/src/core/file_sys/cia_container.cpp @@ -124,6 +124,11 @@ Loader::ResultStatus CIAContainer::LoadHeader(const std::vector& header_data return Loader::ResultStatus::Success; } +Loader::ResultStatus CIAContainer::LoadTicket(const std::vector& ticket_data, + std::size_t offset) { + return cia_ticket.Load(ticket_data, offset); +} + Loader::ResultStatus CIAContainer::LoadTitleMetadata(const std::vector& tmd_data, std::size_t offset) { return cia_tmd.Load(tmd_data, offset); @@ -139,6 +144,10 @@ Loader::ResultStatus CIAContainer::LoadMetadata(const std::vector& meta_data return Loader::ResultStatus::Success; } +const Ticket& CIAContainer::GetTicket() const { + return cia_ticket; +} + const TitleMetadata& CIAContainer::GetTitleMetadata() const { return cia_tmd; } diff --git a/src/core/file_sys/cia_container.h b/src/core/file_sys/cia_container.h index 9ebe0aa34..09bb1fcf5 100644 --- a/src/core/file_sys/cia_container.h +++ b/src/core/file_sys/cia_container.h @@ -10,6 +10,7 @@ #include #include "common/common_types.h" #include "common/swap.h" +#include "core/file_sys/ticket.h" #include "core/file_sys/title_metadata.h" namespace Loader { @@ -44,9 +45,11 @@ public: // Load parts of CIAs (for CIAs streamed in) Loader::ResultStatus LoadHeader(const std::vector& header_data, std::size_t offset = 0); + Loader::ResultStatus LoadTicket(const std::vector& ticket_data, std::size_t offset = 0); Loader::ResultStatus LoadTitleMetadata(const std::vector& tmd_data, std::size_t offset = 0); Loader::ResultStatus LoadMetadata(const std::vector& meta_data, std::size_t offset = 0); + const Ticket& GetTicket() const; const TitleMetadata& GetTitleMetadata() const; std::array& GetDependencies(); u32 GetCoreVersion() const; @@ -99,6 +102,7 @@ private: Header cia_header; Metadata cia_metadata; + Ticket cia_ticket; TitleMetadata cia_tmd; }; diff --git a/src/core/file_sys/ticket.cpp b/src/core/file_sys/ticket.cpp new file mode 100644 index 000000000..15a72116b --- /dev/null +++ b/src/core/file_sys/ticket.cpp @@ -0,0 +1,56 @@ +// Copyright 2018 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include +#include +#include "common/alignment.h" +#include "core/file_sys/cia_common.h" +#include "core/file_sys/ticket.h" +#include "core/hw/aes/key.h" +#include "core/loader/loader.h" + +namespace FileSys { + +Loader::ResultStatus Ticket::Load(const std::vector file_data, std::size_t offset) { + std::size_t total_size = static_cast(file_data.size() - offset); + if (total_size < sizeof(u32)) + return Loader::ResultStatus::Error; + + std::memcpy(&signature_type, &file_data[offset], sizeof(u32)); + + // Signature lengths are variable, and the body follows the signature + u32 signature_size = GetSignatureSize(signature_type); + + // The ticket body start position is rounded to the nearest 0x40 after the signature + std::size_t body_start = Common::AlignUp(signature_size + sizeof(u32), 0x40); + std::size_t body_end = body_start + sizeof(Body); + + if (total_size < body_end) + return Loader::ResultStatus::Error; + + // Read signature + ticket body + ticket_signature.resize(signature_size); + memcpy(ticket_signature.data(), &file_data[offset + sizeof(u32)], signature_size); + memcpy(&ticket_body, &file_data[offset + body_start], sizeof(Body)); + + return Loader::ResultStatus::Success; +} + +boost::optional> Ticket::GetTitleKey() const { + HW::AES::InitKeys(); + std::array ctr{}; + std::memcpy(ctr.data(), &ticket_body.title_id, sizeof(u64)); + HW::AES::SelectCommonKeyIndex(ticket_body.common_key_index); + if (!HW::AES::IsNormalKeyAvailable(HW::AES::KeySlotID::TicketCommonKey)) { + return boost::none; + } + auto key = HW::AES::GetNormalKey(HW::AES::KeySlotID::TicketCommonKey); + auto title_key = ticket_body.title_key; + CryptoPP::CBC_Mode::Decryption{key.data(), key.size(), ctr.data()}.ProcessData( + title_key.data(), title_key.data(), title_key.size()); + return title_key; +} + +} // namespace FileSys diff --git a/src/core/file_sys/ticket.h b/src/core/file_sys/ticket.h new file mode 100644 index 000000000..f8700ae87 --- /dev/null +++ b/src/core/file_sys/ticket.h @@ -0,0 +1,58 @@ +// Copyright 2018 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include +#include +#include "common/common_funcs.h" +#include "common/common_types.h" +#include "common/swap.h" + +namespace Loader { +enum class ResultStatus; +} + +namespace FileSys { +class Ticket { +public: +#pragma pack(push, 1) + struct Body { + std::array issuer; + std::array ecc_public_key; + u8 version; + u8 ca_crl_version; + u8 signer_crl_version; + std::array title_key; + INSERT_PADDING_BYTES(1); + u64_be ticket_id; + u32_be console_id; + u64_be title_id; + INSERT_PADDING_BYTES(2); + u16_be ticket_title_version; + INSERT_PADDING_BYTES(8); + u8 license_type; + u8 common_key_index; + INSERT_PADDING_BYTES(0x2A); + u32_be eshop_account_id; + INSERT_PADDING_BYTES(1); + u8 audit; + INSERT_PADDING_BYTES(0x42); + std::array limits; + std::array content_index; + }; + static_assert(sizeof(Body) == 0x210, "Ticket body structure size is wrong"); +#pragma pack(pop) + + Loader::ResultStatus Load(const std::vector file_data, std::size_t offset = 0); + boost::optional> GetTitleKey() const; + +private: + Body ticket_body; + u32_be signature_type; + std::vector ticket_signature; +}; +} // namespace FileSys diff --git a/src/core/file_sys/title_metadata.cpp b/src/core/file_sys/title_metadata.cpp index 97f0a78bd..21d973328 100644 --- a/src/core/file_sys/title_metadata.cpp +++ b/src/core/file_sys/title_metadata.cpp @@ -7,6 +7,7 @@ #include "common/alignment.h" #include "common/file_util.h" #include "common/logging/log.h" +#include "core/file_sys/cia_common.h" #include "core/file_sys/title_metadata.h" #include "core/loader/loader.h" @@ -15,24 +16,6 @@ namespace FileSys { -static u32 GetSignatureSize(u32 signature_type) { - switch (signature_type) { - case Rsa4096Sha1: - case Rsa4096Sha256: - return 0x200; - - case Rsa2048Sha1: - case Rsa2048Sha256: - return 0x100; - - case EllipticSha1: - case EcdsaSha256: - return 0x3C; - } - - return 0; -} - Loader::ResultStatus TitleMetadata::Load(const std::string& file_path) { FileUtil::IOFile file(file_path, "rb"); if (!file.IsOpen()) @@ -188,6 +171,12 @@ u64 TitleMetadata::GetContentSizeByIndex(u16 index) const { return tmd_chunks[index].size; } +std::array TitleMetadata::GetContentCTRByIndex(u16 index) const { + std::array ctr{}; + std::memcpy(ctr.data(), &tmd_chunks[index].index, sizeof(u16)); + return ctr; +} + void TitleMetadata::SetTitleID(u64 title_id) { tmd_body.title_id = title_id; } diff --git a/src/core/file_sys/title_metadata.h b/src/core/file_sys/title_metadata.h index 6099c6a3a..a5c926ef1 100644 --- a/src/core/file_sys/title_metadata.h +++ b/src/core/file_sys/title_metadata.h @@ -19,15 +19,6 @@ enum class ResultStatus; namespace FileSys { -enum TMDSignatureType : u32 { - Rsa4096Sha1 = 0x10000, - Rsa2048Sha1 = 0x10001, - EllipticSha1 = 0x10002, - Rsa4096Sha256 = 0x10003, - Rsa2048Sha256 = 0x10004, - EcdsaSha256 = 0x10005 -}; - enum TMDContentTypeFlag : u16 { Encrypted = 1 << 0, Disc = 1 << 2, @@ -108,6 +99,7 @@ public: u32 GetContentIDByIndex(u16 index) const; u16 GetContentTypeByIndex(u16 index) const; u64 GetContentSizeByIndex(u16 index) const; + std::array GetContentCTRByIndex(u16 index) const; void SetTitleID(u64 title_id); void SetTitleType(u32 type); From 9668852c0d7bfd2949385313881e51a76e173f21 Mon Sep 17 00:00:00 2001 From: Weiyi Wang Date: Tue, 4 Sep 2018 12:29:59 -0400 Subject: [PATCH 3/3] Service/AM: handle encrypted CIA --- src/core/hle/service/am/am.cpp | 65 +++++++++++++++++++++++++++++----- src/core/hle/service/am/am.h | 13 ++++--- 2 files changed, 64 insertions(+), 14 deletions(-) diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp index 984f5fb83..7a2462632 100644 --- a/src/core/hle/service/am/am.cpp +++ b/src/core/hle/service/am/am.cpp @@ -6,6 +6,8 @@ #include #include #include +#include +#include #include #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::Decryption> content; +}; + +CIAFile::CIAFile(Service::FS::MediaType media_type) + : media_type(media_type), decryption_state(std::make_unique()) {} + +CIAFile::~CIAFile() { + Close(); +} + ResultVal CIAFile::Read(u64 offset, std::size_t length, u8* buffer) const { UNIMPLEMENTED(); return MakeResult(length); } -ResultVal 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 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(length); + return RESULT_SUCCESS; } ResultVal CIAFile::WriteContentData(u64 offset, std::size_t length, const u8* buffer) { @@ -144,7 +176,15 @@ ResultVal 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 temp(buffer + (range_min - offset), + buffer + (range_min - offset) + available_to_write); + + if (tmd.GetContentTypeByIndex(static_cast(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 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(i)) & - FileSys::TMDContentTypeFlag::Encrypted) { + if ((container.GetTitleMetadata().GetContentTypeByIndex(static_cast(i)) & + FileSys::TMDContentTypeFlag::Encrypted) && + !title_key_available) { LOG_ERROR(Service_AM, "File {} is encrypted! Aborting...", path); return InstallStatus::ErrorEncrypted; } diff --git a/src/core/hle/service/am/am.h b/src/core/hle/service/am/am.h index 6729a6b36..7d12b72fc 100644 --- a/src/core/hle/service/am/am.h +++ b/src/core/hle/service/am/am.h @@ -6,6 +6,7 @@ #include #include +#include #include #include #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 Read(u64 offset, std::size_t length, u8* buffer) const override; - ResultVal WriteTitleMetadata(u64 offset, std::size_t length, const u8* buffer); + ResultCode WriteTicket(); + ResultCode WriteTitleMetadata(); ResultVal WriteContentData(u64 offset, std::size_t length, const u8* buffer); ResultVal Write(u64 offset, std::size_t length, bool flush, const u8* buffer) override; @@ -89,6 +89,9 @@ private: std::vector data; std::vector content_written; Service::FS::MediaType media_type; + + class DecryptionState; + std::unique_ptr decryption_state; }; /**