Skip to content

Implement PKCS#11 certificate support in ssh-agent#865

Open
VSSOtt wants to merge 8 commits into
PowerShell:latestw_allfrom
VSSOtt:agent-pkcs11-certificates
Open

Implement PKCS#11 certificate support in ssh-agent#865
VSSOtt wants to merge 8 commits into
PowerShell:latestw_allfrom
VSSOtt:agent-pkcs11-certificates

Conversation

@VSSOtt

@VSSOtt VSSOtt commented Jul 17, 2026

Copy link
Copy Markdown

PR Summary

Implement Windows ssh-agent support for OpenSSH certificates backed by
PKCS#11 token keys via associated-certs-v00@openssh.com.

The implementation covers RSA and ECDSA token keys and includes:

  • certificate-only loading with ssh-add -s provider -C cert.pub
  • matching supplied certificates to token keys
  • persistent certificate identities across agent service restarts
  • individual certificate deletion and provider-wide removal
  • additive, idempotent repeated provider adds, so certificates can be added in
    separate requests without dropping existing persisted identities
  • PKCS#11 token labels as identity comments, with provider-path fallback
  • signing that continues to work when unrelated persisted providers are stale
  • legacy PKCS#11 registry entries that remain readable and removable

PR Context

The Windows ssh-add help text already advertised the PKCS#11 certificate
flow, but the Windows agent did not handle certificate identities
from associated-certs-v00@openssh.com. As a result, adding certificates
appeared to succeed without making them usable as agent identities.

Upstream OpenSSH already defines how certificates are associated with
PKCS#11-backed keys. The main Windows-specific difference is persistence:
unlike the usual Unix agent model, the Windows agent stores PKCS#11 providers
and identities in the registry and reloads them after the service restarts.

Because of that persistent model, repeated provider adds merge with existing
stored identities instead of replacing them. This allows certificates for the
same token provider to be added in separate requests while preserving
certificates and plain token-key identities that were already stored.

This PR intentionally does not change the existing Windows PKCS#11 constraint
semantics. Constraints such as confirm, lifetime, and destination restrictions
may be accepted for PKCS#11 provider additions, but they are still not enforced
for persisted Windows PKCS#11 identities. In particular, ssh-add -c -s ...,
ssh-add -t ... -s ..., and destination-constrained PKCS#11 additions should
not be treated as protected by those constraints.

This implementation keeps these existing security boundaries intact:

  • provider allowlist enforcement remains unchanged
  • remote-provider restrictions remain unchanged
  • PIN storage keeps the existing encrypted registry model
  • no private key material is stored by the agent

Validation

Windows x64 Debug/Release and x86 Release builds, Windows unit tests, Pester
tests, bash regressions, and non-Windows regression coverage pass.

Real-token validation was performed with a YubiKey 5 using RSA 2048 and ECDSA
NIST P-256. It covered:

  • add, list, and sign
  • certificate-only mode
  • unmatched certificates
  • repeated provider adds
  • token-label identity comments
  • individual certificate deletion
  • software certificate deletion
  • stale unrelated provider records
  • provider removal
  • service restart and reload

VSSOtt added 6 commits July 17, 2026 10:34
Bring the Windows agent implementation in line with upstream support for
associated-certs-v00@openssh.com.

Match supplied certificates to PKCS#11-backed RSA and ECDSA keys,
persist and reload certificate identities, and support certificate-only
loading. Enable the existing -C option in the Windows ssh-add argument
parser.

Persist certificate identities separately from their underlying keys
and reconstruct their PKCS#11 signing state after an agent restart.
Allow individual certificate deletion while preserving provider-wide
removal behavior.

Keep the existing provider allowlist, remote-provider restrictions, and
encrypted PIN storage intact. Reject malformed, duplicate, truncated,
and oversized certificate constraints.

Add Windows unit and Pester coverage for RSA and ECDSA certificates,
certificate-only loading, unmatched certificates, deletion, provider
removal, and service restart.
Treat repeated provider additions as additive and idempotent because the Windows service persists providers and identities across client and service lifetimes. Unlike the in-memory Unix agent, this allows certificates to be added in separate requests without replacing earlier identities.

Roll back only identities created by the failed request and preserve all previously persisted state.
Try the regular fingerprint-backed Registry entry before falling back to the PKCS#11 certificate identity name. This preserves individual removal for both software-backed and token-backed certificates when PKCS#11 support is enabled.

Add Windows regression coverage for RSA and ECDSA software certificates.
Continue past persisted providers that cannot be loaded while rebuilding the transient PKCS#11 key set for a sign request. The requested identity still fails when its backing key is unavailable, while software keys and identities from other providers remain usable.

Cover signing with a stale provider record in the Windows PKCS#11 E2E tests.
Persist the PKCS#11 provider association separately from the user-visible identity comment and use the label returned by the provider. Empty labels continue to fall back to the canonical provider path.

Keep legacy Registry entries readable and removable, migrate their metadata on a repeated add, and preserve labels across merged certificate additions and service restarts.
Skip persisted provider records with invalid or oversized Registry metadata while rebuilding the transient signing key set. This keeps corrupt records from causing unbounded allocations or blocking unrelated identities.

Free Windows PKCS#11 provider list entries during termination and extend the stale-provider regression to cover oversized encrypted PIN data.
Copilot AI review requested due to automatic review settings July 17, 2026 16:32
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
There may be pipelines that require an authorized user to comment /azp run to run.

@VSSOtt

VSSOtt commented Jul 17, 2026

Copy link
Copy Markdown
Author

@microsoft-github-policy-service agree

@VSSOtt

VSSOtt commented Jul 17, 2026

Copy link
Copy Markdown
Author

/azp run

@azure-pipelines

Copy link
Copy Markdown
Commenter does not have sufficient privileges for PR 865 in repo PowerShell/openssh-portable

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds Windows ssh-agent support for OpenSSH certificates backed by PKCS#11 token keys (via associated-certs-v00@openssh.com), aligning behavior with upstream semantics while preserving the Windows agent’s registry-backed persistence model.

Changes:

  • Add Windows support code for constructing PKCS#11-backed certificate identities and persisting/loading them across agent restarts.
  • Extend the Windows agent request handling to support PKCS#11 certificate add/list/delete flows and more robust signing behavior with stale providers.
  • Add Windows unit tests + Pester E2E coverage and wire new Win32 PKCS#11 cert helper code into the build.

Reviewed changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
ssh-pkcs11-client.c Fix provider lifecycle memory handling; add Windows implementation for PKCS#11-backed cert private keys.
ssh-add.c Enable -C parsing on Windows to support certificate-only PKCS#11 adds.
regress/unittests/win32compat/tests.h Declare new PKCS#11 cert test entry point.
regress/unittests/win32compat/tests.c Register PKCS#11 cert unit tests in the Windows test runner.
regress/unittests/win32compat/pkcs11_cert_tests.c Add unit tests for PKCS#11 cert constraint parsing and identity naming/comment behavior.
regress/pesterTests/README.md Document required environment variables for PKCS#11 certificate E2E tests.
regress/pesterTests/KeyUtils.Tests.ps1 Add Pester E2E scenarios for PKCS#11 certificate add/sign/restart/delete flows and stale-provider isolation.
regress/pesterTests/CommonUtils.psm1 Make askpass environment setup more robust across PowerShell/Windows environments.
contrib/win32/win32compat/ssh-agent/keyagent-request.c Implement persisted PKCS#11 cert identities + provider/identity registry management and signing behavior updates.
contrib/win32/win32compat/pkcs11-cert.h Add Win32 helper API for cert constraints and identity naming/comment logic.
contrib/win32/win32compat/pkcs11-cert.c Implement constraint parsing and PKCS#11 cert identity naming/comment helpers.
contrib/win32/openssh/unittest-win32compat.vcxproj Add the new helper + unit test compilation units to the Win32 test project.
contrib/win32/openssh/ssh-agent.vcxproj Add the new helper compilation unit/include to the Windows ssh-agent project.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread ssh-pkcs11-client.c
Comment thread contrib/win32/win32compat/ssh-agent/keyagent-request.c
Comment thread contrib/win32/win32compat/ssh-agent/keyagent-request.c
Comment thread contrib/win32/win32compat/ssh-agent/keyagent-request.c
VSSOtt added 2 commits July 17, 2026 19:31
Keep SECURITY_ATTRIBUTES.nLength stable when converting Registry security descriptors by using a separate descriptor-size variable. Also wipe the full encrypted PIN allocation during provider reload cleanup.
Set SSHKEY_FLAG_EXT in the Windows wrap_key() path, matching the non-Windows helper and keeping the runtime flag with the private-operation redirection. Move the flag assignment there for consistency, so wrapped keys are marked external at their creation point instead of only in the certificate path.

Remove the now-redundant certificate-specific assignment.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 13 out of 13 changed files in this pull request and generated no new comments.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants