OXYGENHUB
◆ main openmsx / src / utils / OpenSSL.cc
19.9 KiB · C++ · last changed chore(): initial commit 1 month ago source blame history raw
e8555c89 Andy McCall 1
#include "OpenSSL.hh"
2
3
#include <array>
4
#include <bit>
5
#include <cstdint>
6
#include <cstdio>
7
#include <mutex>
8
#include <string>
9
#include <utility>
10
#include <vector>
11
12
#ifdef _WIN32
13
#include <windows.h>
14
#include <wincrypt.h>
15
#else
16
#include <dlfcn.h>
17
#endif
18
19
namespace openmsx::OpenSSL {
20
21
// Opaque OpenSSL runtime types, used only by the resolved entry points
22
// (forward-declared here so that openMSX never needs the OpenSSL
23
// development headers).
24
struct SslCtx;
25
struct X509;
26
struct X509VerifyParam;
27
struct SslMethod;
28
struct X509StoreCtx;
29
struct SslInitSettings;
30
31
// Resolved OpenSSL entry points. Every member is mandatory (load() refuses
32
// the library when any is missing); the only exception is `version`, whose
33
// three name spellings differ across OpenSSL/LibreSSL and which is
34
// diagnostic-only.
35
struct OpenSSLApi {
36
	// init
37
	int (*init)(unsigned long long opts, const SslInitSettings* settings) = nullptr;
38
	// methods / contexts
39
	const SslMethod* (*tlsClientMethod)() = nullptr;
40
	SslCtx* (*ctxNew)(const SslMethod* method) = nullptr;
41
	int (*ctxSetDefaultVerifyPaths)(SslCtx* ctx) = nullptr;
42
	int (*ctxLoadVerifyLocations)(SslCtx* ctx, const char* caFile,
43
	                              const char* caPath) = nullptr;
44
	// sessions
45
	Ssl* (*newSession)(SslCtx* ctx) = nullptr;
46
	void (*freeSession)(Ssl* ssl) = nullptr;
47
	int (*setFd)(Ssl* ssl, int fd) = nullptr;
48
	void (*setVerify)(Ssl* ssl, int mode, void (*)(int, X509StoreCtx*)) = nullptr;
49
	int (*connect)(Ssl* ssl) = nullptr;
50
	long (*ctrl)(Ssl* ssl, int cmd, long larg, const char* parg) = nullptr;
51
	X509VerifyParam* (*get0Param)(const Ssl* ssl) = nullptr;
52
	int (*verifyParamSet1Host)(X509VerifyParam* param, const char* name,
53
	                           size_t namelen) = nullptr;
54
	// data
55
	int (*read)(Ssl* ssl, uint8_t* buf, int num) = nullptr;
56
	int (*write)(Ssl* ssl, const uint8_t* buf, int num) = nullptr;
57
	int (*pending)(const Ssl* ssl) = nullptr;
58
	int (*shutdown)(Ssl* ssl) = nullptr;
59
	// errors / verification
60
	int (*getError)(const Ssl* ssl, int ret) = nullptr;
61
	long (*getVerifyResult)(const Ssl* ssl) = nullptr;
62
	const char* (*verifyCertErrorString)(long code) = nullptr;
63
	X509* (*get1PeerCertificate)(const Ssl* ssl) = nullptr;
64
	void (*freeX509)(X509* x) = nullptr;
65
	unsigned long (*errGetError)() = nullptr;
66
	void (*errErrorStringN)(unsigned long e, char* buf, size_t len) = nullptr;
67
	const char* (*version)(int type) = nullptr;
68
};
69
70
namespace {
71
72
// ---------------------------------------------------------------------
73
//  Runtime library handle
74
// ---------------------------------------------------------------------
75
76
#ifdef _WIN32
77
using DllHandle = HMODULE;
78
DllHandle openSslLib(const char* name)
79
{
80
	return LoadLibraryA(name);
81
}
82
#else
83
using DllHandle = void*;
84
DllHandle openSslLib(const char* name)
85
{
86
	return dlopen(name, RTLD_NOW | RTLD_LOCAL);
87
}
88
#endif
89
90
#ifdef _WIN32
91
static constexpr std::array libraryCandidates = {
92
	"libssl-4-x64.dll", "libssl-4.dll",
93
	"libssl-3-x64.dll", "libssl-3.dll",
94
	"libssl-1_1-x64.dll", "libssl-1_1.dll",
95
	"libssl.dll"
96
};
97
#elif defined(__APPLE__)
98
static constexpr std::array libraryCandidates = {
99
	// Homebrew (Apple Silicon / Intel) and system-wide installs
100
	"/opt/homebrew/opt/openssl@3/lib/libssl.3.dylib",
101
	"/opt/homebrew/opt/[email protected]/lib/libssl.1.1.dylib",
102
	"/usr/local/opt/openssl@3/lib/libssl.3.dylib",
103
	"/usr/local/opt/[email protected]/lib/libssl.1.1.dylib",
104
	"/usr/local/lib/libssl.dylib",
105
	"/opt/homebrew/lib/libssl.dylib",
106
	// Generic names (default dyld search paths)
107
	"libssl.3.dylib", "libssl.1.1.dylib", "libssl.dylib"
108
};
109
#else
110
static constexpr std::array libraryCandidates = {
111
	// Generic names (ldconfig cache)
112
	"libssl.so.3", "libssl.so.1.1", "libssl.so"
113
};
114
#endif
115
116
// Resolves one exported symbol, reinterpreted to the type the caller
117
// expects. Returns nullptr when it is not present. All pointer types have
118
// the same width, so this is ABI-safe.
119
template<typename T>
120
T resolveSymbol(DllHandle lib, const char* name)
121
{
122
#ifdef _WIN32
123
	return std::bit_cast<T>(GetProcAddress(lib, name));
124
#else
125
	return std::bit_cast<T>(dlsym(lib, name));
126
#endif
127
}
128
129
// ---------------------------------------------------------------------
130
//  Trust store setup
131
// ---------------------------------------------------------------------
132
133
#ifdef _WIN32
134
// Exports the Windows system root certificates as a PEM file and loads it
135
// into the given context, so that the device trusts the same CAs as the
136
// host operating system.
137
bool loadWindowsTrustStore(const OpenSSLApi& api, SslCtx* ctx)
138
{
139
	// The Windows system store provides the full root CA list
140
	HCERTSTORE hStore = CertOpenStore(CERT_STORE_PROV_SYSTEM_A, 0, 0,
141
	                                  CERT_SYSTEM_STORE_CURRENT_USER,
142
	                                  "ROOT");
143
	if (!hStore) return false;
144
145
	std::array<char, MAX_PATH> tmpPath;
146
	std::array<char, MAX_PATH> tmpFile;
147
	if (!GetTempPathA(MAX_PATH, tmpPath.data())) {
148
		CertCloseStore(hStore, 0);
149
		return false;
150
	}
151
	// GetTempFileNameA creates a unique empty file in the temp directory
152
	if (!GetTempFileNameA(tmpPath.data(), "omsx", 0, tmpFile.data())) {
153
		CertCloseStore(hStore, 0);
154
		return false;
155
	}
156
157
	bool ok = false;
158
	if (FILE* f = fopen(tmpFile.data(), "wb"); f) {
159
		PCCERT_CONTEXT pCert = nullptr;
160
		while ((pCert = CertEnumCertificatesInStore(hStore, pCert)) != nullptr) {
161
			DWORD len = 0;
162
			if (!CryptBinaryToStringA(pCert->pbCertEncoded,
163
			                          pCert->cbCertEncoded,
164
			                          CRYPT_STRING_BASE64HEADER,
165
			                          nullptr, &len) || len == 0) {
166
				continue;
167
			}
168
			std::vector<char> buf(len);
169
			if (CryptBinaryToStringA(pCert->pbCertEncoded,
170
			                         pCert->cbCertEncoded,
171
			                         CRYPT_STRING_BASE64HEADER,
172
			                         buf.data(), &len)) {
173
				fwrite(buf.data(), 1, len, f);
174
			}
175
		}
176
		fclose(f);
177
		ok = api.ctxLoadVerifyLocations(ctx, tmpFile.data(), nullptr) == 1;
178
	}
179
	CertCloseStore(hStore, 0);
180
	DeleteFileA(tmpFile.data());
181
	return ok;
182
}
183
#endif
184
185
// Best-effort trust-store setup: failures only reduce what can be
186
// verified, they never prevent TLS, so the return values are ignored.
187
void setupTrustStore(const OpenSSLApi& api, SslCtx* ctx)
188
{
189
#ifdef _WIN32
190
	// The Windows system store provides the full root CA list
191
	loadWindowsTrustStore(api, ctx);
192
#else
193
	// Load the common POSIX CA bundle first: it is needed on macOS
194
	// (Homebrew builds do not use OpenSSL's compiled-in default paths)
195
	// and covers Linux distros whose bundle is not on those paths either.
196
	// Best effort: when the file is missing, the default paths below may
197
	// still work.
198
	api.ctxLoadVerifyLocations(ctx, "/etc/ssl/cert.pem", nullptr);
199
#endif
200
	// The library's compiled-in default paths (e.g. /etc/ssl/certs on
201
	// Linux); on macOS Homebrew builds this fails, but the bundle above
202
	// was already loaded.
203
	api.ctxSetDefaultVerifyPaths(ctx);
204
}
205
206
// Shared client context, created once on first use
207
SslCtx* getSharedContext(const OpenSSLApi& api)
208
{
209
	static std::once_flag flag;
210
	static SslCtx* ctx = nullptr;
211
	std::call_once(flag, [&api] {
212
		// TLS_client_method and SSL_CTX_new are mandatory entry points
213
		ctx = api.ctxNew(api.tlsClientMethod());
214
		if (ctx) {
215
			setupTrustStore(api, ctx);
216
		}
217
	});
218
	return ctx;
219
}
220
221
// ---------------------------------------------------------------------
222
//  Symbol resolution
223
// ---------------------------------------------------------------------
224
225
static constexpr unsigned long OPENSSL_INIT_LOAD_SSL_STRINGS = 0x00200000UL;
226
static constexpr unsigned long OPENSSL_INIT_LOAD_CRYPTO_STRINGS = 0x00000001UL;
227
228
template<typename T>
229
T resolve(DllHandle lib, const char* name)
230
{
231
	return resolveSymbol<T>(lib, name);
232
}
233
234
// Tries the SSL library first, then the crypto library: the X509/ERR
235
// entry points live in libcrypto (GetProcAddress does not search DLL
236
// dependencies, so on Windows the crypto library must be loaded too)
237
template<typename T>
238
T resolveAny(DllHandle lib, DllHandle crypto, const char* name)
239
{
240
	if (auto p = resolveSymbol<T>(lib, name)) {
241
		return p;
242
	}
243
	if (crypto) {
244
		if (auto p = resolveSymbol<T>(crypto, name)) {
245
			return p;
246
		}
247
	}
248
	return nullptr;
249
}
250
251
// Name of the libcrypto DLL that belongs to a libssl DLL
252
std::string cryptoNameFor(std::string n)
253
{
254
	if (auto pos = n.find("libssl"); pos != std::string::npos) {
255
		n.replace(pos, 6, "libcrypto");
256
	}
257
	return n;
258
}
259
260
bool openLibrary(DllHandle& sslLib, DllHandle& cryptoLib)
261
{
262
	for (const char* name : libraryCandidates) {
263
		if (auto h = openSslLib(name)) {
264
			sslLib = h;
265
			// Load the matching libcrypto as well: the X509/ERR entry
266
			// points are exported by libcrypto, and GetProcAddress does
267
			// not search DLL dependencies (best effort; the TLS session
268
			// functions themselves all live in libssl).
269
			std::string cryptoName = cryptoNameFor(name);
270
			if (auto ch = openSslLib(cryptoName.c_str())) {
271
				cryptoLib = ch;
272
			}
273
			return true;
274
		}
275
	}
276
	return false;
277
}
278
279
bool resolveSymbols(OpenSSLApi& api, DllHandle sslLib, DllHandle cryptoLib)
280
{
281
	// All entry points are mandatory (except the OPENSSL_version name
282
	// spellings at the end, which are diagnostic-only). Each entry point
283
	// is resolved and checked immediately, so none can be forgotten and
284
	// missing ones are visible right where they are queried. This
285
	// requires OpenSSL >= 1.1 or LibreSSL >= 3.5.
286
	api.init = resolve<decltype(api.init)>(sslLib, "OPENSSL_init_ssl");
287
	if (!api.init) return false;
288
	api.tlsClientMethod = resolve<decltype(api.tlsClientMethod)>(sslLib, "TLS_client_method");
289
	if (!api.tlsClientMethod) return false;
290
	api.ctxNew = resolve<decltype(api.ctxNew)>(sslLib, "SSL_CTX_new");
291
	if (!api.ctxNew) return false;
292
	api.ctxSetDefaultVerifyPaths = resolve<decltype(api.ctxSetDefaultVerifyPaths)>(sslLib, "SSL_CTX_set_default_verify_paths");
293
	if (!api.ctxSetDefaultVerifyPaths) return false;
294
	api.ctxLoadVerifyLocations = resolve<decltype(api.ctxLoadVerifyLocations)>(sslLib, "SSL_CTX_load_verify_locations");
295
	if (!api.ctxLoadVerifyLocations) return false;
296
	api.newSession = resolve<decltype(api.newSession)>(sslLib, "SSL_new");
297
	if (!api.newSession) return false;
298
	api.freeSession = resolve<decltype(api.freeSession)>(sslLib, "SSL_free");
299
	if (!api.freeSession) return false;
300
	api.setFd = resolve<decltype(api.setFd)>(sslLib, "SSL_set_fd");
301
	if (!api.setFd) return false;
302
	api.setVerify = resolve<decltype(api.setVerify)>(sslLib, "SSL_set_verify");
303
	if (!api.setVerify) return false;
304
	api.connect = resolve<decltype(api.connect)>(sslLib, "SSL_connect");
305
	if (!api.connect) return false;
306
	api.ctrl = resolve<decltype(api.ctrl)>(sslLib, "SSL_ctrl");
307
	if (!api.ctrl) return false;
308
	api.get0Param = resolve<decltype(api.get0Param)>(sslLib, "SSL_get0_param");
309
	if (!api.get0Param) return false;
310
	api.verifyParamSet1Host = resolveAny<decltype(api.verifyParamSet1Host)>(sslLib, cryptoLib, "X509_VERIFY_PARAM_set1_host");
311
	if (!api.verifyParamSet1Host) return false;
312
	api.read = resolve<decltype(api.read)>(sslLib, "SSL_read");
313
	if (!api.read) return false;
314
	api.write = resolve<decltype(api.write)>(sslLib, "SSL_write");
315
	if (!api.write) return false;
316
	api.pending = resolve<decltype(api.pending)>(sslLib, "SSL_pending");
317
	if (!api.pending) return false;
318
	api.shutdown = resolve<decltype(api.shutdown)>(sslLib, "SSL_shutdown");
319
	if (!api.shutdown) return false;
320
	api.getError = resolve<decltype(api.getError)>(sslLib, "SSL_get_error");
321
	if (!api.getError) return false;
322
	api.getVerifyResult = resolve<decltype(api.getVerifyResult)>(sslLib, "SSL_get_verify_result");
323
	if (!api.getVerifyResult) return false;
324
	api.verifyCertErrorString = resolveAny<decltype(api.verifyCertErrorString)>(sslLib, cryptoLib, "X509_verify_cert_error_string");
325
	if (!api.verifyCertErrorString) return false;
326
	api.get1PeerCertificate = resolve<decltype(api.get1PeerCertificate)>(sslLib, "SSL_get1_peer_certificate");
327
	if (!api.get1PeerCertificate) return false;
328
	api.freeX509 = resolveAny<decltype(api.freeX509)>(sslLib, cryptoLib, "X509_free");
329
	if (!api.freeX509) return false;
330
	api.errGetError = resolveAny<decltype(api.errGetError)>(sslLib, cryptoLib, "ERR_get_error");
331
	if (!api.errGetError) return false;
332
	api.errErrorStringN = resolveAny<decltype(api.errErrorStringN)>(sslLib, cryptoLib, "ERR_error_string_n");
333
	if (!api.errErrorStringN) return false;
334
335
	// Diagnostic-only: the version function is not mandatory and has
336
	// different name spellings across OpenSSL/LibreSSL.
337
	api.version = resolveAny<decltype(api.version)>(sslLib, cryptoLib, "OPENSSL_version");
338
	if (!api.version) {
339
		// LibreSSL exports the version function with this exact name
340
		api.version = resolveAny<decltype(api.version)>(sslLib, cryptoLib, "OpenSSL_version");
341
	}
342
	if (!api.version) {
343
		api.version = resolveAny<decltype(api.version)>(sslLib, cryptoLib, "SSLeay_version");
344
	}
345
346
	// Initialize the library (idempotent in OpenSSL >= 1.1)
347
	if (api.init(OPENSSL_INIT_LOAD_SSL_STRINGS |
348
	             OPENSSL_INIT_LOAD_CRYPTO_STRINGS, nullptr) != 1) {
349
		return false;
350
	}
351
	return true;
352
}
353
354
static constexpr int SSL_VERIFY_NONE = 0x00;
355
static constexpr int SSL_VERIFY_PEER = 0x01;
356
static constexpr int SSL_ERROR_WANT_READ = 2;
357
static constexpr int SSL_ERROR_WANT_WRITE = 3;
358
static constexpr int SSL_ERROR_ZERO_RETURN = 6;
359
static constexpr int SSL_CTRL_SET_TLSEXT_HOSTNAME = 55;
360
static constexpr long TLSEXT_NAMETYPE_HOST_NAME = 0;
361
static constexpr int OPENSSL_VERSION = 0;
362
363
// X509 verify error codes (used to map to TCP-IP UNAPI close reasons)
364
static constexpr long X509_V_OK = 0;
365
static constexpr long X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT = 2;
366
static constexpr long X509_V_ERR_CERT_NOT_YET_VALID = 9;
367
static constexpr long X509_V_ERR_CERT_HAS_EXPIRED = 10;
368
static constexpr long X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT = 18;
369
static constexpr long X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN = 19;
370
static constexpr long X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY = 20;
371
static constexpr long X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE = 21;
372
static constexpr long X509_V_ERR_CERT_REVOKED = 23;
373
static constexpr long X509_V_ERR_INVALID_CA = 24;
374
static constexpr long X509_V_ERR_CERT_UNTRUSTED = 27;
375
static constexpr long X509_V_ERR_CERT_REJECTED = 28;
376
static constexpr long X509_V_ERR_HOSTNAME_MISMATCH = 62;
377
378
} // namespace
379
380
// ---------------------------------------------------------------------
381
//  OpenSSL public interface
382
// ---------------------------------------------------------------------
383
384
LibHandle::LibHandle()
385
	: api(std::make_unique<OpenSSLApi>())
386
{
387
}
388
389
LibHandle::~LibHandle() = default;
390
391
LibHandle* load()
392
{
393
	static LibHandle singleton;
394
	static std::once_flag flag;
395
	std::call_once(flag, [] {
396
		// The handles are only needed during symbol resolution; the
397
		// loaded libraries stay resident for the process lifetime.
398
		DllHandle sslLib = nullptr;
399
		DllHandle cryptoLib = nullptr;
400
		if (openLibrary(sslLib, cryptoLib)) {
401
			singleton.loaded = resolveSymbols(*singleton.api, sslLib, cryptoLib);
402
			singleton.libHandle = sslLib;
403
			singleton.cryptoHandle = cryptoLib;
404
		}
405
	});
406
	return singleton.loaded ? &singleton : nullptr;
407
}
408
409
SessionHandle::SessionHandle(Ssl* ssl_, const OpenSSLApi* api_)
410
	: ssl(ssl_)
411
	, api(api_)
412
{
413
}
414
415
SessionHandle::SessionHandle(SessionHandle&& other) noexcept
416
	: ssl(std::exchange(other.ssl, nullptr))
417
	, api(other.api)
418
{
419
}
420
421
SessionHandle& SessionHandle::operator=(SessionHandle&& other) noexcept
422
{
423
	if (this != &other) {
424
		release();
425
		ssl = std::exchange(other.ssl, nullptr);
426
		api = other.api;
427
	}
428
	return *this;
429
}
430
431
SessionHandle::~SessionHandle()
432
{
433
	release();
434
}
435
436
void SessionHandle::release() noexcept
437
{
438
	if (!ssl) return;
439
	// Best-effort shutdown: the peer may already be gone
440
	api->shutdown(ssl);
441
	api->freeSession(ssl);
442
	ssl = nullptr;
443
}
444
445
HandshakeResult SessionHandle::handshake() const
446
{
447
	int r = api->connect(ssl);
448
	if (r == 1) return HandshakeType::Done;
449
	int e = api->getError(ssl, r);
450
	if (e == SSL_ERROR_WANT_READ) return HandshakeType::NeedRead;
451
	if (e == SSL_ERROR_WANT_WRITE) return HandshakeType::NeedWrite;
452
	// Real error: capture the OpenSSL error code now, before any other
453
	// OpenSSL call can overwrite the error queue (mirrors serial::ErrorCode).
454
	return std::unexpected(ErrorCode{api->errGetError()});
455
}
456
457
int SessionHandle::verifyResult() const
458
{
459
	long vr = api->getVerifyResult(ssl);
460
	if (vr == X509_V_OK) return 0;
461
462
	// The server did not provide a certificate at all
463
	X509* peer = api->get1PeerCertificate(ssl);
464
	if (!peer) {
465
		return 9; // TLS: server did not provide a certificate
466
	}
467
	api->freeX509(peer);
468
469
	// Map the X509 verify error to a TCP-IP UNAPI close reason (spec 4.5.4)
470
	switch (vr) {
471
	case X509_V_ERR_HOSTNAME_MISMATCH:
472
		return 11; // host name didn't match
473
	case X509_V_ERR_CERT_HAS_EXPIRED:
474
	case X509_V_ERR_CERT_NOT_YET_VALID:
475
		return 12; // expired
476
	case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT:
477
	case X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN:
478
		return 13; // self-signed
479
	case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT:
480
	case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY:
481
	case X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE:
482
	case X509_V_ERR_CERT_UNTRUSTED:
483
	case X509_V_ERR_CERT_REJECTED:
484
		return 14; // untrusted root
485
	case X509_V_ERR_CERT_REVOKED:
486
		return 15; // revoked
487
	case X509_V_ERR_INVALID_CA:
488
		return 16; // invalid certificate authority
489
	default:
490
		return 10; // invalid server certificate
491
	}
492
}
493
494
std::string SessionHandle::verifyErrorDescription() const
495
{
496
	long vr = api->getVerifyResult(ssl);
497
	if (vr == X509_V_OK) return "";
498
	return api->verifyCertErrorString(vr);
499
}
500
501
IoReturn SessionHandle::read(std::span<uint8_t> buf) const
502
{
503
	int r = api->read(ssl, buf.data(), static_cast<int>(buf.size()));
504
	if (r > 0) return static_cast<size_t>(r);
505
	int e = api->getError(ssl, r);
506
	if (e == SSL_ERROR_WANT_READ || e == SSL_ERROR_WANT_WRITE) {
507
		return std::unexpected(IoError{IoResult::WouldBlock});
508
	}
509
	if (e == SSL_ERROR_ZERO_RETURN) {
510
		return std::unexpected(IoError{IoResult::Closed}); // clean TLS close
511
	}
512
	// Real error: capture the OpenSSL error code now, before any other
513
	// OpenSSL call can overwrite the error queue (mirrors serial::ErrorCode).
514
	return std::unexpected(IoError{std::unexpected(ErrorCode{api->errGetError()})});
515
}
516
517
IoReturn SessionHandle::write(std::span<const uint8_t> buf) const
518
{
519
	int r = api->write(ssl, buf.data(), static_cast<int>(buf.size()));
520
	if (r > 0) return static_cast<size_t>(r);
521
	int e = api->getError(ssl, r);
522
	if (e == SSL_ERROR_WANT_READ || e == SSL_ERROR_WANT_WRITE) {
523
		return std::unexpected(IoError{IoResult::WouldBlock});
524
	}
525
	if (e == SSL_ERROR_ZERO_RETURN) {
526
		return std::unexpected(IoError{IoResult::Closed}); // peer sent close_notify
527
	}
528
	// Real error: capture the OpenSSL error code now, before any other
529
	// OpenSSL call can overwrite the error queue (mirrors serial::ErrorCode).
530
	return std::unexpected(IoError{std::unexpected(ErrorCode{api->errGetError()})});
531
}
532
533
size_t SessionHandle::pending() const
534
{
535
	return static_cast<size_t>(api->pending(ssl));
536
}
537
538
std::optional<SessionHandle> LibHandle::createClientSession(
539
	bool verify, zstring_view hostname, int fd) const
540
{
541
	const OpenSSLApi& sslApi = *this->api;
542
	SslCtx* ctx = getSharedContext(sslApi);
543
	if (!ctx) return std::nullopt;
544
	Ssl* ssl = sslApi.newSession(ctx);
545
	if (!ssl) return std::nullopt;
546
	if (sslApi.setFd(ssl, fd) != 1) {
547
		sslApi.freeSession(ssl);
548
		return std::nullopt;
549
	}
550
	sslApi.setVerify(ssl, verify ? SSL_VERIFY_PEER : SSL_VERIFY_NONE, nullptr);
551
552
	// Server name: sent as SNI and, when verifying, used to check the
553
	// host name in the server certificate
554
	if (!hostname.empty()) {
555
		sslApi.ctrl(ssl, SSL_CTRL_SET_TLSEXT_HOSTNAME,
556
		            TLSEXT_NAMETYPE_HOST_NAME,
557
		            hostname.data());
558
		if (verify) {
559
			sslApi.verifyParamSet1Host(sslApi.get0Param(ssl), hostname.data(),
560
			                           hostname.size());
561
		}
562
	}
563
	return SessionHandle(ssl, &sslApi);
564
}
565
566
zstring_view LibHandle::version() const
567
{
568
	const OpenSSLApi& sslApi = *this->api;
569
	return sslApi.version ? sslApi.version(OPENSSL_VERSION) : "";
570
}
571
572
std::string to_string(ErrorCode ec)
573
{
574
	// An ErrorCode can only have been produced by an I/O call on a
575
	// session, so the library is loaded here (load() is idempotent and
576
	// thread-safe). A value of 0 means no error was queued: report a
577
	// generic message.
578
	if (const auto* lib = load(); lib && ec.value != 0) {
579
		std::array<char, 256> buf;
580
		lib->api->errErrorStringN(ec.value, buf.data(), buf.size());
581
		return buf.data();
582
	}
583
	return "OpenSSL error";
584
}
585
586
} // namespace openmsx::OpenSSL