Add TLS support for non-Windows devices (#144)

* Implement real TLS for non-Windows via vendored mbed TLS

Windows gets TLS for the guest network HLE's SSL ioctlvs for free from
Schannel; every other platform fell into a stub that always returned
failure, meaning any HTTPS-based network feature (WFC login, fetching
the Retro-WFC payload) silently could not work at all on those
platforms regardless of server availability.

Vendors mbed TLS 3.6.7 LTS under runtime/third_party/mbedtls (same
convention as Crypto++/pugixml - a real source checkout, not a
submodule/FetchContent download) and a standard Mozilla CA bundle
(runtime/assets/certs/cacert.pem, via curl.se's redistribution) copied
next to the built product the same way dsp_coef.bin already is.

Verified against real HTTPS servers: a valid certificate completes the
handshake and an HTTP round-trip; a known-expired certificate is
correctly rejected with a real X509 verification failure, not silently
accepted.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qmdewk7VfVVJTfCVd2WStu

* Fix TLS handshake hang and partial-write truncation on non-Windows

Add a POSIX socket timeout to match Windows' existing 15s one, plus a
deadline on the handshake retry loop itself, so a peer that accepts the
TCP connection but never sends TLS data can no longer hang the thread
forever. Also fix SslWrite to loop on partial mbedTLS writes instead of
returning the first partial count, and add mbedTLS to
THIRD-PARTY-NOTICES.md.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* Fetch mbedTLS from a pinned, checksum-verified release instead of vendoring it

Replace the committed mbedTLS source tree with a CMake FetchContent download
of the official mbedtls-3.6.7 release tarball, verified against its signed
SHA-256, matching how aurora-main's own dependencies (SDL, zlib, etc.) are
pulled in. Ships the compiled dependency instead of ~280 tracked upstream
files. CA bundle packaging and THIRD-PARTY-NOTICES.md coverage are unchanged.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* Limit the mbedTLS dependency to the platforms that use it

The FetchContent block ran on every platform, including Windows, whose builds
configure with FETCHCONTENT_FULLY_DISCONNECTED=ON against the offline
dependency set from Launcher/Prepare-Dependencies.ps1 - which has no
mkw_mbedtls_upstream entry, so a clean Windows configure failed. Windows
compiles the Schannel path (network_ssl.cpp is `#ifndef _WIN32` for mbed TLS)
and never links mbed TLS, so nothing needs preparing there: the fetch, the
linkage and the cacert.pem copy are now guarded to non-Windows, while the
mkw::mbedtls alias stays defined everywhere so the link lines in
PublicProducts.cmake remain platform-independent.

Also copy cacert.pem alongside the installed executable in the Linux and macOS
publication paths (Launcher/local-build.sh and Launcher/macos/publish-app.command),
which already copied the other runtime assets but left the TLS root bundle in
the build directory, so published builds could not verify any certificate.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Harden mbed TLS socket I/O handling

* delete wii socket

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Co-authored-by: patchzyy <64382339+patchzyy@users.noreply.github.com>
(cherry picked from commit b59e035b872752df8bfc637bba79689c12abf292)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011tcyLz63pXjoYEwsFjfg8F
This commit is contained in:
dorPXP authored and Claude committed 2026-10-05 12:56:13 +00:00
1 parent f87732d0ad
commit 504ea792da
9 files changed
+3314 -16

No files matched your search

+3 -1
View File
@@ -503,7 +503,9 @@ publish_built_product() {
local exe=$build/$target local exe=$build/$target
assert_file "$exe" "Locally compiled game executable" assert_file "$exe" "Locally compiled game executable"
cp -f "$exe" "$destination/$target" cp -f "$exe" "$destination/$target"
for name in dsp_coef.bin initial_pipeline_cache.db; do # cacert.pem is the TLS root bundle the mbed TLS path looks up beside the executable
# (runtime/src/hle/net/network_ssl.cpp); without it HTTPS fails at runtime.
for name in dsp_coef.bin initial_pipeline_cache.db cacert.pem; do
[[ -f "$build/$name" ]] && cp -f "$build/$name" "$destination/" [[ -f "$build/$name" ]] && cp -f "$build/$name" "$destination/"
done done
[[ -d "$build/wii_bootstrap" ]] && cp -rf "$build/wii_bootstrap" "$destination/" [[ -d "$build/wii_bootstrap" ]] && cp -rf "$build/wii_bootstrap" "$destination/"
+2 -2
View File
@@ -27,7 +27,7 @@ done
[[ "$product" == WiiCompiled || "$product" == RetroRewind ]] || fail '--product must be WiiCompiled or RetroRewind' [[ "$product" == WiiCompiled || "$product" == RetroRewind ]] || fail '--product must be WiiCompiled or RetroRewind'
for tool in codesign ditto install_name_tool otool; do command -v "$tool" >/dev/null || fail "required macOS tool is unavailable: $tool"; done for tool in codesign ditto install_name_tool otool; do command -v "$tool" >/dev/null || fail "required macOS tool is unavailable: $tool"; done
[[ -x "$build_dir/$product" ]] || fail "missing compiled product: $build_dir/$product" [[ -x "$build_dir/$product" ]] || fail "missing compiled product: $build_dir/$product"
for asset in dsp_coef.bin initial_pipeline_cache.db wii_bootstrap; do [[ -e "$build_dir/$asset" ]] || fail "missing runtime asset: $build_dir/$asset"; done for asset in dsp_coef.bin initial_pipeline_cache.db cacert.pem wii_bootstrap; do [[ -e "$build_dir/$asset" ]] || fail "missing runtime asset: $build_dir/$asset"; done
app="$output_dir/$product.app" app="$output_dir/$product.app"
macos="$app/Contents/MacOS" macos="$app/Contents/MacOS"
@@ -52,7 +52,7 @@ cat > "$app/Contents/Info.plist" <<EOF
</dict></plist> </dict></plist>
EOF EOF
ditto "$build_dir/$product" "$macos/$product" ditto "$build_dir/$product" "$macos/$product"
for asset in dsp_coef.bin initial_pipeline_cache.db wii_bootstrap; do for asset in dsp_coef.bin initial_pipeline_cache.db cacert.pem wii_bootstrap; do
ditto "$build_dir/$asset" "$resources/$asset" ditto "$build_dir/$asset" "$resources/$asset"
ln -s "../Resources/$asset" "$macos/$asset" ln -s "../Resources/$asset" "$macos/$asset"
done done
+5 -3
View File
@@ -131,14 +131,16 @@ Source: <https://github.com/higan-emu/libco>. Full license text:
## Fetched at build time and redistributed in release builds ## Fetched at build time and redistributed in release builds
These are pinned in `aurora-main/extern/CMakeLists.txt`, `aurora-main/CMakeLists.txt` and These are pinned in `aurora-main/extern/CMakeLists.txt`, `aurora-main/CMakeLists.txt`,
`aurora-main/cmake/AuroraDawnProvider.cmake`. They are not stored in this repository; the build `aurora-main/cmake/AuroraDawnProvider.cmake`, and (for Mbed TLS) `runtime/CMakeLists.txt`. They are
downloads them, and release installers carry the resulting binaries. Their license texts are not stored in this repository; the build downloads them - each fetch is pinned to an exact version
with a checked SHA-256 - and links or redistributes the resulting binaries. Their license texts are
included in the installer's `licenses/` folder. The Windows installer bundles the pinned source included in the installer's `licenses/` folder. The Windows installer bundles the pinned source
trees themselves (fetched by `Launcher/Prepare-Dependencies.ps1`) so end-user builds run offline. trees themselves (fetched by `Launcher/Prepare-Dependencies.ps1`) so end-user builds run offline.
| Component | Version | License | Upstream | | Component | Version | License | Upstream |
| --- | --- | --- | --- | | --- | --- | --- | --- |
| Mbed TLS | 3.6.7 | Apache-2.0 / GPL-2.0-or-later | <https://github.com/Mbed-TLS/mbedtls> |
| Dawn (WebGPU) | `v20260603.191052` prebuilt | BSD-3-Clause | <https://dawn.googlesource.com/dawn> | | Dawn (WebGPU) | `v20260603.191052` prebuilt | BSD-3-Clause | <https://dawn.googlesource.com/dawn> |
| Tint (part of Dawn) | with Dawn | BSD-3-Clause | <https://dawn.googlesource.com/dawn> | | Tint (part of Dawn) | with Dawn | BSD-3-Clause | <https://dawn.googlesource.com/dawn> |
| DirectXShaderCompiler (`dxcompiler.dll`) | with Dawn | NCSA / University of Illinois Open Source | <https://github.com/microsoft/DirectXShaderCompiler> | | DirectXShaderCompiler (`dxcompiler.dll`) | with Dawn | NCSA / University of Illinois Open Source | <https://github.com/microsoft/DirectXShaderCompiler> |
+37
View File
@@ -144,6 +144,43 @@ if(NOT MKW_NATIVE_PREBUILT_DIR)
set_target_properties(mkw_cryptopp PROPERTIES UNITY_BUILD OFF) set_target_properties(mkw_cryptopp PROPERTIES UNITY_BUILD OFF)
endif() endif()
# TLS for non-Windows guest network HLE (runtime/src/hle/net/network_ssl.cpp) - the Windows path
# uses Schannel (a Windows-only OS API), which has no equivalent on Linux/Android, so this project
# needs its own TLS library there. mbed TLS was chosen over OpenSSL specifically because it cross-
# compiles cleanly for Android with nothing beyond a plain C toolchain (no perl/asm build-script
# dependency the way OpenSSL's build has), matching how this project already prefers toolchain-
# simple libraries (see Crypto++ above, similarly stripped of ASM/SIMD for portability).
# Fetched at build time from a pinned upstream release tarball with a checked SHA-256, the same way
# aurora-main's own dependencies (SDL, zlib, etc.) are pulled in - not committed as a vendored
# source tree, so the repository ships the compiled dependency rather than ~280 tracked upstream
# files. Bump MKW_MBEDTLS_VERSION/MKW_MBEDTLS_SHA256 together when updating; the hash comes from
# upstream's own signed `mbedtls-<version>-sha256sum.txt` release asset.
#
# The alias exists on every platform so the link lines in cmake/PublicProducts.cmake stay
# platform-independent, but it is only populated where network_ssl.cpp actually compiles the mbed
# TLS path (`#ifndef _WIN32`). Windows keeps Schannel and must not fetch anything: its builds run
# with FETCHCONTENT_FULLY_DISCONNECTED=ON against the offline dependency set prepared by
# Launcher/Prepare-Dependencies.ps1, so an unconditional fetch would fail a clean configure there
# and would also add a dependency Windows never links.
add_library(mkw_mbedtls INTERFACE)
add_library(mkw::mbedtls ALIAS mkw_mbedtls)
if(NOT MKW_PLATFORM_WINDOWS)
include(FetchContent)
set(MKW_MBEDTLS_VERSION "3.6.7")
set(MKW_MBEDTLS_SHA256 "a7e8bcbec0e6f761b4af24f25677626b35f762f68eef79c08677a363212d11f6")
FetchContent_Declare(mkw_mbedtls_upstream
URL "https://github.com/Mbed-TLS/mbedtls/releases/download/mbedtls-${MKW_MBEDTLS_VERSION}/mbedtls-${MKW_MBEDTLS_VERSION}.tar.bz2"
URL_HASH SHA256=${MKW_MBEDTLS_SHA256})
# Subproject mode already defaults ENABLE_TESTING off and skips codegen (GEN_FILES), but
# ENABLE_PROGRAMS defaults on and installation/package-config isn't wanted for a linked-in copy.
set(ENABLE_PROGRAMS OFF CACHE BOOL "" FORCE)
set(ENABLE_TESTING OFF CACHE BOOL "" FORCE)
set(MBEDTLS_FATAL_WARNINGS OFF CACHE BOOL "" FORCE)
set(DISABLE_PACKAGE_CONFIG_AND_INSTALL ON CACHE BOOL "" FORCE)
FetchContent_MakeAvailable(mkw_mbedtls_upstream)
target_link_libraries(mkw_mbedtls INTERFACE MbedTLS::mbedtls MbedTLS::mbedx509 MbedTLS::mbedcrypto)
endif()
set(MKW_TRANSLATED_COMPILE_JOBS 0 CACHE STRING set(MKW_TRANSLATED_COMPILE_JOBS 0 CACHE STRING
"Cap on concurrently compiling translated shard TUs via a Ninja job pool (0 = uncapped). \ "Cap on concurrently compiling translated shard TUs via a Ninja job pool (0 = uncapped). \
Scheduling only - never affects output bytes, so it is deliberately outside the canonical flag fingerprint.") Scheduling only - never affects output bytes, so it is deliberately outside the canonical flag fingerprint.")
File diff suppressed because it is too large. Load diff
+18 -3
View File
@@ -110,7 +110,7 @@ target_compile_definitions(mkw_runtime_common PRIVATE
_DISABLE_STRING_ANNOTATION _DISABLE_VECTOR_ANNOTATION) _DISABLE_STRING_ANNOTATION _DISABLE_VECTOR_ANNOTATION)
target_link_libraries(mkw_runtime_common PRIVATE target_link_libraries(mkw_runtime_common PRIVATE
aurora::gx aurora::pad aurora::si aurora::vi aurora::mtx) aurora::gx aurora::pad aurora::si aurora::vi aurora::mtx)
target_link_libraries(mkw_runtime_common PRIVATE mkw_platform mkw::pugixml mkw::toml11 mkw::cryptopp) target_link_libraries(mkw_runtime_common PRIVATE mkw_platform mkw::pugixml mkw::toml11 mkw::cryptopp mkw::mbedtls)
if(MKW_ENABLE_OPENXR) if(MKW_ENABLE_OPENXR)
target_link_libraries(mkw_runtime_common PRIVATE ${MKW_OPENXR_TARGET}) target_link_libraries(mkw_runtime_common PRIVATE ${MKW_OPENXR_TARGET})
endif() endif()
@@ -276,10 +276,10 @@ function(mkw_configure_product target)
mkw_bound_translated_compiles(${target}) mkw_bound_translated_compiles(${target})
if(MKW_PRODUCT_WITHOUT_GAME) if(MKW_PRODUCT_WITHOUT_GAME)
target_link_libraries(${target} PRIVATE target_link_libraries(${target} PRIVATE
mkw_platform mkw::pugixml mkw::toml11 mkw::cryptopp) mkw_platform mkw::pugixml mkw::toml11 mkw::cryptopp mkw::mbedtls)
else() else()
target_link_libraries(${target} PRIVATE target_link_libraries(${target} PRIVATE
mkw_platform mkw_base_shared mkw::pugixml mkw::toml11 mkw::cryptopp) mkw_platform mkw_base_shared mkw::pugixml mkw::toml11 mkw::cryptopp mkw::mbedtls)
endif() endif()
target_link_libraries(${target} PRIVATE target_link_libraries(${target} PRIVATE
@@ -379,6 +379,21 @@ function(mkw_configure_product target)
add_custom_command(TARGET ${target} POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different add_custom_command(TARGET ${target} POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different
"${MKW_INITIAL_PIPELINE_CACHE}" "${MKW_INITIAL_PIPELINE_CACHE}"
"$<TARGET_FILE_DIR:${target}>/initial_pipeline_cache.db") "$<TARGET_FILE_DIR:${target}>/initial_pipeline_cache.db")
# Non-Windows TLS (runtime/src/hle/net/network_ssl.cpp's mbed TLS path) needs a trusted root
# CA bundle to verify server certificates against - Windows gets this for free from the OS via
# Schannel, mbed TLS does not ship one itself. Not SHA256-pinned like the DSP ROM above: unlike
# a fixed hardware ROM, this bundle is expected to be refreshed periodically as CAs rotate.
# Windows gets its trust store from Schannel, so only the platforms that actually build the
# mbed TLS path need the bundle beside the executable.
if(NOT MKW_PLATFORM_WINDOWS)
set(MKW_CA_CERTIFICATE_BUNDLE "${MKW_RUNTIME_SOURCE_DIR}/assets/certs/cacert.pem")
if(NOT EXISTS "${MKW_CA_CERTIFICATE_BUNDLE}")
message(FATAL_ERROR "Missing TLS root CA bundle: ${MKW_CA_CERTIFICATE_BUNDLE}")
endif()
add_custom_command(TARGET ${target} POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different
"${MKW_CA_CERTIFICATE_BUNDLE}" "$<TARGET_FILE_DIR:${target}>/cacert.pem")
endif()
endfunction() endfunction()
# Android ships each product as the shared library SDLActivity loads; the base # Android ships each product as the shared library SDLActivity loads; the base
+1
View File
@@ -242,6 +242,7 @@ void WritePollResults(uint32_t outAddress,
const std::vector<NetworkPollContract::CopiedDescriptor>& descriptors); const std::vector<NetworkPollContract::CopiedDescriptor>& descriptors);
// network_socket.cpp // network_socket.cpp
int32_t DeleteWiiSocket(uint32_t fd);
void CleanupAllWiiSockets(); void CleanupAllWiiSockets();
sockaddr_in ReadWiiSockAddr(uint32_t addr); sockaddr_in ReadWiiSockAddr(uint32_t addr);
int32_t HandleIpTopIoctl(uint32_t cmd, uint32_t inBuf, uint32_t inLen, uint32_t outBuf, int32_t HandleIpTopIoctl(uint32_t cmd, uint32_t inBuf, uint32_t inLen, uint32_t outBuf,
+1 -1
View File
@@ -21,7 +21,7 @@ static int32_t NewWiiSocket(uint32_t af, uint32_t type, uint32_t protocol) {
return wiiFd; return wiiFd;
} }
static int32_t DeleteWiiSocket(uint32_t fd) { int32_t DeleteWiiSocket(uint32_t fd) {
WiiSocket* s = GetWiiSocket(fd); WiiSocket* s = GetWiiSocket(fd);
if (!s) { if (!s) {
return -SO_EBADF; return -SO_EBADF;
+297 -6
View File
@@ -1,4 +1,20 @@
#include "network_internal.h" #include "network_internal.h"
#include "runtime_config.h"
#include "runtime_log.h"
#ifndef _WIN32
#include <mbedtls/ctr_drbg.h>
#include <mbedtls/entropy.h>
#include <mbedtls/error.h>
#include <mbedtls/net_sockets.h>
#include <mbedtls/ssl.h>
#include <mbedtls/x509_crt.h>
#include <chrono>
#include <cstring>
#include <filesystem>
#include <optional>
#endif
namespace NetworkHle { namespace NetworkHle {
@@ -56,6 +72,11 @@ struct SslSession {
CredHandle cred{}; CredHandle cred{};
CtxtHandle context{}; CtxtHandle context{};
SecPkgContext_StreamSizes sizes{}; SecPkgContext_StreamSizes sizes{};
#else
bool haveSsl = false;
mbedtls_ssl_context sslContext{};
mbedtls_ssl_config sslConfig{};
mbedtls_net_context netContext{};
#endif #endif
}; };
@@ -539,20 +560,282 @@ static int32_t SslRead(SslSession& ssl, uint8_t* out, uint32_t size) {
return copied == 0 ? SSL_ERR_ZERO : static_cast<int32_t>(copied); return copied == 0 ? SSL_ERR_ZERO : static_cast<int32_t>(copied);
} }
#else #else
// Windows gets TLS for free from the OS (Schannel, above) - mbed TLS is this project's own
// vendored equivalent for everywhere else (runtime/third_party/mbedtls, see runtime/CMakeLists.txt
// for why mbed TLS specifically). The CA chain and RNG are expensive to set up (parsing ~150 root
// certificates, seeding entropy) and read-only once built, so they're shared process-wide instead
// of being redone per SSL session.
static bool g_mbedtlsCaLoaded = false;
static mbedtls_x509_crt g_mbedtlsCaChain;
static mbedtls_entropy_context g_mbedtlsEntropy;
static mbedtls_ctr_drbg_context g_mbedtlsCtrDrbg;
static ssize_t SendSslSocket(NativeSocket socket, const uint8_t* data, size_t size) {
#ifdef __APPLE__
const int noSigPipe = 1;
if (setsockopt(socket, SOL_SOCKET, SO_NOSIGPIPE, &noSigPipe, sizeof(noSigPipe)) != 0) {
return -1;
}
return send(socket, data, size, 0);
#else
return send(socket, data, size, MSG_NOSIGNAL);
#endif
}
static int MbedtlsSend(void* context, const unsigned char* data, size_t size) {
const auto* net = static_cast<mbedtls_net_context*>(context);
const ssize_t result = SendSslSocket(net->fd, data, size);
if (result >= 0) {
return static_cast<int>(result);
}
if (errno == EINTR) {
return MBEDTLS_ERR_SSL_WANT_WRITE;
}
if (errno == EPIPE || errno == ECONNRESET) {
return MBEDTLS_ERR_NET_CONN_RESET;
}
return MBEDTLS_ERR_NET_SEND_FAILED;
}
static int MbedtlsRecv(void* context, unsigned char* data, size_t size) {
const int result = mbedtls_net_recv(context, data, size);
// Blocking socket timeouts must leave the TLS session retryable.
if (result == MBEDTLS_ERR_NET_RECV_FAILED && (errno == EAGAIN || errno == EWOULDBLOCK)) {
return MBEDTLS_ERR_SSL_WANT_READ;
}
return result;
}
// Mirrors ax_mix.cpp's FindDspCoefficientRom exactly - same three places a bundled asset can live
// depending on platform and how the binary was launched (next to the desktop executable, the
// Android app's own data directory, or a source-tree checkout during development).
static std::optional<std::filesystem::path> FindCaCertificateBundle() {
if (const auto executableDirectory = RuntimeConfigFile::ExecutableDirectory()) {
const auto adjacent = *executableDirectory / "cacert.pem";
if (std::filesystem::is_regular_file(adjacent)) {
return adjacent;
}
}
#if defined(__ANDROID__)
const auto androidAsset = RuntimeConfigFile::ApplicationDataDirectory() / "cacert.pem";
if (std::filesystem::is_regular_file(androidAsset)) {
return androidAsset;
}
#endif
for (auto base = std::filesystem::current_path(); !base.empty();) {
const auto sourceTreeAsset = base / "runtime" / "assets" / "certs" / "cacert.pem";
if (std::filesystem::is_regular_file(sourceTreeAsset)) {
return sourceTreeAsset;
}
const auto parent = base.parent_path();
if (parent == base) {
break;
}
base = parent;
}
return std::nullopt;
}
// Lazy, once-per-process: the first real SSL use pays for parsing the CA bundle and seeding the
// RNG, every session after that reuses the result. Returns false (logging once) if the bundle is
// missing or unparseable - callers treat that as a normal handshake failure, not a crash, since a
// missing TLS root store shouldn't take down gameplay that never touches the network.
static bool EnsureMbedtlsGlobalsInitialized() {
static const bool initialized = [] {
mbedtls_x509_crt_init(&g_mbedtlsCaChain);
mbedtls_entropy_init(&g_mbedtlsEntropy);
mbedtls_ctr_drbg_init(&g_mbedtlsCtrDrbg);
const char* personalization = "wiicompiled_ssl";
if (mbedtls_ctr_drbg_seed(&g_mbedtlsCtrDrbg, mbedtls_entropy_func, &g_mbedtlsEntropy,
reinterpret_cast<const unsigned char*>(personalization),
std::strlen(personalization)) != 0) {
NetFail("ssl: failed to seed TLS random number generator");
return false;
}
const auto bundle = FindCaCertificateBundle();
if (!bundle) {
NetFail("ssl: missing TLS root CA bundle (cacert.pem) - HTTPS connections will fail");
return false;
}
const int parseRet = mbedtls_x509_crt_parse_file(&g_mbedtlsCaChain, bundle->string().c_str());
if (parseRet < 0) {
char errorBuffer[256];
mbedtls_strerror(parseRet, errorBuffer, sizeof(errorBuffer));
NetFail("ssl: failed to parse CA bundle %s: %s", bundle->string().c_str(), errorBuffer);
return false;
}
return true;
}();
g_mbedtlsCaLoaded = initialized;
return initialized;
}
// Builds this session's mbed TLS handshake state exactly once - a second call (e.g. the handshake
// re-running after DOHANDSHAKE was already satisfied) is a no-op via ssl.haveSsl.
static int32_t EnsureMbedtlsSession(SslSession& ssl) {
if (ssl.haveSsl) {
return SSL_OK;
}
if (!EnsureMbedtlsGlobalsInitialized()) {
return SSL_ERR_FAILED;
}
mbedtls_ssl_init(&ssl.sslContext);
mbedtls_ssl_config_init(&ssl.sslConfig);
if (mbedtls_ssl_config_defaults(&ssl.sslConfig, MBEDTLS_SSL_IS_CLIENT, MBEDTLS_SSL_TRANSPORT_STREAM,
MBEDTLS_SSL_PRESET_DEFAULT) != 0) {
return SSL_ERR_FAILED;
}
// Real certificate validation, matching Schannel's SCH_CRED_AUTO_CRED_VALIDATION on the
// Windows side above - a self-signed or wrong-hostname certificate must fail the handshake,
// not just get logged.
mbedtls_ssl_conf_authmode(&ssl.sslConfig, MBEDTLS_SSL_VERIFY_REQUIRED);
mbedtls_ssl_conf_ca_chain(&ssl.sslConfig, &g_mbedtlsCaChain, nullptr);
mbedtls_ssl_conf_rng(&ssl.sslConfig, mbedtls_ctr_drbg_random, &g_mbedtlsCtrDrbg);
if (mbedtls_ssl_setup(&ssl.sslContext, &ssl.sslConfig) != 0) {
return SSL_ERR_FAILED;
}
// The hostname drives both SNI (which certificate the server presents) and the CN/SAN check
// mbedtls_ssl_conf_authmode enforces above - required, not optional, same reasoning as the
// Windows path's own "refuse an empty hostname" check just above SslHandshakeImpl.
mbedtls_ssl_set_hostname(&ssl.sslContext, ssl.hostname.c_str());
ssl.netContext.fd = static_cast<int>(ssl.native);
mbedtls_ssl_set_bio(&ssl.sslContext, &ssl.netContext, MbedtlsSend, MbedtlsRecv, nullptr);
ssl.haveSsl = true;
return SSL_OK;
}
static void ClearSslSession(SslSession& ssl) { static void ClearSslSession(SslSession& ssl) {
if (ssl.haveSsl) {
mbedtls_ssl_free(&ssl.sslContext);
mbedtls_ssl_config_free(&ssl.sslConfig);
}
ssl = {}; ssl = {};
} }
static int32_t SslHandshakeImpl(SslSession&) { static int32_t SslHandshakeImpl(SslSession& ssl) {
return SSL_ERR_FAILED; if (ssl.plaintextWfc) {
ssl.handshaked = true;
return SSL_OK;
}
if (ssl.handshaked) {
return SSL_OK;
}
if (ssl.native == kInvalidSocket) {
return SSL_ERR_SYSCALL;
}
// mbed TLS can authenticate a certificate chain without authenticating a server identity when
// no hostname is set - refuse that ambiguous mode, matching the Windows path's own check.
if (ssl.hostname.empty()) {
return SSL_ERR_VCOMMONNAME;
}
const int32_t setupRet = EnsureMbedtlsSession(ssl);
if (setupRet != SSL_OK) {
return setupRet;
}
// Receive timeouts are retryable, but the handshake must still terminate.
const auto handshakeDeadline = std::chrono::steady_clock::now() + std::chrono::seconds(15);
int handshakeRet;
while ((handshakeRet = mbedtls_ssl_handshake(&ssl.sslContext)) != 0) {
if (handshakeRet == MBEDTLS_ERR_SSL_WANT_READ || handshakeRet == MBEDTLS_ERR_SSL_WANT_WRITE) {
if (std::chrono::steady_clock::now() >= handshakeDeadline) {
NetFail("ssl handshake TIMED OUT host=%s", ssl.hostname.c_str());
return SSL_ERR_FAILED;
}
continue;
}
char errorBuffer[256];
mbedtls_strerror(handshakeRet, errorBuffer, sizeof(errorBuffer));
NetFail("ssl handshake FAILED host=%s mbedtls_err=%s", ssl.hostname.c_str(), errorBuffer);
return handshakeRet == MBEDTLS_ERR_X509_CERT_VERIFY_FAILED ? SSL_ERR_VCOMMONNAME : SSL_ERR_FAILED;
}
ssl.handshaked = true;
return SSL_OK;
} }
static int32_t SslWrite(SslSession&, const uint8_t*, uint32_t) { static int32_t SslWrite(SslSession& ssl, const uint8_t* data, uint32_t size) {
return SSL_ERR_FAILED; if (!data || size == 0) {
return SSL_ERR_ZERO;
}
const int32_t handshakeRet = SslHandshake(ssl);
if (handshakeRet != SSL_OK) {
return handshakeRet;
}
if (ssl.plaintextWfc) {
uint32_t total = 0;
while (total < size) {
const ssize_t sent = SendSslSocket(ssl.native, data + total, size - total);
if (sent <= 0) {
return SSL_ERR_SYSCALL;
}
total += static_cast<uint32_t>(sent);
}
return static_cast<int32_t>(total);
}
// mbed TLS is allowed to write fewer bytes than requested in one call (e.g. when size exceeds
// one TLS record) - the caller must resend the remainder starting from where it left off, so
// loop here until every byte is actually written rather than returning the first partial count.
uint32_t totalWritten = 0;
const auto writeDeadline = std::chrono::steady_clock::now() + std::chrono::seconds(15);
while (totalWritten < size) {
const int ret = mbedtls_ssl_write(&ssl.sslContext, data + totalWritten, size - totalWritten);
if (ret > 0) {
totalWritten += static_cast<uint32_t>(ret);
continue;
}
if (ret == MBEDTLS_ERR_SSL_WANT_READ || ret == MBEDTLS_ERR_SSL_WANT_WRITE) {
if (std::chrono::steady_clock::now() >= writeDeadline) {
DeleteWiiSocket(ssl.socketFd);
return SSL_ERR_FAILED;
}
continue;
}
return SSL_ERR_FAILED;
}
return static_cast<int32_t>(totalWritten);
} }
static int32_t SslRead(SslSession&, uint8_t*, uint32_t) { static int32_t SslRead(SslSession& ssl, uint8_t* out, uint32_t size) {
return SSL_ERR_FAILED; if (!out || size == 0) {
return SSL_ERR_ZERO;
}
const int32_t handshakeRet = SslHandshake(ssl);
if (handshakeRet != SSL_OK) {
return handshakeRet;
}
if (ssl.plaintextWfc) {
const ssize_t ret = recv(ssl.native, out, size, 0);
if (ret == 0) {
return SSL_ERR_ZERO;
}
if (ret < 0) {
return SSL_ERR_RAGAIN;
}
return static_cast<int32_t>(ret);
}
const int ret = mbedtls_ssl_read(&ssl.sslContext, out, size);
if (ret == 0 || ret == MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY) {
return SSL_ERR_ZERO;
}
if (ret == MBEDTLS_ERR_SSL_WANT_READ || ret == MBEDTLS_ERR_SSL_WANT_WRITE) {
return SSL_ERR_RAGAIN;
}
if (ret < 0) {
return SSL_ERR_FAILED;
}
return ret;
} }
#endif #endif
@@ -640,6 +923,14 @@ int32_t HandleSslIoctlv(uint32_t cmd, const std::vector<IoVector>& in, const std
const int timeoutMs = 15000; const int timeoutMs = 15000;
setsockopt(socket->native, SOL_SOCKET, SO_RCVTIMEO, reinterpret_cast<const char*>(&timeoutMs), sizeof(timeoutMs)); setsockopt(socket->native, SOL_SOCKET, SO_RCVTIMEO, reinterpret_cast<const char*>(&timeoutMs), sizeof(timeoutMs));
setsockopt(socket->native, SOL_SOCKET, SO_SNDTIMEO, reinterpret_cast<const char*>(&timeoutMs), sizeof(timeoutMs)); setsockopt(socket->native, SOL_SOCKET, SO_SNDTIMEO, reinterpret_cast<const char*>(&timeoutMs), sizeof(timeoutMs));
#else
// Match the Windows 15s timeout so a peer that accepts the TCP connection but stalls
// during the TLS handshake or a later read/write can't hang this thread forever. POSIX
// takes a struct timeval here, not a plain millisecond count like Windows does.
struct timeval timeout {};
timeout.tv_sec = 15;
setsockopt(socket->native, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout));
setsockopt(socket->native, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout));
#endif #endif
WriteSslReturn(in, SSL_OK); WriteSslReturn(in, SSL_OK);
return 0; return 0;