Skip to content

feat(applets): merge yubikit-applets into yubikit#446

Open
DennisDyallo wants to merge 171 commits intoyubikitfrom
yubikit-applets
Open

feat(applets): merge yubikit-applets into yubikit#446
DennisDyallo wants to merge 171 commits intoyubikitfrom
yubikit-applets

Conversation

@DennisDyallo
Copy link
Copy Markdown
Collaborator

Summary

  • Adds all six YubiKey applet implementations (PIV, OATH, OpenPGP, FIDO2, YubiOTP, HsmAuth) and their CLI tools to the yubikit integration branch
  • Includes 39+ bug fixes found during integration testing across firmware 5.4.3 and 5.8.0-alpha
  • All primary workflows verified working: PIV 44/44, OATH 8/8, OpenPGP 28/28, FIDO2 full (HID excluded — macOS OS constraint)

Key fixes in this branch (recent sessions)

  • core: reject BER-TLV 0x80 indefinite length encoding
  • openpgp: fallback for malformed GetAlgorithmInformation TLV from FW 5.4.3 (matches ykman padding approach)
  • openpgp: relax reset-state test assertions — 5.4.3 does not clear fingerprint DOs on TERMINATE+ACTIVATE
  • hsmauth: correct ChangeCredentialPasswordAdmin TLV field ordering to [Label, MgmtKey, NewPw]
  • piv: bypass .NET TripleDES weak key rejection for PIV default management key
  • otp: use HidOtp transport for HMAC-SHA1 challenge-response tests
  • core: IsSupported() handles 0.0.1 sentinel firmware correctly

Known limitations

  • YubiOTP HMAC-SHA1 HID timeout on FW 5.4.3 at OtpHidProtocol.cs:211 (works on alpha firmware)
  • HsmAuth ChangeCredentialPassword not testable on alpha (INS 0x0B not implemented, SW=0x6D00)
  • FIDO2 HID tests excluded — macOS CTAP daemon holds exclusive access

Test plan

  • Unit tests: 9/9 projects passing, 0 failures
  • PIV integration: 44/44 on FW 5.4.3
  • OATH integration: 8/8 on FW 5.4.3
  • OpenPGP integration: 28/28 on FW 5.4.3
  • YubiOTP integration: 5/7 (2 HID timeout failures on 5.4.3, by-design on CCID)
  • Build: 0 errors, 0 warnings on dotnet build Yubico.YubiKit.sln

🤖 Generated with Claude Code

DennisDyallo and others added 30 commits January 27, 2026 15:57
Adds complete FIDO2/CTAP2 support:
- Authenticator info and capabilities
- MakeCredential and GetAssertion operations
- PIN/UV auth protocols (v1 and v2)
- Credential management
- Bio enrollment
- Large blob storage
- Extensions support
Adds complete PIV smart card support:
- Session management and authentication
- PIN/PUK/Management key operations
- Key generation and certificate management
- Signing and decryption operations
- Slot metadata and touch policies
Adds complete example CLI demonstrating PIV operations:
- Device selection and info display
- PIN/PUK management
- Key generation across slots
- Certificate operations
- Signing and decryption examples
- Attestation verification
… test

Discovered and fixed by running all 20 CLI commands against YubiKey 5.8.0:

- PivDataObject.Attestation: wrong tag 0x5FC121 (PIV Iris Images) → 0x5FFF01
  (Yubico-specific attestation cert object); was returning SW=0x6982 on read
- PivSlotMetadataExtensions: parse raw PIV TLV format (0x86+point for ECC,
  0x81/0x82 for RSA) instead of SPKI; fixed "ASN1 corrupted data" errors
- YubiKeyEcdsaSignatureGenerator: YubiKey returns DER (RFC 3279), not P1363;
  removed ConvertIeeeP1363ToDer that was corrupting CSR signatures
- Signing/Decryption: use explicit algorithm from slot metadata to avoid
  auto-detect overload that checks PIV app version (0.0.1) not device firmware
- Verification: add DSASignatureFormat.Rfc3279DerSequence to ECDsa.VerifyData;
  YubiKey ECDSA signatures are DER-encoded, not IEEE P1363

Also adds the CLI Commands layer (CliRunner, DeviceHelper, JsonOutput) enabling
autonomous end-to-end testing of all PIV session APIs without human interaction.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Added missing newlines at the end of multiple files in the PivTool example.
…ey-manager

Moves RSA padding removal into PivSession.DecryptAsync, matching the Python
yubikey-manager PivSession.decrypt() API where the session owns the full
decrypt-and-unpad operation rather than exposing raw RSA output.

Implementation uses dummy RSA key + BigInteger.ModPow re-wrap technique
(identical to Python's _unpad_message) so .NET's padding-aware RSA.Decrypt
can safely unpad PKCS#1 v1.5 or OAEP output from the raw YubiKey response.

Security:
- rawBytes and reEncrypted intermediates zeroed in finally blocks
- try scope starts before ModPow allocation to guarantee cleanup
- Added b >= mod guard and output length bounds check in ModPow helper

Refactors:
- IAsyncDisposable moved up to ApplicationSession base class; redundant
  overrides removed from PivSession and ManagementSession
- Decryption.cs delegates entirely to session.DecryptAsync (no manual
  PKCS#1 stripping); padding parameter threaded through to call sites

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ding

Covers the 4 key scenarios for IPivSession.DecryptAsync:
- RSA 2048 PKCS#1 v1.5 round-trip: encrypt with host RSA, decrypt via YubiKey, assert exact plaintext
- RSA 2048 OAEP SHA-256 round-trip: same pattern with modern padding
- RSA 4096 PKCS#1 round-trip: exercises the keyBits switch in the session-layer ModPow path
- ECC slot rejection: ArgumentException thrown before any APDU (session checks metadata first)
- Wrong ciphertext length: ArgumentException thrown before any APDU (length vs key size check)

Modelled after Python yubikey-manager's test_piv.py. Engineer extracted
ResetAndAuthenticate and EncryptThenDecryptRoundTrip helpers to eliminate setup
duplication across tests. Reviewer passed clean (no HIGH issues).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Implements TOTP/HOTP one-time password management for YubiKey devices.
Includes session, models, CLI tool, unit tests, and integration tests.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Implements Yubico OTP, HOTP, static password, and HMAC-SHA1 challenge-response.
Includes dual-transport backend (SmartCard + OTP HID), slot configurations,
CLI tool, unit tests, and integration tests.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Implements credential-based authentication for YubiHSM 2 devices.
Supports symmetric (AES-128) and asymmetric (EC P256) credentials.
Includes session, models, CLI tool, unit tests, and integration tests.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Implements OpenPGP card v3.4 with partial class organization.
Supports RSA/EC key generation, sign/decrypt/authenticate, PIN management,
KDF, certificates, and UIF. Includes CLI tool, unit tests, and integration tests.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds Spectre.Console TUI/CLI tool for FIDO2 operations including
authenticator info, PIN management, credential operations, bio enrollment,
and reset. Supports command-line parameters for automated testing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
FIDO2 integration tests depend on Core types (IYubiKeyManager,
DeviceListenerService, etc.) that exist on the yubikit-fido branch
but not on the yubikit base. Excluded until those Core changes are merged.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Zero sensitive data (keyValue, secret, deviceResponse) with CryptographicOperations.ZeroMemory()
- Replace List<byte>.AddRange(span.ToArray()) with direct byte array construction using CopyTo
- Replace List<byte> with MemoryStream in CollectResponseData to avoid repeated ToArray() calls

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Replace bare catch with Enum.IsDefined in PwStatus.Parse (no
  exceptions for control flow)
- Use generic Enum.IsDefined<T> in DiscretionaryDataObjects
  ParseKeyInformation
- Fix undisposed Tlv objects in Crt static property initializers by
  extracting BuildCrt helper with using statement
- Run dotnet format to fix trailing newlines across all 37 source files

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove dead _logger field and unused Microsoft.Extensions.Logging import
from HsmAuthSession (base class Logger property is used instead). Add
security warning comments to example tool files that display key material.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…rastructure

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Replace injected ILogger with static LoggingFactory pattern in
  SmartCardBackend and OtpHidBackend (matches CLAUDE.md guidelines)
- Use ArgumentNullException.ThrowIfNull instead of ?? throw pattern
- Add IDisposable to IYubiOtpBackend to match IManagementBackend style
- Add no-op Dispose() to both backend implementations
- Remove unnecessary .ToArray() on "en"u8 in BuildNdefText, use
  ReadOnlySpan<byte> with Span.CopyTo instead of Array.Copy
- Zero sensitive scanCodes (password bytes) in ProgramCommand static
  password methods with CryptographicOperations.ZeroMemory in finally
- Remove logger parameter from SmartCardBackend constructor calls

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace Spectre.Console interactive menus with ykman-compatible command
tree (info, reset, access change, accounts list/add/code/delete/rename/uri).
Auto-selects device when one YubiKey connected, --force/-f skips prompts,
all output is pipe-friendly with errors on stderr.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Restructure the FidoTool CLI to mirror ykman's fido command tree:
- info, reset [-f], access (change-pin, verify-pin), config
  (toggle-always-uv, enable-ep-attestation), credentials (list,
  delete), fingerprints (list, add, delete, rename)
- PIN provided via --pin option, prompted interactively if omitted
- -f/--force flag skips confirmation prompts on destructive ops
- Positional args for credential/fingerprint IDs (ykman style)
- Rename PinMenu->AccessMenu, BioMenu->FingerprintsMenu,
  CredentialMgmtMenu->CredentialsMenu to match ykman naming
- Add VerifyPinAsync to PinManagement, PromptForPin to OutputHelpers
- Keep MakeCredential, GetAssertion, and extended operations in
  interactive mode only (not part of ykman's fido command tree)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace Spectre.Console interactive menus with ykman-compatible command
tree (info, reset, access change, accounts list/add/code/delete/rename/uri).
Auto-selects device when one YubiKey connected, --force/-f skips prompts,
all output is pipe-friendly with errors on stderr.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace interactive Spectre.Console menu system with Spectre.Console.Cli
command tree matching ykman's openpgp subcommand hierarchy:

- info: display general status
- reset [-f]: factory reset
- access: set-retries, change-pin, change-admin-pin, set-reset-code, unblock-pin
- keys: set-touch, import, generate, attest
- certificates: export, import, delete

All commands support non-interactive use via CLI options (--pin, --admin-pin,
--force) while prompting when options are omitted. Device auto-selected when
only one YubiKey is connected.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
A single YubiKey appears on both SmartCard and HidOtp transports.
Select the first available device, preferring SmartCard transport.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
DennisDyallo and others added 29 commits April 15, 2026 10:02
Fix connection resource leak when session creation throws after
ConnectAsync succeeds. Affects: Fido2, Oath, OpenPgp, SecurityDomain,
YubiHsm, Management. PIV and YubiOtp were already fixed in stage 1.

Each module now wraps CreateAsync in try/catch that disposes the
connection on failure, matching the PIV/YubiOtp pattern.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- PIV: Replace 8 hand-rolled BER-TLV length encoding sites with
  BerLength.Write/EncodingSize (Certificates, Crypto, DataObjects)
- YubiHsm: Delegate ExtractRetries to SWConstants.ExtractRetryCount
- OpenPgp: Replace inline retry extraction with SWConstants.ExtractRetryCount
  (also fixes subtle bug: old mask 0xFF00 matched 0x6300-0x63FF, new mask
  0xFFF0 correctly matches only 0x63C0-0x63CF)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
EnsureReady() was added but never adopted by any module — each session
has its own [MemberNotNull]-annotated protocol guard that the compiler
requires for nullable analysis. Removing to avoid dead code.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Zero inputArr in DesBlockOperation finally block (security)
- Replace single-byte length casts in BuildAuthResponse with BerLength.Write
- Add TODO for GetBioMetadataAsync positional parsing (needs TLV verification)
- Replace 5 inline retry count extractions with SWConstants.ExtractRetryCount
- Delegate PivPinUtilities.GetRetriesFromStatusWord to SWConstants
- Replace List<byte> with ArrayBufferWriter in StoreCertificateAsync/PutObjectAsync

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Fix double SkipValue in ParseDecryptedBlob (corrupted parse on non-empty
  byte string keys — conditional skip based on whether key was consumed)
- Extract shared ECDH P-256 key agreement into PinUvAuthHelpers, eliminating
  ~40 lines of duplicated code between PinUvAuthProtocolV1/V2.Encapsulate

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…params

- Make CredentialData implement IDisposable, zeroing Secret on Dispose
- Zero intermediate HmacShortenKey buffer when different from input
- Update 4 caller sites to use 'using' (CLI commands, examples)
- Change ValidateAsync/SetKeyAsync params from byte[] to ReadOnlyMemory<byte>
- Fix path.Contains(':') missing StringComparison.Ordinal

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ilter

- Replace silent access code truncation with strict size validation
- Add exception filter to prevent swallowing OperationCanceledException

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…NTEGER

- Zero FormatEcSignPayload hash buffer after sign/authenticate APDU
- Fix KdfIterSaltedS2k.Dispose() to call base.Dispose()
- Replace single-byte length in EncodeAsn1Integer with BerLength.Write
- Fix KdfNone.ToBytes() .AsMemory().ToArray() → .AsSpan().ToArray()

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…n, DI param

- Fix GetCaIdentifiersAsync bounds check (Length >= 2 instead of !IsEmpty)
- Dispose Tlv objects in 5 methods (StoreAllowList, StoreCaIssuer,
  StoreCertificates, GetCertificates, and nested constructions)
- Remove ZeroMemory(parameters.D) that mutated caller's ECPrivateKey
- Add firmwareVersion parameter to SecurityDomainSessionFactory delegate

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Encodes a collection of TLV objects then disposes each in a finally
block. Prevents sensitive data from lingering in undisposed Tlv buffers
when Tlvs are created inline for EncodeList calls.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace TlvHelper.EncodeList with EncodeAndDisposeList at all call sites
where Tlvs are created inline. Prevents sensitive data (management keys,
credential passwords, private keys) from lingering in undisposed buffers.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add using to Tlv objects in ParseAuthResponse, ParseCryptoResponse,
UnwrapDataObjectResponse, and ParsePublicKey. Copies data out before
disposal to avoid use-after-dispose.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…shared core

Restore clear encrypt/decrypt semantics at call sites while keeping
shared buffer management in CryptoBlockCore. Parameters renamed to
plaintext/ciphertext for self-documenting data flow.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Plans/todo-backlog-workplan.md: 19 Jira issues (YESDK-1559–1577) prioritized
- Plans/foamy-swimming-summit.md: Stage 4 implementation plan with deferred analysis
- Plans/handoff.md: Updated with full 5-stage session summary

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ModPow returned the raw BigInteger byte array directly when its length
matched the output size, but the finally block then zeroed that same
array via CryptographicOperations.ZeroMemory. Since arrays are reference
types, callers received a zeroed buffer, breaking all RSA decrypt ops.

Clone with .AsSpan().ToArray() before returning so the finally block
safely zeros only the BigInteger's internal allocation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… format

Replace 36 old-format [Trait("RequiresUserPresence", "true")] with
[Trait(TestCategories.Category, TestCategories.RequiresUserPresence)]
across 17 files. The old format was not caught by the --smoke filter
(Category!=RequiresUserPresence), causing 39 false failures.

Add missing trait to 8 test methods that require user presence but
had no trait at all: ToggleAlwaysUv, SetMinPinLength,
GetFingerprintSensorInfo, GetCredentialsMetadata, and 4
EncryptedMetadata tests.

Remove duplicate trait on FidoPinManagementTests.ChangePin.
Update comment in FidoSessionSimpleTests to reference new format.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
HidYubiKey.DeviceId used VendorId:ProductId:Usage which is identical
across multiple YubiKeys, causing one key's HID interfaces to overwrite
the other in the device cache. Include ReaderName (unique device path)
in DeviceId, matching how PcscYubiKey already uses ReaderName.

ClientPin.GetPinUvAuthTokenUsingPinAsync always sent CTAP2.1 subcommand
0x09 (getPinUvAuthTokenUsingPinWithPermissions), failing on CTAP2.0
devices. Check pinUvAuthToken option from GetInfo and fall back to
legacy getPinToken (subcommand 0x05) when unsupported, matching
python-fido2's ClientPin.get_pin_token() logic.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ChangePin_WithValidCurrentPin used OriginalPin="Abc12345" which did not
match the test harness normalized PIN "11234567", causing the test to
fail and corrupt PIN state for all subsequent tests.

Use FidoTestStateExtensions.KnownTestPin as the starting PIN, remove
redundant SetOrVerifyPinAsync call (NormalizePinAsync handles this),
and hoist protocolVersion out of try block for finally block access.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ng signal

- NormalizePinAsync detects forcePinChange and clears it via reversed-PIN
  change (Enhanced PIN keys reject same-PIN changes with PinPolicyViolation)
- SetMinPinLength uses fixed target (6) instead of incrementing each run
- ForceChangePin tests full lifecycle: set flag, verify blocked, change PIN,
  verify restored (matching python-fido2 test_force_pin_change pattern)
- Add PermanentDeviceState trait for one-way device state changes

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…oMemory to all APDU data buffers

- Replace ConcatTlvs helper with [..a, ..b] collection expressions
- Add ZeroMemory for data buffers in CalculateAsync, CalculateCodeAsync,
  ValidateAsync, and SetKeyAsync (RenameCredentialAsync already had it)
- Remove both ConcatTlvs overloads (no longer needed)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
fix: code audit fixes across all YubiKey applet modules
AuthenticatorConfig tests: update authenticated message assertions
from 2-byte [0xff, subCmd] to 34-byte [32×0xff || 0x0D || subCmd]
per CTAP 2.1 spec section 6.8.

ClientPin test: add GetInfoAsync mock returning pinUvAuthToken=true
so the CTAP2.0 fallback path added in c4c591b doesn't null-deref.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
- Trigger CI on yubikit and yubikit-applets branches
- Add packages: write permission for GitHub Packages auth
- Pack with version 2.0.0-preview.<run_number> after tests pass
- Push *.nupkg to nuget.pkg.github.com/Yubico using GITHUB_TOKEN
- Bump version baseline to 2.0.0-preview.1 in Directory.Packages.props

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Replace Node.js 20 action pins with v5 releases using Node.js 24:
- actions/checkout v4.3.1 → v5.0.1
- actions/setup-dotnet v4.3.1 → v5.2.0
- actions/cache v4.3.0 → v5.0.5

Eliminates the Node.js 20 deprecation warnings ahead of the June 2026
forced migration and September 2026 removal deadlines.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
- Convert sync test methods using .GetAwaiter().GetResult() to async Task
- Replace Task.Wait() with await Task.WhenAny() in disposal tests
- Replace Assert.Single(Where(...)) with Assert.Single(collection, predicate)
- Rename build.cs to toolchain.cs and BUILD.md to TOOLCHAIN.md
- Add publish-remote target: accepts --nuget-feed-url and --nuget-api-key,
  pushes built packages to any remote NuGet registry (e.g. GitHub Packages)
- Update CI workflow to use dotnet toolchain.cs publish-remote instead of
  raw dotnet nuget push, completing full CI/toolchain symmetry
- Update all 88 references across docs, agents, skills, and CLAUDE.md files
…dling

Security:
- nugetApiKey now read from --nuget-api-key arg OR NUGET_API_KEY env var
- CI workflow passes GITHUB_TOKEN via step env block, not inline command arg
- dotnet nuget push uses --api-key (key is masked by GitHub Actions secret masking)

Bug fixes:
- Fix stray '--' literal in MTP filter command format string (logic-error)
- Log original exception in setup-feed catch block (error-swallowing)
- Fix pre-existing CS1503: CancellationToken passed as ConnectionType in test

Refactoring:
- Extract PublishPackages() helper shared by publish and publish-remote targets
- Extract FilterProjectPaths() shared by FilterToProject and FilterProjectsByName
- Extract PrintProjectHeader() helper, consistent separator across test/coverage output
- Define ProjectPrefix const for "Yubico.YubiKit." used in DiscoverProjects calls
- PrintHelp() now accepts packableProjects/testProjects as parameters (SRP)
- Remove redundant File.Exists guard in UsesMicrosoftTestingPlatformRunner

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Development

Successfully merging this pull request may close these issues.

2 participants