restructure for maintainability

git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/base/trunk@35598 72102866-910b-0410-8b05-ffd578937521
This commit is contained in:
rfm 2012-09-24 09:07:55 +00:00
parent c5c25f4c8d
commit 10c5815622
8 changed files with 1118 additions and 973 deletions

View file

@ -1,3 +1,14 @@
2012-09-25 Richard Frith-Macdonald <rfm@gnu.org>
* 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 <rfm@gnu.org>
* Source/NSBundle.m: Fix error spotted by Fred.

View file

@ -275,19 +275,19 @@ GS_EXPORT NSString * const NSFileHandleOperationException;
* be set.<br />>
* Expects key value pairs with the follwiing names/meanings:
* <deflist>
* <term>GSTLSCertificateFileKey</term>
* <term>GSTLSCertificateFile</term>
* <desc>The path to a PEM encoded certificate used to identify this end
* of the connection. This option <em>must</em> be set for handing an
* incoming connection, but is optional for outgoing connections.<br />
* This must be used in conjunction with GSTLSPrivateKeyFileKey.
* This must be used in conjunction with GSTLSPrivateKeyFile.
* </desc>
* <term>GSTLSPrivateKeyFileKey</term>
* <term>GSTLSPrivateKeyFile</term>
* <desc>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.
* </desc>
* <term>GSTLSPrivateKeyPasswordKey</term>
* <term>GSTLSPrivateKeyPassword</term>
* <desc>A string to be used as the password to decrypt a key which was
* specified using GSTLSKeyPassword.
* </desc>
@ -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.

View file

@ -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)
{

View file

@ -171,6 +171,7 @@ GSSocketStream.m \
GSStream.m \
GSString.m \
GSTimSort.m \
GSTLS.m \
GSValue.m \
NSAffineTransform.m \
NSArchiver.m \

File diff suppressed because it is too large Load diff

109
Source/GSTLS.h Normal file
View file

@ -0,0 +1,109 @@
/** Interface for GSTLS classes for GNUStep
Copyright (C) 2012 Free Software Foundation, Inc.
Written by: Richard Frith-Macdonald <rfm@gnu.org>
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 <gnutls/gnutls.h>
#include <gnutls/x509.h>
#include <gcrypt.h>
#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.<br />
* 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

616
Source/GSTLS.m Normal file
View file

@ -0,0 +1,616 @@
/** Implementation for GSTLS classes for GNUStep
Copyright (C) 2012 Free Software Foundation, Inc.
Written by: Richard Frith-Macdonald <rfm@gnu.org>
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 <GSTLSOwner> owner;
NSHost *host;
NSDictionary *options;
/* read hostname */
owner = (id<GSTLSOwner>)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 (&params);
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

View file

@ -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 */