Skip to content

Commit 4d76482

Browse files
committed
[keymanager/wsd] Add /keys:destroy endpoint with KEM + binding key destruction
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
1 parent 59e0e4f commit 4d76482

File tree

8 files changed

+470
-16
lines changed

8 files changed

+470
-16
lines changed

keymanager/key_protection_service/key_custody_core/kps_key_custody_core_cgo.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,3 +60,15 @@ func GenerateKEMKeypair(algo *algorithms.HpkeAlgorithm, bindingPubKey []byte, li
6060
copy(pubkey, pubkeyBuf[:pubkeyLen])
6161
return id, pubkey, nil
6262
}
63+
64+
// DestroyKEMKey destroys the KEM key identified by kemUUID via Rust FFI.
65+
func DestroyKEMKey(kemUUID uuid.UUID) error {
66+
uuidBytes := kemUUID[:]
67+
rc := C.key_manager_destroy_kem_key(
68+
(*C.uint8_t)(unsafe.Pointer(&uuidBytes[0])),
69+
)
70+
if rc != 0 {
71+
return fmt.Errorf("key_manager_destroy_kem_key failed with code %d", rc)
72+
}
73+
return nil
74+
}

keymanager/key_protection_service/service.go

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,22 +10,41 @@ import (
1010
)
1111

1212
// KEMKeyGenerator generates KEM keypairs linked to a binding public key.
13+
14+
// KEMKeyDestroyer destroys a KEM key by UUID.
15+
type KEMKeyDestroyer interface {
16+
DestroyKEMKey(kemUUID uuid.UUID) error
17+
}
18+
1319
type KEMKeyGenerator interface {
1420
GenerateKEMKeypair(algo *algorithms.HpkeAlgorithm, bindingPubKey []byte, lifespanSecs uint64) (uuid.UUID, []byte, error)
1521
}
1622

17-
// Service implements KEMKeyGenerator by delegating to the KPS KCC FFI.
23+
// Service implements KEMKeyGenerator and KEMKeyDestroyer by
24+
// delegating to the KPS KCC FFI.
1825
type Service struct {
26+
destroyKEMKeyFn func(kemUUID uuid.UUID) error
1927
generateKEMKeypairFn func(algo *algorithms.HpkeAlgorithm, bindingPubKey []byte, lifespanSecs uint64) (uuid.UUID, []byte, error)
2028
}
2129

2230
// NewService creates a new KPS KOL service with the given KCC function.
23-
func NewService(generateKEMKeypairFn func(algo *algorithms.HpkeAlgorithm, bindingPubKey []byte, lifespanSecs uint64) (uuid.UUID, []byte, error)) *Service {
24-
return &Service{generateKEMKeypairFn: generateKEMKeypairFn}
31+
func NewService(
32+
generateKEMKeypairFn func(algo *algorithms.HpkeAlgorithm, bindingPubKey []byte, lifespanSecs uint64) (uuid.UUID, []byte, error),
33+
destroyKEMKeyFn func(kemUUID uuid.UUID) error,
34+
) *Service {
35+
return &Service{
36+
generateKEMKeypairFn: generateKEMKeypairFn,
37+
destroyKEMKeyFn: destroyKEMKeyFn,
38+
}
2539
}
2640

2741
// GenerateKEMKeypair generates a KEM keypair linked to the provided binding
2842
// public key by calling the KPS KCC FFI.
2943
func (s *Service) GenerateKEMKeypair(algo *algorithms.HpkeAlgorithm, bindingPubKey []byte, lifespanSecs uint64) (uuid.UUID, []byte, error) {
3044
return s.generateKEMKeypairFn(algo, bindingPubKey, lifespanSecs)
3145
}
46+
47+
// DestroyKEMKey destroys the KEM key identified by kemUUID by calling the KPS KCC FFI.
48+
func (s *Service) DestroyKEMKey(kemUUID uuid.UUID) error {
49+
return s.destroyKEMKeyFn(kemUUID)
50+
}

keymanager/key_protection_service/service_test.go

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ import (
99
algorithms "github.com/google/go-tpm-tools/keymanager/km_common/proto"
1010
)
1111

12+
// noopDestroyKEMKey is a placeholder for tests that don't exercise DestroyKEMKey.
13+
func noopDestroyKEMKey(_ uuid.UUID) error {
14+
return nil
15+
}
1216
func TestServiceGenerateKEMKeypairSuccess(t *testing.T) {
1317
expectedUUID := uuid.New()
1418
expectedPubKey := make([]byte, 32)
@@ -24,7 +28,7 @@ func TestServiceGenerateKEMKeypairSuccess(t *testing.T) {
2428
t.Fatalf("expected lifespanSecs 7200, got %d", lifespanSecs)
2529
}
2630
return expectedUUID, expectedPubKey, nil
27-
})
31+
}, noopDestroyKEMKey)
2832

2933
id, pubKey, err := svc.GenerateKEMKeypair(&algorithms.HpkeAlgorithm{}, make([]byte, 32), 7200)
3034
if err != nil {
@@ -41,10 +45,35 @@ func TestServiceGenerateKEMKeypairSuccess(t *testing.T) {
4145
func TestServiceGenerateKEMKeypairError(t *testing.T) {
4246
svc := NewService(func(algo *algorithms.HpkeAlgorithm, bindingPubKey []byte, lifespanSecs uint64) (uuid.UUID, []byte, error) {
4347
return uuid.Nil, nil, fmt.Errorf("FFI error")
44-
})
48+
}, noopDestroyKEMKey)
4549

4650
_, _, err := svc.GenerateKEMKeypair(&algorithms.HpkeAlgorithm{}, make([]byte, 32), 3600)
4751
if err == nil {
4852
t.Fatal("expected error, got nil")
4953
}
5054
}
55+
56+
func TestServiceDestroyKEMKeySuccess(t *testing.T) {
57+
kemUUID := uuid.New()
58+
svc := NewService(nil, func(id uuid.UUID) error {
59+
if id != kemUUID {
60+
t.Fatalf("expected KEM UUID %s, got %s", kemUUID, id)
61+
}
62+
return nil
63+
})
64+
65+
if err := svc.DestroyKEMKey(kemUUID); err != nil {
66+
t.Fatalf("unexpected error: %v", err)
67+
}
68+
}
69+
70+
func TestServiceDestroyKEMKeyError(t *testing.T) {
71+
svc := NewService(nil, func(_ uuid.UUID) error {
72+
return fmt.Errorf("destroy FFI error")
73+
})
74+
75+
err := svc.DestroyKEMKey(uuid.New())
76+
if err == nil {
77+
t.Fatal("expected error, got nil")
78+
}
79+
}

keymanager/workload_service/integration_test.go

Lines changed: 77 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,17 @@ func (r *realBindingKeyGen) GenerateBindingKeypair(algo *algorithms.HpkeAlgorith
2424
return wskcc.GenerateBindingKeypair(algo, lifespanSecs)
2525
}
2626

27+
// realBindingKeyDestroyer wraps the actual WSD KCC FFI for DestroyBindingKey.
28+
type realBindingKeyDestroyer struct{}
29+
30+
func (r *realBindingKeyDestroyer) DestroyBindingKey(bindingUUID uuid.UUID) error {
31+
return wskcc.DestroyBindingKey(bindingUUID)
32+
}
33+
2734
func TestIntegrationGenerateKeysEndToEnd(t *testing.T) {
2835
// Wire up real FFI calls: WSD KCC for binding, KPS KCC (via KPS KOL) for KEM.
29-
kpsSvc := kps.NewService(kpskcc.GenerateKEMKeypair)
30-
srv := NewServer(&realBindingKeyGen{}, kpsSvc)
36+
kpsSvc := kps.NewService(kpskcc.GenerateKEMKeypair, kpskcc.DecapAndSeal, kpskcc.DestroyKEMKey)
37+
srv := NewServer(&realBindingKeyGen{}, kpsSvc, kpsSvc, &realBindingKeyDestroyer{})
3138

3239
reqBody, err := json.Marshal(GenerateKemRequest{
3340
Algorithm: KemAlgorithmDHKEMX25519HKDFSHA256,
@@ -72,8 +79,8 @@ func TestIntegrationGenerateKeysEndToEnd(t *testing.T) {
7279
}
7380

7481
func TestIntegrationGenerateKeysUniqueMappings(t *testing.T) {
75-
kpsSvc := kps.NewService(kpskcc.GenerateKEMKeypair)
76-
srv := NewServer(&realBindingKeyGen{}, kpsSvc)
82+
kpsSvc := kps.NewService(kpskcc.GenerateKEMKeypair, kpskcc.DestroyKEMKey)
83+
srv := NewServer(&realBindingKeyGen{}, kpsSvc, kpsSvc, &realBindingKeyDestroyer{})
7784

7885
// Generate two key sets.
7986
var kemUUIDs [2]uuid.UUID
@@ -121,3 +128,69 @@ func TestIntegrationGenerateKeysUniqueMappings(t *testing.T) {
121128
t.Logf("E2E uniqueness: KEM1=%s→Binding1=%s, KEM2=%s→Binding2=%s",
122129
kemUUIDs[0], binding1, kemUUIDs[1], binding2)
123130
}
131+
132+
func TestIntegrationDestroyKey(t *testing.T) {
133+
kpsSvc := kps.NewService(kpskcc.GenerateKEMKeypair, kpskcc.DestroyKEMKey)
134+
srv := NewServer(&realBindingKeyGen{}, kpsSvc, kpsSvc, &realBindingKeyDestroyer{})
135+
136+
// 1. Generate a key first
137+
reqBody, _ := json.Marshal(GenerateKemRequest{
138+
Algorithm: KemAlgorithmDHKEMX25519HKDFSHA256,
139+
KeyProtectionMechanism: KeyProtectionMechanismVM,
140+
Lifespan: ProtoDuration{Seconds: 3600},
141+
})
142+
reqGen := httptest.NewRequest(http.MethodPost, "/v1/keys:generate_kem", bytes.NewReader(reqBody))
143+
reqGen.Header.Set("Content-Type", "application/json")
144+
wGen := httptest.NewRecorder()
145+
srv.Handler().ServeHTTP(wGen, reqGen)
146+
147+
if wGen.Code != http.StatusOK {
148+
t.Fatalf("setup: expected generate status 200, got %d: %s", wGen.Code, wGen.Body.String())
149+
}
150+
151+
var respGen GenerateKemResponse
152+
if err := json.NewDecoder(wGen.Body).Decode(&respGen); err != nil {
153+
t.Fatalf("setup: failed to decode generate response: %v", err)
154+
}
155+
kemHandle := respGen.KeyHandle.Handle
156+
kemUUID, err := uuid.Parse(kemHandle)
157+
if err != nil {
158+
t.Fatalf("setup: invalid KEM UUID: %v", err)
159+
}
160+
161+
// Verify mapping exists
162+
_, ok := srv.LookupBindingUUID(kemUUID)
163+
if !ok {
164+
t.Fatal("setup: expected mapping to exist")
165+
}
166+
167+
// 2. Destroy the key
168+
reqDestroyBody, _ := json.Marshal(DestroyRequest{
169+
KeyHandle: KeyHandle{Handle: kemHandle},
170+
})
171+
reqDestroy := httptest.NewRequest(http.MethodPost, "/v1/keys:destroy", bytes.NewReader(reqDestroyBody))
172+
reqDestroy.Header.Set("Content-Type", "application/json")
173+
wDestroy := httptest.NewRecorder()
174+
srv.Handler().ServeHTTP(wDestroy, reqDestroy)
175+
176+
if wDestroy.Code != http.StatusNoContent {
177+
t.Fatalf("expected destroy status 204, got %d: %s", wDestroy.Code, wDestroy.Body.String())
178+
}
179+
180+
// 3. Verify mapping is gone
181+
_, ok = srv.LookupBindingUUID(kemUUID)
182+
if ok {
183+
t.Fatal("expected KEM UUID mapping to be removed after destroy")
184+
}
185+
186+
// 4. Try to destroy again (should fail? or be 404? or 500?)
187+
// If mapping is gone, it returns 404.
188+
reqDestroy2 := httptest.NewRequest(http.MethodPost, "/v1/keys:destroy", bytes.NewReader(reqDestroyBody))
189+
reqDestroy2.Header.Set("Content-Type", "application/json")
190+
wDestroy2 := httptest.NewRecorder()
191+
srv.Handler().ServeHTTP(wDestroy2, reqDestroy2)
192+
193+
if wDestroy2.Code != http.StatusNotFound {
194+
t.Fatalf("expected second destroy to return 404, got %d", wDestroy2.Code)
195+
}
196+
}

keymanager/workload_service/key_custody_core/ws_key_custody_core_cgo.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,3 +53,15 @@ func GenerateBindingKeypair(algo *algorithms.HpkeAlgorithm, lifespanSecs uint64)
5353
copy(pubkey, pubkeyBuf[:pubkeyLen])
5454
return id, pubkey, nil
5555
}
56+
57+
// DestroyBindingKey destroys the binding key identified by bindingUUID via Rust FFI.
58+
func DestroyBindingKey(bindingUUID uuid.UUID) error {
59+
uuidBytes := bindingUUID[:]
60+
rc := C.key_manager_destroy_binding_key(
61+
(*C.uint8_t)(unsafe.Pointer(&uuidBytes[0])),
62+
)
63+
if rc != 0 {
64+
return fmt.Errorf("key_manager_destroy_binding_key failed with code %d", rc)
65+
}
66+
return nil
67+
}

keymanager/workload_service/server.go

Lines changed: 65 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,16 @@ type BindingKeyGenerator interface {
2424
}
2525

2626
// KEMKeyGenerator generates KEM keypairs via the KPS KOL/KCC.
27+
// KEMKeyDestroyer destroys a KEM key by UUID via the KPS KCC FFI.
28+
type KEMKeyDestroyer interface {
29+
DestroyKEMKey(kemUUID uuid.UUID) error
30+
}
31+
32+
// BindingKeyDestroyer destroys a binding key by UUID via the WSD KCC FFI.
33+
type BindingKeyDestroyer interface {
34+
DestroyBindingKey(bindingUUID uuid.UUID) error
35+
}
36+
2737
type KEMKeyGenerator interface {
2838
GenerateKEMKeypair(algo *algorithms.HpkeAlgorithm, bindingPubKey []byte, lifespanSecs uint64) (uuid.UUID, []byte, error)
2939
}
@@ -64,6 +74,10 @@ type GenerateKemRequest struct {
6474
}
6575

6676
// GenerateKemResponse is returned by POST /v1/keys:generate_kem.
77+
type DestroyRequest struct {
78+
KeyHandle KeyHandle `json:"key_handle"`
79+
}
80+
6781
type GenerateKemResponse struct {
6882
KeyHandle KeyHandle `json:"key_handle"`
6983
}
@@ -91,8 +105,10 @@ type GetCapabilitiesResponse struct {
91105

92106
// Server is the WSD HTTP server.
93107
type Server struct {
94-
bindingGen BindingKeyGenerator
95-
kemGen KEMKeyGenerator
108+
bindingGen BindingKeyGenerator
109+
kemGen KEMKeyGenerator
110+
kemKeyDestroyer KEMKeyDestroyer
111+
bindingKeyDestroyer BindingKeyDestroyer
96112

97113
mu sync.RWMutex
98114
kemToBindingMap map[uuid.UUID]uuid.UUID
@@ -102,16 +118,19 @@ type Server struct {
102118
}
103119

104120
// NewServer creates a new WSD server with the given dependencies.
105-
func NewServer(bindingGen BindingKeyGenerator, kemGen KEMKeyGenerator) *Server {
121+
func NewServer(bindingGen BindingKeyGenerator, kemGen KEMKeyGenerator, kemKeyDestroyer KEMKeyDestroyer, bindingKeyDestroyer BindingKeyDestroyer) *Server {
106122
s := &Server{
107-
bindingGen: bindingGen,
108-
kemGen: kemGen,
109-
kemToBindingMap: make(map[uuid.UUID]uuid.UUID),
123+
bindingGen: bindingGen,
124+
kemGen: kemGen,
125+
kemKeyDestroyer: kemKeyDestroyer,
126+
bindingKeyDestroyer: bindingKeyDestroyer,
127+
kemToBindingMap: make(map[uuid.UUID]uuid.UUID),
110128
}
111129

112130
mux := http.NewServeMux()
113131
mux.HandleFunc("POST /v1/keys:generate_kem", s.handleGenerateKem)
114132
mux.HandleFunc("GET /v1/capabilities", s.handleGetCapabilities)
133+
mux.HandleFunc("POST /v1/keys:destroy", s.handleDestroy)
115134

116135
s.httpServer = &http.Server{Handler: mux}
117136
return s
@@ -242,3 +261,43 @@ func writeJSON(w http.ResponseWriter, v any, code int) {
242261
func writeError(w http.ResponseWriter, message string, code int) {
243262
writeJSON(w, map[string]string{"error": message}, code)
244263
}
264+
265+
func (s *Server) handleDestroy(w http.ResponseWriter, r *http.Request) {
266+
var req DestroyRequest
267+
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
268+
writeError(w, fmt.Sprintf("invalid request body: %v", err), http.StatusBadRequest)
269+
return
270+
}
271+
272+
kemUUID, err := uuid.Parse(req.KeyHandle.Handle)
273+
if err != nil {
274+
writeError(w, fmt.Sprintf("invalid key handle: %v", err), http.StatusBadRequest)
275+
return
276+
}
277+
278+
// Step 1: Look up the binding UUID for this KEM key.
279+
bindingUUID, ok := s.LookupBindingUUID(kemUUID)
280+
if !ok {
281+
writeError(w, fmt.Sprintf("KEM key handle not found: %s", kemUUID), http.StatusNotFound)
282+
return
283+
}
284+
285+
// Step 2: Destroy the KEM key via KPS.
286+
if err := s.kemKeyDestroyer.DestroyKEMKey(kemUUID); err != nil {
287+
writeError(w, fmt.Sprintf("failed to destroy KEM key: %v", err), http.StatusInternalServerError)
288+
return
289+
}
290+
291+
// Step 3: Destroy the binding key via WSD KCC.
292+
if err := s.bindingKeyDestroyer.DestroyBindingKey(bindingUUID); err != nil {
293+
writeError(w, fmt.Sprintf("failed to destroy binding key: %v", err), http.StatusInternalServerError)
294+
return
295+
}
296+
297+
// Step 4: Remove the mapping.
298+
s.mu.Lock()
299+
delete(s.kemToBindingMap, kemUUID)
300+
s.mu.Unlock()
301+
302+
w.WriteHeader(http.StatusNoContent)
303+
}

0 commit comments

Comments
 (0)