Index: configure.ac ================================================================== --- configure.ac +++ configure.ac @@ -1887,12 +1887,12 @@ AC_CHECK_FUNC(SSLHandshake, [ tls_support="Secure Transport" TLS_LIBS="-framework Foundation $TLS_LIBS" TLS_LIBS="-framework Security $TLS_LIBS" - AC_SUBST(OF_SECURE_TRANSPORT_TLS_STREAM_M, - "OFSecureTransportTLSStream.m") + AC_SUBST(USE_SRCS_SECURETRANSPORT, + '${SRCS_SECURETRANSPORT}') AC_CHECK_FUNCS(SSLCreateContext) ], []) LIBS="$old_LIBS" @@ -1915,12 +1915,11 @@ AC_CHECK_LIB($ssl, SSL_set1_host, [ AC_CHECK_HEADER(openssl/ssl.h, [ tls_support="OpenSSL" TLS_LIBS="-l$ssl -l$crypto $TLS_LIBS" - AC_SUBST(OF_OPENSSL_TLS_STREAM_M, - "OFOpenSSLTLSStream.m") + AC_SUBST(USE_SRCS_OPENSSL, '${SRCS_OPENSSL}') old_LIBS="$LIBS" LIBS="$TLS_LIBS $LIBS" AC_CHECK_FUNCS(SSL_has_pending) LIBS="$old_LIBS" @@ -1933,11 +1932,11 @@ PKG_CHECK_MODULES(gnutls, [gnutls >= 3.5.0], [ tls_support="GnuTLS" TLS_CPPFLAGS="$gnutls_CFLAGS $TLS_CPPFLAGS" TLS_LIBS="$gnutls_LIBS $TLS_LIBS" - AC_SUBST(OF_GNUTLS_TLS_STREAM_M, "OFGnuTLSTLSStream.m") + AC_SUBST(USE_SRCS_GNUTLS, '${SRCS_GNUTLS}') ], [ dnl Disable default action-if-not-found, which exits dnl configure with an error. : ]) @@ -1948,12 +1947,11 @@ AC_CHECK_HEADER(mbedtls/ssl.h, [ tls_support="Mbed TLS" TLS_LIBS="-lmbedx509 -lmbedcrypto $TLS_LIBS" TLS_LIBS="-lmbedtls $TLS_LIBS" - AC_SUBST(OF_MBEDTLS_TLS_STREAM_M, - "OFMbedTLSTLSStream.m") + AC_SUBST(USE_SRCS_MBEDTLS, '${SRCS_MBEDTLS}') ]) ], [], [-lmbedx509 -lmbedcrypto]) ]) AS_IF([test x"$tls_support" != x"no"], [ Index: extra.mk.in ================================================================== --- extra.mk.in +++ extra.mk.in @@ -66,18 +66,14 @@ OFHASH_LIBS = @OFHTTP_LIBS@ 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_GNUTLS_TLS_STREAM_M = @OF_GNUTLS_TLS_STREAM_M@ OF_HTTP_CLIENT_TESTS_M = @OF_HTTP_CLIENT_TESTS_M@ OF_KQUEUE_KERNEL_EVENT_OBSERVER_M = @OF_KQUEUE_KERNEL_EVENT_OBSERVER_M@ -OF_MBEDTLS_TLS_STREAM_M = @OF_MBEDTLS_TLS_STREAM_M@ -OF_OPENSSL_TLS_STREAM_M = @OF_OPENSSL_TLS_STREAM_M@ OF_POLL_KERNEL_EVENT_OBSERVER_M = @OF_POLL_KERNEL_EVENT_OBSERVER_M@ OF_SCTP_SOCKET_M = @OF_SCTP_SOCKET_M@ -OF_SECURE_TRANSPORT_TLS_STREAM_M = @OF_SECURE_TRANSPORT_TLS_STREAM_M@ OF_SELECT_KERNEL_EVENT_OBSERVER_M = @OF_SELECT_KERNEL_EVENT_OBSERVER_M@ REEXPORT_RUNTIME = @REEXPORT_RUNTIME@ REEXPORT_RUNTIME_FRAMEWORK = @REEXPORT_RUNTIME_FRAMEWORK@ RUNTIME = @RUNTIME@ RUNTIME_ARC_TESTS_M = @RUNTIME_ARC_TESTS_M@ @@ -99,16 +95,20 @@ UNICODE_M = @UNICODE_M@ USE_INCLUDES_ATOMIC = @USE_INCLUDES_ATOMIC@ USE_SRCS_APPLETALK = @USE_SRCS_APPLETALK@ USE_SRCS_EVDEV = @USE_SRCS_EVDEV@ USE_SRCS_FILES = @USE_SRCS_FILES@ +USE_SRCS_GNUTLS = @USE_SRCS_GNUTLS@ USE_SRCS_IPX = @USE_SRCS_IPX@ +USE_SRCS_MBEDTLS = @USE_SRCS_MBEDTLS@ USE_SRCS_NINTENDO_3DS = @USE_SRCS_NINTENDO_3DS@ USE_SRCS_NINTENDO_DS = @USE_SRCS_NINTENDO_DS@ USE_SRCS_NINTENDO_SWITCH = @USE_SRCS_NINTENDO_SWITCH@ +USE_SRCS_OPENSSL = @USE_SRCS_OPENSSL@ USE_SRCS_PLUGINS = @USE_SRCS_PLUGINS@ USE_SRCS_SCTP = @USE_SRCS_SCTP@ +USE_SRCS_SECURETRANSPORT = @USE_SRCS_SECURETRANSPORT@ USE_SRCS_SOCKETS = @USE_SRCS_SOCKETS@ USE_SRCS_SUBPROCESSES = @USE_SRCS_SUBPROCESSES@ USE_SRCS_TAGGED_POINTERS = @USE_SRCS_TAGGED_POINTERS@ USE_SRCS_THREADS = @USE_SRCS_THREADS@ USE_SRCS_UNIX_SOCKETS = @USE_SRCS_UNIX_SOCKETS@ Index: src/Makefile ================================================================== --- src/Makefile +++ src/Makefile @@ -150,10 +150,12 @@ OFTCPSocket.m \ OFTLSStream.m \ OFTXTDNSResourceRecord.m \ OFUDPSocket.m \ OFURIDNSResourceRecord.m \ + OFX509Certificate.m \ + OFX509CertificatePrivateKey.m \ ${USE_SRCS_APPLETALK} \ ${USE_SRCS_IPX} \ ${USE_SRCS_SCTP} \ ${USE_SRCS_UNIX_SOCKETS} SRCS_APPLETALK = OFDDPSocket.m Index: src/OFTLSStream.h ================================================================== --- src/OFTLSStream.h +++ src/OFTLSStream.h @@ -17,15 +17,18 @@ * . */ #import "OFStream.h" #import "OFRunLoop.h" +#import "OFX509Certificate.h" +#import "OFX509CertificatePrivateKey.h" OF_ASSUME_NONNULL_BEGIN /** @file */ +@class OFArray OF_GENERIC(ObjectType); @class OFTLSStream; /** * @brief An enum representing an error of an OFTLSStream. */ @@ -50,10 +53,11 @@ * @protocol OFTLSStreamDelegate OFTLSStream.h ObjFW/ObjFW.h * * A delegate for OFTLSStream. */ @protocol OFTLSStreamDelegate +@optional /** * @brief A method which is called when a TLS stream performed the client * handshake. * * @param stream The TLS stream which performed the handshake @@ -62,10 +66,21 @@ * success */ - (void)stream: (OFTLSStream *)stream didPerformClientHandshakeWithHost: (OFString *)host exception: (nullable id)exception; + +/** + * @brief A method which is called when a TLS stream performed the server + * handshake. + * + * @param stream The TLS stream which performed the handshake + * @param exception An exception that occurred during the handshake, or nil on + * success + */ +- (void)streamDidPerformServerHandshake: (OFTLSStream *)stream + exception: (nullable id)exception; @end /** * @class OFTLSStream OFTLSStream.h ObjFW/ObjFW.h * @@ -86,11 +101,13 @@ OFReadyForWritingObserving> { OFStream *_underlyingStream; bool _verifiesCertificates; - OF_RESERVE_IVARS(OFTLSStream, 4) + OFArray OF_GENERIC(OFX509Certificate *) *_Nullable _certificateChain; + OFX509CertificatePrivateKey *_Nullable _privateKey; + OF_RESERVE_IVARS(OFTLSStream, 2) } /** * @brief The underlying stream. */ @@ -109,10 +126,22 @@ /** * @brief Whether certificates are verified. Default is true. */ @property (nonatomic) bool verifiesCertificates; +/** + * @brief The certificate chain to use. + */ +@property OF_NULLABLE_PROPERTY (copy, nonatomic) + OFArray OF_GENERIC(OFX509Certificate *) *certificateChain; + +/** + * @brief The private key to use. + */ +@property OF_NULLABLE_PROPERTY (retain, nonatomic) + OFX509CertificatePrivateKey *privateKey; + - (instancetype)init OF_UNAVAILABLE; /** * @brief Creates a new TLS stream with the specified stream as its underlying * stream. @@ -167,21 +196,49 @@ * @param host The host to perform the handshake with * @throw OFTLSHandshakeFailedException The TLS handshake failed * @throw OFAlreadyOpenException The handshake was already performed */ - (void)performClientHandshakeWithHost: (OFString *)host; + +/** + * @brief Asynchronously performs the TLS server handshake and calls the + * delegate afterwards. + * + * @throw OFTLSHandshakeFailedException The TLS handshake failed + * @throw OFAlreadyOpenException The handshake was already performed + */ +- (void)asyncPerformServerHandshake; + +/** + * @brief Asynchronously performs the TLS server handshake and calls the + * delegate afterwards. + * + * @param runLoopMode The run loop mode in which to perform the async handshake + * + * @throw OFTLSHandshakeFailedException The TLS handshake failed + * @throw OFAlreadyOpenException The handshake was already performed + */ +- (void)asyncPerformServerHandshakeWithRunLoopMode: (OFRunLoopMode)runLoopMode; + +/** + * @brief Performs the TLS server handshake. + * + * @throw OFTLSHandshakeFailedException The TLS handshake failed + * @throw OFAlreadyOpenException The handshake was already performed + */ +- (void)performServerHandshake; @end #ifdef __cplusplus extern "C" { #endif /** * @brief The implementation for OFTLSStream to use. * * This can be set to a class that is always used for OFTLSStream. This is - * useful to either force a specific implementation or use one that ObjFW does - * not know about. + * useful to either force a specific implementation or to use one that ObjFW + * does not know about. */ extern Class OFTLSStreamImplementation; /** * @brief Returns a string description for the TLS stream error code. Index: src/OFTLSStream.m ================================================================== --- src/OFTLSStream.m +++ src/OFTLSStream.m @@ -18,10 +18,11 @@ */ #include "config.h" #import "OFTLSStream.h" +#import "OFArray.h" #import "OFDate.h" #import "OFNotImplementedException.h" #import "OFTLSHandshakeFailedException.h" @@ -81,16 +82,24 @@ exception: (id)exception { _done = true; _exception = [exception retain]; } + +- (void)streamDidPerformServerHandshake: (OFTLSStream *)stream + exception: (id)exception +{ + _done = true; + _exception = [exception retain]; +} @end @implementation OFTLSStream @synthesize underlyingStream = _underlyingStream; @dynamic delegate; @synthesize verifiesCertificates = _verifiesCertificates; +@synthesize privateKey = _privateKey; + (instancetype)alloc { if (self == [OFTLSStream class]) { if (OFTLSStreamImplementation != Nil) @@ -142,10 +151,23 @@ [_underlyingStream release]; _underlyingStream = nil; [super close]; } + +- (void)setCertificateChain: + (OFArray OF_GENERIC(OFX509Certificate *) *)certificateChain +{ + OFArray OF_GENERIC(OFX509Certificate *) *old = _certificateChain; + _certificateChain = [certificateChain copy]; + [old release]; +} + +- (OFArray OF_GENERIC(OFX509Certificate *) *)certificateChain +{ + return _certificateChain; +} - (size_t)lowlevelReadIntoBuffer: (void *)buffer length: (size_t)length { OF_UNRECOGNIZED_SELECTOR } @@ -192,10 +214,45 @@ _delegate = handshakeDelegate; [self asyncPerformClientHandshakeWithHost: host runLoopMode: handshakeRunLoopMode]; + while (!handshakeDelegate->_done) + [runLoop runMode: handshakeRunLoopMode beforeDate: nil]; + + /* Cleanup */ + [runLoop runMode: handshakeRunLoopMode beforeDate: [OFDate date]]; + + _delegate = delegate; + + if (handshakeDelegate->_exception != nil) + @throw handshakeDelegate->_exception; + + objc_autoreleasePoolPop(pool); +} + +- (void)asyncPerformServerHandshake +{ + [self asyncPerformServerHandshakeWithRunLoopMode: OFDefaultRunLoopMode]; +} + +- (void)asyncPerformServerHandshakeWithRunLoopMode: (OFRunLoopMode)runLoopMode +{ + OF_UNRECOGNIZED_SELECTOR +} + +- (void)performServerHandshake +{ + void *pool = objc_autoreleasePoolPush(); + id delegate = _delegate; + OFTLSStreamHandshakeDelegate *handshakeDelegate = + [[[OFTLSStreamHandshakeDelegate alloc] init] autorelease]; + OFRunLoop *runLoop = [OFRunLoop currentRunLoop]; + + _delegate = handshakeDelegate; + [self asyncPerformServerHandshakeWithRunLoopMode: handshakeRunLoopMode]; + while (!handshakeDelegate->_done) [runLoop runMode: handshakeRunLoopMode beforeDate: nil]; /* Cleanup */ [runLoop runMode: handshakeRunLoopMode beforeDate: [OFDate date]]; ADDED src/OFX509Certificate.h Index: src/OFX509Certificate.h ================================================================== --- /dev/null +++ src/OFX509Certificate.h @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2008-2024 Jonathan Schleifer + * + * All rights reserved. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License version 3.0 only, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * version 3.0 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * version 3.0 along with this program. If not, see + * . + */ + +#import "OFObject.h" + +OF_ASSUME_NONNULL_BEGIN + +@class OFArray OF_GENERIC(ObjectType); +@class OFIRI; + +/** + * @class OFX509Certificate OFX509Certificate.h ObjFW/ObjFW.h + * + * @brief An X.509 certificate. + */ +@interface OFX509Certificate: OFObject +{ + OF_RESERVE_IVARS(OFX509Certificate, 4) +} + +/** + * @brief Returns a certificate chain from the specified IRI. + * + * @param IRI The IRI to retrieve the certificate chain from + * + * @throw OFOpenItemFailedException Opening the item failed + * @throw OFUnsupportedProtocolException The specified IRI is not supported + * @throw OFReadFailedException Reading the item failed + * @throw OFInvalidFormatException The format of the item is invalid + */ ++ (OFArray OF_GENERIC(OFX509Certificate *) *) + certificateChainFromIRI: (OFIRI *)IRI; +@end + +#ifdef __cplusplus +extern "C" { +#endif +/** + * @brief The implementation for OFX509Certificate to use. + * + * This can be set to a class that is always used for OFX509Certificate. This + * is useful to either force a specific implementation or to use one that ObjFW + * does not know about. + */ +extern Class OFX509CertificateImplementation; +#ifdef __cplusplus +} +#endif + +OF_ASSUME_NONNULL_END ADDED src/OFX509Certificate.m Index: src/OFX509Certificate.m ================================================================== --- /dev/null +++ src/OFX509Certificate.m @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2008-2024 Jonathan Schleifer + * + * All rights reserved. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License version 3.0 only, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * version 3.0 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * version 3.0 along with this program. If not, see + * . + */ + +#include "config.h" + +#import "OFX509Certificate.h" + +#import "OFNotImplementedException.h" + +Class OFX509CertificateImplementation = Nil; + +@implementation OFX509Certificate ++ (instancetype)alloc +{ + if (self == [OFX509Certificate class]) { + if (OFX509CertificateImplementation != Nil) + return [OFX509CertificateImplementation alloc]; + + @throw [OFNotImplementedException exceptionWithSelector: _cmd + object: self]; + } + + return [super alloc]; +} + ++ (OFArray OF_GENERIC(OFX509Certificate *) *) + certificateChainFromIRI: (OFIRI *)IRI +{ + if (OFX509CertificateImplementation != Nil) + return [OFX509CertificateImplementation + certificateChainFromIRI: IRI]; + + OF_UNRECOGNIZED_SELECTOR +} +@end ADDED src/OFX509CertificatePrivateKey.h Index: src/OFX509CertificatePrivateKey.h ================================================================== --- /dev/null +++ src/OFX509CertificatePrivateKey.h @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2008-2024 Jonathan Schleifer + * + * All rights reserved. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License version 3.0 only, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * version 3.0 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * version 3.0 along with this program. If not, see + * . + */ + +#import "OFObject.h" + +OF_ASSUME_NONNULL_BEGIN + +@class OFIRI; + +/** + * @class OFX509CertificatePrivateKey \ + * OFX509CertificatePrivateKey.h ObjFW/ObjFW.h + * + * @brief The private key for an X.509 certificate. + */ +@interface OFX509CertificatePrivateKey: OFObject +{ + OF_RESERVE_IVARS(OFX509Certificate, 4) +} + +/** + * @brief Returns a private key from the specified IRI. + * + * @param IRI The IRI to retrieve the private key from + * + * @throw OFInitializationFailedException Initializing the private key failed + * @throw OFOpenItemFailedException Opening the item failed + * @throw OFUnsupportedProtocolException The specified IRI is not supported + * @throw OFReadFailedException Reading the item failed + * @throw OFInvalidFormatException The format of the item is invalid + */ ++ (instancetype)privateKeyFromIRI: (OFIRI *)IRI; +@end + +#ifdef __cplusplus +extern "C" { +#endif +/** + * @brief The implementation for OFX509CertificatePrivateKey to use. + * + * This can be set to a class that is always used for + * OFX509CertificatePrivateKey. This is useful to either force a specific + * implementation or to use one that ObjFW does not know about. + */ +extern Class OFX509CertificatePrivateKeyImplementation; +#ifdef __cplusplus +} +#endif + +OF_ASSUME_NONNULL_END ADDED src/OFX509CertificatePrivateKey.m Index: src/OFX509CertificatePrivateKey.m ================================================================== --- /dev/null +++ src/OFX509CertificatePrivateKey.m @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2008-2024 Jonathan Schleifer + * + * All rights reserved. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License version 3.0 only, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * version 3.0 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * version 3.0 along with this program. If not, see + * . + */ + +#include "config.h" + +#import "OFX509CertificatePrivateKey.h" + +#import "OFNotImplementedException.h" + +Class OFX509CertificatePrivateKeyImplementation = Nil; + +@implementation OFX509CertificatePrivateKey ++ (instancetype)alloc +{ + if (self == [OFX509CertificatePrivateKey class]) { + if (OFX509CertificatePrivateKeyImplementation != Nil) + return + [OFX509CertificatePrivateKeyImplementation alloc]; + + @throw [OFNotImplementedException exceptionWithSelector: _cmd + object: self]; + } + + return [super alloc]; +} + ++ (instancetype)privateKeyFromIRI: (OFIRI *)IRI +{ + if (OFX509CertificatePrivateKeyImplementation != Nil) + return [OFX509CertificatePrivateKeyImplementation + privateKeyFromIRI: IRI]; + + OF_UNRECOGNIZED_SELECTOR +} +@end Index: src/ObjFW.h ================================================================== --- src/ObjFW.h +++ src/ObjFW.h @@ -84,10 +84,12 @@ # import "OFDatagramSocket.h" # import "OFSequencedPacketSocket.h" # import "OFTCPSocket.h" # import "OFUDPSocket.h" # import "OFTLSStream.h" +# import "OFX509Certificate.h" +# import "OFX509CertificatePrivateKey.h" # import "OFKernelEventObserver.h" # import "OFDNSQuery.h" # import "OFDNSResourceRecord.h" # import "OFDNSResponse.h" # import "OFDNSResolver.h" Index: src/tls/Makefile ================================================================== --- src/tls/Makefile +++ src/tls/Makefile @@ -8,14 +8,21 @@ LIB_MAJOR = ${OBJFWTLS_LIB_MAJOR} LIB_MINOR = ${OBJFWTLS_LIB_MINOR} LIB_PATCH = ${OBJFWTLS_LIB_PATCH} INCLUDES := ObjFWTLS.h -SRCS = ${OF_GNUTLS_TLS_STREAM_M} \ - ${OF_MBEDTLS_TLS_STREAM_M} \ - ${OF_OPENSSL_TLS_STREAM_M} \ - ${OF_SECURE_TRANSPORT_TLS_STREAM_M} +SRCS = ${USE_SRCS_GNUTLS} \ + ${USE_SRCS_MBEDTLS} \ + ${USE_SRCS_OPENSSL} \ + ${USE_SRCS_SECURETRANSPORT} + +SRCS_GNUTLS = OFGnuTLSTLSStream.m \ + OFGnuTLSX509Certificate.m \ + OFGnuTLSX509CertificatePrivateKey.m +SRCS_MBEDTLS = OFMbedTLSTLSStream.m +SRCS_OPENSSL = OFOpenSSLTLSStream.m +SRCS_SECURETRANSPORT = OFSecureTransportTLSStream.m includesubdir = ObjFWTLS include ../../buildsys.mk Index: src/tls/OFGnuTLSTLSStream.h ================================================================== --- src/tls/OFGnuTLSTLSStream.h +++ src/tls/OFGnuTLSTLSStream.h @@ -21,14 +21,16 @@ #include OF_ASSUME_NONNULL_BEGIN +OF_SUBCLASSING_RESTRICTED @interface OFGnuTLSTLSStream: OFTLSStream { - bool _initialized, _handshakeDone; + bool _server, _handshakeDone; gnutls_session_t _session; + gnutls_certificate_credentials_t _credentials; OFString *_host; } @end OF_ASSUME_NONNULL_END Index: src/tls/OFGnuTLSTLSStream.m ================================================================== --- src/tls/OFGnuTLSTLSStream.m +++ src/tls/OFGnuTLSTLSStream.m @@ -20,21 +20,23 @@ #include "config.h" #include #import "OFGnuTLSTLSStream.h" +#import "OFArray.h" #import "OFData.h" +#import "OFGnuTLSX509Certificate.h" +#import "OFGnuTLSX509CertificatePrivateKey.h" #import "OFAlreadyOpenException.h" #import "OFInitializationFailedException.h" #import "OFNotOpenException.h" #import "OFReadFailedException.h" #import "OFTLSHandshakeFailedException.h" #import "OFWriteFailedException.h" int _ObjFWTLS_reference; -static gnutls_certificate_credentials_t systemTrustCreds; #ifndef GNUTLS_SAFE_PADDING_CHECK /* Some older versions don't have it. */ # define GNUTLS_SAFE_PADDING_CHECK 0 #endif @@ -99,21 +101,10 @@ { if (OFTLSStreamImplementation == Nil) OFTLSStreamImplementation = self; } -+ (void)initialize -{ - if (self != [OFGnuTLSTLSStream class]) - return; - - if (gnutls_certificate_allocate_credentials(&systemTrustCreds) != - GNUTLS_E_SUCCESS || - gnutls_certificate_set_x509_system_trust(systemTrustCreds) < 0) - @throw [OFInitializationFailedException exception]; -} - - (instancetype)initWithStream: (OFStream *)stream { self = [super initWithStream: stream]; @@ -127,28 +118,31 @@ return self; } - (void)dealloc { - if (_initialized) - [self close]; + [self close]; [_host release]; [super dealloc]; } - (void)close { - if (!_initialized) - @throw [OFNotOpenException exceptionWithObject: self]; - if (_handshakeDone) gnutls_bye(_session, GNUTLS_SHUT_WR); - gnutls_deinit(_session); - _initialized = _handshakeDone = false; + if (_session != NULL) + gnutls_deinit(_session); + + if (_credentials != NULL) + gnutls_certificate_free_credentials(_credentials); + + _session = NULL; + _credentials = NULL; + _handshakeDone = false; [_host release]; _host = nil; [super close]; @@ -208,55 +202,88 @@ { return (_underlyingStream.hasDataInReadBuffer || gnutls_record_check_pending(_session) > 0); } -- (void)asyncPerformClientHandshakeWithHost: (OFString *)host - runLoopMode: (OFRunLoopMode)runLoopMode +- (void)of_asyncPerformHandshakeWithHost: (OFString *)host + server: (bool)server + runLoopMode: (OFRunLoopMode)runLoopMode { static const OFTLSStreamErrorCode initFailedErrorCode = OFTLSStreamErrorCodeInitializationFailed; void *pool = objc_autoreleasePoolPush(); id exception = nil; int status; - if (_initialized) + if (_handshakeDone) @throw [OFAlreadyOpenException exceptionWithObject: self]; - if (gnutls_init(&_session, GNUTLS_CLIENT | GNUTLS_NONBLOCK | - GNUTLS_SAFE_PADDING_CHECK) != GNUTLS_E_SUCCESS) + if (gnutls_init(&_session, (server ? GNUTLS_SERVER : GNUTLS_CLIENT) | + GNUTLS_NONBLOCK | GNUTLS_SAFE_PADDING_CHECK) != GNUTLS_E_SUCCESS) @throw [OFTLSHandshakeFailedException exceptionWithStream: self host: host errorCode: initFailedErrorCode]; - _initialized = true; - gnutls_transport_set_ptr(_session, self); gnutls_transport_set_pull_function(_session, readFunc); gnutls_transport_set_push_function(_session, writeFunc); if (gnutls_set_default_priority(_session) != GNUTLS_E_SUCCESS || - gnutls_credentials_set(_session, GNUTLS_CRD_CERTIFICATE, - systemTrustCreds) != GNUTLS_E_SUCCESS) + gnutls_certificate_allocate_credentials(&_credentials) != + GNUTLS_E_SUCCESS || + gnutls_certificate_set_x509_system_trust(_credentials) < 0) @throw [OFTLSHandshakeFailedException exceptionWithStream: self host: host errorCode: initFailedErrorCode]; _host = [host copy]; + _server = server; + + if (!server) { + if (gnutls_server_name_set(_session, GNUTLS_NAME_DNS, + _host.UTF8String, _host.UTF8StringLength) != + GNUTLS_E_SUCCESS) + @throw [OFTLSHandshakeFailedException + exceptionWithStream: self + host: host + errorCode: initFailedErrorCode]; + + if (_verifiesCertificates) + gnutls_session_set_verify_cert(_session, _host.UTF8String, 0); + } + + if (_certificateChain != nil) { + OFGnuTLSX509CertificatePrivateKey *privateKey = + (OFGnuTLSX509CertificatePrivateKey *)_privateKey; + OFMutableData *certs = [OFMutableData + dataWithItemSize: sizeof(gnutls_x509_crt_t) + capacity: _certificateChain.count]; + + for (OFGnuTLSX509Certificate *cert in _certificateChain) { + gnutls_x509_crt_t gnuTLSCert = + cert.of_gnuTLSCertificate; + [certs addItem: &gnuTLSCert]; + } + + if (gnutls_certificate_set_x509_key(_credentials, + (gnutls_x509_crt_t *)certs.items, (unsigned int)certs.count, + privateKey.of_gnuTLSPrivateKey) < 0) + @throw [OFTLSHandshakeFailedException + exceptionWithStream: self + host: host + errorCode: initFailedErrorCode]; + } - if (gnutls_server_name_set(_session, GNUTLS_NAME_DNS, - _host.UTF8String, _host.UTF8StringLength) != GNUTLS_E_SUCCESS) + if (gnutls_credentials_set(_session, GNUTLS_CRD_CERTIFICATE, + _credentials) != GNUTLS_E_SUCCESS) @throw [OFTLSHandshakeFailedException exceptionWithStream: self host: host errorCode: initFailedErrorCode]; - if (_verifiesCertificates) - gnutls_session_set_verify_cert(_session, _host.UTF8String, 0); - status = gnutls_handshake(_session); if (status == GNUTLS_E_INTERRUPTED || status == GNUTLS_E_AGAIN) { if (gnutls_record_get_direction(_session) == 1) [_underlyingStream asyncWriteData: [OFData data] @@ -285,18 +312,40 @@ exceptionWithStream: self host: host errorCode: errorCode]; } - if ([_delegate respondsToSelector: - @selector(stream:didPerformClientHandshakeWithHost:exception:)]) - [_delegate stream: self - didPerformClientHandshakeWithHost: host - exception: exception]; + if (server) { + if ([_delegate respondsToSelector: @selector( + streamDidPerformServerHandshake:exception:)]) + [_delegate streamDidPerformServerHandshake: self + exception: exception]; + } else { + if ([_delegate respondsToSelector: @selector(stream: + didPerformClientHandshakeWithHost:exception:)]) + [_delegate stream: self + didPerformClientHandshakeWithHost: host + exception: exception]; + } objc_autoreleasePoolPop(pool); } + +- (void)asyncPerformClientHandshakeWithHost: (OFString *)host + runLoopMode: (OFRunLoopMode)runLoopMode +{ + [self of_asyncPerformHandshakeWithHost: host + server: false + runLoopMode: runLoopMode]; +} + +- (void)asyncPerformServerHandshakeWithRunLoopMode: (OFRunLoopMode)runLoopMode +{ + [self of_asyncPerformHandshakeWithHost: nil + server: true + runLoopMode: runLoopMode]; +} - (bool)stream: (OFStream *)stream didReadIntoBuffer: (void *)buffer length: (size_t)length exception: (id)exception @@ -323,15 +372,22 @@ exceptionWithStream: self host: _host errorCode: OFTLSStreamErrorCodeUnknown]; } - if ([_delegate respondsToSelector: - @selector(stream:didPerformClientHandshakeWithHost:exception:)]) - [_delegate stream: self - didPerformClientHandshakeWithHost: _host - exception: exception]; + if (_server) { + if ([_delegate respondsToSelector: @selector( + streamDidPerformServerHandshake:exception:)]) + [_delegate streamDidPerformServerHandshake: self + exception: exception]; + } else { + if ([_delegate respondsToSelector: @selector(stream: + didPerformClientHandshakeWithHost:exception:)]) + [_delegate stream: self + didPerformClientHandshakeWithHost: _host + exception: exception]; + } [_delegate release]; return false; } @@ -366,16 +422,23 @@ exceptionWithStream: self host: _host errorCode: OFTLSStreamErrorCodeUnknown]; } - if ([_delegate respondsToSelector: - @selector(stream:didPerformClientHandshakeWithHost:exception:)]) - [_delegate stream: self - didPerformClientHandshakeWithHost: _host - exception: exception]; + if (_server) { + if ([_delegate respondsToSelector: @selector( + streamDidPerformServerHandshake:exception:)]) + [_delegate streamDidPerformServerHandshake: self + exception: exception]; + } else { + if ([_delegate respondsToSelector: @selector(stream: + didPerformClientHandshakeWithHost:exception:)]) + [_delegate stream: self + didPerformClientHandshakeWithHost: _host + exception: exception]; + } [_delegate release]; return nil; } @end ADDED src/tls/OFGnuTLSX509Certificate.h Index: src/tls/OFGnuTLSX509Certificate.h ================================================================== --- /dev/null +++ src/tls/OFGnuTLSX509Certificate.h @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2008-2024 Jonathan Schleifer + * + * All rights reserved. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License version 3.0 only, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * version 3.0 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * version 3.0 along with this program. If not, see + * . + */ + +#import "OFX509Certificate.h" + +#include + +OF_ASSUME_NONNULL_BEGIN + +OF_SUBCLASSING_RESTRICTED +@interface OFGnuTLSX509Certificate: OFX509Certificate +{ + gnutls_x509_crt_t _certificate; +} + +@property (readonly, nonatomic) gnutls_x509_crt_t of_gnuTLSCertificate; + +- (instancetype)of_initWithGnuTLSCertificate: (gnutls_x509_crt_t)certificate; +@end + +OF_ASSUME_NONNULL_END ADDED src/tls/OFGnuTLSX509Certificate.m Index: src/tls/OFGnuTLSX509Certificate.m ================================================================== --- /dev/null +++ src/tls/OFGnuTLSX509Certificate.m @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2008-2024 Jonathan Schleifer + * + * All rights reserved. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License version 3.0 only, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * version 3.0 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * version 3.0 along with this program. If not, see + * . + */ + +#include "config.h" + +#import "OFGnuTLSX509Certificate.h" +#import "OFArray.h" +#import "OFData.h" + +#import "OFInvalidFormatException.h" +#import "OFOutOfRangeException.h" + +@implementation OFGnuTLSX509Certificate +@synthesize of_gnuTLSCertificate = _certificate; + ++ (void)load +{ + if (OFX509CertificateImplementation == Nil) + OFX509CertificateImplementation = self; +} + ++ (OFArray OF_GENERIC(OFX509Certificate *) *) + certificateChainFromIRI: (OFIRI *)IRI +{ + OFMutableArray *chain = [OFMutableArray array]; + void *pool = objc_autoreleasePoolPush(); + OFData *data = [OFData dataWithContentsOfIRI: IRI]; + gnutls_datum_t datum; + gnutls_x509_crt_t *certs; + unsigned int i, size; + + if (data.count * data.itemSize > UINT_MAX) + @throw [OFOutOfRangeException exception]; + + datum.data = (unsigned char *)data.items; + datum.size = (unsigned int)(data.count * data.itemSize); + + if (gnutls_x509_crt_list_import2(&certs, &size, &datum, + GNUTLS_X509_FMT_PEM, 0) != GNUTLS_E_SUCCESS) + @throw [OFInvalidFormatException exception]; + + for (i = 0; i < size; i++) { + OFGnuTLSX509Certificate *certificate; + + @try { + certificate = [[self alloc] + of_initWithGnuTLSCertificate: certs[i]]; + } @catch (id e) { + gnutls_x509_crt_deinit(certs[i]); + gnutls_free(certs); + @throw e; + } + + @try { + [chain addObject: certificate]; + } @catch (id e) { + gnutls_free(certs); + @throw e; + } @finally { + [certificate release]; + } + } + + gnutls_free(certs); + + objc_autoreleasePoolPop(pool); + + return chain; +} + +- (instancetype)of_initWithGnuTLSCertificate: (gnutls_x509_crt_t)certificate +{ + self = [super init]; + + _certificate = certificate; + + return self; +} + +- (void)dealloc +{ + gnutls_x509_crt_deinit(_certificate); + + [super dealloc]; +} +@end ADDED src/tls/OFGnuTLSX509CertificatePrivateKey.h Index: src/tls/OFGnuTLSX509CertificatePrivateKey.h ================================================================== --- /dev/null +++ src/tls/OFGnuTLSX509CertificatePrivateKey.h @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2008-2024 Jonathan Schleifer + * + * All rights reserved. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License version 3.0 only, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * version 3.0 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * version 3.0 along with this program. If not, see + * . + */ + +#import "OFX509CertificatePrivateKey.h" + +#include + +OF_ASSUME_NONNULL_BEGIN + +OF_SUBCLASSING_RESTRICTED +@interface OFGnuTLSX509CertificatePrivateKey: OFX509CertificatePrivateKey +{ + gnutls_x509_privkey_t _privateKey; +} + +@property (readonly, nonatomic) gnutls_x509_privkey_t of_gnuTLSPrivateKey; + +- (instancetype)of_initWithGnuTLSPrivateKey: (gnutls_x509_privkey_t)privateKey; +@end + +OF_ASSUME_NONNULL_END ADDED src/tls/OFGnuTLSX509CertificatePrivateKey.m Index: src/tls/OFGnuTLSX509CertificatePrivateKey.m ================================================================== --- /dev/null +++ src/tls/OFGnuTLSX509CertificatePrivateKey.m @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2008-2024 Jonathan Schleifer + * + * All rights reserved. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License version 3.0 only, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * version 3.0 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * version 3.0 along with this program. If not, see + * . + */ + +#include "config.h" + +#import "OFGnuTLSX509CertificatePrivateKey.h" +#import "OFData.h" + +#import "OFInitializationFailedException.h" +#import "OFInvalidFormatException.h" +#import "OFOutOfRangeException.h" + +@implementation OFGnuTLSX509CertificatePrivateKey +@synthesize of_gnuTLSPrivateKey = _privateKey; + ++ (void)load +{ + if (OFX509CertificatePrivateKeyImplementation == Nil) + OFX509CertificatePrivateKeyImplementation = self; +} + ++ (instancetype)privateKeyFromIRI: (OFIRI *)IRI +{ + void *pool = objc_autoreleasePoolPush(); + OFData *data = [OFData dataWithContentsOfIRI: IRI]; + gnutls_datum_t datum; + gnutls_x509_privkey_t key; + OFGnuTLSX509CertificatePrivateKey *privateKey; + + if (data.count * data.itemSize > UINT_MAX) + @throw [OFOutOfRangeException exception]; + + datum.data = (unsigned char *)data.items; + datum.size = (unsigned int)(data.count * data.itemSize); + + if (gnutls_x509_privkey_init(&key) != GNUTLS_E_SUCCESS) + @throw [OFInitializationFailedException + exceptionWithClass: self]; + + if (gnutls_x509_privkey_import(key, &datum, + GNUTLS_X509_FMT_PEM) != GNUTLS_E_SUCCESS) { + gnutls_x509_privkey_deinit(key); + @throw [OFInvalidFormatException exception]; + } + + @try { + privateKey = [[self alloc] of_initWithGnuTLSPrivateKey: key]; + } @catch (id e) { + gnutls_x509_privkey_deinit(key); + @throw e; + } + + objc_autoreleasePoolPop(pool); + + return [privateKey autorelease]; +} + +- (instancetype)of_initWithGnuTLSPrivateKey: (gnutls_x509_privkey_t)privateKey +{ + self = [super init]; + + _privateKey = privateKey; + + return self; +} + +- (void)dealloc +{ + gnutls_x509_privkey_deinit(_privateKey); + + [super dealloc]; +} +@end Index: src/tls/OFMbedTLSTLSStream.h ================================================================== --- src/tls/OFMbedTLSTLSStream.h +++ src/tls/OFMbedTLSTLSStream.h @@ -21,10 +21,11 @@ #include OF_ASSUME_NONNULL_BEGIN +OF_SUBCLASSING_RESTRICTED @interface OFMbedTLSTLSStream: OFTLSStream { bool _initialized, _handshakeDone; mbedtls_ssl_config _config; mbedtls_ssl_context _SSL; Index: src/tls/OFOpenSSLTLSStream.h ================================================================== --- src/tls/OFOpenSSLTLSStream.h +++ src/tls/OFOpenSSLTLSStream.h @@ -24,10 +24,11 @@ OF_ASSUME_NONNULL_BEGIN #define OFOpenSSLTLSStreamBufferSize 512 +OF_SUBCLASSING_RESTRICTED @interface OFOpenSSLTLSStream: OFTLSStream { bool _handshakeDone; SSL *_SSL; BIO *_readBIO, *_writeBIO; Index: src/tls/OFSecureTransportTLSStream.h ================================================================== --- src/tls/OFSecureTransportTLSStream.h +++ src/tls/OFSecureTransportTLSStream.h @@ -21,10 +21,11 @@ #include OF_ASSUME_NONNULL_BEGIN +OF_SUBCLASSING_RESTRICTED @interface OFSecureTransportTLSStream: OFTLSStream { SSLContextRef _context; OFString *_host; }