From 88348e29033172a33e14a4b733f939a3184d3191 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Lam?= Date: Sun, 11 Jun 2017 15:00:33 +0200 Subject: [PATCH] IOS/ES: Add VerifyContainer Will be used from several functions to verify the signatures for different containers (TMDs, tickets, device signed blobs). An option was added to disable signature checks, because that could be useful for people trying to import unsigned stuff. --- Source/Core/Core/ConfigManager.cpp | 2 + Source/Core/Core/ConfigManager.h | 2 + Source/Core/Core/IOS/ES/ES.cpp | 137 +++++++++++++++++++++++++++++ Source/Core/Core/IOS/ES/ES.h | 18 ++++ 4 files changed, 159 insertions(+) diff --git a/Source/Core/Core/ConfigManager.cpp b/Source/Core/Core/ConfigManager.cpp index 7d10bf17c7..86d499c4de 100644 --- a/Source/Core/Core/ConfigManager.cpp +++ b/Source/Core/Core/ConfigManager.cpp @@ -286,6 +286,7 @@ void SConfig::SaveCoreSettings(IniFile& ini) core->Set("PerfMapDir", m_perfDir); core->Set("EnableCustomRTC", bEnableCustomRTC); core->Set("CustomRTCValue", m_customRTCValue); + core->Set("EnableSignatureChecks", m_enable_signature_checks); } void SConfig::SaveMovieSettings(IniFile& ini) @@ -608,6 +609,7 @@ void SConfig::LoadCoreSettings(IniFile& ini) core->Get("EnableCustomRTC", &bEnableCustomRTC, false); // Default to seconds between 1.1.1970 and 1.1.2000 core->Get("CustomRTCValue", &m_customRTCValue, 946684800); + core->Get("EnableSignatureChecks", &m_enable_signature_checks, true); } void SConfig::LoadMovieSettings(IniFile& ini) diff --git a/Source/Core/Core/ConfigManager.h b/Source/Core/Core/ConfigManager.h index 69947125fc..9a97b68df0 100644 --- a/Source/Core/Core/ConfigManager.h +++ b/Source/Core/Core/ConfigManager.h @@ -168,6 +168,8 @@ struct SConfig : NonCopyable std::set> m_usb_passthrough_devices; bool IsUSBDeviceWhitelisted(std::pair vid_pid) const; + bool m_enable_signature_checks = true; + // SYSCONF settings int m_sensor_bar_position = 0x01; int m_sensor_bar_sensitivity = 0x03; diff --git a/Source/Core/Core/IOS/ES/ES.cpp b/Source/Core/Core/IOS/ES/ES.cpp index c6930e1efc..9a6444d362 100644 --- a/Source/Core/Core/IOS/ES/ES.cpp +++ b/Source/Core/Core/IOS/ES/ES.cpp @@ -11,15 +11,20 @@ #include #include +#include + #include "Common/ChunkFile.h" #include "Common/File.h" #include "Common/FileUtil.h" #include "Common/Logging/Log.h" #include "Common/MsgHandler.h" #include "Common/NandPaths.h" +#include "Common/ScopeGuard.h" +#include "Common/StringUtil.h" #include "Core/ConfigManager.h" #include "Core/HW/Memmap.h" #include "Core/IOS/ES/Formats.h" +#include "Core/IOS/IOSC.h" #include "DiscIO/NANDContentLoader.h" namespace IOS @@ -760,6 +765,138 @@ bool ES::IsActiveTitlePermittedByTicket(const u8* ticket_view) const Common::swap32(ticket_view + offsetof(IOS::ES::TicketView, permitted_title_id)); return title_identifier && (title_identifier & ~permitted_title_mask) == permitted_title_id; } + +bool ES::IsIssuerCorrect(VerifyContainerType type, const IOS::ES::CertReader& issuer_cert) const +{ + switch (type) + { + case VerifyContainerType::TMD: + return issuer_cert.GetName().compare(0, 2, "CP") == 0; + case VerifyContainerType::Ticket: + return issuer_cert.GetName().compare(0, 2, "XS") == 0; + case VerifyContainerType::Device: + return issuer_cert.GetName().compare(0, 2, "MS") == 0; + default: + return false; + } +} + +ReturnCode ES::WriteNewCertToStore(const IOS::ES::CertReader& cert) +{ + const std::string store_path = Common::RootUserPath(Common::FROM_SESSION_ROOT) + "/sys/cert.sys"; + // The certificate store file may not exist, so we use a+b and not r+b here. + File::IOFile store_file{store_path, "a+b"}; + if (!store_file) + return ES_EIO; + + // Read the current store to determine if the new cert needs to be written. + const u64 file_size = store_file.GetSize(); + if (file_size != 0) + { + std::vector certs_bytes(file_size); + if (!store_file.ReadBytes(certs_bytes.data(), certs_bytes.size())) + return ES_SHORT_READ; + + const std::map certs = IOS::ES::ParseCertChain(certs_bytes); + // The cert is already present in the store. Nothing to do. + if (certs.find(cert.GetName()) != certs.end()) + return IPC_SUCCESS; + } + + // Otherwise, write the new cert at the end of the store. + // When opening a file in read-write mode, a seek is required before a write. + store_file.Seek(0, SEEK_END); + if (!store_file.WriteBytes(cert.GetBytes().data(), cert.GetBytes().size())) + return ES_EIO; + return IPC_SUCCESS; +} + +ReturnCode ES::VerifyContainer(VerifyContainerType type, VerifyMode mode, + const IOS::ES::SignedBlobReader& signed_blob, + const std::vector& cert_chain, u32 iosc_handle) +{ + if (!SConfig::GetInstance().m_enable_signature_checks) + return IPC_SUCCESS; + + if (!signed_blob.IsSignatureValid()) + return ES_EINVAL; + + // A blob should have exactly 3 parent issuers. + // Example for a ticket: "Root-CA00000001-XS00000003" => {"Root", "CA00000001", "XS00000003"} + const std::string issuer = signed_blob.GetIssuer(); + const std::vector parents = SplitString(issuer, '-'); + if (parents.size() != 3) + return ES_EINVAL; + + // Find the direct issuer and the CA certificates for the blob. + const std::map certs = IOS::ES::ParseCertChain(cert_chain); + const auto issuer_cert_iterator = certs.find(parents[2]); + const auto ca_cert_iterator = certs.find(parents[1]); + if (issuer_cert_iterator == certs.end() || ca_cert_iterator == certs.end()) + return ES_UNKNOWN_ISSUER; + const IOS::ES::CertReader& issuer_cert = issuer_cert_iterator->second; + const IOS::ES::CertReader& ca_cert = ca_cert_iterator->second; + + // Some blobs can only be signed by specific certificates. + if (!IsIssuerCorrect(type, issuer_cert)) + return ES_EINVAL; + + // Verify the whole cert chain using IOSC. + // IOS assumes that the CA cert will always be signed by the root certificate, + // and that the issuer is signed by the CA. + IOSC& iosc = m_ios.GetIOSC(); + IOSC::Handle handle; + + // Create and initialise a handle for the CA cert and the issuer cert. + ReturnCode ret = iosc.CreateObject(&handle, IOSC::TYPE_PUBLIC_KEY, IOSC::SUBTYPE_RSA2048, PID_ES); + if (ret != IPC_SUCCESS) + return ret; + Common::ScopeGuard ca_guard{[&] { iosc.DeleteObject(handle, PID_ES); }}; + ret = iosc.ImportCertificate(ca_cert.GetBytes().data(), IOSC::HANDLE_ROOT_KEY, handle, PID_ES); + if (ret != IPC_SUCCESS) + return ret; + + IOSC::Handle issuer_handle; + const IOSC::ObjectSubType subtype = + type == VerifyContainerType::Device ? IOSC::SUBTYPE_ECC233 : IOSC::SUBTYPE_RSA2048; + ret = iosc.CreateObject(&issuer_handle, IOSC::TYPE_PUBLIC_KEY, subtype, PID_ES); + if (ret != IPC_SUCCESS) + return ret; + Common::ScopeGuard issuer_guard{[&] { iosc.DeleteObject(issuer_handle, PID_ES); }}; + ret = iosc.ImportCertificate(issuer_cert.GetBytes().data(), handle, issuer_handle, PID_ES); + if (ret != IPC_SUCCESS) + return ret; + + // Calculate the SHA1 of the signed blob. + const size_t skip = type == VerifyContainerType::Device ? offsetof(SignatureECC, issuer) : + offsetof(SignatureRSA2048, issuer); + std::array sha1; + mbedtls_sha1(signed_blob.GetBytes().data() + skip, signed_blob.GetBytes().size() - skip, + sha1.data()); + + // Verify the signature. + const std::vector signature = signed_blob.GetSignatureData(); + ret = iosc.VerifyPublicKeySign(sha1, issuer_handle, signature.data(), PID_ES); + if (ret != IPC_SUCCESS) + return ret; + + if (mode == VerifyMode::UpdateCertStore) + { + ret = WriteNewCertToStore(issuer_cert); + if (ret != IPC_SUCCESS) + ERROR_LOG(IOS_ES, "VerifyContainer: Writing the issuer cert failed with return code %d", ret); + + ret = WriteNewCertToStore(ca_cert); + if (ret != IPC_SUCCESS) + ERROR_LOG(IOS_ES, "VerifyContainer: Writing the CA cert failed with return code %d", ret); + } + + // Import the signed blob to iosc_handle (if a handle was passed to us). + if (ret == IPC_SUCCESS && iosc_handle) + ret = iosc.ImportCertificate(signed_blob.GetBytes().data(), issuer_handle, iosc_handle, PID_ES); + + return ret; +} } // namespace Device } // namespace HLE } // namespace IOS diff --git a/Source/Core/Core/IOS/ES/ES.h b/Source/Core/Core/IOS/ES/ES.h index 5115f9d6b6..52b7b7612c 100644 --- a/Source/Core/Core/IOS/ES/ES.h +++ b/Source/Core/Core/IOS/ES/ES.h @@ -306,6 +306,24 @@ private: ReturnCode CheckStreamKeyPermissions(u32 uid, const u8* ticket_view, const IOS::ES::TMDReader& tmd) const; + enum class VerifyContainerType + { + TMD, + Ticket, + Device, + }; + enum class VerifyMode + { + // Whether or not new certificates should be added to the certificate store (/sys/cert.sys). + DoNotUpdateCertStore, + UpdateCertStore, + }; + bool IsIssuerCorrect(VerifyContainerType type, const IOS::ES::CertReader& issuer_cert) const; + ReturnCode WriteNewCertToStore(const IOS::ES::CertReader& cert); + ReturnCode VerifyContainer(VerifyContainerType type, VerifyMode mode, + const IOS::ES::SignedBlobReader& signed_blob, + const std::vector& cert_chain, u32 iosc_handle = 0); + // Start a title import. bool InitImport(u64 title_id); // Clean up the import content directory and move it back to /title.