macOS IKEv2 PK12 identityReference

Hi there,

I'm trying to obtain the correct persistent keychain reference, but need some help. The following code crashes, but I guess this is because the NEVPNProtocolIKEv2 protocol expects something else (although it would seem logical at first to do it like this):


NSData *data = [[NSData alloc] initWithContentsOfURL: ikecertificatepath];
NSDictionary* options = @{ (id)kSecImportExportPassphrase : password };
CFArrayRef rawItems = NULL;
OSStatus status = SecPKCS12Import((__bridge CFDataRef)data, (__bridge CFDictionaryRef)options, &rawItems);
NSArray* items = (NSArray*)CFBridgingRelease(rawItems);
NSDictionary* firstItem = nil;
if ((status == errSecSuccess) && ([items count]>0)) {
     firstItem = items[0];
     CFTypeRef identity = (SecIdentityRef)CFBridgingRetain(firstItem[(id)kSecImportItemIdentity]);
                       
     p.identityReference = (__bridge NSData * _Nullable)(identity); <= NSCFType copyWithZone crash here


As macOS SecPKCS12Import adds the identity to the keychain, I tried reading it back like this and assign it to the protocol, is that how it is supposed to work?


NSMutableDictionary* query = [@{(__bridge id)kSecClass:(__bridge id)kSecClassIdentity,(__bridge id)kSecImportItemIdentity : (__bridge id)identity,} mutableCopy];
query[(__bridge id)kSecReturnPersistentRef] = @YES;
__block OSStatus status;
CFTypeRef results = nil;
status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &results);
if (results != nil) {
     p.identityReference = (__bridge NSData * _Nullable)(results);
     NSLog(@"New P12 reference");
} else {
     NSLog(@"Unable to create P12 reference");
}


Any pointers in the right direction are welcome!

Accepted Reply

That documentation is wrong, and I’d appreciate you filing a bug against it.

However, I was helping another developer with this issue earlier in the week and was reminded of another problem with

identityData
, namely that it puts the identity into the wrong keychain in this case (IKEv2 on macOS). Specifically, the identity is expected to be in the user keychain whereas
NEVPNManager
puts it in the System keychain).

I’m pretty sure there’s a bug on file about this, although I don’t have the number handy right now.

Which brings us back to

identityReference
. The idea here is that you:
  1. Add your identity to the keychain yourself

  2. Use the keychain to get a persistent reference to the item

  3. Put that persistent reference in the

    identityReference
    property

This does work, although there are some caveats:

  • The code is slightly different on macOS than on iOS-based platforms

  • There’s another bug on macOS that presents the user with a keychain authorisation dialog when they connect; the user must click Always Allow to allow the connection and prevent any future dialogs

Again, I think there’s a bug on file about this last point but I don’t have the bug number handy.

Coming back to steps 1 and 2, here’s a rough outline of what you need:

  1. Use

    SecPKCS12Import
    to import the PKCS#12
  2. Extract the identity from the import results

  3. Here’s where things diverge:

    • On iOS-based platforms, use

      SecItemAdd
      to add the identity to the keychain and get back a persistent reference
    • On macOS the

      SecItemImport
      has already added the identity to the keychain, so you must use
      SecItemCopyMatching
      to get back a persistent reference

Here’s a snippet of code for the macOS side of things:

func identityReference(for pkcs12Data: Data, password: String) -> Data {

    var importResult: CFArray? = nil
    let err = SecPKCS12Import(pkcs12Data as NSData, [
        kSecImportExportPassphrase: password
    ] as NSDictionary, &importResult)
    guard err == errSecSuccess else { fatalError() }
    let importArray = importResult! as! [[String:Any]]
    let identity = importArray[0][kSecImportItemIdentity as String]! as! SecIdentity

    var copyResult: CFTypeRef? = nil
    let err2 = SecItemCopyMatching([
        kSecValueRef: identity,
        kSecReturnPersistentRef: true
    ] as NSDictionary, &copyResult)
    guard err2 == errSecSuccess else { fatalError() }
    return copyResult! as! Data
}

IMPORTANT This code does no error handling and will simply crash if things go wrong. Please don’t integrate it verbatim into your project, but rather rewrite it to throw if there’s an error.

Oh, yeah, and it’s in Swift (-: Sorry about that. Let me know if you have any troubles creating the Objective-C equivalent.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Replies

Is there a reason you’re using

identityReference
here? I’ve found
identityData
and
identityDataPassword
much easier to wrangle.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

I tried that one too, but according to the documentation in macOS I have to use identityReference:


@property(copy) NSData *identityData;

The certificate and private key components of the tunneling protocol authentication credential, encoded in PKCS12 format. In macOS, this property is ignored for NEVPNProtocolIKEv2 objects. In cases where this property is ignored, the identity should be set using the identityReference property.


The documentation around identityReference seems a bit scarce, any info/help is welcome!

That documentation is wrong, and I’d appreciate you filing a bug against it.

However, I was helping another developer with this issue earlier in the week and was reminded of another problem with

identityData
, namely that it puts the identity into the wrong keychain in this case (IKEv2 on macOS). Specifically, the identity is expected to be in the user keychain whereas
NEVPNManager
puts it in the System keychain).

I’m pretty sure there’s a bug on file about this, although I don’t have the number handy right now.

Which brings us back to

identityReference
. The idea here is that you:
  1. Add your identity to the keychain yourself

  2. Use the keychain to get a persistent reference to the item

  3. Put that persistent reference in the

    identityReference
    property

This does work, although there are some caveats:

  • The code is slightly different on macOS than on iOS-based platforms

  • There’s another bug on macOS that presents the user with a keychain authorisation dialog when they connect; the user must click Always Allow to allow the connection and prevent any future dialogs

Again, I think there’s a bug on file about this last point but I don’t have the bug number handy.

Coming back to steps 1 and 2, here’s a rough outline of what you need:

  1. Use

    SecPKCS12Import
    to import the PKCS#12
  2. Extract the identity from the import results

  3. Here’s where things diverge:

    • On iOS-based platforms, use

      SecItemAdd
      to add the identity to the keychain and get back a persistent reference
    • On macOS the

      SecItemImport
      has already added the identity to the keychain, so you must use
      SecItemCopyMatching
      to get back a persistent reference

Here’s a snippet of code for the macOS side of things:

func identityReference(for pkcs12Data: Data, password: String) -> Data {

    var importResult: CFArray? = nil
    let err = SecPKCS12Import(pkcs12Data as NSData, [
        kSecImportExportPassphrase: password
    ] as NSDictionary, &importResult)
    guard err == errSecSuccess else { fatalError() }
    let importArray = importResult! as! [[String:Any]]
    let identity = importArray[0][kSecImportItemIdentity as String]! as! SecIdentity

    var copyResult: CFTypeRef? = nil
    let err2 = SecItemCopyMatching([
        kSecValueRef: identity,
        kSecReturnPersistentRef: true
    ] as NSDictionary, &copyResult)
    guard err2 == errSecSuccess else { fatalError() }
    return copyResult! as! Data
}

IMPORTANT This code does no error handling and will simply crash if things go wrong. Please don’t integrate it verbatim into your project, but rather rewrite it to throw if there’s an error.

Oh, yeah, and it’s in Swift (-: Sorry about that. Let me know if you have any troubles creating the Objective-C equivalent.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Filed a bug report, 37740394. Will start working with your suggestions, thanks!