【问题标题】:iOS SSL Connection in SwiftSwift 中的 iOS SSL 连接
【发布时间】:2016-08-04 08:08:59
【问题描述】:

我正在尝试建立从我的 iOS 应用程序到我的后端服务器 (Node.js) 的简单套接字连接 (NO 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, 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 周。任何帮助将不胜感激。

更喜欢 Swift 代码中的答案,而不是 Objective C,但如果你只有 Obj C 也可以:)

【问题讨论】:

    标签: ios swift ssl


    【解决方案1】:

    好吧,我在这个问题上花了 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
            }
        }
    }
    

    【讨论】:

    • 谢谢伙计,这是好东西!我已经弄清楚了套接字的大部分功能,但这有助于我让 SSL 工作。
    • 我希望我在一开始对整个问题进行故障排除时看到这个
    • 很高兴它对您有所帮助。老实说,Apple 为 SSL 所做的设计决策很糟糕。您应该能够在评估信任“之前”添加自己的受信任根 CA :(
    • 感谢您的回答,它确实有效。但我有一个问题。为什么我们应该在NSStreamEvent.HasSpaceAvailable 中调用所有这些与 SSL 相关的代码?每次我们向服务器发送数据时,都会调用HasSpaceAvailable。这是正确的吗?也许我们应该在NSStreamEvent.OpenCompleted 中只使用一次所有这些代码(不仅仅是addAnchorToTrust)?
    • @don-prog 我使用 hasSpaceAvailable 而不是 openCompleted 评估信任的原因是因为信任对象 (kCFStreamPropertySSLPeerTrust) 为空。在我看来,这一行 (var sslTrustInput: SecTrust? = inputStream!.propertyForKey(kCFStreamPropertySSLPeerTrust as String) as!SecTrust) 在 openCompleted 上调用时仍然为空。我理解你所说的每次服务器发送数据时调用它的意思。您是否尝试在 openCompleted 中调用该代码?如果是,它是如何进行的?
    猜你喜欢
    • 2013-10-14
    • 1970-01-01
    • 2013-12-25
    • 2020-06-30
    • 2017-05-08
    • 1970-01-01
    • 2015-02-14
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多