2 Replies
      Latest reply: Dec 4, 2016 2:31 PM by eskimo RSS
      danielbrewer Level 1 Level 1 (0 points)

        There are many questions on the forum that concern members' difficulties with the SecPKCS12Import function, however after browsing all I find none that exactly share my struggles.  With the code below and using the private.p12 file from the example project from the Apple developer library (https://developer.apple.com/library/content/samplecode/CryptoCompatibility/CryptoCompatibility.zip), I always get the errSecAuthFailed result.  I cannot see why.

        Relevant info: iOS 10.1.x, certificates to be used with NSStream as kCFStreamSSLCertificates for client certificate authentication, (as stated in title) Swift 3, to be used with self-signed certificates whose private key is password protected and a non-null export passphrase for the pkcs12 file.

         

        Please help

         

        Many thanks!

         

        let pkcsFilePath = Bundle.main.path(forResource: "private", ofType: "p12")
        let pkcsData = NSData(contentsOfFile: pkcsFilePath!)
        
        var clientCertificates: CFArray? = nil
        
        let keys = [kSecImportExportPassphrase]
        let keysPointer =  UnsafeMutablePointer<UnsafeRawPointer?>.allocate(capacity: 1)
        keysPointer.initialize(to: keys)
        
        let values: [CFString] = ["test" as CFString]
        let valuesPointer =  UnsafeMutablePointer<UnsafeRawPointer?>.allocate(capacity: 1)
        valuesPointer.initialize(to: values)
        
        let pkcs12ImportOptions = CFDictionaryCreate(kCFAllocatorDefault, keysPointer, valuesPointer, 1, nil, nil)
        
        let importResult: OSStatus = SecPKCS12Import(pkcsData!, pkcs12ImportOptions!, &clientCertificates)
        
        switch importResult {
        case noErr:
             NSLog("noErr: Success \(clientCertificates)")
        case errSecAuthFailed:
             NSLog("errSecAuthFailed: Authorization/Authentication failed. \(clientCertificates)")
        default:
             NSLog("Unspecified OSStatus error: \(importResult)")
        }
        
        • Re: SecPKCS12Import in Swift 3
          danielbrewer Level 1 Level 1 (0 points)

          To any interested parties, this is the Swift code that I arrived at.

           

          let pkcs12Data = NSData(contentsOfFile: Bundle.main.path(forResource: pkcs12.filename, ofType: pkcs12.ext)!)
          var imported: CFArray? = nil
          var options: Dictionary<String, CFString> = [:]
          options[kSecImportExportPassphrase as String] = pkcs12ExportPassword as CFString?
                 
          switch SecPKCS12Import(pkcs12Data!, (options as CFDictionary), &imported) {
          case noErr:
              let identityDict = unsafeBitCast(CFArrayGetValueAtIndex(imported!, 0), to: CFDictionary.self) as NSDictionary
              let importItemIdentity = kSecImportItemIdentity as String
              let identityRef = identityDict[importItemIdentity] as! SecIdentity
          
              var certRef: SecCertificate?
              switch SecIdentityCopyCertificate(identityRef, &certRef) {
              case noErr:
                  sslSettings[kCFStreamSSLCertificates] = NSArray(objects: identityRef, certRef!)
                  ssl = true
              case errSecAuthFailed:
                  NSLog("SecIdentityCopyCertificate - errSecAuthFailed: Authorization/Authentication failed.")
              default:
                  NSLog("SecIdentityCopyCertificate - Unknown OSStatus error")
              }
              break
          case errSecAuthFailed:
              NSLog("SecPKCS12Import - errSecAuthFailed: Authorization/Authentication failed.")
          default:
              NSLog("SecPKCS12Import - Unknown OSStatus error")
          }
          
            • Re: SecPKCS12Import in Swift 3
              eskimo Apple Staff Apple Staff (6,665 points)

              Well, your second version is much better than your first, but you’re still working way too hard.  Try this:

              func identity(named name: String, password: String) throws -> SecIdentity {
                  let url = Bundle.main.url(forResource: name, withExtension: "p12")!
                  let data = try Data(contentsOf: url)
                  var importResult: CFArray? = nil
                  let err = SecPKCS12Import(
                      data as NSData,
                      [kSecImportExportPassphrase as String: password] as NSDictionary,
                      &importResult
                  )
                  guard err == errSecSuccess else {
                      throw NSError(domain: NSOSStatusErrorDomain, code: Int(err), userInfo: nil)
                  }
                  let identityDictionaries = importResult as! [[String:Any]]
                  return identityDictionaries[0][kSecImportItemIdentity as String] as! SecIdentity
              }
              

              There’s a bunch of tricks here:

              • NSData is bridged to CFData, so you can work with Data and then use as NSData to satisfy a CFData parameter.

              • Likewise with NSString, CFString and String.

              • On the flip side, CFArray is bridged to NSArray which you can then force convert to the right type of Array.

              Also, when you set up kCFStreamSSLCertificates, you don’t need to pass the identity’s certificate as the second item in the array.  The system will pick that up from the identity itself.  It might make sense to get any intermediate certificates from the PKCS#12 (via kSecImportItemCertChain) and add them to the array, but in most cases that’s not necessary, as I explain in TLS for App Developers.

              Share and Enjoy

              Quinn “The Eskimo!”
              Apple Developer Relations, Developer Technical Support, Core OS/Hardware
              let myEmail = "eskimo" + "1" + "@apple.com"