From 10c5815622af2295e2bc2d478bd61a8a2691979a Mon Sep 17 00:00:00 2001 From: rfm Date: Mon, 24 Sep 2012 09:07:55 +0000 Subject: [PATCH] restructure for maintainability git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/base/trunk@35598 72102866-910b-0410-8b05-ffd578937521 --- ChangeLog | 11 + Headers/Foundation/NSFileHandle.h | 16 +- SSL/GSSSLHandle.m | 6 +- Source/GNUmakefile | 1 + Source/GSSocketStream.m | 975 +----------------------------- Source/GSTLS.h | 109 ++++ Source/GSTLS.m | 616 +++++++++++++++++++ Source/NSFileHandle.m | 357 ++++++++++- 8 files changed, 1118 insertions(+), 973 deletions(-) create mode 100644 Source/GSTLS.h create mode 100644 Source/GSTLS.m diff --git a/ChangeLog b/ChangeLog index 3a608a985..b5ef86c09 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,14 @@ +2012-09-25 Richard Frith-Macdonald + + * Source/GNUmakefile: + * Source/GSSocketStream.m: + * Source/NSFileHandle.m: + * Headers/Foundation/NSFileHandle.h: + * SSL/GSSSLHandle.m: + * Source/GSTLS.h: + * Source/GSTLS.m: + Restructure to separate some of the tls functionality out. + 2012-09-24 Richard Frith-Macdonald * Source/NSBundle.m: Fix error spotted by Fred. diff --git a/Headers/Foundation/NSFileHandle.h b/Headers/Foundation/NSFileHandle.h index bbdc786e4..dc79c7b57 100644 --- a/Headers/Foundation/NSFileHandle.h +++ b/Headers/Foundation/NSFileHandle.h @@ -275,19 +275,19 @@ GS_EXPORT NSString * const NSFileHandleOperationException; * be set.
> * Expects key value pairs with the follwiing names/meanings: * - * GSTLSCertificateFileKey + * GSTLSCertificateFile * The path to a PEM encoded certificate used to identify this end * of the connection. This option must be set for handing an * incoming connection, but is optional for outgoing connections.
- * This must be used in conjunction with GSTLSPrivateKeyFileKey. + * This must be used in conjunction with GSTLSPrivateKeyFile. *
- * GSTLSPrivateKeyFileKey + * GSTLSPrivateKeyFile * The path to a PEM encoded key used to unlock the certificate * file for the connection. The key in the file may or may not be * encrypted, but if it is encrypted you must specify - * GSTLSPrivateKeyPasswordKey. + * GSTLSPrivateKeyPassword. * - * GSTLSPrivateKeyPasswordKey + * GSTLSPrivateKeyPassword * A string to be used as the password to decrypt a key which was * specified using GSTLSKeyPassword. * @@ -300,17 +300,17 @@ GS_EXPORT NSString * const NSFileHandleOperationException; /** Dictionary key for the path to a PEM encoded certificate used * to identify this end of a connection. */ -GS_EXPORT NSString * const GSTLSCertificateFileKey; +GS_EXPORT NSString * const GSTLSCertificateFile; /** Dictionary key for the path to a PEM encoded private key used * to unlock the certificate used by this end of a connection. */ -GS_EXPORT NSString * const GSTLSPrivateKeyFileKey; +GS_EXPORT NSString * const GSTLSPrivateKeyFile; /** Dictionary key for the password used to decrypt the key file used * to unlock the certificate used by this end of a connection. */ -GS_EXPORT NSString * const GSTLSPrivateKeyPasswordKey; +GS_EXPORT NSString * const GSTLSPrivateKeyPassword; // GNUstep Notification names. diff --git a/SSL/GSSSLHandle.m b/SSL/GSSSLHandle.m index 457b7a0fc..630205ca8 100644 --- a/SSL/GSSSLHandle.m +++ b/SSL/GSSSLHandle.m @@ -365,9 +365,9 @@ static NSString *cipherList = nil; NSString *PEMpasswd; int ret; - certFile = [options objectForKey: GSTLSCertificateFileKey]; - privateKey = [options objectForKey: GSTLSPrivateKeyFileKey]; - PEMpasswd = [options objectForKey: GSTLSPrivateKeyPasswordKey]; + certFile = [options objectForKey: GSTLSCertificateFile]; + privateKey = [options objectForKey: GSTLSPrivateKeyFile]; + PEMpasswd = [options objectForKey: GSTLSPrivateKeyPassword]; if (isStandardFile == YES) { diff --git a/Source/GNUmakefile b/Source/GNUmakefile index 7897ac514..8152592eb 100644 --- a/Source/GNUmakefile +++ b/Source/GNUmakefile @@ -171,6 +171,7 @@ GSSocketStream.m \ GSStream.m \ GSString.m \ GSTimSort.m \ +GSTLS.m \ GSValue.m \ NSAffineTransform.m \ NSArchiver.m \ diff --git a/Source/GSSocketStream.m b/Source/GSSocketStream.m index 4cfa81083..c9226dde6 100644 --- a/Source/GSSocketStream.m +++ b/Source/GSSocketStream.m @@ -30,7 +30,6 @@ #import "Foundation/NSDictionary.h" #import "Foundation/NSEnumerator.h" #import "Foundation/NSException.h" -#import "Foundation/NSFileHandle.h" #import "Foundation/NSHost.h" #import "Foundation/NSLock.h" #import "Foundation/NSNotification.h" @@ -39,18 +38,11 @@ #import "Foundation/NSValue.h" #import "GSPrivate.h" -#define EXPOSE_GSFileHandle_IVARS 1 -#import "GSFileHandle.h" #import "GSStream.h" #import "GSSocketStream.h" #import "GNUstepBase/NSObject+GNUstepBase.h" -/* Perhaps thse should be elsewhere ... maybe in a new file along with - * the gnutls infrastructure. - */ -NSString * const GSTLSCertificateFileKey = @"GSTLSCertificateFileKey"; -NSString * const GSTLSPrivateKeyFileKey = @"GSTLSPrivateKeyFile"; -NSString * const GSTLSPrivateKeyPasswordKey = @"GSTLSPrivateKeyPassword"; +#import "GSTLS.h" #ifndef SHUT_RD # ifdef SD_RECEIVE @@ -357,613 +349,8 @@ GSPrivateSockaddrSetup(NSString *machine, uint16_t port, @end #if defined(HAVE_GNUTLS) -/* Temporarily redefine 'id' in case the headers use the objc reserved word. - */ -#define id GNUTLSID -/* gcrypt uses __attribute__((deprecated)) to mark structure members that are - * private. This causes compiler warnings just from using the header. Turn - * them off... - */ -#define _GCRYPT_IN_LIBGCRYPT -#include -#include -#include -#undef id -/* Set up locking callbacks for gcrypt so that it will be thread-safe. - */ -static int gcry_mutex_init (void **priv) -{ - NSLock *lock = [NSLock new]; - *priv = (void*)lock; - return 0; -} -static int gcry_mutex_destroy (void **lock) -{ - [((NSLock*)*lock) release]; - return 0; -} -static int gcry_mutex_lock (void **lock) -{ - [((NSLock*)*lock) lock]; - return 0; -} -static int gcry_mutex_unlock (void **lock) -{ - [((NSLock*)*lock) unlock]; - return 0; -} -static struct gcry_thread_cbs gcry_threads_other = { - GCRY_THREAD_OPTION_DEFAULT, - NULL, - gcry_mutex_init, - gcry_mutex_destroy, - gcry_mutex_lock, - gcry_mutex_unlock -}; - -static void -GSTLSLog(int level, const char *msg) -{ - NSLog(@"%s", msg); -} - -static NSString *cipherList = nil; - -static gnutls_anon_client_credentials_t anoncred; - -/* This function will verify the peer's certificate, and check - * if the hostname matches, as well as the activation, expiration dates. - */ -static int -_verify_certificate_callback (gnutls_session_t session) -{ - unsigned int status; - const gnutls_datum_t *cert_list; - unsigned int cert_list_size; - int ret; - gnutls_x509_crt_t cert; - const char *hostname; - - /* read hostname */ - hostname = gnutls_session_get_ptr (session); - - /* This verification function uses the trusted CAs in the credentials - * structure. So you must have installed one or more CA certificates. - */ - ret = gnutls_certificate_verify_peers2 (session, &status); - if (ret < 0) - { - printf ("Error\n"); - return GNUTLS_E_CERTIFICATE_ERROR; - } - - if (status & GNUTLS_CERT_SIGNER_NOT_FOUND) - printf ("The certificate hasn't got a known issuer.\n"); - - if (status & GNUTLS_CERT_REVOKED) - printf ("The certificate has been revoked.\n"); - -/* - if (status & GNUTLS_CERT_EXPIRED) - printf ("The certificate has expired\n"); - - if (status & GNUTLS_CERT_NOT_ACTIVATED) - printf ("The certificate is not yet activated\n"); -*/ - - if (status & GNUTLS_CERT_INVALID) - { - printf ("The certificate is not trusted.\n"); - return GNUTLS_E_CERTIFICATE_ERROR; - } - - /* Up to here the process is the same for X.509 certificates and - * OpenPGP keys. From now on X.509 certificates are assumed. This can - * be easily extended to work with openpgp keys as well. - */ - if (gnutls_certificate_type_get (session) != GNUTLS_CRT_X509) - return GNUTLS_E_CERTIFICATE_ERROR; - - if (gnutls_x509_crt_init (&cert) < 0) - { - printf ("error in initialization\n"); - return GNUTLS_E_CERTIFICATE_ERROR; - } - - cert_list = gnutls_certificate_get_peers (session, &cert_list_size); - if (cert_list == NULL) - { - printf ("No certificate was found!\n"); - return GNUTLS_E_CERTIFICATE_ERROR; - } - - if (gnutls_x509_crt_import (cert, &cert_list[0], GNUTLS_X509_FMT_DER) < 0) - { - printf ("error parsing certificate\n"); - return GNUTLS_E_CERTIFICATE_ERROR; - } - - if (!gnutls_x509_crt_check_hostname (cert, hostname)) - { - printf ("The certificate's owner does not match hostname '%s'\n", - hostname); - return GNUTLS_E_CERTIFICATE_ERROR; - } - - gnutls_x509_crt_deinit (cert); - - /* notify gnutls to continue handshake normally */ - return 0; -} - -/* This class is used to ensure that the GNUTLS system is initialised - * and thread-safe. - */ -@interface GNUTLSObject : NSObject -@end - -@implementation GNUTLSObject - -+ (void) _defaultsChanged: (NSNotification*)n -{ - cipherList - = [[NSUserDefaults standardUserDefaults] stringForKey: @"GSCipherList"]; -} - -+ (void) initialize -{ - if ([GNUTLSObject class] == self) - { - static BOOL beenHere = NO; - - if (beenHere == NO) - { - NSUserDefaults *defs; - - beenHere = YES; - - defs = [NSUserDefaults standardUserDefaults]; - cipherList = [defs stringForKey: @"GSCipherList"]; - [[NSNotificationCenter defaultCenter] - addObserver: self - selector: @selector(_defaultsChanged:) - name: NSUserDefaultsDidChangeNotification - object: nil]; - - /* Make gcrypt thread-safe - */ - gcry_control (GCRYCTL_SET_THREAD_CBS, &gcry_threads_other); - - /* Initialise gnutls - */ - gnutls_global_init (); - - /* Allocate global credential information for anonymous tls - */ - gnutls_anon_allocate_client_credentials (&anoncred); - - /* Enable gnutls logging via NSLog - */ - gnutls_global_set_log_function (GSTLSLog); - } - } -} -@end - -@interface GNUTLSDHParams : GNUTLSObject -{ - gnutls_dh_params_t params; -} -+ (GNUTLSDHParams*) current; -- (gnutls_dh_params_t) params; -@end - -@implementation GNUTLSDHParams -static NSLock *paramsLock = nil; -static NSDate *when = nil; -static GNUTLSDHParams *current = nil; - -+ (GNUTLSDHParams*) current -{ - GNUTLSDHParams *p; - - [paramsLock lock]; - if (nil == current) - { - current = [self new]; - } - p = [current retain]; - [paramsLock unlock]; - return [current autorelease]; -} - -+ (void) housekeeping: (NSNotification*)n -{ - NSDate *now; - - now = [NSDate date]; - [paramsLock lock]; - /* Regenerate DH params once per day. - */ - if ([now timeIntervalSinceDate: when] > 24 * 60 * 60) - { - ASSIGN(when, [NSDate date]); - ASSIGN(current, [self new]); - } - [paramsLock unlock]; -} - -+ (void) initialize -{ - if (nil == paramsLock) - { - paramsLock = [NSLock new]; - when = [NSDate new]; - current = [self new]; - [[NSNotificationCenter defaultCenter] addObserver: self - selector: @selector(housekeeping:) - name: @"GSHousekeeping" object: nil]; - } -} - -- (void) dealloc -{ - gnutls_dh_params_deinit (params); - [super dealloc]; -} - -- (id) init -{ - /* Generate Diffie-Hellman parameters - for use with DHE - * kx algorithms. When short bit length is used, it might - * be wise to regenerate parameters often. - */ - gnutls_dh_params_init (¶ms); - gnutls_dh_params_generate2 (params, 2048); - return self; -} - -- (gnutls_dh_params_t) params -{ - return params; -} -@end - -/* Manage certificate lists (for servers and clients) and also provide - * DH params. - */ -@interface GNUTLSCertificateList : GNUTLSObject -{ - NSDate *when; - NSString *path; - gnutls_x509_crt_t *crts; - unsigned int count; -} -+ (GNUTLSCertificateList*) listFromFile: (NSString*)f; -- (gnutls_x509_crt_t*) certificateList; -- (unsigned int) count; -@end - -@implementation GNUTLSCertificateList - -static NSLock *certificateListLock = nil; -static NSMutableDictionary *certificateListCache = nil; - -/* Method to purge older lists from cache. - */ -+ (void) housekeeping: (NSNotification*)n -{ - NSEnumerator *enumerator; - NSString *key; - NSDate *now; - - now = [NSDate date]; - [certificateListLock lock]; - enumerator = [[certificateListCache allKeys] objectEnumerator]; - while (nil != (key = [enumerator nextObject])) - { - GNUTLSCertificateList *list; - - list = [certificateListCache objectForKey: key]; - - if ([now timeIntervalSinceDate: list->when] > 300.0) - { - [certificateListCache removeObjectForKey: key]; - } - } - [certificateListLock unlock]; -} - -+ (void) initialize -{ - if (nil == certificateListLock) - { - certificateListLock = [NSLock new]; - certificateListCache = [NSMutableDictionary new]; - [[NSNotificationCenter defaultCenter] addObserver: self - selector: @selector(housekeeping:) - name: @"GSHousekeeping" object: nil]; - } -} - -+ (GNUTLSCertificateList*) listFromFile: (NSString*)f -{ - GNUTLSCertificateList *l; - - if (nil == f) - { - return nil; - } - [certificateListLock lock]; - l = [[certificateListCache objectForKey: f] retain]; - [certificateListLock unlock]; - - if (nil == l) - { - NSData *data; - int ret; - gnutls_datum_t datum; - unsigned int count = 100; - gnutls_x509_crt_t crts[count]; - - data = [NSData dataWithContentsOfFile: f]; - if (nil == data) - { - NSLog(@"Unable to read certificate file '%@'", f); - return nil; - } - datum.data = (unsigned char*)[data bytes]; - datum.size = (unsigned int)[data length]; - - l = [self alloc]; - l->when = [NSDate new]; - l->path = [f copy]; - ret = gnutls_x509_crt_list_import(crts, &count, &datum, - GNUTLS_X509_FMT_PEM, -// GNUTLS_X509_CRT_LIST_FAIL_IF_UNSORTED | - GNUTLS_X509_CRT_LIST_IMPORT_FAIL_IF_EXCEED); - if (ret < 0) - { - NSLog(@"Unable to parse certificate file '%@': %s", - l->path, gnutls_strerror(ret)); - [l release]; - return nil; - } - l->crts = malloc(sizeof(gnutls_x509_crt_t) * count); - memcpy(l->crts, crts, sizeof(gnutls_x509_crt_t) * count); - l->count = count; - - [certificateListLock lock]; - [certificateListCache setObject: l forKey: l->path]; - [certificateListLock unlock]; - } - - return [l autorelease]; -} - -- (gnutls_x509_crt_t*) certificateList -{ - return crts; -} - -- (unsigned int) count -{ - return count; -} - -- (void) dealloc -{ - if (nil != path) - { - DESTROY(when); - DESTROY(path); - if (count > 0) - { - while (count-- > 0) - { - gnutls_x509_crt_deinit(crts[count]); - } - free(crts); - } - } - [super dealloc]; -} - -@end - - -@interface GNUTLSPrivateKey : GNUTLSObject -{ - NSDate *when; - NSString *path; - NSString *password; - gnutls_x509_privkey_t key; -} -+ (GNUTLSPrivateKey*) keyFromFile: (NSString*)f withPassword: (NSString*)p; -- (gnutls_x509_privkey_t) key; -@end - -@implementation GNUTLSPrivateKey - -static NSLock *privateKeyLock = nil; -static NSMutableDictionary *privateKeyCache0 = nil; -static NSMutableDictionary *privateKeyCache1 = nil; - -/* Method to purge older keys from cache. - */ -+ (void) housekeeping: (NSNotification*)n -{ - NSEnumerator *outer; - NSString *oKey; - NSDate *now; - - now = [NSDate date]; - [privateKeyLock lock]; - outer = [[privateKeyCache0 allKeys] objectEnumerator]; - while (nil != (oKey = [outer nextObject])) - { - GNUTLSPrivateKey *key; - - key = [privateKeyCache0 objectForKey: oKey]; - if ([now timeIntervalSinceDate: key->when] > 300.0) - { - [privateKeyCache0 removeObjectForKey: oKey]; - } - } - outer = [[privateKeyCache1 allKeys] objectEnumerator]; - while (nil != (oKey = [outer nextObject])) - { - NSMutableDictionary *m; - NSEnumerator *inner; - NSString *iKey; - - m = [privateKeyCache1 objectForKey: oKey]; - inner = [[m allKeys] objectEnumerator]; - while (nil != (iKey = [inner nextObject])) - { - GNUTLSPrivateKey *key = [m objectForKey: iKey]; - - if ([now timeIntervalSinceDate: key->when] > 300.0) - { - [m removeObjectForKey: iKey]; - if (0 == [m count]) - { - [privateKeyCache1 removeObjectForKey: oKey]; - } - } - } - } - [privateKeyLock unlock]; -} - -+ (void) initialize -{ - if (nil == privateKeyLock) - { - privateKeyLock = [NSLock new]; - privateKeyCache0 = [NSMutableDictionary new]; - privateKeyCache1 = [NSMutableDictionary new]; - - [[NSNotificationCenter defaultCenter] addObserver: self - selector: @selector(housekeeping:) - name: @"GSHousekeeping" object: nil]; - } -} - -+ (GNUTLSPrivateKey*) keyFromFile: (NSString*)f withPassword: (NSString*)p -{ - GNUTLSPrivateKey *k; - - if (nil == f) - { - return nil; - } - [privateKeyLock lock]; - if (nil == p) - { - k = [privateKeyCache0 objectForKey: f]; - } - else - { - NSMutableDictionary *m; - - m = [privateKeyCache1 objectForKey: f]; - if (nil == m) - { - k = nil; - } - else - { - k = [m objectForKey: p]; - } - } - [k retain]; - [privateKeyLock unlock]; - - if (nil == k) - { - NSData *data; - int ret; - gnutls_datum_t datum; - - data = [NSData dataWithContentsOfFile: f]; - if (nil == data) - { - NSLog(@"Unable to read private key file '%@'", f); - return nil; - } - datum.data = (unsigned char*)[data bytes]; - datum.size = (unsigned int)[data length]; - - k = [self alloc]; - k->when = [NSDate new]; - k->path = [f copy]; - k->password = [p copy]; - gnutls_x509_privkey_init(&k->key); - - if (nil == k->password) - { - ret = gnutls_x509_privkey_import(k->key, &datum, - GNUTLS_X509_FMT_PEM); - } - else - { - ret = gnutls_x509_privkey_import_pkcs8(k->key, &datum, - GNUTLS_X509_FMT_PEM, [k->password UTF8String], 0); - } - if (ret < 0) - { - NSLog(@"Unable to parse private key file '%@': %s", - k->path, gnutls_strerror(ret)); - [k release]; - return nil; - } - [privateKeyLock lock]; - if (nil == k->password) - { - [privateKeyCache0 setObject: k forKey: k->path]; - } - else - { - NSMutableDictionary *m; - - m = [privateKeyCache1 objectForKey: f]; - if (nil == m) - { - m = [NSMutableDictionary new]; - [privateKeyCache1 setObject: m forKey: f]; - [m release]; - } - [m setObject: k forKey: p]; - } - [privateKeyLock unlock]; - } - - return [k autorelease]; -} - -- (void) dealloc -{ - if (nil != path) - { - DESTROY(when); - DESTROY(path); - DESTROY(password); - gnutls_x509_privkey_deinit(key); - } - [super dealloc]; -} - -- (gnutls_x509_privkey_t) key -{ - return key; -} -@end - - - -@interface GSTLS : GSStreamHandler +@interface GSTLSHandler : GSStreamHandler { @public gnutls_session_t session; @@ -978,7 +365,7 @@ static ssize_t GSTLSPull(gnutls_transport_ptr_t handle, void *buffer, size_t len) { ssize_t result; - GSTLS *tls = (GSTLS*)handle; + GSTLSHandler *tls = (GSTLSHandler*)handle; result = [[tls istream] _read: buffer maxLength: len]; if (result < 0) @@ -987,7 +374,7 @@ GSTLSPull(gnutls_transport_ptr_t handle, void *buffer, size_t len) if ([[tls istream] streamStatus] == NSStreamStatusError) { - e = [[[(GSTLS*)handle istream] streamError] code]; + e = [[[(GSTLSHandler*)handle istream] streamError] code]; } else { @@ -1009,7 +396,7 @@ static ssize_t GSTLSPush(gnutls_transport_ptr_t handle, const void *buffer, size_t len) { ssize_t result; - GSTLS *tls = (GSTLS*)handle; + GSTLSHandler *tls = (GSTLSHandler*)handle; result = [[tls ostream] _write: buffer maxLength: len]; if (result < 0) @@ -1034,11 +421,11 @@ GSTLSPush(gnutls_transport_ptr_t handle, const void *buffer, size_t len) return result; } -@implementation GSTLS +@implementation GSTLSHandler + (void) initialize { - [GNUTLSObject class]; + [GSTLSObject class]; } + (void) tryInput: (GSSocketInputStream*)i output: (GSSocketOutputStream*)o @@ -1061,9 +448,9 @@ GSTLSPush(gnutls_transport_ptr_t handle, const void *buffer, size_t len) if (tls != nil) { - GSTLS *h; + GSTLSHandler *h; - h = [[GSTLS alloc] initWithInput: i output: o]; + h = [[GSTLSHandler alloc] initWithInput: i output: o]; [i _setHandler: h]; [o _setHandler: h]; RELEASE(h); @@ -1286,7 +673,7 @@ GSTLSPush(gnutls_transport_ptr_t handle, const void *buffer, size_t len) - (void) stream: (NSStream*)stream handleEvent: (NSStreamEvent)event { NSDebugMLLog(@"NSStream", - @"GSTLS got %d on %p", event, stream); + @"GSTLSHandler got %d on %p", event, stream); if (handshake == YES) { @@ -1299,7 +686,7 @@ GSTLSPush(gnutls_transport_ptr_t handle, const void *buffer, size_t len) if (handshake == NO) { NSDebugMLLog(@"NSStream", - @"GSTLS completed on %p", stream); + @"GSTLSHandler completed on %p", stream); if ([istream streamStatus] == NSStreamStatusOpen) { [istream _resetEvents: NSStreamEventOpenCompleted]; @@ -1337,343 +724,13 @@ GSTLSPush(gnutls_transport_ptr_t handle, const void *buffer, size_t len) @end -#if !defined(__MINGW__) - -@interface GSTLSHandle : GSFileHandle -{ - GNUTLSDHParams *dhParams; -@public - gnutls_session_t session; - gnutls_certificate_credentials_t certcred; - BOOL active; // able to read/write - BOOL handshake; // performing handshake - BOOL outgoing; // handshake direction - BOOL setup; // have set certificate -} -- (void) sslDisconnect; -- (BOOL) sslHandshakeEstablished: (BOOL*)result outgoing: (BOOL)isOutgoing; -- (NSString*) sslSetOptions: (NSDictionary*)options; -@end - - -/* Callback to allow the TLS code to pull data from the remote system. - * If the operation fails, this sets the error number. - */ -static ssize_t -GSTLSHandlePull(gnutls_transport_ptr_t handle, void *buffer, size_t len) -{ - ssize_t result = 0; - GSTLSHandle *tls = (GSTLSHandle*)handle; - int descriptor = [tls fileDescriptor]; - - result = read(descriptor, buffer, len); - if (result < 0) - { -#if HAVE_GNUTLS_TRANSPORT_SET_ERRNO - gnutls_transport_set_errno (tls->session, errno); -#endif - } - return result; -} - -/* Callback to allow the TLS code to push data to the remote system. - * If the operation fails, this sets the error number. - */ -static ssize_t -GSTLSHandlePush(gnutls_transport_ptr_t handle, const void *buffer, size_t len) -{ - ssize_t result = 0; - GSTLSHandle *tls = (GSTLSHandle*)handle; - int descriptor = [tls fileDescriptor]; - - result = write(descriptor, buffer, len); - if (result < 0) - { -#if HAVE_GNUTLS_TRANSPORT_SET_ERRNO - gnutls_transport_set_errno (tls->session, errno); -#endif - } - return result; -} - -@implementation GSTLSHandle - -+ (void) initialize -{ - if (self == [GSTLSHandle class]) - { - [GSTLS class]; // Force initialisation of gnu tls stuff - } -} - -- (void) closeFile -{ - [self sslDisconnect]; - [super closeFile]; -} - -- (void) finalize -{ - [self sslDisconnect]; - if (YES == setup) - { - setup = NO; - gnutls_certificate_free_credentials (certcred); - } - [super finalize]; -} - -- (NSInteger) read: (void*)buf length: (NSUInteger)len -{ - if (YES == active) - { - return gnutls_record_recv (session, buf, len); - } - return [super read: buf length: len]; -} - -- (void) sslDisconnect -{ - if (YES == active || YES == handshake) - { - active = NO; - handshake = NO; - gnutls_bye (session, GNUTLS_SHUT_RDWR); - gnutls_db_remove_session (session); - gnutls_deinit (session); - } - DESTROY(dhParams); -} - -- (BOOL) sslHandshakeEstablished: (BOOL*)result outgoing: (BOOL)isOutgoing -{ - int ret; - - NSAssert(0 != result, NSInvalidArgumentException); - - if (YES == active) - { - return YES; /* Already connected. */ - } - - if (YES == isStandardFile) - { - NSLog(@"Attempt to perform ssl handshake with a standard file"); - return NO; - } - - /* Set the handshake direction so we know how to set up the connection. - */ - outgoing = isOutgoing; - - if (NO == setup) - { - [self sslSetCertificate: nil privateKey: nil PEMpasswd: nil]; - } - - if (NO == handshake) - { - handshake = YES; // Set flag to say a handshake is in progress - - /* Now initialise session and set it up - */ - if (YES == outgoing) - { - gnutls_init (&session, GNUTLS_CLIENT); - } - else - { - gnutls_init (&session, GNUTLS_SERVER); - - /* We don't request any certificate from the client. - * If we did we would need to verify it. - */ - gnutls_certificate_server_set_request (session, GNUTLS_CERT_IGNORE); - - /* FIXME ... need to set up DH information and key/certificate. */ - } - gnutls_set_default_priority (session); - -#if GNUTLS_VERSION_NUMBER < 0x020C00 - { - const int proto_prio[2] = { - GNUTLS_SSL3, - 0 }; - gnutls_protocol_set_priority (session, proto_prio); - } -#else - gnutls_priority_set_direct(session, - "NORMAL:-VERS-TLS-ALL:+VERS-SSL3.0", NULL); -#endif - -/* - { - const int kx_prio[] = { - GNUTLS_KX_RSA, - GNUTLS_KX_RSA_EXPORT, - GNUTLS_KX_DHE_RSA, - GNUTLS_KX_DHE_DSS, - GNUTLS_KX_ANON_DH, - 0 }; - gnutls_kx_set_priority (session, kx_prio); - gnutls_credentials_set (session, GNUTLS_CRD_ANON, anoncred); - } - */ - - - /* Set certificate credentials for this session. - */ - gnutls_credentials_set (session, GNUTLS_CRD_CERTIFICATE, certcred); - - /* Set transport layer to use our low level stream code. - */ -#if GNUTLS_VERSION_NUMBER < 0x020C00 - gnutls_transport_set_lowat (session, 0); -#endif - gnutls_transport_set_pull_function (session, GSTLSHandlePull); - gnutls_transport_set_push_function (session, GSTLSHandlePush); - gnutls_transport_set_ptr (session, (gnutls_transport_ptr_t)self); - - [self setNonBlocking: YES]; - } - - if (outgoing != isOutgoing) - { - [NSException raise: NSInvalidArgumentException - format: @"Attempt to change direction of TLS handshake"]; - } - - ret = gnutls_handshake (session); - if (ret < 0) - { - if (gnutls_error_is_fatal(ret)) - { - NSLog(@"unable to make SSL connection to %@:%@ - %s", - address, service, gnutls_strerror(ret)); - *result = NO; - } - else - { - if (GSDebugSet(@"NSStream") == YES) - { - gnutls_perror(ret); - } - return NO; // Non-fatal error needs a retry. - } - } - else - { - handshake = NO; // Handshake is now complete. - active = YES; // The TLS session is now active. - *result = active; - } - return YES; -} - -- (NSString*) sslSetOptions: (NSDictionary*)options -{ - if (isStandardFile == YES) - { - return @"Attempt to set ssl options for a standard file"; - } - - if (NO == setup) - { - NSString *certFile; - NSString *privateKey; - NSString *PEMpasswd; - GNUTLSPrivateKey *key = nil; - GNUTLSCertificateList *list = nil; - int ret; - - certFile = [options objectForKey: GSTLSCertificateFileKey]; - privateKey = [options objectForKey: GSTLSPrivateKeyFileKey]; - PEMpasswd = [options objectForKey: GSTLSPrivateKeyPasswordKey]; - - if (nil != privateKey) - { - key = [GNUTLSPrivateKey keyFromFile: privateKey - withPassword: PEMpasswd]; - if (nil == key) - { - return @"Unable to load key file"; - } - } - - if (nil != certFile) - { - list = [GNUTLSCertificateList listFromFile: certFile]; - if (nil == list) - { - return @"Unable to load certificate file"; - } - } - - setup = YES; - - /* Configure this session to support certificate based - * operation. - */ - gnutls_certificate_allocate_credentials (&certcred); - - /* FIXME ... should get the trusted authority certificates - * from somewhere sensible to validate the remote end! - */ - gnutls_certificate_set_x509_trust_file - (certcred, "ca.pem", GNUTLS_X509_FMT_PEM); - -/* - gnutls_certificate_set_x509_crl_file (x509_cred, - [certFile UTF8String], GNUTLS_X509_FMT_PEM); -*/ - - if (nil != list) - { - ret = gnutls_certificate_set_x509_key (certcred, - [list certificateList], [list count], [key key]); - if (ret < 0) - { - return [NSString stringWithFormat: - @"Unable to set certificate for session: %s", - gnutls_strerror(ret)]; - } - else if (NO == outgoing) - { - dhParams = [[GNUTLSDHParams current] retain]; - gnutls_certificate_set_dh_params (certcred, [dhParams params]); - } - } - -#if 0 - if (nil != cipherList) - { - SSL_CTX_set_cipher_list(ctx, [cipherList UTF8String]); - } -#endif - - } - return nil; -} - -- (NSInteger) write: (const void*)buf length: (NSUInteger)len -{ - if (YES == active) - { - return gnutls_record_send (session, buf, len); - } - return [super write: buf length: len]; -} - -@end -#endif /* MINGW */ - #else /* HAVE_GNUTLS */ /* GNUTLS not available ... */ -@interface GSTLS : GSStreamHandler +@interface GSTLSHandler : GSStreamHandler @end -@implementation GSTLS +@implementation GSTLSHandler + (void) tryInput: (GSSocketInputStream*)i output: (GSSocketOutputStream*)o { NSString *tls; @@ -1804,7 +861,7 @@ static NSString * const GSSOCKSAckConn = @"GSSOCKSAckConn"; [is _setHandler: nil]; [os _setHandler: nil]; - [GSTLS tryInput: is output: os]; + [GSTLSHandler tryInput: is output: os]; if ([is streamStatus] == NSStreamStatusOpen) { [is _resetEvents: NSStreamEventOpenCompleted]; @@ -2640,7 +1697,7 @@ setNonBlocking(SOCKET fd) if (_handler == nil) { - [GSTLS tryInput: self output: _sibling]; + [GSTLSHandler tryInput: self output: _sibling]; } result = connect([self _sock], &_address.s, GSPrivateSockaddrLength(&_address.s)); @@ -3111,7 +2168,7 @@ setNonBlocking(SOCKET fd) if (_handler == nil) { - [GSTLS tryInput: _sibling output: self]; + [GSTLSHandler tryInput: _sibling output: self]; } result = connect([self _sock], &_address.s, diff --git a/Source/GSTLS.h b/Source/GSTLS.h new file mode 100644 index 000000000..4dec47b69 --- /dev/null +++ b/Source/GSTLS.h @@ -0,0 +1,109 @@ +/** Interface for GSTLS classes for GNUStep + Copyright (C) 2012 Free Software Foundation, Inc. + + Written by: Richard Frith-Macdonald + Date: 2101 + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02111 USA. + + */ + +#import "Foundation/NSObject.h" + +@class NSDate; +@class NSDictionary; +@class NSHost; +@class NSString; + +#if defined(HAVE_GNUTLS) +/* Temporarily redefine 'id' in case the headers use the objc reserved word. + */ +#define id GNUTLSID +/* gcrypt uses __attribute__((deprecated)) to mark structure members that are + * private. This causes compiler warnings just from using the header. Turn + * them off... + */ +#define _GCRYPT_IN_LIBGCRYPT +#include +#include +#include +#undef id + +@protocol GSTLSOwner + +/* Returns the optins dictionary set for this session. + */ +- (NSDictionary*) options; + +/* Returns the host this session should be connected to. + */ +- (NSHost*) remoteHost; + +@end + +/* This class is used to ensure that the GNUTLS system is initialised + * and thread-safe. It also provides session verification. + */ +@interface GSTLSObject : NSObject + +/* Performs verification for the supplied session and returns a GNUTLS + * error code in the event of verification failure or zero on success.
+ * The ponter set in the session with gnutls_session_set_ptr() must be + * the owner of the session and must conform to the GSTLSOwner protocol. + */ ++ (int) verify: (gnutls_session_t)session; +@end + +/* This class provides the current DH paraqmeters for server negotiation. + */ +@interface GSTLSDHParams : GSTLSObject +{ + gnutls_dh_params_t params; +} ++ (GSTLSDHParams*) current; +- (gnutls_dh_params_t) params; +@end + +/* Manage certificate lists (for servers and clients) and also provide + * DH params. + */ +@interface GSTLSCertificateList : GSTLSObject +{ + NSDate *when; + NSString *path; + gnutls_x509_crt_t *crts; + unsigned int count; +} ++ (GSTLSCertificateList*) listFromFile: (NSString*)f; +- (gnutls_x509_crt_t*) certificateList; +- (unsigned int) count; +@end + +/* This encapsulates private keys used to unlock certificates + */ +@interface GSTLSPrivateKey : GSTLSObject +{ + NSDate *when; + NSString *path; + NSString *password; + gnutls_x509_privkey_t key; +} ++ (GSTLSPrivateKey*) keyFromFile: (NSString*)f withPassword: (NSString*)p; +- (gnutls_x509_privkey_t) key; +@end + +#endif + diff --git a/Source/GSTLS.m b/Source/GSTLS.m new file mode 100644 index 000000000..d5d0b4adc --- /dev/null +++ b/Source/GSTLS.m @@ -0,0 +1,616 @@ +/** Implementation for GSTLS classes for GNUStep + Copyright (C) 2012 Free Software Foundation, Inc. + + Written by: Richard Frith-Macdonald + Date: 2101 + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02111 USA. + + */ + +#import "common.h" + +#import "Foundation/NSArray.h" +#import "Foundation/NSData.h" +#import "Foundation/NSDictionary.h" +#import "Foundation/NSEnumerator.h" +#import "Foundation/NSHost.h" +#import "Foundation/NSException.h" +#import "Foundation/NSLock.h" +#import "Foundation/NSNotification.h" +#import "Foundation/NSUserDefaults.h" + +#import "GSTLS.h" + +#import "GSPrivate.h" + +NSString * const GSTLSCertificateFile = @"GSTLSCertificateFile"; +NSString * const GSTLSPrivateKeyFile = @"GSTLSPrivateKeyFile"; +NSString * const GSTLSPrivateKeyPassword = @"GSTLSPrivateKeyPassword"; + +#if defined(HAVE_GNUTLS) + +/* Set up locking callbacks for gcrypt so that it will be thread-safe. + */ +static int gcry_mutex_init (void **priv) +{ + NSLock *lock = [NSLock new]; + *priv = (void*)lock; + return 0; +} +static int gcry_mutex_destroy (void **lock) +{ + [((NSLock*)*lock) release]; + return 0; +} +static int gcry_mutex_lock (void **lock) +{ + [((NSLock*)*lock) lock]; + return 0; +} +static int gcry_mutex_unlock (void **lock) +{ + [((NSLock*)*lock) unlock]; + return 0; +} +static struct gcry_thread_cbs gcry_threads_other = { + GCRY_THREAD_OPTION_DEFAULT, + NULL, + gcry_mutex_init, + gcry_mutex_destroy, + gcry_mutex_lock, + gcry_mutex_unlock +}; + +static void +GSTLSLog(int level, const char *msg) +{ + NSLog(@"%s", msg); +} + +static NSString *cipherList = nil; + +static gnutls_anon_client_credentials_t anoncred; + +/* This class is used to ensure that the GNUTLS system is initialised + * and thread-safe. + */ +@implementation GSTLSObject + ++ (void) _defaultsChanged: (NSNotification*)n +{ + cipherList + = [[NSUserDefaults standardUserDefaults] stringForKey: @"GSCipherList"]; +} + ++ (void) initialize +{ + if ([GSTLSObject class] == self) + { + static BOOL beenHere = NO; + + if (beenHere == NO) + { + NSUserDefaults *defs; + + beenHere = YES; + + defs = [NSUserDefaults standardUserDefaults]; + cipherList = [defs stringForKey: @"GSCipherList"]; + [[NSNotificationCenter defaultCenter] + addObserver: self + selector: @selector(_defaultsChanged:) + name: NSUserDefaultsDidChangeNotification + object: nil]; + + /* Make gcrypt thread-safe + */ + gcry_control (GCRYCTL_SET_THREAD_CBS, &gcry_threads_other); + + /* Initialise gnutls + */ + gnutls_global_init (); + + /* Allocate global credential information for anonymous tls + */ + gnutls_anon_allocate_client_credentials (&anoncred); + + /* Enable gnutls logging via NSLog + */ + gnutls_global_set_log_function (GSTLSLog); + } + } +} + ++ (int) verify: (gnutls_session_t)session +{ + unsigned int status; + const gnutls_datum_t *cert_list; + unsigned int cert_list_size; + int ret; + gnutls_x509_crt_t cert; + id owner; + NSHost *host; + NSDictionary *options; + + /* read hostname */ + owner = (id)gnutls_session_get_ptr(session); + host = [owner remoteHost]; + options = [owner options]; + + /* This verification function uses the trusted CAs in the credentials + * structure. So you must have installed one or more CA certificates. + */ + ret = gnutls_certificate_verify_peers2 (session, &status); + if (ret < 0) + { + NSLog(@"Error"); + return GNUTLS_E_CERTIFICATE_ERROR; + } + + if (status & GNUTLS_CERT_SIGNER_NOT_FOUND) + NSLog(@"The certificate hasn't got a known issuer."); + + if (status & GNUTLS_CERT_REVOKED) + NSLog(@"The certificate has been revoked."); + +/* + if (status & GNUTLS_CERT_EXPIRED) + NSLog(@"The certificate has expired"); + + if (status & GNUTLS_CERT_NOT_ACTIVATED) + NSLog(@"The certificate is not yet activated"); +*/ + + if (status & GNUTLS_CERT_INVALID) + { + NSLog(@"The certificate is not trusted."); + return GNUTLS_E_CERTIFICATE_ERROR; + } + + /* Up to here the process is the same for X.509 certificates and + * OpenPGP keys. From now on X.509 certificates are assumed. This can + * be easily extended to work with openpgp keys as well. + */ + if (gnutls_certificate_type_get (session) != GNUTLS_CRT_X509) + return GNUTLS_E_CERTIFICATE_ERROR; + + if (gnutls_x509_crt_init (&cert) < 0) + { + NSLog(@"error in initialization"); + return GNUTLS_E_CERTIFICATE_ERROR; + } + + cert_list = gnutls_certificate_get_peers (session, &cert_list_size); + if (cert_list == NULL) + { + NSLog(@"No certificate was found!"); + return GNUTLS_E_CERTIFICATE_ERROR; + } + + if (gnutls_x509_crt_import (cert, &cert_list[0], GNUTLS_X509_FMT_DER) < 0) + { + NSLog(@"error parsing certificate"); + return GNUTLS_E_CERTIFICATE_ERROR; + } + + if (nil != host) + { + NSEnumerator *enumerator = [[host names] objectEnumerator]; + BOOL found = NO; + NSString *name; + + while (nil != (name = [enumerator nextObject])) + { + if (0 == gnutls_x509_crt_check_hostname(cert, [name UTF8String])) + { + found = YES; + break; + } + } + if (NO == found) + { + NSLog(@"The certificate's owner does not match host '%@'", host); + gnutls_x509_crt_deinit (cert); + return GNUTLS_E_CERTIFICATE_ERROR; + } + } + + gnutls_x509_crt_deinit (cert); + + return 0; // Verified +} + +@end + +@implementation GSTLSDHParams +static NSLock *paramsLock = nil; +static NSDate *when = nil; +static GSTLSDHParams *current = nil; + ++ (GSTLSDHParams*) current +{ + GSTLSDHParams *p; + + [paramsLock lock]; + if (nil == current) + { + current = [self new]; + } + p = [current retain]; + [paramsLock unlock]; + return [current autorelease]; +} + ++ (void) housekeeping: (NSNotification*)n +{ + NSDate *now; + + now = [NSDate date]; + [paramsLock lock]; + /* Regenerate DH params once per day. + */ + if ([now timeIntervalSinceDate: when] > 24 * 60 * 60) + { + ASSIGN(when, [NSDate date]); + ASSIGN(current, [self new]); + } + [paramsLock unlock]; +} + ++ (void) initialize +{ + if (nil == paramsLock) + { + paramsLock = [NSLock new]; + when = [NSDate new]; + current = [self new]; + [[NSNotificationCenter defaultCenter] addObserver: self + selector: @selector(housekeeping:) + name: @"GSHousekeeping" object: nil]; + } +} + +- (void) dealloc +{ + gnutls_dh_params_deinit (params); + [super dealloc]; +} + +- (id) init +{ + /* Generate Diffie-Hellman parameters - for use with DHE + * kx algorithms. When short bit length is used, it might + * be wise to regenerate parameters often. + */ + gnutls_dh_params_init (¶ms); + gnutls_dh_params_generate2 (params, 2048); + return self; +} + +- (gnutls_dh_params_t) params +{ + return params; +} +@end + +@implementation GSTLSCertificateList + +static NSLock *certificateListLock = nil; +static NSMutableDictionary *certificateListCache = nil; + +/* Method to purge older lists from cache. + */ ++ (void) housekeeping: (NSNotification*)n +{ + NSEnumerator *enumerator; + NSString *key; + NSDate *now; + + now = [NSDate date]; + [certificateListLock lock]; + enumerator = [[certificateListCache allKeys] objectEnumerator]; + while (nil != (key = [enumerator nextObject])) + { + GSTLSCertificateList *list; + + list = [certificateListCache objectForKey: key]; + + if ([now timeIntervalSinceDate: list->when] > 300.0) + { + [certificateListCache removeObjectForKey: key]; + } + } + [certificateListLock unlock]; +} + ++ (void) initialize +{ + if (nil == certificateListLock) + { + certificateListLock = [NSLock new]; + certificateListCache = [NSMutableDictionary new]; + [[NSNotificationCenter defaultCenter] addObserver: self + selector: @selector(housekeeping:) + name: @"GSHousekeeping" object: nil]; + } +} + ++ (GSTLSCertificateList*) listFromFile: (NSString*)f +{ + GSTLSCertificateList *l; + + if (nil == f) + { + return nil; + } + [certificateListLock lock]; + l = [[certificateListCache objectForKey: f] retain]; + [certificateListLock unlock]; + + if (nil == l) + { + NSData *data; + int ret; + gnutls_datum_t datum; + unsigned int count = 100; + gnutls_x509_crt_t crts[count]; + + data = [NSData dataWithContentsOfFile: f]; + if (nil == data) + { + NSLog(@"Unable to read certificate file '%@'", f); + return nil; + } + datum.data = (unsigned char*)[data bytes]; + datum.size = (unsigned int)[data length]; + + l = [self alloc]; + l->when = [NSDate new]; + l->path = [f copy]; + ret = gnutls_x509_crt_list_import(crts, &count, &datum, + GNUTLS_X509_FMT_PEM, +// GNUTLS_X509_CRT_LIST_FAIL_IF_UNSORTED | + GNUTLS_X509_CRT_LIST_IMPORT_FAIL_IF_EXCEED); + if (ret < 0) + { + NSLog(@"Unable to parse certificate file '%@': %s", + l->path, gnutls_strerror(ret)); + [l release]; + return nil; + } + l->crts = malloc(sizeof(gnutls_x509_crt_t) * count); + memcpy(l->crts, crts, sizeof(gnutls_x509_crt_t) * count); + l->count = count; + + [certificateListLock lock]; + [certificateListCache setObject: l forKey: l->path]; + [certificateListLock unlock]; + } + + return [l autorelease]; +} + +- (gnutls_x509_crt_t*) certificateList +{ + return crts; +} + +- (unsigned int) count +{ + return count; +} + +- (void) dealloc +{ + if (nil != path) + { + DESTROY(when); + DESTROY(path); + if (count > 0) + { + while (count-- > 0) + { + gnutls_x509_crt_deinit(crts[count]); + } + free(crts); + } + } + [super dealloc]; +} + +@end + + +@implementation GSTLSPrivateKey + +static NSLock *privateKeyLock = nil; +static NSMutableDictionary *privateKeyCache0 = nil; +static NSMutableDictionary *privateKeyCache1 = nil; + +/* Method to purge older keys from cache. + */ ++ (void) housekeeping: (NSNotification*)n +{ + NSEnumerator *outer; + NSString *oKey; + NSDate *now; + + now = [NSDate date]; + [privateKeyLock lock]; + outer = [[privateKeyCache0 allKeys] objectEnumerator]; + while (nil != (oKey = [outer nextObject])) + { + GSTLSPrivateKey *key; + + key = [privateKeyCache0 objectForKey: oKey]; + if ([now timeIntervalSinceDate: key->when] > 300.0) + { + [privateKeyCache0 removeObjectForKey: oKey]; + } + } + outer = [[privateKeyCache1 allKeys] objectEnumerator]; + while (nil != (oKey = [outer nextObject])) + { + NSMutableDictionary *m; + NSEnumerator *inner; + NSString *iKey; + + m = [privateKeyCache1 objectForKey: oKey]; + inner = [[m allKeys] objectEnumerator]; + while (nil != (iKey = [inner nextObject])) + { + GSTLSPrivateKey *key = [m objectForKey: iKey]; + + if ([now timeIntervalSinceDate: key->when] > 300.0) + { + [m removeObjectForKey: iKey]; + if (0 == [m count]) + { + [privateKeyCache1 removeObjectForKey: oKey]; + } + } + } + } + [privateKeyLock unlock]; +} + ++ (void) initialize +{ + if (nil == privateKeyLock) + { + privateKeyLock = [NSLock new]; + privateKeyCache0 = [NSMutableDictionary new]; + privateKeyCache1 = [NSMutableDictionary new]; + + [[NSNotificationCenter defaultCenter] addObserver: self + selector: @selector(housekeeping:) + name: @"GSHousekeeping" object: nil]; + } +} + ++ (GSTLSPrivateKey*) keyFromFile: (NSString*)f withPassword: (NSString*)p +{ + GSTLSPrivateKey *k; + + if (nil == f) + { + return nil; + } + [privateKeyLock lock]; + if (nil == p) + { + k = [privateKeyCache0 objectForKey: f]; + } + else + { + NSMutableDictionary *m; + + m = [privateKeyCache1 objectForKey: f]; + if (nil == m) + { + k = nil; + } + else + { + k = [m objectForKey: p]; + } + } + [k retain]; + [privateKeyLock unlock]; + + if (nil == k) + { + NSData *data; + int ret; + gnutls_datum_t datum; + + data = [NSData dataWithContentsOfFile: f]; + if (nil == data) + { + NSLog(@"Unable to read private key file '%@'", f); + return nil; + } + datum.data = (unsigned char*)[data bytes]; + datum.size = (unsigned int)[data length]; + + k = [self alloc]; + k->when = [NSDate new]; + k->path = [f copy]; + k->password = [p copy]; + gnutls_x509_privkey_init(&k->key); + + if (nil == k->password) + { + ret = gnutls_x509_privkey_import(k->key, &datum, + GNUTLS_X509_FMT_PEM); + } + else + { + ret = gnutls_x509_privkey_import_pkcs8(k->key, &datum, + GNUTLS_X509_FMT_PEM, [k->password UTF8String], 0); + } + if (ret < 0) + { + NSLog(@"Unable to parse private key file '%@': %s", + k->path, gnutls_strerror(ret)); + [k release]; + return nil; + } + [privateKeyLock lock]; + if (nil == k->password) + { + [privateKeyCache0 setObject: k forKey: k->path]; + } + else + { + NSMutableDictionary *m; + + m = [privateKeyCache1 objectForKey: f]; + if (nil == m) + { + m = [NSMutableDictionary new]; + [privateKeyCache1 setObject: m forKey: f]; + [m release]; + } + [m setObject: k forKey: p]; + } + [privateKeyLock unlock]; + } + + return [k autorelease]; +} + +- (void) dealloc +{ + if (nil != path) + { + DESTROY(when); + DESTROY(path); + DESTROY(password); + gnutls_x509_privkey_deinit(key); + } + [super dealloc]; +} + +- (gnutls_x509_privkey_t) key +{ + return key; +} +@end + +#endif + diff --git a/Source/NSFileHandle.m b/Source/NSFileHandle.m index ab34fe523..4736c85f9 100644 --- a/Source/NSFileHandle.m +++ b/Source/NSFileHandle.m @@ -29,10 +29,13 @@ #define EXPOSE_NSFileHandle_IVARS 1 #import "Foundation/NSData.h" #import "Foundation/NSFileHandle.h" +#import "Foundation/NSException.h" #import "Foundation/NSPathUtilities.h" #import "GNUstepBase/NSObject+GNUstepBase.h" #import "GSPrivate.h" #import "GSNetwork.h" + +#define EXPOSE_GSFileHandle_IVARS 1 #import "GSFileHandle.h" // GNUstep Notification names @@ -844,15 +847,15 @@ NSString * const NSFileHandleOperationException opts = [NSMutableDictionary dictionaryWithCapacity: 3]; if (nil != certFile) { - [opts setObject: certFile forKey: GSTLSCertificateFileKey]; + [opts setObject: certFile forKey: GSTLSCertificateFile]; } if (nil != privateKey) { - [opts setObject: privateKey forKey: GSTLSPrivateKeyFileKey]; + [opts setObject: privateKey forKey: GSTLSPrivateKeyFile]; } if (nil != PEMpasswd) { - [opts setObject: PEMpasswd forKey: GSTLSPrivateKeyPasswordKey]; + [opts setObject: PEMpasswd forKey: GSTLSPrivateKeyPassword]; } err = [self sslSetOptions: opts]; if (nil != err) @@ -868,3 +871,351 @@ NSString * const NSFileHandleOperationException @end +#if defined(HAVE_GNUTLS) + +#import "GSTLS.h" + +#if !defined(__MINGW__) + +@interface GSTLSHandle : GSFileHandle +{ + GSTLSDHParams *dhParams; +@public + gnutls_session_t session; + gnutls_certificate_credentials_t certcred; + BOOL active; // able to read/write + BOOL handshake; // performing handshake + BOOL outgoing; // handshake direction + BOOL setup; // have set certificate +} +- (void) sslDisconnect; +- (BOOL) sslHandshakeEstablished: (BOOL*)result outgoing: (BOOL)isOutgoing; +- (NSString*) sslSetOptions: (NSDictionary*)options; +@end + + +/* Callback to allow the TLS code to pull data from the remote system. + * If the operation fails, this sets the error number. + */ +static ssize_t +GSTLSHandlePull(gnutls_transport_ptr_t handle, void *buffer, size_t len) +{ + ssize_t result = 0; + GSTLSHandle *tls = (GSTLSHandle*)handle; + int descriptor = [tls fileDescriptor]; + + result = read(descriptor, buffer, len); + if (result < 0) + { +#if HAVE_GNUTLS_TRANSPORT_SET_ERRNO + gnutls_transport_set_errno (tls->session, errno); +#endif + } + return result; +} + +/* Callback to allow the TLS code to push data to the remote system. + * If the operation fails, this sets the error number. + */ +static ssize_t +GSTLSHandlePush(gnutls_transport_ptr_t handle, const void *buffer, size_t len) +{ + ssize_t result = 0; + GSTLSHandle *tls = (GSTLSHandle*)handle; + int descriptor = [tls fileDescriptor]; + + result = write(descriptor, buffer, len); + if (result < 0) + { +#if HAVE_GNUTLS_TRANSPORT_SET_ERRNO + gnutls_transport_set_errno (tls->session, errno); +#endif + } + return result; +} + +@implementation GSTLSHandle + ++ (void) initialize +{ + if (self == [GSTLSHandle class]) + { + [GSTLSObject class]; // Force initialisation of gnu tls stuff + } +} + +- (void) closeFile +{ + [self sslDisconnect]; + [super closeFile]; +} + +- (void) finalize +{ + [self sslDisconnect]; + if (YES == setup) + { + setup = NO; + gnutls_certificate_free_credentials (certcred); + } + [super finalize]; +} + +- (NSInteger) read: (void*)buf length: (NSUInteger)len +{ + if (YES == active) + { + return gnutls_record_recv (session, buf, len); + } + return [super read: buf length: len]; +} + +- (void) sslDisconnect +{ + if (YES == active || YES == handshake) + { + active = NO; + handshake = NO; + gnutls_bye (session, GNUTLS_SHUT_RDWR); + gnutls_db_remove_session (session); + gnutls_deinit (session); + } + DESTROY(dhParams); +} + +- (BOOL) sslHandshakeEstablished: (BOOL*)result outgoing: (BOOL)isOutgoing +{ + int ret; + + NSAssert(0 != result, NSInvalidArgumentException); + + if (YES == active) + { + return YES; /* Already connected. */ + } + + if (YES == isStandardFile) + { + NSLog(@"Attempt to perform ssl handshake with a standard file"); + return NO; + } + + /* Set the handshake direction so we know how to set up the connection. + */ + outgoing = isOutgoing; + + if (NO == setup) + { + [self sslSetCertificate: nil privateKey: nil PEMpasswd: nil]; + } + + if (NO == handshake) + { + handshake = YES; // Set flag to say a handshake is in progress + + /* Now initialise session and set it up + */ + if (YES == outgoing) + { + gnutls_init (&session, GNUTLS_CLIENT); + } + else + { + gnutls_init (&session, GNUTLS_SERVER); + + /* We don't request any certificate from the client. + * If we did we would need to verify it. + */ + gnutls_certificate_server_set_request (session, GNUTLS_CERT_IGNORE); + + /* FIXME ... need to set up DH information and key/certificate. */ + } + gnutls_set_default_priority (session); + +#if GNUTLS_VERSION_NUMBER < 0x020C00 + { + const int proto_prio[2] = { + GNUTLS_SSL3, + 0 }; + gnutls_protocol_set_priority (session, proto_prio); + } +#else + gnutls_priority_set_direct(session, + "NORMAL:-VERS-TLS-ALL:+VERS-SSL3.0", NULL); +#endif + +/* + { + const int kx_prio[] = { + GNUTLS_KX_RSA, + GNUTLS_KX_RSA_EXPORT, + GNUTLS_KX_DHE_RSA, + GNUTLS_KX_DHE_DSS, + GNUTLS_KX_ANON_DH, + 0 }; + gnutls_kx_set_priority (session, kx_prio); + gnutls_credentials_set (session, GNUTLS_CRD_ANON, anoncred); + } + */ + + + /* Set certificate credentials for this session. + */ + gnutls_credentials_set (session, GNUTLS_CRD_CERTIFICATE, certcred); + + /* Set transport layer to use our low level stream code. + */ +#if GNUTLS_VERSION_NUMBER < 0x020C00 + gnutls_transport_set_lowat (session, 0); +#endif + gnutls_transport_set_pull_function (session, GSTLSHandlePull); + gnutls_transport_set_push_function (session, GSTLSHandlePush); + gnutls_transport_set_ptr (session, (gnutls_transport_ptr_t)self); + + [self setNonBlocking: YES]; + } + + if (outgoing != isOutgoing) + { + [NSException raise: NSInvalidArgumentException + format: @"Attempt to change direction of TLS handshake"]; + } + + ret = gnutls_handshake (session); + if (ret < 0) + { + if (gnutls_error_is_fatal(ret)) + { + NSLog(@"unable to make SSL connection to %@:%@ - %s", + address, service, gnutls_strerror(ret)); + *result = NO; + } + else + { + if (GSDebugSet(@"NSStream") == YES) + { + gnutls_perror(ret); + } + return NO; // Non-fatal error needs a retry. + } + } + else + { + handshake = NO; // Handshake is now complete. + active = YES; // The TLS session is now active. +{ + ret = [GSTLSObject verify: session]; + if (ret < 0) + { + NSLog(@"unable to verify SSL connection to %@:%@ - %s", + address, service, gnutls_strerror(ret)); + // active = NO; + } +} + *result = active; + } + return YES; +} + +- (NSString*) sslSetOptions: (NSDictionary*)options +{ + if (isStandardFile == YES) + { + return @"Attempt to set ssl options for a standard file"; + } + + if (NO == setup) + { + NSString *certFile; + NSString *privateKey; + NSString *PEMpasswd; + GSTLSPrivateKey *key = nil; + GSTLSCertificateList *list = nil; + int ret; + + certFile = [options objectForKey: GSTLSCertificateFile]; + privateKey = [options objectForKey: GSTLSPrivateKeyFile]; + PEMpasswd = [options objectForKey: GSTLSPrivateKeyPassword]; + + if (nil != privateKey) + { + key = [GSTLSPrivateKey keyFromFile: privateKey + withPassword: PEMpasswd]; + if (nil == key) + { + return @"Unable to load key file"; + } + } + + if (nil != certFile) + { + list = [GSTLSCertificateList listFromFile: certFile]; + if (nil == list) + { + return @"Unable to load certificate file"; + } + } + + setup = YES; + + /* Configure this session to support certificate based + * operation. + */ + gnutls_certificate_allocate_credentials (&certcred); + + /* FIXME ... should get the trusted authority certificates + * from somewhere sensible to validate the remote end! + */ + gnutls_certificate_set_x509_trust_file + (certcred, "ca.pem", GNUTLS_X509_FMT_PEM); + +/* + gnutls_certificate_set_x509_crl_file + (certcred, "crl.pem", GNUTLS_X509_FMT_PEM); + gnutls_certificate_set_verify_function (certcred, + _verify_certificate_callback); + +*/ + + if (nil != list) + { + ret = gnutls_certificate_set_x509_key (certcred, + [list certificateList], [list count], [key key]); + if (ret < 0) + { + return [NSString stringWithFormat: + @"Unable to set certificate for session: %s", + gnutls_strerror(ret)]; + } + else if (NO == outgoing) + { + dhParams = [[GSTLSDHParams current] retain]; + gnutls_certificate_set_dh_params (certcred, [dhParams params]); + } + } + +#if 0 + if (nil != cipherList) + { + SSL_CTX_set_cipher_list(ctx, [cipherList UTF8String]); + } +#endif + + } + return nil; +} + +- (NSInteger) write: (const void*)buf length: (NSUInteger)len +{ + if (YES == active) + { + return gnutls_record_send (session, buf, len); + } + return [super write: buf length: len]; +} + +@end +#endif /* MINGW */ + +#endif /* HAVE_GNUTLS */ +