From 33184a1b335b7fc643725c5afe4b7debe27fe2f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20=C5=A0l=C3=A9gl?= Date: Fri, 9 Oct 2026 21:52:38 +0200 Subject: [PATCH] feat(libSceSsl): implement sceSslGetCaList, sceSslFreeCaList and sceSslUnloadCert PPSA11386 imports sceSslGetCaList and sceSslFreeCaList and PPSA15508 imports sceSslUnloadCert; on main all three are missing and the loaders of both titles stop on them. No root certificate bundle is shipped and no loaded certificate is kept, so an SSL context holds no certificates: sceSslGetCaList clears the list and returns NOT_FOUND, as sceSslGetCaCerts does; sceSslFreeCaList accepts only that cleared list; sceSslUnloadCert has nothing to drop and is assumed to return 0. A null list returns the invalid-argument code that sceSslGetCaCerts and KytyPS5 return. An unknown context and a list sceSslGetCaList did not return throw, as their codes are not confirmed. Signatures and the SceSslCaList layout follow SharpProspero and shadPS4. guest_ssl_tests now links libc, as guest_http2_tests does, so an exception thrown from libSceSsl unwinds with the same unwinder that resumes it. Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libSceSsl/CMakeLists.txt | 3 +- core/libs/prx/libSceSsl/Export.cpp | 31 ++++++++++++++++ core/libs/tests/GuestSsl.cpp | 49 ++++++++++++++++++++++++++ docs/dev/TechnicalDebt.md | 1 + 4 files changed, 83 insertions(+), 1 deletion(-) diff --git a/core/libs/prx/libSceSsl/CMakeLists.txt b/core/libs/prx/libSceSsl/CMakeLists.txt index b6cd45507..d66b9716e 100644 --- a/core/libs/prx/libSceSsl/CMakeLists.txt +++ b/core/libs/prx/libSceSsl/CMakeLists.txt @@ -5,7 +5,8 @@ configure_windows_unwind(libSceSsl) if(BUILD_TESTING) add_test_executable(guest_ssl_tests ${LIBS_INCLUDE_DIR}/tests/GuestSsl.cpp) target_include_directories(guest_ssl_tests PRIVATE ${LIBS_INCLUDE_DIR}) - target_link_libraries(guest_ssl_tests PRIVATE libSceSsl) + target_link_libraries(guest_ssl_tests PRIVATE libSceSsl libc) + configure_windows_unwind(guest_ssl_tests) add_test(NAME guest_ssl COMMAND guest_ssl_tests) if(WIN32) set_tests_properties(guest_ssl PROPERTIES ENVIRONMENT_MODIFICATION diff --git a/core/libs/prx/libSceSsl/Export.cpp b/core/libs/prx/libSceSsl/Export.cpp index cd9b00548..a1623ff2a 100644 --- a/core/libs/prx/libSceSsl/Export.cpp +++ b/core/libs/prx/libSceSsl/Export.cpp @@ -6,6 +6,7 @@ #include #include #include +#include // No network is emulated: contexts, templates and requests can be created, but any request // that would touch the network fails with the library's network error. @@ -37,6 +38,17 @@ struct SslCaCerts { void* pool; }; +struct SslCaList { + void** certs; + int num; +}; +static_assert(sizeof(SslCaList) == 16); + +void RequireContext(const char* function, int sslCtxId) { + std::lock_guard lock(g_poolsMutex); + if (!g_pools.contains(sslCtxId)) throw std::invalid_argument(std::string(function) + ": unknown context"); +} + } extern "C" { @@ -55,6 +67,20 @@ int APS5_VABI sceSslGetCaCerts(int ssl_ctx_id, void* ca_certs) { return ERROR_NOT_FOUND; } +int APS5_VABI sceSslGetCaList(int sslCtxId, SslCaList* caList) { + if (!caList) return ERROR_INVALID_ARG; + RequireContext(__func__, sslCtxId); + *caList = {}; + return ERROR_NOT_FOUND; +} + +int APS5_VABI sceSslFreeCaList(int sslCtxId, SslCaList* caList) { + if (!caList) return ERROR_INVALID_ARG; + RequireContext(__func__, sslCtxId); + if (caList->certs || caList->num) throw std::invalid_argument("sceSslFreeCaList: list not returned by sceSslGetCaList"); + return 0; +} + int APS5_VABI sceSslInit_nid_postfix(uint64_t pool_size) { const int id = g_nextHandle.fetch_add(1, std::memory_order_relaxed); std::lock_guard lock(g_poolsMutex); @@ -83,6 +109,11 @@ int APS5_VABI sceSslLoadCert() { return 0; } +int APS5_VABI sceSslUnloadCert(int sslCtxId) { + RequireContext(__func__, sslCtxId); + return 0; +} + int APS5_VABI sceSslGetMemoryPoolStats(int ssl_ctx_id, SslMemoryPoolStats* stats) { if (stats == nullptr) APS5_INVALID_ARG_EX; std::lock_guard lock(g_poolsMutex); diff --git a/core/libs/tests/GuestSsl.cpp b/core/libs/tests/GuestSsl.cpp index 327e6b0b5..a8bed9258 100644 --- a/core/libs/tests/GuestSsl.cpp +++ b/core/libs/tests/GuestSsl.cpp @@ -2,11 +2,16 @@ #include #include #include +#include extern "C" { int APS5_VABI sceSslInit_nid_postfix(std::size_t); +int APS5_VABI sceSslTerm_nid_postfix(int); int APS5_VABI sceSslGetCaCerts(int, void*); int APS5_VABI sceSslFreeCaCerts(int, void*); +int APS5_VABI sceSslGetCaList(int, void*); +int APS5_VABI sceSslFreeCaList(int, void*); +int APS5_VABI sceSslUnloadCert(int); } struct SslMemoryPoolStats { @@ -20,12 +25,27 @@ extern "C" int APS5_VABI sceSslGetMemoryPoolStats(int, SslMemoryPoolStats*); static void Require(bool value) { if (!value) std::abort(); } +template +static bool Throws(TCall call) { + try { + call(); + } catch (const std::exception&) { + return true; + } + return false; +} + struct SslCaCerts { void* certs; std::size_t num; void* pool; }; +struct SslCaList { + void** certs; + int num; +}; + int main() { constexpr int notFound = static_cast(0x8095F004); constexpr int invalidArg = static_cast(0x8095177A); @@ -46,4 +66,33 @@ int main() { certs = {&marker, 3, &marker}; Require(sceSslFreeCaCerts(context, &certs) == 0); Require(certs.certs == nullptr && certs.num == 0 && certs.pool == nullptr); + + Require(sceSslGetCaList(context, nullptr) == invalidArg); + Require(sceSslFreeCaList(context, nullptr) == invalidArg); + + void* handle = ▮ + SslCaList list{&handle, 3}; + Require(sceSslGetCaList(context, &list) == notFound); + Require(list.certs == nullptr && list.num == 0); + Require(sceSslFreeCaList(context, &list) == 0); + Require(list.certs == nullptr && list.num == 0); + + SslCaList foreign{&handle, 1}; + Require(Throws([&] { sceSslFreeCaList(context, &foreign); })); + foreign = {nullptr, 1}; + Require(Throws([&] { sceSslFreeCaList(context, &foreign); })); + foreign = {&handle, 0}; + Require(Throws([&] { sceSslFreeCaList(context, &foreign); })); + + Require(sceSslUnloadCert(context) == 0); + Require(sceSslGetCaList(context, &list) == notFound); + Require(list.certs == nullptr && list.num == 0); + + Require(sceSslTerm_nid_postfix(context) == 0); + Require(sceSslGetCaList(context, nullptr) == invalidArg); + Require(sceSslFreeCaList(context, nullptr) == invalidArg); + list = {&handle, 3}; + Require(Throws([&] { sceSslGetCaList(context, &list); })); + Require(Throws([&] { sceSslFreeCaList(context, &list); })); + Require(Throws([&] { sceSslUnloadCert(context); })); } diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index 4656d0fbb..5952a0498 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -185,6 +185,7 @@ Throughout the project, every function at every stage either **does exactly what - [sceKernelGetOperationMode](../../core/libs/prx/libkernel/System/src/Process.cpp) (libkernel) - the signature `(int* mode, int* submode)` comes from PPSA12544, which logs both values after the call; the values are unknown and 0 is reported for both, as in the [prosper](https://github.com/mattias800/prosper/pull/4153) reimplementation. Null outputs throw - Font, Http2, Net, Ssl, SystemService and libkernel exports imported by PPSA12544 and added without an implementation have unknown signatures - [sceHttp2GetMemoryPoolStats, sceSslGetMemoryPoolStats, sceNetGetMemoryPoolStats](../../core/libs/prx/libSceNet/Export.cpp) (libSceHttp2, libSceSsl, libSceNet) - signatures `(int id, stats*)` and the statistics layouts (pool size, peak and current use, and for Http2 and Ssl a 32-bit reserved field) follow the PS4 SDK's `SceHttp2MemoryPoolStats`, `SceSslMemoryPoolStats` and `SceNetMemoryPoolStats`, not confirmed for the PS5. The pool size is the one given at init or pool creation; nothing is allocated from these pools, so the peak and current use are 0. Net returns `EBADF` for an unknown pool and `EINVAL` for a null output, as its other pool calls do; for Http2 and Ssl the error codes are unknown, so both cases throw +- [sceSslGetCaList, sceSslFreeCaList, sceSslUnloadCert](../../core/libs/prx/libSceSsl/Export.cpp) (libSceSsl) - signatures and the 16-byte `SceSslCaList` layout (an array of certificate handles and a 32-bit count) follow [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Net/Http.cs) and shadPS4's `OrbisSslCaList`, not confirmed for the PS5. On the console the list holds the certificate authorities the SSL service has; [shadPS4](https://github.com/shadps4-emu/shadPS4/pull/4104) returns a placeholder certificate from `sceSslGetCaCerts` because Rise of the Tomb Raider fails its initialization checks without one. No root certificate bundle is shipped and no loaded certificate is kept, so here a context holds none: `sceSslGetCaList` clears the list and returns `NOT_FOUND`, as `sceSslGetCaCerts` does, `sceSslFreeCaList` accepts only that cleared list, and `sceSslUnloadCert` (imported by PPSA15508) has nothing to drop and is assumed to return 0. A null list returns `0x8095177A`, which `sceSslGetCaCerts` and [KytyPS5](https://github.com/KytyPS5/KytyPS5/blob/59183dbfa81ed7efdff6069177fed50de0552725/src/libs/network.cpp) return for a null argument (shadPS4, PS4, returns `0x8095F007`). An unknown context throws (KytyPS5 returns `SSL_ERROR_INVALID_ID`, `0x8095F006`, from `sceSslGetCaCerts`), and so does a list `sceSslGetCaList` did not return - [sceHttp2SetResolveRetry](../../core/libs/prx/libSceHttp2/Export.cpp) (libSceHttp2) - signature `(int id, int32_t retry)` from libSceHttp's `sceHttpSetResolveRetry`; the value is accepted and ignored like the other Http2 settings, since no request reaches the network - [sceHttp2WebSocketCreateRequest, sceHttp2WebSocketCloseAsync, sceHttp2WebSocketSendTextMessageAsync, sceHttp2WebSocketSendDataMessageAsync](../../core/libs/prx/libSceHttp2/Export.cpp) (libSceHttp2) - imported by PPSA16064 and not called during its start-up; unknown signatures - [libSceVrSetupDialog](../../core/libs/prx/libSceVrSetupDialog/Export.cpp), and the Share, NpTrophy2, NpEntitlementAccess and WebBrowserDialog exports imported by PPSA12544's Unity plugins and added without an implementation, have unknown signatures