Skip to content

Fix software ksp provider cert deletion on windows#1036

Open
darkfronza wants to merge 2 commits into
feat/add-tpm-key-scopefrom
diego/fix-software-ksp-provider-cert-deletion-on-windows
Open

Fix software ksp provider cert deletion on windows#1036
darkfronza wants to merge 2 commits into
feat/add-tpm-key-scopefrom
diego/fix-software-ksp-provider-cert-deletion-on-windows

Conversation

@darkfronza
Copy link
Copy Markdown
Contributor

fix(capi,tpmkms): find/delete Software-KSP certs from a PCP-bound CAPIKMS

When the Windows agent renews a non-attested endpoint cert, the cleanup
path goes through tpmkms.LoadCertificateChain -> CAPIKMS bound to the
Microsoft Platform Crypto Provider, but the actual key lives in the
Microsoft Software Key Storage Provider. The pre-existing
"containerName" branch in CAPIKMS opened the CNG key via the bound
provider to derive a SubjectKeyID; that NCryptOpenKey can't open a
Software-KSP container from a PCP handle, so the lookup failed and the
old cert was never deleted. Each renewal then accumulated another cert
bound to the same container.

Three changes:

  • capi: in the containerName branch of getCertContext and
    DeleteCertificate, fall back to enumerating the store and matching by
    CERT_KEY_PROV_INFO container name when the key can't be opened. The
    property is set by CryptFindCertificateKeyProvInfo at store time
    regardless of provider, so the fallback works for any CNG-backed cert.
    Fix the long-broken CRYPT_KEY_PROV_INFO struct layout and the
    cryptFindCertificateKeyContainerName helper, which previously always
    returned "".

  • capi: stop the LoadCertificateChain walk when child.AuthorityKeyId is
    empty. Building "key-id=" with no value made getCertContext reject the
    URI; the right behavior for self-signed certs (and CAs that don't emit
    AKI) is to return the partial chain we have.

  • tpmkms: in loadCertificateChainFromWindowsCertificateStore and
    deleteCertificateFromWindowsCertificateStore, when GetPublicKey
    returns apiv1.NotFoundError (the key isn't in the TPM), hand CAPI the
    URI with "key=" instead of "key-id=". TPM-resident
    keys keep the existing SKI-indexed path.

Adds Windows-only tests under kms/capi and kms/tpmkms that cover the
fallback load+delete paths, the not-found shape, the indexed-path
regression guard, and round-tripping the container-name property reader.

Co-Authored-By: Claude Opus 4.7 (1M context) noreply@anthropic.com

darkfronza and others added 2 commits May 22, 2026 21:02
Switch github.com/smallstep/go-attestation from v0.4.9 to a pre-release
pseudo-version that includes the upstream changes required for the
per-key key-scope option (Windows machine vs user key store) added in
this branch.

Change-Type: build
Release-Note: no
Audience: developer
Impact: low
Breaking: false
Co-Authored-By: Claude <noreply@anthropic.com>
…IKMS

When the Windows agent renews a non-attested endpoint cert, the cleanup
path goes through tpmkms.LoadCertificateChain -> CAPIKMS bound to the
Microsoft Platform Crypto Provider, but the actual key lives in the
Microsoft Software Key Storage Provider. The pre-existing
"containerName" branch in CAPIKMS opened the CNG key via the bound
provider to derive a SubjectKeyID; that NCryptOpenKey can't open a
Software-KSP container from a PCP handle, so the lookup failed and the
old cert was never deleted. Each renewal then accumulated another cert
bound to the same container.

Three changes:

- capi: in the containerName branch of getCertContext and
  DeleteCertificate, fall back to enumerating the store and matching by
  CERT_KEY_PROV_INFO container name when the key can't be opened. The
  property is set by CryptFindCertificateKeyProvInfo at store time
  regardless of provider, so the fallback works for any CNG-backed cert.
  Fix the long-broken CRYPT_KEY_PROV_INFO struct layout and the
  cryptFindCertificateKeyContainerName helper, which previously always
  returned "".

- capi: stop the LoadCertificateChain walk when child.AuthorityKeyId is
  empty. Building "key-id=" with no value made getCertContext reject the
  URI; the right behavior for self-signed certs (and CAs that don't emit
  AKI) is to return the partial chain we have.

- tpmkms: in loadCertificateChainFromWindowsCertificateStore and
  deleteCertificateFromWindowsCertificateStore, when GetPublicKey
  returns apiv1.NotFoundError (the key isn't in the TPM), hand CAPI the
  URI with "key=<container>" instead of "key-id=<SKI>". TPM-resident
  keys keep the existing SKI-indexed path.

Adds Windows-only tests under kms/capi and kms/tpmkms that cover the
fallback load+delete paths, the not-found shape, the indexed-path
regression guard, and round-tripping the container-name property reader.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@hslatman hslatman changed the title Diego/fix software ksp provider cert deletion on windows Fix software ksp provider cert deletion on windows May 27, 2026
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