how to set an identity and get a certificate CN from a pkcs12 file

I am working on a Swift app which does a TLS connection to a server. I want to set an identity, which the server will validate. I'm given a pkcs12 file. The cert is not trusted locally on my system, but the server can validate it.

First, I didn't need to import the cert - I just want to create an identity that I can use with my connection. I don't think that's possible, so I do this:

var importStatus = SecPKCS12Import(pkcs12Data as CFData, importOptions as CFDictionary, &importArray)

The first time I call this, it's successful. I have come to extract the identity (and certificate) from the importArray returned, but in my case, even though I get an errSecSuccess return status, the importArray is empty.

So first question: why would it be empty?

( if the code is run again, I get an errSecDuplicateItem - I don't need to store it in the keychain but I guess I'm being forced to)

When I imported, I used a UUID as my identifier - I set it in the options:

	let importOptions: [String: Any] = [
		kSecImportExportPassphrase as String: password,
		kSecImportItemLabel as String: identifier
	]

So I try to retrieve the identity from the keychain:

	let identityQuery = [
		kSecClass: kSecClassIdentity,
		kSecReturnRef: true,
		kSecAttrLabel: identifier
	] as NSDictionary
	
	var identityItem: CFTypeRef?
	let status = SecItemCopyMatching(identityQuery as CFDictionary, &identityItem)

where I pass the UUID as identifier, but I actually get back my apple identity, not the certificate. However, if I pass in the certificate's CN, (hard-coded for my testing) I get the right identity back.

So my second question: am I doing something wrong? If i pass an ItemLabel on import, can I retrieve the certificate using that same label?

So for me to get this working, I need to know the CN of my cert, or I need the ItemLabel to work so that I can just retrieve using a UUID.

To determine the CN of my cert, the only apple API I found is this:

SecCertificateCopyCommonName

which requires the cert to be in .der format, rather than .pkcs12. So I have a bit of a chicken and egg problem.

So my last question - is there a way to extract the CN from the pkcs12 file, or to convert the Data from .pkcs12 to .der?

Thanks!

Replies

Similarly, I try to use SecItemImport, passing in my data (as pkcs12Data parameter, same as in my post above)

	var inputFormat: SecExternalFormat = .formatPKCS12;
	var items : CFArray?
	let flags: SecItemImportExportFlags = SecItemImportExportFlags(rawValue: 0)
	
	var parameters = SecItemImportExportKeyParameters()
	parameters.accessRef = nil
	
	var itemType: SecExternalItemType = .itemTypeCertificate
	
	// Set import options
	let status = SecItemImport(pkcs12Data as CFData,
							  nil,
							   &inputFormat,
							   &itemType,
							   flags,
							   &parameters,
							   nil, // importKeychain
							   &items)

While this gives me proper errSecSuccess status, the items returned is empty, so I don't get any keychain items back.

You seem to be working on macOS. That’s important because the keychain APIs work differently on macOS than iOS. I have a bunch of background on this in TN3137 On Mac keychain APIs and implementations.

In this specific case, SecPKCS12Import behaves differently on macOS and iOS. On iOS it creates a standalone identity object (SecIdentity). Or macOS, it imports the identity into the file-based keychain and returns an identity object associated with that keychain item. This is the source of ongoing confusion )-:

It should be possible to call SecItemImport to create a standalone identity object by passing nil to the importKeychain property. Historically this hasn’t worked (r. 25140029) but it’s possible that we fixed that in macOS 14 (r. 101493467). I haven’t yet had a chance to confirm that.

but in my case, even though I get an errSecSuccess return status, the importArray is empty.

That’s weird. This has always worked for me, even with the macOS shim.

When I imported, I used a UUID as my identifier - I set it in the options:

That won’t work. Look at the doc comments in <Security/SecImportExport.h>. There are two groups of keys, one starting with kSecImportExportPassphrase and the other with kSecImportItemLabel. Only the first group applies to the import dictionary you pass to SecPKCS12Import. The second group is how you look at the items dictionary it returns.

I actually get back my apple identity, not the certificate.

Yeah, I suspect that this is just the SecItem shim causing problems again. I have a couple of posts that provide background to this issue:

Search the second one for shim.

While this gives me proper errSecSuccess status, the items returned is empty, so I don't get any keychain items back.

Right. This is the bug I mentioned above (r. 25140029). What version of macOS are you testing this on?

Share and Enjoy

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

Hi Quinn, thanks so much for replying. I find the Apple documentation on all of this lacking, especially in regards to actual examples, and updates to reflect Swift. But I've seen a number of posts / responses from you, which are always quite helpful, so I'm glad that this post caught your eye.

Yes, this is all on macOS (we will be working on iOS later).

First, I'll say that I got around my issues by using OpenSSL. I'd prefer to not bring in a 3rd-party library, but I was stuck, so I used OpenSSL to convert the x509 string to a .der, and then I used SecIdentityCreateWithCertificate to create a certificate, and SecIdentityCreateWithCertificate to create my identity.

That being said, I plan to get back to trying to get it working without OpenSSL, though I'm probably sidelining that for now (as I have other work to do :). So I will try various things you mention, but it might not be for a while.

Regarding the macOS I'm testing on, I'm developing and testing on macOS 14.2.1 (but our minimum target is macOS 13), so I suppose if you think it's the bug you mentioned, it is still not fixed.

Take care,

David