summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/core/hle/service/ssl/ssl_backend_securetransport.cpp219
1 files changed, 219 insertions, 0 deletions
diff --git a/src/core/hle/service/ssl/ssl_backend_securetransport.cpp b/src/core/hle/service/ssl/ssl_backend_securetransport.cpp
new file mode 100644
index 000000000..be40a5aeb
--- /dev/null
+++ b/src/core/hle/service/ssl/ssl_backend_securetransport.cpp
@@ -0,0 +1,219 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "core/hle/service/ssl/ssl_backend.h"
5#include "core/internal_network/network.h"
6#include "core/internal_network/sockets.h"
7
8#include <mutex>
9
10#include <Security/SecureTransport.h>
11
12// SecureTransport has been deprecated in its entirety in favor of
13// Network.framework, but that does not allow layering TLS on top of an
14// arbitrary socket.
15#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
16
17namespace {
18
19template <typename T>
20struct CFReleaser {
21 T ptr;
22
23 YUZU_NON_COPYABLE(CFReleaser);
24 constexpr CFReleaser() : ptr(nullptr) {}
25 constexpr CFReleaser(T ptr) : ptr(ptr) {}
26 constexpr operator T() {
27 return ptr;
28 }
29 ~CFReleaser() {
30 if (ptr) {
31 CFRelease(ptr);
32 }
33 }
34};
35
36std::string CFStringToString(CFStringRef cfstr) {
37 CFReleaser<CFDataRef> cfdata(
38 CFStringCreateExternalRepresentation(nullptr, cfstr, kCFStringEncodingUTF8, 0));
39 ASSERT_OR_EXECUTE(cfdata, { return "???"; });
40 return std::string(reinterpret_cast<const char*>(CFDataGetBytePtr(cfdata)),
41 CFDataGetLength(cfdata));
42}
43
44std::string OSStatusToString(OSStatus status) {
45 CFReleaser<CFStringRef> cfstr(SecCopyErrorMessageString(status, nullptr));
46 if (!cfstr) {
47 return "[unknown error]";
48 }
49 return CFStringToString(cfstr);
50}
51
52} // namespace
53
54namespace Service::SSL {
55
56class SSLConnectionBackendSecureTransport final : public SSLConnectionBackend {
57public:
58 Result Init() {
59 static std::once_flag once_flag;
60 std::call_once(once_flag, []() {
61 if (getenv("SSLKEYLOGFILE")) {
62 LOG_CRITICAL(Service_SSL, "SSLKEYLOGFILE was set but SecureTransport does not "
63 "support exporting keys; not logging keys!");
64 // Not fatal.
65 }
66 });
67
68 context.ptr = SSLCreateContext(nullptr, kSSLClientSide, kSSLStreamType);
69 if (!context) {
70 LOG_ERROR(Service_SSL, "SSLCreateContext failed");
71 return ResultInternalError;
72 }
73
74 OSStatus status;
75 if ((status = SSLSetIOFuncs(context, ReadCallback, WriteCallback)) ||
76 (status = SSLSetConnection(context, this))) {
77 LOG_ERROR(Service_SSL, "SSLContext initialization failed: {}",
78 OSStatusToString(status));
79 return ResultInternalError;
80 }
81
82 return ResultSuccess;
83 }
84
85 void SetSocket(std::shared_ptr<Network::SocketBase> in_socket) override {
86 socket = std::move(in_socket);
87 }
88
89 Result SetHostName(const std::string& hostname) override {
90 OSStatus status = SSLSetPeerDomainName(context, hostname.c_str(), hostname.size());
91 if (status) {
92 LOG_ERROR(Service_SSL, "SSLSetPeerDomainName failed: {}", OSStatusToString(status));
93 return ResultInternalError;
94 }
95 return ResultSuccess;
96 }
97
98 Result DoHandshake() override {
99 OSStatus status = SSLHandshake(context);
100 return HandleReturn("SSLHandshake", 0, status).Code();
101 }
102
103 ResultVal<size_t> Read(std::span<u8> data) override {
104 size_t actual;
105 OSStatus status = SSLRead(context, data.data(), data.size(), &actual);
106 ;
107 return HandleReturn("SSLRead", actual, status);
108 }
109
110 ResultVal<size_t> Write(std::span<const u8> data) override {
111 size_t actual;
112 OSStatus status = SSLWrite(context, data.data(), data.size(), &actual);
113 ;
114 return HandleReturn("SSLWrite", actual, status);
115 }
116
117 ResultVal<size_t> HandleReturn(const char* what, size_t actual, OSStatus status) {
118 switch (status) {
119 case 0:
120 return actual;
121 case errSSLWouldBlock:
122 return ResultWouldBlock;
123 default: {
124 std::string reason;
125 if (got_read_eof) {
126 reason = "server hung up";
127 } else {
128 reason = OSStatusToString(status);
129 }
130 LOG_ERROR(Service_SSL, "{} failed: {}", what, reason);
131 return ResultInternalError;
132 }
133 }
134 }
135
136 ResultVal<std::vector<std::vector<u8>>> GetServerCerts() override {
137 CFReleaser<SecTrustRef> trust;
138 OSStatus status = SSLCopyPeerTrust(context, &trust.ptr);
139 if (status) {
140 LOG_ERROR(Service_SSL, "SSLCopyPeerTrust failed: {}", OSStatusToString(status));
141 return ResultInternalError;
142 }
143 std::vector<std::vector<u8>> ret;
144 for (CFIndex i = 0, count = SecTrustGetCertificateCount(trust); i < count; i++) {
145 SecCertificateRef cert = SecTrustGetCertificateAtIndex(trust, i);
146 CFReleaser<CFDataRef> data(SecCertificateCopyData(cert));
147 ASSERT_OR_EXECUTE(data, { return ResultInternalError; });
148 const u8* ptr = CFDataGetBytePtr(data);
149 ret.emplace_back(ptr, ptr + CFDataGetLength(data));
150 }
151 return ret;
152 }
153
154 static OSStatus ReadCallback(SSLConnectionRef connection, void* data, size_t* dataLength) {
155 return ReadOrWriteCallback(connection, data, dataLength, true);
156 }
157
158 static OSStatus WriteCallback(SSLConnectionRef connection, const void* data,
159 size_t* dataLength) {
160 return ReadOrWriteCallback(connection, const_cast<void*>(data), dataLength, false);
161 }
162
163 static OSStatus ReadOrWriteCallback(SSLConnectionRef connection, void* data, size_t* dataLength,
164 bool is_read) {
165 auto self =
166 static_cast<SSLConnectionBackendSecureTransport*>(const_cast<void*>(connection));
167 ASSERT_OR_EXECUTE_MSG(
168 self->socket, { return 0; }, "SecureTransport asked to {} but we have no socket",
169 is_read ? "read" : "write");
170
171 // SecureTransport callbacks (unlike OpenSSL BIO callbacks) are
172 // expected to read/write the full requested dataLength or return an
173 // error, so we have to add a loop ourselves.
174 size_t requested_len = *dataLength;
175 size_t offset = 0;
176 while (offset < requested_len) {
177 std::span cur(reinterpret_cast<u8*>(data) + offset, requested_len - offset);
178 auto [actual, err] = is_read ? self->socket->Recv(0, cur) : self->socket->Send(cur, 0);
179 LOG_CRITICAL(Service_SSL, "op={}, offset={} actual={}/{} err={}", is_read, offset,
180 actual, cur.size(), static_cast<s32>(err));
181 switch (err) {
182 case Network::Errno::SUCCESS:
183 offset += actual;
184 if (actual == 0) {
185 ASSERT(is_read);
186 self->got_read_eof = true;
187 return errSecEndOfData;
188 }
189 break;
190 case Network::Errno::AGAIN:
191 *dataLength = offset;
192 return errSSLWouldBlock;
193 default:
194 LOG_ERROR(Service_SSL, "Socket {} returned Network::Errno {}",
195 is_read ? "recv" : "send", err);
196 return errSecIO;
197 }
198 }
199 ASSERT(offset == requested_len);
200 return 0;
201 }
202
203private:
204 CFReleaser<SSLContextRef> context = nullptr;
205 bool got_read_eof = false;
206
207 std::shared_ptr<Network::SocketBase> socket;
208};
209
210ResultVal<std::unique_ptr<SSLConnectionBackend>> CreateSSLConnectionBackend() {
211 auto conn = std::make_unique<SSLConnectionBackendSecureTransport>();
212 const Result res = conn->Init();
213 if (res.IsFailure()) {
214 return res;
215 }
216 return conn;
217}
218
219} // namespace Service::SSL