Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 44 additions & 1 deletion kms/tpmkms/tpmkms.go
Original file line number Diff line number Diff line change
Expand Up @@ -1060,6 +1060,20 @@ func (k *TPMKMS) storeCertificateChainToWindowsCertificateStore(req *apiv1.Store
// prefixed with "app-" (see prefixKey / go-attestation), which is how the
// key was persisted in the PCP KSP. CAPI resolves the keyset from key-scope,
// falling back to store-location when key-scope is unset.
//
// Only for a key this TPMKMS actually holds, though. A caller may name a key
// that is not a TPM key at all — a Windows endpoint with no key protection
// has its key created through CAPI in the software KSP, under the bare name,
// and this store has never heard of it. Claiming such a certificate lives in
// "app-<name>" under the Platform Crypto Provider names a container that
// does not exist: the certificate then reports HasPrivateKey while resolving
// no key at all, so it cannot complete a handshake and cannot be found again
// by a lookup that goes through the key.
//
// The non-Windows branch of StoreCertificateChain already resolves the key
// before storing; this makes the Windows branch agree, and falls back to
// discovery — what every caller had before the explicit binding — when the
// name is not ours.
v := url.Values{
"store-location": []string{location},
"store": []string{store},
Expand All @@ -1069,12 +1083,28 @@ func (k *TPMKMS) storeCertificateChainToWindowsCertificateStore(req *apiv1.Store
"intermediate-store-location": []string{intermediateCAStoreLocation},
"intermediate-store": []string{intermediateCAStore},
}
if o.name != "" {
switch {
case o.name == "":
case k.managesKey(o.name):
v.Set("key", tpm.ApplicationKeyName(o.name))
v.Set("provider", microsoftPCP)
if o.keyScope != "" {
v.Set("key-scope", o.keyScope)
}
default:
// Named a key this TPM does not hold, so let CAPI find it. Discovery is
// re-enabled here specifically, overriding the blanket
// skip-find-certificate-key the platform wrapper injects into every
// Windows request: that exists to avoid a smart-card prompt while
// looking for a TPM key discovery cannot find anyway, and neither half
// of that reasoning applies to a key held by another provider.
//
// Without this the certificate is stored with no key association at
// all, which is how a non-attested endpoint's certificate behaved
// between the skip being introduced and the explicit binding being
// added. CAPI restricts the search to the keyset the store location
// implies, so this does not widen it.
v.Set("skip-find-certificate-key", "false")
}

return k.windowsCertificateManager.StoreCertificateChain(&apiv1.StoreCertificateChainRequest{
Expand Down Expand Up @@ -1899,6 +1929,19 @@ func (k *TPMKMS) getAK(ctx context.Context, name string) (*tpm.AK, error) {
return ak, nil
}

// managesKey reports whether name identifies a key held by this TPM.
//
// Only a definitive "no" is treated as one. A lookup that fails for any other
// reason — the TPM busy, unavailable, or unreadable under the current identity
// — leaves the answer unknown, and the caller keeps the explicit association it
// would otherwise have made. Guessing "not ours" there would drop the binding
// for a genuine TPM key and reintroduce the machine-scoped discovery failure
// the explicit association exists to avoid.
func (k *TPMKMS) managesKey(name string) bool {
_, err := k.tpm.GetKey(context.Background(), name)
return !errors.Is(err, tpm.ErrNotFound)
}

func (k *TPMKMS) getKey(ctx context.Context, name string) (*tpm.Key, error) {
key, err := k.tpm.GetKey(ctx, name)
if err != nil {
Expand Down
22 changes: 22 additions & 0 deletions kms/tpmkms/tpmkms_simulator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2796,3 +2796,25 @@ func TestTPMKMS_CleanupCredentials_keyEnumerationDegraded(t *testing.T) {
assert.Contains(t, err.Error(), "fall back to direct key deletion")
})
}

// A certificate is only bound to an "app-<name>" Platform Crypto Provider
// container when the name really is one of this TPM's keys. A caller can name a
// key created elsewhere -- on Windows an endpoint with no key protection has its
// key made through CAPI in the software KSP, under the bare name -- and binding
// that certificate to a PCP container names one that does not exist, leaving a
// certificate that reports HasPrivateKey while resolving no key at all.
func TestTPMKMS_managesKey(t *testing.T) {
tpm := newSimulatedTPM(t)
kms := &TPMKMS{tpm: tpm}

_, err := kms.CreateKey(&apiv1.CreateKeyRequest{
Name: "tpmkms:name=ownkey",
SignatureAlgorithm: apiv1.ECDSAWithSHA256,
})
require.NoError(t, err)

assert.True(t, kms.managesKey("ownkey"),
"a key this TPM created must keep its explicit association")
assert.False(t, kms.managesKey("madeelsewhere"),
"a name this TPM has never held must fall back to discovery instead of claiming a PCP container")
}