简体   繁体   English

iOS Swift SSL 套接字自签名证书

[英]iOS Swift SSL Socket Self Signed Certificate

I'm trying to make an iOS client for my server.我正在尝试为我的服务器制作一个 iOS 客户端。 Ideally I would prefer that when establishing a tls 1.2 connection to the server, iOS will present me with the certificate it got so I can match it to an expected certificate.理想情况下,我希望在与服务器建立 tls 1.2 连接时,iOS 会向我展示它获得的证书,以便我可以将其与预期的证书进行匹配。 After tons of googling it doesn't look like this is possible.经过大量的谷歌搜索后,看起来这是不可能的。 This is easy to do in android.这在安卓中很容易做到。 After that I'm willing to settle for second place and have iOS accept any certificate signed by my own private CA.在那之后,我愿意屈居第二,让 iOS 接受由我自己的私有 CA 签署的任何证书。 This way I can guarantee the server it is connected to is at least mine.这样我就可以保证它所连接的服务器至少是我的。

It also looks like iOS doesn't have a standard socket like in C where after you create it, you read and write on its fd or Java where you get a socket's input and output streams to do read and write like C. It looks like I need to make my own socket like class to get C/Java like read write.看起来 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)
    }
}

The private CA's public key is obtained from a base64 encoding dump into a text box.私有 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
    }

When I try to connect to the server I always get a recoverable trust error.当我尝试连接到服务器时,我总是收到可恢复的信任错误。 I set the CA public key as trust anchor so this shouldn't happen.我将 CA 公钥设置为信任锚,所以这不应该发生。 This strategy is largely based off this该策略主要基于

Here is a package I created recently to handle the latest restrictions imposed on TCP/TLS by Apple - it is Obj-C, but maybe it will still be helpful?这是我最近创建的一个包,用于处理 Apple 对 TCP/TLS 施加的最新限制——它是 Obj-C,但也许它仍然会有帮助?

https://github.com/eamonwhiter73/IOSObjCWebSockets https://github.com/eamonwhiter73/IOSObjCWebSockets

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM