// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include "core/hle/service/ssl/ssl_backend.h" #include "core/internal_network/network.h" #include "core/internal_network/sockets.h" #include "common/fs/file.h" #include "common/hex_util.h" #include "common/string_util.h" #include #include #include #include #include using namespace Common::FS; namespace Service::SSL { // Import OpenSSL's `SSL` type into the namespace. This is needed because the // namespace is also named `SSL`. using ::SSL; namespace { std::once_flag one_time_init_flag; bool one_time_init_success = false; SSL_CTX* ssl_ctx; IOFile key_log_file; // only open if SSLKEYLOGFILE set in environment BIO_METHOD* bio_meth; Result CheckOpenSSLErrors(); void OneTimeInit(); void OneTimeInitLogFile(); bool OneTimeInitBIO(); } // namespace class SSLConnectionBackendOpenSSL 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 OpenSSL one-time initialization failed"); return ResultInternalError; } ssl = SSL_new(ssl_ctx); if (!ssl) { LOG_ERROR(Service_SSL, "SSL_new failed"); return CheckOpenSSLErrors(); } SSL_set_connect_state(ssl); bio = BIO_new(bio_meth); if (!bio) { LOG_ERROR(Service_SSL, "BIO_new failed"); return CheckOpenSSLErrors(); } BIO_set_data(bio, this); BIO_set_init(bio, 1); SSL_set_bio(ssl, bio, bio); return ResultSuccess; } void SetSocket(std::shared_ptr socket_in) override { socket = std::move(socket_in); } Result SetHostName(const std::string& hostname) override { if (!SSL_set1_host(ssl, hostname.c_str())) { // hostname for verification LOG_ERROR(Service_SSL, "SSL_set1_host({}) failed", hostname); return CheckOpenSSLErrors(); } if (!SSL_set_tlsext_host_name(ssl, hostname.c_str())) { // hostname for SNI LOG_ERROR(Service_SSL, "SSL_set_tlsext_host_name({}) failed", hostname); return CheckOpenSSLErrors(); } 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(); } if (ret <= 0) { const int ssl_err = SSL_get_error(ssl, ret); if (ssl_err == SSL_ERROR_ZERO_RETURN || (ssl_err == SSL_ERROR_SYSCALL && got_read_eof)) { LOG_ERROR(Service_SSL, "SSL handshake failed because server hung up"); return ResultInternalError; } } return HandleReturn("SSL_do_handshake", 0, ret).Code(); } ResultVal Read(std::span data) override { size_t actual; const int ret = SSL_read_ex(ssl, data.data(), data.size(), &actual); return HandleReturn("SSL_read_ex", actual, ret); } ResultVal Write(std::span data) override { size_t actual; const int ret = SSL_write_ex(ssl, data.data(), data.size(), &actual); return HandleReturn("SSL_write_ex", actual, ret); } ResultVal HandleReturn(const char* what, size_t actual, int ret) { const int ssl_err = SSL_get_error(ssl, ret); CheckOpenSSLErrors(); switch (ssl_err) { case SSL_ERROR_NONE: return actual; case SSL_ERROR_ZERO_RETURN: LOG_DEBUG(Service_SSL, "{} => SSL_ERROR_ZERO_RETURN", what); // DoHandshake special-cases this, but for Read and Write: return size_t(0); case SSL_ERROR_WANT_READ: LOG_DEBUG(Service_SSL, "{} => SSL_ERROR_WANT_READ", what); return ResultWouldBlock; case SSL_ERROR_WANT_WRITE: LOG_DEBUG(Service_SSL, "{} => SSL_ERROR_WANT_WRITE", what); return ResultWouldBlock; default: if (ssl_err == SSL_ERROR_SYSCALL && got_read_eof) { LOG_DEBUG(Service_SSL, "{} => SSL_ERROR_SYSCALL because server hung up", what); return size_t(0); } LOG_ERROR(Service_SSL, "{} => other SSL_get_error return value {}", what, ssl_err); return ResultInternalError; } } ResultVal>> GetServerCerts() override { STACK_OF(X509)* chain = SSL_get_peer_cert_chain(ssl); if (!chain) { LOG_ERROR(Service_SSL, "SSL_get_peer_cert_chain returned nullptr"); return ResultInternalError; } std::vector> ret; int count = sk_X509_num(chain); ASSERT(count >= 0); for (int i = 0; i < count; i++) { X509* x509 = sk_X509_value(chain, i); ASSERT_OR_EXECUTE(x509 != nullptr, { continue; }); unsigned char* buf = nullptr; int len = i2d_X509(x509, &buf); ASSERT_OR_EXECUTE(len >= 0 && buf, { continue; }); ret.emplace_back(buf, buf + len); OPENSSL_free(buf); } return ret; } ~SSLConnectionBackendOpenSSL() { // these are null-tolerant: SSL_free(ssl); BIO_free(bio); } static void KeyLogCallback(const SSL* ssl, const char* line) { std::string str(line); str.push_back('\n'); // Do this in a single WriteString for atomicity if multiple instances // are running on different threads (though that can't currently // happen). if (key_log_file.WriteString(str) != str.size() || !key_log_file.Flush()) { LOG_CRITICAL(Service_SSL, "Failed to write to SSLKEYLOGFILE"); } LOG_DEBUG(Service_SSL, "Wrote to SSLKEYLOGFILE: {}", line); } static int WriteCallback(BIO* bio, const char* buf, size_t len, size_t* actual_p) { auto self = static_cast(BIO_get_data(bio)); ASSERT_OR_EXECUTE_MSG( self->socket, { return 0; }, "OpenSSL asked to send but we have no socket"); BIO_clear_retry_flags(bio); auto [actual, err] = self->socket->Send({reinterpret_cast(buf), len}, 0); switch (err) { case Network::Errno::SUCCESS: *actual_p = actual; return 1; case Network::Errno::AGAIN: BIO_set_flags(bio, BIO_FLAGS_WRITE | BIO_FLAGS_SHOULD_RETRY); return 0; default: LOG_ERROR(Service_SSL, "Socket send returned Network::Errno {}", err); return -1; } } static int ReadCallback(BIO* bio, char* buf, size_t len, size_t* actual_p) { auto self = static_cast(BIO_get_data(bio)); ASSERT_OR_EXECUTE_MSG( self->socket, { return 0; }, "OpenSSL asked to recv but we have no socket"); BIO_clear_retry_flags(bio); auto [actual, err] = self->socket->Recv(0, {reinterpret_cast(buf), len}); switch (err) { case Network::Errno::SUCCESS: *actual_p = actual; if (actual == 0) { self->got_read_eof = true; } return actual ? 1 : 0; case Network::Errno::AGAIN: BIO_set_flags(bio, BIO_FLAGS_READ | BIO_FLAGS_SHOULD_RETRY); return 0; default: LOG_ERROR(Service_SSL, "Socket recv returned Network::Errno {}", err); return -1; } } static long CtrlCallback(BIO* bio, int cmd, long l_arg, void* p_arg) { switch (cmd) { case BIO_CTRL_FLUSH: // Nothing to flush. return 1; case BIO_CTRL_PUSH: case BIO_CTRL_POP: #ifdef BIO_CTRL_GET_KTLS_SEND case BIO_CTRL_GET_KTLS_SEND: case BIO_CTRL_GET_KTLS_RECV: #endif // We don't support these operations, but don't bother logging them // as they're nothing unusual. return 0; default: LOG_DEBUG(Service_SSL, "OpenSSL BIO got ctrl({}, {}, {})", cmd, l_arg, p_arg); return 0; } } SSL* ssl = nullptr; BIO* bio = nullptr; bool got_read_eof = false; std::shared_ptr socket; }; ResultVal> CreateSSLConnectionBackend() { auto conn = std::make_unique(); const Result res = conn->Init(); if (res.IsFailure()) { return res; } return conn; } namespace { Result CheckOpenSSLErrors() { unsigned long rc; const char* file; int line; const char* func; const char* data; int flags; #if OPENSSL_VERSION_NUMBER >= 0x30000000L while ((rc = ERR_get_error_all(&file, &line, &func, &data, &flags))) #else // Can't get function names from OpenSSL on this version, so use mine: func = __func__; while ((rc = ERR_get_error_line_data(&file, &line, &data, &flags))) #endif { std::string msg; msg.resize(1024, '\0'); ERR_error_string_n(rc, msg.data(), msg.size()); msg.resize(strlen(msg.data()), '\0'); if (flags & ERR_TXT_STRING) { msg.append(" | "); msg.append(data); } Common::Log::FmtLogMessage(Common::Log::Class::Service_SSL, Common::Log::Level::Error, Common::Log::TrimSourcePath(file), line, func, "OpenSSL: {}", msg); } return ResultInternalError; } void OneTimeInit() { ssl_ctx = SSL_CTX_new(TLS_client_method()); if (!ssl_ctx) { LOG_ERROR(Service_SSL, "SSL_CTX_new failed"); CheckOpenSSLErrors(); return; } SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, nullptr); if (!SSL_CTX_set_default_verify_paths(ssl_ctx)) { LOG_ERROR(Service_SSL, "SSL_CTX_set_default_verify_paths failed"); CheckOpenSSLErrors(); return; } OneTimeInitLogFile(); if (!OneTimeInitBIO()) { return; } one_time_init_success = true; } void OneTimeInitLogFile() { const char* logfile = getenv("SSLKEYLOGFILE"); if (logfile) { key_log_file.Open(logfile, FileAccessMode::Append, FileType::TextFile, FileShareFlag::ShareWriteOnly); if (key_log_file.IsOpen()) { SSL_CTX_set_keylog_callback(ssl_ctx, &SSLConnectionBackendOpenSSL::KeyLogCallback); } else { LOG_CRITICAL(Service_SSL, "SSLKEYLOGFILE was set but file could not be opened; not logging keys!"); } } } bool OneTimeInitBIO() { bio_meth = BIO_meth_new(BIO_get_new_index() | BIO_TYPE_SOURCE_SINK, "SSLConnectionBackendOpenSSL"); if (!bio_meth || !BIO_meth_set_write_ex(bio_meth, &SSLConnectionBackendOpenSSL::WriteCallback) || !BIO_meth_set_read_ex(bio_meth, &SSLConnectionBackendOpenSSL::ReadCallback) || !BIO_meth_set_ctrl(bio_meth, &SSLConnectionBackendOpenSSL::CtrlCallback)) { LOG_ERROR(Service_SSL, "Failed to create BIO_METHOD"); return false; } return true; } } // namespace } // namespace Service::SSL