Index: src/tls/Makefile ================================================================== --- src/tls/Makefile +++ src/tls/Makefile @@ -17,11 +17,13 @@ SRCS_GNUTLS = OFGnuTLSTLSStream.m \ OFGnuTLSX509Certificate.m \ OFGnuTLSX509CertificatePrivateKey.m SRCS_MBEDTLS = OFMbedTLSTLSStream.m -SRCS_OPENSSL = OFOpenSSLTLSStream.m +SRCS_OPENSSL = OFOpenSSLTLSStream.m \ + OFOpenSSLX509Certificate.m \ + OFOpenSSLX509CertificatePrivateKey.m SRCS_SECURETRANSPORT = OFSecureTransportTLSStream.m includesubdir = ObjFWTLS include ../../buildsys.mk Index: src/tls/OFGnuTLSTLSStream.m ================================================================== --- src/tls/OFGnuTLSTLSStream.m +++ src/tls/OFGnuTLSTLSStream.m @@ -248,14 +248,15 @@ exceptionWithStream: self host: host errorCode: initFailedErrorCode]; if (_verifiesCertificates) - gnutls_session_set_verify_cert(_session, _host.UTF8String, 0); + gnutls_session_set_verify_cert(_session, + _host.UTF8String, 0); } - if (_certificateChain != nil) { + if (_certificateChain.count > 0) { OFGnuTLSX509CertificatePrivateKey *privateKey = (OFGnuTLSX509CertificatePrivateKey *)_privateKey; OFMutableData *certs = [OFMutableData dataWithItemSize: sizeof(gnutls_x509_crt_t) capacity: _certificateChain.count]; Index: src/tls/OFGnuTLSX509Certificate.m ================================================================== --- src/tls/OFGnuTLSX509Certificate.m +++ src/tls/OFGnuTLSX509Certificate.m @@ -76,10 +76,12 @@ [certificate release]; } } gnutls_free(certs); + + [chain makeImmutable]; objc_autoreleasePoolPop(pool); return chain; } Index: src/tls/OFOpenSSLTLSStream.h ================================================================== --- src/tls/OFOpenSSLTLSStream.h +++ src/tls/OFOpenSSLTLSStream.h @@ -27,13 +27,13 @@ #define OFOpenSSLTLSStreamBufferSize 512 OF_SUBCLASSING_RESTRICTED @interface OFOpenSSLTLSStream: OFTLSStream { - bool _handshakeDone; + BIO *_readBIO, *_writeBIO; SSL *_SSL; - BIO *_readBIO, *_writeBIO; + bool _server, _handshakeDone; OFString *_host; char _buffer[OFOpenSSLTLSStreamBufferSize]; } @end Index: src/tls/OFOpenSSLTLSStream.m ================================================================== --- src/tls/OFOpenSSLTLSStream.m +++ src/tls/OFOpenSSLTLSStream.m @@ -20,11 +20,14 @@ #include "config.h" #include #import "OFOpenSSLTLSStream.h" +#import "OFArray.h" #import "OFData.h" +#import "OFOpenSSLX509Certificate.h" +#import "OFOpenSSLX509CertificatePrivateKey.h" #include #import "OFAlreadyOpenException.h" #import "OFInitializationFailedException.h" @@ -34,11 +37,11 @@ #import "OFWriteFailedException.h" #define bufferSize OFOpenSSLTLSStreamBufferSize int _ObjFWTLS_reference; -static SSL_CTX *clientContext; +static SSL_CTX *clientContext, *serverContext; static OFTLSStreamErrorCode verifyResultToErrorCode(const SSL *SSL_) { switch (SSL_get_verify_result(SSL_)) { @@ -93,10 +96,14 @@ if ((clientContext = SSL_CTX_new(TLS_client_method())) == NULL || SSL_CTX_set_default_verify_paths(clientContext) != 1) @throw [OFInitializationFailedException exceptionWithClass: self]; + + if ((serverContext = SSL_CTX_new(TLS_server_method())) == NULL) + @throw [OFInitializationFailedException + exceptionWithClass: self]; } - (instancetype)initWithStream: (OFStream *)stream { @@ -255,12 +262,13 @@ return (_underlyingStream.hasDataInReadBuffer || SSL_pending(_SSL) > 0 || BIO_ctrl_pending(_readBIO) > 0); #endif } -- (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; @@ -284,38 +292,76 @@ } BIO_set_mem_eof_return(_readBIO, -1); BIO_set_mem_eof_return(_writeBIO, -1); - if ((_SSL = SSL_new(clientContext)) == NULL) { + if ((_SSL = SSL_new(server ? serverContext : clientContext)) == NULL) { BIO_free(_readBIO); BIO_free(_writeBIO); @throw [OFTLSHandshakeFailedException exceptionWithStream: self host: host errorCode: initFailedErrorCode]; } SSL_set_bio(_SSL, _readBIO, _writeBIO); - SSL_set_connect_state(_SSL); + + if (server) + SSL_set_accept_state(_SSL); + else + SSL_set_connect_state(_SSL); _host = [host copy]; - - if (SSL_set_tlsext_host_name(_SSL, _host.UTF8String) != 1) - @throw [OFTLSHandshakeFailedException - exceptionWithStream: self - host: host - errorCode: initFailedErrorCode]; - - if (_verifiesCertificates) { - SSL_set_verify(_SSL, SSL_VERIFY_PEER, NULL); - - if (SSL_set1_host(_SSL, _host.UTF8String) != 1) + _server = server; + + if (!server) { + if (SSL_set_tlsext_host_name(_SSL, _host.UTF8String) != 1) + @throw [OFTLSHandshakeFailedException + exceptionWithStream: self + host: host + errorCode: initFailedErrorCode]; + + if (_verifiesCertificates) { + SSL_set_verify(_SSL, SSL_VERIFY_PEER, NULL); + + if (SSL_set1_host(_SSL, _host.UTF8String) != 1) + @throw [OFTLSHandshakeFailedException + exceptionWithStream: self + host: host + errorCode: initFailedErrorCode]; + } + } + + if (_certificateChain.count > 0) { + OFOpenSSLX509Certificate *certificate = + (OFOpenSSLX509Certificate *)_certificateChain.firstObject; + OFOpenSSLX509CertificatePrivateKey *privateKey = + (OFOpenSSLX509CertificatePrivateKey *)_privateKey; + bool first = true; + + if (SSL_use_certificate(_SSL, + certificate.of_openSSLCertificate) != 1 || + SSL_use_PrivateKey(_SSL, + privateKey.of_openSSLPrivateKey) != 1) @throw [OFTLSHandshakeFailedException exceptionWithStream: self host: host errorCode: initFailedErrorCode]; + + for (OFOpenSSLX509Certificate *iter in _certificateChain) { + if (first) { + first = false; + continue; + } + + if (SSL_add1_chain_cert(_SSL, + iter.of_openSSLCertificate) != 1) + @throw [OFTLSHandshakeFailedException + exceptionWithStream: self + host: host + errorCode: initFailedErrorCode]; + } } ERR_clear_error(); status = SSL_do_handshake(_SSL); @@ -358,18 +404,40 @@ errorCode: OFTLSStreamErrorCodeUnknown]; break; } } - 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 @@ -421,15 +489,22 @@ break; } } } - 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; } @@ -494,16 +569,23 @@ break; } } } - 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/OFOpenSSLX509Certificate.h Index: src/tls/OFOpenSSLX509Certificate.h ================================================================== --- /dev/null +++ src/tls/OFOpenSSLX509Certificate.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 OFOpenSSLX509Certificate: OFX509Certificate +{ + X509 *_certificate; +} + +@property (readonly, nonatomic) X509 *of_openSSLCertificate; + +- (instancetype)of_initWithOpenSSLCertificate: (X509 *)certificate; +@end + +OF_ASSUME_NONNULL_END ADDED src/tls/OFOpenSSLX509Certificate.m Index: src/tls/OFOpenSSLX509Certificate.m ================================================================== --- /dev/null +++ src/tls/OFOpenSSLX509Certificate.m @@ -0,0 +1,108 @@ +/* + * 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 "OFOpenSSLX509Certificate.h" +#import "OFArray.h" +#import "OFData.h" + +#import "OFInvalidFormatException.h" +#import "OFOutOfMemoryException.h" +#import "OFOutOfRangeException.h" + +@implementation OFOpenSSLX509Certificate +@synthesize of_openSSLCertificate = _certificate; + ++ (void)load +{ + if (OFX509CertificateImplementation == Nil) + OFX509CertificateImplementation = self; +} + ++ (OFArray OF_GENERIC(OFX509Certificate *) *) + certificateChainFromPEMFileAtIRI: (OFIRI *)IRI +{ + OFMutableArray *chain = [OFMutableArray array]; + void *pool = objc_autoreleasePoolPush(); + OFData *data = [OFData dataWithContentsOfIRI: IRI]; + BIO *bio; + + if (data.count * data.itemSize > INT_MAX) + @throw [OFOutOfRangeException exception]; + + bio = BIO_new_mem_buf(data.items, (int)(data.count * data.itemSize)); + if (bio == NULL) + @throw [OFOutOfMemoryException + exceptionWithRequestedSize: data.count * data.itemSize]; + + @try { + for (;;) { + OFOpenSSLX509Certificate *certificate; + X509 *cert = X509_new(); + + if (cert == NULL) + @throw [OFOutOfMemoryException exception]; + + if (PEM_read_bio_X509(bio, &cert, NULL, NULL) == NULL) { + X509_free(cert); + break; + } + + @try { + certificate = [[self alloc] + of_initWithOpenSSLCertificate: cert]; + } @catch (id e) { + X509_free(cert); + @throw e; + } + + @try { + [chain addObject: certificate]; + } @finally { + [certificate release]; + } + } + } @finally { + BIO_free(bio); + } + + [chain makeImmutable]; + + objc_autoreleasePoolPop(pool); + + return chain; +} + +- (instancetype)of_initWithOpenSSLCertificate: (X509 *)certificate +{ + self = [super init]; + + _certificate = certificate; + + return self; +} + +- (void)dealloc +{ + X509_free(_certificate); + + [super dealloc]; +} +@end ADDED src/tls/OFOpenSSLX509CertificatePrivateKey.h Index: src/tls/OFOpenSSLX509CertificatePrivateKey.h ================================================================== --- /dev/null +++ src/tls/OFOpenSSLX509CertificatePrivateKey.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 OFOpenSSLX509CertificatePrivateKey: OFX509CertificatePrivateKey +{ + EVP_PKEY *_privateKey; +} + +@property (readonly, nonatomic) EVP_PKEY *of_openSSLPrivateKey; + +- (instancetype)of_initWithOpenSSLPrivateKey: (EVP_PKEY *)privateKey; +@end + +OF_ASSUME_NONNULL_END ADDED src/tls/OFOpenSSLX509CertificatePrivateKey.m Index: src/tls/OFOpenSSLX509CertificatePrivateKey.m ================================================================== --- /dev/null +++ src/tls/OFOpenSSLX509CertificatePrivateKey.m @@ -0,0 +1,91 @@ +/* + * 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 "OFOpenSSLX509CertificatePrivateKey.h" +#import "OFData.h" + +#import "OFInitializationFailedException.h" +#import "OFInvalidFormatException.h" +#import "OFOutOfMemoryException.h" +#import "OFOutOfRangeException.h" + +@implementation OFOpenSSLX509CertificatePrivateKey +@synthesize of_openSSLPrivateKey = _privateKey; + ++ (void)load +{ + if (OFX509CertificatePrivateKeyImplementation == Nil) + OFX509CertificatePrivateKeyImplementation = self; +} + ++ (instancetype)privateKeyFromPEMFileAtIRI: (OFIRI *)IRI +{ + void *pool = objc_autoreleasePoolPush(); + OFData *data = [OFData dataWithContentsOfIRI: IRI]; + BIO *bio; + OFOpenSSLX509CertificatePrivateKey *privateKey; + + if (data.count * data.itemSize > INT_MAX) + @throw [OFOutOfRangeException exception]; + + bio = BIO_new_mem_buf(data.items, (int)(data.count * data.itemSize)); + if (bio == NULL) + @throw [OFOutOfMemoryException + exceptionWithRequestedSize: data.count * data.itemSize]; + + @try { + EVP_PKEY *key = PEM_read_bio_PrivateKey(bio, NULL, NULL, NULL); + + if (key == NULL) + @throw [OFInvalidFormatException exception]; + + @try { + privateKey = [[self alloc] + of_initWithOpenSSLPrivateKey: key]; + } @catch (id e) { + EVP_PKEY_free(key); + @throw e; + } + } @finally { + BIO_free(bio); + } + + objc_autoreleasePoolPop(pool); + + return [privateKey autorelease]; +} + +- (instancetype)of_initWithOpenSSLPrivateKey: (EVP_PKEY *)privateKey +{ + self = [super init]; + + _privateKey = privateKey; + + return self; +} + +- (void)dealloc +{ + EVP_PKEY_free(_privateKey); + + [super dealloc]; +} +@end