OXYGENHUB
◆ main openmsx / src / utils / OpenSSL.hh
6.7 KiB · C++ · last changed chore(): initial commit 1 month ago source blame history raw
e8555c89 Andy McCall 1
#ifndef OPENSSL_HH
2
#define OPENSSL_HH
3
4
#include "zstring_view.hh"
5
6
#include <cstddef>
7
#include <cstdint>
8
#include <expected>
9
#include <memory>
10
#include <optional>
11
#include <span>
12
#include <string>
13
14
namespace openmsx::OpenSSL {
15
16
// Minimal TLS wrapper around the OpenSSL runtime library installed on the
17
// host, loaded dynamically at runtime (LoadLibrary / dlopen). Building
18
// openMSX itself does not require any OpenSSL development package: all
19
// OpenSSL types are opaque and every function is reached through resolved
20
// entry points.
21
//
22
// When OpenSSL is not installed, load() returns nullptr and the SMXWiFi
23
// device degrades gracefully: the TLS capability bits are not advertised
24
// and TCP_OPEN with the TLS flag returns ERR_NOT_IMP.
25
//
26
// Supported: OpenSSL 1.1.x and 3.x on Windows, Linux and macOS (LibreSSL
27
// 3.5 and newer is mostly API-compatible and will also be picked up as a
28
// fallback).
29
30
// Opaque handle for an OpenSSL runtime type, kept forward-declared so that
31
// openMSX itself never needs the OpenSSL development headers. The other
32
// opaque types used internally by the resolved entry points are declared
33
// in OpenSSL.cc.
34
struct Ssl;
35
36
// Resolved OpenSSL entry points (defined in OpenSSL.cc; only declared here
37
// so that the load state can live in the LibHandle singleton).
38
struct OpenSSLApi;
39
40
// OpenSSL error code captured at the moment of failure (the value returned
41
// by ERR_get_error(); 0 when the error queue was empty, e.g. a plain
42
// syscall failure reported through SSL_ERROR_SYSCALL). It is safe to store
43
// the value and format it later with to_string(): no other OpenSSL call
44
// can overwrite it in between (same pattern as serial::ErrorCode).
45
struct ErrorCode {
46
	unsigned long value = 0;
47
};
48
49
// Formats an OpenSSL error code, e.g.
50
// "error:1408A0C1:SSL routines:ssl3_get_record:wrong version number".
51
[[nodiscard]] std::string to_string(ErrorCode ec);
52
53
// Non-failure outcome of a non-blocking read() or write(): no bytes were
54
// transferred, either because the call would block or because the peer
55
// cleanly closed the TLS session.
56
enum class IoResult {
57
	WouldBlock, // the call would block; retry later (either readiness)
58
	Closed      // clean TLS close (peer sent close_notify)
59
};
60
61
// Error channel of a non-blocking read()/write(): either such an outcome,
62
// or a real failure carrying the OpenSSL error code captured at the moment
63
// of the failure. The code can only be reached in the failure case, so it
64
// can never be used when it is not meaningful.
65
using IoError = std::expected<IoResult, ErrorCode>;
66
67
// Result of a (non-blocking) read() or write(): success carries the
68
// number of bytes transferred, otherwise IoError says why.
69
using IoReturn = std::expected<size_t, IoError>;
70
71
// Result of driving the TLS handshake (non-blocking). The caller must
72
// retry the handshake when the socket reaches the requested readiness.
73
// The values of the non-failure outcomes map 1:1 to the underlying
74
// SSL_connect()/SSL_get_error() contract; on failure the captured OpenSSL
75
// error code is returned instead.
76
enum class HandshakeType {
77
	NeedRead = 0,  // retry when the socket is readable
78
	Done = 1,      // handshake completed successfully
79
	NeedWrite = 2, // retry when the socket is writable
80
};
81
using HandshakeResult = std::expected<HandshakeType, ErrorCode>;
82
83
// An active TLS session on a connected (non-blocking) socket. Move-only
84
// RAII: the destructor performs the best-effort shutdown and releases the
85
// session, so a SessionHandle can never outlive its session.
86
struct SessionHandle {
87
	SessionHandle(const SessionHandle&) = delete;
88
	SessionHandle& operator=(const SessionHandle&) = delete;
89
	SessionHandle(SessionHandle&& other) noexcept;
90
	SessionHandle& operator=(SessionHandle&& other) noexcept;
91
	~SessionHandle();
92
93
	// Drives the TLS handshake (non-blocking).
94
	[[nodiscard]] HandshakeResult handshake() const;
95
96
	// Certificate validation result after a handshake attempt (only
97
	// meaningful when the session was created with verify=true; valid
98
	// whether the handshake itself failed or not). Returns 0 when the
99
	// certificate is valid, otherwise a TCP-IP UNAPI close reason code
100
	// in the range 9..19 (spec 4.5.4). The code is purely informational
101
	// - the connection is refused either way - so a non-zero value
102
	// requires no special handling.
103
	[[nodiscard]] int verifyResult() const;
104
105
	// Human-readable description of the certificate verification result
106
	// (e.g. "self-signed certificate", "hostname mismatch"); empty when
107
	// the certificate verified OK. Valid after any handshake attempt.
108
	[[nodiscard]] std::string verifyErrorDescription() const;
109
110
	// Decrypts inbound data. Success: number of bytes read. Failure:
111
	// either a benign no-transfer outcome (WouldBlock; or Closed on a
112
	// clean TLS close (close_notify)) or a real error.
113
	[[nodiscard]] IoReturn read(std::span<uint8_t> buf) const;
114
115
	// Encrypts and sends data. Success: number of bytes written. Failure:
116
	// either a benign no-transfer outcome (WouldBlock; or Closed when the
117
	// peer sent close_notify) or a real error.
118
	[[nodiscard]] IoReturn write(std::span<const uint8_t> buf) const;
119
120
	// Plaintext bytes already decrypted and buffered by the SSL layer,
121
	// readable without waiting for socket readiness.
122
	[[nodiscard]] size_t pending() const;
123
124
private:
125
	friend struct LibHandle;
126
	SessionHandle(Ssl* ssl_, const OpenSSLApi* api_);
127
	void release() noexcept;
128
	Ssl* ssl;
129
	const OpenSSLApi* api;
130
};
131
132
// Handle to the loaded OpenSSL runtime. Obtained from load(); its members
133
// may only be called after load() succeeded.
134
struct LibHandle {
135
	// Version string, e.g. "OpenSSL 3.1.4 24 Oct 2023".
136
	[[nodiscard]] zstring_view version() const;
137
138
	// Creates a TLS client session on the given connected (non-blocking)
139
	// socket.
140
	// - verify: validate the server certificate (the TCP-IP UNAPI spec
141
	//   "Verify the server certificate" flag). When false, no validation
142
	//   at all is performed.
143
	// - hostname: server name, sent as SNI and (when verify is set) used
144
	//   to check the certificate host name. May be empty.
145
	// Returns a session handle, or nullopt on failure.
146
	[[nodiscard]] std::optional<SessionHandle> createClientSession(
147
		bool verify, zstring_view hostname, int fd) const;
148
149
private:
150
	friend LibHandle* load();
151
	friend std::string to_string(ErrorCode ec);
152
	LibHandle();
153
	~LibHandle();
154
	std::unique_ptr<OpenSSLApi> api; // resolved entry points
155
	void* libHandle = nullptr;       // HMODULE / dlopen handle
156
	void* cryptoHandle = nullptr;    // matching libcrypto handle
157
	bool loaded = false;
158
};
159
160
// Loads the runtime library and resolves the needed entry points.
161
// Idempotent and thread-safe (the library is loaded at most once, also
162
// when called concurrently from several threads). Returns nullptr when
163
// OpenSSL is not available (the returned pointer is the same on every
164
// successful call).
165
LibHandle* load();
166
167
} // namespace openmsx::OpenSSL
168
169
#endif