CFStreams: setting NSStreamSocketSecurityLevelKey after NSStream creation

I have a few questions about network streams, particularly NSStreamSocketSecurityLevelKey and what looks like a private property on streams, _kCFStreamPropertySocketPeerName


The documentation for NSStreamSocketSecurityLevelKey on https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/Streams/Articles/NetworkStreams.html states:


For SSL security, NSStream defines various security-level properties (for example, NSStreamSocketSecurityLevelSSLv2). You set these properties by sending setProperty:forKey: to the stream object using the key NSStreamSocketSecurityLevelKey, as in this sample message:


[inputStream setProperty:NSStreamSocketSecurityLevelTLSv1 forKey:NSStreamSocketSecurityLevelKey];


You must set the property before you open the stream. Once it opens, it goes through a handshake protocol to find out what level of SSL security the other side of the connection is using. If the security level is not compatible with the specified property, the stream object generates an error event.


We've seen that it is possible to set this property after opening the stream and sending/receiving some initial data, for example doing a handshake with a CONNECT proxy before starting the real TLS session with the final end point.


Is this ability to start the TLS negotiation at a later date via a delayed setProperty:forKey:NSStreamSocketSecurityLevelKey fully supported?


Also, there is still a problem in the CONNECT proxy case above: the SNI sent to the end-point will be the proxy host not the intended host. Facebook's SocketRocket library uses what looks like a private property to override this:


https://github.com/facebook/SocketRocket/blob/41b57bb2fc292a814f758441a05243eb38457027/SocketRocket/Internal/Proxy/SRProxyConnect.m#L99


[self.outputStream setProperty:self.url.host forKey:@"_kCFStreamPropertySocketPeerName"];


Is this supported? Even though it's kind of private, is this something we could use?


PS: Calling Quinn the Eskimo 😉

Is this ability to start the TLS negotiation at a later date via a delayed

setProperty:forKey:
[of]
NSStreamSocketSecurityLevelKey
fully supported?

Yes. It’s a requirement for implementing STARTTLS, which is used by many different network protocols.

Is this supported?

No it is not. There are two supported ways to override the peer name:

  • kCFStreamSSLPeerName
  • kCFStreamPropertySSLContext
    and then
    SSLSetPeerDomainName

However, I’m not sure how well these play with STARTTLS. I don’t think you’ll be able to continue using

NSStreamSocketSecurityLevelKey
, but you may be able to set the equivalent
kCFStreamPropertySSLSettings
property and include
kCFStreamSSLPeerName
within that dictionary. All of the
kCFStreamPropertySSLSettings
dictionary properties should be applied as one, so that might actually work.

ps Answering this question would be a lot easier if the folks who wrote the code you referenced had:

  • Asked for formal help from Apple before using this private API

  • If that determined that there was no supported way to do this, filed an enhancement request requesting a better option

  • Commented the code with information about why they chose this option and the incident or bug number

Share and Enjoy

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

let myEmail = "eskimo" + "1" + "@apple.com"
CFStreams: setting NSStreamSocketSecurityLevelKey after NSStream creation
 
 
Q