Persistent Token Extension not initialized

Hello,

I am creating CryptotokenKit persistent token extension for macOS using Xcode on Sonoma. The goal is to support external crypto provider over network (with API calls).

I created a bare minimum app and a new target “Persistent Token Extension”. Before I go into specific implementation, I wanted to check if my extension/token initialises correctly. My understanding is that once the host app is started and the extension is registered by the OS, future queries for digital identities should check with it as well.

I tried is accessing mTLS website with Safari and Firefox that require client certificates, as well running custom application using SecItemCopyMatching to query the keychain for identities.

However, Token / TokenDriver seem to not initialize (logging never executes). Am I missing something here?

pluginkit sees the extension:

$ pluginkit -vvvvmi demo.TokenApp.TokenExt

     demo.TokenApp.TokenExt(1.0)
	            Path = /Users/alexander/Library/Developer/Xcode/DerivedData/TokenApp-dzulesgoanwnacguirprimnipibk/Build/Intermediates.noindex/Previews/TokenApp/Products/Debug/TokenApp.app/Contents/PlugIns/TokenExt.appex
	            UUID = 617526E8-987A-493F-A9E3-6295FF5AB00D
	       Timestamp = 2024-01-19 13:13:35 +0000
	             SDK = com.apple.ctk-tokens
	   Parent Bundle = /Users/alexander/Library/Developer/Xcode/DerivedData/TokenApp-dzulesgoanwnacguirprimnipibk/Build/Intermediates.noindex/Previews/TokenApp/Products/Debug/TokenApp.app
	    Display Name = TokenExt
	      Short Name = TokenExt
	     Parent Name = TokenApp
	        Platform = macOS

Token.swift:

import CryptoTokenKit
import OSLog

class Token: TKToken, TKTokenDelegate {
    private let log = Logger(subsystem: "demo.tokenapp", category: "Token");

    func createSession(_ token: TKToken) throws -> TKTokenSession {
        log.log(level: .info, "Token.createSession")
        return TokenSession(token:self)
    }
}

TokenDriver.swift:

import CryptoTokenKit
import OSLog

class TokenDriver: TKTokenDriver, TKTokenDriverDelegate {
    private let log = Logger(subsystem: "demo.tokenapp", category: "TokenDriver");
    
    func tokenDriver(_ driver: TKTokenDriver, tokenFor configuration: TKToken.Configuration) throws -> TKToken {
        log.log(level: .info, "TokenDriver.tokenDriver")
        return Token(tokenDriver: self, instanceID: configuration.instanceID)
    }
}

Replies

as well running custom application using SecItemCopyMatching to query the keychain for identities.

I recommend you start with this. Getting Safari to work presents a whole other array of challenges.

For a persistent token your container app has to set up information about the credentials ‘stored in’ the token. I use code like this:

func setup() {
	log.debug("will set up")
	let drivers = TKTokenDriver.Configuration.driverConfigurations
	guard drivers.count == 1 else {
		log.debug("incorrect driver count, expected: 1, got: \(drivers.count)")
		return
	}
	let driver = drivers.first!.value
	let config: TKToken.Configuration
	if let c = driver.tokenConfigurations[VirtualToken.instanceID] {
		log.debug("will reuse configuration")
		config = c
	} else {
		log.debug("will add configuration")
		config = driver.addTokenConfiguration(for: VirtualToken.instanceID)
	}
	let keychainItems: [TKTokenKeychainItem] = … generates the array …
	config.keychainItems = keychainItems
	log.debug("did set up, keychain item count: \(config.keychainItems.count)")
	self.updateKeys()
}

Creating a certificate item is straightforward:

let certItem = TKTokenKeychainCertificate(
	certificate: identity.certificate,
	objectID: identity.certificateObjectID.value
)!

In this snippet, identity is a custom type that gathers together all the info about the digital identity on my token. It is not a SecIdentity.

Creating the matching key item is a bit trickier:

let certItem = TKTokenKeychainCertificate(
	certificate: identity.certificate,
	objectID: identity.certificateObjectID.value
)!

// Create an item for the private key.  We pass the certificate in here
// so that CTK can infer all of the private key’s details — its type,
// size in bits, and so on — from there.  If we didn’t have the
// certificate, we just had a private key, we could populate that
// ourselves but that’s a lot of work.

let keyItem = TKTokenKeychainKey(
	certificate: identity.certificate,
	objectID: identity.privateKeyObjectID.value
)!

// Tweak the key item’s constraints.  The constraints default to `true
// as NSNumber`, which means that, deep down in the extension’s
// `TokenSession` class, the `tokenSession(_:beginAuthFor:constraint:)`
// method never gets called.  Setting a custom value — it doesn’t really
// matter that the value is as long as it’s not true or false — tells
// the system that it authentication is required and thus it’s OK to
// call that method.

keyItem.constraints![TKTokenOperation.signData.rawValue as NSNumber] = …

Many Bothans died to bring you that last line |-:

Share and Enjoy

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