3

I'm trying to open an input/output stream to a secure server but keep getting CFNetwork SSLHandshake failed (-9806)

I have set the plist values for exception domains etc

<key>NSAppTransportSecurity</key>
<dict>
    <key>NSExceptionDomains</key>
    <dict>
        <key>someserver.com</key>
        <dict>
            <key>NSIncludesSubdomains</key>
            <true/>
            <key>NSThirdPartyExceptionMinimumTLSVersion</key>
            <string>TLSv1.0</string>
            <key>NSThirdPartyExceptionRequiresForwardSecrecy</key>
            <false/>
            <key>NSTemporaryExceptionAllowsInsecureHTTPLoads</key>
            <true/>
        </dict>
    </dict>
</dict>

I also tried this in plist:

<key>NSAppTransportSecurity</key>
<dict>
    <key>NSAllowsArbitraryLoads</key>
    <true/>
</dict>

Here is my code:

-(void)startStream{


NSString *urlStr = @"https://stream.someserver.com";
NSURL *website = [NSURL URLWithString:urlStr];


CFReadStreamRef readStream;
CFWriteStreamRef writeStream;
CFStreamCreatePairWithSocketToHost(NULL, (CFStringRef)CFBridgingRetain([website host]), 443, &readStream, &writeStream);

NSInputStream *inputStream = (__bridge_transfer NSInputStream *)readStream;
NSOutputStream *outputStream = (__bridge_transfer NSOutputStream *)writeStream;


[inputStream setDelegate:self];
[outputStream setDelegate:self];

[inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];


NSMutableDictionary *settings = [NSMutableDictionary dictionaryWithCapacity:1];
[settings setObject:(NSString *)NSStreamSocketSecurityLevelTLSv1 forKey:(NSString *)kCFStreamSSLLevel];
[settings setObject:[NSNumber numberWithBool:YES] forKey:(NSString *)kCFStreamSSLValidatesCertificateChain];
[settings setObject:@"stream.someserver.com" forKey:(NSString *)kCFStreamSSLPeerName];

CFWriteStreamSetProperty((CFWriteStreamRef)outputStream, kCFStreamPropertySSLSettings, (CFTypeRef)settings);
CFReadStreamSetProperty((CFReadStreamRef)inputStream, kCFStreamPropertySSLSettings, (CFTypeRef)settings);


[inputStream open];
[outputStream open];

}

There is a about a 5 second delay after the stream is open before the CFNetwork SSLHandshake failed (-9806) error is thrown.

NOTE: The secure server is not mine and I cannot change any settings there. It is a tested and established server with many users streaming

Zigglzworth
  • 6,645
  • 9
  • 68
  • 107
  • 1
    Can you post the output of the terminal command `openssl s_client -connect stream.someserver.com:443`? – Dave Weston Feb 21 '17 at 06:29
  • The SSL handshake succeeds when i check in terminal with openssl – Zigglzworth Feb 21 '17 at 10:24
  • 1
    Did you try to verify server calls on higher level just by setting `NSURLSessionDelegate` and getting SecTrustRef from `NSURLAuthenticationChallenge` instance passed to delegate call `URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge [...]`? i.e. `[challenge.protectionSpace serverTrust]`. I can give you a snippet in Swift also. – Wladek Surala Feb 22 '17 at 14:37

3 Answers3

1

I'm not sure which stage fails in the ssl connection handshake, but you can try to change the settings dictionary to something like this:

 NSDictionary *settings = [[NSDictionary alloc] initWithObjectsAndKeys:
                          [NSNumber numberWithBool:YES], kCFStreamSSLAllowsExpiredCertificates,
                          [NSNumber numberWithBool:YES], kCFStreamSSLAllowsAnyRoot,
                          [NSNumber numberWithBool:YES], kCFStreamSSLAllowsExpiredRoots,
                          [NSNumber numberWithBool:NO], kCFStreamSSLValidatesCertificateChain,
                          //kCFNull,kCFStreamSSLPeerName,
                          kCFStreamSocketSecurityLevelSSLv3, kCFStreamSSLLevel,
                          [NSNumber numberWithBool:YES], kCFStreamPropertyShouldCloseNativeSocket,
                          nil];

And then if succeeded, remove each "Security breach" defined above.

IdoT
  • 2,831
  • 1
  • 24
  • 35
  • This didn't work. Also, kCFStreamSSLAllowsExpiredCertificates, kCFStreamSSLAllowsAnyRoot, and kCFStreamSSLAllowsExpiredRoots are deprecated – Zigglzworth Feb 06 '17 at 09:51
1

Looking at Apple documentation it seems you are using wrong keys.

For example

  • NSThirdPartyExceptionMinimumTLSVersion must be NSExceptionMinimumTLSVersion
  • NSTemporaryExceptionAllowsInsecureHTTPLoads must be NSExceptionAllowsInsecureHTTPLoads
  • and ecc...
Shebuka
  • 3,148
  • 1
  • 26
  • 43
  • From the docs I've read, ATS isn't enforced by the lower level CFNetwork APIs. – Dave Weston Feb 21 '17 at 13:01
  • @DaveWeston you are incorrect. You CAN use lower level CFNetwork APIs without ATS restrictions, BUT you need to manage all certificate validation by yourself. Zigglzworth isn't doing this because of all the settings he pass to ...StreamSetProperty, so he need ATS permissions. – Shebuka Feb 21 '17 at 17:52
  • Quote: "App Transport Security (ATS) is enforced by the NSURLSession class... ATS protections are not available when using lower-level networking APIs provided by Apple, or when using third-party networking libraries." - https://developer.apple.com/library/prerelease/content/documentation/General/Reference/InfoPlistKeyReference/Articles/CocoaKeys.html#//apple_ref/doc/uid/TP40009251-SW55 – Dave Weston Feb 21 '17 at 17:59
  • Thanks I did try this anyway and it did not resolve the issue – Zigglzworth Feb 27 '17 at 14:09
1

In your SSL settings, set kCFStreamSSLValidatesCertificateChain to @NO to completely disable server trust evaluation.

In your delegate implementation, when you first get the NSStreamEventHasSpaceAvailable or NSStreamEventHasBytesAvailable status, then retrieve the kCFStreamPropertySSLPeerTrust property from the stream.

You should get back a SecTrustRef, which you can evaluate and get information about:

SecTrustRef trust = (SecTrustRef)[stream propertyForKey:kCFStreamPropertySSLPeerTrust];
SecTrustResultType trustResult;
OSStatus status = SecTrustEvaluate(trust, &trustResult);
if (status != errSecSuccess) {
    NSLog(@"failed to evaluate");
} else {
    OSStatus trustResultCode = SecTrustGetTrustResult(trust, &trustResult);
}

The final trustResultCode should give you a specific reason why trust evaluation failed. Depending upon what that specific reason is, you can modify the SecTrustRef or create a whole new one to get a successful evaluation.

The Apple Tech Note HTTPS Server Trust Evaluation is really informative.

Dave Weston
  • 6,527
  • 1
  • 29
  • 44