From bcfd09ad94c72132f02407e3bb13bd5c275886bc Mon Sep 17 00:00:00 2001 From: Dennis Dyallo Date: Tue, 9 Jun 2026 14:50:40 +0200 Subject: [PATCH 01/77] docs: add composite device program plan --- docs/plans/composite-device/ISA.md | 233 ++++++++++++++++++ .../phase-33-composite-device-program-ISA.md | 115 +++++++++ ...e-33-composite-device-program-learnings.md | 99 ++++++++ 3 files changed, 447 insertions(+) create mode 100644 docs/plans/composite-device/ISA.md create mode 100644 docs/plans/composite-device/phase-33-composite-device-program-ISA.md create mode 100644 docs/plans/composite-device/phase-33-composite-device-program-learnings.md diff --git a/docs/plans/composite-device/ISA.md b/docs/plans/composite-device/ISA.md new file mode 100644 index 000000000..dd094d306 --- /dev/null +++ b/docs/plans/composite-device/ISA.md @@ -0,0 +1,233 @@ +# ISA: Composite YubiKey Device Model + +This ISA defines the v2 composite-device program. It begins after the module-consolidation quality gate passed in Phase 32 and after the owner approved starting composite-device planning on a dedicated branch. + +Read this together with: + +- `docs/SDK-HOUSE-STYLE.md` +- `docs/MODULE-CONSOLIDATION-FINAL-REASSESSMENT.md` +- `docs/plans/module-consolidation/phase-20-quality-convergence-before-composite-yubikey-ISA.md` +- `docs/plans/module-consolidation/phase-32-same-criteria-quality-reassessment-learnings.md` +- `../yubikey-manager` on branch `experiment/rust` + +## Problem + +The current Core `IYubiKey` abstraction is closer to a connection handle than a physical device. It exposes `DeviceId`, one concrete `ConnectionType`, and `ConnectAsync()`, but it does not expose the physical key's device information, firmware version, serial, form factor, capabilities, or the set of available interfaces. A normal USB YubiKey can appear as several independent SDK devices when CCID, FIDO HID, and OTP HID are visible. + +That model makes physical-device selection, cache events, capability-aware session defaults, and user-facing discovery semantics harder than they should be. The Rust `yubikey-manager` `experiment/rust` branch already models this better: one local device owns optional SmartCard, OTP HID, and FIDO HID paths plus read-only `DeviceInfo`. + +The .NET SDK needs the same conceptual move without mechanically porting Rust and without breaking the .NET package boundary where `Core` is below `Management` and all applet modules. + +## Vision + +`YubiKeyManager.FindAllAsync()` returns one logical object per physical YubiKey. That object tells callers what the key is, what firmware it runs, what capabilities and interfaces are visible, and which typed connections can be opened. Applet-specific `IYubiKeyExtensions` remain the ergonomic entry point for module sessions, but they can now make smart, documented defaults because the physical key carries real device facts. + +The result should feel obvious to a .NET v2 SDK user: select a physical key once, inspect it once, then open the app/session you need with either smart defaults or explicit transport control. + +## Out of Scope + +- No CLI command-family redesign in this program unless a later phase explicitly promotes a narrow CLI follow-up. +- No CLI command-family redesign in this program. Minimum compile/API migration for CLI consumers of moved Core metadata is in scope when required by the metadata move. +- No broad applet refactor beyond extension-method and test changes required by the new physical-device model. +- No dependency from `Core` to `Management`. +- No applet-module dependency on `Management` solely to inspect physical-device metadata. +- No mechanical Rust port. Rust is the reference behavior and edge-case inventory, not the target architecture. +- No unattended FIDO2/WebAuthn User Presence, UV, touch, or insert/remove ceremony tests. +- No destructive Management configuration tests in this program unless explicitly isolated and human-approved. + +## Principles + +- `IYubiKey` represents a physical key, not one interface handle. +- Read-only device identity belongs at the Core layer because discovery, filtering, applet extensions, tests, and callers need it before choosing a module session. +- Mutating device configuration belongs in `Management` because writes, lock codes, reset, reboot, and mode changes are management operations, not Core discovery facts. +- Raw connection APIs should be explicit; smart defaults belong in app/module extension methods where intent is known. +- Rust reference behavior should inform edge cases, especially same-PID merging, NFC separation, and discovery fallbacks, but the .NET shape must respect assembly boundaries and existing extension ergonomics. +- Each phase must compile, test, review, learn, and commit independently. + +## Constraints + +- Execute on branch `yubikit-composite-device-new`. +- Use `dotnet toolchain.cs` commands only; never raw `dotnet build` or raw `dotnet test`. +- Each phase has a phase ISA before source changes. +- Each implementation phase uses `/DevTeam` implementation/review/fix workflow or records an explicit docs-only review path. +- Cato review is required for Phase 33 planning, any broad API-boundary decision phase, and the final program verification phase. +- Commit only intended files after each phase; never use `git add .`, `git add -A`, or `git commit -a`. +- Public module extension ergonomics should be preserved unless a phase ISA records and justifies a breaking shape. +- Keep `ConnectionType` semantics source-backed and tested when moving from per-interface devices to capability/filter semantics. +- Phase 34 must decide and document the public namespace/API migration strategy for `DeviceInfo` and supporting types before moving them. Supporting types include at least `FormFactor`, `DeviceCapabilities`, `DeviceFlags`, `VersionQualifier`, and `VersionQualifierType`. +- Phase 34 must migrate mandatory compile consumers in the same phase as metadata promotion. This includes `Management`, `Tests.Shared`, and CLI consumers that read `DeviceInfo` today. Optional downstream capabilities remain deferred. +- Phase 36 must decide the disposition of scalar `IYubiKey.ConnectionType`: remove, obsolete, repurpose as a primary-transport hint, or replace with an available-connections property. The decision must be explicit before source edits. + +## Goal + +Build the v2 composite YubiKey device model in staged, reviewable phases so Core discovery returns physical YubiKeys with read-only device metadata and available connection flags, applet extension methods preserve ergonomic smart defaults with explicit override paths, Management retains mutating configuration ownership, and the final SDK behavior is verified with unit tests, safe hardware smoke tests, docs, DevTeam review, Cato review, and per-phase learning notes. + +## Criteria + +### Program Governance + +- [ ] ISC-1: Branch check shows `## yubikit-composite-device-new` before any composite-device edits, build/test commands, review delegation, or commit. +- [ ] ISC-2: `../yubikey-manager` reference branch is recorded as `experiment/rust` before design decisions cite Rust behavior. +- [ ] ISC-3: Every phase has a phase ISA before source changes begin. +- [ ] ISC-4: Every implementation phase records `/DevTeam` review, fixes findings, and records review output or waiver before commit. +- [ ] ISC-5: Cato review is completed for Phase 33 planning, broad API-boundary phases, and final program verification. +- [ ] ISC-6: Every phase writes a learning note and commits only intended files before the next phase begins. +- [ ] ISC-7: Anti: source changes occur on any branch other than `yubikit-composite-device-new`. + +### Architecture + +- [ ] ISC-8: `IYubiKey` represents one physical YubiKey rather than one concrete interface handle. +- [ ] ISC-9: Core owns read-only physical-device metadata needed by `IYubiKey`, including firmware version, serial, form factor, capabilities, and version qualifier facts. +- [ ] ISC-9.1: Phase 34 records the public namespace/API migration strategy for `DeviceInfo`, `FormFactor`, `DeviceCapabilities`, `DeviceFlags`, `VersionQualifier`, and `VersionQualifierType` before moving or splitting types. +- [ ] ISC-9.2: Phase 34 verification includes an API-surface or source-compatibility check appropriate for the approved v2 break policy, so unintended namespace/API fallout is visible before commit. +- [ ] ISC-10: Management owns mutating device configuration, reset, lock, reboot, and mode behavior. +- [ ] ISC-11: Core does not reference `Yubico.YubiKit.Management`. +- [ ] ISC-11.1: Phase 35 verifies any shared read-info logic still preserves the Core-to-Management dependency direction and does not introduce direct, indirect, or helper-mediated Core reliance on Management. +- [ ] ISC-12: Applet modules do not reference `Yubico.YubiKit.Management` solely for physical-device metadata. +- [ ] ISC-13: `IYubiKey` exposes available connection flags and a `SupportsConnection(...)`-style predicate or equivalent. +- [ ] ISC-13.1: Phase 36 records the disposition of the existing scalar `IYubiKey.ConnectionType` property and updates all production call sites that assume one interface per `IYubiKey`. +- [ ] ISC-13.2: Phase 36 verification includes a grep or API check proving no production code still relies on scalar `yubiKey.ConnectionType` for composite-device routing unless that usage is explicitly approved. +- [ ] ISC-14: Typed `ConnectAsync()` routes to the requested concrete interface when available and fails clearly when unsupported. +- [ ] ISC-15: Raw untyped/default connection APIs do not silently choose surprising transports for composite devices. +- [ ] ISC-15.1: Phase 36 or Phase 38 includes focused tests proving raw/default connection behavior is either explicit, unsupported, or documented as an app-specific smart default. + +### Discovery And Identity + +- [ ] ISC-16: `YubiKeyManager.FindAllAsync(ConnectionType.All)` returns one logical device per physical USB YubiKey when CCID, FIDO HID, and OTP HID are all visible. +- [ ] ISC-17: `ConnectionType` filters return devices capable of the requested connection, not duplicate per-interface rows. +- [ ] ISC-18: NFC PC/SC devices are never merged with USB HID or USB CCID devices. +- [ ] ISC-19: Multiple same-PID USB keys are not collapsed unless identity evidence is strong enough. +- [ ] ISC-20: Repository add/remove events are keyed by physical-device identity rather than per-interface identity. + +### Extension Ergonomics + +- [ ] ISC-21: Existing applet `IYubiKeyExtensions` remain the primary ergonomic session-entry surface. +- [ ] ISC-21.1: Phase 38 explicitly rewrites extension-method transport selection away from scalar `IYubiKey.ConnectionType` assumptions, with FIDO2 called out because it currently switches on `yubiKey.ConnectionType`. +- [ ] ISC-22: Smart defaults are app-specific and documented: SmartCard applets prefer SmartCard, FIDO2/WebAuthn prefer HID FIDO when available, Management can prefer SmartCard then FIDO HID then OTP HID. +- [ ] ISC-23: Explicit connection preference or override is available where a module can reasonably use more than one transport. +- [ ] ISC-24: Existing extension-method unit tests are updated to verify both default selection and explicit override behavior. + +### Tests And Verification + +- [ ] ISC-25: Unit tests cover metadata promotion and parsing after read-only types move into Core. +- [ ] ISC-25.1: Phase 34 includes mandatory compile migration for `Tests.Shared` and CLI consumers of moved metadata; optional richer test filtering and smarter CLI selection stay deferred. +- [ ] ISC-26: Unit tests cover Core device-info read behavior over fake SmartCard/FIDO/OTP paths where feasible. +- [ ] ISC-27: Unit tests cover single-PID merge, same-PID conservative no-collapse behavior, NFC no-merge behavior, filter semantics, and repository event diffs. +- [ ] ISC-28: Safe integration smoke verifies physical-device discovery and typed connection opening on allowed hardware without UP/UV/touch ceremony requirements. +- [ ] ISC-29: Active docs explain physical-device semantics, read-only metadata ownership, smart defaults, and migration from per-interface handles. +- [ ] ISC-30: Anti: final verification claims composite readiness without docs QA, focused tests, safe hardware smoke or skip rationale, DevTeam review, and Cato review. + +### Deferred Improvements + +- [ ] ISC-31: A deferred downstream capability audit is recorded for opportunities unlocked by promoting `DeviceInfo` to Core. +- [ ] ISC-31.1: Mandatory consumer migration is not classified as deferred downstream capability work. +- [ ] ISC-32: Anti: downstream capability opportunities are implemented during metadata promotion before the physical-device model is stable. + +## Test Strategy + +| ISC | Type | Check | Threshold | Tool | +| --- | --- | --- | --- | --- | +| ISC-1 | branch | Verify active branch | `## yubikit-composite-device-new` | `git status --short --branch` | +| ISC-2 | branch | Verify sibling repo reference branch | `## experiment/rust` | `git -C ../yubikey-manager status --short --branch` | +| ISC-3 to ISC-6 | governance | Phase artifacts and review records exist | phase ISA, learning note, review evidence present | Read/Grep/Cato output | +| ISC-8 to ISC-15 | API shape | Inspect Core source and public API tests | metadata in Core, no Core->Management ref, typed connect tested | Grep/Read/tests | +| ISC-11.1 | dependency | Verify shared read-info implementation does not couple Core to Management | no Core production reference to Management and no shared helper owned by Management used by Core | Grep/project refs | +| ISC-9.1 to ISC-9.2 | API migration | Verify namespace/API migration strategy and API-surface check | approved break policy recorded and checked | Read/Grep/API check | +| ISC-13.1 to ISC-13.2 | API migration | Verify scalar `IYubiKey.ConnectionType` disposition and call-site migration | no unapproved scalar routing remains | Grep/API tests | +| ISC-15.1 | connection defaults | Verify raw/default behavior | explicit/unsupported/default behavior tested | Core/app extension unit tests | +| ISC-16 to ISC-20 | discovery semantics | Unit tests over fake device inventories | one physical device, correct filters/events | `dotnet toolchain.cs -- test --project Core --filter ...` | +| ISC-21 to ISC-24 | extension behavior | Applet extension unit tests | defaults and overrides pass | focused module tests | +| ISC-25 to ISC-27 | unit coverage | Core/Management/Tests.Shared/CLI compile migration and tests | focused tests pass, consumers compile | `dotnet toolchain.cs -- test --project ... --filter ...` | +| ISC-28 | integration | Safe hardware smoke or recorded skip | pass or explicit skip rationale | `dotnet toolchain.cs -- test --integration --project Core --smoke --filter ...` | +| ISC-29 | docs | Active documentation validates | exit 0 | `dotnet toolchain.cs -- docs-qa` | +| ISC-30 | final review | Cato final audit | pass or resolved concerns | Cato output JSONL | +| ISC-31 to ISC-32 | deferred scope | Deferred item recorded and not implemented early | note present, no premature source scope | Read/Git diff | + +## Features + +| Name | Description | Satisfies | Depends On | Parallelizable | +| --- | --- | --- | --- | --- | +| Phase 33 program planning | Create branch, write this ISA, write Phase 33 ISA/learnings, map Rust reference, run Cato, commit docs only. | ISC-1, ISC-2, ISC-3, ISC-5, ISC-31, ISC-32 | Phase 32 gate | false | +| Phase 34 metadata promotion | Move or split read-only device metadata types from Management into Core while preserving Management behavior and migrating mandatory Tests.Shared/CLI consumers. | ISC-8, ISC-9, ISC-9.1, ISC-9.2, ISC-10, ISC-11, ISC-12, ISC-25, ISC-25.1, ISC-31.1 | Phase 33 | false | +| Phase 35 Core device-info reader | Add Core-owned read-info paths used by discovery without Management dependency and verify any shared read-info logic preserves dependency direction. | ISC-9, ISC-10, ISC-11, ISC-11.1, ISC-26 | Phase 34 | false | +| Phase 36 physical device model | Implement physical `IYubiKey` shape with metadata, available connections, support checks, typed connection routing, explicit scalar `IYubiKey.ConnectionType` disposition, and raw/default connection behavior tests. | ISC-8, ISC-13, ISC-13.1, ISC-13.2, ISC-14, ISC-15, ISC-15.1 | Phase 35 | false | +| Phase 37 composite discovery | Merge partial PC/SC, OTP HID, and FIDO HID discoveries into physical devices with correct filtering and events. | ISC-16, ISC-17, ISC-18, ISC-19, ISC-20, ISC-27 | Phase 36 | false | +| Phase 38 extension defaults | Preserve app-specific extension ergonomics, remove scalar-connection assumptions, and add smart defaults plus explicit overrides where needed. | ISC-21, ISC-21.1, ISC-22, ISC-23, ISC-24 | Phase 37 | true | +| Phase 39 integration and final verification | Update docs, run safe hardware smoke, final tests, final Cato, and final learning note. | ISC-28, ISC-29, ISC-30 | Phase 38 | false | + +## Decisions + +- 2026-06-09: Composite-device work runs on dedicated branch `yubikit-composite-device-new` branched from the completed module-consolidation quality gate. +- 2026-06-09: `IYubiKey` should represent a physical device in v2, not a single interface handle. +- 2026-06-09: Rust `../yubikey-manager` branch `experiment/rust` is the reference implementation for composite-device discovery behavior. +- 2026-06-09: Rust is a same-crate design; .NET must adapt the concept without introducing a `Core` -> `Management` dependency cycle. +- 2026-06-09: Read-only device metadata needed by physical discovery belongs in Core; mutating configuration and management-session behavior remain in Management. +- 2026-06-09: Existing applet `IYubiKeyExtensions` are valued and must be preserved as the ergonomic app/session entry points. +- 2026-06-09: Raw connection selection should stay explicit, while applet extensions may provide smart defaults because they know the application intent. +- 2026-06-09: Implementation phases use `/DevTeam` review/fix/commit workflow; Phase 33 uses docs-only Cato review before commit. +- 2026-06-09: Deferred downstream audit is required because promoting `DeviceInfo` to Core may unlock better capability-aware APIs, extension defaults, test filtering, CLI selection, docs examples, and future feature gates. +- 2026-06-09: Cato identified that `DeviceInfo` promotion carries namespace/API fallout because supporting public types currently live in `Yubico.YubiKit.Management`; Phase 34 must choose and verify the migration strategy explicitly. +- 2026-06-09: Cato identified that scalar `IYubiKey.ConnectionType` is not merely additive debt; it is a core breaking-change decision for the physical-device model and current extension methods. +- 2026-06-09: Cato identified that `Tests.Shared` and CLI consumers of `DeviceInfo` require mandatory compile migration when metadata moves; optional richer behavior remains deferred. +- 2026-06-09: Cato follow-up passed and surfaced two info-level tightenings: give raw/default connection behavior explicit test ownership and verify Phase 35 read-info sharing does not reintroduce Core-to-Management coupling. + +## Changelog + +- conjectured: Composite-device implementation could begin immediately after Phase 32 because the quality gate passed. + refuted by: Owner discussion surfaced unresolved API ownership questions around `IYubiKey`, `DeviceInfo`, Management/Core boundaries, smart defaults, and extension-method preservation. + learned: The program needs a dedicated composite-device ISA and staged implementation phases before source changes. + criterion now: ISC-3, ISC-5, and the Phase 33 feature require design artifacts and Cato review before implementation. +- conjectured: Applet modules could depend on Management to access `DeviceInfo`. + refuted by: `IYubiKey` lives in Core, and applet dependencies on Management would not solve Core-facing physical-device metadata without creating awkward package coupling. + learned: Read-only metadata required by physical discovery belongs in Core; Management should keep mutating operations. + criterion now: ISC-9, ISC-10, ISC-11, and ISC-12 govern the package boundary. + +## Verification + +Verification is populated by each phase learning note. This master ISA is not complete until Phase 39 records final docs QA, focused build/test evidence, safe integration smoke or skip rationale, DevTeam review, Cato final audit, and commit evidence. + +## Phase Order + +### Phase 33: Composite Device Program ISA + +Create/switch to `yubikit-composite-device-new`, write the master ISA and Phase 33 artifacts, record Rust reference branch evidence, run Cato against the plan, verify docs, commit docs only, and stop for the owner's next command before source implementation. + +### Phase 34: Promote Read-Only Device Metadata To Core + +Move or split read-only physical-device metadata from Management into Core, including `DeviceInfo` and supporting value types. Before source edits, decide the public namespace/API migration strategy for `DeviceInfo`, `FormFactor`, `DeviceCapabilities`, `DeviceFlags`, `VersionQualifier`, and `VersionQualifierType`. Preserve Management behavior and update mandatory consumers including `Management`, `Tests.Shared`, and CLI compile consumers. Do not implement optional downstream capability opportunities during this phase. + +### Phase 35: Core Device Info Reader + +Add Core-owned read-only device-info discovery over SmartCard, FIDO HID, and OTP HID paths without depending on Management. Preserve ManagementSession behavior by sharing or delegating read-info logic where practical, but verify the final dependency direction explicitly: Core must not reference Management, and Core must not consume a helper owned by Management. + +### Phase 36: Physical YubiKey Model + +Introduce the physical-device `IYubiKey` shape with `DeviceInfo`, `FirmwareVersion`, available connection flags, support predicates, and typed connection routing. This phase must decide what happens to the existing scalar `IYubiKey.ConnectionType` property and update all production routing assumptions that depended on one interface per device. It must also bind raw/default connection behavior to tests: either the behavior is explicit, unsupported for composite devices, or documented as an app-specific smart default owned by an extension method. + +### Phase 37: Composite Discovery And Repository Semantics + +Implement merge behavior so CCID, FIDO HID, and OTP HID interfaces for one physical USB key become one SDK device. Update filtering, repository cache keys, and add/remove events. + +### Phase 38: Extension Method Smart Defaults + +Update applet extension methods to preserve current ergonomics while using physical-device facts for app-specific smart defaults and explicit connection overrides. This phase must explicitly remove scalar `IYubiKey.ConnectionType` routing assumptions from current extension implementations, including FIDO2. + +### Phase 39: Integration, Docs, Migration, Final Cato + +Run safe integration smoke, update docs and migration notes, run final focused builds/tests/docs QA, run Cato final audit, commit, and record remaining deferred follow-ups. + +## Mandatory Consumer Migration Versus Deferred Capability Audit + +The metadata move itself has mandatory consumer migration work. `Management`, `Tests.Shared`, and CLI code that currently compile against `Management.DeviceInfo` or supporting Management metadata types must be migrated in the phase that moves those types. This is not deferred work and is not a CLI redesign. + +## Deferred Follow-Up: DeviceInfo Promotion Downstream Capability Audit + +Promoting read-only `DeviceInfo` to Core may unlock downstream capabilities that are valuable but not part of metadata promotion itself: + +- capability-aware extension defaults beyond the minimum needed for composite-device correctness +- richer `Tests.Shared` filtering and state objects beyond the mandatory compile migration +- smarter CLI selection and display beyond the mandatory compile migration once the library surface is stable +- simpler docs examples that no longer need a Management session just to identify a key +- future feature-gating APIs that combine firmware, transport, and capability facts + +These opportunities are intentionally deferred until after the physical-device model is implemented and verified. The later audit should inventory the new Core metadata surface and decide which downstream features deserve their own focused phase. diff --git a/docs/plans/composite-device/phase-33-composite-device-program-ISA.md b/docs/plans/composite-device/phase-33-composite-device-program-ISA.md new file mode 100644 index 000000000..fd73edfc6 --- /dev/null +++ b/docs/plans/composite-device/phase-33-composite-device-program-ISA.md @@ -0,0 +1,115 @@ +# Phase 33 ISA: Composite Device Program Planning + +This phase starts the composite-device program. It writes the plan artifacts and verifies them before any source implementation begins. + +Read this together with: + +- `docs/plans/composite-device/ISA.md` +- `docs/plans/module-consolidation/phase-20-quality-convergence-before-composite-yubikey-ISA.md` +- `docs/plans/module-consolidation/phase-32-same-criteria-quality-reassessment-learnings.md` +- `docs/MODULE-CONSOLIDATION-FINAL-REASSESSMENT.md` +- `../yubikey-manager` on branch `experiment/rust` + +## Problem + +The module-consolidation program stopped correctly before composite YubiKey design. Owner discussion now clarified the intended v2 direction: `IYubiKey` should represent a physical device with firmware, read-only device info, available connection types, and app-specific smart defaults through extension methods. + +That design has enough package-boundary and API consequences that implementation must not start as a single broad source change. The work needs a new branch, a new long-lived ISA, a phase sequence, Cato review, and explicit stop after planning. + +## Vision + +Phase 33 leaves the repository ready for implementation but does not implement. The branch exists, the composite-device program ISA defines professional .NET phases, the Rust reference branch is recorded, downstream deferred opportunities are captured, and Cato has reviewed the plan before source files change. + +## Out of Scope + +- No source-code changes in Phase 33. +- No `DeviceInfo` movement in Phase 33. +- No `IYubiKey` API changes in Phase 33. +- No Core, Management, applet, CLI, or test project edits except plan documentation. +- No implementation delegation to `/DevTeam` in Phase 33; later source phases use `/DevTeam`. + +## Principles + +- Planning artifacts are part of the product because they bind future implementation phases. +- Phase 33 should be docs-only, reviewable, and independently commit-ready. +- The plan must explicitly separate package-boundary work, discovery work, physical model work, extension ergonomics, integration, and deferred downstream opportunity mining. +- Cato should audit the plan before implementation to catch missed architecture, test, and workflow concerns. + +## Constraints + +- Execute on branch `yubikit-composite-device-new`. +- Use `dotnet toolchain.cs -- docs-qa` for active docs verification. +- Use `git diff --check` for whitespace verification. +- Resolve Cato through `AgentHarnessRouter.ts`; OpenCode/OpenAI primary must route to Vertex Opus 4.8. +- Commit only `docs/plans/composite-device/ISA.md`, this file, and the Phase 33 learning note. +- Stop after the Phase 33 commit and wait for the owner's command before Phase 34. + +## Goal + +Create and verify the composite-device program planning artifacts on `yubikit-composite-device-new`, including the master ISA, Phase 33 ISA, Phase 33 learning note, Rust reference evidence, deferred downstream capability audit item, Cato review, docs QA, whitespace verification, and a docs-only commit; then stop before source implementation. + +## Criteria + +- [ ] ISC-1: Branch check shows `## yubikit-composite-device-new` before artifact edits. +- [ ] ISC-2: `docs/plans/composite-device/ISA.md` exists and defines the composite-device program. +- [ ] ISC-3: This Phase 33 ISA exists and declares docs-only scope. +- [ ] ISC-4: Phase 33 learning note exists at `docs/plans/composite-device/phase-33-composite-device-program-learnings.md`. +- [ ] ISC-5: The master ISA records `../yubikey-manager` branch `experiment/rust` as the Rust reference. +- [ ] ISC-6: The master ISA records that `IYubiKey` should represent a physical device. +- [ ] ISC-7: The master ISA records that read-only physical-device metadata belongs in Core and mutating configuration remains in Management. +- [ ] ISC-8: The master ISA records that applet `IYubiKeyExtensions` should remain the ergonomic session-entry surface. +- [ ] ISC-9: The master ISA splits implementation into logical phases instead of one broad implementation phase. +- [ ] ISC-10: The master ISA requires `/DevTeam` review/fix/commit workflow for source phases. +- [ ] ISC-11: The master ISA includes a deferred plan item for `DeviceInfo` promotion downstream capability audit. +- [ ] ISC-12: Phase 33 Cato route resolves to `google-vertex-anthropic/claude-opus-4-8@default` or an explicit structured routing failure is recorded. +- [ ] ISC-13: Phase 33 Cato audit returns pass or all findings are resolved before commit. +- [ ] ISC-13.1: Cato findings on `DeviceInfo` namespace/API migration fallout are reflected in the master ISA before commit. +- [ ] ISC-13.2: Cato findings on scalar `IYubiKey.ConnectionType` disposition are reflected in the master ISA before commit. +- [ ] ISC-13.3: Cato findings on mandatory `Tests.Shared` and CLI consumer migration are reflected in the master ISA before commit. +- [ ] ISC-14: `dotnet toolchain.cs -- docs-qa` passes after artifact edits. +- [ ] ISC-15: `git diff --check` passes after artifact edits. +- [ ] ISC-16: `git diff --name-only` shows only Phase 33 docs before commit. +- [ ] ISC-17: Anti: Phase 33 changes files under `src/`. +- [ ] ISC-18: Anti: Phase 33 starts Phase 34 implementation. + +## Test Strategy + +| ISC | Type | Check | Threshold | Tool | +| --- | --- | --- | --- | --- | +| ISC-1 | branch | Verify active branch | `## yubikit-composite-device-new` | `git status --short --branch` | +| ISC-2 to ISC-4 | file | Verify artifacts exist | title present | Read/Grep | +| ISC-5 to ISC-11 | content | Verify design and workflow decisions | required wording present | Read/Grep | +| ISC-12 | review routing | Resolve Cato route | Vertex Opus 4.8 selected | `AgentHarnessRouter.ts --dry-run --json` | +| ISC-13 to ISC-13.3 | review | Run Cato audit and resolve findings | pass or all concerns reflected in plan | Cato output JSONL + Read | +| ISC-14 | docs | Active docs validate | exit 0 | `dotnet toolchain.cs -- docs-qa` | +| ISC-15 | whitespace | Diff has no whitespace errors | exit 0 | `git diff --check` | +| ISC-16 to ISC-18 | git scope | Diff contains only intended docs | no `src/` paths | `git diff --name-only` | + +## Features + +| Name | Description | Satisfies | Depends On | Parallelizable | +| --- | --- | --- | --- | --- | +| Branch setup | Create/switch to `yubikit-composite-device-new` from current consolidation HEAD. | ISC-1 | Phase 32 commit | false | +| Master ISA | Write `docs/plans/composite-device/ISA.md` with goals, criteria, test strategy, phases, decisions, and deferred item. | ISC-2, ISC-5, ISC-6, ISC-7, ISC-8, ISC-9, ISC-10, ISC-11 | Branch setup | false | +| Phase 33 artifacts | Write this ISA and the Phase 33 learning note. | ISC-3, ISC-4, ISC-17, ISC-18 | Master ISA | false | +| Cato review | Route and run cross-vendor Cato review of the plan. | ISC-12, ISC-13 | artifacts complete | false | +| Verification and commit | Run docs QA, whitespace, scope check, stage intended files, and commit. | ISC-14, ISC-15, ISC-16 | Cato resolved | false | + +## Decisions + +- 2026-06-09: Phase 33 is docs-only and exists to prevent broad unreviewed source churn. +- 2026-06-09: Later source phases will use `/DevTeam` review/fix/commit workflow, but Phase 33 uses Cato docs-plan review rather than implementation review. +- 2026-06-09: The composite-device program receives a new plan directory, `docs/plans/composite-device/`, instead of extending module-consolidation phase numbering in the old directory. +- 2026-06-09: The program records a deferred downstream audit because Core `DeviceInfo` may unlock useful capabilities beyond the minimum physical-device model. +- 2026-06-09: Initial Cato review returned concerns on namespace/API migration, scalar `IYubiKey.ConnectionType`, extension-method assumptions, and mandatory consumer migration; these concerns must be reflected before commit. + +## Changelog + +- conjectured: The next action after approval should be implementing `IYubiKey` changes. + refuted by: The owner asked to split the effort into logical professional phases and begin with plan artifacts. + learned: Source implementation must wait behind a composite-device program ISA, Phase 33 review, and a commit. + criterion now: ISC-2 through ISC-18 define docs-only Phase 33 completion. + +## Verification + +Verification evidence is recorded in the Phase 33 learning note before commit. diff --git a/docs/plans/composite-device/phase-33-composite-device-program-learnings.md b/docs/plans/composite-device/phase-33-composite-device-program-learnings.md new file mode 100644 index 000000000..965f17510 --- /dev/null +++ b/docs/plans/composite-device/phase-33-composite-device-program-learnings.md @@ -0,0 +1,99 @@ +# Phase 33 Learnings: Composite Device Program Planning + +Use this note as the handoff record for Phase 33 of the composite-device program. + +## Phase Summary + +- Branch: `yubikit-composite-device-new`. +- Scope: write and verify composite-device planning artifacts only. +- Master ISA: `docs/plans/composite-device/ISA.md`. +- Phase ISA: `docs/plans/composite-device/phase-33-composite-device-program-ISA.md`. +- Source changed: none intended. +- Next phase after owner command: Phase 34, promote read-only device metadata to Core. + +## Evidence Inputs + +- Module-consolidation final reassessment: `docs/MODULE-CONSOLIDATION-FINAL-REASSESSMENT.md`. +- Phase 20 stop-gate ISA: `docs/plans/module-consolidation/phase-20-quality-convergence-before-composite-yubikey-ISA.md`. +- Phase 32 learning note: `docs/plans/module-consolidation/phase-32-same-criteria-quality-reassessment-learnings.md`. +- Core current discovery source: `src/Core/src/YubiKey/`. +- Current Management `DeviceInfo` source: `src/Management/src/DeviceInfo.cs`. +- Rust reference repo: `../yubikey-manager`. +- Rust reference branch: `experiment/rust`. +- Rust reference files: `crates/yubikit/src/platform/device.rs`, `crates/yubikit/src/device.rs`, `crates/yubikit/src/management.rs`, `crates/yubikit/src/core.rs`. + +## What Changed + +- Added `docs/plans/composite-device/ISA.md` as the long-lived composite-device program artifact. +- Added `docs/plans/composite-device/phase-33-composite-device-program-ISA.md` as the docs-only planning phase ISA. +- Added this learning note to capture review, verification, and next-phase inputs. +- Recorded that `IYubiKey` should represent a physical device, not a per-interface handle. +- Recorded that read-only physical-device metadata belongs in Core while mutating configuration remains in Management. +- Recorded that applet `IYubiKeyExtensions` should remain the ergonomic session-entry surface. +- Recorded a deferred downstream capability audit unlocked by Core `DeviceInfo` promotion. + +## Review Evidence + +- Cato route command: `bun ~/.claude/PAI/TOOLS/AgentHarnessRouter.ts --surface cato --role auditor --primary-model "openai/gpt-5.5" --cwd "$(pwd)" --dry-run --json`. +- Cato route result: `google-vertex-anthropic/claude-opus-4-8@default` selected for OpenAI primary. +- Initial Cato audit output: `/tmp/opencode/phase33-cato-audit.jsonl`. +- Initial Cato verdict: `concerns`, high criticality. +- Initial Cato findings addressed in the plan: + - Added explicit Phase 34 namespace/API migration governance for `DeviceInfo`, `FormFactor`, `DeviceCapabilities`, `DeviceFlags`, `VersionQualifier`, and `VersionQualifierType`. + - Added explicit Phase 36 scalar `IYubiKey.ConnectionType` disposition and call-site migration requirements. + - Added explicit Phase 38 requirement to remove scalar-connection routing assumptions from extension methods, including FIDO2. + - Added mandatory `Tests.Shared` and CLI compile consumer migration in the same phase as metadata promotion. + - Split mandatory consumer migration from the deferred downstream capability audit. +- Focused Cato follow-up output: `/tmp/opencode/phase33-cato-followup.jsonl`. +- Focused Cato follow-up verdict: `pass` with info-only notes. +- Focused Cato info notes addressed in the plan: + - Added explicit test ownership for raw/default connection behavior. + - Added explicit Phase 35 verification that shared read-info logic does not reintroduce Core-to-Management coupling. + +## Verification Evidence + +- Branch check command: `git status --short --branch`. +- Branch check result before artifact edits: `## yubikit-composite-device-new`. +- Rust reference branch command: `git -C ../yubikey-manager status --short --branch`. +- Rust reference branch result from planning context: `## experiment/rust...origin/experiment/rust`. +- Initial docs QA command: `dotnet toolchain.cs -- docs-qa`. +- Initial docs QA result: passed; 54 active documentation files validated. +- Initial whitespace command: `git diff --check`. +- Initial whitespace result: passed with no output. +- Final docs QA command: `dotnet toolchain.cs -- docs-qa`. +- Final docs QA result: passed; 54 active documentation files validated. +- Final whitespace command: `git diff --check`. +- Final whitespace result: passed with no output. +- Final scope command: `git status --short --branch`. +- Final scope result before staging: `## yubikit-composite-device-new` with only `?? docs/plans/composite-device/`. + +## What Did Not Work + +- Initial plan under-specified namespace/API fallout from promoting `DeviceInfo` and supporting public Management types into Core. +- Initial plan did not explicitly govern the fate of scalar `IYubiKey.ConnectionType`, even though current `IYubiKey.ConnectAsync()` and FIDO2 extensions depend on the single-interface model. +- Initial deferred downstream item mixed mandatory compile migration for `Tests.Shared` and CLI consumers with optional future capability improvements. + +## Reusable Patterns + +- Separate composite-device work into package-boundary, read-info, physical-model, discovery, extension-default, and final-integration phases. +- Keep smart connection defaults in applet extension methods where application intent is known. +- Use Rust as behavior reference while preserving .NET package boundaries. + +## Deferred Candidates + +- DeviceInfo Promotion Downstream Capability Audit: after the physical-device model is implemented and verified, inventory capability-aware defaults, richer test filtering, smarter CLI selection, simpler docs examples, and future feature gates unlocked by moving read-only device metadata into Core. + +## Next Phase Inputs + +- Phase 34 should move or split read-only `DeviceInfo` and supporting metadata types from Management to Core. +- Phase 34 must not implement downstream capabilities unlocked by the move; it records those for the later deferred audit. +- Phase 34 must preserve Management's mutating configuration/session ownership. +- Phase 34 should use `/DevTeam` implementation/review/fix workflow. +- Phase 34 should run focused Core and Management build/tests before commit. + +## Compact Summary + +- Goal: plan composite-device program. +- Branch: `yubikit-composite-device-new`. +- Scope: docs only. +- Stop: wait for owner before Phase 34. From 13486277ff4cb0174797617894647147ba67cde9 Mon Sep 17 00:00:00 2001 From: Dennis Dyallo Date: Tue, 9 Jun 2026 19:50:28 +0200 Subject: [PATCH 02/77] refactor(core): promote read-only device metadata from management Move DeviceInfo, DeviceCapabilities, DeviceFlags, FormFactor, and VersionQualifier/VersionQualifierType from Yubico.YubiKit.Management to Yubico.YubiKit.Core.YubiKey, along with the internal CapabilityMapper parser support. Management retains device configuration, reset, and session ownership and now returns the Core DeviceInfo type. Migrate mandatory compile consumers (Management, Tests.Shared, CLI shared/commands, PIV and Management examples) to the Core namespace, relocate CapabilityMapperTests into Core, and add Core DeviceInfoTests covering required TLV parsing, version qualifier behavior, and malformed input handling. Phase 34 of the composite-device program (ISC-8, ISC-9, ISC-9.1, ISC-9.2, ISC-10, ISC-11, ISC-12, ISC-25, ISC-25.1, ISC-31.1). --- .../phase-34-metadata-promotion-ISA.md | 148 ++++++++++++++++++ .../phase-34-metadata-promotion-learnings.md | 90 +++++++++++ .../src/Infrastructure/YkDeviceContext.cs | 3 +- src/Cli.Shared/src/Device/DeviceSelection.cs | 1 - .../src/Device/FormFactorFormatter.cs | 2 +- src/Core/CLAUDE.md | 2 +- src/Core/README.md | 1 + .../src/YubiKey}/DeviceCapabilities.cs | 6 +- .../src => Core/src/YubiKey}/DeviceFlags.cs | 4 +- .../src => Core/src/YubiKey}/DeviceInfo.cs | 13 +- .../src => Core/src/YubiKey}/FormFactor.cs | 4 +- .../src/YubiKey}/VersionQualifier.cs | 8 +- .../YubiKey}/CapabilityMapperTests.cs | 43 ++--- .../YubiKey/DeviceInfoTests.cs | 111 +++++++++++++ src/Management/CLAUDE.md | 3 +- src/Management/README.md | 11 +- .../Cli/Output/OutputHelpers.cs | 5 +- .../ManagementTool/Commands/ConfigCommand.cs | 3 +- .../ManagementTool/Commands/InfoCommand.cs | 3 +- .../Features/DeviceConfiguration.cs | 3 +- .../Features/Results/DeviceInfoResult.cs | 4 +- src/Management/src/IManagementSession.cs | 3 +- src/Management/src/IYubiKeyExtensions.cs | 1 + .../ManagementSessionTests.cs | 1 + .../PivTool/Cli/Commands/CliRunner.cs | 5 +- .../PivTool/Cli/Menus/DeviceInfoMenu.cs | 4 +- src/Tests.Shared/YubiKeyTestState.cs | 2 +- .../YubiKeyTestStateExtensions.cs | 1 + 28 files changed, 409 insertions(+), 76 deletions(-) create mode 100644 docs/plans/composite-device/phase-34-metadata-promotion-ISA.md create mode 100644 docs/plans/composite-device/phase-34-metadata-promotion-learnings.md rename src/{Management/src => Core/src/YubiKey}/DeviceCapabilities.cs (93%) rename src/{Management/src => Core/src/YubiKey}/DeviceFlags.cs (97%) rename src/{Management/src => Core/src/YubiKey}/DeviceInfo.cs (96%) rename src/{Management/src => Core/src/YubiKey}/FormFactor.cs (97%) rename src/{Management/src => Core/src/YubiKey}/VersionQualifier.cs (97%) rename src/{Management/tests/Yubico.YubiKit.Management.UnitTests => Core/tests/Yubico.YubiKit.Core.UnitTests/YubiKey}/CapabilityMapperTests.cs (84%) create mode 100644 src/Core/tests/Yubico.YubiKit.Core.UnitTests/YubiKey/DeviceInfoTests.cs diff --git a/docs/plans/composite-device/phase-34-metadata-promotion-ISA.md b/docs/plans/composite-device/phase-34-metadata-promotion-ISA.md new file mode 100644 index 000000000..0102ac1cf --- /dev/null +++ b/docs/plans/composite-device/phase-34-metadata-promotion-ISA.md @@ -0,0 +1,148 @@ +# Phase 34 ISA: Promote Read-Only Device Metadata To Core + +This phase promotes read-only physical-device metadata from `Management` into `Core` so later composite-device phases can make `IYubiKey` a physical-device abstraction without introducing a `Core` -> `Management` dependency. + +Read this together with: + +- `docs/plans/composite-device/ISA.md` +- `docs/plans/composite-device/phase-33-composite-device-program-learnings.md` +- `src/Core/CLAUDE.md` +- `src/Management/CLAUDE.md` +- `src/Core/README.md` +- `src/Management/README.md` + +## Problem + +`DeviceInfo` currently lives in `Yubico.YubiKit.Management`, but the composite-device model requires Core `IYubiKey` to expose firmware, serial, form factor, capabilities, and version qualifier facts before callers choose a Management session. Core cannot reference Management because Management already references Core. + +Moving `DeviceInfo` is not a one-file rename. `DeviceInfo` exposes `FormFactor`, `DeviceCapabilities`, `DeviceFlags`, `VersionQualifier`, and `VersionQualifierType`, all currently declared under the Management namespace. `Tests.Shared`, CLI shared/commands, examples, and Management tests compile against those types today. + +## Vision + +Core owns read-only physical-device metadata. Management still owns sessions, configuration writes, reset, lock codes, reboots, and backend protocol operations. After this phase, consumers that only need to inspect a YubiKey's identity can reference Core metadata types, while consumers that need management behavior still reference Management. + +## Out of Scope + +- No physical/composite `IYubiKey` implementation in Phase 34. +- No Core device-info reader in Phase 34. +- No discovery merge behavior in Phase 34. +- No extension-method smart default changes in Phase 34. +- No CLI command-family redesign; only mandatory compile/API migration for moved metadata types. +- No richer `Tests.Shared` filtering beyond mandatory compile migration. +- No downstream capability audit implementation. + +## Principles + +- Metadata promotion should be mechanical and minimal: move read-only types, update namespaces/usings, preserve behavior. +- Mutating Management concepts remain in Management. +- A v2 source-breaking namespace move is acceptable when explicitly recorded and verified; accidental extra public-surface drift is not. +- Mandatory compile migration is not optional downstream enhancement work. +- Tests should prove the moved parsing/value behavior still works, not just that the projects compile. +- A namespace move is solution-wide fallout. Focused project builds are useful diagnostics, but the unscoped solution build is the authoritative compile gate before commit. + +## Constraints + +- Execute on branch `yubikit-composite-device-new`. +- Use `/DevTeam` review/fix workflow after implementation. +- Cato review must audit the Phase 34 ISA before source edits because this is a broad API-boundary phase. +- Use `dotnet toolchain.cs`; never raw `dotnet build` or `dotnet test`. +- Commit only intended files after learning note and verification are complete. +- Do not introduce a `Core` project reference to `Management`. +- Do not leave applet libraries depending on `Management` solely for physical-device metadata. + +## Goal + +Move read-only device metadata types from `Yubico.YubiKit.Management` into `Yubico.YubiKit.Core.YubiKey`, update Management and mandatory compile consumers, preserve Management behavior, verify metadata parsing/value semantics in Core, verify no Core-to-Management dependency is introduced, run DevTeam review, write a learning note, and commit Phase 34 only. + +## Criteria + +- [ ] ISC-1: Branch check shows `## yubikit-composite-device-new` before source edits, review delegation, build/test, or commit. +- [ ] ISC-2: Cato reviews this Phase 34 ISA before source edits and returns pass or all concerns are resolved. +- [ ] ISC-3: `DeviceInfo` lives in Core under namespace `Yubico.YubiKit.Core.YubiKey`. +- [ ] ISC-4: `FormFactor` lives in Core under namespace `Yubico.YubiKit.Core.YubiKey`. +- [ ] ISC-5: `DeviceCapabilities` lives in Core under namespace `Yubico.YubiKit.Core.YubiKey`. +- [ ] ISC-6: `DeviceFlags` lives in Core under namespace `Yubico.YubiKit.Core.YubiKey`. +- [ ] ISC-7: `VersionQualifier` and `VersionQualifierType` live in Core under namespace `Yubico.YubiKit.Core.YubiKey`. +- [ ] ISC-8: `CapabilityMapper` or equivalent parsing support moves with `DeviceInfo` and does not remain in Management. +- [ ] ISC-8.1: Existing `CapabilityMapperTests` migrate from Management unit tests to Core unit tests, or are replaced by equivalent Core tests before commit. +- [ ] ISC-8.2: `CapabilityMapper.FromFips` disposition is explicit: keep it internal in Core if the existing tests prove useful capability decoding behavior, or delete it with tests removed if no production/future Core reader need exists. +- [ ] ISC-9: `ManagementSession.GetDeviceInfoAsync` returns the Core `DeviceInfo` type without changing behavior. +- [ ] ISC-10: `DeviceConfig`, Management backends, reset/config/lock/reboot behavior, and Management session ownership remain in Management. +- [ ] ISC-11: Core production project still has no `ProjectReference` to Management. +- [ ] ISC-12: Management production project still references Core. +- [ ] ISC-13: `Tests.Shared` compiles against Core metadata types after mandatory migration. +- [ ] ISC-14: CLI shared/commands and example compile consumers of `DeviceInfo` use Core metadata types where they only need metadata. +- [ ] ISC-14.1: Applet integration tests and applet example tools that compile against moved metadata types are migrated or verified by full-solution build. +- [ ] ISC-15: Applet libraries do not gain Management references solely for metadata. +- [ ] ISC-16: Core unit tests cover `DeviceInfo.CreateFromTlvs` success behavior for representative required TLVs. +- [ ] ISC-17: Core unit tests cover `DeviceInfo.CreateFromTlvs` version qualifier behavior. +- [ ] ISC-18: Core unit tests cover invalid version qualifier data or malformed required TLV behavior where current behavior is defined. +- [ ] ISC-19: Existing Management device-info page sequencing tests continue to pass with the Core metadata type. +- [ ] ISC-20: Focused Core build passes. +- [ ] ISC-21: Focused Management build/tests pass. +- [ ] ISC-22: Focused Tests.Shared or representative dependent test/build verification passes. +- [ ] ISC-23: Focused CLI shared/commands build or representative compile verification passes. +- [ ] ISC-23.1: Full solution build passes with `dotnet toolchain.cs build` after namespace migration. +- [ ] ISC-23.2: Phase 34 records a public API/source-surface check showing only intended public metadata ownership changes and no accidental extra public-surface drift. +- [ ] ISC-24: DevTeam cross-vendor review returns pass or all findings are fixed. +- [ ] ISC-25: Phase 34 learning note exists and records source changes, review, verification, deferred candidates, and next phase inputs. +- [ ] ISC-26: Anti: Phase 34 changes `IYubiKey` physical-device semantics. +- [ ] ISC-27: Anti: Phase 34 implements richer Tests.Shared filtering, smarter CLI selection, or extension smart defaults beyond required compile migration. +- [ ] ISC-28: Anti: Phase 34 creates duplicate public `DeviceInfo` models in Core and Management. + +## Test Strategy + +| ISC | Type | Check | Threshold | Tool | +| --- | --- | --- | --- | --- | +| ISC-1 | branch | Check active branch | `## yubikit-composite-device-new` | `git status --short --branch` | +| ISC-2 | review | Cato ISA review | pass or resolved concerns | Cato JSONL | +| ISC-3 to ISC-8 | source | Verify moved types and namespaces | types in Core namespace; no stale Management declarations | Grep/Read | +| ISC-8.1 to ISC-8.2 | unit migration | Verify `CapabilityMapper` test/disposition | tests moved/replaced, `FromFips` kept or removed intentionally | Grep/Read/tests | +| ISC-9 to ISC-10 | source | Verify Management behavior boundary | session returns Core type; mutating types stay in Management | Grep/Read/tests | +| ISC-11 to ISC-12 | dependency | Verify project references | Core has no Management ref; Management has Core ref | `.csproj` grep | +| ISC-13 to ISC-15 | compile migration | Verify mandatory consumers compile/use Core metadata | no metadata-only Management references introduced | Grep/build/tests | +| ISC-14.1 | compile migration | Verify applet integration/example consumers | solution build passes | `dotnet toolchain.cs build` | +| ISC-16 to ISC-18 | unit | Core metadata parsing tests | focused tests pass | `dotnet toolchain.cs -- test --project Core --filter "FullyQualifiedName~DeviceInfo"` | +| ISC-19 | unit | Management device-info tests | focused tests pass | `dotnet toolchain.cs -- test --project Management --filter "FullyQualifiedName~ManagementSessionTests"` | +| ISC-20 | build | Core build | exit 0 | `dotnet toolchain.cs -- build --project Core` | +| ISC-21 | build/test | Management build/tests | exit 0 | `dotnet toolchain.cs -- build --project Management`; focused test | +| ISC-22 | build/test | Tests.Shared dependent verification | exit 0 | `dotnet toolchain.cs -- test --project Core --filter "FullyQualifiedName~YubiKeyDeviceRepositoryTests"` or narrower consumer test | +| ISC-23 | build | CLI compile consumer verification | exit 0 | `dotnet toolchain.cs -- build --project Cli.Shared`; `dotnet toolchain.cs -- build --project Cli.Commands` | +| ISC-23.1 | build | Full solution compile | exit 0 | `dotnet toolchain.cs build` | +| ISC-23.2 | API surface | Verify public/source-surface drift | only intended metadata ownership changes | public API/source grep or diff review | +| ISC-24 | review | DevTeam review | pass or resolved findings | review output | +| ISC-25 | file | Learning note exists | title and evidence present | Read | +| ISC-26 to ISC-28 | scope | Scope guard | no physical model/default/filter feature work | Git diff review | + +## Features + +| Name | Description | Satisfies | Depends On | Parallelizable | +| --- | --- | --- | --- | --- | +| Phase 34 ISA and Cato | Write this ISA and run Cato before source edits. | ISC-1, ISC-2 | Phase 33 | false | +| Metadata type move | Move `DeviceInfo`, `FormFactor`, `DeviceCapabilities`, `DeviceFlags`, `VersionQualifier`, `VersionQualifierType`, and parsing support into Core; migrate `CapabilityMapper` tests or delete dead helper code intentionally. | ISC-3, ISC-4, ISC-5, ISC-6, ISC-7, ISC-8, ISC-8.1, ISC-8.2 | Cato pass | false | +| Management migration | Update Management source/tests/docs to use Core metadata while preserving mutating behavior. | ISC-9, ISC-10, ISC-19, ISC-21 | Metadata type move | false | +| Mandatory consumer migration | Update `Tests.Shared`, CLI shared/commands, applet integration tests, examples, and tests needed to compile against Core metadata. | ISC-13, ISC-14, ISC-14.1, ISC-15, ISC-22, ISC-23, ISC-23.1, ISC-23.2 | Metadata type move | true | +| Core metadata tests | Add focused Core tests for DeviceInfo parsing and version qualifier behavior. | ISC-16, ISC-17, ISC-18, ISC-20 | Metadata type move | false | +| Review, learning, commit | Run DevTeam review, fix findings, verify, write learning note, and commit intended files. | ISC-24, ISC-25, ISC-26, ISC-27, ISC-28 | implementation complete | false | + +## Decisions + +- 2026-06-09: Phase 34 accepts a v2 source namespace move: read-only metadata types move from `Yubico.YubiKit.Management` to `Yubico.YubiKit.Core.YubiKey`. +- 2026-06-09: No duplicate wrapper `DeviceInfo` remains in Management during Phase 34 because duplicate public models would make later physical-device work ambiguous. +- 2026-06-09: No type-forwarding or compatibility shim is added in Phase 34 because the approved v2 direction prioritizes a clear Core ownership boundary and there is no protected external package baseline yet. +- 2026-06-09: Mandatory consumer compile migration includes `Tests.Shared`, CLI shared/commands, examples, and tests that use metadata-only types; richer behavior stays deferred. +- 2026-06-09: `ChallengeResponseTimeout` remains `ReadOnlyMemory` in Phase 34 to preserve behavior; any scalar conversion is deferred unless tests reveal an ownership bug. +- 2026-06-09: `CapabilityMapper.FromFips` has no production caller today but existing tests cover it. Phase 34 may keep it internal in Core with migrated tests if useful for future Core read-info work, or delete it with test removal if implementation review judges it dead code. +- 2026-06-09: Full solution build is required before commit because the namespace move can break applet integration tests and example tools outside the focused Core/Management/CLI project set. +- 2026-06-09: Phase 34 API-surface checking is source-diff based unless an existing public API analyzer is found; the required result is that only the approved metadata ownership move changes public source shape. + +## Changelog + +- conjectured: Metadata promotion might be a simple move of `DeviceInfo` only. + refuted by: Cato and source inspection showed `DeviceInfo` publicly exposes several Management namespace types and mandatory consumers compile against them. + learned: Phase 34 must move the whole read-only metadata surface and migrate required consumers together. + criterion now: ISC-3 through ISC-15 govern the move and mandatory compile migration. + +## Verification + +Verification is populated in the Phase 34 learning note before commit. diff --git a/docs/plans/composite-device/phase-34-metadata-promotion-learnings.md b/docs/plans/composite-device/phase-34-metadata-promotion-learnings.md new file mode 100644 index 000000000..82290b012 --- /dev/null +++ b/docs/plans/composite-device/phase-34-metadata-promotion-learnings.md @@ -0,0 +1,90 @@ +# Phase 34 Learnings: Core Metadata Promotion + +Use this note as the handoff record for Phase 34 of the composite-device program. + +## Phase Summary + +- Branch: `yubikit-composite-device-new`. +- Scope: promote read-only device metadata models from Management to Core and migrate mandatory consumers. +- Phase ISA: `docs/plans/composite-device/phase-34-metadata-promotion-ISA.md`. +- Source changed: Core, Management, Tests.Shared, CLI shared/commands, and examples. +- Next phase after owner command: Phase 35, share read-only device-info retrieval without reintroducing Core-to-Management coupling. + +## What Changed + +- Added Core metadata models under `src/Core/src/YubiKey/`: + - `DeviceInfo` + - `DeviceCapabilities` + - `DeviceFlags` + - `FormFactor` + - `VersionQualifier` + - `VersionQualifierType` +- Moved internal `CapabilityMapper` with `DeviceInfo` into Core. +- Removed the old Management-owned metadata model files. +- Updated Management session/interface/extensions to return Core `DeviceInfo`. +- Updated `Tests.Shared`, CLI shared/commands, PIV example, and Management example imports to use `Yubico.YubiKit.Core.YubiKey`. +- Migrated `CapabilityMapperTests` from Management unit tests to Core unit tests. +- Added Core `DeviceInfoTests` for required TLV parsing, alpha version qualifiers, invalid qualifier length, missing qualifier fields, and invalid UTF-8 part-number fallback. +- Updated Management/Core docs to record that read-only metadata types live in Core while Management owns configuration/reset/session behavior. + +## Review Evidence + +- DevTeam reviewer route command: `bun ~/.claude/PAI/TOOLS/AgentHarnessRouter.ts --surface devteam --role reviewer --primary-model openai/gpt-5.5 --cwd /home/dyallo/Code/y/Yubico.NET.SDK --dry-run --json`. +- DevTeam reviewer route result: `google-vertex-anthropic/claude-opus-4-8@default` selected for OpenAI primary. +- DevTeam review output: `/tmp/opencode/devteam-review-phase34.md`. +- DevTeam verdict: `PASS`. +- DevTeam low notes: + - `CapabilityMapper.FromFips` remains internal and test-covered but has no non-test caller; this was known in the Phase 34 ISA and preserved intentionally. + - Required-tag parsing still throws generic dictionary/index exceptions for absent or short required TLVs; unchanged from the original Management implementation. + - Additional Core parser tests were added for missing version-qualifier fields and invalid part-number UTF-8 fallback. + +## Verification Evidence + +- Full build command: `dotnet toolchain.cs build`. +- Full build result: passed with 0 warnings and 0 errors. +- Core metadata test command: `dotnet toolchain.cs -- test --project Core --filter "FullyQualifiedName~CoreYubiKey"`. +- Core metadata test result: passed; 55 tests. +- Management focused test command: `dotnet toolchain.cs -- test --project Management --filter "FullyQualifiedName~ManagementSessionTests"`. +- Management focused test result: passed; 5 tests. +- Changed-file format command: `dotnet format --verify-no-changes --include $(git diff --name-only --diff-filter=ACM -- '*.cs')`. +- Changed-file format result: passed. +- Docs QA command: `dotnet toolchain.cs -- docs-qa`. +- Docs QA result: passed; 54 active documentation files validated. +- Whitespace command: `git diff --check`. +- Whitespace result: passed with no output. +- Old namespace check: no `Management.(DeviceInfo|DeviceCapabilities|DeviceFlags|FormFactor|VersionQualifier)` source references remain. +- Coupling check: no Core project reference to Management. +- Whole-repo `dotnet format --verify-no-changes` is still blocked by pre-existing unrelated formatting issues outside this phase, so changed-file formatting was used for phase verification. + +## What Did Not Work + +- Running full build concurrently with focused tests caused a transient file-lock warning; rerunning full build alone produced a clean 0-warning result. +- Initial full-solution builds surfaced staggered namespace fallout in CLI shared, CLI commands, Management examples, and PIV examples; full build was necessary because focused Core/Management builds did not cover those example projects. +- A moved test namespace named `Yubico.YubiKit.Core.UnitTests.YubiKey` shadowed existing `YubiKey.FirmwareVersion` references in another Core test, so the metadata test namespace was renamed to `Yubico.YubiKit.Core.UnitTests.CoreYubiKey`. + +## Reusable Patterns + +- For package-boundary moves, first move source and tests, then rely on full solution build to find consumer namespace fallout. +- Keep promoted parser behavior byte-for-byte identical unless the phase explicitly scopes behavior hardening. +- Use changed-file `dotnet format --verify-no-changes --include ...` when the repository has unrelated whole-repo format failures. +- Avoid test namespaces that collide with imported domain namespaces. + +## Deferred Candidates + +- Decide whether `CapabilityMapper.FromFips` should be deleted or retained with a future production caller once downstream FIPS capability semantics are audited. +- Consider typed/descriptive required-TLV error handling for `DeviceInfo.CreateFromTlvs` in a future parser-hardening phase. +- Run the downstream capability audit recorded in the master composite-device ISA after physical-device modeling is implemented. + +## Next Phase Inputs + +- Phase 35 should preserve the new Core metadata ownership and avoid any Core dependency on Management. +- Phase 35 should share read-only device-info retrieval logic across connection paths without changing physical/composite `IYubiKey` semantics prematurely. +- Phase 35 should continue to distinguish read-only metadata from Management-owned mutating configuration/reset behavior. +- Phase 35 should run full solution build, not only Core/Management focused builds, because examples and CLI projects catch package-boundary fallout. + +## Compact Summary + +- Goal: promote read-only device metadata to Core. +- Branch: `yubikit-composite-device-new`. +- Scope: source move, mandatory consumers, docs, tests. +- Status: implementation verified; ready for staging/commit when requested. diff --git a/src/Cli.Commands/src/Infrastructure/YkDeviceContext.cs b/src/Cli.Commands/src/Infrastructure/YkDeviceContext.cs index 3291eccb2..c8eac9bb1 100644 --- a/src/Cli.Commands/src/Infrastructure/YkDeviceContext.cs +++ b/src/Cli.Commands/src/Infrastructure/YkDeviceContext.cs @@ -3,6 +3,7 @@ using Yubico.YubiKit.Cli.Shared.Device; using Yubico.YubiKit.Core.Interfaces; +using Yubico.YubiKit.Core.YubiKey; using Yubico.YubiKit.Management; namespace Yubico.YubiKit.Cli.Commands.Infrastructure; @@ -43,4 +44,4 @@ public sealed class YkDeviceContext Info?.PartNumber is { Length: > 0 } partNumber ? $"{partNumber} — S/N: {Selection.SerialNumber?.ToString() ?? "N/A"} [{Selection.FirmwareVersion}]" : Selection.DisplayName; -} +} \ No newline at end of file diff --git a/src/Cli.Shared/src/Device/DeviceSelection.cs b/src/Cli.Shared/src/Device/DeviceSelection.cs index 06f9979b5..6c4eb8f15 100644 --- a/src/Cli.Shared/src/Device/DeviceSelection.cs +++ b/src/Cli.Shared/src/Device/DeviceSelection.cs @@ -14,7 +14,6 @@ using Yubico.YubiKit.Core.Interfaces; using Yubico.YubiKit.Core.YubiKey; -using Yubico.YubiKit.Management; namespace Yubico.YubiKit.Cli.Shared.Device; diff --git a/src/Cli.Shared/src/Device/FormFactorFormatter.cs b/src/Cli.Shared/src/Device/FormFactorFormatter.cs index ed049e231..eef12f2ed 100644 --- a/src/Cli.Shared/src/Device/FormFactorFormatter.cs +++ b/src/Cli.Shared/src/Device/FormFactorFormatter.cs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -using Yubico.YubiKit.Management; +using Yubico.YubiKit.Core.YubiKey; namespace Yubico.YubiKit.Cli.Shared.Device; diff --git a/src/Core/CLAUDE.md b/src/Core/CLAUDE.md index 872948bdd..b1aefd14b 100644 --- a/src/Core/CLAUDE.md +++ b/src/Core/CLAUDE.md @@ -36,7 +36,7 @@ src/ │ ├── Windows/ # Windows-specific (HidD, Cfgmgr32) │ ├── MacOS/ # macOS-specific (IOKit, CoreFoundation) │ └── Linux/ # Linux-specific (udev, libc) -├── YubiKey/ # YubiKey types, feature flags +├── YubiKey/ # YubiKey types, feature flags, read-only metadata models └── Utils/ # TLV, CRC, byte utilities ``` diff --git a/src/Core/README.md b/src/Core/README.md index b864d9a99..804c1f858 100644 --- a/src/Core/README.md +++ b/src/Core/README.md @@ -12,6 +12,7 @@ Yubico.YubiKit.Core is the foundation that all other SDK modules build upon. It - 📡 **Protocol Handling** - ISO 7816-4 APDU processing with automatic command chaining - 🔐 **Secure Channel Protocol (SCP)** - SCP03, SCP11a/b/c support for secure communication - 🖥️ **Platform Interop** - Cross-platform native library loading and device enumeration +- 🧾 **Device Metadata Models** - Read-only `DeviceInfo`, capability, form-factor, flag, and version qualifier types - 🛠️ **Utilities** - TLV processing, cryptographic key types, COSE encoding ## Installation diff --git a/src/Management/src/DeviceCapabilities.cs b/src/Core/src/YubiKey/DeviceCapabilities.cs similarity index 93% rename from src/Management/src/DeviceCapabilities.cs rename to src/Core/src/YubiKey/DeviceCapabilities.cs index 2af85a56e..94bf8c188 100644 --- a/src/Management/src/DeviceCapabilities.cs +++ b/src/Core/src/YubiKey/DeviceCapabilities.cs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -namespace Yubico.YubiKit.Management; +namespace Yubico.YubiKit.Core.YubiKey; [Flags] public enum DeviceCapabilities @@ -50,7 +50,7 @@ public enum DeviceCapabilities HsmAuth = 0x01_00, /// - /// Identifies the FIDO2 = CTAP2 portion of the FIDO application. + /// Identifies the FIDO2 = CTAP2 portion of the FIDO application. /// Fido2 = 0x02_00, @@ -59,4 +59,4 @@ public enum DeviceCapabilities /// This includes Otp, U2f, OpenPgp, Piv, Oath, HsmAuth, and Fido2. /// All = Otp | U2f | OpenPgp | Piv | Oath | HsmAuth | Fido2 -} \ No newline at end of file +} diff --git a/src/Management/src/DeviceFlags.cs b/src/Core/src/YubiKey/DeviceFlags.cs similarity index 97% rename from src/Management/src/DeviceFlags.cs rename to src/Core/src/YubiKey/DeviceFlags.cs index 83c3bfc6e..03e97149f 100644 --- a/src/Management/src/DeviceFlags.cs +++ b/src/Core/src/YubiKey/DeviceFlags.cs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -namespace Yubico.YubiKit.Management; +namespace Yubico.YubiKit.Core.YubiKey; /// /// Miscellaneous flags representing various settings available on the YubiKey. @@ -49,4 +49,4 @@ public enum DeviceFlags /// /// TouchEject = 0x80 -} \ No newline at end of file +} diff --git a/src/Management/src/DeviceInfo.cs b/src/Core/src/YubiKey/DeviceInfo.cs similarity index 96% rename from src/Management/src/DeviceInfo.cs rename to src/Core/src/YubiKey/DeviceInfo.cs index cbe074abf..86faecf45 100644 --- a/src/Management/src/DeviceInfo.cs +++ b/src/Core/src/YubiKey/DeviceInfo.cs @@ -16,9 +16,8 @@ using System.Text; using Yubico.YubiKit.Core.Extensions; using Yubico.YubiKit.Core.Utils; -using Yubico.YubiKit.Core.YubiKey; -namespace Yubico.YubiKit.Management; +namespace Yubico.YubiKit.Core.YubiKey; public readonly record struct DeviceInfo { @@ -191,14 +190,6 @@ private static (FirmwareVersion, VersionQualifier) DetermineFirmwareVersion( iteration); var isFinalVersion = versionQualifier.Type == VersionQualifierType.Final; - if (!isFinalVersion) - { - // TODO - // var logger = Log.GetLogger(); - // logger.LogDebug("Overriding behavioral version with {FirmwareString}", - // deviceInfo.VersionQualifier.FirmwareVersion); - } - var finalVersion = isFinalVersion ? defaultFirmwareVersion : versionQualifier.FirmwareVersion; @@ -258,4 +249,4 @@ public static DeviceCapabilities FromApp(ReadOnlyMemory appData) ? (DeviceCapabilities)appData.Span[0] : (DeviceCapabilities)BinaryPrimitives.ReadInt16BigEndian(appData.Span); } -} \ No newline at end of file +} diff --git a/src/Management/src/FormFactor.cs b/src/Core/src/YubiKey/FormFactor.cs similarity index 97% rename from src/Management/src/FormFactor.cs rename to src/Core/src/YubiKey/FormFactor.cs index 478de3e17..d83e11f89 100644 --- a/src/Management/src/FormFactor.cs +++ b/src/Core/src/YubiKey/FormFactor.cs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -namespace Yubico.YubiKit.Management; +namespace Yubico.YubiKit.Core.YubiKey; public enum FormFactor { @@ -55,4 +55,4 @@ public enum FormFactor /// The YubiKey is a USB-C key-chain device with a biometric (fingerprint) sensor. /// UsbCBiometricKeychain = 7 -} \ No newline at end of file +} diff --git a/src/Management/src/VersionQualifier.cs b/src/Core/src/YubiKey/VersionQualifier.cs similarity index 97% rename from src/Management/src/VersionQualifier.cs rename to src/Core/src/YubiKey/VersionQualifier.cs index 18e4cb421..d6d4e7dae 100644 --- a/src/Management/src/VersionQualifier.cs +++ b/src/Core/src/YubiKey/VersionQualifier.cs @@ -12,15 +12,13 @@ // See the License for the specific language governing permissions and // limitations under the License. -using Yubico.YubiKit.Core.YubiKey; - -namespace Yubico.YubiKit.Management; +namespace Yubico.YubiKit.Core.YubiKey; /// /// Represents the type of version qualifier for a firmware version. /// The version qualifier type indicates whether the version is an Alpha, Beta, or Final release. /// -public enum VersionQualifierType +public enum VersionQualifierType { Alpha = 0x00, Beta = 0x01, @@ -100,4 +98,4 @@ obj is VersionQualifier other && Iteration == other.Iteration; public override int GetHashCode() => HashCode.Combine(FirmwareVersion, Type, Iteration); -} \ No newline at end of file +} diff --git a/src/Management/tests/Yubico.YubiKit.Management.UnitTests/CapabilityMapperTests.cs b/src/Core/tests/Yubico.YubiKit.Core.UnitTests/YubiKey/CapabilityMapperTests.cs similarity index 84% rename from src/Management/tests/Yubico.YubiKit.Management.UnitTests/CapabilityMapperTests.cs rename to src/Core/tests/Yubico.YubiKit.Core.UnitTests/YubiKey/CapabilityMapperTests.cs index 210b7dbd4..3005c070e 100644 --- a/src/Management/tests/Yubico.YubiKit.Management.UnitTests/CapabilityMapperTests.cs +++ b/src/Core/tests/Yubico.YubiKit.Core.UnitTests/YubiKey/CapabilityMapperTests.cs @@ -1,6 +1,7 @@ using System.Buffers.Binary; +using Yubico.YubiKit.Core.YubiKey; -namespace Yubico.YubiKit.Management.UnitTests; +namespace Yubico.YubiKit.Core.UnitTests.CoreYubiKey; public class CapabilityMapperTests { @@ -41,7 +42,7 @@ public void FromFips_MultipleBits_MapsCorrectly(int fipsValue, DeviceCapabilitie [Fact] public void FromFips_AllFipsCapabilities_ReturnsExpected() { - var buffer = CreateBigEndianBytes(0x1F); // All 5 FIPS bits set + var buffer = CreateBigEndianBytes(0x1F); var result = CapabilityMapper.FromFips(buffer); @@ -56,18 +57,17 @@ public void FromFips_AllFipsCapabilities_ReturnsExpected() } [Theory] - [InlineData(0x20)] // Bit 5 - undefined - [InlineData(0x40)] // Bit 6 - undefined - [InlineData(0x80)] // Bit 7 - undefined - [InlineData(0xE0)] // Bits 5-7 all set - [InlineData(0xFF)] // All bits including undefined + [InlineData(0x20)] + [InlineData(0x40)] + [InlineData(0x80)] + [InlineData(0xE0)] + [InlineData(0xFF)] public void FromFips_UnknownBits_AreIgnored(int fipsValue) { var buffer = CreateBigEndianBytes(fipsValue); var result = CapabilityMapper.FromFips(buffer); - // Should only include known FIPS capabilities (bits 0-4) var knownBits = (DeviceCapabilities)(fipsValue & 0x1F); var knownBuffer = CreateBigEndianBytes((int)knownBits); var expected = CapabilityMapper.FromFips(knownBuffer); @@ -78,7 +78,7 @@ public void FromFips_UnknownBits_AreIgnored(int fipsValue) [Fact] public void FromFips_UnknownBitsCombinedWithKnown_MapsKnownOnly() { - var buffer = CreateBigEndianBytes(0xE1); // Bits 7,6,5 (unknown) + bit 0 (Fido2) + var buffer = CreateBigEndianBytes(0xE1); var result = CapabilityMapper.FromFips(buffer); @@ -98,12 +98,10 @@ public void FromFips_EmptyBuffer_ReturnsNone() [Fact] public void FromFips_HighByte_IsProcessed() { - // Test that high byte is properly handled (16-bit value) - var buffer = new byte[] { 0x01, 0x00 }; // 0x0100 big-endian + var buffer = new byte[] { 0x01, 0x00 }; var result = CapabilityMapper.FromFips(buffer); - // Bit 8 should be ignored (undefined in FIPS mapping) Assert.Equal(DeviceCapabilities.None, result); } @@ -163,7 +161,6 @@ public void FromApp_EmptyBuffer_ReturnsNone() [Fact] public void FromFips_DoesNotIncludeOtpOrU2f() { - // FIPS should never map to Otp or U2f (not FIPS-approved) var buffer = CreateBigEndianBytes(0xFF); var result = CapabilityMapper.FromFips(buffer); @@ -192,7 +189,6 @@ public void FromApp_CanIncludeOtpAndU2f() [InlineData(DeviceCapabilities.HsmAuth)] public void FromFips_RoundTrip_EachFipsCapability(DeviceCapabilities capability) { - // Find which FIPS bit corresponds to this capability var fipsBit = capability switch { DeviceCapabilities.Fido2 => 0x01, @@ -213,8 +209,7 @@ public void FromFips_RoundTrip_EachFipsCapability(DeviceCapabilities capability) [Fact] public void FromFips_BigEndianEncoding_IsRespected() { - // Manually construct big-endian bytes - var buffer = new byte[] { 0x00, 0x1F }; // Big-endian 0x001F + var buffer = new byte[] { 0x00, 0x1F }; var result = CapabilityMapper.FromFips(buffer); @@ -231,8 +226,7 @@ public void FromFips_BigEndianEncoding_IsRespected() [Fact] public void FromApp_BigEndianEncoding_IsRespected() { - // Manually construct big-endian bytes for HsmAuth | Fido2 - var buffer = new byte[] { 0x03, 0x00 }; // Big-endian 0x0300 + var buffer = new byte[] { 0x03, 0x00 }; var result = CapabilityMapper.FromApp(buffer); @@ -272,13 +266,11 @@ public void FromFips_ZeroValue_ReturnsNone(byte[] buffer) Assert.Equal(DeviceCapabilities.None, result); } - [Fact] public void FromFips_MaxInt16_ParsesCorrectly() { - var buffer = new byte[] { 0x7F, 0xFF }; // Max positive int16 + var buffer = new byte[] { 0x7F, 0xFF }; - // Should ignore all high bits, only process bits 0-4 var result = CapabilityMapper.FromFips(buffer); var expected = @@ -294,9 +286,8 @@ public void FromFips_MaxInt16_ParsesCorrectly() [Fact] public void FromFips_NegativeInt16_ParsesCorrectly() { - var buffer = new byte[] { 0x80, 0x00 }; // -32768 as int16, 0x8000 as uint16 + var buffer = new byte[] { 0x80, 0x00 }; - // Should ignore high bits beyond bit 4 var result = CapabilityMapper.FromFips(buffer); Assert.Equal(DeviceCapabilities.None, result); @@ -309,9 +300,3 @@ private static byte[] CreateBigEndianBytes(int value) return buffer; } } - -// Add this to your enum if not present -public static class YubiKeyCapabilitiesExtensions -{ - public const DeviceCapabilities None = 0; -} \ No newline at end of file diff --git a/src/Core/tests/Yubico.YubiKit.Core.UnitTests/YubiKey/DeviceInfoTests.cs b/src/Core/tests/Yubico.YubiKit.Core.UnitTests/YubiKey/DeviceInfoTests.cs new file mode 100644 index 000000000..4415d22d6 --- /dev/null +++ b/src/Core/tests/Yubico.YubiKit.Core.UnitTests/YubiKey/DeviceInfoTests.cs @@ -0,0 +1,111 @@ +using Yubico.YubiKit.Core.Utils; +using Yubico.YubiKit.Core.YubiKey; + +namespace Yubico.YubiKit.Core.UnitTests.CoreYubiKey; + +public class DeviceInfoTests +{ + [Fact] + public void CreateFromTlvs_RequiredTlvs_ParsesDeviceMetadata() + { + var info = DeviceInfo.CreateFromTlvs(CreateRequiredDeviceInfoTlvs(), defaultVersion: null); + + Assert.False(info.IsLocked); + Assert.False(info.IsFips); + Assert.False(info.IsSky); + Assert.Equal(FormFactor.UsbAKeychain, info.FormFactor); + Assert.Equal(0x01020304, info.SerialNumber); + Assert.Equal(DeviceCapabilities.Otp | DeviceCapabilities.Piv, info.UsbEnabled); + Assert.Equal(DeviceCapabilities.All, info.UsbSupported); + Assert.Equal(DeviceCapabilities.Piv, info.NfcEnabled); + Assert.Equal(DeviceCapabilities.Piv | DeviceCapabilities.Oath, info.NfcSupported); + Assert.Equal(DeviceCapabilities.None, info.ResetBlocked); + Assert.Equal(DeviceCapabilities.Fido2, info.FipsCapabilities); + Assert.Equal(DeviceCapabilities.Piv, info.FipsApproved); + Assert.Equal(30, info.AutoEjectTimeout); + Assert.Equal(new byte[] { 0x0F }, info.ChallengeResponseTimeout.ToArray()); + Assert.Equal(DeviceFlags.RemoteWakeup, info.DeviceFlags); + Assert.Equal(new FirmwareVersion(5, 7, 2), info.FirmwareVersion); + Assert.Equal(VersionQualifierType.Final, info.VersionQualifier.Type); + Assert.Equal("5.7.2", info.VersionName); + } + + [Fact] + public void CreateFromTlvs_AlphaVersionQualifier_UsesQualifiedVersionName() + { + var tlvs = CreateRequiredDeviceInfoTlvs([ + new Tlv(0x19, + [ + 0x01, 0x03, 0x06, 0x02, 0x01, + 0x02, 0x01, 0x00, + 0x03, 0x04, 0x00, 0x00, 0x00, 0x2A + ]) + ]); + + var info = DeviceInfo.CreateFromTlvs(tlvs, new FirmwareVersion(5, 7, 2)); + + Assert.Equal(new FirmwareVersion(6, 2, 1), info.FirmwareVersion); + Assert.Equal(new FirmwareVersion(6, 2, 1), info.VersionQualifier.FirmwareVersion); + Assert.Equal(VersionQualifierType.Alpha, info.VersionQualifier.Type); + Assert.Equal(42, info.VersionQualifier.Iteration); + Assert.Equal("6.2.1.alpha.42", info.VersionName); + } + + [Fact] + public void CreateFromTlvs_InvalidVersionQualifierLength_ThrowsArgumentException() + { + var tlvs = CreateRequiredDeviceInfoTlvs([new Tlv(0x19, [0x01, 0x03, 0x05])]); + + var ex = Assert.Throws(() => + DeviceInfo.CreateFromTlvs(tlvs, new FirmwareVersion(5, 7, 2))); + + Assert.Contains("Invalid data length", ex.Message); + } + + [Fact] + public void CreateFromTlvs_VersionQualifierMissingType_ThrowsArgumentException() + { + var tlvs = CreateRequiredDeviceInfoTlvs([ + new Tlv(0x19, + [ + 0x01, 0x03, 0x05, 0x07, 0x02, + 0x03, 0x04, 0x00, 0x00, 0x00, 0x01, + 0x04, 0x01, 0x00 + ]) + ]); + + var ex = Assert.Throws(() => + DeviceInfo.CreateFromTlvs(tlvs, new FirmwareVersion(5, 7, 2))); + + Assert.Contains("TAG_TYPE", ex.Message); + } + + [Fact] + public void CreateFromTlvs_InvalidPartNumberUtf8_ReturnsNullPartNumber() + { + var tlvs = CreateRequiredDeviceInfoTlvs([new Tlv(0x13, [0xC3, 0x28])]); + + var info = DeviceInfo.CreateFromTlvs(tlvs, defaultVersion: null); + + Assert.Null(info.PartNumber); + } + + private static Tlv[] CreateRequiredDeviceInfoTlvs(params Tlv[] additionalTlvs) => + [ + new(0x0A, [0x00]), + new(0x04, [(byte)FormFactor.UsbAKeychain]), + new(0x18, [0x00]), + new(0x03, [0x00, 0x11]), + new(0x01, [0x03, 0x3B]), + new(0x0E, [0x10]), + new(0x0D, [0x30]), + new(0x14, [0x02, 0x00]), + new(0x15, [0x00, 0x10]), + new(0x06, [0x00, 0x1E]), + new(0x07, [0x0F]), + new(0x08, [(byte)DeviceFlags.RemoteWakeup]), + new(0x05, [0x05, 0x07, 0x02]), + new(0x02, [0x01, 0x02, 0x03, 0x04]), + .. additionalTlvs + ]; +} diff --git a/src/Management/CLAUDE.md b/src/Management/CLAUDE.md index e8bd7b098..21f0c1d0c 100644 --- a/src/Management/CLAUDE.md +++ b/src/Management/CLAUDE.md @@ -21,10 +21,11 @@ The Management module is the **primary interface for YubiKey device configuratio **Key Files:** - [`ManagementSession.cs`](src/ManagementSession.cs) - Main session class -- [`DeviceInfo.cs`](src/DeviceInfo.cs) - Device information model (~261 lines) - [`DeviceConfig.cs`](src/DeviceConfig.cs) - Configuration model (~190 lines) - [`IYubiKeyExtensions.cs`](src/IYubiKeyExtensions.cs) - Convenience extensions for `IYubiKey` +Read-only device metadata types returned by `GetDeviceInfoAsync` (`DeviceInfo`, `DeviceCapabilities`, `DeviceFlags`, `FormFactor`, `VersionQualifier`) live in `Core/src/YubiKey` under `Yubico.YubiKit.Core.YubiKey`. + ## Test Infrastructure - Advanced Device Filtering ### The `[WithYubiKey]` Attribute System diff --git a/src/Management/README.md b/src/Management/README.md index 3d9ab1b35..95df2160e 100644 --- a/src/Management/README.md +++ b/src/Management/README.md @@ -238,12 +238,7 @@ await mgmtSession.ResetDeviceAsync(cancellationToken); Yubico.YubiKit.Management/ ├── src/ │ ├── ManagementSession.cs # Main session class -│ ├── DeviceInfo.cs # Device information model │ ├── DeviceConfig.cs # Configuration model -│ ├── DeviceCapabilities.cs # Capability flags enum -│ ├── DeviceFlags.cs # Device flags enum -│ ├── FormFactor.cs # Form factor enum -│ ├── VersionQualifier.cs # Firmware version qualifier │ ├── IYubiKeyExtensions.cs # Convenience extensions │ ├── DependencyInjection.cs # DI support │ └── Yubico.YubiKit.Management.csproj @@ -253,10 +248,12 @@ Yubico.YubiKit.Management/ │ ├── AdvancedManagementTests.cs │ └── ManagementTests.cs └── Yubico.YubiKit.Management.UnitTests/ - ├── CapabilityMapperTests.cs - └── FirmwareVersionTests.cs + ├── FirmwareVersionTests.cs + └── ManagementSessionTests.cs ``` +Read-only device metadata types returned by `GetDeviceInfoAsync` live in `Yubico.YubiKit.Core.YubiKey` (`DeviceInfo`, `DeviceCapabilities`, `DeviceFlags`, `FormFactor`, and `VersionQualifier`). Management owns device configuration and reset operations, but not those metadata model definitions. + ## Common Use Cases ### 1. Query Device Capabilities diff --git a/src/Management/examples/ManagementTool/Cli/Output/OutputHelpers.cs b/src/Management/examples/ManagementTool/Cli/Output/OutputHelpers.cs index bcf6f7cd7..436bb28d2 100644 --- a/src/Management/examples/ManagementTool/Cli/Output/OutputHelpers.cs +++ b/src/Management/examples/ManagementTool/Cli/Output/OutputHelpers.cs @@ -13,8 +13,9 @@ // limitations under the License. using Spectre.Console; -using SharedOutput = Yubico.YubiKit.Cli.Shared.Output.OutputHelpers; +using Yubico.YubiKit.Core.YubiKey; using SharedConfirm = Yubico.YubiKit.Cli.Shared.Output.ConfirmationPrompts; +using SharedOutput = Yubico.YubiKit.Cli.Shared.Output.OutputHelpers; namespace Yubico.YubiKit.Management.Examples.ManagementTool.Cli.Output; @@ -117,4 +118,4 @@ public static void WriteDeviceFlags(string label, DeviceFlags flags) WriteKeyValue(label, string.Join(", ", enabled)); } -} +} \ No newline at end of file diff --git a/src/Management/examples/ManagementTool/Commands/ConfigCommand.cs b/src/Management/examples/ManagementTool/Commands/ConfigCommand.cs index cfc014a96..08450e5b1 100644 --- a/src/Management/examples/ManagementTool/Commands/ConfigCommand.cs +++ b/src/Management/examples/ManagementTool/Commands/ConfigCommand.cs @@ -1,6 +1,7 @@ // Copyright 2026 Yubico AB // Licensed under the Apache License, Version 2.0. +using Yubico.YubiKit.Core.YubiKey; using Yubico.YubiKit.Management.Examples.ManagementTool.Cli.Output; using Yubico.YubiKit.Management.Examples.ManagementTool.Cli.Prompts; using Yubico.YubiKit.Management.Examples.ManagementTool.Features; @@ -72,4 +73,4 @@ public static async Task ExecuteAsync(CancellationToken cancellationToken = return 0; } -} +} \ No newline at end of file diff --git a/src/Management/examples/ManagementTool/Commands/InfoCommand.cs b/src/Management/examples/ManagementTool/Commands/InfoCommand.cs index 06e8504aa..99d700335 100644 --- a/src/Management/examples/ManagementTool/Commands/InfoCommand.cs +++ b/src/Management/examples/ManagementTool/Commands/InfoCommand.cs @@ -1,6 +1,7 @@ // Copyright 2026 Yubico AB // Licensed under the Apache License, Version 2.0. +using Yubico.YubiKit.Core.YubiKey; using Yubico.YubiKit.Management.Examples.ManagementTool.Cli.Output; using Yubico.YubiKit.Management.Examples.ManagementTool.Cli.Prompts; using Yubico.YubiKit.Management.Examples.ManagementTool.Features; @@ -67,4 +68,4 @@ public static async Task ExecuteAsync(CancellationToken cancellationToken = return 0; } -} +} \ No newline at end of file diff --git a/src/Management/examples/ManagementTool/Features/DeviceConfiguration.cs b/src/Management/examples/ManagementTool/Features/DeviceConfiguration.cs index 8dc0c368a..706663407 100644 --- a/src/Management/examples/ManagementTool/Features/DeviceConfiguration.cs +++ b/src/Management/examples/ManagementTool/Features/DeviceConfiguration.cs @@ -13,6 +13,7 @@ // limitations under the License. using Yubico.YubiKit.Core; +using Yubico.YubiKit.Core.YubiKey; using Yubico.YubiKit.Management.Examples.ManagementTool.Features.Results; namespace Yubico.YubiKit.Management.Examples.ManagementTool.Features; @@ -282,4 +283,4 @@ public static async Task RemoveLockCodeAsync( } } } -} +} \ No newline at end of file diff --git a/src/Management/examples/ManagementTool/Features/Results/DeviceInfoResult.cs b/src/Management/examples/ManagementTool/Features/Results/DeviceInfoResult.cs index bcab4c27a..9cc8805c2 100644 --- a/src/Management/examples/ManagementTool/Features/Results/DeviceInfoResult.cs +++ b/src/Management/examples/ManagementTool/Features/Results/DeviceInfoResult.cs @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +using Yubico.YubiKit.Core.YubiKey; + namespace Yubico.YubiKit.Management.Examples.ManagementTool.Features.Results; /// @@ -45,4 +47,4 @@ public static DeviceInfoResult Succeeded(DeviceInfo info) => /// public static DeviceInfoResult Failed(string error) => new() { Success = false, ErrorMessage = error }; -} +} \ No newline at end of file diff --git a/src/Management/src/IManagementSession.cs b/src/Management/src/IManagementSession.cs index 6c8fd046d..9c44162fe 100644 --- a/src/Management/src/IManagementSession.cs +++ b/src/Management/src/IManagementSession.cs @@ -13,6 +13,7 @@ // limitations under the License. using Yubico.YubiKit.Core.Interfaces; +using Yubico.YubiKit.Core.YubiKey; namespace Yubico.YubiKit.Management; @@ -28,4 +29,4 @@ Task SetDeviceConfigAsync( CancellationToken cancellationToken = default); Task ResetDeviceAsync(CancellationToken cancellationToken = default); -} +} \ No newline at end of file diff --git a/src/Management/src/IYubiKeyExtensions.cs b/src/Management/src/IYubiKeyExtensions.cs index 70388a24b..000b40749 100644 --- a/src/Management/src/IYubiKeyExtensions.cs +++ b/src/Management/src/IYubiKeyExtensions.cs @@ -15,6 +15,7 @@ using Yubico.YubiKit.Core.Interfaces; using Yubico.YubiKit.Core.SmartCard; using Yubico.YubiKit.Core.SmartCard.Scp; +using Yubico.YubiKit.Core.YubiKey; namespace Yubico.YubiKit.Management; diff --git a/src/Management/tests/Yubico.YubiKit.Management.IntegrationTests/ManagementSessionTests.cs b/src/Management/tests/Yubico.YubiKit.Management.IntegrationTests/ManagementSessionTests.cs index 24f2f2f8c..011e6ff07 100644 --- a/src/Management/tests/Yubico.YubiKit.Management.IntegrationTests/ManagementSessionTests.cs +++ b/src/Management/tests/Yubico.YubiKit.Management.IntegrationTests/ManagementSessionTests.cs @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +using Yubico.YubiKit.Core.YubiKey; using Yubico.YubiKit.Tests.Shared; using Yubico.YubiKit.Tests.Shared.Infrastructure; diff --git a/src/Piv/examples/PivTool/Cli/Commands/CliRunner.cs b/src/Piv/examples/PivTool/Cli/Commands/CliRunner.cs index c36e3a894..37afa17ee 100644 --- a/src/Piv/examples/PivTool/Cli/Commands/CliRunner.cs +++ b/src/Piv/examples/PivTool/Cli/Commands/CliRunner.cs @@ -13,6 +13,7 @@ // limitations under the License. using System.Security.Cryptography; +using Yubico.YubiKit.Core.YubiKey; using Yubico.YubiKit.Management; using Yubico.YubiKit.Piv.Examples.PivTool.PivExamples; @@ -138,7 +139,7 @@ private static async Task HandleDeviceInfoAsync( Console.WriteLine($"Security Key: {deviceInfo.IsSky}"); Console.WriteLine($"USB Supported: {deviceInfo.UsbSupported}"); Console.WriteLine($"USB Enabled: {deviceInfo.UsbEnabled}"); - if (deviceInfo.NfcSupported != Management.DeviceCapabilities.None) + if (deviceInfo.NfcSupported != DeviceCapabilities.None) { Console.WriteLine($"NFC Supported: {deviceInfo.NfcSupported}"); Console.WriteLine($"NFC Enabled: {deviceInfo.NfcEnabled}"); @@ -1040,4 +1041,4 @@ private static void ZeroAndReturn(params byte[]?[] buffers) } } } -} \ No newline at end of file +} diff --git a/src/Piv/examples/PivTool/Cli/Menus/DeviceInfoMenu.cs b/src/Piv/examples/PivTool/Cli/Menus/DeviceInfoMenu.cs index 778de5cdd..4996a940f 100644 --- a/src/Piv/examples/PivTool/Cli/Menus/DeviceInfoMenu.cs +++ b/src/Piv/examples/PivTool/Cli/Menus/DeviceInfoMenu.cs @@ -48,7 +48,7 @@ await AnsiConsole.Status() }); } - private static void DisplayDeviceDetails(Management.DeviceInfo info) + private static void DisplayDeviceDetails(DeviceInfo info) { OutputHelpers.WriteKeyValue("Serial Number", info.SerialNumber?.ToString()); OutputHelpers.WriteKeyValue("Firmware Version", info.VersionName); @@ -103,4 +103,4 @@ private static string FormatCapabilities(DeviceCapabilities caps) return string.Join(", ", parts); } -} \ No newline at end of file +} diff --git a/src/Tests.Shared/YubiKeyTestState.cs b/src/Tests.Shared/YubiKeyTestState.cs index 1fc1e8f50..3726c3bf8 100644 --- a/src/Tests.Shared/YubiKeyTestState.cs +++ b/src/Tests.Shared/YubiKeyTestState.cs @@ -26,7 +26,7 @@ namespace Yubico.YubiKit.Tests.Shared; /// /// /// -/// This class wraps an and its +/// This class wraps an and its /// for use with xUnit Theory tests via . /// /// diff --git a/src/Tests.Shared/YubiKeyTestStateExtensions.cs b/src/Tests.Shared/YubiKeyTestStateExtensions.cs index 8d4e6966a..523cb40b9 100644 --- a/src/Tests.Shared/YubiKeyTestStateExtensions.cs +++ b/src/Tests.Shared/YubiKeyTestStateExtensions.cs @@ -14,6 +14,7 @@ using Yubico.YubiKit.Core.SmartCard; using Yubico.YubiKit.Core.SmartCard.Scp; +using Yubico.YubiKit.Core.YubiKey; using Yubico.YubiKit.Management; namespace Yubico.YubiKit.Tests.Shared; From e2aa2192b3cf836509fd1296403b623efdac6e3b Mon Sep 17 00:00:00 2001 From: Dennis Dyallo Date: Tue, 9 Jun 2026 19:53:37 +0200 Subject: [PATCH 03/77] style: apply dotnet format across solution Bring the tree into compliance with the repository .editorconfig (import ordering, final-newline, and whitespace rules). dotnet format was not previously CI-enforced, so formatting had drifted across modules. No behavioral changes; solution builds clean. Run via: dotnet format --- src/Cli.Commands/src/Fido/FidoCommands.cs | 4 +- .../src/HsmAuth/HsmAuthCommands.cs | 4 +- .../src/Infrastructure/ExitCode.cs | 2 +- .../src/Infrastructure/GlobalSettings.cs | 4 +- .../Infrastructure/YkCommandInterceptor.cs | 2 +- .../src/Management/ManagementConfigCommand.cs | 4 +- .../src/Management/ManagementInfoCommand.cs | 4 +- .../src/Management/ManagementResetCommand.cs | 6 +- src/Cli.Commands/src/Oath/OathCommands.cs | 4 +- .../src/OpenPgp/OpenPgpAccessCommands.cs | 4 +- .../OpenPgp/OpenPgpCertificatesCommands.cs | 24 +- .../src/OpenPgp/OpenPgpHelpers.cs | 2 +- .../src/OpenPgp/OpenPgpInfoCommand.cs | 4 +- .../src/OpenPgp/OpenPgpKeysCommands.cs | 4 +- .../src/OpenPgp/OpenPgpResetCommand.cs | 4 +- src/Cli.Commands/src/Otp/OtpCommands.cs | 4 +- .../src/Cli/InteractiveMenuBuilder.cs | 2 +- src/Cli.Shared/src/Cli/SessionHelper.cs | 2 +- src/Cli/YkTool/Program.cs | 8 +- .../Credentials/ConsoleCredentialReader.cs | 2 +- .../Credentials/CredentialReaderOptions.cs | 2 +- .../src/Credentials/IConsoleInputSource.cs | 2 +- .../Credentials/ISecureCredentialReader.cs | 2 +- src/Core/src/Cryptography/AesUtilities.cs | 2 +- src/Core/src/Cryptography/ArkgPrimitives.cs | 2 +- .../src/Cryptography/ArkgPrimitivesOpenSsl.cs | 2 +- .../src/Cryptography/AsnPrivateKeyDecoder.cs | 2 +- .../src/Cryptography/AsnPrivateKeyEncoder.cs | 12 +- .../src/Cryptography/AsnPublicKeyDecoder.cs | 2 +- .../src/Cryptography/AsnPublicKeyEncoder.cs | 4 +- src/Core/src/Cryptography/AsnUtilities.cs | 2 +- .../Cose/CoseAlgorithmIdentifier.cs | 2 +- src/Core/src/Cryptography/Cose/CoseEcCurve.cs | 2 +- .../src/Cryptography/Cose/CoseEcPublicKey.cs | 2 +- .../Cryptography/Cose/CoseEdDsaPublicKey.cs | 2 +- src/Core/src/Cryptography/Cose/CoseKey.cs | 2 +- .../Cryptography/Cose/CoseKeyOperations.cs | 2 +- src/Core/src/Cryptography/Cose/CoseKeyType.cs | 2 +- .../src/Cryptography/CryptographyProviders.cs | 2 +- .../src/Cryptography/Curve25519PrivateKey.cs | 2 +- .../src/Cryptography/Curve25519PublicKey.cs | 2 +- src/Core/src/Cryptography/ECPrivateKey.cs | 3 +- src/Core/src/Cryptography/EcdsaVerify.cs | 2 +- src/Core/src/Cryptography/HkdfUtilities.cs | 4 +- src/Core/src/Cryptography/IArkgPrimitives.cs | 2 +- src/Core/src/Cryptography/IKeyBase.cs | 2 +- src/Core/src/Cryptography/IPrivateKey.cs | 2 +- .../src/Cryptography/IPrivateKeyExtensions.cs | 2 +- src/Core/src/Cryptography/IPublicKey.cs | 2 +- .../src/Cryptography/IPublicKeyExtensions.cs | 4 +- src/Core/src/Cryptography/KeyDefinitions.cs | 8 +- src/Core/src/Cryptography/KeyType.cs | 2 +- .../src/Cryptography/KeyTypeExtensions.cs | 12 +- src/Core/src/Cryptography/Oids.cs | 14 +- src/Core/src/Cryptography/PrivateKey.cs | 2 +- src/Core/src/Cryptography/PublicKey.cs | 2 +- .../Cryptography/RSAParametersExtensions.cs | 24 +- src/Core/src/Cryptography/RSAPrivateKey.cs | 2 +- src/Core/src/Cryptography/RSAPublicKey.cs | 2 +- .../Cryptography/RandomNumberGeneratorExt.cs | 4 +- src/Core/src/Cryptography/RsaFormat.cs | 2 +- src/Core/src/DeviceListenerStatus.cs | 2 +- src/Core/src/Hid/Constants/CtapConstants.cs | 8 +- src/Core/src/Hid/Constants/HidUsagePage.cs | 2 +- src/Core/src/Hid/Constants/KeyboardLayout.cs | 1 + src/Core/src/Hid/Fido/FidoHidConnection.cs | 2 +- src/Core/src/Hid/Fido/FidoHidProtocol.cs | 22 +- src/Core/src/Hid/Fido/FidoProtocolFactory.cs | 2 +- src/Core/src/Hid/Fido/IFidoHidConnection.cs | 2 +- src/Core/src/Hid/Fido/IFidoHidProtocol.cs | 10 +- src/Core/src/Hid/FindHidDevices.cs | 4 +- src/Core/src/Hid/HidDescriptorInfo.cs | 10 +- src/Core/src/Hid/HidDeviceListener.cs | 4 +- src/Core/src/Hid/HidInterfaceClassifier.cs | 8 +- src/Core/src/Hid/HidInterfaceType.cs | 6 +- src/Core/src/Hid/HidReportType.cs | 4 +- .../src/Hid/Interfaces/IOtpHidConnection.cs | 2 +- src/Core/src/Hid/Linux/LinuxHidDevice.cs | 26 +- .../src/Hid/Linux/LinuxHidDeviceListener.cs | 8 +- .../Linux/LinuxHidFeatureReportConnection.cs | 2 +- .../Hid/Linux/LinuxHidIOReportConnection.cs | 2 +- src/Core/src/Hid/MacOS/MacOSHidDevice.cs | 12 +- .../src/Hid/MacOS/MacOSHidDeviceListener.cs | 10 +- .../Hid/MacOS/MacOSHidIOReportConnection.cs | 6 +- src/Core/src/Hid/Otp/ChecksumUtils.cs | 2 +- src/Core/src/Hid/Otp/IOtpHidProtocol.cs | 2 +- src/Core/src/Hid/Otp/OtpConstants.cs | 2 +- src/Core/src/Hid/Otp/OtpHidConnection.cs | 2 +- src/Core/src/Hid/Otp/OtpHidProtocol.cs | 6 +- src/Core/src/Hid/Otp/OtpProtocolFactory.cs | 2 +- .../Hid/Windows/WindowsHidDeviceListener.cs | 6 +- .../src/Interfaces/IApplicationSession.cs | 2 +- src/Core/src/SmartCard/ApduCommand.cs | 8 +- .../src/SmartCard/ApduFormatterExtended.cs | 8 +- src/Core/src/SmartCard/ApduFormatterShort.cs | 2 +- src/Core/src/SmartCard/ApduTransmitter.cs | 2 +- .../DesktopSmartCardDeviceListener.cs | 4 +- src/Core/src/SmartCard/IApduFormatter.cs | 2 +- .../src/SmartCard/ISmartCardDeviceListener.cs | 2 +- src/Core/src/SmartCard/PcscProtocolFactory.cs | 6 +- src/Core/src/SmartCard/SWConstants.cs | 2 +- .../src/SmartCard/Scp/Scp11KeyParameters.cs | 2 +- src/Core/src/SmartCard/Scp/Scp11X963Kdf.cs | 7 +- src/Core/src/SmartCard/Scp/ScpProcessor.cs | 2 +- src/Core/src/Utils/BerLength.cs | 2 +- .../src/Utils/DisposableArrayPoolBuffer.cs | 4 +- src/Core/src/Utils/DisposableBufferHandle.cs | 8 +- src/Core/src/Utils/DisposableTlvDictionary.cs | 3 +- src/Core/src/YubiKey/DeviceCapabilities.cs | 2 +- src/Core/src/YubiKey/DeviceFlags.cs | 2 +- src/Core/src/YubiKey/DeviceInfo.cs | 2 +- src/Core/src/YubiKey/FormFactor.cs | 2 +- .../YubiKey/IYubiKeyDeviceMonitorService.cs | 2 +- src/Core/src/YubiKey/VersionQualifier.cs | 2 +- src/Core/src/YubiKey/YubiKeyDeviceManager.cs | 2 +- .../YubiKey/YubiKeyDeviceMonitorService.cs | 8 +- src/Core/src/YubiKitLogging.cs | 2 +- .../Core/CoreTests.cs | 4 +- .../Core/MonitorService_Disabled_Tests.cs | 4 +- .../Core/MonitorService_Enabled_Tests.cs | 6 +- .../Core/YubiKeyManagerTests.cs | 4 +- .../Hid/HidDeviceListenerIntegrationTests.cs | 2 +- .../Hid/HidEnumerationTests.cs | 6 +- ...SmartCardDeviceListenerIntegrationTests.cs | 2 +- .../ConsoleCredentialReaderTests.cs | 8 +- .../Credentials/HexParsingTests.cs | 2 +- .../Cryptography/ArkgP256Tests.cs | 2 +- .../Hid/HidDeviceListenerDisposalTests.cs | 4 +- .../Hid/Otp/OtpHidProtocolTests.cs | 2 +- ...topSmartCardDeviceListenerDisposalTests.cs | 4 +- .../SmartCard/Fakes/FakeApduProcessor.cs | 2 +- .../Fakes/FakeSmartCardConnection.cs | 2 +- .../SmartCard/PcscProtocolTests.cs | 2 +- .../SmartCard/Scp/Scp11Tests.cs | 14 +- .../SmartCard/Scp/Scp11X963KdfTests.cs | 2 +- .../Utils/DisposableArrayPoolBufferTests.cs | 2 +- .../Utils/TlvHelperTests.cs | 2 +- .../YubiKey/CapabilityMapperTests.cs | 2 +- .../YubiKey/DeviceInfoTests.cs | 2 +- .../YubiKeyDeviceManagerTests.cs | 2 +- .../YubiKeyDeviceMonitorServiceTests.cs | 2 +- .../YubiKeyManagerStaticTests.cs | 2 +- .../examples/FidoTool/Cli/Menus/AccessMenu.cs | 2 +- .../examples/FidoTool/Cli/Menus/ConfigMenu.cs | 2 +- .../FidoTool/Cli/Menus/CredentialMenu.cs | 2 +- .../FidoTool/Cli/Menus/CredentialsMenu.cs | 2 +- .../FidoTool/Cli/Menus/FingerprintsMenu.cs | 2 +- .../examples/FidoTool/Cli/Menus/InfoMenu.cs | 2 +- .../examples/FidoTool/Cli/Menus/ResetMenu.cs | 2 +- .../FidoTool/Cli/Output/OutputHelpers.cs | 4 +- .../FidoTool/Cli/Prompts/DeviceSelector.cs | 2 +- .../Cli/Prompts/FidoDeviceSelector.cs | 2 +- .../FidoTool/Cli/Prompts/FidoPinHelper.cs | 2 +- .../FidoTool/FidoExamples/BioEnrollment.cs | 2 +- .../FidoTool/FidoExamples/ConfigManagement.cs | 2 +- .../FidoExamples/CredentialManagement.cs | 2 +- .../FidoTool/FidoExamples/GetAssertion.cs | 2 +- .../FidoExamples/GetAuthenticatorInfo.cs | 2 +- .../FidoTool/FidoExamples/MakeCredential.cs | 2 +- .../FidoTool/FidoExamples/PinManagement.cs | 2 +- .../FidoExamples/ResetAuthenticator.cs | 2 +- src/Fido2/examples/FidoTool/Program.cs | 540 +++++++++--------- src/Fido2/src/Arkg/ArkgP256.cs | 2 +- src/Fido2/src/AuthenticatorInfo.cs | 206 +++---- .../src/BioEnrollment/BioEnrollmentModels.cs | 110 ++-- .../BioEnrollment/BioEnrollmentSubCommand.cs | 16 +- .../BioEnrollment/FingerprintBioEnrollment.cs | 200 +++---- src/Fido2/src/Cbor/CoseKeyWriter.cs | 2 +- src/Fido2/src/Cbor/CtapResponseParser.cs | 26 +- src/Fido2/src/Config/AuthenticatorConfig.cs | 64 +-- src/Fido2/src/Config/ConfigSubCommand.cs | 10 +- src/Fido2/src/Cose/CoseArkgP256SeedKey.cs | 2 +- .../CredManagementSubCommand.cs | 14 +- .../CredentialManagementModels.cs | 64 +-- .../src/Credentials/AttestationFormat.cs | 2 +- .../src/Credentials/AttestationStatement.cs | 2 +- .../src/Credentials/AuthenticatorData.cs | 68 +-- .../src/Credentials/CredentialOptions.cs | 46 +- .../src/Credentials/FidoCredentialOptions.cs | 2 +- .../src/Credentials/GetAssertionResponse.cs | 38 +- .../src/Credentials/MakeCredentialResponse.cs | 24 +- .../Credentials/PublicKeyCredentialTypes.cs | 96 ++-- .../src/Crypto/EncryptedMetadataDecryptor.cs | 28 +- src/Fido2/src/Ctap/CtapCommand.cs | 26 +- src/Fido2/src/Ctap/CtapException.cs | 14 +- src/Fido2/src/Ctap/CtapStatus.cs | 100 ++-- src/Fido2/src/DependencyInjection.cs | 2 +- .../CredBlob/CredBlobAssertionOutput.cs | 2 +- .../src/Extensions/CredBlob/CredBlobInput.cs | 2 +- .../CredBlob/CredBlobMakeCredentialOutput.cs | 2 +- .../CredProtect/CredProtectPolicy.cs | 6 +- .../Extensions/HmacSecret/HmacSecretInput.cs | 2 +- .../Extensions/HmacSecret/HmacSecretOutput.cs | 2 +- .../LargeBlob/LargeBlobAssertionInput.cs | 2 +- .../Extensions/LargeBlob/LargeBlobInput.cs | 2 +- .../Extensions/LargeBlob/LargeBlobOutput.cs | 2 +- .../Extensions/LargeBlob/LargeBlobSupport.cs | 2 +- .../MinPinLength/MinPinLengthInput.cs | 2 +- .../MinPinLength/MinPinLengthOutput.cs | 2 +- .../PreviewSign/ArkgP256SignArgs.cs | 2 +- .../Extensions/PreviewSign/CoseSignArgs.cs | 2 +- .../PreviewSignAuthenticationInput.cs | 2 +- .../PreviewSignAuthenticationOutput.cs | 2 +- .../Extensions/PreviewSign/PreviewSignCbor.cs | 2 +- .../PreviewSign/PreviewSignDerivedKey.cs | 2 +- .../PreviewSign/PreviewSignGeneratedKey.cs | 2 +- .../PreviewSignRegistrationInput.cs | 2 +- .../PreviewSignRegistrationOutput.cs | 2 +- .../PreviewSign/PreviewSignSigningParams.cs | 2 +- src/Fido2/src/Extensions/Prf/PrfInput.cs | 2 +- .../src/Extensions/Prf/PrfInputValues.cs | 2 +- src/Fido2/src/Extensions/Prf/PrfOutput.cs | 2 +- .../src/Extensions/Shared/ExtensionBuilder.cs | 60 +- .../Extensions/Shared/ExtensionIdentifiers.cs | 16 +- .../src/Extensions/Shared/ExtensionOutput.cs | 90 +-- src/Fido2/src/IFidoSession.cs | 14 +- src/Fido2/src/IYubiKeyExtensions.cs | 2 +- src/Fido2/src/LargeBlobs/LargeBlobData.cs | 78 +-- src/Fido2/src/LargeBlobs/LargeBlobStorage.cs | 124 ++-- src/Fido2/src/Pin/ClientPin.cs | 116 ++-- src/Fido2/src/Pin/ClientPinSubCommand.cs | 52 +- src/Fido2/src/Pin/IPinUvAuthProtocol.cs | 16 +- src/Fido2/src/Pin/PinUvAuthHelpers.cs | 2 +- src/Fido2/src/Pin/PinUvAuthProtocolV1.cs | 38 +- src/Fido2/src/Pin/PinUvAuthProtocolV2.cs | 58 +- .../FidoAlgorithmSupportTests.cs | 2 +- .../FidoAuthenticatorConfigTests.cs | 2 +- .../FidoBioEnrollmentTests.cs | 2 +- .../FidoCredBlobTests.cs | 2 +- .../FidoCredProtectTests.cs | 2 +- .../FidoCredentialManagementExtendedTests.cs | 2 +- .../FidoCredentialManagementTests.cs | 2 +- .../FidoEncryptedMetadataTests.cs | 2 +- .../FidoEnhancedPinTests.cs | 2 +- .../FidoEnterpriseAttestationTests.cs | 2 +- .../FidoFipsComplianceTests.cs | 2 +- .../FidoGetAssertionTests.cs | 2 +- .../FidoGetInfoTests.cs | 2 +- .../FidoHmacSecretTests.cs | 2 +- .../FidoLargeBlobTests.cs | 2 +- .../FidoMakeCredentialTests.cs | 2 +- .../FidoMinPinLengthTests.cs | 2 +- .../FidoPinManagementTests.cs | 2 +- .../FidoPreviewSignTests.cs | 2 +- .../FidoPrfTests.cs | 2 +- .../FidoSessionSimpleTests.cs | 2 +- .../FidoTestData.cs | 30 +- .../FidoTestHelpers.cs | 2 +- .../IntegrationTestBase.cs | 2 +- .../TestExtensions/FidoTestStateExtensions.cs | 2 +- .../AuthenticatorInfoTests.cs | 109 ++-- .../BioEnrollment/BioEnrollmentModelsTests.cs | 82 +-- .../FingerprintBioEnrollmentTests.cs | 182 +++--- .../Cbor/CtapResponseParserTests.cs | 108 ++-- .../Config/AuthenticatorConfigTests.cs | 142 ++--- .../Cose/CoseArkgP256SeedKeyTests.cs | 2 +- .../CredentialManagementModelsTests.cs | 158 ++--- .../Credentials/AttestationStatementTests.cs | 2 +- .../Credentials/AuthenticatorDataTests.cs | 56 +- .../Credentials/CredentialResponseTests.cs | 122 ++-- .../PublicKeyCredentialTypesTests.cs | 110 ++-- .../Crypto/EncryptedMetadataDecryptorTests.cs | 182 +++--- .../CtapExceptionTests.cs | 24 +- .../CredBlob/CredBlobAssertionOutputTests.cs | 2 +- .../Extensions/CredBlob/CredBlobInputTests.cs | 2 +- .../CredBlobMakeCredentialOutputTests.cs | 2 +- .../CredProtect/CredProtectPolicyTests.cs | 2 +- .../HmacSecret/HmacSecretInputTests.cs | 2 +- .../HmacSecret/HmacSecretOutputTests.cs | 2 +- .../LargeBlob/LargeBlobAssertionInputTests.cs | 2 +- .../LargeBlob/LargeBlobInputTests.cs | 2 +- .../LargeBlob/LargeBlobOutputTests.cs | 2 +- .../MinPinLength/MinPinLengthInputTests.cs | 2 +- .../MinPinLength/MinPinLengthOutputTests.cs | 2 +- .../PreviewSign/ArkgP256SignArgsTests.cs | 2 +- .../PreviewSign/CoseSignArgsTests.cs | 2 +- .../PreviewSign/PreviewSignCborTests.cs | 2 +- .../PreviewSignGeneratedKeyTests.cs | 2 +- .../Extensions/Prf/PrfInputTests.cs | 2 +- .../Extensions/Prf/PrfOutputTests.cs | 2 +- .../Shared/ExtensionBuilderTests.cs | 140 ++--- .../Shared/ExtensionIdentifiersTests.cs | 2 +- .../Extensions/Shared/ExtensionOutputTests.cs | 2 +- .../LargeBlobs/LargeBlobDataTests.cs | 126 ++-- .../LargeBlobs/LargeBlobStorageTests.cs | 136 ++--- .../MakeCredentialResponseTests.cs | 2 +- .../Pin/PinUvAuthProtocolV1Tests.cs | 136 ++--- .../Pin/PinUvAuthProtocolV2Tests.cs | 158 ++--- .../Cli/Menus/CapabilitiesMenu.cs | 2 +- .../Cli/Menus/DeviceFlagsMenu.cs | 2 +- .../Cli/Menus/DeviceInfoMenu.cs | 2 +- .../ManagementTool/Cli/Menus/LockCodeMenu.cs | 4 +- .../ManagementTool/Cli/Menus/ResetMenu.cs | 2 +- .../ManagementTool/Cli/Menus/TimeoutsMenu.cs | 10 +- .../Cli/Prompts/DeviceSelector.cs | 2 +- .../Cli/Prompts/LockCodePrompt.cs | 4 +- .../Cli/Prompts/ManagementDeviceSelector.cs | 2 +- .../Features/DeviceInfoQuery.cs | 2 +- .../ManagementTool/Features/DeviceReset.cs | 2 +- .../Features/Results/ConfigResult.cs | 2 +- .../Features/Results/ResetResult.cs | 2 +- .../examples/ManagementTool/Program.cs | 2 +- src/Management/src/DependencyInjection.cs | 2 +- .../ManagementLockCodeTests.cs | 2 +- .../ManagementSessionCapabilityTests.cs | 2 +- .../ManagementSessionSimpleTests.cs | 7 +- .../TestExtensions/TestStateExtensions.cs | 2 +- .../DependencyInjectionTests.cs | 2 +- .../OathTool/Cli/Prompts/DeviceSelector.cs | 2 +- .../Cli/Prompts/OathDeviceSelector.cs | 2 +- .../src/Credentials/OathCredentialOptions.cs | 2 +- src/Oath/src/DependencyInjection.cs | 2 +- src/Oath/src/IOathSession.cs | 2 +- src/Oath/src/IYubiKeyExtensions.cs | 2 +- .../OathHashAlgorithmTests.cs | 2 +- .../OathPasswordChangeTests.cs | 2 +- .../TestExtensions/OathTestStateExtensions.cs | 2 +- .../OathSessionTests.cs | 2 +- .../Cli/Commands/OpenPgpCommand.cs | 6 +- .../OpenPgpTool/Cli/Output/OutputHelpers.cs | 4 +- .../OpenPgpTool/Cli/Prompts/DeviceSelector.cs | 2 +- .../Cli/Prompts/OpenPgpDeviceSelector.cs | 2 +- .../Credentials/OpenPgpCredentialOptions.cs | 2 +- src/OpenPgp/src/DependencyInjection.cs | 2 +- .../OpenPgpAdvancedTests.cs | 2 +- .../OpenPgpDecryptTests.cs | 2 +- .../OpenPgpKeyImportTests.cs | 2 +- .../OpenPgpMultiCurveTests.cs | 2 +- .../OpenPgpPinManagementTests.cs | 2 +- .../OpenPgpSessionTests.cs | 2 +- .../AlgorithmAttributesTests.cs | 2 +- .../ApplicationRelatedDataTests.cs | 2 +- .../BcdHelperTests.cs | 2 +- .../CurveOidTests.cs | 2 +- .../DigestInfoTests.cs | 2 +- .../KdfTests.cs | 2 +- .../OpenPgpAidTests.cs | 2 +- .../PrivateKeyTemplateTests.cs | 2 +- .../PwStatusTests.cs | 2 +- .../PivAuthenticationProtocol.cs | 16 +- src/Piv/src/Bio/PivBioProtocol.cs | 52 +- .../Certificates/PivCertificateProtocol.cs | 16 +- .../PivCryptographicOperations.cs | 2 +- .../src/DataObjects/PivDataObjectProtocol.cs | 6 +- src/Piv/src/Keys/PivKeyProtocol.cs | 2 +- src/Piv/src/Metadata/PivMetadataProtocol.cs | 2 +- src/Piv/src/PivAlgorithm.cs | 14 +- src/Piv/src/PivDataObject.cs | 64 +-- src/Piv/src/PivFeatures.cs | 20 +- src/Piv/src/PivManagementKeyType.cs | 6 +- src/Piv/src/PivPinPolicy.cs | 10 +- src/Piv/src/PivPinUtilities.cs | 2 +- src/Piv/src/PivSession.cs | 62 +- src/Piv/src/PivSlot.cs | 48 +- src/Piv/src/PivSlotMetadataExtensions.cs | 6 +- src/Piv/src/PivTouchPolicy.cs | 6 +- src/Piv/src/TouchNotification.cs | 2 +- .../PivAuthenticationTests.cs | 38 +- .../PivCertificateTests.cs | 24 +- .../PivCompressedCertTests.cs | 2 +- .../PivCryptoTests.cs | 170 +++--- .../PivFullWorkflowTests.cs | 72 +-- .../PivImportTests.cs | 2 +- .../PivKeyOperationsTests.cs | 70 +-- .../PivManagementKeyTests.cs | 30 +- .../PivMetadataTests.cs | 28 +- .../PivPinRetryTests.cs | 2 +- .../PivPolicyTests.cs | 2 +- .../PivPukTests.cs | 44 +- .../PivResetTests.cs | 20 +- .../PivSigningAlgorithmTests.cs | 2 +- .../PivSlotOverwriteTests.cs | 2 +- .../PivSlotMetadataExtensionsTests.cs | 2 +- .../TouchNotificationTests.cs | 14 +- src/SecurityDomain/src/CaIdentifier.cs | 2 +- src/SecurityDomain/src/DependencyInjection.cs | 2 +- .../src/ISecurityDomainSession.cs | 2 +- src/SecurityDomain/src/KeyInfo.cs | 2 +- .../src/SecurityDomainKeyMaterial.cs | 2 +- .../src/SecurityDomainSession.cs | 2 +- .../src/SecurityDomainTlvEncoding.cs | 2 +- ...yDomainSession_DependencyInjectionTests.cs | 2 +- .../SecurityDomainSession_NegativeTests.cs | 2 +- ...ityDomainSession_Scp03KeyLifecycleTests.cs | 2 +- .../SecurityDomainSession_Scp03Tests.cs | 2 +- .../SecurityDomainSession_Scp11Tests.cs | 13 +- .../SecurityDomainSession_Scp11cTests.cs | 2 +- src/Tests.Shared/Infrastructure/AllowList.cs | 2 +- .../Infrastructure/AuthorizedDevices.cs | 3 +- .../Infrastructure/FilterCriteria.cs | 2 +- .../Infrastructure/TestCategories.cs | 2 +- .../WithYubiKeyTraitDiscoverer.cs | 2 +- .../RecordingSmartCardConnection.cs | 2 +- .../Controllers/DiTestController.cs | 9 +- src/Tests.TestProject/Program.cs | 9 +- .../Attestation/WebAuthnAttestationObject.cs | 2 +- .../src/Client/PinUvAuthTokenSession.cs | 2 +- .../src/Client/Status/StatusChannel.cs | 2 +- .../src/Client/Status/WebAuthnStatus.cs | 2 +- .../src/Client/UserVerification/UvDecision.cs | 2 +- .../src/Client/Validation/RpIdValidator.cs | 2 +- src/WebAuthn/src/Client/WebAuthnClientData.cs | 2 +- src/WebAuthn/src/Client/WebAuthnOrigin.cs | 2 +- .../Extensions/Adapters/CredPropsAdapter.cs | 2 +- .../Extensions/Adapters/CredProtectAdapter.cs | 2 +- .../src/Extensions/Inputs/CredPropsInput.cs | 2 +- .../src/Extensions/Inputs/CredProtectInput.cs | 2 +- .../src/Extensions/Outputs/CredPropsOutput.cs | 2 +- .../Extensions/Outputs/CredProtectOutput.cs | 2 +- .../src/Extensions/Outputs/LargeBlobOutput.cs | 2 +- .../src/Extensions/Outputs/PrfOutput.cs | 2 +- .../PreviewSign/ByteArrayKeyComparer.cs | 2 +- .../PreviewSignAuthenticationInput.cs | 2 +- .../PreviewSignAuthenticationOutput.cs | 2 +- .../PreviewSign/PreviewSignErrors.cs | 2 +- .../PreviewSign/PreviewSignFlags.cs | 2 +- .../PreviewSignRegistrationOutput.cs | 2 +- .../src/Internal/ExcludeListPreflight.cs | 2 +- .../src/Preferences/AttestationPreference.cs | 2 +- .../src/Preferences/ResidentKeyPreference.cs | 2 +- .../Preferences/UserVerificationPreference.cs | 2 +- src/WebAuthn/src/Util/Base64Url.cs | 2 +- src/WebAuthn/src/WebAuthnAuthenticatorData.cs | 2 +- src/WebAuthn/src/WebAuthnClientError.cs | 2 +- src/WebAuthn/src/WebAuthnTransport.cs | 2 +- .../PreviewSignTests.cs | 2 +- .../WebAuthnExcludeListStressTests.cs | 2 +- .../Attestation/AttestationObjectTests.cs | 2 +- .../Client/WebAuthnClientDataTests.cs | 2 +- .../Client/WebAuthnOriginTests.cs | 2 +- .../Cose/AaguidTests.cs | 2 +- .../Cose/Fixtures.cs | 2 +- .../Internal/ExcludeListPreflightTests.cs | 4 +- .../TestSupport/MockFido2Responses.cs | 2 +- .../WebAuthnAuthenticatorDataTests.cs | 2 +- .../HsmAuthTool/Cli/Commands/AccessCommand.cs | 4 +- .../HsmAuthTool/Cli/Commands/CommandArgs.cs | 2 +- .../Cli/Commands/CredentialsCommand.cs | 4 +- .../HsmAuthTool/Cli/Commands/InfoCommand.cs | 2 +- .../HsmAuthTool/Cli/Commands/ResetCommand.cs | 2 +- .../HsmAuthTool/Cli/Menus/CredentialMenu.cs | 2 +- .../Cli/Menus/ManagementKeyMenu.cs | 2 +- .../HsmAuthTool/Cli/Menus/ResetMenu.cs | 2 +- .../HsmAuthTool/Cli/Menus/SessionKeyMenu.cs | 2 +- .../HsmAuthTool/Cli/Output/OutputHelpers.cs | 4 +- .../HsmAuthTool/Cli/Prompts/DeviceSelector.cs | 2 +- .../Cli/Prompts/HsmAuthDeviceSelector.cs | 2 +- .../HsmAuthExamples/AddDerivedCredential.cs | 4 +- .../HsmAuthExamples/AddSymmetricCredential.cs | 4 +- .../HsmAuthExamples/CalculateSessionKeys.cs | 4 +- .../HsmAuthExamples/ChangeManagementKey.cs | 4 +- .../HsmAuthExamples/DeleteCredential.cs | 4 +- .../GenerateAsymmetricCredential.cs | 4 +- .../GetManagementKeyRetries.cs | 2 +- .../HsmAuthExamples/ListCredentials.cs | 2 +- .../HsmAuthExamples/ResetHsmAuth.cs | 2 +- src/YubiHsm/examples/HsmAuthTool/Program.cs | 2 +- src/YubiHsm/src/DependencyInjection.cs | 2 +- src/YubiHsm/src/HsmAuthAlgorithm.cs | 2 +- src/YubiHsm/src/HsmAuthCredential.cs | 2 +- src/YubiHsm/src/IHsmAuthSession.cs | 2 +- src/YubiHsm/src/IYubiKeyExtensions.cs | 2 +- src/YubiHsm/src/SessionKeys.cs | 2 +- .../HsmAuthAsymmetricTests.cs | 4 +- .../HsmAuthSessionTests.cs | 4 +- .../CredentialPasswordTests.cs | 2 +- .../HsmAuthCredentialTests.cs | 2 +- .../HsmAuthSessionByteLevelTests.cs | 2 +- .../LabelValidationTests.cs | 2 +- .../Pbkdf2DerivationTests.cs | 2 +- .../RetryExtractionTests.cs | 2 +- src/YubiOtp/src/ConfigFlag.cs | 2 +- src/YubiOtp/src/ConfigSlot.cs | 2 +- src/YubiOtp/src/DependencyInjection.cs | 2 +- src/YubiOtp/src/ExtendedFlag.cs | 2 +- src/YubiOtp/src/HmacSha1SlotConfiguration.cs | 2 +- src/YubiOtp/src/HotpSlotConfiguration.cs | 2 +- src/YubiOtp/src/IYubiKeyExtensions.cs | 2 +- src/YubiOtp/src/IYubiOtpBackend.cs | 2 +- src/YubiOtp/src/IYubiOtpSession.cs | 2 +- src/YubiOtp/src/KeyboardSlotConfiguration.cs | 2 +- src/YubiOtp/src/NdefType.cs | 2 +- src/YubiOtp/src/OtpHidBackend.cs | 2 +- src/YubiOtp/src/Slot.cs | 2 +- src/YubiOtp/src/SmartCardBackend.cs | 2 +- .../src/StaticPasswordSlotConfiguration.cs | 2 +- .../src/StaticTicketSlotConfiguration.cs | 2 +- src/YubiOtp/src/TicketFlag.cs | 2 +- src/YubiOtp/src/UpdateConfiguration.cs | 2 +- src/YubiOtp/src/YubiOtpConstants.cs | 2 +- src/YubiOtp/src/YubiOtpSession.cs | 4 +- src/YubiOtp/src/YubiOtpSlotConfiguration.cs | 2 +- .../YubiOtpSessionIntegrationTests.cs | 2 +- .../YubiOtpSlotConfigTests.cs | 5 +- .../ConfigStateTests.cs | 2 +- .../OtpHidBackendTests.cs | 2 +- .../SlotConfigurationTests.cs | 2 +- .../SmartCardBackendTests.cs | 2 +- .../YubiOtpSessionTests.cs | 2 +- 499 files changed, 3220 insertions(+), 3225 deletions(-) diff --git a/src/Cli.Commands/src/Fido/FidoCommands.cs b/src/Cli.Commands/src/Fido/FidoCommands.cs index 1184cb068..b909103a3 100644 --- a/src/Cli.Commands/src/Fido/FidoCommands.cs +++ b/src/Cli.Commands/src/Fido/FidoCommands.cs @@ -6,8 +6,8 @@ using System.ComponentModel; using System.Security.Cryptography; using System.Text; -using Yubico.YubiKit.Cli.Shared.Output; using Yubico.YubiKit.Cli.Commands.Infrastructure; +using Yubico.YubiKit.Cli.Shared.Output; using Yubico.YubiKit.Core.YubiKey; using Yubico.YubiKit.Fido2; using Yubico.YubiKit.Fido2.BioEnrollment; @@ -1141,4 +1141,4 @@ CtapStatus.PinAuthBlocked or _ => ExitCode.GenericError }; -} +} \ No newline at end of file diff --git a/src/Cli.Commands/src/HsmAuth/HsmAuthCommands.cs b/src/Cli.Commands/src/HsmAuth/HsmAuthCommands.cs index f0fd68398..8f6164c44 100644 --- a/src/Cli.Commands/src/HsmAuth/HsmAuthCommands.cs +++ b/src/Cli.Commands/src/HsmAuth/HsmAuthCommands.cs @@ -5,8 +5,8 @@ using Spectre.Console.Cli; using System.ComponentModel; using System.Security.Cryptography; -using Yubico.YubiKit.Cli.Shared.Output; using Yubico.YubiKit.Cli.Commands.Infrastructure; +using Yubico.YubiKit.Cli.Shared.Output; using Yubico.YubiKit.Core.YubiKey; using Yubico.YubiKit.YubiHsm; @@ -391,4 +391,4 @@ await session.GenerateCredentialAsymmetricAsync( CryptographicOperations.ZeroMemory(mgmtKey); } } -} +} \ No newline at end of file diff --git a/src/Cli.Commands/src/Infrastructure/ExitCode.cs b/src/Cli.Commands/src/Infrastructure/ExitCode.cs index 06159e07e..73ae48ac8 100644 --- a/src/Cli.Commands/src/Infrastructure/ExitCode.cs +++ b/src/Cli.Commands/src/Infrastructure/ExitCode.cs @@ -26,4 +26,4 @@ public static class ExitCode /// The requested feature is not supported on this device or firmware version. public const int FeatureUnsupported = 7; -} +} \ No newline at end of file diff --git a/src/Cli.Commands/src/Infrastructure/GlobalSettings.cs b/src/Cli.Commands/src/Infrastructure/GlobalSettings.cs index 9b46f4cee..349255918 100644 --- a/src/Cli.Commands/src/Infrastructure/GlobalSettings.cs +++ b/src/Cli.Commands/src/Infrastructure/GlobalSettings.cs @@ -1,8 +1,8 @@ // Copyright 2026 Yubico AB // Licensed under the Apache License, Version 2.0. -using System.ComponentModel; using Spectre.Console.Cli; +using System.ComponentModel; namespace Yubico.YubiKit.Cli.Commands.Infrastructure; @@ -23,4 +23,4 @@ public class GlobalSettings : CommandSettings [CommandOption("-i|--interactive")] [Description("Launch the interactive menu for this applet instead of running a command.")] public bool Interactive { get; set; } -} +} \ No newline at end of file diff --git a/src/Cli.Commands/src/Infrastructure/YkCommandInterceptor.cs b/src/Cli.Commands/src/Infrastructure/YkCommandInterceptor.cs index 85f59bf8e..e7fb563dc 100644 --- a/src/Cli.Commands/src/Infrastructure/YkCommandInterceptor.cs +++ b/src/Cli.Commands/src/Infrastructure/YkCommandInterceptor.cs @@ -22,4 +22,4 @@ public void Intercept(CommandContext context, CommandSettings settings) // Currently a no-op; individual commands read GlobalSettings directly // from their TSettings (which inherits GlobalSettings). } -} +} \ No newline at end of file diff --git a/src/Cli.Commands/src/Management/ManagementConfigCommand.cs b/src/Cli.Commands/src/Management/ManagementConfigCommand.cs index 510bd12d6..0c0f7b984 100644 --- a/src/Cli.Commands/src/Management/ManagementConfigCommand.cs +++ b/src/Cli.Commands/src/Management/ManagementConfigCommand.cs @@ -3,8 +3,8 @@ using Spectre.Console; using Spectre.Console.Cli; -using Yubico.YubiKit.Cli.Shared.Output; using Yubico.YubiKit.Cli.Commands.Infrastructure; +using Yubico.YubiKit.Cli.Shared.Output; using Yubico.YubiKit.Core.YubiKey; using Yubico.YubiKit.Management; @@ -77,4 +77,4 @@ private static string FormatCapabilities(DeviceCapabilities capabilities) => private static string FormatDeviceFlags(DeviceFlags flags) => flags == DeviceFlags.None ? "None" : flags.ToString(); -} +} \ No newline at end of file diff --git a/src/Cli.Commands/src/Management/ManagementInfoCommand.cs b/src/Cli.Commands/src/Management/ManagementInfoCommand.cs index cbf1c84c9..0ee17a9aa 100644 --- a/src/Cli.Commands/src/Management/ManagementInfoCommand.cs +++ b/src/Cli.Commands/src/Management/ManagementInfoCommand.cs @@ -3,8 +3,8 @@ using Spectre.Console; using Spectre.Console.Cli; -using Yubico.YubiKit.Cli.Shared.Output; using Yubico.YubiKit.Cli.Commands.Infrastructure; +using Yubico.YubiKit.Cli.Shared.Output; using Yubico.YubiKit.Core.YubiKey; using Yubico.YubiKit.Management; @@ -64,4 +64,4 @@ protected override Task ExecuteCommandAsync( private static string FormatCapabilities(DeviceCapabilities capabilities) => capabilities == DeviceCapabilities.None ? "None" : capabilities.ToString(); -} +} \ No newline at end of file diff --git a/src/Cli.Commands/src/Management/ManagementResetCommand.cs b/src/Cli.Commands/src/Management/ManagementResetCommand.cs index 93cf0ff81..f619b4e38 100644 --- a/src/Cli.Commands/src/Management/ManagementResetCommand.cs +++ b/src/Cli.Commands/src/Management/ManagementResetCommand.cs @@ -1,10 +1,10 @@ // Copyright 2026 Yubico AB // Licensed under the Apache License, Version 2.0. -using System.ComponentModel; using Spectre.Console.Cli; -using Yubico.YubiKit.Cli.Shared.Output; +using System.ComponentModel; using Yubico.YubiKit.Cli.Commands.Infrastructure; +using Yubico.YubiKit.Cli.Shared.Output; using Yubico.YubiKit.Core.YubiKey; using Yubico.YubiKit.Management; @@ -65,4 +65,4 @@ protected override async Task ExecuteCommandAsync( OutputHelpers.WriteSuccess("Device has been factory reset."); return ExitCode.Success; } -} +} \ No newline at end of file diff --git a/src/Cli.Commands/src/Oath/OathCommands.cs b/src/Cli.Commands/src/Oath/OathCommands.cs index e5ead2406..ff9625f37 100644 --- a/src/Cli.Commands/src/Oath/OathCommands.cs +++ b/src/Cli.Commands/src/Oath/OathCommands.cs @@ -6,8 +6,8 @@ using System.ComponentModel; using System.Security.Cryptography; using System.Text; -using Yubico.YubiKit.Cli.Shared.Output; using Yubico.YubiKit.Cli.Commands.Infrastructure; +using Yubico.YubiKit.Cli.Shared.Output; using Yubico.YubiKit.Core.YubiKey; using Yubico.YubiKit.Oath; using static Yubico.YubiKit.Cli.Commands.Oath.OathHelpers; @@ -547,4 +547,4 @@ protected override async Task ExecuteCommandAsync( return ExitCode.Success; } } -} +} \ No newline at end of file diff --git a/src/Cli.Commands/src/OpenPgp/OpenPgpAccessCommands.cs b/src/Cli.Commands/src/OpenPgp/OpenPgpAccessCommands.cs index 9bf0c3928..6ee37b0b6 100644 --- a/src/Cli.Commands/src/OpenPgp/OpenPgpAccessCommands.cs +++ b/src/Cli.Commands/src/OpenPgp/OpenPgpAccessCommands.cs @@ -5,8 +5,8 @@ using System.ComponentModel; using System.Security.Cryptography; using System.Text; -using Yubico.YubiKit.Cli.Shared.Output; using Yubico.YubiKit.Cli.Commands.Infrastructure; +using Yubico.YubiKit.Cli.Shared.Output; using Yubico.YubiKit.Core.YubiKey; using Yubico.YubiKit.OpenPgp; using static Yubico.YubiKit.Cli.Commands.OpenPgp.OpenPgpHelpers; @@ -304,4 +304,4 @@ protected override async Task ExecuteCommandAsync( } } } -} +} \ No newline at end of file diff --git a/src/Cli.Commands/src/OpenPgp/OpenPgpCertificatesCommands.cs b/src/Cli.Commands/src/OpenPgp/OpenPgpCertificatesCommands.cs index 3e2001ce1..7bc0af718 100644 --- a/src/Cli.Commands/src/OpenPgp/OpenPgpCertificatesCommands.cs +++ b/src/Cli.Commands/src/OpenPgp/OpenPgpCertificatesCommands.cs @@ -7,8 +7,8 @@ using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using System.Text; -using Yubico.YubiKit.Cli.Shared.Output; using Yubico.YubiKit.Cli.Commands.Infrastructure; +using Yubico.YubiKit.Cli.Shared.Output; using Yubico.YubiKit.Core.YubiKey; using Yubico.YubiKit.OpenPgp; using static Yubico.YubiKit.Cli.Commands.OpenPgp.OpenPgpHelpers; @@ -83,17 +83,17 @@ protected override async Task ExecuteCommandAsync( switch (format) { case "PEM": - { - var pem = PemEncoding.Write("CERTIFICATE", cert.RawData); - AnsiConsole.WriteLine(new string(pem)); - break; - } + { + var pem = PemEncoding.Write("CERTIFICATE", cert.RawData); + AnsiConsole.WriteLine(new string(pem)); + break; + } case "DER": - { - using var stdout = Console.OpenStandardOutput(); - stdout.Write(cert.RawData); - break; - } + { + using var stdout = Console.OpenStandardOutput(); + stdout.Write(cert.RawData); + break; + } default: OutputHelpers.WriteError($"Unsupported format: {settings.Format}. Use PEM or DER."); return ExitCode.GenericError; @@ -186,4 +186,4 @@ protected override async Task ExecuteCommandAsync( OutputHelpers.WriteSuccess($"Certificate deleted from {FormatKeyRef(keyRef)} slot."); return ExitCode.Success; } -} +} \ No newline at end of file diff --git a/src/Cli.Commands/src/OpenPgp/OpenPgpHelpers.cs b/src/Cli.Commands/src/OpenPgp/OpenPgpHelpers.cs index 10d849c59..0b90ecb54 100644 --- a/src/Cli.Commands/src/OpenPgp/OpenPgpHelpers.cs +++ b/src/Cli.Commands/src/OpenPgp/OpenPgpHelpers.cs @@ -51,4 +51,4 @@ public static bool ConfirmAction(string action, bool force) return ConfirmationPrompts.ConfirmDangerous(action); } -} +} \ No newline at end of file diff --git a/src/Cli.Commands/src/OpenPgp/OpenPgpInfoCommand.cs b/src/Cli.Commands/src/OpenPgp/OpenPgpInfoCommand.cs index 9d349ead9..282aeaf81 100644 --- a/src/Cli.Commands/src/OpenPgp/OpenPgpInfoCommand.cs +++ b/src/Cli.Commands/src/OpenPgp/OpenPgpInfoCommand.cs @@ -3,8 +3,8 @@ using Spectre.Console; using Spectre.Console.Cli; -using Yubico.YubiKit.Cli.Shared.Output; using Yubico.YubiKit.Cli.Commands.Infrastructure; +using Yubico.YubiKit.Cli.Shared.Output; using Yubico.YubiKit.Core.YubiKey; using Yubico.YubiKit.OpenPgp; @@ -160,4 +160,4 @@ private static string FormatEcAlgorithm(int algorithmId) => 0x16 => "EdDSA", _ => "EC" }; -} +} \ No newline at end of file diff --git a/src/Cli.Commands/src/OpenPgp/OpenPgpKeysCommands.cs b/src/Cli.Commands/src/OpenPgp/OpenPgpKeysCommands.cs index ba1b3aa13..355e19fbe 100644 --- a/src/Cli.Commands/src/OpenPgp/OpenPgpKeysCommands.cs +++ b/src/Cli.Commands/src/OpenPgp/OpenPgpKeysCommands.cs @@ -6,8 +6,8 @@ using System.ComponentModel; using System.Security.Cryptography; using System.Text; -using Yubico.YubiKit.Cli.Shared.Output; using Yubico.YubiKit.Cli.Commands.Infrastructure; +using Yubico.YubiKit.Cli.Shared.Output; using Yubico.YubiKit.Core.YubiKey; using Yubico.YubiKit.OpenPgp; using static Yubico.YubiKit.Cli.Commands.OpenPgp.OpenPgpHelpers; @@ -341,4 +341,4 @@ protected override async Task ExecuteCommandAsync( return ExitCode.Success; } -} +} \ No newline at end of file diff --git a/src/Cli.Commands/src/OpenPgp/OpenPgpResetCommand.cs b/src/Cli.Commands/src/OpenPgp/OpenPgpResetCommand.cs index e238806dc..60c433cbc 100644 --- a/src/Cli.Commands/src/OpenPgp/OpenPgpResetCommand.cs +++ b/src/Cli.Commands/src/OpenPgp/OpenPgpResetCommand.cs @@ -4,8 +4,8 @@ using Spectre.Console; using Spectre.Console.Cli; using System.ComponentModel; -using Yubico.YubiKit.Cli.Shared.Output; using Yubico.YubiKit.Cli.Commands.Infrastructure; +using Yubico.YubiKit.Cli.Shared.Output; using Yubico.YubiKit.Core.YubiKey; using Yubico.YubiKit.OpenPgp; @@ -48,4 +48,4 @@ await AnsiConsole.Status() OutputHelpers.WriteInfo("Default Admin PIN: 12345678"); return ExitCode.Success; } -} +} \ No newline at end of file diff --git a/src/Cli.Commands/src/Otp/OtpCommands.cs b/src/Cli.Commands/src/Otp/OtpCommands.cs index 6c4680ef8..dada4551b 100644 --- a/src/Cli.Commands/src/Otp/OtpCommands.cs +++ b/src/Cli.Commands/src/Otp/OtpCommands.cs @@ -6,8 +6,8 @@ using System.ComponentModel; using System.Security.Cryptography; using System.Text; -using Yubico.YubiKit.Cli.Shared.Output; using Yubico.YubiKit.Cli.Commands.Infrastructure; +using Yubico.YubiKit.Cli.Shared.Output; using Yubico.YubiKit.Core.YubiKey; using Yubico.YubiKit.YubiOtp; @@ -749,4 +749,4 @@ protected override async Task ExecuteCommandAsync( CryptographicOperations.ZeroMemory(accessCode); } } -} +} \ No newline at end of file diff --git a/src/Cli.Shared/src/Cli/InteractiveMenuBuilder.cs b/src/Cli.Shared/src/Cli/InteractiveMenuBuilder.cs index 94b4a3346..b26871bed 100644 --- a/src/Cli.Shared/src/Cli/InteractiveMenuBuilder.cs +++ b/src/Cli.Shared/src/Cli/InteractiveMenuBuilder.cs @@ -178,4 +178,4 @@ public async Task RunAsync(CancellationToken cancellationToken = default) return 0; } -} +} \ No newline at end of file diff --git a/src/Cli.Shared/src/Cli/SessionHelper.cs b/src/Cli.Shared/src/Cli/SessionHelper.cs index 874602550..f65452301 100644 --- a/src/Cli.Shared/src/Cli/SessionHelper.cs +++ b/src/Cli.Shared/src/Cli/SessionHelper.cs @@ -171,4 +171,4 @@ public static async Task WithStatusAsync( Func> operation) => await AnsiConsole.Status() .StartAsync(statusMessage, async _ => await operation()); -} +} \ No newline at end of file diff --git a/src/Cli/YkTool/Program.cs b/src/Cli/YkTool/Program.cs index 376783e7c..cf97fa279 100644 --- a/src/Cli/YkTool/Program.cs +++ b/src/Cli/YkTool/Program.cs @@ -2,14 +2,14 @@ // Licensed under the Apache License, Version 2.0. using Spectre.Console.Cli; +using Yubico.YubiKit.Cli.Commands.Fido; +using Yubico.YubiKit.Cli.Commands.HsmAuth; +using Yubico.YubiKit.Cli.Commands.Infrastructure; using Yubico.YubiKit.Cli.Commands.Management; using Yubico.YubiKit.Cli.Commands.Oath; using Yubico.YubiKit.Cli.Commands.OpenPgp; -using Yubico.YubiKit.Cli.Commands.HsmAuth; using Yubico.YubiKit.Cli.Commands.Otp; using Yubico.YubiKit.Cli.Commands.Piv; -using Yubico.YubiKit.Cli.Commands.Fido; -using Yubico.YubiKit.Cli.Commands.Infrastructure; var app = new CommandApp(); @@ -333,4 +333,4 @@ }); }); -return app.Run(args); +return app.Run(args); \ No newline at end of file diff --git a/src/Core/src/Credentials/ConsoleCredentialReader.cs b/src/Core/src/Credentials/ConsoleCredentialReader.cs index aee1e1aa2..09d7f7d99 100644 --- a/src/Core/src/Credentials/ConsoleCredentialReader.cs +++ b/src/Core/src/Credentials/ConsoleCredentialReader.cs @@ -323,4 +323,4 @@ internal ConsoleCredentialReader(IConsoleInputSource console) private static void ClearCharBuffer(char[] buffer, int length) => CryptographicOperations.ZeroMemory(MemoryMarshal.AsBytes(buffer.AsSpan(0, length))); -} +} \ No newline at end of file diff --git a/src/Core/src/Credentials/CredentialReaderOptions.cs b/src/Core/src/Credentials/CredentialReaderOptions.cs index 6b4ed5a0c..31a445a3c 100644 --- a/src/Core/src/Credentials/CredentialReaderOptions.cs +++ b/src/Core/src/Credentials/CredentialReaderOptions.cs @@ -131,4 +131,4 @@ public sealed record CredentialReaderOptions CharacterFilter = static c => char.IsAsciiHexDigit(c) || c is ' ' or ':' or '-' }; -} +} \ No newline at end of file diff --git a/src/Core/src/Credentials/IConsoleInputSource.cs b/src/Core/src/Credentials/IConsoleInputSource.cs index c954cabfa..5fce02443 100644 --- a/src/Core/src/Credentials/IConsoleInputSource.cs +++ b/src/Core/src/Credentials/IConsoleInputSource.cs @@ -139,4 +139,4 @@ public ConsoleKeyInfo ReadKey(bool intercept) public void Write(string text) => _output.Add(text); public void WriteLine(string text) => _output.Add(text + Environment.NewLine); -} +} \ No newline at end of file diff --git a/src/Core/src/Credentials/ISecureCredentialReader.cs b/src/Core/src/Credentials/ISecureCredentialReader.cs index 1e49a4a5b..8086b3b2d 100644 --- a/src/Core/src/Credentials/ISecureCredentialReader.cs +++ b/src/Core/src/Credentials/ISecureCredentialReader.cs @@ -76,4 +76,4 @@ public interface ISecureCredentialReader /// /// Thrown when cancellation is requested. IMemoryOwner? ReadCredentialWithConfirmation(CredentialReaderOptions options, CancellationToken cancellationToken = default); -} +} \ No newline at end of file diff --git a/src/Core/src/Cryptography/AesUtilities.cs b/src/Core/src/Cryptography/AesUtilities.cs index ed848d8bf..ddab9d209 100644 --- a/src/Core/src/Cryptography/AesUtilities.cs +++ b/src/Core/src/Cryptography/AesUtilities.cs @@ -222,4 +222,4 @@ public static ReadOnlyMemory AesCbcDecrypt(ReadOnlySpan decryptionKe public static byte[] AesCbcDecrypt(byte[] decryptionKey, byte[] iv, ReadOnlySpan ciphertext) => AesCbcDecrypt(decryptionKey.AsSpan(), iv.AsSpan(), ciphertext).ToArray(); } -} +} \ No newline at end of file diff --git a/src/Core/src/Cryptography/ArkgPrimitives.cs b/src/Core/src/Cryptography/ArkgPrimitives.cs index 3897c42ff..845318112 100644 --- a/src/Core/src/Cryptography/ArkgPrimitives.cs +++ b/src/Core/src/Cryptography/ArkgPrimitives.cs @@ -43,4 +43,4 @@ internal static class ArkgPrimitives /// /// public static IArkgPrimitives Create() => new ArkgPrimitivesOpenSsl(); -} +} \ No newline at end of file diff --git a/src/Core/src/Cryptography/ArkgPrimitivesOpenSsl.cs b/src/Core/src/Cryptography/ArkgPrimitivesOpenSsl.cs index bfac41e84..239a59da7 100644 --- a/src/Core/src/Cryptography/ArkgPrimitivesOpenSsl.cs +++ b/src/Core/src/Cryptography/ArkgPrimitivesOpenSsl.cs @@ -726,4 +726,4 @@ protected override bool ReleaseHandle() public override bool IsInvalid => handle == IntPtr.Zero; } -} +} \ No newline at end of file diff --git a/src/Core/src/Cryptography/AsnPrivateKeyDecoder.cs b/src/Core/src/Cryptography/AsnPrivateKeyDecoder.cs index c5f60f1b7..18646882a 100644 --- a/src/Core/src/Cryptography/AsnPrivateKeyDecoder.cs +++ b/src/Core/src/Cryptography/AsnPrivateKeyDecoder.cs @@ -296,4 +296,4 @@ public static RSAParameters CreateRSAParameters(ReadOnlyMemory pkcs8Encode return rsaParameters.NormalizeParameters(); } -} +} \ No newline at end of file diff --git a/src/Core/src/Cryptography/AsnPrivateKeyEncoder.cs b/src/Core/src/Cryptography/AsnPrivateKeyEncoder.cs index caab2af0f..7b1dfae03 100644 --- a/src/Core/src/Cryptography/AsnPrivateKeyEncoder.cs +++ b/src/Core/src/Cryptography/AsnPrivateKeyEncoder.cs @@ -123,10 +123,10 @@ public static byte[] EncodeToPkcs8(ECParameters parameters) { throw new ArgumentException("Private key parameter D must be provided."); } - - if(parameters.Curve.Oid.Value is null) + + if (parameters.Curve.Oid.Value is null) throw new ArgumentException("Curve OID is null."); - + ReadOnlyMemory privateKey = parameters.D; // Create public point if Q coordinates are available @@ -142,7 +142,7 @@ public static byte[] EncodeToPkcs8(ECParameters parameters) yCoordinate.CopyTo(uncompressedPoint[(1 + xCoordinate.Length)..]); publicPoint = uncompressedPoint; } - + var curveOid = parameters.Curve.Oid.Value; return EncodeECKey(privateKey, curveOid, publicPoint); } @@ -238,7 +238,7 @@ private static byte[] EncodeCurve25519Key(ReadOnlySpan privateKey, string // PrivateKey as OCTET STRING var privateKeyWriter = new AsnWriter(AsnEncodingRules.DER); privateKeyWriter.WriteOctetString(privateKey); - + using var privateKeyBytesHandle = new DisposableBufferHandle(privateKeyWriter.Encode()); writer.WriteOctetString(privateKeyBytesHandle.Data.Span); @@ -247,4 +247,4 @@ private static byte[] EncodeCurve25519Key(ReadOnlySpan privateKey, string return writer.Encode(); } -} +} \ No newline at end of file diff --git a/src/Core/src/Cryptography/AsnPublicKeyDecoder.cs b/src/Core/src/Cryptography/AsnPublicKeyDecoder.cs index ffa6be2af..a9d129e69 100644 --- a/src/Core/src/Cryptography/AsnPublicKeyDecoder.cs +++ b/src/Core/src/Cryptography/AsnPublicKeyDecoder.cs @@ -143,4 +143,4 @@ private static ECPublicKey CreateECPublicKey(string curveOid, byte[] subjectPubl return ECPublicKey.CreateFromParameters(ecParams); } -} +} \ No newline at end of file diff --git a/src/Core/src/Cryptography/AsnPublicKeyEncoder.cs b/src/Core/src/Cryptography/AsnPublicKeyEncoder.cs index 870f0f10b..697e954d4 100644 --- a/src/Core/src/Cryptography/AsnPublicKeyEncoder.cs +++ b/src/Core/src/Cryptography/AsnPublicKeyEncoder.cs @@ -124,7 +124,7 @@ public static byte[] EncodeToSubjectPublicKeyInfo(ECParameters parameters) if (parameters.Curve.Oid.Value is null) throw new ArgumentException("Curve OID is null."); - + var curveOid = parameters.Curve.Oid.Value; // Create the uncompressed EC point format: 0x04 || X || Y @@ -227,4 +227,4 @@ private static byte[] EncodeECDsaPublicKey(ReadOnlyMemory publicPoint, str return writer.Encode(); } -} +} \ No newline at end of file diff --git a/src/Core/src/Cryptography/AsnUtilities.cs b/src/Core/src/Cryptography/AsnUtilities.cs index 252eca520..ab8a29afd 100644 --- a/src/Core/src/Cryptography/AsnUtilities.cs +++ b/src/Core/src/Cryptography/AsnUtilities.cs @@ -108,4 +108,4 @@ private static int GetLeadingZeroCount(ReadOnlySpan data) ? data.Length - 1 // return last byte position : startIndex; // return first non-zero byte position } -} +} \ No newline at end of file diff --git a/src/Core/src/Cryptography/Cose/CoseAlgorithmIdentifier.cs b/src/Core/src/Cryptography/Cose/CoseAlgorithmIdentifier.cs index a811999f5..b758069da 100644 --- a/src/Core/src/Cryptography/Cose/CoseAlgorithmIdentifier.cs +++ b/src/Core/src/Cryptography/Cose/CoseAlgorithmIdentifier.cs @@ -62,4 +62,4 @@ public enum CoseAlgorithmIdentifier /// RS256 = -257, } -} +} \ No newline at end of file diff --git a/src/Core/src/Cryptography/Cose/CoseEcCurve.cs b/src/Core/src/Cryptography/Cose/CoseEcCurve.cs index a3e267bc3..b91e3b46d 100644 --- a/src/Core/src/Cryptography/Cose/CoseEcCurve.cs +++ b/src/Core/src/Cryptography/Cose/CoseEcCurve.cs @@ -65,4 +65,4 @@ public enum CoseEcCurve /// Ed448 = 7 } -} +} \ No newline at end of file diff --git a/src/Core/src/Cryptography/Cose/CoseEcPublicKey.cs b/src/Core/src/Cryptography/Cose/CoseEcPublicKey.cs index 50da240e2..909f4fd8d 100644 --- a/src/Core/src/Cryptography/Cose/CoseEcPublicKey.cs +++ b/src/Core/src/Cryptography/Cose/CoseEcPublicKey.cs @@ -328,4 +328,4 @@ // } // } // } -// } +// } \ No newline at end of file diff --git a/src/Core/src/Cryptography/Cose/CoseEdDsaPublicKey.cs b/src/Core/src/Cryptography/Cose/CoseEdDsaPublicKey.cs index d69b72df9..e328ba2b9 100644 --- a/src/Core/src/Cryptography/Cose/CoseEdDsaPublicKey.cs +++ b/src/Core/src/Cryptography/Cose/CoseEdDsaPublicKey.cs @@ -162,4 +162,4 @@ // ExceptionMessages.UnsupportedAlgorithm)); // } // } -// } +// } \ No newline at end of file diff --git a/src/Core/src/Cryptography/Cose/CoseKey.cs b/src/Core/src/Cryptography/Cose/CoseKey.cs index 625260097..fc811de3a 100644 --- a/src/Core/src/Cryptography/Cose/CoseKey.cs +++ b/src/Core/src/Cryptography/Cose/CoseKey.cs @@ -133,4 +133,4 @@ // return algorithm; // } // } -// } +// } \ No newline at end of file diff --git a/src/Core/src/Cryptography/Cose/CoseKeyOperations.cs b/src/Core/src/Cryptography/Cose/CoseKeyOperations.cs index 71ea4d4f7..fa43b1fc9 100644 --- a/src/Core/src/Cryptography/Cose/CoseKeyOperations.cs +++ b/src/Core/src/Cryptography/Cose/CoseKeyOperations.cs @@ -74,4 +74,4 @@ // /// // MacVerify = 10, // } -// } +// } \ No newline at end of file diff --git a/src/Core/src/Cryptography/Cose/CoseKeyType.cs b/src/Core/src/Cryptography/Cose/CoseKeyType.cs index 68c33951f..0287ed0e2 100644 --- a/src/Core/src/Cryptography/Cose/CoseKeyType.cs +++ b/src/Core/src/Cryptography/Cose/CoseKeyType.cs @@ -45,4 +45,4 @@ public enum CoseKeyType /// Symmetric = 4, } -} +} \ No newline at end of file diff --git a/src/Core/src/Cryptography/CryptographyProviders.cs b/src/Core/src/Cryptography/CryptographyProviders.cs index 85132d724..c37e6e96d 100644 --- a/src/Core/src/Cryptography/CryptographyProviders.cs +++ b/src/Core/src/Cryptography/CryptographyProviders.cs @@ -393,4 +393,4 @@ public static class CryptographyProviders // /// // public static Func HmacCreator { get; set; } = HMAC.Create; } -} +} \ No newline at end of file diff --git a/src/Core/src/Cryptography/Curve25519PrivateKey.cs b/src/Core/src/Cryptography/Curve25519PrivateKey.cs index 640a203a6..bcfd10e4e 100644 --- a/src/Core/src/Cryptography/Curve25519PrivateKey.cs +++ b/src/Core/src/Cryptography/Curve25519PrivateKey.cs @@ -107,4 +107,4 @@ public static Curve25519PrivateKey CreateFromPkcs8(ReadOnlyMemory pkcs8Enc /// Thrown if privateKey does not match expected format. public static Curve25519PrivateKey CreateFromValue(ReadOnlyMemory privateKey, KeyType keyType) => new(privateKey, keyType); -} +} \ No newline at end of file diff --git a/src/Core/src/Cryptography/Curve25519PublicKey.cs b/src/Core/src/Cryptography/Curve25519PublicKey.cs index 17edf4502..a5a31f43f 100644 --- a/src/Core/src/Cryptography/Curve25519PublicKey.cs +++ b/src/Core/src/Cryptography/Curve25519PublicKey.cs @@ -112,4 +112,4 @@ public static Curve25519PublicKey CreateFromValue(ReadOnlyMemory publicPoi [Obsolete("Use CreateFromSubjectPublicKeyInfo instead", false)] public static Curve25519PublicKey CreateFromPkcs8(ReadOnlyMemory subjectPublicKeyInfo) => CreateFromSubjectPublicKeyInfo(subjectPublicKeyInfo); -} +} \ No newline at end of file diff --git a/src/Core/src/Cryptography/ECPrivateKey.cs b/src/Core/src/Cryptography/ECPrivateKey.cs index 2bc9312ea..b0f834ab5 100644 --- a/src/Core/src/Cryptography/ECPrivateKey.cs +++ b/src/Core/src/Cryptography/ECPrivateKey.cs @@ -182,5 +182,4 @@ public ECDiffieHellman ToECDiffieHellman() return ECDiffieHellman.Create(Parameters); } } -} - +} \ No newline at end of file diff --git a/src/Core/src/Cryptography/EcdsaVerify.cs b/src/Core/src/Cryptography/EcdsaVerify.cs index 9c08c0509..c1a0f3fac 100644 --- a/src/Core/src/Cryptography/EcdsaVerify.cs +++ b/src/Core/src/Cryptography/EcdsaVerify.cs @@ -481,4 +481,4 @@ // GC.SuppressFinalize(this); // } // } -// } +// } \ No newline at end of file diff --git a/src/Core/src/Cryptography/HkdfUtilities.cs b/src/Core/src/Cryptography/HkdfUtilities.cs index 2a1ca2964..ed33d02c1 100644 --- a/src/Core/src/Cryptography/HkdfUtilities.cs +++ b/src/Core/src/Cryptography/HkdfUtilities.cs @@ -87,10 +87,10 @@ private static Memory HkdfExpand( currentBlock .AsSpan(0, bytesToCopy) .CopyTo(outputKeyMaterial.AsSpan(blockOffset)); - + previousBlock = currentBlock; } return outputKeyMaterial; } -} +} \ No newline at end of file diff --git a/src/Core/src/Cryptography/IArkgPrimitives.cs b/src/Core/src/Cryptography/IArkgPrimitives.cs index 5a485f66a..32279a6aa 100644 --- a/src/Core/src/Cryptography/IArkgPrimitives.cs +++ b/src/Core/src/Cryptography/IArkgPrimitives.cs @@ -78,4 +78,4 @@ internal interface IArkgPrimitives ReadOnlySpan pkKem, ReadOnlySpan ikm, ReadOnlySpan ctx); -} +} \ No newline at end of file diff --git a/src/Core/src/Cryptography/IKeyBase.cs b/src/Core/src/Cryptography/IKeyBase.cs index 2957f746d..747775ca7 100644 --- a/src/Core/src/Cryptography/IKeyBase.cs +++ b/src/Core/src/Cryptography/IKeyBase.cs @@ -30,4 +30,4 @@ public interface IKeyBase /// A value indicating the type of the key. /// public KeyType KeyType { get; } -} +} \ No newline at end of file diff --git a/src/Core/src/Cryptography/IPrivateKey.cs b/src/Core/src/Cryptography/IPrivateKey.cs index 7fb3cd782..e8bcf439a 100644 --- a/src/Core/src/Cryptography/IPrivateKey.cs +++ b/src/Core/src/Cryptography/IPrivateKey.cs @@ -36,4 +36,4 @@ public interface IPrivateKey : IKeyBase /// Clears the buffers containing private key data. /// public void Clear(); -} +} \ No newline at end of file diff --git a/src/Core/src/Cryptography/IPrivateKeyExtensions.cs b/src/Core/src/Cryptography/IPrivateKeyExtensions.cs index eff7209f1..b187ee0b2 100644 --- a/src/Core/src/Cryptography/IPrivateKeyExtensions.cs +++ b/src/Core/src/Cryptography/IPrivateKeyExtensions.cs @@ -43,4 +43,4 @@ public static class IPrivateKeyExtensions /// public static T Cast(this IPrivateKey key) where T : class, IPrivateKey => key as T ?? throw new InvalidCastException($"Cannot cast {key.GetType()} to {typeof(T)}"); -} +} \ No newline at end of file diff --git a/src/Core/src/Cryptography/IPublicKey.cs b/src/Core/src/Cryptography/IPublicKey.cs index c607087cb..25262fd2b 100644 --- a/src/Core/src/Cryptography/IPublicKey.cs +++ b/src/Core/src/Cryptography/IPublicKey.cs @@ -34,4 +34,4 @@ public interface IPublicKey : IKeyBase /// A byte array containing the X.509 SubjectPublicKeyInfo representation of the public-key portion of this key /// public byte[] ExportSubjectPublicKeyInfo(); -} +} \ No newline at end of file diff --git a/src/Core/src/Cryptography/IPublicKeyExtensions.cs b/src/Core/src/Cryptography/IPublicKeyExtensions.cs index 6b634b805..7de169fc4 100644 --- a/src/Core/src/Cryptography/IPublicKeyExtensions.cs +++ b/src/Core/src/Cryptography/IPublicKeyExtensions.cs @@ -23,6 +23,6 @@ public static class IPublicKeyExtensions /// /// /// - public static T Cast(this IPublicKey key) where T : class, IPublicKey + public static T Cast(this IPublicKey key) where T : class, IPublicKey => key as T ?? throw new InvalidCastException($"Cannot cast {key.GetType()} to {typeof(T)}"); -} +} \ No newline at end of file diff --git a/src/Core/src/Cryptography/KeyDefinitions.cs b/src/Core/src/Cryptography/KeyDefinitions.cs index e104749f2..10eaa1812 100644 --- a/src/Core/src/Cryptography/KeyDefinitions.cs +++ b/src/Core/src/Cryptography/KeyDefinitions.cs @@ -105,7 +105,7 @@ public static KeyDefinition GetByRSALength(int keySizeBits) throw new NotSupportedException($"Unsupported RSA length: {keySizeBits}"); } - + public static KeyDefinition GetByRSAModulusLength(byte[] modulus) => GetByRSALength(modulus.Length * 8); /// @@ -160,13 +160,13 @@ public static KeyDefinition GetByOid(string? oid) { throw new ArgumentNullException(nameof(oid)); } - + if (string.Equals(oid, Oids.RSA, StringComparison.OrdinalIgnoreCase)) { throw new NotSupportedException( "RSA keys are not supported by this method as all RSA keys share the same OID."); } - + if (string.Equals(oid, Oids.ECDSA, StringComparison.OrdinalIgnoreCase)) { throw new NotSupportedException( @@ -477,4 +477,4 @@ public class CoseKeyDefinition public CoseEcCurve CurveIdentifier { get; set; } // crv - Curve identifier public CoseAlgorithmIdentifier AlgorithmIdentifier { get; set; } // alg - Algorithm identifier } -} +} \ No newline at end of file diff --git a/src/Core/src/Cryptography/KeyType.cs b/src/Core/src/Cryptography/KeyType.cs index eb14b7b0d..7f59cbc92 100644 --- a/src/Core/src/Cryptography/KeyType.cs +++ b/src/Core/src/Cryptography/KeyType.cs @@ -33,4 +33,4 @@ public enum KeyType AES128, AES192, AES256 -} +} \ No newline at end of file diff --git a/src/Core/src/Cryptography/KeyTypeExtensions.cs b/src/Core/src/Cryptography/KeyTypeExtensions.cs index fc4d33a29..c05e87a19 100644 --- a/src/Core/src/Cryptography/KeyTypeExtensions.cs +++ b/src/Core/src/Cryptography/KeyTypeExtensions.cs @@ -17,19 +17,19 @@ namespace Yubico.YubiKit.Core.Cryptography; public static class KeyTypeExtensions { public static KeyDefinition GetKeyDefinition(this KeyType keyType) => KeyDefinitions.GetByKeyType(keyType); - public static string GetAlgorithmOid(this KeyType keyType) => Oids.GetOidsByKeyType(keyType).AlgorithmOid; - public static string? GetCurveOid(this KeyType keyType) => Oids.GetOidsByKeyType(keyType).Curveoid; + public static string GetAlgorithmOid(this KeyType keyType) => Oids.GetOidsByKeyType(keyType).AlgorithmOid; + public static string? GetCurveOid(this KeyType keyType) => Oids.GetOidsByKeyType(keyType).Curveoid; public static int GetKeySizeBits(this KeyType keyType) => keyType.GetKeyDefinition().LengthInBits; public static int GetKeySizeBytes(this KeyType keyType) => keyType.GetKeyDefinition().LengthInBytes; public static bool IsCoseKey(this KeyType keyType) => keyType.GetKeyDefinition().CoseKeyDefinition is not null; public static bool IsECDsa(this KeyType keyType) => keyType is KeyType.ECP256 or KeyType.ECP384 or KeyType.ECP521; public static bool IsCurve25519(this KeyType keyType) => keyType is KeyType.X25519 or KeyType.Ed25519; - public static bool IsAsymmetric(this KeyType keyType) => + public static bool IsAsymmetric(this KeyType keyType) => IsEllipticCurve(keyType) || IsRSA(keyType); - public static bool IsSymmetric(this KeyType keyType) => + public static bool IsSymmetric(this KeyType keyType) => keyType is KeyType.TripleDES or KeyType.AES128 or KeyType.AES192 or KeyType.AES256; - public static bool IsEllipticCurve(this KeyType keyType) => + public static bool IsEllipticCurve(this KeyType keyType) => keyType is KeyType.ECP256 or KeyType.ECP384 or KeyType.ECP521 or KeyType.X25519 or KeyType.Ed25519; public static bool IsRSA(this KeyType keyType) => keyType is KeyType.RSA1024 or KeyType.RSA2048 or KeyType.RSA3072 or KeyType.RSA4096; -} +} \ No newline at end of file diff --git a/src/Core/src/Cryptography/Oids.cs b/src/Core/src/Cryptography/Oids.cs index 2e860ebc4..ccec7dbf9 100644 --- a/src/Core/src/Cryptography/Oids.cs +++ b/src/Core/src/Cryptography/Oids.cs @@ -53,22 +53,22 @@ public static class Oids /// Represents the OID for NIST P-521 curve (also known as secp521r1) /// public const string ECP521 = "1.3.132.0.35"; - + /// /// Represents the OID for AES-128 in CBC mode /// public const string AES128Cbc = "2.16.840.1.101.3.4.1.2"; - + /// /// Represents the OID for AES-192 in CBC mode /// public const string AES192Cbc = "2.16.840.1.101.3.4.1.22"; - + /// /// Represents the OID for AES-256 in CBC mode /// public const string AES256Cbc = "2.16.840.1.101.3.4.1.42"; - + /// /// Represents the OID for Triple DES in CBC mode /// @@ -92,12 +92,12 @@ public static (string AlgorithmOid, string? Curveoid) GetOidsByKeyType(KeyType k KeyType.X25519 => (X25519, null), KeyType.Ed25519 => (Ed25519, null), - + KeyType.AES128 => (AES128Cbc, null), KeyType.AES192 => (AES192Cbc, null), KeyType.AES256 => (AES256Cbc, null), KeyType.TripleDES => (TripleDESCbc, null), - + _ => throw new ArgumentException($"Unsupported key type: {keyType}") }; } @@ -106,4 +106,4 @@ public static (string AlgorithmOid, string? Curveoid) GetOidsByKeyType(KeyType k public static bool IsCurve25519Algorithm(string? algorithmOid) => algorithmOid is X25519 or Ed25519; } -} +} \ No newline at end of file diff --git a/src/Core/src/Cryptography/PrivateKey.cs b/src/Core/src/Cryptography/PrivateKey.cs index b4838b523..2ec4eab7a 100644 --- a/src/Core/src/Cryptography/PrivateKey.cs +++ b/src/Core/src/Cryptography/PrivateKey.cs @@ -70,4 +70,4 @@ protected void ThrowIfDisposed() throw new ObjectDisposedException(GetType().Name); } } -} +} \ No newline at end of file diff --git a/src/Core/src/Cryptography/PublicKey.cs b/src/Core/src/Cryptography/PublicKey.cs index d7bd84610..dbf6e6c9d 100644 --- a/src/Core/src/Cryptography/PublicKey.cs +++ b/src/Core/src/Cryptography/PublicKey.cs @@ -32,4 +32,4 @@ public abstract class PublicKey : IPublicKey /// public abstract byte[] ExportSubjectPublicKeyInfo(); -} +} \ No newline at end of file diff --git a/src/Core/src/Cryptography/RSAParametersExtensions.cs b/src/Core/src/Cryptography/RSAParametersExtensions.cs index a9f2fea04..604c09090 100644 --- a/src/Core/src/Cryptography/RSAParametersExtensions.cs +++ b/src/Core/src/Cryptography/RSAParametersExtensions.cs @@ -39,7 +39,7 @@ public static RSAParameters DeepCopy(this RSAParameters original) return copy; } - + /// /// Normalizes the RSA parameters to ensure consistent cross-platform behavior: /// - If D is present, it must have the same length as Modulus @@ -52,28 +52,28 @@ public static RSAParameters DeepCopy(this RSAParameters original) internal static RSAParameters NormalizeParameters(this RSAParameters parameters) { var normalized = parameters.DeepCopy(); - if (normalized.D is null || normalized.P is null || normalized.Q is null || + if (normalized.D is null || normalized.P is null || normalized.Q is null || normalized.DP is null || normalized.DQ is null || normalized.InverseQ is null || normalized.Modulus is null) { return normalized; // Can't normalize if missing required components } - + // For private key, we need D to be same length as Modulus, // and P, Q, DP, DQ, InverseQ to be half the length of Modulus (rounded up) var modulusLength = normalized.Modulus.Length; var halfLength = (modulusLength + 1) / 2; // Round up - + normalized.D = PadToLength(normalized.D, modulusLength); normalized.P = PadToLength(normalized.P, halfLength); normalized.Q = PadToLength(normalized.Q, halfLength); normalized.DP = PadToLength(normalized.DP, halfLength); normalized.DQ = PadToLength(normalized.DQ, halfLength); normalized.InverseQ = PadToLength(normalized.InverseQ, halfLength); - + return normalized; } - + /// /// Pads a byte array to the specified length by adding leading zeros. /// This preserves the value of the array while ensuring it meets length requirements. @@ -85,18 +85,18 @@ private static byte[] PadToLength(byte[] data, int targetLength) { return data; } - + if (data.Length > targetLength) { - return data; + return data; } - + // Pad with zeros at the beginning (most significant bytes) var result = new byte[targetLength]; var padding = targetLength - data.Length; System.Buffer.BlockCopy(data, 0, result, padding, data.Length); - + return result; } - -} + +} \ No newline at end of file diff --git a/src/Core/src/Cryptography/RSAPrivateKey.cs b/src/Core/src/Cryptography/RSAPrivateKey.cs index d5268c08e..35e56efcc 100644 --- a/src/Core/src/Cryptography/RSAPrivateKey.cs +++ b/src/Core/src/Cryptography/RSAPrivateKey.cs @@ -116,4 +116,4 @@ public static RSAPrivateKey CreateFromPkcs8(ReadOnlyMemory encodedKey) /// A new instance of . /// public static RSAPrivateKey CreateFromParameters(RSAParameters parameters) => new(parameters); -} +} \ No newline at end of file diff --git a/src/Core/src/Cryptography/RSAPublicKey.cs b/src/Core/src/Cryptography/RSAPublicKey.cs index 5a4a40271..172438fb7 100644 --- a/src/Core/src/Cryptography/RSAPublicKey.cs +++ b/src/Core/src/Cryptography/RSAPublicKey.cs @@ -113,4 +113,4 @@ public static RSAPublicKey CreateFromSubjectPublicKeyInfo(ReadOnlyMemory s [Obsolete("Use CreateFromSubjectPublicKeyInfo instead", false)] public static RSAPublicKey CreateFromPkcs8(ReadOnlyMemory subjectPublicKeyInfo) => CreateFromSubjectPublicKeyInfo(subjectPublicKeyInfo); -} +} \ No newline at end of file diff --git a/src/Core/src/Cryptography/RandomNumberGeneratorExt.cs b/src/Core/src/Cryptography/RandomNumberGeneratorExt.cs index 646aadf37..3a6474552 100644 --- a/src/Core/src/Cryptography/RandomNumberGeneratorExt.cs +++ b/src/Core/src/Cryptography/RandomNumberGeneratorExt.cs @@ -45,7 +45,7 @@ public static int GetInt32( { throw new ArithmeticException(string.Format( CultureInfo.CurrentCulture, - "ExceptionMessages.ValueMustBeBetweenXandY,"+ + "ExceptionMessages.ValueMustBeBetweenXandY," + int.MinValue, (long)int.MaxValue + 1)); } @@ -106,4 +106,4 @@ public static byte GetByte( return (byte)rng.GetInt32(fromInclusive, toExclusive); } } -} +} \ No newline at end of file diff --git a/src/Core/src/Cryptography/RsaFormat.cs b/src/Core/src/Cryptography/RsaFormat.cs index 162b5849d..44f77bef4 100644 --- a/src/Core/src/Cryptography/RsaFormat.cs +++ b/src/Core/src/Cryptography/RsaFormat.cs @@ -1619,4 +1619,4 @@ // "ExceptionMessages.UnsupportedAlgorithm)),"); // }; // } -// } +// } \ No newline at end of file diff --git a/src/Core/src/DeviceListenerStatus.cs b/src/Core/src/DeviceListenerStatus.cs index ecce1855c..3fe73415d 100644 --- a/src/Core/src/DeviceListenerStatus.cs +++ b/src/Core/src/DeviceListenerStatus.cs @@ -33,4 +33,4 @@ public enum DeviceListenerStatus /// The listener encountered an error and stopped. /// Error -} +} \ No newline at end of file diff --git a/src/Core/src/Hid/Constants/CtapConstants.cs b/src/Core/src/Hid/Constants/CtapConstants.cs index 7294b41be..fcab77388 100644 --- a/src/Core/src/Hid/Constants/CtapConstants.cs +++ b/src/Core/src/Hid/Constants/CtapConstants.cs @@ -26,17 +26,17 @@ internal static class CtapConstants // Packet Structure public const int PacketSize = 64; public const int MaxPayloadSize = 7609; // 64 - 7 + 128 * (64 - 5) - + public const int InitHeaderSize = 7; public const int InitDataSize = PacketSize - InitHeaderSize; // 57 bytes - + public const int ContinuationHeaderSize = 5; public const int ContinuationDataSize = PacketSize - ContinuationHeaderSize; // 59 bytes // Channel Management public const uint BroadcastChannelId = 0xFFFFFFFF; public const int NonceSize = 8; - + // Bit masks public const byte InitPacketMask = 0x80; // Bit 7 set for init packets -} +} \ No newline at end of file diff --git a/src/Core/src/Hid/Constants/HidUsagePage.cs b/src/Core/src/Hid/Constants/HidUsagePage.cs index 6eb45cc3e..8138a8560 100644 --- a/src/Core/src/Hid/Constants/HidUsagePage.cs +++ b/src/Core/src/Hid/Constants/HidUsagePage.cs @@ -27,7 +27,7 @@ namespace Yubico.YubiKit.Core.Hid.Constants; /// /// [Obsolete("This enum is misleading. Use YubiKeyHidInterfaceType and HidInterfaceClassifier instead for proper interface type detection.")] -public enum HidUsagePage +public enum HidUsagePage { Unknown = 0, Fido = 0xF1D0, // 61904 - FIDO CTAP HID usage page diff --git a/src/Core/src/Hid/Constants/KeyboardLayout.cs b/src/Core/src/Hid/Constants/KeyboardLayout.cs index 65f2cfe46..eb8a8f419 100644 --- a/src/Core/src/Hid/Constants/KeyboardLayout.cs +++ b/src/Core/src/Hid/Constants/KeyboardLayout.cs @@ -14,6 +14,7 @@ // ReSharper disable InconsistentNaming namespace Yubico.YubiKit.Core.Hid.Constants; + public enum KeyboardLayout { ModHex = 0, diff --git a/src/Core/src/Hid/Fido/FidoHidConnection.cs b/src/Core/src/Hid/Fido/FidoHidConnection.cs index 050631ce5..c8ce02900 100644 --- a/src/Core/src/Hid/Fido/FidoHidConnection.cs +++ b/src/Core/src/Hid/Fido/FidoHidConnection.cs @@ -65,4 +65,4 @@ public ValueTask DisposeAsync() Dispose(); return ValueTask.CompletedTask; } -} +} \ No newline at end of file diff --git a/src/Core/src/Hid/Fido/FidoHidProtocol.cs b/src/Core/src/Hid/Fido/FidoHidProtocol.cs index 6d12c1966..ae0691841 100644 --- a/src/Core/src/Hid/Fido/FidoHidProtocol.cs +++ b/src/Core/src/Hid/Fido/FidoHidProtocol.cs @@ -44,7 +44,7 @@ public async Task> SendVendorCommandAsync( CancellationToken cancellationToken = default) { ObjectDisposedException.ThrowIf(_disposed, this); - + // Auto-initialize channel if not already done if (!IsChannelInitialized) { @@ -72,7 +72,7 @@ public async Task> TransmitAndReceiveAsync( CancellationToken cancellationToken = default) { ObjectDisposedException.ThrowIf(_disposed, this); - + // Auto-initialize channel if not already done if (!IsChannelInitialized) { @@ -85,7 +85,7 @@ public async Task> TransmitAndReceiveAsync( // For Management application, use CTAPHID_MSG (0x03) to send raw APDUs // Serialize the APDU command var apduBytes = SerializeApdu(command); - + // Send via CTAP HID MSG command var response = await TransmitCommand( _channelId!.Value, @@ -96,7 +96,7 @@ public async Task> TransmitAndReceiveAsync( // Parse response APDU var apduResponse = ParseApduResponse(response); - + if (!apduResponse.IsOK()) throw ApduException.FromResponse(apduResponse, command, "HID APDU command failed"); @@ -111,7 +111,7 @@ public async Task> SelectAsync( CancellationToken cancellationToken = default) { ObjectDisposedException.ThrowIf(_disposed, this); - + // Auto-initialize channel if not already done if (!IsChannelInitialized) { @@ -167,7 +167,7 @@ private void AcquireCtapHidChannel() // Extract channel ID (bytes 8-11, big-endian) _channelId = BinaryPrimitives.ReadUInt32BigEndian(response.Span[8..12]); - + // Extract firmware version (bytes 13-15) - skip protocol version byte at 12 if (response.Length >= 16) { @@ -177,7 +177,7 @@ private void AcquireCtapHidChannel() _firmwareVersion = new FirmwareVersion(major, minor, patch); _logger.LogDebug("Extracted firmware version from CTAPHID_INIT: {Version}", _firmwareVersion); } - + _logger.LogDebug("Acquired CTAP HID channel: 0x{ChannelId:X8}", _channelId.Value); } @@ -259,12 +259,12 @@ private async Task> ReceiveResponse( // Allocate buffer for complete response var responseData = new byte[responseLength]; var initDataLength = Math.Min(responseLength, CtapConstants.InitDataSize); - + // Ensure we don't try to read more data than the packet contains var availableDataInPacket = Math.Min(initDataLength, initPacket.Length - CtapConstants.InitHeaderSize); if (availableDataInPacket < 0) availableDataInPacket = 0; - + initPacket.Span.Slice(CtapConstants.InitHeaderSize, availableDataInPacket) .CopyTo(responseData); @@ -391,9 +391,9 @@ private static ApduResponse ParseApduResponse(ReadOnlyMemory response) public void Dispose() { if (_disposed) return; - + _channelId = null; _connection.Dispose(); _disposed = true; } -} +} \ No newline at end of file diff --git a/src/Core/src/Hid/Fido/FidoProtocolFactory.cs b/src/Core/src/Hid/Fido/FidoProtocolFactory.cs index c08ef9b3b..1c792bad7 100644 --- a/src/Core/src/Hid/Fido/FidoProtocolFactory.cs +++ b/src/Core/src/Hid/Fido/FidoProtocolFactory.cs @@ -26,4 +26,4 @@ public IFidoHidProtocol Create(IFidoHidConnection connection) /// public static FidoProtocolFactory Create(ILoggerFactory? loggerFactory = null) => new(loggerFactory ?? YubiKitLogging.LoggerFactory); -} +} \ No newline at end of file diff --git a/src/Core/src/Hid/Fido/IFidoHidConnection.cs b/src/Core/src/Hid/Fido/IFidoHidConnection.cs index 0f4e4c6f0..8b93a4923 100644 --- a/src/Core/src/Hid/Fido/IFidoHidConnection.cs +++ b/src/Core/src/Hid/Fido/IFidoHidConnection.cs @@ -40,4 +40,4 @@ public interface IFidoHidConnection : IConnection /// Cancellation token. /// The received packet (64 bytes). Task> ReceiveAsync(CancellationToken cancellationToken = default); -} +} \ No newline at end of file diff --git a/src/Core/src/Hid/Fido/IFidoHidProtocol.cs b/src/Core/src/Hid/Fido/IFidoHidProtocol.cs index dcfd841cb..a557eaa75 100644 --- a/src/Core/src/Hid/Fido/IFidoHidProtocol.cs +++ b/src/Core/src/Hid/Fido/IFidoHidProtocol.cs @@ -23,7 +23,7 @@ public interface IFidoHidProtocol : IProtocol Task> TransmitAndReceiveAsync( ApduCommand command, CancellationToken cancellationToken = default); - + /// /// Sends a CTAP vendor command and receives the response. /// Used for Management application over HID. @@ -36,7 +36,7 @@ Task> SendVendorCommandAsync( byte command, ReadOnlyMemory data, CancellationToken cancellationToken = default); - + /// /// Selects an application on the YubiKey. For HID, this returns version info. /// @@ -46,14 +46,14 @@ Task> SendVendorCommandAsync( Task> SelectAsync( ReadOnlyMemory applicationId, CancellationToken cancellationToken = default); - + /// /// Gets whether the HID channel has been initialized. /// bool IsChannelInitialized { get; } - + /// /// Gets the firmware version reported during channel initialization. /// FirmwareVersion? FirmwareVersion { get; } -} +} \ No newline at end of file diff --git a/src/Core/src/Hid/FindHidDevices.cs b/src/Core/src/Hid/FindHidDevices.cs index 7b8193d73..2b2ce3511 100644 --- a/src/Core/src/Hid/FindHidDevices.cs +++ b/src/Core/src/Hid/FindHidDevices.cs @@ -12,9 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -using System.Runtime.Versioning; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; +using System.Runtime.Versioning; using Yubico.YubiKit.Core.Hid.Interfaces; using Yubico.YubiKit.Core.Hid.Linux; using Yubico.YubiKit.Core.Hid.MacOS; @@ -84,4 +84,4 @@ private IReadOnlyList FindAllLinux() public static FindHidDevices Create(ILogger? logger = null) => new(logger ?? NullLogger.Instance); -} +} \ No newline at end of file diff --git a/src/Core/src/Hid/HidDescriptorInfo.cs b/src/Core/src/Hid/HidDescriptorInfo.cs index b5896eb1b..dfe3ae25d 100644 --- a/src/Core/src/Hid/HidDescriptorInfo.cs +++ b/src/Core/src/Hid/HidDescriptorInfo.cs @@ -28,26 +28,26 @@ public sealed record HidDescriptorInfo /// HID Usage Page value from descriptor (e.g., 0x01 for Generic Desktop, 0xF1D0 for FIDO). /// public ushort UsagePage { get; init; } - + /// /// HID Usage value from descriptor (e.g., 0x06 for Keyboard, 0x01 for U2F Device). /// public ushort Usage { get; init; } - + /// /// Device path or identifier. /// public string DevicePath { get; init; } = string.Empty; - + /// /// Vendor ID (e.g., 0x1050 for Yubico). /// public short VendorId { get; init; } - + /// /// Product ID. /// public short ProductId { get; init; } public override string ToString() => $"UsagePage=0x{UsagePage:X4}, Usage=0x{Usage:X4}, DevicePath={DevicePath}, VendorId=0x{VendorId:X4}, ProductId=0x{ProductId:X4}"; -} +} \ No newline at end of file diff --git a/src/Core/src/Hid/HidDeviceListener.cs b/src/Core/src/Hid/HidDeviceListener.cs index 50d0fb7ab..920b924c4 100644 --- a/src/Core/src/Hid/HidDeviceListener.cs +++ b/src/Core/src/Hid/HidDeviceListener.cs @@ -29,7 +29,7 @@ namespace Yubico.YubiKit.Core.Hid; public abstract class HidDeviceListener : IDisposable { private static readonly ILogger Logger = YubiKitLogging.CreateLogger(); - + private bool _disposed; /// @@ -119,4 +119,4 @@ public void Dispose() Dispose(disposing: true); GC.SuppressFinalize(this); } -} +} \ No newline at end of file diff --git a/src/Core/src/Hid/HidInterfaceClassifier.cs b/src/Core/src/Hid/HidInterfaceClassifier.cs index dee0d9672..dd9d98075 100644 --- a/src/Core/src/Hid/HidInterfaceClassifier.cs +++ b/src/Core/src/Hid/HidInterfaceClassifier.cs @@ -38,7 +38,7 @@ public static class HidInterfaceClassifier public static HidInterfaceType Classify(HidDescriptorInfo descriptorInfo) { ArgumentNullException.ThrowIfNull(descriptorInfo); - + return (descriptorInfo.UsagePage, descriptorInfo.Usage) switch { (UsagePageFido, UsageU2fDevice) => HidInterfaceType.Fido, @@ -46,7 +46,7 @@ public static HidInterfaceType Classify(HidDescriptorInfo descriptorInfo) _ => HidInterfaceType.Unknown }; } - + /// /// Checks if the HID descriptor represents a supported YubiKey interface. /// @@ -57,7 +57,7 @@ public static bool IsSupported(HidDescriptorInfo descriptorInfo) ArgumentNullException.ThrowIfNull(descriptorInfo); return Classify(descriptorInfo) != HidInterfaceType.Unknown; } - + /// /// Gets the expected report communication method for an interface type. /// @@ -71,4 +71,4 @@ public static HidReportType GetReportType(HidInterfaceType interfaceType) => HidInterfaceType.Otp => HidReportType.Feature, _ => throw new ArgumentException($"Unsupported interface type: {interfaceType}", nameof(interfaceType)) }; -} +} \ No newline at end of file diff --git a/src/Core/src/Hid/HidInterfaceType.cs b/src/Core/src/Hid/HidInterfaceType.cs index f09e10b74..558bbac6d 100644 --- a/src/Core/src/Hid/HidInterfaceType.cs +++ b/src/Core/src/Hid/HidInterfaceType.cs @@ -24,18 +24,18 @@ public enum HidInterfaceType /// Unknown or unsupported HID interface type. /// Unknown = 0, - + /// /// FIDO/U2F interface using CTAPHID protocol. /// Uses I/O reports (64-byte packets). /// Requires UsagePage=0xF1D0 (FIDO Alliance) and Usage=0x01 (U2F Device). /// Fido, - + /// /// OTP/YubiOTP interface using keyboard emulation. /// Uses feature reports (8-byte packets). /// Requires UsagePage=0x01 (Generic Desktop) and Usage=0x06 (Keyboard). /// Otp -} +} \ No newline at end of file diff --git a/src/Core/src/Hid/HidReportType.cs b/src/Core/src/Hid/HidReportType.cs index 9f663e59f..c68a8b6d8 100644 --- a/src/Core/src/Hid/HidReportType.cs +++ b/src/Core/src/Hid/HidReportType.cs @@ -24,10 +24,10 @@ public enum HidReportType /// Typically 64-byte packets for CTAPHID protocol. /// InputOutput, - + /// /// Feature reports (used for OTP). /// Typically 8-byte packets for YubiOTP protocol. /// Feature -} +} \ No newline at end of file diff --git a/src/Core/src/Hid/Interfaces/IOtpHidConnection.cs b/src/Core/src/Hid/Interfaces/IOtpHidConnection.cs index 3b6cd1b5b..8401e0ff2 100644 --- a/src/Core/src/Hid/Interfaces/IOtpHidConnection.cs +++ b/src/Core/src/Hid/Interfaces/IOtpHidConnection.cs @@ -40,4 +40,4 @@ public interface IOtpHidConnection : IConnection /// Cancellation token. /// The received report (8 bytes). Task> ReceiveAsync(CancellationToken cancellationToken = default); -} +} \ No newline at end of file diff --git a/src/Core/src/Hid/Linux/LinuxHidDevice.cs b/src/Core/src/Hid/Linux/LinuxHidDevice.cs index 0ac28e715..c4c8ccea7 100644 --- a/src/Core/src/Hid/Linux/LinuxHidDevice.cs +++ b/src/Core/src/Hid/Linux/LinuxHidDevice.cs @@ -30,14 +30,14 @@ namespace Yubico.YubiKit.Core.Hid.Linux; internal sealed class LinuxHidDevice : IHidDevice { private readonly string _devNode; - + public string ReaderName => _devNode; - + /// /// Raw HID descriptor information as reported by the operating system. /// public HidDescriptorInfo DescriptorInfo { get; } - + /// /// The classified YubiKey HID interface type. /// @@ -108,9 +108,9 @@ public static IReadOnlyList GetList() if (!device.IsInvalid) { var descriptorInfo = ParseHidDescriptor(device); - + // Only include Yubico devices with supported interface types - if (descriptorInfo.VendorId == 0x1050 && + if (descriptorInfo.VendorId == 0x1050 && HidInterfaceClassifier.IsSupported(descriptorInfo)) { devices.Add(new LinuxHidDevice(descriptorInfo)); @@ -243,22 +243,22 @@ private static short GetDevicePropertyAsShort(IntPtr device, string property) private static (ushort UsagePage, ushort Usage) GetHidUsageFromDescriptor(string devNode) { LinuxFileSafeHandle? handle = null; - + try { // Try opening with O_RDWR | O_NONBLOCK first (for descriptor reading) - handle = LibcNativeMethods.open(devNode, + handle = LibcNativeMethods.open(devNode, LibcNativeMethods.OpenFlags.O_RDWR | LibcNativeMethods.OpenFlags.O_NONBLOCK); - + if (handle.IsInvalid) { // Dispose the invalid handle before trying again handle.Dispose(); - + // O_RDWR failed, try O_RDONLY | O_NONBLOCK - handle = LibcNativeMethods.open(devNode, + handle = LibcNativeMethods.open(devNode, LibcNativeMethods.OpenFlags.O_RDONLY | LibcNativeMethods.OpenFlags.O_NONBLOCK); - + if (handle.IsInvalid) { return (0, 0); @@ -329,7 +329,7 @@ private static (ushort UsagePage, ushort Usage) ParseHidDescriptorBytes(ReadOnly // HID descriptor parsing - looking for Usage Page and Usage items // HID descriptor format: each item has a prefix byte followed by data // Prefix: bits 0-1 = size (0,1,2,4 bytes), bits 2-3 = type (0=main, 1=global, 2=local), bits 4-7 = tag - + // IMPORTANT: We need to validate the UsagePage + Usage combination // Don't just return the first values found - parse both, then validate @@ -394,4 +394,4 @@ private static (ushort UsagePage, ushort Usage) ParseHidDescriptorBytes(ReadOnly return (usagePage, usage); } -} +} \ No newline at end of file diff --git a/src/Core/src/Hid/Linux/LinuxHidDeviceListener.cs b/src/Core/src/Hid/Linux/LinuxHidDeviceListener.cs index 706f34a91..38739c8a0 100644 --- a/src/Core/src/Hid/Linux/LinuxHidDeviceListener.cs +++ b/src/Core/src/Hid/Linux/LinuxHidDeviceListener.cs @@ -12,8 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -using System.Runtime.InteropServices; using Microsoft.Extensions.Logging; +using System.Runtime.InteropServices; using Yubico.YubiKit.Core.PlatformInterop.Linux.Libc; using Yubico.YubiKit.Core.PlatformInterop.Linux.Udev; using LibcNativeMethods = Yubico.YubiKit.Core.PlatformInterop.Linux.Libc.NativeMethods; @@ -86,7 +86,7 @@ public override void Start() _monitorHandle, UdevNativeMethods.UdevSubsystemName, null); - + if (filterResult < 0) { Logger.LogWarning("Failed to add udev filter: {Result}", filterResult); @@ -197,7 +197,7 @@ private void ListenerThreadProc() { continue; } - + Logger.LogWarning("poll() failed with error: {Error}", error); continue; } @@ -299,4 +299,4 @@ protected override void Dispose(bool disposing) { Dispose(disposing: false); } -} +} \ No newline at end of file diff --git a/src/Core/src/Hid/Linux/LinuxHidFeatureReportConnection.cs b/src/Core/src/Hid/Linux/LinuxHidFeatureReportConnection.cs index f117e4cd6..87ea117ce 100644 --- a/src/Core/src/Hid/Linux/LinuxHidFeatureReportConnection.cs +++ b/src/Core/src/Hid/Linux/LinuxHidFeatureReportConnection.cs @@ -157,4 +157,4 @@ private void Dispose(bool disposing) { Dispose(false); } -} +} \ No newline at end of file diff --git a/src/Core/src/Hid/Linux/LinuxHidIOReportConnection.cs b/src/Core/src/Hid/Linux/LinuxHidIOReportConnection.cs index 419d89561..3ff28110f 100644 --- a/src/Core/src/Hid/Linux/LinuxHidIOReportConnection.cs +++ b/src/Core/src/Hid/Linux/LinuxHidIOReportConnection.cs @@ -263,4 +263,4 @@ private void Dispose(bool disposing) { Dispose(false); } -} +} \ No newline at end of file diff --git a/src/Core/src/Hid/MacOS/MacOSHidDevice.cs b/src/Core/src/Hid/MacOS/MacOSHidDevice.cs index 5a35a5b92..8fd8e4d5f 100644 --- a/src/Core/src/Hid/MacOS/MacOSHidDevice.cs +++ b/src/Core/src/Hid/MacOS/MacOSHidDevice.cs @@ -29,14 +29,14 @@ namespace Yubico.YubiKit.Core.Hid.MacOS; internal sealed class MacOSHidDevice : IHidDevice { private readonly long _entryId; - + public string ReaderName => _entryId.ToString(CultureInfo.InvariantCulture); - + /// /// Raw HID descriptor information as reported by the operating system. /// public HidDescriptorInfo DescriptorInfo { get; } - + /// /// The classified YubiKey HID interface type. /// @@ -83,9 +83,9 @@ public static IReadOnlyList GetList() UsagePage = (ushort)(IOKitHelpers.GetNullableIntPropertyValue(device, IOKitHidConstants.DevicePropertyPrimaryUsagePage) ?? 0), DevicePath = GetEntryId(device).ToString(CultureInfo.InvariantCulture) }; - + // Only include Yubico devices with supported interface types - if (descriptorInfo.VendorId == 0x1050 && + if (descriptorInfo.VendorId == 0x1050 && HidInterfaceClassifier.IsSupported(descriptorInfo)) { result.Add(new MacOSHidDevice(GetEntryId(device), descriptorInfo)); @@ -141,4 +141,4 @@ internal static long GetEntryId(IntPtr device) return entryId; } -} +} \ No newline at end of file diff --git a/src/Core/src/Hid/MacOS/MacOSHidDeviceListener.cs b/src/Core/src/Hid/MacOS/MacOSHidDeviceListener.cs index 303e4eac2..c9b3e8c3b 100644 --- a/src/Core/src/Hid/MacOS/MacOSHidDeviceListener.cs +++ b/src/Core/src/Hid/MacOS/MacOSHidDeviceListener.cs @@ -12,11 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. -using System.Text; using Microsoft.Extensions.Logging; +using System.Text; using Yubico.YubiKit.Core.PlatformInterop.MacOS.CoreFoundation; -using IOKitNativeMethods = Yubico.YubiKit.Core.PlatformInterop.MacOS.IOKitFramework.NativeMethods; using CFNativeMethods = Yubico.YubiKit.Core.PlatformInterop.MacOS.CoreFoundation.NativeMethods; +using IOKitNativeMethods = Yubico.YubiKit.Core.PlatformInterop.MacOS.IOKitFramework.NativeMethods; namespace Yubico.YubiKit.Core.Hid.MacOS; @@ -31,7 +31,7 @@ internal sealed class MacOSHidDeviceListener : HidDeviceListener { private static readonly TimeSpan CheckForChangesWaitTime = TimeSpan.FromMilliseconds(100); private static readonly TimeSpan MaxDisposalWaitTime = TimeSpan.FromSeconds(8); - + private static readonly ILogger Logger = YubiKitLogging.CreateLogger(); private readonly Lock _syncLock = new(); @@ -175,7 +175,7 @@ private void ListenerThreadProc() var modeBytes = Encoding.UTF8.GetBytes("kCFRunLoopDefaultMode"); _runLoopMode = CFNativeMethods.CFStringCreateWithCString( IntPtr.Zero, - [..modeBytes, 0], + [.. modeBytes, 0], 0x08000100); // kCFStringEncodingUTF8 // Schedule the HID manager with this run loop @@ -296,4 +296,4 @@ protected override void Dispose(bool disposing) { Dispose(disposing: false); } -} +} \ No newline at end of file diff --git a/src/Core/src/Hid/MacOS/MacOSHidIOReportConnection.cs b/src/Core/src/Hid/MacOS/MacOSHidIOReportConnection.cs index 71a36cf42..560858894 100644 --- a/src/Core/src/Hid/MacOS/MacOSHidIOReportConnection.cs +++ b/src/Core/src/Hid/MacOS/MacOSHidIOReportConnection.cs @@ -63,7 +63,7 @@ public MacOSHidIOReportConnection(long entryId) InputReportSize = IOKitHelpers.GetIntPropertyValue(_deviceHandle, IOKitHidConstants.MaxInputReportSize); OutputReportSize = IOKitHelpers.GetIntPropertyValue(_deviceHandle, IOKitHidConstants.MaxOutputReportSize); - + } public int InputReportSize { get; } @@ -73,7 +73,7 @@ public byte[] GetReport() { ObjectDisposedException.ThrowIf(_disposed, this); - + if (_reportsQueue.TryDequeue(out var report)) { return report; @@ -181,7 +181,7 @@ private static void ReportCallback( byte[] report, long reportLength) { - + if (result != 0 || type != IOKitHidConstants.kIOHidReportTypeInput || reportId != 0 || reportLength < 0) { return; diff --git a/src/Core/src/Hid/Otp/ChecksumUtils.cs b/src/Core/src/Hid/Otp/ChecksumUtils.cs index 26620387c..71dbedbf7 100644 --- a/src/Core/src/Hid/Otp/ChecksumUtils.cs +++ b/src/Core/src/Hid/Otp/ChecksumUtils.cs @@ -65,4 +65,4 @@ public static ushort CalculateCrc(ReadOnlySpan data, int length) /// True if the checksum is correct, false otherwise. public static bool CheckCrc(ReadOnlySpan data, int length) => CalculateCrc(data, length) == ValidResidue; -} +} \ No newline at end of file diff --git a/src/Core/src/Hid/Otp/IOtpHidProtocol.cs b/src/Core/src/Hid/Otp/IOtpHidProtocol.cs index 4b6312cb7..110b65058 100644 --- a/src/Core/src/Hid/Otp/IOtpHidProtocol.cs +++ b/src/Core/src/Hid/Otp/IOtpHidProtocol.cs @@ -47,4 +47,4 @@ Task> SendAndReceiveAsync( /// Gets the firmware version reported during protocol initialization. /// FirmwareVersion? FirmwareVersion { get; } -} +} \ No newline at end of file diff --git a/src/Core/src/Hid/Otp/OtpConstants.cs b/src/Core/src/Hid/Otp/OtpConstants.cs index 1e3841209..f91b8f181 100644 --- a/src/Core/src/Hid/Otp/OtpConstants.cs +++ b/src/Core/src/Hid/Otp/OtpConstants.cs @@ -105,4 +105,4 @@ public static class OtpConstants /// Write device info command. /// public const byte CmdYk4SetDeviceInfo = 0x15; -} +} \ No newline at end of file diff --git a/src/Core/src/Hid/Otp/OtpHidConnection.cs b/src/Core/src/Hid/Otp/OtpHidConnection.cs index 4caf173de..210867cbe 100644 --- a/src/Core/src/Hid/Otp/OtpHidConnection.cs +++ b/src/Core/src/Hid/Otp/OtpHidConnection.cs @@ -66,4 +66,4 @@ public ValueTask DisposeAsync() Dispose(); return ValueTask.CompletedTask; } -} +} \ No newline at end of file diff --git a/src/Core/src/Hid/Otp/OtpHidProtocol.cs b/src/Core/src/Hid/Otp/OtpHidProtocol.cs index 21363c6df..e7fffb5da 100644 --- a/src/Core/src/Hid/Otp/OtpHidProtocol.cs +++ b/src/Core/src/Hid/Otp/OtpHidProtocol.cs @@ -12,9 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -using System.Buffers.Binary; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; +using System.Buffers.Binary; using Yubico.YubiKit.Core.Hid.Interfaces; using Yubico.YubiKit.Core.SmartCard; using Yubico.YubiKit.Core.YubiKey; @@ -110,7 +110,7 @@ public async Task> SendAndReceiveAsync( _logger.LogTrace("Sending OTP slot command 0x{Slot:X2} with {Length} bytes payload", slot, data.Length); var programmingSequence = await SendFrameAsync(slot, payload, cancellationToken).ConfigureAwait(false); - + // Read response using Java-style single polling loop return await ReadFrameJavaStyleAsync(programmingSequence, cancellationToken).ConfigureAwait(false); } @@ -427,4 +427,4 @@ public void Dispose() _connection.Dispose(); _disposed = true; } -} +} \ No newline at end of file diff --git a/src/Core/src/Hid/Otp/OtpProtocolFactory.cs b/src/Core/src/Hid/Otp/OtpProtocolFactory.cs index 42cd30cbe..b0f9efc2e 100644 --- a/src/Core/src/Hid/Otp/OtpProtocolFactory.cs +++ b/src/Core/src/Hid/Otp/OtpProtocolFactory.cs @@ -27,4 +27,4 @@ public IOtpHidProtocol Create(IOtpHidConnection connection) /// public static OtpProtocolFactory Create(ILoggerFactory? loggerFactory = null) => new(loggerFactory ?? YubiKitLogging.LoggerFactory); -} +} \ No newline at end of file diff --git a/src/Core/src/Hid/Windows/WindowsHidDeviceListener.cs b/src/Core/src/Hid/Windows/WindowsHidDeviceListener.cs index 0e1d10ad0..0c914183b 100644 --- a/src/Core/src/Hid/Windows/WindowsHidDeviceListener.cs +++ b/src/Core/src/Hid/Windows/WindowsHidDeviceListener.cs @@ -12,8 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -using System.Runtime.InteropServices; using Microsoft.Extensions.Logging; +using System.Runtime.InteropServices; using Yubico.YubiKit.Core.PlatformInterop.Windows.Cfgmgr32; namespace Yubico.YubiKit.Core.Hid.Windows; @@ -28,7 +28,7 @@ namespace Yubico.YubiKit.Core.Hid.Windows; internal sealed class WindowsHidDeviceListener : HidDeviceListener { private static readonly ILogger Logger = YubiKitLogging.CreateLogger(); - + /// /// GUID for the HID device interface class. /// @@ -251,4 +251,4 @@ protected override void Dispose(bool disposing) { Dispose(disposing: false); } -} +} \ No newline at end of file diff --git a/src/Core/src/Interfaces/IApplicationSession.cs b/src/Core/src/Interfaces/IApplicationSession.cs index 802e06228..8b3057e9f 100644 --- a/src/Core/src/Interfaces/IApplicationSession.cs +++ b/src/Core/src/Interfaces/IApplicationSession.cs @@ -23,4 +23,4 @@ public interface IApplicationSession : IDisposable, IAsyncDisposable bool IsAuthenticated { get; } bool IsSupported(Feature feature); void EnsureSupports(Feature feature); -} +} \ No newline at end of file diff --git a/src/Core/src/SmartCard/ApduCommand.cs b/src/Core/src/SmartCard/ApduCommand.cs index 6af7e5f4e..5ff548322 100644 --- a/src/Core/src/SmartCard/ApduCommand.cs +++ b/src/Core/src/SmartCard/ApduCommand.cs @@ -44,10 +44,10 @@ public ApduCommand(int cla, int ins, int p1, int p2, ReadOnlyMemory data = { Cla = ByteUtils.ValidateByte(cla, nameof(cla)); Ins = ByteUtils.ValidateByte(ins, nameof(ins)); - P1 = ByteUtils.ValidateByte(p1, nameof(p1)); - P2 = ByteUtils.ValidateByte(p2, nameof(p2)); + P1 = ByteUtils.ValidateByte(p1, nameof(p1)); + P2 = ByteUtils.ValidateByte(p2, nameof(p2)); Data = data; - Le = le; + Le = le; } /// Gets the CLA byte. @@ -81,4 +81,4 @@ public override string ToString() => // NOTE: record struct auto-generates Equals/GetHashCode over all properties. // ReadOnlyMemory uses reference equality (pointer + offset + length), not content equality. // Do not use ApduCommand in Dictionary, HashSet, or == comparisons that expect content-based equality. -} +} \ No newline at end of file diff --git a/src/Core/src/SmartCard/ApduFormatterExtended.cs b/src/Core/src/SmartCard/ApduFormatterExtended.cs index ad32671b5..bb0cd2202 100644 --- a/src/Core/src/SmartCard/ApduFormatterExtended.cs +++ b/src/Core/src/SmartCard/ApduFormatterExtended.cs @@ -29,10 +29,10 @@ public Memory Format(byte cla, byte ins, byte p1, byte p2, ReadOnlyMemory< // Case 2E (no data, extended Le): CLA INS P1 P2 00 00 Le_hi Le_lo (7 bytes) -- simplified to Le_hi Le_lo for <=256 // Case 3E (data, no Le): CLA INS P1 P2 00 Lc_hi Lc_lo [data] (7+ bytes) // Case 4E (data, extended Le): CLA INS P1 P2 00 Lc_hi Lc_lo [data] Le_hi Le_lo (9+ bytes) - + bool hasData = data.Length > 0; bool hasLe = le > 0; - + int totalLength; if (!hasData && !hasLe) { @@ -54,7 +54,7 @@ public Memory Format(byte cla, byte ins, byte p1, byte p2, ReadOnlyMemory< // Case 4E: Header + 00 + 2-byte Lc + data + 2-byte Le totalLength = 4 + 1 + 2 + data.Length + 2; } - + if (totalLength > maxApduSize) throw new NotSupportedException("APDU length exceeds YubiKey capability."); @@ -96,4 +96,4 @@ public Memory Format(byte cla, byte ins, byte p1, byte p2, ReadOnlyMemory< return buffer.ToArray(); } -} +} \ No newline at end of file diff --git a/src/Core/src/SmartCard/ApduFormatterShort.cs b/src/Core/src/SmartCard/ApduFormatterShort.cs index 71a487675..49938696f 100644 --- a/src/Core/src/SmartCard/ApduFormatterShort.cs +++ b/src/Core/src/SmartCard/ApduFormatterShort.cs @@ -58,4 +58,4 @@ public Memory Format(byte cla, byte ins, byte p1, byte p2, ReadOnlyMemory< public Memory Format(ApduCommand apdu) => Format(apdu.Cla, apdu.Ins, apdu.P1, apdu.P2, apdu.Data, apdu.Le); -} +} \ No newline at end of file diff --git a/src/Core/src/SmartCard/ApduTransmitter.cs b/src/Core/src/SmartCard/ApduTransmitter.cs index b16bea6a6..f0a7f128c 100644 --- a/src/Core/src/SmartCard/ApduTransmitter.cs +++ b/src/Core/src/SmartCard/ApduTransmitter.cs @@ -45,4 +45,4 @@ public virtual async Task TransmitAsync(ApduCommand command, bool } } -} +} \ No newline at end of file diff --git a/src/Core/src/SmartCard/DesktopSmartCardDeviceListener.cs b/src/Core/src/SmartCard/DesktopSmartCardDeviceListener.cs index 016dbc06d..f0aceac7b 100644 --- a/src/Core/src/SmartCard/DesktopSmartCardDeviceListener.cs +++ b/src/Core/src/SmartCard/DesktopSmartCardDeviceListener.cs @@ -12,8 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -using System.Runtime.InteropServices; using Microsoft.Extensions.Logging; +using System.Runtime.InteropServices; using Yubico.YubiKit.Core.PlatformInterop.Desktop.SCard; namespace Yubico.YubiKit.Core.SmartCard; @@ -345,4 +345,4 @@ public void Dispose() _shouldStop = true; _context?.Dispose(); } -} +} \ No newline at end of file diff --git a/src/Core/src/SmartCard/IApduFormatter.cs b/src/Core/src/SmartCard/IApduFormatter.cs index c0c00296a..d0ed625d3 100644 --- a/src/Core/src/SmartCard/IApduFormatter.cs +++ b/src/Core/src/SmartCard/IApduFormatter.cs @@ -32,4 +32,4 @@ Memory Format( /// Transmitters may zero the returned buffer after transmission because formatted APDUs can contain secrets. /// Memory Format(ApduCommand apdu); -} +} \ No newline at end of file diff --git a/src/Core/src/SmartCard/ISmartCardDeviceListener.cs b/src/Core/src/SmartCard/ISmartCardDeviceListener.cs index ea177ec2b..43d3fa171 100644 --- a/src/Core/src/SmartCard/ISmartCardDeviceListener.cs +++ b/src/Core/src/SmartCard/ISmartCardDeviceListener.cs @@ -52,4 +52,4 @@ public interface ISmartCardDeviceListener : IDisposable /// Calling Stop() on an already stopped listener has no effect. /// void Stop(); -} +} \ No newline at end of file diff --git a/src/Core/src/SmartCard/PcscProtocolFactory.cs b/src/Core/src/SmartCard/PcscProtocolFactory.cs index dc729842f..f4fe342fe 100644 --- a/src/Core/src/SmartCard/PcscProtocolFactory.cs +++ b/src/Core/src/SmartCard/PcscProtocolFactory.cs @@ -28,16 +28,16 @@ public class PcscProtocolFactory(ILoggerFactory loggerFactory) where TConnection : IConnection { - public ISmartCardProtocol Create(TConnection connection) + public ISmartCardProtocol Create(TConnection connection) { if (connection is not ISmartCardConnection scConnection) throw new NotSupportedException( $"The connection type {typeof(TConnection).Name} is not supported by this protocol factory."); - + return new PcscProtocol(scConnection, logger: loggerFactory.CreateLogger()); } public static PcscProtocolFactory Create(ILoggerFactory? loggerFactory = null) => new(loggerFactory ?? YubiKitLogging.LoggerFactory); -} +} \ No newline at end of file diff --git a/src/Core/src/SmartCard/SWConstants.cs b/src/Core/src/SmartCard/SWConstants.cs index ad47edddf..cc8e2844e 100644 --- a/src/Core/src/SmartCard/SWConstants.cs +++ b/src/Core/src/SmartCard/SWConstants.cs @@ -279,4 +279,4 @@ public static bool IsVerifyFailWithRetries(short sw) => _ => sw >= 0 ? $"Unknown status word (SW=0x{sw:X4})" : $"Unknown success code (SW=0x{sw:X4})" } }; -} +} \ No newline at end of file diff --git a/src/Core/src/SmartCard/Scp/Scp11KeyParameters.cs b/src/Core/src/SmartCard/Scp/Scp11KeyParameters.cs index c55e7b6fb..c6637d409 100644 --- a/src/Core/src/SmartCard/Scp/Scp11KeyParameters.cs +++ b/src/Core/src/SmartCard/Scp/Scp11KeyParameters.cs @@ -67,7 +67,7 @@ public Scp11KeyParameters( public Scp11KeyParameters(KeyReference keyReference, ECPublicKey pkSdEcka) : this(keyReference, pkSdEcka, null, null, []) { - + } /// diff --git a/src/Core/src/SmartCard/Scp/Scp11X963Kdf.cs b/src/Core/src/SmartCard/Scp/Scp11X963Kdf.cs index f236fd570..ec64bde0a 100644 --- a/src/Core/src/SmartCard/Scp/Scp11X963Kdf.cs +++ b/src/Core/src/SmartCard/Scp/Scp11X963Kdf.cs @@ -39,8 +39,8 @@ ReadOnlyMemory sdReceipt // Yubikey receipt !TlvHelper.TryFindValue(0x81, a6Value.Span, out var keyLen)) throw new InvalidOperationException("Missing required tags (95, 80, 81) in A6 container"); - byte[] sharedInfo = [..keyUsage.Span, ..keyType.Span, ..keyLen.Span]; - byte[] keyAgreementData = [..oceAuthenticateData.Span, ..ePkSdEcka.Span]; + byte[] sharedInfo = [.. keyUsage.Span, .. keyType.Span, .. keyLen.Span]; + byte[] keyAgreementData = [.. oceAuthenticateData.Span, .. ePkSdEcka.Span]; var keyMaterial = GetSharedSecret(eSkOceEcka, skOceEcka, pkSdEcka, ePkSdEcka); const int keyCount = 5; @@ -103,7 +103,8 @@ private static ECDiffieHellmanPublicKey CreateECDiffieHellmanPublicKey(ReadOnlyM Curve = ECCurve.NamedCurves.nistP256, Q = new ECPoint { - X = ePkSdEckaEncodedPoint.Span[1..33].ToArray(), Y = ePkSdEckaEncodedPoint.Span[33..].ToArray() + X = ePkSdEckaEncodedPoint.Span[1..33].ToArray(), + Y = ePkSdEckaEncodedPoint.Span[33..].ToArray() } }; diff --git a/src/Core/src/SmartCard/Scp/ScpProcessor.cs b/src/Core/src/SmartCard/Scp/ScpProcessor.cs index b9d6ccf1f..62fb3b1d4 100644 --- a/src/Core/src/SmartCard/Scp/ScpProcessor.cs +++ b/src/Core/src/SmartCard/Scp/ScpProcessor.cs @@ -159,4 +159,4 @@ public Task TransmitAsync(ApduCommand command, bool useScp = true, CancellationToken cancellationToken = default) => TransmitAsync(command, useScp, true, cancellationToken); -} +} \ No newline at end of file diff --git a/src/Core/src/Utils/BerLength.cs b/src/Core/src/Utils/BerLength.cs index 6c98d2ff3..97f600bb4 100644 --- a/src/Core/src/Utils/BerLength.cs +++ b/src/Core/src/Utils/BerLength.cs @@ -53,4 +53,4 @@ public static int Write(Span destination, int length) return 1; } } -} +} \ No newline at end of file diff --git a/src/Core/src/Utils/DisposableArrayPoolBuffer.cs b/src/Core/src/Utils/DisposableArrayPoolBuffer.cs index 538fb3bb8..cf7e8c5dd 100644 --- a/src/Core/src/Utils/DisposableArrayPoolBuffer.cs +++ b/src/Core/src/Utils/DisposableArrayPoolBuffer.cs @@ -44,8 +44,8 @@ public DisposableArrayPoolBuffer(int size, bool clearOnCreate = true) } /// - public Memory Memory => _rentedBuffer is not null - ? _rentedBuffer.AsMemory(0, _length) + public Memory Memory => _rentedBuffer is not null + ? _rentedBuffer.AsMemory(0, _length) : throw new ObjectDisposedException(nameof(DisposableArrayPoolBuffer)); /// diff --git a/src/Core/src/Utils/DisposableBufferHandle.cs b/src/Core/src/Utils/DisposableBufferHandle.cs index 57a587caf..0f0a59983 100644 --- a/src/Core/src/Utils/DisposableBufferHandle.cs +++ b/src/Core/src/Utils/DisposableBufferHandle.cs @@ -33,10 +33,10 @@ public class DisposableBufferHandle(Memory data) : IDisposable public int Count => Length; private bool _disposed; - public Memory Data => _disposed - ? throw new ObjectDisposedException(nameof(DisposableBufferHandle)) + public Memory Data => _disposed + ? throw new ObjectDisposedException(nameof(DisposableBufferHandle)) : data; - + public void Dispose() { if (_disposed) @@ -53,4 +53,4 @@ public void Dispose() { Dispose(); } -} +} \ No newline at end of file diff --git a/src/Core/src/Utils/DisposableTlvDictionary.cs b/src/Core/src/Utils/DisposableTlvDictionary.cs index 787b225be..c58c1c930 100644 --- a/src/Core/src/Utils/DisposableTlvDictionary.cs +++ b/src/Core/src/Utils/DisposableTlvDictionary.cs @@ -106,5 +106,4 @@ // // { // // _tlvs = tlvs.ToDictionary(k => k.Tag, v => v); // // } -// } - +// } \ No newline at end of file diff --git a/src/Core/src/YubiKey/DeviceCapabilities.cs b/src/Core/src/YubiKey/DeviceCapabilities.cs index 94bf8c188..aa9de0ba5 100644 --- a/src/Core/src/YubiKey/DeviceCapabilities.cs +++ b/src/Core/src/YubiKey/DeviceCapabilities.cs @@ -59,4 +59,4 @@ public enum DeviceCapabilities /// This includes Otp, U2f, OpenPgp, Piv, Oath, HsmAuth, and Fido2. /// All = Otp | U2f | OpenPgp | Piv | Oath | HsmAuth | Fido2 -} +} \ No newline at end of file diff --git a/src/Core/src/YubiKey/DeviceFlags.cs b/src/Core/src/YubiKey/DeviceFlags.cs index 03e97149f..5035160fd 100644 --- a/src/Core/src/YubiKey/DeviceFlags.cs +++ b/src/Core/src/YubiKey/DeviceFlags.cs @@ -49,4 +49,4 @@ public enum DeviceFlags /// /// TouchEject = 0x80 -} +} \ No newline at end of file diff --git a/src/Core/src/YubiKey/DeviceInfo.cs b/src/Core/src/YubiKey/DeviceInfo.cs index 86faecf45..8c038bef1 100644 --- a/src/Core/src/YubiKey/DeviceInfo.cs +++ b/src/Core/src/YubiKey/DeviceInfo.cs @@ -249,4 +249,4 @@ public static DeviceCapabilities FromApp(ReadOnlyMemory appData) ? (DeviceCapabilities)appData.Span[0] : (DeviceCapabilities)BinaryPrimitives.ReadInt16BigEndian(appData.Span); } -} +} \ No newline at end of file diff --git a/src/Core/src/YubiKey/FormFactor.cs b/src/Core/src/YubiKey/FormFactor.cs index d83e11f89..0ed2ebf1d 100644 --- a/src/Core/src/YubiKey/FormFactor.cs +++ b/src/Core/src/YubiKey/FormFactor.cs @@ -55,4 +55,4 @@ public enum FormFactor /// The YubiKey is a USB-C key-chain device with a biometric (fingerprint) sensor. /// UsbCBiometricKeychain = 7 -} +} \ No newline at end of file diff --git a/src/Core/src/YubiKey/IYubiKeyDeviceMonitorService.cs b/src/Core/src/YubiKey/IYubiKeyDeviceMonitorService.cs index a16f7d7ce..25162dca8 100644 --- a/src/Core/src/YubiKey/IYubiKeyDeviceMonitorService.cs +++ b/src/Core/src/YubiKey/IYubiKeyDeviceMonitorService.cs @@ -51,4 +51,4 @@ internal interface IYubiKeyDeviceMonitorService : IAsyncDisposable /// Stops monitoring for device changes. /// void StopMonitoring(); -} +} \ No newline at end of file diff --git a/src/Core/src/YubiKey/VersionQualifier.cs b/src/Core/src/YubiKey/VersionQualifier.cs index d6d4e7dae..c579fb51c 100644 --- a/src/Core/src/YubiKey/VersionQualifier.cs +++ b/src/Core/src/YubiKey/VersionQualifier.cs @@ -98,4 +98,4 @@ obj is VersionQualifier other && Iteration == other.Iteration; public override int GetHashCode() => HashCode.Combine(FirmwareVersion, Type, Iteration); -} +} \ No newline at end of file diff --git a/src/Core/src/YubiKey/YubiKeyDeviceManager.cs b/src/Core/src/YubiKey/YubiKeyDeviceManager.cs index 1978c352f..fea976a3a 100644 --- a/src/Core/src/YubiKey/YubiKeyDeviceManager.cs +++ b/src/Core/src/YubiKey/YubiKeyDeviceManager.cs @@ -198,4 +198,4 @@ public static YubiKeyDeviceManager Create() return new YubiKeyDeviceManager(repository, monitorService); } -} +} \ No newline at end of file diff --git a/src/Core/src/YubiKey/YubiKeyDeviceMonitorService.cs b/src/Core/src/YubiKey/YubiKeyDeviceMonitorService.cs index 918560c8a..8bfb231c9 100644 --- a/src/Core/src/YubiKey/YubiKeyDeviceMonitorService.cs +++ b/src/Core/src/YubiKey/YubiKeyDeviceMonitorService.cs @@ -12,10 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. +using Microsoft.Extensions.Logging; using System.Reactive; using System.Reactive.Linq; using System.Reactive.Subjects; -using Microsoft.Extensions.Logging; using Yubico.YubiKit.Core.Hid; using Yubico.YubiKit.Core.SmartCard; @@ -106,10 +106,10 @@ public void StartMonitoring(TimeSpan interval) // Setup Rx subject for event coalescing _rescanTrigger = new Subject(); - + // Setup listeners BEFORE starting them SetupListeners(); - + _monitoringCts = new CancellationTokenSource(); _monitoringTask = Task.Run(() => MonitoringLoopAsync(_monitoringCts.Token)); @@ -335,4 +335,4 @@ public async ValueTask DisposeAsync() Logger.LogDebug("YubiKeyDeviceMonitorService disposed"); } -} +} \ No newline at end of file diff --git a/src/Core/src/YubiKitLogging.cs b/src/Core/src/YubiKitLogging.cs index 25a3d65e3..f6642bd38 100644 --- a/src/Core/src/YubiKitLogging.cs +++ b/src/Core/src/YubiKitLogging.cs @@ -66,4 +66,4 @@ public void Dispose() _restore = null; } } -} +} \ No newline at end of file diff --git a/src/Core/tests/Yubico.YubiKit.Core.IntegrationTests/Core/CoreTests.cs b/src/Core/tests/Yubico.YubiKit.Core.IntegrationTests/Core/CoreTests.cs index 910ff8803..7825da62f 100644 --- a/src/Core/tests/Yubico.YubiKit.Core.IntegrationTests/Core/CoreTests.cs +++ b/src/Core/tests/Yubico.YubiKit.Core.IntegrationTests/Core/CoreTests.cs @@ -10,9 +10,9 @@ public Task InitializeAsync() YubiKeyManager.StartMonitoring(); return Task.CompletedTask; } - + public async Task DisposeAsync() => await YubiKeyManager.ShutdownAsync(); - + [Fact] [Trait(TestCategories.Category, TestCategories.RequiresUserPresence)] [Trait(TestCategories.Category, TestCategories.Slow)] diff --git a/src/Core/tests/Yubico.YubiKit.Core.IntegrationTests/Core/MonitorService_Disabled_Tests.cs b/src/Core/tests/Yubico.YubiKit.Core.IntegrationTests/Core/MonitorService_Disabled_Tests.cs index 9a2670dad..6a751fae9 100644 --- a/src/Core/tests/Yubico.YubiKit.Core.IntegrationTests/Core/MonitorService_Disabled_Tests.cs +++ b/src/Core/tests/Yubico.YubiKit.Core.IntegrationTests/Core/MonitorService_Disabled_Tests.cs @@ -27,9 +27,9 @@ public Task InitializeAsync() // Don't start monitoring return Task.CompletedTask; } - + public async Task DisposeAsync() => await YubiKeyManager.ShutdownAsync(); - + [Fact] public async Task WhenMonitoringDisabled_StillFindsDevicesOnDemand() { diff --git a/src/Core/tests/Yubico.YubiKit.Core.IntegrationTests/Core/MonitorService_Enabled_Tests.cs b/src/Core/tests/Yubico.YubiKit.Core.IntegrationTests/Core/MonitorService_Enabled_Tests.cs index aa62c2352..fa7001dd8 100644 --- a/src/Core/tests/Yubico.YubiKit.Core.IntegrationTests/Core/MonitorService_Enabled_Tests.cs +++ b/src/Core/tests/Yubico.YubiKit.Core.IntegrationTests/Core/MonitorService_Enabled_Tests.cs @@ -26,16 +26,16 @@ public Task InitializeAsync() YubiKeyManager.StartMonitoring(); return Task.CompletedTask; } - + public async Task DisposeAsync() => await YubiKeyManager.ShutdownAsync(); - + [Fact] public async Task WhenMonitoringEnabled_FindsDevices() { var devices = await YubiKeyManager.FindAllAsync(ConnectionType.All); Assert.NotEmpty(devices); } - + [Fact] public void WhenMonitoringEnabled_IsMonitoringReturnsTrue() { diff --git a/src/Core/tests/Yubico.YubiKit.Core.IntegrationTests/Core/YubiKeyManagerTests.cs b/src/Core/tests/Yubico.YubiKit.Core.IntegrationTests/Core/YubiKeyManagerTests.cs index 486c2d173..e0c75ba3e 100644 --- a/src/Core/tests/Yubico.YubiKit.Core.IntegrationTests/Core/YubiKeyManagerTests.cs +++ b/src/Core/tests/Yubico.YubiKit.Core.IntegrationTests/Core/YubiKeyManagerTests.cs @@ -19,9 +19,9 @@ namespace Yubico.YubiKit.Core.IntegrationTests.Core; public class YubiKeyManagerTests : IAsyncLifetime { public Task InitializeAsync() => Task.CompletedTask; - + public async Task DisposeAsync() => await YubiKeyManager.ShutdownAsync(); - + [Fact] public async Task FindAllAsync_HasAtLeastOneDevice() { diff --git a/src/Core/tests/Yubico.YubiKit.Core.IntegrationTests/Hid/HidDeviceListenerIntegrationTests.cs b/src/Core/tests/Yubico.YubiKit.Core.IntegrationTests/Hid/HidDeviceListenerIntegrationTests.cs index a4e5c2bd0..20eee965e 100644 --- a/src/Core/tests/Yubico.YubiKit.Core.IntegrationTests/Hid/HidDeviceListenerIntegrationTests.cs +++ b/src/Core/tests/Yubico.YubiKit.Core.IntegrationTests/Hid/HidDeviceListenerIntegrationTests.cs @@ -208,4 +208,4 @@ public void Dispose_MultipleTimes_NoException() Assert.Null(exception); _output.WriteLine("Multiple dispose calls succeeded without exception"); } -} +} \ No newline at end of file diff --git a/src/Core/tests/Yubico.YubiKit.Core.IntegrationTests/Hid/HidEnumerationTests.cs b/src/Core/tests/Yubico.YubiKit.Core.IntegrationTests/Hid/HidEnumerationTests.cs index 135071b9c..9880b15e8 100644 --- a/src/Core/tests/Yubico.YubiKit.Core.IntegrationTests/Hid/HidEnumerationTests.cs +++ b/src/Core/tests/Yubico.YubiKit.Core.IntegrationTests/Hid/HidEnumerationTests.cs @@ -12,8 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -using System.Runtime.Versioning; using Microsoft.Extensions.Logging; +using System.Runtime.Versioning; using Xunit.Abstractions; using Yubico.YubiKit.Core.Hid; @@ -42,7 +42,7 @@ public async Task FindHidDevices_EnumeratesYubicoDevices() var loggerFactory = LoggerFactory.Create(builder => builder.AddConsole().SetMinimumLevel(LogLevel.Debug)); - + var finder = new FindHidDevices(loggerFactory.CreateLogger()); var devices = await finder.FindAllAsync(); @@ -85,4 +85,4 @@ public async Task FindYubiKeys_IncludesHidDevices() Assert.True(yubiKeys.Count >= 0, "Should not fail even if no devices present"); } -} +} \ No newline at end of file diff --git a/src/Core/tests/Yubico.YubiKit.Core.IntegrationTests/SmartCard/SmartCardDeviceListenerIntegrationTests.cs b/src/Core/tests/Yubico.YubiKit.Core.IntegrationTests/SmartCard/SmartCardDeviceListenerIntegrationTests.cs index f03e85963..04d541171 100644 --- a/src/Core/tests/Yubico.YubiKit.Core.IntegrationTests/SmartCard/SmartCardDeviceListenerIntegrationTests.cs +++ b/src/Core/tests/Yubico.YubiKit.Core.IntegrationTests/SmartCard/SmartCardDeviceListenerIntegrationTests.cs @@ -171,4 +171,4 @@ public async Task Dispose_CompletesWithin5Seconds() Assert.True(completedTask == disposeTask, "Dispose should complete within 5 seconds"); _output.WriteLine("Dispose completed within timeout"); } -} +} \ No newline at end of file diff --git a/src/Core/tests/Yubico.YubiKit.Core.UnitTests/Credentials/ConsoleCredentialReaderTests.cs b/src/Core/tests/Yubico.YubiKit.Core.UnitTests/Credentials/ConsoleCredentialReaderTests.cs index 29d27b521..447ace88d 100644 --- a/src/Core/tests/Yubico.YubiKit.Core.UnitTests/Credentials/ConsoleCredentialReaderTests.cs +++ b/src/Core/tests/Yubico.YubiKit.Core.UnitTests/Credentials/ConsoleCredentialReaderTests.cs @@ -208,15 +208,15 @@ public void ReadCredential_WithCancelledToken_ThrowsOperationCanceledException() // Arrange var mock = new MockConsoleInput(); // Don't enqueue any keys - the reader should check cancellation before blocking - + var reader = new ConsoleCredentialReader(mock); var cts = new CancellationTokenSource(); cts.Cancel(); - + var options = CredentialReaderOptions.ForPin(); // Act & Assert - Assert.Throws(() => + Assert.Throws(() => reader.ReadCredential(options, cts.Token)); } -} +} \ No newline at end of file diff --git a/src/Core/tests/Yubico.YubiKit.Core.UnitTests/Credentials/HexParsingTests.cs b/src/Core/tests/Yubico.YubiKit.Core.UnitTests/Credentials/HexParsingTests.cs index 743019c2e..36457b426 100644 --- a/src/Core/tests/Yubico.YubiKit.Core.UnitTests/Credentials/HexParsingTests.cs +++ b/src/Core/tests/Yubico.YubiKit.Core.UnitTests/Credentials/HexParsingTests.cs @@ -158,4 +158,4 @@ public void ReadCredential_HexMode_MixedCase() byte[] expected = [0xAA, 0xBB, 0xCC, 0xDD]; Assert.Equal(expected, result.Memory.ToArray()); } -} +} \ No newline at end of file diff --git a/src/Core/tests/Yubico.YubiKit.Core.UnitTests/Cryptography/ArkgP256Tests.cs b/src/Core/tests/Yubico.YubiKit.Core.UnitTests/Cryptography/ArkgP256Tests.cs index 6e560ba06..4f9957cb4 100644 --- a/src/Core/tests/Yubico.YubiKit.Core.UnitTests/Cryptography/ArkgP256Tests.cs +++ b/src/Core/tests/Yubico.YubiKit.Core.UnitTests/Cryptography/ArkgP256Tests.cs @@ -108,4 +108,4 @@ private static byte[] HexToBytes(string hex) return bytes; } -} +} \ No newline at end of file diff --git a/src/Core/tests/Yubico.YubiKit.Core.UnitTests/Hid/HidDeviceListenerDisposalTests.cs b/src/Core/tests/Yubico.YubiKit.Core.UnitTests/Hid/HidDeviceListenerDisposalTests.cs index 51039fd44..52e7780b7 100644 --- a/src/Core/tests/Yubico.YubiKit.Core.UnitTests/Hid/HidDeviceListenerDisposalTests.cs +++ b/src/Core/tests/Yubico.YubiKit.Core.UnitTests/Hid/HidDeviceListenerDisposalTests.cs @@ -117,7 +117,7 @@ public void Stop_TransitionsToStoppedStatus() try { listener.Start(); - + // Act listener.Stop(); @@ -407,4 +407,4 @@ public void Dispose_SetsStatusToStopped() Assert.Equal(DeviceListenerStatus.Stopped, listener.Status); } -} +} \ No newline at end of file diff --git a/src/Core/tests/Yubico.YubiKit.Core.UnitTests/Hid/Otp/OtpHidProtocolTests.cs b/src/Core/tests/Yubico.YubiKit.Core.UnitTests/Hid/Otp/OtpHidProtocolTests.cs index f5f4542b9..903314d8f 100644 --- a/src/Core/tests/Yubico.YubiKit.Core.UnitTests/Hid/Otp/OtpHidProtocolTests.cs +++ b/src/Core/tests/Yubico.YubiKit.Core.UnitTests/Hid/Otp/OtpHidProtocolTests.cs @@ -147,4 +147,4 @@ public async Task SendAndReceiveAsync_AfterDispose_ThrowsObjectDisposedException await Assert.ThrowsAsync( () => protocol.SendAndReceiveAsync(0x13, ReadOnlyMemory.Empty, TestContext.Current.CancellationToken)); } -} +} \ No newline at end of file diff --git a/src/Core/tests/Yubico.YubiKit.Core.UnitTests/SmartCard/DesktopSmartCardDeviceListenerDisposalTests.cs b/src/Core/tests/Yubico.YubiKit.Core.UnitTests/SmartCard/DesktopSmartCardDeviceListenerDisposalTests.cs index f813e8d31..facf6eb5f 100644 --- a/src/Core/tests/Yubico.YubiKit.Core.UnitTests/SmartCard/DesktopSmartCardDeviceListenerDisposalTests.cs +++ b/src/Core/tests/Yubico.YubiKit.Core.UnitTests/SmartCard/DesktopSmartCardDeviceListenerDisposalTests.cs @@ -108,7 +108,7 @@ public void Stop_TransitionsToStoppedStatus() try { listener.Start(); - + // Act listener.Stop(); @@ -350,4 +350,4 @@ void CreateAndAbandonListener() Assert.Null(exception); } -} +} \ No newline at end of file diff --git a/src/Core/tests/Yubico.YubiKit.Core.UnitTests/SmartCard/Fakes/FakeApduProcessor.cs b/src/Core/tests/Yubico.YubiKit.Core.UnitTests/SmartCard/Fakes/FakeApduProcessor.cs index cf451b2c1..9c8f20691 100644 --- a/src/Core/tests/Yubico.YubiKit.Core.UnitTests/SmartCard/Fakes/FakeApduProcessor.cs +++ b/src/Core/tests/Yubico.YubiKit.Core.UnitTests/SmartCard/Fakes/FakeApduProcessor.cs @@ -80,4 +80,4 @@ public Memory Format(byte cla, byte ins, byte p1, byte p2, ReadOnlyMemory< public Memory Format(ApduCommand apdu) => Format(apdu.Cla, apdu.Ins, apdu.P1, apdu.P2, apdu.Data, apdu.Le); -} +} \ No newline at end of file diff --git a/src/Core/tests/Yubico.YubiKit.Core.UnitTests/SmartCard/Fakes/FakeSmartCardConnection.cs b/src/Core/tests/Yubico.YubiKit.Core.UnitTests/SmartCard/Fakes/FakeSmartCardConnection.cs index 641efab85..31bc77339 100644 --- a/src/Core/tests/Yubico.YubiKit.Core.UnitTests/SmartCard/Fakes/FakeSmartCardConnection.cs +++ b/src/Core/tests/Yubico.YubiKit.Core.UnitTests/SmartCard/Fakes/FakeSmartCardConnection.cs @@ -68,4 +68,4 @@ public async ValueTask DisposeAsync() // TODO release managed resources here } -} +} \ No newline at end of file diff --git a/src/Core/tests/Yubico.YubiKit.Core.UnitTests/SmartCard/PcscProtocolTests.cs b/src/Core/tests/Yubico.YubiKit.Core.UnitTests/SmartCard/PcscProtocolTests.cs index 10e03ece8..02251af55 100644 --- a/src/Core/tests/Yubico.YubiKit.Core.UnitTests/SmartCard/PcscProtocolTests.cs +++ b/src/Core/tests/Yubico.YubiKit.Core.UnitTests/SmartCard/PcscProtocolTests.cs @@ -431,4 +431,4 @@ public void Dispose() public ValueTask DisposeAsync() => default; } -} +} \ No newline at end of file diff --git a/src/Core/tests/Yubico.YubiKit.Core.UnitTests/SmartCard/Scp/Scp11Tests.cs b/src/Core/tests/Yubico.YubiKit.Core.UnitTests/SmartCard/Scp/Scp11Tests.cs index 494d90f58..c84f516ac 100644 --- a/src/Core/tests/Yubico.YubiKit.Core.UnitTests/SmartCard/Scp/Scp11Tests.cs +++ b/src/Core/tests/Yubico.YubiKit.Core.UnitTests/SmartCard/Scp/Scp11Tests.cs @@ -67,7 +67,7 @@ public void Scp11a_KeyParams_WithOceAndCerts_Succeeds() var ocePrivateKey = ECPrivateKey.CreateFromParameters(oceEcdh.ExportParameters(true)); var sdPublicKey = ECPublicKey.CreateFromParameters(sdEcdh.PublicKey.ExportParameters()); - + var certBundle = ParseTestCertificates(); // Act - Create SCP11a key params (with OCE and certs) @@ -90,10 +90,10 @@ public void Scp11c_KeyParams_WithOceAndCerts_Succeeds() // Arrange - Create keys and certs for SCP11c using var oceEcdh = ECDiffieHellman.Create(ECCurve.NamedCurves.nistP256); using var sdEcdh = ECDiffieHellman.Create(ECCurve.NamedCurves.nistP256); - + var ocePrivateKey = ECPrivateKey.CreateFromParameters(oceEcdh.ExportParameters(true)); var sdPublicKey = ECPublicKey.CreateFromParameters(sdEcdh.PublicKey.ExportParameters()); - + var certBundle = ParseTestCertificates(); // Act - Create SCP11c key params @@ -115,10 +115,10 @@ public void Scp11a_KeyParams_WithoutCerts_ThrowsArgumentException() // Arrange using var oceEcdh = ECDiffieHellman.Create(ECCurve.NamedCurves.nistP256); using var sdEcdh = ECDiffieHellman.Create(ECCurve.NamedCurves.nistP256); - + var ocePrivateKey = ECPrivateKey.CreateFromParameters(oceEcdh.ExportParameters(true)); var sdPublicKey = ECPublicKey.CreateFromParameters(sdEcdh.PublicKey.ExportParameters()); - + var sessionRef = new KeyReference(ScpKid.SCP11a, 0x3); var oceRef = new KeyReference(0x10, 0x3); @@ -132,7 +132,7 @@ public void Scp11a_KeyParams_WithoutOceKey_ThrowsArgumentNullException() { // Arrange using var sdEcdh = ECDiffieHellman.Create(ECCurve.NamedCurves.nistP256); - + var sdPublicKey = ECPublicKey.CreateFromParameters(sdEcdh.PublicKey.ExportParameters()); var certBundle = ParseTestCertificates(); var sessionRef = new KeyReference(ScpKid.SCP11a, 0x3); @@ -148,7 +148,7 @@ public void Scp11_InvalidKeyId_ThrowsArgumentException() { // Arrange using var ecdh = ECDiffieHellman.Create(ECCurve.NamedCurves.nistP256); - + var sdPublicKey = ECPublicKey.CreateFromParameters(ecdh.PublicKey.ExportParameters()); // Act & Assert - Invalid KID (not 0x11, 0x13, or 0x15) var invalidKeyRef = new KeyReference(0x99, 0x1); diff --git a/src/Core/tests/Yubico.YubiKit.Core.UnitTests/SmartCard/Scp/Scp11X963KdfTests.cs b/src/Core/tests/Yubico.YubiKit.Core.UnitTests/SmartCard/Scp/Scp11X963KdfTests.cs index c7d347e39..98983d0a8 100644 --- a/src/Core/tests/Yubico.YubiKit.Core.UnitTests/SmartCard/Scp/Scp11X963KdfTests.cs +++ b/src/Core/tests/Yubico.YubiKit.Core.UnitTests/SmartCard/Scp/Scp11X963KdfTests.cs @@ -92,7 +92,7 @@ private static ReadOnlyMemory GenerateTestSdReceipt( { var keyAgreementData = Scp11X963Kdf.GetKeyAgreementData(hostAuthenticateTlvBytes, epkSdEckaTlvBytes); var keyMaterial = Scp11X963Kdf.GetSharedSecret(ephemeralOceEcka, skOceEcka, pkSdEcka, epkSdEckaTlvBytes); - byte[] sharedInfo = [..keyUsage.Span, ..keyType.Span, ..keyLen.Span]; + byte[] sharedInfo = [.. keyUsage.Span, .. keyType.Span, .. keyLen.Span]; // Derive keys diff --git a/src/Core/tests/Yubico.YubiKit.Core.UnitTests/Utils/DisposableArrayPoolBufferTests.cs b/src/Core/tests/Yubico.YubiKit.Core.UnitTests/Utils/DisposableArrayPoolBufferTests.cs index ba1ea29db..327ff1060 100644 --- a/src/Core/tests/Yubico.YubiKit.Core.UnitTests/Utils/DisposableArrayPoolBufferTests.cs +++ b/src/Core/tests/Yubico.YubiKit.Core.UnitTests/Utils/DisposableArrayPoolBufferTests.cs @@ -79,4 +79,4 @@ public void Dispose_CanBeCalledMultipleTimes() buffer.Dispose(); buffer.Dispose(); } -} +} \ No newline at end of file diff --git a/src/Core/tests/Yubico.YubiKit.Core.UnitTests/Utils/TlvHelperTests.cs b/src/Core/tests/Yubico.YubiKit.Core.UnitTests/Utils/TlvHelperTests.cs index 19bd60a03..037e85f5c 100644 --- a/src/Core/tests/Yubico.YubiKit.Core.UnitTests/Utils/TlvHelperTests.cs +++ b/src/Core/tests/Yubico.YubiKit.Core.UnitTests/Utils/TlvHelperTests.cs @@ -143,4 +143,4 @@ public void Create_TagBoundary1F_IsParsedAsTwoByteTag() Assert.Equal(1, tlv.Length); Assert.Equal(0xAA, tlv.Value.Span[0]); } -} +} \ No newline at end of file diff --git a/src/Core/tests/Yubico.YubiKit.Core.UnitTests/YubiKey/CapabilityMapperTests.cs b/src/Core/tests/Yubico.YubiKit.Core.UnitTests/YubiKey/CapabilityMapperTests.cs index 3005c070e..33ba3b149 100644 --- a/src/Core/tests/Yubico.YubiKit.Core.UnitTests/YubiKey/CapabilityMapperTests.cs +++ b/src/Core/tests/Yubico.YubiKit.Core.UnitTests/YubiKey/CapabilityMapperTests.cs @@ -299,4 +299,4 @@ private static byte[] CreateBigEndianBytes(int value) BinaryPrimitives.WriteInt16BigEndian(buffer, (short)value); return buffer; } -} +} \ No newline at end of file diff --git a/src/Core/tests/Yubico.YubiKit.Core.UnitTests/YubiKey/DeviceInfoTests.cs b/src/Core/tests/Yubico.YubiKit.Core.UnitTests/YubiKey/DeviceInfoTests.cs index 4415d22d6..b67a43874 100644 --- a/src/Core/tests/Yubico.YubiKit.Core.UnitTests/YubiKey/DeviceInfoTests.cs +++ b/src/Core/tests/Yubico.YubiKit.Core.UnitTests/YubiKey/DeviceInfoTests.cs @@ -108,4 +108,4 @@ private static Tlv[] CreateRequiredDeviceInfoTlvs(params Tlv[] additionalTlvs) = new(0x02, [0x01, 0x02, 0x03, 0x04]), .. additionalTlvs ]; -} +} \ No newline at end of file diff --git a/src/Core/tests/Yubico.YubiKit.Core.UnitTests/YubiKeyDeviceManagerTests.cs b/src/Core/tests/Yubico.YubiKit.Core.UnitTests/YubiKeyDeviceManagerTests.cs index 1edb7dcc4..31ea27343 100644 --- a/src/Core/tests/Yubico.YubiKit.Core.UnitTests/YubiKeyDeviceManagerTests.cs +++ b/src/Core/tests/Yubico.YubiKit.Core.UnitTests/YubiKeyDeviceManagerTests.cs @@ -377,4 +377,4 @@ public Task ConnectAsync(CancellationToken cancellatio => throw new NotSupportedException("FakeYubiKey does not support connections."); } -} +} \ No newline at end of file diff --git a/src/Core/tests/Yubico.YubiKit.Core.UnitTests/YubiKeyDeviceMonitorServiceTests.cs b/src/Core/tests/Yubico.YubiKit.Core.UnitTests/YubiKeyDeviceMonitorServiceTests.cs index 34cae45d4..b3178213e 100644 --- a/src/Core/tests/Yubico.YubiKit.Core.UnitTests/YubiKeyDeviceMonitorServiceTests.cs +++ b/src/Core/tests/Yubico.YubiKit.Core.UnitTests/YubiKeyDeviceMonitorServiceTests.cs @@ -347,4 +347,4 @@ public Task ConnectAsync(CancellationToken cancellatio => throw new NotSupportedException("FakeYubiKey does not support connections."); } -} +} \ No newline at end of file diff --git a/src/Core/tests/Yubico.YubiKit.Core.UnitTests/YubiKeyManagerStaticTests.cs b/src/Core/tests/Yubico.YubiKit.Core.UnitTests/YubiKeyManagerStaticTests.cs index c83583bbc..9c8b2a035 100644 --- a/src/Core/tests/Yubico.YubiKit.Core.UnitTests/YubiKeyManagerStaticTests.cs +++ b/src/Core/tests/Yubico.YubiKit.Core.UnitTests/YubiKeyManagerStaticTests.cs @@ -463,4 +463,4 @@ public async Task YubiKeyManager_DeviceChanges_AfterShutdown_AutoRecreatesContex var subscription = observable.Subscribe(_ => { }); subscription.Dispose(); } -} +} \ No newline at end of file diff --git a/src/Fido2/examples/FidoTool/Cli/Menus/AccessMenu.cs b/src/Fido2/examples/FidoTool/Cli/Menus/AccessMenu.cs index 967d01a7e..98cf4c41a 100644 --- a/src/Fido2/examples/FidoTool/Cli/Menus/AccessMenu.cs +++ b/src/Fido2/examples/FidoTool/Cli/Menus/AccessMenu.cs @@ -193,4 +193,4 @@ private static async Task RunUvRetriesAsync(IYubiKey device, CancellationToken c OutputHelpers.WriteError(result.ErrorMessage!); } } -} +} \ No newline at end of file diff --git a/src/Fido2/examples/FidoTool/Cli/Menus/ConfigMenu.cs b/src/Fido2/examples/FidoTool/Cli/Menus/ConfigMenu.cs index e7b900d19..39fdebf3b 100644 --- a/src/Fido2/examples/FidoTool/Cli/Menus/ConfigMenu.cs +++ b/src/Fido2/examples/FidoTool/Cli/Menus/ConfigMenu.cs @@ -170,4 +170,4 @@ await ConfigManagement.SetMinPinLengthAsync( OutputHelpers.WriteError(result.ErrorMessage!); } } -} +} \ No newline at end of file diff --git a/src/Fido2/examples/FidoTool/Cli/Menus/CredentialMenu.cs b/src/Fido2/examples/FidoTool/Cli/Menus/CredentialMenu.cs index f6c083da5..8fb2ded6e 100644 --- a/src/Fido2/examples/FidoTool/Cli/Menus/CredentialMenu.cs +++ b/src/Fido2/examples/FidoTool/Cli/Menus/CredentialMenu.cs @@ -181,4 +181,4 @@ internal static void DisplayAssertionResult(GetAssertion.GetAssertionResult resu OutputHelpers.WriteKeyValue("Total Matching Credentials", result.TotalCredentials.ToString()); } } -} +} \ No newline at end of file diff --git a/src/Fido2/examples/FidoTool/Cli/Menus/CredentialsMenu.cs b/src/Fido2/examples/FidoTool/Cli/Menus/CredentialsMenu.cs index a907679e0..a73d22665 100644 --- a/src/Fido2/examples/FidoTool/Cli/Menus/CredentialsMenu.cs +++ b/src/Fido2/examples/FidoTool/Cli/Menus/CredentialsMenu.cs @@ -281,4 +281,4 @@ internal static void DisplayStoredCredential(StoredCredentialInfo cred) OutputHelpers.WriteKeyValue(" Cred Protect", protectLevel); } } -} +} \ No newline at end of file diff --git a/src/Fido2/examples/FidoTool/Cli/Menus/FingerprintsMenu.cs b/src/Fido2/examples/FidoTool/Cli/Menus/FingerprintsMenu.cs index 2c5f6c130..a156b232b 100644 --- a/src/Fido2/examples/FidoTool/Cli/Menus/FingerprintsMenu.cs +++ b/src/Fido2/examples/FidoTool/Cli/Menus/FingerprintsMenu.cs @@ -294,4 +294,4 @@ internal static void DisplayTemplate(TemplateInfo template) OutputHelpers.WriteKeyValue(" Samples", template.SampleCount.ToString()); } } -} +} \ No newline at end of file diff --git a/src/Fido2/examples/FidoTool/Cli/Menus/InfoMenu.cs b/src/Fido2/examples/FidoTool/Cli/Menus/InfoMenu.cs index 7efc8d7b1..56d3f5205 100644 --- a/src/Fido2/examples/FidoTool/Cli/Menus/InfoMenu.cs +++ b/src/Fido2/examples/FidoTool/Cli/Menus/InfoMenu.cs @@ -195,4 +195,4 @@ public static void DisplayAuthenticatorInfo(AuthenticatorInfo info) } } } -} +} \ No newline at end of file diff --git a/src/Fido2/examples/FidoTool/Cli/Menus/ResetMenu.cs b/src/Fido2/examples/FidoTool/Cli/Menus/ResetMenu.cs index 2880c1841..825646157 100644 --- a/src/Fido2/examples/FidoTool/Cli/Menus/ResetMenu.cs +++ b/src/Fido2/examples/FidoTool/Cli/Menus/ResetMenu.cs @@ -116,4 +116,4 @@ public static async Task RunAsync(CancellationToken cancellationToken) OutputHelpers.WriteError(result.ErrorMessage!); } } -} +} \ No newline at end of file diff --git a/src/Fido2/examples/FidoTool/Cli/Output/OutputHelpers.cs b/src/Fido2/examples/FidoTool/Cli/Output/OutputHelpers.cs index a7315bac2..20fdb3044 100644 --- a/src/Fido2/examples/FidoTool/Cli/Output/OutputHelpers.cs +++ b/src/Fido2/examples/FidoTool/Cli/Output/OutputHelpers.cs @@ -13,8 +13,8 @@ // limitations under the License. using Spectre.Console; -using SharedOutput = Yubico.YubiKit.Cli.Shared.Output.OutputHelpers; using SharedConfirm = Yubico.YubiKit.Cli.Shared.Output.ConfirmationPrompts; +using SharedOutput = Yubico.YubiKit.Cli.Shared.Output.OutputHelpers; using SharedPin = Yubico.YubiKit.Cli.Shared.Output.PinPrompt; namespace Yubico.YubiKit.Fido2.Examples.FidoTool.Cli.Output; @@ -81,4 +81,4 @@ public static bool ConfirmDestructive(string action, string confirmationWord = " /// public static string PromptForPin(string label = "PIN") => SharedPin.PromptForPin(label); -} +} \ No newline at end of file diff --git a/src/Fido2/examples/FidoTool/Cli/Prompts/DeviceSelector.cs b/src/Fido2/examples/FidoTool/Cli/Prompts/DeviceSelector.cs index 233e54dd4..b2c0ceea2 100644 --- a/src/Fido2/examples/FidoTool/Cli/Prompts/DeviceSelector.cs +++ b/src/Fido2/examples/FidoTool/Cli/Prompts/DeviceSelector.cs @@ -194,4 +194,4 @@ private static string FormatDeviceChoice(IYubiKey device, DeviceInfo? info) return $"YubiKey {FormFactorFormatter.Format(info.Value.FormFactor)} - Serial: {info.Value.SerialNumber?.ToString() ?? "N/A"}, Firmware: {info.Value.FirmwareVersion} ({transport})"; } -} +} \ No newline at end of file diff --git a/src/Fido2/examples/FidoTool/Cli/Prompts/FidoDeviceSelector.cs b/src/Fido2/examples/FidoTool/Cli/Prompts/FidoDeviceSelector.cs index 250e9031a..45ed69694 100644 --- a/src/Fido2/examples/FidoTool/Cli/Prompts/FidoDeviceSelector.cs +++ b/src/Fido2/examples/FidoTool/Cli/Prompts/FidoDeviceSelector.cs @@ -48,4 +48,4 @@ public sealed class FidoDeviceSelector : DeviceSelectorBase protected override IYubiKey? AutoSelectDevice(IReadOnlyList devices) => devices.FirstOrDefault(d => d.ConnectionType == ConnectionType.HidFido) ?? devices[0]; -} +} \ No newline at end of file diff --git a/src/Fido2/examples/FidoTool/Cli/Prompts/FidoPinHelper.cs b/src/Fido2/examples/FidoTool/Cli/Prompts/FidoPinHelper.cs index 9508216ec..871e81d38 100644 --- a/src/Fido2/examples/FidoTool/Cli/Prompts/FidoPinHelper.cs +++ b/src/Fido2/examples/FidoTool/Cli/Prompts/FidoPinHelper.cs @@ -94,4 +94,4 @@ internal static class FidoPinHelper return PromptForNewPin(prompt); } -} +} \ No newline at end of file diff --git a/src/Fido2/examples/FidoTool/FidoExamples/BioEnrollment.cs b/src/Fido2/examples/FidoTool/FidoExamples/BioEnrollment.cs index 1a4935773..6b2c03e03 100644 --- a/src/Fido2/examples/FidoTool/FidoExamples/BioEnrollment.cs +++ b/src/Fido2/examples/FidoTool/FidoExamples/BioEnrollment.cs @@ -404,4 +404,4 @@ private static string MapCtapError(CtapException ex) => CtapStatus.InvalidCommand => "Bio enrollment is not supported on this authenticator.", _ => $"CTAP error: {ex.Message} (0x{(byte)ex.Status:X2})" }; -} +} \ No newline at end of file diff --git a/src/Fido2/examples/FidoTool/FidoExamples/ConfigManagement.cs b/src/Fido2/examples/FidoTool/FidoExamples/ConfigManagement.cs index 5d6e878e9..06f051205 100644 --- a/src/Fido2/examples/FidoTool/FidoExamples/ConfigManagement.cs +++ b/src/Fido2/examples/FidoTool/FidoExamples/ConfigManagement.cs @@ -210,4 +210,4 @@ private static string MapCtapError(CtapException ex) => "The new minimum PIN length violates the authenticator's PIN policy.", _ => $"CTAP error: {ex.Message} (0x{(byte)ex.Status:X2})" }; -} +} \ No newline at end of file diff --git a/src/Fido2/examples/FidoTool/FidoExamples/CredentialManagement.cs b/src/Fido2/examples/FidoTool/FidoExamples/CredentialManagement.cs index 604916f75..36be241e4 100644 --- a/src/Fido2/examples/FidoTool/FidoExamples/CredentialManagement.cs +++ b/src/Fido2/examples/FidoTool/FidoExamples/CredentialManagement.cs @@ -358,4 +358,4 @@ private static string MapCtapError(CtapException ex) => CtapStatus.KeyStoreFull => "The authenticator's credential storage is full.", _ => $"CTAP error: {ex.Message} (0x{(byte)ex.Status:X2})" }; -} +} \ No newline at end of file diff --git a/src/Fido2/examples/FidoTool/FidoExamples/GetAssertion.cs b/src/Fido2/examples/FidoTool/FidoExamples/GetAssertion.cs index e2c288f37..18dfe64dc 100644 --- a/src/Fido2/examples/FidoTool/FidoExamples/GetAssertion.cs +++ b/src/Fido2/examples/FidoTool/FidoExamples/GetAssertion.cs @@ -134,4 +134,4 @@ private static string MapCtapError(CtapException ex) => CtapStatus.OperationDenied => "The operation was denied by the authenticator.", _ => $"CTAP error: {ex.Message} (0x{(byte)ex.Status:X2})" }; -} +} \ No newline at end of file diff --git a/src/Fido2/examples/FidoTool/FidoExamples/GetAuthenticatorInfo.cs b/src/Fido2/examples/FidoTool/FidoExamples/GetAuthenticatorInfo.cs index 62338e2fd..34af63491 100644 --- a/src/Fido2/examples/FidoTool/FidoExamples/GetAuthenticatorInfo.cs +++ b/src/Fido2/examples/FidoTool/FidoExamples/GetAuthenticatorInfo.cs @@ -66,4 +66,4 @@ public static async Task QueryAsync( return AuthenticatorInfoResult.Failed($"Failed to query authenticator: {ex.Message}"); } } -} +} \ No newline at end of file diff --git a/src/Fido2/examples/FidoTool/FidoExamples/MakeCredential.cs b/src/Fido2/examples/FidoTool/FidoExamples/MakeCredential.cs index 836d6ea6f..eb69db75b 100644 --- a/src/Fido2/examples/FidoTool/FidoExamples/MakeCredential.cs +++ b/src/Fido2/examples/FidoTool/FidoExamples/MakeCredential.cs @@ -159,4 +159,4 @@ private static string MapCtapError(CtapException ex) => CtapStatus.OperationDenied => "The operation was denied by the authenticator.", _ => $"CTAP error: {ex.Message} (0x{(byte)ex.Status:X2})" }; -} +} \ No newline at end of file diff --git a/src/Fido2/examples/FidoTool/FidoExamples/PinManagement.cs b/src/Fido2/examples/FidoTool/FidoExamples/PinManagement.cs index f1be8f66e..d928f44ea 100644 --- a/src/Fido2/examples/FidoTool/FidoExamples/PinManagement.cs +++ b/src/Fido2/examples/FidoTool/FidoExamples/PinManagement.cs @@ -258,4 +258,4 @@ private static string MapCtapPinError(CtapException ex) => "Operation not allowed. A PIN may already be set (use 'change' instead of 'set').", _ => $"CTAP error: {ex.Message} (0x{(byte)ex.Status:X2})" }; -} +} \ No newline at end of file diff --git a/src/Fido2/examples/FidoTool/FidoExamples/ResetAuthenticator.cs b/src/Fido2/examples/FidoTool/FidoExamples/ResetAuthenticator.cs index 892c047b1..d57c06dde 100644 --- a/src/Fido2/examples/FidoTool/FidoExamples/ResetAuthenticator.cs +++ b/src/Fido2/examples/FidoTool/FidoExamples/ResetAuthenticator.cs @@ -129,4 +129,4 @@ private static string MapCtapError(CtapException ex) => "Reset not allowed. Remove the YubiKey, re-insert it, and try again within 5 seconds.", _ => $"CTAP error: {ex.Message} (0x{(byte)ex.Status:X2})" }; -} +} \ No newline at end of file diff --git a/src/Fido2/examples/FidoTool/Program.cs b/src/Fido2/examples/FidoTool/Program.cs index 4d6cbd9c1..ac255929b 100644 --- a/src/Fido2/examples/FidoTool/Program.cs +++ b/src/Fido2/examples/FidoTool/Program.cs @@ -206,54 +206,54 @@ static async Task RunAccessVerbAsync(string[] args, CancellationToken cance switch (subVerb) { case "set-pin": - { - using var newPinOwner = FidoPinHelper.GetNewPin(ParseOption(args, "--new-pin")); - if (newPinOwner is null) { OutputHelpers.WriteError("PIN required."); return 1; } + { + using var newPinOwner = FidoPinHelper.GetNewPin(ParseOption(args, "--new-pin")); + if (newPinOwner is null) { OutputHelpers.WriteError("PIN required."); return 1; } - var result = await PinManagement.SetPinAsync(selection.Device, newPinOwner.Memory, cancellationToken); - if (result.Success) { OutputHelpers.WriteSuccess("PIN set successfully."); return 0; } - OutputHelpers.WriteError(result.ErrorMessage!); - return 1; - } + var result = await PinManagement.SetPinAsync(selection.Device, newPinOwner.Memory, cancellationToken); + if (result.Success) { OutputHelpers.WriteSuccess("PIN set successfully."); return 0; } + OutputHelpers.WriteError(result.ErrorMessage!); + return 1; + } case "change-pin": - { - using var pinOwner = FidoPinHelper.GetPin(ParseOption(args, "--pin"), "Enter current PIN: "); - if (pinOwner is null) { OutputHelpers.WriteError("Current PIN required."); return 1; } + { + using var pinOwner = FidoPinHelper.GetPin(ParseOption(args, "--pin"), "Enter current PIN: "); + if (pinOwner is null) { OutputHelpers.WriteError("Current PIN required."); return 1; } - using var newPinOwner = FidoPinHelper.GetNewPin(ParseOption(args, "--new-pin")); - if (newPinOwner is null) { OutputHelpers.WriteError("New PIN required."); return 1; } + using var newPinOwner = FidoPinHelper.GetNewPin(ParseOption(args, "--new-pin")); + if (newPinOwner is null) { OutputHelpers.WriteError("New PIN required."); return 1; } - var result = await PinManagement.ChangePinAsync( - selection.Device, pinOwner.Memory, newPinOwner.Memory, cancellationToken); + var result = await PinManagement.ChangePinAsync( + selection.Device, pinOwner.Memory, newPinOwner.Memory, cancellationToken); - if (result.Success) - { - OutputHelpers.WriteSuccess("PIN changed successfully."); - return 0; - } + if (result.Success) + { + OutputHelpers.WriteSuccess("PIN changed successfully."); + return 0; + } - OutputHelpers.WriteError(result.ErrorMessage!); - return 1; - } + OutputHelpers.WriteError(result.ErrorMessage!); + return 1; + } case "verify-pin": - { - using var pinOwner = FidoPinHelper.GetPin(ParseOption(args, "--pin")); - if (pinOwner is null) { OutputHelpers.WriteError("PIN required."); return 1; } + { + using var pinOwner = FidoPinHelper.GetPin(ParseOption(args, "--pin")); + if (pinOwner is null) { OutputHelpers.WriteError("PIN required."); return 1; } - var result = await PinManagement.VerifyPinAsync( - selection.Device, pinOwner.Memory, cancellationToken); + var result = await PinManagement.VerifyPinAsync( + selection.Device, pinOwner.Memory, cancellationToken); - if (result.Success) - { - OutputHelpers.WriteSuccess("PIN is correct."); - return 0; - } + if (result.Success) + { + OutputHelpers.WriteSuccess("PIN is correct."); + return 0; + } - OutputHelpers.WriteError(result.ErrorMessage!); - return 1; - } + OutputHelpers.WriteError(result.ErrorMessage!); + return 1; + } default: AnsiConsole.MarkupLine("[bold]Usage:[/] FidoTool access [[options]]"); @@ -287,40 +287,40 @@ static async Task RunConfigVerbAsync(string[] args, CancellationToken cance switch (subVerb) { case "toggle-always-uv": - { - using var pinOwner = FidoPinHelper.GetPin(ParseOption(args, "--pin")); - if (pinOwner is null) { OutputHelpers.WriteError("PIN required."); return 1; } + { + using var pinOwner = FidoPinHelper.GetPin(ParseOption(args, "--pin")); + if (pinOwner is null) { OutputHelpers.WriteError("PIN required."); return 1; } - var result = await ConfigManagement.ToggleAlwaysUvAsync( - selection.Device, pinOwner.Memory, cancellationToken); + var result = await ConfigManagement.ToggleAlwaysUvAsync( + selection.Device, pinOwner.Memory, cancellationToken); - if (result.Success) - { - OutputHelpers.WriteSuccess("Always-UV setting toggled."); - return 0; - } + if (result.Success) + { + OutputHelpers.WriteSuccess("Always-UV setting toggled."); + return 0; + } - OutputHelpers.WriteError(result.ErrorMessage!); - return 1; - } + OutputHelpers.WriteError(result.ErrorMessage!); + return 1; + } case "enable-ep-attestation": - { - using var pinOwner = FidoPinHelper.GetPin(ParseOption(args, "--pin")); - if (pinOwner is null) { OutputHelpers.WriteError("PIN required."); return 1; } + { + using var pinOwner = FidoPinHelper.GetPin(ParseOption(args, "--pin")); + if (pinOwner is null) { OutputHelpers.WriteError("PIN required."); return 1; } - var result = await ConfigManagement.EnableEnterpriseAttestationAsync( - selection.Device, pinOwner.Memory, cancellationToken); + var result = await ConfigManagement.EnableEnterpriseAttestationAsync( + selection.Device, pinOwner.Memory, cancellationToken); - if (result.Success) - { - OutputHelpers.WriteSuccess("Enterprise attestation enabled."); - return 0; - } + if (result.Success) + { + OutputHelpers.WriteSuccess("Enterprise attestation enabled."); + return 0; + } - OutputHelpers.WriteError(result.ErrorMessage!); - return 1; - } + OutputHelpers.WriteError(result.ErrorMessage!); + return 1; + } default: AnsiConsole.MarkupLine("[bold]Usage:[/] FidoTool config [[options]]"); @@ -354,97 +354,97 @@ static async Task RunCredentialsVerbAsync(string[] args, CancellationToken switch (subVerb) { case "list": - { - using var pinOwner = FidoPinHelper.GetPin(ParseOption(args, "--pin")); - if (pinOwner is null) { OutputHelpers.WriteError("PIN required."); return 1; } - - var rpResult = await CredentialManagementExample.EnumerateRelyingPartiesAsync( - selection.Device, pinOwner.Memory, cancellationToken); - - if (!rpResult.Success) { - OutputHelpers.WriteError(rpResult.ErrorMessage!); - return 1; - } + using var pinOwner = FidoPinHelper.GetPin(ParseOption(args, "--pin")); + if (pinOwner is null) { OutputHelpers.WriteError("PIN required."); return 1; } - if (rpResult.RelyingParties.Count == 0) - { - OutputHelpers.WriteInfo("No credentials stored on this authenticator."); - return 0; - } + var rpResult = await CredentialManagementExample.EnumerateRelyingPartiesAsync( + selection.Device, pinOwner.Memory, cancellationToken); - OutputHelpers.WriteSuccess($"Found {rpResult.RelyingParties.Count} relying party(ies)."); - AnsiConsole.WriteLine(); + if (!rpResult.Success) + { + OutputHelpers.WriteError(rpResult.ErrorMessage!); + return 1; + } - foreach (var rp in rpResult.RelyingParties) - { - AnsiConsole.MarkupLine($" [green bold]{Markup.Escape(rp.RelyingParty.Id)}[/]"); - OutputHelpers.WriteHex(" RP ID Hash", rp.RpIdHash); + if (rpResult.RelyingParties.Count == 0) + { + OutputHelpers.WriteInfo("No credentials stored on this authenticator."); + return 0; + } - var credResult = await CredentialManagementExample.EnumerateCredentialsAsync( - selection.Device, pinOwner.Memory, rp.RpIdHash, cancellationToken); + OutputHelpers.WriteSuccess($"Found {rpResult.RelyingParties.Count} relying party(ies)."); + AnsiConsole.WriteLine(); - if (credResult.Success) + foreach (var rp in rpResult.RelyingParties) { - foreach (var cred in credResult.Credentials) + AnsiConsole.MarkupLine($" [green bold]{Markup.Escape(rp.RelyingParty.Id)}[/]"); + OutputHelpers.WriteHex(" RP ID Hash", rp.RpIdHash); + + var credResult = await CredentialManagementExample.EnumerateCredentialsAsync( + selection.Device, pinOwner.Memory, rp.RpIdHash, cancellationToken); + + if (credResult.Success) { - CredentialsMenu.DisplayStoredCredential(cred); + foreach (var cred in credResult.Credentials) + { + CredentialsMenu.DisplayStoredCredential(cred); + } } + + AnsiConsole.WriteLine(); } - AnsiConsole.WriteLine(); + return 0; } - return 0; - } - case "delete": - { - // Positional argument: CREDENTIAL_ID (hex), expected at args[2] - if (args.Length < 3 || args[2].StartsWith("--") || args[2].StartsWith("-")) { - OutputHelpers.WriteError( - "Missing credential ID. Usage: FidoTool credentials delete CREDENTIAL_ID [--pin PIN] [-f]"); - return 1; - } + // Positional argument: CREDENTIAL_ID (hex), expected at args[2] + if (args.Length < 3 || args[2].StartsWith("--") || args[2].StartsWith("-")) + { + OutputHelpers.WriteError( + "Missing credential ID. Usage: FidoTool credentials delete CREDENTIAL_ID [--pin PIN] [-f]"); + return 1; + } - var idHex = args[2]; - byte[] credentialId; - try - { - credentialId = Convert.FromHexString(idHex); - } - catch (FormatException) - { - OutputHelpers.WriteError("Invalid hex string for credential ID."); - return 1; - } + var idHex = args[2]; + byte[] credentialId; + try + { + credentialId = Convert.FromHexString(idHex); + } + catch (FormatException) + { + OutputHelpers.WriteError("Invalid hex string for credential ID."); + return 1; + } - var force = HasFlag(args, "-f") || HasFlag(args, "--force"); - if (!force) - { - if (!OutputHelpers.ConfirmDangerous("permanently delete this credential")) + var force = HasFlag(args, "-f") || HasFlag(args, "--force"); + if (!force) { - OutputHelpers.WriteInfo("Operation cancelled."); - return 0; + if (!OutputHelpers.ConfirmDangerous("permanently delete this credential")) + { + OutputHelpers.WriteInfo("Operation cancelled."); + return 0; + } } - } - using var pinOwner = FidoPinHelper.GetPin(ParseOption(args, "--pin")); - if (pinOwner is null) { OutputHelpers.WriteError("PIN required."); return 1; } + using var pinOwner = FidoPinHelper.GetPin(ParseOption(args, "--pin")); + if (pinOwner is null) { OutputHelpers.WriteError("PIN required."); return 1; } - var deleteResult = await CredentialManagementExample.DeleteCredentialAsync( - selection.Device, pinOwner.Memory, credentialId, cancellationToken); + var deleteResult = await CredentialManagementExample.DeleteCredentialAsync( + selection.Device, pinOwner.Memory, credentialId, cancellationToken); - if (deleteResult.Success) - { - OutputHelpers.WriteSuccess("Credential deleted successfully."); - return 0; - } + if (deleteResult.Success) + { + OutputHelpers.WriteSuccess("Credential deleted successfully."); + return 0; + } - OutputHelpers.WriteError(deleteResult.ErrorMessage!); - return 1; - } + OutputHelpers.WriteError(deleteResult.ErrorMessage!); + return 1; + } default: AnsiConsole.MarkupLine("[bold]Usage:[/] FidoTool credentials [[options]]"); @@ -480,182 +480,182 @@ static async Task RunFingerprintsVerbAsync(string[] args, CancellationToken switch (subVerb) { case "list": - { - using var pinOwner = FidoPinHelper.GetPin(ParseOption(args, "--pin")); - if (pinOwner is null) { - OutputHelpers.WriteError("PIN is required."); - return 1; - } - - var result = await BioEnrollmentExample.EnumerateEnrollmentsAsync( - selection.Device, pinOwner.Memory, cancellationToken); + using var pinOwner = FidoPinHelper.GetPin(ParseOption(args, "--pin")); + if (pinOwner is null) + { + OutputHelpers.WriteError("PIN is required."); + return 1; + } - if (!result.Success) - { - OutputHelpers.WriteError(result.ErrorMessage!); - return 1; - } + var result = await BioEnrollmentExample.EnumerateEnrollmentsAsync( + selection.Device, pinOwner.Memory, cancellationToken); - if (result.Templates.Count == 0) - { - OutputHelpers.WriteInfo("No fingerprints enrolled."); - return 0; - } + if (!result.Success) + { + OutputHelpers.WriteError(result.ErrorMessage!); + return 1; + } - OutputHelpers.WriteSuccess($"Found {result.Templates.Count} enrollment(s)."); - AnsiConsole.WriteLine(); + if (result.Templates.Count == 0) + { + OutputHelpers.WriteInfo("No fingerprints enrolled."); + return 0; + } - foreach (var template in result.Templates) - { - FingerprintsMenu.DisplayTemplate(template); - } + OutputHelpers.WriteSuccess($"Found {result.Templates.Count} enrollment(s)."); + AnsiConsole.WriteLine(); - return 0; - } + foreach (var template in result.Templates) + { + FingerprintsMenu.DisplayTemplate(template); + } - case "add": - { - // Positional argument: NAME, expected at args[2] - string? friendlyName = null; - if (args.Length > 2 && !args[2].StartsWith("--") && !args[2].StartsWith("-")) - { - friendlyName = args[2]; + return 0; } - using var pinOwner = FidoPinHelper.GetPin(ParseOption(args, "--pin")); - if (pinOwner is null) + case "add": { - OutputHelpers.WriteError("PIN is required."); - return 1; - } - - AnsiConsole.MarkupLine("[yellow]Touch your YubiKey now...[/]"); + // Positional argument: NAME, expected at args[2] + string? friendlyName = null; + if (args.Length > 2 && !args[2].StartsWith("--") && !args[2].StartsWith("-")) + { + friendlyName = args[2]; + } - var result = await BioEnrollmentExample.EnrollFingerprintAsync( - selection.Device, - pinOwner.Memory, - friendlyName, - onSampleCaptured: (sample, remaining, status) => + using var pinOwner = FidoPinHelper.GetPin(ParseOption(args, "--pin")); + if (pinOwner is null) { - var statusText = BioEnrollmentExample.FormatSampleStatus(status); - AnsiConsole.MarkupLine($" Sample {sample}: {Markup.Escape(statusText)} ({remaining} remaining)"); + OutputHelpers.WriteError("PIN is required."); + return 1; + } + + AnsiConsole.MarkupLine("[yellow]Touch your YubiKey now...[/]"); - if (remaining > 0) + var result = await BioEnrollmentExample.EnrollFingerprintAsync( + selection.Device, + pinOwner.Memory, + friendlyName, + onSampleCaptured: (sample, remaining, status) => { - AnsiConsole.MarkupLine("[yellow] Touch the sensor again...[/]"); - } - }, - cancellationToken); + var statusText = BioEnrollmentExample.FormatSampleStatus(status); + AnsiConsole.MarkupLine($" Sample {sample}: {Markup.Escape(statusText)} ({remaining} remaining)"); - if (result.Success) - { - OutputHelpers.WriteSuccess("Fingerprint enrolled successfully."); - OutputHelpers.WriteHex("Template ID", result.TemplateId); - return 0; - } + if (remaining > 0) + { + AnsiConsole.MarkupLine("[yellow] Touch the sensor again...[/]"); + } + }, + cancellationToken); - OutputHelpers.WriteError(result.ErrorMessage!); - return 1; - } + if (result.Success) + { + OutputHelpers.WriteSuccess("Fingerprint enrolled successfully."); + OutputHelpers.WriteHex("Template ID", result.TemplateId); + return 0; + } - case "delete": - { - // Positional argument: FINGERPRINT_ID (hex), expected at args[2] - if (args.Length < 3 || args[2].StartsWith("--") || args[2].StartsWith("-")) - { - OutputHelpers.WriteError( - "Missing fingerprint ID. Usage: FidoTool fingerprints delete FINGERPRINT_ID [--pin PIN] [-f]"); + OutputHelpers.WriteError(result.ErrorMessage!); return 1; } - var idHex = args[2]; - byte[] templateId; - try - { - templateId = Convert.FromHexString(idHex); - } - catch (FormatException) + case "delete": { - OutputHelpers.WriteError("Invalid hex string for fingerprint ID."); - return 1; - } + // Positional argument: FINGERPRINT_ID (hex), expected at args[2] + if (args.Length < 3 || args[2].StartsWith("--") || args[2].StartsWith("-")) + { + OutputHelpers.WriteError( + "Missing fingerprint ID. Usage: FidoTool fingerprints delete FINGERPRINT_ID [--pin PIN] [-f]"); + return 1; + } - var force = HasFlag(args, "-f") || HasFlag(args, "--force"); - if (!force) - { - if (!OutputHelpers.ConfirmDangerous("permanently delete this fingerprint enrollment")) + var idHex = args[2]; + byte[] templateId; + try { - OutputHelpers.WriteInfo("Operation cancelled."); - return 0; + templateId = Convert.FromHexString(idHex); + } + catch (FormatException) + { + OutputHelpers.WriteError("Invalid hex string for fingerprint ID."); + return 1; } - } - using var pinOwner = FidoPinHelper.GetPin(ParseOption(args, "--pin")); - if (pinOwner is null) - { - OutputHelpers.WriteError("PIN is required."); - return 1; - } + var force = HasFlag(args, "-f") || HasFlag(args, "--force"); + if (!force) + { + if (!OutputHelpers.ConfirmDangerous("permanently delete this fingerprint enrollment")) + { + OutputHelpers.WriteInfo("Operation cancelled."); + return 0; + } + } - var result = await BioEnrollmentExample.RemoveEnrollmentAsync( - selection.Device, pinOwner.Memory, templateId, cancellationToken); + using var pinOwner = FidoPinHelper.GetPin(ParseOption(args, "--pin")); + if (pinOwner is null) + { + OutputHelpers.WriteError("PIN is required."); + return 1; + } - if (result.Success) - { - OutputHelpers.WriteSuccess("Fingerprint enrollment removed successfully."); - return 0; - } + var result = await BioEnrollmentExample.RemoveEnrollmentAsync( + selection.Device, pinOwner.Memory, templateId, cancellationToken); - OutputHelpers.WriteError(result.ErrorMessage!); - return 1; - } + if (result.Success) + { + OutputHelpers.WriteSuccess("Fingerprint enrollment removed successfully."); + return 0; + } - case "rename": - { - // Positional arguments: FINGERPRINT_ID NAME, expected at args[2] and args[3] - if (args.Length < 4 - || args[2].StartsWith("--") || args[2].StartsWith("-") - || args[3].StartsWith("--") || args[3].StartsWith("-")) - { - OutputHelpers.WriteError( - "Missing arguments. Usage: FidoTool fingerprints rename FINGERPRINT_ID NAME [--pin PIN]"); + OutputHelpers.WriteError(result.ErrorMessage!); return 1; } - var idHex = args[2]; - var newName = args[3]; - - byte[] templateId; - try - { - templateId = Convert.FromHexString(idHex); - } - catch (FormatException) + case "rename": { - OutputHelpers.WriteError("Invalid hex string for fingerprint ID."); - return 1; - } + // Positional arguments: FINGERPRINT_ID NAME, expected at args[2] and args[3] + if (args.Length < 4 + || args[2].StartsWith("--") || args[2].StartsWith("-") + || args[3].StartsWith("--") || args[3].StartsWith("-")) + { + OutputHelpers.WriteError( + "Missing arguments. Usage: FidoTool fingerprints rename FINGERPRINT_ID NAME [--pin PIN]"); + return 1; + } - using var pinOwner = FidoPinHelper.GetPin(ParseOption(args, "--pin")); - if (pinOwner is null) - { - OutputHelpers.WriteError("PIN is required."); - return 1; - } + var idHex = args[2]; + var newName = args[3]; - var result = await BioEnrollmentExample.RenameEnrollmentAsync( - selection.Device, pinOwner.Memory, templateId, newName, cancellationToken); + byte[] templateId; + try + { + templateId = Convert.FromHexString(idHex); + } + catch (FormatException) + { + OutputHelpers.WriteError("Invalid hex string for fingerprint ID."); + return 1; + } - if (result.Success) - { - OutputHelpers.WriteSuccess("Fingerprint enrollment renamed successfully."); - return 0; - } + using var pinOwner = FidoPinHelper.GetPin(ParseOption(args, "--pin")); + if (pinOwner is null) + { + OutputHelpers.WriteError("PIN is required."); + return 1; + } - OutputHelpers.WriteError(result.ErrorMessage!); - return 1; - } + var result = await BioEnrollmentExample.RenameEnrollmentAsync( + selection.Device, pinOwner.Memory, templateId, newName, cancellationToken); + + if (result.Success) + { + OutputHelpers.WriteSuccess("Fingerprint enrollment renamed successfully."); + return 0; + } + + OutputHelpers.WriteError(result.ErrorMessage!); + return 1; + } default: AnsiConsole.MarkupLine("[bold]Usage:[/] FidoTool fingerprints [[options]]"); @@ -721,4 +721,4 @@ static int ShowUnknownCommand(string command) OutputHelpers.WriteError($"Unknown command: {command}"); AnsiConsole.MarkupLine("[grey]Run 'FidoTool help' for available commands.[/]"); return 1; -} +} \ No newline at end of file diff --git a/src/Fido2/src/Arkg/ArkgP256.cs b/src/Fido2/src/Arkg/ArkgP256.cs index cdd38f57c..fa13b3dcb 100644 --- a/src/Fido2/src/Arkg/ArkgP256.cs +++ b/src/Fido2/src/Arkg/ArkgP256.cs @@ -48,4 +48,4 @@ internal static (byte[] derivedPk, byte[] arkgKeyHandle) DerivePublicKey( { return CryptographyProviders.ArkgPrimitivesCreator().Derive(pkBl, pkKem, ikm, ctx); } -} +} \ No newline at end of file diff --git a/src/Fido2/src/AuthenticatorInfo.cs b/src/Fido2/src/AuthenticatorInfo.cs index 151461253..307a69ade 100644 --- a/src/Fido2/src/AuthenticatorInfo.cs +++ b/src/Fido2/src/AuthenticatorInfo.cs @@ -61,7 +61,7 @@ public sealed class AuthenticatorInfo private const int KeyMaxPinLength = 0x1D; private const int KeyEncCredStoreState = 0x1E; private const int KeyAuthenticatorConfigCommands = 0x1F; - + /// /// Gets the CTAP versions supported by the authenticator. /// @@ -69,12 +69,12 @@ public sealed class AuthenticatorInfo /// Common values: "FIDO_2_0", "FIDO_2_1", "FIDO_2_1_PRE", "U2F_V2" /// public IReadOnlyList Versions { get; init; } = []; - + /// /// Gets the extensions supported by the authenticator. /// public IReadOnlyList Extensions { get; init; } = []; - + /// /// Gets the AAGUID (Authenticator Attestation GUID). /// @@ -82,7 +82,7 @@ public sealed class AuthenticatorInfo /// This is a 128-bit identifier that identifies the type of authenticator. /// public ReadOnlyMemory Aaguid { get; init; } - + /// /// Gets the options supported by the authenticator. /// @@ -91,107 +91,107 @@ public sealed class AuthenticatorInfo /// "plat" (platform device), "clientPin", "credentialMgmt", "bioEnroll", etc. /// public IReadOnlyDictionary Options { get; init; } = ReadOnlyDictionary.Empty; - + /// /// Gets the maximum message size in bytes. /// public int? MaxMsgSize { get; init; } - + /// /// Gets the PIN/UV auth protocol versions supported. /// public IReadOnlyList PinUvAuthProtocols { get; init; } = []; - + /// /// Gets the maximum number of credentials in the allow/exclude list. /// public int? MaxCredentialCountInList { get; init; } - + /// /// Gets the maximum credential ID length in bytes. /// public int? MaxCredentialIdLength { get; init; } - + /// /// Gets the transports supported by the authenticator. /// public IReadOnlyList Transports { get; init; } = []; - + /// /// Gets the algorithms supported for credential creation. /// public IReadOnlyList Algorithms { get; init; } = []; - + /// /// Gets the maximum size of the serialized large blob array. /// public int? MaxSerializedLargeBlobArray { get; init; } - + /// /// Gets whether the authenticator requires PIN change. /// public bool? ForcePinChange { get; init; } - + /// /// Gets the minimum PIN length required. /// public int? MinPinLength { get; init; } - + /// /// Gets the firmware version as reported by the authenticator. /// public FirmwareVersion? FirmwareVersion { get; init; } - + /// /// Gets the maximum credBlob length in bytes. /// public int? MaxCredBlobLength { get; init; } - + /// /// Gets the maximum number of RP IDs for setMinPINLength. /// public int? MaxRpidsForSetMinPinLength { get; init; } - + /// /// Gets the preferred number of platform UV attempts. /// public int? PreferredPlatformUvAttempts { get; init; } - + /// /// Gets the UV modality flags. /// public int? UvModality { get; init; } - + /// /// Gets the certifications held by the authenticator. /// public IReadOnlyDictionary Certifications { get; init; } = ReadOnlyDictionary.Empty; - + /// /// Gets the remaining discoverable credential slots. /// public int? RemainingDiscoverableCredentials { get; init; } - + /// /// Gets the vendor prototype config commands supported. /// public IReadOnlyList VendorPrototypeConfigCommands { get; init; } = []; - + /// /// Gets the attestation formats supported. /// public IReadOnlyList AttestationFormats { get; init; } = []; - + /// /// Gets the UV count since last PIN entry. /// public int? UvCountSinceLastPinEntry { get; init; } - + /// /// Gets whether long touch is required for reset. /// public bool? LongTouchForReset { get; init; } - + /// /// Gets the encrypted credential identifier (YubiKey 5.7+). /// @@ -206,7 +206,7 @@ public sealed class AuthenticatorInfo /// /// public ReadOnlyMemory? EncIdentifier { get; init; } - + /// /// Gets the transports available for authenticator reset (CTAP 2.3). /// @@ -215,7 +215,7 @@ public sealed class AuthenticatorInfo /// Common values include "usb", "nfc", and "ble". /// public IReadOnlyList TransportsForReset { get; init; } = []; - + /// /// Gets whether PIN complexity policy is enforced (CTAP 2.3). /// @@ -224,7 +224,7 @@ public sealed class AuthenticatorInfo /// by . /// public bool? PinComplexityPolicy { get; init; } - + /// /// Gets the URL describing the PIN complexity policy (CTAP 2.3). /// @@ -233,7 +233,7 @@ public sealed class AuthenticatorInfo /// enforced by the authenticator. /// public string? PinComplexityPolicyUrl { get; init; } - + /// /// Gets the maximum PIN length supported by the authenticator (CTAP 2.3). /// @@ -242,7 +242,7 @@ public sealed class AuthenticatorInfo /// Complements . /// public int? MaxPinLength { get; init; } - + /// /// Gets the encrypted credential store state (YubiKey 5.8+). /// @@ -257,7 +257,7 @@ public sealed class AuthenticatorInfo /// /// public ReadOnlyMemory? EncCredStoreState { get; init; } - + /// /// Gets the authenticator config commands supported (CTAP 2.3). /// @@ -271,7 +271,7 @@ public sealed class AuthenticatorInfo /// /// public IReadOnlyList AuthenticatorConfigCommands { get; init; } = []; - + /// /// Decodes an AuthenticatorInfo from CBOR-encoded data. /// @@ -282,14 +282,14 @@ public static AuthenticatorInfo Decode(ReadOnlyMemory data) var reader = new CborReader(data, CborConformanceMode.Ctap2Canonical); return Parse(reader); } - + /// /// Parses an AuthenticatorInfo from a CBOR reader. /// internal static AuthenticatorInfo Parse(CborReader reader) { var mapLength = reader.ReadStartMap(); - + List? versions = null; List? extensions = null; byte[]? aaguid = null; @@ -321,148 +321,148 @@ internal static AuthenticatorInfo Parse(CborReader reader) int? maxPinLength = null; byte[]? encCredStoreState = null; List? authenticatorConfigCommands = null; - + for (var i = 0; i < mapLength; i++) { var key = reader.ReadInt32(); - + switch (key) { case KeyVersions: versions = ReadStringArray(reader); break; - + case KeyExtensions: extensions = ReadStringArray(reader); break; - + case KeyAaguid: aaguid = reader.ReadByteString(); break; - + case KeyOptions: options = ReadBoolMap(reader); break; - + case KeyMaxMsgSize: maxMsgSize = reader.ReadInt32(); break; - + case KeyPinUvAuthProtocols: pinUvAuthProtocols = ReadIntArray(reader); break; - + case KeyMaxCredentialCountInList: maxCredentialCountInList = reader.ReadInt32(); break; - + case KeyMaxCredentialIdLength: maxCredentialIdLength = reader.ReadInt32(); break; - + case KeyTransports: transports = ReadStringArray(reader); break; - + case KeyAlgorithms: algorithms = ReadAlgorithms(reader); break; - + case KeyMaxSerializedLargeBlobArray: maxSerializedLargeBlobArray = reader.ReadInt32(); break; - + case KeyForcePinChange: forcePinChange = reader.ReadBoolean(); break; - + case KeyMinPinLength: minPinLength = reader.ReadInt32(); break; - + case KeyFirmwareVersion: var fwValue = reader.ReadUInt64(); firmwareVersion = DecodeFirmwareVersion(fwValue); break; - + case KeyMaxCredBlobLength: maxCredBlobLength = reader.ReadInt32(); break; - + case KeyMaxRpidsForSetMinPinLength: maxRpidsForSetMinPinLength = reader.ReadInt32(); break; - + case KeyPreferredPlatformUvAttempts: preferredPlatformUvAttempts = reader.ReadInt32(); break; - + case KeyUvModality: uvModality = reader.ReadInt32(); break; - + case KeyCertifications: certifications = ReadCertifications(reader); break; - + case KeyRemainingDiscoverableCredentials: remainingDiscoverableCredentials = reader.ReadInt32(); break; - + case KeyVendorPrototypeConfigCommands: vendorPrototypeConfigCommands = ReadIntArray(reader); break; - + case KeyAttestationFormats: attestationFormats = ReadStringArray(reader); break; - + case KeyUvCountSinceLastPinEntry: uvCountSinceLastPinEntry = reader.ReadInt32(); break; - + case KeyLongTouchForReset: longTouchForReset = reader.ReadBoolean(); break; - + case KeyEncIdentifier: encIdentifier = reader.ReadByteString(); break; - + case KeyTransportsForReset: transportsForReset = ReadStringArray(reader); break; - + case KeyPinComplexityPolicy: pinComplexityPolicy = reader.ReadBoolean(); break; - + case KeyPinComplexityPolicyUrl: // CBOR encodes as byte string (UTF-8), decode to string var policyUrlBytes = reader.ReadByteString(); pinComplexityPolicyUrl = System.Text.Encoding.UTF8.GetString(policyUrlBytes); break; - + case KeyMaxPinLength: maxPinLength = reader.ReadInt32(); break; - + case KeyEncCredStoreState: encCredStoreState = reader.ReadByteString(); break; - + case KeyAuthenticatorConfigCommands: authenticatorConfigCommands = ReadIntArray(reader); break; - + default: reader.SkipValue(); break; } } - + reader.ReadEndMap(); - + return new AuthenticatorInfo { Versions = versions ?? [], @@ -502,81 +502,81 @@ internal static AuthenticatorInfo Parse(CborReader reader) AuthenticatorConfigCommands = authenticatorConfigCommands ?? [] }; } - + private static List ReadStringArray(CborReader reader) { var length = reader.ReadStartArray() ?? 0; var result = new List((int)length); - + for (var i = 0; i < length; i++) { result.Add(reader.ReadTextString()); } - + reader.ReadEndArray(); return result; } - + private static List ReadIntArray(CborReader reader) { var length = reader.ReadStartArray() ?? 0; var result = new List((int)length); - + for (var i = 0; i < length; i++) { result.Add(reader.ReadInt32()); } - + reader.ReadEndArray(); return result; } - + private static Dictionary ReadBoolMap(CborReader reader) { var length = reader.ReadStartMap() ?? 0; var result = new Dictionary((int)length); - + for (var i = 0; i < length; i++) { var key = reader.ReadTextString(); var value = reader.ReadBoolean(); result[key] = value; } - + reader.ReadEndMap(); return result; } - + private static Dictionary ReadCertifications(CborReader reader) { var length = reader.ReadStartMap() ?? 0; var result = new Dictionary((int)length); - + for (var i = 0; i < length; i++) { var key = reader.ReadTextString(); var value = reader.ReadInt32(); result[key] = value; } - + reader.ReadEndMap(); return result; } - + private static List ReadAlgorithms(CborReader reader) { var length = reader.ReadStartArray() ?? 0; var result = new List((int)length); - + for (var i = 0; i < length; i++) { result.Add(PublicKeyCredentialParameters.Parse(reader)); } - + reader.ReadEndArray(); return result; } - + private static FirmwareVersion DecodeFirmwareVersion(ulong value) { // Firmware version is encoded as major.minor.patch in the upper bytes @@ -594,24 +594,24 @@ private static FirmwareVersion DecodeFirmwareVersion(ulong value) public sealed class PublicKeyCredentialParameters { private const string TypePublicKey = "public-key"; - + /// /// Gets the credential type (always "public-key"). /// public string Type { get; init; } = TypePublicKey; - + /// /// Gets the COSE algorithm identifier. /// public CoseAlgorithmIdentifier Algorithm { get; init; } - + /// /// Creates a new credential parameters instance. /// public PublicKeyCredentialParameters() { } - + /// /// Creates a new credential parameters instance with the specified algorithm. /// @@ -620,22 +620,22 @@ public PublicKeyCredentialParameters(CoseAlgorithmIdentifier algorithm) { Algorithm = algorithm; } - + /// /// Creates ES256 credential parameters. /// public static PublicKeyCredentialParameters CreateES256() => new(CoseAlgorithmIdentifier.ES256); - + /// /// Creates RS256 credential parameters. /// public static PublicKeyCredentialParameters CreateRS256() => new(CoseAlgorithmIdentifier.RS256); - + /// /// Creates EdDSA credential parameters. /// public static PublicKeyCredentialParameters CreateEdDSA() => new(CoseAlgorithmIdentifier.EdDSA); - + /// /// Encodes this parameters object as CBOR. /// @@ -643,27 +643,27 @@ public PublicKeyCredentialParameters(CoseAlgorithmIdentifier algorithm) public void Encode(CborWriter writer) { writer.WriteStartMap(2); - + writer.WriteTextString("type"); writer.WriteTextString(Type); - + writer.WriteTextString("alg"); writer.WriteInt32((int)Algorithm); - + writer.WriteEndMap(); } - + internal static PublicKeyCredentialParameters Parse(CborReader reader) { var mapLength = reader.ReadStartMap(); - + string type = TypePublicKey; var algorithm = CoseAlgorithmIdentifier.None; - + for (var i = 0; i < mapLength; i++) { var key = reader.ReadTextString(); - + switch (key) { case "type": @@ -677,13 +677,13 @@ internal static PublicKeyCredentialParameters Parse(CborReader reader) break; } } - + reader.ReadEndMap(); - + return new PublicKeyCredentialParameters { Type = type, Algorithm = algorithm }; } -} +} \ No newline at end of file diff --git a/src/Fido2/src/BioEnrollment/BioEnrollmentModels.cs b/src/Fido2/src/BioEnrollment/BioEnrollmentModels.cs index a8453d496..f865c23b3 100644 --- a/src/Fido2/src/BioEnrollment/BioEnrollmentModels.cs +++ b/src/Fido2/src/BioEnrollment/BioEnrollmentModels.cs @@ -36,67 +36,67 @@ public enum FingerprintSampleStatus /// Good sample captured (0x00). /// Good = 0x00, - + /// /// Sample too high on sensor (0x01). /// TooHigh = 0x01, - + /// /// Sample too low on sensor (0x02). /// TooLow = 0x02, - + /// /// Sample too left on sensor (0x03). /// TooLeft = 0x03, - + /// /// Sample too right on sensor (0x04). /// TooRight = 0x04, - + /// /// Finger moved too fast (0x05). /// TooFast = 0x05, - + /// /// Finger moved too slow (0x06). /// TooSlow = 0x06, - + /// /// Poor quality sample (0x07). /// PoorQuality = 0x07, - + /// /// Sample too skewed (0x08). /// TooSkewed = 0x08, - + /// /// Sample too short (0x09). /// TooShort = 0x09, - + /// /// Merge failure (0x0A). /// MergeFailure = 0x0A, - + /// /// Data storage full (0x0B). /// StorageFull = 0x0B, - + /// /// No user activity (timeout) (0x0C). /// NoUserActivity = 0x0C, - + /// /// No user presence detected (0x0D). /// @@ -115,7 +115,7 @@ public sealed class FingerprintSensorInfo /// Gets the fingerprint kind (touch or swipe). /// public FingerprintKind FingerprintKind { get; private init; } - + /// /// Gets the maximum number of samples required for enrollment. /// @@ -124,12 +124,12 @@ public sealed class FingerprintSensorInfo /// Actual enrollment may complete with fewer samples. /// public int MaxCaptureSamplesRequiredForEnroll { get; private init; } - + /// /// Gets the maximum number of templates that can be stored. /// public int? MaxTemplateCount { get; private init; } - + /// /// Decodes a FingerprintSensorInfo from a CBOR response. /// @@ -138,39 +138,39 @@ public sealed class FingerprintSensorInfo public static FingerprintSensorInfo Decode(ReadOnlyMemory data) { var reader = new CborReader(data, CborConformanceMode.Lax); - + var fingerprintKind = FingerprintKind.Touch; var maxSamples = 0; int? maxTemplates = null; - + var mapCount = reader.ReadStartMap() ?? 0; - + for (var i = 0; i < mapCount; i++) { var key = reader.ReadInt32(); - + switch (key) { case 2: // fingerprintKind (0x02) fingerprintKind = (FingerprintKind)reader.ReadInt32(); break; - + case 3: // maxCaptureSamplesRequiredForEnroll (0x03) maxSamples = reader.ReadInt32(); break; - + case 4: // maxTemplateCount (0x04) - vendor extension maxTemplates = reader.ReadInt32(); break; - + default: reader.SkipValue(); break; } } - + reader.ReadEndMap(); - + return new FingerprintSensorInfo { FingerprintKind = fingerprintKind, @@ -189,7 +189,7 @@ public enum FingerprintKind /// Touch sensor - finger placed on sensor (0x01). /// Touch = 0x01, - + /// /// Swipe sensor - finger swiped across sensor (0x02). /// @@ -205,12 +205,12 @@ public sealed class EnrollmentSampleResult /// Gets the current template ID for this enrollment. /// public ReadOnlyMemory TemplateId { get; internal init; } - + /// /// Gets the status of the last sample capture. /// public FingerprintSampleStatus LastSampleStatus { get; internal init; } - + /// /// Gets the number of remaining samples needed to complete enrollment. /// @@ -218,12 +218,12 @@ public sealed class EnrollmentSampleResult /// A value of 0 indicates enrollment is complete. /// public int RemainingSamples { get; internal init; } - + /// /// Gets whether the enrollment is complete. /// public bool IsComplete => RemainingSamples == 0; - + /// /// Decodes an EnrollmentSampleResult from a CBOR response. /// @@ -232,39 +232,39 @@ public sealed class EnrollmentSampleResult public static EnrollmentSampleResult Decode(ReadOnlyMemory data) { var reader = new CborReader(data, CborConformanceMode.Lax); - + ReadOnlyMemory templateId = default; var lastStatus = FingerprintSampleStatus.Good; var remaining = 0; - + var mapCount = reader.ReadStartMap() ?? 0; - + for (var i = 0; i < mapCount; i++) { var key = reader.ReadInt32(); - + switch (key) { case 4: // templateId (0x04) templateId = reader.ReadByteString(); break; - + case 5: // lastEnrollSampleStatus (0x05) lastStatus = (FingerprintSampleStatus)reader.ReadInt32(); break; - + case 6: // remainingSamples (0x06) remaining = reader.ReadInt32(); break; - + default: reader.SkipValue(); break; } } - + reader.ReadEndMap(); - + return new EnrollmentSampleResult { TemplateId = templateId, @@ -283,12 +283,12 @@ public sealed class TemplateInfo /// Gets the unique identifier of this template. /// public ReadOnlyMemory TemplateId { get; internal init; } - + /// /// Gets the friendly name of this template, if set. /// public string? FriendlyName { get; internal init; } - + /// /// Gets the number of samples captured for this template. /// @@ -296,7 +296,7 @@ public sealed class TemplateInfo /// This is a vendor extension and may not be available on all authenticators. /// public int? SampleCount { get; internal init; } - + /// /// Decodes a TemplateInfo from a CBOR response. /// @@ -307,7 +307,7 @@ public static TemplateInfo Decode(ReadOnlyMemory data) var reader = new CborReader(data, CborConformanceMode.Lax); return DecodeFromReader(reader); } - + /// /// Decodes a TemplateInfo from an existing CBOR reader. /// @@ -316,35 +316,35 @@ internal static TemplateInfo DecodeFromReader(CborReader reader) ReadOnlyMemory templateId = default; string? friendlyName = null; int? sampleCount = null; - + var mapCount = reader.ReadStartMap() ?? 0; - + for (var i = 0; i < mapCount; i++) { var key = reader.ReadInt32(); - + switch (key) { case 4: // templateId (0x04) templateId = reader.ReadByteString(); break; - + case 7: // templateFriendlyName (0x07) friendlyName = reader.ReadTextString(); break; - + case 8: // sampleCount (0x08) - vendor extension sampleCount = reader.ReadInt32(); break; - + default: reader.SkipValue(); break; } } - + reader.ReadEndMap(); - + return new TemplateInfo { TemplateId = templateId, @@ -363,7 +363,7 @@ public sealed class EnrollmentEnumerationResult /// Gets the list of enrolled templates. /// public IReadOnlyList Templates { get; private init; } = []; - + /// /// Decodes an EnrollmentEnumerationResult from a CBOR response. /// @@ -373,13 +373,13 @@ public static EnrollmentEnumerationResult Decode(ReadOnlyMemory data) { // The response contains a single template info var template = TemplateInfo.Decode(data); - + return new EnrollmentEnumerationResult { Templates = [template] }; } - + /// /// Creates an enumeration result from a list of templates. /// @@ -387,4 +387,4 @@ internal static EnrollmentEnumerationResult FromTemplates(IReadOnlyList