Skip to content

capi KMS: LoadCertificate loses store-location when key is referenced by container name only, causing NTE_BAD_KEYSET #365

Description

@dcdamien

Summary

When a capi key is referenced only by container name (key=<name>, no sha1=/key-id=/issuer=), LoadCertificate (via getCertContext) rebuilds an internal URI for GetPublicKey that carries only the container name and drops store-location. getKeyFlags then falls into its default case and never sets NCRYPT_MACHINE_KEY_FLAG, so nCryptOpenKey searches the user keyset instead of the machine keyset and fails with NTE_BAD_KEYSET.

Version

step-kms-plugin.exe version
step-kms-plugin/0.17.0 (windows/amd64)
Release Date: 2026-04-08T18:48:33Z

Current latest release.

Where

kms/capi/capi.go, getCertContext, the containerName != "" branch. It builds the lookup URI with only the container name argument, unlike CreateKey/CreateSigner, which correctly include the store location.

Repro

step kms create capi:store-location=machine;key=mykey
# sign a cert for that key - this step works, CreateSigner sets the machine flag correctly
step kms certificate --import cert.crt capi:store-location=machine;store=My;key=mykey

The write in the last step succeeds - the certificate is correctly imported into Cert:\LocalMachine\My and the private key is correctly bound (independently verified: HasPrivateKey is True on the resulting certificate). But the plugin's own read-back check right after the write, LoadCertificate, fails with:

unable to open key: NCryptOpenKey for container "mykey" returned NTE_BAD_KEYSET

...and the command exits non-zero even though the real work already succeeded. Any caller checking the exit code sees a false failure after a successful operation.

Expected

The URI rebuilt in getCertContext for the container-name-only branch should also carry the store location (and store name, if set), so getKeyFlags can set NCRYPT_MACHINE_KEY_FLAG correctly on that GetPublicKey call, the same way CreateKey/CreateSigner already do.

Workaround

Identify the key with sha1=<thumbprint> instead of key=<name> on the store step - that routes getCertContext through a different branch that doesn't hit this.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions