0

I am writing a TCPClient class for communicating with a SSL/TLS TCP socket server in Swift, and the server is automatically generating self-signed certificates when it doesn't find or isn't able to successfully use a valid certificate. These self-signed certificates are being used as the socket server

I am trying to set up an iOS app to prompt the user with a similar dialog of browsing an invalidly signed webpage when the TCP client gets an invalid SSL certificate.

Here's the dialog I want to display to users:

Verify Server Identity

Here's how the TCP connection is made:

public class TCPClient: NSObject, NSStreamDelegate {
    let serverAddress = "127.0.0.1"
    let serverPort = 7000

    private var inputStream: NSInputStream?
    private var outputStream: NSOutputStream?

    public func connect() {
        println("connecting...")

        NSStream.getStreamsToHostWithName(self.serverAddress, port: self.serverPort, inputStream: &self.inputStream, outputStream: &self.outputStream)

        self.inputStream!.delegate = self
        self.outputStream!.delegate = self

        self.inputStream!.open()
        self.outputStream!.open()

        self.inputStream!.setProperty(NSStreamSocketSecurityLevelTLSv1, forKey: NSStreamSocketSecurityLevelKey)
        self.outputStream!.setProperty(NSStreamSocketSecurityLevelTLSv1, forKey: NSStreamSocketSecurityLevelKey)

        var buffer: [UInt8] = [0]
        // buffer is a UInt8 array containing bytes of a string.
        self.outputStream!.write(&buffer, maxLength: buffer.count)
    }

    public func stream(aStream: NSStream, handleEvent eventCode: NSStreamEvent) {
        println("stream event.")
    }
}

Currently I only see this when trying to connect:

connecting...
2015-09-13 19:48:25.562 AppName[] CFNetwork SSLHandshake failed (-9807)
Hank Brekke
  • 2,024
  • 2
  • 24
  • 33

2 Answers2

1

First, make sure you are familiar with new app transport security motes as of iOS 9 and OS X 10.11, and apply as needed. It is preferable to support proper technologies in your server, if possible.

Before connecting your streams, use NSURLSession to create a mock task to your server. Implement the - URLSession:didReceiveChallenge:completionHandler: delegate method, and verify the certificate of the server there. If it is untrusted, display the alert to the user. I am not familiar with a system-provided method of displaying certificate information, so you may need to implement that. Once the user approves, add the certificate from the challenge to your app's keychain. This will allow the streams to connect to the server. Once the challenge is over, attempt your stream connection as usual and proceed with normal app workflow.

Léo Natan
  • 56,823
  • 9
  • 150
  • 195
0

The settings are bound to fail because the properties are changed after the connection is opened, and according to the documentation:

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.

Furthermore, NSStream class does not support connecting to a remote host on iOS. Paraphrasing the Stream Programming Guide, use the CFStream to establish a socket connection, then cast your CFStreams to NSStreams.

Addressing your code snippet in Swift 3, it becomes:

inputStream.schedule(in: RunLoop.main, forMode: RunLoopMode.defaultRunLoopMode)
outputStream.schedule(in: RunLoop.main, forMode: RunLoopMode.defaultRunLoopMode)

inputStream.setProperty(StreamSocketSecurityLevel.tlSv1,
                        forKey: Stream.PropertyKey.socketSecurityLevelKey)
outputStream.setProperty(StreamSocketSecurityLevel.tlSv1,
                         forKey: Stream.PropertyKey.socketSecurityLevelKey)

inputStream.open()
outputStream.open()
SwiftArchitect
  • 47,376
  • 28
  • 140
  • 179