Skip to content

[keymanager/wsd] Add /keys:destroy Go KOL handler with KEM + binding key destruction#651

Merged
atulpatildbz merged 8 commits intogoogle:mainfrom
atulpatildbz:wsd_destroy_go
Mar 5, 2026
Merged

[keymanager/wsd] Add /keys:destroy Go KOL handler with KEM + binding key destruction#651
atulpatildbz merged 8 commits intogoogle:mainfrom
atulpatildbz:wsd_destroy_go

Conversation

@atulpatildbz
Copy link
Copy Markdown
Collaborator

@atulpatildbz atulpatildbz commented Feb 9, 2026

Implements the Go orchestration layer (KOL) for POST /v1/keys:destroy, the key destruction endpoint that allows workloads to explicitly destroy a key pair.

Flow:

  1. Workload sends {key_handle: {handle: ...}} to WSD
  2. WSD looks up binding UUID from the KEM→Binding map
  3. WSD calls KPS DestroyKEMKey — destroys the KEM key from the registry
  4. WSD calls WSD KCC DestroyBindingKey — destroys the binding key from the registry
  5. WSD removes the KEM→Binding mapping
  6. Returns 204 No Content

Changes:

  • C headers: Added key_manager_destroy_kem_key (KPS) and key_manager_destroy_binding_key (WSD) declarations
  • CGO bridges: Added DestroyKEMKey() and DestroyBindingKey() Go wrappers for the Rust FFI functions
  • KPS service: Extended Service with DestroyKEMKey method and KEMKeyDestroyer interface
  • WSD server: Added KEMKeyDestroyer/BindingKeyDestroyer interfaces, DestroyRequest type, handleDestroy handler, /v1/keys:destroy route registration
  • Tests: 7 new destroy handler tests + 2 new KPS service tests + Integration test TestIntegrationDestroyKey

Dependencies

This PR is built on top of:

What to review

Please review the following commits:

  1. d1d6badfeat(keymanager): Add manual FFI headers for destroy (C headers for destroy)
  2. 60466aa[keymanager/wsd] Add /keys:destroy endpoint with KEM + binding key destruction (Go implementation)

All other commits are from dependencies (PRs #647, #652) that are not yet merged to main but required for this build.

Verification

Automated Tests

  • Unit tests: go test ./keymanager/workload_service/... (Passed)
  • Integration tests: go test -tags=integration ./keymanager/workload_service/... (Passed)

Manual Verification (Curl)

Manually verified the destruction workflow locally:

  1. Generate Key: POST /v1/keys:generate_kem -> Returns Handle.
  2. Destroy Key (First): POST /v1/keys:destroy -> Returns 204.
  3. Destroy Key (Second): POST /v1/keys:destroy -> Returns 404 (Key mapping removed).

@atulpatildbz atulpatildbz force-pushed the wsd_destroy_go branch 2 times, most recently from 60466aa to cff8e70 Compare February 18, 2026 07:07
@atulpatildbz atulpatildbz force-pushed the wsd_destroy_go branch 7 times, most recently from 4d76482 to 87f6a9f Compare February 26, 2026 19:21
Comment thread keymanager/workload_service/server.go Outdated

// NewServer creates a new WSD server with the given dependencies.
func NewServer(bindingGen BindingKeyGenerator, kemGen KEMKeyGenerator) *Server {
func NewServer(bindingGen BindingKeyGenerator, kemGen KEMKeyGenerator, kemKeyDestroyer KEMKeyDestroyer, bindingKeyDestroyer BindingKeyDestroyer) *Server {
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

this will need change after #678 is merged.
May have to rebase over that

…struction

Implement the Go KOL handler for POST /keys:destroy that orchestrates
the full key destruction flow:
1. Workload sends {kemKeyHandle} to WSD
2. WSD looks up binding UUID from KEM-to-binding map
3. WSD calls KPS DestroyKEMKey to destroy the KEM key
4. WSD calls WSD KCC DestroyBindingKey to destroy the binding key
5. WSD removes the KEM→Binding mapping
6. Returns 204 No Content

Changes:
- C headers: add key_manager_destroy_kem_key (KPS) and
  key_manager_destroy_binding_key (WSD) declarations
- CGO bridges: add DestroyKEMKey and DestroyBindingKey Go wrappers
- KPS service: extend with DestroyKEMKey method and KEMKeyDestroyer
  interface
- WSD server: add KEMKeyDestroyer/BindingKeyDestroyer interfaces,
  DestroyRequest type, handleDestroy handler, /keys:destroy route
- Tests: 7 new destroy handler tests + 2 new KPS service tests

Remove DecapAndSeal and Open from KPS and WSD
// KEMKeyGenerator generates KEM keypairs linked to a binding public key.

// KEMKeyDestroyer destroys a KEM key by UUID.
type KEMKeyDestroyer interface {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Done

kemUUIDs[0], binding1, kemUUIDs[1], binding2)
}

func TestIntegrationDestroyKey(t *testing.T) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Should we also add an integration test to validate the auto-destroy mechanism with this PR? And also verify that calling a destroy on a auto-destroyed key will not cause problems (since the kem to binding mapping will still exist in the KOL layer).

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Done! I have added a new integration test TestIntegrationAutoDestroy

I also verified the behavior offline: when the KEM key and binding key expire and are auto-destroyed in the Rust KCC layer, an explicit call to /v1/keys:destroy does not cause any problems. The Rust backend returns success, allowing the Go KOL to cleanly remove the stale mapping from its kemToBindingMap and return an HTTP 204 No Content. The new integration test ensures this behavior won't regress.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Awesome, thank you.


// Step 4: Remove the mapping.
s.mu.Lock()
delete(s.kemToBindingMap, kemUUID)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I am wondering if we should try to remove the kemUUID from the kemToBindingMap even in case of errors. For example, if for some reason DestroyKEMKey or DestroyBindingKey fails the delete call will not go through. A subsequent decap call on the same key will pass through since the mapping exist and fail in the Rust layer. Which is okay, but would it better to try to delete the mapping to avoid such a situation?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

That makes sense.

I have updated the logic to ensure that both KEM and Binding keys are always attempted for destruction, and the mapping is always deleted from the map, regardless of individual failure outcomes. I've also updated server_test.go to explicitly verify this behavior.

Even if delete fails for some reason, the reaper would eventually delete expired keys right?

@jkl73 jkl73 requested a review from pgonda March 2, 2026 19:33
func DestroyKEMKey(kemUUID uuid.UUID) error {
uuidBytes := kemUUID[:]
rc := C.key_manager_destroy_kem_key(
(*C.uint8_t)(unsafe.Pointer(&uuidBytes[0])),
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

if rc:= ...; rc :=0 {
..
}

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Done

# Conflicts:
#	keymanager/key_protection_service/service.go
#	keymanager/workload_service/server.go
#	keymanager/workload_service/server_test.go
@atulpatildbz atulpatildbz force-pushed the wsd_destroy_go branch 2 times, most recently from 9080eeb to 830a9ee Compare March 3, 2026 14:29
- fix: fix handleDestroy to prevent resource and memory leaks by always clearing the KeyManager mapping.
- test: update handleDestroy tests to assert mapping deletion.
- test: add integration test for auto-destroy mechanism.
Comment thread keymanager/workload_service/server.go Outdated
kemUUIDs[0], binding1, kemUUIDs[1], binding2)
}

func TestIntegrationDestroyKey(t *testing.T) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Awesome, thank you.

Comment thread keymanager/workload_service/server.go Outdated
errKps := s.keyProtectionService.DestroyKEMKey(kemUUID)
errWs := s.workloadService.DestroyBindingKey(bindingUUID)

// Step 4: Remove the mapping.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I think this is Step 2 now

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Removed Step from the comment as this comment pattern isn't used in our codebase

Comment thread keymanager/workload_service/server.go Outdated
Comment on lines +414 to +422
if errKps != nil {
writeError(w, fmt.Sprintf("failed to destroy KEM key: %v", errKps), http.StatusInternalServerError)
return
}

if errWs != nil {
writeError(w, fmt.Sprintf("failed to destroy binding key: %v", errWs), http.StatusInternalServerError)
return
}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This works, but if the errKps fails, errWs is ignored. We can use errors.Join() to join the errors, so that both or either of them are surfaced.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Done

- removed `Step <x>` comments
- used errors.Join to collectively check of either KPS or WS destroy
failed
@atulpatildbz atulpatildbz merged commit 17d39d0 into google:main Mar 5, 2026
11 of 12 checks passed
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.

3 participants