在Swift中的iOS SSL连接

问题描述:

我想从我的iOS应用程序到我的后端服务器(Node.js)建立一个简单的套接字连接(NO HTTP)。服务器证书已使用自制的自定义CA创建并签署。我相信为了让iOS信任我的服务器,我必须以某种方式将此自定义CA证书添加到用于确定Java/Android中TrustStore工作方式的信任排序的可信证书列表中。在Swift中的iOS SSL连接

我试图使用下面的代码连接,但没有错误,但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, kCFStreamPropertySSLSettings, 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个星期。任何帮助,将不胜感激。

喜欢在斯威夫特代码,而不是目标C答案,但如果你只拥有的OBJç那也没关系:)

好,我花了在这个问题上8周:(但我终于成功地组建了一个可行的解决方案。我必须说iOS上的SSL/TLS是一个笑话,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 
     } 
    } 
} 
+0

谢谢你,这是好东西!我已经想出了大部分套接字功能,但是这帮助我获得了SSL的工作。 – Sethmr

+0

我希望我在开始排除这件事时看到了这个, – Sethmr

+0

很高兴为您提供帮助。老实说,苹果公司为SSL做出的设计决定非常糟糕。你应该可以添加自己的受信任的根CA“BEFORE”信任评估:( – Snapper26