Evaluating Trust from self signed keystore fails

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.

I’m confused by your goal here. You wrote:

I am using self signed certificates

and then go on to show a whole bunch of trust evaluation code. What benefit are you expecting to get from that?

In general, trust evaluation only makes sense when you have certificates issued by CAs, where the trust evaluator builds a potentially complex chain of trust from the certificate to a set of trusted anchors. If all your certificates are self signed, there’s very little point doing trust evaluation on them.

My general advice is to steer clear of self-signed certificates in general. One place where there’s often not possible is when working with accessories on the local network, and I have specific advice about that in TLS For Accessory Developers. Some of that is likely to be relevant to you here.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Hi Eskimo,

First of all, thank you for taking the time and responding to me, it is much appreciated.

My knowledge in security is a bit limited so any advise is always appreciated. I am trying to setup a connection between the client and the server. The connection is indeed on a local network (forgot to mention that) and there is no guarentee of having internet on the device or desktop, so my understanding is that I cannot use CAs for that and since it is on local network only (well worst case scenario that is) I can only used self signed certificates. It will be used on different locations in already existing situations.

So from what I have read, when doing mutual authentication I need to check the certificates from what I get with what I have. As I have tried a lot of things, searching the internet for solutions I also read some sources that mention that I needed to evaluate the trust to have the handshake working. As it is failing, my assumption is that this is the reason why the handshake fails at the client_hello process after the server_hello process succeeded, but at this point it is an assumption so I might be wrong and may not need to do it at all. On Android I need to check if incoming certs and codes are trusted and conform the expected but I do not have to evaluate certificate trust locally, just loading them is enough to be able to use them. So I might not be required to do so on iOS as well but at this point I simply do not know and have not found information about which made me understand.

I have read the link you shared and as far as I understood, I cannot use the URLSession as it does not support basic socket byte communication but operates on a HTTP level? Although the data type might be usable. I will investigate this avenue but if you have any additional information or solution, please do not hesitate to share them, thank you very much so far

If you are doing local network TLS with a self signed certificate for testing, this article I wrote may help you also. Creating an Identity for Local Network TLS. It describes the details in using Network framework along with a self signed identity on a local or even remote network. Migrating to something like this will also allow you to get away from the deprecated Secure Transport APIs.

Hi Meaton,

Thank you for your response, appreciate it!

I have read through the article but the big issue is that I currently need to implement the iOS communication in an existing environment which at this moment cannot be changed (I hope in the future that I can make it happen but for now I cannot.) So I cannot change the setup to use a local network CA. I did saw a part in the article about the SecItemAdd which I have not yet encountered as part of the solution, so I will spend some time in getting to understand what that part does and if it can be of help for my situation.

As for the rest of the set up in the link your shared, it uses a lot of features not available in iOS 9.3. The NWListener for example seems to be available from iOS 12. So that route is unfortunately not an option for me.

The NWListener for example seems to be available from iOS 12. So that route is unfortunately not an option for me.

Right, this would be forward looking resource to support more modern clients and APIs.

So by reducing the lifetime of the certificate to a year the error changed to "certificate is not permitted for this usage". I have created the keystore using openssl and I have added the configuration for the EKU to set it to extendedKeyUsage = clientAuth but it still gives that error. Are there any other steps I need to take for the self signed keystore to be permitted?

Are there any other steps I need to take for the self signed keystore to be permitted?

You should be able to create a local CA for TLS testing by reviewing the steps from this Technote: TN2326 Creating Certificates for TLS Testing.

Hi Meaton, thank you very much for responsing, hopefully I can get to the bottom of this as it is driving me crazy :-(

Can you elaborate a bit more on to why I need a local CA? I have managed to create the Android implementation and I did not have to use local CAs. I do not have access to the servers implementation (besides being able to add the client certificate). On the Android part I did need to implement my own Trust manager, which then does the trust checking but as far as I understand, I am doing exactly that as well in my ios code by loading up the certificates and putting them in the trust object and using that for the streams. I think the bigger question at this point might even be, is the trust evaluation necessary (I just assumed it is) for a local trust object using self signed certificates to function? If it is not I might be looking in the wrong place all together.

Can you elaborate a bit more on to why I need a local CA?

Running your own CA may or may not be useful depending on your specific circumstances. What’s your end goal here? Are you just trying to get this working for testing purposes? Or is your final product going to be deployed with a local server?

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Evaluating Trust from self signed keystore fails
 
 
Q