gerege-token-kit — FEITIAN USB token SPM package¶
gerege-token-kit is a Swift Package Manager package for interacting locally
(offline, directly plugged in) with FEITIAN USB tokens (smart-card-form crypto
devices). It provides the ability to generate keys on the token, log in with a
PIN, produce ECDSA/RSA signatures, read/write certificates, and generate a
PKCS#10 CSR.
Dependency-free. It uses no third-party packages at all — it relies only on Apple's system frameworks (
Foundation,CryptoTokenKit,CommonCrypto,os). Source:desktop/gerege-token-kit/Package.swift.Local-only. This package interacts only with a plugged-in USB token directly — it makes no RP-API or any network call. Delivering the CSR/signature created on the token to the server is the responsibility of the calling app (e.g. the macOS desktop).
1. What it does¶
The package offers two levels of access:
| Level | Type | Method | Token |
|---|---|---|---|
| Standard (PKCS#11) | PKCS11Module |
Loads the FEITIAN Castle middleware (libcastle*.dylib) via dlopen and calls the C_* functions |
FEITIAN Castle FTSmartToken |
| Low-level (APDU) | BioPassDriver |
Sends ISO 7816-4 APDUs directly and, after mutual-auth, uses Secure Messaging (3DES) | BioPass2003 / ePass2003 (EnterSafe-FIPS applet) |
Additional supporting components:
TokenManager— reader detection and session management based onCryptoTokenKit(TKSmartCard).APDUCommand/APDUResponse/APDUTransceiver— the APDU build/transmit engine.SecureMessaging— the ePass2003 3DES secure channel (mutual auth, APDU wrap/unwrap).CSR— PKCS#10 CSR (EC P-256 + ECDSA-SHA256) builder.TokenError— anenumwith error text in Mongolian.
2. Installation¶
Add it via a local path (the package ships in the repo at the same time):
Then link it to your target:
.target(name: "MyApp", dependencies: [
.product(name: "GeregeTokenKit", targets: ["GeregeTokenKit"])
])
Import the package:
Package information (Package.swift):
- Name / product:
GeregeTokenKit - Platform: macOS 14+, iOS 17+ (since the PKCS#11
dlopenpath requires real USB middleware, in practice it works on macOS — see §5 below). - swift-tools-version: 5.9
3. Public API¶
PKCS11Module (standard path)¶
Source: Sources/GeregeTokenKit/PKCS11Module.swift.
Default middleware paths (defaultLibraryPaths):
/usr/local/lib/libcastle.1.0.0.dylib, /usr/local/lib/libcastle.dylib,
/Library/OpenSC/lib/opensc-pkcs11.so.
Low-level functions:
| Function | Role |
|---|---|
static open(libraryPath:) |
Loads the .dylib/.so via dlopen and finds the C_* symbols |
initialize() / finalize() |
C_Initialize / C_Finalize |
getSlotList(tokenPresent:) |
IDs of slots with a token plugged in |
openSession(slotId:readWrite:) / closeSession(_:) |
Open/close a session |
login(session:pin:userType:) / logout(_:) |
C_Login (CKU_USER/CKU_SO) / C_Logout |
findPrivateKey(session:label:) / findPublicKey(session:label:) |
Find a key by label |
signECDSA(session:privateKey:hash:) |
ECDSA signature — returns: r‖s |
readECPoint(session:publicKey:) |
Read the raw EC point from CKA_EC_POINT |
initToken(slotId:soPIN:label:) |
C_InitToken — factory reset + SO PIN |
initPIN(session:userPIN:) / setPIN(session:oldPIN:newPIN:) |
Set/change the user PIN |
generateECKeyPair(session:label:keyID:) |
Generate an EC P-256 keypair on the token |
writeCertificate(session:certificateDER:label:keyID:subjectDER:) |
Store an X.509 certificate via C_CreateObject |
listObjects(session:) |
List all objects on the token as TokenObjectInfo |
destroyObject(session:handle:) |
Delete an object (C_DestroyObject) |
High-level helper functions (they select the slot, log in, and log out automatically, and the session closes itself):
| Function | Role |
|---|---|
signECDSA(pin:keyLabel:hash:) |
Log in with the PIN, find the key by label, and sign |
generateCSR(pin:keyLabel:subject:) |
Generate a PKCS#10 CSR (DER+PEM) with the key on the token |
fullProvision(soPIN:userPIN:label:keyLabel:keyID:) |
Factory reset + SO/User PIN + EC keypair (⚠ all previous keys are erased) |
listObjects(pin:) |
Log in and list all objects |
importCertificate(pin:label:certificateDER:keyID:) |
Import a certificate |
deleteObject(pin:idHex:kind:) |
Delete an object by idHex+kind |
generateSigningKey(pin:label:keyID:) |
Generate an EC P-256 signing keypair |
changeUserPIN(oldPIN:newPIN:) / changeSOPIN(oldPIN:newPIN:) |
Change a PIN |
unlockUserPIN(soPIN:newUserPIN:) |
Unlock a locked User PIN with the SO PIN |
BioPassDriver (low-level/APDU path)¶
Source: Sources/GeregeTokenKit/BioPassDriver.swift.
Works directly with TKSmartCard (a Swift port of OpenSC's card-epass2003.c).
Main functions:
| Function | Role |
|---|---|
selectApplet(card:) |
Select the EnterSafe-FIPS applet |
establishSecureSession(card:...) |
Mutual auth → establish a Secure Messaging session |
verifyPIN(_:reference:card:) |
Verify the User/SO PIN (if wrong, pinVerifyFailed(retriesLeft:)) |
getTokenInfo(card:) |
ATR, label, whether FIPS (TokenInfo) |
signECDSA(hash:card:) / signRSA(data:card:) |
Signing |
generateECKeyPair(keyID:card:) / generateRSAKeyPair(keyID:keySize:card:) |
Generate a key (requires SM) |
readCertificate(fileID:card:) / writeCertificate(data:fileID:card:) |
Read/write a certificate |
initializePIN(pin:puk:card:) |
Fully initialize the token (erase + transport key + PKCS#15 file system + PIN) |
TokenManager (reader/session)¶
Source: Sources/GeregeTokenKit/TokenManager.swift.
TokenManager.shared— singletongetReaderNames() -> [String],hasToken() -> BoolwithSession(readerIndex:_:)— opens a session, runs the operation, and closes automaticallygetATR(readerIndex:)
CSR¶
Source: Sources/GeregeTokenKit/CSR.swift.
CSR.Subject(commonName:organization:country:email:)CSR.buildP256(subject:publicKeyPoint:signer:)— returns a PKCS#10 CSR (DER + PEM) from a 65-byte uncompressed EC point + a signing closure.
4. Usage examples¶
Check whether a reader is present¶
import GeregeTokenKit
let mgr = TokenManager.shared
if mgr.hasToken() {
print("Reader-ууд:", mgr.getReaderNames())
let atr = try await mgr.getATR()
print("ATR:", bytesToHex(atr))
}
Signing with PKCS#11 (high level)¶
let hash = /* SHA-256 digest, 32 bytes */
let p11 = try PKCS11Module.open() // loads libcastle*.dylib
let signature = try p11.signECDSA( // returns r‖s
pin: "12345678",
keyLabel: "gerege",
hash: hash
)
Generate a CSR with the key on the token¶
let p11 = try PKCS11Module.open()
let subject = CSR.Subject(commonName: "Бат-Эрдэнэ", country: "MN")
let (der, pem) = try await p11.generateCSR(
pin: "12345678",
keyLabel: "gerege",
subject: subject
)
// send the pem to the CA to obtain a certificate (outside this package)
At the APDU level (BioPassDriver)¶
let driver = BioPassDriver()
try await TokenManager.shared.withSession { card in
_ = try await driver.selectApplet(card: card)
try await driver.establishSecureSession(card: card) // 3DES SM
try await driver.verifyPIN("12345678", card: card)
let sig = try await driver.signECDSA(hash: hash, card: card)
return sig
}
5. Limitations¶
- Local only. It sends nothing to the network — it interacts only with a plugged-in token.
- Platform.
PKCS11Moduleloads native.dylibmiddleware viadlopen, so in practice it is used on macOS. AlthoughPackage.swiftalso declares iOS 17, the PKCS#11 path will not work on iOS since there is no real USB token - middleware.
- Supported tokens:
- PKCS#11: FEITIAN Castle FTSmartToken (
libcastle.1.0.0.dylib); OpenSCopensc-pkcs11.sois also included in the default paths. - APDU: BioPass2003 / ePass2003 (EnterSafe-FIPS applet).
- Crypto: EC P-256 (ECDSA-SHA256) is the primary path; RSA is supported by
BioPassDriver/PKCS11Module, but the CSR builder is P-256 only. - Provisioning is destructive.
fullProvision/initToken/initializePINerase all keys and certificates on the token.
6. Relationship to the macOS desktop app¶
This package is used by the macOS desktop app (the eID
Mongolia macOS RP client): that app wraps
GeregeTokenKit.TokenManager.shared and BioPassDriver in its own
Core/Token/TokenManager.swift and runs an experimental flow of signing a PDF
with a USB token.
7. Links¶
- Package:
desktop/gerege-token-kit/Package.swift - PKCS#11:
Sources/GeregeTokenKit/PKCS11Module.swift - APDU driver:
Sources/GeregeTokenKit/BioPassDriver.swift - Session/reader:
Sources/GeregeTokenKit/TokenManager.swift - APDU engine:
Sources/GeregeTokenKit/APDUEngine.swift - Secure Messaging:
Sources/GeregeTokenKit/SecureMessaging.swift - CSR builder:
Sources/GeregeTokenKit/CSR.swift - Errors:
Sources/GeregeTokenKit/TokenError.swift