Repository navigation
feat: route all applet protocol operations through libcanokey #27
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
+31,399
−5,043
Merged
Changes from 9 commits
Commits
Show all changes
19 commits
Select commit
Hold shift + click to select a range
ba3e133
feat: begin migrating PIV operations to libcanokey
dangfan 6620bfc
feat: route all applet protocol operations through libcanokey
dangfan 7085eb7
fix: authenticate pass slot reads on gated firmware
dangfan ed320c7
fix: authenticate the gated algorithm-extension read on firmware 3.0.x
dangfan 4ca1cc5
feat: adopt upstream CTAP2 client and consolidate card clients
dangfan 69ef2b1
refactor: adopt upstream firmware authentication-gate modeling
dangfan 66271c7
chore: unify wasm-bindgen family at 0.2.128 and refresh dependencies
dangfan e7f77fa
refactor: adopt libcanokey 41a3ea60 and rewrite the integration doc
dangfan fd4ebdc
chore: drop the dead resetApdu enum field
dangfan 5e32c7d
fix: dart2js-safe 64-bit decodes and skip redundant Admin SELECTs
dangfan 0b23585
feat: show build commit and build time in the About dialog
dangfan f7ea0cf
feat: skip the duplicate serial read in probes
dangfan 77a9c97
fix: address review follow-ups
dangfan b4ec54d
feat: polish the build info in the About dialog
dangfan 1a8cf0e
docs: note the remaining wire redundancies needing upstream support
dangfan 0e3fc3d
fix: use the PR head commit for BUILD_COMMIT
dangfan c03ea2e
feat: bundle app fonts and subset Noto Sans SC for CJK
dangfan 02f26b7
build: regenerate CJK font subsets as part of the build
dangfan eb86774
ci: set up an isolated Python for the font subsetting step
dangfan File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,106 @@ | ||
| # libcanokey integration | ||
|
|
||
| All card protocol operations in Console go through | ||
| [libcanokey](https://github.com/canokeys/libcanokey) via the Flutter Rust | ||
| Bridge facade in `rust/src/api/protocol.rs`. The pinned revision lives in | ||
| `rust/Cargo.toml`. Dart never constructs APDUs; host-side policy (CSR | ||
| building, UI flows, credential prompting) stays in Console. | ||
|
|
||
| ## Architecture | ||
|
|
||
| - `rust/src/api/protocol.rs` — the FRB facade. `ProtocolProfile` holds probed | ||
| device evidence; `ProtocolOperation` is an opaque state machine that Dart | ||
| drives APDU-by-APDU (`start()`/`advance(response)` → `ProtocolStep`). | ||
| Handles (`CtapPinSession`, `CtapPinToken`, profiles) are opaque and | ||
| close/dispose locally only. | ||
| - `lib/helper/utils/card_client.dart` — shared client infrastructure: | ||
| `CardClientBase` (transport/lease guards, `withSession`, cancellation), | ||
| `ProfileCardClient` (`prepareProfile`, `executePrepared`, | ||
| `lastStatusWord`), `ProfileBinding` (lease + generation evidence), and | ||
| `AdminSessionCardClient` (Admin progress and session evidence) in | ||
| `admin_card.dart`. Per-applet clients (`piv_card.dart`, `oath_card.dart`, | ||
| `openpgp_card.dart`, `ndef_card.dart`, `pass_card.dart`, | ||
| `webauthn_card.dart`) hold only domain logic. | ||
| - `lib/helper/utils/smartcard.dart` — the `SmartCard.process` queue; every | ||
| use case (connect, SELECT, authenticate, commands, cleanup) runs inside it. | ||
| Connection bootstrap identity commands use upstream `admin::command` | ||
| builders via `ProtocolOperation.bootstrapIdentity`. | ||
|
|
||
| ## Contracts that must not be broken | ||
|
|
||
| - A physical lease (`CardLease`) owns selection/profile/authentication | ||
| generations. A closed, failed or replaced lease cannot exchange or publish | ||
| results. One executor reserves its lease for a whole operation; raw | ||
| exchanges, other operations and rebinds cannot interleave continuation | ||
| APDUs. | ||
| - Profiles are immutable evidence, never authentication tokens. Controllers | ||
| `prepare()` explicitly before dependent reads; an explicit SELECT or a | ||
| profile-affecting write invalidates the evidence. Failed re-probing never | ||
| leaves an older profile usable. | ||
| - Mutations are never retried, resumed or rolled back by the host. A | ||
| cancelled or uncertain write is exposed as unconfirmed, not reported | ||
| successful. | ||
| - Credentials are explicit per request and enter zeroizing containers; Dart | ||
| wipes its mutable copies. Admin session evidence (`Access::Existing` | ||
| reuse) is recorded only by a successful facade `VerifyPin`, is stamped | ||
| with the lease generations, and is invalidated by cross-applet selection, | ||
| profile invalidation, uncertain writes, failed Existing requests or lease | ||
| replacement. UI PIN caches never produce authorization. | ||
| - Capability policy: firmware outside the audited matrix is | ||
| `CapabilityUnknown` and unsupported operations fail at construction, | ||
| before any I/O. Console never bypasses capability checks with invented | ||
| profiles or speculative wire formats. | ||
| - Errors keep kind, phase and status word as separate fields. On CTAP paths | ||
| `statusWord` carries the raw CTAP status byte (widened), not an ISO SW. | ||
| Transport errors propagate unchanged. | ||
|
|
||
| ## Applet notes | ||
|
|
||
| - **Admin** — full coverage (config, PIN, NFC, SM2, keymap, applet/factory | ||
| reset, Pass slots). Requests default to SELECT + per-request PIN and | ||
| converge to `Access::Existing` while session evidence is valid. | ||
| - **PIV** — full coverage, including PQ seed import, attestation and | ||
| streaming sign, all with `Access::Existing`. The algorithm-extension read | ||
| uses the upstream profile-based operation: on 3.0.x firmware it requires | ||
| management-key authentication (`Access::Management` authenticates and | ||
| reads in one operation); an unauthenticated read fails with | ||
| `SecurityStatusNotSatisfied` and the UI falls back to firmware defaults | ||
| only for that known gate. Certificate/object framing, gzip bounds and | ||
| continuation are owned upstream. | ||
| - **OATH** — full coverage; password-protected applets pass | ||
| `accessKey`/`accessChallenge` per operation. Set-default legacy dialects | ||
| are split by the upstream capability (`OathSetDefaultSlots`). | ||
| - **OpenPGP** — full coverage (data reads, PIN/reset-code/unblock, touch | ||
| policy/cache, retries, generate, terminate/activate). Optional data | ||
| objects map only `NotFound` to null. | ||
| - **NDEF** — profile-free read capability/message and crash-safe write. | ||
| The CC advertises the file ID; read-only and oversized writes fail at the | ||
| CC preflight (`SecurityStatusNotSatisfied` / `LimitExceeded`). | ||
| - **Pass** — Admin `PassSlots`/`SetPassSlot`. Both are protected; reads take | ||
| the lease's verified PIN unless session evidence applies. OATH-linked | ||
| slots are read-only here (configured via OATH); unknown slot types surface | ||
| as unknown. | ||
| - **WebAuthn/CTAP2** — upstream `ctap2`/`pin`/`credmgmt` client layer | ||
| (getInfo, ClientPin v1/v2, credential management) with canonical CBOR in | ||
| Rust. Tokens are ceremony-scoped and never cached; the ephemeral scalar | ||
| and IV are generated by the facade's CSPRNG. Non-zero CTAP status bytes | ||
| are data for the UI, not transport errors. | ||
|
|
||
| ## Verification | ||
|
|
||
| ```sh | ||
| flutter_rust_bridge_codegen generate # after facade signature changes | ||
| cargo test --manifest-path rust/Cargo.toml --locked | ||
| cargo build --manifest-path rust/Cargo.toml --release --locked | ||
| cargo check --manifest-path rust/Cargo.toml --target wasm32-unknown-unknown --locked | ||
| flutter test --no-pub | ||
| flutter test --no-pub --tags native # injected-transport transcripts | ||
| ``` | ||
|
|
||
| The FRB WASM package (`flutter_rust_bridge_codegen build-web --release | ||
| --wasm-pack-rustup-toolchain <nightly>`) and `flutter build web --no-pub` | ||
| must be rebuilt after facade changes; the wasm-bindgen crate family in | ||
| `rust/Cargo.toml` tracks the `wasm-bindgen-cli` version used by wasm-pack. | ||
| The USB/IP firmware matrix runs in CI (`.github/workflows/usbip.yml`) via | ||
| `test/usbip/console_smoke.dart`; physical-card and browser-transport | ||
| coverage is not exercised locally. | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
🔎 Supported by static analysis
🏁 Script executed:
Repository: canokeys/canokey-console
Length of output: 2858
🏁 Script executed:
Repository: canokeys/canokey-console
Length of output: 31134
Limit the claim to production application operations.
test/usbip/console_smoke.dartmanually constructs APDUs in_sendand_sendChained, then transmits them throughcard.transceive(...). The documentation does not define an application-only scope. Qualify the statement or document this USB/IP test exception.🤖 Prompt for AI Agents