Search code examples
iosswiftkeychainx509pkcs#12

How to verify if my certificate is successfully added to device keychain


I am trying to import a certificate to the device keychain, which I am getting my API. I have used these references to save received data in certificate format. references: (SecPKCS12Import returns different results on iOS 11 than on iOS 10) and Add Certificate to IOS Keychain in Swift By doing some modifications to add the certificate to the keychain I have done below code:

 ` func certificateFromCertificate(certP12: Data, psswd: String) {
        let decodedData =  certP12
        
        let keytmp : NSString = kSecImportExportPassphrase as NSString
        let options : NSDictionary = [keytmp : psswd]
        
        var certificateRef: SecCertificate? = nil
        
        var items : CFArray?
        
        let securityError: OSStatus = SecPKCS12Import(decodedData as CFData, options, &items)
        
        let theArray: CFArray = items!
        if securityError == noErr && CFArrayGetCount(theArray) > 0 {
            let newArray = theArray as [AnyObject] as NSArray
            let dictionary = newArray.object(at: 0)
            let secIdentity = (dictionary as AnyObject)[kSecImportItemIdentity as String] as! SecIdentity
            let securityError = SecIdentityCopyCertificate(secIdentity , &certificateRef)
            if securityError != noErr {
                certificateRef = nil
            }
        }
        
                var keychainQueryDictionary = [String : Any]()

                if let tempSecCert = certificateRef {
                    keychainQueryDictionary = [kSecClass as String : kSecClassCertificate, kSecValueRef as String : tempSecCert, kSecAttrLabel as String: "My Certificate"]
                }

                let summary =  SecCertificateCopySubjectSummary(certificateRef!)!  as String
                print("Cert summary: \(summary)")

                let status = SecItemAdd(keychainQueryDictionary as CFDictionary, nil)

            print(SecCopyErrorMessageString(status, nil))

    }`

I am getting proper certificate summary and also this print(SecCopyErrorMessageString(status, nil)) is returning no error. Optional(No error.)

The doubt I have is whether this certificate is properly installed in the device keychain or not. How do I verify that? Please note I don't want to trust the certificate forcefully.

I tried

var keychainQueryDictionary = [String : Any]()
    
if let tempSecCert = certificateRef {
  keychainQueryDictionary = [kSecClass as String : kSecClassCertificate, kSecValueRef as String : tempSecCert, kSecAttrLabel as String: "My Certificate"]
}
    
let summary =  SecCertificateCopySubjectSummary(certificateRef!)!  as String
print("Cert summary: \(summary)")
    
let status = SecItemAdd(keychainQueryDictionary as CFDictionary, nil)

This will import the certificate to the keychain of the device. I want to verify if it is installed successfully.


Solution

  • Your import code is correct.

    iOS does not offer a way to view these imported certificates on UI, you have to query them via Security API, this code loops through all certificates and filter the certificate by its X509 Common Name:

    func queryCertificate(with commonName: String) -> Bool{
      let query: [String: Any] = [
        kSecClass as String: kSecClassCertificate,
        kSecReturnRef as String: true,
        kSecMatchLimit as String: kSecMatchLimitAll
      ]
      
      
      var items: CFTypeRef?
      let status = SecItemCopyMatching(query as CFDictionary, &items)
      
      if status == errSecSuccess {
        if let certificates = items as? [SecCertificate] {
          for certificate in certificates {
            var commonName: CFString?
            if SecCertificateCopyCommonName(certificate, &commonName) == errSecSuccess, let cn = commonName {
              if cn == commonName {
                debugPrint("Found Cert:\(certificate)")
                return true
              }
            }
          }
        }
      }
      
      print("Certificate not found")
      return false
    }
    

    You can get the common name with openssl or view it in KeyChain App after imported to macOS.

    enter image description here

    However, unlike macos, which has a unified spaces to manage certificates and their trust settings. iOS enforces app sandboxing, which means that keychain items, including certificates, are 'accessible' only to the app that added them (or to apps in the same group, if explicitly configured to share access). They are not trusted or accessible system-wide or by other apps outside of your control or app group.

    When we say "accessible", it does not mean the imported cert is trusted, you still need:

    func SecTrustCreateWithCertificates(_ certificates: CFTypeRef, _ policies: CFTypeRef?, _ trust: UnsafeMutablePointer<SecTrust?>) -> OSStatus together with func SecTrustEvaluateWithError(_ trust: SecTrust, _ error: UnsafeMutablePointer<CFError?>?) -> Bool APIs to handle the trust evaluation within your app.

    For example, this gist offers an example to decide if a ssl is trusted in URLSession.