From d14ea03271211e844befd507369334714a4aaf4f Mon Sep 17 00:00:00 2001 From: Pengfei Date: Fri, 30 Jul 2021 14:47:53 +0800 Subject: [PATCH] Refactor code - Add Signature class --- src/core/CMakeLists.txt | 2 + src/core/ncch/certificate.cpp | 60 ++++++++------------- src/core/ncch/certificate.h | 8 +-- src/core/ncch/cia_builder.cpp | 10 ++-- src/core/ncch/cia_common.h | 28 ---------- src/core/ncch/signature.cpp | 91 ++++++++++++++++++++++++++++++++ src/core/ncch/signature.h | 40 ++++++++++++++ src/core/ncch/ticket.cpp | 44 ++++----------- src/core/ncch/ticket.h | 4 +- src/core/ncch/title_metadata.cpp | 81 +++++++--------------------- src/core/ncch/title_metadata.h | 4 +- 11 files changed, 195 insertions(+), 177 deletions(-) create mode 100644 src/core/ncch/signature.cpp create mode 100644 src/core/ncch/signature.h diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index f4a653f..322a4c9 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -19,6 +19,8 @@ add_library(core STATIC ncch/cia_common.h ncch/ncch_container.cpp ncch/ncch_container.h + ncch/signature.cpp + ncch/signature.h ncch/seed_db.cpp ncch/seed_db.h ncch/smdh.cpp diff --git a/src/core/ncch/certificate.cpp b/src/core/ncch/certificate.cpp index 304f766..30605a5 100644 --- a/src/core/ncch/certificate.cpp +++ b/src/core/ncch/certificate.cpp @@ -7,6 +7,7 @@ #include #include "common/alignment.h" #include "common/assert.h" +#include "common/common_funcs.h" #include "common/file_util.h" #include "common/logging/log.h" #include "common/string_util.h" @@ -31,57 +32,36 @@ inline std::size_t GetPublicKeySize(u32 public_key_type) { return 0; } -std::size_t Certificate::Load(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 0; +bool Certificate::Load(std::vector file_data, std::size_t offset) { + const auto total_size = static_cast(file_data.size() - offset); - 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); - if (signature_size == 0) { - return 0; + if (!signature.Load(file_data, offset)) { + return false; } - - // The certificate 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 0; - - // Read signature + certificate body - signature.resize(signature_size); - std::memcpy(signature.data(), &file_data[offset + sizeof(u32)], signature_size); - std::memcpy(&body, &file_data[offset + body_start], sizeof(Body)); + // certificate body + const auto signature_size = signature.GetSize(); + TRY_MEMCPY(&body, file_data, offset + signature_size, sizeof(Body)); // Public key lengths are variable - std::size_t public_key_size = GetPublicKeySize(body.key_type); + const auto public_key_size = GetPublicKeySize(body.key_type); if (public_key_size == 0) { - return 0; + return false; } public_key.resize(public_key_size); - std::memcpy(public_key.data(), &file_data[offset + body_end], public_key.size()); - return body_end + public_key.size(); + const auto public_key_offset = offset + signature_size + sizeof(Body); + TRY_MEMCPY(public_key.data(), file_data, public_key_offset, public_key.size()); + return true; } bool Certificate::Save(FileUtil::IOFile& file) const { // signature - if (file.WriteBytes(&signature_type, sizeof(signature_type)) != sizeof(signature_type) || - file.WriteBytes(signature.data(), signature.size()) != signature.size()) { - - LOG_ERROR(Core, "Failed to write signature"); + if (!signature.Save(file)) { return false; } // body - const std::size_t body_start = Common::AlignUp(signature.size() + sizeof(u32), 0x40); - const std::size_t body_end = body_start + sizeof(body); - if (!file.Seek(body_start - signature.size() - sizeof(u32), SEEK_CUR) || - file.WriteBytes(&body, sizeof(body)) != sizeof(body)) { - + if (file.WriteBytes(&body, sizeof(body)) != sizeof(body)) { LOG_ERROR(Core, "Failed to write body"); return false; } @@ -95,6 +75,10 @@ bool Certificate::Save(FileUtil::IOFile& file) const { return true; } +std::size_t Certificate::GetSize() const { + return signature.GetSize() + sizeof(Body) + public_key.size(); +} + std::pair Certificate::GetRSAPublicKey() const { if (body.key_type == PublicKeyType::RSA_2048) { return {CryptoPP::Integer(public_key.data(), 0x100), @@ -139,10 +123,10 @@ bool Load(const std::string& path) { std::size_t pos = sizeof(header); while (pos < total_size) { Certificate cert; - const auto size = cert.Load(data[0], pos); - if (!size) { // Failed to load + if (!cert.Load(data[0], pos)) { // Failed to load return false; } + pos += cert.GetSize(); const auto issuer = Common::StringFromFixedZeroTerminatedBuffer(cert.body.issuer.data(), cert.body.issuer.size()); @@ -150,8 +134,6 @@ bool Load(const std::string& path) { cert.body.name.size()); const auto full_name = issuer + "-" + name; g_certs.emplace(full_name, std::move(cert)); - - pos += size; } for (const auto& cert : CIACertNames) { diff --git a/src/core/ncch/certificate.h b/src/core/ncch/certificate.h index 302c41f..35f8014 100644 --- a/src/core/ncch/certificate.h +++ b/src/core/ncch/certificate.h @@ -9,6 +9,7 @@ #include #include "common/common_funcs.h" #include "common/swap.h" +#include "core/ncch/signature.h" namespace CryptoPP { class Integer; @@ -36,15 +37,14 @@ public: }; static_assert(sizeof(Body) == 0x88); - // Returns: 0 on failure, size of the cert on success - std::size_t Load(std::vector file_data, std::size_t offset = 0); + bool Load(std::vector file_data, std::size_t offset = 0); bool Save(FileUtil::IOFile& file) const; + std::size_t GetSize() const; /// (modulus, exponent) std::pair GetRSAPublicKey() const; - u32_be signature_type; - std::vector signature; + Signature signature; Body body; std::vector public_key; }; diff --git a/src/core/ncch/cia_builder.cpp b/src/core/ncch/cia_builder.cpp index 1500d22..7b558c3 100644 --- a/src/core/ncch/cia_builder.cpp +++ b/src/core/ncch/cia_builder.cpp @@ -64,9 +64,11 @@ bool CIABuilder::Init(CIABuildType type_, const std::string& destination, TitleM } tmd = std::move(tmd_); - // Remove encrypted flag from TMD chunks - for (auto& chunk : tmd.tmd_chunks) { - chunk.type &= ~0x01; + if (type == CIABuildType::Standard) { + // Remove encrypted flag from TMD chunks + for (auto& chunk : tmd.tmd_chunks) { + chunk.type &= ~0x01; + } } header.header_size = sizeof(header); @@ -114,7 +116,7 @@ bool CIABuilder::WriteCert(const std::string& certs_db_path) { file->Seek(cert_offset, SEEK_SET); for (const auto& cert : CIACertNames) { if (!Certs::Get(cert).Save(*file)) { - LOG_ERROR(Core, "Failed to write cert"); + LOG_ERROR(Core, "Failed to write cert {}", cert); return false; } } diff --git a/src/core/ncch/cia_common.h b/src/core/ncch/cia_common.h index 9ab777f..36d7ec5 100644 --- a/src/core/ncch/cia_common.h +++ b/src/core/ncch/cia_common.h @@ -9,34 +9,6 @@ namespace Core { -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; - } - - LOG_ERROR(Common_Filesystem, "Tried to read ticket with bad signature {}", signature_type); - return 0; -} - /// Full names of the certificates contained in a CIA. constexpr std::array CIACertNames{{ "Root-CA00000003", diff --git a/src/core/ncch/signature.cpp b/src/core/ncch/signature.cpp new file mode 100644 index 0000000..5edb797 --- /dev/null +++ b/src/core/ncch/signature.cpp @@ -0,0 +1,91 @@ +// Copyright 2021 threeSD Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include "common/alignment.h" +#include "common/common_funcs.h" +#include "common/file_util.h" +#include "common/logging/log.h" +#include "core/ncch/certificate.h" +#include "core/ncch/signature.h" + +namespace Core { + +enum SignatureType : u32 { + Rsa4096Sha1 = 0x10000, + Rsa2048Sha1 = 0x10001, + EllipticSha1 = 0x10002, + Rsa4096Sha256 = 0x10003, + Rsa2048Sha256 = 0x10004, + EcdsaSha256 = 0x10005 +}; + +static u32 GetSignatureSize(u32 type) { + switch (type) { + case Rsa4096Sha1: + case Rsa4096Sha256: + return 0x200; + + case Rsa2048Sha1: + case Rsa2048Sha256: + return 0x100; + + case EllipticSha1: + case EcdsaSha256: + return 0x3C; + } + + LOG_ERROR(Common_Filesystem, "Invalid signature type {}", type); + return 0; +} + +bool Signature::Load(const std::vector& file_data, std::size_t offset) { + TRY_MEMCPY(&type, file_data, offset, sizeof(type)); + + const auto data_size = GetSignatureSize(type); + if (data_size == 0) { + return false; + } + + data.resize(data_size); + TRY_MEMCPY(data.data(), file_data, offset + sizeof(u32), data_size); + return true; +} + +bool Signature::Save(FileUtil::IOFile& file) const { + if (file.WriteBytes(&type, sizeof(type)) != sizeof(type)) { + LOG_ERROR(Core, "Could not write to file"); + return false; + } + if (file.WriteBytes(data.data(), data.size()) != data.size()) { + LOG_ERROR(Core, "Could not write to file"); + return false; + } + return file.Seek(GetSize() - data.size() - sizeof(type), SEEK_CUR); +} + +std::size_t Signature::GetSize() const { + return Common::AlignUp(data.size() + sizeof(type), 0x40); +} + +bool Signature::Verify(const std::string& issuer, + const std::function& func) const { + + const auto& cert = Certs::Get(issuer); + if (type != SignatureType::Rsa2048Sha256 || cert.body.key_type != PublicKeyType::RSA_2048) { + + LOG_ERROR(Core, "Unsupported signature type or cert public key type"); + return false; + } + + const auto [modulus, exponent] = cert.GetRSAPublicKey(); + CryptoPP::RSASS::Verifier verifier(modulus, exponent); + + auto* message = verifier.NewVerificationAccumulator(); + func(message); + verifier.InputSignature(*message, data.data(), data.size()); + return verifier.Verify(message); +} + +} // namespace Core diff --git a/src/core/ncch/signature.h b/src/core/ncch/signature.h new file mode 100644 index 0000000..b4392f2 --- /dev/null +++ b/src/core/ncch/signature.h @@ -0,0 +1,40 @@ +// Copyright 2021 threeSD Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include "common/common_types.h" +#include "common/swap.h" + +namespace CryptoPP { +class PK_MessageAccumulator; +} + +namespace FileUtil { +class IOFile; +} + +namespace Core { + +/// Consists of a signature type, a signature, and alignment to 0x40. +class Signature { +public: + bool Load(const std::vector& file_data, std::size_t offset = 0); + + /// Writes signature to file. Includes the alignment + bool Save(FileUtil::IOFile& file) const; + + std::size_t GetSize() const; + + /// Verifies the signature. Accepts a functor which should add the message to the accumulator + bool Verify(const std::string& issuer, + const std::function& func) const; + + u32_be type; + std::vector data; +}; + +} // namespace Core diff --git a/src/core/ncch/ticket.cpp b/src/core/ncch/ticket.cpp index 854da53..336438a 100644 --- a/src/core/ncch/ticket.cpp +++ b/src/core/ncch/ticket.cpp @@ -6,6 +6,7 @@ #include #include "common/alignment.h" #include "common/assert.h" +#include "common/common_funcs.h" #include "common/file_util.h" #include "core/ncch/cia_common.h" #include "core/ncch/ticket.h" @@ -13,48 +14,21 @@ namespace Core { bool 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 false; - - 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); - if (signature_size == 0) { + if (!signature.Load(file_data, offset)) { return false; } - - // 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 false; - - // Read signature + ticket body - signature.resize(signature_size); - memcpy(signature.data(), &file_data[offset + sizeof(u32)], signature_size); - memcpy(&body, &file_data[offset + body_start], sizeof(Body)); - + TRY_MEMCPY(&body, file_data, offset + signature.GetSize(), sizeof(Body)); return true; } bool Ticket::Save(FileUtil::IOFile& file) const { // signature - if (file.WriteBytes(&signature_type, sizeof(signature_type)) != sizeof(signature_type) || - file.WriteBytes(signature.data(), signature.size()) != signature.size()) { - - LOG_ERROR(Core, "Failed to write signature"); + if (!signature.Save(file)) { return false; } // body - const std::size_t body_start = Common::AlignUp(signature.size() + sizeof(u32), 0x40); - const std::size_t body_end = body_start + sizeof(body); - if (!file.Seek(body_start - signature.size() - sizeof(u32), SEEK_CUR) || - file.WriteBytes(&body, sizeof(body)) != sizeof(body)) { - + if (file.WriteBytes(&body, sizeof(body)) != sizeof(body)) { LOG_ERROR(Core, "Failed to write body"); return false; } @@ -63,7 +37,7 @@ bool Ticket::Save(FileUtil::IOFile& file) const { } std::size_t Ticket::GetSize() const { - return Common::AlignUp(signature.size() + sizeof(u32), 0x40) + sizeof(body); + return signature.GetSize() + sizeof(body); } constexpr std::string_view TicketIssuer = "Root-CA00000003-XS0000000c"; @@ -80,10 +54,10 @@ constexpr std::array TicketContentIndex{ // Values taken from GodMode9 Ticket BuildFakeTicket(u64 title_id) { Ticket ticket{}; - ticket.signature_type = 0x10004; // RSA_2048 SHA256 - ticket.signature.resize(GetSignatureSize(ticket.signature_type)); - std::memset(ticket.signature.data(), 0xFF, ticket.signature.size()); + ticket.signature.type = 0x10004; // RSA_2048 SHA256 + ticket.signature.data.resize(0x100); // Size of RSA_2048 signature + std::memset(ticket.signature.data.data(), 0xFF, ticket.signature.data.size()); auto& body = ticket.body; std::memcpy(body.issuer.data(), TicketIssuer.data(), TicketIssuer.size()); diff --git a/src/core/ncch/ticket.h b/src/core/ncch/ticket.h index 4189643..007e293 100644 --- a/src/core/ncch/ticket.h +++ b/src/core/ncch/ticket.h @@ -9,6 +9,7 @@ #include "common/common_funcs.h" #include "common/common_types.h" #include "common/swap.h" +#include "core/ncch/signature.h" namespace FileUtil { class IOFile; @@ -50,9 +51,8 @@ public: bool Save(FileUtil::IOFile& file) const; std::size_t GetSize() const; + Signature signature; Body body; - u32_be signature_type; - std::vector signature; }; Ticket BuildFakeTicket(u64 title_id); diff --git a/src/core/ncch/title_metadata.cpp b/src/core/ncch/title_metadata.cpp index 3bb51ac..ad4d8d0 100644 --- a/src/core/ncch/title_metadata.cpp +++ b/src/core/ncch/title_metadata.cpp @@ -22,28 +22,19 @@ ResultStatus TitleMetadata::Load(const std::vector file_data, std::size_t of if (total_size < sizeof(u32_be)) return ResultStatus::Error; - memcpy(&signature_type, &file_data[offset], sizeof(u32_be)); - - // Signature lengths are variable, and the body follows the signature - u32 signature_size = GetSignatureSize(signature_type); - if (signature_size == 0) { + if (!signature.Load(file_data, offset)) { return ResultStatus::Error; } - - // The TMD 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); - + const auto signature_size = signature.GetSize(); + std::size_t body_end = signature_size + sizeof(Body); if (total_size < body_end) return ResultStatus::Error; - // Read signature + TMD body, then load the amount of ContentChunks specified - tmd_signature.resize(signature_size); - memcpy(tmd_signature.data(), &file_data[offset + sizeof(u32_be)], signature_size); - memcpy(&tmd_body, &file_data[offset + body_start], sizeof(TitleMetadata::Body)); + // Read TMD body, then load the amount of ContentChunks specified + std::memcpy(&tmd_body, &file_data[offset + signature_size], sizeof(TitleMetadata::Body)); - std::size_t expected_size = - body_start + sizeof(Body) + static_cast(tmd_body.content_count) * sizeof(ContentChunk); + std::size_t expected_size = signature_size + sizeof(Body) + + static_cast(tmd_body.content_count) * sizeof(ContentChunk); if (total_size < expected_size) { LOG_ERROR(Service_FS, "Malformed TMD, expected size 0x{:x}, got 0x{:x}!", expected_size, total_size); @@ -53,8 +44,8 @@ ResultStatus TitleMetadata::Load(const std::vector file_data, std::size_t of for (u16 i = 0; i < tmd_body.content_count; i++) { ContentChunk chunk; - memcpy(&chunk, &file_data[offset + body_end + (i * sizeof(ContentChunk))], - sizeof(ContentChunk)); + std::memcpy(&chunk, &file_data[offset + body_end + (i * sizeof(ContentChunk))], + sizeof(ContentChunk)); tmd_chunks.push_back(chunk); } @@ -67,22 +58,10 @@ ResultStatus TitleMetadata::Save(FileUtil::IOFile& file) { if (!file.IsOpen()) return ResultStatus::Error; - if (!file.WriteBytes(&signature_type, sizeof(u32_be))) - return ResultStatus::Error; - - // Signature lengths are variable, and the body follows the signature - u32 signature_size = GetSignatureSize(signature_type); - if (signature_size == 0) { + if (!signature.Save(file)) { return ResultStatus::Error; } - if (!file.WriteBytes(tmd_signature.data(), signature_size)) - return ResultStatus::Error; - - // The TMD body start position is rounded to the nearest 0x40 after the signature - std::size_t body_start = Common::AlignUp(signature_size + sizeof(u32), 0x40); - file.Seek(offset + body_start, SEEK_SET); - // Update our TMD body values and hashes tmd_body.content_count = static_cast(tmd_chunks.size()); @@ -124,42 +103,18 @@ ResultStatus TitleMetadata::Save(const std::string& file_path) { } bool TitleMetadata::ValidateSignature() const { - if (!Certs::IsLoaded()) { - LOG_ERROR(Core, "Certificates not available"); - return false; - } - - const auto cert_name = + const auto issuer = Common::StringFromFixedZeroTerminatedBuffer(tmd_body.issuer.data(), tmd_body.issuer.size()); - if (!Certs::Exists(cert_name)) { - LOG_ERROR(Core, "Cert {} does not exist", cert_name); - return false; - } - - const auto& cert = Certs::Get(cert_name); - if (signature_type != TMDSignatureType::Rsa2048Sha256 || - cert.body.key_type != PublicKeyType::RSA_2048) { - - LOG_ERROR(Core, "Unsupported TMD signature type or cert public key type"); - return false; - } - - const auto [modulus, exponent] = Certs::Get(cert_name).GetRSAPublicKey(); - CryptoPP::RSASS::Verifier verifier(modulus, exponent); - - auto* message = verifier.NewVerificationAccumulator(); - message->Update(reinterpret_cast(&tmd_body), sizeof(tmd_body)); - message->Update(reinterpret_cast(tmd_chunks.data()), - tmd_chunks.size() * sizeof(ContentChunk)); - verifier.InputSignature(*message, tmd_signature.data(), tmd_signature.size()); - - return verifier.Verify(message); + return signature.Verify(issuer, [this](auto* message) { + message->Update(reinterpret_cast(&tmd_body), sizeof(tmd_body)); + message->Update(reinterpret_cast(tmd_chunks.data()), + tmd_chunks.size() * sizeof(ContentChunk)); + }); } std::size_t TitleMetadata::GetSize() const { - const std::size_t body_start = - Common::AlignUp(GetSignatureSize(signature_type) + sizeof(u32), 0x40); - return body_start + sizeof(TitleMetadata::Body) + sizeof(ContentChunk) * tmd_chunks.size(); + return signature.GetSize() + sizeof(TitleMetadata::Body) + + sizeof(ContentChunk) * tmd_chunks.size(); } u64 TitleMetadata::GetTitleID() const { diff --git a/src/core/ncch/title_metadata.h b/src/core/ncch/title_metadata.h index 393e104..3bc9a9a 100644 --- a/src/core/ncch/title_metadata.h +++ b/src/core/ncch/title_metadata.h @@ -9,6 +9,7 @@ #include #include "common/common_types.h" #include "common/swap.h" +#include "core/ncch/signature.h" #include "core/result_status.h" namespace Core { @@ -110,9 +111,8 @@ public: void Print() const; + Signature signature; Body tmd_body; - u32_be signature_type; - std::vector tmd_signature; std::vector tmd_chunks; };