mirror of
https://git.eden-emu.dev/archive/citron
synced 2026-04-12 14:18:49 -04:00
fix(ssl): implement SetVerifyOption instead of stubbing it
Games can now properly disable SSL certificate verification by setting option=0, which was previously ignored causing handshake failures. Thanks to Raytwo and DogeThis (https://github.com/Raytwo/Cobalt) for helping debug this issue. Signed-off-by: Zephyron <zephyron@citron-emu.org>
This commit is contained in:
@@ -146,6 +146,7 @@ private:
|
|||||||
bool get_server_cert_chain = false;
|
bool get_server_cert_chain = false;
|
||||||
std::shared_ptr<Network::SocketBase> socket;
|
std::shared_ptr<Network::SocketBase> socket;
|
||||||
bool did_handshake = false;
|
bool did_handshake = false;
|
||||||
|
u32 verify_option = 0;
|
||||||
|
|
||||||
Result SetSocketDescriptorImpl(s32* out_fd, s32 fd) {
|
Result SetSocketDescriptorImpl(s32* out_fd, s32 fd) {
|
||||||
LOG_DEBUG(Service_SSL, "called, fd={}", fd);
|
LOG_DEBUG(Service_SSL, "called, fd={}", fd);
|
||||||
@@ -184,8 +185,9 @@ private:
|
|||||||
|
|
||||||
Result SetVerifyOptionImpl(u32 option) {
|
Result SetVerifyOptionImpl(u32 option) {
|
||||||
ASSERT(!did_handshake);
|
ASSERT(!did_handshake);
|
||||||
LOG_WARNING(Service_SSL, "(STUBBED) called. option={}", option);
|
LOG_DEBUG(Service_SSL, "called. option={}", option);
|
||||||
return ResultSuccess;
|
verify_option = option;
|
||||||
|
return backend->SetVerifyOption(option);
|
||||||
}
|
}
|
||||||
|
|
||||||
Result SetIoModeImpl(u32 input_mode) {
|
Result SetIoModeImpl(u32 input_mode) {
|
||||||
@@ -387,11 +389,11 @@ private:
|
|||||||
}
|
}
|
||||||
|
|
||||||
void GetVerifyOption(HLERequestContext& ctx) {
|
void GetVerifyOption(HLERequestContext& ctx) {
|
||||||
LOG_WARNING(Service_SSL, "(STUBBED) called");
|
LOG_DEBUG(Service_SSL, "called, returning verify_option={}", verify_option);
|
||||||
|
|
||||||
IPC::ResponseBuilder rb{ctx, 3};
|
IPC::ResponseBuilder rb{ctx, 3};
|
||||||
rb.Push(ResultSuccess);
|
rb.Push(ResultSuccess);
|
||||||
rb.Push<u32>(0); // Stub: return default verify option
|
rb.Push<u32>(verify_option);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GetIoMode(HLERequestContext& ctx) {
|
void GetIoMode(HLERequestContext& ctx) {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2026 citron Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
@@ -34,6 +35,7 @@ public:
|
|||||||
virtual ~SSLConnectionBackend() {}
|
virtual ~SSLConnectionBackend() {}
|
||||||
virtual void SetSocket(std::shared_ptr<Network::SocketBase> socket) = 0;
|
virtual void SetSocket(std::shared_ptr<Network::SocketBase> socket) = 0;
|
||||||
virtual Result SetHostName(const std::string& hostname) = 0;
|
virtual Result SetHostName(const std::string& hostname) = 0;
|
||||||
|
virtual Result SetVerifyOption(u32 verify_option) = 0;
|
||||||
virtual Result DoHandshake() = 0;
|
virtual Result DoHandshake() = 0;
|
||||||
virtual Result Read(size_t* out_size, std::span<u8> data) = 0;
|
virtual Result Read(size_t* out_size, std::span<u8> data) = 0;
|
||||||
virtual Result Write(size_t* out_size, std::span<const u8> data) = 0;
|
virtual Result Write(size_t* out_size, std::span<const u8> data) = 0;
|
||||||
|
|||||||
@@ -22,6 +22,11 @@ public:
|
|||||||
return ResultSuccess;
|
return ResultSuccess;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Result SetVerifyOption(u32 verify_option) override {
|
||||||
|
LOG_WARNING(Service_SSL, "(STUBBED) SetVerifyOption option={}", verify_option);
|
||||||
|
return ResultSuccess;
|
||||||
|
}
|
||||||
|
|
||||||
Result DoHandshake() override {
|
Result DoHandshake() override {
|
||||||
LOG_WARNING(Service_SSL, "(STUBBED) Pretending to do TLS handshake");
|
LOG_WARNING(Service_SSL, "(STUBBED) Pretending to do TLS handshake");
|
||||||
return ResultSuccess;
|
return ResultSuccess;
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2026 citron Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
@@ -88,15 +89,43 @@ public:
|
|||||||
return ResultSuccess;
|
return ResultSuccess;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Result SetVerifyOption(u32 verify_option) override {
|
||||||
|
// verify_option is a bitfield:
|
||||||
|
// Bit 0: PeerCa - verify peer certificate
|
||||||
|
// Bit 1: HostName - verify hostname matches certificate
|
||||||
|
// Bit 2: DateCheck - verify certificate date
|
||||||
|
// Bit 3: EvPolicyOid - verify EV policy OID
|
||||||
|
// Bit 4: ChainSignature - verify chain signatures
|
||||||
|
// Bit 5 and above: Reserved
|
||||||
|
// When verify_option is 0, skip all verification
|
||||||
|
skip_cert_verification = (verify_option == 0);
|
||||||
|
LOG_DEBUG(Service_SSL, "SetVerifyOption: option={}, skip_verification={}", verify_option,
|
||||||
|
skip_cert_verification);
|
||||||
|
|
||||||
|
if (skip_cert_verification) {
|
||||||
|
// Disable certificate verification
|
||||||
|
SSL_set_verify(ssl, SSL_VERIFY_NONE, nullptr);
|
||||||
|
} else {
|
||||||
|
// Enable certificate verification
|
||||||
|
SSL_set_verify(ssl, SSL_VERIFY_PEER, nullptr);
|
||||||
|
}
|
||||||
|
return ResultSuccess;
|
||||||
|
}
|
||||||
|
|
||||||
Result DoHandshake() override {
|
Result DoHandshake() override {
|
||||||
SSL_set_verify_result(ssl, X509_V_OK);
|
SSL_set_verify_result(ssl, X509_V_OK);
|
||||||
const int ret = SSL_do_handshake(ssl);
|
const int ret = SSL_do_handshake(ssl);
|
||||||
const long verify_result = SSL_get_verify_result(ssl);
|
|
||||||
if (verify_result != X509_V_OK) {
|
// Only check verification result if verification is enabled
|
||||||
LOG_ERROR(Service_SSL, "SSL cert verification failed because: {}",
|
if (!skip_cert_verification) {
|
||||||
X509_verify_cert_error_string(verify_result));
|
const long verify_result = SSL_get_verify_result(ssl);
|
||||||
return CheckOpenSSLErrors();
|
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) {
|
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 ||
|
if (ssl_err == SSL_ERROR_ZERO_RETURN ||
|
||||||
@@ -247,6 +276,7 @@ public:
|
|||||||
SSL* ssl = nullptr;
|
SSL* ssl = nullptr;
|
||||||
BIO* bio = nullptr;
|
BIO* bio = nullptr;
|
||||||
bool got_read_eof = false;
|
bool got_read_eof = false;
|
||||||
|
bool skip_cert_verification = false;
|
||||||
|
|
||||||
std::shared_ptr<Network::SocketBase> socket;
|
std::shared_ptr<Network::SocketBase> socket;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2026 citron Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
@@ -86,6 +87,18 @@ public:
|
|||||||
return ResultSuccess;
|
return ResultSuccess;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Result SetVerifyOption(u32 verify_option) override {
|
||||||
|
// verify_option is a bitfield:
|
||||||
|
// Bit 0: PeerCa - verify peer certificate
|
||||||
|
// Bit 1: HostName - verify hostname matches certificate
|
||||||
|
// Bit 2: DateCheck - verify certificate date
|
||||||
|
// When verify_option is 0, skip all verification
|
||||||
|
skip_cert_verification = (verify_option == 0);
|
||||||
|
LOG_DEBUG(Service_SSL, "SetVerifyOption: option={}, skip_verification={}", verify_option,
|
||||||
|
skip_cert_verification);
|
||||||
|
return ResultSuccess;
|
||||||
|
}
|
||||||
|
|
||||||
Result DoHandshake() override {
|
Result DoHandshake() override {
|
||||||
while (1) {
|
while (1) {
|
||||||
Result r;
|
Result r;
|
||||||
@@ -172,10 +185,16 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
Result CallInitializeSecurityContext() {
|
Result CallInitializeSecurityContext() {
|
||||||
const unsigned long req = ISC_REQ_ALLOCATE_MEMORY | ISC_REQ_CONFIDENTIALITY |
|
unsigned long req = ISC_REQ_ALLOCATE_MEMORY | ISC_REQ_CONFIDENTIALITY |
|
||||||
ISC_REQ_INTEGRITY | ISC_REQ_REPLAY_DETECT |
|
ISC_REQ_INTEGRITY | ISC_REQ_REPLAY_DETECT |
|
||||||
ISC_REQ_SEQUENCE_DETECT | ISC_REQ_STREAM |
|
ISC_REQ_SEQUENCE_DETECT | ISC_REQ_STREAM |
|
||||||
ISC_REQ_USE_SUPPLIED_CREDS;
|
ISC_REQ_USE_SUPPLIED_CREDS;
|
||||||
|
|
||||||
|
// When certificate verification is disabled, use manual credential validation
|
||||||
|
// This allows the handshake to succeed even with invalid/self-signed certificates
|
||||||
|
if (skip_cert_verification) {
|
||||||
|
req |= ISC_REQ_MANUAL_CRED_VALIDATION;
|
||||||
|
}
|
||||||
unsigned long attr;
|
unsigned long attr;
|
||||||
// https://learn.microsoft.com/en-us/windows/win32/secauthn/initializesecuritycontext--schannel
|
// https://learn.microsoft.com/en-us/windows/win32/secauthn/initializesecuritycontext--schannel
|
||||||
std::array<SecBuffer, 2> input_buffers{{
|
std::array<SecBuffer, 2> input_buffers{{
|
||||||
@@ -533,6 +552,7 @@ public:
|
|||||||
std::vector<u8> cleartext_write_buf;
|
std::vector<u8> cleartext_write_buf;
|
||||||
|
|
||||||
bool got_read_eof = false;
|
bool got_read_eof = false;
|
||||||
|
bool skip_cert_verification = false;
|
||||||
size_t read_buf_fill_size = 0;
|
size_t read_buf_fill_size = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2026 citron Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
@@ -98,8 +99,38 @@ public:
|
|||||||
return ResultSuccess;
|
return ResultSuccess;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Result SetVerifyOption(u32 verify_option) override {
|
||||||
|
// verify_option is a bitfield:
|
||||||
|
// Bit 0: PeerCa - verify peer certificate
|
||||||
|
// Bit 1: HostName - verify hostname matches certificate
|
||||||
|
// Bit 2: DateCheck - verify certificate date
|
||||||
|
// When verify_option is 0, skip all verification
|
||||||
|
skip_cert_verification = (verify_option == 0);
|
||||||
|
LOG_DEBUG(Service_SSL, "SetVerifyOption: option={}, skip_verification={}", verify_option,
|
||||||
|
skip_cert_verification);
|
||||||
|
|
||||||
|
if (skip_cert_verification) {
|
||||||
|
// Break on server auth to allow us to bypass certificate verification
|
||||||
|
OSStatus status = SSLSetSessionOption(context, kSSLSessionOptionBreakOnServerAuth, true);
|
||||||
|
if (status) {
|
||||||
|
LOG_ERROR(Service_SSL, "SSLSetSessionOption(kSSLSessionOptionBreakOnServerAuth) failed: {}",
|
||||||
|
OSStatusToString(status));
|
||||||
|
return ResultInternalError;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ResultSuccess;
|
||||||
|
}
|
||||||
|
|
||||||
Result DoHandshake() override {
|
Result DoHandshake() override {
|
||||||
OSStatus status = SSLHandshake(context);
|
OSStatus status = SSLHandshake(context);
|
||||||
|
|
||||||
|
// If we're skipping verification and got errSSLServerAuthCompleted,
|
||||||
|
// continue the handshake without verifying the certificate
|
||||||
|
if (skip_cert_verification && status == errSSLServerAuthCompleted) {
|
||||||
|
LOG_DEBUG(Service_SSL, "Skipping certificate verification as requested");
|
||||||
|
status = SSLHandshake(context);
|
||||||
|
}
|
||||||
|
|
||||||
return HandleReturn("SSLHandshake", 0, status);
|
return HandleReturn("SSLHandshake", 0, status);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -201,6 +232,7 @@ public:
|
|||||||
private:
|
private:
|
||||||
CFReleaser<SSLContextRef> context = nullptr;
|
CFReleaser<SSLContextRef> context = nullptr;
|
||||||
bool got_read_eof = false;
|
bool got_read_eof = false;
|
||||||
|
bool skip_cert_verification = false;
|
||||||
|
|
||||||
std::shared_ptr<Network::SocketBase> socket;
|
std::shared_ptr<Network::SocketBase> socket;
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user