This is my code
Code Block swiftlet PKCS12Data = NSData(base64Encoded: pfx, options: .ignoreUnknownCharacters)let inPKCS12Data = CFDataCreate(kCFAllocatorDefault, PKCS12Data!.bytes.assumingMemoryBound(to: UInt8.self), (PKCS12Data?.length)!) var items: CFArray?let optionDict: NSMutableDictionary = [ kSecImportExportPassphrase as NSString: "Password", kSecAttrKeyType:kSecAttrKeyTypeRSA, kSecAttrKeySizeInBits:2048, kSecAttrIsExtractable:false, kSecPrivateKeyAttrs:[ kSecAttrIsPermanent:"YES", kSecAttrCanDerive:"NO", kSecAttrIsSensitive:"YES" ], ]let sanityCheck = SecPKCS12Import(PKCS12Data!, optionDict, &items)
Thanks you for the support
[AFAICT slackware1590 never did open a DTS case about this, so I didn’t get a chance to dig into it at the time.]
This came up today in a different context, and I wanted to share my results. Consider this small test program:
import Foundation let benjyP12Bytes: [UInt8] = [ … ] … func main() throws { let keychains = try secCall { SecKeychainCopySearchList($0) } as! [SecKeychain] let scratchQ = keychains.first { kc in kc.url?.deletingPathExtension().lastPathComponent == "Scratch" } let scratch = try secCall { scratchQ } let identity: SecIdentity do { print("will import") let benjyP12Data = Data(benjyP12Bytes) var format = SecExternalFormat.formatPKCS12 var type = SecExternalItemType.itemTypeUnknown var parameters = SecItemImportExportKeyParameters() let passphrase = Unmanaged.passRetained("test" as NSString as AnyObject) defer { passphrase.release() } parameters.passphrase = passphrase let keyAttributes = Unmanaged.passRetained([kSecAttrIsPermanent, kSecAttrIsSensitive] as CFArray) defer { keyAttributes.release() } parameters.keyAttributes = keyAttributes let identities = try secCall { SecItemImport( benjyP12Data as NSData, "Benjy.p12" as NSString, &format, &type, [], ¶meters, scratch, $0 ) } as! [SecIdentity] print("did import, identities: \(identities)") identity = try secCall { identities.first } } catch { print("did not import, error: \(error)") throw error } let privateKey = try secCall { SecIdentityCopyPrivateKey(identity, $0) } let attributes = try secCall { SecKeyCopyAttributes(privateKey) } as! [String: Any] for (attrName, attrValue) in attributes { print("\(attrName): \(attrValue)") } } try main()
Note This isn’t a complete program. At the end of this post I have more details on how I ran this test.
When I run this I see:
did import, identities: [<SecIdentity 0x600003823fc0 [0x20638e240]>] … extr: 0 …
extr
is kSecAttrIsExtractable
, and a value of 0 indicates that this private key is not extractable.
In contrast, if I comment out the line that sets parameters.keyAttributes
and run it again, it prints:
did import, identities: [<SecIdentity 0x600000515280 [0x20638e240]>] … extr: 1 …
indicating that the key is extractable in that case.
I’m pretty sure that this is ‘case closed’, but if you have any follow-up questions please post them here.
Share and Enjoy
—
Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"
I ran my test on macOS 15.2 with Xcode 16.2.
I used Keychain Access to create a new keychain, called “Scratch”, for this test to target. That ensures that the imported digital identity doesn’t get lost in my main login keychain.
Between each test I used Keychain Access to delete the imported digital identity (well, the certificate and private key) from the “Scratch” keychain.
I’ve elided the PKCS#12 data in the above program because it’s quite long. To test this yourself:
-
Fill in
benjyP12Bytes
with the hex bytes of your PKCS#12 blob. -
Replace
test
with its passphrase.
The program uses the helpers from Calling Security Framework from Swift
Finally, the code relies on one additional helper method:
extension SecKeychain { var url: URL? { var length: UInt32 = 1024 var buffer = [CChar](repeating: 0, count: Int(length)) let err = SecKeychainGetPath(self, &length, &buffer) guard err == errSecSuccess else { return nil } return URL(fileURLWithFileSystemRepresentation: buffer, isDirectory: false, relativeTo: nil) } }