Skip to content

Commit 60466aa

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
1 parent d1d6bad commit 60466aa

File tree

10 files changed

+794
-39
lines changed

10 files changed

+794
-39
lines changed

keymanager/key_protection_service/key_custody_core/kps_key_custody_core_cgo.go

Lines changed: 61 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,10 @@ func GenerateKEMKeypair(bindingPubKey []byte, lifespanSecs uint64) (uuid.UUID, [
2929
var pubkeyBuf [32]byte
3030
pubkeyLen := C.size_t(len(pubkeyBuf))
3131

32-
algo := C.KmHpkeAlgorithm{
33-
kem: C.KM_KEM_ALGORITHM_DHKEM_X25519_HKDF_SHA256,
34-
kdf: C.KM_KDF_ALGORITHM_HKDF_SHA256,
35-
aead: C.KM_AEAD_ALGORITHM_AES_256_GCM,
32+
algo := C.KpsHpkeAlgorithm{
33+
kem: C.KPS_KEM_ALGORITHM_DHKEM_X25519_HKDF_SHA256,
34+
kdf: C.KPS_KDF_ALGORITHM_HKDF_SHA256,
35+
aead: C.KPS_AEAD_ALGORITHM_AES_256_GCM,
3636
}
3737

3838
rc := C.key_manager_generate_kem_keypair(
@@ -42,7 +42,7 @@ func GenerateKEMKeypair(bindingPubKey []byte, lifespanSecs uint64) (uuid.UUID, [
4242
C.uint64_t(lifespanSecs),
4343
(*C.uint8_t)(unsafe.Pointer(&uuidBytes[0])),
4444
(*C.uint8_t)(unsafe.Pointer(&pubkeyBuf[0])),
45-
pubkeyLen,
45+
&pubkeyLen,
4646
)
4747
if rc != 0 {
4848
return uuid.Nil, nil, fmt.Errorf("key_manager_generate_kem_keypair failed with code %d", rc)
@@ -57,3 +57,59 @@ func GenerateKEMKeypair(bindingPubKey []byte, lifespanSecs uint64) (uuid.UUID, [
5757
copy(pubkey, pubkeyBuf[:pubkeyLen])
5858
return id, pubkey, nil
5959
}
60+
61+
// DecapAndSeal decapsulates a shared secret using the stored KEM key and
62+
// reseals it with the associated binding public key via Rust FFI.
63+
// Returns the new encapsulated key and sealed ciphertext.
64+
func DecapAndSeal(kemUUID uuid.UUID, encapsulatedKey, aad []byte) ([]byte, []byte, error) {
65+
if len(encapsulatedKey) == 0 {
66+
return nil, nil, fmt.Errorf("encapsulated key must not be empty")
67+
}
68+
69+
uuidBytes := kemUUID[:]
70+
71+
var outEncKey [32]byte
72+
outEncKeyLen := C.size_t(len(outEncKey))
73+
var outCT [48]byte // 32-byte secret + 16-byte GCM tag
74+
outCTLen := C.size_t(len(outCT))
75+
76+
var aadPtr *C.uint8_t
77+
aadLen := C.size_t(0)
78+
if len(aad) > 0 {
79+
aadPtr = (*C.uint8_t)(unsafe.Pointer(&aad[0]))
80+
aadLen = C.size_t(len(aad))
81+
}
82+
83+
rc := C.key_manager_decap_and_seal(
84+
(*C.uint8_t)(unsafe.Pointer(&uuidBytes[0])),
85+
(*C.uint8_t)(unsafe.Pointer(&encapsulatedKey[0])),
86+
C.size_t(len(encapsulatedKey)),
87+
aadPtr,
88+
aadLen,
89+
(*C.uint8_t)(unsafe.Pointer(&outEncKey[0])),
90+
&outEncKeyLen,
91+
(*C.uint8_t)(unsafe.Pointer(&outCT[0])),
92+
&outCTLen,
93+
)
94+
if rc != 0 {
95+
return nil, nil, fmt.Errorf("key_manager_decap_and_seal failed with code %d", rc)
96+
}
97+
98+
sealEnc := make([]byte, outEncKeyLen)
99+
copy(sealEnc, outEncKey[:outEncKeyLen])
100+
sealedCT := make([]byte, outCTLen)
101+
copy(sealedCT, outCT[:outCTLen])
102+
return sealEnc, sealedCT, nil
103+
}
104+
105+
// DestroyKEMKey destroys the KEM key identified by kemUUID via Rust FFI.
106+
func DestroyKEMKey(kemUUID uuid.UUID) error {
107+
uuidBytes := kemUUID[:]
108+
rc := C.key_manager_destroy_kem_key(
109+
(*C.uint8_t)(unsafe.Pointer(&uuidBytes[0])),
110+
)
111+
if rc != 0 {
112+
return fmt.Errorf("key_manager_destroy_kem_key failed with code %d", rc)
113+
}
114+
return nil
115+
}

keymanager/key_protection_service/key_custody_core/src/lib.rs

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -70,42 +70,61 @@ pub unsafe extern "C" fn key_manager_generate_kem_keypair(
7070
expiry_secs: u64,
7171
out_uuid: *mut u8,
7272
out_pubkey: *mut u8,
73-
out_pubkey_len: usize,
73+
out_pubkey_len: *mut usize,
7474
) -> i32 {
7575
// Safety Invariant Checks
7676
if binding_pubkey.is_null()
7777
|| binding_pubkey_len == 0
7878
|| out_pubkey.is_null()
7979
|| out_uuid.is_null()
80+
|| out_pubkey_len.is_null()
8081
{
8182
return -1;
8283
}
8384

8485
// Convert to Safe Types
8586
let binding_pubkey_slice = unsafe { slice::from_raw_parts(binding_pubkey, binding_pubkey_len) };
8687
let out_uuid = unsafe { slice::from_raw_parts_mut(out_uuid, 16) };
87-
let out_pubkey = unsafe { slice::from_raw_parts_mut(out_pubkey, out_pubkey_len) };
88+
// We cannot verify pubkey length safely without dereferencing out_pubkey_len
89+
let pubkey_capacity = unsafe { *out_pubkey_len };
90+
let out_pubkey = unsafe { slice::from_raw_parts_mut(out_pubkey, pubkey_capacity) };
8891

8992
let binding_pubkey = match PublicKey::try_from(binding_pubkey_slice.to_vec()) {
9093
Ok(pk) => pk,
9194
Err(_) => return -1,
9295
};
9396

94-
// Call Safe Internal Function
9597
// Call Safe Internal Function
9698
match generate_kem_keypair_internal(algo.into(), binding_pubkey, expiry_secs) {
9799
Ok((id, pubkey)) => {
98-
if out_pubkey_len != pubkey.as_bytes().len() {
99-
return -2;
100+
let actual_len = pubkey.as_bytes().len();
101+
if pubkey_capacity < actual_len {
102+
return -2;
100103
}
104+
unsafe { *out_pubkey_len = actual_len };
101105
out_uuid.copy_from_slice(id.as_bytes());
102-
out_pubkey.copy_from_slice(pubkey.as_bytes());
106+
out_pubkey[..actual_len].copy_from_slice(pubkey.as_bytes());
103107
0 // Success
104108
}
105109
Err(e) => e,
106110
}
107111
}
108112

113+
#[unsafe(no_mangle)]
114+
pub unsafe extern "C" fn key_manager_decap_and_seal(
115+
_uuid_bytes: *const u8,
116+
_encapsulated_key: *const u8,
117+
_encapsulated_key_len: usize,
118+
_aad: *const u8,
119+
_aad_len: usize,
120+
_out_encapsulated_key: *mut u8,
121+
_out_encapsulated_key_len: *mut usize,
122+
_out_ciphertext: *mut u8,
123+
_out_ciphertext_len: *mut usize,
124+
) -> i32 {
125+
-1 // Not implemented
126+
}
127+
109128
/// Destroys the KEM key associated with the given UUID.
110129
///
111130
/// ## Arguments

keymanager/key_protection_service/service.go

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,49 @@ type KEMKeyGenerator interface {
1010
GenerateKEMKeypair(bindingPubKey []byte, lifespanSecs uint64) (uuid.UUID, []byte, error)
1111
}
1212

13-
// Service implements KEMKeyGenerator by delegating to the KPS KCC FFI.
13+
// DecapSealer decapsulates a shared secret and reseals it with the binding key.
14+
type DecapSealer interface {
15+
DecapAndSeal(kemUUID uuid.UUID, encapsulatedKey, aad []byte) (sealEnc []byte, sealedCT []byte, err error)
16+
}
17+
18+
// KEMKeyDestroyer destroys a KEM key by UUID.
19+
type KEMKeyDestroyer interface {
20+
DestroyKEMKey(kemUUID uuid.UUID) error
21+
}
22+
23+
// Service implements KEMKeyGenerator, DecapSealer, and KEMKeyDestroyer by
24+
// delegating to the KPS KCC FFI.
1425
type Service struct {
1526
generateKEMKeypairFn func(bindingPubKey []byte, lifespanSecs uint64) (uuid.UUID, []byte, error)
27+
decapAndSealFn func(kemUUID uuid.UUID, encapsulatedKey, aad []byte) ([]byte, []byte, error)
28+
destroyKEMKeyFn func(kemUUID uuid.UUID) error
1629
}
1730

18-
// NewService creates a new KPS KOL service with the given KCC function.
19-
func NewService(generateKEMKeypairFn func(bindingPubKey []byte, lifespanSecs uint64) (uuid.UUID, []byte, error)) *Service {
20-
return &Service{generateKEMKeypairFn: generateKEMKeypairFn}
31+
// NewService creates a new KPS KOL service with the given KCC functions.
32+
func NewService(
33+
generateKEMKeypairFn func(bindingPubKey []byte, lifespanSecs uint64) (uuid.UUID, []byte, error),
34+
decapAndSealFn func(kemUUID uuid.UUID, encapsulatedKey, aad []byte) ([]byte, []byte, error),
35+
destroyKEMKeyFn func(kemUUID uuid.UUID) error,
36+
) *Service {
37+
return &Service{
38+
generateKEMKeypairFn: generateKEMKeypairFn,
39+
decapAndSealFn: decapAndSealFn,
40+
destroyKEMKeyFn: destroyKEMKeyFn,
41+
}
2142
}
2243

2344
// GenerateKEMKeypair generates a KEM keypair linked to the provided binding
2445
// public key by calling the KPS KCC FFI.
2546
func (s *Service) GenerateKEMKeypair(bindingPubKey []byte, lifespanSecs uint64) (uuid.UUID, []byte, error) {
2647
return s.generateKEMKeypairFn(bindingPubKey, lifespanSecs)
2748
}
49+
50+
// DestroyKEMKey destroys the KEM key identified by kemUUID by calling the KPS KCC FFI.
51+
func (s *Service) DestroyKEMKey(kemUUID uuid.UUID) error {
52+
return s.destroyKEMKeyFn(kemUUID)
53+
}
54+
55+
// DecapAndSeal decapsulates a shared secret and reseals it with the binding key.
56+
func (s *Service) DecapAndSeal(kemUUID uuid.UUID, encapsulatedKey, aad []byte) ([]byte, []byte, error) {
57+
return s.decapAndSealFn(kemUUID, encapsulatedKey, aad)
58+
}

keymanager/key_protection_service/service_test.go

Lines changed: 71 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,15 @@ import (
77
"github.com/google/uuid"
88
)
99

10+
// noopDecapAndSeal is a placeholder for tests that don't exercise DecapAndSeal.
11+
func noopDecapAndSeal(_ uuid.UUID, _, _ []byte) ([]byte, []byte, error) {
12+
return nil, nil, nil
13+
}
14+
15+
// noopDestroyKEMKey is a placeholder for tests that don't exercise DestroyKEMKey.
16+
func noopDestroyKEMKey(_ uuid.UUID) error {
17+
return nil
18+
}
1019
func TestServiceGenerateKEMKeypairSuccess(t *testing.T) {
1120
expectedUUID := uuid.New()
1221
expectedPubKey := make([]byte, 32)
@@ -22,7 +31,7 @@ func TestServiceGenerateKEMKeypairSuccess(t *testing.T) {
2231
t.Fatalf("expected lifespanSecs 7200, got %d", lifespanSecs)
2332
}
2433
return expectedUUID, expectedPubKey, nil
25-
})
34+
}, noopDecapAndSeal, noopDestroyKEMKey)
2635

2736
id, pubKey, err := svc.GenerateKEMKeypair(make([]byte, 32), 7200)
2837
if err != nil {
@@ -39,10 +48,70 @@ func TestServiceGenerateKEMKeypairSuccess(t *testing.T) {
3948
func TestServiceGenerateKEMKeypairError(t *testing.T) {
4049
svc := NewService(func(bindingPubKey []byte, lifespanSecs uint64) (uuid.UUID, []byte, error) {
4150
return uuid.Nil, nil, fmt.Errorf("FFI error")
42-
})
51+
}, noopDecapAndSeal, noopDestroyKEMKey)
4352

4453
_, _, err := svc.GenerateKEMKeypair(make([]byte, 32), 3600)
4554
if err == nil {
4655
t.Fatal("expected error, got nil")
4756
}
4857
}
58+
59+
func TestServiceDecapAndSealSuccess(t *testing.T) {
60+
kemUUID := uuid.New()
61+
expectedSealEnc := []byte("seal-enc-key")
62+
expectedSealedCT := []byte("sealed-ciphertext")
63+
64+
svc := NewService(nil, func(id uuid.UUID, encKey, aad []byte) ([]byte, []byte, error) {
65+
if id != kemUUID {
66+
t.Fatalf("expected KEM UUID %s, got %s", kemUUID, id)
67+
}
68+
return expectedSealEnc, expectedSealedCT, nil
69+
}, noopDestroyKEMKey)
70+
71+
sealEnc, sealedCT, err := svc.DecapAndSeal(kemUUID, []byte("enc-key"), []byte("aad"))
72+
if err != nil {
73+
t.Fatalf("unexpected error: %v", err)
74+
}
75+
if string(sealEnc) != string(expectedSealEnc) {
76+
t.Fatalf("expected seal enc %q, got %q", expectedSealEnc, sealEnc)
77+
}
78+
if string(sealedCT) != string(expectedSealedCT) {
79+
t.Fatalf("expected sealed CT %q, got %q", expectedSealedCT, sealedCT)
80+
}
81+
}
82+
83+
func TestServiceDecapAndSealError(t *testing.T) {
84+
svc := NewService(nil, func(_ uuid.UUID, _, _ []byte) ([]byte, []byte, error) {
85+
return nil, nil, fmt.Errorf("decap FFI error")
86+
}, noopDestroyKEMKey)
87+
88+
_, _, err := svc.DecapAndSeal(uuid.New(), []byte("enc-key"), nil)
89+
if err == nil {
90+
t.Fatal("expected error, got nil")
91+
}
92+
}
93+
94+
func TestServiceDestroyKEMKeySuccess(t *testing.T) {
95+
kemUUID := uuid.New()
96+
svc := NewService(nil, noopDecapAndSeal, func(id uuid.UUID) error {
97+
if id != kemUUID {
98+
t.Fatalf("expected KEM UUID %s, got %s", kemUUID, id)
99+
}
100+
return nil
101+
})
102+
103+
if err := svc.DestroyKEMKey(kemUUID); err != nil {
104+
t.Fatalf("unexpected error: %v", err)
105+
}
106+
}
107+
108+
func TestServiceDestroyKEMKeyError(t *testing.T) {
109+
svc := NewService(nil, noopDecapAndSeal, func(_ uuid.UUID) error {
110+
return fmt.Errorf("destroy FFI error")
111+
})
112+
113+
err := svc.DestroyKEMKey(uuid.New())
114+
if err == nil {
115+
t.Fatal("expected error, got nil")
116+
}
117+
}

0 commit comments

Comments
 (0)