mirror of
https://git.eden-emu.dev/eden-emu/eden.git
synced 2026-09-14 23:43:49 +00:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1d58c56c8c | |||
| a504069938 | |||
| ee73920d28 | |||
| 42642f8bad |
+6
-18
@@ -1261,23 +1261,11 @@ if (ARCHITECTURE_x86_64 OR ARCHITECTURE_arm64 OR ARCHITECTURE_riscv64 OR ARCHITE
|
||||
target_link_libraries(core PRIVATE dynarmic::dynarmic)
|
||||
endif()
|
||||
|
||||
target_sources(core PRIVATE hle/service/ssl/ssl_backend_openssl.cpp)
|
||||
|
||||
target_link_libraries(core PRIVATE OpenSSL::SSL OpenSSL::Crypto)
|
||||
|
||||
# TODO
|
||||
|
||||
# elseif (APPLE)
|
||||
# target_sources(core PRIVATE
|
||||
# hle/service/ssl/ssl_backend_securetransport.cpp)
|
||||
# target_link_libraries(core PRIVATE "-framework Security")
|
||||
# elseif (WIN32)
|
||||
# target_sources(core PRIVATE
|
||||
# hle/service/ssl/ssl_backend_schannel.cpp)
|
||||
# target_link_libraries(core PRIVATE crypt32 secur32)
|
||||
# else()
|
||||
# target_sources(core PRIVATE
|
||||
# hle/service/ssl/ssl_backend_none.cpp)
|
||||
# endif()
|
||||
if (TARGET OpenSSL::SSL)
|
||||
target_sources(core PRIVATE hle/service/ssl/ssl_backend_openssl.cpp)
|
||||
target_link_libraries(core PRIVATE OpenSSL::SSL OpenSSL::Crypto)
|
||||
else()
|
||||
target_sources(core PRIVATE hle/service/ssl/ssl_backend_none.cpp)
|
||||
endif()
|
||||
|
||||
create_target_directory_groups(core)
|
||||
|
||||
@@ -42,6 +42,7 @@ public:
|
||||
{22, &IUser::GetApplicationAreaSize, "GetApplicationAreaSize"},
|
||||
{23, &IUser::AttachAvailabilityChangeEvent, "AttachAvailabilityChangeEvent"},
|
||||
{24, &IUser::RecreateApplicationArea, "RecreateApplicationArea"},
|
||||
{25, &IUser::StartDetection, "StartDetectionWithFilter"},
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
@@ -74,6 +75,7 @@ public:
|
||||
{20, &ISystem::GetDeviceState, "GetDeviceState"},
|
||||
{21, &ISystem::GetNpadId, "GetNpadId"},
|
||||
{23, &ISystem::AttachAvailabilityChangeEvent, "AttachAvailabilityChangeEvent"},
|
||||
{25, &ISystem::StartDetection, "StartDetectionWithFilter"},
|
||||
{100, &ISystem::Format, "Format"},
|
||||
{101, &ISystem::GetAdminInfo, "GetAdminInfo"},
|
||||
{102, &ISystem::GetRegisterInfoPrivate, "GetRegisterInfoPrivate"},
|
||||
@@ -118,6 +120,7 @@ public:
|
||||
{22, &IDebug::GetApplicationAreaSize, "GetApplicationAreaSize"},
|
||||
{23, &IDebug::AttachAvailabilityChangeEvent, "AttachAvailabilityChangeEvent"},
|
||||
{24, &IDebug::RecreateApplicationArea, "RecreateApplicationArea"},
|
||||
{25, &IDebug::StartDetection, "StartDetectionWithFilter"},
|
||||
{100, &IDebug::Format, "Format"},
|
||||
{101, &IDebug::GetAdminInfo, "GetAdminInfo"},
|
||||
{102, &IDebug::GetRegisterInfoPrivate, "GetRegisterInfoPrivate"},
|
||||
|
||||
+210
-241
@@ -48,6 +48,12 @@ enum class OptionType : u32 {
|
||||
EnableAlpn = 3,
|
||||
};
|
||||
|
||||
// This is nn::ssl::sf::RenegotiationMode
|
||||
enum RenegotiationMode : u32 {
|
||||
None = 0, ///< None
|
||||
Secure = 1, ///< Secure
|
||||
};
|
||||
|
||||
// This is nn::ssl::sf::SslVersion
|
||||
struct SslVersion {
|
||||
union {
|
||||
@@ -75,34 +81,34 @@ public:
|
||||
shared_data{shared_data_in}, backend{std::move(backend_in)} {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, &ISslConnection::SetSocketDescriptor, "SetSocketDescriptor"},
|
||||
{1, &ISslConnection::SetHostName, "SetHostName"},
|
||||
{2, &ISslConnection::SetVerifyOption, "SetVerifyOption"},
|
||||
{3, &ISslConnection::SetIoMode, "SetIoMode"},
|
||||
{4, nullptr, "GetSocketDescriptor"},
|
||||
{5, nullptr, "GetHostName"},
|
||||
{0, D<&ISslConnection::SetSocketDescriptor>, "SetSocketDescriptor"},
|
||||
{1, D<&ISslConnection::SetHostName>, "SetHostName"},
|
||||
{2, D<&ISslConnection::SetVerifyOption>, "SetVerifyOption"},
|
||||
{3, D<&ISslConnection::SetIoMode>, "SetIoMode"},
|
||||
{4, D<&ISslConnection::GetSocketDescriptor>, "GetSocketDescriptor"},
|
||||
{5, D<&ISslConnection::GetHostName>, "GetHostName"},
|
||||
{6, nullptr, "GetVerifyOption"},
|
||||
{7, nullptr, "GetIoMode"},
|
||||
{8, &ISslConnection::DoHandshake, "DoHandshake"},
|
||||
{7, D<&ISslConnection::GetIoMode>, "GetIoMode"},
|
||||
{8, D<&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, &ISslConnection::SetSessionCacheMode, "SetSessionCacheMode"},
|
||||
{18, nullptr, "GetSessionCacheMode"},
|
||||
{19, nullptr, "FlushSessionCache"},
|
||||
{20, nullptr, "SetRenegotiationMode"},
|
||||
{21, nullptr, "GetRenegotiationMode"},
|
||||
{22, &ISslConnection::SetOption, "SetOption"},
|
||||
{23, &ISslConnection::GetOption, "GetOption"},
|
||||
{10, D<&ISslConnection::Read>, "Read"},
|
||||
{11, D<&ISslConnection::Write>, "Write"},
|
||||
{12, D<&ISslConnection::Pending>, "Pending"},
|
||||
{13, D<&ISslConnection::Peek>, "Peek"},
|
||||
{14, D<&ISslConnection::Poll>, "Poll"},
|
||||
{15, D<&ISslConnection::GetVerifyCertError>, "GetVerifyCertError"},
|
||||
{16, D<&ISslConnection::GetNeededServerCertBufferSize>, "GetNeededServerCertBufferSize"},
|
||||
{17, D<&ISslConnection::SetSessionCacheMode>, "SetSessionCacheMode"},
|
||||
{18, D<&ISslConnection::GetSessionCacheMode>, "GetSessionCacheMode"},
|
||||
{19, D<&ISslConnection::FlushSessionCache>, "FlushSessionCache"},
|
||||
{20, D<&ISslConnection::SetRenegotiationMode>, "SetRenegotiationMode"},
|
||||
{21, D<&ISslConnection::GetRenegotiationMode>, "GetRenegotiationMode"},
|
||||
{22, D<&ISslConnection::SetOption>, "SetOption"},
|
||||
{23, D<&ISslConnection::GetOption>, "GetOption"},
|
||||
{24, nullptr, "GetVerifyCertErrors"},
|
||||
{25, nullptr, "GetCipherInfo"},
|
||||
{26, &ISslConnection::SetNextAlpnProto, "SetNextAlpnProto"},
|
||||
{27, &ISslConnection::GetNextAlpnProto, "GetNextAlpnProto"},
|
||||
{26, D<&ISslConnection::SetNextAlpnProto>, "SetNextAlpnProto"},
|
||||
{27, D<&ISslConnection::GetNextAlpnProto>, "GetNextAlpnProto"},
|
||||
{28, nullptr, "SetDtlsSocketDescriptor"},
|
||||
{29, nullptr, "GetDtlsHandshakeTimeout"},
|
||||
{30, nullptr, "SetPrivateOption"},
|
||||
@@ -141,80 +147,6 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
SslVersion ssl_version;
|
||||
std::shared_ptr<SslContextSharedData> shared_data;
|
||||
std::unique_ptr<SSLConnectionBackend> backend;
|
||||
std::optional<int> fd_to_close;
|
||||
bool do_not_close_socket = false;
|
||||
bool get_server_cert_chain = false;
|
||||
bool skip_default_verify = false;
|
||||
bool enable_alpn = false;
|
||||
std::shared_ptr<Network::SocketBase> socket;
|
||||
std::vector<u8> next_alpn_proto;
|
||||
bool did_handshake = false;
|
||||
u32 verify_option = 0;
|
||||
|
||||
Result SetSocketDescriptorImpl(s32* out_fd, s32 fd) {
|
||||
LOG_DEBUG(Service_SSL, "called, fd={}", fd);
|
||||
ASSERT(!did_handshake);
|
||||
auto bsd = system.ServiceManager().GetService<Service::Sockets::BSD_USA>("bsd:u");
|
||||
ASSERT_OR_EXECUTE(bsd, { return ResultInternalError; });
|
||||
|
||||
auto const res_v = bsd->DuplicateSocketImpl(fd);
|
||||
if (auto *res = std::get_if<s32>(&res_v)) {
|
||||
const s32 duplicated_fd = *res;
|
||||
if (do_not_close_socket) {
|
||||
*out_fd = duplicated_fd;
|
||||
} else {
|
||||
*out_fd = -1;
|
||||
fd_to_close = duplicated_fd;
|
||||
}
|
||||
std::optional<std::shared_ptr<Network::SocketBase>> sock = bsd->GetSocket(duplicated_fd);
|
||||
if (!sock.has_value()) {
|
||||
LOG_ERROR(Service_SSL, "invalid socket fd {} after duplication", duplicated_fd);
|
||||
return ResultInvalidSocket;
|
||||
}
|
||||
socket = std::move(*sock);
|
||||
backend->SetSocket(socket);
|
||||
return ResultSuccess;
|
||||
}
|
||||
LOG_ERROR(Service_SSL, "Failed to duplicate socket with fd {}", fd);
|
||||
return ResultInvalidSocket;
|
||||
}
|
||||
|
||||
Result SetHostNameImpl(const std::string& hostname) {
|
||||
LOG_DEBUG(Service_SSL, "called. hostname={}", hostname);
|
||||
ASSERT(!did_handshake);
|
||||
return backend->SetHostName(hostname);
|
||||
}
|
||||
|
||||
Result SetVerifyOptionImpl(u32 option) {
|
||||
ASSERT(!did_handshake);
|
||||
LOG_DEBUG(Service_SSL, "called. option={} (forcing 0)", option);
|
||||
verify_option = 0;
|
||||
backend->SetVerifyOption(0);
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result SetIoModeImpl(u32 input_mode) {
|
||||
auto mode = static_cast<IoMode>(input_mode);
|
||||
ASSERT(mode == IoMode::Blocking || mode == IoMode::NonBlocking);
|
||||
ASSERT_OR_EXECUTE(socket, { return ResultNoSocket; });
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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; });
|
||||
Result res = backend->DoHandshake();
|
||||
@@ -234,19 +166,17 @@ private:
|
||||
};
|
||||
if (!get_server_cert_chain) {
|
||||
// Just return the first one, unencoded.
|
||||
ASSERT_OR_EXECUTE_MSG(
|
||||
!certs.empty(), { return {}; }, "Should be at least one server cert");
|
||||
ASSERT_OR_EXECUTE_MSG(!certs.empty(), { return {}; }, "Should be at least one server cert");
|
||||
return certs[0];
|
||||
}
|
||||
std::vector<u8> ret;
|
||||
Header header{0x4E4D684374726543, static_cast<u32>(certs.size()), 0};
|
||||
Header header{0x4E4D684374726543, u32(certs.size()), 0};
|
||||
ret.insert(ret.end(), reinterpret_cast<u8*>(&header), reinterpret_cast<u8*>(&header + 1));
|
||||
size_t data_offset = sizeof(Header) + certs.size() * sizeof(EntryHeader);
|
||||
for (auto& cert : certs) {
|
||||
EntryHeader entry_header{static_cast<u32>(cert.size()), static_cast<u32>(data_offset)};
|
||||
EntryHeader entry_header{u32(cert.size()), u32(data_offset)};
|
||||
data_offset += cert.size();
|
||||
ret.insert(ret.end(), reinterpret_cast<u8*>(&entry_header),
|
||||
reinterpret_cast<u8*>(&entry_header + 1));
|
||||
ret.insert(ret.end(), reinterpret_cast<u8*>(&entry_header), reinterpret_cast<u8*>(&entry_header + 1));
|
||||
}
|
||||
for (auto& cert : certs) {
|
||||
ret.insert(ret.end(), cert.begin(), cert.end());
|
||||
@@ -254,65 +184,77 @@ private:
|
||||
return ret;
|
||||
}
|
||||
|
||||
Result ReadImpl(std::vector<u8>* out_data) {
|
||||
ASSERT_OR_EXECUTE(did_handshake, { return ResultInternalError; });
|
||||
size_t actual_size{};
|
||||
Result res = backend->Read(&actual_size, *out_data);
|
||||
if (res != ResultSuccess) {
|
||||
return res;
|
||||
Result SetSocketDescriptor(s32 in_fd, Out<s32> out_fd) {
|
||||
LOG_DEBUG(Service_SSL, "called, fd={}", in_fd);
|
||||
ASSERT(!did_handshake);
|
||||
auto bsd = system.ServiceManager().GetService<Service::Sockets::BSD_USA>("bsd:u");
|
||||
ASSERT_OR_EXECUTE(bsd, { return ResultInternalError; });
|
||||
|
||||
auto const res_v = bsd->DuplicateSocketImpl(in_fd);
|
||||
if (auto *res = std::get_if<s32>(&res_v)) {
|
||||
const s32 dup_fd = *res;
|
||||
*out_fd = do_not_close_socket ? dup_fd : -1;
|
||||
if (!do_not_close_socket)
|
||||
fd_to_close = dup_fd;
|
||||
auto const sock = bsd->GetSocket(dup_fd);
|
||||
if (!sock.has_value()) {
|
||||
LOG_ERROR(Service_SSL, "invalid socket fd {} after duplication", dup_fd);
|
||||
return ResultInvalidSocket;
|
||||
}
|
||||
socket = std::move(*sock);
|
||||
backend->SetSocket(std::move(socket));
|
||||
return ResultSuccess;
|
||||
}
|
||||
out_data->resize(actual_size);
|
||||
return res;
|
||||
LOG_ERROR(Service_SSL, "Failed to duplicate socket with fd {}", in_fd);
|
||||
return ResultInvalidSocket;
|
||||
}
|
||||
|
||||
Result WriteImpl(size_t* out_size, std::span<const u8> data) {
|
||||
ASSERT_OR_EXECUTE(did_handshake, { return ResultInternalError; });
|
||||
return backend->Write(out_size, data);
|
||||
Result SetHostName(InBuffer<BufferAttr_HipcMapAlias> buf) {
|
||||
auto const hostname = Common::StringFromBuffer(buf);
|
||||
LOG_DEBUG(Service_SSL, "called. hostname={}", hostname);
|
||||
ASSERT(!did_handshake);
|
||||
return backend->SetHostName(hostname.c_str());
|
||||
}
|
||||
|
||||
Result PendingImpl(s32* out_pending) {
|
||||
LOG_WARNING(Service_SSL, "(STUBBED) called.");
|
||||
*out_pending = 0;
|
||||
return ResultSuccess;
|
||||
Result SetVerifyOption(u32 option) {
|
||||
LOG_DEBUG(Service_SSL, "called. option={} (forcing 0)", option);
|
||||
ASSERT(!did_handshake);
|
||||
verify_option = 0;
|
||||
backend->SetVerifyOption(0);
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
void SetSocketDescriptor(HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const s32 in_fd = rp.Pop<s32>();
|
||||
s32 out_fd{-1};
|
||||
const Result res = SetSocketDescriptorImpl(&out_fd, in_fd);
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(res);
|
||||
rb.Push<s32>(out_fd);
|
||||
Result SetIoMode(u32 input_mode) {
|
||||
auto mode = IoMode(input_mode);
|
||||
ASSERT(mode == IoMode::Blocking || mode == IoMode::NonBlocking);
|
||||
R_UNLESS(socket, ResultNoSocket);
|
||||
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);
|
||||
}
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
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);
|
||||
Result GetSocketDescriptor(Out<u32> out_fd) {
|
||||
LOG_WARNING(Service_SSL, "(STUBBED)");
|
||||
*out_fd = socket->GetFD();
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
void SetVerifyOption(HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const u32 option = rp.Pop<u32>();
|
||||
const Result res = SetVerifyOptionImpl(option);
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(res);
|
||||
Result GetHostName(OutBuffer<BufferAttr_HipcMapAlias> data, Out<u32> out_size) {
|
||||
LOG_WARNING(Service_SSL, "(STUBBED)");
|
||||
ASSERT(!did_handshake);
|
||||
return backend->GetHostName(std::span<u8>{data.begin(), data.end()}, out_size);
|
||||
}
|
||||
|
||||
void SetIoMode(HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const u32 mode = rp.Pop<u32>();
|
||||
const Result res = SetIoModeImpl(mode);
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(res);
|
||||
Result GetIoMode(Out<u32> out_mode) {
|
||||
LOG_WARNING(Service_SSL, "(STUBBED)");
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
void DoHandshake(HLERequestContext& ctx) {
|
||||
const Result res = DoHandshakeImpl();
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(res);
|
||||
Result DoHandshake() {
|
||||
return DoHandshakeImpl();
|
||||
}
|
||||
|
||||
void DoHandshakeGetServerCert(HLERequestContext& ctx) {
|
||||
@@ -351,131 +293,158 @@ private:
|
||||
rb.PushRaw(out);
|
||||
}
|
||||
|
||||
void Read(HLERequestContext& ctx) {
|
||||
std::vector<u8> output_bytes(ctx.GetWriteBufferSize());
|
||||
const Result res = ReadImpl(&output_bytes);
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(res);
|
||||
if (res == ResultSuccess) {
|
||||
rb.Push(static_cast<u32>(output_bytes.size()));
|
||||
ctx.WriteBuffer(output_bytes);
|
||||
} else {
|
||||
rb.Push(static_cast<u32>(0));
|
||||
}
|
||||
Result Read(OutBuffer<BufferAttr_HipcMapAlias> data, Out<u32> out_size) {
|
||||
R_UNLESS(did_handshake, ResultInternalError);
|
||||
size_t tmp{};
|
||||
auto const res = backend->Read(&tmp, data);
|
||||
*out_size = u32(tmp);
|
||||
return res;
|
||||
}
|
||||
|
||||
void Write(HLERequestContext& ctx) {
|
||||
size_t write_size{0};
|
||||
const Result res = WriteImpl(&write_size, ctx.ReadBuffer());
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(res);
|
||||
rb.Push(static_cast<u32>(write_size));
|
||||
Result Write(InBuffer<BufferAttr_HipcMapAlias> data, Out<u32> out_size) {
|
||||
R_UNLESS(did_handshake, ResultInternalError);
|
||||
size_t tmp{};
|
||||
auto const res = backend->Write(&tmp, data);
|
||||
*out_size = u32(tmp);
|
||||
return res;
|
||||
}
|
||||
|
||||
void Pending(HLERequestContext& ctx) {
|
||||
s32 pending_size{0};
|
||||
const Result res = PendingImpl(&pending_size);
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(res);
|
||||
rb.Push<s32>(pending_size);
|
||||
Result Pending(Out<s32> out_pending_size) {
|
||||
LOG_WARNING(Service_SSL, "(STUBBED)");
|
||||
*out_pending_size = s32(backend->Pending());
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
void SetSessionCacheMode(HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const u32 mode = rp.Pop<u32>();
|
||||
const Result res = SetSessionCacheModeImpl(mode);
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(res);
|
||||
Result Peek(OutBuffer<BufferAttr_HipcMapAlias> data, Out<u32> out_size) {
|
||||
LOG_WARNING(Service_SSL, "(STUBBED)");
|
||||
size_t tmp{};
|
||||
auto const res = backend->Peek(&tmp, data);
|
||||
*out_size = u32(tmp);
|
||||
return 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<Parameters>();
|
||||
|
||||
switch (parameters.option) {
|
||||
case OptionType::DoNotCloseSocket:
|
||||
do_not_close_socket = static_cast<bool>(parameters.value);
|
||||
break;
|
||||
case OptionType::GetServerCertChain:
|
||||
get_server_cert_chain = static_cast<bool>(parameters.value);
|
||||
break;
|
||||
case OptionType::SkipDefaultVerify:
|
||||
skip_default_verify = static_cast<bool>(parameters.value);
|
||||
break;
|
||||
case OptionType::EnableAlpn:
|
||||
enable_alpn = static_cast<bool>(parameters.value);
|
||||
break;
|
||||
default:
|
||||
LOG_WARNING(Service_SSL, "Unknown option={}, value={}", parameters.option,
|
||||
parameters.value);
|
||||
}
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(ResultSuccess);
|
||||
Result Poll(u32 in_pollevent, u32 timer, Out<u32> out_pollevent) {
|
||||
LOG_WARNING(Service_SSL, "(STUBBED)");
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
void GetOption(HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto option = rp.PopRaw<OptionType>();
|
||||
Result GetVerifyCertError() {
|
||||
LOG_WARNING(Service_SSL, "(STUBBED)");
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
u8 value = 0;
|
||||
Result GetNeededServerCertBufferSize(Out<u32> out_needed_buffer_size) {
|
||||
LOG_WARNING(Service_SSL, "(STUBBED)");
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result SetSessionCacheMode(u32 mode) {
|
||||
LOG_WARNING(Service_SSL, "(STUBBED) called. value={}", mode);
|
||||
R_UNLESS(!did_handshake, ResultInternalError);
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result GetSessionCacheMode(Out<u32> mode) {
|
||||
LOG_WARNING(Service_SSL, "(STUBBED)");
|
||||
R_UNLESS(!did_handshake, ResultInternalError);
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result FlushSessionCache() {
|
||||
LOG_WARNING(Service_SSL, "(STUBBED)");
|
||||
R_UNLESS(!did_handshake, ResultInternalError);
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result SetRenegotiationMode(RenegotiationMode mode) {
|
||||
LOG_WARNING(Service_SSL, "(STUBBED)");
|
||||
backend->SetRenegotiationMode(u32(mode));
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result GetRenegotiationMode(Out<RenegotiationMode> mode) {
|
||||
LOG_WARNING(Service_SSL, "(STUBBED)");
|
||||
u32 tmp{};
|
||||
auto const res = backend->GetRenegotiationMode(&tmp);
|
||||
*mode = RenegotiationMode(tmp);
|
||||
return res;
|
||||
}
|
||||
|
||||
Result SetOption(OptionType option, s32 value) {
|
||||
switch (option) {
|
||||
case OptionType::DoNotCloseSocket:
|
||||
value = static_cast<u8>(do_not_close_socket);
|
||||
do_not_close_socket = bool(value);
|
||||
break;
|
||||
case OptionType::GetServerCertChain:
|
||||
value = static_cast<u8>(get_server_cert_chain);
|
||||
get_server_cert_chain = bool(value);
|
||||
break;
|
||||
case OptionType::SkipDefaultVerify:
|
||||
value = static_cast<u8>(skip_default_verify);
|
||||
skip_default_verify = bool(value);
|
||||
break;
|
||||
case OptionType::EnableAlpn:
|
||||
value = static_cast<u8>(enable_alpn);
|
||||
enable_alpn = bool(value);
|
||||
break;
|
||||
default:
|
||||
LOG_WARNING(Service_SSL, "Unknown option={}, value={}", option, value);
|
||||
}
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result GetOption(OptionType option, Out<u8> value) {
|
||||
switch (option) {
|
||||
case OptionType::DoNotCloseSocket:
|
||||
*value = u8(do_not_close_socket);
|
||||
break;
|
||||
case OptionType::GetServerCertChain:
|
||||
*value = u8(get_server_cert_chain);
|
||||
break;
|
||||
case OptionType::SkipDefaultVerify:
|
||||
*value = u8(skip_default_verify);
|
||||
break;
|
||||
case OptionType::EnableAlpn:
|
||||
*value = u8(enable_alpn);
|
||||
break;
|
||||
default:
|
||||
LOG_WARNING(Service_SSL, "Unknown option={}", option);
|
||||
value = 0;
|
||||
*value = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
LOG_DEBUG(Service_SSL, "GetOption called, option={}, ret value={}", option, value);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(ResultSuccess);
|
||||
rb.Push<u8>(value);
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
void SetNextAlpnProto(HLERequestContext& ctx) {
|
||||
const auto data = ctx.ReadBuffer(0);
|
||||
next_alpn_proto.assign(data.begin(), data.end());
|
||||
|
||||
Result SetNextAlpnProto(InBuffer<BufferAttr_HipcMapAlias> data) {
|
||||
auto const to_write = u32((std::min)(next_alpn_proto.size(), data.size()));
|
||||
next_alpn_proto.assign(data.begin(), data.begin() + to_write);
|
||||
LOG_DEBUG(Service_SSL, "SetNextAlpnProto called, size={}", next_alpn_proto.size());
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(ResultSuccess);
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
void GetNextAlpnProto(HLERequestContext& ctx) {
|
||||
const size_t writable = ctx.GetWriteBufferSize();
|
||||
const size_t to_write = (std::min)(next_alpn_proto.size(), writable);
|
||||
|
||||
if (to_write != 0) {
|
||||
ctx.WriteBuffer(std::span<const u8>(next_alpn_proto.data(), to_write));
|
||||
}
|
||||
|
||||
LOG_DEBUG(Service_SSL, "GetNextAlpnProto called, size={}", to_write);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(ResultSuccess);
|
||||
rb.Push<u32>(static_cast<u32>(to_write));
|
||||
Result GetNextAlpnProto(OutBuffer<BufferAttr_HipcMapAlias> data, Out<u32> to_write) {
|
||||
*to_write = u32((std::min)(next_alpn_proto.size(), data.size()));
|
||||
next_alpn_proto.assign(data.begin(), data.begin() + *to_write);
|
||||
LOG_DEBUG(Service_SSL, "GetNextAlpnProto called, size={}", *to_write);
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result GetVerifyCertErrors(OutBuffer<BufferAttr_HipcMapAlias> unk0, Out<u32> unk1, Out<u32> unk2) {
|
||||
LOG_WARNING(Service_SSL, "(STUBBED)");
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
SslVersion ssl_version;
|
||||
std::shared_ptr<SslContextSharedData> shared_data;
|
||||
std::unique_ptr<SSLConnectionBackend> backend;
|
||||
std::optional<int> fd_to_close;
|
||||
std::shared_ptr<Network::SocketBase> socket;
|
||||
std::vector<u8> next_alpn_proto;
|
||||
u32 verify_option = 0;
|
||||
|
||||
bool do_not_close_socket = false;
|
||||
bool get_server_cert_chain = false;
|
||||
bool skip_default_verify = false;
|
||||
bool enable_alpn = false;
|
||||
bool did_handshake = false;
|
||||
};
|
||||
|
||||
class ISslContext final : public ServiceFramework<ISslContext> {
|
||||
|
||||
@@ -36,12 +36,17 @@ class SSLConnectionBackend {
|
||||
public:
|
||||
virtual ~SSLConnectionBackend() {}
|
||||
virtual void SetSocket(std::shared_ptr<Network::SocketBase> socket) = 0;
|
||||
virtual Result SetHostName(const std::string& hostname) = 0;
|
||||
virtual void SetVerifyOption(u32 option) = 0;
|
||||
virtual Result DoHandshake() = 0;
|
||||
virtual Result Read(size_t* out_size, std::span<u8> data) = 0;
|
||||
virtual Result Peek(size_t* out_size, std::span<u8> data) = 0;
|
||||
virtual Result Write(size_t* out_size, std::span<const u8> data) = 0;
|
||||
virtual Result GetServerCerts(std::vector<std::vector<u8>>* out_certs) = 0;
|
||||
virtual Result SetHostName(const char* hostname) = 0;
|
||||
virtual Result GetHostName(std::span<u8> hostname, u32* out_size) = 0;
|
||||
virtual int Pending() = 0;
|
||||
virtual Result SetRenegotiationMode(u32 mode) = 0;
|
||||
virtual Result GetRenegotiationMode(u32* mode) = 0;
|
||||
};
|
||||
|
||||
Result CreateSSLConnectionBackend(std::unique_ptr<SSLConnectionBackend>* out_backend);
|
||||
|
||||
@@ -157,18 +157,30 @@ public:
|
||||
socket = std::move(socket_in);
|
||||
}
|
||||
|
||||
Result SetHostName(const std::string& hostname) override {
|
||||
Result SetHostName(const char* hostname) override {
|
||||
if (!skip_cert_verification) {
|
||||
if (!SSL_set1_host(ssl, hostname.c_str())) {
|
||||
if (!SSL_set1_host(ssl, hostname)) {
|
||||
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)) { // hostname for SNI
|
||||
LOG_ERROR(Service_SSL, "SSL_set_tlsext_host_name({}) failed", hostname);
|
||||
return CheckOpenSSLErrors();
|
||||
}
|
||||
return ResultSuccess;
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result GetHostName(std::span<u8> data, u32* out_size) override {
|
||||
auto const peer_name = SSL_get0_peername(ssl);
|
||||
if (peer_name == nullptr) {
|
||||
LOG_ERROR(Service_SSL, "SSL_get0_peername()");
|
||||
return CheckOpenSSLErrors();
|
||||
}
|
||||
auto const s = std::string{peer_name};
|
||||
*out_size = u32(s.size());
|
||||
std::memcpy(data.data(), s.data(), (std::min)(s.size(), data.size()));
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
void SetVerifyOption(u32 option) override {
|
||||
@@ -213,6 +225,13 @@ public:
|
||||
return HandleReturn("SSL_read_ex", out_size, ret);
|
||||
}
|
||||
|
||||
Result Peek(size_t* out_size, std::span<u8> data) override {
|
||||
auto const n = (std::min)(data.size(), *out_size);
|
||||
const int ret = SSL_peek(ssl, data.data(), int(n));
|
||||
*out_size = n;
|
||||
return HandleReturn("SSL_write_ex", out_size, ret);
|
||||
}
|
||||
|
||||
Result Write(size_t* out_size, std::span<const u8> data) override {
|
||||
const int ret = SSL_write_ex(ssl, data.data(), data.size(), out_size);
|
||||
return HandleReturn("SSL_write_ex", out_size, ret);
|
||||
@@ -263,7 +282,25 @@ public:
|
||||
out_certs->emplace_back(buf, buf + len);
|
||||
OPENSSL_free(buf);
|
||||
}
|
||||
return ResultSuccess;
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
int Pending() override {
|
||||
return SSL_pending(ssl);
|
||||
}
|
||||
|
||||
Result SetRenegotiationMode(u32 mode) override {
|
||||
if (mode == 0) {
|
||||
SSL_CTX_set_options(ssl_ctx, SSL_OP_NO_RENEGOTIATION);
|
||||
} else {
|
||||
SSL_CTX_set_options(ssl_ctx, SSL_OP_ALLOW_CLIENT_RENEGOTIATION);
|
||||
}
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result GetRenegotiationMode(u32* mode) override {
|
||||
*mode = SSL_get_secure_renegotiation_support(ssl) ? 1 : 0;
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
~SSLConnectionBackendOpenSSL() {
|
||||
|
||||
@@ -1,563 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <mutex>
|
||||
|
||||
#include "common/error.h"
|
||||
#include "common/fs/file.h"
|
||||
#include "common/hex_util.h"
|
||||
#include "common/string_util.h"
|
||||
|
||||
#include "core/hle/service/ssl/ssl_backend.h"
|
||||
#include "core/internal_network/network.h"
|
||||
#include "core/internal_network/sockets.h"
|
||||
|
||||
namespace {
|
||||
|
||||
// These includes are inside the namespace to avoid a conflict on MinGW where
|
||||
// the headers define an enum containing Network and Service as enumerators
|
||||
// (which clash with the correspondingly named namespaces).
|
||||
#define SECURITY_WIN32
|
||||
#include <schnlsp.h>
|
||||
#include <security.h>
|
||||
#include <wincrypt.h>
|
||||
|
||||
std::once_flag one_time_init_flag;
|
||||
bool one_time_init_success = false;
|
||||
|
||||
SCHANNEL_CRED schannel_cred{};
|
||||
CredHandle cred_handle;
|
||||
|
||||
static void OneTimeInit() {
|
||||
schannel_cred.dwVersion = SCHANNEL_CRED_VERSION;
|
||||
schannel_cred.dwFlags =
|
||||
SCH_USE_STRONG_CRYPTO | // don't allow insecure protocols
|
||||
SCH_CRED_NO_SERVERNAME_CHECK | // don't validate server names
|
||||
SCH_CRED_NO_DEFAULT_CREDS; // don't automatically present a client certificate
|
||||
// ^ I'm assuming that nobody would want to connect Yuzu to a
|
||||
// service that requires some OS-provided corporate client
|
||||
// certificate, and presenting one to some arbitrary server
|
||||
// might be a privacy concern? Who knows, though.
|
||||
|
||||
const SECURITY_STATUS ret =
|
||||
AcquireCredentialsHandle(nullptr, const_cast<LPTSTR>(UNISP_NAME), SECPKG_CRED_OUTBOUND,
|
||||
nullptr, &schannel_cred, nullptr, nullptr, &cred_handle, nullptr);
|
||||
if (ret != SEC_E_OK) {
|
||||
// SECURITY_STATUS codes are a type of HRESULT and can be used with NativeErrorToString.
|
||||
LOG_ERROR(Service_SSL, "AcquireCredentialsHandle failed: {}",
|
||||
Common::NativeErrorToString(ret));
|
||||
return;
|
||||
}
|
||||
|
||||
if (getenv("SSLKEYLOGFILE")) {
|
||||
LOG_CRITICAL(Service_SSL, "SSLKEYLOGFILE was set but Schannel does not support exporting "
|
||||
"keys; not logging keys!");
|
||||
// Not fatal.
|
||||
}
|
||||
|
||||
one_time_init_success = true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace Service::SSL {
|
||||
|
||||
class SSLConnectionBackendSchannel final : public SSLConnectionBackend {
|
||||
public:
|
||||
Result Init() {
|
||||
std::call_once(one_time_init_flag, OneTimeInit);
|
||||
|
||||
if (!one_time_init_success) {
|
||||
LOG_ERROR(
|
||||
Service_SSL,
|
||||
"Can't create SSL connection because Schannel one-time initialization failed");
|
||||
return ResultInternalError;
|
||||
}
|
||||
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
void SetSocket(std::shared_ptr<Network::SocketBase> socket_in) override {
|
||||
socket = std::move(socket_in);
|
||||
}
|
||||
|
||||
Result SetHostName(const std::string& hostname_in) override {
|
||||
hostname = hostname_in;
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
void SetVerifyOption(u32 option) override {
|
||||
skip_cert_verification = (option == 0);
|
||||
LOG_WARNING(Service_SSL, "option={} skip_verification={}", option,
|
||||
skip_cert_verification);
|
||||
}
|
||||
|
||||
Result DoHandshake() override {
|
||||
while (1) {
|
||||
Result r;
|
||||
switch (handshake_state) {
|
||||
case HandshakeState::Initial:
|
||||
if ((r = FlushCiphertextWriteBuf()) != ResultSuccess ||
|
||||
(r = CallInitializeSecurityContext()) != ResultSuccess) {
|
||||
return r;
|
||||
}
|
||||
// CallInitializeSecurityContext updated `handshake_state`.
|
||||
continue;
|
||||
case HandshakeState::ContinueNeeded:
|
||||
case HandshakeState::IncompleteMessage:
|
||||
if ((r = FlushCiphertextWriteBuf()) != ResultSuccess ||
|
||||
(r = FillCiphertextReadBuf()) != ResultSuccess) {
|
||||
return r;
|
||||
}
|
||||
if (ciphertext_read_buf.empty()) {
|
||||
LOG_ERROR(Service_SSL, "SSL handshake failed because server hung up");
|
||||
return ResultInternalError;
|
||||
}
|
||||
if ((r = CallInitializeSecurityContext()) != ResultSuccess) {
|
||||
return r;
|
||||
}
|
||||
// CallInitializeSecurityContext updated `handshake_state`.
|
||||
continue;
|
||||
case HandshakeState::DoneAfterFlush:
|
||||
if ((r = FlushCiphertextWriteBuf()) != ResultSuccess) {
|
||||
return r;
|
||||
}
|
||||
handshake_state = HandshakeState::Connected;
|
||||
return ResultSuccess;
|
||||
case HandshakeState::Connected:
|
||||
LOG_ERROR(Service_SSL, "Called DoHandshake but we already handshook");
|
||||
return ResultInternalError;
|
||||
case HandshakeState::Error:
|
||||
return ResultInternalError;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Result FillCiphertextReadBuf() {
|
||||
const size_t fill_size = read_buf_fill_size ? read_buf_fill_size : 4096;
|
||||
read_buf_fill_size = 0;
|
||||
// This unnecessarily zeroes the buffer; oh well.
|
||||
const size_t offset = ciphertext_read_buf.size();
|
||||
ASSERT_OR_EXECUTE(offset + fill_size >= offset, { return ResultInternalError; });
|
||||
ciphertext_read_buf.resize(offset + fill_size, 0);
|
||||
const auto read_span = std::span(ciphertext_read_buf).subspan(offset, fill_size);
|
||||
const auto [actual, err] = socket->Recv(0, read_span);
|
||||
switch (err) {
|
||||
case Network::Errno::SUCCESS:
|
||||
ASSERT(static_cast<size_t>(actual) <= fill_size);
|
||||
ciphertext_read_buf.resize(offset + actual);
|
||||
return ResultSuccess;
|
||||
case Network::Errno::AGAIN:
|
||||
ciphertext_read_buf.resize(offset);
|
||||
return ResultWouldBlock;
|
||||
default:
|
||||
ciphertext_read_buf.resize(offset);
|
||||
LOG_ERROR(Service_SSL, "Socket recv returned Network::Errno {}", err);
|
||||
return ResultInternalError;
|
||||
}
|
||||
}
|
||||
|
||||
// Returns success if the write buffer has been completely emptied.
|
||||
Result FlushCiphertextWriteBuf() {
|
||||
while (!ciphertext_write_buf.empty()) {
|
||||
const auto [actual, err] = socket->Send(ciphertext_write_buf, 0);
|
||||
switch (err) {
|
||||
case Network::Errno::SUCCESS:
|
||||
ASSERT(static_cast<size_t>(actual) <= ciphertext_write_buf.size());
|
||||
ciphertext_write_buf.erase(ciphertext_write_buf.begin(),
|
||||
ciphertext_write_buf.begin() + actual);
|
||||
break;
|
||||
case Network::Errno::AGAIN:
|
||||
return ResultWouldBlock;
|
||||
default:
|
||||
LOG_ERROR(Service_SSL, "Socket send returned Network::Errno {}", err);
|
||||
return ResultInternalError;
|
||||
}
|
||||
}
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result CallInitializeSecurityContext() {
|
||||
unsigned long req = ISC_REQ_ALLOCATE_MEMORY | ISC_REQ_CONFIDENTIALITY |
|
||||
ISC_REQ_INTEGRITY | ISC_REQ_REPLAY_DETECT |
|
||||
ISC_REQ_SEQUENCE_DETECT | ISC_REQ_STREAM |
|
||||
ISC_REQ_USE_SUPPLIED_CREDS;
|
||||
|
||||
if (skip_cert_verification) {
|
||||
req |= ISC_REQ_MANUAL_CRED_VALIDATION;
|
||||
}
|
||||
|
||||
unsigned long attr;
|
||||
// https://learn.microsoft.com/en-us/windows/win32/secauthn/initializesecuritycontext--schannel
|
||||
std::array<SecBuffer, 2> input_buffers{{
|
||||
// only used if `initial_call_done`
|
||||
{
|
||||
// [0]
|
||||
.cbBuffer = static_cast<unsigned long>(ciphertext_read_buf.size()),
|
||||
.BufferType = SECBUFFER_TOKEN,
|
||||
.pvBuffer = ciphertext_read_buf.data(),
|
||||
},
|
||||
{
|
||||
// [1] (will be replaced by SECBUFFER_MISSING when SEC_E_INCOMPLETE_MESSAGE is
|
||||
// returned, or SECBUFFER_EXTRA when SEC_E_CONTINUE_NEEDED is returned if the
|
||||
// whole buffer wasn't used)
|
||||
.cbBuffer = 0,
|
||||
.BufferType = SECBUFFER_EMPTY,
|
||||
.pvBuffer = nullptr,
|
||||
},
|
||||
}};
|
||||
std::array<SecBuffer, 2> output_buffers{{
|
||||
{
|
||||
.cbBuffer = 0,
|
||||
.BufferType = SECBUFFER_TOKEN,
|
||||
.pvBuffer = nullptr,
|
||||
}, // [0]
|
||||
{
|
||||
.cbBuffer = 0,
|
||||
.BufferType = SECBUFFER_ALERT,
|
||||
.pvBuffer = nullptr,
|
||||
}, // [1]
|
||||
}};
|
||||
SecBufferDesc input_desc{
|
||||
.ulVersion = SECBUFFER_VERSION,
|
||||
.cBuffers = static_cast<unsigned long>(input_buffers.size()),
|
||||
.pBuffers = input_buffers.data(),
|
||||
};
|
||||
SecBufferDesc output_desc{
|
||||
.ulVersion = SECBUFFER_VERSION,
|
||||
.cBuffers = static_cast<unsigned long>(output_buffers.size()),
|
||||
.pBuffers = output_buffers.data(),
|
||||
};
|
||||
ASSERT_OR_EXECUTE_MSG(
|
||||
input_buffers[0].cbBuffer == ciphertext_read_buf.size(),
|
||||
{ return ResultInternalError; }, "read buffer too large");
|
||||
|
||||
bool initial_call_done = handshake_state != HandshakeState::Initial;
|
||||
if (initial_call_done) {
|
||||
LOG_DEBUG(Service_SSL, "Passing {} bytes into InitializeSecurityContext",
|
||||
ciphertext_read_buf.size());
|
||||
}
|
||||
|
||||
char* hostname_ptr = hostname ? const_cast<char*>(hostname->c_str()) : nullptr;
|
||||
const SECURITY_STATUS ret = InitializeSecurityContextA(
|
||||
&cred_handle, initial_call_done ? &ctxt : nullptr, hostname_ptr, req,
|
||||
0, // Reserved1
|
||||
0, // TargetDataRep not used with Schannel
|
||||
initial_call_done ? &input_desc : nullptr,
|
||||
0, // Reserved2
|
||||
initial_call_done ? nullptr : &ctxt, &output_desc, &attr,
|
||||
nullptr); // ptsExpiry
|
||||
|
||||
if (output_buffers[0].pvBuffer) {
|
||||
const std::span span(static_cast<u8*>(output_buffers[0].pvBuffer),
|
||||
output_buffers[0].cbBuffer);
|
||||
ciphertext_write_buf.insert(ciphertext_write_buf.end(), span.begin(), span.end());
|
||||
FreeContextBuffer(output_buffers[0].pvBuffer);
|
||||
}
|
||||
|
||||
if (output_buffers[1].pvBuffer) {
|
||||
const std::span span(static_cast<u8*>(output_buffers[1].pvBuffer),
|
||||
output_buffers[1].cbBuffer);
|
||||
// The documentation doesn't explain what format this data is in.
|
||||
LOG_DEBUG(Service_SSL, "Got a {}-byte alert buffer: {}", span.size(),
|
||||
Common::HexToString(span));
|
||||
}
|
||||
|
||||
switch (ret) {
|
||||
case SEC_I_CONTINUE_NEEDED:
|
||||
LOG_DEBUG(Service_SSL, "InitializeSecurityContext => SEC_I_CONTINUE_NEEDED");
|
||||
if (input_buffers[1].BufferType == SECBUFFER_EXTRA) {
|
||||
LOG_DEBUG(Service_SSL, "EXTRA of size {}", input_buffers[1].cbBuffer);
|
||||
ASSERT(input_buffers[1].cbBuffer <= ciphertext_read_buf.size());
|
||||
ciphertext_read_buf.erase(ciphertext_read_buf.begin(),
|
||||
ciphertext_read_buf.end() - input_buffers[1].cbBuffer);
|
||||
} else {
|
||||
ASSERT(input_buffers[1].BufferType == SECBUFFER_EMPTY);
|
||||
ciphertext_read_buf.clear();
|
||||
}
|
||||
handshake_state = HandshakeState::ContinueNeeded;
|
||||
return ResultSuccess;
|
||||
case SEC_E_INCOMPLETE_MESSAGE:
|
||||
LOG_DEBUG(Service_SSL, "InitializeSecurityContext => SEC_E_INCOMPLETE_MESSAGE");
|
||||
ASSERT(input_buffers[1].BufferType == SECBUFFER_MISSING);
|
||||
read_buf_fill_size = input_buffers[1].cbBuffer;
|
||||
handshake_state = HandshakeState::IncompleteMessage;
|
||||
return ResultSuccess;
|
||||
case SEC_E_OK:
|
||||
LOG_DEBUG(Service_SSL, "InitializeSecurityContext => SEC_E_OK");
|
||||
ciphertext_read_buf.clear();
|
||||
handshake_state = HandshakeState::DoneAfterFlush;
|
||||
return GrabStreamSizes();
|
||||
default:
|
||||
LOG_ERROR(Service_SSL,
|
||||
"InitializeSecurityContext failed (probably certificate/protocol issue): {}",
|
||||
Common::NativeErrorToString(ret));
|
||||
handshake_state = HandshakeState::Error;
|
||||
return ResultInternalError;
|
||||
}
|
||||
}
|
||||
|
||||
Result GrabStreamSizes() {
|
||||
const SECURITY_STATUS ret =
|
||||
QueryContextAttributes(&ctxt, SECPKG_ATTR_STREAM_SIZES, &stream_sizes);
|
||||
if (ret != SEC_E_OK) {
|
||||
LOG_ERROR(Service_SSL, "QueryContextAttributes(SECPKG_ATTR_STREAM_SIZES) failed: {}",
|
||||
Common::NativeErrorToString(ret));
|
||||
handshake_state = HandshakeState::Error;
|
||||
return ResultInternalError;
|
||||
}
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result Read(size_t* out_size, std::span<u8> data) override {
|
||||
*out_size = 0;
|
||||
if (handshake_state != HandshakeState::Connected) {
|
||||
LOG_ERROR(Service_SSL, "Called Read but we did not successfully handshake");
|
||||
return ResultInternalError;
|
||||
}
|
||||
if (data.size() == 0 || got_read_eof) {
|
||||
return ResultSuccess;
|
||||
}
|
||||
while (1) {
|
||||
if (!cleartext_read_buf.empty()) {
|
||||
*out_size = (std::min)(cleartext_read_buf.size(), data.size());
|
||||
std::memcpy(data.data(), cleartext_read_buf.data(), *out_size);
|
||||
cleartext_read_buf.erase(cleartext_read_buf.begin(),
|
||||
cleartext_read_buf.begin() + *out_size);
|
||||
return ResultSuccess;
|
||||
}
|
||||
if (!ciphertext_read_buf.empty()) {
|
||||
SecBuffer empty{
|
||||
.cbBuffer = 0,
|
||||
.BufferType = SECBUFFER_EMPTY,
|
||||
.pvBuffer = nullptr,
|
||||
};
|
||||
std::array<SecBuffer, 5> buffers{{
|
||||
{
|
||||
.cbBuffer = static_cast<unsigned long>(ciphertext_read_buf.size()),
|
||||
.BufferType = SECBUFFER_DATA,
|
||||
.pvBuffer = ciphertext_read_buf.data(),
|
||||
},
|
||||
empty,
|
||||
empty,
|
||||
empty,
|
||||
}};
|
||||
ASSERT_OR_EXECUTE_MSG(
|
||||
buffers[0].cbBuffer == ciphertext_read_buf.size(),
|
||||
{ return ResultInternalError; }, "read buffer too large");
|
||||
SecBufferDesc desc{
|
||||
.ulVersion = SECBUFFER_VERSION,
|
||||
.cBuffers = static_cast<unsigned long>(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<u8*>(buffers[1].pvBuffer),
|
||||
static_cast<u8*>(buffers[1].pvBuffer) +
|
||||
buffers[1].cbBuffer);
|
||||
if (buffers[3].BufferType == SECBUFFER_EXTRA) {
|
||||
ASSERT(buffers[3].cbBuffer <= ciphertext_read_buf.size());
|
||||
ciphertext_read_buf.erase(ciphertext_read_buf.begin(),
|
||||
ciphertext_read_buf.end() - buffers[3].cbBuffer);
|
||||
} else {
|
||||
ASSERT(buffers[3].BufferType == SECBUFFER_EMPTY);
|
||||
ciphertext_read_buf.clear();
|
||||
}
|
||||
continue;
|
||||
case SEC_E_INCOMPLETE_MESSAGE:
|
||||
break;
|
||||
case SEC_I_CONTEXT_EXPIRED:
|
||||
// Server hung up by sending close_notify.
|
||||
got_read_eof = true;
|
||||
*out_size = 0;
|
||||
return ResultSuccess;
|
||||
default:
|
||||
LOG_ERROR(Service_SSL, "DecryptMessage failed: {}",
|
||||
Common::NativeErrorToString(ret));
|
||||
return ResultInternalError;
|
||||
}
|
||||
}
|
||||
const Result r = FillCiphertextReadBuf();
|
||||
if (r != ResultSuccess) {
|
||||
return r;
|
||||
}
|
||||
if (ciphertext_read_buf.empty()) {
|
||||
got_read_eof = true;
|
||||
*out_size = 0;
|
||||
return ResultSuccess;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Result Write(size_t* out_size, std::span<const u8> data) override {
|
||||
*out_size = 0;
|
||||
|
||||
if (handshake_state != HandshakeState::Connected) {
|
||||
LOG_ERROR(Service_SSL, "Called Write but we did not successfully handshake");
|
||||
return ResultInternalError;
|
||||
}
|
||||
if (data.size() == 0) {
|
||||
return ResultSuccess;
|
||||
}
|
||||
data = data.subspan(0, std::min<size_t>(data.size(), stream_sizes.cbMaximumMessage));
|
||||
if (!cleartext_write_buf.empty()) {
|
||||
// Already in the middle of a write. It wouldn't make sense to not
|
||||
// finish sending the entire buffer since TLS has
|
||||
// header/MAC/padding/etc.
|
||||
if (data.size() != cleartext_write_buf.size() ||
|
||||
std::memcmp(data.data(), cleartext_write_buf.data(), data.size())) {
|
||||
LOG_ERROR(Service_SSL, "Called Write but buffer does not match previous buffer");
|
||||
return ResultInternalError;
|
||||
}
|
||||
return WriteAlreadyEncryptedData(out_size);
|
||||
} else {
|
||||
cleartext_write_buf.assign(data.begin(), data.end());
|
||||
}
|
||||
|
||||
std::vector<u8> header_buf(stream_sizes.cbHeader, 0);
|
||||
std::vector<u8> tmp_data_buf = cleartext_write_buf;
|
||||
std::vector<u8> trailer_buf(stream_sizes.cbTrailer, 0);
|
||||
|
||||
std::array<SecBuffer, 3> buffers{{
|
||||
{
|
||||
.cbBuffer = stream_sizes.cbHeader,
|
||||
.BufferType = SECBUFFER_STREAM_HEADER,
|
||||
.pvBuffer = header_buf.data(),
|
||||
},
|
||||
{
|
||||
.cbBuffer = static_cast<unsigned long>(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<unsigned long>(buffers.size()),
|
||||
.pBuffers = buffers.data(),
|
||||
};
|
||||
|
||||
const SECURITY_STATUS ret = EncryptMessage(&ctxt, /*fQOP*/ 0, &desc, /*MessageSeqNo*/ 0);
|
||||
if (ret != SEC_E_OK) {
|
||||
LOG_ERROR(Service_SSL, "EncryptMessage failed: {}", Common::NativeErrorToString(ret));
|
||||
return ResultInternalError;
|
||||
}
|
||||
ciphertext_write_buf.insert(ciphertext_write_buf.end(), header_buf.begin(),
|
||||
header_buf.end());
|
||||
ciphertext_write_buf.insert(ciphertext_write_buf.end(), tmp_data_buf.begin(),
|
||||
tmp_data_buf.end());
|
||||
ciphertext_write_buf.insert(ciphertext_write_buf.end(), trailer_buf.begin(),
|
||||
trailer_buf.end());
|
||||
return WriteAlreadyEncryptedData(out_size);
|
||||
}
|
||||
|
||||
Result WriteAlreadyEncryptedData(size_t* out_size) {
|
||||
const Result r = FlushCiphertextWriteBuf();
|
||||
if (r != ResultSuccess) {
|
||||
return r;
|
||||
}
|
||||
// write buf is empty
|
||||
*out_size = cleartext_write_buf.size();
|
||||
cleartext_write_buf.clear();
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result GetServerCerts(std::vector<std::vector<u8>>* out_certs) override {
|
||||
PCCERT_CONTEXT returned_cert = nullptr;
|
||||
const SECURITY_STATUS ret =
|
||||
QueryContextAttributes(&ctxt, SECPKG_ATTR_REMOTE_CERT_CONTEXT, &returned_cert);
|
||||
if (ret != SEC_E_OK) {
|
||||
LOG_ERROR(Service_SSL,
|
||||
"QueryContextAttributes(SECPKG_ATTR_REMOTE_CERT_CONTEXT) failed: {}",
|
||||
Common::NativeErrorToString(ret));
|
||||
return ResultInternalError;
|
||||
}
|
||||
PCCERT_CONTEXT some_cert = nullptr;
|
||||
while ((some_cert = CertEnumCertificatesInStore(returned_cert->hCertStore, some_cert)) !=
|
||||
nullptr) {
|
||||
out_certs->emplace_back(static_cast<u8*>(some_cert->pbCertEncoded),
|
||||
static_cast<u8*>(some_cert->pbCertEncoded) +
|
||||
some_cert->cbCertEncoded);
|
||||
}
|
||||
std::reverse(out_certs->begin(),
|
||||
out_certs->end()); // Windows returns certs in reverse order from what we want
|
||||
CertFreeCertificateContext(returned_cert);
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
~SSLConnectionBackendSchannel() {
|
||||
if (handshake_state != HandshakeState::Initial) {
|
||||
DeleteSecurityContext(&ctxt);
|
||||
}
|
||||
}
|
||||
|
||||
enum class HandshakeState {
|
||||
// Haven't called anything yet.
|
||||
Initial,
|
||||
// `SEC_I_CONTINUE_NEEDED` was returned by
|
||||
// `InitializeSecurityContext`; must finish sending data (if any) in
|
||||
// the write buffer, then read at least one byte before calling
|
||||
// `InitializeSecurityContext` again.
|
||||
ContinueNeeded,
|
||||
// `SEC_E_INCOMPLETE_MESSAGE` was returned by
|
||||
// `InitializeSecurityContext`; hopefully the write buffer is empty;
|
||||
// must read at least one byte before calling
|
||||
// `InitializeSecurityContext` again.
|
||||
IncompleteMessage,
|
||||
// `SEC_E_OK` was returned by `InitializeSecurityContext`; must
|
||||
// finish sending data in the write buffer before having `DoHandshake`
|
||||
// report success.
|
||||
DoneAfterFlush,
|
||||
// We finished the above and are now connected. At this point, writing
|
||||
// and reading are separate 'state machines' represented by the
|
||||
// nonemptiness of the ciphertext and cleartext read and write buffers.
|
||||
Connected,
|
||||
// Another error was returned and we shouldn't allow initialization
|
||||
// to continue.
|
||||
Error,
|
||||
} handshake_state = HandshakeState::Initial;
|
||||
|
||||
CtxtHandle ctxt;
|
||||
SecPkgContext_StreamSizes stream_sizes;
|
||||
|
||||
std::shared_ptr<Network::SocketBase> socket;
|
||||
std::optional<std::string> hostname;
|
||||
|
||||
std::vector<u8> ciphertext_read_buf;
|
||||
std::vector<u8> ciphertext_write_buf;
|
||||
std::vector<u8> cleartext_read_buf;
|
||||
std::vector<u8> cleartext_write_buf;
|
||||
|
||||
bool got_read_eof = false;
|
||||
bool skip_cert_verification = false;
|
||||
size_t read_buf_fill_size = 0;
|
||||
};
|
||||
|
||||
Result CreateSSLConnectionBackend(std::unique_ptr<SSLConnectionBackend>* out_backend) {
|
||||
auto conn = std::make_unique<SSLConnectionBackendSchannel>();
|
||||
|
||||
R_TRY(conn->Init());
|
||||
|
||||
*out_backend = std::move(conn);
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
} // namespace Service::SSL
|
||||
@@ -1,236 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <mutex>
|
||||
|
||||
// SecureTransport has been deprecated in its entirety in favor of
|
||||
// Network.framework, but that does not allow layering TLS on top of an
|
||||
// arbitrary socket.
|
||||
#if defined(__GNUC__) || defined(__clang__)
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
|
||||
#include <Security/SecureTransport.h>
|
||||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
|
||||
#include "core/hle/service/ssl/ssl_backend.h"
|
||||
#include "core/internal_network/network.h"
|
||||
#include "core/internal_network/sockets.h"
|
||||
|
||||
namespace {
|
||||
|
||||
template <typename T>
|
||||
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<CFDataRef> cfdata(
|
||||
CFStringCreateExternalRepresentation(nullptr, cfstr, kCFStringEncodingUTF8, 0));
|
||||
ASSERT_OR_EXECUTE(cfdata, { return "???"; });
|
||||
return std::string(reinterpret_cast<const char*>(CFDataGetBytePtr(cfdata)),
|
||||
CFDataGetLength(cfdata));
|
||||
}
|
||||
|
||||
std::string OSStatusToString(OSStatus status) {
|
||||
CFReleaser<CFStringRef> 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<Network::SocketBase> in_socket) override {
|
||||
socket = std::move(in_socket);
|
||||
}
|
||||
|
||||
Result SetHostName(const std::string& hostname) override {
|
||||
OSStatus status = SSLSetPeerDomainName(context, hostname.c_str(), hostname.size());
|
||||
if (status) {
|
||||
LOG_ERROR(Service_SSL, "SSLSetPeerDomainName failed: {}", OSStatusToString(status));
|
||||
return ResultInternalError;
|
||||
}
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
void SetVerifyOption(u32 option) override {
|
||||
skip_cert_verification = (option == 0);
|
||||
LOG_WARNING(Service_SSL, "option={} skip_verification={}", option,
|
||||
skip_cert_verification);
|
||||
if (skip_cert_verification) {
|
||||
SSLSetSessionOption(context, kSSLSessionOptionBreakOnServerAuth, true);
|
||||
}
|
||||
}
|
||||
|
||||
Result DoHandshake() override {
|
||||
OSStatus status = SSLHandshake(context);
|
||||
|
||||
if (skip_cert_verification && status == errSSLServerAuthCompleted) {
|
||||
LOG_DEBUG(Service_SSL, "Skipping certificate verification as requested");
|
||||
status = SSLHandshake(context);
|
||||
}
|
||||
|
||||
return HandleReturn("SSLHandshake", 0, status);
|
||||
}
|
||||
|
||||
Result Read(size_t* out_size, std::span<u8> data) override {
|
||||
OSStatus status = SSLRead(context, data.data(), data.size(), out_size);
|
||||
return HandleReturn("SSLRead", out_size, status);
|
||||
}
|
||||
|
||||
Result Write(size_t* out_size, std::span<const u8> data) override {
|
||||
OSStatus status = SSLWrite(context, data.data(), data.size(), out_size);
|
||||
return HandleReturn("SSLWrite", out_size, status);
|
||||
}
|
||||
|
||||
Result HandleReturn(const char* what, size_t* actual, OSStatus status) {
|
||||
switch (status) {
|
||||
case 0:
|
||||
return ResultSuccess;
|
||||
case errSSLWouldBlock:
|
||||
return ResultWouldBlock;
|
||||
default: {
|
||||
std::string reason;
|
||||
if (got_read_eof) {
|
||||
reason = "server hung up";
|
||||
} else {
|
||||
reason = OSStatusToString(status);
|
||||
}
|
||||
LOG_ERROR(Service_SSL, "{} failed: {}", what, reason);
|
||||
return ResultInternalError;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Result GetServerCerts(std::vector<std::vector<u8>>* out_certs) override {
|
||||
CFReleaser<SecTrustRef> trust;
|
||||
OSStatus status = SSLCopyPeerTrust(context, &trust.ptr);
|
||||
if (status) {
|
||||
LOG_ERROR(Service_SSL, "SSLCopyPeerTrust failed: {}", OSStatusToString(status));
|
||||
return ResultInternalError;
|
||||
}
|
||||
for (CFIndex i = 0, count = SecTrustGetCertificateCount(trust); i < count; i++) {
|
||||
SecCertificateRef cert = SecTrustGetCertificateAtIndex(trust, i);
|
||||
CFReleaser<CFDataRef> data(SecCertificateCopyData(cert));
|
||||
ASSERT_OR_EXECUTE(data, { return ResultInternalError; });
|
||||
const u8* ptr = CFDataGetBytePtr(data);
|
||||
out_certs->emplace_back(ptr, ptr + CFDataGetLength(data));
|
||||
}
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
static OSStatus ReadCallback(SSLConnectionRef connection, void* data, size_t* dataLength) {
|
||||
return ReadOrWriteCallback(connection, data, dataLength, true);
|
||||
}
|
||||
|
||||
static OSStatus WriteCallback(SSLConnectionRef connection, const void* data,
|
||||
size_t* dataLength) {
|
||||
return ReadOrWriteCallback(connection, const_cast<void*>(data), dataLength, false);
|
||||
}
|
||||
|
||||
static OSStatus ReadOrWriteCallback(SSLConnectionRef connection, void* data, size_t* dataLength,
|
||||
bool is_read) {
|
||||
auto self =
|
||||
static_cast<SSLConnectionBackendSecureTransport*>(const_cast<void*>(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<u8*>(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<s32>(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<SSLContextRef> context = nullptr;
|
||||
bool got_read_eof = false;
|
||||
bool skip_cert_verification = false;
|
||||
|
||||
std::shared_ptr<Network::SocketBase> socket;
|
||||
};
|
||||
|
||||
Result CreateSSLConnectionBackend(std::unique_ptr<SSLConnectionBackend>* out_backend) {
|
||||
auto conn = std::make_unique<SSLConnectionBackendSecureTransport>();
|
||||
|
||||
R_TRY(conn->Init());
|
||||
|
||||
*out_backend = std::move(conn);
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
} // namespace Service::SSL
|
||||
@@ -151,9 +151,6 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QObject* parent) {
|
||||
tr("The anti-aliasing method to use.\nSMAA offers the best quality.\nFXAA "
|
||||
"can produce a more stable picture in lower resolutions."));
|
||||
INSERT(Settings, post_shader_chain, QString(), QString());
|
||||
INSERT(Settings, post_shader_preset, QString(), QString());
|
||||
INSERT(Settings, post_shader_enabled, tr("Enable post-processing effects"),
|
||||
tr("Applies post-processing effects to the final image."));
|
||||
INSERT(Settings, fullscreen_mode, tr("Fullscreen Mode:"),
|
||||
tr("The method used to render the window in fullscreen.\nBorderless offers the best "
|
||||
"compatibility with the on-screen keyboard that some games request for "
|
||||
|
||||
@@ -37,10 +37,6 @@
|
||||
#include "yuzu/configuration/configure_graphics.h"
|
||||
#include "yuzu/configuration/shared_widget.h"
|
||||
|
||||
#ifdef HAS_RESHADE
|
||||
#include "yuzu/configuration/configure_post_processing.h"
|
||||
#endif
|
||||
|
||||
ConfigureGraphics::ConfigureGraphics(
|
||||
const Core::System& system_, std::vector<VkDeviceInfo::Record>& records_,
|
||||
const std::function<void()>& expose_compute_option_,
|
||||
@@ -220,12 +216,6 @@ void ConfigureGraphics::Setup(const ConfigurationShared::Builder& builder) {
|
||||
std::vector<QWidget*> hold_api;
|
||||
|
||||
for (const auto setting : Settings::values.linkage.by_category[Settings::Category::Renderer]) {
|
||||
#ifndef HAS_RESHADE
|
||||
if (setting->Id() == Settings::values.post_shader_enabled.Id()) {
|
||||
continue;
|
||||
}
|
||||
#endif
|
||||
|
||||
ConfigurationShared::Widget* widget = [&]() {
|
||||
if (setting->Id() == Settings::values.fsr_sharpening_slider.Id()) {
|
||||
// FSR needs a reversed slider and a 0.5 multiplier
|
||||
@@ -305,23 +295,6 @@ void ConfigureGraphics::Setup(const ConfigurationShared::Builder& builder) {
|
||||
// Keep track of the resolution combobox to update other UI tabs that need it
|
||||
resolution_combobox = widget->combobox;
|
||||
hold_graphics.emplace(setting->Id(), widget);
|
||||
#ifdef HAS_RESHADE
|
||||
} else if (setting->Id() == Settings::values.post_shader_enabled.Id()) {
|
||||
QPushButton* post_shader_button = new QPushButton(tr("Configure Effects..."), widget);
|
||||
post_shader_button->setVisible(widget->checkbox->isChecked());
|
||||
|
||||
connect(post_shader_button, &QAbstractButton::clicked, this, [this]() {
|
||||
ConfigurePostProcessing dialog(this);
|
||||
dialog.exec();
|
||||
});
|
||||
connect(widget->checkbox, &QCheckBox::toggled, post_shader_button,
|
||||
&QWidget::setVisible);
|
||||
|
||||
QBoxLayout* row = qobject_cast<QBoxLayout*>(widget->layout());
|
||||
row->insertWidget(1, post_shader_button);
|
||||
|
||||
hold_graphics.emplace(setting->Id(), widget);
|
||||
#endif
|
||||
} else {
|
||||
hold_graphics.emplace(setting->Id(), widget);
|
||||
}
|
||||
|
||||
@@ -70,8 +70,10 @@ ConfigurePostProcessing::ConfigurePostProcessing(QWidget* parent) : QDialog(pare
|
||||
|
||||
auto* root = new QVBoxLayout(this);
|
||||
|
||||
auto* description =
|
||||
new QLabel(tr("Changes apply immediately while a game is running."), this);
|
||||
auto* description = new QLabel(
|
||||
tr("ReShade FX effects are loaded from the post_shaders folder in the Eden data "
|
||||
"directory. Changes apply immediately while a game is running."),
|
||||
this);
|
||||
description->setWordWrap(true);
|
||||
root->addWidget(description);
|
||||
|
||||
@@ -126,15 +128,17 @@ ConfigurePostProcessing::ConfigurePostProcessing(QWidget* parent) : QDialog(pare
|
||||
});
|
||||
preset_row->addWidget(save_button);
|
||||
|
||||
auto* clear_button = new QPushButton(tr("Clear"), this);
|
||||
clear_button->setToolTip(tr("Remove every effect in use and deselect the preset."));
|
||||
connect(clear_button, &QPushButton::clicked, this, [this]() {
|
||||
VideoCore::FxChain::Instance().Clear();
|
||||
VideoCore::SetActiveFxPreset(std::string());
|
||||
preset_combo->setCurrentIndex(0);
|
||||
ApplyStructuralChange();
|
||||
auto* delete_button = new QPushButton(tr("Delete"), this);
|
||||
connect(delete_button, &QPushButton::clicked, this, [this]() {
|
||||
const QString name = preset_combo->currentData().toString();
|
||||
if (name.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
VideoCore::DeleteFxPreset(name.toStdString());
|
||||
PopulatePresetCombo();
|
||||
RefreshPresetStatus();
|
||||
});
|
||||
preset_row->addWidget(clear_button);
|
||||
preset_row->addWidget(delete_button);
|
||||
|
||||
root->addLayout(preset_row);
|
||||
|
||||
@@ -199,7 +203,6 @@ void ConfigurePostProcessing::ApplyStructuralChange() {
|
||||
void ConfigurePostProcessing::PopulatePresetCombo() {
|
||||
const QString previous = preset_combo->currentData().toString();
|
||||
preset_combo->clear();
|
||||
preset_combo->addItem(tr("None"), QString());
|
||||
|
||||
for (const auto& preset : VideoCore::GetFxPresetCatalog()) {
|
||||
const QString name = QString::fromStdString(preset.name);
|
||||
@@ -209,7 +212,7 @@ void ConfigurePostProcessing::PopulatePresetCombo() {
|
||||
}
|
||||
|
||||
const int restored = preset_combo->findData(previous);
|
||||
if (!previous.isEmpty() && restored >= 0) {
|
||||
if (restored >= 0) {
|
||||
preset_combo->setCurrentIndex(restored);
|
||||
return;
|
||||
}
|
||||
@@ -217,21 +220,12 @@ void ConfigurePostProcessing::PopulatePresetCombo() {
|
||||
const int active = preset_combo->findData(QString::fromStdString(VideoCore::GetActiveFxPreset()));
|
||||
if (active >= 0) {
|
||||
preset_combo->setCurrentIndex(active);
|
||||
return;
|
||||
}
|
||||
|
||||
preset_combo->setCurrentIndex(0);
|
||||
}
|
||||
|
||||
void ConfigurePostProcessing::RefreshPresetStatus() {
|
||||
const std::string active = VideoCore::GetActiveFxPreset();
|
||||
if (active.empty()) {
|
||||
if (VideoCore::FxChain::Instance().Size() == 0) {
|
||||
preset_status->setText(
|
||||
tr("No effects in use. Pick a preset and press Apply, or add effects one "
|
||||
"by one."));
|
||||
return;
|
||||
}
|
||||
preset_status->setText(tr("Custom chain. Pick a preset above and press Apply to replace it."));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
#include "render/performance_overlay.h"
|
||||
#ifdef HAS_RESHADE
|
||||
#include "configuration/configure_post_processing.h"
|
||||
#include "video_core/post_processing/fx_preset.h"
|
||||
#endif
|
||||
#include "updater/update_dialog.h"
|
||||
|
||||
@@ -1091,41 +1090,6 @@ void MainWindow::InitializeWidgets() {
|
||||
|
||||
statusBar()->insertPermanentWidget(0, volume_button);
|
||||
|
||||
#ifdef HAS_RESHADE
|
||||
post_shader_status_button = new QPushButton();
|
||||
post_shader_status_button->setObjectName(QStringLiteral("TogglableStatusBarButton"));
|
||||
post_shader_status_button->setFocusPolicy(Qt::NoFocus);
|
||||
post_shader_status_button->setCheckable(true);
|
||||
connect(post_shader_status_button, &QPushButton::clicked, this, [this] {
|
||||
const bool enabled = Settings::values.post_shader_enabled.GetValue();
|
||||
Settings::values.post_shader_enabled.SetValue(!enabled);
|
||||
UpdatePostShaderText();
|
||||
});
|
||||
UpdatePostShaderText();
|
||||
post_shader_status_button->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
connect(post_shader_status_button, &QPushButton::customContextMenuRequested,
|
||||
[this](const QPoint& menu_location) {
|
||||
QMenu context_menu;
|
||||
|
||||
for (auto const& preset : VideoCore::GetFxPresetCatalog()) {
|
||||
context_menu.addAction(QString::fromStdString(preset.name),
|
||||
[this, name = preset.name] {
|
||||
VideoCore::ApplyFxPreset(name);
|
||||
Settings::values.post_shader_enabled.SetValue(true);
|
||||
UpdatePostShaderText();
|
||||
});
|
||||
}
|
||||
|
||||
context_menu.addSeparator();
|
||||
context_menu.addAction(tr("Configure Effects..."), this,
|
||||
&MainWindow::OnPostProcessingShaders);
|
||||
|
||||
context_menu.exec(post_shader_status_button->mapToGlobal(menu_location));
|
||||
post_shader_status_button->repaint();
|
||||
});
|
||||
statusBar()->insertPermanentWidget(0, post_shader_status_button);
|
||||
#endif
|
||||
|
||||
// setup AA button
|
||||
aa_status_button = new QPushButton();
|
||||
aa_status_button->setObjectName(QStringLiteral("TogglableStatusBarButton"));
|
||||
@@ -3944,7 +3908,6 @@ void MainWindow::OnPostProcessingShaders() {
|
||||
connect(post_processing_dialog, &QDialog::finished, post_processing_dialog, [this]() {
|
||||
post_processing_dialog->deleteLater();
|
||||
post_processing_dialog = nullptr;
|
||||
UpdatePostShaderText();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -4299,26 +4262,6 @@ void MainWindow::UpdateAAText() {
|
||||
: aa_text.toUpper());
|
||||
}
|
||||
|
||||
#ifdef HAS_RESHADE
|
||||
void MainWindow::UpdatePostShaderText() {
|
||||
const bool enabled = Settings::values.post_shader_enabled.GetValue();
|
||||
post_shader_status_button->setChecked(enabled);
|
||||
|
||||
if (!enabled) {
|
||||
post_shader_status_button->setText(tr("NO FX"));
|
||||
return;
|
||||
}
|
||||
|
||||
const std::string preset = VideoCore::GetActiveFxPreset();
|
||||
if (preset.empty()) {
|
||||
post_shader_status_button->setText(tr("FX"));
|
||||
return;
|
||||
}
|
||||
|
||||
post_shader_status_button->setText(QString::fromStdString(preset).toUpper());
|
||||
}
|
||||
#endif
|
||||
|
||||
void MainWindow::UpdateVolumeUI() {
|
||||
const auto volume_value = static_cast<int>(Settings::values.volume.GetValue());
|
||||
volume_slider->setValue(volume_value);
|
||||
|
||||
@@ -448,7 +448,6 @@ private:
|
||||
void UpdateAPIText();
|
||||
void UpdateFilterText();
|
||||
void UpdateAAText();
|
||||
void UpdatePostShaderText();
|
||||
void UpdateVolumeUI();
|
||||
void UpdateStatusBar();
|
||||
void UpdateGPUAccuracyButton();
|
||||
@@ -525,7 +524,6 @@ private:
|
||||
QPushButton* dock_status_button = nullptr;
|
||||
QPushButton* filter_status_button = nullptr;
|
||||
QPushButton* aa_status_button = nullptr;
|
||||
QPushButton* post_shader_status_button = nullptr;
|
||||
VolumeButton* volume_button = nullptr;
|
||||
QWidget* volume_popup = nullptr;
|
||||
QSlider* volume_slider = nullptr;
|
||||
|
||||
Reference in New Issue
Block a user