feat(applets): merge yubikit-applets into yubikit#446
Open
DennisDyallo wants to merge 296 commits into
Open
Conversation
…rmware - OtpBackend.ReadConfigAsync: add bounds check before CheckCrc to prevent IndexOutOfRangeException when alpha firmware response length exceeds buffer - SlotConfiguration.IsSupportedBy: honour Major==0 sentinel firmware (matches ApplicationSession.IsSupported) so PutConfigurationAsync works on alpha - YubiOtpSession.InitializeAsync: always recreate SmartCard backend after SELECT so _lastProgSeq reflects actual device state, not hardcoded 0 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
After ykman config reset, alpha firmware stops exposing serial numbers via the Management API. This blocked all integration tests. - Add AllowUnknownSerials option to IAllowListProvider/AllowList/ AppSettingsAllowListProvider, read from appsettings.json - YubiKeyTestInfrastructure: authorize devices without serial when AllowUnknownSerials=true - YubiOTP integration tests: pin GetSerial and SwapSlots to SmartCard to avoid HidOtp 1023ms timeout on alpha firmware flash programming - GetSerial assertion relaxed to >= 0 (serial may be hidden) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…cumented Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
DevTeam review identified ~2600 LOC duplicated across 5 CLI tools. 4-phase extraction plan with difficulty ratings, inconsistencies to normalize, and proposed Yubico.YubiKit.Cli.Shared project structure. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…Shared (#12) New shared project consolidating ~2600 LOC of duplicated CLI patterns: - DeviceSelectorBase: abstract base for device discovery/selection - OutputHelpers: Spectre.Console formatting (WriteSuccess/Error/Warning/etc.) - ConfirmationPrompts: dangerous/destructive operation confirmations - PinPrompt: masked PIN input - ArgumentParser: flag/option/positional parsing - CommandHelper: YubiKeyManager lifecycle + CTS boilerplate - ConnectionTypeFormatter/FormFactorFormatter: enum display formatting All 5 CLIs migrated (ManagementTool, OathTool, FidoTool, OpenPgpTool, HsmAuthTool): - Each CLI gets a sealed DeviceSelectorBase subclass for transport filtering - OutputHelpers delegate to shared library (except OathTool: plain-text by design) - OathTool retains custom pipe-safe output intentionally Reviewer fixes applied: - Non-interactive multi-device guard (returns null vs crashing on Prompt) - Logger.LogDebug replaces silent console debug in GetDeviceInfoAsync - WriteTouchPrompt renamed to PromptForTouch for consistency Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Mark CLI shared infrastructure (#12) Phases 1-3 as done in both plan docs - Add handoff document for session continuity Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Brings in full PIV application implementation (~13,850 lines): - PivSession with authentication, certificates, crypto, key pairs, metadata, bio - IPivSession interface, domain types (PivAlgorithm, PivSlot, PivPinPolicy, etc.) - PivTool CLI example application - Unit and integration tests - Module CLAUDE.md and README.md Conflict resolution: kept yubikit-applets' async DisposeAsync pattern (proper DisposeAsyncCore + Dispose(false)) over yubikit-piv's simpler synchronous version. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
… default - WithYubiKeyAttribute now implements ITraitAttribute, automatically adding RequiresHardware and Integration category traits to all integration tests that use [WithYubiKey] - build.cs test target now runs only unit tests by default - New --integration flag requires --project to prevent accidentally running all integration tests at once - FilterBullseyeArgs now properly distinguishes flags from value options Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
…bugs - Fix GetArgument/HasFlag to use captured top-level args instead of Environment.GetCommandLineArgs() which shadowed the variable - Fix FilterBullseyeArgs call to strip --package-version, --nuget-feed-name, --nuget-feed-path (previously leaked to Bullseye as unknown args) - Extract PrintColored, PrintProjectList, PrintNoProjectsFound helpers - Extract RunTestProjects and PrintTestSummary to eliminate duplication between test and coverage targets - Extract FilterToProject for --project filtering logic - Remove unused allTestProjects intermediate variable - Simplify UsesMicrosoftTestingPlatformRunner to single return expression - Update BUILD.md: document --integration option, add Code Coverage section noting Microsoft.Testing.Platform exclusion
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
…do2, and Piv - OtpHidProtocol: Fix assertion to expect 6-byte status response (not 0) for empty payload commands, matching actual protocol behavior - YubiKeyDeviceManager: Populate cache before starting monitoring since StartMonitoring is event-driven and does not trigger an initial scan - LargeBlobArray.Deserialize: Fix minimum size check from HashSize+2 to HashSize+1 since a valid empty CBOR array (0x80) is only 1 byte - PivSession: Use reflection to set FirmwareVersion to 4.0.0 instead of relying on default 0.0.0, which is treated as alpha/beta (latest) and bypasses the firmware version gate Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
…Shared (Phase 4) Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
The test used a 32-byte key expecting ArgumentException, but Authenticate now accepts both 32-byte (PIN token) and 64-byte (shared secret) keys. Changed to 16 bytes which is genuinely invalid. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Cross-referenced with ykman 5.8.0 FIDO reset flow: - Query LongTouchForReset from AuthenticatorInfo to show correct touch message (10s hold for 5.8+/0.0.0 keys, tap for older) - Add TransportsForReset pre-check in interactive menu - Fix --force to skip reinsertion flow entirely (matching ykman) - Add ResetPreflightInfo record and GetPreflightInfoAsync method - Add PinAuthBlocked error mapping, align error messages with ykman Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
clean target must now be specified explicitly; dotnet build.cs clean build for full clean build
OATH: Fix touch property encoding to use raw bytes [tag, value] instead of TLV encoding [tag, length, value], matching ykman's struct.pack. YubiOTP: Fix ConfigFlag values (StrongPw1, StrongPw2, ManUpdate) to match ykman constants. Add missing ChalHmac, ChalYubico, OathHotp8 flags. Add default ext flags (SerialApiVisible, AllowUpdate) to base SlotConfiguration. Add default tkt/ext flags (AppendCr, FastTrigger) to KeyboardSlotConfiguration. Add ChalHmac|HmacLt64 to HMAC-SHA1 config. Add OathFixedModhex2 to HOTP config. Fix Use8Digits to use OathHotp8 instead of OathFixedModhex1. Remove incorrect StaticTicket from StaticPassword config. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
…ation
Remove DependsOn("build") from test and coverage targets and remove
--no-build flags so dotnet handles incremental building naturally.
Running `dotnet build.cs test` no longer forces a full rebuild — it
only recompiles if sources changed, matching dotnet.exe behavior.
Explicit rebuild via `dotnet build.cs clean build test`.
Add TranslateToMtpFilter() to convert VSTest --filter expressions to
xUnit v3 MTP native options (--filter-method, --filter-trait, etc.).
Add --minimum-expected-tests 0 to prevent failure when no tests match.
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
- Replace == null / != null with is null / is not null across all modules - Remove all #region / #endregion directives (91 files, 399 lines removed) - Replace SequenceEqual with CryptographicOperations.FixedTimeEquals in security-sensitive contexts (ATR, FIDO nonce, RP ID hash, large blob hash, OATH credential ID, OpenPGP OID) - Replace Array.Empty<byte>() with [] where target type supports it Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add --smoke flag to skip Slow and RequiresUserPresence tests for fast integration runs. Extract DiscoverProjects() and FilterProjectsByName() to eliminate repeated project discovery logic. Add --project support to coverage target. Update BUILD.md, CLAUDE.md, and TESTING.md with integration test strategy. Mark RSA 3072/4096 PIV tests as [Slow]. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Convert `new byte[] { ... }` to `[...]` and `Array.Empty<T>()` to `[]`
where C# 14 collection expressions are supported by the target type.
Skipped instances where `var` inference, `Memory<byte>`, attribute args,
or method chaining prevent collection expression usage.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The stub class in Core was unreferenced — no code in Core throws, catches, or uses it. The full implementation lives in Fido2/Ctap/CtapException.cs where it belongs. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The extended parameter's mapping to P2 values was reversed vs ykman canonical. extended=false should send P2=0x81 (signature-only) and extended=true should send P2=0x82 (decrypt/authenticate/attest). Fix tests to use correct extended values for each operation type. Integration: 27/28 pass (AttestKey remains alpha firmware gap). Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
- Convert.ToHexString accepts ReadOnlySpan<byte> natively on net10 - Collection expression spread operator supports ReadOnlySpan<byte> - Tlv.ToString: replace BitConverter.ToString().Replace() with Convert.ToHexString() (cleaner + zero-alloc) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
AttestKeyAsync incorrectly parsed the GET_ATTESTATION APDU response as a certificate. Per ykman canonical, GET_ATTESTATION writes the cert to the slot — read it back with GetCertificateAsync. Also fix SelectCertificateSlotAsync index mapping (should be 3-keyRef: SIG=2, DEC=1, AUT=0) and non-standard FW<=5.4.3 byte placement (prepend before TLV, not inside). Integration: 28/28 pass. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Alpha/beta firmware reports 0.0.1 sentinel from applet SELECT, causing raw FirmwareVersion comparisons to fail. IsSupported() handles Major==0 by assuming all features available. Fixes PIV GetPinAttemptsAsync returning wrong retry count and several other feature gates across OATH, OpenPGP, and PIV modules. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
HMAC-SHA1 challenge-response is not supported over USB CCID (SmartCard), matching ykman's not_usb_ccid condition. Changed ConnectionType from SmartCard to HidOtp and removed incorrect RequiresUserPresence trait since the test doesn't enable touch-triggered mode. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
…hase 9.7) Phase 9.7 enforces the strict no-duplication rule: zero duplicated code or behavior in WebAuthn; Fido2 owns anything both projects need. Shipped 12 of 19 architectural violations identified by the SoC audit (Plans/phase-9.7-soc-consolidation.md). Group C (4 attestation items) deferred to Phase 9.8 (Plans/phase-9.8-attestation-typed-variants.md) due to a Fido2 public-API-freeze conflict that requires explicit maintainer sign-off to resolve cleanly. Groups complete: - A (6): extension Input/Output type families consolidated to Fido2 (CredBlob, MinPinLength, LargeBlob, Prf). Phase 9.6 32-byte CredBlob validation absorbed into A1 and now closed. - B (3): WebAuthn identity types deleted; consumers use Fido2's PublicKeyCredentialDescriptor / RpEntity / UserEntity. - D (2): COSE typed model promoted to Yubico.YubiKit.Fido2.Cose (new public additions: CoseKey, CoseAlgorithm). - E (1): AAGUID big-endian/mixed-endian helper unified at Yubico.YubiKit.Fido2.Cbor.AaguidConverter (internal). - F (2): PreviewSign output decoders moved into Fido2 alongside the encoders; WebAuthn adapter no longer reads CBOR. - G (2): dead using-alias removed; private ByteArrayComparer duplicate in PrfAdapter deleted. Constraints honored: - Fido2 public API surface: only additions, zero breaking changes. - All 10 test projects pass (Fido2 357/0, WebAuthn 90/0). - Build clean (0 errors). - Fido CLI builds and consumes Fido2 unchanged. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…ierarchy (Phase 9.8 Group C)
Replaces Fido2's flat sealed-class AttestationStatement with a typed
discriminated-union hierarchy promoted from WebAuthn. Closes Group C of
the SoC consolidation deferred from Phase 9.7.
This is a BREAKING CHANGE to Fido2's public API — explicitly authorized
by Dennis after consumer-surface audit confirmed zero external callers.
Before:
public sealed class AttestationStatement {
ReadOnlyMemory<byte>? Signature; IReadOnlyList<...>? X5c;
ReadOnlyMemory<byte>? EcdaaKeyId; int? Algorithm;
ReadOnlyMemory<byte> RawData; bool IsNone;
}
After:
public abstract record AttestationStatement(AttestationFormat Format, ReadOnlyMemory<byte> RawCbor)
public sealed record PackedAttestationStatement : AttestationStatement
public sealed record FidoU2FAttestationStatement : AttestationStatement
public sealed record AppleAttestationStatement : AttestationStatement
public sealed record NoneAttestationStatement : AttestationStatement
public sealed record UnknownAttestationStatement : AttestationStatement
MakeCredentialResponse.AttestationStatement property keeps its name and
declared type. Consumers reading .Signature/.X5c/.Algorithm directly need
to pattern-match on the variant.
Items shipped:
- C1 internal decoder dispatches by fmt string to typed variants
- C2 typed hierarchy promoted to Yubico.YubiKit.Fido2.Credentials (public)
- AttestationFormat enum promoted alongside
- WebAuthn's local copies removed (git mv preserves history)
Items intentionally deferred:
- C3/C4 envelope writer/decoder helpers — WebAuthn already has working
implementations at WebAuthnAttestationObject.{EncodeAttestationObject,Decode};
extracting them to Fido2 internal helpers can be a follow-up if other
consumers ever need them.
Constraint compliance:
- Pre-mapped consumer migration: all 5 sites updated (Fido2 internal
decoder, 4 test methods)
- Fido CLI: zero changes needed (confirmed by grep — Fido CLI examples
do not consume .AttestationStatement.* fields)
- All 10 test projects pass (Fido2 357/0, WebAuthn 90/0)
- Build clean (0 errors)
- Test fixture in CredentialResponseTests updated to produce valid
packed attStmt (alg + sig per CTAP 2.1 §8.2) when format=="packed"
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Reduce root CLAUDE.md from 1395→586 lines (40KB→22KB) by extracting deep-dive content to JIT-loadable docs while preserving every behavioral mandate. - Quick Reference rules retained, each with a JIT pointer that names the trigger context (e.g. "load when allocating buffers") - Type Selection (273 lines) and Test Philosophy (121 lines) preserved verbatim — they catch the most expensive AI-agent failure modes - New JIT docs (MEMORY-MANAGEMENT.md, CRYPTO-APIS.md, CSHARP-PATTERNS.md) open with a `READ WHEN <triggers>` frontmatter mirroring the PAI skill discoverability pattern, so agents pull them in by intent rather than enumeration - Build/test/security deep-dives deleted in favor of existing skill files (`domain-build`, `domain-test`, `domain-security-guidelines`, `domain-secure-credential-prompt`) - Verified: 29/29 semantic mandates retained across the new file set; all docs/ and .claude/skills/ pointers resolve Includes the trim plan (Plans/do-you-see-a-whimsical-spring.md) which documents the per-section treatment matrix and a Tier 2 A/B agent harness spec for measuring real behavioral impact in a follow-up session. Baseline CLAUDE.md snapshot at Plans/eval/baseline/ enables one-revert recovery. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…not 8 The CTAP MakeCredential response field for unsignedExtensionOutputs is key 6 per CTAP 2.2 / WebAuthn L3, aligned with yubikit-swift, yubikit-android, and yubikit-python. v2 .NET was the lone outlier reading at key 8 (an early CTAP v4 draft value), causing silent data loss against real YubiKey 5.8+ firmware: MakeCredentialResponse.UnsignedExtensionOutputs was always null, and PreviewSignAdapter.ParseRegistrationOutput silently fell back to the authData path with AttestationObject: null. Includes a regression unit test pinning the new behavior: a response carrying the legacy key 8 must not populate UnsignedExtensionOutputs. Cross-source evidence: - yubikit-swift release/1.3.0: key 6 - yubikit-swift commit 339a7792: explicit Java + Python alignment - 5-8-hello-world README: key 6 - v1 .NET PoC (add-preview-sign): key 6 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… CredProtect L2 CTAP 2.x: a regular pinUvAuthToken must be regenerated for each authenticator invocation. PPUAT (Per-credential Persistent UV Auth Token) is the read-only exception, not used here. The CredProtect L2 test was reusing the same token across two GetAssertion calls (line 120 and the prior line 129), which fails with "PIN authentication failed" on real YubiKey firmware on the second call. Fix: mint a fresh pinUvAuthToken and recompute the auth param immediately before the second GetAssertion call, with dedicated variable names so the per-call freshness is visible to readers. Verified by isolated hardware run on a freshly Reset YubiKey: the test now passes end-to-end in 34s with focused user touches. Prior runs failed at either line 120 (touch timeout under multi-test cascade) or line 129 (token reuse). Both root causes are addressed. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…USB-only devices The three FidoNfcTests methods were failing (not skipping) on USB-connected NFC-capable YubiKeys. The [WithYubiKey(RequireNfc=true)] filter matches the device's NFC capability, not its current connection — so a USB-A YubiKey 5 NFC passes the filter, then SDK throws NotSupportedException when trying to open a SmartCard FIDO2 session over USB CCID (intentionally blocked). Mirrors the established pattern in FidoTransportTests.MakeCredential_OverNfcSmartCard: 1. Pre-runtime check on state.ConnectionType to skip cleanly 2. catch (NotSupportedException) → Skip.If(true, ...) for the runtime case [SkippableTheory] alone only converts SkipException to Skipped, not arbitrary SDK exceptions — explicit catch is required. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Implements client-layer excludeList pre-flight in Yubico.YubiKit.WebAuthn, mirroring yubikit-android's Ctap2Client.makeCredential + Utils.filterCreds architecture (Ctap2Client.java:520-564, Utils.java:860-938). When excludeCredentials is non-empty, WebAuthnClient now: 1. Acquires a pinUvAuthToken with MakeCredential | GetAssertion permissions 2. Chunks the exclude list by info.MaxCredentialCountInList 3. Probes each chunk via GetAssertion(up=false) to find the first matching cred 4. Calls MakeCredential with at-most-1 entry in excludeList This preserves CTAP 2.1 semantics on YubiKey firmware that evaluates request processing limits before excludeList matching for large lists. Without pre-flight, a 17-entry exclude list returned LimitExceeded instead of the expected CredentialExcluded. Architectural rule preserved: the pre-flight is a WebAuthn client-orchestration concern, NOT a low-level CTAP concern. src/Fido2/ is unchanged; only WebAuthn module gains the new logic. Also maps CTAP CredentialExcluded → WebAuthnClientErrorCode.InvalidState per WebAuthn L2 §5.1.3. Test layering changes: - Deletes src/Fido2/tests/.../FidoExcludeListStressTests.cs (test exercised behavior that requires the WebAuthn pre-flight layer; cannot pass at the Fido2-direct layer alone) - Adds src/WebAuthn/tests/.../WebAuthnExcludeListStressTests.cs covering the same scenario at the correct architectural layer - Adds 4 unit tests for ExcludeListPreflight (empty, single-match, no-match, chunked iteration) - Test uses separate FidoSessions for setup vs WebAuthnClient body to avoid PIN/UV protocol state contamination from connection reuse - Documents the operator-Reset precondition mirroring yubikit-android's FidoTestState.withCtap2() contract KNOWN ISSUE: On YubiKey 5.8.0, after pre-flight's GetAssertion(up=false) consumes the token's GetAssertion permission, the subsequent MakeCredential returns PinAuthInvalid. Per CTAP 2.1 §6.5.5.7, authenticators MAY consume permissions on use. Java parity at the WebAuthn layer requires re-acquiring the pinUvAuthToken between pre-flight and MakeCredential when excludeList is non-empty. Tracked for next session — see Plans/handoff.md. Empirically validated end-to-end on freshly-Reset YubiKey 5.8.0: - 17 RKs created via WebAuthnClient.MakeCredentialAsync ✓ - Pre-flight GetAssertion(up=false) executes without consuming a touch ✓ - Device returned CredentialExcluded → WebAuthnClient surfaces typed error ✓ - Final InvalidState mapping pending the token-rotation fix above Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…nd MakeCredential Resolves the KNOWN ISSUE documented in commit f62c7c4: on YubiKey 5.8.0 the pre-flight's GetAssertion(up=false) consumes the GetAssertion permission from the pinUvAuthToken (per CTAP 2.1 §6.5.5.7, "authenticators MAY consume permissions on use"). The subsequent MakeCredential then fails with PinAuthInvalid because the same token can no longer authorize ANY operation, even one (MakeCredential) whose permission slot is logically untouched. Fix: when ExcludeListPreflight has run, dispose the original token and mint a fresh one scoped to MakeCredential-only permissions before BuildMakeCredentialRequest computes the pinUvAuthParam. tokenCopy zeroed in finally for hygiene. Java parity: yubikit-android Ctap2Client.java:472-474 mints the token with permissions = MC | (excludeCredentials.isEmpty() ? 0 : GA) — same single-token shape as ours. Whether Java hits the same firmware behavior on 5.8.0 is untested; either way, this is the correct WebAuthn-layer fix per the commit message that introduced the preflight. Empirically validated: - WebAuthnExcludeListStressTests: FAIL → PASS in 34s on freshly-Reset YK5.8.0 - 17 RKs created, preflight matches one of them, final excluded MakeCredential surfaces InvalidState as the test expects - Build clean, all unit tests still green Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…error mapping) CodeAudit pass on WebAuthn + adjacent Fido2 surface flagged 4 critical issues post-dc2ed141. All addressed in this commit; build clean (0 errors), WebAuthn unit tests green. 1. PinUvAuthTokenSession.cs: add finalizer fallback. The class owns a private byte[] clone of a sensitive token but had no finalizer, so a leaked instance never zeroed before GC. CLAUDE.md mandates IDisposable + defensive zeroing for owned sensitive byte[]. Now: Dispose() suppresses finalize; finalizer zeroes if Dispose was never called. 2. CredentialMatcher.cs: stop swallowing CtapStatus.NotAllowed (0x30) as "no credentials." NotAllowed is the generic CTAP "device denied the operation" status (user cancel, policy reject) — semantically distinct from "no matching credential." Folding them together sent users down the wrong recovery UX path. Now propagates so the upstream mapper translates to WebAuthnClientErrorCode.NotAllowed. 3. WebAuthnClient.MakeCredentialCoreAsync: explicit torn-state guard between the post-preflight token Dispose() and the re-mint AcquirePinUvTokenWithRetryAsync. If the re-mint throws, tokenSession is now null instead of referencing a disposed instance — the outer finally is a clean no-op. 4. WebAuthnClient: introduce private MapCtapStatusToWebAuthnError + add general catch arms in both MakeCredentialCoreAsync and GetAssertionCoreAsync. Previously, only previewSign-requested registrations had typed CTAP→WebAuthn mapping; every other CTAP error leaked as raw CtapException, contradicting the WebAuthn module rule "never expose raw CTAP status codes to high-level API consumers." Mapping covers PinAuth*/PinBlocked/NotAllowed/OperationDenied → NotAllowed; KeyStoreFull/LimitExceeded/Timeout → Constraint; Unsupported* → NotSupported; PinNotSet/UpRequired → Security; NoCredentials/InvalidCredential → InvalidState; everything else → Unknown with the inner CtapException preserved. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…odeAudit Critical fixes Captures: WebAuthnExcludeListStressTests went FAIL → PASS via dc2ed14; 4 Critical CodeAudit findings landed in a0070db; all 6 prior+session commits pushed to origin; lessons captured (CTAP 2.1 §6.5.5.7 permission consumption, three-agent triangulation pattern, vslsp-driven audit). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…atusChannel CodeAudit HIGH finding (StatusChannel.cs:125-137): CreateUvRequest had zero callers (verified via vslsp find_usages — definition site only). The _uvResponseTcs field was set inside CreateUvRequest and read inside the same closure but never observed from outside. The interactive UV flow doesn't go through the channel-mediated TCS pattern at all — WebAuthnClient auto-responds WebAuthnStatusRequestingUv directly (lines 108, 176, 401, 475). Removes ~20 LOC of unreachable interactive-protocol code. The WebAuthnStatusRequestingUv record is retained — it has 9 real usages. Build clean, WebAuthn unit tests green. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…resolution Follow-up to 37ff02f which added catch (NotSupportedException) → Skip.If(true, ...) guards to all 3 FidoNfcTests methods but did not add the Xunit using directive that Skip lives in. The file appears to have resolved Skip via transitive imports in some build configurations but not others; explicit import matches the FidoTransportTests reference pattern verbatim. DevTeam Ship verification: - Engineer agent correctly identified that the catch guards were already in place (avoiding a redundant edit) and pinpointed the missing import as the real gap - Build clean (dotnet toolchain.cs build) — 0 errors - All 358 Fido2 unit tests pass - All WebAuthn unit tests pass Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ead public API Closes 3 of the remaining 7 HIGH-severity CodeAudit findings. Build clean, WebAuthn unit tests green. 1. Add WebAuthnClientErrorCode.Cancelled and OperationCanceledException catch arm to both producer Task.Run lambdas in WebAuthnClient (~lines 240, 320). Cancellation was previously surfaced as WebAuthnClientErrorCode.Unknown wrapping a TaskCanceledException via the general Exception catch — consumers could not distinguish "I cancelled" from "device errored." OCE arm precedes the general Exception arm to avoid being shadowed. 2. Remove BackendMakeCredentialRequest.EnterpriseAttestation (declared on the internal request record but never populated by WebAuthnClient and never read by FidoSessionWebAuthnBackend). Public API surface that advertised a feature wired nowhere. 3. Remove IWebAuthnBackend.GetUvRetriesAsync, IWebAuthnBackend.GetPinRetriesAsync, their FidoSessionWebAuthnBackend implementations, and the now-orphan PinRetriesResult record. The interface members were declared and implemented but never called from any client code path; consumers using them would depend on a code path with no integration coverage. Better to add them back later as part of a deliberate PIN-failure-flow story than to ship untested surface. CodeAudit reference: post-dc2ed141 audit findings #2, #3, #7 (HIGH severity). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…4 Criticals + 4 HIGH) Captures full session arc: WebAuthnExcludeListStressTests went FAIL → PASS via dc2ed14; 4 Critical CodeAudit findings landed in a0070db; 3 HIGH findings landed in 489c853 + 2b1b085; NFC test using-import fix in f547fca. All pushed to origin. Documents the 3 remaining HIGH findings (Tier B ExtensionPipeline + Tier C WebAuthnClient.cs split absorbing both DRY findings) plus 6 MEDIUM + 6 LOW audit items as named follow-ups with file:line and effort estimates so the next session can pick them up cold. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…2b00 YK 5.8.0-beta firmware accepts only Esp256SplitArkgPlaceholder (-65539, "ARKG-P256-ESP256") as the request alg for previewSign. Esp256 (-9) names the *output signature* alg only and is rejected at protocol-decode time if sent on the wire. This bug was independently caught by python-fido2, cnh-authenticator-rs, and the Yubico.NET.SDK-Legacy preview-sign branch (commit fe82b00 — fix on identical hardware, identical firmware). Three test files updated: 1. src/Fido2/tests/.../FidoPreviewSignTests.cs - Registration uses algorithms: [-65539] (was [-9]) - Output assertion expects -65539 (device echoes the negotiated request alg at extension key 3, not the internal sig alg) - Comments rewritten to spell out the -9 vs -65539 trap so future readers cannot reintroduce the bug 2. src/Fido2/tests/.../PreviewSignCborTests.cs - Sample COSE_Sign_Args bytes use alg=-65539 (was -9) - Encoder is a passthrough — the value is documentation, not behaviour, but documenting the wrong constant is what produced bug #1 in Legacy 3. src/WebAuthn/tests/.../PreviewSignTests.cs - Comment on the still-skipped FullCeremony test corrected; explains auth still requires additional_args (COSE_Sign_Args) builder, which is Phase 10 work — see Plans/phase-10-arkg-sign-args-builder-prd.md Verification on YK 5.8.0-beta, macOS arm64: Yubico.YubiKit.Fido2.UnitTests --filter "*PreviewSign*": 4/4 PASS Yubico.YubiKit.WebAuthn.UnitTests --filter "*PreviewSign*": 9/9 PASS Yubico.YubiKit.Fido2.IntegrationTests MakeCredential_WithPreviewSignExtension_ReturnsGeneratedSigningKey: PASS (was FAIL — wrong alg + wrong assertion) Diagnosis path: two parallel Engineer agents (python-fido2 forensics + Legacy SDK forensics) independently converged on the same root cause in one round. Detailed report: /tmp/arkg-forensics/LEGACY_PREVIEWSIGN_FORENSICS.md Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… (Phase 10 §3)
Replaces the opaque `ReadOnlyMemory<byte>? AdditionalArgs` field on
`PreviewSignSigningParams` with a typed, layered, Fido2-canonical builder
for the CTAP v4 `COSE_Sign_Args` map. Makes the `-9` vs `-65539` algorithm
bug class unrepresentable at the type level and ships the surface needed
for the FullCeremony hardware test.
Files changed (8):
- src/Fido2/src/Cose/CoseAlgorithm.cs (+ ArkgP256 alias = -65539)
- src/Fido2/src/Extensions/CoseSignArgs.cs (NEW: closed union)
- src/Fido2/src/Extensions/PreviewSignExtension.cs
(PreviewSignSigningParams: AdditionalArgs
→ CoseSignArgs; new EncodeCoseSignArgs)
- src/WebAuthn/src/Extensions/PreviewSign/PreviewSignSigningParams.cs
(re-exports Fido2 type, drops ad-hoc CBOR
validity check — type system enforces it)
- src/WebAuthn/src/Extensions/Adapters/PreviewSignAdapter.cs
(passes typed value through unchanged)
- src/Fido2/tests/.../PreviewSignCborTests.cs (typed builder; 8 new tests)
- src/WebAuthn/tests/.../PreviewSignSigningParamsTests.cs (NEW; 6 tests)
- src/WebAuthn/tests/.../PreviewSignTests.cs (FullCeremony rewired; still
Skip.If — awaits Dennis HW)
Public API delta (BREAKING — preview-stage, no external consumers):
- PreviewSignSigningParams.AdditionalArgs : ReadOnlyMemory<byte>?
+ PreviewSignSigningParams.CoseSignArgs : CoseSignArgs?
+ abstract record class CoseSignArgs { abstract int Algorithm; static
ArkgP256(ROM<byte>, ROM<byte>) }
+ sealed record class ArkgP256SignArgs : CoseSignArgs { ROM<byte>
KeyHandle; ROM<byte> Context }
+ static readonly CoseAlgorithm CoseAlgorithm.ArkgP256
Breaking-change justification: the codebase is preview-stage on
webauthn/phase-9.2-rust-port; the only consumers of `AdditionalArgs` were
in this repo (one unit test, one skipped integration test). Keeping both
fields would create a "two ways to do it" trap that re-admits the
`-9` vs `-65539` bug Dennis just fixed in 6ecbae3. The typed API is the
mitigation; leaving the escape hatch defeats it (PRD §6).
Constants (do NOT confuse — covered in XML doc + test asserts):
-65539 CoseAlgorithm.ArkgP256 / Esp256SplitArkgPlaceholder
→ wire signing-op alg, COSE_Sign_Args key 3 (this PRD)
-65700 ARKG_P256_PLACEHOLDER.ALGORITHM (python-fido2)
→ seed-key COSE-key alg, different layer (NOT used here)
Validation (binary):
✅ dotnet toolchain.cs build
Build succeeded. 0 Error(s), 1 pre-existing test-sdk warning.
✅ Fido2.UnitTests *PreviewSign* 17/17 passed (was 4 — added 13 new)
✅ Fido2.UnitTests full suite 371/371 passed (regression check)
✅ WebAuthn.UnitTests *PreviewSign* 15/15 passed (was 9 — added 6 new)
✅ WebAuthn.UnitTests full suite 100/100 passed (regression check)
✅ WebAuthn.IntegrationTests build clean (FullCeremony rewired,
Skip.If(true) awaiting Dennis manual HW verification)
Spec / forensics references:
- Plans/phase-10-arkg-sign-args-builder-prd.md (binding spec)
- Yubico.NET.SDK-Legacy commit fe82b00 — EncodeArkgSignArgs in
GetAssertionParameters.cs:402-499 (legacy reference shape)
- python-fido2/tests/test_arkg.py:36-73 (deterministic vectors;
CTX = "ARKG-P256.test vectors", 22 bytes)
- python-fido2/fido2/cose.py:389-460 (COSE_Sign_Args layer)
- /tmp/arkg-forensics/LEGACY_PREVIEWSIGN_FORENSICS.md (wire format §3.4)
Encoder byte-shape (test-asserted byte-for-byte, 126 bytes total):
A3 # map(3)
03 3A 0001 0002 # 3 : -65539
20 58 51 <81-byte KH> # -1 : bstr len 81
21 58 20 <32-byte CTX> # -2 : bstr len 32
Memory hygiene: KeyHandle/Context are ReadOnlyMemory<byte> passthroughs
(no internal clone) — caller owns and zeros after the request lands on
the wire (documented in XML doc; per repo CLAUDE.md "Security" guidance
that ROM passthrough is safe in record types).
PRD §4 had a 125-byte arithmetic typo — corrected to 126 in the test
sanity assertion. Byte-for-byte structural assertion is the binding
contract.
Hardware FullCeremony test pending Dennis manual verification.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…Sign
Root cause: Hardware-failing test `Registration_WithPreviewSign_ReturnsGeneratedSigningKey`
on YubiKey 5.8.0-beta crashed with `System.InvalidOperationException: Cannot perform the
requested operation, the next CBOR data item is of major type '0'` at
WebAuthnAttestationObject.cs:79.
The inner attestation object embedded in `unsignedExtensionOutputs["previewSign"][7]` is
CTAP-shaped — its keys are integers (`{1:fmt, 2:authData, 3:attStmt}`), NOT WebAuthn
text strings (`{"fmt","authData","attStmt"}`). The Fido2 decoder was returning the raw
inner CBOR bytes verbatim and the WebAuthn adapter was feeding them straight into
`WebAuthnAttestationObject.Decode`, which expects the WebAuthn text-keyed shape — hence
"major type 0" (unsigned int 1 for `fmt`) where it expected major type 3 (text string).
This matches the on-the-wire layout documented in the legacy SDK
(Yubico.NET.SDK-Legacy/Yubico.YubiKey/src/Yubico/YubiKey/Fido2/PreviewSignExtension.cs:144-147,
249-282) and was masked in the unit-test suite by `BuildAttestationObject` synthesizing a
WebAuthn-shaped (text-keyed) attestation object — the test exercised a shape the device
never emits. Phase 10 §3's typed `CoseSignArgs` builder is unaffected and remains green.
Fix:
- `Fido2.Extensions.PreviewSignCbor.DecodeUnsignedRegistrationOutput` now returns a typed
`InnerAttestationObject(string Fmt, ReadOnlyMemory<byte> AuthData,
ReadOnlyMemory<byte> AttStmtRawCbor)` decoded from the CTAP-shaped inner map. Per the
layering rule, canonical CBOR decode lives in Fido2.
- `WebAuthn.Extensions.Adapters.PreviewSignAdapter.ParseRegistrationOutput` consumes the
decoded components and rebuilds the spec attestation object via the existing
`WebAuthnAttestationObject.Create(authenticatorData, statement)` factory. WebAuthn owns
the wrap to the spec shape; no CBOR is decoded here.
- `PreviewSignAdapterTests.BuildAttestationObject` rewritten to emit the CTAP-shaped
inner map so the unit test reflects what the YubiKey actually returns. This becomes the
regression test for the parser bug.
Breaking change (internal): `DecodeUnsignedRegistrationOutput` return type changed from
`ReadOnlyMemory<byte>` to `PreviewSignCbor.InnerAttestationObject`. The only caller in
the codebase is `PreviewSignAdapter`; preview-stage internal API per branch convention.
Verification:
- Build: `dotnet toolchain.cs build` → Succeeded.
- Fido2 unit tests: 371/371 pass (PreviewSignExtension change covered).
- WebAuthn unit tests: 100/100 pass (regression test now reflects device truth).
- Hardware integration test will be re-run by Dennis on YubiKey 5.8.0-beta.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…wSign hardware-path advance - Adds binding PRD for Phase 10 §3 typed CoseSignArgs builder (was untracked through commits 6ecbae3/adcff793/0fbeb9c9; locked-decisions block in §7 records all 9 design choices Dennis approved this session) - Rewrites Plans/handoff.md to reflect 3-commit afternoon wave: 6ecbae3 previewSign -9 -> -65539 algo fix (Fido2 HW test PASS) adcff79 typed CoseSignArgs builder (471 unit tests green) 0fbeb9c WebAuthn CTAP-shaped attestation parser fix - Surfaces newly-discovered ARKG-P256 CoseKey decoder gap as Open Follow-up A (Phase 10 §4 candidate); WebAuthn FullCeremony hardware test now blocked on this + Yubico.Core ARKG port Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ign key holders Port Yubico.Core ARKG-P256 cryptographic primitives (525-LOC OpenSSL P/Invoke, 7 ZeroMemory sites, 3 KAT vectors) from Legacy SDK. Add typed CoseArkgP256SeedKey decoder, PreviewSignGeneratedKey/PreviewSignDerivedKey with offline ARKG derivation and ECDSA signature verification. Hardware-verified registration + ARKG seed-key extraction + offline derivation on YK 5.8.0-beta. FullCeremony GetAssertion+previewSign+ARKG remains skipped — CBOR encoding and ARKG derivation proven byte-identical to python-fido2 (cross-verified), but firmware rejects with 0x7F. Root cause narrowed to macOS HID transport layer; needs targeted FidoHidProtocol deep-dive. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Resolves Audit Gate 3 finding H-3 by adding CTAP error mapping for previewSign extension during authentication. Maps authenticator errors (UnsupportedAlgorithm, InvalidCredential, MissingParameter, etc.) to typed WebAuthnClientError enums instead of surfacing raw CtapException. Matches existing MakeCredential pattern. Removes TODO comment (line 715). Verified: All 100 WebAuthn + 377 Fido2 unit tests pass. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The ARKG-P256 seed-key wire format per draft-bradleylundberg-cfrg-arkg-10 and python-fido2 (cose.py:428-433) is: -1 = pkBl (blinding public key) -2 = pkKem (KEM public key) Modern's Decode and Encode had these inverted, causing the offline ARKG key handle to be encapsulated against the wrong KEM key. Firmware could not decapsulate, returning CTAP2_ERR_OTHER (0x7F) at GetAssertion time. The morning's "byte-identical wire" cross-check missed this because the CBOR bytes are identical end-to-end - only the labels attached to the two nested COSE keys were swapped. The existing Decode_WithValidArkgSeedKey_ReturnsCorrectVariant test embedded the same inversion in its producer/consumer expectations, so CI green proved nothing. Adds Decode_pkBlAtMinus1_pkKemAtMinus2_PerSpec - a spec-contract regression guard that uses distinguishable byte patterns (BL=0xAA, KEM=0xBB) at CBOR keys -1/-2 and asserts the decoder routes them to BlPublicKey and KemPublicKey respectively. Corrects the existing test to use the spec-correct wire layout. Hardware verified: FullCeremony_RegisterDeriveSignVerify_RoundTrip passes end-to-end on YubiKey 5.8.0-beta (12s, 2 user-presence touches). The integration test changes also fold in test-correctness fixes hardware-validated during phase D forensics: - omit second PIN-token acquisition (firmware previewSign state) - options.UserPresence = false to mirror python-fido2 reference - registration flags = 0 (no UP requirement on derived signing key) - non-resident credential (no rk option) - pass raw message to VerifySignature (avoids double-hash via VerifyData) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Handoff captures the full diagnostic chain (legacy ↔ python-fido2 ↔ modern parser diff), the swap-fix application via TDD, and the hardware-validated end-to-end green for FullCeremony. The strolling-star plan is the focused fix plan that replaced the parked wire-chatter cleanup plan; it stays for traceability of the root-cause narrative across future agents reading the PR history. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Prerelease 1.16.1-prerelease.20260428.1 is not on NuGet's official feed; CI resolves 1.16.1 instead, triggering NU1603 (warning-as-error). Pin to the stable release that is actually available. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
…to yubikit-applets
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
yubikitintegration branchKey fixes in this branch (recent sessions)
0x80indefinite length encodingGetAlgorithmInformationTLV from FW 5.4.3 (matches ykman padding approach)ChangeCredentialPasswordAdminTLV field ordering to[Label, MgmtKey, NewPw]IsSupported()handles0.0.1sentinel firmware correctlyKnown limitations
OtpHidProtocol.cs:211(works on alpha firmware)ChangeCredentialPasswordnot testable on alpha (INS0x0Bnot implemented,SW=0x6D00)Test plan
dotnet build Yubico.YubiKit.sln🤖 Generated with Claude Code