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 static if (isOpenSSL11)
104 {
105 	alias SSLv23_client_method = TLSv1_2_client_method;
106 	alias SSLv23_server_method = TLSv1_2_server_method;
107 	void SSL_load_error_strings() {}
108 	struct OPENSSL_INIT_SETTINGS;
109 	extern(C) void OPENSSL_init_ssl(uint64_t opts, const OPENSSL_INIT_SETTINGS *settings) nothrow;
110 	void SSL_library_init() { OPENSSL_init_ssl(0, null); }
111 	void OpenSSL_add_all_algorithms() { SSL_library_init(); }
112 	extern(C) BIGNUM *BN_get_rfc3526_prime_1536(BIGNUM *bn) nothrow;
113 	alias get_rfc3526_prime_1536 = BN_get_rfc3526_prime_1536;
114 	extern(C) BIGNUM *BN_get_rfc3526_prime_2048(BIGNUM *bn) nothrow;
115 	alias get_rfc3526_prime_2048 = BN_get_rfc3526_prime_2048;
116 	extern(C) BIGNUM *BN_get_rfc3526_prime_3072(BIGNUM *bn) nothrow;
117 	alias get_rfc3526_prime_3072 = BN_get_rfc3526_prime_3072;
118 	extern(C) BIGNUM *BN_get_rfc3526_prime_4096(BIGNUM *bn) nothrow;
119 	alias get_rfc3526_prime_4096 = BN_get_rfc3526_prime_4096;
120 	extern(C) BIGNUM *BN_get_rfc3526_prime_6144(BIGNUM *bn) nothrow;
121 	alias get_rfc3526_prime_6144 = BN_get_rfc3526_prime_6144;
122 	extern(C) BIGNUM *BN_get_rfc3526_prime_8192(BIGNUM *bn) nothrow;
123 	alias get_rfc3526_prime_8192 = BN_get_rfc3526_prime_8192;
124 	extern(C) int SSL_in_init(const SSL *s) nothrow;
125 }
126 else
127 {
128 	extern(C) void X509_VERIFY_PARAM_set_hostflags(X509_VERIFY_PARAM *param, uint flags) nothrow;
129 	extern(C) X509_VERIFY_PARAM *SSL_get0_param(SSL *ssl) nothrow;
130 	enum X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS = 0x4;
131 	extern(C) int X509_VERIFY_PARAM_set1_host(X509_VERIFY_PARAM *param, const char *name, size_t namelen) nothrow;
132 }
133 
134 // ***************************************************************************
135 
136 shared static this()
137 {
138 	SSL_load_error_strings();
139 	SSL_library_init();
140 	OpenSSL_add_all_algorithms();
141 }
142 
143 // ***************************************************************************
144 
145 /// `SSLProvider` implementation.
146 class OpenSSLProvider : SSLProvider
147 {
148 	override SSLContext createContext(SSLContext.Kind kind)
149 	{
150 		return new OpenSSLContext(kind);
151 	} ///
152 
153 	override SSLAdapter createAdapter(SSLContext context, IConnection next)
154 	{
155 		auto ctx = cast(OpenSSLContext)context;
156 		assert(ctx, "Not an OpenSSLContext");
157 		return new OpenSSLAdapter(ctx, next);
158 	} ///
159 }
160 
161 /// `SSLContext` implementation.
162 class OpenSSLContext : SSLContext
163 {
164 	SSL_CTX* sslCtx; /// The C OpenSSL context object.
165 	Kind kind; /// Client or server.
166 	Verify verify; ///
167 
168 	const(ubyte)[] psk; /// PSK (Pre-Shared Key) configuration.
169 	string pskID; /// ditto
170 
171 	this(Kind kind)
172 	{
173 		this.kind = kind;
174 
175 		const(SSL_METHOD)* method;
176 
177 		final switch (kind)
178 		{
179 			case Kind.client:
180 				method = SSLv23_client_method().sslEnforce();
181 				break;
182 			case Kind.server:
183 				method = SSLv23_server_method().sslEnforce();
184 				break;
185 		}
186 		sslCtx = SSL_CTX_new(method).sslEnforce();
187 		setCipherList(["ALL", "!MEDIUM", "!LOW", "!aNULL", "!eNULL", "!SSLv2", "!DH", "!TLSv1"]);
188 
189 		SSL_CTX_set_default_verify_paths(sslCtx);
190 	} ///
191 
192 	override void setCipherList(string[] ciphers)
193 	{
194 		SSL_CTX_set_cipher_list(sslCtx, ciphers.join(":").toStringz()).sslEnforce();
195 	} /// `SSLContext` method implementation.
196 
197 	override void enableDH(int bits)
198 	{
199 		typeof(&get_rfc3526_prime_2048) func;
200 
201 		switch (bits)
202 		{
203 			case 1536: func = &get_rfc3526_prime_1536; break;
204 			case 2048: func = &get_rfc3526_prime_2048; break;
205 			case 3072: func = &get_rfc3526_prime_3072; break;
206 			case 4096: func = &get_rfc3526_prime_4096; break;
207 			case 6144: func = &get_rfc3526_prime_6144; break;
208 			case 8192: func = &get_rfc3526_prime_8192; break;
209 			default: assert(false, "No RFC3526 prime available for %d bits".format(bits));
210 		}
211 
212 		DH* dh;
213 		scope(exit) DH_free(dh);
214 
215 		dh = DH_new().sslEnforce();
216 		dh.p = func(null).sslEnforce();
217 		ubyte gen = 2;
218 		dh.g = BN_bin2bn(&gen, gen.sizeof, null);
219 		SSL_CTX_set_tmp_dh(sslCtx, dh).sslEnforce();
220 	} /// ditto
221 
222 	override void enableECDH()
223 	{
224 		auto ecdh = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1).sslEnforce();
225 		scope(exit) EC_KEY_free(ecdh);
226 		SSL_CTX_set_tmp_ecdh(sslCtx, ecdh).sslEnforce();
227 	} /// ditto
228 
229 	override void setCertificate(string path)
230 	{
231 		SSL_CTX_use_certificate_chain_file(sslCtx, toStringz(path))
232 			.sslEnforce("Failed to load certificate file " ~ path);
233 	} /// ditto
234 
235 	override void setPrivateKey(string path)
236 	{
237 		SSL_CTX_use_PrivateKey_file(sslCtx, toStringz(path), SSL_FILETYPE_PEM)
238 			.sslEnforce("Failed to load private key file " ~ path);
239 	} /// ditto
240 
241 	override void setPreSharedKey(string id, const(ubyte)[] key)
242 	{
243 		pskID = id;
244 		psk = key;
245 	} /// ditto
246 
247 	override void setPeerVerify(Verify verify)
248 	{
249 		static const int[enumLength!Verify] modes =
250 		[
251 			SSL_VERIFY_NONE,
252 			SSL_VERIFY_PEER,
253 			SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT,
254 		];
255 		SSL_CTX_set_verify(sslCtx, modes[verify], null);
256 		this.verify = verify;
257 	} /// ditto
258 
259 	override void setPeerRootCertificate(string path)
260 	{
261 		auto szPath = toStringz(path);
262 		SSL_CTX_load_verify_locations(sslCtx, szPath, null).sslEnforce();
263 
264 		if (kind == Kind.server)
265 		{
266 			auto list = SSL_load_client_CA_file(szPath).sslEnforce();
267 			SSL_CTX_set_client_CA_list(sslCtx, list);
268 		}
269 	} /// ditto
270 
271 	override void setFlags(int flags)
272 	{
273 		SSL_CTX_set_options(sslCtx, flags).sslEnforce();
274 	} /// ditto
275 }
276 
277 static this()
278 {
279 	ssl = new OpenSSLProvider();
280 }
281 
282 // ***************************************************************************
283 
284 /// `SSLAdapter` implementation.
285 class OpenSSLAdapter : SSLAdapter
286 {
287 	SSL* sslHandle; /// The C OpenSSL connection object.
288 	OpenSSLContext context; ///
289 	ConnectionState connectionState; ///
290 	const(char)* hostname; ///
291 
292 	this(OpenSSLContext context, IConnection next)
293 	{
294 		this.context = context;
295 		super(next);
296 
297 		sslHandle = sslEnforce(SSL_new(context.sslCtx));
298 		SSL_set_ex_data(sslHandle, 0, cast(void*)this).sslEnforce();
299 		SSL_set_bio(sslHandle, r.bio, w.bio);
300 
301 		if (next.state == ConnectionState.connected)
302 			initialize();
303 	} ///
304 
305 	override void onConnect()
306 	{
307 		initialize();
308 	} /// `SSLAdapter` method implementation.
309 
310 	override void onReadData(Data data)
311 	{
312 		debug(OPENSSL_DATA) stderr.writefln("OpenSSL: { Got %d incoming bytes from network", data.length);
313 
314 		if (next.state == ConnectionState.disconnecting)
315 		{
316 			return;
317 		}
318 
319 		assert(r.data.length == 0, "Would clobber data");
320 		r.set(data.contents);
321 
322 		try
323 		{
324 			// We must buffer all cleartext data and send it off in a
325 			// single `super.onReadData` call. It cannot be split up
326 			// into multiple calls, because the `readDataHandler` may
327 			// be set to null in the middle of our loop.
328 			Data clearText;
329 
330 			while (true)
331 			{
332 				static ubyte[4096] buf;
333 				debug(OPENSSL_DATA) auto oldLength = r.data.length;
334 				auto result = SSL_read(sslHandle, buf.ptr, buf.length);
335 				debug(OPENSSL_DATA) stderr.writefln("OpenSSL: < SSL_read ate %d bytes and spat out %d bytes", oldLength - r.data.length, result);
336 				if (result > 0)
337 				{
338 					updateState();
339 					clearText ~= buf[0..result];
340 				}
341 				else
342 				{
343 					sslError(result, "SSL_read");
344 					updateState();
345 					break;
346 				}
347 			}
348 			enforce(r.data.length == 0, "SSL did not consume all read data");
349 			super.onReadData(clearText);
350 		}
351 		catch (CaughtException e)
352 		{
353 			debug(OPENSSL) stderr.writeln("Error while %s and processing incoming data: %s".format(next.state, e.msg));
354 			if (next.state != ConnectionState.disconnecting && next.state != ConnectionState.disconnected)
355 				disconnect(e.msg, DisconnectType.error);
356 			else
357 				throw e;
358 		}
359 	} /// `SSLAdapter` method implementation.
360 
361 	override void send(scope Data[] data, int priority = DEFAULT_PRIORITY)
362 	{
363 		assert(state == ConnectionState.connected, "Attempting to send to a non-connected socket");
364 		while (data.length)
365 		{
366 			auto datum = data[0];
367 			data = data[1 .. $];
368 			if (!datum.length)
369 				continue;
370 
371 			debug(OPENSSL_DATA) stderr.writefln("OpenSSL: > Got %d outgoing bytes from program", datum.length);
372 
373 			debug(OPENSSL_DATA) auto oldLength = w.data.length;
374 			auto result = SSL_write(sslHandle, datum.ptr, datum.length.to!int);
375 			debug(OPENSSL_DATA) stderr.writefln("OpenSSL:   SSL_write ate %d bytes and spat out %d bytes", datum.length, w.data.length - oldLength);
376 			if (result > 0)
377 			{
378 				// "SSL_write() will only return with success, when the
379 				// complete contents of buf of length num has been written."
380 			}
381 			else
382 			{
383 				sslError(result, "SSL_write");
384 				break;
385 			}
386 		}
387 		updateState();
388 	} /// ditto
389 
390 	override @property ConnectionState state()
391 	{
392 		return connectionState;
393 	} /// ditto
394 
395 	override void disconnect(string reason, DisconnectType type)
396 	{
397 		debug(OPENSSL) stderr.writefln("OpenSSL: disconnect called ('%s')", reason);
398 		if (!SSL_in_init(sslHandle))
399 		{
400 			debug(OPENSSL) stderr.writefln("OpenSSL: Calling SSL_shutdown");
401 			SSL_shutdown(sslHandle);
402 			connectionState = ConnectionState.disconnecting;
403 			updateState();
404 		}
405 		else
406 			debug(OPENSSL) stderr.writefln("OpenSSL: In init, not calling SSL_shutdown");
407 		debug(OPENSSL) stderr.writefln("OpenSSL: SSL_shutdown done, flushing");
408 		debug(OPENSSL) stderr.writefln("OpenSSL: SSL_shutdown output flushed");
409 		super.disconnect(reason, type);
410 	} /// ditto
411 
412 	override void onDisconnect(string reason, DisconnectType type)
413 	{
414 		debug(OPENSSL) stderr.writefln("OpenSSL: onDisconnect ('%s'), calling SSL_free", reason);
415 		r.clear();
416 		w.clear();
417 		SSL_free(sslHandle);
418 		sslHandle = null;
419 		r = MemoryBIO.init; // Was owned by sslHandle, destroyed by SSL_free
420 		w = MemoryBIO.init; // ditto
421 		connectionState = ConnectionState.disconnected;
422 		debug(OPENSSL) stderr.writeln("OpenSSL: onDisconnect: SSL_free called, calling super.onDisconnect");
423 		super.onDisconnect(reason, type);
424 		debug(OPENSSL) stderr.writeln("OpenSSL: onDisconnect finished");
425 	} /// ditto
426 
427 	override void setHostName(string hostname, ushort port = 0, string service = null)
428 	{
429 		this.hostname = cast(char*)hostname.toStringz();
430 		SSL_set_tlsext_host_name(sslHandle, cast(char*)this.hostname);
431 	} /// ditto
432 
433 	override OpenSSLCertificate getHostCertificate()
434 	{
435 		return new OpenSSLCertificate(SSL_get_certificate(sslHandle).sslEnforce());
436 	} /// ditto
437 
438 	override OpenSSLCertificate getPeerCertificate()
439 	{
440 		return new OpenSSLCertificate(SSL_get_peer_certificate(sslHandle).sslEnforce());
441 	} /// ditto
442 
443 protected:
444 	MemoryBIO r; // BIO for incoming ciphertext
445 	MemoryBIO w; // BIO for outgoing ciphertext
446 
447 	private final void initialize()
448 	{
449 		if (context.psk)
450 		{
451 			final switch (context.kind)
452 			{
453 				case OpenSSLContext.Kind.client: SSL_set_psk_client_callback(sslHandle, &pskClientCallback); break;
454 				case OpenSSLContext.Kind.server: SSL_set_psk_server_callback(sslHandle, &pskServerCallback); break;
455 			}
456 		}
457 
458 		final switch (context.kind)
459 		{
460 			case OpenSSLContext.Kind.client: SSL_connect(sslHandle).sslEnforce(); break;
461 			case OpenSSLContext.Kind.server: SSL_accept (sslHandle).sslEnforce(); break;
462 		}
463 		connectionState = ConnectionState.connecting;
464 		updateState();
465 
466 		if (context.verify && hostname && context.kind == OpenSSLContext.Kind.client)
467 		{
468 			static if (!isOpenSSL11)
469 			{
470 				import core.stdc.string : strlen;
471 				X509_VERIFY_PARAM* param = SSL_get0_param(sslHandle);
472 				X509_VERIFY_PARAM_set_hostflags(param, X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS);
473 				X509_VERIFY_PARAM_set1_host(param, hostname, strlen(hostname)).sslEnforce("X509_VERIFY_PARAM_set1_host");
474 			}
475 			else
476 			{
477 				SSL_set_hostflags(sslHandle, X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS);
478 				SSL_set1_host(sslHandle, hostname).sslEnforce("SSL_set1_host");
479 			}
480 		}
481 	}
482 
483 	extern (C) private static uint pskClientCallback(
484 		SSL* ssl, const(char)* hint,
485 		char* identity, uint max_identity_len, ubyte* psk,
486 		uint max_psk_len)
487 	{
488 		auto self = cast(typeof(this))SSL_get_ex_data(ssl, 0);
489 		if (self.context.pskID.length + 1 > max_identity_len ||
490 			self.context.psk.length       > max_psk_len)
491 		{
492 			debug(OPENSSL) stderr.writeln("PSK or PSK ID too long");
493 			return 0;
494 		}
495 
496 		identity[0 .. self.context.pskID.length] = self.context.pskID[];
497 		identity[     self.context.pskID.length] = 0;
498 		psk[0 .. self.context.psk.length] = self.context.psk[];
499 		return cast(uint)self.context.psk.length;
500 	}
501 
502 	extern (C) private static uint pskServerCallback(
503 		SSL* ssl, const(char)* identity,
504 		ubyte* psk, uint max_psk_len)
505 	{
506 		auto self = cast(typeof(this))SSL_get_ex_data(ssl, 0);
507 		auto identityStr = fromStringz(identity);
508 		if (identityStr != self.context.pskID)
509 		{
510 			debug(OPENSSL) stderr.writefln("PSK ID mismatch: expected %s, got %s",
511 				self.context.pskID, identityStr);
512 			return 0;
513 		}
514 		if (self.context.psk.length > max_psk_len)
515 		{
516 			debug(OPENSSL) stderr.writeln("PSK too long");
517 			return 0;
518 		}
519 		psk[0 .. self.context.psk.length] = self.context.psk[];
520 		return cast(uint)self.context.psk.length;
521 	}
522 
523 	protected final void updateState()
524 	{
525 		// Flush any accumulated outgoing ciphertext to the network
526 		if (w.data.length)
527 		{
528 			debug(OPENSSL_DATA) stderr.writefln("OpenSSL: } Flushing %d outgoing bytes from OpenSSL to network", w.data.length);
529 			next.send(Data(w.data));
530 			w.clear();
531 		}
532 
533 		// Has the handshake been completed?
534 		if (connectionState == ConnectionState.connecting && SSL_is_init_finished(sslHandle))
535 		{
536 			connectionState = ConnectionState.connected;
537 			if (context.verify)
538 				try
539 					if (!SSL_get_peer_certificate(sslHandle))
540 						enforce(context.verify != SSLContext.Verify.require, "No SSL peer certificate was presented");
541 					else
542 					{
543 						auto result = SSL_get_verify_result(sslHandle);
544 						enforce(result == X509_V_OK,
545 							"SSL peer verification failed with error " ~ result.to!string);
546 					}
547 				catch (Exception e)
548 				{
549 					disconnect(e.msg, DisconnectType.error);
550 					return;
551 				}
552 			super.onConnect();
553 		}
554 	}
555 
556 	alias send = SSLAdapter.send;
557 
558 	void sslError(int ret, string msg)
559 	{
560 		auto err = SSL_get_error(sslHandle, ret);
561 		debug(OPENSSL) stderr.writefln("OpenSSL: SSL error ('%s', ret %d): %s", msg, ret, err);
562 		switch (err)
563 		{
564 			case SSL_ERROR_WANT_READ:
565 			case SSL_ERROR_ZERO_RETURN:
566 				return;
567 			case SSL_ERROR_SYSCALL:
568 				errnoEnforce(false, msg ~ " failed");
569 				assert(false);
570 			default:
571 				sslEnforce(false, "%s failed - error code %s".format(msg, err));
572 		}
573 	}
574 }
575 
576 /// `SSLCertificate` implementation.
577 class OpenSSLCertificate : SSLCertificate
578 {
579 	X509* x509; /// The C OpenSSL certificate object.
580 
581 	this(X509* x509)
582 	{
583 		this.x509 = x509;
584 	} ///
585 
586 	override string getSubjectName()
587 	{
588 		char[256] buf;
589 		X509_NAME_oneline(X509_get_subject_name(x509), buf.ptr, buf.length);
590 		buf[$-1] = 0;
591 		return buf.ptr.to!string();
592 	} /// `SSLCertificate` method implementation.
593 }
594 
595 // ***************************************************************************
596 
597 /// TODO: replace with custom BIO which hooks into IConnection
598 struct MemoryBIO
599 {
600 	@disable this(this);
601 
602 	this(const(void)[] data)
603 	{
604 		bio_ = BIO_new_mem_buf(cast(void*)data.ptr, data.length.to!int);
605 	} ///
606 
607 	void set(const(void)[] data)
608 	{
609 		BUF_MEM *bptr = BUF_MEM_new();
610 		if (data.length)
611 		{
612 			BUF_MEM_grow(bptr, data.length);
613 			bptr.data[0..bptr.length] = cast(char[])data;
614 		}
615 		BIO_set_mem_buf(bio, bptr, BIO_CLOSE);
616 	} ///
617 
618 	void clear() { set(null); } ///
619 
620 	@property BIO* bio()
621 	{
622 		if (!bio_)
623 		{
624 			bio_ = sslEnforce(BIO_new(BIO_s_mem()));
625 			BIO_set_close(bio_, BIO_CLOSE);
626 		}
627 		return bio_;
628 	} ///
629 
630 	const(void)[] data()
631 	{
632 		BUF_MEM *bptr;
633 		BIO_get_mem_ptr(bio, &bptr);
634 		return bptr.data[0..bptr.length];
635 	} ///
636 
637 private:
638 	BIO* bio_;
639 }
640 
641 /// Convert an OpenSSL error into a thrown D exception.
642 T sslEnforce(T)(T v, string message = null)
643 {
644 	if (v)
645 		return v;
646 
647 	{
648 		MemoryBIO m;
649 		ERR_print_errors(m.bio);
650 		string msg = (cast(char[])m.data).idup;
651 
652 		if (message)
653 			msg = message ~ ": " ~ msg;
654 
655 		throw new Exception(msg);
656 	}
657 }
658 
659 // ***************************************************************************
660 
661 version (unittest) import ae.net.ssl.test;
662 unittest { testSSL(new OpenSSLProvider); }