Refactor code - Add Signature class

This commit is contained in:
Pengfei
2021-07-30 14:47:53 +08:00
parent 15b1af7dd1
commit d14ea03271
11 changed files with 195 additions and 177 deletions
+2
View File
@@ -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
+21 -39
View File
@@ -7,6 +7,7 @@
#include <cryptopp/integer.h>
#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<u8> file_data, std::size_t offset) {
std::size_t total_size = static_cast<std::size_t>(file_data.size() - offset);
if (total_size < sizeof(u32))
return 0;
bool Certificate::Load(std::vector<u8> file_data, std::size_t offset) {
const auto total_size = static_cast<std::size_t>(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<CryptoPP::Integer, CryptoPP::Integer> 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) {
+4 -4
View File
@@ -9,6 +9,7 @@
#include <vector>
#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<u8> file_data, std::size_t offset = 0);
bool Load(std::vector<u8> file_data, std::size_t offset = 0);
bool Save(FileUtil::IOFile& file) const;
std::size_t GetSize() const;
/// (modulus, exponent)
std::pair<CryptoPP::Integer, CryptoPP::Integer> GetRSAPublicKey() const;
u32_be signature_type;
std::vector<u8> signature;
Signature signature;
Body body;
std::vector<u8> public_key;
};
+6 -4
View File
@@ -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;
}
}
-28
View File
@@ -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<const char*, 3> CIACertNames{{
"Root-CA00000003",
+91
View File
@@ -0,0 +1,91 @@
// Copyright 2021 threeSD Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <cryptopp/rsa.h>
#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<u8>& 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<void(CryptoPP::PK_MessageAccumulator*)>& 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<CryptoPP::PKCS1v15, CryptoPP::SHA256>::Verifier verifier(modulus, exponent);
auto* message = verifier.NewVerificationAccumulator();
func(message);
verifier.InputSignature(*message, data.data(), data.size());
return verifier.Verify(message);
}
} // namespace Core
+40
View File
@@ -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 <functional>
#include <vector>
#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<u8>& 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<void(CryptoPP::PK_MessageAccumulator*)>& func) const;
u32_be type;
std::vector<u8> data;
};
} // namespace Core
+9 -35
View File
@@ -6,6 +6,7 @@
#include <string_view>
#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<u8> file_data, std::size_t offset) {
std::size_t total_size = static_cast<std::size_t>(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<u8, 44> 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());
+2 -2
View File
@@ -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<u8> signature;
};
Ticket BuildFakeTicket(u64 title_id);
+18 -63
View File
@@ -22,28 +22,19 @@ ResultStatus TitleMetadata::Load(const std::vector<u8> 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<u16>(tmd_body.content_count) * sizeof(ContentChunk);
std::size_t expected_size = signature_size + sizeof(Body) +
static_cast<u16>(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<u8> 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<u16>(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<CryptoPP::PKCS1v15, CryptoPP::SHA256>::Verifier verifier(modulus, exponent);
auto* message = verifier.NewVerificationAccumulator();
message->Update(reinterpret_cast<const u8*>(&tmd_body), sizeof(tmd_body));
message->Update(reinterpret_cast<const u8*>(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<const u8*>(&tmd_body), sizeof(tmd_body));
message->Update(reinterpret_cast<const u8*>(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 {
+2 -2
View File
@@ -9,6 +9,7 @@
#include <vector>
#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<u8> tmd_signature;
std::vector<ContentChunk> tmd_chunks;
};