iosmacosswiftsocketssecure-transport

How should Secure Transport TLS be used with BSD sockets in Swift?


I'm trying to use Secure Transport with BSD sockets using Swift. It seems like it should be simple enough, but I can't get it to work and documentation on the subject is scarce.

I've boiled my issue down to a simple "Socket" class, where I've (to the best of my knowledge) fulfilled the requirements of Secure Transport.

import Cocoa

class Socket: NSObject {

    private let hello = "Hello!"
    private var socketfd: Int32
    private var sock_addr: sockaddr

    private var sslContext: SSLContext?

    var sslWriteCallbackFunc: SSLWriteFunc {
        get {
            let ump = UnsafeMutablePointer<((SSLConnectionRef, UnsafePointer<Void>,
                UnsafeMutablePointer<Int>) -> OSStatus)>.alloc(1)

            ump.initialize(sslWriteCallback)

            return CFunctionPointer<((SSLConnectionRef, UnsafePointer<Void>,
                UnsafeMutablePointer<Int>) -> OSStatus)>(COpaquePointer(ump))
        }
    }

    var sslReadCallbackFunc: SSLReadFunc {
        get {
            let ump = UnsafeMutablePointer<((SSLConnectionRef, UnsafeMutablePointer<Void>,
                UnsafeMutablePointer<Int>) -> OSStatus)>.alloc(1)

            ump.initialize(sslReadCallback)

            return CFunctionPointer<((SSLConnectionRef, UnsafeMutablePointer<Void>,
                UnsafeMutablePointer<Int>) -> OSStatus)>(COpaquePointer(ump))
        }
    }

    init(address: String, port: UInt16) {
        socketfd = Darwin.socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)

        var addr = Darwin.sockaddr_in(sin_len: __uint8_t(sizeof(sockaddr_in)), sin_family: sa_family_t(AF_INET), sin_port: CFSwapInt16(port), sin_addr: in_addr(s_addr: inet_addr(address)), sin_zero: (0, 0, 0, 0, 0, 0, 0, 0))
        sock_addr = Darwin.sockaddr(sa_len: 0, sa_family: 0, sa_data: (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0))
        Darwin.memcpy(&sock_addr, &addr, Int(sizeof(sockaddr_in)))

        super.init()
    }

    func connect() -> Socket {
        let err = Darwin.connect(socketfd, &sock_addr, socklen_t(sizeof(sockaddr_in)))

        return self
    }

    func makeSecure() -> Socket {
        if let umc = SSLCreateContext(nil, kSSLClientSide, kSSLStreamType) {
            sslContext = umc.takeRetainedValue()

            var status = SSLSetIOFuncs(sslContext!, sslReadCallbackFunc, sslWriteCallbackFunc)
            status = SSLSetConnection(sslContext!, &socketfd)

            SSLHandshake(sslContext!)
        }

        return self
    }

    func sendHello() -> Socket {
        let bytes = [UInt8](hello.utf8)
        let data = NSData(bytes: bytes, length: bytes.count)

        let test = UnsafeMutablePointer<Int>.alloc(1)
        test.initialize(bytes.count)

        self.sslWriteCallback(&socketfd, data: data.bytes, dataLength: test)

        return self
    }

    // MARK: - SSL Callback Methods

    func sslReadCallback(connection: SSLConnectionRef,
        data: UnsafeMutablePointer<Void>,
        dataLength: UnsafeMutablePointer<Int>) -> OSStatus {

            let bytesRead = read(socketfd, data, UnsafePointer<Int>(dataLength).memory)

            return noErr
    }

    func sslWriteCallback(connection: SSLConnectionRef,
        data: UnsafePointer<Void>,
        dataLength: UnsafeMutablePointer<Int>) -> OSStatus {

            let sent = Darwin.sendto(socketfd, data, UnsafePointer<Int>(dataLength).memory, 0, &sock_addr, socklen_t(sizeof(sockaddr_in)))
            if (sent < 0) {
                let error = NSError(domain: NSPOSIXErrorDomain, code: Int(errno), userInfo: nil)
                println(error.localizedDescription)
            } else {
                println("Sent \(sent) bytes (\(hello))")
            }

            return noErr
    }
}

I've tested the non-TLS socket communication by making a simple instance:

let socket = Socket(address: "some-ip-address", port: 8080)
socket.connect().sendHello()

and running an echo server on the target machine using netcat. This works fine.

nc -l -p 8080

Trying to wrap the socket in Secure Transport's TLS (calling the makeSecure() method) crashes at the call to SSLHandshake(...) with a EXC_BAD_ADDRESS(code=2, address=...) error. Does anyone have any pointers as to what it is I'm missing here?

EDIT

I can see Console puts out:

04/06/15 09:20:48,000 kernel[0]: Data/Stack execution not permitted: TheProject[pid 29184] at virtual address 0x100602000, protections were read-write

EDIT 2

I got it working with Swift 2 in the Xcode 7 beta. See below.


Solution

  • Starting with Swift 2 included with the Xcode 7 beta, Function Pointers in Swift work and have been greatly simplified. I turned my example above into this, which works:

    import Foundation
    
    func sslReadCallback(connection: SSLConnectionRef,
        data: UnsafeMutablePointer<Void>,
        var dataLength: UnsafeMutablePointer<Int>) -> OSStatus {
    
            let socketfd = UnsafePointer<Int32>(connection).memory
    
            let bytesRequested = dataLength.memory
            let bytesRead = read(socketfd, data, UnsafePointer<Int>(dataLength).memory)
    
            if (bytesRead > 0) {
                dataLength = UnsafeMutablePointer<Int>.alloc(1)
                dataLength.initialize(bytesRead)
                if bytesRequested > bytesRead {
                    return Int32(errSSLWouldBlock)
                } else {
                    return noErr
                }
            } else if (bytesRead == 0) {
                dataLength = UnsafeMutablePointer<Int>.alloc(1)
                dataLength.initialize(0)
                return Int32(errSSLClosedGraceful)
            } else {
                dataLength = UnsafeMutablePointer<Int>.alloc(1)
                dataLength.initialize(0)
                switch (errno) {
                case ENOENT: return Int32(errSSLClosedGraceful)
                case EAGAIN: return Int32(errSSLWouldBlock)
                case ECONNRESET: return Int32(errSSLClosedAbort)
                default: return Int32(errSecIO)
                }
            }
    }
    
    func sslWriteCallback(connection: SSLConnectionRef,
        data: UnsafePointer<Void>,
        var dataLength: UnsafeMutablePointer<Int>) -> OSStatus {
    
            let socketfd = UnsafePointer<Int32>(connection).memory
    
            let bytesToWrite = dataLength.memory
            let bytesWritten = write(socketfd, data, UnsafePointer<Int>(dataLength).memory)
    
            if (bytesWritten > 0) {
                dataLength = UnsafeMutablePointer<Int>.alloc(1)
                dataLength.initialize(bytesWritten)
                if (bytesToWrite > bytesWritten) {
                    return Int32(errSSLWouldBlock)
                } else {
                    return noErr
                }
            } else if (bytesWritten == 0) {
                dataLength = UnsafeMutablePointer<Int>.alloc(1)
                dataLength.initialize(0)
                return Int32(errSSLClosedGraceful)
            } else {
                dataLength = UnsafeMutablePointer<Int>.alloc(1)
                dataLength.initialize(0)
                if (EAGAIN == errno) {
                    return Int32(errSSLWouldBlock)
                } else {
                    return Int32(errSecIO)
                }
            }
    }
    var socketfd = Darwin.socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)
    
    var addr = Darwin.sockaddr_in(sin_len: __uint8_t(sizeof(sockaddr_in)), sin_family: sa_family_t(AF_INET), sin_port: CFSwapInt16(8080), sin_addr: in_addr(s_addr: inet_addr("192.168.0.113")), sin_zero: (0, 0, 0, 0, 0, 0, 0, 0))
    var sock_addr = Darwin.sockaddr(sa_len: 0, sa_family: 0, sa_data: (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0))
    Darwin.memcpy(&sock_addr, &addr, Int(sizeof(sockaddr_in)))
    
    var err = Darwin.connect(socketfd, &sock_addr, socklen_t(sizeof(sockaddr_in)))
    
    if let umc = SSLCreateContext(kCFAllocatorDefault, kSSLClientSide, kSSLStreamType) {
        var sslContext = umc.takeRetainedValue()
        SSLSetIOFuncs(sslContext, sslReadCallback, sslWriteCallback)
        SSLSetConnection(sslContext, &socketfd)
        SSLSetSessionOption(sslContext, kSSLSessionOptionBreakOnClientAuth, Boolean(1))
        SSLHandshake(sslContext)
    }