Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
1787e82
ctap2.1: add authenticatorConfig (0x0D) command and authnrCfg option
0x0ece May 8, 2026
3a3197c
fixup! ctap2.1: add authenticatorConfig (0x0D) command and authnrCfg …
robin-nitrokey May 21, 2026
9aec0ca
fixup! ctap2.1: add authenticatorConfig (0x0D) command and authnrCfg …
robin-nitrokey May 22, 2026
21480a9
ctap2.1: implement setMinPINLength + minPinLength extension + forcePI…
0x0ece May 9, 2026
6b97e76
fixup! ctap2.1: implement setMinPINLength + minPinLength extension + …
robin-nitrokey May 22, 2026
ce55f3f
fixup! ctap2.1: implement setMinPINLength + minPinLength extension + …
robin-nitrokey May 22, 2026
c8ec2f3
fixup! ctap2.1: implement setMinPINLength + minPinLength extension + …
robin-nitrokey May 22, 2026
c5a6686
fixup! ctap2.1: implement setMinPINLength + minPinLength extension + …
robin-nitrokey May 22, 2026
78fce58
Update to ctap-types v0.6.0-rc.2
0x0ece May 24, 2026
a84d6a9
WIP: redirect ctap-types to 0x0ece git rev (drop before merge)
0x0ece May 24, 2026
ac2443e
ctap2.1: setMinPINLength (§6.11.4) + minPinLength extension at MakeCr…
0x0ece May 23, 2026
af4c155
tests: shrink TestMakeCredential / TestGetAssertion exhaustive matrices
0x0ece May 23, 2026
192f7c1
ctap2.1: alwaysUv + toggleAlwaysUv (CTAP 2.1 §6.4, §6.11.2, §7.2) — f…
0x0ece May 23, 2026
326ce04
ctap2.1: §6.5.5 PIN management — code-point validation + reject same-…
0x0ece May 23, 2026
4c369c7
ctap2.1: include user field in RK allowlist GetAssertion response
0x0ece May 11, 2026
74aa0fe
ctap2.2: implement hmac-secret-mc extension at MakeCredential time
0x0ece May 9, 2026
b6c0697
ctap2.3: advertise smart-card transport when CCID is enabled
0x0ece May 9, 2026
102f2fc
ctap2.3: getinfo advertises FIDO_2_3
0x0ece May 9, 2026
72e16b4
ctap2.3: long-touch reset (§6.11.5, §7.7) + Reset clears all §6.7 fea…
0x0ece May 23, 2026
c27d13e
ctap2: refactor make_credential / get_assertion to take &mut Response
0x0ece May 15, 2026
ce6c884
ctap2.3: implement ML-DSA-44 (COSE alg -50, behind mldsa44 feature)
0x0ece May 15, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## Unreleased

- Update to `ctap-types` v0.6.0-rc.1.
- Update to `ctap-types` v0.6.0-rc.2 (rename `authenticator_config` module to `config`).
- Set `algorithms`, `firmware_version` and `remaining_discoverable_credentials` in `get_info` and add `firmware_version` to `Config`.
- Implement the `credBlob` extension.
- Implement these new extensions:
- `credBlob`
- `minPinLength`
- Implement the `authenticatorConfig` command with these subcommands:
- `setMinPINLength`
- Load full credential from filesstem for getAssertion if an allow list is used with a discoverable credential.

## [v0.3.0](https://github.com/trussed-dev/fido-authenticator/releases/tag/v0.3.0) (2026-03-25)
Expand Down
28 changes: 25 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ description = "FIDO authenticator Trussed app"
apdu-app = { version = "0.2", optional = true }
cbor-smol = "0.5"
cosey = "0.4"
ctap-types = { version = "=0.6.0-rc.1", features = ["get-info-full", "large-blobs", "third-party-payment"] }
ctap-types = { version = "=0.6.0-rc.2", features = ["get-info-full", "large-blobs", "third-party-payment"] }
ctaphid-app = { version = "0.2", optional = true }
delog = "0.1"
heapless = "0.9"
Expand All @@ -37,6 +37,14 @@ disable-reset-time-window = []
# enables support for a large-blob array longer than 1024 bytes
chunked = ["dep:trussed-chunked"]

# enables ML-DSA-44 (FIPS 204, COSE alg -50). When off, the variant, the
# `pubKeyCredParams = -50` arm, the GetInfo algorithm entry, and the
# `Mldsa44` Trussed-core requirement are all elided. Off by default.
# Also bumps `MAX_PACKED_SIG_LENGTH`, x5c element size, and authData buffer
# in ctap-types so packed attestation can carry the 2420-byte ML-DSA-44 sig
# alongside the larger 1322-byte COSE_Key in authData.
mldsa44 = ["trussed-core/mldsa44", "ctap-types/mldsa44"]

log-all = []
log-none = []
log-trace = []
Expand All @@ -52,6 +60,14 @@ cbc = { version = "0.1.2", features = ["alloc"] }
ciborium = "0.2.2"
ciborium-io = "0.2.2"
cipher = "0.4.4"
# The lib's `mldsa44` feature normally turns on both `trussed-core/mldsa44`
# and `ctap-types/mldsa44` together; they keep `Message`'s inner Bytes size
# (1024 → 2048) and `x5c`'s inner Bytes size in lockstep. The dev-dep
# `trussed` below pulls in `trussed-core/mldsa44` unconditionally for the
# test runner, so we need `ctap-types/mldsa44` here too — otherwise the
# `x5c.push(cert)` sites in `ctap2.rs` see `Bytes<2048>` going into a
# `Bytes<1024>` slot and `cargo test` fails to compile.
ctap-types = { version = "=0.6.0-rc.2", features = ["mldsa44"] }
ctaphid = { version = "0.3.1", default-features = false }
ctaphid-dispatch = "0.4"
delog = { version = "0.1.6", features = ["std-log"] }
Expand All @@ -67,7 +83,7 @@ rand = "0.8.4"
rand_chacha = "0.3"
sha2 = "0.10"
serde_test = "1.0.176"
trussed = { git = "https://github.com/trussed-dev/trussed.git", rev = "0f8df68be879acdde1f8cf428c11e5d29692a47b", features = ["virt"] }
trussed = { git = "https://github.com/trussed-dev/trussed.git", rev = "0f8df68be879acdde1f8cf428c11e5d29692a47b", features = ["mldsa44", "virt"] }
trussed-staging = { git = "https://github.com/trussed-dev/trussed-staging.git", tag = "v0.4.0", features = ["chunked", "hkdf", "virt", "fs-info"] }
trussed-usbip = { git = "https://github.com/trussed-dev/pc-usbip-runner.git", rev = "017921df0930707c4af68882ccb1f8b3f1bbf7c5", default-features = false, features = ["ctaphid"] }
usbd-ctaphid = "0.4"
Expand All @@ -77,7 +93,13 @@ x509-parser = "0.16"
features = ["chunked", "dispatch"]

[patch.crates-io]
trussed = { git = "https://github.com/trussed-dev/trussed.git", rev = "0f8df68be879acdde1f8cf428c11e5d29692a47b" }
ctap-types = { git = "https://github.com/0x0ece/ctap-types.git", rev = "385e066ee63baeb8a43e78c737853141d0aaebc7" }
trussed = { git = "https://github.com/0x0ece/trussed.git", rev = "d657819388f9b3cefed1ccdcb741109f0a2588d6" }
trussed-core = { git = "https://github.com/0x0ece/trussed.git", rev = "d657819388f9b3cefed1ccdcb741109f0a2588d6" }

[patch."https://github.com/trussed-dev/trussed.git"]
trussed = { git = "https://github.com/0x0ece/trussed.git", rev = "d657819388f9b3cefed1ccdcb741109f0a2588d6" }
trussed-core = { git = "https://github.com/0x0ece/trussed.git", rev = "d657819388f9b3cefed1ccdcb741109f0a2588d6" }

[profile.test]
opt-level = 2
3 changes: 2 additions & 1 deletion fuzz/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ edition = "2021"
cargo-fuzz = true

[dependencies]
ctap-types = { version = "=0.6.0-rc.1", features = ["arbitrary"] }
ctap-types = { version = "=0.6.0-rc.2", features = ["arbitrary"] }
libfuzzer-sys = "0.4"
trussed = { version = "0.1", features = ["certificate-client", "crypto-client", "filesystem-client", "management-client", "aes256-cbc", "ed255", "p256", "sha256"] }
trussed-staging = { version = "0.4.0", features = ["chunked", "hkdf", "virt", "fs-info"] }
Expand All @@ -26,3 +26,4 @@ bench = false
[patch.crates-io]
trussed = { git = "https://github.com/trussed-dev/trussed.git", rev = "0f8df68be879acdde1f8cf428c11e5d29692a47b" }
trussed-staging = { git = "https://github.com/trussed-dev/trussed-staging.git", tag = "v0.4.0" }
ctap-types = { git = "https://github.com/0x0ece/ctap-types.git", rev = "385e066ee63baeb8a43e78c737853141d0aaebc7" }
10 changes: 8 additions & 2 deletions fuzz/fuzz_targets/ctap.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
#![no_main]

use ctap_types::{authenticator::Request, ctap1::Authenticator as _, ctap2::Authenticator as _};
use ctap_types::{
authenticator::Request,
ctap1::Authenticator as _,
ctap2::{Authenticator as _, Response},
};
use fido_authenticator::{Authenticator, Config, Conforming};
use trussed::virt::StoreConfig;
use trussed_staging::virt;
Expand All @@ -18,6 +22,7 @@ fuzz_target!(|requests: Vec<Request<'_>>| {
max_resident_credential_count: None,
large_blobs: None,
nfc_transport: false,
ccid_transport: false,
firmware_version: Some(0),
},
);
Expand All @@ -28,7 +33,8 @@ fuzz_target!(|requests: Vec<Request<'_>>| {
authenticator.call_ctap1(&request).ok();
}
Request::Ctap2(request) => {
authenticator.call_ctap2(&request).ok();
let mut response = Response::Reset;
authenticator.call_ctap2(&request, &mut response).ok();
}
}
}
Expand Down
20 changes: 20 additions & 0 deletions src/ctap1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,15 @@ impl<UP: UserPresence, T: TrussedRequirements> Authenticator for crate::Authenti
/// Also note that CTAP1 credentials should be assertable over CTAP2. I believe this is
/// currently not the case.
fn register(&mut self, reg: &register::Request) -> Result<register::Response> {
// CTAP 2.1 §7.2.4 step 2: when alwaysUv is enabled, U2F_REGISTER and
// U2F_AUTHENTICATE MUST immediately fail with SW_COMMAND_NOT_ALLOWED
// (0x6986). Our device has no built-in UV, so alwaysUv unconditionally
// disables CTAP1/U2F. The matching `getInfo` change (drop "U2F_V2"
// from `versions`) lives in `src/ctap2.rs`.
if self.state.persistent.always_uv() {
return Err(Error::CommandNotAllowedNoEf);
}

self.up
.user_present(&mut self.trussed, constants::U2F_UP_TIMEOUT)
.map_err(|_| Error::ConditionsOfUseNotSatisfied)?;
Expand Down Expand Up @@ -139,6 +148,12 @@ impl<UP: UserPresence, T: TrussedRequirements> Authenticator for crate::Authenti
}
};

// U2F register's `attestation_certificate` is fixed at `Bytes<1024>`.
// Real attestation certs comfortably fit; we lift it from the
// trussed `Message`-typed read so it works regardless of how the
// mldsa44 feature sizes that Message buffer.
let cert =
ctap_types::Bytes::<1024>::try_from(&*cert).map_err(|_| Error::NotEnoughMemory)?;
Ok(register::Response::new(
0x05,
&cose_key,
Expand All @@ -149,6 +164,11 @@ impl<UP: UserPresence, T: TrussedRequirements> Authenticator for crate::Authenti
}

fn authenticate(&mut self, auth: &authenticate::Request) -> Result<authenticate::Response> {
// CTAP 2.1 §7.2.4 step 2: see `register` above.
if self.state.persistent.always_uv() {
return Err(Error::CommandNotAllowedNoEf);
}

let cred = Credential::try_from_bytes(self, auth.app_id, auth.key_handle);

let user_presence_byte = match auth.control_byte {
Expand Down
Loading
Loading