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