1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one 3 * or more contributor license agreements. See the NOTICE file 4 * distributed with this work for additional information 5 * regarding copyright ownership. The ASF licenses this file 6 * to you under the Apache License, Version 2.0 (the 7 * "License"); you may not use this file except in compliance 8 * with the License. You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, 13 * software distributed under the License is distributed on an 14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 * KIND, either express or implied. See the License for the 16 * specific language governing permissions and limitations 17 * under the License. 18 */ 19 20 /** 21 * OpenSSL socket implementation, in large parts ported from C++. 22 */ 23 module thrift.transport.ssl; 24 25 import core.exception : onOutOfMemoryError; 26 import core.stdc.errno : errno, EINTR; 27 import core.sync.mutex : Mutex; 28 import core.memory : GC; 29 import core.stdc.config; 30 import core.stdc.stdlib : free, malloc; 31 import std.ascii : toUpper; 32 import std.array : empty, front, popFront; 33 import std.conv : emplace, to; 34 import std.exception : enforce; 35 import std.socket : Address, InternetAddress, Internet6Address, Socket; 36 import std..string : toStringz; 37 import deimos.openssl.err; 38 import deimos.openssl.rand; 39 import deimos.openssl.ssl; 40 import deimos.openssl.x509v3; 41 import thrift.base; 42 import thrift.internal.ssl; 43 import thrift.transport.base; 44 import thrift.transport.socket; 45 46 /** 47 * SSL encrypted socket implementation using OpenSSL. 48 * 49 * Note: 50 * On Posix systems which do not have the BSD-specific SO_NOSIGPIPE flag, you 51 * might want to ignore the SIGPIPE signal, as OpenSSL might try to write to 52 * a closed socket if the peer disconnects abruptly: 53 * --- 54 * import core.stdc.signal; 55 * import core.sys.posix.signal; 56 * signal(SIGPIPE, SIG_IGN); 57 * --- 58 */ 59 final class TSSLSocket : TSocket { 60 /** 61 * Creates an instance that wraps an already created, connected (!) socket. 62 * 63 * Params: 64 * context = The SSL socket context to use. A reference to it is stored so 65 * that it doesn't get cleaned up while the socket is used. 66 * socket = Already created, connected socket object. 67 */ 68 this(TSSLContext context, Socket socket) { 69 super(socket); 70 context_ = context; 71 serverSide_ = context.serverSide; 72 accessManager_ = context.accessManager; 73 } 74 75 /** 76 * Creates a new unconnected socket that will connect to the given host 77 * on the given port. 78 * 79 * Params: 80 * context = The SSL socket context to use. A reference to it is stored so 81 * that it doesn't get cleaned up while the socket is used. 82 * host = Remote host. 83 * port = Remote port. 84 */ 85 this(TSSLContext context, string host, ushort port) { 86 super(host, port); 87 context_ = context; 88 serverSide_ = context.serverSide; 89 accessManager_ = context.accessManager; 90 } 91 92 override bool isOpen() @property { 93 if (ssl_ is null || !super.isOpen()) return false; 94 95 auto shutdown = SSL_get_shutdown(ssl_); 96 bool shutdownReceived = (shutdown & SSL_RECEIVED_SHUTDOWN) != 0; 97 bool shutdownSent = (shutdown & SSL_SENT_SHUTDOWN) != 0; 98 return !(shutdownReceived && shutdownSent); 99 } 100 101 override bool peek() { 102 if (!isOpen) return false; 103 checkHandshake(); 104 105 byte bt; 106 auto rc = SSL_peek(ssl_, &bt, bt.sizeof); 107 enforce(rc >= 0, getSSLException("SSL_peek")); 108 109 if (rc == 0) { 110 ERR_clear_error(); 111 } 112 return (rc > 0); 113 } 114 115 override void open() { 116 enforce(!serverSide_, "Cannot open a server-side SSL socket."); 117 if (isOpen) return; 118 super.open(); 119 } 120 121 override void close() { 122 if (!isOpen) return; 123 124 if (ssl_ !is null) { 125 // Two-step SSL shutdown. 126 auto rc = SSL_shutdown(ssl_); 127 if (rc == 0) { 128 rc = SSL_shutdown(ssl_); 129 } 130 if (rc < 0) { 131 // Do not throw an exception here as leaving the transport "open" will 132 // probably produce only more errors, and the chance we can do 133 // something about the error e.g. by retrying is very low. 134 logError("Error shutting down SSL: %s", getSSLException()); 135 } 136 137 SSL_free(ssl_); 138 ssl_ = null; 139 ERR_remove_state(0); 140 } 141 super.close(); 142 } 143 144 override size_t read(ubyte[] buf) { 145 checkHandshake(); 146 147 int bytes; 148 foreach (_; 0 .. maxRecvRetries) { 149 bytes = SSL_read(ssl_, buf.ptr, cast(int)buf.length); 150 if (bytes >= 0) break; 151 152 auto errnoCopy = errno; 153 if (SSL_get_error(ssl_, bytes) == SSL_ERROR_SYSCALL) { 154 if (ERR_get_error() == 0 && errnoCopy == EINTR) { 155 // FIXME: Windows. 156 continue; 157 } 158 } 159 throw getSSLException("SSL_read"); 160 } 161 return bytes; 162 } 163 164 override void write(in ubyte[] buf) { 165 checkHandshake(); 166 167 // Loop in case SSL_MODE_ENABLE_PARTIAL_WRITE is set in SSL_CTX. 168 size_t written = 0; 169 while (written < buf.length) { 170 auto bytes = SSL_write(ssl_, buf.ptr + written, 171 cast(int)(buf.length - written)); 172 if (bytes <= 0) { 173 throw getSSLException("SSL_write"); 174 } 175 written += bytes; 176 } 177 } 178 179 override void flush() { 180 checkHandshake(); 181 182 auto bio = SSL_get_wbio(ssl_); 183 enforce(bio !is null, new TSSLException("SSL_get_wbio returned null")); 184 185 auto rc = BIO_flush(bio); 186 enforce(rc == 1, getSSLException("BIO_flush")); 187 } 188 189 /** 190 * Whether to use client or server side SSL handshake protocol. 191 */ 192 bool serverSide() @property const { 193 return serverSide_; 194 } 195 196 /// Ditto 197 void serverSide(bool value) @property { 198 serverSide_ = value; 199 } 200 201 /** 202 * The access manager to use. 203 */ 204 void accessManager(TAccessManager value) @property { 205 accessManager_ = value; 206 } 207 208 private: 209 void checkHandshake() { 210 enforce(super.isOpen(), new TTransportException( 211 TTransportException.Type.NOT_OPEN)); 212 213 if (ssl_ !is null) return; 214 ssl_ = context_.createSSL(); 215 216 SSL_set_fd(ssl_, cast(int)socketHandle); 217 int rc; 218 if (serverSide_) { 219 rc = SSL_accept(ssl_); 220 } else { 221 rc = SSL_connect(ssl_); 222 } 223 enforce(rc > 0, getSSLException()); 224 authorize(ssl_, accessManager_, getPeerAddress(), 225 (serverSide_ ? getPeerAddress().toHostNameString() : host)); 226 } 227 228 bool serverSide_; 229 SSL* ssl_; 230 TSSLContext context_; 231 TAccessManager accessManager_; 232 } 233 234 /** 235 * Represents an OpenSSL context with certification settings, etc. and handles 236 * initialization/teardown. 237 * 238 * OpenSSL is initialized when the first instance of this class is created 239 * and shut down when the last one is destroyed (thread-safe). 240 */ 241 class TSSLContext { 242 this() { 243 initMutex_.lock(); 244 scope(exit) initMutex_.unlock(); 245 246 if (count_ == 0) { 247 initializeOpenSSL(); 248 randomize(); 249 } 250 count_++; 251 252 static if (OPENSSL_VERSION_NUMBER >= 0x1010000f) { // OPENSSL_VERSION_AT_LEAST(1, 1)) { 253 ctx_ = SSL_CTX_new(TLS_method()); 254 } else { 255 ctx_ = SSL_CTX_new(SSLv23_method()); 256 SSL_CTX_set_options(ctx_, SSL_OP_NO_SSLv2); 257 } 258 SSL_CTX_set_options(ctx_, SSL_OP_NO_SSLv3); // THRIFT-3164 259 enforce(ctx_, getSSLException("SSL_CTX_new")); 260 SSL_CTX_set_mode(ctx_, SSL_MODE_AUTO_RETRY); 261 } 262 263 ~this() { 264 initMutex_.lock(); 265 scope(exit) initMutex_.unlock(); 266 267 if (ctx_ !is null) { 268 SSL_CTX_free(ctx_); 269 ctx_ = null; 270 } 271 272 count_--; 273 if (count_ == 0) { 274 cleanupOpenSSL(); 275 } 276 } 277 278 /** 279 * Ciphers to be used in SSL handshake process. 280 * 281 * The string must be in the colon-delimited OpenSSL notation described in 282 * ciphers(1), for example: "ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH". 283 */ 284 void ciphers(string enable) @property { 285 auto rc = SSL_CTX_set_cipher_list(ctx_, toStringz(enable)); 286 287 enforce(ERR_peek_error() == 0, getSSLException("SSL_CTX_set_cipher_list")); 288 enforce(rc > 0, new TSSLException("None of specified ciphers are supported")); 289 } 290 291 /** 292 * Whether peer is required to present a valid certificate. 293 */ 294 void authenticate(bool required) @property { 295 int mode; 296 if (required) { 297 mode = SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT | 298 SSL_VERIFY_CLIENT_ONCE; 299 } else { 300 mode = SSL_VERIFY_NONE; 301 } 302 SSL_CTX_set_verify(ctx_, mode, null); 303 } 304 305 /** 306 * Load server certificate. 307 * 308 * Params: 309 * path = Path to the certificate file. 310 * format = Certificate file format. Defaults to PEM, which is currently 311 * the only one supported. 312 */ 313 void loadCertificate(string path, string format = "PEM") { 314 enforce(path !is null && format !is null, new TTransportException( 315 "loadCertificateChain: either <path> or <format> is null", 316 TTransportException.Type.BAD_ARGS)); 317 318 if (format == "PEM") { 319 enforce(SSL_CTX_use_certificate_chain_file(ctx_, toStringz(path)), 320 getSSLException( 321 `Could not load SSL server certificate from file "` ~ path ~ `"` 322 ) 323 ); 324 } else { 325 throw new TSSLException("Unsupported certificate format: " ~ format); 326 } 327 } 328 329 /* 330 * Load private key. 331 * 332 * Params: 333 * path = Path to the certificate file. 334 * format = Private key file format. Defaults to PEM, which is currently 335 * the only one supported. 336 */ 337 void loadPrivateKey(string path, string format = "PEM") { 338 enforce(path !is null && format !is null, new TTransportException( 339 "loadPrivateKey: either <path> or <format> is NULL", 340 TTransportException.Type.BAD_ARGS)); 341 342 if (format == "PEM") { 343 enforce(SSL_CTX_use_PrivateKey_file(ctx_, toStringz(path), SSL_FILETYPE_PEM), 344 getSSLException( 345 `Could not load SSL private key from file "` ~ path ~ `"` 346 ) 347 ); 348 } else { 349 throw new TSSLException("Unsupported certificate format: " ~ format); 350 } 351 } 352 353 /** 354 * Load trusted certificates from specified file (in PEM format). 355 * 356 * Params. 357 * path = Path to the file containing the trusted certificates. 358 */ 359 void loadTrustedCertificates(string path) { 360 enforce(path !is null, new TTransportException( 361 "loadTrustedCertificates: <path> is NULL", 362 TTransportException.Type.BAD_ARGS)); 363 364 enforce(SSL_CTX_load_verify_locations(ctx_, toStringz(path), null), 365 getSSLException( 366 `Could not load SSL trusted certificate list from file "` ~ path ~ `"` 367 ) 368 ); 369 } 370 371 /** 372 * Called during OpenSSL initialization to seed the OpenSSL entropy pool. 373 * 374 * Defaults to simply calling RAND_poll(), but it can be overwritten if a 375 * different, perhaps more secure implementation is desired. 376 */ 377 void randomize() { 378 RAND_poll(); 379 } 380 381 /** 382 * Whether to use client or server side SSL handshake protocol. 383 */ 384 bool serverSide() @property const { 385 return serverSide_; 386 } 387 388 /// Ditto 389 void serverSide(bool value) @property { 390 serverSide_ = value; 391 } 392 393 /** 394 * The access manager to use. 395 */ 396 TAccessManager accessManager() @property { 397 if (!serverSide_ && !accessManager_) { 398 accessManager_ = new TDefaultClientAccessManager; 399 } 400 return accessManager_; 401 } 402 403 /// Ditto 404 void accessManager(TAccessManager value) @property { 405 accessManager_ = value; 406 } 407 408 SSL* createSSL() out (result) { 409 assert(result); 410 } body { 411 auto result = SSL_new(ctx_); 412 enforce(result, getSSLException("SSL_new")); 413 return result; 414 } 415 416 protected: 417 /** 418 * Override this method for custom password callback. It may be called 419 * multiple times at any time during a session as necessary. 420 * 421 * Params: 422 * size = Maximum length of password, including null byte. 423 */ 424 string getPassword(int size) nothrow out(result) { 425 assert(result.length < size); 426 } body { 427 return ""; 428 } 429 430 /** 431 * Notifies OpenSSL to use getPassword() instead of the default password 432 * callback with getPassword(). 433 */ 434 void overrideDefaultPasswordCallback() { 435 SSL_CTX_set_default_passwd_cb(ctx_, &passwordCallback); 436 SSL_CTX_set_default_passwd_cb_userdata(ctx_, cast(void*)this); 437 } 438 439 SSL_CTX* ctx_; 440 441 private: 442 bool serverSide_; 443 TAccessManager accessManager_; 444 445 shared static this() { 446 initMutex_ = new Mutex(); 447 } 448 449 static void initializeOpenSSL() { 450 if (initialized_) { 451 return; 452 } 453 initialized_ = true; 454 455 static if (OPENSSL_VERSION_NUMBER < 0x1010000f) { // OPENSSL_VERSION_BEFORE(1, 1)) { 456 SSL_library_init(); 457 SSL_load_error_strings(); 458 459 mutexes_ = new Mutex[CRYPTO_num_locks()]; 460 foreach (ref m; mutexes_) { 461 m = new Mutex; 462 } 463 464 import thrift.internal.traits; 465 // As per the OpenSSL threads manpage, this isn't needed on Windows. 466 version (Posix) { 467 CRYPTO_set_id_callback(assumeNothrow(&threadIdCallback)); 468 } 469 CRYPTO_set_locking_callback(assumeNothrow(&lockingCallback)); 470 CRYPTO_set_dynlock_create_callback(assumeNothrow(&dynlockCreateCallback)); 471 CRYPTO_set_dynlock_lock_callback(assumeNothrow(&dynlockLockCallback)); 472 CRYPTO_set_dynlock_destroy_callback(assumeNothrow(&dynlockDestroyCallback)); 473 } 474 } 475 476 static void cleanupOpenSSL() { 477 if (!initialized_) return; 478 479 initialized_ = false; 480 static if (OPENSSL_VERSION_NUMBER < 0x1010000f) { // OPENSSL_VERSION_BEFORE(1, 1)) { 481 CRYPTO_set_locking_callback(null); 482 CRYPTO_set_dynlock_create_callback(null); 483 CRYPTO_set_dynlock_lock_callback(null); 484 CRYPTO_set_dynlock_destroy_callback(null); 485 CRYPTO_cleanup_all_ex_data(); 486 ERR_free_strings(); 487 ERR_remove_state(0); 488 } 489 } 490 491 static extern(C) { 492 version (Posix) { 493 import core.sys.posix.pthread : pthread_self; 494 c_ulong threadIdCallback() { 495 return cast(c_ulong)pthread_self(); 496 } 497 } 498 499 void lockingCallback(int mode, int n, const(char)* file, int line) { 500 if (mode & CRYPTO_LOCK) { 501 mutexes_[n].lock(); 502 } else { 503 mutexes_[n].unlock(); 504 } 505 } 506 507 CRYPTO_dynlock_value* dynlockCreateCallback(const(char)* file, int line) { 508 enum size = __traits(classInstanceSize, Mutex); 509 auto mem = malloc(size)[0 .. size]; 510 if (!mem) onOutOfMemoryError(); 511 GC.addRange(mem.ptr, size); 512 auto mutex = emplace!Mutex(mem); 513 return cast(CRYPTO_dynlock_value*)mutex; 514 } 515 516 void dynlockLockCallback(int mode, CRYPTO_dynlock_value* l, 517 const(char)* file, int line) 518 { 519 if (l is null) return; 520 if (mode & CRYPTO_LOCK) { 521 (cast(Mutex)l).lock(); 522 } else { 523 (cast(Mutex)l).unlock(); 524 } 525 } 526 527 void dynlockDestroyCallback(CRYPTO_dynlock_value* l, 528 const(char)* file, int line) 529 { 530 GC.removeRange(l); 531 destroy(cast(Mutex)l); 532 free(l); 533 } 534 535 int passwordCallback(char* password, int size, int, void* data) nothrow { 536 auto context = cast(TSSLContext) data; 537 auto userPassword = context.getPassword(size); 538 auto len = userPassword.length; 539 if (len > size) { 540 len = size; 541 } 542 password[0 .. len] = userPassword[0 .. len]; // TODO: \0 handling correct? 543 return cast(int)len; 544 } 545 } 546 547 static __gshared bool initialized_; 548 static __gshared Mutex initMutex_; 549 static __gshared Mutex[] mutexes_; 550 static __gshared uint count_; 551 } 552 553 /** 554 * Decides whether a remote host is legitimate or not. 555 * 556 * It is usually set at a TSSLContext, which then passes it to all the created 557 * TSSLSockets. 558 */ 559 class TAccessManager { 560 /// 561 enum Decision { 562 DENY = -1, /// Deny access. 563 SKIP = 0, /// Cannot decide, move on to next check (deny if last). 564 ALLOW = 1 /// Allow access. 565 } 566 567 /** 568 * Determines whether a peer should be granted access or not based on its 569 * IP address. 570 * 571 * Called once after SSL handshake is completes successfully and before peer 572 * certificate is examined. 573 * 574 * If a valid decision (ALLOW or DENY) is returned, the peer certificate 575 * will not be verified. 576 */ 577 Decision verify(Address address) { 578 return Decision.DENY; 579 } 580 581 /** 582 * Determines whether a peer should be granted access or not based on a 583 * name from its certificate. 584 * 585 * Called every time a DNS subjectAltName/common name is extracted from the 586 * peer's certificate. 587 * 588 * Params: 589 * host = The actual host name string from the socket connection. 590 * certHost = A host name string from the certificate. 591 */ 592 Decision verify(string host, const(char)[] certHost) { 593 return Decision.DENY; 594 } 595 596 /** 597 * Determines whether a peer should be granted access or not based on an IP 598 * address from its certificate. 599 * 600 * Called every time an IP subjectAltName is extracted from the peer's 601 * certificate. 602 * 603 * Params: 604 * address = The actual address from the socket connection. 605 * certHost = A host name string from the certificate. 606 */ 607 Decision verify(Address address, ubyte[] certAddress) { 608 return Decision.DENY; 609 } 610 } 611 612 /** 613 * Default access manager implementation, which just checks the host name 614 * resp. IP address of the connection against the certificate. 615 */ 616 class TDefaultClientAccessManager : TAccessManager { 617 override Decision verify(Address address) { 618 return Decision.SKIP; 619 } 620 621 override Decision verify(string host, const(char)[] certHost) { 622 if (host.empty || certHost.empty) { 623 return Decision.SKIP; 624 } 625 return (matchName(host, certHost) ? Decision.ALLOW : Decision.SKIP); 626 } 627 628 override Decision verify(Address address, ubyte[] certAddress) { 629 bool match; 630 if (certAddress.length == 4) { 631 if (auto ia = cast(InternetAddress)address) { 632 match = ((cast(ubyte*)ia.addr())[0 .. 4] == certAddress[]); 633 } 634 } else if (certAddress.length == 16) { 635 if (auto ia = cast(Internet6Address)address) { 636 match = (ia.addr() == certAddress[]); 637 } 638 } 639 return (match ? Decision.ALLOW : Decision.SKIP); 640 } 641 } 642 643 private { 644 /** 645 * Matches a name with a pattern. The pattern may include wildcard. A single 646 * wildcard "*" can match up to one component in the domain name. 647 * 648 * Params: 649 * host = Host name to match, typically the SSL remote peer. 650 * pattern = Host name pattern, typically from the SSL certificate. 651 * 652 * Returns: true if host matches pattern, false otherwise. 653 */ 654 bool matchName(const(char)[] host, const(char)[] pattern) { 655 while (!host.empty && !pattern.empty) { 656 if (toUpper(pattern.front) == toUpper(host.front)) { 657 host.popFront; 658 pattern.popFront; 659 } else if (pattern.front == '*') { 660 while (!host.empty && host.front != '.') { 661 host.popFront; 662 } 663 pattern.popFront; 664 } else { 665 break; 666 } 667 } 668 return (host.empty && pattern.empty); 669 } 670 671 unittest { 672 enforce(matchName("thrift.apache.org", "*.apache.org")); 673 enforce(!matchName("thrift.apache.org", "apache.org")); 674 enforce(matchName("thrift.apache.org", "thrift.*.*")); 675 enforce(matchName("", "")); 676 enforce(!matchName("", "*")); 677 } 678 } 679 680 /** 681 * SSL-level exception. 682 */ 683 class TSSLException : TTransportException { 684 /// 685 this(string msg, string file = __FILE__, size_t line = __LINE__, 686 Throwable next = null) 687 { 688 super(msg, TTransportException.Type.INTERNAL_ERROR, file, line, next); 689 } 690 }