For anyone else trying to do TLS connection with a P12 certificate, this was the solution I came up with. If anyone has a better way of doing it please let me know thanks.
//
// SSLConnection.swift
// SSLConnection
//
// Created by JC Castano on 3/27/17.
// Copyright © 2017 1stPayGateway. All rights reserved.
//
import Foundation
class SSLConnection: NSObject, StreamDelegate {
private static var inputStream:InputStream!
private static var outputStream:OutputStream!
private var ipAddress:String = ""
private var sslEnabled: Bool = false
public func connectToIngenico(address:String, sslEnabled: Bool) {
// TODO: Create dispatch queue to handle Ingenico connection
// initIngenicoQueue()
self.ipAddress = address
var readStream: Unmanaged<CFReadStream>?
var writeStream: Unmanaged<CFWriteStream>?
CFStreamCreatePairWithSocketToHost(nil, address as CFString!,12000, &readStream, &writeStream)
// Documentation suggests readStream and writeStream can be assumed to
// be non-nil. If you believe otherwise, you can test if either is nil
// and implement whatever error-handling you wish.
SSLConnection.inputStream = readStream!.takeRetainedValue()
SSLConnection.outputStream = writeStream!.takeRetainedValue()
SSLConnection.inputStream.delegate = self
SSLConnection.outputStream.delegate = self
SSLConnection.inputStream.schedule(in: RunLoop.current, forMode: RunLoopMode.defaultRunLoopMode)
SSLConnection.outputStream.schedule(in: RunLoop.current, forMode: RunLoopMode.defaultRunLoopMode)
if sslEnabled {
// Enable SSL/TLS on the streams
SSLConnection.inputStream.setProperty(StreamSocketSecurityLevel.negotiatedSSL, forKey: Stream.PropertyKey.socketSecurityLevelKey)
SSLConnection.outputStream.setProperty(StreamSocketSecurityLevel.negotiatedSSL, forKey: Stream.PropertyKey.socketSecurityLevelKey)
let sslSettings = [
// NSStream automatically sets up the socket, the streams and creates a trust object and evaulates it before you even get a chance to check the trust yourself. Only proper SSL certificates will work with this method. If you have a self signed certificate like I do, you need to disable the trust check here and evaulate the trust against your custom root CA yourself.
NSString(format: kCFStreamSSLValidatesCertificateChain): kCFBooleanFalse,
// We are an SSL/TLS client, not a server
NSString(format: kCFStreamSSLIsServer): kCFBooleanFalse,
// Get the key chain items and add it to ssl settings
NSString(format: kCFStreamSSLCertificates): getKeyChain(fileName: "CLIENT", ofType: "p12", password: "password")
] as [NSString : Any]
SSLConnection.inputStream.setProperty(sslSettings, forKey: kCFStreamPropertySSLSettings as Stream.PropertyKey)
SSLConnection.outputStream.setProperty(sslSettings, forKey: kCFStreamPropertySSLSettings as Stream.PropertyKey)
}
SSLConnection.inputStream.open()
SSLConnection.outputStream.open()
}
func getKeyChain(fileName: String, ofType type: String, password: String) -> CFArray {
let mainBundle = Bundle.main
let thePath = mainBundle.path(forResource: fileName, ofType: type)!
let PKCS12Data: NSData = NSData(contentsOfFile: thePath)!
var items: CFArray?
let optionDict: NSMutableDictionary = [kSecImportExportPassphrase as NSString: password]
let sanityCheck = SecPKCS12Import(PKCS12Data, optionDict, &items)
if sanityCheck == errSecSuccess && CFArrayGetCount(items) > 0 {
return parseKeyChainItems(items!)
} else {
switch sanityCheck {
case errSecSuccess:
print("Error importing p12: errSecSuccess")
case errSecUnimplemented:
print("Error importing p12: errSecUnimplemented")
case errSecIO:
print("Error importing p12: errSecIO")
case errSecOpWr:
print("Error importing p12: errSecOpWr")
case errSecParam:
print("Error importing p12: errSecParam")
case errSecAllocate:
print("Error importing p12: errSecAllocate")
case errSecUserCanceled:
print("Error importing p12: errSecUserCanceled")
case errSecBadReq:
print("Error importing p12: errSecBadReq")
case errSecInternalComponent:
print("Error importing p12: errSecInternalComponent")
case errSecNotAvailable:
print("Error importing p12: errSecNotAvailable")
case errSecDuplicateItem:
print("Error importing p12: errSecDuplicateItem")
case errSecItemNotFound:
print("Error importing p12: errSecItemNotFound")
case errSecInteractionNotAllowed:
print("Error importing p12: errSecInteractionNotAllowed")
case errSecDecode:
print("Error importing p12: errSecDecode")
case errSecAuthFailed:
print("Error importing p12: errSecAuthFailed")
default:
print("Error importing p12: Unknown items: \(items)")
break
}
}
return [] as CFArray
}
func parseKeyChainItems(_ keychainArray: NSArray) -> CFArray {
print("Key chain array: \(keychainArray)")
let dict = keychainArray[0] as! Dictionary<String,AnyObject>
let key = String(kSecImportItemIdentity)
let identity = dict[key] as! SecIdentity?
let certArray:[AnyObject] = dict["chain"] as! [SecCertificate]
var certChain:[AnyObject] = [identity!]
for item in certArray {
certChain.append(item)
}
return certChain as CFArray
}
func stream(_ aStream: Stream, handle eventCode: Stream.Event) {
let streamName = getStreamName(aStream)
switch eventCode {
case Stream.Event.openCompleted:
print("\(streamName).OpenCompleted")
break
case Stream.Event.hasBytesAvailable:
print("\(streamName).HasBytesAvailable")
case Stream.Event.hasSpaceAvailable:
print("\(streamName).HasSpaceAvailable")
break
case Stream.Event.endEncountered:
print("\(streamName).EndEncountered")
break
case Stream.Event.errorOccurred:
print("\(streamName).ErrorOccurred")
break
default:
print("\(streamName) unknown event")
break
}
}
func getStreamName(_ aStream: Stream) -> String {
if comparedStreamEqual(aStream, bStream: SSLConnection.inputStream) {
return "InputStream"
} else if comparedStreamEqual(aStream, bStream: SSLConnection.outputStream) {
return "OutputStream"
}
return "UnknownStream"
}
func comparedStreamEqual(_ aStream: Stream? , bStream: Stream?) -> Bool {
if aStream != nil && bStream != nil {
if aStream == bStream {
return true
}
}
return false
}
}