CFNetwork SSLHandshake failed (-9824 -> -9829) in iOS-App

Hi,


I tried to implement a connection between a iOS-App (iOS 9 /10) and a TCP-server with TLS 1.2 by using a real certificate for testing, not a self signed.


As you can see in the code, I use NSStream for the data transfer between the server and the app.

    CFReadStreamRef readStream;
    CFWriteStreamRef writeStream;
    CFDictionaryRef sslSettingsDict;

    CFStreamCreatePairWithSocketToHost(NULL, (__bridge CFStringRef)ns_IP, ui_Port, &readStream, &writeStream);

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

    [inputStream setDelegate:(id)self];
    [outputStream setDelegate:(id)self];

    [inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
    [outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
    
    const void* keys[] = {  kCFStreamSSLLevel, kCFStreamPropertyShouldCloseNativeSocket};
    const void* values[] = { CFSTR("kCFStreamSocketSecurityLevelTLSv1_2"), CFSTR("kCFBooleanTrue") };
    
    sslSettingsDict = CFDictionaryCreate(kCFAllocatorDefault, keys, values, 2,
                                                            &kCFTypeDictionaryKeyCallBacks,
                                                            &kCFTypeDictionaryValueCallBacks);

    CFReadStreamSetProperty(readStream, kCFStreamPropertySSLSettings, sslSettingsDict);
    CFWriteStreamSetProperty(writeStream, kCFStreamPropertySSLSettings, sslSettingsDict);
    CFRelease(sslSettingsDict);

    [inputStream open];
    [outputStream open];


We tried several settings on the server. First step was that the client verifies the certificate from the server. That was ok with my code.

In the second step the server expects a certificate from the client. But now I get the error CFNetwork SSLHandshake failed (-9824 -> -9829).


I added NSAllowsArbitraryLoads to my Info.plist, but that doesn't make a difference.


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


Thanks for the help 🙂

Accepted Reply

Let’s start with some random things:

  • There's no need to monkey with

    kCFStreamPropertyShouldCloseNativeSocket
    when you create a stream pair with
    CFStreamCreatePairWithSocketToHost
    ; it defaults to true in that case.
  • You don’t need to set

    kCFStreamPropertySSLSettings
    on both streams; the streams form a pair, so setting properties on one applies those properties to both.
  • Life is much easier if you learn to love toll-free bridging. Personally, I never use CFDictionary, I just work with the much nicer NSDictionary (or Swift

    Dictionary
    ) type and then toll-free bridge to CFDictionary at the last minute.
  • It’s best to avoid

    kCFStreamSocketSecurityLevelTLSv1_2
    . CFSocketStream will use TLS 1.2 by default. If you want to constrain it to only use TLS 1.2, use
    kCFStreamPropertySSLContext
    to get the
    SSLContext
    and set the minimum TLS version via
    SSLSetProtocolVersionMin
    .
  • App Transport Security only applies to high-level HTTP[S] APIs (NSURLSession, NSURLConnection, and things layered on top of those). It has no effect on CFSocketStream.

You should check out my TLSTool sample code, which shows these techniques in action.

As to your actual problem, you wrote:

First step was that the client verifies the certificate from the server. That was ok with my code.

Cool.

In the second step the server expects a certificate from the client.

You set that via the

kCFStreamSSLCertificates
subkey of
kCFStreamPropertySSLSettings
. Again, TLSTool shows an example of this.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Replies

Let’s start with some random things:

  • There's no need to monkey with

    kCFStreamPropertyShouldCloseNativeSocket
    when you create a stream pair with
    CFStreamCreatePairWithSocketToHost
    ; it defaults to true in that case.
  • You don’t need to set

    kCFStreamPropertySSLSettings
    on both streams; the streams form a pair, so setting properties on one applies those properties to both.
  • Life is much easier if you learn to love toll-free bridging. Personally, I never use CFDictionary, I just work with the much nicer NSDictionary (or Swift

    Dictionary
    ) type and then toll-free bridge to CFDictionary at the last minute.
  • It’s best to avoid

    kCFStreamSocketSecurityLevelTLSv1_2
    . CFSocketStream will use TLS 1.2 by default. If you want to constrain it to only use TLS 1.2, use
    kCFStreamPropertySSLContext
    to get the
    SSLContext
    and set the minimum TLS version via
    SSLSetProtocolVersionMin
    .
  • App Transport Security only applies to high-level HTTP[S] APIs (NSURLSession, NSURLConnection, and things layered on top of those). It has no effect on CFSocketStream.

You should check out my TLSTool sample code, which shows these techniques in action.

As to your actual problem, you wrote:

First step was that the client verifies the certificate from the server. That was ok with my code.

Cool.

In the second step the server expects a certificate from the client.

You set that via the

kCFStreamSSLCertificates
subkey of
kCFStreamPropertySSLSettings
. Again, TLSTool shows an example of this.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"