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 <vladimir@thecybershadow.net>
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 
56 import ae.net.asockets;
57 import ae.net.ssl;
58 import ae.utils.exception : CaughtException;
59 import ae.utils.meta : enumLength;
60 import ae.utils.text;
61 
62 debug(OPENSSL) import std.stdio : stderr;
63 
64 // ***************************************************************************
65 
66 static if (is(typeof(OPENSSL_MAKE_VERSION)))
67 	enum isOpenSSL11 = OPENSSL_VERSION_NUMBER >= OPENSSL_MAKE_VERSION(1, 1, 0, 0);
68 else
69 	enum isOpenSSL11 = false;
70 
71 mixin template SSLUseLib()
72 {
73 	static if (ae.net.ssl.openssl.isOpenSSL11)
74 	{
75 		pragma(lib, "ssl");
76 		pragma(lib, "crypto");
77 	}
78 	else
79 	{
80 		version(Win64)
81 		{
82 			pragma(lib, "ssleay32");
83 			pragma(lib, "libeay32");
84 		}
85 		else
86 		{
87 			pragma(lib, "ssl");
88 			version(Windows)
89 				{ pragma(lib, "eay"); }
90 			else
91 				{ pragma(lib, "crypto"); }
92 		}
93 	}
94 }
95 
96 // Patch up incomplete Deimos bindings.
97 
98 static if (isOpenSSL11)
99 private
100 {
101 	alias SSLv23_client_method = TLS_client_method;
102 	alias SSLv23_server_method = TLS_server_method;
103 	void SSL_load_error_strings() {}
104 	struct OPENSSL_INIT_SETTINGS;
105 	extern(C) void OPENSSL_init_ssl(uint64_t opts, const OPENSSL_INIT_SETTINGS *settings) nothrow;
106 	void SSL_library_init() { OPENSSL_init_ssl(0, null); }
107 	void OpenSSL_add_all_algorithms() { SSL_library_init(); }
108 	extern(C) BIGNUM *BN_get_rfc3526_prime_1536(BIGNUM *bn) nothrow;
109 	alias get_rfc3526_prime_1536 = BN_get_rfc3526_prime_1536;
110 	extern(C) BIGNUM *BN_get_rfc3526_prime_2048(BIGNUM *bn) nothrow;
111 	alias get_rfc3526_prime_2048 = BN_get_rfc3526_prime_2048;
112 	extern(C) BIGNUM *BN_get_rfc3526_prime_3072(BIGNUM *bn) nothrow;
113 	alias get_rfc3526_prime_3072 = BN_get_rfc3526_prime_3072;
114 	extern(C) BIGNUM *BN_get_rfc3526_prime_4096(BIGNUM *bn) nothrow;
115 	alias get_rfc3526_prime_4096 = BN_get_rfc3526_prime_4096;
116 	extern(C) BIGNUM *BN_get_rfc3526_prime_6144(BIGNUM *bn) nothrow;
117 	alias get_rfc3526_prime_6144 = BN_get_rfc3526_prime_6144;
118 	extern(C) BIGNUM *BN_get_rfc3526_prime_8192(BIGNUM *bn) nothrow;
119 	alias get_rfc3526_prime_8192 = BN_get_rfc3526_prime_8192;
120 	extern(C) int SSL_in_init(const SSL *s) nothrow;
121 }
122 
123 // ***************************************************************************
124 
125 shared static this()
126 {
127 	SSL_load_error_strings();
128 	SSL_library_init();
129 	OpenSSL_add_all_algorithms();
130 }
131 
132 // ***************************************************************************
133 
134 class OpenSSLProvider : SSLProvider
135 {
136 	override SSLContext createContext(SSLContext.Kind kind)
137 	{
138 		return new OpenSSLContext(kind);
139 	}
140 
141 	override SSLAdapter createAdapter(SSLContext context, IConnection next)
142 	{
143 		auto ctx = cast(OpenSSLContext)context;
144 		assert(ctx, "Not an OpenSSLContext");
145 		return new OpenSSLAdapter(ctx, next);
146 	}
147 }
148 
149 class OpenSSLContext : SSLContext
150 {
151 	SSL_CTX* sslCtx;
152 	Kind kind;
153 
154 	this(Kind kind)
155 	{
156 		this.kind = kind;
157 
158 		const(SSL_METHOD)* method;
159 
160 		final switch (kind)
161 		{
162 			case Kind.client:
163 				method = SSLv23_client_method().sslEnforce();
164 				break;
165 			case Kind.server:
166 				method = SSLv23_server_method().sslEnforce();
167 				break;
168 		}
169 		sslCtx = SSL_CTX_new(method).sslEnforce();
170 	}
171 
172 	override void setCipherList(string[] ciphers)
173 	{
174 		SSL_CTX_set_cipher_list(sslCtx, ciphers.join(":").toStringz()).sslEnforce();
175 	}
176 
177 	override void enableDH(int bits)
178 	{
179 		typeof(&get_rfc3526_prime_2048) func;
180 
181 		switch (bits)
182 		{
183 			case 1536: func = &get_rfc3526_prime_1536; break;
184 			case 2048: func = &get_rfc3526_prime_2048; break;
185 			case 3072: func = &get_rfc3526_prime_3072; break;
186 			case 4096: func = &get_rfc3526_prime_4096; break;
187 			case 6144: func = &get_rfc3526_prime_6144; break;
188 			case 8192: func = &get_rfc3526_prime_8192; break;
189 			default: assert(false, "No RFC3526 prime available for %d bits".format(bits));
190 		}
191 
192 		DH* dh;
193 		scope(exit) DH_free(dh);
194 
195 		dh = DH_new().sslEnforce();
196 		dh.p = func(null).sslEnforce();
197 		ubyte gen = 2;
198 		dh.g = BN_bin2bn(&gen, gen.sizeof, null);
199 		SSL_CTX_set_tmp_dh(sslCtx, dh).sslEnforce();
200 	}
201 
202 	override void enableECDH()
203 	{
204 		auto ecdh = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1).sslEnforce();
205 		scope(exit) EC_KEY_free(ecdh);
206 		SSL_CTX_set_tmp_ecdh(sslCtx, ecdh).sslEnforce();
207 	}
208 
209 	override void setCertificate(string path)
210 	{
211 		SSL_CTX_use_certificate_chain_file(sslCtx, toStringz(path))
212 			.sslEnforce("Failed to load certificate file " ~ path);
213 	}
214 
215 	override void setPrivateKey(string path)
216 	{
217 		SSL_CTX_use_PrivateKey_file(sslCtx, toStringz(path), SSL_FILETYPE_PEM)
218 			.sslEnforce("Failed to load private key file " ~ path);
219 	}
220 
221 	override void setPeerVerify(Verify verify)
222 	{
223 		static const int[enumLength!Verify] modes =
224 		[
225 			SSL_VERIFY_NONE,
226 			SSL_VERIFY_PEER,
227 			SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT,
228 		];
229 		SSL_CTX_set_verify(sslCtx, modes[verify], null);
230 	}
231 
232 	override void setPeerRootCertificate(string path)
233 	{
234 		auto szPath = toStringz(path);
235 		SSL_CTX_load_verify_locations(sslCtx, szPath, null).sslEnforce();
236 
237 		if (kind == Kind.server)
238 		{
239 			auto list = SSL_load_client_CA_file(szPath).sslEnforce();
240 			SSL_CTX_set_client_CA_list(sslCtx, list);
241 		}
242 	}
243 
244 	override void setFlags(int flags)
245 	{
246 		SSL_CTX_set_options(sslCtx, flags).sslEnforce();
247 	}
248 }
249 
250 static this()
251 {
252 	ssl = new OpenSSLProvider();
253 }
254 
255 // ***************************************************************************
256 
257 class OpenSSLAdapter : SSLAdapter
258 {
259 	SSL* sslHandle;
260 	OpenSSLContext context;
261 
262 	this(OpenSSLContext context, IConnection next)
263 	{
264 		this.context = context;
265 		super(next);
266 
267 		sslHandle = sslEnforce(SSL_new(context.sslCtx));
268 		SSL_set_bio(sslHandle, r.bio, w.bio);
269 
270 		if (next.state == ConnectionState.connected)
271 			initialize();
272 	}
273 
274 	override void onConnect()
275 	{
276 		initialize();
277 		super.onConnect();
278 	}
279 
280 	private final void initialize()
281 	{
282 		final switch (context.kind)
283 		{
284 			case OpenSSLContext.Kind.client: SSL_connect(sslHandle).sslEnforce(); break;
285 			case OpenSSLContext.Kind.server: SSL_accept (sslHandle).sslEnforce(); break;
286 		}
287 	}
288 
289 	MemoryBIO r; // BIO for incoming ciphertext
290 	MemoryBIO w; // BIO for outgoing ciphertext
291 
292 	override void onReadData(Data data)
293 	{
294 		debug(OPENSSL_DATA) stderr.writefln("OpenSSL: Got %d incoming bytes from network", data.length);
295 
296 		if (next.state == ConnectionState.disconnecting)
297 		{
298 			return;
299 		}
300 
301 		assert(r.data.length == 0, "Would clobber data");
302 		r.set(data.contents);
303 		debug(OPENSSL_DATA) stderr.writefln("OpenSSL: r.data.length = %d", r.data.length);
304 
305 		try
306 		{
307 			if (queue.length)
308 				flushQueue();
309 
310 			while (true)
311 			{
312 				static ubyte[4096] buf;
313 				debug(OPENSSL_DATA) auto oldLength = r.data.length;
314 				auto result = SSL_read(sslHandle, buf.ptr, buf.length);
315 				debug(OPENSSL_DATA) stderr.writefln("OpenSSL: SSL_read ate %d bytes and spat out %d bytes", oldLength - r.data.length, result);
316 				flushWritten();
317 				if (result > 0)
318 				{
319 					super.onReadData(Data(buf[0..result]));
320 					// Stop if upstream decided to disconnect.
321 					if (next.state != ConnectionState.connected)
322 						return;
323 				}
324 				else
325 				{
326 					sslError(result, "SSL_read");
327 					break;
328 				}
329 			}
330 			enforce(r.data.length == 0, "SSL did not consume all read data");
331 		}
332 		catch (CaughtException e)
333 		{
334 			debug(OPENSSL) stderr.writeln("Error while %s and processing incoming data: %s".format(next.state, e.msg));
335 			if (next.state != ConnectionState.disconnecting && next.state != ConnectionState.disconnected)
336 				disconnect(e.msg, DisconnectType.error);
337 			else
338 				throw e;
339 		}
340 	}
341 
342 	Data[] queue; /// Queue of outgoing plaintext
343 
344 	override void send(Data[] data, int priority = DEFAULT_PRIORITY)
345 	{
346 		foreach (datum; data)
347 			if (datum.length)
348 			{
349 				debug(OPENSSL_DATA) stderr.writefln("OpenSSL: Got %d outgoing bytes from program", datum.length);
350 				queue ~= datum;
351 			}
352 
353 		flushQueue();
354 	}
355 
356 	/// Encrypt outgoing plaintext
357 	/// queue -> SSL_write -> w
358 	void flushQueue()
359 	{
360 		while (queue.length)
361 		{
362 			debug(OPENSSL_DATA) auto oldLength = w.data.length;
363 			auto result = SSL_write(sslHandle, queue[0].ptr, queue[0].length.to!int);
364 			debug(OPENSSL_DATA) stderr.writefln("OpenSSL: SSL_write ate %d bytes and spat out %d bytes", queue[0].length, w.data.length - oldLength);
365 			if (result > 0)
366 			{
367 				// "SSL_write() will only return with success, when the
368 				// complete contents of buf of length num has been written."
369 				queue = queue[1..$];
370 			}
371 			else
372 			{
373 				sslError(result, "SSL_write");
374 				break;
375 			}
376 		}
377 		flushWritten();
378 	}
379 
380 	/// Flush any accumulated outgoing ciphertext to the network
381 	void flushWritten()
382 	{
383 		if (w.data.length)
384 		{
385 			next.send([Data(w.data)]);
386 			w.clear();
387 		}
388 	}
389 
390 	override void disconnect(string reason, DisconnectType type)
391 	{
392 		debug(OPENSSL) stderr.writefln("OpenSSL: disconnect called ('%s')", reason);
393 		if (!SSL_in_init(sslHandle))
394 		{
395 			debug(OPENSSL) stderr.writefln("OpenSSL: Calling SSL_shutdown");
396 			SSL_shutdown(sslHandle);
397 		}
398 		else
399 			debug(OPENSSL) stderr.writefln("OpenSSL: In init, not calling SSL_shutdown");
400 		debug(OPENSSL) stderr.writefln("OpenSSL: SSL_shutdown done, flushing");
401 		flushWritten();
402 		debug(OPENSSL) stderr.writefln("OpenSSL: SSL_shutdown output flushed");
403 		super.disconnect(reason, type);
404 	}
405 
406 	override void onDisconnect(string reason, DisconnectType type)
407 	{
408 		debug(OPENSSL) stderr.writefln("OpenSSL: onDisconnect ('%s'), calling SSL_free", reason);
409 		r.clear();
410 		w.clear();
411 		SSL_free(sslHandle);
412 		sslHandle = null;
413 		debug(OPENSSL) stderr.writeln("OpenSSL: onDisconnect: SSL_free called, calling super.onDisconnect");
414 		super.onDisconnect(reason, type);
415 		debug(OPENSSL) stderr.writeln("OpenSSL: onDisconnect finished");
416 	}
417 
418 	alias send = SSLAdapter.send;
419 
420 	void sslError(int ret, string msg)
421 	{
422 		auto err = SSL_get_error(sslHandle, ret);
423 		debug(OPENSSL) stderr.writefln("OpenSSL: SSL error ('%s', ret %d): %s", msg, ret, err);
424 		switch (err)
425 		{
426 			case SSL_ERROR_WANT_READ:
427 			case SSL_ERROR_ZERO_RETURN:
428 				return;
429 			case SSL_ERROR_SYSCALL:
430 				errnoEnforce(false, msg ~ " failed");
431 				assert(false);
432 			default:
433 				sslEnforce(false, "%s failed - error code %s".format(msg, err));
434 		}
435 	}
436 
437 	override void setHostName(string hostname)
438 	{
439 		SSL_set_tlsext_host_name(sslHandle, cast(char*)hostname.toStringz());
440 	}
441 
442 	override OpenSSLCertificate getHostCertificate()
443 	{
444 		return new OpenSSLCertificate(SSL_get_certificate(sslHandle).sslEnforce());
445 	}
446 
447 	override OpenSSLCertificate getPeerCertificate()
448 	{
449 		return new OpenSSLCertificate(SSL_get_peer_certificate(sslHandle).sslEnforce());
450 	}
451 }
452 
453 class OpenSSLCertificate : SSLCertificate
454 {
455 	X509* x509;
456 
457 	this(X509* x509)
458 	{
459 		this.x509 = x509;
460 	}
461 
462 	override string getSubjectName()
463 	{
464 		char[256] buf;
465 		X509_NAME_oneline(X509_get_subject_name(x509), buf.ptr, buf.length);
466 		buf[$-1] = 0;
467 		return buf.ptr.to!string();
468 	}
469 }
470 
471 // ***************************************************************************
472 
473 /// TODO: replace with custom BIO which hooks into IConnection
474 struct MemoryBIO
475 {
476 	@disable this(this);
477 
478 	this(const(void)[] data)
479 	{
480 		bio_ = BIO_new_mem_buf(cast(void*)data.ptr, data.length.to!int);
481 	}
482 
483 	void set(const(void)[] data)
484 	{
485 		BUF_MEM *bptr = BUF_MEM_new();
486 		if (data.length)
487 		{
488 			BUF_MEM_grow(bptr, data.length);
489 			bptr.data[0..bptr.length] = cast(char[])data;
490 		}
491 		BIO_set_mem_buf(bio, bptr, BIO_CLOSE);
492 	}
493 
494 	void clear() { set(null); }
495 
496 	@property BIO* bio()
497 	{
498 		if (!bio_)
499 		{
500 			bio_ = sslEnforce(BIO_new(BIO_s_mem()));
501 			BIO_set_close(bio_, BIO_CLOSE);
502 		}
503 		return bio_;
504 	}
505 
506 	const(void)[] data()
507 	{
508 		BUF_MEM *bptr;
509 		BIO_get_mem_ptr(bio, &bptr);
510 		return bptr.data[0..bptr.length];
511 	}
512 
513 private:
514 	BIO* bio_;
515 }
516 
517 T sslEnforce(T)(T v, string message = null)
518 {
519 	if (v)
520 		return v;
521 
522 	{
523 		MemoryBIO m;
524 		ERR_print_errors(m.bio);
525 		string msg = (cast(char[])m.data).idup;
526 
527 		if (message)
528 			msg = message ~ ": " ~ msg;
529 
530 		throw new Exception(msg);
531 	}
532 }
533 
534 // ***************************************************************************
535 
536 unittest
537 {
538 	void testServer(string host, ushort port)
539 	{
540 		auto c = new TcpConnection;
541 		auto ctx = ssl.createContext(SSLContext.Kind.client);
542 		auto s = ssl.createAdapter(ctx, c);
543 
544 		s.handleConnect =
545 		{
546 			debug(OPENSSL) stderr.writeln("Connected!");
547 			s.send(Data("GET / HTTP/1.0\r\nHost: www.openssl.org\r\n\r\n"));
548 		};
549 		s.handleReadData = (Data data)
550 		{
551 			debug(OPENSSL) { stderr.write(cast(string)data.contents); stderr.flush(); }
552 		};
553 		c.connect(host, port);
554 		socketManager.loop();
555 	}
556 
557 	testServer("www.openssl.org", 443);
558 }