DotNetDupe 4.0.6
C++17/20 Implementation of the .NET Base Class Library (BCL)
Loading...
Searching...
No Matches
SslStream.cpp
Go to the documentation of this file.
1#include "pch.h"
6#include <mutex>
7
8#if defined(_WIN32)
9 #include <openssl/ssl.h>
10 #include <openssl/err.h>
11 #include <openssl/bio.h>
12#else
13 #include <openssl/ssl.h>
14 #include <openssl/err.h>
15 #include <openssl/bio.h>
16#endif
17
18namespace DotNetDupe {
19 namespace System {
20 namespace Net {
21 namespace Security {
22
23 static std::once_flag s_sslInitOnce;
24
25 void SslStream::InitializeOpenSSL() {
27 std::call_once(s_sslInitOnce, []() {
28 SSL_library_init();
29 SSL_load_error_strings();
30 OpenSSL_add_all_algorithms();
31 });
32 }
33
35 : m_spInnerStream(innerStream),
36 m_bLeaveInnerStreamOpen(false),
37 m_bDisposed(false),
38 m_pSslCtx(nullptr),
39 m_pSsl(nullptr),
40 m_pBioIn(nullptr),
41 m_pBioOut(nullptr) {
43 if (innerStream.IsNull()) {
44 throw ArgumentNullException("innerStream cannot be null.");
45 }
46 InitializeOpenSSL();
47 }
48
49 SslStream::SslStream(const SmartPointer<IO::Stream>& innerStream, bool leaveInnerStreamOpen)
50 : m_spInnerStream(innerStream),
51 m_bLeaveInnerStreamOpen(leaveInnerStreamOpen),
52 m_bDisposed(false),
53 m_pSslCtx(nullptr),
54 m_pSsl(nullptr),
55 m_pBioIn(nullptr),
56 m_pBioOut(nullptr) {
58 if (innerStream.IsNull()) {
59 throw ArgumentNullException("innerStream cannot be null.");
60 }
61 InitializeOpenSSL();
62 }
63
68
69 void* SslStream::CreateSslContext(bool isServer) {
71 SSL_CTX* ctx = SSL_CTX_new(isServer ? TLS_server_method() : TLS_client_method());
72 if (!ctx) {
73 throw IO::IOException("Failed to create SSL context.");
74 }
75
76 SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION);
77 return ctx;
78 }
79
80 void SslStream::ConfigureServerCert(void* rawCtx, const SmartPointer<::DotNetDupe::System::Security::Cryptography::X509Certificates::X509Certificate2>& certificate) {
82 SSL_CTX* ctx = static_cast<SSL_CTX*>(rawCtx);
83 X509* cert = static_cast<X509*>(certificate->GetInternalCert());
84 EVP_PKEY* pkey = static_cast<EVP_PKEY*>(certificate->GetInternalKey());
85
86 if (SSL_CTX_use_certificate(ctx, cert) <= 0) {
87 SSL_CTX_free(ctx);
88 m_pSslCtx = nullptr;
89 throw ArgumentException("Failed to configure certificate in SSL context.");
90 }
91
92 if (SSL_CTX_use_PrivateKey(ctx, pkey) <= 0) {
93 SSL_CTX_free(ctx);
94 m_pSslCtx = nullptr;
95 throw ArgumentException("Failed to configure private key in SSL context.");
96 }
97 }
98
99 static SSL* CreateAndBindSsl(SSL_CTX* ctx, void*& pBioIn, void*& pBioOut) {
101 SSL* ssl = SSL_new(ctx);
102 if (!ssl) {
103 SSL_CTX_free(ctx);
104 throw IO::IOException("Failed to create SSL handle.");
105 }
106 BIO* bioIn = BIO_new(BIO_s_mem());
107 BIO* bioOut = BIO_new(BIO_s_mem());
108 pBioIn = bioIn;
109 pBioOut = bioOut;
110 SSL_set_bio(ssl, bioIn, bioOut);
111 return ssl;
112 }
113
114 void SslStream::AuthenticateAsClient(const String& targetHost) {
116 if (m_bDisposed) throw IO::IOException("Stream is disposed.");
117 if (m_pSsl) throw IO::IOException("Already authenticated.");
118
120 SSL_CTX* ctx = static_cast<SSL_CTX*>(CreateSslContext(false));
121 m_pSslCtx = ctx;
122 SSL* ssl = CreateAndBindSsl(ctx, m_pBioIn, m_pBioOut);
123 m_pSsl = ssl;
124 SSL_set_tlsext_host_name(ssl, targetHost.GetRawString());
125 SSL_set_connect_state(ssl);
126 ProcessHandshake();
127 }
128
131 if (m_bDisposed) throw IO::IOException("Stream is disposed.");
132 if (m_pSsl) throw IO::IOException("Already authenticated.");
133 if (certificate.IsNull()) throw ArgumentNullException("certificate cannot be null.");
134
136 SSL_CTX* ctx = static_cast<SSL_CTX*>(CreateSslContext(true));
137 m_pSslCtx = ctx;
138 ConfigureServerCert(ctx, certificate);
139 SSL* ssl = CreateAndBindSsl(ctx, m_pBioIn, m_pBioOut);
140 m_pSsl = ssl;
141 SSL_set_accept_state(ssl);
142 ProcessHandshake();
143 }
144
145 static void PumpNetworkToBio(IO::Stream* pStream, void* pBioIn, const char* pErrorContext) {
146 char buffer[4096];
147 int read = pStream->Read(buffer, 0, sizeof(buffer));
148 if (read <= 0) throw IO::IOException(pErrorContext);
149 BIO_write(static_cast<BIO*>(pBioIn), buffer, read);
150 }
151
152 static void FlushBioOutbound(void* pBioOut, const SmartPointer<IO::Stream>& spInnerStream) {
153 if (!pBioOut) return;
154 char buffer[4096];
155 while (true) {
156 int read = BIO_read(static_cast<BIO*>(pBioOut), buffer, sizeof(buffer));
157 if (read <= 0) break;
158 spInnerStream->Write(buffer, 0, read);
159 }
160 }
161
162 static void HandleHandshakeError(int err, void* pBioIn, void* pBioOut, const SmartPointer<IO::Stream>& spStream) {
163 FlushBioOutbound(pBioOut, spStream);
164 if (err == SSL_ERROR_WANT_READ) {
165 PumpNetworkToBio(spStream.Get(), pBioIn, "Connection closed during SSL handshake.");
166 } else if (err != SSL_ERROR_WANT_WRITE) {
167 char errBuf[256];
168 ERR_error_string_n(ERR_get_error(), errBuf, sizeof(errBuf));
169 throw IO::IOException(String("SSL handshake failed: ") + errBuf);
170 }
171 }
172
173 void SslStream::ProcessHandshake() {
175 SSL* ssl = static_cast<SSL*>(m_pSsl);
176 while (!SSL_is_init_finished(ssl)) {
177 int ret = SSL_do_handshake(ssl);
178 if (ret == 1) break;
179 HandleHandshakeError(SSL_get_error(ssl, ret), m_pBioIn, m_pBioOut, m_spInnerStream);
180 }
181 FlushOutboundBio();
182 }
183
184 void SslStream::FlushOutboundBio() {
186 FlushBioOutbound(m_pBioOut, m_spInnerStream);
187 }
188
189 bool SslStream::CanRead() const { return !m_bDisposed; }
190 bool SslStream::CanSeek() const { return false; }
191 bool SslStream::CanWrite() const { return !m_bDisposed; }
192 long SslStream::GetLength() const { throw IO::IOException("SslStream does not support seeking."); }
193 long SslStream::GetPosition() const { throw IO::IOException("SslStream does not support seeking."); }
194 void SslStream::SetPosition(long value) { throw IO::IOException("SslStream does not support seeking."); }
195
198 if (m_bDisposed) throw IO::IOException("Stream is disposed.");
199 m_spInnerStream->Flush();
200 }
201
202 long SslStream::Seek(long offset, int origin) { throw IO::IOException("SslStream does not support seeking."); }
203 void SslStream::SetLength(long value) { throw IO::IOException("SslStream does not support seeking."); }
204
205 static int HandleReadError(int err, const SmartPointer<IO::Stream>& spStream, void* pBioIn, void* pBioOut) {
206 if (err == SSL_ERROR_WANT_READ) {
207 FlushBioOutbound(pBioOut, spStream);
208 char rawBuf[4096];
209 int read = spStream->Read(rawBuf, 0, sizeof(rawBuf));
210 if (read <= 0) return 0;
211 BIO_write(static_cast<BIO*>(pBioIn), rawBuf, read);
212 return -1;
213 }
214 if (err == SSL_ERROR_ZERO_RETURN) return 0;
215 if (err == SSL_ERROR_WANT_WRITE) {
216 FlushBioOutbound(pBioOut, spStream);
217 return -1;
218 }
219 char errBuf[256];
220 ERR_error_string_n(ERR_get_error(), errBuf, sizeof(errBuf));
221 throw IO::IOException(String("SSL read failed: ") + errBuf);
222 }
223
224 int SslStream::Read(char* buffer, int offset, int count) {
226 if (m_bDisposed) throw IO::IOException("Stream is disposed.");
227 if (!m_pSsl) throw IO::IOException("SslStream is not authenticated.");
228 SSL* ssl = static_cast<SSL*>(m_pSsl);
230 while (true) {
231 int ret = SSL_read(ssl, buffer + offset, count);
232 if (ret > 0) return ret;
233 int r = HandleReadError(SSL_get_error(ssl, ret), m_spInnerStream, m_pBioIn, m_pBioOut);
234 if (r >= 0) return r;
235 }
236 }
237
238 static void HandleWriteError(int err, const SmartPointer<IO::Stream>& spStream, void* pBioIn, void* pBioOut) {
239 if (err == SSL_ERROR_WANT_WRITE) {
240 FlushBioOutbound(pBioOut, spStream);
241 } else if (err == SSL_ERROR_WANT_READ) {
242 FlushBioOutbound(pBioOut, spStream);
243 PumpNetworkToBio(spStream.Get(), pBioIn, "Connection closed during SSL write.");
244 } else {
245 char errBuf[256];
246 ERR_error_string_n(ERR_get_error(), errBuf, sizeof(errBuf));
247 throw IO::IOException(String("SSL write failed: ") + errBuf);
248 }
249 }
250
251 void SslStream::Write(const char* buffer, int offset, int count) {
253 if (m_bDisposed) throw IO::IOException("Stream is disposed.");
254 if (!m_pSsl) throw IO::IOException("SslStream is not authenticated.");
255 SSL* ssl = static_cast<SSL*>(m_pSsl);
257 int written = 0;
258 while (written < count) {
259 int ret = SSL_write(ssl, buffer + offset + written, count - written);
260 if (ret <= 0) {
261 HandleWriteError(SSL_get_error(ssl, ret), m_spInnerStream, m_pBioIn, m_pBioOut);
262 } else {
263 written += ret;
264 FlushOutboundBio();
265 }
266 }
267 }
268
271 if (!m_bDisposed) {
272 m_bDisposed = true;
273
275 if (m_pSsl) {
276 SSL_free(static_cast<SSL*>(m_pSsl));
277 m_pSsl = nullptr;
278 }
279
281 if (m_pSslCtx) {
282 SSL_CTX_free(static_cast<SSL_CTX*>(m_pSslCtx));
283 m_pSslCtx = nullptr;
284 }
285
286 m_pBioIn = nullptr;
287 m_pBioOut = nullptr;
288
290 if (!m_bLeaveInnerStreamOpen && !m_spInnerStream.IsNull()) {
291 m_spInnerStream->Dispose();
292 }
293 m_spInnerStream = nullptr;
294 }
295 }
296
297 }
298 }
299 }
300}
Defines the exception thrown when an invalid argument is provided to a method.
Defines the exception thrown when a null reference is passed to a method that does not accept it.
The exception that is thrown when an I/O error occurs.
Provides a stream that uses the Transport Layer Security (TLS) protocol to secure network communicati...
ArgumentException(const String &sMessage)
Initializes a new instance of the ArgumentException class with a specified error message.
ArgumentNullException(const String &sMessage)
Initializes a new instance of the ArgumentNullException class with a specified error message.
The exception that is thrown when an I/O error occurs.
Definition IOException.h:17
Provides a generic view of a sequence of bytes.
Definition Stream.h:16
virtual int Read(char *buffer, int offset, int count)=0
Reads a sequence of bytes from the current stream and advances the position within the stream by the ...
bool CanWrite() const override
Gets a value indicating whether the current stream supports writing.
void Flush() override
Flushes data written to the stream to the underlying transport.
void AuthenticateAsServer(const SmartPointer<::DotNetDupe::System::Security::Cryptography::X509Certificates::X509Certificate2 > &certificate)
Called by servers to authenticate the server and optionally the client in a client-server connection.
long GetLength() const override
Gets the length of the data in the stream (unsupported for TLS stream).
long GetPosition() const override
Gets the position within the current stream (unsupported for TLS stream).
void SetPosition(long value) override
Sets the position within the current stream (unsupported for TLS stream).
void AuthenticateAsClient(const String &targetHost)
Called by clients to authenticate the server and optionally the client in a client-server connection.
void SetLength(long value) override
Sets the length of this stream (unsupported for TLS stream).
int Read(char *buffer, int offset, int count) override
Reads data from this stream into the specified byte buffer.
SslStream(const SmartPointer< IO::Stream > &innerStream)
Initializes a new instance of the SslStream class using the specified inner stream.
Definition SslStream.cpp:34
~SslStream() override
Releases all unmanaged resources and closes OpenSSL structures.
Definition SslStream.cpp:64
bool CanSeek() const override
Gets a value indicating whether the current stream supports seeking (always false for TLS).
void Write(const char *buffer, int offset, int count) override
Encrypts and writes the specified number of bytes to the underlying stream.
long Seek(long offset, int origin) override
Sets the current position of this stream to the given value (unsupported).
bool CanRead() const override
Gets a value indicating whether the current stream supports reading.
void Dispose() override
Disposes TLS context, BIO buffers, and optionally inner transport.
A unified smart pointer that supports both unique and shared ownership semantics.
bool IsNull() const noexcept
Checks if the SmartPointer is null.
T * Get() const noexcept
Gets the raw pointer.
Represents text as a sequence of UTF-8 code units with culture-invariant operations.
Definition String.h:74
String()
Initializes a new instance of the String class to an empty string.
Definition String.cpp:64
const char * GetRawString() const
Definition String.cpp:230
static int HandleReadError(int err, const SmartPointer< IO::Stream > &spStream, void *pBioIn, void *pBioOut)
static void FlushBioOutbound(void *pBioOut, const SmartPointer< IO::Stream > &spInnerStream)
static std::once_flag s_sslInitOnce
Definition SslStream.cpp:23
static void PumpNetworkToBio(IO::Stream *pStream, void *pBioIn, const char *pErrorContext)
static void HandleWriteError(int err, const SmartPointer< IO::Stream > &spStream, void *pBioIn, void *pBioOut)
static void HandleHandshakeError(int err, void *pBioIn, void *pBioOut, const SmartPointer< IO::Stream > &spStream)
static SSL * CreateAndBindSsl(SSL_CTX *ctx, void *&pBioIn, void *&pBioOut)
Definition SslStream.cpp:99