New code for secure (encrypted) inter-host distrinuted objects.

This commit is contained in:
Richard Frith-Macdonald 2021-05-31 13:19:42 +01:00
parent e2cbf5e871
commit e42d9fdc6b
5 changed files with 392 additions and 46 deletions

View file

@ -1,3 +1,17 @@
2021-05-31 Richard Frith-Macdonald <rfm@gnu.org>
* Headers/GNUstepBase/GSTLS.h: ([GSTLSCredentials selfSigned:])
* Source/GSTLS.m: Add new method to use certtool to generate a
key and a self-signed certificate. Use the new method to set up
server sessions if no certificate/key is configured.
* Headers/Foundation/NSPort.h: ([NSSocketPost setOptionsForTLS:])
* Source/NSSocketPort.m: New methods to configure socket ports to
use TLS so that inter-host distributed object connections can be
securely encrypted. The class method should turn on encryption
for all subsequent connections using socket ports (the instance
method can be used to override the effects of the class method
for an individual instance).
2021-05-23 Richard Frith-Macdonald <rfm@gnu.org> 2021-05-23 Richard Frith-Macdonald <rfm@gnu.org>
* Source/GSStream.m: * Source/GSStream.m:

View file

@ -208,6 +208,7 @@ GS_EXPORT_CLASS
uint16_t portNum; /* TCP port in host byte order. */ uint16_t portNum; /* TCP port in host byte order. */
SOCKET listener; SOCKET listener;
NSMapTable *handles; /* Handles indexed by socket. */ NSMapTable *handles; /* Handles indexed by socket. */
NSDictionary *tlsopts; /* TLS options */
#if defined(_WIN32) #if defined(_WIN32)
WSAEVENT eventListener; WSAEVENT eventListener;
NSMapTable *events; NSMapTable *events;
@ -273,6 +274,21 @@ GS_EXPORT_CLASS
* Returns port number of underlying socket. * Returns port number of underlying socket.
*/ */
- (uint16_t) portNumber; - (uint16_t) portNumber;
#if !NO_GNUSTEP
/** Sets the default options for use of TLS by socket ports.<br />
* Setting nil (the default) means that TLS is not used.<br />
* Setting an empty dictionary means that TLS is used with normal options.
*/
+ (void) setOptionsForTLS: (NSDictionary*)opts;
/** Overrides the default options for use of TLS by the receiver.<br />
* Setting nil (the default) means that TLS is not used.<br />
* Setting an empty dictionary means that TLS is used with normal options.<br />
* This method has no effect on network sessions which are already established.
*/
- (void) setOptionsForTLS: (NSDictionary*)opts;
#endif
@end @end

View file

@ -175,6 +175,7 @@ GS_EXPORT_CLASS
certificateKeyPassword: (NSString*)cp certificateKeyPassword: (NSString*)cp
asClient: (BOOL)client asClient: (BOOL)client
debug: (BOOL)debug; debug: (BOOL)debug;
+ (GSTLSCredentials*) selfSigned: (BOOL)debug;
- (gnutls_certificate_credentials_t) credentials; - (gnutls_certificate_credentials_t) credentials;
- (GSTLSPrivateKey*) key; - (GSTLSPrivateKey*) key;
- (GSTLSCertificateList*) list; - (GSTLSCertificateList*) list;

View file

@ -35,8 +35,10 @@
#import "Foundation/NSNotification.h" #import "Foundation/NSNotification.h"
#import "Foundation/NSProcessInfo.h" #import "Foundation/NSProcessInfo.h"
#import "Foundation/NSStream.h" #import "Foundation/NSStream.h"
#import "Foundation/NSTask.h"
#import "Foundation/NSThread.h" #import "Foundation/NSThread.h"
#import "Foundation/NSUserDefaults.h" #import "Foundation/NSUserDefaults.h"
#import "Foundation/NSUUID.h"
#import "GNUstepBase/GSTLS.h" #import "GNUstepBase/GSTLS.h"
@ -1091,6 +1093,89 @@ static NSMutableDictionary *credentialsCache = nil;
} }
} }
+ (GSTLSCredentials*) selfSigned: (BOOL)debug
{
NSString *crtPath = standardizedPath(@"self-signed-crt");
NSString *keyPath = standardizedPath(@"self-signed-key");
NSData *crt = [self dataForTLSFile: crtPath];
NSData *key = [self dataForTLSFile: keyPath];
if (nil == crt || nil == key)
{
static NSString *tmp = @"organization = SelfSigned\n"
@"state = Example\n"
@"country = EX\n"
@"cn = SelfSigned\n"
@"serial = 007\n"
@"expiration_days = 730\n"
@"dns_name = server.selfsigned.com\n"
@"tls_www_server\n"
@"encryption_key\n";
NSFileManager *mgr = [NSFileManager defaultManager];
NSString *path = NSTemporaryDirectory();
NSTask *task;
NSString *tmpPath;
path = [path stringByAppendingPathComponent: [[NSUUID UUID] UUIDString]];
keyPath = [path stringByAppendingPathExtension: @"key"];
tmpPath = [path stringByAppendingPathExtension: @"tmp"];
crtPath = [path stringByAppendingPathExtension: @"crt"];
[tmp writeToFile: tmpPath atomically: NO];
task = [NSTask new];
[task setLaunchPath: @"certtool"];
[task setArguments: [NSArray arrayWithObjects:
@"--generate-privkey", @"--sec-param", @"high", @"--outfile", keyPath,
nil]];
[task setStandardOutput: [NSFileHandle fileHandleWithNullDevice]];
[task setStandardError: [NSFileHandle fileHandleWithNullDevice]];
[task launch];
[task waitUntilExit];
RELEASE(task);
key = [NSData dataWithContentsOfFile: keyPath];
task = [NSTask new];
[task setLaunchPath: @"certtool"];
[task setArguments: [NSArray arrayWithObjects:
@"--generate-self-signed", @"--load-privkey", keyPath,
@"--template", tmpPath, @"--outfile", crtPath,
nil]];
[task setStandardOutput: [NSFileHandle fileHandleWithNullDevice]];
[task setStandardError: [NSFileHandle fileHandleWithNullDevice]];
[task launch];
[task waitUntilExit];
crt = [NSData dataWithContentsOfFile: crtPath];
[mgr removeFileAtPath: keyPath handler: nil];
[mgr removeFileAtPath: tmpPath handler: nil];
[mgr removeFileAtPath: crtPath handler: nil];
if (nil == key)
{
NSLog(@"Failed to make self-signed certificate key using 'certtool'");
return nil;
}
if (nil == crt)
{
NSLog(@"Failed to make self-signed certificate using 'certtool'");
return nil;
}
keyPath = standardizedPath(@"self-signed-key");
[self setData: key forTLSFile: keyPath];
crtPath = standardizedPath(@"self-signed-crt-");
[self setData: crt forTLSFile: crtPath];
}
return [self credentialsFromCAFile: nil
defaultCAFile: nil
revokeFile: nil
defaultRevokeFile: nil
certificateFile: crtPath
certificateKeyFile: keyPath
certificateKeyPassword: nil
asClient: NO
debug: debug];
}
+ (GSTLSCredentials*) credentialsFromCAFile: (NSString*)ca + (GSTLSCredentials*) credentialsFromCAFile: (NSString*)ca
defaultCAFile: (NSString*)dca defaultCAFile: (NSString*)dca
revokeFile: (NSString*)rv revokeFile: (NSString*)rv
@ -1322,6 +1407,7 @@ static NSMutableDictionary *credentialsCache = nil;
return [c autorelease]; return [c autorelease];
} }
- (void) dealloc - (void) dealloc
{ {
if (nil != name) if (nil != name)
@ -1539,17 +1625,18 @@ retrieve_callback(gnutls_session_t session,
{ {
if (nil != (self = [super init])) if (nil != (self = [super init]))
{ {
NSString *ca; GSTLSCredentials *cr;
NSString *dca; NSString *ca;
NSString *rv; NSString *dca;
NSString *drv; NSString *rv;
NSString *cf; NSString *drv;
NSString *ck; NSString *cf;
NSString *cp; NSString *ck;
NSString *pri; NSString *cp;
NSString *str; NSString *pri;
BOOL trust; NSString *str;
BOOL verify; BOOL trust;
BOOL verify;
created = [NSDate timeIntervalSinceReferenceDate]; created = [NSDate timeIntervalSinceReferenceDate];
opts = [options copy]; opts = [options copy];
@ -1632,28 +1719,41 @@ retrieve_callback(gnutls_session_t session,
} }
setup = YES; setup = YES;
ca = [opts objectForKey: GSTLSCAFile];
dca = caFile;
rv = [opts objectForKey: GSTLSRevokeFile];
drv = revokeFile;
cf = [opts objectForKey: GSTLSCertificateFile]; cf = [opts objectForKey: GSTLSCertificateFile];
ck = [opts objectForKey: GSTLSCertificateKeyFile];
cp = [opts objectForKey: GSTLSCertificateKeyPassword];
credentials = [[GSTLSCredentials credentialsFromCAFile: ca if (nil == cf && NO == outgoing)
defaultCAFile: dca {
revokeFile: rv /* Server with no certiticate supplied: generate self signed one.
defaultRevokeFile: drv */
certificateFile: cf cr = [GSTLSCredentials selfSigned: debug];
certificateKeyFile: ck }
certificateKeyPassword: cp else
asClient: outgoing {
debug: debug] retain]; ca = [opts objectForKey: GSTLSCAFile];
dca = caFile;
rv = [opts objectForKey: GSTLSRevokeFile];
drv = revokeFile;
ck = [opts objectForKey: GSTLSCertificateKeyFile];
cp = [opts objectForKey: GSTLSCertificateKeyPassword];
cr = [GSTLSCredentials credentialsFromCAFile: ca
defaultCAFile: dca
revokeFile: rv
defaultRevokeFile: drv
certificateFile: cf
certificateKeyFile: ck
certificateKeyPassword: cp
asClient: outgoing
debug: debug];
}
if (nil == credentials) if (cr)
{ {
[self release]; ASSIGN(credentials, cr);
}
else
{
RELEASE(self);
return nil; return nil;
} }

View file

@ -26,6 +26,7 @@
#define EXPOSE_NSPort_IVARS 1 #define EXPOSE_NSPort_IVARS 1
#define EXPOSE_NSSocketPort_IVARS 1 #define EXPOSE_NSSocketPort_IVARS 1
#import "GNUstepBase/GSLock.h" #import "GNUstepBase/GSLock.h"
#import "GNUstepBase/GSTLS.h"
#import "Foundation/NSArray.h" #import "Foundation/NSArray.h"
#import "Foundation/NSNotification.h" #import "Foundation/NSNotification.h"
#import "Foundation/NSNotificationQueue.h" #import "Foundation/NSNotificationQueue.h"
@ -122,6 +123,10 @@
*/ */
static uint32_t maxDataLength = 32 * 1024 * 1024; static uint32_t maxDataLength = 32 * 1024 * 1024;
/* Options for TLS encryption of connections
*/
static NSDictionary *tlsOptions;
#if 0 #if 0
#define M_LOCK(X) {NSDebugMLLog(@"GSTcpHandleLock",@"lock %@ in %@",X,[NSThread currentThread]); [X lock];} #define M_LOCK(X) {NSDebugMLLog(@"GSTcpHandleLock",@"lock %@ in %@",X,[NSThread currentThread]); [X lock];}
#define M_UNLOCK(X) {NSDebugMLLog(@"GSTcpHandleLock",@"unlock %@ in %@",X,[NSThread currentThread]); [X unlock];} #define M_UNLOCK(X) {NSDebugMLLog(@"GSTcpHandleLock",@"unlock %@ in %@",X,[NSThread currentThread]); [X unlock];}
@ -200,9 +205,15 @@ typedef enum {
GS_H_CONNECTED // Currently connected. GS_H_CONNECTED // Currently connected.
} GSHandleState; } GSHandleState;
@interface NSSocketPort (GSTcpHandle)
- (NSDictionary*) optionsForTLS;
@end
@interface GSTcpHandle : NSObject <RunLoopEvents> @interface GSTcpHandle : NSObject <RunLoopEvents>
{ {
SOCKET desc; /* File descriptor for I/O. */ SOCKET desc; /* File descriptor for I/O. */
NSData *cData; /* Connection data. */
unsigned cLength; /* Connection data written. */
unsigned wItem; /* Index of item being written. */ unsigned wItem; /* Index of item being written. */
NSMutableData *wData; /* Data object being written. */ NSMutableData *wData; /* Data object being written. */
unsigned wLength; /* Ammount written so far. */ unsigned wLength; /* Ammount written so far. */
@ -223,7 +234,6 @@ typedef enum {
BOOL inReplyMode; /* Indicate when have addEvent self */ BOOL inReplyMode; /* Indicate when have addEvent self */
BOOL readyToSend; /* Indicate when send */ BOOL readyToSend; /* Indicate when send */
#endif #endif
@public @public
NSRecursiveLock *myLock; /* Lock for this handle. */ NSRecursiveLock *myLock; /* Lock for this handle. */
BOOL caller; /* Did we connect to other end? */ BOOL caller; /* Did we connect to other end? */
@ -232,6 +242,7 @@ typedef enum {
NSSocketPort *sendPort; NSSocketPort *sendPort;
struct sockaddr sockAddr; /* Far end of connection. */ struct sockaddr sockAddr; /* Far end of connection. */
NSString *defaultAddress; NSString *defaultAddress;
GSTLSSession *session; /* Session for encryption. */
} }
+ (GSTcpHandle*) handleWithDescriptor: (SOCKET)d; + (GSTcpHandle*) handleWithDescriptor: (SOCKET)d;
@ -253,9 +264,95 @@ typedef enum {
- (NSSocketPort*) sendPort; - (NSSocketPort*) sendPort;
- (void) setState: (GSHandleState)s; - (void) setState: (GSHandleState)s;
- (GSHandleState) state; - (GSHandleState) state;
- (void) setupTLS: (NSSocketPort*)opts asClient: (BOOL)outgoing;
@end @end
#if defined(HAVE_GNUTLS)
/* 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;
GSTcpHandle *tls = (GSTcpHandle*)handle;
int descriptor = [tls descriptor];
result = recv(descriptor, buffer, len, 0);
if (result < 0)
{
#if HAVE_GNUTLS_TRANSPORT_SET_ERRNO
if (tls->session && tls->session->session)
{
int e;
#if defined(_WIN32)
/* For windows, we need to map winsock errors to unix ones that
* gnutls understands.
*/
e = WSAGetLastError();
if (WSAEWOULDBLOCK == e)
{
e = EAGAIN;
}
else if (WSAEINTR == e)
{
e = EINTR;
}
#else
e = errno;
#endif
gnutls_transport_set_errno (tls->session->session, e);
}
#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;
GSTcpHandle *tls = (GSTcpHandle*)handle;
int descriptor = [tls descriptor];
result = send(descriptor, buffer, len, 0);
if (result < 0)
{
#if HAVE_GNUTLS_TRANSPORT_SET_ERRNO
if (tls->session && tls->session->session)
{
int e;
#if defined(_WIN32)
/* For windows, we need to map winsock errors to unix ones that
* gnutls understands.
*/
e = WSAGetLastError();
if (WSAEWOULDBLOCK == e)
{
e = EAGAIN;
}
else if (WSAEINTR == e)
{
e = EINTR;
}
#else
e = errno;
#endif
gnutls_transport_set_errno(tls->session->session, e);
}
#endif
}
return result;
}
#endif
/* /*
* Utility functions for encoding and decoding ports. * Utility functions for encoding and decoding ports.
*/ */
@ -696,6 +793,7 @@ static Class runLoopClass;
{ {
[self finalize]; [self finalize];
DESTROY(defaultAddress); DESTROY(defaultAddress);
DESTROY(cData);
DESTROY(rData); DESTROY(rData);
DESTROY(rItems); DESTROY(rItems);
DESTROY(wMsgs); DESTROY(wMsgs);
@ -724,6 +822,11 @@ static Class runLoopClass;
- (void) finalize - (void) finalize
{ {
[self invalidate]; [self invalidate];
if (session)
{
[session disconnect: NO];
DESTROY(session);
}
#if defined(_WIN32) #if defined(_WIN32)
if (event != WSA_INVALID_EVENT) if (event != WSA_INVALID_EVENT)
{ {
@ -804,7 +907,21 @@ static Class runLoopClass;
* Now try to fill the buffer with data. * Now try to fill the buffer with data.
*/ */
bytes = [rData mutableBytes]; bytes = [rData mutableBytes];
res = recv(desc, bytes + rLength, want - rLength, 0); if (session)
{
if ([session handshake])
{
res = [session read: bytes + rLength length: want - rLength];
}
else
{
res = -1;
}
}
else
{
res = recv(desc, bytes + rLength, want - rLength, 0);
}
if (res <= 0) if (res <= 0)
{ {
if (res == 0) if (res == 0)
@ -1070,7 +1187,7 @@ static Class runLoopClass;
if (shouldDispatch == YES) if (shouldDispatch == YES)
{ {
NSPortMessage *pm; NSPortMessage *pm;
NSSocketPort *rp = [self recvPort]; NSSocketPort *rp = [self recvPort];
pm = [portMessageClass allocWithZone: NSDefaultMallocZone()]; pm = [portMessageClass allocWithZone: NSDefaultMallocZone()];
pm = [pm initWithSendPort: [self sendPort] pm = [pm initWithSendPort: [self sendPort]
@ -1123,29 +1240,72 @@ static Class runLoopClass;
} }
else else
{ {
NSData *d = newDataWithEncodedPort([self recvPort]); unsigned l;
const void *b;
len = send(desc, [d bytes], [d length], 0); if (nil == cData)
if (len == (int)[d length]) {
{ ASSIGN(cData, newDataWithEncodedPort([self recvPort]));
ASSIGN(defaultAddress, GSPrivateSockaddrHost(&sockAddr)); cLength = 0;
NSDebugMLLog(@"GSTcpHandle", }
@"wrote %d bytes on 0x%"PRIxPTR, len, (NSUInteger)self); b = [cData bytes];
state = GS_H_CONNECTED; l = [cData length];
if (session)
{
if ([session handshake])
{
len = [session write: b + cLength length: l - cLength];
}
else
{
len = -1;
}
} }
else else
{ {
state = GS_H_UNCON; len = send(desc, b + cLength, l - cLength, 0);
NSLog(@"connect write attempt failed - %@", }
[NSError _last]); if (len <= 0)
{
#ifdef _WIN32
if (WSAGetLastError() != WSAEINTR
&& WSAGetLastError() != WSAEWOULDBLOCK)
#else
if (errno != EINTR && errno != EAGAIN)
#endif /* !_WIN32 */
{
DESTROY(cData);
state = GS_H_UNCON;
NSLog(@"connect write attempt failed - %@",
[NSError _last]);
return;
}
#ifdef _WIN32
if (WSAGetLastError() == WSAEWOULDBLOCK)
{
readyToSend = NO;
}
#endif /* !_WIN32 */
}
else
{
cLength += len;
if (cLength >= l)
{
DESTROY(cData);
ASSIGN(defaultAddress, GSPrivateSockaddrHost(&sockAddr));
NSDebugMLLog(@"GSTcpHandle",
@"wrote %d bytes on 0x%"PRIxPTR, len, (NSUInteger)self);
state = GS_H_CONNECTED;
}
} }
RELEASE(d);
} }
} }
else else
{ {
int res; int res;
unsigned l; unsigned l;
const void *b; const void *b;
if (wData == nil) if (wData == nil)
@ -1165,7 +1325,21 @@ static Class runLoopClass;
} }
b = [wData bytes]; b = [wData bytes];
l = [wData length]; l = [wData length];
res = send(desc, b + wLength, l - wLength, 0); if (session)
{
if ([session handshake])
{
res = [session write: b + wLength length: l - wLength];
}
else
{
res = -1;
}
}
else
{
res = send(desc, b + wLength, l - wLength, 0);
}
if (res < 0) if (res < 0)
{ {
#ifdef _WIN32 #ifdef _WIN32
@ -1424,6 +1598,20 @@ static Class runLoopClass;
state = s; state = s;
} }
- (void) setupTLS: (NSSocketPort*)port asClient: (BOOL)outgoing
{
NSDictionary *opts;
if (nil == session && (opts = [port optionsForTLS]) != nil)
{
session = [[GSTLSSession alloc] initWithOptions: opts
direction: outgoing
transport: (void*)self
push: GSTLSHandlePush
pull: GSTLSHandlePull];
}
}
- (GSHandleState) state - (GSHandleState) state
{ {
return state; return state;
@ -1700,6 +1888,7 @@ static Class tcpPortClass;
NSMapInsert(thePorts, (void*)aHost, (void*)port); NSMapInsert(thePorts, (void*)aHost, (void*)port);
NSDebugMLLog(@"NSPort", @"Created speaking port: %@", port); NSDebugMLLog(@"NSPort", @"Created speaking port: %@", port);
} }
[port setOptionsForTLS: tlsOptions];
} }
else else
{ {
@ -1712,6 +1901,11 @@ static Class tcpPortClass;
return port; return port;
} }
+ (void) setOptionsForTLS: (NSDictionary*)options
{
ASSIGNCOPY(tlsOptions, options);
}
- (void) addHandle: (GSTcpHandle*)handle forSend: (BOOL)send - (void) addHandle: (GSTcpHandle*)handle forSend: (BOOL)send
{ {
M_LOCK(myLock); M_LOCK(myLock);
@ -1774,6 +1968,7 @@ static Class tcpPortClass;
NSFreeMapTable(handles); NSFreeMapTable(handles);
handles = 0; handles = 0;
} }
DESTROY(tlsopts);
DESTROY(host); DESTROY(host);
TEST_RELEASE(address); TEST_RELEASE(address);
DESTROY(myLock); DESTROY(myLock);
@ -1959,6 +2154,7 @@ static Class tcpPortClass;
} }
else else
{ {
[handle setupTLS: self asClient: YES];
[recvPort addHandle: handle forSend: NO]; [recvPort addHandle: handle forSend: NO];
} }
M_UNLOCK(myLock); M_UNLOCK(myLock);
@ -2085,6 +2281,16 @@ static Class tcpPortClass;
return NO; return NO;
} }
- (NSDictionary*) optionsForTLS
{
NSDictionary *opts;
M_LOCK(myLock);
opts = RETAIN(tlsopts);
M_UNLOCK(myLock);
return AUTORELEASE(opts);
}
- (uint16_t) portNumber - (uint16_t) portNumber
{ {
return portNum; return portNum;
@ -2142,8 +2348,8 @@ static Class tcpPortClass;
handle = [GSTcpHandle handleWithDescriptor: desc]; handle = [GSTcpHandle handleWithDescriptor: desc];
memcpy(&handle->sockAddr, &sockAddr, sizeof(sockAddr)); memcpy(&handle->sockAddr, &sockAddr, sizeof(sockAddr));
ASSIGN(handle->defaultAddress, GSPrivateSockaddrHost(&sockAddr)); ASSIGN(handle->defaultAddress, GSPrivateSockaddrHost(&sockAddr));
[handle setState: GS_H_ACCEPT]; [handle setState: GS_H_ACCEPT];
[handle setupTLS: self asClient: NO];
[self addHandle: handle forSend: NO]; [self addHandle: handle forSend: NO];
} }
} }
@ -2437,4 +2643,13 @@ static Class tcpPortClass;
return sent; return sent;
} }
/** Sets the TLS options for network connections created by this port.
*/
- (void) setOptionsForTLS: (NSDictionary*)options
{
M_LOCK(myLock);
ASSIGNCOPY(tlsopts, options);
M_UNLOCK(myLock);
}
@end @end