/* * Copyright (c) 2008-2024 Jonathan Schleifer <js@nil.im> * * 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 * <https://www.gnu.org/licenses/>. */ #include "config.h" #import "OFTLSStream.h" #import "OFArray.h" #import "OFDate.h" #import "OFNotImplementedException.h" #import "OFTLSHandshakeFailedException.h" @interface OFTLSStreamHandshakeDelegate: OFObject <OFTLSStreamDelegate> { @public bool _done; id _exception; } @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 OF_VISIBILITY_HIDDEN _references_to_exceptions_of_OFTLSStream(void) { _OFTLSHandshakeFailedException_reference = 1; } OFString * OFTLSStreamErrorCodeDescription(OFTLSStreamErrorCode errorCode) { switch (errorCode) { case OFTLSStreamErrorCodeInitializationFailed: return @"Initialization of TLS context failed"; case OFTLSStreamErrorCodeCertificateVerificationFailed: return @"Failed to verify certificate"; case OFTLSStreamErrorCodeCertificateIssuerUntrusted: return @"The certificate has an untrusted or unknown issuer"; case OFTLSStreamErrorCodeCertificateNameMismatch: return @"The certificate is for a different name"; case OFTLSStreamErrorCodeCertificatedExpired: return @"The certificate has expired or is not yet valid"; case OFTLSStreamErrorCodeCertificateRevoked: return @"The certificate has been revoked"; default: return @"Unknown error"; } } @implementation OFTLSStreamHandshakeDelegate - (void)dealloc { [_exception release]; [super dealloc]; } - (void)stream: (OFTLSStream *)stream didPerformClientHandshakeWithHost: (OFString *)host 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) return [OFTLSStreamImplementation alloc]; @throw [OFNotImplementedException exceptionWithSelector: _cmd object: self]; } return [super alloc]; } + (instancetype)streamWithStream: (OFStream <OFReadyForReadingObserving, OFReadyForWritingObserving> *)stream { return [[[self alloc] initWithStream: stream] autorelease]; } - (instancetype)init { OF_INVALID_INIT_METHOD } - (instancetype)initWithStream: (OFStream <OFReadyForReadingObserving, OFReadyForWritingObserving> *)stream { self = [super init]; @try { _underlyingStream = [stream retain]; _verifiesCertificates = true; } @catch (id e) { [self release]; @throw e; } return self; } - (void)dealloc { [_underlyingStream release]; [super dealloc]; } - (void)close { [_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 } - (size_t)lowlevelWriteBuffer: (const void *)buffer length: (size_t)length { OF_UNRECOGNIZED_SELECTOR } - (bool)lowlevelIsAtEndOfStream { return _underlyingStream.atEndOfStream; } - (int)fileDescriptorForReading { return _underlyingStream.fileDescriptorForReading; } - (int)fileDescriptorForWriting { return _underlyingStream.fileDescriptorForWriting; } - (void)asyncPerformClientHandshakeWithHost: (OFString *)host { [self asyncPerformClientHandshakeWithHost: host runLoopMode: OFDefaultRunLoopMode]; } - (void)asyncPerformClientHandshakeWithHost: (OFString *)host runLoopMode: (OFRunLoopMode)runLoopMode { OF_UNRECOGNIZED_SELECTOR } - (void)performClientHandshakeWithHost: (OFString *)host { void *pool = objc_autoreleasePoolPush(); id <OFTLSStreamDelegate> delegate = _delegate; OFTLSStreamHandshakeDelegate *handshakeDelegate = [[[OFTLSStreamHandshakeDelegate alloc] init] autorelease]; OFRunLoop *runLoop = [OFRunLoop currentRunLoop]; _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 <OFTLSStreamDelegate> 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]]; _delegate = delegate; if (handshakeDelegate->_exception != nil) @throw handshakeDelegate->_exception; objc_autoreleasePoolPop(pool); } @end