From 8e703e08dfcf735a08df2ceff6a05221b7cc981f Mon Sep 17 00:00:00 2001 From: comex Date: Mon, 19 Jun 2023 18:17:43 -0700 Subject: Implement SSL service This implements some missing network APIs including a large chunk of the SSL service, enough for Mario Maker (with an appropriate mod applied) to connect to the fan server [Open Course World](https://opencourse.world/). Connecting to first-party servers is out of scope of this PR and is a minefield I'd rather not step into. ## TLS TLS is implemented with multiple backends depending on the system's 'native' TLS library. Currently there are two backends: Schannel for Windows, and OpenSSL for Linux. (In reality Linux is a bit of a free-for-all where there's no one 'native' library, but OpenSSL is the closest it gets.) On macOS the 'native' library is SecureTransport but that isn't implemented in this PR. (Instead, all non-Windows OSes will use OpenSSL unless disabled with `-DENABLE_OPENSSL=OFF`.) Why have multiple backends instead of just using a single library, especially given that Yuzu already embeds mbedtls for cryptographic algorithms? Well, I tried implementing this on mbedtls first, but the problem is TLS policies - mainly trusted certificate policies, and to a lesser extent trusted algorithms, SSL versions, etc. ...In practice, the chance that someone is going to conduct a man-in-the-middle attack on a third-party game server is pretty low, but I'm a security nerd so I like to do the right security things. My base assumption is that we want to use the host system's TLS policies. An alternative would be to more closely emulate the Switch's TLS implementation (which is based on NSS). But for one thing, I don't feel like reverse engineering it. And I'd argue that for third-party servers such as Open Course World, it's theoretically preferable to use the system's policies rather than the Switch's, for two reasons 1. Someday the Switch will stop being updated, and the trusted cert list, algorithms, etc. will start to go stale, but users will still want to connect to third-party servers, and there's no reason they shouldn't have up-to-date security when doing so. At that point, homebrew users on actual hardware may patch the TLS implementation, but for emulators it's simpler to just use the host's stack. 2. Also, it's good to respect any custom certificate policies the user may have added systemwide. For example, they may have added custom trusted CAs in order to use TLS debugging tools or pass through corporate MitM middleboxes. Or they may have removed some CAs that are normally trusted out of paranoia. Note that this policy wouldn't work as-is for connecting to first-party servers, because some of them serve certificates based on Nintendo's own CA rather than a publicly trusted one. However, this could probably be solved easily by using appropriate APIs to adding Nintendo's CA as an alternate trusted cert for Yuzu's connections. That is not implemented in this PR because, again, first-party servers are out of scope. (If anything I'd rather have an option to _block_ connections to Nintendo servers, but that's not implemented here.) To use the host's TLS policies, there are three theoretical options: a) Import the host's trusted certificate list into a cross-platform TLS library (presumably mbedtls). b) Use the native TLS library to verify certificates but use a cross-platform TLS library for everything else. c) Use the native TLS library for everything. Two problems with option a). First, importing the trusted certificate list at minimum requires a bunch of platform-specific code, which mbedtls does not have built in. Interestingly, OpenSSL recently gained the ability to import the Windows certificate trust store... but that leads to the second problem, which is that a list of trusted certificates is [not expressive enough](https://bugs.archlinux.org/task/41909) to express a modern certificate trust policy. For example, Windows has the concept of [explicitly distrusted certificates](https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2012-r2-and-2012/dn265983(v=ws.11)), and macOS requires Certificate Transparency validation for some certificates with complex rules for when it's required. Option b) (using native library just to verify certs) is probably feasible, but it would miss aspects of TLS policy other than trusted certs (like allowed algorithms), and in any case it might well require writing more code, not less, compared to using the native library for everything. So I ended up at option c), using the native library for everything. What I'd *really* prefer would be to use a third-party library that does option c) for me. Rust has a good library for this, [native-tls](https://docs.rs/native-tls/latest/native_tls/). I did search, but I couldn't find a good option in the C or C++ ecosystem, at least not any that wasn't part of some much larger framework. I was surprised - isn't this a pretty common use case? Well, many applications only need TLS for HTTPS, and they can use libcurl, which has a TLS abstraction layer internally but doesn't expose it. Other applications only support a single TLS library, or use one of the aforementioned larger frameworks, or are platform-specific to begin with, or of course are written in a non-C/C++ language, most of which have some canonical choice for TLS. But there are also many applications that have a set of TLS backends just like this; it's just that nobody has gone ahead and abstracted the pattern into a library, at least not a widespread one. Amusingly, there is one TLS abstraction layer that Yuzu already bundles: the one in ffmpeg. But it is missing some features that would be needed to use it here (like reusing an existing socket rather than managing the socket itself). Though, that does mean that the wiki's build instructions for Linux (and macOS for some reason?) already recommend installing OpenSSL, so no need to update those. ## Other APIs implemented - Sockets: - GetSockOpt(`SO_ERROR`) - SetSockOpt(`SO_NOSIGPIPE`) (stub, I have no idea what this does on Switch) - `DuplicateSocket` (because the SSL sysmodule calls it internally) - More `PollEvents` values - NSD: - `Resolve` and `ResolveEx` (stub, good enough for Open Course World and probably most third-party servers, but not first-party) - SFDNSRES: - `GetHostByNameRequest` and `GetHostByNameRequestWithOptions` - `ResolverSetOptionRequest` (stub) ## Fixes - Parts of the socket code were previously allocating a `sockaddr` object on the stack when calling functions that take a `sockaddr*` (e.g. `accept`). This might seem like the right thing to do to avoid illegal aliasing, but in fact `sockaddr` is not guaranteed to be large enough to hold any particular type of address, only the header. This worked in practice because in practice `sockaddr` is the same size as `sockaddr_in`, but it's not how the API is meant to be used. I changed this to allocate an `sockaddr_in` on the stack and `reinterpret_cast` it. I could try to do something cleverer with `aligned_storage`, but casting is the idiomatic way to use these particular APIs, so it's really the system's responsibility to avoid any aliasing issues. - I rewrote most of the `GetAddrInfoRequest[WithOptions]` implementation. The old implementation invoked the host's getaddrinfo directly from sfdnsres.cpp, and directly passed through the host's socket type, protocol, etc. values rather than looking up the corresponding constants on the Switch. To be fair, these constants don't tend to actually vary across systems, but still... I added a wrapper for `getaddrinfo` in `internal_network/network.cpp` similar to the ones for other socket APIs, and changed the `GetAddrInfoRequest` implementation to use it. While I was at it, I rewrote the serialization to use the same approach I used to implement `GetHostByNameRequest`, because it reduces the number of size calculations. While doing so I removed `AF_INET6` support because the Switch doesn't support IPv6; it might be nice to support IPv6 anyway, but that would have to apply to all of the socket APIs. I also corrected the IPC wrappers for `GetAddrInfoRequest` and `GetAddrInfoRequestWithOptions` based on reverse engineering and hardware testing. Every call to `GetAddrInfoRequestWithOptions` returns *four* different error codes (IPC status, getaddrinfo error code, netdb error code, and errno), and `GetAddrInfoRequest` returns three of those but in a different order, and it doesn't really matter but the existing implementation was a bit off, as I discovered while testing `GetHostByNameRequest`. - The new serialization code is based on two simple helper functions: ```cpp template static void Append(std::vector& vec, T t); void AppendNulTerminated(std::vector& vec, std::string_view str); ``` I was thinking there must be existing functions somewhere that assist with serialization/deserialization of binary data, but all I could find was the helper methods in `IOFile` and `HLERequestContext`, not anything that could be used with a generic byte buffer. If I'm not missing something, then maybe I should move the above functions to a new header in `common`... right now they're just sitting in `sfdnsres.cpp` where they're used. - Not a fix, but `SocketBase::Recv`/`Send` is changed to use `std::span` rather than `std::vector&` to avoid needing to copy the data to/from a vector when those methods are called from the TLS implementation. --- src/core/hle/service/sockets/bsd.cpp | 107 ++++- src/core/hle/service/sockets/bsd.h | 13 +- src/core/hle/service/sockets/nsd.cpp | 58 ++- src/core/hle/service/sockets/nsd.h | 4 + src/core/hle/service/sockets/sfdnsres.cpp | 345 ++++++++------ src/core/hle/service/sockets/sfdnsres.h | 3 + src/core/hle/service/sockets/sockets.h | 33 +- src/core/hle/service/sockets/sockets_translate.cpp | 114 ++++- src/core/hle/service/sockets/sockets_translate.h | 17 +- src/core/hle/service/ssl/ssl.cpp | 349 +++++++++++++- src/core/hle/service/ssl/ssl_backend.h | 44 ++ src/core/hle/service/ssl/ssl_backend_none.cpp | 15 + src/core/hle/service/ssl/ssl_backend_openssl.cpp | 342 +++++++++++++ src/core/hle/service/ssl/ssl_backend_schannel.cpp | 529 +++++++++++++++++++++ 14 files changed, 1770 insertions(+), 203 deletions(-) create mode 100644 src/core/hle/service/ssl/ssl_backend.h create mode 100644 src/core/hle/service/ssl/ssl_backend_none.cpp create mode 100644 src/core/hle/service/ssl/ssl_backend_openssl.cpp create mode 100644 src/core/hle/service/ssl/ssl_backend_schannel.cpp (limited to 'src/core/hle') diff --git a/src/core/hle/service/sockets/bsd.cpp b/src/core/hle/service/sockets/bsd.cpp index bce45d321..6677689dc 100644 --- a/src/core/hle/service/sockets/bsd.cpp +++ b/src/core/hle/service/sockets/bsd.cpp @@ -20,6 +20,9 @@ #include "core/internal_network/sockets.h" #include "network/network.h" +using Common::Expected; +using Common::Unexpected; + namespace Service::Sockets { namespace { @@ -265,16 +268,19 @@ void BSD::GetSockOpt(HLERequestContext& ctx) { const u32 level = rp.Pop(); const auto optname = static_cast(rp.Pop()); - LOG_WARNING(Service, "(STUBBED) called. fd={} level={} optname=0x{:x}", fd, level, optname); - std::vector optval(ctx.GetWriteBufferSize()); + LOG_WARNING(Service, "called. fd={} level={} optname=0x{:x} len=0x{:x}", fd, level, optname, + optval.size()); + + Errno err = GetSockOptImpl(fd, level, optname, optval); + ctx.WriteBuffer(optval); IPC::ResponseBuilder rb{ctx, 5}; rb.Push(ResultSuccess); - rb.Push(-1); - rb.PushEnum(Errno::NOTCONN); + rb.Push(err == Errno::SUCCESS ? 0 : -1); + rb.PushEnum(err); rb.Push(static_cast(optval.size())); } @@ -436,6 +442,18 @@ void BSD::Close(HLERequestContext& ctx) { BuildErrnoResponse(ctx, CloseImpl(fd)); } +void BSD::DuplicateSocket(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const s32 fd = rp.Pop(); + [[maybe_unused]] const u64 unused = rp.Pop(); + + Common::Expected res = DuplicateSocketImpl(fd); + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(ResultSuccess); + rb.Push(res.value_or(0)); // ret + rb.Push(res ? 0 : static_cast(res.error())); // bsd errno +} + void BSD::EventFd(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const u64 initval = rp.Pop(); @@ -477,12 +495,12 @@ std::pair BSD::SocketImpl(Domain domain, Type type, Protocol protoco auto room_member = room_network.GetRoomMember().lock(); if (room_member && room_member->IsConnected()) { - descriptor.socket = std::make_unique(room_network); + descriptor.socket = std::make_shared(room_network); } else { - descriptor.socket = std::make_unique(); + descriptor.socket = std::make_shared(); } - descriptor.socket->Initialize(Translate(domain), Translate(type), Translate(type, protocol)); + descriptor.socket->Initialize(Translate(domain), Translate(type), Translate(protocol)); descriptor.is_connection_based = IsConnectionBased(type); return {fd, Errno::SUCCESS}; @@ -538,7 +556,7 @@ std::pair BSD::PollImpl(std::vector& write_buffer, std::spansocket.get(); - result.events = TranslatePollEventsToHost(pollfd.events); + result.events = Translate(pollfd.events); result.revents = Network::PollEvents{}; return result; }); @@ -547,7 +565,7 @@ std::pair BSD::PollImpl(std::vector& write_buffer, std::span& write_buffer) { } const SockAddrIn guest_addrin = Translate(addr_in); - ASSERT(write_buffer.size() == sizeof(guest_addrin)); + ASSERT(write_buffer.size() >= sizeof(guest_addrin)); + write_buffer.resize(sizeof(guest_addrin)); std::memcpy(write_buffer.data(), &guest_addrin, sizeof(guest_addrin)); return Translate(bsd_errno); } @@ -633,7 +652,8 @@ Errno BSD::GetSockNameImpl(s32 fd, std::vector& write_buffer) { } const SockAddrIn guest_addrin = Translate(addr_in); - ASSERT(write_buffer.size() == sizeof(guest_addrin)); + ASSERT(write_buffer.size() >= sizeof(guest_addrin)); + write_buffer.resize(sizeof(guest_addrin)); std::memcpy(write_buffer.data(), &guest_addrin, sizeof(guest_addrin)); return Translate(bsd_errno); } @@ -671,13 +691,47 @@ std::pair BSD::FcntlImpl(s32 fd, FcntlCmd cmd, s32 arg) { } } -Errno BSD::SetSockOptImpl(s32 fd, u32 level, OptName optname, size_t optlen, const void* optval) { - UNIMPLEMENTED_IF(level != 0xffff); // SOL_SOCKET +Errno BSD::GetSockOptImpl(s32 fd, u32 level, OptName optname, std::vector& optval) { + if (!IsFileDescriptorValid(fd)) { + return Errno::BADF; + } + + if (level != static_cast(SocketLevel::SOCKET)) { + UNIMPLEMENTED_MSG("Unknown getsockopt level"); + return Errno::SUCCESS; + } + + Network::SocketBase* const socket = file_descriptors[fd]->socket.get(); + + switch (optname) { + case OptName::ERROR_: { + auto [pending_err, getsockopt_err] = socket->GetPendingError(); + if (getsockopt_err == Network::Errno::SUCCESS) { + Errno translated_pending_err = Translate(pending_err); + ASSERT_OR_EXECUTE_MSG( + optval.size() == sizeof(Errno), { return Errno::INVAL; }, + "Incorrect getsockopt option size"); + optval.resize(sizeof(Errno)); + memcpy(optval.data(), &translated_pending_err, sizeof(Errno)); + } + return Translate(getsockopt_err); + } + default: + UNIMPLEMENTED_MSG("Unimplemented optname={}", optname); + return Errno::SUCCESS; + } +} +Errno BSD::SetSockOptImpl(s32 fd, u32 level, OptName optname, size_t optlen, const void* optval) { if (!IsFileDescriptorValid(fd)) { return Errno::BADF; } + if (level != static_cast(SocketLevel::SOCKET)) { + UNIMPLEMENTED_MSG("Unknown setsockopt level"); + return Errno::SUCCESS; + } + Network::SocketBase* const socket = file_descriptors[fd]->socket.get(); if (optname == OptName::LINGER) { @@ -711,6 +765,9 @@ Errno BSD::SetSockOptImpl(s32 fd, u32 level, OptName optname, size_t optlen, con return Translate(socket->SetSndTimeo(value)); case OptName::RCVTIMEO: return Translate(socket->SetRcvTimeo(value)); + case OptName::NOSIGPIPE: + LOG_WARNING(Service, "(STUBBED) setting NOSIGPIPE to {}", value); + return Errno::SUCCESS; default: UNIMPLEMENTED_MSG("Unimplemented optname={}", optname); return Errno::SUCCESS; @@ -841,6 +898,28 @@ Errno BSD::CloseImpl(s32 fd) { return bsd_errno; } +Expected BSD::DuplicateSocketImpl(s32 fd) { + if (!IsFileDescriptorValid(fd)) { + return Unexpected(Errno::BADF); + } + + const s32 new_fd = FindFreeFileDescriptorHandle(); + if (new_fd < 0) { + LOG_ERROR(Service, "No more file descriptors available"); + return Unexpected(Errno::MFILE); + } + + file_descriptors[new_fd] = file_descriptors[fd]; + return new_fd; +} + +std::optional> BSD::GetSocket(s32 fd) { + if (!IsFileDescriptorValid(fd)) { + return std::nullopt; + } + return file_descriptors[fd]->socket; +} + s32 BSD::FindFreeFileDescriptorHandle() noexcept { for (s32 fd = 0; fd < static_cast(file_descriptors.size()); ++fd) { if (!file_descriptors[fd]) { @@ -911,7 +990,7 @@ BSD::BSD(Core::System& system_, const char* name) {24, &BSD::Write, "Write"}, {25, &BSD::Read, "Read"}, {26, &BSD::Close, "Close"}, - {27, nullptr, "DuplicateSocket"}, + {27, &BSD::DuplicateSocket, "DuplicateSocket"}, {28, nullptr, "GetResourceStatistics"}, {29, nullptr, "RecvMMsg"}, {30, nullptr, "SendMMsg"}, diff --git a/src/core/hle/service/sockets/bsd.h b/src/core/hle/service/sockets/bsd.h index 30ae9c140..430edb97c 100644 --- a/src/core/hle/service/sockets/bsd.h +++ b/src/core/hle/service/sockets/bsd.h @@ -8,6 +8,7 @@ #include #include "common/common_types.h" +#include "common/expected.h" #include "common/socket_types.h" #include "core/hle/service/service.h" #include "core/hle/service/sockets/sockets.h" @@ -29,12 +30,19 @@ public: explicit BSD(Core::System& system_, const char* name); ~BSD() override; + // These methods are called from SSL; the first two are also called from + // this class for the corresponding IPC methods. + // On the real device, the SSL service makes IPC calls to this service. + Common::Expected DuplicateSocketImpl(s32 fd); + Errno CloseImpl(s32 fd); + std::optional> GetSocket(s32 fd); + private: /// Maximum number of file descriptors static constexpr size_t MAX_FD = 128; struct FileDescriptor { - std::unique_ptr socket; + std::shared_ptr socket; s32 flags = 0; bool is_connection_based = false; }; @@ -138,6 +146,7 @@ private: void Write(HLERequestContext& ctx); void Read(HLERequestContext& ctx); void Close(HLERequestContext& ctx); + void DuplicateSocket(HLERequestContext& ctx); void EventFd(HLERequestContext& ctx); template @@ -153,6 +162,7 @@ private: Errno GetSockNameImpl(s32 fd, std::vector& write_buffer); Errno ListenImpl(s32 fd, s32 backlog); std::pair FcntlImpl(s32 fd, FcntlCmd cmd, s32 arg); + Errno GetSockOptImpl(s32 fd, u32 level, OptName optname, std::vector& optval); Errno SetSockOptImpl(s32 fd, u32 level, OptName optname, size_t optlen, const void* optval); Errno ShutdownImpl(s32 fd, s32 how); std::pair RecvImpl(s32 fd, u32 flags, std::vector& message); @@ -161,7 +171,6 @@ private: std::pair SendImpl(s32 fd, u32 flags, std::span message); std::pair SendToImpl(s32 fd, u32 flags, std::span message, std::span addr); - Errno CloseImpl(s32 fd); s32 FindFreeFileDescriptorHandle() noexcept; bool IsFileDescriptorValid(s32 fd) const noexcept; diff --git a/src/core/hle/service/sockets/nsd.cpp b/src/core/hle/service/sockets/nsd.cpp index 6491a73be..22c3a31a0 100644 --- a/src/core/hle/service/sockets/nsd.cpp +++ b/src/core/hle/service/sockets/nsd.cpp @@ -1,10 +1,15 @@ // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include "core/hle/service/ipc_helpers.h" #include "core/hle/service/sockets/nsd.h" +#include "common/string_util.h" + namespace Service::Sockets { +constexpr Result ResultOverflow{ErrorModule::NSD, 6}; + NSD::NSD(Core::System& system_, const char* name) : ServiceFramework{system_, name} { // clang-format off static const FunctionInfo functions[] = { @@ -15,8 +20,8 @@ NSD::NSD(Core::System& system_, const char* name) : ServiceFramework{system_, na {13, nullptr, "DeleteSettings"}, {14, nullptr, "ImportSettings"}, {15, nullptr, "SetChangeEnvironmentIdentifierDisabled"}, - {20, nullptr, "Resolve"}, - {21, nullptr, "ResolveEx"}, + {20, &NSD::Resolve, "Resolve"}, + {21, &NSD::ResolveEx, "ResolveEx"}, {30, nullptr, "GetNasServiceSetting"}, {31, nullptr, "GetNasServiceSettingEx"}, {40, nullptr, "GetNasRequestFqdn"}, @@ -40,6 +45,55 @@ NSD::NSD(Core::System& system_, const char* name) : ServiceFramework{system_, na RegisterHandlers(functions); } +static ResultVal ResolveImpl(const std::string& fqdn_in) { + // The real implementation makes various substitutions. + // For now we just return the string as-is, which is good enough when not + // connecting to real Nintendo servers. + LOG_WARNING(Service, "(STUBBED) called({})", fqdn_in); + return fqdn_in; +} + +static Result ResolveCommon(const std::string& fqdn_in, std::array& fqdn_out) { + const auto res = ResolveImpl(fqdn_in); + if (res.Failed()) { + return res.Code(); + } + if (res->size() >= fqdn_out.size()) { + return ResultOverflow; + } + std::memcpy(fqdn_out.data(), res->c_str(), res->size() + 1); + return ResultSuccess; +} + +void NSD::Resolve(HLERequestContext& ctx) { + const std::string fqdn_in = Common::StringFromBuffer(ctx.ReadBuffer(0)); + + std::array fqdn_out{}; + Result res = ResolveCommon(fqdn_in, fqdn_out); + + ctx.WriteBuffer(fqdn_out); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(res); +} + +void NSD::ResolveEx(HLERequestContext& ctx) { + const std::string fqdn_in = Common::StringFromBuffer(ctx.ReadBuffer(0)); + + std::array fqdn_out; + Result res = ResolveCommon(fqdn_in, fqdn_out); + + if (res.IsError()) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(res); + return; + } + + ctx.WriteBuffer(fqdn_out); + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(ResultSuccess); + rb.Push(ResultSuccess); +} + NSD::~NSD() = default; } // namespace Service::Sockets diff --git a/src/core/hle/service/sockets/nsd.h b/src/core/hle/service/sockets/nsd.h index 5cc12b855..a7379a8a9 100644 --- a/src/core/hle/service/sockets/nsd.h +++ b/src/core/hle/service/sockets/nsd.h @@ -15,6 +15,10 @@ class NSD final : public ServiceFramework { public: explicit NSD(Core::System& system_, const char* name); ~NSD() override; + +private: + void Resolve(HLERequestContext& ctx); + void ResolveEx(HLERequestContext& ctx); }; } // namespace Service::Sockets diff --git a/src/core/hle/service/sockets/sfdnsres.cpp b/src/core/hle/service/sockets/sfdnsres.cpp index 132dd5797..1196fb86c 100644 --- a/src/core/hle/service/sockets/sfdnsres.cpp +++ b/src/core/hle/service/sockets/sfdnsres.cpp @@ -10,27 +10,18 @@ #include "core/core.h" #include "core/hle/service/ipc_helpers.h" #include "core/hle/service/sockets/sfdnsres.h" +#include "core/hle/service/sockets/sockets.h" +#include "core/hle/service/sockets/sockets_translate.h" +#include "core/internal_network/network.h" #include "core/memory.h" -#ifdef _WIN32 -#include -#elif YUZU_UNIX -#include -#include -#include -#include -#ifndef EAI_NODATA -#define EAI_NODATA EAI_NONAME -#endif -#endif - namespace Service::Sockets { SFDNSRES::SFDNSRES(Core::System& system_) : ServiceFramework{system_, "sfdnsres"} { static const FunctionInfo functions[] = { {0, nullptr, "SetDnsAddressesPrivateRequest"}, {1, nullptr, "GetDnsAddressPrivateRequest"}, - {2, nullptr, "GetHostByNameRequest"}, + {2, &SFDNSRES::GetHostByNameRequest, "GetHostByNameRequest"}, {3, nullptr, "GetHostByAddrRequest"}, {4, nullptr, "GetHostStringErrorRequest"}, {5, nullptr, "GetGaiStringErrorRequest"}, @@ -38,11 +29,11 @@ SFDNSRES::SFDNSRES(Core::System& system_) : ServiceFramework{system_, "sfdnsres" {7, nullptr, "GetNameInfoRequest"}, {8, nullptr, "RequestCancelHandleRequest"}, {9, nullptr, "CancelRequest"}, - {10, nullptr, "GetHostByNameRequestWithOptions"}, + {10, &SFDNSRES::GetHostByNameRequestWithOptions, "GetHostByNameRequestWithOptions"}, {11, nullptr, "GetHostByAddrRequestWithOptions"}, {12, &SFDNSRES::GetAddrInfoRequestWithOptions, "GetAddrInfoRequestWithOptions"}, {13, nullptr, "GetNameInfoRequestWithOptions"}, - {14, nullptr, "ResolverSetOptionRequest"}, + {14, &SFDNSRES::ResolverSetOptionRequest, "ResolverSetOptionRequest"}, {15, nullptr, "ResolverGetOptionRequest"}, }; RegisterHandlers(functions); @@ -59,188 +50,246 @@ enum class NetDbError : s32 { NoData = 4, }; -static NetDbError AddrInfoErrorToNetDbError(s32 result) { - // Best effort guess to map errors +static NetDbError GetAddrInfoErrorToNetDbError(GetAddrInfoError result) { + // These combinations have been verified on console (but are not + // exhaustive). switch (result) { - case 0: + case GetAddrInfoError::SUCCESS: return NetDbError::Success; - case EAI_AGAIN: + case GetAddrInfoError::AGAIN: return NetDbError::TryAgain; - case EAI_NODATA: - return NetDbError::NoData; + case GetAddrInfoError::NODATA: + return NetDbError::HostNotFound; + case GetAddrInfoError::SERVICE: + return NetDbError::Success; default: return NetDbError::HostNotFound; } } -static std::vector SerializeAddrInfo(const addrinfo* addrinfo, s32 result_code, +static Errno GetAddrInfoErrorToErrno(GetAddrInfoError result) { + // These combinations have been verified on console (but are not + // exhaustive). + switch (result) { + case GetAddrInfoError::SUCCESS: + // Note: Sometimes a successful lookup sets errno to EADDRNOTAVAIL for + // some reason, but that doesn't seem useful to implement. + return Errno::SUCCESS; + case GetAddrInfoError::AGAIN: + return Errno::SUCCESS; + case GetAddrInfoError::NODATA: + return Errno::SUCCESS; + case GetAddrInfoError::SERVICE: + return Errno::INVAL; + default: + return Errno::SUCCESS; + } +} + +template +static void Append(std::vector& vec, T t) { + size_t off = vec.size(); + vec.resize(off + sizeof(T)); + std::memcpy(vec.data() + off, &t, sizeof(T)); +} + +static void AppendNulTerminated(std::vector& vec, std::string_view str) { + size_t off = vec.size(); + vec.resize(off + str.size() + 1); + std::memcpy(vec.data() + off, str.data(), str.size()); +} + +// We implement gethostbyname using the host's getaddrinfo rather than the +// host's gethostbyname, because it simplifies portability: e.g., getaddrinfo +// behaves the same on Unix and Windows, unlike gethostbyname where Windows +// doesn't implement h_errno. +static std::vector SerializeAddrInfoAsHostEnt(const std::vector& vec, + std::string_view host) { + + std::vector data; + // h_name: use the input hostname (append nul-terminated) + AppendNulTerminated(data, host); + // h_aliases: leave empty + + Append(data, 0); // count of h_aliases + // (If the count were nonzero, the aliases would be appended as nul-terminated here.) + Append(data, static_cast(Domain::INET)); // h_addrtype + Append(data, sizeof(Network::IPv4Address)); // h_length + // h_addr_list: + size_t count = vec.size(); + ASSERT(count <= UINT32_MAX); + Append(data, static_cast(count)); + for (const Network::AddrInfo& addrinfo : vec) { + // On the Switch, this is passed through htonl despite already being + // big-endian, so it ends up as little-endian. + Append(data, Network::IPv4AddressToInteger(addrinfo.addr.ip)); + + LOG_INFO(Service, "Resolved host '{}' to IPv4 address {}", host, + Network::IPv4AddressToString(addrinfo.addr.ip)); + } + return data; +} + +static std::pair GetHostByNameRequestImpl(HLERequestContext& ctx) { + struct Parameters { + u8 use_nsd_resolve; + u32 cancel_handle; + u64 process_id; + }; + + IPC::RequestParser rp{ctx}; + const auto parameters = rp.PopRaw(); + + LOG_WARNING( + Service, + "called with ignored parameters: use_nsd_resolve={}, cancel_handle={}, process_id={}", + parameters.use_nsd_resolve, parameters.cancel_handle, parameters.process_id); + + const auto host_buffer = ctx.ReadBuffer(0); + const std::string host = Common::StringFromBuffer(host_buffer); + // For now, ignore options, which are in input buffer 1 for GetHostByNameRequestWithOptions. + + auto res = Network::GetAddrInfo(host, /*service*/ std::nullopt); + if (!res.has_value()) { + return {0, Translate(res.error())}; + } + + std::vector data = SerializeAddrInfoAsHostEnt(res.value(), host); + u32 data_size = static_cast(data.size()); + ctx.WriteBuffer(data, 0); + + return {data_size, GetAddrInfoError::SUCCESS}; +} + +void SFDNSRES::GetHostByNameRequest(HLERequestContext& ctx) { + auto [data_size, emu_gai_err] = GetHostByNameRequestImpl(ctx); + + IPC::ResponseBuilder rb{ctx, 5}; + rb.Push(ResultSuccess); + rb.Push(static_cast(GetAddrInfoErrorToNetDbError(emu_gai_err))); // netdb error code + rb.Push(static_cast(GetAddrInfoErrorToErrno(emu_gai_err))); // errno + rb.Push(data_size); // serialized size +} + +void SFDNSRES::GetHostByNameRequestWithOptions(HLERequestContext& ctx) { + auto [data_size, emu_gai_err] = GetHostByNameRequestImpl(ctx); + + IPC::ResponseBuilder rb{ctx, 5}; + rb.Push(ResultSuccess); + rb.Push(data_size); // serialized size + rb.Push(static_cast(GetAddrInfoErrorToNetDbError(emu_gai_err))); // netdb error code + rb.Push(static_cast(GetAddrInfoErrorToErrno(emu_gai_err))); // errno +} + +static std::vector SerializeAddrInfo(const std::vector& vec, std::string_view host) { // Adapted from // https://github.com/switchbrew/libnx/blob/c5a9a909a91657a9818a3b7e18c9b91ff0cbb6e3/nx/source/runtime/resolver.c#L190 std::vector data; - auto* current = addrinfo; - while (current != nullptr) { - struct SerializedResponseHeader { - u32 magic; - s32 flags; - s32 family; - s32 socket_type; - s32 protocol; - u32 address_length; - }; - static_assert(sizeof(SerializedResponseHeader) == 0x18, - "Response header size must be 0x18 bytes"); - - constexpr auto header_size = sizeof(SerializedResponseHeader); - const auto addr_size = - current->ai_addr && current->ai_addrlen > 0 ? current->ai_addrlen : 4; - const auto canonname_size = current->ai_canonname ? strlen(current->ai_canonname) + 1 : 1; - - const auto last_size = data.size(); - data.resize(last_size + header_size + addr_size + canonname_size); - - // Header in network byte order - SerializedResponseHeader header{}; - - constexpr auto HEADER_MAGIC = 0xBEEFCAFE; - header.magic = htonl(HEADER_MAGIC); - header.family = htonl(current->ai_family); - header.flags = htonl(current->ai_flags); - header.socket_type = htonl(current->ai_socktype); - header.protocol = htonl(current->ai_protocol); - header.address_length = current->ai_addr ? htonl((u32)current->ai_addrlen) : 0; - - auto* header_ptr = data.data() + last_size; - std::memcpy(header_ptr, &header, header_size); - - if (header.address_length == 0) { - std::memset(header_ptr + header_size, 0, 4); - } else { - switch (current->ai_family) { - case AF_INET: { - struct SockAddrIn { - s16 sin_family; - u16 sin_port; - u32 sin_addr; - u8 sin_zero[8]; - }; - - SockAddrIn serialized_addr{}; - const auto addr = *reinterpret_cast(current->ai_addr); - serialized_addr.sin_port = htons(addr.sin_port); - serialized_addr.sin_family = htons(addr.sin_family); - serialized_addr.sin_addr = htonl(addr.sin_addr.s_addr); - std::memcpy(header_ptr + header_size, &serialized_addr, sizeof(SockAddrIn)); - - char addr_string_buf[64]{}; - inet_ntop(AF_INET, &addr.sin_addr, addr_string_buf, std::size(addr_string_buf)); - LOG_INFO(Service, "Resolved host '{}' to IPv4 address {}", host, addr_string_buf); - break; - } - case AF_INET6: { - struct SockAddrIn6 { - s16 sin6_family; - u16 sin6_port; - u32 sin6_flowinfo; - u8 sin6_addr[16]; - u32 sin6_scope_id; - }; - - SockAddrIn6 serialized_addr{}; - const auto addr = *reinterpret_cast(current->ai_addr); - serialized_addr.sin6_family = htons(addr.sin6_family); - serialized_addr.sin6_port = htons(addr.sin6_port); - serialized_addr.sin6_flowinfo = htonl(addr.sin6_flowinfo); - serialized_addr.sin6_scope_id = htonl(addr.sin6_scope_id); - std::memcpy(serialized_addr.sin6_addr, &addr.sin6_addr, - sizeof(SockAddrIn6::sin6_addr)); - std::memcpy(header_ptr + header_size, &serialized_addr, sizeof(SockAddrIn6)); - - char addr_string_buf[64]{}; - inet_ntop(AF_INET6, &addr.sin6_addr, addr_string_buf, std::size(addr_string_buf)); - LOG_INFO(Service, "Resolved host '{}' to IPv6 address {}", host, addr_string_buf); - break; - } - default: - std::memcpy(header_ptr + header_size, current->ai_addr, addr_size); - break; - } - } - if (current->ai_canonname) { - std::memcpy(header_ptr + addr_size, current->ai_canonname, canonname_size); + for (const Network::AddrInfo& addrinfo : vec) { + // serialized addrinfo: + Append(data, 0xBEEFCAFE); // magic + Append(data, 0); // ai_flags + Append(data, static_cast(Translate(addrinfo.family))); // ai_family + Append(data, static_cast(Translate(addrinfo.socket_type))); // ai_socktype + Append(data, static_cast(Translate(addrinfo.protocol))); // ai_protocol + Append(data, sizeof(SockAddrIn)); // ai_addrlen + // ^ *not* sizeof(SerializedSockAddrIn), not that it matters since they're the same size + + // ai_addr: + Append(data, static_cast(Translate(addrinfo.addr.family))); // sin_family + // On the Switch, the following fields are passed through htonl despite + // already being big-endian, so they end up as little-endian. + Append(data, addrinfo.addr.portno); // sin_port + Append(data, Network::IPv4AddressToInteger(addrinfo.addr.ip)); // sin_addr + data.resize(data.size() + 8, 0); // sin_zero + + if (addrinfo.canon_name.has_value()) { + AppendNulTerminated(data, *addrinfo.canon_name); } else { - *(header_ptr + header_size + addr_size) = 0; + data.push_back(0); } - current = current->ai_next; + LOG_INFO(Service, "Resolved host '{}' to IPv4 address {}", host, + Network::IPv4AddressToString(addrinfo.addr.ip)); } - // 4-byte sentinel value - data.push_back(0); - data.push_back(0); - data.push_back(0); - data.push_back(0); + data.resize(data.size() + 4, 0); // 4-byte sentinel value return data; } -static std::pair GetAddrInfoRequestImpl(HLERequestContext& ctx) { +static std::pair GetAddrInfoRequestImpl(HLERequestContext& ctx) { struct Parameters { u8 use_nsd_resolve; - u32 unknown; + u32 cancel_handle; u64 process_id; }; IPC::RequestParser rp{ctx}; const auto parameters = rp.PopRaw(); - LOG_WARNING(Service, - "called with ignored parameters: use_nsd_resolve={}, unknown={}, process_id={}", - parameters.use_nsd_resolve, parameters.unknown, parameters.process_id); + LOG_WARNING( + Service, + "called with ignored parameters: use_nsd_resolve={}, cancel_handle={}, process_id={}", + parameters.use_nsd_resolve, parameters.cancel_handle, parameters.process_id); + + // TODO: If use_nsd_resolve is true, pass the name through NSD::Resolve + // before looking up. const auto host_buffer = ctx.ReadBuffer(0); const std::string host = Common::StringFromBuffer(host_buffer); - const auto service_buffer = ctx.ReadBuffer(1); - const std::string service = Common::StringFromBuffer(service_buffer); - - addrinfo* addrinfo; - // Pass null for hints. Serialized hints are also passed in a buffer, but are ignored for now - s32 result_code = getaddrinfo(host.c_str(), service.c_str(), nullptr, &addrinfo); + std::optional service = std::nullopt; + if (ctx.CanReadBuffer(1)) { + std::span service_buffer = ctx.ReadBuffer(1); + service = Common::StringFromBuffer(service_buffer); + } - u32 data_size = 0; - if (result_code == 0 && addrinfo != nullptr) { - const std::vector& data = SerializeAddrInfo(addrinfo, result_code, host); - data_size = static_cast(data.size()); - freeaddrinfo(addrinfo); + // Serialized hints are also passed in a buffer, but are ignored for now. - ctx.WriteBuffer(data, 0); + auto res = Network::GetAddrInfo(host, service); + if (!res.has_value()) { + return {0, Translate(res.error())}; } - return std::make_pair(data_size, result_code); + std::vector data = SerializeAddrInfo(res.value(), host); + u32 data_size = static_cast(data.size()); + ctx.WriteBuffer(data, 0); + + return {data_size, GetAddrInfoError::SUCCESS}; } void SFDNSRES::GetAddrInfoRequest(HLERequestContext& ctx) { - auto [data_size, result_code] = GetAddrInfoRequestImpl(ctx); + auto [data_size, emu_gai_err] = GetAddrInfoRequestImpl(ctx); - IPC::ResponseBuilder rb{ctx, 4}; + IPC::ResponseBuilder rb{ctx, 5}; rb.Push(ResultSuccess); - rb.Push(static_cast(AddrInfoErrorToNetDbError(result_code))); // NetDBErrorCode - rb.Push(result_code); // errno - rb.Push(data_size); // serialized size + rb.Push(static_cast(GetAddrInfoErrorToErrno(emu_gai_err))); // errno + rb.Push(static_cast(emu_gai_err)); // getaddrinfo error code + rb.Push(data_size); // serialized size } void SFDNSRES::GetAddrInfoRequestWithOptions(HLERequestContext& ctx) { // Additional options are ignored - auto [data_size, result_code] = GetAddrInfoRequestImpl(ctx); + auto [data_size, emu_gai_err] = GetAddrInfoRequestImpl(ctx); + + IPC::ResponseBuilder rb{ctx, 6}; + rb.Push(ResultSuccess); + rb.Push(data_size); // serialized size + rb.Push(static_cast(emu_gai_err)); // getaddrinfo error code + rb.Push(static_cast(GetAddrInfoErrorToNetDbError(emu_gai_err))); // netdb error code + rb.Push(static_cast(GetAddrInfoErrorToErrno(emu_gai_err))); // errno +} + +void SFDNSRES::ResolverSetOptionRequest(HLERequestContext& ctx) { + LOG_WARNING(Service, "(STUBBED) called"); + + IPC::ResponseBuilder rb{ctx, 3}; - IPC::ResponseBuilder rb{ctx, 5}; rb.Push(ResultSuccess); - rb.Push(data_size); // serialized size - rb.Push(result_code); // errno - rb.Push(static_cast(AddrInfoErrorToNetDbError(result_code))); // NetDBErrorCode - rb.Push(0); + rb.Push(0); // bsd errno } } // namespace Service::Sockets diff --git a/src/core/hle/service/sockets/sfdnsres.h b/src/core/hle/service/sockets/sfdnsres.h index 18e3cd60c..d99a9d560 100644 --- a/src/core/hle/service/sockets/sfdnsres.h +++ b/src/core/hle/service/sockets/sfdnsres.h @@ -17,8 +17,11 @@ public: ~SFDNSRES() override; private: + void GetHostByNameRequest(HLERequestContext& ctx); + void GetHostByNameRequestWithOptions(HLERequestContext& ctx); void GetAddrInfoRequest(HLERequestContext& ctx); void GetAddrInfoRequestWithOptions(HLERequestContext& ctx); + void ResolverSetOptionRequest(HLERequestContext& ctx); }; } // namespace Service::Sockets diff --git a/src/core/hle/service/sockets/sockets.h b/src/core/hle/service/sockets/sockets.h index acd2dae7b..77426c46e 100644 --- a/src/core/hle/service/sockets/sockets.h +++ b/src/core/hle/service/sockets/sockets.h @@ -22,13 +22,35 @@ enum class Errno : u32 { CONNRESET = 104, NOTCONN = 107, TIMEDOUT = 110, + INPROGRESS = 115, +}; + +enum class GetAddrInfoError : s32 { + SUCCESS = 0, + ADDRFAMILY = 1, + AGAIN = 2, + BADFLAGS = 3, + FAIL = 4, + FAMILY = 5, + MEMORY = 6, + NODATA = 7, + NONAME = 8, + SERVICE = 9, + SOCKTYPE = 10, + SYSTEM = 11, + BADHINTS = 12, + PROTOCOL = 13, + OVERFLOW_ = 14, // avoid name collision with Windows macro + OTHER = 15, }; enum class Domain : u32 { + Unspecified = 0, INET = 2, }; enum class Type : u32 { + Unspecified = 0, STREAM = 1, DGRAM = 2, RAW = 3, @@ -36,12 +58,16 @@ enum class Type : u32 { }; enum class Protocol : u32 { - UNSPECIFIED = 0, + Unspecified = 0, ICMP = 1, TCP = 6, UDP = 17, }; +enum class SocketLevel : u32 { + SOCKET = 0xffff, // i.e. SOL_SOCKET +}; + enum class OptName : u32 { REUSEADDR = 0x4, KEEPALIVE = 0x8, @@ -51,6 +77,8 @@ enum class OptName : u32 { RCVBUF = 0x1002, SNDTIMEO = 0x1005, RCVTIMEO = 0x1006, + ERROR_ = 0x1007, // avoid name collision with Windows macro + NOSIGPIPE = 0x800, // at least according to libnx }; enum class ShutdownHow : s32 { @@ -80,6 +108,9 @@ enum class PollEvents : u16 { Err = 1 << 3, Hup = 1 << 4, Nval = 1 << 5, + RdNorm = 1 << 6, + RdBand = 1 << 7, + WrBand = 1 << 8, }; DECLARE_ENUM_FLAG_OPERATORS(PollEvents); diff --git a/src/core/hle/service/sockets/sockets_translate.cpp b/src/core/hle/service/sockets/sockets_translate.cpp index 594e58f90..2f9a0e39c 100644 --- a/src/core/hle/service/sockets/sockets_translate.cpp +++ b/src/core/hle/service/sockets/sockets_translate.cpp @@ -29,6 +29,8 @@ Errno Translate(Network::Errno value) { return Errno::TIMEDOUT; case Network::Errno::CONNRESET: return Errno::CONNRESET; + case Network::Errno::INPROGRESS: + return Errno::INPROGRESS; default: UNIMPLEMENTED_MSG("Unimplemented errno={}", value); return Errno::SUCCESS; @@ -39,8 +41,50 @@ std::pair Translate(std::pair value) { return {value.first, Translate(value.second)}; } +GetAddrInfoError Translate(Network::GetAddrInfoError error) { + switch (error) { + case Network::GetAddrInfoError::SUCCESS: + return GetAddrInfoError::SUCCESS; + case Network::GetAddrInfoError::ADDRFAMILY: + return GetAddrInfoError::ADDRFAMILY; + case Network::GetAddrInfoError::AGAIN: + return GetAddrInfoError::AGAIN; + case Network::GetAddrInfoError::BADFLAGS: + return GetAddrInfoError::BADFLAGS; + case Network::GetAddrInfoError::FAIL: + return GetAddrInfoError::FAIL; + case Network::GetAddrInfoError::FAMILY: + return GetAddrInfoError::FAMILY; + case Network::GetAddrInfoError::MEMORY: + return GetAddrInfoError::MEMORY; + case Network::GetAddrInfoError::NODATA: + return GetAddrInfoError::NODATA; + case Network::GetAddrInfoError::NONAME: + return GetAddrInfoError::NONAME; + case Network::GetAddrInfoError::SERVICE: + return GetAddrInfoError::SERVICE; + case Network::GetAddrInfoError::SOCKTYPE: + return GetAddrInfoError::SOCKTYPE; + case Network::GetAddrInfoError::SYSTEM: + return GetAddrInfoError::SYSTEM; + case Network::GetAddrInfoError::BADHINTS: + return GetAddrInfoError::BADHINTS; + case Network::GetAddrInfoError::PROTOCOL: + return GetAddrInfoError::PROTOCOL; + case Network::GetAddrInfoError::OVERFLOW_: + return GetAddrInfoError::OVERFLOW_; + case Network::GetAddrInfoError::OTHER: + return GetAddrInfoError::OTHER; + default: + UNIMPLEMENTED_MSG("Unimplemented GetAddrInfoError={}", error); + return GetAddrInfoError::OTHER; + } +} + Network::Domain Translate(Domain domain) { switch (domain) { + case Domain::Unspecified: + return Network::Domain::Unspecified; case Domain::INET: return Network::Domain::INET; default: @@ -51,6 +95,8 @@ Network::Domain Translate(Domain domain) { Domain Translate(Network::Domain domain) { switch (domain) { + case Network::Domain::Unspecified: + return Domain::Unspecified; case Network::Domain::INET: return Domain::INET; default: @@ -61,39 +107,69 @@ Domain Translate(Network::Domain domain) { Network::Type Translate(Type type) { switch (type) { + case Type::Unspecified: + return Network::Type::Unspecified; case Type::STREAM: return Network::Type::STREAM; case Type::DGRAM: return Network::Type::DGRAM; + case Type::RAW: + return Network::Type::RAW; + case Type::SEQPACKET: + return Network::Type::SEQPACKET; default: UNIMPLEMENTED_MSG("Unimplemented type={}", type); return Network::Type{}; } } -Network::Protocol Translate(Type type, Protocol protocol) { +Type Translate(Network::Type type) { + switch (type) { + case Network::Type::Unspecified: + return Type::Unspecified; + case Network::Type::STREAM: + return Type::STREAM; + case Network::Type::DGRAM: + return Type::DGRAM; + case Network::Type::RAW: + return Type::RAW; + case Network::Type::SEQPACKET: + return Type::SEQPACKET; + default: + UNIMPLEMENTED_MSG("Unimplemented type={}", type); + return Type{}; + } +} + +Network::Protocol Translate(Protocol protocol) { switch (protocol) { - case Protocol::UNSPECIFIED: - LOG_WARNING(Service, "Unspecified protocol, assuming protocol from type"); - switch (type) { - case Type::DGRAM: - return Network::Protocol::UDP; - case Type::STREAM: - return Network::Protocol::TCP; - default: - return Network::Protocol::TCP; - } + case Protocol::Unspecified: + return Network::Protocol::Unspecified; case Protocol::TCP: return Network::Protocol::TCP; case Protocol::UDP: return Network::Protocol::UDP; default: UNIMPLEMENTED_MSG("Unimplemented protocol={}", protocol); - return Network::Protocol::TCP; + return Network::Protocol::Unspecified; + } +} + +Protocol Translate(Network::Protocol protocol) { + switch (protocol) { + case Network::Protocol::Unspecified: + return Protocol::Unspecified; + case Network::Protocol::TCP: + return Protocol::TCP; + case Network::Protocol::UDP: + return Protocol::UDP; + default: + UNIMPLEMENTED_MSG("Unimplemented protocol={}", protocol); + return Protocol::Unspecified; } } -Network::PollEvents TranslatePollEventsToHost(PollEvents flags) { +Network::PollEvents Translate(PollEvents flags) { Network::PollEvents result{}; const auto translate = [&result, &flags](PollEvents from, Network::PollEvents to) { if (True(flags & from)) { @@ -107,12 +183,15 @@ Network::PollEvents TranslatePollEventsToHost(PollEvents flags) { translate(PollEvents::Err, Network::PollEvents::Err); translate(PollEvents::Hup, Network::PollEvents::Hup); translate(PollEvents::Nval, Network::PollEvents::Nval); + translate(PollEvents::RdNorm, Network::PollEvents::RdNorm); + translate(PollEvents::RdBand, Network::PollEvents::RdBand); + translate(PollEvents::WrBand, Network::PollEvents::WrBand); UNIMPLEMENTED_IF_MSG((u16)flags != 0, "Unimplemented flags={}", (u16)flags); return result; } -PollEvents TranslatePollEventsToGuest(Network::PollEvents flags) { +PollEvents Translate(Network::PollEvents flags) { PollEvents result{}; const auto translate = [&result, &flags](Network::PollEvents from, PollEvents to) { if (True(flags & from)) { @@ -127,13 +206,18 @@ PollEvents TranslatePollEventsToGuest(Network::PollEvents flags) { translate(Network::PollEvents::Err, PollEvents::Err); translate(Network::PollEvents::Hup, PollEvents::Hup); translate(Network::PollEvents::Nval, PollEvents::Nval); + translate(Network::PollEvents::RdNorm, PollEvents::RdNorm); + translate(Network::PollEvents::RdBand, PollEvents::RdBand); + translate(Network::PollEvents::WrBand, PollEvents::WrBand); UNIMPLEMENTED_IF_MSG((u16)flags != 0, "Unimplemented flags={}", (u16)flags); return result; } Network::SockAddrIn Translate(SockAddrIn value) { - ASSERT(value.len == 0 || value.len == sizeof(value)); + // Note: 6 is incorrect, but can be passed by homebrew (because libnx sets + // sin_len to 6 when deserializing getaddrinfo results). + ASSERT(value.len == 0 || value.len == sizeof(value) || value.len == 6); return { .family = Translate(static_cast(value.family)), diff --git a/src/core/hle/service/sockets/sockets_translate.h b/src/core/hle/service/sockets/sockets_translate.h index c93291d3e..694868b37 100644 --- a/src/core/hle/service/sockets/sockets_translate.h +++ b/src/core/hle/service/sockets/sockets_translate.h @@ -17,6 +17,9 @@ Errno Translate(Network::Errno value); /// Translate abstract return value errno pair to guest return value errno pair std::pair Translate(std::pair value); +/// Translate abstract getaddrinfo error to guest getaddrinfo error +GetAddrInfoError Translate(Network::GetAddrInfoError value); + /// Translate guest domain to abstract domain Network::Domain Translate(Domain domain); @@ -26,14 +29,20 @@ Domain Translate(Network::Domain domain); /// Translate guest type to abstract type Network::Type Translate(Type type); +/// Translate abstract type to guest type +Type Translate(Network::Type type); + /// Translate guest protocol to abstract protocol -Network::Protocol Translate(Type type, Protocol protocol); +Network::Protocol Translate(Protocol protocol); -/// Translate abstract poll event flags to guest poll event flags -Network::PollEvents TranslatePollEventsToHost(PollEvents flags); +/// Translate abstract protocol to guest protocol +Protocol Translate(Network::Protocol protocol); /// Translate guest poll event flags to abstract poll event flags -PollEvents TranslatePollEventsToGuest(Network::PollEvents flags); +Network::PollEvents Translate(PollEvents flags); + +/// Translate abstract poll event flags to guest poll event flags +PollEvents Translate(Network::PollEvents flags); /// Translate guest socket address structure to abstract socket address structure Network::SockAddrIn Translate(SockAddrIn value); diff --git a/src/core/hle/service/ssl/ssl.cpp b/src/core/hle/service/ssl/ssl.cpp index 2b99dd7ac..a3b54c7f0 100644 --- a/src/core/hle/service/ssl/ssl.cpp +++ b/src/core/hle/service/ssl/ssl.cpp @@ -1,10 +1,18 @@ // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include "common/string_util.h" + +#include "core/core.h" #include "core/hle/service/ipc_helpers.h" #include "core/hle/service/server_manager.h" #include "core/hle/service/service.h" +#include "core/hle/service/sm/sm.h" +#include "core/hle/service/sockets/bsd.h" #include "core/hle/service/ssl/ssl.h" +#include "core/hle/service/ssl/ssl_backend.h" +#include "core/internal_network/network.h" +#include "core/internal_network/sockets.h" namespace Service::SSL { @@ -20,6 +28,18 @@ enum class ContextOption : u32 { CrlImportDateCheckEnable = 1, }; +// This is nn::ssl::Connection::IoMode +enum class IoMode : u32 { + Blocking = 1, + NonBlocking = 2, +}; + +// This is nn::ssl::sf::OptionType +enum class OptionType : u32 { + DoNotCloseSocket = 0, + GetServerCertChain = 1, +}; + // This is nn::ssl::sf::SslVersion struct SslVersion { union { @@ -34,35 +54,42 @@ struct SslVersion { }; }; +struct SslContextSharedData { + u32 connection_count = 0; +}; + class ISslConnection final : public ServiceFramework { public: - explicit ISslConnection(Core::System& system_, SslVersion version) - : ServiceFramework{system_, "ISslConnection"}, ssl_version{version} { + explicit ISslConnection(Core::System& system_, SslVersion version, + std::shared_ptr& shared_data, + std::unique_ptr&& backend) + : ServiceFramework{system_, "ISslConnection"}, ssl_version{version}, + shared_data_{shared_data}, backend_{std::move(backend)} { // clang-format off static const FunctionInfo functions[] = { - {0, nullptr, "SetSocketDescriptor"}, - {1, nullptr, "SetHostName"}, - {2, nullptr, "SetVerifyOption"}, - {3, nullptr, "SetIoMode"}, + {0, &ISslConnection::SetSocketDescriptor, "SetSocketDescriptor"}, + {1, &ISslConnection::SetHostName, "SetHostName"}, + {2, &ISslConnection::SetVerifyOption, "SetVerifyOption"}, + {3, &ISslConnection::SetIoMode, "SetIoMode"}, {4, nullptr, "GetSocketDescriptor"}, {5, nullptr, "GetHostName"}, {6, nullptr, "GetVerifyOption"}, {7, nullptr, "GetIoMode"}, - {8, nullptr, "DoHandshake"}, - {9, nullptr, "DoHandshakeGetServerCert"}, - {10, nullptr, "Read"}, - {11, nullptr, "Write"}, - {12, nullptr, "Pending"}, + {8, &ISslConnection::DoHandshake, "DoHandshake"}, + {9, &ISslConnection::DoHandshakeGetServerCert, "DoHandshakeGetServerCert"}, + {10, &ISslConnection::Read, "Read"}, + {11, &ISslConnection::Write, "Write"}, + {12, &ISslConnection::Pending, "Pending"}, {13, nullptr, "Peek"}, {14, nullptr, "Poll"}, {15, nullptr, "GetVerifyCertError"}, {16, nullptr, "GetNeededServerCertBufferSize"}, - {17, nullptr, "SetSessionCacheMode"}, + {17, &ISslConnection::SetSessionCacheMode, "SetSessionCacheMode"}, {18, nullptr, "GetSessionCacheMode"}, {19, nullptr, "FlushSessionCache"}, {20, nullptr, "SetRenegotiationMode"}, {21, nullptr, "GetRenegotiationMode"}, - {22, nullptr, "SetOption"}, + {22, &ISslConnection::SetOption, "SetOption"}, {23, nullptr, "GetOption"}, {24, nullptr, "GetVerifyCertErrors"}, {25, nullptr, "GetCipherInfo"}, @@ -80,21 +107,295 @@ public: // clang-format on RegisterHandlers(functions); + + shared_data->connection_count++; + } + + ~ISslConnection() { + shared_data_->connection_count--; + if (fd_to_close_.has_value()) { + s32 fd = *fd_to_close_; + if (!do_not_close_socket_) { + LOG_ERROR(Service_SSL, + "do_not_close_socket was changed after setting socket; is this right?"); + } else { + auto bsd = system.ServiceManager().GetService("bsd:u"); + if (bsd) { + auto err = bsd->CloseImpl(fd); + if (err != Service::Sockets::Errno::SUCCESS) { + LOG_ERROR(Service_SSL, "failed to close duplicated socket: {}", err); + } + } + } + } } private: SslVersion ssl_version; + std::shared_ptr shared_data_; + std::unique_ptr backend_; + std::optional fd_to_close_; + bool do_not_close_socket_ = false; + bool get_server_cert_chain_ = false; + std::shared_ptr socket_; + bool did_set_host_name_ = false; + bool did_handshake_ = false; + + ResultVal SetSocketDescriptorImpl(s32 fd) { + LOG_DEBUG(Service_SSL, "called, fd={}", fd); + ASSERT(!did_handshake_); + auto bsd = system.ServiceManager().GetService("bsd:u"); + ASSERT_OR_EXECUTE(bsd, { return ResultInternalError; }); + s32 ret_fd; + // Based on https://switchbrew.org/wiki/SSL_services#SetSocketDescriptor + if (do_not_close_socket_) { + auto res = bsd->DuplicateSocketImpl(fd); + if (!res.has_value()) { + LOG_ERROR(Service_SSL, "failed to duplicate socket"); + return ResultInvalidSocket; + } + fd = *res; + fd_to_close_ = fd; + ret_fd = fd; + } else { + ret_fd = -1; + } + std::optional> sock = bsd->GetSocket(fd); + if (!sock.has_value()) { + LOG_ERROR(Service_SSL, "invalid socket fd {}", fd); + return ResultInvalidSocket; + } + socket_ = std::move(*sock); + backend_->SetSocket(socket_); + return ret_fd; + } + + Result SetHostNameImpl(const std::string& hostname) { + LOG_DEBUG(Service_SSL, "SetHostNameImpl({})", hostname); + ASSERT(!did_handshake_); + Result res = backend_->SetHostName(hostname); + if (res == ResultSuccess) { + did_set_host_name_ = true; + } + return res; + } + + Result SetVerifyOptionImpl(u32 option) { + ASSERT(!did_handshake_); + LOG_WARNING(Service_SSL, "(STUBBED) called. option={}", option); + return ResultSuccess; + } + + Result SetIOModeImpl(u32 _mode) { + auto mode = static_cast(_mode); + ASSERT(mode == IoMode::Blocking || mode == IoMode::NonBlocking); + ASSERT_OR_EXECUTE(socket_, { return ResultNoSocket; }); + + bool non_block = mode == IoMode::NonBlocking; + Network::Errno e = socket_->SetNonBlock(non_block); + if (e != Network::Errno::SUCCESS) { + LOG_ERROR(Service_SSL, "Failed to set native socket non-block flag to {}", non_block); + } + return ResultSuccess; + } + + Result SetSessionCacheModeImpl(u32 mode) { + ASSERT(!did_handshake_); + LOG_WARNING(Service_SSL, "(STUBBED) called. value={}", mode); + return ResultSuccess; + } + + Result DoHandshakeImpl() { + ASSERT_OR_EXECUTE(!did_handshake_ && socket_, { return ResultNoSocket; }); + ASSERT_OR_EXECUTE_MSG( + did_set_host_name_, { return ResultInternalError; }, + "Expected SetHostName before DoHandshake"); + Result res = backend_->DoHandshake(); + did_handshake_ = res.IsSuccess(); + return res; + } + + std::vector SerializeServerCerts(const std::vector>& certs) { + struct Header { + u64 magic; + u32 count; + u32 pad; + }; + struct EntryHeader { + u32 size; + u32 offset; + }; + 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"); + return certs[0]; + } + std::vector ret; + Header header{0x4E4D684374726543, static_cast(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)}; + data_offset += cert.size(); + 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()); + } + return ret; + } + + ResultVal> ReadImpl(size_t size) { + ASSERT_OR_EXECUTE(did_handshake_, { return ResultInternalError; }); + std::vector res(size); + ResultVal actual = backend_->Read(res); + if (actual.Failed()) { + return actual.Code(); + } + res.resize(*actual); + return res; + } + + ResultVal WriteImpl(std::span data) { + ASSERT_OR_EXECUTE(did_handshake_, { return ResultInternalError; }); + return backend_->Write(data); + } + + ResultVal PendingImpl() { + LOG_WARNING(Service_SSL, "(STUBBED) called."); + return 0; + } + + void SetSocketDescriptor(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const s32 fd = rp.Pop(); + const ResultVal res = SetSocketDescriptorImpl(fd); + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(res.Code()); + rb.Push(res.ValueOr(-1)); + } + + void SetHostName(HLERequestContext& ctx) { + const std::string hostname = Common::StringFromBuffer(ctx.ReadBuffer()); + const Result res = SetHostNameImpl(hostname); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(res); + } + + void SetVerifyOption(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const u32 option = rp.Pop(); + const Result res = SetVerifyOptionImpl(option); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(res); + } + + void SetIoMode(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const u32 mode = rp.Pop(); + const Result res = SetIOModeImpl(mode); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(res); + } + + void DoHandshake(HLERequestContext& ctx) { + const Result res = DoHandshakeImpl(); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(res); + } + + void DoHandshakeGetServerCert(HLERequestContext& ctx) { + Result res = DoHandshakeImpl(); + u32 certs_count = 0; + u32 certs_size = 0; + if (res == ResultSuccess) { + auto certs = backend_->GetServerCerts(); + if (certs.Succeeded()) { + std::vector certs_buf = SerializeServerCerts(*certs); + ctx.WriteBuffer(certs_buf); + certs_count = static_cast(certs->size()); + certs_size = static_cast(certs_buf.size()); + } + } + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(res); + rb.Push(certs_size); + rb.Push(certs_count); + } + + void Read(HLERequestContext& ctx) { + const ResultVal> res = ReadImpl(ctx.GetWriteBufferSize()); + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(res.Code()); + if (res.Succeeded()) { + rb.Push(static_cast(res->size())); + ctx.WriteBuffer(*res); + } else { + rb.Push(static_cast(0)); + } + } + + void Write(HLERequestContext& ctx) { + const ResultVal res = WriteImpl(ctx.ReadBuffer()); + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(res.Code()); + rb.Push(static_cast(res.ValueOr(0))); + } + + void Pending(HLERequestContext& ctx) { + const ResultVal res = PendingImpl(); + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(res.Code()); + rb.Push(res.ValueOr(0)); + } + + void SetSessionCacheMode(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const u32 mode = rp.Pop(); + const Result res = SetSessionCacheModeImpl(mode); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(res); + } + + void SetOption(HLERequestContext& ctx) { + struct Parameters { + OptionType option; + s32 value; + }; + static_assert(sizeof(Parameters) == 0x8, "Parameters is an invalid size"); + + IPC::RequestParser rp{ctx}; + const auto parameters = rp.PopRaw(); + + switch (parameters.option) { + case OptionType::DoNotCloseSocket: + do_not_close_socket_ = static_cast(parameters.value); + break; + case OptionType::GetServerCertChain: + get_server_cert_chain_ = static_cast(parameters.value); + break; + default: + LOG_WARNING(Service_SSL, "unrecognized option={}, value={}", parameters.option, + parameters.value); + } + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); + } }; class ISslContext final : public ServiceFramework { public: explicit ISslContext(Core::System& system_, SslVersion version) - : ServiceFramework{system_, "ISslContext"}, ssl_version{version} { + : ServiceFramework{system_, "ISslContext"}, ssl_version{version}, + shared_data_{std::make_shared()} { static const FunctionInfo functions[] = { {0, &ISslContext::SetOption, "SetOption"}, {1, nullptr, "GetOption"}, {2, &ISslContext::CreateConnection, "CreateConnection"}, - {3, nullptr, "GetConnectionCount"}, + {3, &ISslContext::GetConnectionCount, "GetConnectionCount"}, {4, &ISslContext::ImportServerPki, "ImportServerPki"}, {5, &ISslContext::ImportClientPki, "ImportClientPki"}, {6, nullptr, "RemoveServerPki"}, @@ -111,6 +412,7 @@ public: private: SslVersion ssl_version; + std::shared_ptr shared_data_; void SetOption(HLERequestContext& ctx) { struct Parameters { @@ -130,11 +432,24 @@ private: } void CreateConnection(HLERequestContext& ctx) { - LOG_WARNING(Service_SSL, "(STUBBED) called"); + LOG_WARNING(Service_SSL, "called"); + + auto backend_res = CreateSSLConnectionBackend(); IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(backend_res.Code()); + if (backend_res.Succeeded()) { + rb.PushIpcInterface(system, ssl_version, shared_data_, + std::move(*backend_res)); + } + } + + void GetConnectionCount(HLERequestContext& ctx) { + LOG_WARNING(Service_SSL, "connection_count={}", shared_data_->connection_count); + + IPC::ResponseBuilder rb{ctx, 3}; rb.Push(ResultSuccess); - rb.PushIpcInterface(system, ssl_version); + rb.Push(shared_data_->connection_count); } void ImportServerPki(HLERequestContext& ctx) { diff --git a/src/core/hle/service/ssl/ssl_backend.h b/src/core/hle/service/ssl/ssl_backend.h new file mode 100644 index 000000000..624e07d41 --- /dev/null +++ b/src/core/hle/service/ssl/ssl_backend.h @@ -0,0 +1,44 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "core/hle/result.h" + +#include "common/common_types.h" + +#include +#include +#include +#include + +namespace Network { +class SocketBase; +} + +namespace Service::SSL { + +constexpr Result ResultNoSocket{ErrorModule::SSLSrv, 103}; +constexpr Result ResultInvalidSocket{ErrorModule::SSLSrv, 106}; +constexpr Result ResultTimeout{ErrorModule::SSLSrv, 205}; +constexpr Result ResultInternalError{ErrorModule::SSLSrv, 999}; // made up + +constexpr Result ResultWouldBlock{ErrorModule::SSLSrv, 204}; +// ^ ResultWouldBlock is returned from Read and Write, and oddly, DoHandshake, +// with no way in the latter case to distinguish whether the client should poll +// for read or write. The one official client I've seen handles this by always +// polling for read (with a timeout). + +class SSLConnectionBackend { +public: + virtual void SetSocket(std::shared_ptr socket) = 0; + virtual Result SetHostName(const std::string& hostname) = 0; + virtual Result DoHandshake() = 0; + virtual ResultVal Read(std::span data) = 0; + virtual ResultVal Write(std::span data) = 0; + virtual ResultVal>> GetServerCerts() = 0; +}; + +ResultVal> CreateSSLConnectionBackend(); + +} // 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 new file mode 100644 index 000000000..eb01561e2 --- /dev/null +++ b/src/core/hle/service/ssl/ssl_backend_none.cpp @@ -0,0 +1,15 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "core/hle/service/ssl/ssl_backend.h" + +#include "common/logging/log.h" + +namespace Service::SSL { + +ResultVal> CreateSSLConnectionBackend() { + LOG_ERROR(Service_SSL, "No SSL backend 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 new file mode 100644 index 000000000..cf9b904ac --- /dev/null +++ b/src/core/hle/service/ssl/ssl_backend_openssl.cpp @@ -0,0 +1,342 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "core/hle/service/ssl/ssl_backend.h" +#include "core/internal_network/network.h" +#include "core/internal_network/sockets.h" + +#include "common/fs/file.h" +#include "common/hex_util.h" +#include "common/string_util.h" + +#include + +#include +#include +#include +#include + +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(); + +} // namespace + +class SSLConnectionBackendOpenSSL 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 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) override { + socket_ = socket; + } + + Result SetHostName(const std::string& hostname) override { + if (!SSL_set1_host(ssl_, hostname.c_str())) { // hostname for verification + 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; + } + + Result DoHandshake() override { + SSL_set_verify_result(ssl_, X509_V_OK); + int ret = SSL_do_handshake(ssl_); + 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) { + 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).Code(); + } + + ResultVal Read(std::span data) override { + size_t actual; + int ret = SSL_read_ex(ssl_, data.data(), data.size(), &actual); + return HandleReturn("SSL_read_ex", actual, ret); + } + + ResultVal Write(std::span data) override { + size_t actual; + int ret = SSL_write_ex(ssl_, data.data(), data.size(), &actual); + return HandleReturn("SSL_write_ex", actual, ret); + } + + ResultVal HandleReturn(const char* what, size_t actual, int ret) { + int ssl_err = SSL_get_error(ssl_, ret); + CheckOpenSSLErrors(); + switch (ssl_err) { + case SSL_ERROR_NONE: + return actual; + case SSL_ERROR_ZERO_RETURN: + LOG_DEBUG(Service_SSL, "{} => SSL_ERROR_ZERO_RETURN", what); + // DoHandshake special-cases this, but for Read and Write: + return size_t(0); + 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); + return size_t(0); + } + LOG_ERROR(Service_SSL, "{} => other SSL_get_error return value {}", what, ssl_err); + return ResultInternalError; + } + } + + ResultVal>> GetServerCerts() 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; + } + std::vector> ret; + 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; }); + ret.emplace_back(buf, buf + len); + OPENSSL_free(buf); + } + return ret; + } + + ~SSLConnectionBackendOpenSSL() { + // these are null-tolerant: + SSL_free(ssl_); + BIO_free(bio_); + } + + 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 larg, void* parg) { + switch (cmd) { + case BIO_CTRL_FLUSH: + // Nothing to flush. + return 1; + case BIO_CTRL_PUSH: + case BIO_CTRL_POP: + case BIO_CTRL_GET_KTLS_SEND: + case BIO_CTRL_GET_KTLS_RECV: + // 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, larg, parg); + return 0; + } + } + + SSL* ssl_ = nullptr; + BIO* bio_ = nullptr; + bool got_read_eof_ = false; + + std::shared_ptr socket_; +}; + +ResultVal> CreateSSLConnectionBackend() { + auto conn = std::make_unique(); + Result res = conn->Init(); + if (res.IsFailure()) { + return res; + } + return conn; +} + +namespace { + +Result CheckOpenSSLErrors() { + unsigned long rc; + const char* file; + int line; + const char* func; + const char* data; + int flags; + while ((rc = ERR_get_error_all(&file, &line, &func, &data, &flags))) { + 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, + Common::Log::TrimSourcePath(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 new file mode 100644 index 000000000..0a326b536 --- /dev/null +++ b/src/core/hle/service/ssl/ssl_backend_schannel.cpp @@ -0,0 +1,529 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "core/hle/service/ssl/ssl_backend.h" +#include "core/internal_network/network.h" +#include "core/internal_network/sockets.h" + +#include "common/error.h" +#include "common/fs/file.h" +#include "common/hex_util.h" +#include "common/string_util.h" + +#include + +#define SECURITY_WIN32 +#include +#include + +namespace { + +std::once_flag one_time_init_flag; +bool one_time_init_success = false; + +SCHANNEL_CRED schannel_cred{ + .dwVersion = SCHANNEL_CRED_VERSION, + .dwFlags = SCH_USE_STRONG_CRYPTO | // don't allow insecure protocols + SCH_CRED_AUTO_CRED_VALIDATION | // validate certs + 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. +}; + +CredHandle cred_handle; + +static void OneTimeInit() { + 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; + } + + 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) override { + socket_ = socket; + } + + Result SetHostName(const std::string& hostname) override { + hostname_ = hostname; + return ResultSuccess; + } + + 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() { + 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. + size_t offset = ciphertext_read_buf_.size(); + ASSERT_OR_EXECUTE(offset + fill_size >= offset, { return ResultInternalError; }); + ciphertext_read_buf_.resize(offset + fill_size, 0); + auto read_span = std::span(ciphertext_read_buf_).subspan(offset, fill_size); + 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()) { + 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; + 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) + .BufferType = SECBUFFER_EMPTY, + }, + }}; + std::array output_buffers{{ + { + .BufferType = SECBUFFER_TOKEN, + }, // [0] + { + .BufferType = SECBUFFER_ALERT, + }, // [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()); + } + + SECURITY_STATUS ret = + InitializeSecurityContextA(&cred_handle, initial_call_done ? &ctxt_ : nullptr, + // Caller ensured we have set a hostname: + const_cast(hostname_.value().c_str()), 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) { + 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) { + 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() { + 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; + } + + ResultVal Read(std::span data) override { + 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 size_t(0); + } + while (1) { + if (!cleartext_read_buf_.empty()) { + size_t read_size = std::min(cleartext_read_buf_.size(), data.size()); + std::memcpy(data.data(), cleartext_read_buf_.data(), read_size); + cleartext_read_buf_.erase(cleartext_read_buf_.begin(), + cleartext_read_buf_.begin() + read_size); + return read_size; + } + if (!ciphertext_read_buf_.empty()) { + std::array buffers{{ + { + .cbBuffer = static_cast(ciphertext_read_buf_.size()), + .BufferType = SECBUFFER_DATA, + .pvBuffer = ciphertext_read_buf_.data(), + }, + { + .BufferType = SECBUFFER_EMPTY, + }, + { + .BufferType = SECBUFFER_EMPTY, + }, + { + .BufferType = SECBUFFER_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; + return size_t(0); + default: + LOG_ERROR(Service_SSL, "DecryptMessage failed: {}", + Common::NativeErrorToString(ret)); + return ResultInternalError; + } + } + Result r = FillCiphertextReadBuf(); + if (r != ResultSuccess) { + return r; + } + if (ciphertext_read_buf_.empty()) { + got_read_eof_ = true; + return size_t(0); + } + } + } + + ResultVal Write(std::span data) override { + if (handshake_state_ != HandshakeState::Connected) { + LOG_ERROR(Service_SSL, "Called Write but we did not successfully handshake"); + return ResultInternalError; + } + if (data.size() == 0) { + return size_t(0); + } + 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(); + } 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(), + }; + + 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(); + } + + ResultVal WriteAlreadyEncryptedData() { + Result r = FlushCiphertextWriteBuf(); + if (r != ResultSuccess) { + return r; + } + // write buf is empty + size_t cleartext_bytes_written = cleartext_write_buf_.size(); + cleartext_write_buf_.clear(); + return cleartext_bytes_written; + } + + ResultVal>> GetServerCerts() override { + PCCERT_CONTEXT returned_cert = nullptr; + 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; + std::vector> certs; + while ((some_cert = CertEnumCertificatesInStore(returned_cert->hCertStore, some_cert))) { + certs.emplace_back(static_cast(some_cert->pbCertEncoded), + static_cast(some_cert->pbCertEncoded) + + some_cert->cbCertEncoded); + } + std::reverse(certs.begin(), + certs.end()); // Windows returns certs in reverse order from what we want + CertFreeCertificateContext(returned_cert); + return certs; + } + + ~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; + size_t read_buf_fill_size_ = 0; +}; + +ResultVal> CreateSSLConnectionBackend() { + auto conn = std::make_unique(); + Result res = conn->Init(); + if (res.IsFailure()) { + return res; + } + return conn; +} + +} // namespace Service::SSL -- cgit v1.2.3 From 8905142f4362e06bd8f0a35cf9887d5110c1f308 Mon Sep 17 00:00:00 2001 From: comex Date: Sun, 25 Jun 2023 13:07:40 -0700 Subject: ssl: rename argument to avoid false positive codespell warning The original name `larg` was copied from the OpenSSL documentation and is not a typo of 'large' but rather an abbreviation of '`long` argument'. But whatever, no harm in adding an underscore. --- src/core/hle/service/ssl/ssl_backend_openssl.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/core/hle') diff --git a/src/core/hle/service/ssl/ssl_backend_openssl.cpp b/src/core/hle/service/ssl/ssl_backend_openssl.cpp index cf9b904ac..edbf0ad3e 100644 --- a/src/core/hle/service/ssl/ssl_backend_openssl.cpp +++ b/src/core/hle/service/ssl/ssl_backend_openssl.cpp @@ -226,7 +226,7 @@ public: } } - static long CtrlCallback(BIO* bio, int cmd, long larg, void* parg) { + static long CtrlCallback(BIO* bio, int cmd, long l_arg, void* p_arg) { switch (cmd) { case BIO_CTRL_FLUSH: // Nothing to flush. @@ -239,7 +239,7 @@ public: // as they're nothing unusual. return 0; default: - LOG_DEBUG(Service_SSL, "OpenSSL BIO got ctrl({}, {}, {})", cmd, larg, parg); + LOG_DEBUG(Service_SSL, "OpenSSL BIO got ctrl({}, {}, {})", cmd, l_arg, p_arg); return 0; } } -- cgit v1.2.3 From 4a355699219710d0a9ad620722393b3d9a16d84d Mon Sep 17 00:00:00 2001 From: comex Date: Sun, 25 Jun 2023 14:57:34 -0700 Subject: Fixes: - Add missing virtual destructor on `SSLBackend`. - On Windows, filter out `POLLWRBAND` (one of the new flags added) when calling `WSAPoll`, because despite the constant being defined on Windows, passing it calls `WSAPoll` to yield `EINVAL`. - Reduce OpenSSL version requirement to satisfy CI; I haven't tested whether it actually builds (or runs) against 1.1.1, but if not, I'll figure it out. - Change an instance of memcpy to memmove, even though the arguments cannot overlap, to avoid a [strange GCC error](https://github.com/yuzu-emu/yuzu/pull/10912#issuecomment-1606283351). --- src/core/hle/service/sockets/sfdnsres.cpp | 2 +- src/core/hle/service/ssl/ssl_backend.h | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) (limited to 'src/core/hle') diff --git a/src/core/hle/service/sockets/sfdnsres.cpp b/src/core/hle/service/sockets/sfdnsres.cpp index 1196fb86c..fb8798b42 100644 --- a/src/core/hle/service/sockets/sfdnsres.cpp +++ b/src/core/hle/service/sockets/sfdnsres.cpp @@ -96,7 +96,7 @@ static void Append(std::vector& vec, T t) { static void AppendNulTerminated(std::vector& vec, std::string_view str) { size_t off = vec.size(); vec.resize(off + str.size() + 1); - std::memcpy(vec.data() + off, str.data(), str.size()); + std::memmove(vec.data() + off, str.data(), str.size()); } // We implement gethostbyname using the host's getaddrinfo rather than the diff --git a/src/core/hle/service/ssl/ssl_backend.h b/src/core/hle/service/ssl/ssl_backend.h index 624e07d41..0dd8d9118 100644 --- a/src/core/hle/service/ssl/ssl_backend.h +++ b/src/core/hle/service/ssl/ssl_backend.h @@ -31,6 +31,7 @@ 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 Result DoHandshake() = 0; -- cgit v1.2.3 From 42015de49b578108a1a82b8df947397d2d123ad4 Mon Sep 17 00:00:00 2001 From: comex Date: Sun, 25 Jun 2023 16:09:16 -0700 Subject: ssl: fix compatibility with OpenSSL 1.1.1 Turns out changes were needed after all. --- src/core/hle/service/ssl/ssl_backend_openssl.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) (limited to 'src/core/hle') diff --git a/src/core/hle/service/ssl/ssl_backend_openssl.cpp b/src/core/hle/service/ssl/ssl_backend_openssl.cpp index edbf0ad3e..bc797b76b 100644 --- a/src/core/hle/service/ssl/ssl_backend_openssl.cpp +++ b/src/core/hle/service/ssl/ssl_backend_openssl.cpp @@ -233,8 +233,10 @@ public: 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; @@ -269,7 +271,14 @@ Result CheckOpenSSLErrors() { const char* func; const char* data; int flags; - while ((rc = ERR_get_error_all(&file, &line, &func, &data, &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()); -- cgit v1.2.3 From ac939f08a4c116b6a38978358b667b1fa0c51ef9 Mon Sep 17 00:00:00 2001 From: comex Date: Sun, 25 Jun 2023 17:00:05 -0700 Subject: Fix more Windows build errors I did test this beforehand, but not on MinGW, and the error that showed up on the msvc builder didn't happen for me... --- src/core/hle/service/sockets/sfdnsres.cpp | 4 +- src/core/hle/service/ssl/ssl_backend_schannel.cpp | 49 +++++++++++++---------- 2 files changed, 30 insertions(+), 23 deletions(-) (limited to 'src/core/hle') diff --git a/src/core/hle/service/sockets/sfdnsres.cpp b/src/core/hle/service/sockets/sfdnsres.cpp index fb8798b42..c5eaec920 100644 --- a/src/core/hle/service/sockets/sfdnsres.cpp +++ b/src/core/hle/service/sockets/sfdnsres.cpp @@ -149,7 +149,7 @@ static std::pair GetHostByNameRequestImpl(HLERequestConte const std::string host = Common::StringFromBuffer(host_buffer); // For now, ignore options, which are in input buffer 1 for GetHostByNameRequestWithOptions. - auto res = Network::GetAddrInfo(host, /*service*/ std::nullopt); + auto res = Network::GetAddressInfo(host, /*service*/ std::nullopt); if (!res.has_value()) { return {0, Translate(res.error())}; } @@ -249,7 +249,7 @@ static std::pair GetAddrInfoRequestImpl(HLERequestContext // Serialized hints are also passed in a buffer, but are ignored for now. - auto res = Network::GetAddrInfo(host, service); + auto res = Network::GetAddressInfo(host, service); if (!res.has_value()) { return {0, Translate(res.error())}; } diff --git a/src/core/hle/service/ssl/ssl_backend_schannel.cpp b/src/core/hle/service/ssl/ssl_backend_schannel.cpp index 0a326b536..92b2dddaa 100644 --- a/src/core/hle/service/ssl/ssl_backend_schannel.cpp +++ b/src/core/hle/service/ssl/ssl_backend_schannel.cpp @@ -12,29 +12,31 @@ #include +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 -namespace { - std::once_flag one_time_init_flag; bool one_time_init_success = false; -SCHANNEL_CRED schannel_cred{ - .dwVersion = SCHANNEL_CRED_VERSION, - .dwFlags = SCH_USE_STRONG_CRYPTO | // don't allow insecure protocols - SCH_CRED_AUTO_CRED_VALIDATION | // validate certs - SCH_CRED_NO_DEFAULT_CREDS, // don't automatically present a client certificate +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_AUTO_CRED_VALIDATION | // validate certs + 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. -}; - -CredHandle cred_handle; -static void OneTimeInit() { SECURITY_STATUS ret = AcquireCredentialsHandle(nullptr, const_cast(UNISP_NAME), SECPKG_CRED_OUTBOUND, nullptr, &schannel_cred, nullptr, nullptr, &cred_handle, nullptr); @@ -179,15 +181,21 @@ public: // [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{ @@ -299,21 +307,20 @@ public: return read_size; } 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(), }, - { - .BufferType = SECBUFFER_EMPTY, - }, - { - .BufferType = SECBUFFER_EMPTY, - }, - { - .BufferType = SECBUFFER_EMPTY, - }, + empty, + empty, + empty, }}; ASSERT_OR_EXECUTE_MSG( buffers[0].cbBuffer == ciphertext_read_buf_.size(), -- cgit v1.2.3 From cd4b8f037ca59efbc9fc45f1954de2284017c4c4 Mon Sep 17 00:00:00 2001 From: comex Date: Sun, 25 Jun 2023 17:09:54 -0700 Subject: re-format --- src/core/hle/service/ssl/ssl_backend_schannel.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) (limited to 'src/core/hle') diff --git a/src/core/hle/service/ssl/ssl_backend_schannel.cpp b/src/core/hle/service/ssl/ssl_backend_schannel.cpp index 92b2dddaa..d293adcf7 100644 --- a/src/core/hle/service/ssl/ssl_backend_schannel.cpp +++ b/src/core/hle/service/ssl/ssl_backend_schannel.cpp @@ -18,8 +18,8 @@ namespace { // 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; @@ -29,9 +29,10 @@ 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_AUTO_CRED_VALIDATION | // validate certs - SCH_CRED_NO_DEFAULT_CREDS; // don't automatically present a client certificate + schannel_cred.dwFlags = + SCH_USE_STRONG_CRYPTO | // don't allow insecure protocols + SCH_CRED_AUTO_CRED_VALIDATION | // validate certs + 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 -- cgit v1.2.3 From d885dd5b642807d0587acad43668cfccfdf06d1e Mon Sep 17 00:00:00 2001 From: comex Date: Sun, 25 Jun 2023 19:23:23 -0700 Subject: PR feedback + constification --- src/core/hle/service/sockets/bsd.cpp | 8 ++-- src/core/hle/service/sockets/nsd.cpp | 6 +-- src/core/hle/service/sockets/sfdnsres.cpp | 22 +++++------ src/core/hle/service/ssl/ssl.cpp | 20 +++++----- src/core/hle/service/ssl/ssl_backend.h | 4 +- src/core/hle/service/ssl/ssl_backend_none.cpp | 3 +- src/core/hle/service/ssl/ssl_backend_openssl.cpp | 14 +++---- src/core/hle/service/ssl/ssl_backend_schannel.cpp | 45 ++++++++++++----------- 8 files changed, 62 insertions(+), 60 deletions(-) (limited to 'src/core/hle') diff --git a/src/core/hle/service/sockets/bsd.cpp b/src/core/hle/service/sockets/bsd.cpp index 6677689dc..6034cc0b5 100644 --- a/src/core/hle/service/sockets/bsd.cpp +++ b/src/core/hle/service/sockets/bsd.cpp @@ -270,10 +270,10 @@ void BSD::GetSockOpt(HLERequestContext& ctx) { std::vector optval(ctx.GetWriteBufferSize()); - LOG_WARNING(Service, "called. fd={} level={} optname=0x{:x} len=0x{:x}", fd, level, optname, - optval.size()); + LOG_DEBUG(Service, "called. fd={} level={} optname=0x{:x} len=0x{:x}", fd, level, optname, + optval.size()); - Errno err = GetSockOptImpl(fd, level, optname, optval); + const Errno err = GetSockOptImpl(fd, level, optname, optval); ctx.WriteBuffer(optval); @@ -447,7 +447,7 @@ void BSD::DuplicateSocket(HLERequestContext& ctx) { const s32 fd = rp.Pop(); [[maybe_unused]] const u64 unused = rp.Pop(); - Common::Expected res = DuplicateSocketImpl(fd); + Expected res = DuplicateSocketImpl(fd); IPC::ResponseBuilder rb{ctx, 4}; rb.Push(ResultSuccess); rb.Push(res.value_or(0)); // ret diff --git a/src/core/hle/service/sockets/nsd.cpp b/src/core/hle/service/sockets/nsd.cpp index 22c3a31a0..0dfb0f166 100644 --- a/src/core/hle/service/sockets/nsd.cpp +++ b/src/core/hle/service/sockets/nsd.cpp @@ -49,7 +49,7 @@ static ResultVal ResolveImpl(const std::string& fqdn_in) { // The real implementation makes various substitutions. // For now we just return the string as-is, which is good enough when not // connecting to real Nintendo servers. - LOG_WARNING(Service, "(STUBBED) called({})", fqdn_in); + LOG_WARNING(Service, "(STUBBED) called, fqdn_in={}", fqdn_in); return fqdn_in; } @@ -69,7 +69,7 @@ void NSD::Resolve(HLERequestContext& ctx) { const std::string fqdn_in = Common::StringFromBuffer(ctx.ReadBuffer(0)); std::array fqdn_out{}; - Result res = ResolveCommon(fqdn_in, fqdn_out); + const Result res = ResolveCommon(fqdn_in, fqdn_out); ctx.WriteBuffer(fqdn_out); IPC::ResponseBuilder rb{ctx, 2}; @@ -80,7 +80,7 @@ void NSD::ResolveEx(HLERequestContext& ctx) { const std::string fqdn_in = Common::StringFromBuffer(ctx.ReadBuffer(0)); std::array fqdn_out; - Result res = ResolveCommon(fqdn_in, fqdn_out); + const Result res = ResolveCommon(fqdn_in, fqdn_out); if (res.IsError()) { IPC::ResponseBuilder rb{ctx, 2}; diff --git a/src/core/hle/service/sockets/sfdnsres.cpp b/src/core/hle/service/sockets/sfdnsres.cpp index c5eaec920..45f0f526a 100644 --- a/src/core/hle/service/sockets/sfdnsres.cpp +++ b/src/core/hle/service/sockets/sfdnsres.cpp @@ -88,15 +88,15 @@ static Errno GetAddrInfoErrorToErrno(GetAddrInfoError result) { template static void Append(std::vector& vec, T t) { - size_t off = vec.size(); - vec.resize(off + sizeof(T)); - std::memcpy(vec.data() + off, &t, sizeof(T)); + const size_t offset = vec.size(); + vec.resize(offset + sizeof(T)); + std::memcpy(vec.data() + offset, &t, sizeof(T)); } static void AppendNulTerminated(std::vector& vec, std::string_view str) { - size_t off = vec.size(); - vec.resize(off + str.size() + 1); - std::memmove(vec.data() + off, str.data(), str.size()); + const size_t offset = vec.size(); + vec.resize(offset + str.size() + 1); + std::memmove(vec.data() + offset, str.data(), str.size()); } // We implement gethostbyname using the host's getaddrinfo rather than the @@ -154,8 +154,8 @@ static std::pair GetHostByNameRequestImpl(HLERequestConte return {0, Translate(res.error())}; } - std::vector data = SerializeAddrInfoAsHostEnt(res.value(), host); - u32 data_size = static_cast(data.size()); + const std::vector data = SerializeAddrInfoAsHostEnt(res.value(), host); + const u32 data_size = static_cast(data.size()); ctx.WriteBuffer(data, 0); return {data_size, GetAddrInfoError::SUCCESS}; @@ -243,7 +243,7 @@ static std::pair GetAddrInfoRequestImpl(HLERequestContext std::optional service = std::nullopt; if (ctx.CanReadBuffer(1)) { - std::span service_buffer = ctx.ReadBuffer(1); + const std::span service_buffer = ctx.ReadBuffer(1); service = Common::StringFromBuffer(service_buffer); } @@ -254,8 +254,8 @@ static std::pair GetAddrInfoRequestImpl(HLERequestContext return {0, Translate(res.error())}; } - std::vector data = SerializeAddrInfo(res.value(), host); - u32 data_size = static_cast(data.size()); + const std::vector data = SerializeAddrInfo(res.value(), host); + const u32 data_size = static_cast(data.size()); ctx.WriteBuffer(data, 0); return {data_size, GetAddrInfoError::SUCCESS}; diff --git a/src/core/hle/service/ssl/ssl.cpp b/src/core/hle/service/ssl/ssl.cpp index a3b54c7f0..5638dd693 100644 --- a/src/core/hle/service/ssl/ssl.cpp +++ b/src/core/hle/service/ssl/ssl.cpp @@ -114,7 +114,7 @@ public: ~ISslConnection() { shared_data_->connection_count--; if (fd_to_close_.has_value()) { - s32 fd = *fd_to_close_; + const s32 fd = *fd_to_close_; if (!do_not_close_socket_) { LOG_ERROR(Service_SSL, "do_not_close_socket was changed after setting socket; is this right?"); @@ -123,7 +123,7 @@ public: if (bsd) { auto err = bsd->CloseImpl(fd); if (err != Service::Sockets::Errno::SUCCESS) { - LOG_ERROR(Service_SSL, "failed to close duplicated socket: {}", err); + LOG_ERROR(Service_SSL, "Failed to close duplicated socket: {}", err); } } } @@ -151,7 +151,7 @@ private: if (do_not_close_socket_) { auto res = bsd->DuplicateSocketImpl(fd); if (!res.has_value()) { - LOG_ERROR(Service_SSL, "failed to duplicate socket"); + LOG_ERROR(Service_SSL, "Failed to duplicate socket with fd {}", fd); return ResultInvalidSocket; } fd = *res; @@ -171,7 +171,7 @@ private: } Result SetHostNameImpl(const std::string& hostname) { - LOG_DEBUG(Service_SSL, "SetHostNameImpl({})", hostname); + LOG_DEBUG(Service_SSL, "called. hostname={}", hostname); ASSERT(!did_handshake_); Result res = backend_->SetHostName(hostname); if (res == ResultSuccess) { @@ -191,9 +191,9 @@ private: ASSERT(mode == IoMode::Blocking || mode == IoMode::NonBlocking); ASSERT_OR_EXECUTE(socket_, { return ResultNoSocket; }); - bool non_block = mode == IoMode::NonBlocking; - Network::Errno e = socket_->SetNonBlock(non_block); - if (e != Network::Errno::SUCCESS) { + const bool non_block = mode == IoMode::NonBlocking; + const Network::Errno error = socket_->SetNonBlock(non_block); + if (error != Network::Errno::SUCCESS) { LOG_ERROR(Service_SSL, "Failed to set native socket non-block flag to {}", non_block); } return ResultSuccess; @@ -307,13 +307,13 @@ private: } void DoHandshakeGetServerCert(HLERequestContext& ctx) { - Result res = DoHandshakeImpl(); + const Result res = DoHandshakeImpl(); u32 certs_count = 0; u32 certs_size = 0; if (res == ResultSuccess) { auto certs = backend_->GetServerCerts(); if (certs.Succeeded()) { - std::vector certs_buf = SerializeServerCerts(*certs); + const std::vector certs_buf = SerializeServerCerts(*certs); ctx.WriteBuffer(certs_buf); certs_count = static_cast(certs->size()); certs_size = static_cast(certs_buf.size()); @@ -377,7 +377,7 @@ private: get_server_cert_chain_ = static_cast(parameters.value); break; default: - LOG_WARNING(Service_SSL, "unrecognized option={}, value={}", parameters.option, + LOG_WARNING(Service_SSL, "Unknown option={}, value={}", parameters.option, parameters.value); } diff --git a/src/core/hle/service/ssl/ssl_backend.h b/src/core/hle/service/ssl/ssl_backend.h index 0dd8d9118..25c16bcc1 100644 --- a/src/core/hle/service/ssl/ssl_backend.h +++ b/src/core/hle/service/ssl/ssl_backend.h @@ -23,11 +23,11 @@ constexpr Result ResultInvalidSocket{ErrorModule::SSLSrv, 106}; constexpr Result ResultTimeout{ErrorModule::SSLSrv, 205}; constexpr Result ResultInternalError{ErrorModule::SSLSrv, 999}; // made up -constexpr Result ResultWouldBlock{ErrorModule::SSLSrv, 204}; -// ^ ResultWouldBlock is returned from Read and Write, and oddly, DoHandshake, +// ResultWouldBlock is returned from Read and Write, and oddly, DoHandshake, // with no way in the latter case to distinguish whether the client should poll // for read or write. The one official client I've seen handles this by always // polling for read (with a timeout). +constexpr Result ResultWouldBlock{ErrorModule::SSLSrv, 204}; class SSLConnectionBackend { public: diff --git a/src/core/hle/service/ssl/ssl_backend_none.cpp b/src/core/hle/service/ssl/ssl_backend_none.cpp index eb01561e2..f2f0ef706 100644 --- a/src/core/hle/service/ssl/ssl_backend_none.cpp +++ b/src/core/hle/service/ssl/ssl_backend_none.cpp @@ -8,7 +8,8 @@ namespace Service::SSL { ResultVal> CreateSSLConnectionBackend() { - LOG_ERROR(Service_SSL, "No SSL backend on this platform"); + LOG_ERROR(Service_SSL, + "Can't create SSL connection because no SSL backend is available on this platform"); return ResultInternalError; } diff --git a/src/core/hle/service/ssl/ssl_backend_openssl.cpp b/src/core/hle/service/ssl/ssl_backend_openssl.cpp index bc797b76b..e7d5801fd 100644 --- a/src/core/hle/service/ssl/ssl_backend_openssl.cpp +++ b/src/core/hle/service/ssl/ssl_backend_openssl.cpp @@ -90,15 +90,15 @@ public: Result DoHandshake() override { SSL_set_verify_result(ssl_, X509_V_OK); - int ret = SSL_do_handshake(ssl_); - long verify_result = SSL_get_verify_result(ssl_); + const int ret = SSL_do_handshake(ssl_); + 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) { - int ssl_err = SSL_get_error(ssl_, ret); + 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"); @@ -110,18 +110,18 @@ public: ResultVal Read(std::span data) override { size_t actual; - int ret = SSL_read_ex(ssl_, data.data(), data.size(), &actual); + const int ret = SSL_read_ex(ssl_, data.data(), data.size(), &actual); return HandleReturn("SSL_read_ex", actual, ret); } ResultVal Write(std::span data) override { size_t actual; - int ret = SSL_write_ex(ssl_, data.data(), data.size(), &actual); + const int ret = SSL_write_ex(ssl_, data.data(), data.size(), &actual); return HandleReturn("SSL_write_ex", actual, ret); } ResultVal HandleReturn(const char* what, size_t actual, int ret) { - int ssl_err = SSL_get_error(ssl_, ret); + const int ssl_err = SSL_get_error(ssl_, ret); CheckOpenSSLErrors(); switch (ssl_err) { case SSL_ERROR_NONE: @@ -255,7 +255,7 @@ public: ResultVal> CreateSSLConnectionBackend() { auto conn = std::make_unique(); - Result res = conn->Init(); + const Result res = conn->Init(); if (res.IsFailure()) { return res; } diff --git a/src/core/hle/service/ssl/ssl_backend_schannel.cpp b/src/core/hle/service/ssl/ssl_backend_schannel.cpp index d293adcf7..775d5cc07 100644 --- a/src/core/hle/service/ssl/ssl_backend_schannel.cpp +++ b/src/core/hle/service/ssl/ssl_backend_schannel.cpp @@ -38,7 +38,7 @@ static void OneTimeInit() { // certificate, and presenting one to some arbitrary server // might be a privacy concern? Who knows, though. - SECURITY_STATUS ret = + 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) { @@ -121,14 +121,14 @@ public: } Result FillCiphertextReadBuf() { - size_t fill_size = read_buf_fill_size_ ? read_buf_fill_size_ : 4096; + 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. - size_t offset = ciphertext_read_buf_.size(); + 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); - auto read_span = std::span(ciphertext_read_buf_).subspan(offset, fill_size); - auto [actual, err] = socket_->Recv(0, read_span); + 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); @@ -147,7 +147,7 @@ public: // Returns success if the write buffer has been completely emptied. Result FlushCiphertextWriteBuf() { while (!ciphertext_write_buf_.empty()) { - auto [actual, err] = socket_->Send(ciphertext_write_buf_, 0); + const auto [actual, err] = socket_->Send(ciphertext_write_buf_, 0); switch (err) { case Network::Errno::SUCCESS: ASSERT(static_cast(actual) <= ciphertext_write_buf_.size()); @@ -165,9 +165,10 @@ public: } 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; + const 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; unsigned long attr; // https://learn.microsoft.com/en-us/windows/win32/secauthn/initializesecuritycontext--schannel std::array input_buffers{{ @@ -219,7 +220,7 @@ public: ciphertext_read_buf_.size()); } - SECURITY_STATUS ret = + const SECURITY_STATUS ret = InitializeSecurityContextA(&cred_handle, initial_call_done ? &ctxt_ : nullptr, // Caller ensured we have set a hostname: const_cast(hostname_.value().c_str()), req, @@ -231,15 +232,15 @@ public: nullptr); // ptsExpiry if (output_buffers[0].pvBuffer) { - std::span span(static_cast(output_buffers[0].pvBuffer), - output_buffers[0].cbBuffer); + 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) { - std::span span(static_cast(output_buffers[1].pvBuffer), - output_buffers[1].cbBuffer); + 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)); @@ -280,7 +281,7 @@ public: } Result GrabStreamSizes() { - SECURITY_STATUS ret = + 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: {}", @@ -301,7 +302,7 @@ public: } while (1) { if (!cleartext_read_buf_.empty()) { - size_t read_size = std::min(cleartext_read_buf_.size(), data.size()); + const size_t read_size = std::min(cleartext_read_buf_.size(), data.size()); std::memcpy(data.data(), cleartext_read_buf_.data(), read_size); cleartext_read_buf_.erase(cleartext_read_buf_.begin(), cleartext_read_buf_.begin() + read_size); @@ -366,7 +367,7 @@ public: return ResultInternalError; } } - Result r = FillCiphertextReadBuf(); + const Result r = FillCiphertextReadBuf(); if (r != ResultSuccess) { return r; } @@ -430,7 +431,7 @@ public: .pBuffers = buffers.data(), }; - SECURITY_STATUS ret = EncryptMessage(&ctxt_, /*fQOP*/ 0, &desc, /*MessageSeqNo*/ 0); + 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; @@ -445,19 +446,19 @@ public: } ResultVal WriteAlreadyEncryptedData() { - Result r = FlushCiphertextWriteBuf(); + const Result r = FlushCiphertextWriteBuf(); if (r != ResultSuccess) { return r; } // write buf is empty - size_t cleartext_bytes_written = cleartext_write_buf_.size(); + const size_t cleartext_bytes_written = cleartext_write_buf_.size(); cleartext_write_buf_.clear(); return cleartext_bytes_written; } ResultVal>> GetServerCerts() override { PCCERT_CONTEXT returned_cert = nullptr; - SECURITY_STATUS ret = + const SECURITY_STATUS ret = QueryContextAttributes(&ctxt_, SECPKG_ATTR_REMOTE_CERT_CONTEXT, &returned_cert); if (ret != SEC_E_OK) { LOG_ERROR(Service_SSL, @@ -527,7 +528,7 @@ public: ResultVal> CreateSSLConnectionBackend() { auto conn = std::make_unique(); - Result res = conn->Init(); + const Result res = conn->Init(); if (res.IsFailure()) { return res; } -- cgit v1.2.3 From 0e191c271125321589dfdbb09731413550710c9a Mon Sep 17 00:00:00 2001 From: comex Date: Sat, 1 Jul 2023 15:02:25 -0700 Subject: Updates: - Address PR feedback. - Add SecureTransport backend for macOS. --- src/core/hle/service/sockets/bsd.cpp | 23 ++- src/core/hle/service/sockets/sfdnsres.cpp | 73 ++++++-- src/core/hle/service/ssl/ssl.cpp | 106 +++++------ src/core/hle/service/ssl/ssl_backend_openssl.cpp | 66 +++---- src/core/hle/service/ssl/ssl_backend_schannel.cpp | 207 +++++++++++----------- 5 files changed, 268 insertions(+), 207 deletions(-) (limited to 'src/core/hle') diff --git a/src/core/hle/service/sockets/bsd.cpp b/src/core/hle/service/sockets/bsd.cpp index 6034cc0b5..e63b0a357 100644 --- a/src/core/hle/service/sockets/bsd.cpp +++ b/src/core/hle/service/sockets/bsd.cpp @@ -443,15 +443,28 @@ void BSD::Close(HLERequestContext& ctx) { } void BSD::DuplicateSocket(HLERequestContext& ctx) { + struct InputParameters { + s32 fd; + u64 reserved; + }; + static_assert(sizeof(InputParameters) == 0x10); + + struct OutputParameters { + s32 ret; + Errno bsd_errno; + }; + static_assert(sizeof(OutputParameters) == 0x8); + IPC::RequestParser rp{ctx}; - const s32 fd = rp.Pop(); - [[maybe_unused]] const u64 unused = rp.Pop(); + auto input = rp.PopRaw(); - Expected res = DuplicateSocketImpl(fd); + Expected res = DuplicateSocketImpl(input.fd); IPC::ResponseBuilder rb{ctx, 4}; rb.Push(ResultSuccess); - rb.Push(res.value_or(0)); // ret - rb.Push(res ? 0 : static_cast(res.error())); // bsd errno + rb.PushRaw(OutputParameters{ + .ret = res.value_or(0), + .bsd_errno = res ? Errno::SUCCESS : res.error(), + }); } void BSD::EventFd(HLERequestContext& ctx) { diff --git a/src/core/hle/service/sockets/sfdnsres.cpp b/src/core/hle/service/sockets/sfdnsres.cpp index 45f0f526a..84cc79de8 100644 --- a/src/core/hle/service/sockets/sfdnsres.cpp +++ b/src/core/hle/service/sockets/sfdnsres.cpp @@ -131,14 +131,15 @@ static std::vector SerializeAddrInfoAsHostEnt(const std::vector GetHostByNameRequestImpl(HLERequestContext& ctx) { - struct Parameters { + struct InputParameters { u8 use_nsd_resolve; u32 cancel_handle; u64 process_id; }; + static_assert(sizeof(InputParameters) == 0x10); IPC::RequestParser rp{ctx}; - const auto parameters = rp.PopRaw(); + const auto parameters = rp.PopRaw(); LOG_WARNING( Service, @@ -164,21 +165,39 @@ static std::pair GetHostByNameRequestImpl(HLERequestConte void SFDNSRES::GetHostByNameRequest(HLERequestContext& ctx) { auto [data_size, emu_gai_err] = GetHostByNameRequestImpl(ctx); + struct OutputParameters { + NetDbError netdb_error; + Errno bsd_errno; + u32 data_size; + }; + static_assert(sizeof(OutputParameters) == 0xc); + IPC::ResponseBuilder rb{ctx, 5}; rb.Push(ResultSuccess); - rb.Push(static_cast(GetAddrInfoErrorToNetDbError(emu_gai_err))); // netdb error code - rb.Push(static_cast(GetAddrInfoErrorToErrno(emu_gai_err))); // errno - rb.Push(data_size); // serialized size + rb.PushRaw(OutputParameters{ + .netdb_error = GetAddrInfoErrorToNetDbError(emu_gai_err), + .bsd_errno = GetAddrInfoErrorToErrno(emu_gai_err), + .data_size = data_size, + }); } void SFDNSRES::GetHostByNameRequestWithOptions(HLERequestContext& ctx) { auto [data_size, emu_gai_err] = GetHostByNameRequestImpl(ctx); + struct OutputParameters { + u32 data_size; + NetDbError netdb_error; + Errno bsd_errno; + }; + static_assert(sizeof(OutputParameters) == 0xc); + IPC::ResponseBuilder rb{ctx, 5}; rb.Push(ResultSuccess); - rb.Push(data_size); // serialized size - rb.Push(static_cast(GetAddrInfoErrorToNetDbError(emu_gai_err))); // netdb error code - rb.Push(static_cast(GetAddrInfoErrorToErrno(emu_gai_err))); // errno + rb.PushRaw(OutputParameters{ + .data_size = data_size, + .netdb_error = GetAddrInfoErrorToNetDbError(emu_gai_err), + .bsd_errno = GetAddrInfoErrorToErrno(emu_gai_err), + }); } static std::vector SerializeAddrInfo(const std::vector& vec, @@ -221,14 +240,15 @@ static std::vector SerializeAddrInfo(const std::vector& v } static std::pair GetAddrInfoRequestImpl(HLERequestContext& ctx) { - struct Parameters { + struct InputParameters { u8 use_nsd_resolve; u32 cancel_handle; u64 process_id; }; + static_assert(sizeof(InputParameters) == 0x10); IPC::RequestParser rp{ctx}; - const auto parameters = rp.PopRaw(); + const auto parameters = rp.PopRaw(); LOG_WARNING( Service, @@ -264,23 +284,42 @@ static std::pair GetAddrInfoRequestImpl(HLERequestContext void SFDNSRES::GetAddrInfoRequest(HLERequestContext& ctx) { auto [data_size, emu_gai_err] = GetAddrInfoRequestImpl(ctx); + struct OutputParameters { + Errno bsd_errno; + GetAddrInfoError gai_error; + u32 data_size; + }; + static_assert(sizeof(OutputParameters) == 0xc); + IPC::ResponseBuilder rb{ctx, 5}; rb.Push(ResultSuccess); - rb.Push(static_cast(GetAddrInfoErrorToErrno(emu_gai_err))); // errno - rb.Push(static_cast(emu_gai_err)); // getaddrinfo error code - rb.Push(data_size); // serialized size + rb.PushRaw(OutputParameters{ + .bsd_errno = GetAddrInfoErrorToErrno(emu_gai_err), + .gai_error = emu_gai_err, + .data_size = data_size, + }); } void SFDNSRES::GetAddrInfoRequestWithOptions(HLERequestContext& ctx) { // Additional options are ignored auto [data_size, emu_gai_err] = GetAddrInfoRequestImpl(ctx); + struct OutputParameters { + u32 data_size; + GetAddrInfoError gai_error; + NetDbError netdb_error; + Errno bsd_errno; + }; + static_assert(sizeof(OutputParameters) == 0x10); + IPC::ResponseBuilder rb{ctx, 6}; rb.Push(ResultSuccess); - rb.Push(data_size); // serialized size - rb.Push(static_cast(emu_gai_err)); // getaddrinfo error code - rb.Push(static_cast(GetAddrInfoErrorToNetDbError(emu_gai_err))); // netdb error code - rb.Push(static_cast(GetAddrInfoErrorToErrno(emu_gai_err))); // errno + rb.PushRaw(OutputParameters{ + .data_size = data_size, + .gai_error = emu_gai_err, + .netdb_error = GetAddrInfoErrorToNetDbError(emu_gai_err), + .bsd_errno = GetAddrInfoErrorToErrno(emu_gai_err), + }); } void SFDNSRES::ResolverSetOptionRequest(HLERequestContext& ctx) { diff --git a/src/core/hle/service/ssl/ssl.cpp b/src/core/hle/service/ssl/ssl.cpp index 5638dd693..0919be55f 100644 --- a/src/core/hle/service/ssl/ssl.cpp +++ b/src/core/hle/service/ssl/ssl.cpp @@ -64,7 +64,7 @@ public: std::shared_ptr& shared_data, std::unique_ptr&& backend) : ServiceFramework{system_, "ISslConnection"}, ssl_version{version}, - shared_data_{shared_data}, backend_{std::move(backend)} { + shared_data{shared_data}, backend{std::move(backend)} { // clang-format off static const FunctionInfo functions[] = { {0, &ISslConnection::SetSocketDescriptor, "SetSocketDescriptor"}, @@ -112,10 +112,10 @@ public: } ~ISslConnection() { - shared_data_->connection_count--; - if (fd_to_close_.has_value()) { - const s32 fd = *fd_to_close_; - if (!do_not_close_socket_) { + shared_data->connection_count--; + if (fd_to_close.has_value()) { + const s32 fd = *fd_to_close; + if (!do_not_close_socket) { LOG_ERROR(Service_SSL, "do_not_close_socket was changed after setting socket; is this right?"); } else { @@ -132,30 +132,30 @@ public: private: SslVersion ssl_version; - std::shared_ptr shared_data_; - std::unique_ptr backend_; - std::optional fd_to_close_; - bool do_not_close_socket_ = false; - bool get_server_cert_chain_ = false; - std::shared_ptr socket_; - bool did_set_host_name_ = false; - bool did_handshake_ = false; + std::shared_ptr shared_data; + std::unique_ptr backend; + std::optional fd_to_close; + bool do_not_close_socket = false; + bool get_server_cert_chain = false; + std::shared_ptr socket; + bool did_set_host_name = false; + bool did_handshake = false; ResultVal SetSocketDescriptorImpl(s32 fd) { LOG_DEBUG(Service_SSL, "called, fd={}", fd); - ASSERT(!did_handshake_); + ASSERT(!did_handshake); auto bsd = system.ServiceManager().GetService("bsd:u"); ASSERT_OR_EXECUTE(bsd, { return ResultInternalError; }); s32 ret_fd; // Based on https://switchbrew.org/wiki/SSL_services#SetSocketDescriptor - if (do_not_close_socket_) { + if (do_not_close_socket) { auto res = bsd->DuplicateSocketImpl(fd); if (!res.has_value()) { LOG_ERROR(Service_SSL, "Failed to duplicate socket with fd {}", fd); return ResultInvalidSocket; } fd = *res; - fd_to_close_ = fd; + fd_to_close = fd; ret_fd = fd; } else { ret_fd = -1; @@ -165,34 +165,34 @@ private: LOG_ERROR(Service_SSL, "invalid socket fd {}", fd); return ResultInvalidSocket; } - socket_ = std::move(*sock); - backend_->SetSocket(socket_); + socket = std::move(*sock); + backend->SetSocket(socket); return ret_fd; } Result SetHostNameImpl(const std::string& hostname) { LOG_DEBUG(Service_SSL, "called. hostname={}", hostname); - ASSERT(!did_handshake_); - Result res = backend_->SetHostName(hostname); + ASSERT(!did_handshake); + Result res = backend->SetHostName(hostname); if (res == ResultSuccess) { - did_set_host_name_ = true; + did_set_host_name = true; } return res; } Result SetVerifyOptionImpl(u32 option) { - ASSERT(!did_handshake_); + ASSERT(!did_handshake); LOG_WARNING(Service_SSL, "(STUBBED) called. option={}", option); return ResultSuccess; } - Result SetIOModeImpl(u32 _mode) { - auto mode = static_cast(_mode); + Result SetIoModeImpl(u32 input_mode) { + auto mode = static_cast(input_mode); ASSERT(mode == IoMode::Blocking || mode == IoMode::NonBlocking); - ASSERT_OR_EXECUTE(socket_, { return ResultNoSocket; }); + ASSERT_OR_EXECUTE(socket, { return ResultNoSocket; }); const bool non_block = mode == IoMode::NonBlocking; - const Network::Errno error = socket_->SetNonBlock(non_block); + const Network::Errno error = socket->SetNonBlock(non_block); if (error != Network::Errno::SUCCESS) { LOG_ERROR(Service_SSL, "Failed to set native socket non-block flag to {}", non_block); } @@ -200,18 +200,18 @@ private: } Result SetSessionCacheModeImpl(u32 mode) { - ASSERT(!did_handshake_); + ASSERT(!did_handshake); LOG_WARNING(Service_SSL, "(STUBBED) called. value={}", mode); return ResultSuccess; } Result DoHandshakeImpl() { - ASSERT_OR_EXECUTE(!did_handshake_ && socket_, { return ResultNoSocket; }); + ASSERT_OR_EXECUTE(!did_handshake && socket, { return ResultNoSocket; }); ASSERT_OR_EXECUTE_MSG( - did_set_host_name_, { return ResultInternalError; }, + did_set_host_name, { return ResultInternalError; }, "Expected SetHostName before DoHandshake"); - Result res = backend_->DoHandshake(); - did_handshake_ = res.IsSuccess(); + Result res = backend->DoHandshake(); + did_handshake = res.IsSuccess(); return res; } @@ -225,7 +225,7 @@ private: u32 size; u32 offset; }; - if (!get_server_cert_chain_) { + 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"); @@ -248,9 +248,9 @@ private: } ResultVal> ReadImpl(size_t size) { - ASSERT_OR_EXECUTE(did_handshake_, { return ResultInternalError; }); + ASSERT_OR_EXECUTE(did_handshake, { return ResultInternalError; }); std::vector res(size); - ResultVal actual = backend_->Read(res); + ResultVal actual = backend->Read(res); if (actual.Failed()) { return actual.Code(); } @@ -259,8 +259,8 @@ private: } ResultVal WriteImpl(std::span data) { - ASSERT_OR_EXECUTE(did_handshake_, { return ResultInternalError; }); - return backend_->Write(data); + ASSERT_OR_EXECUTE(did_handshake, { return ResultInternalError; }); + return backend->Write(data); } ResultVal PendingImpl() { @@ -295,7 +295,7 @@ private: void SetIoMode(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const u32 mode = rp.Pop(); - const Result res = SetIOModeImpl(mode); + const Result res = SetIoModeImpl(mode); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(res); } @@ -307,22 +307,26 @@ private: } void DoHandshakeGetServerCert(HLERequestContext& ctx) { + struct OutputParameters { + u32 certs_size; + u32 certs_count; + }; + static_assert(sizeof(OutputParameters) == 0x8); + const Result res = DoHandshakeImpl(); - u32 certs_count = 0; - u32 certs_size = 0; + OutputParameters out{}; if (res == ResultSuccess) { - auto certs = backend_->GetServerCerts(); + auto certs = backend->GetServerCerts(); if (certs.Succeeded()) { const std::vector certs_buf = SerializeServerCerts(*certs); ctx.WriteBuffer(certs_buf); - certs_count = static_cast(certs->size()); - certs_size = static_cast(certs_buf.size()); + out.certs_count = static_cast(certs->size()); + out.certs_size = static_cast(certs_buf.size()); } } IPC::ResponseBuilder rb{ctx, 4}; rb.Push(res); - rb.Push(certs_size); - rb.Push(certs_count); + rb.PushRaw(out); } void Read(HLERequestContext& ctx) { @@ -371,10 +375,10 @@ private: switch (parameters.option) { case OptionType::DoNotCloseSocket: - do_not_close_socket_ = static_cast(parameters.value); + do_not_close_socket = static_cast(parameters.value); break; case OptionType::GetServerCertChain: - get_server_cert_chain_ = static_cast(parameters.value); + get_server_cert_chain = static_cast(parameters.value); break; default: LOG_WARNING(Service_SSL, "Unknown option={}, value={}", parameters.option, @@ -390,7 +394,7 @@ class ISslContext final : public ServiceFramework { public: explicit ISslContext(Core::System& system_, SslVersion version) : ServiceFramework{system_, "ISslContext"}, ssl_version{version}, - shared_data_{std::make_shared()} { + shared_data{std::make_shared()} { static const FunctionInfo functions[] = { {0, &ISslContext::SetOption, "SetOption"}, {1, nullptr, "GetOption"}, @@ -412,7 +416,7 @@ public: private: SslVersion ssl_version; - std::shared_ptr shared_data_; + std::shared_ptr shared_data; void SetOption(HLERequestContext& ctx) { struct Parameters { @@ -439,17 +443,17 @@ private: IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(backend_res.Code()); if (backend_res.Succeeded()) { - rb.PushIpcInterface(system, ssl_version, shared_data_, + rb.PushIpcInterface(system, ssl_version, shared_data, std::move(*backend_res)); } } void GetConnectionCount(HLERequestContext& ctx) { - LOG_WARNING(Service_SSL, "connection_count={}", shared_data_->connection_count); + LOG_DEBUG(Service_SSL, "connection_count={}", shared_data->connection_count); IPC::ResponseBuilder rb{ctx, 3}; rb.Push(ResultSuccess); - rb.Push(shared_data_->connection_count); + rb.Push(shared_data->connection_count); } void ImportServerPki(HLERequestContext& ctx) { diff --git a/src/core/hle/service/ssl/ssl_backend_openssl.cpp b/src/core/hle/service/ssl/ssl_backend_openssl.cpp index e7d5801fd..f69674f77 100644 --- a/src/core/hle/service/ssl/ssl_backend_openssl.cpp +++ b/src/core/hle/service/ssl/ssl_backend_openssl.cpp @@ -51,37 +51,37 @@ public: return ResultInternalError; } - ssl_ = SSL_new(ssl_ctx); - if (!ssl_) { + ssl = SSL_new(ssl_ctx); + if (!ssl) { LOG_ERROR(Service_SSL, "SSL_new failed"); return CheckOpenSSLErrors(); } - SSL_set_connect_state(ssl_); + SSL_set_connect_state(ssl); - bio_ = BIO_new(bio_meth); - if (!bio_) { + 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_); + BIO_set_data(bio, this); + BIO_set_init(bio, 1); + SSL_set_bio(ssl, bio, bio); return ResultSuccess; } - void SetSocket(std::shared_ptr socket) override { - socket_ = socket; + void SetSocket(std::shared_ptr socket_in) override { + socket = std::move(socket_in); } Result SetHostName(const std::string& hostname) override { - if (!SSL_set1_host(ssl_, hostname.c_str())) { // hostname for verification + if (!SSL_set1_host(ssl, hostname.c_str())) { // hostname for verification LOG_ERROR(Service_SSL, "SSL_set1_host({}) failed", hostname); return CheckOpenSSLErrors(); } - if (!SSL_set_tlsext_host_name(ssl_, hostname.c_str())) { // hostname for SNI + 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(); } @@ -89,18 +89,18 @@ public: } Result DoHandshake() override { - SSL_set_verify_result(ssl_, X509_V_OK); - const int ret = SSL_do_handshake(ssl_); - const long verify_result = SSL_get_verify_result(ssl_); + SSL_set_verify_result(ssl, X509_V_OK); + const int ret = SSL_do_handshake(ssl); + 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); + const int ssl_err = SSL_get_error(ssl, ret); if (ssl_err == SSL_ERROR_ZERO_RETURN || - (ssl_err == SSL_ERROR_SYSCALL && got_read_eof_)) { + (ssl_err == SSL_ERROR_SYSCALL && got_read_eof)) { LOG_ERROR(Service_SSL, "SSL handshake failed because server hung up"); return ResultInternalError; } @@ -110,18 +110,18 @@ public: ResultVal Read(std::span data) override { size_t actual; - const int ret = SSL_read_ex(ssl_, data.data(), data.size(), &actual); + const int ret = SSL_read_ex(ssl, data.data(), data.size(), &actual); return HandleReturn("SSL_read_ex", actual, ret); } ResultVal Write(std::span data) override { size_t actual; - const int ret = SSL_write_ex(ssl_, data.data(), data.size(), &actual); + const int ret = SSL_write_ex(ssl, data.data(), data.size(), &actual); return HandleReturn("SSL_write_ex", actual, ret); } ResultVal HandleReturn(const char* what, size_t actual, int ret) { - const int ssl_err = SSL_get_error(ssl_, ret); + const int ssl_err = SSL_get_error(ssl, ret); CheckOpenSSLErrors(); switch (ssl_err) { case SSL_ERROR_NONE: @@ -137,7 +137,7 @@ public: LOG_DEBUG(Service_SSL, "{} => SSL_ERROR_WANT_WRITE", what); return ResultWouldBlock; default: - if (ssl_err == SSL_ERROR_SYSCALL && got_read_eof_) { + if (ssl_err == SSL_ERROR_SYSCALL && got_read_eof) { LOG_DEBUG(Service_SSL, "{} => SSL_ERROR_SYSCALL because server hung up", what); return size_t(0); } @@ -147,7 +147,7 @@ public: } ResultVal>> GetServerCerts() override { - STACK_OF(X509)* chain = SSL_get_peer_cert_chain(ssl_); + 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; @@ -169,8 +169,8 @@ public: ~SSLConnectionBackendOpenSSL() { // these are null-tolerant: - SSL_free(ssl_); - BIO_free(bio_); + SSL_free(ssl); + BIO_free(bio); } static void KeyLogCallback(const SSL* ssl, const char* line) { @@ -188,9 +188,9 @@ public: 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"); + 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); + auto [actual, err] = self->socket->Send({reinterpret_cast(buf), len}, 0); switch (err) { case Network::Errno::SUCCESS: *actual_p = actual; @@ -207,14 +207,14 @@ public: 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"); + 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}); + 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; + self->got_read_eof = true; } return actual ? 1 : 0; case Network::Errno::AGAIN: @@ -246,11 +246,11 @@ public: } } - SSL* ssl_ = nullptr; - BIO* bio_ = nullptr; - bool got_read_eof_ = false; + SSL* ssl = nullptr; + BIO* bio = nullptr; + bool got_read_eof = false; - std::shared_ptr socket_; + std::shared_ptr socket; }; ResultVal> CreateSSLConnectionBackend() { diff --git a/src/core/hle/service/ssl/ssl_backend_schannel.cpp b/src/core/hle/service/ssl/ssl_backend_schannel.cpp index 775d5cc07..a1d6a186e 100644 --- a/src/core/hle/service/ssl/ssl_backend_schannel.cpp +++ b/src/core/hle/service/ssl/ssl_backend_schannel.cpp @@ -48,6 +48,12 @@ static void OneTimeInit() { 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; } @@ -70,25 +76,25 @@ public: return ResultSuccess; } - void SetSocket(std::shared_ptr socket) override { - socket_ = socket; + void SetSocket(std::shared_ptr socket_in) override { + socket = std::move(socket_in); } - Result SetHostName(const std::string& hostname) override { - hostname_ = hostname; + Result SetHostName(const std::string& hostname_in) override { + hostname = hostname_in; return ResultSuccess; } Result DoHandshake() override { while (1) { Result r; - switch (handshake_state_) { + switch (handshake_state) { case HandshakeState::Initial: if ((r = FlushCiphertextWriteBuf()) != ResultSuccess || (r = CallInitializeSecurityContext()) != ResultSuccess) { return r; } - // CallInitializeSecurityContext updated `handshake_state_`. + // CallInitializeSecurityContext updated `handshake_state`. continue; case HandshakeState::ContinueNeeded: case HandshakeState::IncompleteMessage: @@ -96,20 +102,20 @@ public: (r = FillCiphertextReadBuf()) != ResultSuccess) { return r; } - if (ciphertext_read_buf_.empty()) { + 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_`. + // CallInitializeSecurityContext updated `handshake_state`. continue; case HandshakeState::DoneAfterFlush: if ((r = FlushCiphertextWriteBuf()) != ResultSuccess) { return r; } - handshake_state_ = HandshakeState::Connected; + handshake_state = HandshakeState::Connected; return ResultSuccess; case HandshakeState::Connected: LOG_ERROR(Service_SSL, "Called DoHandshake but we already handshook"); @@ -121,24 +127,24 @@ public: } Result FillCiphertextReadBuf() { - const size_t fill_size = read_buf_fill_size_ ? read_buf_fill_size_ : 4096; - read_buf_fill_size_ = 0; + 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(); + 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); + 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); + ciphertext_read_buf.resize(offset + actual); return ResultSuccess; case Network::Errno::AGAIN: - ciphertext_read_buf_.resize(offset); + ciphertext_read_buf.resize(offset); return ResultWouldBlock; default: - ciphertext_read_buf_.resize(offset); + ciphertext_read_buf.resize(offset); LOG_ERROR(Service_SSL, "Socket recv returned Network::Errno {}", err); return ResultInternalError; } @@ -146,13 +152,13 @@ public: // 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); + 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); + 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; @@ -175,9 +181,9 @@ public: // only used if `initial_call_done` { // [0] - .cbBuffer = static_cast(ciphertext_read_buf_.size()), + .cbBuffer = static_cast(ciphertext_read_buf.size()), .BufferType = SECBUFFER_TOKEN, - .pvBuffer = ciphertext_read_buf_.data(), + .pvBuffer = ciphertext_read_buf.data(), }, { // [1] (will be replaced by SECBUFFER_MISSING when SEC_E_INCOMPLETE_MESSAGE is @@ -211,30 +217,30 @@ public: .pBuffers = output_buffers.data(), }; ASSERT_OR_EXECUTE_MSG( - input_buffers[0].cbBuffer == ciphertext_read_buf_.size(), + input_buffers[0].cbBuffer == ciphertext_read_buf.size(), { return ResultInternalError; }, "read buffer too large"); - bool initial_call_done = handshake_state_ != HandshakeState::Initial; + bool initial_call_done = handshake_state != HandshakeState::Initial; if (initial_call_done) { LOG_DEBUG(Service_SSL, "Passing {} bytes into InitializeSecurityContext", - ciphertext_read_buf_.size()); + ciphertext_read_buf.size()); } const SECURITY_STATUS ret = - InitializeSecurityContextA(&cred_handle, initial_call_done ? &ctxt_ : nullptr, + InitializeSecurityContextA(&cred_handle, initial_call_done ? &ctxt : nullptr, // Caller ensured we have set a hostname: - const_cast(hostname_.value().c_str()), req, + const_cast(hostname.value().c_str()), 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, + 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()); + ciphertext_write_buf.insert(ciphertext_write_buf.end(), span.begin(), span.end()); FreeContextBuffer(output_buffers[0].pvBuffer); } @@ -251,64 +257,64 @@ public: 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); + 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(); + ciphertext_read_buf.clear(); } - handshake_state_ = HandshakeState::ContinueNeeded; + 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; + 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; + 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; + handshake_state = HandshakeState::Error; return ResultInternalError; } } Result GrabStreamSizes() { const SECURITY_STATUS ret = - QueryContextAttributes(&ctxt_, SECPKG_ATTR_STREAM_SIZES, &stream_sizes_); + 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; + handshake_state = HandshakeState::Error; return ResultInternalError; } return ResultSuccess; } ResultVal Read(std::span data) override { - if (handshake_state_ != HandshakeState::Connected) { + 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_) { + if (data.size() == 0 || got_read_eof) { return size_t(0); } while (1) { - if (!cleartext_read_buf_.empty()) { - const size_t read_size = std::min(cleartext_read_buf_.size(), data.size()); - std::memcpy(data.data(), cleartext_read_buf_.data(), read_size); - cleartext_read_buf_.erase(cleartext_read_buf_.begin(), - cleartext_read_buf_.begin() + read_size); + if (!cleartext_read_buf.empty()) { + const size_t read_size = std::min(cleartext_read_buf.size(), data.size()); + std::memcpy(data.data(), cleartext_read_buf.data(), read_size); + cleartext_read_buf.erase(cleartext_read_buf.begin(), + cleartext_read_buf.begin() + read_size); return read_size; } - if (!ciphertext_read_buf_.empty()) { + if (!ciphertext_read_buf.empty()) { SecBuffer empty{ .cbBuffer = 0, .BufferType = SECBUFFER_EMPTY, @@ -316,16 +322,16 @@ public: }; std::array buffers{{ { - .cbBuffer = static_cast(ciphertext_read_buf_.size()), + .cbBuffer = static_cast(ciphertext_read_buf.size()), .BufferType = SECBUFFER_DATA, - .pvBuffer = ciphertext_read_buf_.data(), + .pvBuffer = ciphertext_read_buf.data(), }, empty, empty, empty, }}; ASSERT_OR_EXECUTE_MSG( - buffers[0].cbBuffer == ciphertext_read_buf_.size(), + buffers[0].cbBuffer == ciphertext_read_buf.size(), { return ResultInternalError; }, "read buffer too large"); SecBufferDesc desc{ .ulVersion = SECBUFFER_VERSION, @@ -333,7 +339,7 @@ public: .pBuffers = buffers.data(), }; SECURITY_STATUS ret = - DecryptMessage(&ctxt_, &desc, /*MessageSeqNo*/ 0, /*pfQOP*/ nullptr); + DecryptMessage(&ctxt, &desc, /*MessageSeqNo*/ 0, /*pfQOP*/ nullptr); switch (ret) { case SEC_E_OK: ASSERT_OR_EXECUTE(buffers[0].BufferType == SECBUFFER_STREAM_HEADER, @@ -342,24 +348,23 @@ public: { 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); + 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); + 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(); + 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; + got_read_eof = true; return size_t(0); default: LOG_ERROR(Service_SSL, "DecryptMessage failed: {}", @@ -371,43 +376,43 @@ public: if (r != ResultSuccess) { return r; } - if (ciphertext_read_buf_.empty()) { - got_read_eof_ = true; + if (ciphertext_read_buf.empty()) { + got_read_eof = true; return size_t(0); } } } ResultVal Write(std::span data) override { - if (handshake_state_ != HandshakeState::Connected) { + if (handshake_state != HandshakeState::Connected) { LOG_ERROR(Service_SSL, "Called Write but we did not successfully handshake"); return ResultInternalError; } if (data.size() == 0) { return size_t(0); } - data = data.subspan(0, std::min(data.size(), stream_sizes_.cbMaximumMessage)); - if (!cleartext_write_buf_.empty()) { + 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())) { + 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(); } else { - cleartext_write_buf_.assign(data.begin(), data.end()); + 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::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, + .cbBuffer = stream_sizes.cbHeader, .BufferType = SECBUFFER_STREAM_HEADER, .pvBuffer = header_buf.data(), }, @@ -417,7 +422,7 @@ public: .pvBuffer = tmp_data_buf.data(), }, { - .cbBuffer = stream_sizes_.cbTrailer, + .cbBuffer = stream_sizes.cbTrailer, .BufferType = SECBUFFER_STREAM_TRAILER, .pvBuffer = trailer_buf.data(), }, @@ -431,17 +436,17 @@ public: .pBuffers = buffers.data(), }; - const SECURITY_STATUS ret = EncryptMessage(&ctxt_, /*fQOP*/ 0, &desc, /*MessageSeqNo*/ 0); + 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()); + 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(); } @@ -451,15 +456,15 @@ public: return r; } // write buf is empty - const size_t cleartext_bytes_written = cleartext_write_buf_.size(); - cleartext_write_buf_.clear(); + const size_t cleartext_bytes_written = cleartext_write_buf.size(); + cleartext_write_buf.clear(); return cleartext_bytes_written; } ResultVal>> GetServerCerts() override { PCCERT_CONTEXT returned_cert = nullptr; const SECURITY_STATUS ret = - QueryContextAttributes(&ctxt_, SECPKG_ATTR_REMOTE_CERT_CONTEXT, &returned_cert); + 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: {}", @@ -480,8 +485,8 @@ public: } ~SSLConnectionBackendSchannel() { - if (handshake_state_ != HandshakeState::Initial) { - DeleteSecurityContext(&ctxt_); + if (handshake_state != HandshakeState::Initial) { + DeleteSecurityContext(&ctxt); } } @@ -509,21 +514,21 @@ public: // Another error was returned and we shouldn't allow initialization // to continue. Error, - } handshake_state_ = HandshakeState::Initial; + } handshake_state = HandshakeState::Initial; - CtxtHandle ctxt_; - SecPkgContext_StreamSizes stream_sizes_; + CtxtHandle ctxt; + SecPkgContext_StreamSizes stream_sizes; - std::shared_ptr socket_; - std::optional hostname_; + 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_; + 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; - size_t read_buf_fill_size_ = 0; + bool got_read_eof = false; + size_t read_buf_fill_size = 0; }; ResultVal> CreateSSLConnectionBackend() { -- cgit v1.2.3 From 0ed1cb7266e35c4b1161fa01f172d27befdc64a4 Mon Sep 17 00:00:00 2001 From: comex Date: Sat, 1 Jul 2023 17:48:19 -0700 Subject: ...actually add the SecureTransport backend to Git. --- .../service/ssl/ssl_backend_securetransport.cpp | 219 +++++++++++++++++++++ 1 file changed, 219 insertions(+) create mode 100644 src/core/hle/service/ssl/ssl_backend_securetransport.cpp (limited to 'src/core/hle') diff --git a/src/core/hle/service/ssl/ssl_backend_securetransport.cpp b/src/core/hle/service/ssl/ssl_backend_securetransport.cpp new file mode 100644 index 000000000..be40a5aeb --- /dev/null +++ b/src/core/hle/service/ssl/ssl_backend_securetransport.cpp @@ -0,0 +1,219 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "core/hle/service/ssl/ssl_backend.h" +#include "core/internal_network/network.h" +#include "core/internal_network/sockets.h" + +#include + +#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. +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" + +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; + } + + Result DoHandshake() override { + OSStatus status = SSLHandshake(context); + return HandleReturn("SSLHandshake", 0, status).Code(); + } + + ResultVal Read(std::span data) override { + size_t actual; + OSStatus status = SSLRead(context, data.data(), data.size(), &actual); + ; + return HandleReturn("SSLRead", actual, status); + } + + ResultVal Write(std::span data) override { + size_t actual; + OSStatus status = SSLWrite(context, data.data(), data.size(), &actual); + ; + return HandleReturn("SSLWrite", actual, status); + } + + ResultVal HandleReturn(const char* what, size_t actual, OSStatus status) { + switch (status) { + case 0: + return actual; + 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; + } + } + } + + ResultVal>> GetServerCerts() override { + CFReleaser trust; + OSStatus status = SSLCopyPeerTrust(context, &trust.ptr); + if (status) { + LOG_ERROR(Service_SSL, "SSLCopyPeerTrust failed: {}", OSStatusToString(status)); + return ResultInternalError; + } + std::vector> ret; + 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); + ret.emplace_back(ptr, ptr + CFDataGetLength(data)); + } + return ret; + } + + 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; + + std::shared_ptr socket; +}; + +ResultVal> CreateSSLConnectionBackend() { + auto conn = std::make_unique(); + const Result res = conn->Init(); + if (res.IsFailure()) { + return res; + } + return conn; +} + +} // namespace Service::SSL -- cgit v1.2.3 From 644c3ce609c7a327866ae0107bd7772e34e8ecb9 Mon Sep 17 00:00:00 2001 From: comex Date: Sat, 1 Jul 2023 22:03:21 -0700 Subject: Rename variables to avoid -Wshadow warnings under GCC --- src/core/hle/service/ssl/ssl.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'src/core/hle') diff --git a/src/core/hle/service/ssl/ssl.cpp b/src/core/hle/service/ssl/ssl.cpp index 0919be55f..9c96f9763 100644 --- a/src/core/hle/service/ssl/ssl.cpp +++ b/src/core/hle/service/ssl/ssl.cpp @@ -60,11 +60,11 @@ struct SslContextSharedData { class ISslConnection final : public ServiceFramework { public: - explicit ISslConnection(Core::System& system_, SslVersion version, - std::shared_ptr& shared_data, - std::unique_ptr&& backend) - : ServiceFramework{system_, "ISslConnection"}, ssl_version{version}, - shared_data{shared_data}, backend{std::move(backend)} { + explicit ISslConnection(Core::System& system_in, SslVersion ssl_version_in, + std::shared_ptr& shared_data_in, + std::unique_ptr&& backend_in) + : ServiceFramework{system_in, "ISslConnection"}, ssl_version{ssl_version_in}, + shared_data{shared_data_in}, backend{std::move(backend_in)} { // clang-format off static const FunctionInfo functions[] = { {0, &ISslConnection::SetSocketDescriptor, "SetSocketDescriptor"}, -- cgit v1.2.3