Hi all,
I am trying to get a mutual authentication (client authentication) connection working (sockets, not http and all local network currently) which is working for other platforms (clients) but for iOS (client) I do not seem to be able to get it working. In comparison with other platforms, the connection is closed at the moment that the client handshake part of the SSL handshake process (according to the SSL debug output on the server) should be happening. The code on iOS shows an error when trying to evaluate the trust of the keystore. On other platforms it shows the client handshake and the connection works. So something is probably wrong with the client keystore or setting it up in the app.
I am using self signed certificates, in the p12 format. They have a long life currently, 2027 end year. I also create a certificate from a .cer file which is the public key of the server. The connection is of type TLSv1.2. Additional information is that the app support iOS 9.3 and up. I use Swift 5.
So the first step to get it all working is to get the trust evaluating go successful. The error that I currently get is "certificate is not standards compliant". The keystore is loaded, both the trust and the identity is retrieved, the certificate array holds the appropriate amount of certificates.
Here is the code that sets up the connection:
sslContext = SSLCreateContext(nil, .clientSide, .streamType)
if(sslContext != nil) {
let tlsProtocolVersion: SSLProtocol = .tlsProtocol12
SSLSetProtocolVersionMin(sslContext!, tlsProtocolVersion)
SSLSetProtocolVersionMax(sslContext!, tlsProtocolVersion)
} else {
print("sslContext is null")
return
}
var certs : [SecCertificate] = []
do {
certs = try loadCertificates()
} catch {
print("Crap, cannot load the certificates")
print(error)
return
}
let cfArray = certs as CFArray
var trustObj: SecTrust?
do {
trustObj = try loadTrustFromKeyStore()
} catch {
print("Crap, cannot load the Trust object")
print(error)
return
}
// Set the trust evaluation options
let trustPolicy = SecPolicyCreateSSL(true, nil)
SecTrustSetPolicies(trustObj!, trustPolicy)
SecTrustSetAnchorCertificates(trustObj!, cfArray)
var trustResult: SecTrustResultType = .invalid
var trustError: UnsafeMutablePointer<CFError?>?
var isTrusted = true
if #available(iOS 12.0, *) {
var error:CFError?
isTrusted = SecTrustEvaluateWithError(trustObj!, &error)
if error != nil {
print("Error: " + error!.localizedDescription)
}
}
And reading the keystore to get the trust and the identify object (both separate functions)
func loadTrustFromKeyStore() throws -> SecTrust? {
var trust : SecTrust?
let store = "client-ios-keystore"
print("Reading store for Trust object: " + store)
guard let keystorePath = Bundle.main.path(forResource: store, ofType: "p12") else {
print("Failed to find " + store)
throw "Cannot find or open keystore"
}
let keystoreURL = URL(fileURLWithPath: keystorePath)
let keystoreData = try Data(contentsOf: keystoreURL)
let keystoreOptions: [String: Any] = [
kSecImportExportPassphrase as String: password
]
var keystoreItems: CFArray?
let keystoreStatus = SecPKCS12Import(keystoreData as CFData, keystoreOptions as CFDictionary, &keystoreItems)
guard keystoreStatus == errSecSuccess
else {
print("Failed SecPCKS12Import")
throw NSError(domain: NSOSStatusErrorDomain, code: Int(keystoreStatus), userInfo: nil)
}
if(keystoreItems != nil) {
let normalArray = keystoreItems as! Array<CFDictionary>
normalArray.forEach { item in
print("Checking item in keystore")
if let dict = item as? [String: AnyObject] {
print("Keystore content: " + dict.debugDescription)
for (key, value) in dict {
if(key == "identity") {
// Skip in this case
} else if(key == "trust") {
let trustRef: SecTrust = value as! SecTrust
trust = trustRef
} else if(key == "chain") {
// Skip in this case
} else {
print("Found unexpected content in p12 file")
}
}
}
}
}
return trust
}
And the loadCertificates function is the same as the loadTrustFromKeyStore function except that it checks the identify instead of the trust and exports an array of certificates like:
if(key == "identity") {
let keystoreItem = value as! SecIdentity
let certArray: CFArray = [keystoreItem] as CFArray
SSLSetCertificate(sslContext!, certArray)
var secCert: SecCertificate?
let status = SecIdentityCopyCertificate(keystoreItem, &secCert)
// Checking if the certificate retrieval was successful
if status == errSecSuccess {
print("success")
let certCopy = SecCertificateCopyData(secCert!)
let certificate = SecCertificateCreateWithData(kCFAllocatorDefault, certCopy)
certificates.append(certificate!)
} else {
print("failed to import certificate")
}
}
I hope I have mentioned all important information and left out the non-essential information but if you need more information, please let me know. I am probably missing something obvious or important but currently I am just stuck and in need of some fresh eyes, in any case, thank you for taking the time to check out this issue.