Get identities from a smart card in an authorization plugin

Hello,

I’m working on an authorization plugin which allows users to login and unlock their computer with various methods like a FIDO key. I need to add smart cards support to it. If I understand correctly, I need to construct a URLCredential object with the identity from the smart card and pass it to the completion handler of URLSessionDelegate.urlSession(_:didReceive:completionHandler:) method. I’ve read the documentation at Using Cryptographic Assets Stored on a Smart Card, TN3137: On Mac keychain APIs and implementations, and SecItem: Pitfalls and Best Practices and created a simple code that reads the identities from the keychain:

CFArrayRef identities = nil;

OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)@{
    (id)kSecClass: (id)kSecClassIdentity,
    (id)kSecMatchLimit: (id)kSecMatchLimitAll,
    (id)kSecReturnRef: @YES,
}, (CFTypeRef *)&identities);

if (status == errSecSuccess && identities) {
    os_log(OS_LOG_DEFAULT, "Found identities: %{public}ld\n", CFArrayGetCount(identities));
} else {
    os_log(OS_LOG_DEFAULT, "Error: %{public}ld\n", (long)status);
}

When I use this code in a simple demo app, it finds my Yubikey identities without problem. When I use it in my authorization plugin, it doesn’t find anything in system.login.console right and finds Yubikey in authenticate right only if I register my plugin as non-,privileged. I tried modifying the query in various ways, in particular by using SecKeychainCopyDomainSearchList with the domain kSecPreferencesDomainDynamic and adding it to the query as kSecMatchSearchList and trying other SecKeychain* methods, but ended up with nothing. I concluded that the identities from a smart card are being added to the data protection keychain rather than to a file based keychain and since I’m working in a privileged context, I won’t be able to get them. If this is indeed the case, could you please advise how to proceed? Thanks in advance.

What sort of authorisation plug-in is this? Specifically, are you subclassing SFAuthorizationPluginView in order to present a custom login UI?

Share and Enjoy

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

I have a mechanism that provides UI by subclassing SFAuthorizationPluginView and a privileged mechanism that performs the authorization. I tried reading Yubikey identities in both of them and at various points, with no luck.

Thanks for the extra info.

First up, an SFAuthorizationPluginView can’t support smart card authentication in the same way as the built-in login UI can (FB11978008). That’s because the built-in UI populates various authorization context values but SFAuthorizationPluginView does not. The name and format of these values is not documented, so the authorization plug-in can’t work around this issue itself.

However, that’s not what you’re trying to do. Rather, you’re trying to simply use smart-card-back credentials, and that’s more feasible.

There’s one thing to check here, and then a roadblock to get around.

The thing to check is that your smart card is available in pre-login context. To do that, disable your authorisation plug-in, reverting to the built-in login UI. Then check that you can use the built-in PIV smart card support to pair your user with your smart card, and thus log in with your YubiKey. I don’t have docs for how to do that handy — that’s more of an Apple Support thing than a DTS thing — but I do it all the time and it’s not hard.

Once you’ve confirmed that’s working, unpair your user (sc_auth unpair), restart, and then re-install your authorisation plug-in. That gets you back to where you started, but you know that your YubiKey is working pre-login.

And now things get tricky )-: Consider:

  • Access to smart cards is gated by entitlements. See this post for the exact set.
  • Your authorisation plug-in is an old school in-memory plug-in, so you have no control over your entitlements. You get whatever entitlements are available to the host process.

Now, the current authorisation plug-in hosts are signed with com.apple.security.smartcard:

% codesign -d --entitlements - /System/Library/Frameworks/Security.framework/Versions/A/MachServices/authorizationhost.bundle/Contents/XPCServices/authorizationhosthelper.arm64.xpc
…
[Dict]
    …
    [Key] com.apple.security.smartcard
    [Value]
        [Bool] true
% codesign -d --entitlements - /System/Library/Frameworks/Security.framework/Versions/A/MachServices/SecurityAgent.bundle/Contents/XPCServices/SecurityAgentHelper-arm64.xpc 
…
[Dict]
    …
    [Key] com.apple.security.smartcard
    [Value]
        [Bool] true

So you should be able to see the smart card via CTK APIs, most notably TKTokenWatcher. I recommend you try that first.

If that works then the next step is to try accessing the credentials in the kSecAttrAccessGroupToken keychain access group. I’m not sure whether that’ll work. My concern is that the data protection keychain might not kick in because there’s no App ID entitlement (on macOS this is com.apple.application-identifier). However, my best guess is that it won’t matter, because you’re specifically targeting kSecAttrAccessGroupToken, so I think it’s worth testing this way first.

Do this stuff in your non-privileged mechanism, at least to start. Depending on the results you get there, you can then think about re-testing in your privileged mechanism.

Oh, and if it turns out that the App ID entitlement is required, it’s not a complete showstopper, but it does make things messier )-:

Share and Enjoy

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

Thanks for the suggestions.

I successfully paired my Yubikey with sc_auth pair. After a restart, on the FileVault screen, I got a prompt to enter a PIN. When I did, I got an error "Smartcard configuration is invalid for this account" (I assume because I didn't do sc_auth filevault enable), so I had to unlock FileVault with my password. After that, the smart card worked normally: I was able to log out and then log back in with the PIN. So I have to assume my Yubikey works. I then did sc_unpair and proceeded to test the kSecAttrAccessGroupToken theory.

I modified my code like this:

TKTokenWatcher *watcher = [TKTokenWatcher new];
[watcher setInsertionHandler:^(NSString *tokenID) {
    if (![tokenID containsString:@"pivtoken"])
        return;

    os_log(OS_LOG_DEFAULT, "Inserted token: %{public}@\n", tokenID);
    [watcher addRemovalHandler:^(NSString *tokenID) {
        os_log(OS_LOG_DEFAULT, "Removed token: %{public}@\n", tokenID);
    } forTokenID:tokenID];

    CFArrayRef identities = nil;

    OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)@{
        (id)kSecClass: (id)kSecClassIdentity,
        (id)kSecMatchLimit: (id)kSecMatchLimitAll,
        (id)kSecReturnRef: @YES,
        (id)kSecAttrAccessGroup: (id)kSecAttrAccessGroupToken,
        (id)kSecAttrTokenID: tokenID,
    }, (CFTypeRef *)&identities);

    if (status == errSecSuccess && identities) {
        os_log(OS_LOG_DEFAULT, "Found identities: %{public}ld\n", CFArrayGetCount(identities));
    } else {
        os_log(OS_LOG_DEFAULT, "Error: %{public}ld\n", (long)status);
    }
}];

It works fine in the authenticate right, no matter whether I run security authorize -u authenticate or go to the System Settings and mess with the checkboxes (I removed irrelevant parts from the log):

Inserted token: com.apple.pivtoken:<redacted>
Found identities: 1

However, I get errSecNotAvailable error in the system.login.console right:

Inserted token: com.apple.pivtoken:<redacted>
Error: -25291

The mechanism is registered as non-privileged in both cases.

Does this means that the App ID entitlement is required?

Honestly, I’m not sure what’s going on here.

The mechanism is registered as non-privileged in both cases.

Hey hey, that was the next thing I was gonna ask you (-:

OK, one more thing. Is the mechanism hosted by the same process in each case? I’d expect that to be the case, and for that process to be SecurityAgentHelper-arm64.xpc, but I wanna double check.

Share and Enjoy

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

Yes, here's a full log (I replaced the bundle ID and redacted the token hash):

$ log stream --predicate 'subsystem = "com.example.NameAndPassword"' --style compact
Filtering the log data using "subsystem ==[cd] "com.example.NameAndPassword""
Timestamp               Ty Process[PID:TID]
2026-01-15 20:41:18.941 Df SecurityAgentHelper-arm64[11135:3f6df] [com.example.NameAndPassword:AuthPlugin] Inserted token: com.apple.pivtoken:<redacted>
2026-01-15 20:41:18.951 Df SecurityAgentHelper-arm64[11135:3f6df] [com.example.NameAndPassword:AuthPlugin] Found identities: 1
2026-01-15 20:41:25.722 Df SecurityAgentHelper-arm64[11164:3f7db] [com.example.NameAndPassword:AuthPlugin] Inserted token: com.apple.pivtoken:<redacted>
2026-01-15 20:41:25.723 Df SecurityAgentHelper-arm64[11164:3f7db] [com.example.NameAndPassword:AuthPlugin] Error: -25291

Oh, wow, you’re testing with NameAndPassword. That’s not good. Please open a DTS code-level support request so that I can send you something privately.

IMPORTANT In the submission form, where it asks who sent you down this path, reference this forums post.


Anyway, coming back to the main issue, your logs make it clear that your mechanism is running in SecurityAgentHelper-arm64 in both cases. That’s what I expected, which leaves me struggling to find a path forward )-:

The steps that you described earlier make it clear that you restarted, and that you have FileVault enabled. These are both complexities that I’d like to bypass, at least for the moment. Try this:

  1. Boot the machine and log in normally.
  2. Install your authorisation plug-in.
  3. Attach your smart card and verify that it’s working in general. You can do that from a small test app, or using your mechanism attached to the authenticate rule.
  4. Install your authorisation mechanism for system.login.console.
  5. Choose Apple menu > Log Out.
  6. Log in.
  7. Look at your logs to see what you got back from SecItemCopyMatching.

Share and Enjoy

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

Thanks, I have opened a support request like you suggested. I want to clarify that I'm using NameAndPassword solely because it provides a convenient scaffolding for an authorization plugin and it is much quicker to iterate with it than with my actual plugin (think 10-20 seconds versus 3+ minutes per attempt). Still, I did test the same code in the latter, and I also went through your steps, all with the same results.

Get identities from a smart card in an authorization plugin
 
 
Q