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:
Zephyron
2026-01-24 14:46:34 +10:00
parent 31a2a6212d
commit 44f9cb6347
6 changed files with 104 additions and 13 deletions

View File

@@ -146,6 +146,7 @@ private:
bool get_server_cert_chain = false;
std::shared_ptr<Network::SocketBase> socket;
bool did_handshake = false;
u32 verify_option = 0;
Result SetSocketDescriptorImpl(s32* out_fd, s32 fd) {
LOG_DEBUG(Service_SSL, "called, fd={}", fd);
@@ -184,8 +185,9 @@ private:
Result SetVerifyOptionImpl(u32 option) {
ASSERT(!did_handshake);
LOG_WARNING(Service_SSL, "(STUBBED) called. option={}", option);
return ResultSuccess;
LOG_DEBUG(Service_SSL, "called. option={}", option);
verify_option = option;
return backend->SetVerifyOption(option);
}
Result SetIoModeImpl(u32 input_mode) {
@@ -387,11 +389,11 @@ private:
}
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};
rb.Push(ResultSuccess);
rb.Push<u32>(0); // Stub: return default verify option
rb.Push<u32>(verify_option);
}
void GetIoMode(HLERequestContext& ctx) {

View File

@@ -1,4 +1,5 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 citron Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -34,6 +35,7 @@ public:
virtual ~SSLConnectionBackend() {}
virtual void SetSocket(std::shared_ptr<Network::SocketBase> socket) = 0;
virtual Result SetHostName(const std::string& hostname) = 0;
virtual Result SetVerifyOption(u32 verify_option) = 0;
virtual Result DoHandshake() = 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;

View File

@@ -22,6 +22,11 @@ public:
return ResultSuccess;
}
Result SetVerifyOption(u32 verify_option) override {
LOG_WARNING(Service_SSL, "(STUBBED) SetVerifyOption option={}", verify_option);
return ResultSuccess;
}
Result DoHandshake() override {
LOG_WARNING(Service_SSL, "(STUBBED) Pretending to do TLS handshake");
return ResultSuccess;

View File

@@ -1,4 +1,5 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 citron Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <mutex>
@@ -88,15 +89,43 @@ public:
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 {
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();
// Only check verification result if verification is enabled
if (!skip_cert_verification) {
const long verify_result = SSL_get_verify_result(ssl);
if (verify_result != X509_V_OK) {
LOG_ERROR(Service_SSL, "SSL cert verification failed because: {}",
X509_verify_cert_error_string(verify_result));
return CheckOpenSSLErrors();
}
}
if (ret <= 0) {
const int ssl_err = SSL_get_error(ssl, ret);
if (ssl_err == SSL_ERROR_ZERO_RETURN ||
@@ -247,6 +276,7 @@ public:
SSL* ssl = nullptr;
BIO* bio = nullptr;
bool got_read_eof = false;
bool skip_cert_verification = false;
std::shared_ptr<Network::SocketBase> socket;
};

View File

@@ -1,4 +1,5 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 citron Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <mutex>
@@ -86,6 +87,18 @@ public:
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 {
while (1) {
Result r;
@@ -172,10 +185,16 @@ public:
}
Result CallInitializeSecurityContext() {
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 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;
// 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;
// https://learn.microsoft.com/en-us/windows/win32/secauthn/initializesecuritycontext--schannel
std::array<SecBuffer, 2> input_buffers{{
@@ -533,6 +552,7 @@ public:
std::vector<u8> cleartext_write_buf;
bool got_read_eof = false;
bool skip_cert_verification = false;
size_t read_buf_fill_size = 0;
};

View File

@@ -1,4 +1,5 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 citron Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <mutex>
@@ -98,8 +99,38 @@ public:
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 {
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);
}
@@ -201,6 +232,7 @@ public:
private:
CFReleaser<SSLContextRef> context = nullptr;
bool got_read_eof = false;
bool skip_cert_verification = false;
std::shared_ptr<Network::SocketBase> socket;
};