diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 1eb43d816e..63a6da12e6 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -1047,9 +1047,12 @@ add_library(core STATIC hle/service/spl/spl_module.h hle/service/spl/spl_results.h hle/service/spl/spl_types.h + hle/service/ssl/cert_store.cpp + hle/service/ssl/cert_store.h hle/service/ssl/ssl.cpp hle/service/ssl/ssl.h hle/service/ssl/ssl_backend.h + hle/service/ssl/ssl_types.h hle/service/usb/usb.cpp hle/service/usb/usb.h hle/service/vi/application_display_service.cpp diff --git a/src/core/hle/service/ssl/cert_store.cpp b/src/core/hle/service/ssl/cert_store.cpp new file mode 100644 index 0000000000..b321e5d32b --- /dev/null +++ b/src/core/hle/service/ssl/cert_store.cpp @@ -0,0 +1,156 @@ +// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "common/alignment.h" +#include "core/core.h" +#include "core/file_sys/content_archive.h" +#include "core/file_sys/nca_metadata.h" +#include "core/file_sys/registered_cache.h" +#include "core/file_sys/romfs.h" +#include "core/hle/service/filesystem/filesystem.h" +#include "core/hle/service/ssl/cert_store.h" + +namespace Service::SSL { + +// https://switchbrew.org/wiki/SSL_services#CertStore + +CertStore::CertStore(Core::System& system) { + constexpr u64 CertStoreDataId = 0x0100000000000800ULL; + + auto& fsc = system.GetFileSystemController(); + + // Attempt to load certificate data from storage + const auto nca = + fsc.GetSystemNANDContents()->GetEntry(CertStoreDataId, FileSys::ContentRecordType::Data); + if (!nca) { + return; + } + const auto romfs = nca->GetRomFS(); + if (!romfs) { + return; + } + const auto extracted = FileSys::ExtractRomFS(romfs); + if (!extracted) { + LOG_ERROR(Service_SSL, "CertStore could not be extracted, corrupt RomFS?"); + return; + } + const auto cert_store_file = extracted->GetFile("ssl_TrustedCerts.bdf"); + if (!cert_store_file) { + LOG_ERROR(Service_SSL, "Failed to find trusted certificates in CertStore"); + return; + } + + // Read and verify the header. + CertStoreHeader header; + cert_store_file->ReadObject(std::addressof(header)); + + if (header.magic != Common::MakeMagic('s', 's', 'l', 'T')) { + LOG_ERROR(Service_SSL, "Invalid certificate store magic"); + return; + } + + // Ensure the file can contains the number of entries it says it does. + const u64 expected_size = sizeof(header) + sizeof(CertStoreEntry) * header.num_entries; + const u64 actual_size = cert_store_file->GetSize(); + if (actual_size < expected_size) { + LOG_ERROR(Service_SSL, "Size mismatch, expected at least {} bytes, got {}", expected_size, + actual_size); + return; + } + + // Read entries. + std::vector entries(header.num_entries); + cert_store_file->ReadArray(entries.data(), header.num_entries, sizeof(header)); + + // Insert into memory store. + for (const auto& entry : entries) { + m_certs.emplace(entry.certificate_id, + Certificate{ + .status = entry.certificate_status, + .der_data = cert_store_file->ReadBytes( + entry.der_size, entry.der_offset + sizeof(header)), + }); + } +} + +CertStore::~CertStore() = default; + +template +void CertStore::ForEachCertificate(std::span certificate_ids, F&& f) { + if (certificate_ids.size() == 1 && certificate_ids.front() == CaCertificateId::All) { + for (const auto& entry : m_certs) { + f(entry); + } + } else { + for (const auto certificate_id : certificate_ids) { + const auto entry = m_certs.find(certificate_id); + if (entry == m_certs.end()) { + continue; + } + f(*entry); + } + } +} + +Result CertStore::GetCertificates(u32* out_num_entries, std::span out_data, + std::span certificate_ids) { + // Ensure the buffer is large enough to hold the output. + u32 required_size; + R_TRY(this->GetCertificateBufSize(std::addressof(required_size), out_num_entries, + certificate_ids)); + R_UNLESS(out_data.size_bytes() >= required_size, ResultUnknown); + + // Make parallel arrays. + std::vector cert_infos; + std::vector der_datas; + + const u32 der_data_offset = (*out_num_entries + 1) * sizeof(BuiltInCertificateInfo); + u32 cur_der_offset = der_data_offset; + + // Fill output. + this->ForEachCertificate(certificate_ids, [&](auto& entry) { + const auto& [status, cur_der_data] = entry.second; + BuiltInCertificateInfo cert_info{ + .cert_id = entry.first, + .status = status, + .der_size = cur_der_data.size(), + .der_offset = cur_der_offset, + }; + + cert_infos.push_back(cert_info); + der_datas.insert(der_datas.end(), cur_der_data.begin(), cur_der_data.end()); + cur_der_offset += static_cast(cur_der_data.size()); + }); + + // Append terminator entry. + cert_infos.push_back(BuiltInCertificateInfo{ + .cert_id = CaCertificateId::All, + .status = TrustedCertStatus::Invalid, + .der_size = 0, + .der_offset = 0, + }); + + // Write to output span. + std::memcpy(out_data.data(), cert_infos.data(), + cert_infos.size() * sizeof(BuiltInCertificateInfo)); + std::memcpy(out_data.data() + der_data_offset, der_datas.data(), der_datas.size()); + + R_SUCCEED(); +} + +Result CertStore::GetCertificateBufSize(u32* out_size, u32* out_num_entries, + std::span certificate_ids) { + // Output size is at least the size of the terminator entry. + *out_size = sizeof(BuiltInCertificateInfo); + *out_num_entries = 0; + + this->ForEachCertificate(certificate_ids, [&](auto& entry) { + *out_size += sizeof(BuiltInCertificateInfo); + *out_size += Common::AlignUp(static_cast(entry.second.der_data.size()), 4); + (*out_num_entries)++; + }); + + R_SUCCEED(); +} + +} // namespace Service::SSL diff --git a/src/core/hle/service/ssl/cert_store.h b/src/core/hle/service/ssl/cert_store.h new file mode 100644 index 0000000000..613d7b02af --- /dev/null +++ b/src/core/hle/service/ssl/cert_store.h @@ -0,0 +1,42 @@ +// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include + +#include "core/hle/result.h" +#include "core/hle/service/ssl/ssl_types.h" + +namespace Core { +class System; +} + +namespace Service::SSL { + +class CertStore { +public: + explicit CertStore(Core::System& system); + ~CertStore(); + + Result GetCertificates(u32* out_num_entries, std::span out_data, + std::span certificate_ids); + Result GetCertificateBufSize(u32* out_size, u32* out_num_entries, + std::span certificate_ids); + +private: + template + void ForEachCertificate(std::span certs, F&& f); + +private: + struct Certificate { + TrustedCertStatus status; + std::vector der_data; + }; + + std::map m_certs; +}; + +} // namespace Service::SSL diff --git a/src/core/hle/service/ssl/ssl.cpp b/src/core/hle/service/ssl/ssl.cpp index 0fbb43057f..008ee44923 100644 --- a/src/core/hle/service/ssl/ssl.cpp +++ b/src/core/hle/service/ssl/ssl.cpp @@ -5,11 +5,13 @@ #include "core/core.h" #include "core/hle/result.h" +#include "core/hle/service/cmif_serialization.h" #include "core/hle/service/ipc_helpers.h" #include "core/hle/service/server_manager.h" #include "core/hle/service/service.h" #include "core/hle/service/sm/sm.h" #include "core/hle/service/sockets/bsd.h" +#include "core/hle/service/ssl/cert_store.h" #include "core/hle/service/ssl/ssl.h" #include "core/hle/service/ssl/ssl_backend.h" #include "core/internal_network/network.h" @@ -492,13 +494,14 @@ private: class ISslService final : public ServiceFramework { public: - explicit ISslService(Core::System& system_) : ServiceFramework{system_, "ssl"} { + explicit ISslService(Core::System& system_) + : ServiceFramework{system_, "ssl"}, cert_store{system} { // clang-format off static const FunctionInfo functions[] = { {0, &ISslService::CreateContext, "CreateContext"}, {1, nullptr, "GetContextCount"}, - {2, nullptr, "GetCertificates"}, - {3, nullptr, "GetCertificateBufSize"}, + {2, D<&ISslService::GetCertificates>, "GetCertificates"}, + {3, D<&ISslService::GetCertificateBufSize>, "GetCertificateBufSize"}, {4, nullptr, "DebugIoctl"}, {5, &ISslService::SetInterfaceVersion, "SetInterfaceVersion"}, {6, nullptr, "FlushSessionCache"}, @@ -540,6 +543,22 @@ private: IPC::ResponseBuilder rb{ctx, 2}; rb.Push(ResultSuccess); } + + Result GetCertificateBufSize( + Out out_size, InArray certificate_ids) { + LOG_INFO(Service_SSL, "called"); + u32 num_entries; + R_RETURN(cert_store.GetCertificateBufSize(out_size, &num_entries, certificate_ids)); + } + + Result GetCertificates(Out out_num_entries, OutBuffer out_buffer, + InArray certificate_ids) { + LOG_INFO(Service_SSL, "called"); + R_RETURN(cert_store.GetCertificates(out_num_entries, out_buffer, certificate_ids)); + } + +private: + CertStore cert_store; }; void LoopProcess(Core::System& system) { diff --git a/src/core/hle/service/ssl/ssl_types.h b/src/core/hle/service/ssl/ssl_types.h new file mode 100644 index 0000000000..dbc3dbf64c --- /dev/null +++ b/src/core/hle/service/ssl/ssl_types.h @@ -0,0 +1,107 @@ +// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "common/common_types.h" + +namespace Service::SSL { + +enum class CaCertificateId : s32 { + All = -1, + NintendoCAG3 = 1, + NintendoClass2CAG3 = 2, + NintendoRootCAG4 = 3, + AmazonRootCA1 = 1000, + StarfieldServicesRootCertificateAuthorityG2 = 1001, + AddTrustExternalCARoot = 1002, + COMODOCertificationAuthority = 1003, + UTNDATACorpSGC = 1004, + UTNUSERFirstHardware = 1005, + BaltimoreCyberTrustRoot = 1006, + CybertrustGlobalRoot = 1007, + VerizonGlobalRootCA = 1008, + DigiCertAssuredIDRootCA = 1009, + DigiCertAssuredIDRootG2 = 1010, + DigiCertGlobalRootCA = 1011, + DigiCertGlobalRootG2 = 1012, + DigiCertHighAssuranceEVRootCA = 1013, + EntrustnetCertificationAuthority2048 = 1014, + EntrustRootCertificationAuthority = 1015, + EntrustRootCertificationAuthorityG2 = 1016, + GeoTrustGlobalCA2 = 1017, + GeoTrustGlobalCA = 1018, + GeoTrustPrimaryCertificationAuthorityG3 = 1019, + GeoTrustPrimaryCertificationAuthority = 1020, + GlobalSignRootCA = 1021, + GlobalSignRootCAR2 = 1022, + GlobalSignRootCAR3 = 1023, + GoDaddyClass2CertificationAuthority = 1024, + GoDaddyRootCertificateAuthorityG2 = 1025, + StarfieldClass2CertificationAuthority = 1026, + StarfieldRootCertificateAuthorityG2 = 1027, + thawtePrimaryRootCAG3 = 1028, + thawtePrimaryRootCA = 1029, + VeriSignClass3PublicPrimaryCertificationAuthorityG3 = 1030, + VeriSignClass3PublicPrimaryCertificationAuthorityG5 = 1031, + VeriSignUniversalRootCertificationAuthority = 1032, + DSTRootCAX3 = 1033, + USERTrustRsaCertificationAuthority = 1034, + ISRGRootX10 = 1035, + USERTrustEccCertificationAuthority = 1036, + COMODORsaCertificationAuthority = 1037, + COMODOEccCertificationAuthority = 1038, + AmazonRootCA2 = 1039, + AmazonRootCA3 = 1040, + AmazonRootCA4 = 1041, + DigiCertAssuredIDRootG3 = 1042, + DigiCertGlobalRootG3 = 1043, + DigiCertTrustedRootG4 = 1044, + EntrustRootCertificationAuthorityEC1 = 1045, + EntrustRootCertificationAuthorityG4 = 1046, + GlobalSignECCRootCAR4 = 1047, + GlobalSignECCRootCAR5 = 1048, + GlobalSignECCRootCAR6 = 1049, + GTSRootR1 = 1050, + GTSRootR2 = 1051, + GTSRootR3 = 1052, + GTSRootR4 = 1053, + SecurityCommunicationRootCA = 1054, + GlobalSignRootE4 = 1055, + GlobalSignRootR4 = 1056, + TTeleSecGlobalRootClass2 = 1057, + DigiCertTLSECCP384RootG5 = 1058, + DigiCertTLSRSA4096RootG5 = 1059, +}; + +enum class TrustedCertStatus : s32 { + Invalid = -1, + Removed = 0, + EnabledTrusted = 1, + EnabledNotTrusted = 2, + Revoked = 3, +}; + +struct BuiltInCertificateInfo { + CaCertificateId cert_id; + TrustedCertStatus status; + u64 der_size; + u64 der_offset; +}; +static_assert(sizeof(BuiltInCertificateInfo) == 0x18, "BuiltInCertificateInfo has incorrect size."); + +struct CertStoreHeader { + u32 magic; + u32 num_entries; +}; +static_assert(sizeof(CertStoreHeader) == 0x8, "CertStoreHeader has incorrect size."); + +struct CertStoreEntry { + CaCertificateId certificate_id; + TrustedCertStatus certificate_status; + u32 der_size; + u32 der_offset; +}; +static_assert(sizeof(CertStoreEntry) == 0x10, "CertStoreEntry has incorrect size."); + +} // namespace Service::SSL