1/* 2 Copyright (C) 2000-2005 SKYRIX Software AG 3 Copyright (C) 2011 Jeroen Dekkers <jeroen@dekkers.ch> 4 Copyright (C) 2020 Nicolas Höft 5 6 This file is part of SOPE. 7 8 SOPE is free software; you can redistribute it and/or modify it under 9 the terms of the GNU Lesser General Public License as published by the 10 Free Software Foundation; either version 2, or (at your option) any 11 later version. 12 13 SOPE is distributed in the hope that it will be useful, but WITHOUT ANY 14 WARRANTY; without even the implied warranty of MERCHANTABILITY or 15 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 16 License for more details. 17 18 You should have received a copy of the GNU Lesser General Public 19 License along with SOPE; see the file COPYING. If not, write to the 20 Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA 21 02111-1307, USA. 22*/ 23 24 25/* 26 27match_function() and _validateHostname are based on boost asio implementations 28 29Boost Software License - Version 1.0 - August 17th, 2003 30 31Permission is hereby granted, free of charge, to any person or organization 32obtaining a copy of the software and accompanying documentation covered by 33this license (the "Software") to use, reproduce, display, distribute, 34execute, and transmit the Software, and to prepare derivative works of the 35Software, and to permit third-parties to whom the Software is furnished to 36do so, all subject to the following: 37 38The copyright notices in the Software and this entire statement, including 39the above license grant, this restriction and the following disclaimer, 40must be included in all copies of the Software, in whole or in part, and 41all derivative works of the Software, unless such copies or derivative 42works are solely in the form of machine-executable object code generated by 43a source language processor. 44 45THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 46IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 47FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 48SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 49FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 50ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 51DEALINGS IN THE SOFTWARE. 52*/ 53 54 55#include <NGStreams/NGActiveSSLSocket.h> 56#include "common.h" 57 58#if HAVE_GNUTLS 59# include <gnutls/gnutls.h> 60# include <gnutls/x509.h> 61#define LOOP_CHECK(rval, cmd) \ 62 do { \ 63 rval = cmd; \ 64 } while(rval == GNUTLS_E_AGAIN || rval == GNUTLS_E_INTERRUPTED); 65#elif HAVE_OPENSSL 66# define id openssl_id 67# include <openssl/ssl.h> 68# include <openssl/err.h> 69# include <openssl/x509v3.h> 70# undef id 71#endif 72#include <fcntl.h> 73 74 75@interface NGActiveSocket(UsedPrivates) 76- (id)initWithDomain:(id<NGSocketDomain>)_domain onHostName: (NSString *)_hostName; 77- (BOOL)primaryConnectToAddress:(id<NGSocketAddress>)_address; 78#if HAVE_OPENSSL 79- (BOOL) _validateHostname: (X509 *) server_cert; 80#endif /* HAVE_OPENSSL */ 81@end 82 83@implementation NGActiveSSLSocket 84 85- (void) validatePeerCertificate:(BOOL)_validate 86{ 87 validatePeer = _validate; 88} 89 90 91- (BOOL)primaryConnectToAddress:(id<NGSocketAddress>)_address { 92 93 if (![super primaryConnectToAddress:_address]) 94 /* could not connect to Unix socket ... */ 95 return NO; 96 97 return [self startTLS]; 98} 99 100 101+ (id) socketConnectedToAddress: (id<NGSocketAddress>) _address 102 withVerifyMode: (int) mode 103{ 104 NGActiveSSLSocket *sock = [[self alloc] initWithDomain:[_address domain] 105 onHostName: [_address hostName]]; 106 if (mode == TLSVerifyNone || 107 ([_address isLocalhost] && mode == TLSVerifyAllowInsecureLocalhost)) { 108 [sock validatePeerCertificate: NO]; 109 } 110 else { 111 [sock validatePeerCertificate: YES]; 112 } 113 if (![sock connectToAddress:_address]) { 114 NSException *e; 115 e = [[sock lastException] retain]; 116 [self release]; 117 e = [e autorelease]; 118 [e raise]; 119 return nil; 120 } 121 sock = [sock autorelease]; 122 return sock; 123} 124 125- (id)initWithConnectedActiveSocket: (NGActiveSocket *) _socket 126 withVerifyMode: (int) mode 127{ 128 id<NGSocketAddress> remoteAddr; 129 int oldopts; 130 int sock_fd; 131 132 if (![_socket isConnected]) { 133 NSLog(@"ERROR(%s): Socket needs to be connected to remote", __PRETTY_FUNCTION__); 134 [self release]; 135 return nil; 136 } 137 remoteAddr = [_socket remoteAddress]; 138 [self initWithDomain: [remoteAddr domain] 139 onHostName: [remoteAddr hostName]]; 140 141 if (mode == TLSVerifyNone || 142 ([remoteAddr isLocalhost] && mode == TLSVerifyAllowInsecureLocalhost)) { 143 [self validatePeerCertificate: NO]; 144 } 145 else { 146 [self validatePeerCertificate: YES]; 147 } 148 149 sock_fd = [_socket fileDescriptor]; 150 // the fd is still owned by the other socket 151 [self setFileDescriptor: sock_fd closeWhenDone: NO]; 152 // We remove the NON-BLOCKING I/O flag on the file descriptor, otherwise 153 // SOPE will break on SSL-sockets. 154 oldopts = fcntl(sock_fd, F_GETFL, 0); 155 fcntl(sock_fd, F_SETFL, oldopts & !O_NONBLOCK); 156 return self; 157} 158 159#if HAVE_GNUTLS 160 161#if GNUTLS_VERSION_NUMBER < 0x030406 162/* This function will verify the peer's certificate, and check 163 * if the hostname matches, as well as the activation, expiration dates. 164 */ 165static int 166_verify_certificate_callback (gnutls_session_t session) 167{ 168 unsigned int status; 169 const gnutls_datum_t *cert_list; 170 unsigned int cert_list_size; 171 int ret; 172 gnutls_x509_crt_t cert; 173 const char *hostname; 174 175 /* read hostname */ 176 hostname = gnutls_session_get_ptr (session); 177 178 /* This verification function uses the trusted CAs in the credentials 179 * structure. So you must have installed one or more CA certificates. 180 */ 181 ret = gnutls_certificate_verify_peers2 (session, &status); 182 if (ret < 0) 183 { 184 NSLog(@"ERROR: verify_peer2 failed"); 185 return GNUTLS_E_CERTIFICATE_ERROR; 186 } 187 188 if (status & GNUTLS_CERT_SIGNER_NOT_FOUND) 189 NSLog(@"ERROR: The certificate hasn't got a known issuer."); 190 191 if (status & GNUTLS_CERT_REVOKED) 192 NSLog(@"ERROR: The certificate has been revoked."); 193 194 if (status & GNUTLS_CERT_EXPIRED) 195 NSLog(@"ERROR: The certificate has expired"); 196 197 if (status & GNUTLS_CERT_NOT_ACTIVATED) 198 NSLog(@"ERROR: The certificate is not yet activated"); 199 200 if (status & GNUTLS_CERT_INVALID) 201 { 202 NSLog(@"ERROR: The certificate is not trusted."); 203 return GNUTLS_E_CERTIFICATE_ERROR; 204 } 205 206 /* Up to here the process is the same for X.509 certificates and 207 * OpenPGP keys. From now on X.509 certificates are assumed. This can 208 * be easily extended to work with openpgp keys as well. 209 */ 210 if (gnutls_certificate_type_get (session) != GNUTLS_CRT_X509) 211 return GNUTLS_E_CERTIFICATE_ERROR; 212 213 if (gnutls_x509_crt_init (&cert) < 0) 214 { 215 NSLog(@"WARNING: could not initialize gnutls certificate"); 216 return GNUTLS_E_CERTIFICATE_ERROR; 217 } 218 219 cert_list = gnutls_certificate_get_peers (session, &cert_list_size); 220 if (cert_list == NULL) 221 { 222 NSLog(@"WARNING: GnuTLS certificate not found"); 223 return GNUTLS_E_CERTIFICATE_ERROR; 224 } 225 226 if (gnutls_x509_crt_import (cert, &cert_list[0], GNUTLS_X509_FMT_DER) < 0) 227 { 228 NSLog(@"WARNING: Unable to parse certificate"); 229 return GNUTLS_E_CERTIFICATE_ERROR; 230 } 231 232 233 if (!gnutls_x509_crt_check_hostname (cert, hostname)) 234 { 235 NSLog(@"ERROR: Certificate does not match hostname '%s'", hostname); 236 return GNUTLS_E_CERTIFICATE_ERROR; 237 } 238 239 gnutls_x509_crt_deinit (cert); 240 241 /* notify gnutls to continue handshake normally */ 242 return 0; 243} 244#endif /* GNUTLS_VERSION_NUMBER < 0x030406 */ 245 246 247- (id)initWithDomain:(id<NGSocketDomain>)_domain 248 onHostName: (NSString *)_hostName 249{ 250 hostName = [_hostName copy]; 251 if ((self = [super initWithDomain:_domain])) { 252 static BOOL didGlobalInit = NO; 253 int ret; 254 255 if (!didGlobalInit) { 256 /* Global system initialization*/ 257 if (gnutls_global_init()) { 258 [self release]; 259 return nil; 260 } 261 262 didGlobalInit = YES; 263 } 264 265 ret = gnutls_certificate_allocate_credentials((gnutls_certificate_credentials_t *) &self->cred); 266 if (ret) 267 { 268 NSLog(@"ERROR(%s): couldn't create GnuTLS credentials (%s)", 269 __PRETTY_FUNCTION__, gnutls_strerror(ret)); 270 [self release]; 271 return nil; 272 } 273#ifdef CA_BUNDLE 274 ret = gnutls_certificate_set_x509_trust_file(self->cred, CA_BUNDLE, GNUTLS_X509_FMT_PEM); 275#elif GNUTLS_VERSION_NUMBER >= 0x030020 276 ret = gnutls_certificate_set_x509_system_trust(self->cred); 277#else 278#error "Cant use default system trust, CA_BUNDLE needs to be passed" 279#endif 280 if (ret < 0) 281 { 282 NSLog(@"ERROR(%s): could not set GnuTLS system trust (%s)", 283 __PRETTY_FUNCTION__, gnutls_strerror(ret)); 284 [self release]; 285 return nil; 286 } 287 288 self->session = NULL; 289 } 290 return self; 291} 292 293- (void)dealloc { 294 if (self->session) { 295 gnutls_deinit((gnutls_session_t) self->session); 296 self->session = NULL; 297 } 298 if (self->cred) { 299 gnutls_certificate_free_credentials((gnutls_certificate_credentials_t) self->cred); 300 self->cred = NULL; 301 } 302 [hostName release]; 303 [super dealloc]; 304} 305 306/* basic IO, reading and writing bytes */ 307 308- (unsigned)readBytes:(void *)_buf count:(unsigned)_len { 309 ssize_t ret; 310 311 if (self->session == NULL) 312 // should throw error 313 return NGStreamError; 314 315 316 LOOP_CHECK(ret, gnutls_record_recv((gnutls_session_t) self->session, _buf, _len)); 317 if (ret <= 0) 318 return NGStreamError; 319 else 320 return ret; 321} 322 323- (unsigned)writeBytes:(const void *)_buf count:(unsigned)_len { 324 ssize_t ret; 325 326 if (self->session == NULL) 327 // should throw error 328 return NGStreamError; 329 330 LOOP_CHECK(ret, gnutls_record_send((gnutls_session_t) self->session, _buf, _len)); 331 if (ret <= 0) 332 return NGStreamError; 333 else 334 return ret; 335} 336 337/* connection and shutdown */ 338 339- (BOOL)markNonblockingAfterConnect { 340 return NO; 341} 342 343- (BOOL) startTLS 344{ 345 int ret; 346 gnutls_session_t sess; 347 348 [self disableNagle: YES]; 349 350 ret = gnutls_init((gnutls_session_t *) &self->session, GNUTLS_CLIENT); 351 if (ret) { 352 // should set exception ! 353 NSLog(@"ERROR(%s): couldn't create GnuTLS session (%s)", 354 __PRETTY_FUNCTION__, gnutls_strerror(ret)); 355 return NO; 356 } 357 sess = (gnutls_session_t) self->session; 358 359 gnutls_set_default_priority(sess); 360 361 ret = gnutls_credentials_set(sess, GNUTLS_CRD_CERTIFICATE, (gnutls_certificate_credentials_t) self->cred); 362 if (ret) { 363 // should set exception ! 364 NSLog(@"ERROR(%s): couldn't set GnuTLS credentials (%s)", 365 __PRETTY_FUNCTION__, gnutls_strerror(ret)); 366 return NO; 367 } 368 369 // set SNI 370 ret = gnutls_server_name_set(sess, GNUTLS_NAME_DNS, [hostName UTF8String], [hostName length]); 371 if (ret) { 372 // should set exception ! 373 NSLog(@"ERROR(%s): couldn't set GnuTLS SNI (%s)", 374 __PRETTY_FUNCTION__, gnutls_strerror(ret)); 375 return NO; 376 } 377 if (validatePeer) 378 { 379#if GNUTLS_VERSION_NUMBER >= 0x030406 380 gnutls_session_set_verify_cert(sess, [hostName UTF8String], 0); 381#else 382 gnutls_session_set_ptr(session, (void *) [hostName UTF8String]); 383 gnutls_certificate_set_verify_function(self->cred, _verify_certificate_callback); 384#endif /* GNUTLS_VERSION_NUMBER >= 0x030406*/ 385 } 386#if GNUTLS_VERSION_NUMBER < 0x030109 387 gnutls_transport_set_ptr(sess, (gnutls_transport_ptr_t)(long)self->fd); 388#else 389 gnutls_transport_set_int(sess, self->fd); 390#endif /* GNUTLS_VERSION_NUMBER < 0x030109 */ 391 392#if GNUTLS_VERSION_NUMBER >= 0x030100 393 gnutls_handshake_set_timeout(sess, GNUTLS_DEFAULT_HANDSHAKE_TIMEOUT); 394#endif 395 do { 396 ret = gnutls_handshake(sess); 397 } while (ret < 0 && gnutls_error_is_fatal(ret) == 0); 398 399 if (ret < 0) { 400 NSLog(@"ERROR(%s):GnutTLS handshake failed on socket (%s)", 401 __PRETTY_FUNCTION__, gnutls_strerror(ret)); 402 if (ret == GNUTLS_E_FATAL_ALERT_RECEIVED) { 403 NSLog(@"Alert: %s", gnutls_alert_get_name(gnutls_alert_get(sess))); 404 } 405 [self shutdown]; 406 return NO; 407 } 408 409 return YES; 410} 411 412- (BOOL)shutdown { 413 414 if (self->session) { 415 int ret; 416 LOOP_CHECK(ret, gnutls_bye((gnutls_session_t)self->session, GNUTLS_SHUT_RDWR)); 417 gnutls_deinit((gnutls_session_t) self->session); 418 self->session = NULL; 419 } 420 if (self->cred) { 421 gnutls_certificate_free_credentials((gnutls_certificate_credentials_t) self->cred); 422 self->cred = NULL; 423 } 424 return [super shutdown]; 425} 426 427#elif HAVE_OPENSSL 428 429#if OPENSSL_VERSION_NUMBER < 0x10020000L 430static BOOL match_pattern(const char* pattern, size_t pattern_length, const char* host) 431{ 432 const char* p = pattern; 433 const char* p_end = p + pattern_length; 434 const char* h = host; 435 436 while (p != p_end && *h) { 437 if (*p == '*') { 438 ++p; 439 while (*h && *h != '.') { 440 if (match_pattern(p, p_end - p, h++)) { 441 return YES; 442 } 443 } 444 } 445 else if (tolower(*p) == tolower(*h)) { 446 ++p; 447 ++h; 448 } 449 else { 450 return NO; 451 } 452 } 453 454 return p == p_end && !*h; 455} 456 457- (BOOL) _validateHostname: (X509 *) server_cert 458{ 459 // Go through the alternate names in the certificate looking for matching DNS 460 // entries. 461 int i; 462 GENERAL_NAMES* gens = (GENERAL_NAMES*) X509_get_ext_d2i(server_cert, NID_subject_alt_name, 0, 0); 463 464 for (i = 0; i < sk_GENERAL_NAME_num(gens); ++i) { 465 GENERAL_NAME* gen = sk_GENERAL_NAME_value(gens, i); 466 if (gen->type == GEN_DNS) { 467 ASN1_IA5STRING* dns_name = gen->d.dNSName; 468 if (dns_name->type == V_ASN1_IA5STRING && dns_name->data && dns_name->length) { 469 const char* pattern = (const char*)dns_name->data; 470 size_t pattern_length = dns_name->length; 471 if (match_pattern(pattern, pattern_length, [hostName UTF8String])) { 472 GENERAL_NAMES_free(gens); 473 return YES; 474 } 475 } 476 } 477 } 478 GENERAL_NAMES_free(gens); 479 480 // No match in the alternate names, so try the common names. We should only 481 // use the "most specific" common name, which is the last one in the list. 482 X509_NAME* name = X509_get_subject_name(server_cert); 483 ASN1_STRING* common_name = 0; 484 while ((i = X509_NAME_get_index_by_NID(name, NID_commonName, i)) >= 0) { 485 X509_NAME_ENTRY* name_entry = X509_NAME_get_entry(name, i); 486 common_name = X509_NAME_ENTRY_get_data(name_entry); 487 } 488 489 if (common_name && common_name->data && common_name->length) { 490 const char* pattern = (const char*) common_name->data; 491 size_t pattern_length = common_name->length; 492 if (match_pattern(pattern, pattern_length, [hostName UTF8String])) { 493 return YES; 494 } 495 } 496 497 return NO; 498} 499 500 501/* See http://archives.seul.org/libevent/users/Jan-2013/msg00039.html */ 502static int cert_verify_callback(X509_STORE_CTX *x509_ctx, void *arg) 503{ 504 NGActiveSSLSocket *sslsock = (NGActiveSSLSocket *)arg; 505 BOOL validation_result = NO; 506 507 /* This is the function that OpenSSL would call if we hadn't called 508 * SSL_CTX_set_cert_verify_callback(). Therefore, we are "wrapping" 509 * the default functionality, rather than replacing it. */ 510 int ok_so_far = 0; 511 512 X509 *server_cert = NULL; 513 514 ok_so_far = X509_verify_cert(x509_ctx) == 1; 515 516 server_cert = X509_STORE_CTX_get_current_cert(x509_ctx); 517 518 if (ok_so_far) { 519 validation_result = [sslsock _validateHostname: server_cert]; 520 } 521 522 if (validation_result == YES) { 523 return 1; 524 } else { 525 NSLog(@"SSL(%s): Certificate validation failed", 526 __PRETTY_FUNCTION__); 527 return 0; 528 } 529} 530#endif /* #OPENSSL_VERSION_NUMBER < 0x10020000L */ 531 532- (id)initWithDomain:(id<NGSocketDomain>)_domain 533 onHostName: (NSString *)_hostName 534{ 535 536 hostName = [_hostName copy]; 537 if ((self = [super initWithDomain:_domain])) { 538 539#if OPENSSL_VERSION_NUMBER < 0x10100000L 540 static BOOL didGlobalInit = NO; 541 if (!didGlobalInit) { 542 /* Global system initialization*/ 543 SSL_library_init(); 544 SSL_load_error_strings(); 545 didGlobalInit = YES; 546 } 547#endif /* OPENSSL_VERSION_NUMBER < 0x10100000L */ 548 549 550 /* Create our context*/ 551 if ((self->ctx = SSL_CTX_new(SSLv23_method())) == NULL) { 552 NSLog(@"ERROR(%s): couldn't create SSL context for v23 method !", 553 __PRETTY_FUNCTION__); 554 [self release]; 555 return nil; 556 } 557 // use system default trust store 558 559#ifdef CA_BUNDLE 560 SSL_CTX_load_verify_locations(self->ctx, CA_BUNDLE, NULL); 561#else 562 SSL_CTX_set_default_verify_paths(self->ctx); 563#endif // CA_BUNDLE 564 565 if ((self->ssl = SSL_new(self->ctx)) == NULL) { 566 // should set exception ! 567 NSLog(@"ERROR(%s): couldn't create SSL socket structure ...", 568 __PRETTY_FUNCTION__); 569 return nil; 570 } 571#if OPENSSL_VERSION_NUMBER < 0x10020000L 572 SSL_CTX_set_cert_verify_callback(self->ctx, cert_verify_callback, (void *)self); 573#elif OPENSSL_VERSION_NUMBER < 0x10100000L 574 X509_VERIFY_PARAM *param = NULL; 575 param = SSL_get0_param(self->ssl); 576 X509_VERIFY_PARAM_set_hostflags(param, X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS); 577 if (!X509_VERIFY_PARAM_set1_host(param, [hostName UTF8String], 0)) { 578 return nil; 579 } 580#else 581 SSL_set1_host(self->ssl, [hostName UTF8String]); 582#endif /* OPENSSL_VERSION_NUMBER < 0x10100000L */ 583 584 // send SNI 585 SSL_set_tlsext_host_name(self->ssl, [hostName UTF8String]); 586 587 } 588 return self; 589} 590 591- (void)dealloc { 592 [self shutdown]; 593 [hostName release]; 594 [super dealloc]; 595} 596 597/* basic IO, reading and writing bytes */ 598 599- (unsigned)readBytes:(void *)_buf count:(unsigned)_len { 600 int ret; 601 602 if (self->ssl == NULL) 603 // should throw error 604 return NGStreamError; 605 606 ret = SSL_read(self->ssl, _buf, _len); 607 608 if (ret <= 0) 609 return NGStreamError; 610 611 return ret; 612} 613 614- (unsigned)writeBytes:(const void *)_buf count:(unsigned)_len { 615 return SSL_write(self->ssl, _buf, _len); 616} 617 618/* connection and shutdown */ 619 620- (BOOL)markNonblockingAfterConnect { 621 return NO; 622} 623 624- (BOOL) startTLS 625{ 626 int ret; 627 int verifyMode = validatePeer == YES ? SSL_VERIFY_PEER : SSL_VERIFY_NONE; 628 629 [self disableNagle: YES]; 630 631 SSL_set_verify(self->ssl, verifyMode, NULL); 632 633 if (self->ssl == NULL) { 634 NSLog(@"ERROR(%s): SSL structure is not set up!", 635 __PRETTY_FUNCTION__); 636 return NO; 637 } 638 639 if (SSL_set_fd(self->ssl, self->fd) <= 0) { 640 // should set exception ! 641 NSLog(@"ERROR(%s): couldn't set FD ...", 642 __PRETTY_FUNCTION__); 643 return NO; 644 } 645 646 ret = SSL_connect(self->ssl); 647 if (ret <= 0) { 648 NSLog(@"ERROR(%s): couldn't setup SSL connection on host %@ (%s)...", 649 __PRETTY_FUNCTION__, hostName, ERR_error_string(SSL_get_error(self->ssl, ret), NULL)); 650 [self shutdown]; 651 return NO; 652 } 653 654 return YES; 655} 656 657- (BOOL)shutdown { 658 if (self->ssl) { 659 int ret = SSL_shutdown(self->ssl); 660 // call shutdown a second time 661 if (ret == 0) 662 SSL_shutdown(self->ssl); 663 SSL_free(self->ssl); 664 self->ssl = NULL; 665 } 666 if (self->ctx) { 667 SSL_CTX_free(self->ctx); 668 self->ctx = NULL; 669 } 670 return [super shutdown]; 671} 672 673#else /* no OpenSSL available */ 674 675+ (void)initialize { 676 NSLog(@"WARNING: The NGActiveSSLSocket class was accessed, " 677 @"but OpenSSL support is turned off."); 678} 679- (id)initWithDomain:(id<NGSocketDomain>)_domain onHostName: (NSString *)_hostName withVerifyMode: (int) mode { 680 [self release]; 681 return nil; 682} 683 684 685#endif 686 687@end /* NGActiveSSLSocket */ 688