title: iOS Swift SSL Socket Self Signed Certificate
body: <p>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. </p>
  
  <p>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.</p>
  
  <pre><code>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: &amp;inputStream, outputStream: &amp;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!, &amp;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)
      }
  }
  </code></pre>
  
  <p>The private CA's public key is obtained from a base64 encoding dump into a text box.</p>
  
  <pre><code>        var certDumpValue: String? = certDump.text
      if(certDumpValue == nil || certDumpValue! == "" || certDumpValue!.characters.count &lt; 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
      }
  </code></pre>
  
  <p>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 <a href="https://stackoverflow.com/questions/38761837/ios-ssl-connection-in-swift">this</a></p>