I'm trying to connect an iOS app to a Windows C# sever using TLS over TCP/IP.
The TLS connection is using untrusted certificates created from an untrusted CA root certificate using the makecert utility.
To test these certificates I created a simple C# client and using those certificates it was able to connect and communicate with the server.
I'm not skilled at iOS development, but I did manage to find some code that connects me to the server, as follows:
-(bool)CreateAndConnect:(NSString *) remoteHost withPort:(NSInteger) serverPort
{
CFReadStreamRef readStream;
CFWriteStreamRef writeStream;
CFStreamCreatePairWithSocketToHost(NULL, (__bridge CFStringRef)(remoteHost),
serverPort, &readStream, &writeStream);
CFReadStreamSetProperty(readStream, kCFStreamPropertySocketSecurityLevel,
kCFStreamSocketSecurityLevelNegotiatedSSL);
NSInputStream *inputStream = (__bridge_transfer NSInputStream *)readStream;
NSOutputStream *outputStream = (__bridge_transfer NSOutputStream *)writeStream;
[inputStream setProperty:NSStreamSocketSecurityLevelNegotiatedSSL forKey:NSStreamSocketSecurityLevelKey];
// load certificate from servers exported p12 file
NSArray *certificates = [[NSArray alloc] init];
[self loadClientCertificates:certificates];
NSDictionary *sslSettings = [NSDictionary dictionaryWithObjectsAndKeys:
(id)kCFBooleanFalse, (id)kCFStreamSSLValidatesCertificateChain,
certificates,(id)kCFStreamSSLCertificates,
nil];
[inputStream setProperty:sslSettings forKey:(__bridge NSString *)kCFStreamPropertySSLSettings];
[inputStream setDelegate:self];
[outputStream setDelegate:self];
[inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
CFReadStreamOpen(readStream);
CFWriteStreamOpen(writeStream);
return true;
}
The code also seems to do some form of TLS negotiation, as the C# server rejects the connection if the p12 certificates are not provided as part of the NSStream settings.
So it appears like the first stage of the TLS negotiation is working.
To validate the server certificate I have this function, which gets called by the NSStream delegate on the NSStreamEventHasSpaceAvailable event:
// return YES if certificate verification is successful, otherwise NO
-(BOOL) VerifyCertificate:(NSStream *)stream
{
NSData *trustedCertData = nil;
BOOL result = NO;
SecTrustRef trustRef = NULL;
NSString *root_certificate_name = @"reference_cert";
NSString *root_certificate_extension = @"der";
/* Load reference cetificate */
NSBundle *bundle = [NSBundle bundleForClass:[self class]];
trustedCertData = [NSData dataWithContentsOfFile:[bundle pathForResource: root_certificate_name ofType: root_certificate_extension]];
/* get trust object */
/* !!!!! error is here as trustRef is NULL !!!! */
trustRef = (__bridge SecTrustRef)[stream propertyForKey:(__bridge id)kCFStreamPropertySSLPeerTrust];
/* loacate the reference certificate */
NSInteger numCerts = SecTrustGetCertificateCount(trustRef);
for (NSInteger i = 0; i < numCerts; i++) {
SecCertificateRef secCertRef = SecTrustGetCertificateAtIndex(trustRef, i);
NSData *certData = CFBridgingRelease(SecCertificateCopyData(secCertRef));
if ([trustedCertData isEqualToData: certData]) {
result = YES;
break;
}
}
return result;
}
Now the problem is, no matter what I try, the trustRef object is always null.
From this Apple developer link: https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/NetworkingTopics/Articles/OverridingSSLChainValidationCorrectly.html
There is this quote that suggest this should not be the case:
By the time your stream delegate’s event handler gets called to indicate that there is space available on the socket, the operating system has already constructed a TLS channel, obtained a certificate chain from the other end of the connection, and created a trust object to evaluate it.
Any hints on how to fix this?
How can I get access to that trustRef object for the NSStream?
Edit:
Thanks for the reply 100phole.
In trying to get this to work, I thought this might have something to do with the issue and in one of my many attempts I moved all of those socket related items into a class:
Something like this:
@interface Socket
CFReadStreamRef readStream;
CFWriteStreamRef writeStream;
NSInputStream *inputStream;
NSOutputStream *outputStream;
@end
But that came up with the same results :(
I only reverted back to the version shown above because, based on my Google searching, that appears to be a fairly code common pattern.
For example, even this code from the Apple Developer site uses a very similar style:
As I mentioned earlier, I'm no expert in Objective-C (far from it), so I might be wrong, but from what I have seen, moving those items into a class and having them persist did not seem to make any difference.
/* Store a reference to the input and output streams so that they don't go away.... */