Secure Transport TLS如何与Swift中的BSD套接字一起使用?

我正在尝试使用安全传输与BSD套接字使用Swift。 它似乎应该足够简单,但我无法让它工作,关于这个主题的文档很少。

我已经将我的问题归结为一个简单的“Socket”课程,我已经(据我所知)满足了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, UnsafeMutablePointer) -> OSStatus)>.alloc(1) ump.initialize(sslWriteCallback) return CFunctionPointer<((SSLConnectionRef, UnsafePointer, UnsafeMutablePointer) -> OSStatus)>(COpaquePointer(ump)) } } var sslReadCallbackFunc: SSLReadFunc { get { let ump = UnsafeMutablePointer<((SSLConnectionRef, UnsafeMutablePointer, UnsafeMutablePointer) -> OSStatus)>.alloc(1) ump.initialize(sslReadCallback) return CFunctionPointer<((SSLConnectionRef, UnsafeMutablePointer, UnsafeMutablePointer) -> 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.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, dataLength: UnsafeMutablePointer) -> OSStatus { let bytesRead = read(socketfd, data, UnsafePointer(dataLength).memory) return noErr } func sslWriteCallback(connection: SSLConnectionRef, data: UnsafePointer, dataLength: UnsafeMutablePointer) -> OSStatus { let sent = Darwin.sendto(socketfd, data, UnsafePointer(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 } } 

我通过一个简单的实例测试了非TLS套接字通信:

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

使用netcat在目标机器上运行echo服务器。 这很好用。

 nc -l -p 8080 

尝试将套接字包装在Secure Transport的TLS(调用makeSecure()方法)时,在调用SSLHandshake(…)时出现EXC_BAD_ADDRESS(code = 2,address = …)错误。 有没有人有任何关于我在这里缺少什么的指示?

编辑

我可以看到Console推出:

 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 

编辑2

我在Xcode 7 beta中使用了Swift 2。 见下文。

从Xcode 7 beta附带的Swift 2开始,Swift中的Function Pointers工作并且已经大大简化了。 我把上面的例子变成了这个,它有效:

 import Foundation func sslReadCallback(connection: SSLConnectionRef, data: UnsafeMutablePointer, var dataLength: UnsafeMutablePointer) -> OSStatus { let socketfd = UnsafePointer(connection).memory let bytesRequested = dataLength.memory let bytesRead = read(socketfd, data, UnsafePointer(dataLength).memory) if (bytesRead > 0) { dataLength = UnsafeMutablePointer.alloc(1) dataLength.initialize(bytesRead) if bytesRequested > bytesRead { return Int32(errSSLWouldBlock) } else { return noErr } } else if (bytesRead == 0) { dataLength = UnsafeMutablePointer.alloc(1) dataLength.initialize(0) return Int32(errSSLClosedGraceful) } else { dataLength = UnsafeMutablePointer.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, var dataLength: UnsafeMutablePointer) -> OSStatus { let socketfd = UnsafePointer(connection).memory let bytesToWrite = dataLength.memory let bytesWritten = write(socketfd, data, UnsafePointer(dataLength).memory) if (bytesWritten > 0) { dataLength = UnsafeMutablePointer.alloc(1) dataLength.initialize(bytesWritten) if (bytesToWrite > bytesWritten) { return Int32(errSSLWouldBlock) } else { return noErr } } else if (bytesWritten == 0) { dataLength = UnsafeMutablePointer.alloc(1) dataLength.initialize(0) return Int32(errSSLClosedGraceful) } else { dataLength = UnsafeMutablePointer.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) } 

Hans的回答似乎是不必要地分配内存。 以下是Swift 3.1版本,它具有更多错误检查和URL支持,并获取返回的通用名称(而不是实际读取或写入数据)。

 func getCNforSSL(at url:URL, port:UInt16) -> String? { var socketfd = Darwin.socket(AF_INET, SOCK_STREAM, IPPROTO_TCP) guard let ip = urlToIP(url) else { NSLog("Could not get IP from URL \(url)") return nil } let inAddr = in_addr(s_addr: inet_addr(ip)) var addr = sockaddr_in(sin_len: __uint8_t(MemoryLayout.size), sin_family: sa_family_t(AF_INET), sin_port: CFSwapInt16(port), sin_addr: inAddr, sin_zero: (0, 0, 0, 0, 0, 0, 0, 0)) var sock_addr = sockaddr(sa_len: 0, sa_family: 0, sa_data: (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)) _ = memcpy(&sock_addr, &addr, MemoryLayout.size) guard connect(socketfd, &sock_addr, socklen_t(MemoryLayout.size)) == 0 else { NSLog("Failed connection for \(url) port \(port) with error \(Darwin.errno)") return nil } defer { if close(socketfd) != 0 { NSLog("Error closing socket for \(url) port \(port) with error \(Darwin.errno)") } } guard let sslContext = SSLCreateContext(kCFAllocatorDefault, .clientSide, .streamType) else { NSLog("Could not create SSL Context for \(url) port \(port)") return nil } defer { SSLClose(sslContext) } SSLSetIOFuncs(sslContext, sslReadCallback, sslWriteCallback) SSLSetConnection(sslContext, &socketfd) SSLSetSessionOption(sslContext, .breakOnServerAuth, true) var secTrust:SecTrust? = nil var status:OSStatus = 0 var subject:String? = nil repeat { status = SSLHandshake(sslContext) if status == errSSLPeerAuthCompleted { SSLCopyPeerTrust(sslContext, &secTrust) if let trust = secTrust { // 0 always garunteed to exist let cert = SecTrustGetCertificateAtIndex(trust, 0)! subject = SecCertificateCopySubjectSummary(cert) as String? } } } while status == errSSLWouldBlock guard status == errSSLPeerAuthCompleted else { NSLog("SSL Handshake Error for \(url) port \(port) OSStatus \(status)") return nil } return subject } func sslReadCallback(connection: SSLConnectionRef, data: UnsafeMutableRawPointer, dataLength: UnsafeMutablePointer) -> OSStatus { let socketfd = connection.load(as: Int32.self) let bytesRequested = dataLength.pointee let bytesRead = read(socketfd, data, UnsafePointer(dataLength).pointee) if (bytesRead > 0) { dataLength.initialize(to: bytesRead) if bytesRequested > bytesRead { return Int32(errSSLWouldBlock) } else { return noErr } } else if (bytesRead == 0) { dataLength.initialize(to: 0) return Int32(errSSLClosedGraceful) } else { dataLength.initialize(to: 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: UnsafeRawPointer, dataLength: UnsafeMutablePointer) -> OSStatus { let socketfd = connection.load(as: Int32.self) let bytesToWrite = dataLength.pointee let bytesWritten = write(socketfd, data, UnsafePointer(dataLength).pointee) if (bytesWritten > 0) { dataLength.initialize(to: bytesWritten) if (bytesToWrite > bytesWritten) { return Int32(errSSLWouldBlock) } else { return noErr } } else if (bytesWritten == 0) { dataLength.initialize(to: 0) return Int32(errSSLClosedGraceful) } else { dataLength.initialize(to: 0) if (EAGAIN == errno) { return Int32(errSSLWouldBlock) } else { return Int32(errSecIO) } } } private func urlToIP(_ url:URL) -> String? { guard let hostname = url.host else { return nil } guard let host = hostname.withCString({gethostbyname($0)}) else { return nil } guard host.pointee.h_length > 0 else { return nil } var addr = in_addr() memcpy(&addr.s_addr, host.pointee.h_addr_list[0], Int(host.pointee.h_length)) guard let remoteIPAsC = inet_ntoa(addr) else { return nil } return String.init(cString: remoteIPAsC) } 

我问网络大师同行你的问题; 这是他的回答:

此人是SOL,因为Secure Transport要求您实现C函数回调,而Swift当前不支持。

我建议开发人员使用CFSocketStream,它负责TLS并且可以从Swift轻松调用。 请参阅TLSTool示例代码。

https://developer.apple.com/library/mac/samplecode/SC1236/