SecPKCS12Import in Swift 3

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)")
}

Replies

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")
}

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"