CFNetwork SSLHandshake failing

So I'm still working on my HTTP/2 implementation. I know, I'm crazy. I'm opening an InputStream and OutputStream to the apns site but I'm getting a CFNetwork SSLHandshake failure. I'm not sure where to even start looking for how to handle this. I've opened my connection like so:



        Stream.getStreamsToHost(withName: host, port: port, inputStream: &inputStream, outputStream: &outputStream)

        super.init()

        inputStream!.delegate = self
        inputStream!.setProperty(StreamSocketSecurityLevel.tlSv1.rawValue, forKey: .socketSecurityLevelKey)
        inputStream!.schedule(in: .main, forMode: .defaultRunLoopMode)
        inputStream!.open()

        outputStream!.delegate = self
        outputStream!.setProperty(StreamSocketSecurityLevel.tlSv1.rawValue, forKey: .socketSecurityLevelKey)
        outputStream!.schedule(in: .main, forMode: .defaultRunLoopMode)
        outputStream!.open()


And then I write the "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n" message to the outputStream. I'm using api.development.push.apple.com as the host and 443 as the port.


After a few seconds I get back this:


MessageTracer: load_domain_whitelist_search_tree:73: Search tree file's format version number (0) is not supported

MessageTracer: Falling back to default whitelist

CFNetwork SSLHandshake failed (-9806)

TCP Conn 0x60c000160a80 SSLHandshake failed (-9806)

SocketStream write error [0x60c000160a80]: 3 -9806

I think the informative thing to do is for you to turn on the full ssl diagnostics.

See https://developer.apple.com/library/content/qa/qa1887/_index.html

to see where the SSL handshake goes wrong.

I'm not sure how that helps me. I see a couple packets being sent, but the end result is still that I don't know how to handle the SSL handshake in my code. Googling for it is basically a dead-end as I either see ios info about ATS, URLSession, or general errors in deployed software.

I know, I'm crazy.

You’re so crazy you don’t even know how crazy you are!

As to your code, there’s a bunch of problems that I can see:

  • You’re specifically forcing the connection to use TLS 1.0, a long-obsolete version of TLS that it likely to be rejected by the server. You should use

    NSStreamSocketSecurityLevelNegotiatedSSL
    , or set this up via the low-level properties found in
    <CFNetwork/CFSocketStream.h>
    (which you’re going to need anyway, see below).
  • APNs authenticates clients via their TLS client certificate, so you’ll need to set a client identity on the stream (via

    kCFStreamSSLCertificates
    ).
  • When you run HTTP/2 over TLS you have to explicitly tell the server by setting an entry in the ALPN extension. You can’t do this using a high-level API; you’ll have to get the Secure Transport context, via the

    kCFStreamPropertySSLContext
    property, and then call
    SSLSetALPNProtocols
    on that.
  • You’re not checking the function result of

    setProperty(_:forKey:)
    . While this is unlikely to be relevant in production, in development it’s important to check that result so you can tell when you’ve done something wrong.

Also, keep in mind the following:

  • TCP connections are represented by a stream pair, where each stream shares the state of the connection. Thus there’s no need to set properties on both streams; setting it on one is just fine.

  • Stream
    and
    CFStream
    are toll-free bridged, so you can get and set
    CFStream
    properties using the
    Stream
    property API.

Finally, you should check out the TLSTool sample code, which shows a bunch of these techniques.

Share and Enjoy

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

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

Thanks. This is just a great learning experience trying to implement this. You mentioned the kCFStreamPropertySSLContext. When I call property(forKey:) on the Stream it wants an Stream.PropertyKey value passed in, and there's no corresponding key that would return the certificate. So I'm not sure how to get the SSL context from the Stream that I just opened.

You need to bounce through the raw value.

// First enable TLS with the default settings.

let success = inputStream.setProperty([:], forKey: Stream.PropertyKey(kCFStreamPropertySSLSettings as String))
assert(success)

// Then get the Secure Transport context for the stream pair.

let context = inputStream.property(forKey: Stream.PropertyKey(kCFStreamPropertySSLContext as String))
assert(context != nil)

Share and Enjoy

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

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

Apparently it's a known issue that SSLSetALPNProtocols isn't exported. Getting linker errors and seeing multiple people saying they have radars on this.

Indeed. We have a bug tracking this (r. [33907676][r33907676]) and I’m hoping it’ll get fixed sooner rather than later. In the meantime, note that this bug only affects macOS; the symbol is present in iOS 11.

Share and Enjoy

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

let myEmail = "eskimo" + "1" + "@apple.com"
CFNetwork SSLHandshake failing
 
 
Q