mirror of
https://git.citron-emu.org/citron/emulator
synced 2026-01-25 12:13:27 +00: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;
|
||||
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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user