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_, 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 ctx_ = SSL_CTX_new(TLSv1_method()); 253 enforce(ctx_, getSSLException("SSL_CTX_new")); 254 SSL_CTX_set_mode(ctx_, SSL_MODE_AUTO_RETRY); 255 } 256 257 ~this() { 258 initMutex_.lock(); 259 scope(exit) initMutex_.unlock(); 260 261 if (ctx_ !is null) { 262 SSL_CTX_free(ctx_); 263 ctx_ = null; 264 } 265 266 count_--; 267 if (count_ == 0) { 268 cleanupOpenSSL(); 269 } 270 } 271 272 /** 273 * Ciphers to be used in SSL handshake process. 274 * 275 * The string must be in the colon-delimited OpenSSL notation described in 276 * ciphers(1), for example: "ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH". 277 */ 278 void ciphers(string enable) @property { 279 auto rc = SSL_CTX_set_cipher_list(ctx_, toStringz(enable)); 280 281 enforce(ERR_peek_error() == 0, getSSLException("SSL_CTX_set_cipher_list")); 282 enforce(rc > 0, new TSSLException("None of specified ciphers are supported")); 283 } 284 285 /** 286 * Whether peer is required to present a valid certificate. 287 */ 288 void authenticate(bool required) @property { 289 int mode; 290 if (required) { 291 mode = SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT | 292 SSL_VERIFY_CLIENT_ONCE; 293 } else { 294 mode = SSL_VERIFY_NONE; 295 } 296 SSL_CTX_set_verify(ctx_, mode, null); 297 } 298 299 /** 300 * Load server certificate. 301 * 302 * Params: 303 * path = Path to the certificate file. 304 * format = Certificate file format. Defaults to PEM, which is currently 305 * the only one supported. 306 */ 307 void loadCertificate(string path, string format = "PEM") { 308 enforce(path !is null && format !is null, new TTransportException( 309 "loadCertificateChain: either <path> or <format> is null", 310 TTransportException.Type.BAD_ARGS)); 311 312 if (format == "PEM") { 313 enforce(SSL_CTX_use_certificate_chain_file(ctx_, toStringz(path)), 314 getSSLException( 315 `Could not load SSL server certificate from file "` ~ path ~ `"` 316 ) 317 ); 318 } else { 319 throw new TSSLException("Unsupported certificate format: " ~ format); 320 } 321 } 322 323 /* 324 * Load private key. 325 * 326 * Params: 327 * path = Path to the certificate file. 328 * format = Private key file format. Defaults to PEM, which is currently 329 * the only one supported. 330 */ 331 void loadPrivateKey(string path, string format = "PEM") { 332 enforce(path !is null && format !is null, new TTransportException( 333 "loadPrivateKey: either <path> or <format> is NULL", 334 TTransportException.Type.BAD_ARGS)); 335 336 if (format == "PEM") { 337 enforce(SSL_CTX_use_PrivateKey_file(ctx_, toStringz(path), SSL_FILETYPE_PEM), 338 getSSLException( 339 `Could not load SSL private key from file "` ~ path ~ `"` 340 ) 341 ); 342 } else { 343 throw new TSSLException("Unsupported certificate format: " ~ format); 344 } 345 } 346 347 /** 348 * Load trusted certificates from specified file (in PEM format). 349 * 350 * Params. 351 * path = Path to the file containing the trusted certificates. 352 */ 353 void loadTrustedCertificates(string path) { 354 enforce(path !is null, new TTransportException( 355 "loadTrustedCertificates: <path> is NULL", 356 TTransportException.Type.BAD_ARGS)); 357 358 enforce(SSL_CTX_load_verify_locations(ctx_, toStringz(path), null), 359 getSSLException( 360 `Could not load SSL trusted certificate list from file "` ~ path ~ `"` 361 ) 362 ); 363 } 364 365 /** 366 * Called during OpenSSL initialization to seed the OpenSSL entropy pool. 367 * 368 * Defaults to simply calling RAND_poll(), but it can be overwritten if a 369 * different, perhaps more secure implementation is desired. 370 */ 371 void randomize() { 372 RAND_poll(); 373 } 374 375 /** 376 * Whether to use client or server side SSL handshake protocol. 377 */ 378 bool serverSide() @property const { 379 return serverSide_; 380 } 381 382 /// Ditto 383 void serverSide(bool value) @property { 384 serverSide_ = value; 385 } 386 387 /** 388 * The access manager to use. 389 */ 390 TAccessManager accessManager() @property { 391 if (!serverSide_ && !accessManager_) { 392 accessManager_ = new TDefaultClientAccessManager; 393 } 394 return accessManager_; 395 } 396 397 /// Ditto 398 void accessManager(TAccessManager value) @property { 399 accessManager_ = value; 400 } 401 402 SSL* createSSL() out (result) { 403 assert(result); 404 } body { 405 auto result = SSL_new(ctx_); 406 enforce(result, getSSLException("SSL_new")); 407 return result; 408 } 409 410 protected: 411 /** 412 * Override this method for custom password callback. It may be called 413 * multiple times at any time during a session as necessary. 414 * 415 * Params: 416 * size = Maximum length of password, including null byte. 417 */ 418 string getPassword(int size) nothrow out(result) { 419 assert(result.length < size); 420 } body { 421 return ""; 422 } 423 424 /** 425 * Notifies OpenSSL to use getPassword() instead of the default password 426 * callback with getPassword(). 427 */ 428 void overrideDefaultPasswordCallback() { 429 SSL_CTX_set_default_passwd_cb(ctx_, &passwordCallback); 430 SSL_CTX_set_default_passwd_cb_userdata(ctx_, cast(void*)this); 431 } 432 433 SSL_CTX* ctx_; 434 435 private: 436 bool serverSide_; 437 TAccessManager accessManager_; 438 439 shared static this() { 440 initMutex_ = new Mutex(); 441 } 442 443 static void initializeOpenSSL() { 444 if (initialized_) { 445 return; 446 } 447 initialized_ = true; 448 449 SSL_library_init(); 450 SSL_load_error_strings(); 451 452 mutexes_ = new Mutex[CRYPTO_num_locks()]; 453 foreach (ref m; mutexes_) { 454 m = new Mutex; 455 } 456 457 import thrift.internal.traits; 458 // As per the OpenSSL threads manpage, this isn't needed on Windows. 459 version (Posix) { 460 CRYPTO_set_id_callback(assumeNothrow(&threadIdCallback)); 461 } 462 CRYPTO_set_locking_callback(assumeNothrow(&lockingCallback)); 463 CRYPTO_set_dynlock_create_callback(assumeNothrow(&dynlockCreateCallback)); 464 CRYPTO_set_dynlock_lock_callback(assumeNothrow(&dynlockLockCallback)); 465 CRYPTO_set_dynlock_destroy_callback(assumeNothrow(&dynlockDestroyCallback)); 466 } 467 468 static void cleanupOpenSSL() { 469 if (!initialized_) return; 470 471 initialized_ = false; 472 CRYPTO_set_locking_callback(null); 473 CRYPTO_set_dynlock_create_callback(null); 474 CRYPTO_set_dynlock_lock_callback(null); 475 CRYPTO_set_dynlock_destroy_callback(null); 476 CRYPTO_cleanup_all_ex_data(); 477 ERR_free_strings(); 478 ERR_remove_state(0); 479 } 480 481 static extern(C) { 482 version (Posix) { 483 import core.sys.posix.pthread : pthread_self; 484 c_ulong threadIdCallback() { 485 return cast(c_ulong)pthread_self(); 486 } 487 } 488 489 void lockingCallback(int mode, int n, const(char)* file, int line) { 490 if (mode & CRYPTO_LOCK) { 491 mutexes_[n].lock(); 492 } else { 493 mutexes_[n].unlock(); 494 } 495 } 496 497 CRYPTO_dynlock_value* dynlockCreateCallback(const(char)* file, int line) { 498 enum size = __traits(classInstanceSize, Mutex); 499 auto mem = malloc(size)[0 .. size]; 500 if (!mem) onOutOfMemoryError(); 501 GC.addRange(mem.ptr, size); 502 auto mutex = emplace!Mutex(mem); 503 return cast(CRYPTO_dynlock_value*)mutex; 504 } 505 506 void dynlockLockCallback(int mode, CRYPTO_dynlock_value* l, 507 const(char)* file, int line) 508 { 509 if (l is null) return; 510 if (mode & CRYPTO_LOCK) { 511 (cast(Mutex)l).lock(); 512 } else { 513 (cast(Mutex)l).unlock(); 514 } 515 } 516 517 void dynlockDestroyCallback(CRYPTO_dynlock_value* l, 518 const(char)* file, int line) 519 { 520 GC.removeRange(l); 521 clear(cast(Mutex)l); 522 free(l); 523 } 524 525 int passwordCallback(char* password, int size, int, void* data) nothrow { 526 auto context = cast(TSSLContext) data; 527 auto userPassword = context.getPassword(size); 528 auto len = userPassword.length; 529 if (len > size) { 530 len = size; 531 } 532 password[0 .. len] = userPassword[0 .. len]; // TODO: \0 handling correct? 533 return cast(int)len; 534 } 535 } 536 537 static __gshared bool initialized_; 538 static __gshared Mutex initMutex_; 539 static __gshared Mutex[] mutexes_; 540 static __gshared uint count_; 541 } 542 543 /** 544 * Decides whether a remote host is legitimate or not. 545 * 546 * It is usually set at a TSSLContext, which then passes it to all the created 547 * TSSLSockets. 548 */ 549 class TAccessManager { 550 /// 551 enum Decision { 552 DENY = -1, /// Deny access. 553 SKIP = 0, /// Cannot decide, move on to next check (deny if last). 554 ALLOW = 1 /// Allow access. 555 } 556 557 /** 558 * Determines whether a peer should be granted access or not based on its 559 * IP address. 560 * 561 * Called once after SSL handshake is completes successfully and before peer 562 * certificate is examined. 563 * 564 * If a valid decision (ALLOW or DENY) is returned, the peer certificate 565 * will not be verified. 566 */ 567 Decision verify(Address address) { 568 return Decision.DENY; 569 } 570 571 /** 572 * Determines whether a peer should be granted access or not based on a 573 * name from its certificate. 574 * 575 * Called every time a DNS subjectAltName/common name is extracted from the 576 * peer's certificate. 577 * 578 * Params: 579 * host = The actual host name string from the socket connection. 580 * certHost = A host name string from the certificate. 581 */ 582 Decision verify(string host, const(char)[] certHost) { 583 return Decision.DENY; 584 } 585 586 /** 587 * Determines whether a peer should be granted access or not based on an IP 588 * address from its certificate. 589 * 590 * Called every time an IP subjectAltName is extracted from the peer's 591 * certificate. 592 * 593 * Params: 594 * address = The actual address from the socket connection. 595 * certHost = A host name string from the certificate. 596 */ 597 Decision verify(Address address, ubyte[] certAddress) { 598 return Decision.DENY; 599 } 600 } 601 602 /** 603 * Default access manager implementation, which just checks the host name 604 * resp. IP address of the connection against the certificate. 605 */ 606 class TDefaultClientAccessManager : TAccessManager { 607 override Decision verify(Address address) { 608 return Decision.SKIP; 609 } 610 611 override Decision verify(string host, const(char)[] certHost) { 612 if (host.empty || certHost.empty) { 613 return Decision.SKIP; 614 } 615 return (matchName(host, certHost) ? Decision.ALLOW : Decision.SKIP); 616 } 617 618 override Decision verify(Address address, ubyte[] certAddress) { 619 bool match; 620 if (certAddress.length == 4) { 621 if (auto ia = cast(InternetAddress)address) { 622 match = ((cast(ubyte*)ia.addr())[0 .. 4] == certAddress[]); 623 } 624 } else if (certAddress.length == 16) { 625 if (auto ia = cast(Internet6Address)address) { 626 match = (ia.addr() == certAddress[]); 627 } 628 } 629 return (match ? Decision.ALLOW : Decision.SKIP); 630 } 631 } 632 633 private { 634 /** 635 * Matches a name with a pattern. The pattern may include wildcard. A single 636 * wildcard "*" can match up to one component in the domain name. 637 * 638 * Params: 639 * host = Host name to match, typically the SSL remote peer. 640 * pattern = Host name pattern, typically from the SSL certificate. 641 * 642 * Returns: true if host matches pattern, false otherwise. 643 */ 644 bool matchName(const(char)[] host, const(char)[] pattern) { 645 while (!host.empty && !pattern.empty) { 646 if (toUpper(pattern.front) == toUpper(host.front)) { 647 host.popFront; 648 pattern.popFront; 649 } else if (pattern.front == '*') { 650 while (!host.empty && host.front != '.') { 651 host.popFront; 652 } 653 pattern.popFront; 654 } else { 655 break; 656 } 657 } 658 return (host.empty && pattern.empty); 659 } 660 661 unittest { 662 enforce(matchName("thrift.apache.org", "*.apache.org")); 663 enforce(!matchName("thrift.apache.org", "apache.org")); 664 enforce(matchName("thrift.apache.org", "thrift.*.*")); 665 enforce(matchName("", "")); 666 enforce(!matchName("", "*")); 667 } 668 } 669 670 /** 671 * SSL-level exception. 672 */ 673 class TSSLException : TTransportException { 674 /// 675 this(string msg, string file = __FILE__, size_t line = __LINE__, 676 Throwable next = null) 677 { 678 super(msg, TTransportException.Type.INTERNAL_ERROR, file, line, next); 679 } 680 }