diff --git a/src/core/hle/service/ssl/ssl.cpp b/src/core/hle/service/ssl/ssl.cpp index efee7f279..505c58492 100644 --- a/src/core/hle/service/ssl/ssl.cpp +++ b/src/core/hle/service/ssl/ssl.cpp @@ -146,6 +146,7 @@ private: bool get_server_cert_chain = false; std::shared_ptr 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(0); // Stub: return default verify option + rb.Push(verify_option); } void GetIoMode(HLERequestContext& ctx) { diff --git a/src/core/hle/service/ssl/ssl_backend.h b/src/core/hle/service/ssl/ssl_backend.h index a2ec8e694..55794d793 100644 --- a/src/core/hle/service/ssl/ssl_backend.h +++ b/src/core/hle/service/ssl/ssl_backend.h @@ -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 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 data) = 0; virtual Result Write(size_t* out_size, std::span data) = 0; diff --git a/src/core/hle/service/ssl/ssl_backend_none.cpp b/src/core/hle/service/ssl/ssl_backend_none.cpp index 3a5c3a661..69c200404 100644 --- a/src/core/hle/service/ssl/ssl_backend_none.cpp +++ b/src/core/hle/service/ssl/ssl_backend_none.cpp @@ -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; diff --git a/src/core/hle/service/ssl/ssl_backend_openssl.cpp b/src/core/hle/service/ssl/ssl_backend_openssl.cpp index 5714e6f3c..0c9ab004f 100644 --- a/src/core/hle/service/ssl/ssl_backend_openssl.cpp +++ b/src/core/hle/service/ssl/ssl_backend_openssl.cpp @@ -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 @@ -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 socket; }; diff --git a/src/core/hle/service/ssl/ssl_backend_schannel.cpp b/src/core/hle/service/ssl/ssl_backend_schannel.cpp index d55dca49b..6288a96b1 100644 --- a/src/core/hle/service/ssl/ssl_backend_schannel.cpp +++ b/src/core/hle/service/ssl/ssl_backend_schannel.cpp @@ -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 @@ -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 input_buffers{{ @@ -533,6 +552,7 @@ public: std::vector cleartext_write_buf; bool got_read_eof = false; + bool skip_cert_verification = false; size_t read_buf_fill_size = 0; }; diff --git a/src/core/hle/service/ssl/ssl_backend_securetransport.cpp b/src/core/hle/service/ssl/ssl_backend_securetransport.cpp index 297203184..b64bfd881 100644 --- a/src/core/hle/service/ssl/ssl_backend_securetransport.cpp +++ b/src/core/hle/service/ssl/ssl_backend_securetransport.cpp @@ -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 @@ -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 context = nullptr; bool got_read_eof = false; + bool skip_cert_verification = false; std::shared_ptr socket; };