1 /**
2  * OpenSSL support.
3  *
4  * License:
5  *   This Source Code Form is subject to the terms of
6  *   the Mozilla Public License, v. 2.0. If a copy of
7  *   the MPL was not distributed with this file, You
8  *   can obtain one at http://mozilla.org/MPL/2.0/.
9  *
10  * Authors:
11  *   Vladimir Panteleev <ae@cy.md>
12  */
13 
14 /**
15    This module selects which OpenSSL version to target depending on
16    what version of D bindings are available. The "openssl" Deimos
17    package version 1.x targets OpenSSL 1.0, and version 2.x targets
18    OpenSSL 1.1.
19 
20    If you use ae with Dub, you can specify the version of the OpenSSL
21    D bindings in your project's dub.sdl. The ae:openssl subpackage
22    also has configurations which indicate the library file names to
23    link against.
24 
25    Thus, to target OpenSSL 1.0, you can use:
26 
27    ---
28    dependency "ae:openssl" version="..."
29    dependency "openssl" version="~>1.0"
30    subConfiguration "ae:openssl" "lib-explicit-1.0"
31    ---
32 
33    And, to target OpenSSL 1.1:
34 
35    ---
36    dependency "ae:openssl" version="..."
37    dependency "openssl" version="~>2.0"
38    subConfiguration "ae:openssl" "lib-implicit-1.1"
39    ---
40  */
41 
42 module ae.net.ssl.openssl;
43 
44 import core.stdc.stdint;
45 
46 import std.conv : to;
47 import std.exception : enforce, errnoEnforce;
48 import std.functional;
49 import std.socket;
50 import std.string;
51 
52 //import deimos.openssl.rand;
53 import deimos.openssl.ssl;
54 import deimos.openssl.err;
55 import deimos.openssl.x509_vfy;
56 import deimos.openssl.x509v3;
57 
58 import ae.net.asockets;
59 import ae.net.ssl;
60 import ae.utils.exception : CaughtException;
61 import ae.utils.meta : enumLength;
62 import ae.utils.text;
63 
64 debug(OPENSSL) import std.stdio : stderr;
65 
66 // ***************************************************************************
67 
68 /// Are the current Deimos OpenSSL bindings 1.1 or newer?
69 static if (is(typeof(OPENSSL_MAKE_VERSION)))
70 	enum isOpenSSL11 = OPENSSL_VERSION_NUMBER >= OPENSSL_MAKE_VERSION(1, 1, 0, 0);
71 else
72 	enum isOpenSSL11 = false;
73 
74 /// `mixin` this in your program to link to OpenSSL.
75 mixin template SSLUseLib()
76 {
77 	static if (ae.net.ssl.openssl.isOpenSSL11)
78 	{
79 		pragma(lib, "ssl");
80 		pragma(lib, "crypto");
81 	}
82 	else
83 	{
84 		version(Win64)
85 		{
86 			pragma(lib, "ssleay32");
87 			pragma(lib, "libeay32");
88 		}
89 		else
90 		{
91 			pragma(lib, "ssl");
92 			version(Windows)
93 				{ pragma(lib, "eay"); }
94 			else
95 				{ pragma(lib, "crypto"); }
96 		}
97 	}
98 }
99 
100 // Patch up incomplete Deimos bindings.
101 
102 private
103 {
104 	enum TLS1_3_VERSION = 0x0304;
105 	enum SSL_CTRL_SET_MIN_PROTO_VERSION          = 123;
106 	enum SSL_CTRL_SET_MAX_PROTO_VERSION          = 124;
107 	long SSL_CTX_set_min_proto_version(SSL_CTX* ctx, int version_) { return SSL_CTX_ctrl(ctx, SSL_CTRL_SET_MIN_PROTO_VERSION, version_, null); }
108 	long SSL_CTX_set_max_proto_version(SSL_CTX* ctx, int version_) { return SSL_CTX_ctrl(ctx, SSL_CTRL_SET_MAX_PROTO_VERSION, version_, null); }
109 
110 	static if (isOpenSSL11)
111 	{
112 		alias SSLv23_client_method = TLS_client_method;
113 		alias SSLv23_server_method = TLS_server_method;
114 		void SSL_load_error_strings() {}
115 		struct OPENSSL_INIT_SETTINGS;
116 		extern(C) void OPENSSL_init_ssl(uint64_t opts, const OPENSSL_INIT_SETTINGS *settings) nothrow;
117 		void SSL_library_init() { OPENSSL_init_ssl(0, null); }
118 		void OpenSSL_add_all_algorithms() { SSL_library_init(); }
119 		extern(C) BIGNUM *BN_get_rfc3526_prime_1536(BIGNUM *bn) nothrow;
120 		alias get_rfc3526_prime_1536 = BN_get_rfc3526_prime_1536;
121 		extern(C) BIGNUM *BN_get_rfc3526_prime_2048(BIGNUM *bn) nothrow;
122 		alias get_rfc3526_prime_2048 = BN_get_rfc3526_prime_2048;
123 		extern(C) BIGNUM *BN_get_rfc3526_prime_3072(BIGNUM *bn) nothrow;
124 		alias get_rfc3526_prime_3072 = BN_get_rfc3526_prime_3072;
125 		extern(C) BIGNUM *BN_get_rfc3526_prime_4096(BIGNUM *bn) nothrow;
126 		alias get_rfc3526_prime_4096 = BN_get_rfc3526_prime_4096;
127 		extern(C) BIGNUM *BN_get_rfc3526_prime_6144(BIGNUM *bn) nothrow;
128 		alias get_rfc3526_prime_6144 = BN_get_rfc3526_prime_6144;
129 		extern(C) BIGNUM *BN_get_rfc3526_prime_8192(BIGNUM *bn) nothrow;
130 		alias get_rfc3526_prime_8192 = BN_get_rfc3526_prime_8192;
131 		extern(C) int SSL_in_init(const SSL *s) nothrow;
132 		extern(C) int SSL_CTX_set_ciphersuites(SSL_CTX* ctx, const(char)* str);
133 	}
134 	else
135 	{
136 		extern(C) void X509_VERIFY_PARAM_set_hostflags(X509_VERIFY_PARAM *param, uint flags) nothrow;
137 		extern(C) X509_VERIFY_PARAM *SSL_get0_param(SSL *ssl) nothrow;
138 		enum X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS = 0x4;
139 		extern(C) int X509_VERIFY_PARAM_set1_host(X509_VERIFY_PARAM *param, const char *name, size_t namelen) nothrow;
140 	}
141 }
142 
143 // ***************************************************************************
144 
145 shared static this()
146 {
147 	SSL_load_error_strings();
148 	SSL_library_init();
149 	OpenSSL_add_all_algorithms();
150 }
151 
152 // ***************************************************************************
153 
154 /// `SSLProvider` implementation.
155 class OpenSSLProvider : SSLProvider
156 {
157 	override SSLContext createContext(SSLContext.Kind kind)
158 	{
159 		return new OpenSSLContext(kind);
160 	} ///
161 
162 	override SSLAdapter createAdapter(SSLContext context, IConnection next)
163 	{
164 		auto ctx = cast(OpenSSLContext)context;
165 		assert(ctx, "Not an OpenSSLContext");
166 		return new OpenSSLAdapter(ctx, next);
167 	} ///
168 }
169 
170 /// `SSLContext` implementation.
171 class OpenSSLContext : SSLContext
172 {
173 	SSL_CTX* sslCtx; /// The C OpenSSL context object.
174 	Kind kind; /// Client or server.
175 	Verify verify; ///
176 
177 	const(ubyte)[] psk; /// PSK (Pre-Shared Key) configuration.
178 	string pskID; /// ditto
179 
180 	this(Kind kind)
181 	{
182 		this.kind = kind;
183 
184 		const(SSL_METHOD)* method;
185 
186 		final switch (kind)
187 		{
188 			case Kind.client:
189 				method = SSLv23_client_method().sslEnforce();
190 				break;
191 			case Kind.server:
192 				method = SSLv23_server_method().sslEnforce();
193 				break;
194 		}
195 		sslCtx = SSL_CTX_new(method).sslEnforce();
196 		setCipherList(["ALL", "!MEDIUM", "!LOW", "!aNULL", "!eNULL", "!SSLv2", "!DH", "!TLSv1"]);
197 
198 		SSL_CTX_set_default_verify_paths(sslCtx);
199 	} ///
200 
201 	/// OpenSSL uses different APIs to specify the cipher list for
202 	/// TLSv1.2 and below and to specify the ciphersuites for TLSv1.3.
203 	/// When calling `setCipherList`, use this value to delimit them:
204 	/// values before `cipherListTLS13Delimiter` will be specified via
205 	/// SSL_CTX_set_cipher_list (for TLSv1.2 and older), and those
206 	/// after `cipherListTLS13Delimiter` will be specified via
207 	/// `SSL_CTX_set_ciphersuites` (for TLSv1.3).
208 	static immutable cipherListTLS13Delimiter = "\0ae-net-ssl-openssl-cipher-list-tls-1.3-delimiter";
209 
210 	override void setCipherList(string[] ciphers)
211 	{
212 		assert(ciphers.length, "Empty cipher list");
213 		import std.algorithm.searching : findSplit;
214 		auto parts = ciphers.findSplit((&cipherListTLS13Delimiter)[0..1]);
215 		auto oldCiphers = parts[0];
216 		auto newCiphers = parts[2];
217 		if (oldCiphers.length)
218 			SSL_CTX_set_cipher_list(sslCtx, oldCiphers.join(":").toStringz()).sslEnforce();
219 		if (newCiphers.length)
220 		{
221 			static if (isOpenSSL11)
222 				SSL_CTX_set_ciphersuites(sslCtx, newCiphers.join(":").toStringz()).sslEnforce();
223 			else
224 				assert(false, "Not built against OpenSSL version with TLSv1.3 support.");
225 		}
226 	} /// `SSLContext` method implementation.
227 
228 	override void enableDH(int bits)
229 	{
230 		typeof(&get_rfc3526_prime_2048) func;
231 
232 		switch (bits)
233 		{
234 			case 1536: func = &get_rfc3526_prime_1536; break;
235 			case 2048: func = &get_rfc3526_prime_2048; break;
236 			case 3072: func = &get_rfc3526_prime_3072; break;
237 			case 4096: func = &get_rfc3526_prime_4096; break;
238 			case 6144: func = &get_rfc3526_prime_6144; break;
239 			case 8192: func = &get_rfc3526_prime_8192; break;
240 			default: assert(false, "No RFC3526 prime available for %d bits".format(bits));
241 		}
242 
243 		DH* dh;
244 		scope(exit) DH_free(dh);
245 
246 		dh = DH_new().sslEnforce();
247 		dh.p = func(null).sslEnforce();
248 		ubyte gen = 2;
249 		dh.g = BN_bin2bn(&gen, gen.sizeof, null);
250 		SSL_CTX_set_tmp_dh(sslCtx, dh).sslEnforce();
251 	} /// ditto
252 
253 	override void enableECDH()
254 	{
255 		auto ecdh = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1).sslEnforce();
256 		scope(exit) EC_KEY_free(ecdh);
257 		SSL_CTX_set_tmp_ecdh(sslCtx, ecdh).sslEnforce();
258 	} /// ditto
259 
260 	override void setCertificate(string path)
261 	{
262 		SSL_CTX_use_certificate_chain_file(sslCtx, toStringz(path))
263 			.sslEnforce("Failed to load certificate file " ~ path);
264 	} /// ditto
265 
266 	override void setPrivateKey(string path)
267 	{
268 		SSL_CTX_use_PrivateKey_file(sslCtx, toStringz(path), SSL_FILETYPE_PEM)
269 			.sslEnforce("Failed to load private key file " ~ path);
270 	} /// ditto
271 
272 	override void setPreSharedKey(string id, const(ubyte)[] key)
273 	{
274 		pskID = id;
275 		psk = key;
276 
277 		final switch (kind)
278 		{
279 			case Kind.client: SSL_CTX_set_psk_client_callback(sslCtx, psk ? &pskClientCallback : null); break;
280 			case Kind.server: SSL_CTX_set_psk_server_callback(sslCtx, psk ? &pskServerCallback : null); break;
281 		}
282 	} /// ditto
283 
284 	extern (C) private static uint pskClientCallback(
285 		SSL* ssl, const(char)* hint,
286 		char* identity, uint max_identity_len, ubyte* psk,
287 		uint max_psk_len)
288 	{
289 		debug(OPENSSL) stderr.writeln("pskClientCallback! hint=", hint);
290 
291 		auto self = cast(OpenSSLAdapter)SSL_get_ex_data(ssl, 0);
292 		if (self.context.pskID.length + 1 > max_identity_len ||
293 			self.context.psk.length       > max_psk_len)
294 		{
295 			debug(OPENSSL) stderr.writeln("PSK or PSK ID too long");
296 			return 0;
297 		}
298 
299 		identity[0 .. self.context.pskID.length] = self.context.pskID[];
300 		identity[     self.context.pskID.length] = 0;
301 		psk[0 .. self.context.psk.length] = self.context.psk[];
302 		return cast(uint)self.context.psk.length;
303 	}
304 
305 	extern (C) private static uint pskServerCallback(
306 		SSL* ssl, const(char)* identity,
307 		ubyte* psk, uint max_psk_len)
308 	{
309 		auto self = cast(OpenSSLAdapter)SSL_get_ex_data(ssl, 0);
310 		auto identityStr = fromStringz(identity);
311 		if (identityStr != self.context.pskID)
312 		{
313 			debug(OPENSSL) stderr.writefln("PSK ID mismatch: expected %s, got %s",
314 				self.context.pskID, identityStr);
315 			return 0;
316 		}
317 		if (self.context.psk.length > max_psk_len)
318 		{
319 			debug(OPENSSL) stderr.writeln("PSK too long");
320 			return 0;
321 		}
322 		psk[0 .. self.context.psk.length] = self.context.psk[];
323 		return cast(uint)self.context.psk.length;
324 	}
325 
326 	override void setPeerVerify(Verify verify)
327 	{
328 		static const int[enumLength!Verify] modes =
329 		[
330 			SSL_VERIFY_NONE,
331 			SSL_VERIFY_PEER,
332 			SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT,
333 		];
334 		SSL_CTX_set_verify(sslCtx, modes[verify], null);
335 		this.verify = verify;
336 	} /// ditto
337 
338 	override void setPeerRootCertificate(string path)
339 	{
340 		auto szPath = toStringz(path);
341 		SSL_CTX_load_verify_locations(sslCtx, szPath, null).sslEnforce();
342 
343 		if (kind == Kind.server)
344 		{
345 			auto list = SSL_load_client_CA_file(szPath).sslEnforce();
346 			SSL_CTX_set_client_CA_list(sslCtx, list);
347 		}
348 	} /// ditto
349 
350 	override void setFlags(int flags)
351 	{
352 		SSL_CTX_set_options(sslCtx, flags).sslEnforce();
353 	} /// ditto
354 
355 	private static immutable int[enumLength!SSLVersion] sslVersions = [
356 		0,
357 		SSL3_VERSION,
358 		TLS1_VERSION,
359 		TLS1_1_VERSION,
360 		TLS1_2_VERSION,
361 		TLS1_3_VERSION,
362 	];
363 
364 	override void setMinimumVersion(SSLVersion v)
365 	{
366 		SSL_CTX_set_min_proto_version(sslCtx, sslVersions[v]).sslEnforce();
367 	} /// ditto
368 
369 	override void setMaximumVersion(SSLVersion v)
370 	{
371 		SSL_CTX_set_max_proto_version(sslCtx, sslVersions[v]).sslEnforce();
372 	} /// ditto
373 }
374 
375 static this()
376 {
377 	ssl = new OpenSSLProvider();
378 }
379 
380 // ***************************************************************************
381 
382 /// `SSLAdapter` implementation.
383 class OpenSSLAdapter : SSLAdapter
384 {
385 	SSL* sslHandle; /// The C OpenSSL connection object.
386 	OpenSSLContext context; ///
387 	ConnectionState connectionState; ///
388 	const(char)* hostname; ///
389 
390 	this(OpenSSLContext context, IConnection next)
391 	{
392 		this.context = context;
393 		super(next);
394 
395 		sslHandle = sslEnforce(SSL_new(context.sslCtx));
396 		SSL_set_ex_data(sslHandle, 0, cast(void*)this).sslEnforce();
397 		SSL_set_bio(sslHandle, r.bio, w.bio);
398 
399 		if (next.state == ConnectionState.connected)
400 			initialize();
401 	} ///
402 
403 	override void onConnect()
404 	{
405 		initialize();
406 	} /// `SSLAdapter` method implementation.
407 
408 	override void onReadData(Data data)
409 	{
410 		debug(OPENSSL_DATA) stderr.writefln("OpenSSL: { Got %d incoming bytes from network", data.length);
411 
412 		if (next.state == ConnectionState.disconnecting)
413 		{
414 			return;
415 		}
416 
417 		assert(r.data.length == 0, "Would clobber data");
418 		r.set(data.contents);
419 
420 		try
421 		{
422 			// We must buffer all cleartext data and send it off in a
423 			// single `super.onReadData` call. It cannot be split up
424 			// into multiple calls, because the `readDataHandler` may
425 			// be set to null in the middle of our loop.
426 			Data clearText;
427 
428 			while (true)
429 			{
430 				static ubyte[4096] buf;
431 				debug(OPENSSL_DATA) auto oldLength = r.data.length;
432 				auto result = SSL_read(sslHandle, buf.ptr, buf.length);
433 				debug(OPENSSL_DATA) stderr.writefln("OpenSSL: < SSL_read ate %d bytes and spat out %d bytes", oldLength - r.data.length, result);
434 				if (result > 0)
435 				{
436 					updateState();
437 					clearText ~= buf[0..result];
438 				}
439 				else
440 				{
441 					sslError(result, "SSL_read");
442 					updateState();
443 					break;
444 				}
445 			}
446 			enforce(r.data.length == 0, "SSL did not consume all read data");
447 			super.onReadData(clearText);
448 		}
449 		catch (CaughtException e)
450 		{
451 			debug(OPENSSL) stderr.writeln("Error while %s and processing incoming data: %s".format(next.state, e.msg));
452 			if (next.state != ConnectionState.disconnecting && next.state != ConnectionState.disconnected)
453 				disconnect(e.msg, DisconnectType.error);
454 			else
455 				throw e;
456 		}
457 	} /// `SSLAdapter` method implementation.
458 
459 	override void send(scope Data[] data, int priority = DEFAULT_PRIORITY)
460 	{
461 		assert(state == ConnectionState.connected, "Attempting to send to a non-connected socket");
462 		while (data.length)
463 		{
464 			auto datum = data[0];
465 			data = data[1 .. $];
466 			if (!datum.length)
467 				continue;
468 
469 			debug(OPENSSL_DATA) stderr.writefln("OpenSSL: > Got %d outgoing bytes from program", datum.length);
470 
471 			debug(OPENSSL_DATA) auto oldLength = w.data.length;
472 			auto result = SSL_write(sslHandle, datum.ptr, datum.length.to!int);
473 			debug(OPENSSL_DATA) stderr.writefln("OpenSSL:   SSL_write ate %d bytes and spat out %d bytes", datum.length, w.data.length - oldLength);
474 			if (result > 0)
475 			{
476 				// "SSL_write() will only return with success, when the
477 				// complete contents of buf of length num has been written."
478 			}
479 			else
480 			{
481 				sslError(result, "SSL_write");
482 				break;
483 			}
484 		}
485 		updateState();
486 	} /// ditto
487 
488 	override @property ConnectionState state()
489 	{
490 		return connectionState;
491 	} /// ditto
492 
493 	override void disconnect(string reason, DisconnectType type)
494 	{
495 		debug(OPENSSL) stderr.writefln("OpenSSL: disconnect called ('%s')", reason);
496 		if (!SSL_in_init(sslHandle))
497 		{
498 			debug(OPENSSL) stderr.writefln("OpenSSL: Calling SSL_shutdown");
499 			SSL_shutdown(sslHandle);
500 			connectionState = ConnectionState.disconnecting;
501 			updateState();
502 		}
503 		else
504 			debug(OPENSSL) stderr.writefln("OpenSSL: In init, not calling SSL_shutdown");
505 		debug(OPENSSL) stderr.writefln("OpenSSL: SSL_shutdown done, flushing");
506 		debug(OPENSSL) stderr.writefln("OpenSSL: SSL_shutdown output flushed");
507 		super.disconnect(reason, type);
508 	} /// ditto
509 
510 	override void onDisconnect(string reason, DisconnectType type)
511 	{
512 		debug(OPENSSL) stderr.writefln("OpenSSL: onDisconnect ('%s'), calling SSL_free", reason);
513 		r.clear();
514 		w.clear();
515 		SSL_free(sslHandle);
516 		sslHandle = null;
517 		r = MemoryBIO.init; // Was owned by sslHandle, destroyed by SSL_free
518 		w = MemoryBIO.init; // ditto
519 		connectionState = ConnectionState.disconnected;
520 		debug(OPENSSL) stderr.writeln("OpenSSL: onDisconnect: SSL_free called, calling super.onDisconnect");
521 		super.onDisconnect(reason, type);
522 		debug(OPENSSL) stderr.writeln("OpenSSL: onDisconnect finished");
523 	} /// ditto
524 
525 	override void setHostName(string hostname, ushort port = 0, string service = null)
526 	{
527 		this.hostname = cast(char*)hostname.toStringz();
528 		SSL_set_tlsext_host_name(sslHandle, cast(char*)this.hostname);
529 	} /// ditto
530 
531 	override OpenSSLCertificate getHostCertificate()
532 	{
533 		return new OpenSSLCertificate(SSL_get_certificate(sslHandle).sslEnforce());
534 	} /// ditto
535 
536 	override OpenSSLCertificate getPeerCertificate()
537 	{
538 		return new OpenSSLCertificate(SSL_get_peer_certificate(sslHandle).sslEnforce());
539 	} /// ditto
540 
541 protected:
542 	MemoryBIO r; // BIO for incoming ciphertext
543 	MemoryBIO w; // BIO for outgoing ciphertext
544 
545 	private final void initialize()
546 	{
547 		final switch (context.kind)
548 		{
549 			case OpenSSLContext.Kind.client: SSL_connect(sslHandle).sslEnforce(); break;
550 			case OpenSSLContext.Kind.server: SSL_accept (sslHandle).sslEnforce(); break;
551 		}
552 		connectionState = ConnectionState.connecting;
553 		updateState();
554 
555 		if (context.verify && hostname && context.kind == OpenSSLContext.Kind.client)
556 		{
557 			static if (!isOpenSSL11)
558 			{
559 				import core.stdc.string : strlen;
560 				X509_VERIFY_PARAM* param = SSL_get0_param(sslHandle);
561 				X509_VERIFY_PARAM_set_hostflags(param, X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS);
562 				X509_VERIFY_PARAM_set1_host(param, hostname, strlen(hostname)).sslEnforce("X509_VERIFY_PARAM_set1_host");
563 			}
564 			else
565 			{
566 				SSL_set_hostflags(sslHandle, X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS);
567 				SSL_set1_host(sslHandle, hostname).sslEnforce("SSL_set1_host");
568 			}
569 		}
570 	}
571 
572 	protected final void updateState()
573 	{
574 		// Flush any accumulated outgoing ciphertext to the network
575 		if (w.data.length)
576 		{
577 			debug(OPENSSL_DATA) stderr.writefln("OpenSSL: } Flushing %d outgoing bytes from OpenSSL to network", w.data.length);
578 			next.send(Data(w.data));
579 			w.clear();
580 		}
581 
582 		// Has the handshake been completed?
583 		if (connectionState == ConnectionState.connecting && SSL_is_init_finished(sslHandle))
584 		{
585 			connectionState = ConnectionState.connected;
586 			if (context.verify)
587 				try
588 					if (!SSL_get_peer_certificate(sslHandle))
589 						enforce(context.verify != SSLContext.Verify.require, "No SSL peer certificate was presented");
590 					else
591 					{
592 						auto result = SSL_get_verify_result(sslHandle);
593 						enforce(result == X509_V_OK,
594 							"SSL peer verification failed with error " ~ result.to!string);
595 					}
596 				catch (Exception e)
597 				{
598 					disconnect(e.msg, DisconnectType.error);
599 					return;
600 				}
601 			super.onConnect();
602 		}
603 	}
604 
605 	alias send = SSLAdapter.send;
606 
607 	void sslError(int ret, string msg)
608 	{
609 		auto err = SSL_get_error(sslHandle, ret);
610 		debug(OPENSSL) stderr.writefln("OpenSSL: SSL error ('%s', ret %d): %s", msg, ret, err);
611 		switch (err)
612 		{
613 			case SSL_ERROR_WANT_READ:
614 			case SSL_ERROR_ZERO_RETURN:
615 				return;
616 			case SSL_ERROR_SYSCALL:
617 				errnoEnforce(false, msg ~ " failed");
618 				assert(false);
619 			default:
620 				sslEnforce(false, "%s failed - error code %s".format(msg, err));
621 		}
622 	}
623 }
624 
625 /// `SSLCertificate` implementation.
626 class OpenSSLCertificate : SSLCertificate
627 {
628 	X509* x509; /// The C OpenSSL certificate object.
629 
630 	this(X509* x509)
631 	{
632 		this.x509 = x509;
633 	} ///
634 
635 	override string getSubjectName()
636 	{
637 		char[256] buf;
638 		X509_NAME_oneline(X509_get_subject_name(x509), buf.ptr, buf.length);
639 		buf[$-1] = 0;
640 		return buf.ptr.to!string();
641 	} /// `SSLCertificate` method implementation.
642 }
643 
644 // ***************************************************************************
645 
646 /// TODO: replace with custom BIO which hooks into IConnection
647 struct MemoryBIO
648 {
649 	@disable this(this);
650 
651 	this(const(void)[] data)
652 	{
653 		bio_ = BIO_new_mem_buf(cast(void*)data.ptr, data.length.to!int);
654 	} ///
655 
656 	void set(const(void)[] data)
657 	{
658 		BUF_MEM *bptr = BUF_MEM_new();
659 		if (data.length)
660 		{
661 			BUF_MEM_grow(bptr, data.length);
662 			bptr.data[0..bptr.length] = cast(char[])data;
663 		}
664 		BIO_set_mem_buf(bio, bptr, BIO_CLOSE);
665 	} ///
666 
667 	void clear() { set(null); } ///
668 
669 	@property BIO* bio()
670 	{
671 		if (!bio_)
672 		{
673 			bio_ = sslEnforce(BIO_new(BIO_s_mem()));
674 			BIO_set_close(bio_, BIO_CLOSE);
675 		}
676 		return bio_;
677 	} ///
678 
679 	const(void)[] data()
680 	{
681 		BUF_MEM *bptr;
682 		BIO_get_mem_ptr(bio, &bptr);
683 		return bptr.data[0..bptr.length];
684 	} ///
685 
686 private:
687 	BIO* bio_;
688 }
689 
690 /// Convert an OpenSSL error into a thrown D exception.
691 T sslEnforce(T)(T v, string message = null)
692 {
693 	if (v)
694 		return v;
695 
696 	{
697 		MemoryBIO m;
698 		ERR_print_errors(m.bio);
699 		string msg = (cast(char[])m.data).idup;
700 
701 		if (message)
702 			msg = message ~ ": " ~ msg;
703 
704 		throw new Exception(msg);
705 	}
706 }
707 
708 // ***************************************************************************
709 
710 version (unittest) import ae.net.ssl.test;
711 unittest { testSSL(new OpenSSLProvider); }