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>
* Source/GSStream.m:

View file

@ -208,6 +208,7 @@ GS_EXPORT_CLASS
uint16_t portNum; /* TCP port in host byte order. */
SOCKET listener;
NSMapTable *handles; /* Handles indexed by socket. */
NSDictionary *tlsopts; /* TLS options */
#if defined(_WIN32)
WSAEVENT eventListener;
NSMapTable *events;
@ -273,6 +274,21 @@ GS_EXPORT_CLASS
* Returns port number of underlying socket.
*/
- (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

View file

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

View file

@ -35,8 +35,10 @@
#import "Foundation/NSNotification.h"
#import "Foundation/NSProcessInfo.h"
#import "Foundation/NSStream.h"
#import "Foundation/NSTask.h"
#import "Foundation/NSThread.h"
#import "Foundation/NSUserDefaults.h"
#import "Foundation/NSUUID.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
defaultCAFile: (NSString*)dca
revokeFile: (NSString*)rv
@ -1322,6 +1407,7 @@ static NSMutableDictionary *credentialsCache = nil;
return [c autorelease];
}
- (void) dealloc
{
if (nil != name)
@ -1539,6 +1625,7 @@ retrieve_callback(gnutls_session_t session,
{
if (nil != (self = [super init]))
{
GSTLSCredentials *cr;
NSString *ca;
NSString *dca;
NSString *rv;
@ -1632,15 +1719,24 @@ retrieve_callback(gnutls_session_t session,
}
setup = YES;
cf = [opts objectForKey: GSTLSCertificateFile];
if (nil == cf && NO == outgoing)
{
/* Server with no certiticate supplied: generate self signed one.
*/
cr = [GSTLSCredentials selfSigned: debug];
}
else
{
ca = [opts objectForKey: GSTLSCAFile];
dca = caFile;
rv = [opts objectForKey: GSTLSRevokeFile];
drv = revokeFile;
cf = [opts objectForKey: GSTLSCertificateFile];
ck = [opts objectForKey: GSTLSCertificateKeyFile];
cp = [opts objectForKey: GSTLSCertificateKeyPassword];
credentials = [[GSTLSCredentials credentialsFromCAFile: ca
cr = [GSTLSCredentials credentialsFromCAFile: ca
defaultCAFile: dca
revokeFile: rv
defaultRevokeFile: drv
@ -1648,12 +1744,16 @@ retrieve_callback(gnutls_session_t session,
certificateKeyFile: ck
certificateKeyPassword: cp
asClient: outgoing
debug: debug] retain];
debug: debug];
}
if (nil == credentials)
if (cr)
{
[self release];
ASSIGN(credentials, cr);
}
else
{
RELEASE(self);
return nil;
}

View file

@ -26,6 +26,7 @@
#define EXPOSE_NSPort_IVARS 1
#define EXPOSE_NSSocketPort_IVARS 1
#import "GNUstepBase/GSLock.h"
#import "GNUstepBase/GSTLS.h"
#import "Foundation/NSArray.h"
#import "Foundation/NSNotification.h"
#import "Foundation/NSNotificationQueue.h"
@ -122,6 +123,10 @@
*/
static uint32_t maxDataLength = 32 * 1024 * 1024;
/* Options for TLS encryption of connections
*/
static NSDictionary *tlsOptions;
#if 0
#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];}
@ -200,9 +205,15 @@ typedef enum {
GS_H_CONNECTED // Currently connected.
} GSHandleState;
@interface NSSocketPort (GSTcpHandle)
- (NSDictionary*) optionsForTLS;
@end
@interface GSTcpHandle : NSObject <RunLoopEvents>
{
SOCKET desc; /* File descriptor for I/O. */
NSData *cData; /* Connection data. */
unsigned cLength; /* Connection data written. */
unsigned wItem; /* Index of item being written. */
NSMutableData *wData; /* Data object being written. */
unsigned wLength; /* Ammount written so far. */
@ -223,7 +234,6 @@ typedef enum {
BOOL inReplyMode; /* Indicate when have addEvent self */
BOOL readyToSend; /* Indicate when send */
#endif
@public
NSRecursiveLock *myLock; /* Lock for this handle. */
BOOL caller; /* Did we connect to other end? */
@ -232,6 +242,7 @@ typedef enum {
NSSocketPort *sendPort;
struct sockaddr sockAddr; /* Far end of connection. */
NSString *defaultAddress;
GSTLSSession *session; /* Session for encryption. */
}
+ (GSTcpHandle*) handleWithDescriptor: (SOCKET)d;
@ -253,9 +264,95 @@ typedef enum {
- (NSSocketPort*) sendPort;
- (void) setState: (GSHandleState)s;
- (GSHandleState) state;
- (void) setupTLS: (NSSocketPort*)opts asClient: (BOOL)outgoing;
@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.
*/
@ -696,6 +793,7 @@ static Class runLoopClass;
{
[self finalize];
DESTROY(defaultAddress);
DESTROY(cData);
DESTROY(rData);
DESTROY(rItems);
DESTROY(wMsgs);
@ -724,6 +822,11 @@ static Class runLoopClass;
- (void) finalize
{
[self invalidate];
if (session)
{
[session disconnect: NO];
DESTROY(session);
}
#if defined(_WIN32)
if (event != WSA_INVALID_EVENT)
{
@ -804,7 +907,21 @@ static Class runLoopClass;
* Now try to fill the buffer with data.
*/
bytes = [rData mutableBytes];
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)
@ -1123,23 +1240,66 @@ static Class runLoopClass;
}
else
{
NSData *d = newDataWithEncodedPort([self recvPort]);
unsigned l;
const void *b;
len = send(desc, [d bytes], [d length], 0);
if (len == (int)[d length])
if (nil == cData)
{
ASSIGN(cData, newDataWithEncodedPort([self recvPort]));
cLength = 0;
}
b = [cData bytes];
l = [cData length];
if (session)
{
if ([session handshake])
{
len = [session write: b + cLength length: l - cLength];
}
else
{
len = -1;
}
}
else
{
len = send(desc, b + cLength, l - cLength, 0);
}
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;
}
else
{
state = GS_H_UNCON;
NSLog(@"connect write attempt failed - %@",
[NSError _last]);
}
RELEASE(d);
}
}
else
@ -1165,7 +1325,21 @@ static Class runLoopClass;
}
b = [wData bytes];
l = [wData length];
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)
{
#ifdef _WIN32
@ -1424,6 +1598,20 @@ static Class runLoopClass;
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
{
return state;
@ -1700,6 +1888,7 @@ static Class tcpPortClass;
NSMapInsert(thePorts, (void*)aHost, (void*)port);
NSDebugMLLog(@"NSPort", @"Created speaking port: %@", port);
}
[port setOptionsForTLS: tlsOptions];
}
else
{
@ -1712,6 +1901,11 @@ static Class tcpPortClass;
return port;
}
+ (void) setOptionsForTLS: (NSDictionary*)options
{
ASSIGNCOPY(tlsOptions, options);
}
- (void) addHandle: (GSTcpHandle*)handle forSend: (BOOL)send
{
M_LOCK(myLock);
@ -1774,6 +1968,7 @@ static Class tcpPortClass;
NSFreeMapTable(handles);
handles = 0;
}
DESTROY(tlsopts);
DESTROY(host);
TEST_RELEASE(address);
DESTROY(myLock);
@ -1959,6 +2154,7 @@ static Class tcpPortClass;
}
else
{
[handle setupTLS: self asClient: YES];
[recvPort addHandle: handle forSend: NO];
}
M_UNLOCK(myLock);
@ -2085,6 +2281,16 @@ static Class tcpPortClass;
return NO;
}
- (NSDictionary*) optionsForTLS
{
NSDictionary *opts;
M_LOCK(myLock);
opts = RETAIN(tlsopts);
M_UNLOCK(myLock);
return AUTORELEASE(opts);
}
- (uint16_t) portNumber
{
return portNum;
@ -2142,8 +2348,8 @@ static Class tcpPortClass;
handle = [GSTcpHandle handleWithDescriptor: desc];
memcpy(&handle->sockAddr, &sockAddr, sizeof(sockAddr));
ASSIGN(handle->defaultAddress, GSPrivateSockaddrHost(&sockAddr));
[handle setState: GS_H_ACCEPT];
[handle setupTLS: self asClient: NO];
[self addHandle: handle forSend: NO];
}
}
@ -2437,4 +2643,13 @@ static Class tcpPortClass;
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