From ea298881036788abd34ce620e4c5da2a35aa9054 Mon Sep 17 00:00:00 2001 From: Thilo Molitor Date: Sat, 28 Oct 2023 09:26:32 +0200 Subject: [PATCH] Implement starttls using network framework This gives us TLSv1.3 and channel-binding even when using starttls and is compatible with iOS >= 16 --- Monal/Classes/MLStream.h | 3 +- Monal/Classes/MLStream.m | 348 ++++++++++++++++++++++++++++++--------- Monal/Classes/xmpp.m | 68 ++------ 3 files changed, 289 insertions(+), 130 deletions(-) diff --git a/Monal/Classes/MLStream.h b/Monal/Classes/MLStream.h index b3b2a25af6..a1b3adff1b 100644 --- a/Monal/Classes/MLStream.h +++ b/Monal/Classes/MLStream.h @@ -19,7 +19,8 @@ NS_ASSUME_NONNULL_BEGIN @property(nullable, readonly) NSArray* supportedChannelBindingTypes; @property(readonly) BOOL isTLS13; -+(void) connectWithSNIDomain:(NSString*) SNIDomain connectHost:(NSString*) host connectPort:(NSNumber*) port inputStream:(NSInputStream* _Nullable * _Nonnull) inputStream outputStream:(NSOutputStream* _Nullable * _Nonnull) outputStream; ++(void) connectWithSNIDomain:(NSString*) SNIDomain connectHost:(NSString*) host connectPort:(NSNumber*) port tls:(BOOL) tls inputStream:(NSInputStream* _Nullable * _Nonnull) inputStream outputStream:(NSOutputStream* _Nullable * _Nonnull) outputStream; +-(void) startTLS; -(NSData* _Nullable) channelBindingDataForType:(NSString* _Nullable) type; @end diff --git a/Monal/Classes/MLStream.m b/Monal/Classes/MLStream.m index 7240467add..3f2af88e78 100644 --- a/Monal/Classes/MLStream.m +++ b/Monal/Classes/MLStream.m @@ -24,6 +24,10 @@ @interface MLSharedStreamState : NSObject @property (atomic) nw_connection_t connection; @property (atomic) BOOL opening; @property (atomic) BOOL open; +@property (atomic) BOOL hasTLS; +@property (atomic) nw_parameters_configure_protocol_block_t configure_tls_block; +@property (atomic) nw_framer_t _Nullable framer; +@property (atomic) NSCondition* tlsHandshakeCompleteCondition; @end @interface MLStream() @@ -42,6 +46,7 @@ @interface MLInputStream() NSData* _buf; BOOL _reading; } +@property (atomic, readonly) void (^incoming_data_handler)(NSData* _Nullable, BOOL, NSError* _Nullable); @end @interface MLOutputStream() @@ -52,13 +57,15 @@ @interface MLOutputStream() @implementation MLSharedStreamState --(instancetype) initWithConnection:(nw_connection_t) connection +-(instancetype) init { self = [super init]; - self.connection = connection; self.error = nil; self.opening = NO; self.open = NO; + self.hasTLS = NO; + self.framer = nil; + self.tlsHandshakeCompleteCondition = [NSCondition new]; return self; } @@ -71,7 +78,49 @@ -(instancetype) initWithSharedState:(MLSharedStreamState*) shared { self = [super initWithSharedState:shared]; _buf = [NSData new]; - _reading = YES; + _reading = NO; + + //this handler will be called by our framer or the schedule_read method + //since the framer swallows all data, nw_connection_receive() and the framer cannot race against each other and deliver reordered data + weakify(self); + _incoming_data_handler = ^(NSData* _Nullable content, BOOL is_complete, NSError* _Nullable st_error) { + strongify(self); + if(self == nil) + return; + + DDLogVerbose(@"Incoming data handler called with is_complete=%@, st_error=%@, content=%@", bool2str(is_complete), st_error, content); + self->_reading = NO; + + //handle content received + if(content != NULL) + { + if([content length] > 0) + { + self->_buf = content; + [self generateEvent:NSStreamEventHasBytesAvailable]; + } + } + + //handle errors + if(st_error) + { + //ignore enodata and eagain errors + if([st_error.domain isEqualToString:(__bridge NSString *)kNWErrorDomainPOSIX] && (st_error.code == ENODATA || st_error.code == EAGAIN)) + DDLogWarn(@"Ignoring transient receive error: %@", st_error); + else + { + @synchronized(self.shared_state) { + self.shared_state.error = st_error; + } + [self generateEvent:NSStreamEventErrorOccurred]; + } + } + + //check if we're read-closed and stop our loop if true + //this has to be done *after* processing content + if(is_complete) + [self generateEvent:NSStreamEventEndEncountered]; + }; return self; } @@ -120,45 +169,28 @@ -(void) schedule_read { if(self.closed || !self.open_called || !self.shared_state.open) { - DDLogVerbose(@"ignoring nw_connection_receive call because connection is closed: %@", self); + DDLogVerbose(@"ignoring schedule_read call because connection is closed: %@", self); + return; + } + + //don't call nw_connection_receive() while our framer is active, this will result in multiple calls in parallel + //(one call for every read: call) and cause reordering and truncation problems + @synchronized(self.shared_state) { + if(self.shared_state.framer != nil) + { + DDLogVerbose(@"Framer active, not scheduling nw_connection_receive() read..."); + return; + } } _reading = YES; DDLogVerbose(@"calling nw_connection_receive"); nw_connection_receive(self.shared_state.connection, 1, BUFFER_SIZE, ^(dispatch_data_t content, nw_content_context_t context __unused, bool is_complete, nw_error_t receive_error) { - DDLogVerbose(@"nw_connection_receive got callback with is_complete:%@, receive_error=%@", bool2str(is_complete), receive_error); - self->_reading = NO; - - //handle content received - if(content != NULL) - { - if([(NSData*)content length] > 0) - { - self->_buf = (NSData*)content; - [self generateEvent:NSStreamEventHasBytesAvailable]; - } - } - - //handle errors + DDLogVerbose(@"nw_connection_receive got callback with is_complete:%@, receive_error=%@, length=%zu", bool2str(is_complete), receive_error, (unsigned long)((NSData*)content).length); + NSError* st_error = nil; if(receive_error) - { - NSError* st_error = (NSError*)CFBridgingRelease(nw_error_copy_cf_error(receive_error)); - //ignore enodata and eagain errors - if([st_error.domain isEqualToString:(__bridge NSString *)kNWErrorDomainPOSIX] && (st_error.code == ENODATA || st_error.code == EAGAIN)) - DDLogWarn(@"Ignoring transient receive error: %@", st_error); - else - { - @synchronized(self.shared_state) { - self.shared_state.error = st_error; - } - [self generateEvent:NSStreamEventErrorOccurred]; - } - } - - //check if we're read-closed and stop our loop if true - //this has to be done *after* processing content - if(is_complete) - [self generateEvent:NSStreamEventEndEncountered]; + st_error = (NSError*)CFBridgingRelease(nw_error_copy_cf_error(receive_error)); + self.incoming_data_handler((NSData*)content, is_complete, st_error); }); } @@ -188,28 +220,19 @@ -(NSInteger) write:(const uint8_t*) buffer maxLength:(NSUInteger) len if(self.closed) return -1; } - //the call to dispatch_get_main_queue() is a dummy because we are using DISPATCH_DATA_DESTRUCTOR_DEFAULT which is performed inline - dispatch_data_t data = dispatch_data_create(buffer, len, dispatch_get_main_queue(), DISPATCH_DATA_DESTRUCTOR_DEFAULT); - - //support tcp fast open for all data sent before the connection got opened - /*if(!self.open_called) - { - DDLogInfo(@"Sending TCP fast open early data: %@", data); - nw_connection_send(self.shared_state.connection, data, NW_CONNECTION_DEFAULT_MESSAGE_CONTEXT, NO, NW_CONNECTION_SEND_IDEMPOTENT_CONTENT); - return len; - }*/ - @synchronized(self.shared_state) { - if(self.closed || !self.open_called || !self.shared_state.open) - return -1; - } - @synchronized(self) { - _writing++; - } - nw_connection_send(self.shared_state.connection, data, NW_CONNECTION_DEFAULT_MESSAGE_CONTEXT, NO, ^(nw_error_t _Nullable error) { + NSCondition* condition = [NSCondition new]; + void (^write_completion)(nw_error_t) = ^(nw_error_t _Nullable error) { + DDLogVerbose(@"Write completed..."); + @synchronized(self) { self->_writing--; } + + [condition lock]; + [condition signal]; + [condition unlock]; + if(error) { NSError* st_error = (NSError*)CFBridgingRelease(nw_error_copy_cf_error(error)); @@ -225,7 +248,71 @@ -(NSInteger) write:(const uint8_t*) buffer maxLength:(NSUInteger) len [self generateEvent:NSStreamEventHasSpaceAvailable]; } } - }); + }; + + //the call to dispatch_get_main_queue() is a dummy because we are using DISPATCH_DATA_DESTRUCTOR_DEFAULT which is performed inline + dispatch_data_t data = dispatch_data_create(buffer, len, dispatch_get_main_queue(), DISPATCH_DATA_DESTRUCTOR_DEFAULT); + + //support tcp fast open for all data sent before the connection got opened + /*if(!self.open_called) + { + DDLogInfo(@"Sending TCP fast open early data: %@", data); + nw_connection_send(self.shared_state.connection, data, NW_CONNECTION_DEFAULT_MESSAGE_CONTEXT, NO, NW_CONNECTION_SEND_IDEMPOTENT_CONTENT); + return len; + }*/ + + @synchronized(self.shared_state) { + if(!self.open_called || !self.shared_state.open) + return -1; + } + @synchronized(self) { + _writing++; + } + + //decide if we should use our framer or normal nw_connection_send() + //framer being nil is the hot path --> make it fast (we'll check if it's still != nil in an @synchronized block below --> still threadsafe + //for the record: wrapping this into an @synchronized block would create a deadlock with our condition wait inside this + //block and the second @synchronized block inside nw_framer_async() + [condition lock]; + if(self.shared_state.framer != nil) + { + DDLogDebug(@"Switching async to framer thread in COLD path..."); + //framer methods must be called inside the framer thread + nw_framer_async(self.shared_state.framer, ^{ + //make sure that self.shared_state.framer still isn't nil, if it is, we fall back to nw_connection_send() + @synchronized(self.shared_state) { + if(self.shared_state.framer != nil) + { + DDLogDebug(@"Calling nw_framer_write_output_data() in COLD path..."); + nw_framer_write_output_data(self.shared_state.framer, data); + //make sure to not call the write_completion inside this @synchronized block + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + write_completion(nil); //TODO: can we detect write errors like in nw_connection_send() somehow? + }); + } + else + { + //make sure to not call nw_connection_send() and the following write_completion inside this @synchronized block + //we don't know if calling nw_connection_send() from the framer thread is safe --> just don't do this to be on the safe side + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + DDLogDebug(@"Calling nw_connection_send() in COLD path..."); + nw_connection_send(self.shared_state.connection, data, NW_CONNECTION_DEFAULT_MESSAGE_CONTEXT, NO, write_completion); + }); + } + } + }); + //wait for write complete signal + [condition wait]; + [condition unlock]; + DDLogDebug(@"Returning from write in COLD path: %zu", (unsigned long)len); + return len; //return instead of else to leave @synchronized block early + } + DDLogVerbose(@"Calling nw_connection_send() in hot path..."); + nw_connection_send(self.shared_state.connection, data, NW_CONNECTION_DEFAULT_MESSAGE_CONTEXT, NO, write_completion); + //wait for write complete signal + [condition wait]; + [condition unlock]; + DDLogVerbose(@"Returning from write in hot path: %zu", (unsigned long)len); return len; } @@ -260,12 +347,21 @@ -(void) generateEvent:(NSStreamEvent) event @implementation MLStream -+(void) connectWithSNIDomain:(NSString*) SNIDomain connectHost:(NSString*) host connectPort:(NSNumber*) port inputStream:(NSInputStream* _Nullable * _Nonnull) inputStream outputStream:(NSOutputStream* _Nullable * _Nonnull) outputStream ++(void) connectWithSNIDomain:(NSString*) SNIDomain connectHost:(NSString*) host connectPort:(NSNumber*) port tls:(BOOL) tls inputStream:(NSInputStream* _Nullable * _Nonnull) inputStream outputStream:(NSOutputStream* _Nullable * _Nonnull) outputStream { - nw_endpoint_t endpoint = nw_endpoint_create_host([host cStringUsingEncoding:NSUTF8StringEncoding], [[port stringValue] cStringUsingEncoding:NSUTF8StringEncoding]); + //create state + __block BOOL wasOpenOnce = NO; + MLSharedStreamState* shared_state = [[MLSharedStreamState alloc] init]; + + //create and configure public stream instances returned later + MLInputStream* input = [[MLInputStream alloc] initWithSharedState:shared_state]; + MLOutputStream* output = [[MLOutputStream alloc] initWithSharedState:shared_state]; - //always configure tls - nw_parameters_t parameters = nw_parameters_create_secure_tcp(^(nw_protocol_options_t tls_options) { + nw_parameters_configure_protocol_block_t tcp_options = ^(nw_protocol_options_t tcp_options) { + nw_tcp_options_set_enable_fast_open(tcp_options, YES); //enable tcp fast open + //nw_tcp_options_set_no_delay(tcp_options, YES); //disable nagle's algorithm + }; + nw_parameters_configure_protocol_block_t configure_tls_block = ^(nw_protocol_options_t tls_options) { sec_protocol_options_t options = nw_tls_copy_sec_protocol_options(tls_options); sec_protocol_options_set_tls_server_name(options, [SNIDomain cStringUsingEncoding:NSUTF8StringEncoding]); sec_protocol_options_add_tls_application_protocol(options, "xmpp-client"); @@ -277,24 +373,76 @@ +(void) connectWithSNIDomain:(NSString*) SNIDomain connectHost:(NSString*) host sec_protocol_options_set_tls_renegotiation_enabled(options, 0); //tls-exporter channel-binding is only usable if DHE is used instead of RSA key exchange sec_protocol_options_append_tls_ciphersuite_group(options, tls_ciphersuite_group_ats); - }, ^(nw_protocol_options_t tcp_options) { - nw_tcp_options_set_enable_fast_open(tcp_options, YES); //enable tcp fast open - //nw_tcp_options_set_no_delay(tcp_options, YES); //disable nagle's algorithm - }); + }; + + //configure tcp connection parameters + nw_parameters_t parameters; + if(tls) + { + parameters = nw_parameters_create_secure_tcp(configure_tls_block, tcp_options); + shared_state.hasTLS = YES; + } + else + { + parameters = nw_parameters_create_secure_tcp(NW_PARAMETERS_DISABLE_PROTOCOL, tcp_options); + shared_state.hasTLS = NO; + + //create simple framer and append it to our stack + __block uint8_t framer_init_count = 0; + nw_protocol_definition_t starttls_framer_definition = nw_framer_create_definition("starttls_framer", NW_FRAMER_CREATE_FLAGS_DEFAULT, ^(nw_framer_t framer) { + DDLogInfo(@"Framer %@ start called with wasOpenOnce=%@, framer_init_count=%u...", framer, bool2str(wasOpenOnce), framer_init_count); + nw_framer_set_stop_handler(framer, (nw_framer_stop_handler_t)^(nw_framer_t _Nullable framer) { + DDLogInfo(@"Framer stop called: %@", framer); + return YES; + }); + + //don't know why, but our framer is started twice and *presumably* only the second one indicates a successfully opened tcp connection + //--> just ignore the first one (which will get stopped once the second one is started) + if(framer_init_count++ < 1) + return nw_framer_start_result_will_mark_ready; + + //we have to simulate nw_connection_state_ready because the connection state will not reflect that while our framer is active + //--> use framer start as "connection active" signal + if(!wasOpenOnce) + { + wasOpenOnce = YES; + @synchronized(shared_state) { + shared_state.open = YES; + } + //make sure to not do this inside the framer thread to not cause any deadlocks + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + [input generateEvent:NSStreamEventOpenCompleted]; + [output generateEvent:NSStreamEventOpenCompleted]; + }); + } + + nw_framer_set_input_handler(framer, ^size_t(nw_framer_t framer) { + nw_framer_parse_input(framer, 1, BUFFER_SIZE, nil, ^size_t(uint8_t* buffer, size_t buffer_length, bool is_complete) { + //pass data on to our input stream providing the NSStream api + input.incoming_data_handler([NSData dataWithBytes:buffer length:buffer_length], is_complete, nil); + return buffer_length; + }); + return 0; //why that? + }); + shared_state.framer = framer; + return nw_framer_start_result_will_mark_ready; + }); + DDLogInfo(@"Not doing direct TLS: appending framer to protocol stack..."); + nw_protocol_stack_prepend_application_protocol(nw_parameters_copy_default_protocol_stack(parameters), nw_framer_create_options(starttls_framer_definition)); + } //not needed, will be done by apple's tls implementation automatically (only needed for plain tcp and manual sending of idempotent data) //nw_parameters_set_fast_open_enabled(parameters, YES); //create and configure connection object + nw_endpoint_t endpoint = nw_endpoint_create_host([host cStringUsingEncoding:NSUTF8StringEncoding], [[port stringValue] cStringUsingEncoding:NSUTF8StringEncoding]); nw_connection_t connection = nw_connection_create(endpoint, parameters); nw_connection_set_queue(connection, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0)); - //create and configure public stream instances returned later - MLSharedStreamState* shared_state = [[MLSharedStreamState alloc] initWithConnection:connection]; - MLInputStream* input = [[MLInputStream alloc] initWithSharedState:shared_state]; - MLOutputStream* output = [[MLOutputStream alloc] initWithSharedState:shared_state]; - + //configure shared state + shared_state.connection = connection; + shared_state.configure_tls_block = configure_tls_block; + //configure state change handler proxying state changes to our public stream instances - __block BOOL wasOpenOnce = NO; nw_connection_set_state_changed_handler(connection, ^(nw_connection_state_t state, nw_error_t error) { @synchronized(shared_state) { //connection was opened once (e.g. opening=YES) and closed later on (e.g. open=NO) @@ -324,13 +472,31 @@ +(void) connectWithSNIDomain:(NSString*) SNIDomain connectHost:(NSString*) host } else if(state == nw_connection_state_ready) { - DDLogInfo(@"Connection established"); - wasOpenOnce = YES; - @synchronized(shared_state) { - shared_state.open = YES; + DDLogInfo(@"Connection established: %@", bool2str(wasOpenOnce)); + if(!wasOpenOnce) + { + wasOpenOnce = YES; + @synchronized(shared_state) { + shared_state.open = YES; + } + [input generateEvent:NSStreamEventOpenCompleted]; + [output generateEvent:NSStreamEventOpenCompleted]; + } + else + { + //the nw_connection_state_ready state while already wasOpenOnce comes from our framer set to ready + //this informs the upper layer that the connection is in ready state now, but we already treat the framer start + //as connection ready event + + //unlock thread waiting on tls handshake completion (starttls) + [shared_state.tlsHandshakeCompleteCondition lock]; + [shared_state.tlsHandshakeCompleteCondition signal]; + [shared_state.tlsHandshakeCompleteCondition unlock]; + + //we still want to inform our stream users that they can write data now and schedule a read operation + [output generateEvent:NSStreamEventHasSpaceAvailable]; + [input schedule_read]; } - [input generateEvent:NSStreamEventOpenCompleted]; - [output generateEvent:NSStreamEventOpenCompleted]; } else if(state == nw_connection_state_cancelled) { @@ -355,6 +521,31 @@ +(void) connectWithSNIDomain:(NSString*) SNIDomain connectHost:(NSString*) host *outputStream = (NSOutputStream*)output; } +-(void) startTLS +{ + [self.shared_state.tlsHandshakeCompleteCondition lock]; + @synchronized(self.shared_state) { + MLAssert(!self.shared_state.hasTLS, @"We already have TLS on this connection!"); + DDLogInfo(@"Activating TLS on framer: %@", self.shared_state.framer); + nw_framer_async(self.shared_state.framer, ^{ + @synchronized(self.shared_state) { + DDLogVerbose(@"Prepending tls to framer: %@", self.shared_state.framer); + nw_protocol_options_t tls_options = nw_tls_create_options(); + self.shared_state.configure_tls_block(tls_options); + nw_framer_prepend_application_protocol(self.shared_state.framer, tls_options); + nw_framer_pass_through_input(self.shared_state.framer); + nw_framer_pass_through_output(self.shared_state.framer); + nw_framer_mark_ready(self.shared_state.framer); + self.shared_state.framer = nil; + DDLogVerbose(@"Framer deactivated and TLS prepended now..."); + } + }); + } + [self.shared_state.tlsHandshakeCompleteCondition wait]; + [self.shared_state.tlsHandshakeCompleteCondition unlock]; + DDLogInfo(@"TLS activated now..."); +} + -(instancetype) initWithSharedState:(MLSharedStreamState*) shared { self = [super init]; @@ -401,7 +592,10 @@ -(void) open MLAssert(!self.closed, @"streams can not be reopened!"); self.open_called = YES; if(!self.shared_state.opening) + { + DDLogVerbose(@"Calling nw_connection_start()..."); nw_connection_start(self.shared_state.connection); + } self.shared_state.opening = YES; //already opened by stream for other direction? --> directly trigger open event if(self.shared_state.open) @@ -411,6 +605,7 @@ -(void) open -(void) close { + DDLogVerbose(@"Closing connection via nw_connection_send()..."); nw_connection_send(self.shared_state.connection, NULL, NW_CONNECTION_FINAL_MESSAGE_CONTEXT, YES, ^(nw_error_t _Nullable error) { if(error) { @@ -516,6 +711,7 @@ -(NSData* _Nullable) channelBindingDataForType:(NSString* _Nullable) type -(BOOL) isTLS13 { MLAssert([self streamStatus] >= NSStreamStatusOpen && [self streamStatus] < NSStreamStatusClosed, @"Stream must be open to call this method!", (@{@"streamStatus": @([self streamStatus])})); + MLAssert(self.shared_state.hasTLS, @"Stream must have TLS negotiated to call this method!"); nw_protocol_metadata_t p_metadata = nw_connection_copy_protocol_metadata(self.shared_state.connection, nw_protocol_copy_tls_definition()); MLAssert(nw_protocol_metadata_is_tls(p_metadata), @"Protocol metadata is not TLS!"); sec_protocol_metadata_t s_metadata = nw_tls_copy_sec_protocol_metadata(p_metadata); @@ -525,6 +721,7 @@ -(BOOL) isTLS13 -(NSData*) channelBindingData_TLSExporter { MLAssert([self streamStatus] >= NSStreamStatusOpen && [self streamStatus] < NSStreamStatusClosed, @"Stream must be open to call this method!", (@{@"streamStatus": @([self streamStatus])})); + MLAssert(self.shared_state.hasTLS, @"Stream must have TLS negotiated to call this method!"); nw_protocol_metadata_t p_metadata = nw_connection_copy_protocol_metadata(self.shared_state.connection, nw_protocol_copy_tls_definition()); MLAssert(nw_protocol_metadata_is_tls(p_metadata), @"Protocol metadata is not TLS!"); sec_protocol_metadata_t s_metadata = nw_tls_copy_sec_protocol_metadata(p_metadata); @@ -535,6 +732,7 @@ -(NSData*) channelBindingData_TLSExporter -(NSData*) channelBindingData_TLSServerEndPoint { MLAssert([self streamStatus] >= NSStreamStatusOpen && [self streamStatus] < NSStreamStatusClosed, @"Stream must be open to call this method!", (@{@"streamStatus": @([self streamStatus])})); + MLAssert(self.shared_state.hasTLS, @"Stream must have TLS negotiated to call this method!"); nw_protocol_metadata_t p_metadata = nw_connection_copy_protocol_metadata(self.shared_state.connection, nw_protocol_copy_tls_definition()); MLAssert(nw_protocol_metadata_is_tls(p_metadata), @"Protocol metadata is not TLS!"); sec_protocol_metadata_t s_metadata = nw_tls_copy_sec_protocol_metadata(p_metadata); diff --git a/Monal/Classes/xmpp.m b/Monal/Classes/xmpp.m index 565999c5a8..4ae525078b 100644 --- a/Monal/Classes/xmpp.m +++ b/Monal/Classes/xmpp.m @@ -557,12 +557,12 @@ -(void) createStreams if(self.connectionProperties.server.isDirectTLS == YES) { DDLogInfo(@"creating directTLS streams"); - [MLStream connectWithSNIDomain:self.connectionProperties.identity.domain connectHost:self.connectionProperties.server.connectServer connectPort:self.connectionProperties.server.connectPort inputStream:&localIStream outputStream:&localOStream]; + [MLStream connectWithSNIDomain:self.connectionProperties.identity.domain connectHost:self.connectionProperties.server.connectServer connectPort:self.connectionProperties.server.connectPort tls:YES inputStream:&localIStream outputStream:&localOStream]; } else { DDLogInfo(@"creating plaintext streams"); - [NSStream getStreamsToHostWithName:self.connectionProperties.server.connectServer port:self.connectionProperties.server.connectPort.integerValue inputStream:&localIStream outputStream:&localOStream]; + [MLStream connectWithSNIDomain:self.connectionProperties.identity.domain connectHost:self.connectionProperties.server.connectServer connectPort:self.connectionProperties.server.connectPort tls:NO inputStream:&localIStream outputStream:&localOStream]; } if(localOStream) @@ -654,7 +654,7 @@ -(BOOL) connectionTask DDLogInfo(@"NO SRV records found, using standard xmpp config: %@:%@ (using starttls)", self.connectionProperties.server.connectServer, self.connectionProperties.server.connectPort); } } - + // Show warning when xmpp-client srv entry prohibits connections for(NSDictionary* row in _discoveredServersList) { @@ -2347,11 +2347,8 @@ -(void) processInput:(MLXMLNode*) parsedStanza withDelayedReplay:(BOOL) delayedR if(self.accountState >= kStateLoggedIn) return [self invalidXMLError]; - //record TLS version (starttls is always TLS 1.2) - if(!self.connectionProperties.server.isDirectTLS) - self.connectionProperties.tlsVersion = @"1.2"; - else - self.connectionProperties.tlsVersion = [((MLStream*)self->_oStream) isTLS13] ? @"1.3" : @"1.2"; + //record TLS version + self.connectionProperties.tlsVersion = [((MLStream*)self->_oStream) isTLS13] ? @"1.3" : @"1.2"; NSString* message = [parsedStanza findFirst:@"text#"];; if([parsedStanza check:@"not-authorized"]) @@ -2393,11 +2390,8 @@ -(void) processInput:(MLXMLNode*) parsedStanza withDelayedReplay:(BOOL) delayedR if(self.accountState >= kStateLoggedIn) return [self invalidXMLError]; - //record TLS version (starttls is always TLS 1.2) - if(!self.connectionProperties.server.isDirectTLS) - self.connectionProperties.tlsVersion = @"1.2"; - else - self.connectionProperties.tlsVersion = [((MLStream*)self->_oStream) isTLS13] ? @"1.3" : @"1.2"; + //record TLS version + self.connectionProperties.tlsVersion = [((MLStream*)self->_oStream) isTLS13] ? @"1.3" : @"1.2"; //perform logic to handle sasl success DDLogInfo(@"Got SASL Success"); @@ -2459,7 +2453,7 @@ -(void) processInput:(MLXMLNode*) parsedStanza withDelayedReplay:(BOOL) delayedR return; } - NSData* channelBindingData = [self channelBindingDataForType:[self channelBindingToUse]]; + NSData* channelBindingData = [((MLStream*)self->_oStream) channelBindingDataForType:[self channelBindingToUse]]; MLXMLNode* responseXML = [[MLXMLNode alloc] initWithElement:@"response" andNamespace:@"urn:xmpp:sasl:2" withAttributes:@{} andChildren:@[] andData:[HelperTools encodeBase64WithString:[self->_scramHandler clientFinalMessageWithChannelBindingData:channelBindingData]]]; [self send:responseXML]; @@ -2542,11 +2536,8 @@ -(void) processInput:(MLXMLNode*) parsedStanza withDelayedReplay:(BOOL) delayedR //record SDDP support self.connectionProperties.supportsSSDP = self->_scramHandler.ssdpSupported; - //record TLS version (starttls is always TLS 1.2) - if(!self.connectionProperties.server.isDirectTLS) - self.connectionProperties.tlsVersion = @"1.2"; - else - self.connectionProperties.tlsVersion = [((MLStream*)self->_oStream) isTLS13] ? @"1.3" : @"1.2"; + //record TLS version + self.connectionProperties.tlsVersion = [((MLStream*)self->_oStream) isTLS13] ? @"1.3" : @"1.2"; //make sure this error is reported, even if there are other SRV records left (we disconnect here and won't try again) [HelperTools postError:message withNode:nil andAccount:self andIsSevere:YES andDisableAccount:YES]; @@ -2586,11 +2577,8 @@ -(void) processInput:(MLXMLNode*) parsedStanza withDelayedReplay:(BOOL) delayedR //record SDDP support self.connectionProperties.supportsSSDP = self->_scramHandler.ssdpSupported; - //record TLS version (starttls is always TLS 1.2) - if(!self.connectionProperties.server.isDirectTLS) - self.connectionProperties.tlsVersion = @"1.2"; - else - self.connectionProperties.tlsVersion = [((MLStream*)self->_oStream) isTLS13] ? @"1.3" : @"1.2"; + //record TLS version + self.connectionProperties.tlsVersion = [((MLStream*)self->_oStream) isTLS13] ? @"1.3" : @"1.2"; self->_scramHandler = nil; self->_blockToCallOnTCPOpen = nil; //just to be sure but not strictly necessary @@ -2745,17 +2733,7 @@ -(void) processInput:(MLXMLNode*) parsedStanza withDelayedReplay:(BOOL) delayedR //this will create an sslContext and, if the underlying TCP socket is already connected, immediately start the ssl handshake DDLogInfo(@"configuring/starting tls handshake"); self->_streamHasSpace = NO; //make sure we do not try to send any data while the tls handshake is still performed - NSMutableDictionary* settings = [NSMutableDictionary new]; - [settings setObject:(NSNumber*)kCFBooleanTrue forKey:(NSString*)kCFStreamSSLValidatesCertificateChain]; - [settings setObject:self.connectionProperties.identity.domain forKey:(NSString*)kCFStreamSSLPeerName]; - [settings setObject:@"kCFStreamSocketSecurityLevelTLSv1_2" forKey:(NSString*)kCFStreamSSLLevel]; - if(CFWriteStreamSetProperty((__bridge CFWriteStreamRef)self->_oStream, kCFStreamPropertySSLSettings, (__bridge CFTypeRef)settings)) - DDLogInfo(@"Set TLS properties on streams. Security level %@", [self->_oStream propertyForKey:NSStreamSocketSecurityLevelKey]); - else - { - DDLogError(@"not sure.. Could not confirm Set TLS properties on streams."); - DDLogInfo(@"Set TLS properties on streams.security level %@", [self->_oStream propertyForKey:NSStreamSocketSecurityLevelKey]); - } + [((MLStream*)self->_oStream) startTLS]; self->_startTLSComplete = YES; //stop everything coming after this (we don't want to process stanzas that came in *before* a secure TLS context was established!) @@ -3051,7 +3029,7 @@ -(void) handleScramInSuccessOrContinue:(MLXMLNode*) parsedStanza -(NSString* _Nullable) channelBindingToUse { - NSArray* typesList = [self supportedChannelBindingTypes]; + NSArray* typesList = [((MLStream*)self->_oStream) supportedChannelBindingTypes]; if(typesList == nil || typesList.count == 0) return nil; //we don't support any channel-binding for this TLS connection for(NSString* type in typesList) @@ -3062,24 +3040,6 @@ -(NSString* _Nullable) channelBindingToUse return kServerDoesNotFollowXep0440Error; //this will make sure the authentication fails } -//proxy this to ostream if directTLS is used --(NSArray* _Nullable) supportedChannelBindingTypes -{ - //channel binding is only supported for direct tls due to dependency on network framework - if(!self.connectionProperties.server.isDirectTLS) - return nil; - return [((MLStream*)self->_oStream) supportedChannelBindingTypes]; -} - -//proxy this to ostream if directTLS is used --(NSData* _Nullable) channelBindingDataForType:(NSString*) type -{ - //channel binding is only supported for direct tls due to dependency on network framework - if(!self.connectionProperties.server.isDirectTLS) - return nil; - return [((MLStream*)self->_oStream) channelBindingDataForType:type]; -} - #pragma mark stanza handling -(void) sendIq:(XMPPIQ*) iq withResponseHandler:(monal_iq_handler_t) resultHandler andErrorHandler:(monal_iq_handler_t) errorHandler