iOS Swift SSL 套接字自签名证书

标签 ios swift sockets ssl

我正在尝试为我的服务器制作一个 iOS 客户端。理想情况下,我希望在与服务器建立 tls 1.2 连接时,iOS 会向我提供它获得的证书,以便我可以将其与预期的证书相匹配。经过大量的谷歌搜索后,这似乎是不可能的。这在android中很容易做到。在那之后,我愿意屈居第二,让 iOS 接受我自己的私有(private) CA 签署的任何证书。这样我可以保证它连接的服务器至少是我的。

看起来iOS没有像C中那样的标准套接字,在创建它之后,您可以在其fd或Java上读取和写入,您可以获得套接字的输入和输出流来像C一样进行读取和写入。看起来像我需要制作自己的类套接字来获得像读写一样的 C/Java。

import Foundation

class SSLSocket: NSObject, StreamDelegate
{
    //(allow the getter to be public, only the setter is private)
    private(set) var inputStream: InputStream?
    private(set) var outputStream: OutputStream?

    private var inputDelegate: StreamDelegate?
    private var outputDelegate: StreamDelegate?
    private var host: String
    private var port: Int

    public init(host: String, port: Int)
    {
        self.host = host
        self.port = port
    }

    func connect()
    {
        Stream.getStreamsToHost(withName: host, port: port, inputStream: &inputStream, outputStream: &outputStream)

        //iOS specific oddities (nothing similar in the android version)
        inputDelegate = self
        outputDelegate = self
        inputStream!.delegate = inputDelegate
        outputStream!.delegate = outputDelegate
        inputStream!.schedule(in: RunLoop.main, forMode: RunLoopMode.defaultRunLoopMode)
        outputStream!.schedule(in: RunLoop.main, forMode: RunLoopMode.defaultRunLoopMode)

        //tlsv1.0+ enforcement??? (looks like no 1.2 only)
        //don't do any "legitimacy checks" on the certificate or the host.
        let sslSettings = //must present ssl properties in an array, not 1 by 1 in .setPropery(...
        [
            String(kCFStreamPropertySocketSecurityLevel): kCFStreamSocketSecurityLevelTLSv1,
            String(kCFStreamSSLValidatesCertificateChain): kCFBooleanFalse
        ] as [String : Any]
        inputStream!.setProperty(sslSettings, forKey: kCFStreamPropertySSLSettings as Stream.PropertyKey)
        outputStream!.setProperty(sslSettings, forKey: kCFStreamPropertySSLSettings as Stream.PropertyKey)

        //open the streams
        inputStream!.open()
        outputStream!.open()

    }

    func close() //better not cause timing problems because it is a bit less than instantaneous
    {
        inputStream!.delegate = nil
        outputStream!.delegate = nil

        inputStream!.close()
        outputStream!.close()

        inputStream!.remove(from: .main, forMode: .defaultRunLoopMode)
        outputStream!.remove(from: .main, forMode: .defaultRunLoopMode)

        //let the automatic reference count get rid of these
        inputStream = nil
        outputStream = nil
    }

    func stream(_ aStream: Stream, handle eventCode: Stream.Event)
    {
        switch eventCode
        {
        case Stream.Event.endEncountered:
            print("socked died")
            aStream.close()
            aStream.remove(from: RunLoop.main, forMode: .defaultRunLoopMode)
            break
        case Stream.Event.hasSpaceAvailable:
            print("matching presented certificate with expected")

            //get the presented certificate
            let sslTrustInput: SecTrust? = aStream.property(forKey: kCFStreamPropertySSLPeerTrust as Stream.PropertyKey) as! SecTrust
            if(sslTrustInput == nil)
            {
                print("something went horribly wrong in fetching the presented certificate")
                broadcastSocketResult(result: false)
                return
            }

            if(Vars.expectedCert == nil)
            {
                print("probably a bug, there is no expected certificate in Vars. fail/crash in 3, 2, 1...")
                broadcastSocketResult(result: false)
                return
            }

            //set the expected certificate as the only "trusted" one
            let acceptedCerts: NSMutableArray = NSMutableArray()
            acceptedCerts.add(Vars.expectedCert!)
            SecTrustSetAnchorCertificates(sslTrustInput!, acceptedCerts)

            //check the certificate match test results

            var result: SecTrustResultType = SecTrustResultType.fatalTrustFailure //must initialize with something
            let err: OSStatus = SecTrustEvaluate(sslTrustInput!, &result)
            if(err != errSecSuccess)
            {
                print("problem evaluating certificate match")
                broadcastSocketResult(result: false)
                return
            }
            if (result != SecTrustResultType.proceed)
            {
                print("certificate was not signed by private CA")
                broadcastSocketResult(result: false)
                return
            }
            print("socket ssl turned out ok")
            broadcastSocketResult(result: true)
            break
        case Stream.Event.openCompleted:
            print("socket is useable")
            break
        case Stream.Event.errorOccurred:
            print("something bad happened")
            broadcastSocketResult(result: false)
            break;
        default:
            print("some other code" + String(describing: eventCode))
            broadcastSocketResult(result: false)
            break
        }
    }

    private func broadcastSocketResult(result: Bool)
    {
        let extras = [Const.BORADCAST_SOCKET_RESULT: result]
        NotificationCenter.default.post(name: NSNotification.Name(rawValue: Const.BROADCAST_SOCKET), object: extras)
    }
}

私有(private) CA 的公钥是从 base64 编码转储到文本框中获得的。
        var certDumpValue: String? = certDump.text
    if(certDumpValue == nil || certDumpValue! == "" || certDumpValue!.characters.count < 28)
    {
        //in android the certificate is either there or not. In iOS it could be there, or not, or incomplete.
        //also no longer a file in iOS but a base64 dump
        Utils.showOk(screen: self, message: "Certificate corrupted. Can't use.")
        return;
    }
    else
    {
        //check it is a real certificate and not just random text or a poem
        //https://stackoverflow.com/questions/28957940/remove-all-line-breaks-at-the-beginning-of-a-string-in-swift
        //https://digitalleaves.com/blog/2015/10/sharing-public-keys-between-ios-and-the-rest-of-the-world/

        //prepare the base64 string dump for usage
        //trim off the ---start ceritificate--- and end certificate tags, get rid of the newlines
        certDumpValue = certDumpValue!.replacingOccurrences(of: "\n", with: "")
        let startChop = certDumpValue!.index(certDumpValue!.startIndex, offsetBy: 27)
        let endChop = certDumpValue!.index(certDumpValue!.startIndex, offsetBy: certDumpValue!.characters.count-26)
        certDumpValue = certDumpValue![startChop...endChop] //most complicated method of substring imaginable

        //check if the string is a valid base64 encoded string
        let certRaw: NSData? = NSData(base64Encoded: certDumpValue!)
        if(certRaw == nil)
        {
            Utils.showOk(screen: self, message: "Certificate corrupted. Can't use.")
            return;
        }

        //if it is valid base64, check if it's a real certificate
        let cert = SecCertificateCreateWithData(nil, certRaw!)
        if(cert == nil)
        {
            Utils.showOk(screen: self, message: "Certificate corrupted. Can't use.")
            return;
        }
        Vars.expectedCert = cert
    }

当我尝试连接到服务器时,我总是得到一个可恢复的信任错误。我将 CA 公钥设置为信任 anchor ,所以这不应该发生。该策略主要基于 this

最佳答案

这是我最近创建的一个包,用于处理 Apple 对 TCP/TLS 施加的最新限制 - 它是 Obj-C,但也许它仍然有用?

https://github.com/eamonwhiter73/IOSObjCWebSockets

关于iOS Swift SSL 套接字自签名证书,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41296260/

相关文章:

ios - 动态生成按钮并删除单击下一个按钮时生成的所有按钮

ios - 多点连接 : Invalid service type

c# - 如何从服务器优雅地关闭套接字

ios - 按钮宽度和高度不可更改

objective-c - 如何在不使用属性或 ivar 的情况下引用对象

ios - 当我通过设置登录时 FBSessionStateClosedLoginFailed

swift 4 Realm 根据 parent 姓名过滤子对象

java - Android tcp套接字设置etimedout值

java - MulticastSocket 确定收到的消息是否是单播的

ios - 如何 Hook ios 连接更改