mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-10-22 00:22:57 -05:00
Merge pull request #3897 from eliasbruno124-dev/fix/ssl-certificate-import
fix(ssl): retain loaded certificate authorities
This commit is contained in:
10 files changed
+732
-103
No files matched your search
@@ -37,3 +37,6 @@
|
||||
[submodule "3rdparty/libusb-cmake"]
|
||||
path = 3rdparty/libusb-cmake
|
||||
url = https://github.com/libusb/libusb-cmake.git
|
||||
[submodule "3rdparty/mbedtls"]
|
||||
path = 3rdparty/mbedtls
|
||||
url = https://github.com/Mbed-TLS/mbedtls.git
|
||||
+1
Submodule 3rdparty/mbedtls added at 068ff080b3.
@@ -1,5 +1,68 @@
|
||||
function(configure_ssl_backend)
|
||||
set(options ENABLE_PROGRAMS ENABLE_TESTING GEN_FILES USE_STATIC_MBEDTLS_LIBRARY
|
||||
USE_SHARED_MBEDTLS_LIBRARY DISABLE_PACKAGE_CONFIG_AND_INSTALL MBEDTLS_CONFIG_FILE MBEDTLS_USER_CONFIG_FILE)
|
||||
foreach(option IN LISTS options)
|
||||
get_property(saved_${option}_exists CACHE ${option} PROPERTY TYPE SET)
|
||||
if(saved_${option}_exists)
|
||||
get_property(saved_${option}_type CACHE ${option} PROPERTY TYPE)
|
||||
get_property(saved_${option}_help CACHE ${option} PROPERTY HELPSTRING)
|
||||
get_property(saved_${option}_value CACHE ${option} PROPERTY VALUE)
|
||||
endif()
|
||||
endforeach()
|
||||
set(ENABLE_PROGRAMS OFF)
|
||||
set(ENABLE_TESTING OFF)
|
||||
set(GEN_FILES OFF)
|
||||
set(USE_STATIC_MBEDTLS_LIBRARY ON)
|
||||
set(USE_SHARED_MBEDTLS_LIBRARY OFF)
|
||||
set(DISABLE_PACKAGE_CONFIG_AND_INSTALL ON)
|
||||
set(MBEDTLS_CONFIG_FILE "")
|
||||
set(MBEDTLS_USER_CONFIG_FILE "${CMAKE_CURRENT_SOURCE_DIR}/MbedConfig.hpp")
|
||||
foreach(option IN LISTS options)
|
||||
if(option MATCHES "CONFIG_FILE$")
|
||||
set(${option} "${${option}}" CACHE FILEPATH "" FORCE)
|
||||
else()
|
||||
set(${option} "${${option}}" CACHE BOOL "" FORCE)
|
||||
endif()
|
||||
endforeach()
|
||||
if(NOT DEFINED CMAKE_POLICY_VERSION_MINIMUM OR CMAKE_POLICY_VERSION_MINIMUM VERSION_LESS 3.5)
|
||||
set(CMAKE_POLICY_VERSION_MINIMUM 3.5)
|
||||
endif()
|
||||
add_subdirectory(${CMAKE_SOURCE_DIR}/3rdparty/mbedtls ${CMAKE_BINARY_DIR}/3rdparty/mbedtls EXCLUDE_FROM_ALL)
|
||||
foreach(property LINK_LIBRARIES INTERFACE_LINK_LIBRARIES)
|
||||
get_target_property(libraries mbedcrypto ${property})
|
||||
if(libraries)
|
||||
list(REMOVE_ITEM libraries everest)
|
||||
set_property(TARGET mbedcrypto PROPERTY ${property} "${libraries}")
|
||||
endif()
|
||||
endforeach()
|
||||
set_target_properties(everest PROPERTIES EXCLUDE_FROM_ALL ON)
|
||||
foreach(option IN LISTS options)
|
||||
if(saved_${option}_exists)
|
||||
set(${option} "${saved_${option}_value}" CACHE ${saved_${option}_type} "${saved_${option}_help}" FORCE)
|
||||
else()
|
||||
unset(${option} CACHE)
|
||||
endif()
|
||||
endforeach()
|
||||
endfunction()
|
||||
|
||||
configure_ssl_backend()
|
||||
set_target_properties(mbedcrypto mbedx509 p256m PROPERTIES POSITION_INDEPENDENT_CODE ON)
|
||||
if(MINGW)
|
||||
target_compile_options(mbedcrypto PRIVATE -fno-asynchronous-unwind-tables)
|
||||
target_compile_options(mbedx509 PRIVATE -fno-asynchronous-unwind-tables)
|
||||
target_compile_options(p256m PRIVATE -fno-asynchronous-unwind-tables)
|
||||
endif()
|
||||
|
||||
add_library(libSceSsl SHARED EXCLUDE_FROM_ALL Export.cpp)
|
||||
target_include_directories(libSceSsl PRIVATE ${LIBS_INCLUDE_DIR})
|
||||
target_link_libraries(libSceSsl PRIVATE mbedx509 mbedcrypto)
|
||||
set_target_properties(libSceSsl PROPERTIES VISIBILITY_INLINES_HIDDEN ON)
|
||||
if(MINGW OR CMAKE_SYSTEM_NAME STREQUAL "Linux")
|
||||
target_link_options(libSceSsl PRIVATE LINKER:--exclude-libs,ALL)
|
||||
endif()
|
||||
if(WIN32)
|
||||
target_link_libraries(libSceSsl PRIVATE bcrypt ws2_32)
|
||||
endif()
|
||||
configure_windows_unwind(libSceSsl)
|
||||
|
||||
if(BUILD_TESTING)
|
||||
@@ -8,8 +71,13 @@ if(BUILD_TESTING)
|
||||
target_link_libraries(guest_ssl_tests PRIVATE libSceSsl libc)
|
||||
configure_windows_unwind(guest_ssl_tests)
|
||||
add_test(NAME guest_ssl COMMAND guest_ssl_tests)
|
||||
add_test_executable(guest_ssl_load_tests ${LIBS_INCLUDE_DIR}/tests/GuestSslLoad.cpp)
|
||||
target_include_directories(guest_ssl_load_tests PRIVATE ${LIBS_INCLUDE_DIR})
|
||||
target_link_libraries(guest_ssl_load_tests PRIVATE libSceSsl libc)
|
||||
configure_windows_unwind(guest_ssl_load_tests)
|
||||
add_test(NAME guest_ssl_load COMMAND guest_ssl_load_tests)
|
||||
if(WIN32)
|
||||
set_tests_properties(guest_ssl PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
set_tests_properties(guest_ssl guest_ssl_load PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceSsl>")
|
||||
endif()
|
||||
endif()
|
||||
@@ -1,51 +1,160 @@
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
#include <cstring>
|
||||
#include "SceTypes.hpp"
|
||||
#include "SslTypes.hpp"
|
||||
#include "prx/libc/include/General.hpp"
|
||||
#include <atomic>
|
||||
#include <map>
|
||||
|
||||
#include <mbedtls/asn1.h>
|
||||
#include <mbedtls/ctr_drbg.h>
|
||||
#include <mbedtls/entropy.h>
|
||||
#include <mbedtls/pk.h>
|
||||
#include <mbedtls/platform.h>
|
||||
#include <mbedtls/platform_util.h>
|
||||
#include <mbedtls/x509_crt.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
// 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.
|
||||
static constexpr int ERROR_NETWORK = static_cast<int>(0x80435001);
|
||||
static std::atomic<int> g_nextHandle{1};
|
||||
static std::mutex g_poolsMutex;
|
||||
static std::map<int, uint64_t> g_pools;
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr int ERROR_NOT_FOUND = static_cast<int>(0x8095F004);
|
||||
constexpr int ERROR_INVALID_ARG = static_cast<int>(0x8095177A);
|
||||
constexpr int ERROR_INVALID_ID = static_cast<int>(0x8095F006);
|
||||
constexpr int ERROR_INVALID_VALUE = static_cast<int>(0x8095F007);
|
||||
constexpr int ErrorNotFound = static_cast<int>(0x8095f004);
|
||||
constexpr int ErrorInvalidArg = static_cast<int>(0x8095177a);
|
||||
constexpr int ErrorInvalidId = static_cast<int>(0x8095f006);
|
||||
|
||||
struct SslData {
|
||||
char* ptr;
|
||||
size_t size;
|
||||
struct Pool {
|
||||
std::size_t size, current = 0, peak = 0;
|
||||
bool exhausted = false;
|
||||
};
|
||||
|
||||
struct SslMemoryPoolStats {
|
||||
size_t pool_size;
|
||||
size_t max_inuse_size;
|
||||
size_t current_inuse_size;
|
||||
int32_t reserved;
|
||||
struct alignas(std::max_align_t) Allocation {
|
||||
Pool* pool;
|
||||
std::size_t size;
|
||||
};
|
||||
|
||||
struct SslCaCerts {
|
||||
SslData* certs;
|
||||
size_t num;
|
||||
void* pool;
|
||||
thread_local Pool* currentPool = nullptr;
|
||||
|
||||
void* PoolCalloc(std::size_t count, std::size_t size) noexcept {
|
||||
if (!currentPool) return nullptr;
|
||||
if (size && count > std::numeric_limits<std::size_t>::max() / size) {
|
||||
currentPool->exhausted = true;
|
||||
return nullptr;
|
||||
}
|
||||
const auto bytes = count * size;
|
||||
if (bytes > currentPool->size - currentPool->current || bytes > std::numeric_limits<std::size_t>::max() - sizeof(Allocation)) {
|
||||
currentPool->exhausted = true;
|
||||
return nullptr;
|
||||
}
|
||||
auto* allocation = static_cast<Allocation*>(std::calloc(1, sizeof(Allocation) + bytes));
|
||||
if (!allocation) {
|
||||
currentPool->exhausted = true;
|
||||
return nullptr;
|
||||
}
|
||||
allocation->pool = currentPool;
|
||||
allocation->size = bytes;
|
||||
currentPool->current += bytes;
|
||||
currentPool->peak = std::max(currentPool->peak, currentPool->current);
|
||||
return allocation + 1;
|
||||
}
|
||||
|
||||
void PoolFree(void* pointer) noexcept {
|
||||
if (!pointer) return;
|
||||
auto* allocation = static_cast<Allocation*>(pointer) - 1;
|
||||
allocation->pool->current -= allocation->size;
|
||||
mbedtls_platform_zeroize(pointer, allocation->size);
|
||||
std::free(allocation);
|
||||
}
|
||||
|
||||
struct PoolScope {
|
||||
Pool* previous;
|
||||
explicit PoolScope(Pool& pool) : previous(std::exchange(currentPool, &pool)) { pool.exhausted = false; }
|
||||
~PoolScope() { currentPool = previous; }
|
||||
};
|
||||
|
||||
struct SslCaList {
|
||||
void** certs;
|
||||
int num;
|
||||
struct PoolDeleter {
|
||||
void operator()(std::uint8_t* bytes) const { PoolFree(bytes); }
|
||||
};
|
||||
|
||||
using Block = std::unique_ptr<std::uint8_t, PoolDeleter>;
|
||||
|
||||
Block Allocate(std::size_t size) {
|
||||
Block block(static_cast<std::uint8_t*>(PoolCalloc(1, std::max<std::size_t>(1, size))));
|
||||
if (!block) throw std::bad_alloc();
|
||||
return block;
|
||||
}
|
||||
|
||||
std::size_t Add(std::size_t left, std::size_t right) {
|
||||
if (right > std::numeric_limits<std::size_t>::max() - left) throw std::length_error("SSL allocation size overflow");
|
||||
return left + right;
|
||||
}
|
||||
|
||||
void CheckBackend(int error, const char* message) {
|
||||
if (error == 0) return;
|
||||
if (currentPool && currentPool->exhausted) throw std::bad_alloc();
|
||||
throw std::invalid_argument(message);
|
||||
}
|
||||
|
||||
struct CertificateChain;
|
||||
|
||||
struct CertificateHandle {
|
||||
std::weak_ptr<CertificateChain> owner;
|
||||
const mbedtls_x509_crt* certificate;
|
||||
};
|
||||
|
||||
struct CertificateChain {
|
||||
mbedtls_x509_crt root;
|
||||
std::vector<CertificateHandle*> handles;
|
||||
CertificateChain() { mbedtls_x509_crt_init(&root); }
|
||||
~CertificateChain() { mbedtls_x509_crt_free(&root); }
|
||||
};
|
||||
|
||||
struct PrivateKey {
|
||||
mbedtls_pk_context key;
|
||||
PrivateKey() { mbedtls_pk_init(&key); }
|
||||
~PrivateKey() { mbedtls_pk_free(&key); }
|
||||
};
|
||||
|
||||
struct Random {
|
||||
mbedtls_entropy_context entropy;
|
||||
mbedtls_ctr_drbg_context drbg;
|
||||
Random() {
|
||||
mbedtls_entropy_init(&entropy);
|
||||
mbedtls_ctr_drbg_init(&drbg);
|
||||
}
|
||||
~Random() {
|
||||
mbedtls_ctr_drbg_free(&drbg);
|
||||
mbedtls_entropy_free(&entropy);
|
||||
}
|
||||
void Seed() {
|
||||
CheckBackend(mbedtls_ctr_drbg_seed(&drbg, mbedtls_entropy_func, &entropy, nullptr, 0), "SSL random generator initialization failed");
|
||||
}
|
||||
};
|
||||
|
||||
struct Identity {
|
||||
std::shared_ptr<CertificateChain> chain;
|
||||
PrivateKey key;
|
||||
};
|
||||
|
||||
struct Snapshot {
|
||||
Block data;
|
||||
std::vector<std::shared_ptr<CertificateChain>> certificates;
|
||||
std::size_t count;
|
||||
};
|
||||
|
||||
struct Context {
|
||||
Pool pool;
|
||||
std::vector<std::shared_ptr<CertificateChain>> authorities;
|
||||
std::shared_ptr<Identity> identity;
|
||||
std::vector<std::unique_ptr<CertificateHandle>> certificateHandles;
|
||||
std::unordered_map<void*, Snapshot> caLists;
|
||||
std::unordered_map<void*, Snapshot> caCerts;
|
||||
explicit Context(std::size_t size) : pool{size} {}
|
||||
};
|
||||
static_assert(sizeof(SslCaList) == 16);
|
||||
|
||||
using SslVerifyCallback = int (APS5_VABI *)(int, unsigned int, void**, int, void*);
|
||||
|
||||
@@ -57,85 +166,268 @@ struct SslConnection {
|
||||
void* verifyArgument;
|
||||
};
|
||||
|
||||
std::map<int, SslConnection> g_connections;
|
||||
struct Runtime {
|
||||
std::mutex mutex;
|
||||
std::unordered_map<int, std::unique_ptr<Context>> contexts;
|
||||
std::unordered_map<int, SslConnection> connections;
|
||||
unsigned nextHandle = 1;
|
||||
Runtime() {
|
||||
if (mbedtls_platform_set_calloc_free(PoolCalloc, PoolFree) != 0)
|
||||
throw std::runtime_error("SSL allocator initialization failed");
|
||||
}
|
||||
};
|
||||
|
||||
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");
|
||||
Runtime& State() {
|
||||
static auto* runtime = new Runtime;
|
||||
return *runtime;
|
||||
}
|
||||
|
||||
Context& GetContext(int handle) {
|
||||
const auto found = State().contexts.find(handle);
|
||||
if (found == State().contexts.end()) throw std::invalid_argument("Invalid SSL context");
|
||||
return *found->second;
|
||||
}
|
||||
|
||||
SslConnection& RequireConnection(const char* function, int sslConnectionId) {
|
||||
const auto connection = g_connections.find(sslConnectionId);
|
||||
if (connection == g_connections.end()) throw std::invalid_argument(std::string(function) + ": unknown connection");
|
||||
if (!g_pools.contains(connection->second.context)) {
|
||||
const auto connection = State().connections.find(sslConnectionId);
|
||||
if (connection == State().connections.end()) throw std::invalid_argument(std::string(function) + ": unknown connection");
|
||||
if (!State().contexts.contains(connection->second.context)) {
|
||||
throw std::invalid_argument(std::string(function) + ": the connection's context was terminated");
|
||||
}
|
||||
return connection->second;
|
||||
}
|
||||
|
||||
struct Input {
|
||||
Block bytes;
|
||||
std::size_t size;
|
||||
};
|
||||
|
||||
Input CopyInput(const SceSsl::Data& data) {
|
||||
if (!data.ptr || !data.size) throw std::invalid_argument("Empty SSL certificate/key data");
|
||||
auto bytes = Allocate(Add(data.size, 1));
|
||||
std::memcpy(bytes.get(), data.ptr, data.size);
|
||||
auto size = data.size;
|
||||
std::size_t first = 0;
|
||||
while (first < size && (bytes.get()[first] == ' ' || bytes.get()[first] == '\r' || bytes.get()[first] == '\n' || bytes.get()[first] == '\t')) ++first;
|
||||
const bool pem = first < size && bytes.get()[first] == '-';
|
||||
if (pem) {
|
||||
if (bytes.get()[size - 1] != 0) ++size;
|
||||
if (std::memchr(bytes.get(), 0, size - 1)) throw std::invalid_argument("Embedded null in SSL PEM data");
|
||||
} else {
|
||||
auto* position = bytes.get();
|
||||
std::size_t length = 0;
|
||||
CheckBackend(mbedtls_asn1_get_tag(&position, bytes.get() + size, &length, MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE),
|
||||
"Invalid SSL DER sequence");
|
||||
if (length != static_cast<std::size_t>(bytes.get() + size - position)) throw std::invalid_argument("Trailing SSL DER data");
|
||||
}
|
||||
return {std::move(bytes), size};
|
||||
}
|
||||
|
||||
std::shared_ptr<CertificateChain> ParseCertificate(const SceSsl::Data& data) {
|
||||
auto chain = std::make_shared<CertificateChain>();
|
||||
const auto input = CopyInput(data);
|
||||
CheckBackend(mbedtls_x509_crt_parse(&chain->root, input.bytes.get(), input.size), "Invalid SSL certificate chain");
|
||||
return chain;
|
||||
}
|
||||
|
||||
std::size_t CertificateCount(const Context& context) {
|
||||
std::size_t count = 0;
|
||||
for (const auto& chain : context.authorities) count = Add(count, chain->handles.size());
|
||||
return count;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
||||
int APS5_VABI sceSslFreeCaCerts(int ssl_ctx_id, void* ca_certs) {
|
||||
(void)ssl_ctx_id;
|
||||
if (!ca_certs) return ERROR_INVALID_ARG;
|
||||
*static_cast<SslCaCerts*>(ca_certs) = {};
|
||||
int APS5_VABI sceSslInit_nid_postfix(std::uint64_t poolSize) {
|
||||
auto& state = State();
|
||||
std::lock_guard lock(state.mutex);
|
||||
if (state.nextHandle > static_cast<unsigned>(std::numeric_limits<int>::max())) throw std::overflow_error("SSL context handles exhausted");
|
||||
const auto handle = static_cast<int>(state.nextHandle);
|
||||
state.contexts.emplace(handle, std::make_unique<Context>(poolSize));
|
||||
++state.nextHandle;
|
||||
return handle;
|
||||
}
|
||||
|
||||
int APS5_VABI sceSslTerm_nid_postfix(int sslCtxId) {
|
||||
auto& state = State();
|
||||
std::lock_guard lock(state.mutex);
|
||||
state.contexts.erase(sslCtxId);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceSslGetCaCerts(int ssl_ctx_id, void* ca_certs) {
|
||||
(void)ssl_ctx_id;
|
||||
if (!ca_certs) return ERROR_INVALID_ARG;
|
||||
*static_cast<SslCaCerts*>(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");
|
||||
int APS5_VABI sceSslGetMemoryPoolStats(int sslCtxId, SceSsl::MemoryPoolStats* stats) {
|
||||
if (!stats) return static_cast<int>(0x8095F007);
|
||||
auto& state = State();
|
||||
std::lock_guard lock(state.mutex);
|
||||
const auto found = state.contexts.find(sslCtxId);
|
||||
if (found == state.contexts.end()) {
|
||||
std::memset(stats, 0, sizeof(*stats));
|
||||
return static_cast<int>(0x8095F006);
|
||||
}
|
||||
const auto& pool = found->second->pool;
|
||||
*stats = {pool.size, pool.peak, pool.current, 0};
|
||||
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);
|
||||
g_pools[id] = pool_size;
|
||||
return id;
|
||||
int APS5_VABI sceSslLoadCert(int sslCtxId, int caCertNum, SceSsl::Data** caList, SceSsl::Data* cert, SceSsl::Data* privateKey) {
|
||||
if (caCertNum < 0 || (caCertNum && !caList) || (!!cert != !!privateKey))
|
||||
throw std::invalid_argument("Invalid SSL certificate load arguments");
|
||||
auto& state = State();
|
||||
std::lock_guard lock(state.mutex);
|
||||
auto& context = GetContext(sslCtxId);
|
||||
PoolScope scope(context.pool);
|
||||
std::vector<std::shared_ptr<CertificateChain>> authorities;
|
||||
std::vector<std::unique_ptr<CertificateHandle>> handles;
|
||||
for (int index = 0; index < caCertNum; ++index) {
|
||||
if (!caList[index]) throw std::invalid_argument("Missing SSL certificate descriptor");
|
||||
auto chain = ParseCertificate(*caList[index]);
|
||||
for (auto* entry = &chain->root; entry; entry = entry->next) {
|
||||
auto handle = std::make_unique<CertificateHandle>(CertificateHandle{chain, entry});
|
||||
chain->handles.push_back(handle.get());
|
||||
handles.push_back(std::move(handle));
|
||||
}
|
||||
authorities.push_back(std::move(chain));
|
||||
}
|
||||
std::shared_ptr<Identity> identity;
|
||||
if (cert) {
|
||||
identity = std::make_shared<Identity>();
|
||||
identity->chain = ParseCertificate(*cert);
|
||||
const auto key = CopyInput(*privateKey);
|
||||
Random random;
|
||||
random.Seed();
|
||||
CheckBackend(mbedtls_pk_parse_key(&identity->key.key, key.bytes.get(), key.size, nullptr, 0,
|
||||
mbedtls_ctr_drbg_random, &random.drbg), "Invalid SSL private key");
|
||||
CheckBackend(mbedtls_pk_check_pair(&identity->chain->root.pk, &identity->key.key, mbedtls_ctr_drbg_random, &random.drbg),
|
||||
"SSL client certificate and private key do not match");
|
||||
}
|
||||
context.authorities.reserve(Add(context.authorities.size(), authorities.size()));
|
||||
context.certificateHandles.reserve(Add(context.certificateHandles.size(), handles.size()));
|
||||
for (auto& chain : authorities) context.authorities.push_back(std::move(chain));
|
||||
for (auto& handle : handles) context.certificateHandles.push_back(std::move(handle));
|
||||
if (identity) context.identity = std::move(identity);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceSslTerm_nid_postfix(int ssl_ctx_id) {
|
||||
std::lock_guard lock(g_poolsMutex);
|
||||
g_pools.erase(ssl_ctx_id);
|
||||
int APS5_VABI sceSslUnloadCert(int sslCtxId) {
|
||||
auto& state = State();
|
||||
std::lock_guard lock(state.mutex);
|
||||
auto& context = GetContext(sslCtxId);
|
||||
context.authorities.clear();
|
||||
context.identity.reset();
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceSslGetCaList(int sslCtxId, SceSsl::CaList* output) {
|
||||
if (!output) return ErrorInvalidArg;
|
||||
auto& state = State();
|
||||
std::lock_guard lock(state.mutex);
|
||||
auto& context = GetContext(sslCtxId);
|
||||
PoolScope scope(context.pool);
|
||||
const auto count = CertificateCount(context);
|
||||
if (!count) {
|
||||
*output = {};
|
||||
return ErrorNotFound;
|
||||
}
|
||||
if (count > static_cast<std::size_t>(std::numeric_limits<int>::max())) throw std::length_error("Too many SSL authorities");
|
||||
Snapshot snapshot{Allocate(count * sizeof(void*)), context.authorities, count};
|
||||
auto** pointers = reinterpret_cast<void**>(snapshot.data.get());
|
||||
std::size_t index = 0;
|
||||
for (const auto& chain : snapshot.certificates) for (auto* handle : chain->handles) pointers[index++] = handle;
|
||||
context.caLists.emplace(pointers, std::move(snapshot));
|
||||
*output = {pointers, static_cast<int>(count)};
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceSslFreeCaList(int sslCtxId, SceSsl::CaList* list) {
|
||||
if (!list) return ErrorInvalidArg;
|
||||
auto& state = State();
|
||||
std::lock_guard lock(state.mutex);
|
||||
auto& context = GetContext(sslCtxId);
|
||||
if (!list->certs && list->num == 0) return 0;
|
||||
const auto found = context.caLists.find(list->certs);
|
||||
if (found == context.caLists.end() || list->num < 0 || static_cast<std::size_t>(list->num) != found->second.count)
|
||||
throw std::invalid_argument("Invalid SSL CA list allocation");
|
||||
context.caLists.erase(found);
|
||||
*list = {};
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceSslGetCaCerts(int sslCtxId, SceSsl::CaCerts* output) {
|
||||
if (!output) return ErrorInvalidArg;
|
||||
auto& state = State();
|
||||
std::lock_guard lock(state.mutex);
|
||||
const auto found = state.contexts.find(sslCtxId);
|
||||
if (found == state.contexts.end()) {
|
||||
*output = {};
|
||||
return ErrorNotFound;
|
||||
}
|
||||
auto& context = *found->second;
|
||||
PoolScope scope(context.pool);
|
||||
const auto count = CertificateCount(context);
|
||||
if (!count) {
|
||||
*output = {};
|
||||
return ErrorNotFound;
|
||||
}
|
||||
if (count > std::numeric_limits<std::size_t>::max() / sizeof(SceSsl::Data)) throw std::length_error("Too many SSL authorities");
|
||||
auto total = count * sizeof(SceSsl::Data);
|
||||
for (const auto& chain : context.authorities)
|
||||
for (auto* entry = &chain->root; entry; entry = entry->next) total = Add(total, entry->raw.len);
|
||||
Snapshot snapshot{Allocate(total), {}, count};
|
||||
auto* data = reinterpret_cast<SceSsl::Data*>(snapshot.data.get());
|
||||
auto* next = snapshot.data.get() + count * sizeof(SceSsl::Data);
|
||||
std::size_t index = 0;
|
||||
for (const auto& chain : context.authorities) {
|
||||
for (auto* entry = &chain->root; entry; entry = entry->next) {
|
||||
data[index++] = {next, entry->raw.len};
|
||||
std::memcpy(next, entry->raw.p, entry->raw.len);
|
||||
next += entry->raw.len;
|
||||
}
|
||||
}
|
||||
context.caCerts.emplace(data, std::move(snapshot));
|
||||
*output = {data, count, data};
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceSslFreeCaCerts(int sslCtxId, SceSsl::CaCerts* certificates) {
|
||||
if (!certificates) return ErrorInvalidArg;
|
||||
auto& state = State();
|
||||
std::lock_guard lock(state.mutex);
|
||||
const auto found = state.contexts.find(sslCtxId);
|
||||
if (found != state.contexts.end()) {
|
||||
auto& snapshots = found->second->caCerts;
|
||||
const auto allocation = snapshots.find(certificates->pool);
|
||||
if (allocation != snapshots.end()) {
|
||||
if (certificates->certs != certificates->pool || certificates->num != allocation->second.count)
|
||||
throw std::invalid_argument("Invalid SSL CA certificate allocation");
|
||||
snapshots.erase(allocation);
|
||||
}
|
||||
}
|
||||
*certificates = {};
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceSslCreateConnection(int sslCtxId, int sockId, const char* host) {
|
||||
if (!host) throw std::invalid_argument("sceSslCreateConnection: null host");
|
||||
std::lock_guard lock(g_poolsMutex);
|
||||
if (!g_pools.contains(sslCtxId)) return ERROR_INVALID_ID;
|
||||
const int id = g_nextHandle.fetch_add(1, std::memory_order_relaxed);
|
||||
g_connections[id] = {sslCtxId, sockId, host, nullptr, nullptr};
|
||||
std::lock_guard lock(State().mutex);
|
||||
if (!State().contexts.contains(sslCtxId)) return ErrorInvalidId;
|
||||
auto& state = State();
|
||||
if (state.nextHandle > static_cast<unsigned>(std::numeric_limits<int>::max())) throw std::overflow_error("SSL connection handles exhausted");
|
||||
const auto id = static_cast<int>(state.nextHandle);
|
||||
state.connections.emplace(id, SslConnection{sslCtxId, sockId, host, nullptr, nullptr});
|
||||
++state.nextHandle;
|
||||
return id;
|
||||
}
|
||||
|
||||
int APS5_VABI sceSslDeleteConnection(int sslConnectionId) {
|
||||
std::lock_guard lock(g_poolsMutex);
|
||||
if (!g_connections.erase(sslConnectionId)) throw std::invalid_argument("sceSslDeleteConnection: unknown connection");
|
||||
std::lock_guard lock(State().mutex);
|
||||
if (!State().connections.erase(sslConnectionId)) throw std::invalid_argument("sceSslDeleteConnection: unknown connection");
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceSslSetVerifyCallback(int sslConnectionId, SslVerifyCallback cbfunc, void* userArg) {
|
||||
std::lock_guard lock(g_poolsMutex);
|
||||
std::lock_guard lock(State().mutex);
|
||||
SslConnection& connection = RequireConnection(__func__, sslConnectionId);
|
||||
connection.verifyCallback = cbfunc;
|
||||
connection.verifyArgument = userArg;
|
||||
@@ -176,28 +468,6 @@ int APS5_VABI sceSslGetSerialNumber() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceSslLoadCert() {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
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) return ERROR_INVALID_VALUE;
|
||||
std::lock_guard lock(g_poolsMutex);
|
||||
const auto pool = g_pools.find(ssl_ctx_id);
|
||||
if (pool == g_pools.end()) {
|
||||
std::memset(stats, 0, sizeof(*stats));
|
||||
return ERROR_INVALID_ID;
|
||||
}
|
||||
*stats = {static_cast<size_t>(pool->second), 0, 0, 0};
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceSslFreeSslCertName(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
#pragma once
|
||||
|
||||
#define MBEDTLS_PLATFORM_MEMORY
|
||||
#undef MBEDTLS_ECDH_VARIANT_EVEREST_ENABLED
|
||||
@@ -0,0 +1,34 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
namespace SceSsl {
|
||||
|
||||
struct Data {
|
||||
std::uint8_t* ptr;
|
||||
std::size_t size;
|
||||
};
|
||||
|
||||
struct CaCerts {
|
||||
Data* certs;
|
||||
std::size_t num;
|
||||
void* pool;
|
||||
};
|
||||
|
||||
struct CaList {
|
||||
void** certs;
|
||||
int num;
|
||||
};
|
||||
|
||||
struct MemoryPoolStats {
|
||||
std::size_t poolSize, maxInuseSize, currentInuseSize;
|
||||
std::uint32_t reserved;
|
||||
};
|
||||
|
||||
static_assert(sizeof(Data) == 0x10);
|
||||
static_assert(sizeof(CaCerts) == 0x18);
|
||||
static_assert(sizeof(CaList) == 0x10);
|
||||
static_assert(sizeof(MemoryPoolStats) == 0x20);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
#include "prx/libSceSsl/SslTypes.hpp"
|
||||
#include "prx/libc/include/general/VabiMacros.hpp"
|
||||
#include "SslCertificateFixture.hpp"
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <exception>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
extern "C" {
|
||||
int APS5_VABI sceSslInit_nid_postfix(std::uint64_t);
|
||||
int APS5_VABI sceSslTerm_nid_postfix(int);
|
||||
int APS5_VABI sceSslGetMemoryPoolStats(int, SceSsl::MemoryPoolStats*);
|
||||
int APS5_VABI sceSslLoadCert(int, int, SceSsl::Data**, SceSsl::Data*, SceSsl::Data*);
|
||||
int APS5_VABI sceSslUnloadCert(int);
|
||||
int APS5_VABI sceSslGetCaList(int, SceSsl::CaList*);
|
||||
int APS5_VABI sceSslFreeCaList(int, SceSsl::CaList*);
|
||||
int APS5_VABI sceSslGetCaCerts(int, SceSsl::CaCerts*);
|
||||
int APS5_VABI sceSslFreeCaCerts(int, SceSsl::CaCerts*);
|
||||
int APS5_VABI sceSslCreateConnection(int, int, const char*);
|
||||
int APS5_VABI sceSslDeleteConnection(int);
|
||||
int APS5_VABI sceSslSetVerifyCallback(int, int (APS5_VABI *)(int, unsigned int, void**, int, void*), void*);
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr int NotFound = static_cast<int>(0x8095F004);
|
||||
|
||||
void Check(bool condition, const char* message) {
|
||||
if (!condition) throw std::runtime_error(message);
|
||||
}
|
||||
|
||||
template <typename TAction>
|
||||
void Throws(TAction action) {
|
||||
bool caught = false;
|
||||
try { action(); } catch (const std::exception&) { caught = true; }
|
||||
Check(caught, "unsupported or malformed load was accepted");
|
||||
}
|
||||
|
||||
SceSsl::Data Data(const char* text) {
|
||||
return {reinterpret_cast<std::uint8_t*>(const_cast<char*>(text)), std::strlen(text)};
|
||||
}
|
||||
|
||||
SceSsl::MemoryPoolStats Stats(int id) {
|
||||
SceSsl::MemoryPoolStats stats{};
|
||||
Check(sceSslGetMemoryPoolStats(id, &stats) == 0, "stats failed");
|
||||
Check(stats.currentInuseSize <= stats.maxInuseSize && stats.maxInuseSize <= stats.poolSize && !stats.reserved,
|
||||
"pool accounting bounds failed");
|
||||
return stats;
|
||||
}
|
||||
|
||||
void CheckRoot(const SceSsl::CaCerts& snapshot) {
|
||||
Check(snapshot.num == 1 && snapshot.certs[0].size == RootDer.size(), "CA count or DER size differs");
|
||||
Check(std::memcmp(snapshot.certs[0].ptr, RootDer.data(), RootDer.size()) == 0, "CA bytes changed");
|
||||
}
|
||||
|
||||
void Lifecycle() {
|
||||
const int id = sceSslInit_nid_postfix(1024 * 1024);
|
||||
std::string original = RootPem;
|
||||
auto data = Data(original.c_str());
|
||||
auto* pointer = &data;
|
||||
Check(sceSslLoadCert(id, 1, &pointer, nullptr, nullptr) == 0, "initial CA load failed");
|
||||
original.assign(original.size(), '?');
|
||||
Check(Stats(id).currentInuseSize > RootDer.size(), "loaded certificate is not retained/accounted");
|
||||
SceSsl::CaCerts bytes{};
|
||||
Check(sceSslGetCaCerts(id, &bytes) == 0, "encoded CA query failed");
|
||||
CheckRoot(bytes);
|
||||
SceSsl::CaList handles{};
|
||||
Check(sceSslGetCaList(id, &handles) == 0 && handles.num == 1 && handles.certs[0], "CA handle absent");
|
||||
const int other = sceSslInit_nid_postfix(1024 * 1024);
|
||||
SceSsl::CaList empty{};
|
||||
Check(sceSslGetCaList(other, &empty) == NotFound, "CA escaped its context");
|
||||
auto foreign = handles;
|
||||
Throws([&] { sceSslFreeCaList(other, &foreign); });
|
||||
Check(sceSslUnloadCert(id) == 0, "unload failed");
|
||||
Check(sceSslGetCaList(id, &empty) == NotFound && !empty.certs && !empty.num, "unload retained active CA");
|
||||
CheckRoot(bytes);
|
||||
Check(sceSslFreeCaList(id, &handles) == 0 && !handles.certs && !handles.num, "handle snapshot release failed");
|
||||
Check(sceSslFreeCaCerts(id, &bytes) == 0 && !bytes.certs && !bytes.pool && !bytes.num, "encoded snapshot release failed");
|
||||
Check(Stats(id).currentInuseSize == 0, "certificate allocation leaked after release");
|
||||
Throws([&] { sceSslFreeCaList(id, &foreign); });
|
||||
Check(sceSslTerm_nid_postfix(id) == 0 && sceSslTerm_nid_postfix(other) == 0, "term failed");
|
||||
}
|
||||
|
||||
void ConnectionLifetime() {
|
||||
const int id = sceSslInit_nid_postfix(1024 * 1024);
|
||||
const int connection = sceSslCreateConnection(id, -1, "example.invalid");
|
||||
const int other = sceSslInit_nid_postfix(1024 * 1024);
|
||||
Check(connection > 0 && connection != id && connection != other, "SSL handles overlap");
|
||||
auto data = Data(RootPem);
|
||||
auto* pointer = &data;
|
||||
Check(sceSslLoadCert(id, 1, &pointer, nullptr, nullptr) == 0, "CA load with a connection failed");
|
||||
Check(sceSslSetVerifyCallback(connection, nullptr, nullptr) == 0, "certificate load invalidated the connection");
|
||||
SceSsl::CaCerts bytes{};
|
||||
Check(sceSslGetCaCerts(id, &bytes) == 0, "CA query with a connection failed");
|
||||
CheckRoot(bytes);
|
||||
SceSsl::CaCerts empty{};
|
||||
Check(sceSslGetCaCerts(other, &empty) == NotFound, "CA escaped into another context");
|
||||
SceSsl::MemoryPoolStats stats{};
|
||||
Check(sceSslGetMemoryPoolStats(connection, &stats) == static_cast<int>(0x8095F006), "connection accepted as context");
|
||||
const int retained = sceSslCreateConnection(id, -1, "example.invalid");
|
||||
Check(retained > 0 && retained != connection && retained != other, "SSL connection handle was reused");
|
||||
Check(sceSslDeleteConnection(connection) == 0, "connection deletion failed");
|
||||
Check(sceSslUnloadCert(id) == 0, "credential unload failed with a retained connection");
|
||||
CheckRoot(bytes);
|
||||
Check(sceSslSetVerifyCallback(retained, nullptr, nullptr) == 0, "credential unload invalidated the connection");
|
||||
Check(sceSslFreeCaCerts(id, &bytes) == 0 && Stats(id).currentInuseSize == 0, "CA snapshot leaked with a retained connection");
|
||||
Check(sceSslTerm_nid_postfix(id) == 0, "context termination failed with a retained connection");
|
||||
Throws([&] { sceSslSetVerifyCallback(retained, nullptr, nullptr); });
|
||||
Check(sceSslDeleteConnection(retained) == 0, "terminated context's connection cannot be released");
|
||||
Check(sceSslGetCaCerts(other, &empty) == NotFound, "unrelated context was invalidated");
|
||||
Check(sceSslTerm_nid_postfix(other) == 0, "unrelated context termination failed");
|
||||
}
|
||||
|
||||
void AtomicFailures() {
|
||||
const int id = sceSslInit_nid_postfix(1024 * 1024);
|
||||
SceSsl::Data der{const_cast<std::uint8_t*>(RootDer.data()), RootDer.size()};
|
||||
auto* pointer = &der;
|
||||
Check(sceSslLoadCert(id, 1, &pointer, nullptr, nullptr) == 0, "DER CA load failed");
|
||||
const auto before = Stats(id).currentInuseSize;
|
||||
auto bad = Data("not a certificate");
|
||||
SceSsl::Data* mixed[]{&der, &bad};
|
||||
Throws([&] { sceSslLoadCert(id, 2, mixed, nullptr, nullptr); });
|
||||
Check(Stats(id).currentInuseSize == before, "failed chain changed live allocation");
|
||||
SceSsl::CaCerts bytes{};
|
||||
Check(sceSslGetCaCerts(id, &bytes) == 0, "CA vanished after failure");
|
||||
CheckRoot(bytes);
|
||||
Check(sceSslFreeCaCerts(id, &bytes) == 0, "snapshot free failed");
|
||||
Throws([&] { sceSslLoadCert(id, -1, nullptr, nullptr, nullptr); });
|
||||
Throws([&] { sceSslLoadCert(id, 1, nullptr, nullptr, nullptr); });
|
||||
SceSsl::Data* absent = nullptr;
|
||||
Throws([&] { sceSslLoadCert(id, 1, &absent, nullptr, nullptr); });
|
||||
Throws([&] { sceSslLoadCert(id, 0, nullptr, &der, nullptr); });
|
||||
Check(Stats(id).currentInuseSize == before, "argument failure changed retained CA");
|
||||
Check(sceSslTerm_nid_postfix(id) == 0, "term failed");
|
||||
Throws([&] { sceSslLoadCert(id, 1, &pointer, nullptr, nullptr); });
|
||||
const int tiny = sceSslInit_nid_postfix(32);
|
||||
Throws([&] { sceSslLoadCert(tiny, 1, &pointer, nullptr, nullptr); });
|
||||
Check(Stats(tiny).currentInuseSize == 0, "failed small-pool load leaked");
|
||||
Check(sceSslTerm_nid_postfix(tiny) == 0, "small-pool term failed");
|
||||
}
|
||||
|
||||
void ClientIdentity() {
|
||||
const int id = sceSslInit_nid_postfix(1024 * 1024);
|
||||
auto client = Data(ClientPem);
|
||||
auto key = Data(ClientKey);
|
||||
auto wrong = Data(RootKey);
|
||||
Check(sceSslLoadCert(id, 0, nullptr, &client, &key) == 0, "client identity load failed");
|
||||
const auto before = Stats(id).currentInuseSize;
|
||||
Check(before > 0, "identity not retained");
|
||||
Throws([&] { sceSslLoadCert(id, 0, nullptr, &client, &wrong); });
|
||||
Check(Stats(id).currentInuseSize == before, "mismatched key changed identity");
|
||||
Check(sceSslUnloadCert(id) == 0 && Stats(id).currentInuseSize == 0, "identity release failed");
|
||||
Check(sceSslTerm_nid_postfix(id) == 0, "identity term failed");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main() {
|
||||
try {
|
||||
Lifecycle();
|
||||
AtomicFailures();
|
||||
ClientIdentity();
|
||||
ConnectionLifetime();
|
||||
std::vector<std::thread> workers;
|
||||
for (int i = 0; i < 3; ++i) workers.emplace_back([] { for (int n = 0; n < 4; ++n) Lifecycle(); });
|
||||
for (auto& worker : workers) worker.join();
|
||||
std::puts("SSL load lifecycle passed");
|
||||
} catch (const std::exception& error) {
|
||||
std::fprintf(stderr, "SSL load lifecycle failed: %s\n", error.what());
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
#pragma once
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
inline constexpr char RootPem[] = R"FIXTURE(-----BEGIN CERTIFICATE-----
|
||||
MIIByDCCAW6gAwIBAgIDAIChMAoGCCqGSM49BAMCMEIxCzAJBgNVBAYTAkdCMRUw
|
||||
EwYDVQQKDAxBbnlQUzUgVGVzdHMxHDAaBgNVBAMME0FueVBTNSBmaXh0dXJlIHJv
|
||||
b3QwHhcNMjYxMDA4MjIxNDAyWhcNMzYxMDA1MjIxNDAyWjBCMQswCQYDVQQGEwJH
|
||||
QjEVMBMGA1UECgwMQW55UFM1IFRlc3RzMRwwGgYDVQQDDBNBbnlQUzUgZml4dHVy
|
||||
ZSByb290MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEGBewuI37COE1s3xt1y52
|
||||
kxoDW1vLl0IzSIlIjKyLrj80kjZWqXOR6X4Th8cyBPVeQkHh0CE+TjZtKpI3CL/H
|
||||
6aNTMFEwHQYDVR0OBBYEFFZolbTcGqYVMtT6BD37g9yqaAzcMB8GA1UdIwQYMBaA
|
||||
FFZolbTcGqYVMtT6BD37g9yqaAzcMA8GA1UdEwEB/wQFMAMBAf8wCgYIKoZIzj0E
|
||||
AwIDSAAwRQIgUQ4NhPYRVB5YgwemEZ5vm76nEY97hJYlq/GymdmmsLgCIQDKMTQC
|
||||
eNbT9zlbdI+uo50utM9rDZ8kSBx5RsuAVj8M3w==
|
||||
-----END CERTIFICATE-----
|
||||
)FIXTURE";
|
||||
inline constexpr char ClientPem[] = R"FIXTURE(-----BEGIN CERTIFICATE-----
|
||||
MIIB3TCCAYOgAwIBAgIDEjRWMAoGCCqGSM49BAMCMEIxCzAJBgNVBAYTAkdCMRUw
|
||||
EwYDVQQKDAxBbnlQUzUgVGVzdHMxHDAaBgNVBAMME0FueVBTNSBmaXh0dXJlIHJv
|
||||
b3QwHhcNMjYxMDA4MjIxNDAyWhcNMzYxMDA1MjIxNDAyWjBoMQswCQYDVQQGEwJH
|
||||
QjEVMBMGA1UECgwMQW55UFM1IFRlc3RzMSIwDgYDVQQLDAdQcmltYXJ5MBAGA1UE
|
||||
CwwJU2Vjb25kYXJ5MR4wHAYDVQQDDBVBbnlQUzUgZml4dHVyZSBjbGllbnQwWTAT
|
||||
BgcqhkjOPQIBBggqhkjOPQMBBwNCAAR1Njvxhr/lzV/EnLlOEyir7zimMPNEDnWt
|
||||
ZYBape38P+jLkWNUgklWh0rIaqQq7rzISu0+mK+ee16bJC3qdWsXo0IwQDAdBgNV
|
||||
HQ4EFgQUwVSZJzELeQ6acITiH929Z4kUuEUwHwYDVR0jBBgwFoAUVmiVtNwaphUy
|
||||
1PoEPfuD3KpoDNwwCgYIKoZIzj0EAwIDSAAwRQIhAIJ3dzC+ddOvUbBXg4sCYTG3
|
||||
xXS9+jF9gxro9MqK1ZenAiA+l/Y8EMrvzjXx7kN5ZV+cVSnH1Ngn3kqaE8w7gIRK
|
||||
NQ==
|
||||
-----END CERTIFICATE-----
|
||||
)FIXTURE";
|
||||
inline constexpr char RootKey[] = R"FIXTURE(-----BEGIN PRIVATE KEY-----
|
||||
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgHIXi+x3Z7ozqaKSg
|
||||
w4mBVS67OC+pzhTpjT6EhQ9/V32hRANCAAQYF7C4jfsI4TWzfG3XLnaTGgNbW8uX
|
||||
QjNIiUiMrIuuPzSSNlapc5HpfhOHxzIE9V5CQeHQIT5ONm0qkjcIv8fp
|
||||
-----END PRIVATE KEY-----
|
||||
)FIXTURE";
|
||||
inline constexpr char ClientKey[] = R"FIXTURE(-----BEGIN PRIVATE KEY-----
|
||||
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgHoANtFzCHilXfZPd
|
||||
GmO1G+NKG5sSUnUWmDrU29+IaQahRANCAAR1Njvxhr/lzV/EnLlOEyir7zimMPNE
|
||||
DnWtZYBape38P+jLkWNUgklWh0rIaqQq7rzISu0+mK+ee16bJC3qdWsX
|
||||
-----END PRIVATE KEY-----
|
||||
)FIXTURE";
|
||||
inline constexpr std::array<std::uint8_t, 460> RootDer{
|
||||
0x30, 0x82, 0x01, 0xc8, 0x30, 0x82, 0x01, 0x6e, 0xa0, 0x03, 0x02, 0x01, 0x02, 0x02, 0x03, 0x00,
|
||||
0x80, 0xa1, 0x30, 0x0a, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x02, 0x30, 0x42,
|
||||
0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x47, 0x42, 0x31, 0x15, 0x30,
|
||||
0x13, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x0c, 0x0c, 0x41, 0x6e, 0x79, 0x50, 0x53, 0x35, 0x20, 0x54,
|
||||
0x65, 0x73, 0x74, 0x73, 0x31, 0x1c, 0x30, 0x1a, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0c, 0x13, 0x41,
|
||||
0x6e, 0x79, 0x50, 0x53, 0x35, 0x20, 0x66, 0x69, 0x78, 0x74, 0x75, 0x72, 0x65, 0x20, 0x72, 0x6f,
|
||||
0x6f, 0x74, 0x30, 0x1e, 0x17, 0x0d, 0x32, 0x36, 0x31, 0x30, 0x30, 0x38, 0x32, 0x32, 0x31, 0x34,
|
||||
0x30, 0x32, 0x5a, 0x17, 0x0d, 0x33, 0x36, 0x31, 0x30, 0x30, 0x35, 0x32, 0x32, 0x31, 0x34, 0x30,
|
||||
0x32, 0x5a, 0x30, 0x42, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x47,
|
||||
0x42, 0x31, 0x15, 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x0c, 0x0c, 0x41, 0x6e, 0x79, 0x50,
|
||||
0x53, 0x35, 0x20, 0x54, 0x65, 0x73, 0x74, 0x73, 0x31, 0x1c, 0x30, 0x1a, 0x06, 0x03, 0x55, 0x04,
|
||||
0x03, 0x0c, 0x13, 0x41, 0x6e, 0x79, 0x50, 0x53, 0x35, 0x20, 0x66, 0x69, 0x78, 0x74, 0x75, 0x72,
|
||||
0x65, 0x20, 0x72, 0x6f, 0x6f, 0x74, 0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce,
|
||||
0x3d, 0x02, 0x01, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07, 0x03, 0x42, 0x00,
|
||||
0x04, 0x18, 0x17, 0xb0, 0xb8, 0x8d, 0xfb, 0x08, 0xe1, 0x35, 0xb3, 0x7c, 0x6d, 0xd7, 0x2e, 0x76,
|
||||
0x93, 0x1a, 0x03, 0x5b, 0x5b, 0xcb, 0x97, 0x42, 0x33, 0x48, 0x89, 0x48, 0x8c, 0xac, 0x8b, 0xae,
|
||||
0x3f, 0x34, 0x92, 0x36, 0x56, 0xa9, 0x73, 0x91, 0xe9, 0x7e, 0x13, 0x87, 0xc7, 0x32, 0x04, 0xf5,
|
||||
0x5e, 0x42, 0x41, 0xe1, 0xd0, 0x21, 0x3e, 0x4e, 0x36, 0x6d, 0x2a, 0x92, 0x37, 0x08, 0xbf, 0xc7,
|
||||
0xe9, 0xa3, 0x53, 0x30, 0x51, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14,
|
||||
0x56, 0x68, 0x95, 0xb4, 0xdc, 0x1a, 0xa6, 0x15, 0x32, 0xd4, 0xfa, 0x04, 0x3d, 0xfb, 0x83, 0xdc,
|
||||
0xaa, 0x68, 0x0c, 0xdc, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80,
|
||||
0x14, 0x56, 0x68, 0x95, 0xb4, 0xdc, 0x1a, 0xa6, 0x15, 0x32, 0xd4, 0xfa, 0x04, 0x3d, 0xfb, 0x83,
|
||||
0xdc, 0xaa, 0x68, 0x0c, 0xdc, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04,
|
||||
0x05, 0x30, 0x03, 0x01, 0x01, 0xff, 0x30, 0x0a, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04,
|
||||
0x03, 0x02, 0x03, 0x48, 0x00, 0x30, 0x45, 0x02, 0x20, 0x51, 0x0e, 0x0d, 0x84, 0xf6, 0x11, 0x54,
|
||||
0x1e, 0x58, 0x83, 0x07, 0xa6, 0x11, 0x9e, 0x6f, 0x9b, 0xbe, 0xa7, 0x11, 0x8f, 0x7b, 0x84, 0x96,
|
||||
0x25, 0xab, 0xf1, 0xb2, 0x99, 0xd9, 0xa6, 0xb0, 0xb8, 0x02, 0x21, 0x00, 0xca, 0x31, 0x34, 0x02,
|
||||
0x78, 0xd6, 0xd3, 0xf7, 0x39, 0x5b, 0x74, 0x8f, 0xae, 0xa3, 0x9d, 0x2e, 0xb4, 0xcf, 0x6b, 0x0d,
|
||||
0x9f, 0x24, 0x48, 0x1c, 0x79, 0x46, 0xcb, 0x80, 0x56, 0x3f, 0x0c, 0xdf,
|
||||
};
|
||||
@@ -3,7 +3,7 @@
|
||||
### Unknown function info
|
||||
|
||||
- PPSA01530 and PPSA13356 imports declared without parameters, signatures unknown, added without an implementation because the titles fail to load without them: [sceNetShowIfconfig](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet), [sce::Json::Value::setNullAccessCallBack](../../../core/libs/prx/libSceJson2/Export.cpp) (libSceJson2), and [scePadUnknown_n3kSX62fgNo](../../../core/libs/prx/libScePad/Export.cpp) (libScePad), whose real name is unknown. [_sceUltWaitingQueueResourcePoolCreate, _sceUltSemaphoreCreate, _sceUltMutexOptParamInitialize, _sceUltMutexCreate, _sceUltQueueDataResourcePoolCreate and _sceUltQueueCreate](../../../core/libs/prx/libSceUlt/Export.cpp) (libSceUlt) forward to the exports without the underscore; that their argument lists match is inferred from the other underscore Ult exports, not measured.
|
||||
- [sceHttp2GetMemoryPoolStats, sceSslGetMemoryPoolStats, sceNetGetMemoryPoolStats](../../../core/libs/prx/libSceNet/Export.cpp) (libSceHttp2, libSceSsl, libSceNet) - the signatures `(int id, stats*)`, the layouts (pool size, peak and current use, and for Http2 and Ssl a 32-bit reserved field) and the error codes were measured on a PS5 (firmware 13.60). 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, where the console already reports use right after initialization (0x15C0 bytes for Ssl, 0x1640 for Http2 and 4 for a Net pool)
|
||||
- [sceHttp2GetMemoryPoolStats, sceSslGetMemoryPoolStats, sceNetGetMemoryPoolStats](../../../core/libs/prx/libSceNet/Export.cpp) (libSceHttp2, libSceSsl, libSceNet) - the signatures `(int id, stats*)`, the layouts (pool size, peak and current use, and for Http2 and Ssl a 32-bit reserved field) and the error codes were measured on a PS5 (firmware 13.60). The pool size is the one given at init or pool creation. Http2 and Net allocate nothing from these pools. Ssl counts its certificate backend allocations and returned data against its pool; C++ bookkeeping is outside that budget. All three initially report zero use, where the console already reports 0x15C0 bytes for Ssl, 0x1640 for Http2 and 4 for a Net pool. Ssl's allocation sizes and peak usage do not reproduce the console allocator
|
||||
- [sceNetShowNetstat](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - imported by PPSA11386 and not called during its start-up; unknown signature, declared without parameters
|
||||
|
||||
### Functional
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
- [sceSslCreateConnection, sceSslDeleteConnection, sceSslSetVerifyCallback, sceSslConnect, sceSslRead, sceSslWrite](../../../core/libs/prx/libSceSsl/Export.cpp) (libSceSsl) - imported by `ffmpeg.prx`. The repository has no TLS client, so a connection only records its context, socket, host and verification callback, and `sceSslConnect`, `sceSslRead` and `sceSslWrite` throw `NotImplemented_nid_no_patch(__func__)` until there is a transport. Signatures follow [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/60977b29103452b9681ae10c7a9011d44965bf16/src/SharpProspero/Interop/Net/Http.cs#L195-L252), which also says the socket stays open after `sceSslDeleteConnection`. An unknown context returns `SSL_ERROR_INVALID_ID` (0x8095F006), as `sceSslGetMemoryPoolStats` does on the console and as [KytyPS5](https://github.com/KytyPS5/KytyPS5/blob/4865dd92ca6b6bb907748f125883093af038f53b/src/libs/network.cpp#L2688-L2720) and [sharpemu](https://github.com/sharpemu/sharpemu/blob/29021b5a71bbc80f262d887e6ba18b3a72963f49/src/SharpEmu.Libs/Network/SslExports.cs#L41-L54) return for other context calls; for an unknown connection, a connection whose context was terminated and a null host the console's codes are unknown, so these throw. The socket is not checked when the connection is created, `sceSslTerm` leaves the context's connections to `sceSslDeleteConnection`, and `sceSslSetVerifyCallback` accepts any callback, null included, and returns 0; none of this is verified on the console. `SSL_ERROR_BEFORE_INIT` (0x8095F001) is never returned, as the library keeps no state apart from its contexts
|
||||
- [sceSslClose](../../../core/libs/prx/libSceSsl/Export.cpp) (libSceSsl) - unknown signature
|
||||
- [sceSslGetSerialNumber](../../../core/libs/prx/libSceSsl/Export.cpp) (libSceSsl) - unknown signature
|
||||
- [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
|
||||
- PPSA01317 imports declared without parameters, signatures unknown: [sceSslDisableVerifyOption, sceSslGetAlpnSelected, sceSslSetAlpn, sceSslSetMinSslVersion](../../../core/libs/prx/libSceSsl/Export.cpp) (libSceSsl). They act on a connection, which `sceSslCreateConnection` does not create yet, so they throw until connections exist
|
||||
- [sceSslLoadCert, sceSslGetCaList, sceSslFreeCaList, sceSslGetCaCerts, sceSslFreeCaCerts, sceSslUnloadCert](../../../core/libs/prx/libSceSsl/Export.cpp) (libSceSsl) - signatures and layouts follow [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Net/Http.cs). PPSA15508/01.002.000 calls `sceSslLoadCert` with one 1,408-byte PEM CA and null client certificate/key arguments; this confirms that call shape, not console error behavior. Mbed TLS 3.6.7 parses DER/PEM chains and checks an optional client certificate/key pair. Imports are retained per context and committed together; a failed import preserves existing credentials. CA queries return independently owned snapshots; unload clears active credentials while outstanding snapshots remain valid. Appending repeated CA imports, snapshot lifetime across unload, memory overhead and error precedence have not been measured on a PS5. Invalid loads, malformed material, exhausted pools and unsupported keys throw rather than inventing console error codes. No root bundle is shipped; an empty context still returns `NOT_FOUND`. The existing null-list error, unknown-context behavior and legacy clearing of foreign raw CA lists remain unchanged. Certificate readers remain unimplemented. Loading credentials neither starts a TLS connection nor authenticates a peer
|
||||
- PPSA01317 imports declared without parameters, signatures unknown: [sceSslDisableVerifyOption, sceSslGetAlpnSelected, sceSslSetAlpn, sceSslSetMinSslVersion](../../../core/libs/prx/libSceSsl/Export.cpp) (libSceSsl). Connection storage exists, but their signatures and option behavior remain unknown, so they still throw
|
||||
|
||||
### Functional
|
||||
|
||||
|
||||
Reference in new issue
Block a user