more tls reorganisation

git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/base/trunk@35605 72102866-910b-0410-8b05-ffd578937521
This commit is contained in:
Richard Frith-MacDonald 2012-09-26 15:23:24 +00:00
parent bca4db00c9
commit db9c84ff4e
6 changed files with 921 additions and 482 deletions

View file

@ -2,6 +2,12 @@
* Source/NSUserDefaults.m: Fix error synchronising from changes in
database ... was not sending the notification to say we had updated.
* Headers/Foundation/NSFileHandle.h:
* Source/GSSocketStream.m:
* Source/GSTLS.h:
* Source/GSTLS.m:
* Source/NSFileHandle.m:
More TLS reorganisation and adding diagnostics
2012-09-25 Richard Frith-Macdonald <rfm@gnu.org>

View file

@ -279,15 +279,15 @@ GS_EXPORT NSString * const NSFileHandleOperationException;
* <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 GSTLSPrivateKeyFile.
* This must be used in conjunction with GSTLSCertificateKeyFile.
* </desc>
* <term>GSTLSPrivateKeyFile</term>
* <term>GSTLSCertificateKeyFile</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
* GSTLSPrivateKeyPassword.
* GSTLSCertificateKeyPassword.
* </desc>
* <term>GSTLSPrivateKeyPassword</term>
* <term>GSTLSCertificateKeyPassword</term>
* <desc>A string to be used as the password to decrypt a key which was
* specified using GSTLSKeyPassword.
* </desc>
@ -305,12 +305,12 @@ 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 GSTLSPrivateKeyFile;
GS_EXPORT NSString * const GSTLSCertificateKeyFile;
/** 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 GSTLSPrivateKeyPassword;
GS_EXPORT NSString * const GSTLSCertificateKeyPassword;
// GNUstep Notification names.

View file

@ -353,8 +353,7 @@ GSPrivateSockaddrSetup(NSString *machine, uint16_t port,
@interface GSTLSHandler : GSStreamHandler
{
@public
gnutls_session_t session;
gnutls_certificate_credentials_t certcred;
GSTLSSession *session;
}
@end
@ -381,7 +380,7 @@ GSTLSPull(gnutls_transport_ptr_t handle, void *buffer, size_t len)
e = EAGAIN; // Tell GNUTLS this would block.
}
#if HAVE_GNUTLS_TRANSPORT_SET_ERRNO
gnutls_transport_set_errno (tls->session, e);
gnutls_transport_set_errno (tls->session->session, e);
#else
errno = e; // Not thread-safe
#endif
@ -412,7 +411,7 @@ GSTLSPush(gnutls_transport_ptr_t handle, const void *buffer, size_t len)
e = EAGAIN; // Tell GNUTLS this would block.
}
#if HAVE_GNUTLS_TRANSPORT_SET_ERRNO
gnutls_transport_set_errno (tls->session, e);
gnutls_transport_set_errno (tls->session->session, e);
#else
errno = e; // Not thread-safe
#endif
@ -459,20 +458,15 @@ GSTLSPush(gnutls_transport_ptr_t handle, const void *buffer, size_t len)
- (void) bye
{
if (active == YES || handshake == YES)
{
active = NO;
handshake = NO;
gnutls_bye (session, GNUTLS_SHUT_RDWR);
}
handshake = NO;
active = NO;
[session disconnect];
}
- (void) dealloc
{
[self bye];
gnutls_db_remove_session (session);
gnutls_deinit (session);
gnutls_certificate_free_credentials (certcred);
DESTROY(session);
[super dealloc];
}
@ -485,28 +479,16 @@ GSTLSPush(gnutls_transport_ptr_t handle, const void *buffer, size_t len)
{
if (active == NO)
{
int ret;
if (handshake == NO)
{
/* Set flag to say we are now doing a handshake.
*/
handshake = YES;
}
ret = gnutls_handshake (session);
if (ret < 0)
if ([session handshake] == YES)
{
NSDebugMLLog(@"NSStream",
@"Handshake status %d", ret);
if (GSDebugSet(@"NSStream") == YES)
{
gnutls_perror(ret);
}
}
else
{
handshake = NO; // Handshake is now complete.
active = YES; // The TLS session is now active.
handshake = NO; // Handshake is now complete.
active = [session active]; // The TLS session is now active.
}
}
}
@ -514,9 +496,11 @@ GSTLSPush(gnutls_transport_ptr_t handle, const void *buffer, size_t len)
- (id) initWithInput: (GSSocketInputStream*)i
output: (GSSocketOutputStream*)o
{
NSString *proto = [i propertyForKey: NSStreamSocketSecurityLevelKey];
NSString *proto;
NSDictionary *opts;
BOOL server = NO;
proto = [i propertyForKey: NSStreamSocketSecurityLevelKey];
/* FIXME
if ([[o propertyForKey: NSStreamSocketCertificateServerKey] boolValue] == YES)
{
@ -539,119 +523,22 @@ GSTLSPush(gnutls_transport_ptr_t handle, const void *buffer, size_t len)
DESTROY(self);
return nil;
}
if ([proto isEqualToString: NSStreamSocketSecurityLevelNone] == YES)
{
// proto = NSStreamSocketSecurityLevelNone;
DESTROY(self);
return nil;
}
else if ([proto isEqualToString: NSStreamSocketSecurityLevelSSLv2] == YES)
{
// proto = NSStreamSocketSecurityLevelSSLv2;
GSOnceMLog(@"NSStreamSocketSecurityLevelTLSv2 is insecure ..."
@" not implemented");
DESTROY(self);
return nil;
}
else if ([proto isEqualToString: NSStreamSocketSecurityLevelSSLv3] == YES)
{
proto = NSStreamSocketSecurityLevelSSLv3;
}
else if ([proto isEqualToString: NSStreamSocketSecurityLevelTLSv1] == YES)
{
proto = NSStreamSocketSecurityLevelTLSv1;
}
else
{
proto = NSStreamSocketSecurityLevelNegotiatedSSL;
}
opts = [NSDictionary dictionaryWithObjectsAndKeys:
proto, NSStreamSocketSecurityLevelKey,
nil];
if ((self = [super initWithInput: i output: o]) == nil)
{
return nil;
}
session = [[GSTLSSession alloc] initWithOptions: opts
direction: (server ? NO : YES)
transport: (void*)self
push: GSTLSPush
pull: GSTLSPull
host: nil];
initialised = 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);
/* Initialise session and set default priorities foir key exchange.
*/
if (server)
{
gnutls_init (&session, GNUTLS_SERVER);
/* FIXME ... need to set up DH information and key/certificate. */
}
else
{
gnutls_init (&session, GNUTLS_CLIENT);
}
gnutls_set_default_priority (session);
if ([proto isEqualToString: NSStreamSocketSecurityLevelTLSv1] == YES)
{
#if GNUTLS_VERSION_NUMBER < 0x020C00
const int proto_prio[4] = {
#if defined(GNUTLS_TLS1_2)
GNUTLS_TLS1_2,
#endif
GNUTLS_TLS1_1,
GNUTLS_TLS1_0,
0 };
gnutls_protocol_set_priority (session, proto_prio);
#else
gnutls_priority_set_direct(session,
"NORMAL:-VERS-SSL3.0:+VERS-TLS-ALL", NULL);
#endif
}
if ([proto isEqualToString: NSStreamSocketSecurityLevelSSLv3] == YES)
{
#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, GSTLSPull);
gnutls_transport_set_push_function (session, GSTLSPush);
gnutls_transport_set_ptr (session, (gnutls_transport_ptr_t)self);
return self;
}
@ -667,7 +554,7 @@ GSTLSPush(gnutls_transport_ptr_t handle, const void *buffer, size_t len)
- (NSInteger) read: (uint8_t *)buffer maxLength: (NSUInteger)len
{
return gnutls_record_recv (session, buffer, len);
return [session read: buffer length: len];
}
- (void) stream: (NSStream*)stream handleEvent: (NSStreamEvent)event
@ -719,7 +606,7 @@ GSTLSPush(gnutls_transport_ptr_t handle, const void *buffer, size_t len)
- (NSInteger) write: (const uint8_t *)buffer maxLength: (NSUInteger)len
{
return gnutls_record_send (session, buffer, len);
return [session write: buffer length: len];
}
@end

View file

@ -42,29 +42,10 @@
#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.
* and thread-safe.
*/
@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 autogenerated Diffie Hellman parameters
@ -122,5 +103,78 @@
- (gnutls_x509_privkey_t) key;
@end
/* Declare a pointer to a function to be used for I/O
*/
typedef ssize_t (*GSTLSIOR)(gnutls_transport_ptr_t, void *, size_t);
typedef ssize_t (*GSTLSIOW)(gnutls_transport_ptr_t, const void *, size_t);
/* This class encapsulates a session to a remote system.
* Sessions are created with a direction and an options dictionary,
* defining how they will operate. The handle, pushFunc and pullFunc
* provide the I/O mechanism, and the host specifies the host that the
* session is connected to.
*/
@interface GSTLSSession : GSTLSObject
{
NSDictionary *opts;
NSHost *host;
GSTLSPrivateKey *key;
GSTLSCertificateList *list;
GSTLSDHParams *dhParams;
gnutls_certificate_credentials_t certcred;
BOOL outgoing;
BOOL active;
BOOL handshake;
BOOL setup;
@public
gnutls_session_t session;
}
+ (GSTLSSession*) sessionWithOptions: (NSDictionary*)options
direction: (BOOL)isOutgoing
transport: (void*)handle
push: (GSTLSIOW)pushFunc
pull: (GSTLSIOR)pullFunc
host: (NSHost*)remote;
- (id) initWithOptions: (NSDictionary*)options
direction: (BOOL)isOutgoing
transport: (void*)handle
push: (GSTLSIOW)pushFunc
pull: (GSTLSIOR)pullFunc
host: (NSHost*)remote;
/* Return YES if the session is active (handshake has succeeded and the
* session has not been disconnected), NO otherwise.
*/
- (BOOL) active;
/* Disconnects and closes down the session.
*/
- (void) disconnect;
/* Try to complete a handshake ... return YES if complete, NO if we need
* to try again (would have to wait for the remote end).<br />
*/
- (BOOL) handshake;
/* Read data from the session.
*/
- (NSInteger) read: (void*)buf length: (NSUInteger)len;
/** Get a report of the SSL/TLS status of the current session.
*/
- (NSString*) sessionInfo;
/* Write data to the session.
*/
- (NSInteger) write: (const void*)buf length: (NSUInteger)len;
/* For internal use to verify the remmote system's vertificate.
* Returns 0 on success, negative on failure.
*/
- (int) verify;
@end
#endif

View file

@ -31,6 +31,8 @@
#import "Foundation/NSException.h"
#import "Foundation/NSLock.h"
#import "Foundation/NSNotification.h"
#import "Foundation/NSProcessInfo.h"
#import "Foundation/NSStream.h"
#import "Foundation/NSThread.h"
#import "Foundation/NSUserDefaults.h"
@ -38,9 +40,15 @@
#import "GSPrivate.h"
/* Constants to control TLS/SSL (options).
*/
NSString * const GSTLSCAFile = @"GSTLSCAFile";
NSString * const GSTLSCertificateFile = @"GSTLSCertificateFile";
NSString * const GSTLSPrivateKeyFile = @"GSTLSPrivateKeyFile";
NSString * const GSTLSPrivateKeyPassword = @"GSTLSPrivateKeyPassword";
NSString * const GSTLSCertificateKeyFile = @"GSTLSCertificateKeyFile";
NSString * const GSTLSCertificateKeyPassword = @"GSTLSCertificateKeyPassword";
NSString * const GSTLSDebug = @"GSTLSDebug";
NSString * const GSTLSCAVerify = @"GSTLSCAVerify";
#if defined(HAVE_GNUTLS)
@ -82,6 +90,33 @@ GSTLSLog(int level, const char *msg)
NSLog(@"%s", msg);
}
/* The caFile variable holds the location of the file containing the default
* certificate authorities to be used by our system.
* The hard-coded value can be overridden by the GS_TLS_CA_FILE environment
* variable, which in turn will be overridden by the GSTLSCAFile user
* default string.
*/
static NSString *caFile = @"/etc/ssl/certs/ca-certificates.crt";
/* The verifyServer variable tells us if connections to a remote server should
* (by default) verify its certificate against trusted authorities.
* The hard-coded value can be overridden by the GS_TLS_CA_VERIFY environment
* variable, which in turn will be overridden by the GSTLSCAVerify user
* default string.
* Any option set for a specific session overrides this default
*/
static BOOL verifyServer = NO;
/* The globalDebug variable turns on gnutls debug. The hard-code value is
* overridden by GS_TLS_DEBUG, which in turn can be overridden by the
* GSTLSDebug user default. This is an integer debug level with higher
* values producing more debug output. Usually levels higher than 1 are
* too verbose and not useful unless you have the gnutls source code to hand.
* NB. The GSTLSDebug session option is a boolean to turn on extra debug for
* a particular session to be produced on verification failure.
*/
static int globalDebug = 0;
static NSString *cipherList = nil;
static gnutls_anon_client_credentials_t anoncred;
@ -93,8 +128,36 @@ static gnutls_anon_client_credentials_t anoncred;
+ (void) _defaultsChanged: (NSNotification*)n
{
NSString *str;
cipherList
= [[NSUserDefaults standardUserDefaults] stringForKey: @"GSCipherList"];
/* The GSTLSCAFile user default overrides the builtin value or the
* GS_TLS_CA_FILE environment variable.
*/
str = [[NSUserDefaults standardUserDefaults] stringForKey: GSTLSCAFile];
if (nil != str)
{
ASSIGN(caFile, str);
}
str = [[NSUserDefaults standardUserDefaults] stringForKey: GSTLSCAVerify];
if (nil != str)
{
verifyServer = [str boolValue];
}
str = [[NSUserDefaults standardUserDefaults] stringForKey: GSTLSDebug];
if (nil != str)
{
globalDebug = [str intValue];
}
if (globalDebug < 0)
{
globalDebug = 0;
}
gnutls_global_set_log_level(globalDebug);
}
+ (void) initialize
@ -106,11 +169,35 @@ static gnutls_anon_client_credentials_t anoncred;
if (beenHere == NO)
{
NSUserDefaults *defs;
NSProcessInfo *pi;
NSString *str;
beenHere = YES;
/* Let the GS_TLS_CA_FILE environment variable override the
* default certificate authority location.
*/
pi = [NSProcessInfo processInfo];
str = [[pi environment] objectForKey: @"GS_TLS_CA_FILE"];
if (nil != str)
{
ASSIGN(caFile, str);
}
str = [[pi environment] objectForKey: @"GS_TLS_CA_VERIFY"];
if (nil != str)
{
verifyServer = [str boolValue];
}
str = [[pi environment] objectForKey: @"GS_TLS_DEBUG"];
if (nil != str)
{
globalDebug = [str intValue];
}
defs = [NSUserDefaults standardUserDefaults];
cipherList = [defs stringForKey: @"GSCipherList"];
[[NSNotificationCenter defaultCenter]
addObserver: self
selector: @selector(_defaultsChanged:)
@ -132,109 +219,12 @@ static gnutls_anon_client_credentials_t anoncred;
/* Enable gnutls logging via NSLog
*/
gnutls_global_set_log_function (GSTLSLog);
[self _defaultsChanged: nil];
}
}
}
+ (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
@ -711,5 +701,692 @@ static NSMutableDictionary *privateKeyCache1 = nil;
}
@end
@implementation GSTLSSession
+ (GSTLSSession*) sessionWithOptions: (NSDictionary*)options
direction: (BOOL)isOutgoing
transport: (void*)handle
push: (GSTLSIOW)pushFunc
pull: (GSTLSIOR)pullFunc
host: (NSHost*)host
{
GSTLSSession *sess;
sess = [[self alloc] initWithOptions: options
direction: isOutgoing
transport: handle
push: pushFunc
pull: pullFunc
host: host];
return [sess autorelease];
}
- (BOOL) active
{
return active;
}
- (void) dealloc
{
[self finalize];
DESTROY(opts);
DESTROY(host);
DESTROY(list);
DESTROY(key);
DESTROY(dhParams);
[super dealloc];
}
- (void) disconnect
{
if (YES == active || YES == handshake)
{
active = NO;
handshake = NO;
gnutls_bye(session, GNUTLS_SHUT_RDWR);
}
if (YES == setup)
{
setup = NO;
gnutls_db_remove_session(session);
gnutls_deinit(session);
gnutls_certificate_free_credentials(certcred);
}
}
- (void) finalize
{
[self disconnect];
[super finalize];
}
- (id) initWithOptions: (NSDictionary*)options
direction: (BOOL)isOutgoing
transport: (void*)handle
push: (GSTLSIOW)pushFunc
pull: (GSTLSIOR)pullFunc
host: (NSHost*)remote
{
if (nil != (self = [super init]))
{
NSString *certFile;
NSString *privateKey;
NSString *PEMpasswd;
NSString *pri;
NSString *str;
int ret;
BOOL debug = (globalDebug > 0) ? YES : NO;
opts = [options copy];
host = [remote copy];
outgoing = isOutgoing ? YES : NO;
if (NO == debug)
{
debug = [[opts objectForKey: GSTLSDebug] boolValue];
}
/* Now initialise session and set it up. It's simplest to always
* allocate a credentials structure at this point (and get rid of
* it when the session is disconnected) too.
*/
gnutls_certificate_allocate_credentials(&certcred);
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);
}
setup = YES;
/* Set the default trusted authority certificates.
*/
if ([caFile length] > 0)
{
const char *path = [caFile fileSystemRepresentation];
int ret;
ret = gnutls_certificate_set_x509_trust_file(certcred,
path, GNUTLS_X509_FMT_PEM);
if (ret < 0)
{
NSLog(@"Problem loading trusted authorities from %@: %s",
caFile, gnutls_strerror(ret));
}
else if (0 == ret && YES == debug)
{
NSLog(@"No certificates processed from %@", caFile);
}
}
/* Load any specified trusted authority certificates.
*/
str = [opts objectForKey: GSTLSCAFile];
if ([str length] > 0)
{
const char *path = [str fileSystemRepresentation];
int ret;
ret = gnutls_certificate_set_x509_trust_file(certcred,
path, GNUTLS_X509_FMT_PEM);
if (ret < 0)
{
NSLog(@"Problem loading trusted authorities from %@: %s",
str, gnutls_strerror(ret));
}
else if (0 == ret)
{
NSLog(@"No certificates processed from %@", str);
}
}
/*
gnutls_certificate_set_x509_crl_file
(certcred, "crl.pem", GNUTLS_X509_FMT_PEM);
gnutls_certificate_set_verify_function (certcred,
_verify_certificate_callback);
*/
certFile = [opts objectForKey: GSTLSCertificateFile];
privateKey = [opts objectForKey: GSTLSCertificateKeyFile];
PEMpasswd = [opts objectForKey: GSTLSCertificateKeyPassword];
if (nil != privateKey)
{
key = [[GSTLSPrivateKey keyFromFile: privateKey
withPassword: PEMpasswd] retain];
if (nil == key)
{
[self release];
return nil;
}
}
if (nil != certFile)
{
list = [[GSTLSCertificateList listFromFile: certFile] retain];
if (nil == list)
{
[self release];
return nil;
}
}
if (nil != list)
{
ret = gnutls_certificate_set_x509_key(certcred,
[list certificateList], [list count], [key key]);
if (ret < 0)
{
NSLog(@"Unable to set certificate for session: %s",
gnutls_strerror(ret));
[self release];
return nil;
}
/*
else if (NO == outgoing)
{
dhParams = [[GSTLSDHParams current] retain];
gnutls_certificate_set_dh_params (certcred, [dhParams params]);
}
*/
}
gnutls_set_default_priority(session);
pri = [opts objectForKey: NSStreamSocketSecurityLevelKey];
if ([pri isEqualToString: NSStreamSocketSecurityLevelNone] == YES)
{
// pri = NSStreamSocketSecurityLevelNone;
GSOnceMLog(@"NSStreamSocketSecurityLevelNone is insecure ..."
@" not implemented");
DESTROY(self);
return nil;
}
else if ([pri isEqualToString: NSStreamSocketSecurityLevelSSLv2] == YES)
{
// pri = NSStreamSocketSecurityLevelSSLv2;
GSOnceMLog(@"NSStreamSocketSecurityLevelTLSv2 is insecure ..."
@" not implemented");
DESTROY(self);
return nil;
}
else if ([pri isEqualToString: NSStreamSocketSecurityLevelSSLv3] == YES)
{
#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
}
else if ([pri isEqualToString: NSStreamSocketSecurityLevelTLSv1] == YES)
{
#if GNUTLS_VERSION_NUMBER < 0x020C00
const int proto_prio[4] = {
#if defined(GNUTLS_TLS1_2)
GNUTLS_TLS1_2,
#endif
GNUTLS_TLS1_1,
GNUTLS_TLS1_0,
0 };
gnutls_protocol_set_priority (session, proto_prio);
#else
gnutls_priority_set_direct(session,
"NORMAL:-VERS-SSL3.0:+VERS-TLS-ALL", NULL);
#endif
}
/* Set certificate credentials for this session.
*/
gnutls_credentials_set(session, GNUTLS_CRD_CERTIFICATE, certcred);
/* Set transport layer to use
*/
#if GNUTLS_VERSION_NUMBER < 0x020C00
gnutls_transport_set_lowat (session, 0);
#endif
gnutls_transport_set_pull_function(session, pullFunc);
gnutls_transport_set_push_function(session, pushFunc);
gnutls_transport_set_ptr(session, (gnutls_transport_ptr_t)handle);
}
return self;
}
- (BOOL) handshake
{
int ret;
if (YES == active || NO == setup)
{
return YES; // Handshake completed or impossible.
}
handshake = YES;
ret = gnutls_handshake(session);
if (ret < 0)
{
if (gnutls_error_is_fatal(ret))
{
NSLog(@"unable to make SSL connection: %s",
gnutls_strerror(ret));
[self disconnect];
return YES; // Failed ... not active.
}
else
{
if (GSDebugSet(@"NSStream") == YES)
{
gnutls_perror(ret);
}
return NO; // Non-fatal error needs a retry.
}
}
else
{
NSString *str;
BOOL shouldVerify = NO;
active = YES; // The TLS session is now active.
handshake = NO; // Handshake is over.
if (YES == outgoing)
{
shouldVerify = verifyServer; // Verify remote server?
}
str = [opts objectForKey: GSTLSCAVerify];
if (nil != str)
{
shouldVerify = [str boolValue];
}
if (globalDebug > 1)
{
NSLog(@"Before verify:\n%@", [self sessionInfo]);
}
if (YES == shouldVerify)
{
ret = [self verify];
if (ret < 0)
{
if (globalDebug > 0
|| YES == [[opts objectForKey: GSTLSDebug] boolValue])
{
NSLog(@"unable to verify SSL connection - %s",
gnutls_strerror(ret));
NSLog(@"%@", [self sessionInfo]);
}
[self disconnect];
}
}
return YES; // Handshake complete
}
}
- (NSInteger) read: (void*)buf length: (NSUInteger)len
{
return gnutls_record_recv(session, buf, len);
}
- (NSInteger) write: (const void*)buf length: (NSUInteger)len
{
return gnutls_record_send (session, buf, len);
}
/* Copied/based on the public domain code provided by gnutls
* to print the session ... I've left in details for features
* we don't yet support.
*/
- (NSString*) sessionInfo
{
NSMutableString *str;
const char *tmp;
gnutls_credentials_type_t cred;
gnutls_kx_algorithm_t kx;
int dhe;
int ecdh;
dhe = ecdh = 0;
str = [NSMutableString stringWithCapacity: 2000];
/* get the key exchange's algorithm name
*/
kx = gnutls_kx_get(session);
tmp = gnutls_kx_get_name(kx);
[str appendFormat: _(@"- Key Exchange: %s\n"), tmp];
/* Check the authentication type used and switch to the appropriate.
*/
cred = gnutls_auth_get_type(session);
switch (cred)
{
case GNUTLS_CRD_IA:
[str appendString: _(@"- TLS/IA session\n")];
break;
case GNUTLS_CRD_SRP:
#ifdef ENABLE_SRP
[str appendFormat: _(@"- SRP session with username %s\n"),
gnutls_srp_server_get_username(session)];
#endif
break;
case GNUTLS_CRD_PSK:
#if 0
/* This returns NULL in server side.
*/
if (gnutls_psk_client_get_hint (session) != NULL)
{
[str appendFormat: _(@"- PSK authentication. PSK hint '%s'\n"),
gnutls_psk_client_get_hint(session)];
}
/* This returns NULL in client side.
*/
if (gnutls_psk_server_get_username (session) != NULL)
{
[str appendFormat: _(@"- PSK authentication. Connected as '%s'\n"),
gnutls_psk_server_get_username(session)];
}
if (GNUTLS_KX_ECDHE_PSK == kx)
{
dhe = 0;
ecdh = 1;
}
else if (GNUTLS_KX_DHE_PSK == kx)
{
dhe = 1;
ecdh = 0;
}
#endif
break;
case GNUTLS_CRD_ANON: /* anonymous authentication */
#if 0
[str appendFormat: _(@"- Anonymous authentication.\n")];
if (GNUTLS_KX_ANON_ECDH == kx)
{
dhe = 0;
ecdh = 1;
}
else if (GNUTLS_KX_ANON_DH == kx)
{
dhe = 1;
ecdh = 0;
}
#endif
break;
case GNUTLS_CRD_CERTIFICATE: /* certificate authentication */
{
unsigned int cert_list_size = 0;
const gnutls_datum *cert_list;
gnutls_x509_crt cert;
/* Check if we have been using ephemeral Diffie-Hellman.
*/
if (GNUTLS_KX_DHE_RSA == kx || GNUTLS_KX_DHE_DSS == kx)
{
dhe = 1;
ecdh = 0;
}
#if 0
if (GNUTLS_KX_ECDHE_RSA == kx || GNUTLS_KX_ECDHE_ECDSA == kx)
{
dhe = 0;
ecdh = 1;
}
#endif
/* if the certificate list is available, then
* print some information about it.
*/
cert_list = gnutls_certificate_get_peers(session, &cert_list_size);
if (cert_list_size > 0
&& gnutls_certificate_type_get(session) == GNUTLS_CRT_X509)
{
char dn[128];
char serial[40];
size_t dn_size = sizeof(dn);
size_t serial_size = sizeof(serial);
time_t expiret;
time_t activet;
int algo;
unsigned int bits;
int i;
int cert_num;
for (cert_num = 0; cert_num < cert_list_size; cert_num++)
{
gnutls_x509_crt_init(&cert);
/* NB. the list of peer certificate is in memory in native
* format (DER) rather than the normal file format (PEM).
*/
gnutls_x509_crt_import(cert,
&cert_list[cert_num], GNUTLS_X509_FMT_DER);
[str appendFormat: _(@"- Certificate %d info:\n"), cert_num];
expiret = gnutls_x509_crt_get_expiration_time(cert);
activet = gnutls_x509_crt_get_activation_time(cert);
[str appendFormat: _(@"- Certificate is valid since: %s"),
ctime(&activet)];
[str appendFormat: _(@"- Certificate expires: %s"),
ctime (&expiret)];
#if 0
{
char digest[20];
size_t digest_size = sizeof(digest);
if (gnutls_x509_fingerprint(GNUTLS_DIG_MD5,
&cert_list[0], digest, &digest_size) >= 0)
{
[str appendString: _(@"- Certificate fingerprint: ")];
for (i = 0; i < digest_size; i++)
{
[str appendFormat: @"%.2x ", (unsigned char)digest[i]];
}
[str appendString: @"\n"];
}
}
#endif
if (gnutls_x509_crt_get_serial(cert, serial, &serial_size) >= 0)
{
[str appendString: _(@"- Certificate serial number: ")];
for (i = 0; i < serial_size; i++)
{
[str appendFormat: @"%.2x ", (unsigned char)serial[i]];
}
[str appendString: @"\n"];
}
[str appendString: _(@"- Certificate public key: ")];
algo = gnutls_x509_crt_get_pk_algorithm(cert, &bits);
if (GNUTLS_PK_RSA == algo)
{
[str appendString: _(@"RSA\n")];
[str appendFormat: _(@"- Modulus: %d bits\n"), bits];
}
else if (GNUTLS_PK_DSA == algo)
{
[str appendString: _(@"DSA\n")];
[str appendFormat: _(@"- Exponent: %d bits\n"), bits];
}
else
{
[str appendString: _(@"UNKNOWN\n")];
}
[str appendFormat: _(@"- Certificate version: #%d\n"),
gnutls_x509_crt_get_version(cert)];
gnutls_x509_crt_get_dn(cert, dn, &dn_size);
[str appendFormat: @"- Certificate DN: %s\n", dn];
gnutls_x509_crt_get_issuer_dn (cert, dn, &dn_size);
[str appendFormat: _(@"- Certificate Issuer's DN: %s\n"), dn];
gnutls_x509_crt_deinit(cert);
}
}
}
break;
} /* switch */
#if 0
if (ecdh != 0)
{
[str appendFormat: _(@"- Ephemeral ECDH using curve %s\n"),
gnutls_ecc_curve_get_name(gnutls_ecc_curve_get(session))];
}
#endif
if (dhe != 0)
{
[str appendFormat: _(@"- Ephemeral DH using prime of %d bits\n"),
gnutls_dh_get_prime_bits(session)];
}
/* print the protocol's name (ie TLS 1.0)
*/
tmp = gnutls_protocol_get_name(gnutls_protocol_get_version(session));
[str appendFormat: _(@"- Protocol: %s\n"), tmp];
/* print the certificate type of the peer.
* ie X.509
*/
tmp = gnutls_certificate_type_get_name(gnutls_certificate_type_get(session));
[str appendFormat: _(@"- Certificate Type: %s\n"), tmp];
/* print the compression algorithm (if any)
*/
tmp = gnutls_compression_get_name(gnutls_compression_get(session));
[str appendFormat: _(@"- Compression: %s\n"), tmp];
/* print the name of the cipher used.
* ie 3DES.
*/
tmp = gnutls_cipher_get_name(gnutls_cipher_get(session));
[str appendFormat: _(@"- Cipher: %s\n"), tmp];
/* Print the MAC algorithms name.
* ie SHA1
*/
tmp = gnutls_mac_get_name(gnutls_mac_get(session));
[str appendFormat: _(@"- MAC: %s\n"), tmp];
return str;
}
- (int) verify
{
BOOL debug = (globalDebug > 0) ? YES : NO;
unsigned int status;
const gnutls_datum_t *cert_list;
unsigned int cert_list_size;
int ret;
gnutls_x509_crt_t cert;
if (NO == debug)
{
debug = [[opts objectForKey: GSTLSDebug] boolValue];
}
/* 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 %s", gnutls_strerror(ret));
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
#endif

View file

@ -851,11 +851,11 @@ NSString * const NSFileHandleOperationException
}
if (nil != privateKey)
{
[opts setObject: privateKey forKey: GSTLSPrivateKeyFile];
[opts setObject: privateKey forKey: GSTLSCertificateKeyFile];
}
if (nil != PEMpasswd)
{
[opts setObject: PEMpasswd forKey: GSTLSPrivateKeyPassword];
[opts setObject: PEMpasswd forKey: GSTLSCertificateKeyPassword];
}
err = [self sslSetOptions: opts];
if (nil != err)
@ -879,14 +879,9 @@ NSString * const NSFileHandleOperationException
@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
NSDictionary *opts;
GSTLSSession *session;
}
- (void) sslDisconnect;
- (BOOL) sslHandshakeEstablished: (BOOL*)result outgoing: (BOOL)isOutgoing;
@ -908,7 +903,7 @@ GSTLSHandlePull(gnutls_transport_ptr_t handle, void *buffer, size_t len)
if (result < 0)
{
#if HAVE_GNUTLS_TRANSPORT_SET_ERRNO
gnutls_transport_set_errno (tls->session, errno);
gnutls_transport_set_errno (tls->session->session, errno);
#endif
}
return result;
@ -928,7 +923,7 @@ GSTLSHandlePush(gnutls_transport_ptr_t handle, const void *buffer, size_t len)
if (result < 0)
{
#if HAVE_GNUTLS_TRANSPORT_SET_ERRNO
gnutls_transport_set_errno (tls->session, errno);
gnutls_transport_set_errno (tls->session->session, errno);
#endif
}
return result;
@ -950,46 +945,38 @@ GSTLSHandlePush(gnutls_transport_ptr_t handle, const void *buffer, size_t len)
[super closeFile];
}
- (void) dealloc
{
DESTROY(opts);
DESTROY(session);
[super dealloc];
}
- (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)
if (YES == [session active])
{
return gnutls_record_recv (session, buf, len);
return [session read: buf length: 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);
[session disconnect];
}
- (BOOL) sslHandshakeEstablished: (BOOL*)result outgoing: (BOOL)isOutgoing
{
int ret;
NSAssert(0 != result, NSInvalidArgumentException);
if (YES == active)
if (YES == [session active])
{
return YES; /* Already connected. */
}
@ -997,125 +984,31 @@ GSTLSHandlePush(gnutls_transport_ptr_t handle, const void *buffer, size_t len)
if (YES == isStandardFile)
{
NSLog(@"Attempt to perform ssl handshake with a standard file");
return NO;
return YES;
}
/* Set the handshake direction so we know how to set up the connection.
*/
outgoing = isOutgoing;
if (NO == setup)
if (nil == session)
{
[self sslSetCertificate: nil privateKey: nil PEMpasswd: nil];
session = [[GSTLSSession alloc] initWithOptions: opts
direction: isOutgoing
transport: (void*)self
push: GSTLSHandlePush
pull: GSTLSHandlePull
host: nil];
}
if (NO == handshake)
if (NO == [session 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.
}
return NO; // Need more.
}
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 = [session active];
return YES;
}
}
*result = active;
}
return YES;
}
- (NSString*) sslSetOptions: (NSDictionary*)options
{
@ -1123,93 +1016,15 @@ GSTLSHandlePush(gnutls_transport_ptr_t handle, const void *buffer, size_t len)
{
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
}
ASSIGNCOPY(opts, options);
return nil;
}
- (NSInteger) write: (const void*)buf length: (NSUInteger)len
{
if (YES == active)
if (YES == [session active])
{
return gnutls_record_send (session, buf, len);
return [session write: buf length: len];
}
return [super write: buf length: len];
}