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 private enum isOpenSSL11 = OPENSSL_VERSION_NUMBER >= OPENSSL_MAKE_VERSION(1, 1, 0, 0); 68 else 69 private enum isOpenSSL11 = false; 70 71 mixin template SSLUseLib() 72 { 73 static if (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 }