From 2876c4ec52ca3d3843b16593af9d987c9b172322 Mon Sep 17 00:00:00 2001 From: lizzie Date: Tue, 18 Aug 2026 20:31:12 +0000 Subject: [PATCH] [hle/service/ssl] remove non-OpenSSL backends, force builds to use OpenSSL and remove passthru backend Signed-off-by: lizzie --- src/core/CMakeLists.txt | 17 - src/core/hle/service/ssl/ssl.cpp | 453 +++++++++++++- src/core/hle/service/ssl/ssl_backend.h | 14 - src/core/hle/service/ssl/ssl_backend_none.cpp | 19 - .../hle/service/ssl/ssl_backend_openssl.cpp | 450 -------------- .../hle/service/ssl/ssl_backend_schannel.cpp | 563 ------------------ .../ssl/ssl_backend_securetransport.cpp | 236 -------- 7 files changed, 424 insertions(+), 1328 deletions(-) delete mode 100644 src/core/hle/service/ssl/ssl_backend_none.cpp delete mode 100644 src/core/hle/service/ssl/ssl_backend_openssl.cpp delete mode 100644 src/core/hle/service/ssl/ssl_backend_schannel.cpp delete mode 100644 src/core/hle/service/ssl/ssl_backend_securetransport.cpp diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index e04b3dde26..81349164a0 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -1267,23 +1267,6 @@ if (ARCHITECTURE_x86_64 OR ARCHITECTURE_arm64 OR ARCHITECTURE_riscv64 OR ARCHITE target_link_libraries(core PRIVATE dynarmic::dynarmic) endif() -target_sources(core PRIVATE hle/service/ssl/ssl_backend_openssl.cpp) - target_link_libraries(core PRIVATE OpenSSL::SSL OpenSSL::Crypto) -# TODO - -# elseif (APPLE) -# target_sources(core PRIVATE -# hle/service/ssl/ssl_backend_securetransport.cpp) -# target_link_libraries(core PRIVATE "-framework Security") -# elseif (WIN32) -# target_sources(core PRIVATE -# hle/service/ssl/ssl_backend_schannel.cpp) -# target_link_libraries(core PRIVATE crypt32 secur32) -# else() -# target_sources(core PRIVATE -# hle/service/ssl/ssl_backend_none.cpp) -# endif() - create_target_directory_groups(core) diff --git a/src/core/hle/service/ssl/ssl.cpp b/src/core/hle/service/ssl/ssl.cpp index f826993920..3f4e21eb86 100644 --- a/src/core/hle/service/ssl/ssl.cpp +++ b/src/core/hle/service/ssl/ssl.cpp @@ -4,6 +4,18 @@ // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include + +#include +#include +#include +#include +#ifdef YUZU_BUNDLED_OPENSSL +#include +#endif + +#include "common/fs/file.h" +#include "common/hex_util.h" #include "common/string_util.h" #include "core/core.h" @@ -22,6 +34,377 @@ namespace Service::SSL { +namespace { + +std::once_flag one_time_init_flag; +bool one_time_init_success = false; +SSL_CTX* ssl_ctx = nullptr; +BIO_METHOD* bio_meth = nullptr; +Common::FS::IOFile key_log_file; // only open if SSLKEYLOGFILE set in environment + +Result CheckOpenSSLErrors(); +void OneTimeInit(); +void OneTimeInitLogFile(); +bool OneTimeInitBIO(); + +#ifdef YUZU_BUNDLED_OPENSSL +// This is ported from httplib +struct scope_exit { + explicit scope_exit(std::function &&f) + : exit_function(std::move(f)), execute_on_destruction{true} {} + + scope_exit(scope_exit &&rhs) noexcept + : exit_function(std::move(rhs.exit_function)), + execute_on_destruction{rhs.execute_on_destruction} { + rhs.release(); + } + + ~scope_exit() { + if (execute_on_destruction) { this->exit_function(); } + } + + void release() { this->execute_on_destruction = false; } + +private: + scope_exit(const scope_exit &) = delete; + void operator=(const scope_exit &) = delete; + scope_exit &operator=(scope_exit &&) = delete; + + std::function exit_function; + bool execute_on_destruction; +}; + +inline X509_STORE *CreateCaCertStore(const char *ca_cert, + std::size_t size) { + auto mem = BIO_new_mem_buf(ca_cert, static_cast(size)); + auto se = scope_exit([&] { BIO_free_all(mem); }); + if (!mem) { return nullptr; } + + auto inf = PEM_X509_INFO_read_bio(mem, nullptr, nullptr, nullptr); + if (!inf) { return nullptr; } + + auto cts = X509_STORE_new(); + if (cts) { + for (auto i = 0; i < static_cast(sk_X509_INFO_num(inf)); i++) { + auto itmp = sk_X509_INFO_value(inf, i); + if (!itmp) { continue; } + + if (itmp->x509) { X509_STORE_add_cert(cts, itmp->x509); } + if (itmp->crl) { X509_STORE_add_crl(cts, itmp->crl); } + } + } + + sk_X509_INFO_pop_free(inf, X509_INFO_free); + return cts; +} + +inline void SetCaCertStore(SSL_CTX *ctx, X509_STORE *ca_cert_store) { + if (ca_cert_store) { + if (ctx) { + if (SSL_CTX_get_cert_store(ctx) != ca_cert_store) { + // Free memory allocated for old cert and use new store `ca_cert_store` + SSL_CTX_set_cert_store(ctx, ca_cert_store); + } + } else { + X509_STORE_free(ca_cert_store); + } + } +} + +inline void LoadCaCertStore(SSL_CTX* ctx, const char* ca_cert, std::size_t size) +{ + SetCaCertStore(ctx, CreateCaCertStore(ca_cert, size)); +} +#endif + +} // namespace + +class SSLConnectionBackend final { +public: + Result Init() { + // on bundled OpenSSL, load ca cert store +#ifdef YUZU_BUNDLED_OPENSSL + LoadCaCertStore(ssl_ctx, kCert, sizeof(kCert)); +#endif + std::call_once(one_time_init_flag, OneTimeInit); + + if (!one_time_init_success) { + LOG_ERROR(Service_SSL, "Can't create SSL connection because OpenSSL one-time initialization failed"); + return ResultInternalError; + } + + ssl = SSL_new(ssl_ctx); + if (!ssl) { + LOG_ERROR(Service_SSL, "SSL_new failed"); + return CheckOpenSSLErrors(); + } + SSL_set_connect_state(ssl); + bio = BIO_new(bio_meth); + if (!bio) { + LOG_ERROR(Service_SSL, "BIO_new failed"); + return CheckOpenSSLErrors(); + } + BIO_set_data(bio, this); + BIO_set_init(bio, 1); + SSL_set_bio(ssl, bio, bio); + return ResultSuccess; + } + + Result SetHostName(const std::string& hostname) { + if (!skip_cert_verification) { + if (!SSL_set1_host(ssl, hostname.c_str())) { + LOG_ERROR(Service_SSL, "SSL_set1_host({}) failed", hostname); + return CheckOpenSSLErrors(); + } + } + if (!SSL_set_tlsext_host_name(ssl, hostname.c_str())) { // hostname for SNI + LOG_ERROR(Service_SSL, "SSL_set_tlsext_host_name({}) failed", hostname); + return CheckOpenSSLErrors(); + } + return ResultSuccess; + } + + void SetVerifyOption(u32 option) { + skip_cert_verification = (option == 0); + LOG_WARNING(Service_SSL, "option={} skip_verification={}", option, + skip_cert_verification); + if (skip_cert_verification) { + SSL_set_verify(ssl, SSL_VERIFY_NONE, nullptr); + SSL_set1_host(ssl, nullptr); + SSL_set_hostflags(ssl, 0); + } else { + SSL_set_verify(ssl, SSL_VERIFY_PEER, nullptr); + } + } + + Result DoHandshake() { + SSL_set_verify_result(ssl, X509_V_OK); + const int ret = SSL_do_handshake(ssl); + + if (!skip_cert_verification) { + const long verify_result = SSL_get_verify_result(ssl); + if (verify_result != X509_V_OK) { + LOG_ERROR(Service_SSL, "SSL cert verification failed because: {}", + X509_verify_cert_error_string(verify_result)); + return CheckOpenSSLErrors(); + } + } + + if (ret <= 0) { + const int ssl_err = SSL_get_error(ssl, ret); + if (ssl_err == SSL_ERROR_ZERO_RETURN || + (ssl_err == SSL_ERROR_SYSCALL && got_read_eof)) { + LOG_ERROR(Service_SSL, "SSL handshake failed because server hung up"); + return ResultInternalError; + } + } + return HandleReturn("SSL_do_handshake", 0, ret); + } + + Result HandleReturn(const char* what, size_t* actual, int ret) { + const int ssl_err = SSL_get_error(ssl, ret); + CheckOpenSSLErrors(); + switch (ssl_err) { + case SSL_ERROR_NONE: + return ResultSuccess; + case SSL_ERROR_ZERO_RETURN: + LOG_DEBUG(Service_SSL, "{} => SSL_ERROR_ZERO_RETURN", what); + // DoHandshake special-cases this, but for Read and Write: + *actual = 0; + return ResultSuccess; + case SSL_ERROR_WANT_READ: + LOG_DEBUG(Service_SSL, "{} => SSL_ERROR_WANT_READ", what); + return ResultWouldBlock; + case SSL_ERROR_WANT_WRITE: + LOG_DEBUG(Service_SSL, "{} => SSL_ERROR_WANT_WRITE", what); + return ResultWouldBlock; + default: + if (ssl_err == SSL_ERROR_SYSCALL && got_read_eof) { + LOG_DEBUG(Service_SSL, "{} => SSL_ERROR_SYSCALL because server hung up", what); + *actual = 0; + return ResultSuccess; + } + LOG_ERROR(Service_SSL, "{} => other SSL_get_error return value {}", what, ssl_err); + return ResultInternalError; + } + } + + ~SSLConnectionBackend() { + // this is null-tolerant: + SSL_free(ssl); + } + + static void KeyLogCallback(const ::SSL* ssl, const char* line) { + std::string str(line); + str.push_back('\n'); + // Do this in a single WriteString for atomicity if multiple instances + // are running on different threads (though that can't currently + // happen). + if (key_log_file.WriteString(str) != str.size() || !key_log_file.Flush()) { + LOG_CRITICAL(Service_SSL, "Failed to write to SSLKEYLOGFILE"); + } + LOG_DEBUG(Service_SSL, "Wrote to SSLKEYLOGFILE: {}", line); + } + + static int WriteCallback(BIO* bio, const char* buf, size_t len, size_t* actual_p) { + auto self = static_cast(BIO_get_data(bio)); + ASSERT_OR_EXECUTE_MSG( + self->socket, { return 0; }, "OpenSSL asked to send but we have no socket"); + BIO_clear_retry_flags(bio); + auto [actual, err] = self->socket->Send({reinterpret_cast(buf), len}, 0); + switch (err) { + case Network::Errno::SUCCESS: + *actual_p = actual; + return 1; + case Network::Errno::AGAIN: + BIO_set_flags(bio, BIO_FLAGS_WRITE | BIO_FLAGS_SHOULD_RETRY); + return 0; + default: + LOG_ERROR(Service_SSL, "Socket send returned Network::Errno {}", err); + return -1; + } + } + + static int ReadCallback(BIO* bio, char* buf, size_t len, size_t* actual_p) { + auto self = static_cast(BIO_get_data(bio)); + ASSERT_OR_EXECUTE_MSG( + self->socket, { return 0; }, "OpenSSL asked to recv but we have no socket"); + BIO_clear_retry_flags(bio); + auto [actual, err] = self->socket->Recv(0, {reinterpret_cast(buf), len}); + switch (err) { + case Network::Errno::SUCCESS: + *actual_p = actual; + if (actual == 0) { + self->got_read_eof = true; + } + return actual ? 1 : 0; + case Network::Errno::AGAIN: + BIO_set_flags(bio, BIO_FLAGS_READ | BIO_FLAGS_SHOULD_RETRY); + return 0; + default: + LOG_ERROR(Service_SSL, "Socket recv returned Network::Errno {}", err); + return -1; + } + } + + static long CtrlCallback(BIO* bio, int cmd, long l_arg, void* p_arg) { + switch (cmd) { + case BIO_CTRL_FLUSH: + // Nothing to flush. + return 1; + case BIO_CTRL_PUSH: + case BIO_CTRL_POP: +#ifdef BIO_CTRL_GET_KTLS_SEND + case BIO_CTRL_GET_KTLS_SEND: + case BIO_CTRL_GET_KTLS_RECV: +#endif + // We don't support these operations, but don't bother logging them + // as they're nothing unusual. + return 0; + default: + LOG_DEBUG(Service_SSL, "OpenSSL BIO got ctrl({}, {}, {})", cmd, l_arg, p_arg); + return 0; + } + } + + ::SSL* ssl = nullptr; + BIO* bio = nullptr; + bool got_read_eof = false; + bool skip_cert_verification = false; + std::shared_ptr socket; +}; + +Result CreateSSLConnectionBackend(std::unique_ptr* out_backend) { + auto conn = std::make_unique(); + R_TRY(conn->Init()); + *out_backend = std::move(conn); + return ResultSuccess; +} + +namespace { + +Result CheckOpenSSLErrors() { + unsigned long rc; + const char* file; + int line; + const char* func; + const char* data; + int flags; +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + while ((rc = ERR_get_error_all(&file, &line, &func, &data, &flags))) +#else + // Can't get function names from OpenSSL on this version, so use mine: + func = __func__; + while ((rc = ERR_get_error_line_data(&file, &line, &data, &flags))) +#endif + { + std::string msg; + msg.resize(1024, '\0'); + ERR_error_string_n(rc, msg.data(), msg.size()); + msg.resize(strlen(msg.data()), '\0'); + if (flags & ERR_TXT_STRING) { + msg.append(" | "); + msg.append(data); + } + Common::Log::FmtLogMessage(Common::Log::Class::Service_SSL, Common::Log::Level::Error, + file, line, func, "OpenSSL: {}", + msg); + } + return ResultInternalError; +} + +void OneTimeInit() { + ssl_ctx = SSL_CTX_new(TLS_client_method()); + if (!ssl_ctx) { + LOG_ERROR(Service_SSL, "SSL_CTX_new failed"); + CheckOpenSSLErrors(); + return; + } + + SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, nullptr); + + if (!SSL_CTX_set_default_verify_paths(ssl_ctx)) { + LOG_ERROR(Service_SSL, "SSL_CTX_set_default_verify_paths failed"); + CheckOpenSSLErrors(); + return; + } + + OneTimeInitLogFile(); + + if (!OneTimeInitBIO()) { + return; + } + + one_time_init_success = true; +} + +void OneTimeInitLogFile() { + const char* logfile = getenv("SSLKEYLOGFILE"); + if (logfile) { + key_log_file.Open(logfile, Common::FS::FileAccessMode::Append, Common::FS::FileType::TextFile, Common::FS::FileShareFlag::ShareWriteOnly); + if (key_log_file.IsOpen()) { + SSL_CTX_set_keylog_callback(ssl_ctx, &SSLConnectionBackend::KeyLogCallback); + } else { + LOG_CRITICAL(Service_SSL, "SSLKEYLOGFILE was set but file could not be opened; not logging keys!"); + } + } +} + +bool OneTimeInitBIO() { + bio_meth = + BIO_meth_new(BIO_get_new_index() | BIO_TYPE_SOURCE_SINK, "SSLConnectionBackend"); + if (!bio_meth || + !BIO_meth_set_write_ex(bio_meth, &SSLConnectionBackend::WriteCallback) || + !BIO_meth_set_read_ex(bio_meth, &SSLConnectionBackend::ReadCallback) || + !BIO_meth_set_ctrl(bio_meth, &SSLConnectionBackend::CtrlCallback)) { + LOG_ERROR(Service_SSL, "Failed to create BIO_METHOD"); + return false; + } + return true; +} + +} // namespace + // This is nn::ssl::sf::CertificateFormat enum class CertificateFormat : u32 { Pem = 1, @@ -162,20 +545,17 @@ private: auto const res_v = bsd->DuplicateSocketImpl(fd); if (auto *res = std::get_if(&res_v)) { - const s32 duplicated_fd = *res; - if (do_not_close_socket) { - *out_fd = duplicated_fd; - } else { - *out_fd = -1; - fd_to_close = duplicated_fd; - } - std::optional> sock = bsd->GetSocket(duplicated_fd); + const s32 dup_fd = *res; + *out_fd = do_not_close_socket ? dup_fd : -1; + if (!do_not_close_socket) + fd_to_close = dup_fd; + auto const sock = bsd->GetSocket(dup_fd); if (!sock.has_value()) { - LOG_ERROR(Service_SSL, "invalid socket fd {} after duplication", duplicated_fd); + LOG_ERROR(Service_SSL, "invalid socket fd {} after duplication", dup_fd); return ResultInvalidSocket; } socket = std::move(*sock); - backend->SetSocket(socket); + backend->socket = std::move(socket); return ResultSuccess; } LOG_ERROR(Service_SSL, "Failed to duplicate socket with fd {}", fd); @@ -189,11 +569,11 @@ private: } Result SetVerifyOptionImpl(u32 option) { - ASSERT(!did_handshake); LOG_DEBUG(Service_SSL, "called. option={} (forcing 0)", option); + ASSERT(!did_handshake); verify_option = 0; backend->SetVerifyOption(0); - return ResultSuccess; + R_SUCCEED(); } Result SetIoModeImpl(u32 input_mode) { @@ -206,13 +586,13 @@ private: if (error != Network::Errno::SUCCESS) { LOG_ERROR(Service_SSL, "Failed to set native socket non-block flag to {}", non_block); } - return ResultSuccess; + R_SUCCEED(); } Result SetSessionCacheModeImpl(u32 mode) { ASSERT(!did_handshake); LOG_WARNING(Service_SSL, "(STUBBED) called. value={}", mode); - return ResultSuccess; + R_SUCCEED(); } Result DoHandshakeImpl() { @@ -234,19 +614,17 @@ private: }; if (!get_server_cert_chain) { // Just return the first one, unencoded. - ASSERT_OR_EXECUTE_MSG( - !certs.empty(), { return {}; }, "Should be at least one server cert"); + ASSERT_OR_EXECUTE_MSG(!certs.empty(), { return {}; }, "Should be at least one server cert"); return certs[0]; } std::vector ret; - Header header{0x4E4D684374726543, static_cast(certs.size()), 0}; + Header header{0x4E4D684374726543, u32(certs.size()), 0}; ret.insert(ret.end(), reinterpret_cast(&header), reinterpret_cast(&header + 1)); size_t data_offset = sizeof(Header) + certs.size() * sizeof(EntryHeader); for (auto& cert : certs) { - EntryHeader entry_header{static_cast(cert.size()), static_cast(data_offset)}; + EntryHeader entry_header{u32(cert.size()), u32(data_offset)}; data_offset += cert.size(); - ret.insert(ret.end(), reinterpret_cast(&entry_header), - reinterpret_cast(&entry_header + 1)); + ret.insert(ret.end(), reinterpret_cast(&entry_header), reinterpret_cast(&entry_header + 1)); } for (auto& cert : certs) { ret.insert(ret.end(), cert.begin(), cert.end()); @@ -257,7 +635,8 @@ private: Result ReadImpl(std::vector* out_data) { ASSERT_OR_EXECUTE(did_handshake, { return ResultInternalError; }); size_t actual_size{}; - Result res = backend->Read(&actual_size, *out_data); + const int ret = SSL_read_ex(backend->ssl, out_data->data(), out_data->size(), &actual_size); + Result res = backend->HandleReturn("SSL_read_ex", &actual_size, ret); if (res != ResultSuccess) { return res; } @@ -267,12 +646,13 @@ private: Result WriteImpl(size_t* out_size, std::span data) { ASSERT_OR_EXECUTE(did_handshake, { return ResultInternalError; }); - return backend->Write(out_size, data); + const int ret = SSL_write_ex(backend->ssl, data.data(), data.size(), out_size); + return backend->HandleReturn("SSL_write_ex", out_size, ret); } Result PendingImpl(s32* out_pending) { LOG_WARNING(Service_SSL, "(STUBBED) called."); - *out_pending = 0; + *out_pending = SSL_pending(backend->ssl); return ResultSuccess; } @@ -326,24 +706,39 @@ private: OutputParameters out{}; if (res == ResultSuccess) { std::vector> certs; - res = backend->GetServerCerts(&certs); - if (res == ResultSuccess) { + STACK_OF(X509)* chain = SSL_get_peer_cert_chain(backend->ssl); + if (chain) { + int count = sk_X509_num(chain); + ASSERT(count >= 0); + for (int i = 0; i < count; i++) { + X509* x509 = sk_X509_value(chain, i); + ASSERT_OR_EXECUTE(x509 != nullptr, { continue; }); + unsigned char* buf = nullptr; + int len = i2d_X509(x509, &buf); + ASSERT_OR_EXECUTE(len >= 0 && buf, { continue; }); + certs.emplace_back(buf, buf + len); + OPENSSL_free(buf); + } + + // succeed! const std::vector certs_buf = SerializeServerCerts(certs); if (ctx.CanWriteBuffer()) { const size_t buffer_size = ctx.GetWriteBufferSize(); if (certs_buf.size() <= buffer_size) { ctx.WriteBuffer(certs_buf); } else { - LOG_WARNING(Service_SSL, "Certificate buffer too small: {} bytes needed, {} bytes available", - certs_buf.size(), buffer_size); + LOG_WARNING(Service_SSL, "Certificate buffer too small: {} bytes needed, {} bytes available", certs_buf.size(), buffer_size); ctx.WriteBuffer(std::span(certs_buf.data(), buffer_size)); } } else { LOG_DEBUG(Service_SSL, "No output buffer provided for certificates ({} bytes)", certs_buf.size()); } - out.certs_count = static_cast(certs.size()); - out.certs_size = static_cast(certs_buf.size()); + out.certs_count = u32(certs.size()); + out.certs_size = u32(certs_buf.size()); + } else { + LOG_ERROR(Service_SSL, "SSL_get_peer_cert_chain returned nullptr"); + res = ResultInternalError; } } IPC::ResponseBuilder rb{ctx, 4}; diff --git a/src/core/hle/service/ssl/ssl_backend.h b/src/core/hle/service/ssl/ssl_backend.h index a3127342a2..3bbfce7bd8 100644 --- a/src/core/hle/service/ssl/ssl_backend.h +++ b/src/core/hle/service/ssl/ssl_backend.h @@ -32,18 +32,4 @@ constexpr Result ResultInternalError{ErrorModule::SSLSrv, 999}; // made up // polling for read (with a timeout). constexpr Result ResultWouldBlock{ErrorModule::SSLSrv, 204}; -class SSLConnectionBackend { -public: - virtual ~SSLConnectionBackend() {} - virtual void SetSocket(std::shared_ptr socket) = 0; - virtual Result SetHostName(const std::string& hostname) = 0; - virtual void SetVerifyOption(u32 option) = 0; - virtual Result DoHandshake() = 0; - virtual Result Read(size_t* out_size, std::span data) = 0; - virtual Result Write(size_t* out_size, std::span data) = 0; - virtual Result GetServerCerts(std::vector>* out_certs) = 0; -}; - -Result CreateSSLConnectionBackend(std::unique_ptr* out_backend); - } // namespace Service::SSL diff --git a/src/core/hle/service/ssl/ssl_backend_none.cpp b/src/core/hle/service/ssl/ssl_backend_none.cpp deleted file mode 100644 index 3a8474e093..0000000000 --- a/src/core/hle/service/ssl/ssl_backend_none.cpp +++ /dev/null @@ -1,19 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project -// SPDX-License-Identifier: GPL-3.0-or-later - -// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include "common/logging.h" - -#include "core/hle/service/ssl/ssl_backend.h" - -namespace Service::SSL { - -Result CreateSSLConnectionBackend(std::unique_ptr* out_backend) { - LOG_ERROR(Service_SSL, - "Can't create SSL connection because no SSL backend is available on this platform"); - return ResultInternalError; -} - -} // namespace Service::SSL diff --git a/src/core/hle/service/ssl/ssl_backend_openssl.cpp b/src/core/hle/service/ssl/ssl_backend_openssl.cpp deleted file mode 100644 index f0e05266ba..0000000000 --- a/src/core/hle/service/ssl/ssl_backend_openssl.cpp +++ /dev/null @@ -1,450 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project -// SPDX-License-Identifier: GPL-3.0-or-later - -// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include - -#include -#include -#include -#include - -#include "common/fs/file.h" -#include "common/hex_util.h" -#include "common/string_util.h" - -#include "core/hle/service/ssl/ssl_backend.h" -#include "core/internal_network/network.h" -#include "core/internal_network/sockets.h" - -#ifdef YUZU_BUNDLED_OPENSSL -#include -#endif - -using namespace Common::FS; - -namespace Service::SSL { - -// Import OpenSSL's `SSL` type into the namespace. This is needed because the -// namespace is also named `SSL`. -using ::SSL; - -namespace { - -std::once_flag one_time_init_flag; -bool one_time_init_success = false; - -SSL_CTX* ssl_ctx; -IOFile key_log_file; // only open if SSLKEYLOGFILE set in environment -BIO_METHOD* bio_meth; - -Result CheckOpenSSLErrors(); -void OneTimeInit(); -void OneTimeInitLogFile(); -bool OneTimeInitBIO(); - -#ifdef YUZU_BUNDLED_OPENSSL -// This is ported from httplib -struct scope_exit { - explicit scope_exit(std::function &&f) - : exit_function(std::move(f)), execute_on_destruction{true} {} - - scope_exit(scope_exit &&rhs) noexcept - : exit_function(std::move(rhs.exit_function)), - execute_on_destruction{rhs.execute_on_destruction} { - rhs.release(); - } - - ~scope_exit() { - if (execute_on_destruction) { this->exit_function(); } - } - - void release() { this->execute_on_destruction = false; } - -private: - scope_exit(const scope_exit &) = delete; - void operator=(const scope_exit &) = delete; - scope_exit &operator=(scope_exit &&) = delete; - - std::function exit_function; - bool execute_on_destruction; -}; - -inline X509_STORE *CreateCaCertStore(const char *ca_cert, - std::size_t size) { - auto mem = BIO_new_mem_buf(ca_cert, static_cast(size)); - auto se = scope_exit([&] { BIO_free_all(mem); }); - if (!mem) { return nullptr; } - - auto inf = PEM_X509_INFO_read_bio(mem, nullptr, nullptr, nullptr); - if (!inf) { return nullptr; } - - auto cts = X509_STORE_new(); - if (cts) { - for (auto i = 0; i < static_cast(sk_X509_INFO_num(inf)); i++) { - auto itmp = sk_X509_INFO_value(inf, i); - if (!itmp) { continue; } - - if (itmp->x509) { X509_STORE_add_cert(cts, itmp->x509); } - if (itmp->crl) { X509_STORE_add_crl(cts, itmp->crl); } - } - } - - sk_X509_INFO_pop_free(inf, X509_INFO_free); - return cts; -} - -inline void SetCaCertStore(SSL_CTX *ctx, X509_STORE *ca_cert_store) { - if (ca_cert_store) { - if (ctx) { - if (SSL_CTX_get_cert_store(ctx) != ca_cert_store) { - // Free memory allocated for old cert and use new store `ca_cert_store` - SSL_CTX_set_cert_store(ctx, ca_cert_store); - } - } else { - X509_STORE_free(ca_cert_store); - } - } -} - -inline void LoadCaCertStore(SSL_CTX* ctx, const char* ca_cert, std::size_t size) -{ - SetCaCertStore(ctx, CreateCaCertStore(ca_cert, size)); -} -#endif - -} // namespace - -class SSLConnectionBackendOpenSSL final : public SSLConnectionBackend { -public: - Result Init() { - // on bundled OpenSSL, load ca cert store -#ifdef YUZU_BUNDLED_OPENSSL - LoadCaCertStore(ssl_ctx, kCert, sizeof(kCert)); -#endif - std::call_once(one_time_init_flag, OneTimeInit); - - if (!one_time_init_success) { - LOG_ERROR(Service_SSL, - "Can't create SSL connection because OpenSSL one-time initialization failed"); - return ResultInternalError; - } - - ssl = SSL_new(ssl_ctx); - if (!ssl) { - LOG_ERROR(Service_SSL, "SSL_new failed"); - return CheckOpenSSLErrors(); - } - - SSL_set_connect_state(ssl); - - bio = BIO_new(bio_meth); - if (!bio) { - LOG_ERROR(Service_SSL, "BIO_new failed"); - return CheckOpenSSLErrors(); - } - - BIO_set_data(bio, this); - BIO_set_init(bio, 1); - SSL_set_bio(ssl, bio, bio); - - return ResultSuccess; - } - - void SetSocket(std::shared_ptr socket_in) override { - socket = std::move(socket_in); - } - - Result SetHostName(const std::string& hostname) override { - if (!skip_cert_verification) { - if (!SSL_set1_host(ssl, hostname.c_str())) { - LOG_ERROR(Service_SSL, "SSL_set1_host({}) failed", hostname); - return CheckOpenSSLErrors(); - } - } - if (!SSL_set_tlsext_host_name(ssl, hostname.c_str())) { // hostname for SNI - LOG_ERROR(Service_SSL, "SSL_set_tlsext_host_name({}) failed", hostname); - return CheckOpenSSLErrors(); - } - return ResultSuccess; - } - - void SetVerifyOption(u32 option) override { - skip_cert_verification = (option == 0); - LOG_WARNING(Service_SSL, "option={} skip_verification={}", option, - skip_cert_verification); - if (skip_cert_verification) { - SSL_set_verify(ssl, SSL_VERIFY_NONE, nullptr); - SSL_set1_host(ssl, nullptr); - SSL_set_hostflags(ssl, 0); - } else { - SSL_set_verify(ssl, SSL_VERIFY_PEER, nullptr); - } - } - - Result DoHandshake() override { - SSL_set_verify_result(ssl, X509_V_OK); - const int ret = SSL_do_handshake(ssl); - - if (!skip_cert_verification) { - const long verify_result = SSL_get_verify_result(ssl); - if (verify_result != X509_V_OK) { - LOG_ERROR(Service_SSL, "SSL cert verification failed because: {}", - X509_verify_cert_error_string(verify_result)); - return CheckOpenSSLErrors(); - } - } - - if (ret <= 0) { - const int ssl_err = SSL_get_error(ssl, ret); - if (ssl_err == SSL_ERROR_ZERO_RETURN || - (ssl_err == SSL_ERROR_SYSCALL && got_read_eof)) { - LOG_ERROR(Service_SSL, "SSL handshake failed because server hung up"); - return ResultInternalError; - } - } - return HandleReturn("SSL_do_handshake", 0, ret); - } - - Result Read(size_t* out_size, std::span data) override { - const int ret = SSL_read_ex(ssl, data.data(), data.size(), out_size); - return HandleReturn("SSL_read_ex", out_size, ret); - } - - Result Write(size_t* out_size, std::span data) override { - const int ret = SSL_write_ex(ssl, data.data(), data.size(), out_size); - return HandleReturn("SSL_write_ex", out_size, ret); - } - - Result HandleReturn(const char* what, size_t* actual, int ret) { - const int ssl_err = SSL_get_error(ssl, ret); - CheckOpenSSLErrors(); - switch (ssl_err) { - case SSL_ERROR_NONE: - return ResultSuccess; - case SSL_ERROR_ZERO_RETURN: - LOG_DEBUG(Service_SSL, "{} => SSL_ERROR_ZERO_RETURN", what); - // DoHandshake special-cases this, but for Read and Write: - *actual = 0; - return ResultSuccess; - case SSL_ERROR_WANT_READ: - LOG_DEBUG(Service_SSL, "{} => SSL_ERROR_WANT_READ", what); - return ResultWouldBlock; - case SSL_ERROR_WANT_WRITE: - LOG_DEBUG(Service_SSL, "{} => SSL_ERROR_WANT_WRITE", what); - return ResultWouldBlock; - default: - if (ssl_err == SSL_ERROR_SYSCALL && got_read_eof) { - LOG_DEBUG(Service_SSL, "{} => SSL_ERROR_SYSCALL because server hung up", what); - *actual = 0; - return ResultSuccess; - } - LOG_ERROR(Service_SSL, "{} => other SSL_get_error return value {}", what, ssl_err); - return ResultInternalError; - } - } - - Result GetServerCerts(std::vector>* out_certs) override { - STACK_OF(X509)* chain = SSL_get_peer_cert_chain(ssl); - if (!chain) { - LOG_ERROR(Service_SSL, "SSL_get_peer_cert_chain returned nullptr"); - return ResultInternalError; - } - int count = sk_X509_num(chain); - ASSERT(count >= 0); - for (int i = 0; i < count; i++) { - X509* x509 = sk_X509_value(chain, i); - ASSERT_OR_EXECUTE(x509 != nullptr, { continue; }); - unsigned char* buf = nullptr; - int len = i2d_X509(x509, &buf); - ASSERT_OR_EXECUTE(len >= 0 && buf, { continue; }); - out_certs->emplace_back(buf, buf + len); - OPENSSL_free(buf); - } - return ResultSuccess; - } - - ~SSLConnectionBackendOpenSSL() { - // this is null-tolerant: - SSL_free(ssl); - } - - static void KeyLogCallback(const SSL* ssl, const char* line) { - std::string str(line); - str.push_back('\n'); - // Do this in a single WriteString for atomicity if multiple instances - // are running on different threads (though that can't currently - // happen). - if (key_log_file.WriteString(str) != str.size() || !key_log_file.Flush()) { - LOG_CRITICAL(Service_SSL, "Failed to write to SSLKEYLOGFILE"); - } - LOG_DEBUG(Service_SSL, "Wrote to SSLKEYLOGFILE: {}", line); - } - - static int WriteCallback(BIO* bio, const char* buf, size_t len, size_t* actual_p) { - auto self = static_cast(BIO_get_data(bio)); - ASSERT_OR_EXECUTE_MSG( - self->socket, { return 0; }, "OpenSSL asked to send but we have no socket"); - BIO_clear_retry_flags(bio); - auto [actual, err] = self->socket->Send({reinterpret_cast(buf), len}, 0); - switch (err) { - case Network::Errno::SUCCESS: - *actual_p = actual; - return 1; - case Network::Errno::AGAIN: - BIO_set_flags(bio, BIO_FLAGS_WRITE | BIO_FLAGS_SHOULD_RETRY); - return 0; - default: - LOG_ERROR(Service_SSL, "Socket send returned Network::Errno {}", err); - return -1; - } - } - - static int ReadCallback(BIO* bio, char* buf, size_t len, size_t* actual_p) { - auto self = static_cast(BIO_get_data(bio)); - ASSERT_OR_EXECUTE_MSG( - self->socket, { return 0; }, "OpenSSL asked to recv but we have no socket"); - BIO_clear_retry_flags(bio); - auto [actual, err] = self->socket->Recv(0, {reinterpret_cast(buf), len}); - switch (err) { - case Network::Errno::SUCCESS: - *actual_p = actual; - if (actual == 0) { - self->got_read_eof = true; - } - return actual ? 1 : 0; - case Network::Errno::AGAIN: - BIO_set_flags(bio, BIO_FLAGS_READ | BIO_FLAGS_SHOULD_RETRY); - return 0; - default: - LOG_ERROR(Service_SSL, "Socket recv returned Network::Errno {}", err); - return -1; - } - } - - static long CtrlCallback(BIO* bio, int cmd, long l_arg, void* p_arg) { - switch (cmd) { - case BIO_CTRL_FLUSH: - // Nothing to flush. - return 1; - case BIO_CTRL_PUSH: - case BIO_CTRL_POP: -#ifdef BIO_CTRL_GET_KTLS_SEND - case BIO_CTRL_GET_KTLS_SEND: - case BIO_CTRL_GET_KTLS_RECV: -#endif - // We don't support these operations, but don't bother logging them - // as they're nothing unusual. - return 0; - default: - LOG_DEBUG(Service_SSL, "OpenSSL BIO got ctrl({}, {}, {})", cmd, l_arg, p_arg); - return 0; - } - } - - SSL* ssl = nullptr; - BIO* bio = nullptr; - bool got_read_eof = false; - bool skip_cert_verification = false; - - std::shared_ptr socket; -}; - -Result CreateSSLConnectionBackend(std::unique_ptr* out_backend) { - auto conn = std::make_unique(); - - R_TRY(conn->Init()); - - *out_backend = std::move(conn); - return ResultSuccess; -} - -namespace { - -Result CheckOpenSSLErrors() { - unsigned long rc; - const char* file; - int line; - const char* func; - const char* data; - int flags; -#if OPENSSL_VERSION_NUMBER >= 0x30000000L - while ((rc = ERR_get_error_all(&file, &line, &func, &data, &flags))) -#else - // Can't get function names from OpenSSL on this version, so use mine: - func = __func__; - while ((rc = ERR_get_error_line_data(&file, &line, &data, &flags))) -#endif - { - std::string msg; - msg.resize(1024, '\0'); - ERR_error_string_n(rc, msg.data(), msg.size()); - msg.resize(strlen(msg.data()), '\0'); - if (flags & ERR_TXT_STRING) { - msg.append(" | "); - msg.append(data); - } - Common::Log::FmtLogMessage(Common::Log::Class::Service_SSL, Common::Log::Level::Error, - file, line, func, "OpenSSL: {}", - msg); - } - return ResultInternalError; -} - -void OneTimeInit() { - ssl_ctx = SSL_CTX_new(TLS_client_method()); - if (!ssl_ctx) { - LOG_ERROR(Service_SSL, "SSL_CTX_new failed"); - CheckOpenSSLErrors(); - return; - } - - SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, nullptr); - - if (!SSL_CTX_set_default_verify_paths(ssl_ctx)) { - LOG_ERROR(Service_SSL, "SSL_CTX_set_default_verify_paths failed"); - CheckOpenSSLErrors(); - return; - } - - OneTimeInitLogFile(); - - if (!OneTimeInitBIO()) { - return; - } - - one_time_init_success = true; -} - -void OneTimeInitLogFile() { - const char* logfile = getenv("SSLKEYLOGFILE"); - if (logfile) { - key_log_file.Open(logfile, FileAccessMode::Append, FileType::TextFile, - FileShareFlag::ShareWriteOnly); - if (key_log_file.IsOpen()) { - SSL_CTX_set_keylog_callback(ssl_ctx, &SSLConnectionBackendOpenSSL::KeyLogCallback); - } else { - LOG_CRITICAL(Service_SSL, - "SSLKEYLOGFILE was set but file could not be opened; not logging keys!"); - } - } -} - -bool OneTimeInitBIO() { - bio_meth = - BIO_meth_new(BIO_get_new_index() | BIO_TYPE_SOURCE_SINK, "SSLConnectionBackendOpenSSL"); - if (!bio_meth || - !BIO_meth_set_write_ex(bio_meth, &SSLConnectionBackendOpenSSL::WriteCallback) || - !BIO_meth_set_read_ex(bio_meth, &SSLConnectionBackendOpenSSL::ReadCallback) || - !BIO_meth_set_ctrl(bio_meth, &SSLConnectionBackendOpenSSL::CtrlCallback)) { - LOG_ERROR(Service_SSL, "Failed to create BIO_METHOD"); - return false; - } - return true; -} - -} // namespace - -} // namespace Service::SSL diff --git a/src/core/hle/service/ssl/ssl_backend_schannel.cpp b/src/core/hle/service/ssl/ssl_backend_schannel.cpp deleted file mode 100644 index 2b68f29eaf..0000000000 --- a/src/core/hle/service/ssl/ssl_backend_schannel.cpp +++ /dev/null @@ -1,563 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project -// SPDX-License-Identifier: GPL-3.0-or-later - -// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include - -#include "common/error.h" -#include "common/fs/file.h" -#include "common/hex_util.h" -#include "common/string_util.h" - -#include "core/hle/service/ssl/ssl_backend.h" -#include "core/internal_network/network.h" -#include "core/internal_network/sockets.h" - -namespace { - -// These includes are inside the namespace to avoid a conflict on MinGW where -// the headers define an enum containing Network and Service as enumerators -// (which clash with the correspondingly named namespaces). -#define SECURITY_WIN32 -#include -#include -#include - -std::once_flag one_time_init_flag; -bool one_time_init_success = false; - -SCHANNEL_CRED schannel_cred{}; -CredHandle cred_handle; - -static void OneTimeInit() { - schannel_cred.dwVersion = SCHANNEL_CRED_VERSION; - schannel_cred.dwFlags = - SCH_USE_STRONG_CRYPTO | // don't allow insecure protocols - SCH_CRED_NO_SERVERNAME_CHECK | // don't validate server names - SCH_CRED_NO_DEFAULT_CREDS; // don't automatically present a client certificate - // ^ I'm assuming that nobody would want to connect Yuzu to a - // service that requires some OS-provided corporate client - // certificate, and presenting one to some arbitrary server - // might be a privacy concern? Who knows, though. - - const SECURITY_STATUS ret = - AcquireCredentialsHandle(nullptr, const_cast(UNISP_NAME), SECPKG_CRED_OUTBOUND, - nullptr, &schannel_cred, nullptr, nullptr, &cred_handle, nullptr); - if (ret != SEC_E_OK) { - // SECURITY_STATUS codes are a type of HRESULT and can be used with NativeErrorToString. - LOG_ERROR(Service_SSL, "AcquireCredentialsHandle failed: {}", - Common::NativeErrorToString(ret)); - return; - } - - if (getenv("SSLKEYLOGFILE")) { - LOG_CRITICAL(Service_SSL, "SSLKEYLOGFILE was set but Schannel does not support exporting " - "keys; not logging keys!"); - // Not fatal. - } - - one_time_init_success = true; -} - -} // namespace - -namespace Service::SSL { - -class SSLConnectionBackendSchannel final : public SSLConnectionBackend { -public: - Result Init() { - std::call_once(one_time_init_flag, OneTimeInit); - - if (!one_time_init_success) { - LOG_ERROR( - Service_SSL, - "Can't create SSL connection because Schannel one-time initialization failed"); - return ResultInternalError; - } - - return ResultSuccess; - } - - void SetSocket(std::shared_ptr socket_in) override { - socket = std::move(socket_in); - } - - Result SetHostName(const std::string& hostname_in) override { - hostname = hostname_in; - return ResultSuccess; - } - - void SetVerifyOption(u32 option) override { - skip_cert_verification = (option == 0); - LOG_WARNING(Service_SSL, "option={} skip_verification={}", option, - skip_cert_verification); - } - - Result DoHandshake() override { - while (1) { - Result r; - switch (handshake_state) { - case HandshakeState::Initial: - if ((r = FlushCiphertextWriteBuf()) != ResultSuccess || - (r = CallInitializeSecurityContext()) != ResultSuccess) { - return r; - } - // CallInitializeSecurityContext updated `handshake_state`. - continue; - case HandshakeState::ContinueNeeded: - case HandshakeState::IncompleteMessage: - if ((r = FlushCiphertextWriteBuf()) != ResultSuccess || - (r = FillCiphertextReadBuf()) != ResultSuccess) { - return r; - } - if (ciphertext_read_buf.empty()) { - LOG_ERROR(Service_SSL, "SSL handshake failed because server hung up"); - return ResultInternalError; - } - if ((r = CallInitializeSecurityContext()) != ResultSuccess) { - return r; - } - // CallInitializeSecurityContext updated `handshake_state`. - continue; - case HandshakeState::DoneAfterFlush: - if ((r = FlushCiphertextWriteBuf()) != ResultSuccess) { - return r; - } - handshake_state = HandshakeState::Connected; - return ResultSuccess; - case HandshakeState::Connected: - LOG_ERROR(Service_SSL, "Called DoHandshake but we already handshook"); - return ResultInternalError; - case HandshakeState::Error: - return ResultInternalError; - } - } - } - - Result FillCiphertextReadBuf() { - const size_t fill_size = read_buf_fill_size ? read_buf_fill_size : 4096; - read_buf_fill_size = 0; - // This unnecessarily zeroes the buffer; oh well. - const size_t offset = ciphertext_read_buf.size(); - ASSERT_OR_EXECUTE(offset + fill_size >= offset, { return ResultInternalError; }); - ciphertext_read_buf.resize(offset + fill_size, 0); - const auto read_span = std::span(ciphertext_read_buf).subspan(offset, fill_size); - const auto [actual, err] = socket->Recv(0, read_span); - switch (err) { - case Network::Errno::SUCCESS: - ASSERT(static_cast(actual) <= fill_size); - ciphertext_read_buf.resize(offset + actual); - return ResultSuccess; - case Network::Errno::AGAIN: - ciphertext_read_buf.resize(offset); - return ResultWouldBlock; - default: - ciphertext_read_buf.resize(offset); - LOG_ERROR(Service_SSL, "Socket recv returned Network::Errno {}", err); - return ResultInternalError; - } - } - - // Returns success if the write buffer has been completely emptied. - Result FlushCiphertextWriteBuf() { - while (!ciphertext_write_buf.empty()) { - const auto [actual, err] = socket->Send(ciphertext_write_buf, 0); - switch (err) { - case Network::Errno::SUCCESS: - ASSERT(static_cast(actual) <= ciphertext_write_buf.size()); - ciphertext_write_buf.erase(ciphertext_write_buf.begin(), - ciphertext_write_buf.begin() + actual); - break; - case Network::Errno::AGAIN: - return ResultWouldBlock; - default: - LOG_ERROR(Service_SSL, "Socket send returned Network::Errno {}", err); - return ResultInternalError; - } - } - return ResultSuccess; - } - - Result CallInitializeSecurityContext() { - unsigned long req = ISC_REQ_ALLOCATE_MEMORY | ISC_REQ_CONFIDENTIALITY | - ISC_REQ_INTEGRITY | ISC_REQ_REPLAY_DETECT | - ISC_REQ_SEQUENCE_DETECT | ISC_REQ_STREAM | - ISC_REQ_USE_SUPPLIED_CREDS; - - if (skip_cert_verification) { - req |= ISC_REQ_MANUAL_CRED_VALIDATION; - } - - unsigned long attr; - // https://learn.microsoft.com/en-us/windows/win32/secauthn/initializesecuritycontext--schannel - std::array input_buffers{{ - // only used if `initial_call_done` - { - // [0] - .cbBuffer = static_cast(ciphertext_read_buf.size()), - .BufferType = SECBUFFER_TOKEN, - .pvBuffer = ciphertext_read_buf.data(), - }, - { - // [1] (will be replaced by SECBUFFER_MISSING when SEC_E_INCOMPLETE_MESSAGE is - // returned, or SECBUFFER_EXTRA when SEC_E_CONTINUE_NEEDED is returned if the - // whole buffer wasn't used) - .cbBuffer = 0, - .BufferType = SECBUFFER_EMPTY, - .pvBuffer = nullptr, - }, - }}; - std::array output_buffers{{ - { - .cbBuffer = 0, - .BufferType = SECBUFFER_TOKEN, - .pvBuffer = nullptr, - }, // [0] - { - .cbBuffer = 0, - .BufferType = SECBUFFER_ALERT, - .pvBuffer = nullptr, - }, // [1] - }}; - SecBufferDesc input_desc{ - .ulVersion = SECBUFFER_VERSION, - .cBuffers = static_cast(input_buffers.size()), - .pBuffers = input_buffers.data(), - }; - SecBufferDesc output_desc{ - .ulVersion = SECBUFFER_VERSION, - .cBuffers = static_cast(output_buffers.size()), - .pBuffers = output_buffers.data(), - }; - ASSERT_OR_EXECUTE_MSG( - input_buffers[0].cbBuffer == ciphertext_read_buf.size(), - { return ResultInternalError; }, "read buffer too large"); - - bool initial_call_done = handshake_state != HandshakeState::Initial; - if (initial_call_done) { - LOG_DEBUG(Service_SSL, "Passing {} bytes into InitializeSecurityContext", - ciphertext_read_buf.size()); - } - - char* hostname_ptr = hostname ? const_cast(hostname->c_str()) : nullptr; - const SECURITY_STATUS ret = InitializeSecurityContextA( - &cred_handle, initial_call_done ? &ctxt : nullptr, hostname_ptr, req, - 0, // Reserved1 - 0, // TargetDataRep not used with Schannel - initial_call_done ? &input_desc : nullptr, - 0, // Reserved2 - initial_call_done ? nullptr : &ctxt, &output_desc, &attr, - nullptr); // ptsExpiry - - if (output_buffers[0].pvBuffer) { - const std::span span(static_cast(output_buffers[0].pvBuffer), - output_buffers[0].cbBuffer); - ciphertext_write_buf.insert(ciphertext_write_buf.end(), span.begin(), span.end()); - FreeContextBuffer(output_buffers[0].pvBuffer); - } - - if (output_buffers[1].pvBuffer) { - const std::span span(static_cast(output_buffers[1].pvBuffer), - output_buffers[1].cbBuffer); - // The documentation doesn't explain what format this data is in. - LOG_DEBUG(Service_SSL, "Got a {}-byte alert buffer: {}", span.size(), - Common::HexToString(span)); - } - - switch (ret) { - case SEC_I_CONTINUE_NEEDED: - LOG_DEBUG(Service_SSL, "InitializeSecurityContext => SEC_I_CONTINUE_NEEDED"); - if (input_buffers[1].BufferType == SECBUFFER_EXTRA) { - LOG_DEBUG(Service_SSL, "EXTRA of size {}", input_buffers[1].cbBuffer); - ASSERT(input_buffers[1].cbBuffer <= ciphertext_read_buf.size()); - ciphertext_read_buf.erase(ciphertext_read_buf.begin(), - ciphertext_read_buf.end() - input_buffers[1].cbBuffer); - } else { - ASSERT(input_buffers[1].BufferType == SECBUFFER_EMPTY); - ciphertext_read_buf.clear(); - } - handshake_state = HandshakeState::ContinueNeeded; - return ResultSuccess; - case SEC_E_INCOMPLETE_MESSAGE: - LOG_DEBUG(Service_SSL, "InitializeSecurityContext => SEC_E_INCOMPLETE_MESSAGE"); - ASSERT(input_buffers[1].BufferType == SECBUFFER_MISSING); - read_buf_fill_size = input_buffers[1].cbBuffer; - handshake_state = HandshakeState::IncompleteMessage; - return ResultSuccess; - case SEC_E_OK: - LOG_DEBUG(Service_SSL, "InitializeSecurityContext => SEC_E_OK"); - ciphertext_read_buf.clear(); - handshake_state = HandshakeState::DoneAfterFlush; - return GrabStreamSizes(); - default: - LOG_ERROR(Service_SSL, - "InitializeSecurityContext failed (probably certificate/protocol issue): {}", - Common::NativeErrorToString(ret)); - handshake_state = HandshakeState::Error; - return ResultInternalError; - } - } - - Result GrabStreamSizes() { - const SECURITY_STATUS ret = - QueryContextAttributes(&ctxt, SECPKG_ATTR_STREAM_SIZES, &stream_sizes); - if (ret != SEC_E_OK) { - LOG_ERROR(Service_SSL, "QueryContextAttributes(SECPKG_ATTR_STREAM_SIZES) failed: {}", - Common::NativeErrorToString(ret)); - handshake_state = HandshakeState::Error; - return ResultInternalError; - } - return ResultSuccess; - } - - Result Read(size_t* out_size, std::span data) override { - *out_size = 0; - if (handshake_state != HandshakeState::Connected) { - LOG_ERROR(Service_SSL, "Called Read but we did not successfully handshake"); - return ResultInternalError; - } - if (data.size() == 0 || got_read_eof) { - return ResultSuccess; - } - while (1) { - if (!cleartext_read_buf.empty()) { - *out_size = (std::min)(cleartext_read_buf.size(), data.size()); - std::memcpy(data.data(), cleartext_read_buf.data(), *out_size); - cleartext_read_buf.erase(cleartext_read_buf.begin(), - cleartext_read_buf.begin() + *out_size); - return ResultSuccess; - } - if (!ciphertext_read_buf.empty()) { - SecBuffer empty{ - .cbBuffer = 0, - .BufferType = SECBUFFER_EMPTY, - .pvBuffer = nullptr, - }; - std::array buffers{{ - { - .cbBuffer = static_cast(ciphertext_read_buf.size()), - .BufferType = SECBUFFER_DATA, - .pvBuffer = ciphertext_read_buf.data(), - }, - empty, - empty, - empty, - }}; - ASSERT_OR_EXECUTE_MSG( - buffers[0].cbBuffer == ciphertext_read_buf.size(), - { return ResultInternalError; }, "read buffer too large"); - SecBufferDesc desc{ - .ulVersion = SECBUFFER_VERSION, - .cBuffers = static_cast(buffers.size()), - .pBuffers = buffers.data(), - }; - SECURITY_STATUS ret = - DecryptMessage(&ctxt, &desc, /*MessageSeqNo*/ 0, /*pfQOP*/ nullptr); - switch (ret) { - case SEC_E_OK: - ASSERT_OR_EXECUTE(buffers[0].BufferType == SECBUFFER_STREAM_HEADER, - { return ResultInternalError; }); - ASSERT_OR_EXECUTE(buffers[1].BufferType == SECBUFFER_DATA, - { return ResultInternalError; }); - ASSERT_OR_EXECUTE(buffers[2].BufferType == SECBUFFER_STREAM_TRAILER, - { return ResultInternalError; }); - cleartext_read_buf.assign(static_cast(buffers[1].pvBuffer), - static_cast(buffers[1].pvBuffer) + - buffers[1].cbBuffer); - if (buffers[3].BufferType == SECBUFFER_EXTRA) { - ASSERT(buffers[3].cbBuffer <= ciphertext_read_buf.size()); - ciphertext_read_buf.erase(ciphertext_read_buf.begin(), - ciphertext_read_buf.end() - buffers[3].cbBuffer); - } else { - ASSERT(buffers[3].BufferType == SECBUFFER_EMPTY); - ciphertext_read_buf.clear(); - } - continue; - case SEC_E_INCOMPLETE_MESSAGE: - break; - case SEC_I_CONTEXT_EXPIRED: - // Server hung up by sending close_notify. - got_read_eof = true; - *out_size = 0; - return ResultSuccess; - default: - LOG_ERROR(Service_SSL, "DecryptMessage failed: {}", - Common::NativeErrorToString(ret)); - return ResultInternalError; - } - } - const Result r = FillCiphertextReadBuf(); - if (r != ResultSuccess) { - return r; - } - if (ciphertext_read_buf.empty()) { - got_read_eof = true; - *out_size = 0; - return ResultSuccess; - } - } - } - - Result Write(size_t* out_size, std::span data) override { - *out_size = 0; - - if (handshake_state != HandshakeState::Connected) { - LOG_ERROR(Service_SSL, "Called Write but we did not successfully handshake"); - return ResultInternalError; - } - if (data.size() == 0) { - return ResultSuccess; - } - data = data.subspan(0, std::min(data.size(), stream_sizes.cbMaximumMessage)); - if (!cleartext_write_buf.empty()) { - // Already in the middle of a write. It wouldn't make sense to not - // finish sending the entire buffer since TLS has - // header/MAC/padding/etc. - if (data.size() != cleartext_write_buf.size() || - std::memcmp(data.data(), cleartext_write_buf.data(), data.size())) { - LOG_ERROR(Service_SSL, "Called Write but buffer does not match previous buffer"); - return ResultInternalError; - } - return WriteAlreadyEncryptedData(out_size); - } else { - cleartext_write_buf.assign(data.begin(), data.end()); - } - - std::vector header_buf(stream_sizes.cbHeader, 0); - std::vector tmp_data_buf = cleartext_write_buf; - std::vector trailer_buf(stream_sizes.cbTrailer, 0); - - std::array buffers{{ - { - .cbBuffer = stream_sizes.cbHeader, - .BufferType = SECBUFFER_STREAM_HEADER, - .pvBuffer = header_buf.data(), - }, - { - .cbBuffer = static_cast(tmp_data_buf.size()), - .BufferType = SECBUFFER_DATA, - .pvBuffer = tmp_data_buf.data(), - }, - { - .cbBuffer = stream_sizes.cbTrailer, - .BufferType = SECBUFFER_STREAM_TRAILER, - .pvBuffer = trailer_buf.data(), - }, - }}; - ASSERT_OR_EXECUTE_MSG( - buffers[1].cbBuffer == tmp_data_buf.size(), { return ResultInternalError; }, - "temp buffer too large"); - SecBufferDesc desc{ - .ulVersion = SECBUFFER_VERSION, - .cBuffers = static_cast(buffers.size()), - .pBuffers = buffers.data(), - }; - - const SECURITY_STATUS ret = EncryptMessage(&ctxt, /*fQOP*/ 0, &desc, /*MessageSeqNo*/ 0); - if (ret != SEC_E_OK) { - LOG_ERROR(Service_SSL, "EncryptMessage failed: {}", Common::NativeErrorToString(ret)); - return ResultInternalError; - } - ciphertext_write_buf.insert(ciphertext_write_buf.end(), header_buf.begin(), - header_buf.end()); - ciphertext_write_buf.insert(ciphertext_write_buf.end(), tmp_data_buf.begin(), - tmp_data_buf.end()); - ciphertext_write_buf.insert(ciphertext_write_buf.end(), trailer_buf.begin(), - trailer_buf.end()); - return WriteAlreadyEncryptedData(out_size); - } - - Result WriteAlreadyEncryptedData(size_t* out_size) { - const Result r = FlushCiphertextWriteBuf(); - if (r != ResultSuccess) { - return r; - } - // write buf is empty - *out_size = cleartext_write_buf.size(); - cleartext_write_buf.clear(); - return ResultSuccess; - } - - Result GetServerCerts(std::vector>* out_certs) override { - PCCERT_CONTEXT returned_cert = nullptr; - const SECURITY_STATUS ret = - QueryContextAttributes(&ctxt, SECPKG_ATTR_REMOTE_CERT_CONTEXT, &returned_cert); - if (ret != SEC_E_OK) { - LOG_ERROR(Service_SSL, - "QueryContextAttributes(SECPKG_ATTR_REMOTE_CERT_CONTEXT) failed: {}", - Common::NativeErrorToString(ret)); - return ResultInternalError; - } - PCCERT_CONTEXT some_cert = nullptr; - while ((some_cert = CertEnumCertificatesInStore(returned_cert->hCertStore, some_cert)) != - nullptr) { - out_certs->emplace_back(static_cast(some_cert->pbCertEncoded), - static_cast(some_cert->pbCertEncoded) + - some_cert->cbCertEncoded); - } - std::reverse(out_certs->begin(), - out_certs->end()); // Windows returns certs in reverse order from what we want - CertFreeCertificateContext(returned_cert); - return ResultSuccess; - } - - ~SSLConnectionBackendSchannel() { - if (handshake_state != HandshakeState::Initial) { - DeleteSecurityContext(&ctxt); - } - } - - enum class HandshakeState { - // Haven't called anything yet. - Initial, - // `SEC_I_CONTINUE_NEEDED` was returned by - // `InitializeSecurityContext`; must finish sending data (if any) in - // the write buffer, then read at least one byte before calling - // `InitializeSecurityContext` again. - ContinueNeeded, - // `SEC_E_INCOMPLETE_MESSAGE` was returned by - // `InitializeSecurityContext`; hopefully the write buffer is empty; - // must read at least one byte before calling - // `InitializeSecurityContext` again. - IncompleteMessage, - // `SEC_E_OK` was returned by `InitializeSecurityContext`; must - // finish sending data in the write buffer before having `DoHandshake` - // report success. - DoneAfterFlush, - // We finished the above and are now connected. At this point, writing - // and reading are separate 'state machines' represented by the - // nonemptiness of the ciphertext and cleartext read and write buffers. - Connected, - // Another error was returned and we shouldn't allow initialization - // to continue. - Error, - } handshake_state = HandshakeState::Initial; - - CtxtHandle ctxt; - SecPkgContext_StreamSizes stream_sizes; - - std::shared_ptr socket; - std::optional hostname; - - std::vector ciphertext_read_buf; - std::vector ciphertext_write_buf; - std::vector cleartext_read_buf; - std::vector cleartext_write_buf; - - bool got_read_eof = false; - bool skip_cert_verification = false; - size_t read_buf_fill_size = 0; -}; - -Result CreateSSLConnectionBackend(std::unique_ptr* out_backend) { - auto conn = std::make_unique(); - - R_TRY(conn->Init()); - - *out_backend = std::move(conn); - return ResultSuccess; -} - -} // namespace Service::SSL diff --git a/src/core/hle/service/ssl/ssl_backend_securetransport.cpp b/src/core/hle/service/ssl/ssl_backend_securetransport.cpp deleted file mode 100644 index 65f81be631..0000000000 --- a/src/core/hle/service/ssl/ssl_backend_securetransport.cpp +++ /dev/null @@ -1,236 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project -// SPDX-License-Identifier: GPL-3.0-or-later - -// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include - -// SecureTransport has been deprecated in its entirety in favor of -// Network.framework, but that does not allow layering TLS on top of an -// arbitrary socket. -#if defined(__GNUC__) || defined(__clang__) -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wdeprecated-declarations" -#include -#pragma GCC diagnostic pop -#endif - -#include "core/hle/service/ssl/ssl_backend.h" -#include "core/internal_network/network.h" -#include "core/internal_network/sockets.h" - -namespace { - -template -struct CFReleaser { - T ptr; - - YUZU_NON_COPYABLE(CFReleaser); - constexpr CFReleaser() : ptr(nullptr) {} - constexpr CFReleaser(T ptr) : ptr(ptr) {} - constexpr operator T() { - return ptr; - } - ~CFReleaser() { - if (ptr) { - CFRelease(ptr); - } - } -}; - -std::string CFStringToString(CFStringRef cfstr) { - CFReleaser cfdata( - CFStringCreateExternalRepresentation(nullptr, cfstr, kCFStringEncodingUTF8, 0)); - ASSERT_OR_EXECUTE(cfdata, { return "???"; }); - return std::string(reinterpret_cast(CFDataGetBytePtr(cfdata)), - CFDataGetLength(cfdata)); -} - -std::string OSStatusToString(OSStatus status) { - CFReleaser cfstr(SecCopyErrorMessageString(status, nullptr)); - if (!cfstr) { - return "[unknown error]"; - } - return CFStringToString(cfstr); -} - -} // namespace - -namespace Service::SSL { - -class SSLConnectionBackendSecureTransport final : public SSLConnectionBackend { -public: - Result Init() { - static std::once_flag once_flag; - std::call_once(once_flag, []() { - if (getenv("SSLKEYLOGFILE")) { - LOG_CRITICAL(Service_SSL, "SSLKEYLOGFILE was set but SecureTransport does not " - "support exporting keys; not logging keys!"); - // Not fatal. - } - }); - - context.ptr = SSLCreateContext(nullptr, kSSLClientSide, kSSLStreamType); - if (!context) { - LOG_ERROR(Service_SSL, "SSLCreateContext failed"); - return ResultInternalError; - } - - OSStatus status; - if ((status = SSLSetIOFuncs(context, ReadCallback, WriteCallback)) || - (status = SSLSetConnection(context, this))) { - LOG_ERROR(Service_SSL, "SSLContext initialization failed: {}", - OSStatusToString(status)); - return ResultInternalError; - } - - return ResultSuccess; - } - - void SetSocket(std::shared_ptr in_socket) override { - socket = std::move(in_socket); - } - - Result SetHostName(const std::string& hostname) override { - OSStatus status = SSLSetPeerDomainName(context, hostname.c_str(), hostname.size()); - if (status) { - LOG_ERROR(Service_SSL, "SSLSetPeerDomainName failed: {}", OSStatusToString(status)); - return ResultInternalError; - } - return ResultSuccess; - } - - void SetVerifyOption(u32 option) override { - skip_cert_verification = (option == 0); - LOG_WARNING(Service_SSL, "option={} skip_verification={}", option, - skip_cert_verification); - if (skip_cert_verification) { - SSLSetSessionOption(context, kSSLSessionOptionBreakOnServerAuth, true); - } - } - - Result DoHandshake() override { - OSStatus status = SSLHandshake(context); - - if (skip_cert_verification && status == errSSLServerAuthCompleted) { - LOG_DEBUG(Service_SSL, "Skipping certificate verification as requested"); - status = SSLHandshake(context); - } - - return HandleReturn("SSLHandshake", 0, status); - } - - Result Read(size_t* out_size, std::span data) override { - OSStatus status = SSLRead(context, data.data(), data.size(), out_size); - return HandleReturn("SSLRead", out_size, status); - } - - Result Write(size_t* out_size, std::span data) override { - OSStatus status = SSLWrite(context, data.data(), data.size(), out_size); - return HandleReturn("SSLWrite", out_size, status); - } - - Result HandleReturn(const char* what, size_t* actual, OSStatus status) { - switch (status) { - case 0: - return ResultSuccess; - case errSSLWouldBlock: - return ResultWouldBlock; - default: { - std::string reason; - if (got_read_eof) { - reason = "server hung up"; - } else { - reason = OSStatusToString(status); - } - LOG_ERROR(Service_SSL, "{} failed: {}", what, reason); - return ResultInternalError; - } - } - } - - Result GetServerCerts(std::vector>* out_certs) override { - CFReleaser trust; - OSStatus status = SSLCopyPeerTrust(context, &trust.ptr); - if (status) { - LOG_ERROR(Service_SSL, "SSLCopyPeerTrust failed: {}", OSStatusToString(status)); - return ResultInternalError; - } - for (CFIndex i = 0, count = SecTrustGetCertificateCount(trust); i < count; i++) { - SecCertificateRef cert = SecTrustGetCertificateAtIndex(trust, i); - CFReleaser data(SecCertificateCopyData(cert)); - ASSERT_OR_EXECUTE(data, { return ResultInternalError; }); - const u8* ptr = CFDataGetBytePtr(data); - out_certs->emplace_back(ptr, ptr + CFDataGetLength(data)); - } - return ResultSuccess; - } - - static OSStatus ReadCallback(SSLConnectionRef connection, void* data, size_t* dataLength) { - return ReadOrWriteCallback(connection, data, dataLength, true); - } - - static OSStatus WriteCallback(SSLConnectionRef connection, const void* data, - size_t* dataLength) { - return ReadOrWriteCallback(connection, const_cast(data), dataLength, false); - } - - static OSStatus ReadOrWriteCallback(SSLConnectionRef connection, void* data, size_t* dataLength, - bool is_read) { - auto self = - static_cast(const_cast(connection)); - ASSERT_OR_EXECUTE_MSG( - self->socket, { return 0; }, "SecureTransport asked to {} but we have no socket", - is_read ? "read" : "write"); - - // SecureTransport callbacks (unlike OpenSSL BIO callbacks) are - // expected to read/write the full requested dataLength or return an - // error, so we have to add a loop ourselves. - size_t requested_len = *dataLength; - size_t offset = 0; - while (offset < requested_len) { - std::span cur(reinterpret_cast(data) + offset, requested_len - offset); - auto [actual, err] = is_read ? self->socket->Recv(0, cur) : self->socket->Send(cur, 0); - LOG_CRITICAL(Service_SSL, "op={}, offset={} actual={}/{} err={}", is_read, offset, - actual, cur.size(), static_cast(err)); - switch (err) { - case Network::Errno::SUCCESS: - offset += actual; - if (actual == 0) { - ASSERT(is_read); - self->got_read_eof = true; - return errSecEndOfData; - } - break; - case Network::Errno::AGAIN: - *dataLength = offset; - return errSSLWouldBlock; - default: - LOG_ERROR(Service_SSL, "Socket {} returned Network::Errno {}", - is_read ? "recv" : "send", err); - return errSecIO; - } - } - ASSERT(offset == requested_len); - return 0; - } - -private: - CFReleaser context = nullptr; - bool got_read_eof = false; - bool skip_cert_verification = false; - - std::shared_ptr socket; -}; - -Result CreateSSLConnectionBackend(std::unique_ptr* out_backend) { - auto conn = std::make_unique(); - - R_TRY(conn->Init()); - - *out_backend = std::move(conn); - return ResultSuccess; -} - -} // namespace Service::SSL