diff --git a/externals/cryptopp/CMakeLists.txt b/externals/cryptopp/CMakeLists.txt index 974f02b..23d0fd2 100644 --- a/externals/cryptopp/CMakeLists.txt +++ b/externals/cryptopp/CMakeLists.txt @@ -158,6 +158,7 @@ set(cryptopp_SOURCES cryptopp/neon-simd.cpp cryptopp/oaep.cpp cryptopp/osrng.cpp + cryptopp/pkcspad.cpp cryptopp/pubkey.cpp cryptopp/queue.cpp cryptopp/randpool.cpp @@ -165,6 +166,7 @@ set(cryptopp_SOURCES cryptopp/rijndael-simd.cpp cryptopp/rijndael.cpp cryptopp/rng.cpp + cryptopp/rsa.cpp cryptopp/sha-simd.cpp cryptopp/sha.cpp cryptopp/sse-simd.cpp diff --git a/src/core/ncch/certificate.cpp b/src/core/ncch/certificate.cpp index 8466ae6..304f766 100644 --- a/src/core/ncch/certificate.cpp +++ b/src/core/ncch/certificate.cpp @@ -4,8 +4,9 @@ #include #include -#include +#include #include "common/alignment.h" +#include "common/assert.h" #include "common/file_util.h" #include "common/logging/log.h" #include "common/string_util.h" @@ -94,6 +95,18 @@ bool Certificate::Save(FileUtil::IOFile& file) const { return true; } +std::pair Certificate::GetRSAPublicKey() const { + if (body.key_type == PublicKeyType::RSA_2048) { + return {CryptoPP::Integer(public_key.data(), 0x100), + CryptoPP::Integer(public_key.data() + 0x100, 0x4)}; + } else if (body.key_type == PublicKeyType::RSA_4096) { + return {CryptoPP::Integer(public_key.data(), 0x200), + CryptoPP::Integer(public_key.data() + 0x200, 0x4)}; + } else { + UNREACHABLE_MSG("Certificate is not RSA"); + } +} + namespace Certs { static std::unordered_map g_certs; @@ -131,9 +144,12 @@ bool Load(const std::string& path) { return false; } + const auto issuer = Common::StringFromFixedZeroTerminatedBuffer(cert.body.issuer.data(), + cert.body.issuer.size()); const auto name = Common::StringFromFixedZeroTerminatedBuffer(cert.body.name.data(), cert.body.name.size()); - g_certs.emplace(name, std::move(cert)); + const auto full_name = issuer + "-" + name; + g_certs.emplace(full_name, std::move(cert)); pos += size; } @@ -157,6 +173,10 @@ const Certificate& Get(const std::string& name) { return g_certs.at(name); } +bool Exists(const std::string& name) { + return g_certs.count(name); +} + } // namespace Certs } // namespace Core diff --git a/src/core/ncch/certificate.h b/src/core/ncch/certificate.h index b3973be..302c41f 100644 --- a/src/core/ncch/certificate.h +++ b/src/core/ncch/certificate.h @@ -10,6 +10,10 @@ #include "common/common_funcs.h" #include "common/swap.h" +namespace CryptoPP { +class Integer; +} + namespace FileUtil { class IOFile; } @@ -36,6 +40,9 @@ public: std::size_t Load(std::vector file_data, std::size_t offset = 0); bool Save(FileUtil::IOFile& file) const; + /// (modulus, exponent) + std::pair GetRSAPublicKey() const; + u32_be signature_type; std::vector signature; Body body; @@ -55,6 +62,7 @@ namespace Certs { bool Load(const std::string& path); bool IsLoaded(); const Certificate& Get(const std::string& name); +bool Exists(const std::string& name); } // namespace Certs diff --git a/src/core/ncch/cia_common.h b/src/core/ncch/cia_common.h index 425ed53..9ab777f 100644 --- a/src/core/ncch/cia_common.h +++ b/src/core/ncch/cia_common.h @@ -37,10 +37,11 @@ inline u32 GetSignatureSize(u32 signature_type) { return 0; } +/// Full names of the certificates contained in a CIA. constexpr std::array CIACertNames{{ - "CA00000003", - "XS0000000c", - "CP0000000b", + "Root-CA00000003", + "Root-CA00000003-XS0000000c", + "Root-CA00000003-CP0000000b", }}; } // namespace Core diff --git a/src/core/ncch/title_metadata.cpp b/src/core/ncch/title_metadata.cpp index 7390e5c..3bb51ac 100644 --- a/src/core/ncch/title_metadata.cpp +++ b/src/core/ncch/title_metadata.cpp @@ -4,11 +4,14 @@ #include #include +#include #include #include "common/alignment.h" #include "common/assert.h" #include "common/file_util.h" #include "common/logging/log.h" +#include "common/string_util.h" +#include "core/ncch/certificate.h" #include "core/ncch/cia_common.h" #include "core/ncch/title_metadata.h" @@ -120,6 +123,39 @@ ResultStatus TitleMetadata::Save(const std::string& file_path) { return Save(file); } +bool TitleMetadata::ValidateSignature() const { + if (!Certs::IsLoaded()) { + LOG_ERROR(Core, "Certificates not available"); + return false; + } + + const auto cert_name = + 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); +} + std::size_t TitleMetadata::GetSize() const { const std::size_t body_start = Common::AlignUp(GetSignatureSize(signature_type) + sizeof(u32), 0x40); diff --git a/src/core/ncch/title_metadata.h b/src/core/ncch/title_metadata.h index c1e885e..393e104 100644 --- a/src/core/ncch/title_metadata.h +++ b/src/core/ncch/title_metadata.h @@ -51,7 +51,7 @@ public: #pragma pack(push, 1) struct Body { - std::array issuer; + std::array issuer; u8 version; u8 ca_crl_version; u8 signer_crl_version; @@ -82,8 +82,9 @@ public: ResultStatus Save(FileUtil::IOFile& file); ResultStatus Save(const std::string& file_path); - std::size_t GetSize() const; + bool ValidateSignature() const; + std::size_t GetSize() const; u64 GetTitleID() const; u32 GetTitleType() const; u16 GetTitleVersion() const;