简体   繁体   中英

iOS Swift SSL Socket Self Signed Certificate

I'm trying to make an iOS client for my server. 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. 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. 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.

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.

        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. 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?

https://github.com/eamonwhiter73/IOSObjCWebSockets

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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