Using mTLS with YubiKey via USB-C and PIV

I've been trying over the past few days to use a PIV-programmed Yubikey to perform mTLS (i.e. mutual client cert auth) in my custom app. My understanding is that I need to feed NSURLSession a SecIdentity to do so.

Yubico's instructions state that I need their Yubico Authenticator app for this, but this directly contradicts Apple's own documentation here. I dont need NFC/lightening support, and I only need support for my specific app.

When I plug in my key to my iPhone and have TKTokenWatcher active, I DO see "com.apple.pivtoken" appear in the logs. And using Yubico's SDK, I CAN get data from the key (so I'm pretty sure my entitlements and such are correct).

But using the below query to get the corresponding (fake? temporary?) keychain item, it returns NULL no matter what I do:

let query: [String: Any] = [
    kSecClass as String: kSecClassIdentity,
    kSecReturnRef as String: true,
    kSecAttrTokenID as String: "apple.com.pivtoken", // Essential for shared iPads
    kSecMatchLimit as String: kSecMatchLimitOne
]

var item: CFTypeRef?
let status = SecItemCopyMatching(query as CFDictionary, &item)

"status" is always -25300 (which is "not found").

I've also created a CTK extension (as Yubico's authenticator does) and tried to use self.keychainContents.fill(), and then tried to access it with kSecAttrTokenID as "<myAppExtensionId>:Yubico YubiKey OTP+FIDO+CCID", as that's what shows via TKTokenWatcher, and this also doesn't work. I've also tried just the app extension ID, and that doesn't work. Both my extension and my main app have the following entitlements:

	<key>com.apple.developer.default-data-protection</key>
	<string>NSFileProtectionComplete</string>
	<key>com.apple.security.application-groups</key>
	<array/>
	<key>com.apple.security.smartcard</key>
	<true/>
    <key>keychain-access-groups</key>
    <array>
        <string>$(AppIdentifierPrefix)com.apple.pivtoken</string>
        <string>$(AppIdentifierPrefix)myAppExtensionId</string>
    </array>

As one final test, I tried using the yubikey in safari to access my server using mTLS, and it works! I get prompted for a PIN (which is odd because I've programmed it not to require a PIN), but the request succeeds using the key's default PIN. I just cannot get it working with my own app.

Can anyone here (or preferably, at Apple) point me in the right direction? I have a feeling that the documentation I've been reading applies to MacOS, and that iOS/ipadOS have their own restrictions that I either need to work around, or which prevent me from doing what I need to do. It's obviously possible (i.e. the Yubico Authenticator sort of does what I need it to), but not in the way that Apple seems to describe in their own documentation.

Answered by DTS Engineer in 883587022
I've also created a CTK extension

So, yeah, you don’t want to go down that path. Because the YubiKey presents a PIV interface over USB, it will be picked up by the built-in PIV support, so you don’t need to mess around with a CTK app extension.

And this also means that you’re correct that you don’t need to install Yubico Authenticator.

As to what you should be doing, I recommend that you break this up into two steps:

  1. Find and then use the key, just to make sure it’s working.
  2. Then try to pass the corresponding digital identity to URLSession for mTLS.

For step 1, see my advice in this post.

Share and Enjoy

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

I've also created a CTK extension

So, yeah, you don’t want to go down that path. Because the YubiKey presents a PIV interface over USB, it will be picked up by the built-in PIV support, so you don’t need to mess around with a CTK app extension.

And this also means that you’re correct that you don’t need to install Yubico Authenticator.

As to what you should be doing, I recommend that you break this up into two steps:

  1. Find and then use the key, just to make sure it’s working.
  2. Then try to pass the corresponding digital identity to URLSession for mTLS.

For step 1, see my advice in this post.

Share and Enjoy

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

@DTS Engineer Quinn,

Thank you for your reply; I think I'm on a better path now. Unfortunately, I'm now getting an -34018 error and "nil" with the code below:

var result: CFTypeRef?
let status = SecItemCopyMatching([
    kSecClass: kSecClassCertificate,
    kSecAttrAccessGroup: kSecAttrAccessGroupToken,
    kSecMatchLimit: kSecMatchLimitAll,
    kSecReturnRef: true,
] as CFDictionary, &result)
print("status=\(status) result=\(String(describing: result))")

I've tried your trouble shooting steps mentioned in your other posts, including printing out my app's entitlements, and they all look correct:

    <plist version="1.0">
        <dict>
            <key>application-identifier</key>
            <string>MY_APP_ID.MY_APP_BUNDLE_ID</string>
            <key>com.apple.developer.default-data-protection</key>
            <string>NSFileProtectionComplete</string>
            <key>com.apple.developer.team-identifier</key>
            <string>MY_APP_ID</string>
            <key>com.apple.security.application-groups</key>
            <array></array>
            <key>get-task-allow</key>
            <true/>
            <key>keychain-access-groups</key>
            <array>
                <string>MY_APP_ID.com.apple.token</string>
                <string>MY_APP_ID.com.apple.pivtoken</string>
                <string>MY_APP_ID.MY_APP_BUNDLE_ID</string>
            </array>
        </dict>
    </plist>

I've tried both an xcode-managed provisioning profile, and an enterprise one owned by my org, and neither work.

I ALSO saw your note here about REMOVING the app ID prefix from the entitlement, but xcode won't let me run the app because it complains because "Provisioning profile "<my profile>" doesn't match the entitlements file's value for the keychain-access-groups entitlement. "

Any pointers? Feel like I'm close but must be overlooking something trivial. I am testing on real hardware, FYI.

An update: when I remove ALL of my keychain groups except for "com.apple.token" (i.e. I removed the one for pivtoken), it works ONLY WITH my enterprise provisioning profile. For some reason, xcode is OK with me manually modifying my .entitlement file to remove the app prefix ID that xcode is automatically adding.

If I use my own personal account, xcode is NOT OK with me modifying the provisioning profile to remove the app prefix from "com.apple.token" (xcode says my provisioning profile "doesn't match the entitlements file's value for the keychain-access-groups entitlement. ")

I'd like to get it working in both scenatios. Is there something I can do in xcode to make my automatically-managed one work?

OK, you’re definitely on the right path here.

it works ONLY WITH my enterprise provisioning profile.

Interesting. I didn’t expect to see a difference between team types here, because Developer Account Help > Reference > Supported capabilities (iOS) says that Keychain Sharing is available for all three team. However, I tested this and discovered a discrepancy.

Specifically, I created a test project with two app targets:

  • Individual, signed by the Individual team I use for testing (SKMME9E2Y8)
  • PersonalTeam, signed by the Personal Team I use for testing (SKMME9E2Y8)

Note Personal Team is the term that Xcode uses for Apple’s free provisioning feature. It corresponds to the Apple Developer column in the table I referenced earlier. In contrast, an Individual team is covered by the ADP column.

I then added the Keychain Sharing capability to both, built them for the device, and then dumped the profiles:

% security cms -D -i Individual.app/embedded.mobileprovision | plutil -p - 
{
  …
  "Entitlements" => {
    …
    "keychain-access-groups" => [
      0 => "SKMME9E2Y8.*"
      1 => "com.apple.token"
    ]
  }
  …
}
% security cms -D -i PersonalTeam.app/embedded.mobileprovision | plutil -p -
{
  …
  "Entitlements" => {
    …
    "keychain-access-groups" => [
      0 => "VL9SQP756U.*"
    ]
  }
  …
}

And would you look at that!?! In the Individual team case we add the com.apple.token keychain access group automatically, but we don’t do that for a Personal Team.

If by “personal account” you mean a Personal Team, then this explains the issue you’re seeing.

I’m not sure whether we deliberately chose to restrict this for folks using a Personal Team, or whether this is simply something that slipped through the cracks. Regardless, this is clearly something you want so I recommend that you file a bug about it.

Please post your bug number, just for the record.

In the meantime, you’ll have to use your Enterprise team.

Note Just to be clear, this is only an issue on iOS and its child platforms. On macOS, any app with access to the data protection keychain can access com.apple.token without claiming it in their entitlements. This asymmetry, caused by platform differences in a bygone era, is the source of some confusion.

Share and Enjoy

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

@DTS Engineer Quinn,

THANK YOU. You were vital in getting me on the right track. I ended up noticing the same thing; that my personal account’s provisioning profile lacked the entitlement that I needed. With your help and insight, everything started working as expected.

Bug report number is FB22461947 (I also mentioned the appPrefixId issue that IMO should also be fixed) Thanks again! I definitely learned something from your guidance.

Using mTLS with YubiKey via USB-C and PIV
 
 
Q