iOS Swift SSL套接字自签名证书

时间:2016-12-23 06:28:07

标签: ios swift sockets ssl

我正在尝试为我的服务器创建一个iOS客户端。理想情况下,我希望在建立与服务器的tls 1.2连接时,iOS会向我提供它所获得的证书,以便我可以将其与预期的证书相匹配。经过大量的谷歌搜索,它看起来不可能。这在android中很容易做到。之后,我愿意选择第二名并让iOS接受我自己的私人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)
    }
}

私有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公钥设置为信任锚,因此不应该发生这种情况。该策略主要基于this

1 个答案:

答案 0 :(得分:0)

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

https://github.com/eamonwhiter73/IOSObjCWebSockets