A Rust host-side library for CanoKey devices. It builds the
command APDUs for each on-key applet (PIV, OpenPGP, OATH, FIDO2/CTAP, Admin, NDEF)
and parses the responses — you bring the transport (PC/SC, USB, NFC) and drive each
exchange. There is no runtime, no background thread, no credential cache and no
mutable global state, so it embeds cleanly in desktop apps, mobile apps (via FRB)
and C programs. A C ABI is available as canokey-c (experimental).
Add the facade crate:
cargo add canokeyEvery operation follows the same pattern: construct it, then loop over
start/command/advance, performing one raw transport transmit per iteration:
use canokey::{probe_device, ProbeMode, ProbeOptions, Step};
let mut op = probe_device(ProbeOptions {
mode: ProbeMode::Piv, ..Default::default()
})?;
let mut step = op.start()?;
while step == Step::Exchange {
// One raw transmit of your own: PC/SC SCardTransmit, USB CCID, NFC, ...
let response = my_card_transmit(op.command()?.as_bytes())?;
step = op.advance(&response)?; // response data including SW1/SW2
}
let profile = op.take_result()?; // owned; independent of the operationRunnable offline versions of this loop (with fixture transcripts you can replace with real I/O) live in crates/canokey/examples.
C applications link canokey-c and include
include/canokey.h. The runnable example
probe.c shows size queries, profile transfer
and cleanup; build and run it with bash scripts/run-c-example.sh. The C ABI is
experimental. See the PKCS#11 integration guide
for a complete session sketch.
The library never touches a device itself. Your application owns the connection and follows five rules:
- Implement one raw transmit: send a complete command APDU, return the complete response including SW1/SW2.
- Hold an exclusive connection lease for the whole operation loop.
- Disable transport-level continuation (61xx/6Cxx handling) and retries — the core performs those itself and must see the card's exact words.
- Getters never send APDUs; only
start/advanceadvance the exchange. - On I/O failure, drop the operation and drain or isolate pending I/O before reusing the connection. Dropping or cancelling never rolls back card effects.
PIV — selection, PIN status/verification/logout, PIN/PUK change and unblock; external/mutual 3DES or AES-192 management authentication with caller-supplied challenges; authenticated object/certificate writes and management-key replacement; PIN-managed protection validation/finalization with explicit PUK blocking; key rotation maintaining PRINTED. Object/certificate reads with bounded gzip decoding, certificate deletion, metadata and algorithm-configuration reads; compact directory with entry diagnostics and UTF-16 container names; key move/delete; explicit PIN/PUK retry reset; attestation DER; explicit reset of a blocked PIV application. Key generation/import (P-256/P-384/P-521/secp256k1/SM2 scalars, RSA CRT, Ed25519/X25519/ML seeds) with public-key SPKI export. Classic RSA/ECDSA/SM2/Ed25519 signing with DER/P1363 conversion; explicit ML-DSA (empty context), randomized Ed25519 and SM2 full-message streaming signing, including empty messages; raw RSA decryption, ECDH (P-256/P-384/P-521/secp256k1), X25519, ML-KEM-768 decapsulation and SM2 agreement with pre-exchanged peer keys. Classic PIV hashing/padding and postprocessing KDF remain caller responsibilities. Batch requests run under one SELECT and retain completed results after a later failure.
OpenPGP — data-object/certificate reads and writes, separate PW1-sign/PW1-other modes, password/reset management, explicit policies/fingerprints/timestamps, key generation/import, shared SPKI export, signatures, PKCS#1 v1.5 decipher and ECDH/X25519.
OATH — SELECT/access-code validation, PBKDF2 password derivation, credential CRUD, full/truncated calculations and paged results with explicit HOTP/touch markers. Set-default marks an HOTP credential as the touch keyboard-emulation default (two-slot/append-enter dialect only on firmware 3.0.0+). The vendor extension commands (GET SERIAL, HMAC-SHA1 challenge-response from a PASS slot) provide the KeePassXC interop path on firmware 3.1.0.
CTAP/FIDO2 — ISO 7816 transport envelope (explicit FIDO2 selection, 80 10
message wrap, 80 C0 continuation) plus a typed CTAP2 client: strict canonical
CBOR, COSE key and authenticatorData parsing, getInfo/makeCredential/getAssertion/
reset/selection. The clientpin feature (default in canokey-ctap, opt-in on the
facade) adds ClientPIN protocols 1/2, credential management with bounded enumeration,
authenticatorConfig and fragmented largeBlobs. Raw CTAP1/U2F register/authenticate/
check-only/version commands are ungated; the hmac-secret extension covers the
makeCredential declaration and the encrypted salt exchange (including the CanoKey
hmac-secret-mc variant). WebAuthn ceremonies (clientDataJSON, attestation trust,
rpId policy) remain host-side.
Admin — identity/storage/configuration reads, PIN, NFC/NDEF and CTAP SM2
configuration, explicit applet/device resets; configuration patches retain confirmed
writes on failure. admin::operation_with_access (Access::Existing) reuses the
caller's selected Admin transaction without SELECT or implicit VERIFY. Typed PASS
slots read both touch slots (Off/Static/HmacSha1/Oath/Unknown) and write Off,
static-password or HMAC-SHA1 configurations.
NDEF — capability-container reads and chunked message read/replace with zero-NLEN-first crash-consistent writes; profile-free.
Cross-cutting — minimal/PIV device probing with firmware and PIV version separation; observed algorithm IDs with explicit Supported/Unsupported/Unknown evidence; optional X.509 certificate inspection (see below).
Audited firmware layouts 1.3–3.1.0 are covered with per-operation gates. Legacy
OATH commands, OpenPGP DO framing and Admin configuration fields are selected from
the actual firmware version; unknown base versions never enable mutations, and
development builds follow their declared numeric base version. Every factory checks
required capability evidence. PIV sign_streaming handles ML-DSA and empty Ed25519
messages explicitly; SM2 initiators require PIN Never/Once and peer keys supplied at
construction. See compatibility contracts
for the model and each applet's historical restrictions. Validation uses pinned sources
and offline transcripts; hardware checks and consumer integration remain separate.
Install rustup; the repository selects Rust 1.85.1 (MSRV 1.85). All examples use synthetic offline transcripts and check emitted commands. Test credentials, challenges and certificate payloads are fixtures, not production inputs.
| Example | Demonstrates |
|---|---|
| historical | Firmware 1.3 OATH LIST and profile-aware OpenPGP field parsing |
| openpgp | Observed key attributes, explicit PW1-sign and owned signature |
| oath | Caller-supplied TOTP time step and owned code bytes |
| admin | Explicit PIN and configuration patch under one SELECT |
| probe | Caller-owned device profile |
| read_certificate | Operation and result lifetimes |
| write_certificate | Mutual authentication followed by PUT DATA |
| decapsulate | Algorithm discovery, PIN, chained ML-KEM ciphertext and owned secret |
| sign_streaming | Explicit randomized Ed25519 signing of an empty message |
| batch | Successful preceding results after a later failure |
| C probe | Size queries, profile transfer and cleanup |
cargo run -p canokey --example admin --locked
cargo run -p canokey --example oath --locked
cargo run -p canokey --example openpgp --locked
cargo run -p canokey --example batch --locked
bash scripts/run-c-example.shBoundary sketches for real integrations: Console/Dart, PKCS#11/C.
Enable canokey/x509 for parsing, or canokey/serde for optional serialization.
Default builds omit the parser; applications choose their own serializers or FRB DTOs.
let info = canokey::x509::parse_der(certificate.der(), Default::default())?;
// With canokey/serde and the application's serde_json dependency:
let json = serde_json::to_string(&info.summary())?;The x509-info documentation owns certificate models, CLI formats and schema. Parsing does not verify certificate trust or validity.
Rust applications depend on canokey; C applications link canokey-c.
Depend on a lower-level crate directly only when you need it without the facade.
| Crate | Responsibility |
|---|---|
canokey |
Facade: re-exports all applets plus device probing |
canokey-c |
Experimental C ABI (copied descriptors, opaque handles) |
canokey-piv |
PIV operations and certificate container parsing |
canokey-openpgp |
OpenPGP data, passwords, policies and key operations |
canokey-oath |
OATH access, credentials and calculations |
canokey-ctap |
CTAP/FIDO2 envelope and CTAP2 client (ClientPIN, credential management) |
canokey-admin |
Admin reads, configuration, PIN and explicit resets |
canokey-ndef |
NDEF capability reads and crash-consistent message writes |
canokey-protocol |
APDU/TLV codecs, owned operations, limits, errors, secret buffers |
canokey-compat |
Immutable profiles, capability evidence, firmware rules |
canokey-key |
Shared public-key TLV fields and pure SPKI export |
The facade optionally re-exports the independent crates.io package
x509-info 0.1.1. The full architecture
and layering rationale live in the documentation map.
cargo fmt --all --check
cargo test --workspace --locked
cargo test --workspace --all-features --locked
cargo clippy --workspace --all-targets --all-features --locked -- -D warnings
RUSTDOCFLAGS="-D warnings" cargo doc --workspace --all-features --no-deps --locked
cargo build --workspace --locked
cargo build -p canokey --all-features --target wasm32-unknown-unknown --locked
python3 scripts/check-dependencies.py
python3 scripts/check-licenses.py
bash scripts/test-c-abi.shCI checks native/wasm builds, examples, doctests, strict rustdoc/clippy, dependency
boundaries, licenses and C/C++ linking. Open target/doc/canokey/index.html for
public API documentation. Contribution rules (language, architecture, checks,
commits) are in AGENTS.md.
- Documentation map: design documents vs user guides, with an architecture overview.
- API contracts: ownership, execution and binding rules.
- Reference evidence: pinned firmware and consumer sources.
- The companion canokey-pkcs11 migration validates the PIV C ABI against native Windows hardware. Console/ckman and general Python bindings are separate projects, out of scope for this repository.
Copyright 2026 canokeys.org. Licensed under Apache-2.0. Each workspace crate inherits the metadata and includes a copy of the root license for packaging. Third-party dependencies and reference repositories retain their own licenses.