Do you need help on a specific subject? Use the contact form (Request a blog entry) on the right hand side.
Showing posts with label SwifterSockets. Show all posts
Showing posts with label SwifterSockets. Show all posts

2017-02-01

Swift and OpenSSL, part 3: Connecting to a SSL Server

In this part I will talk about the high-level view of creating a secure connection to a server. I will use the methods and wrappers from SecureSockets as these are easier to read and follow. (SecureSockets can be downloaded from github)

If you are new to socket programming it is probably best to first read up on socket programming. I always refer to beej's guide on network programming as a very fine resource for this.

It turns out that the way we need to use OpenSSL is very similar to normal socket programming.

For the POSIX "connect" call, there is a parallel "SSL_connect".

For the POSIX "accept" call there is a parallel "SSL_accept". For "read" there is "SSL_read" and for "write" there is "SSL_write".

The main difference is that instead of working on sockets, the openSSL calls work on "sessions".

The way this is implemented is that we first have to set up the socket and then we call the session operations.

An example, suppose we want to connect to a server:

First we set up the socket:

    var socket: Int32
    switch connectToTipServer(atAddress: address, atPort: port) {
    case let .error(message): return .error(message: message)
    case let .success(s): socket = s
    }

I'll hope you pardon me the usage of my own SwifterSockets framework (which can be downloaded for free from github)

The connectToTipServer returns a socket on success. From this moment on, the socket is connected to the server but no data has been transferred. The server has accepted the connection and is now waiting for the client to start transmitting something. In this case -of course- the server wants to start a SSL-handshake. But it waits for the client to initiate this.

At the client side we continue the sequence by creating a session:

    guard let ssl = Ssl(context: ctx) else {
        return .error(message: "Failed to create Ssl,\n\n\(errPrintErrors())")
    }

Then we assign the socket to the session:


    switch ssl.setFd(socket) {
    case let .error(message): return .error(message: "Failed to set socket to ssl,\n\(message)")
    case .success: break
    }

And then we tell the client to connect securely to the server:


    switch ssl.connect(socket: socket, timeout: timeoutTime) {
    case .timeout: return .timeout
    case let .error(message): return .error(message: "Failed to connect via SSL,\n\(message)")
    case .closed: return .error(message: "Connection unexpectedly closed")
    case .ready: break
    }

Before we start transferring data back and forth, we need to check if the connection was established securely.

This is a two step approach: first we need to know if the server did send a certificate, and secondly we need to know if the certificate is valid.


    guard let x509 = ssl.getPeerCertificate() else {
        return .error(message: "Verification failed, no certificate received")
    }
        
    switch ssl.getVerifyResult() {
    case let .error(message): return .error(message: "Verification failed,\n\(message)")  
    case .success: break
    }

Once this sequence completes, the "ssl" session is ready to transfer data back and forth.

Conceptually that is all there is to a secure client / server connection. But I did gloss over some details as you noticed.

Still the big picture on the client side is complete.

Just in case you want to know how the data transfers look like:

Writing: let res = SSL_write(optr, buf, num)

Reading: let res = SSL_read(optr, buf, num)

These are almost "drop in" replacements to the POSIX read and write. Except that they operate on the session instead of the socket.

Once the session can be closed, use SSL_shutdown(optr). It is possible to simply terminate the connection, but let's play nice ;-)

The next post will address some of the details that I glossed over today.

2017-02-07: One more thing...

Managing and maintaining a SSL connection means that the SSL layer will start read & write actions on its own. When using the POSIX select call to wait for events to happen on a connect, write, read etc, be sure to monitor for all events. For example monitoring only read events for a SSL_read will most likely fail at some point because the SSL layer wants to write.

Happy coding...

Did this help?, then please help out a small independent.
If you decide that you want to make a small donation, you can do so by clicking this
link: a cup of coffee ($2) or use the popup on the right hand side for different amounts.
Payments will be processed by PayPal, receiver will be sales at balancingrock dot nl
Bitcoins will be gladly accepted at: 1GacSREBxPy1yskLMc9de2nofNv2SNdwqH

We don't get the world we wish for... we get the world we pay for.

2016-08-06

New versions for SwifterLog, SwifterSockets, SwifterJSON and Swiftfire | Swift 3 adaptation.

2016.08.17: Just upgraded to Xcode 8 beta 6, and lo and behold... have to do it all again...

I just pushed the new versions for SwifterLog (v0.9.12), SwifterSockets (v0.9.6), SwifterJSON (v0.9.10) and Swiftfire (v0.9.13) to github.

All of them upgrades to Swift 3 (beta), so only make the change if you are also working with Swift 3/Xcode 8 beta 3.

All in all I am rather pleased with the changes in Swift 3, they do make the code better. It took me about a week to change all of the above mentioned source code. Which is a lot, so yes, upgrading an existing code base to Swift 3 will cost you some. Worth it or not, that is something only you can decide. For open source projects like the above, it's a no-brainer of course.

So... on and forward to v1.0... :-)

Happy coding...

Did this help?, then please help out a small independent.
If you decide that you want to make a small donation, you can do so by clicking this
link: a cup of coffee ($2) or use the popup on the right hand side for different amounts.
Payments will be processed by PayPal, receiver will be sales at balancingrock dot nl
Bitcoins will be gladly accepted at: 1GacSREBxPy1yskLMc9de2nofNv2SNdwqH

We don't get the world we wish for... we get the world we pay for.

2016-03-02

Socket Programming in Swift: Part 9 - SwifterSockets

Updated on 2016-08-12 for Swift 3 Xcode 8 beta 3

I admit it, the name kind-a-sucks. But after having published SwifterJSON, SwifterLog it seems that SwifterSockets is a logical choice. Besides, calling it SocketUtils (used as working name) creates naming conflicts with the thousands of other SocketUtils out there. So SwifterSockets it is.

What is it? It is a collection of APIs that use the Unix socket calls to implement network transfers.

There are few general purpose APIs:

Some socket related helper functions:

enum SocketAddress {...}
func isValidIpAddress(_ address: String) -> Bool
func sockaddrDescription(_ addr: UnsafePointer<sockaddr>) -> (ipAddress: String?, portNumber: String?) { ... }
func fdZero(_ set: inout fd_set) { ... }
func fdSet(_ fd: Int32, set: inout fd_set) { ... }
func fdClr(_ fd: Int32, set: inout fd_set) { ... }
func fdIsSet(_ fd: Int32, set: inout fd_set) -> Bool { ... }
func logAddrInfoIPAddresses(_ infoPtr: UnsafeMutablePointer<addrinfo>) -> String { ... }
func logSocketOptions(_ socket: Int32) -> String { ... }
func closeSocket(_ socket: Int32?) -> Bool?


Then we get to the meat of the matter, initializing a server socket:

func setupServer(
        onPort port: String,
        maxPendingConnectionRequest: Int32) -> SetupServerReturn { ... }
func setupServerOrThrow(
        onPort port: String,
        maxPendingConnectionRequest: Int32) throws -> Int32 { ... }
func setupServerOrThrowAsync(
        onPort port: String,
        maxPendingConnectionRequest: Int32,
        postProcessingQueue: DispatchQueue,
        postProcessor: SetupServerPostProcessing) throws { ... }

Initializing a client socket:

func connectToServer(atAddress address: String, atPort port: String) -> ClientResult { ... }
func connectToServerOrThrow(atAddress address: String, atPort port: String) throws -> Int32 { ... }
func connectToServerOrThrowAsync(
        atAddress address: String,
        atPort port: String,
        onQueue queue: DispatchQueue,
        postProcessor: ClientPostProcessing) throws { ... }
func connectToServerOrThrowTransmitAsync(
        atAddress address: String,
        atPort port: String,
        transmitQueue: DispatchQueue,
        transmitData: String,
        transmitTimeout: TimeInterval,
        transmitTelemetry: TransmitTelemetry?,
        transmitPostProcessor: TransmitPostProcessing?) throws { ... }
func connectToServerOrThrowTransmitAsync(
        atAddress address: String,
        atPort port: String,
        transmitQueue: DispatchQueue,
        transmitData: Data,
        transmitTimeout: TimeInterval,
        transmitTelemetry: TransmitTelemetry?,

        transmitPostProcessor: TransmitPostProcessing?) throws { ... }
func connectToServerOrThrowTransmitAsync(
        atAddress address: String,
        atPort port: String,
        queue: DispatchQueue,
        transmitData: UnsafeBufferPointer<UInt8>,
        transmitTimeout: TimeInterval,
        transmitTelemetry: TransmitTelemetry?,
        transmitPostProcessor: TransmitPostProcessing?) throws { ... }

Setting up a data transfer:

func transmit(
        toSocket socket: Int32,
        fromBuffer buffer: UnsafeBufferPointer<UInt8>,
        timeout: TimeInterval,
        telemetry: TransmitTelemetry?) -> TransmitResult { ... }
func transmit(
        toSocket socket: Int32,
        data: Data,
        timeout: TimeInterval,

        telemetry: TransmitTelemetry?) -> TransmitResult { ... }
func transmit(
        toSocket socket: Int32,
        string: String,
        timeout: TimeInterval,
        telemetry: TransmitTelemetry?) -> TransmitResult { ... }
func transmitOrThrow(
        toSocket socket: Int32,
        fromBuffer buffer: UnsafeBufferPointer<UInt8>,
        timeout: TimeInterval,
        telemetry: TransmitTelemetry?) throws { ... }
func transmitOrThrow(
        toSocket socket: Int32,
        data: Data,
        timeout: TimeInterval,

        telemetry: TransmitTelemetry?) throws { ... }
func transmitOrThrow(
        toSocket socket: Int32,
        string: String,
        timeout: TimeInterval,
        telemetry: TransmitTelemetry?) throws { ... }
func transmitAsync(
        onQueue queue: DispatchQueue,
        toSocket socket: Int32,
        fromBuffer buffer: UnsafeBufferPointer<UInt8>,
        timeout: TimeInterval,
        telemetry: TransmitTelemetry?,
        postProcessor: TransmitPostProcessing?) { ... }
func transmitAsync(
        onQueue queue: DispatchQueue,
        toSocket socket: Int32,
        data: Data,
        timeout: TimeInterval,
        telemetry: TransmitTelemetry?,

        postProcessor: TransmitPostProcessing?) { ... }
func transmitAsync(
        onQueue queue: DispatchQueue,
        toSocket socket: Int32,
        string: String,
        timeout: TimeInterval,
        telemetry: TransmitTelemetry?,
        postProcessor: TransmitPostProcessing?) { ... }

Setting up the receiving end:

func receiveBytes(
        fromSocket socket: Int32,
        intoBuffer buffer: UnsafeMutableBufferPointer<UInt8>,
        timeout: TimeInterval,
        dataEndDetector: DataEndDetector,
        telemetry: ReceiveTelemetry?) -> ReceiveResult { ... }
func receiveData(
        fromSocket socket: Int32,
        timeout: TimeInterval,
        dataEndDetector: DataEndDetector,

        telemetry: ReceiveTelemetry?) -> ReceiveResult { ... }
func receiveString(
        fromSocket socket: Int32,
        timeout: TimeInterval,
        dataEndDetector: DataEndDetector,
        telemetry: ReceiveTelemetry?) -> ReceiveResult { ... }
func receiveBytesOrThrow(
        fromSocket socket: Int32,
        intoBuffer buffer: UnsafeMutableBufferPointer<UInt8>,
        timeout: TimeInterval,
        dataEndDetector: DataEndDetector,
        telemetry: ReceiveTelemetry?) throws -> Int { ... }
func receiveNSDataOrThrow(
        fromSocket socket: Int32,
        timeout: TimeInterval,
        dataEndDetector: DataEndDetector,

        telemetry: ReceiveTelemetry?) throws -> Data { ... }
func receiveStringOrThrow(
        fromSocket socket: Int32,
        timeout: TimeInterval,
        dataEndDetector: DataEndDetector,

        telemetry: ReceiveTelemetry?) throws -> String { ... }
func receiveAsync(
        onQueue queue: DispatchQueue,
        fromSocket socket: Int32,
        timeout: TimeInterval,
        dataEndDetector: DataEndDetector,
        telemetry: ReceiveTelemetry?,
        postProcessor: ReceivePostProcessing?) { ... }

And to accept a connection request:

func acceptNoThrow(
        onSocket socket: Int32,
        abortFlag: inout Bool,
        abortFlagPollInterval: TimeInterval?,
        timeout: TimeInterval? = nil,
        telemetry: AcceptTelemetry? = nil)
        -> AcceptResult { ... }
func acceptOrThrow(
        onSocket socket: Int32,
        abortFlag: inout Bool,
        abortFlagPollInterval: TimeInterval?,
        timeout: TimeInterval? = nil,
        telemetry: AcceptTelemetry?) throws -> Int32 { ... }

Here is how to setup a server that accepts connection requests and processes the received data:

func serverSetup_oldSchool() {
    
    
    // Assume that incoming data ends when a 0x00 byte is received
    
    class DataEndsOnZeroByte: DataEndDetector {
        func endReached(buffer: UnsafeBufferPointer<UInt8>) -> Bool {
            for byte in buffer {
                if byte == 0x00 { return true }
            }
            return false
        }
    }

    
    // Setup a socket for usage as the server socket
    
    let setupResult = SwifterSockets.setupServer(onPort: "80", maxPendingConnectionRequest: 10)
    
    guard case let SwifterSockets.SetupServerReturn.socket(serverSocket) = setupResult else { return }
    
    
    
    // Start the accept loop (ends on accept errors only)
    
    var neverAborts: Bool = false
    
    while true {
        
        
        // Accept a (the next) connection request
        
        let acceptResult = SwifterSockets.acceptNoThrow(onSocket: serverSocket, abortFlag: &neverAborts, abortFlagPollInterval: 10.0, timeout: nil, telemetry: nil)
        
        guard case let SwifterSockets.AcceptResult.accepted(socket: receiveSocket) = acceptResult else { break }
        
        
        // Receive the incoming data
        
        let dataEndsOnZeroByte = DataEndsOnZeroByte()
        
        let receiveResult = SwifterSockets.receiveData(fromSocket: receiveSocket, timeout: 10.0, dataEndDetector: dataEndsOnZeroByte, telemetry: nil)
        
        guard case let SwifterSockets.ReceiveResult.ready(data: receivedData) = receiveResult else { break }
        
        
        // Process the data that was received
        
        processReceivedData(data: receivedData as? Data)
        
        
        // Close the socket
        
        SwifterSockets.closeSocket(receiveSocket)
    }
    
    SwifterSockets.closeSocket(serverSocket)
}

SwifterSockets is available from Github
The project homepage is at Balancingrock

Check out my Port Spy app in the App Store. A utility that helps you debug your socket based application and includes its own source code. So you can see first hand how to implement socket based io in Swift. And you will be helping this blog!

Happy coding...

Did this help?, then please help out a small independent.
If you decide that you want to make a small donation, you can do so by clicking this
link: a cup of coffee ($2) or use the popup on the right hand side for different amounts.
Payments will be processed by PayPal, receiver will be sales at balancingrock dot nl
Bitcoins will be gladly accepted at: 1GacSREBxPy1yskLMc9de2nofNv2SNdwqH

We don't get the world we wish for... we get the world we pay for.