Skip to content

Commit 84401d9

Browse files
committed
keymanager/wsd: Implement Enumerate Keys API (GET /v1/keys)
Enumerate Keys API (GET /v1/keys) to the Workload Service Daemon (WSD), allowing clients to list active KEM keys stored in the Key Protection Service (KPS). Key changes: 1. **Workload Service (Go)**: - Added EnumerateKEMKeys integration with the KCC FFI. - Exposed GET /v1/keys endpoint. - Defined response structures aligned with the design doc: - snake_case JSON tags (key_handle, kem_pub_key, etc.). - Stringified enums for algorithms (HKDF_SHA384, AES_256_GCM). - Stringified durations (e.g., 3600s). 2. **Key Protection Service (Rust)**: - Added key_manager_enumerate_kem_keys FFI function to key_custody_core. - Implemented enumerate_kem_keys in KeyRegistry to safely expose key metadata. - Added KmHpkeAlgorithm struct for C compatibility. 3. **Testing**: - Added unit tests in server_test.go covering success, empty state, and error handling. - Added Rust FFI tests validating memory safety and correctness.
1 parent 763e293 commit 84401d9

File tree

10 files changed

+755
-23
lines changed

10 files changed

+755
-23
lines changed

keymanager/key_protection_service/key_custody_core/include/kps_key_custody_core.h

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,29 @@ int32_t key_manager_generate_kem_keypair(KmHpkeAlgorithm algo,
1919
uint8_t *out_pubkey,
2020
size_t out_pubkey_len);
2121

22+
// KpsKeyInfo holds metadata for a single KEM key entry.
23+
typedef struct {
24+
uint8_t uuid[16];
25+
KmHpkeAlgorithm algorithm;
26+
uint8_t kem_pub_key[64];
27+
size_t kem_pub_key_len;
28+
uint8_t binding_pub_key[64];
29+
size_t binding_pub_key_len;
30+
uint64_t remaining_lifespan_secs;
31+
} KpsKeyInfo;
32+
33+
// key_manager_enumerate_kem_keys enumerates all active KEM keys in the registry.
34+
// Writes up to max_entries KpsKeyInfo structs to out_entries and sets out_count
35+
// to the number actually written.
36+
//
37+
// Returns 0 on success, -1 on error.
38+
int32_t key_manager_enumerate_kem_keys(
39+
KpsKeyInfo *out_entries,
40+
size_t max_entries,
41+
size_t *out_count);
42+
2243
#ifdef __cplusplus
2344
} // extern "C"
2445
#endif // __cplusplus
2546

26-
#endif /* KPS_KEY_CUSTODY_CORE_H_ */
47+
#endif // KPS_KEY_CUSTODY_CORE_H_

keymanager/key_protection_service/key_custody_core/kps_key_custody_core_cgo.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,3 +57,47 @@ func GenerateKEMKeypair(bindingPubKey []byte, lifespanSecs uint64) (uuid.UUID, [
5757
copy(pubkey, pubkeyBuf[:pubkeyLen])
5858
return id, pubkey, nil
5959
}
60+
61+
// EnumerateKEMKeys retrieves all active KEM key entries from the Rust KCC registry.
62+
func EnumerateKEMKeys() ([]KEMKeyInfo, error) {
63+
const maxEntries = 256
64+
var entries [maxEntries]C.KpsKeyInfo
65+
var count C.size_t
66+
67+
rc := C.key_manager_enumerate_kem_keys(
68+
&entries[0],
69+
C.size_t(maxEntries),
70+
&count,
71+
)
72+
if rc != 0 {
73+
return nil, fmt.Errorf("key_manager_enumerate_kem_keys failed with code %d", rc)
74+
}
75+
76+
result := make([]KEMKeyInfo, count)
77+
for i := C.size_t(0); i < count; i++ {
78+
e := entries[i]
79+
80+
id, err := uuid.FromBytes(C.GoBytes(unsafe.Pointer(&e.uuid[0]), 16))
81+
if err != nil {
82+
return nil, fmt.Errorf("invalid UUID at index %d: %w", i, err)
83+
}
84+
85+
kemPubKey := make([]byte, e.kem_pub_key_len)
86+
copy(kemPubKey, C.GoBytes(unsafe.Pointer(&e.kem_pub_key[0]), C.int(e.kem_pub_key_len)))
87+
88+
bindingPubKey := make([]byte, e.binding_pub_key_len)
89+
copy(bindingPubKey, C.GoBytes(unsafe.Pointer(&e.binding_pub_key[0]), C.int(e.binding_pub_key_len)))
90+
91+
result[i] = KEMKeyInfo{
92+
ID: id,
93+
KemAlgorithm: int32(e.algorithm.kem),
94+
KdfAlgorithm: int32(e.algorithm.kdf),
95+
AeadAlgorithm: int32(e.algorithm.aead),
96+
KEMPubKey: kemPubKey,
97+
BindingPubKey: bindingPubKey,
98+
RemainingLifespanSecs: uint64(e.remaining_lifespan_secs),
99+
}
100+
}
101+
102+
return result, nil
103+
}

keymanager/key_protection_service/key_custody_core/src/lib.rs

Lines changed: 148 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use km_common::crypto::PublicKey;
33
use km_common::key_types::{KeyRecord, KeyRegistry, KeySpec};
44
use std::slice;
55
use std::sync::LazyLock;
6-
use std::time::Duration;
6+
use std::time::{Duration, Instant};
77

88
static KEY_REGISTRY: LazyLock<KeyRegistry> = LazyLock::new(KeyRegistry::default);
99

@@ -99,6 +99,75 @@ pub unsafe extern "C" fn key_manager_generate_kem_keypair(
9999
}
100100
}
101101

102+
#[repr(C)]
103+
pub struct KpsKeyInfo {
104+
pub uuid: [u8; 16],
105+
pub algorithm: KmHpkeAlgorithm,
106+
pub kem_pub_key: [u8; 64],
107+
pub kem_pub_key_len: usize,
108+
pub binding_pub_key: [u8; 64],
109+
pub binding_pub_key_len: usize,
110+
pub remaining_lifespan_secs: u64,
111+
}
112+
113+
#[unsafe(no_mangle)]
114+
pub extern "C" fn key_manager_enumerate_kem_keys(
115+
out_entries: *mut KpsKeyInfo,
116+
max_entries: usize,
117+
out_count: *mut usize,
118+
) -> i32 {
119+
if out_entries.is_null() || out_count.is_null() {
120+
return -1;
121+
}
122+
123+
let metas = KEY_REGISTRY.enumerate_keys();
124+
let now = Instant::now();
125+
let mut count = 0usize;
126+
127+
for meta in &metas {
128+
if count >= max_entries {
129+
break;
130+
}
131+
if let KeySpec::KemWithBindingPub {
132+
algo,
133+
kem_public_key,
134+
binding_public_key,
135+
} = &meta.spec
136+
{
137+
if kem_public_key.as_bytes().len() > 64 || binding_public_key.as_bytes().len() > 64 {
138+
continue;
139+
}
140+
141+
let remaining = meta.delete_after.saturating_duration_since(now).as_secs();
142+
143+
let entry = unsafe { &mut *out_entries.add(count) };
144+
entry.uuid.copy_from_slice(meta.id.as_bytes());
145+
entry.algorithm = KmHpkeAlgorithm {
146+
kem: (*algo).kem,
147+
kdf: (*algo).kdf,
148+
aead: (*algo).aead,
149+
};
150+
151+
entry.kem_pub_key = [0u8; 64];
152+
entry.kem_pub_key[..kem_public_key.as_bytes().len()].copy_from_slice(kem_public_key.as_bytes());
153+
entry.kem_pub_key_len = kem_public_key.as_bytes().len();
154+
155+
entry.binding_pub_key = [0u8; 64];
156+
entry.binding_pub_key[..binding_public_key.as_bytes().len()].copy_from_slice(binding_public_key.as_bytes());
157+
entry.binding_pub_key_len = binding_public_key.as_bytes().len();
158+
159+
entry.remaining_lifespan_secs = remaining;
160+
161+
count += 1;
162+
}
163+
}
164+
165+
unsafe {
166+
*out_count = count;
167+
}
168+
0
169+
}
170+
102171
#[cfg(test)]
103172
mod tests {
104173
use super::*;
@@ -261,4 +330,82 @@ mod tests {
261330

262331
assert_eq!(result, -1);
263332
}
333+
334+
#[test]
335+
fn test_enumerate_kem_keys_null_pointers() {
336+
let result = key_manager_enumerate_kem_keys(std::ptr::null_mut(), 10, std::ptr::null_mut());
337+
assert_eq!(result, -1);
338+
}
339+
340+
#[test]
341+
fn test_enumerate_kem_keys_after_generate() {
342+
let binding_pubkey = [7u8; 32];
343+
let mut uuid_bytes = [0u8; 16];
344+
let mut pubkey_bytes = [0u8; 64];
345+
let pubkey_len: usize = 32;
346+
let algo = HpkeAlgorithm {
347+
kem: KemAlgorithm::DhkemX25519HkdfSha256 as i32,
348+
kdf: KdfAlgorithm::HkdfSha256 as i32,
349+
aead: AeadAlgorithm::Aes256Gcm as i32,
350+
};
351+
352+
// Generate a key first.
353+
let rc = unsafe {
354+
key_manager_generate_kem_keypair(
355+
KmHpkeAlgorithm {
356+
kem: algo.kem,
357+
kdf: algo.kdf,
358+
aead: algo.aead,
359+
},
360+
binding_pubkey.as_ptr(),
361+
binding_pubkey.len(),
362+
3600,
363+
uuid_bytes.as_mut_ptr(),
364+
pubkey_bytes.as_mut_ptr(),
365+
pubkey_len,
366+
)
367+
};
368+
assert_eq!(rc, 0);
369+
370+
// Enumerate.
371+
let mut entries: Vec<KpsKeyInfo> = Vec::with_capacity(256);
372+
entries.resize_with(256, || KpsKeyInfo {
373+
uuid: [0; 16],
374+
algorithm: KmHpkeAlgorithm {
375+
kem: 0,
376+
kdf: 0,
377+
aead: 0,
378+
},
379+
kem_pub_key: [0; 64],
380+
kem_pub_key_len: 0,
381+
binding_pub_key: [0; 64],
382+
binding_pub_key_len: 0,
383+
remaining_lifespan_secs: 0,
384+
});
385+
let mut count: usize = 0;
386+
387+
let rc = unsafe { key_manager_enumerate_kem_keys(entries.as_mut_ptr(), entries.len(), &mut count) };
388+
assert_eq!(rc, 0);
389+
// At least 1 key should be enumerated (the one we just generated).
390+
// Note: other tests may have added keys to the global registry too.
391+
assert!(count >= 1);
392+
393+
// Find our key in the results.
394+
let mut found = false;
395+
for i in 0..count {
396+
if entries[i].uuid == uuid_bytes {
397+
found = true;
398+
assert_eq!(
399+
entries[i].algorithm.kem,
400+
KemAlgorithm::DhkemX25519HkdfSha256 as i32
401+
);
402+
assert_eq!(entries[i].kem_pub_key_len, 32);
403+
assert_eq!(entries[i].binding_pub_key_len, 32);
404+
assert_eq!(&entries[i].binding_pub_key[..32], &binding_pubkey);
405+
assert!(entries[i].remaining_lifespan_secs > 0);
406+
break;
407+
}
408+
}
409+
assert!(found, "generated key not found in enumerate results");
410+
}
264411
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package kpskcc
2+
3+
import "github.com/google/uuid"
4+
5+
// KEMKeyInfo holds metadata for a single KEM key returned by EnumerateKEMKeys.
6+
type KEMKeyInfo struct {
7+
ID uuid.UUID
8+
KemAlgorithm int32
9+
KdfAlgorithm int32
10+
AeadAlgorithm int32
11+
KEMPubKey []byte
12+
BindingPubKey []byte
13+
RemainingLifespanSecs uint64
14+
}
Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,47 @@
11
// Package key_protection_service implements the Key Orchestration Layer (KOL)
22
// for the Key Protection Service. It wraps the KPS Key Custody Core (KCC) FFI
3-
// to provide a Go-native interface for KEM key generation.
3+
// to provide a Go-native interface for KEM key generation and enumeration.
44
package key_protection_service
55

6-
import "github.com/google/uuid"
6+
import (
7+
kpskcc "github.com/google/go-tpm-tools/keymanager/key_protection_service/key_custody_core"
8+
"github.com/google/uuid"
9+
)
710

811
// KEMKeyGenerator generates KEM keypairs linked to a binding public key.
912
type KEMKeyGenerator interface {
1013
GenerateKEMKeypair(bindingPubKey []byte, lifespanSecs uint64) (uuid.UUID, []byte, error)
1114
}
1215

13-
// Service implements KEMKeyGenerator by delegating to the KPS KCC FFI.
16+
// KEMKeyEnumerator enumerates active KEM keys in the KPS registry.
17+
type KEMKeyEnumerator interface {
18+
EnumerateKEMKeys() ([]kpskcc.KEMKeyInfo, error)
19+
}
20+
21+
// Service implements KEMKeyGenerator and KEMKeyEnumerator by delegating to the KPS KCC FFI.
1422
type Service struct {
1523
generateKEMKeypairFn func(bindingPubKey []byte, lifespanSecs uint64) (uuid.UUID, []byte, error)
24+
enumerateKEMKeysFn func() ([]kpskcc.KEMKeyInfo, error)
1625
}
1726

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}
27+
// NewService creates a new KPS KOL service with the given KCC functions.
28+
func NewService(
29+
generateKEMKeypairFn func(bindingPubKey []byte, lifespanSecs uint64) (uuid.UUID, []byte, error),
30+
enumerateKEMKeysFn func() ([]kpskcc.KEMKeyInfo, error),
31+
) *Service {
32+
return &Service{
33+
generateKEMKeypairFn: generateKEMKeypairFn,
34+
enumerateKEMKeysFn: enumerateKEMKeysFn,
35+
}
2136
}
2237

2338
// GenerateKEMKeypair generates a KEM keypair linked to the provided binding
2439
// public key by calling the KPS KCC FFI.
2540
func (s *Service) GenerateKEMKeypair(bindingPubKey []byte, lifespanSecs uint64) (uuid.UUID, []byte, error) {
2641
return s.generateKEMKeypairFn(bindingPubKey, lifespanSecs)
2742
}
43+
44+
// EnumerateKEMKeys retrieves all active KEM key entries from the KPS KCC registry.
45+
func (s *Service) EnumerateKEMKeys() ([]kpskcc.KEMKeyInfo, error) {
46+
return s.enumerateKEMKeysFn()
47+
}

0 commit comments

Comments
 (0)