Index: .fossil-settings/clean-glob ================================================================== --- .fossil-settings/clean-glob +++ .fossil-settings/clean-glob @@ -30,10 +30,11 @@ src/bridge/Info.plist src/libobjfw.* src/objfw-defs.h src/runtime/Info.plist src/runtime/libobjfwrt.* +src/tls/Info.plist tests/DerivedData tests/EBOOT.PBP tests/Info.plist tests/PARAM.SFO tests/objc_sync/objc_sync Index: .fossil-settings/ignore-glob ================================================================== --- .fossil-settings/ignore-glob +++ .fossil-settings/ignore-glob @@ -32,10 +32,11 @@ src/bridge/Info.plist src/libobjfw.* src/objfw-defs.h src/runtime/Info.plist src/runtime/libobjfwrt.* +src/tls/Info.plist tests/DerivedData tests/EBOOT.PBP tests/Info.plist tests/PARAM.SFO tests/iOS.xcodeproj/*.pbxuser Index: .gitignore ================================================================== --- .gitignore +++ .gitignore @@ -32,10 +32,11 @@ src/bridge/Info.plist src/libobjfw.* src/objfw-defs.h src/runtime/Info.plist src/runtime/libobjfwrt.* +src/tls/Info.plist tests/DerivedData tests/EBOOT.PBP tests/Info.plist tests/PARAM.SFO tests/iOS.xcodeproj/*.pbxuser Index: configure.ac ================================================================== --- configure.ac +++ configure.ac @@ -1534,21 +1534,49 @@ "OFSelectKernelEventObserver.m") ]) ;; esac + tls_support="no" AC_CHECK_HEADERS(Security/SecureTransport.h, [ old_LIBS="$LIBS" LIBS="-framework Security -framework Foundation $LIBS" AC_CHECK_FUNC(SSLHandshake, [ AC_DEFINE(HAVE_SECURE_TRANSPORT, 1, [Whether we have Secure Transport]) + + tls_support="securetransport" + TLS_LIBS="-framework Foundation $TLS_LIBS" + TLS_LIBS="-framework Security $TLS_LIBS" + AC_SUBST(OF_SECURE_TRANSPORT_TLS_STREAM_M, "OFSecureTransportTLSStream.m") - ], [ - LIBS="$old_LIBS" + ], []) + + LIBS="$old_LIBS" + ]) + + AS_IF([test x"$tls_support" != x"no"], [ + AC_SUBST(TLS, "tls") + AC_SUBST(TLS_LIBS) + AC_DEFINE(HAVE_TLS_SUPPORT, 1, + [Whether we have an implementation for TLS]) + AC_CONFIG_FILES(src/tls/Info.plist) + + OFHTTP_LIBS="-lobjfwtls $TLS_LIBS $OFHTTP_LIBS" + + AS_IF([test x"$enable_shared" != x"no"], [ + AC_SUBST(OBJFWTLS_SHARED_LIB, + "${LIB_PREFIX}objfwtls${LIB_SUFFIX}") + ]) + AS_IF([test x"$enable_static" = x"yes" \ + -o x"$enable_shared" = x"no"], [ + AC_SUBST(OBJFWTLS_STATIC_LIB, "libobjfwtls.a") + ]) + AS_IF([test x"$build_framework" = x"yes"], [ + AC_SUBST(OBJFWTLS_FRAMEWORK, "ObjFWTLS.framework") ]) ]) AS_IF([test x"$enable_threads" != x"no"], [ AC_SUBST(OF_HTTP_CLIENT_TESTS_M, "OFHTTPClientTests.m") @@ -1555,10 +1583,11 @@ ]) AC_SUBST(OFDNS, "ofdns") AS_IF([test x"$enable_files" != x"no"], [ AC_SUBST(OFHTTP, "ofhttp") + AC_SUBST(OFHTTP_LIBS) ]) ]) AC_DEFUN([CHECK_BUILTIN_BSWAP], [ AC_MSG_CHECKING(for __builtin_bswap$1) Index: extra.mk.in ================================================================== --- extra.mk.in +++ extra.mk.in @@ -15,10 +15,14 @@ OBJFWBRIDGE_SHARED_LIB = @OBJFWBRIDGE_SHARED_LIB@ OBJFWBRIDGE_STATIC_LIB = @OBJFWBRIDGE_STATIC_LIB@ OBJFWBRIDGE_FRAMEWORK = @OBJFWBRIDGE_FRAMEWORK@ +OBJFWTLS_SHARED_LIB = @OBJFWTLS_SHARED_LIB@ +OBJFWTLS_STATIC_LIB = @OBJFWTLS_STATIC_LIB@ +OBJFWTLS_FRAMEWORK = @OBJFWTLS_FRAMEWORK@ + BIN_PREFIX = @BIN_PREFIX@ BRIDGE = @BRIDGE@ CVINCLUDE_INLINE_H = @CVINCLUDE_INLINE_H@ ENCODINGS_A = @ENCODINGS_A@ ENCODINGS_LIB_A = @ENCODINGS_LIB_A@ @@ -41,10 +45,11 @@ OBJC_SYNC = @OBJC_SYNC@ OFARC = @OFARC@ OFDNS = @OFDNS@ OFHASH = @OFHASH@ OFHTTP = @OFHTTP@ +OFHTTP_LIBS = @OFHTTP_LIBS@ OF_BLOCK_TESTS_M = @OF_BLOCK_TESTS_M@ OF_EPOLL_KERNEL_EVENT_OBSERVER_M = @OF_EPOLL_KERNEL_EVENT_OBSERVER_M@ OF_HTTP_CLIENT_TESTS_M = @OF_HTTP_CLIENT_TESTS_M@ OF_KQUEUE_KERNEL_EVENT_OBSERVER_M = @OF_KQUEUE_KERNEL_EVENT_OBSERVER_M@ OF_POLL_KERNEL_EVENT_OBSERVER_M = @OF_POLL_KERNEL_EVENT_OBSERVER_M@ @@ -64,10 +69,12 @@ SFD_FILE = @SFD_FILE@ TESTPLUGIN = @TESTPLUGIN@ TESTPLUGIN_LIBS = @TESTPLUGIN_LIBS@ TESTS_LIBS = @TESTS_LIBS@ TESTS_STATIC_LIB = @TESTS_STATIC_LIB@ +TLS = @TLS@ +TLS_LIBS = @TLS_LIBS@ UNICODE_M = @UNICODE_M@ USE_INCLUDES_ATOMIC = @USE_INCLUDES_ATOMIC@ USE_SRCS_FILES = @USE_SRCS_FILES@ USE_SRCS_IPX = @USE_SRCS_IPX@ USE_SRCS_PLUGINS = @USE_SRCS_PLUGINS@ Index: src/Makefile ================================================================== --- src/Makefile +++ src/Makefile @@ -1,9 +1,9 @@ include ../extra.mk SUBDIRS = ${RUNTIME} exceptions encodings forwarding -SUBDIRS_AFTER = ${BRIDGE} +SUBDIRS_AFTER = ${BRIDGE} ${TLS} DISTCLEAN = Info.plist objfw-defs.h SHARED_LIB = ${OBJFW_SHARED_LIB} STATIC_LIB = ${OBJFW_STATIC_LIB} FRAMEWORK = ${OBJFW_FRAMEWORK} @@ -214,11 +214,10 @@ OFHostAddressResolver.m \ OFIPSocketAsyncConnector.m \ OFKernelEventObserver.m \ ${OF_KQUEUE_KERNEL_EVENT_OBSERVER_M} \ ${OF_POLL_KERNEL_EVENT_OBSERVER_M} \ - ${OF_SECURE_TRANSPORT_TLS_STREAM_M} \ ${OF_SELECT_KERNEL_EVENT_OBSERVER_M} \ OFTCPSocketSOCKS5Connector.m OBJS_EXTRA = exceptions/exceptions.a \ encodings/encodings.a \ DELETED src/OFSecureTransportTLSStream.h Index: src/OFSecureTransportTLSStream.h ================================================================== --- src/OFSecureTransportTLSStream.h +++ src/OFSecureTransportTLSStream.h @@ -1,27 +0,0 @@ -/* - * Copyright (c) 2008-2021 Jonathan Schleifer - * - * All rights reserved. - * - * This file is part of ObjFW. It may be distributed under the terms of the - * Q Public License 1.0, which can be found in the file LICENSE.QPL included in - * the packaging of this file. - * - * Alternatively, it may be distributed under the terms of the GNU General - * Public License, either version 2 or 3, which can be found in the file - * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this - * file. - */ - -#import "OFTLSStream.h" - -OF_ASSUME_NONNULL_BEGIN - -@interface OFSecureTransportTLSStream: OFTLSStream -{ - struct SSLContext *_context; - OFString *_host; -} -@end - -OF_ASSUME_NONNULL_END DELETED src/OFSecureTransportTLSStream.m Index: src/OFSecureTransportTLSStream.m ================================================================== --- src/OFSecureTransportTLSStream.m +++ src/OFSecureTransportTLSStream.m @@ -1,251 +0,0 @@ -/* - * Copyright (c) 2008-2021 Jonathan Schleifer - * - * All rights reserved. - * - * This file is part of ObjFW. It may be distributed under the terms of the - * Q Public License 1.0, which can be found in the file LICENSE.QPL included in - * the packaging of this file. - * - * Alternatively, it may be distributed under the terms of the GNU General - * Public License, either version 2 or 3, which can be found in the file - * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this - * file. - */ - -#include "config.h" - -#import "OFSecureTransportTLSStream.h" - -#include - -#import "OFAlreadyConnectedException.h" -#import "OFNotOpenException.h" -#import "OFReadFailedException.h" -#import "OFTLSHandshakeFailedException.h" -#import "OFWriteFailedException.h" - -static OSStatus -readFunc(SSLConnectionRef connection, void *data, size_t *dataLength) -{ - bool incomplete; - size_t length = [((OFTLSStream *)connection).wrappedStream - readIntoBuffer: data - length: *dataLength]; - - incomplete = (length < *dataLength); - *dataLength = length; - - return (incomplete ? errSSLWouldBlock : noErr); -} - -static OSStatus -writeFunc(SSLConnectionRef connection, const void *data, size_t *dataLength) -{ - @try { - [((OFTLSStream *)connection).wrappedStream - writeBuffer: data - length: *dataLength]; - } @catch (OFWriteFailedException *e) { - *dataLength = e.bytesWritten; - - if (e.errNo == EWOULDBLOCK) - return errSSLWouldBlock; - - @throw e; - } - - return noErr; -} - -/* - * Apple deprecated Secure Transport without providing a replacement that can - * work with any socket. On top of that, their replacement, Network.framework, - * doesn't support STARTTLS at all. - */ -#pragma GCC diagnostic ignored "-Wdeprecated" - -@implementation OFSecureTransportTLSStream -- (instancetype)initWithStream: (OFStream *)stream -{ - self = [super initWithStream: stream]; - - @try { - _wrappedStream.delegate = self; - } @catch (id e) { - [self release]; - @throw e; - } - - return self; -} - -- (void)dealloc -{ - if (_context != NULL) - [self close]; - - [_host release]; - - [super dealloc]; -} - -- (void)close -{ - if (_context == NULL) - @throw [OFNotOpenException exceptionWithObject: self]; - - SSLClose(_context); - CFRelease(_context); - _context = NULL; - - [super close]; -} - -- (size_t)lowlevelReadIntoBuffer: (void *)buffer length: (size_t)length -{ - OSStatus status; - size_t ret; - - if (_context == NULL) - @throw [OFNotOpenException exceptionWithObject: self]; - - status = SSLRead(_context, buffer, length, &ret); - if (status != noErr && status != errSSLWouldBlock) - /* FIXME: Translate status to errNo */ - @throw [OFReadFailedException exceptionWithObject: self - requestedLength: length - errNo: 0]; - - return ret; -} - -- (size_t)lowlevelWriteBuffer: (const void *)buffer length: (size_t)length -{ - OSStatus status; - size_t ret = 0; - - if (_context == NULL) - @throw [OFNotOpenException exceptionWithObject: self]; - - status = SSLWrite(_context, buffer, length, &ret); - if (status != noErr && status != errSSLWouldBlock) - /* FIXME: Translate status to errNo */ - @throw [OFWriteFailedException exceptionWithObject: self - requestedLength: length - bytesWritten: ret - errNo: 0]; - - return ret; -} - -- (bool)hasDataInReadBuffer -{ - size_t bufferSize; - - if (SSLGetBufferedReadSize(_context, &bufferSize) == noErr && - bufferSize > 0) - return true; - - return super.hasDataInReadBuffer; -} - -- (void)asyncPerformClientHandshakeWithHost: (OFString *)host - runLoopMode: (OFRunLoopMode)runLoopMode -{ - static const OFTLSStreamErrorCode initFailedErrorCode = - OFTLSStreamErrorCodeInitializationFailed; - id exception = nil; - OSStatus status; - - if (_context != NULL) - @throw [OFAlreadyConnectedException exceptionWithSocket: self]; - - if ((_context = SSLCreateContext(kCFAllocatorDefault, kSSLClientSide, - kSSLStreamType)) == NULL) - @throw [OFTLSHandshakeFailedException - exceptionWithStream: self - host: host - errorCode: initFailedErrorCode]; - - if (SSLSetIOFuncs(_context, readFunc, writeFunc) != noErr || - SSLSetConnection(_context, self) != noErr) - @throw [OFTLSHandshakeFailedException - exceptionWithStream: self - host: host - errorCode: initFailedErrorCode]; - - if (_verifiesCertificates) - if (SSLSetPeerDomainName(_context, - host.UTF8String, host.UTF8StringLength) != noErr) - @throw [OFTLSHandshakeFailedException - exceptionWithStream: self - host: host - errorCode: initFailedErrorCode]; - - status = SSLHandshake(_context); - - if (status == errSSLWouldBlock) { - /* - * Theoretically it is possible we block because Secure - * Transport cannot write without blocking. But unfortunately, - * Secure Transport does not tell us whether it's blocked on - * reading or writing. Waiting for the stream to be either - * readable or writable doesn't work either, as the stream is - * almost always at least ready for one of the two. - */ - [_wrappedStream asyncReadIntoBuffer: (void *)"" - length: 0 - runLoopMode: runLoopMode]; - [_delegate retain]; - _host = [host copy]; - return; - } - - if (status != noErr) - /* FIXME: Map to better errors */ - exception = [OFTLSHandshakeFailedException - exceptionWithStream: self - host: host - errorCode: OFTLSStreamErrorCodeUnknown]; - - if ([_delegate respondsToSelector: - @selector(stream:didPerformClientHandshakeWithHost:exception:)]) - [_delegate stream: self - didPerformClientHandshakeWithHost: host - exception: exception]; -} - -- (bool)stream: (OFStream *)stream - didReadIntoBuffer: (void *)buffer - length: (size_t)length - exception: (nullable id)exception -{ - if (exception == nil) { - OSStatus status = SSLHandshake(_context); - - if (status == errSSLWouldBlock) - return true; - - if (status != noErr) - exception = [OFTLSHandshakeFailedException - exceptionWithStream: self - host: _host - errorCode: OFTLSStreamErrorCodeUnknown]; - } - - if ([_delegate respondsToSelector: - @selector(stream:didPerformClientHandshakeWithHost:exception:)]) - [_delegate stream: self - didPerformClientHandshakeWithHost: _host - exception: exception]; - - [_host release]; - _host = nil; - - [_delegate release]; - - return false; -} -@end Index: src/OFTLSStream.m ================================================================== --- src/OFTLSStream.m +++ src/OFTLSStream.m @@ -15,15 +15,13 @@ #include "config.h" #import "OFTLSStream.h" #import "OFDate.h" -#ifdef HAVE_SECURE_TRANSPORT -# import "OFSecureTransportTLSStream.h" -#endif #import "OFNotImplementedException.h" +#import "OFTLSHandshakeFailedException.h" @interface OFTLSStreamHandshakeDelegate: OFObject { @public bool _done; @@ -32,10 +30,20 @@ @end Class OFTLSStreamImplementation = Nil; static const OFRunLoopMode handshakeRunLoopMode = @"OFTLSStreamHandshakeRunLoopMode"; + +/* + * References to exceptions. This is needed because they are only used by + * subclasses that are in a different library. + */ +void +_references_to_exceptions_of_OFTLSStream(void) +{ + _OFTLSHandshakeFailedException_reference = 1; +} OFString * OFTLSStreamErrorCodeDescription(OFTLSStreamErrorCode errorCode) { switch (errorCode) { @@ -72,16 +80,12 @@ { if (self == [OFTLSStream class]) { if (OFTLSStreamImplementation != Nil) return [OFTLSStreamImplementation alloc]; -#ifdef HAVE_SECURE_TRANSPORT - return [OFSecureTransportTLSStream alloc]; -#else @throw [OFNotImplementedException exceptionWithSelector: _cmd object: self]; -#endif } return [super alloc]; } Index: src/exceptions/OFTLSHandshakeFailedException.h ================================================================== --- src/exceptions/OFTLSHandshakeFailedException.h +++ src/exceptions/OFTLSHandshakeFailedException.h @@ -20,10 +20,18 @@ #endif #import "OFTLSStream.h" OF_ASSUME_NONNULL_BEGIN + +#ifdef __cplusplus +extern "C" { +#endif +extern int _OFTLSHandshakeFailedException_reference; +#ifdef __cplusplus +} +#endif /** * @class OFTLSHandshakeFailedException \ * OFTLSHandshakeFailedException.h ObjFW/OFTLSHandshakeFailedException.h * Index: src/exceptions/OFTLSHandshakeFailedException.m ================================================================== --- src/exceptions/OFTLSHandshakeFailedException.m +++ src/exceptions/OFTLSHandshakeFailedException.m @@ -15,10 +15,12 @@ #include "config.h" #import "OFTLSHandshakeFailedException.h" #import "OFString.h" + +int _OFTLSHandshakeFailedException_reference; @implementation OFTLSHandshakeFailedException @synthesize stream = _stream, host = _host, errorCode = _errorCode; + (instancetype)exception ADDED src/tls/Info.plist.in Index: src/tls/Info.plist.in ================================================================== --- src/tls/Info.plist.in +++ src/tls/Info.plist.in @@ -0,0 +1,22 @@ + + + + + CFBundleExecutable + ObjFWTLS + CFBundleName + ObjFWTLS + CFBundleIdentifier + im.nil.objfw.tls + CFBundleInfoDictionaryVersion + 6.0 + CFBundlePackageType + FMWK + CFBundleVersion + @BUNDLE_VERSION@ + CFBundleShortVersionString + @BUNDLE_SHORT_VERSION@ + MinimumOSVersion + 9.0 + + ADDED src/tls/Makefile Index: src/tls/Makefile ================================================================== --- src/tls/Makefile +++ src/tls/Makefile @@ -0,0 +1,21 @@ +include ../../extra.mk + +DISTCLEAN = Info.plist + +SHARED_LIB = ${OBJFWTLS_SHARED_LIB} +STATIC_LIB = ${OBJFWTLS_STATIC_LIB} +FRAMEWORK = ${OBJFWTLS_FRAMEWORK} +LIB_MAJOR = ${OBJFW_LIB_MAJOR} +LIB_MINOR = ${OBJFW_LIB_MINOR} + +INCLUDES := ObjFWTLS.h +SRCS = ${OF_SECURE_TRANSPORT_TLS_STREAM_M} + +includesubdir = ObjFWTLS + +include ../../buildsys.mk + +CPPFLAGS += -I. -I.. -I../.. -I../exceptions +LD = ${OBJC} +FRAMEWORK_LIBS := ${TLS_LIBS} -F.. -framework ObjFW ${LIBS} +LIBS := ${TLS_LIBS} -L.. -lobjfw ${LIBS} ADDED src/tls/OFSecureTransportTLSStream.h Index: src/tls/OFSecureTransportTLSStream.h ================================================================== --- src/tls/OFSecureTransportTLSStream.h +++ src/tls/OFSecureTransportTLSStream.h @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2008-2021 Jonathan Schleifer + * + * All rights reserved. + * + * This file is part of ObjFW. It may be distributed under the terms of the + * Q Public License 1.0, which can be found in the file LICENSE.QPL included in + * the packaging of this file. + * + * Alternatively, it may be distributed under the terms of the GNU General + * Public License, either version 2 or 3, which can be found in the file + * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this + * file. + */ + +#import "OFTLSStream.h" + +OF_ASSUME_NONNULL_BEGIN + +extern int _OFSecureTransportTLSSocket_reference; + +@interface OFSecureTransportTLSStream: OFTLSStream +{ + struct SSLContext *_context; + OFString *_host; +} +@end + +OF_ASSUME_NONNULL_END ADDED src/tls/OFSecureTransportTLSStream.m Index: src/tls/OFSecureTransportTLSStream.m ================================================================== --- src/tls/OFSecureTransportTLSStream.m +++ src/tls/OFSecureTransportTLSStream.m @@ -0,0 +1,259 @@ +/* + * Copyright (c) 2008-2021 Jonathan Schleifer + * + * All rights reserved. + * + * This file is part of ObjFW. It may be distributed under the terms of the + * Q Public License 1.0, which can be found in the file LICENSE.QPL included in + * the packaging of this file. + * + * Alternatively, it may be distributed under the terms of the GNU General + * Public License, either version 2 or 3, which can be found in the file + * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this + * file. + */ + +#include "config.h" + +#import "OFSecureTransportTLSStream.h" + +#include + +#import "OFAlreadyConnectedException.h" +#import "OFNotOpenException.h" +#import "OFReadFailedException.h" +#import "OFTLSHandshakeFailedException.h" +#import "OFWriteFailedException.h" + +int _ObjFWTLS_reference; + +static OSStatus +readFunc(SSLConnectionRef connection, void *data, size_t *dataLength) +{ + bool incomplete; + size_t length = [((OFTLSStream *)connection).wrappedStream + readIntoBuffer: data + length: *dataLength]; + + incomplete = (length < *dataLength); + *dataLength = length; + + return (incomplete ? errSSLWouldBlock : noErr); +} + +static OSStatus +writeFunc(SSLConnectionRef connection, const void *data, size_t *dataLength) +{ + @try { + [((OFTLSStream *)connection).wrappedStream + writeBuffer: data + length: *dataLength]; + } @catch (OFWriteFailedException *e) { + *dataLength = e.bytesWritten; + + if (e.errNo == EWOULDBLOCK) + return errSSLWouldBlock; + + @throw e; + } + + return noErr; +} + +/* + * Apple deprecated Secure Transport without providing a replacement that can + * work with any socket. On top of that, their replacement, Network.framework, + * doesn't support STARTTLS at all. + */ +#pragma GCC diagnostic ignored "-Wdeprecated" + +@implementation OFSecureTransportTLSStream ++ (void)load +{ + if (OFTLSStreamImplementation == Nil) + OFTLSStreamImplementation = self; +} + +- (instancetype)initWithStream: (OFStream *)stream +{ + self = [super initWithStream: stream]; + + @try { + _wrappedStream.delegate = self; + } @catch (id e) { + [self release]; + @throw e; + } + + return self; +} + +- (void)dealloc +{ + if (_context != NULL) + [self close]; + + [_host release]; + + [super dealloc]; +} + +- (void)close +{ + if (_context == NULL) + @throw [OFNotOpenException exceptionWithObject: self]; + + SSLClose(_context); + CFRelease(_context); + _context = NULL; + + [super close]; +} + +- (size_t)lowlevelReadIntoBuffer: (void *)buffer length: (size_t)length +{ + OSStatus status; + size_t ret; + + if (_context == NULL) + @throw [OFNotOpenException exceptionWithObject: self]; + + status = SSLRead(_context, buffer, length, &ret); + if (status != noErr && status != errSSLWouldBlock) + /* FIXME: Translate status to errNo */ + @throw [OFReadFailedException exceptionWithObject: self + requestedLength: length + errNo: 0]; + + return ret; +} + +- (size_t)lowlevelWriteBuffer: (const void *)buffer length: (size_t)length +{ + OSStatus status; + size_t ret = 0; + + if (_context == NULL) + @throw [OFNotOpenException exceptionWithObject: self]; + + status = SSLWrite(_context, buffer, length, &ret); + if (status != noErr && status != errSSLWouldBlock) + /* FIXME: Translate status to errNo */ + @throw [OFWriteFailedException exceptionWithObject: self + requestedLength: length + bytesWritten: ret + errNo: 0]; + + return ret; +} + +- (bool)hasDataInReadBuffer +{ + size_t bufferSize; + + if (SSLGetBufferedReadSize(_context, &bufferSize) == noErr && + bufferSize > 0) + return true; + + return super.hasDataInReadBuffer; +} + +- (void)asyncPerformClientHandshakeWithHost: (OFString *)host + runLoopMode: (OFRunLoopMode)runLoopMode +{ + static const OFTLSStreamErrorCode initFailedErrorCode = + OFTLSStreamErrorCodeInitializationFailed; + id exception = nil; + OSStatus status; + + if (_context != NULL) + @throw [OFAlreadyConnectedException exceptionWithSocket: self]; + + if ((_context = SSLCreateContext(kCFAllocatorDefault, kSSLClientSide, + kSSLStreamType)) == NULL) + @throw [OFTLSHandshakeFailedException + exceptionWithStream: self + host: host + errorCode: initFailedErrorCode]; + + if (SSLSetIOFuncs(_context, readFunc, writeFunc) != noErr || + SSLSetConnection(_context, self) != noErr) + @throw [OFTLSHandshakeFailedException + exceptionWithStream: self + host: host + errorCode: initFailedErrorCode]; + + if (_verifiesCertificates) + if (SSLSetPeerDomainName(_context, + host.UTF8String, host.UTF8StringLength) != noErr) + @throw [OFTLSHandshakeFailedException + exceptionWithStream: self + host: host + errorCode: initFailedErrorCode]; + + status = SSLHandshake(_context); + + if (status == errSSLWouldBlock) { + /* + * Theoretically it is possible we block because Secure + * Transport cannot write without blocking. But unfortunately, + * Secure Transport does not tell us whether it's blocked on + * reading or writing. Waiting for the stream to be either + * readable or writable doesn't work either, as the stream is + * almost always at least ready for one of the two. + */ + [_wrappedStream asyncReadIntoBuffer: (void *)"" + length: 0 + runLoopMode: runLoopMode]; + [_delegate retain]; + _host = [host copy]; + return; + } + + if (status != noErr) + /* FIXME: Map to better errors */ + exception = [OFTLSHandshakeFailedException + exceptionWithStream: self + host: host + errorCode: OFTLSStreamErrorCodeUnknown]; + + if ([_delegate respondsToSelector: + @selector(stream:didPerformClientHandshakeWithHost:exception:)]) + [_delegate stream: self + didPerformClientHandshakeWithHost: host + exception: exception]; +} + +- (bool)stream: (OFStream *)stream + didReadIntoBuffer: (void *)buffer + length: (size_t)length + exception: (nullable id)exception +{ + if (exception == nil) { + OSStatus status = SSLHandshake(_context); + + if (status == errSSLWouldBlock) + return true; + + if (status != noErr) + exception = [OFTLSHandshakeFailedException + exceptionWithStream: self + host: _host + errorCode: OFTLSStreamErrorCodeUnknown]; + } + + if ([_delegate respondsToSelector: + @selector(stream:didPerformClientHandshakeWithHost:exception:)]) + [_delegate stream: self + didPerformClientHandshakeWithHost: _host + exception: exception]; + + [_host release]; + _host = nil; + + [_delegate release]; + + return false; +} +@end ADDED src/tls/ObjFWTLS.h Index: src/tls/ObjFWTLS.h ================================================================== --- src/tls/ObjFWTLS.h +++ src/tls/ObjFWTLS.h @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2008-2021 Jonathan Schleifer + * + * All rights reserved. + * + * This file is part of ObjFW. It may be distributed under the terms of the + * Q Public License 1.0, which can be found in the file LICENSE.QPL included in + * the packaging of this file. + * + * Alternatively, it may be distributed under the terms of the GNU General + * Public License, either version 2 or 3, which can be found in the file + * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this + * file. + */ + +#import "macros.h" + +#ifdef __cplusplus +extern "C" { +#endif +extern int _ObjFWTLS_reference; +#ifdef __cplusplus +} +#endif Index: utils/ofhttp/Makefile ================================================================== --- utils/ofhttp/Makefile +++ utils/ofhttp/Makefile @@ -13,14 +13,15 @@ ${PROG}: ${LIBOBJFW_DEP_LVL2} ${LIBOBJFWRT_DEP_LVL2} CPPFLAGS += -I../../src \ -I../../src/runtime \ -I../../src/exceptions \ + -I../../src/tls \ -I../.. \ -DLANGUAGE_DIR='"${datadir}/ofhttp/lang"' \ -DLIB_PREFIX='"${LIB_PREFIX}"' \ -DLIB_SUFFIX='"${LIB_SUFFIX}"' -LIBS := -L../../src -lobjfw \ +LIBS := -L../../src -L../../src/tls ${OFHTTP_LIBS} -lobjfw \ -L../../src/runtime -L../../src/runtime/linklib ${RUNTIME_LIBS} \ ${LIBS} LD = ${OBJC} LDFLAGS += ${LDFLAGS_RPATH} Index: utils/ofhttp/OFHTTP.m ================================================================== --- utils/ofhttp/OFHTTP.m +++ utils/ofhttp/OFHTTP.m @@ -33,10 +33,14 @@ #import "OFStdIOStream.h" #import "OFSystemInfo.h" #import "OFTCPSocket.h" #import "OFTLSStream.h" #import "OFURL.h" + +#ifdef HAVE_TLS_SUPPORT +# import "ObjFWTLS.h" +#endif #import "OFConnectionFailedException.h" #import "OFHTTPRequestFailedException.h" #import "OFInvalidArgumentException.h" #import "OFInvalidFormatException.h" @@ -75,10 +79,18 @@ ProgressBar *_progressBar; } - (void)downloadNextURL; @end + +#ifdef HAVE_TLS_SUPPORT +void +_reference_to_ObjFWTLS(void) +{ + _ObjFWTLS_reference = 1; +} +#endif OF_APPLICATION_DELEGATE(OFHTTP) static void help(OFStream *stream, bool full, int status)