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;
}