我正在尝试从我的iOS应用程序到我的后端服务器(Node.js)建立一个简单的套接字连接(无HTTP).已使用我自己创建的自定义CA创建并签署了服务器证书.我相信,为了让iOS信任我的服务器,我必须以某种方式将这个自定义CA证书添加到可信证书列表中,这些证书用于确定Java / Android中的TrustStore如何工作的信任类型.
我尝试使用下面的代码进行连接,但没有错误,但write()函数似乎没有成功.
主视图控制器:
- override func viewDidLoad() {
- super.viewDidLoad()
- // Do any additional setup after loading the view,typically from a nib.
- let api: APIClient = APIClient()
- api.initialiseSSL("10.13.37.200",port: 8080)
- api.write("Hello")
- api.deinitialise()
- print("Done")
- }
APIClient类
- class APIClient: NSObject,NSStreamDelegate {
- var readStream: Unmanaged<CFReadStreamRef>?
- var writeStream: Unmanaged<CFWriteStreamRef>?
- var inputStream: NSInputStream?
- var outputStream: NSOutputStream?
- func initialiseSSL(host: String,port: UInt32) {
- CFStreamCreatePairWithSocketToHost(kcfAllocatorDefault,host,port,&readStream,&writeStream)
- inputStream = readStream!.takeRetainedValue()
- outputStream = writeStream!.takeRetainedValue()
- inputStream?.delegate = self
- outputStream?.delegate = self
- inputStream!.scheduleInRunLoop(NSRunLoop.currentRunLoop(),forMode: NSDefaultRunLoopMode)
- outputStream!.scheduleInRunLoop(NSRunLoop.currentRunLoop(),forMode: NSDefaultRunLoopMode)
- let cert: SecCertificateRef? = CreateCertificateFromFile("ca",ext: "der")
- if cert != nil {
- print("GOT CERTIFICATE")
- }
- let certs: NSArray = NSArray(objects: cert!)
- let sslSettings = [
- NSString(format: kcfStreamSSLLevel): kcfStreamSocketSecurityLevelNegotiatedSSL,NSString(format: kcfStreamSSLValidatesCertificateChain): kcfBooleanFalse,NSString(format: kcfStreamSSLPeerName): kcfNull,NSString(format: kcfStreamSSLCertificates): certs,NSString(format: kcfStreamSSLIsServer): kcfBooleanFalse
- ]
- CFReadStreamSetProperty(inputStream,kcfStreamPropertySSLSettings,sslSettings)
- CFWriteStreamSetProperty(outputStream,sslSettings)
- inputStream!.open()
- outputStream!.open()
- }
- func write(text: String) {
- let data = [UInt8](text.utf8)
- outputStream?.write(data,maxLength: data.count)
- }
- func CreateCertificateFromFile(filename: String,ext: String) -> SecCertificateRef? {
- var cert: SecCertificateRef!
- if let path = NSBundle.mainBundle().pathForResource(filename,ofType: ext) {
- let data = NSData(contentsOfFile: path)!
- cert = SecCertificateCreateWithData(kcfAllocatorDefault,data)!
- }
- else {
- }
- return cert
- }
- func deinitialise() {
- inputStream?.close()
- outputStream?.close()
- }
}
我理解SSL / TLS是如何工作的,因为我在同一个应用程序的Android版本中完成了这一切.我只是对SSL的iOS实现感到困惑.
我来自Java背景,已经解决了这个问题3周.任何帮助,将不胜感激.
喜欢Swift代码中的答案,而不是目标C,但如果你只有Obj C那也可以:)
好吧,我在这个问题上花了8个星期:(但我终于设法组建了一个有效的解决方案.我必须说iOS上的SSL / TLS是一个笑话.Java Android上的Java让它死了.这是完全荒谬的,为了评估自签名证书的信任,您必须完全禁用证书链验证并自行完成.完全荒谬.无论如何,这是使用自签名服务器证书连接到远程套接字服务器(无HTTP)的完全可用的解决方案.编辑这个答案以提供更好的答案,因为我还没有更改添加发送和接收数据的代码:)
- // SecureSocket
- //
- // Created by snapper26 on 2/9/16.
- // Copyright © 2016 snapper26. All rights reserved.
- //
- import Foundation
- class ProXimityAPIClient: NSObject,StreamDelegate {
- // Input and output streams for socket
- var inputStream: InputStream?
- var outputStream: OutputStream?
- // Secondary delegate reference to prevent ARC deallocating the NSStreamDelegate
- var inputDelegate: StreamDelegate?
- var outputDelegate: StreamDelegate?
- // Add a trusted root CA to out SecTrust object
- func addAnchorToTrust(trust: SecTrust,certificate: SecCertificate) -> SecTrust {
- let array: NSMutableArray = NSMutableArray()
- array.add(certificate)
- SecTrustSetAnchorCertificates(trust,array)
- return trust
- }
- // Create a SecCertificate object from a DER formatted certificate file
- func createCertificateFromFile(filename: String,ext: String) -> SecCertificate {
- let rootCertPath = Bundle.main.path(forResource:filename,ofType: ext)
- let rootCertData = NSData(contentsOfFile: rootCertPath!)
- return SecCertificateCreateWithData(kcfAllocatorDefault,rootCertData!)!
- }
- // Connect to remote host/server
- func connect(host: String,port: Int) {
- // Specify host and port number. Get reference to newly created socket streams both in and out
- Stream.getStreamsToHost(withName:host,port: port,inputStream: &inputStream,outputStream: &outputStream)
- // Create strong delegate reference to stop ARC deallocating the object
- inputDelegate = self
- outputDelegate = self
- // Now that we have a strong reference,assign the object to the stream delegates
- inputStream!.delegate = inputDelegate
- outputStream!.delegate = outputDelegate
- // This doesn't work because of arc memory management. Thats why another strong reference above is needed.
- //inputStream!.delegate = self
- //outputStream!.delegate = self
- // Schedule our run loops. This is needed so that we can receive StreamEvents
- inputStream!.schedule(in:RunLoop.main,forMode: RunLoopMode.defaultRunLoopMode)
- outputStream!.schedule(in:RunLoop.main,forMode: RunLoopMode.defaultRunLoopMode)
- // Enable SSL/TLS on the streams
- inputStream!.setProperty(kcfStreamSocketSecurityLevelNegotiatedSSL,forKey: Stream.PropertyKey.socketSecurityLevelKey)
- outputStream!.setProperty(kcfStreamSocketSecurityLevelNegotiatedSSL,forKey: Stream.PropertyKey.socketSecurityLevelKey)
- // Defin custom SSL/TLS settings
- let sslSettings : [NSString: Any] = [
- // NSStream automatically sets up the socket,the streams and creates a trust object and evaulates it before you even get a chance to check the trust yourself. Only proper SSL certificates will work with this method. If you have a self signed certificate like I do,you need to disable the trust check here and evaulate the trust against your custom root CA yourself.
- NSString(format: kcfStreamSSLValidatesCertificateChain): kcfBooleanFalse,//
- NSString(format: kcfStreamSSLPeerName): kcfNull,// We are an SSL/TLS client,not a server
- NSString(format: kcfStreamSSLIsServer): kcfBooleanFalse
- ]
- // Set the SSL/TLS settingson the streams
- inputStream!.setProperty(sslSettings,forKey: kcfStreamPropertySSLSettings as Stream.PropertyKey)
- outputStream!.setProperty(sslSettings,forKey: kcfStreamPropertySSLSettings as Stream.PropertyKey)
- // Open the streams
- inputStream!.open()
- outputStream!.open()
- }
- // This is where we get all our events (haven't finished writing this class)
- func stream(_ aStream: Stream,handle eventCode: Stream.Event) {
- switch eventCode {
- case Stream.Event.endEncountered:
- print("End Encountered")
- break
- case Stream.Event.openCompleted:
- print("Open Completed")
- break
- case Stream.Event.hasSpaceAvailable:
- print("Has Space Available")
- // If you try and obtain the trust object (aka kcfStreamPropertySSLPeerTrust) before the stream is available for writing I found that the oject is always nil!
- var sslTrustInput: SecTrust? = inputStream! .property(forKey:kcfStreamPropertySSLPeerTrust as Stream.PropertyKey) as! SecTrust?
- var sslTrustOutput: SecTrust? = outputStream!.property(forKey:kcfStreamPropertySSLPeerTrust as Stream.PropertyKey) as! SecTrust?
- if (sslTrustInput == nil) {
- print("INPUT TRUST NIL")
- }
- else {
- print("INPUT TRUST NOT NIL")
- }
- if (sslTrustOutput == nil) {
- print("OUTPUT TRUST NIL")
- }
- else {
- print("OUTPUT TRUST NOT NIL")
- }
- // Get our certificate reference. Make sure to add your root certificate file into your project.
- let rootCert: SecCertificate? = createCertificateFromFile(filename: "ca",ext: "der")
- // TODO: Don't want to keep adding the certificate every time???
- // Make sure to add your trusted root CA to the list of trusted anchors otherwise trust evaulation will fail
- sslTrustInput = addAnchorToTrust(trust: sslTrustInput!,certificate: rootCert!)
- sslTrustOutput = addAnchorToTrust(trust: sslTrustOutput!,certificate: rootCert!)
- // convert kSecTrustResultUnspecified type to SecTrustResultType for comparison
- var result: SecTrustResultType = SecTrustResultType.unspecified
- // This is it! Evaulate the trust.
- let error: OSStatus = SecTrustEvaluate(sslTrustInput!,&result)
- // An error occured evaluating the trust check the OSStatus codes for Apple at osstatus.com
- if (error != noErr) {
- print("Evaluation Failed")
- }
- if (result != SecTrustResultType.proceed && result != SecTrustResultType.unspecified) {
- // Trust Failed. This will happen if you faile to add the trusted anchor as mentioned above
- print("Peer is not trusted :(")
- }
- else {
- // Peer certificate is trusted. Now we can send data. Woohoo!
- print("Peer is trusted :)")
- }
- break
- case Stream.Event.hasBytesAvailable:
- print("Has Bytes Available")
- break
- case Stream.Event.errorOccurred:
- print("Error Occured")
- break
- default:
- print("Default")
- break
- }
- }
- }