Skip to content

Commit 59e0e4f

Browse files
authored
[KeyManager] FFI for Enumerate keys (#665)
Implements FFI for the Enumerate Keys API in the Key Custody Core (KCC). It exposes the necessary Rust functions to allow the Go Workload Service to list active KEM keys. ## Dependency - Created on top of PR #652 (Key Generation KOL changes) ## Key Changes 1. **Key Custody Core (`key_custody_core`)**: - Added `key_manager_enumerate_kem_keys` FFI function. - Implemented `enumerate_kem_keys` in `KeyRegistry` to safely expose key metadata. - Added `KmHpkeAlgorithm` struct for C compatibility. 2. **C Header (`kps_key_custody_core.h`)**: - Exported `key_manager_enumerate_kem_keys` definition. ## Verification - **Rust Tests**: - Ran `cargo check` and `cargo test` in `keymanager/key_protection_service/key_custody_core`. - Verified that the new FFI function compiles and integrates with the existing system.
1 parent c6e1090 commit 59e0e4f

File tree

8 files changed

+531
-32
lines changed

8 files changed

+531
-32
lines changed

keymanager/key_protection_service/key_custody_core/cbindgen.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ pragma_once = false
44
autogen_warning = "/* Auto-generated by cbindgen. DO NOT EDIT. */"
55
cpp_compat = true
66
no_includes = true
7-
style = "tag"
7+
style = "type"
88
documentation = false
99
usize_is_size_t = true
1010
sys_includes = ["stddef.h", "stdint.h"]
@@ -15,7 +15,7 @@ parse_deps = false
1515
clean = true
1616

1717
[export]
18-
item_types = ["functions"]
19-
include = ["key_manager_generate_kem_keypair"]
18+
item_types = ["functions", "structs", "constants", "enums"]
19+
include = ["key_manager_generate_kem_keypair", "key_manager_destroy_kem_key", "key_manager_enumerate_kem_keys", "KpsKeyInfo", "MAX_ALGORITHM_LEN", "MAX_PUBLIC_KEY_LEN"]
2020

2121

keymanager/key_protection_service/key_custody_core/include/kps_key_custody_core.h

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,21 @@
66
#include <stddef.h>
77
#include <stdint.h>
88

9+
#define MAX_ALGORITHM_LEN 128
10+
11+
#define MAX_PUBLIC_KEY_LEN 2048
12+
13+
typedef struct {
14+
uint8_t uuid[16];
15+
uint8_t algorithm[MAX_ALGORITHM_LEN];
16+
size_t algorithm_len;
17+
uint8_t pub_key[MAX_PUBLIC_KEY_LEN];
18+
size_t pub_key_len;
19+
uint8_t binding_pub_key[MAX_PUBLIC_KEY_LEN];
20+
size_t binding_pub_key_len;
21+
uint64_t remaining_lifespan_secs;
22+
} KpsKeyInfo;
23+
924
#ifdef __cplusplus
1025
extern "C" {
1126
#endif // __cplusplus
@@ -21,6 +36,11 @@ int32_t key_manager_generate_kem_keypair(const uint8_t *algo_ptr,
2136

2237
int32_t key_manager_destroy_kem_key(const uint8_t *uuid_bytes);
2338

39+
int32_t key_manager_enumerate_kem_keys(KpsKeyInfo *out_entries,
40+
size_t max_entries,
41+
size_t offset,
42+
bool *out_has_more);
43+
2444
int32_t key_manager_decap_and_seal(const uint8_t *uuid_bytes,
2545
const uint8_t *encapsulated_key,
2646
size_t encapsulated_key_len,

keymanager/key_protection_service/key_custody_core/src/lib.rs

Lines changed: 269 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@ use km_common::crypto::PublicKey;
33
use km_common::key_types::{KeyRecord, KeyRegistry, KeySpec};
44
use prost::Message;
55
use std::slice;
6+
use std::sync::atomic::AtomicBool;
67
use std::sync::Arc;
78
use std::sync::LazyLock;
8-
use std::sync::atomic::AtomicBool;
9-
use std::time::Duration;
9+
use std::time::{Duration, Instant};
1010
use uuid::Uuid;
1111

1212
static KEY_REGISTRY: LazyLock<KeyRegistry> = LazyLock::new(|| {
@@ -196,6 +196,125 @@ fn decap_and_seal_internal(
196196
}
197197
}
198198

199+
pub const MAX_ALGORITHM_LEN: usize = 128;
200+
pub const MAX_PUBLIC_KEY_LEN: usize = 2048;
201+
202+
#[repr(C)]
203+
pub struct KpsKeyInfo {
204+
pub uuid: [u8; 16],
205+
pub algorithm: [u8; MAX_ALGORITHM_LEN],
206+
pub algorithm_len: usize,
207+
pub pub_key: [u8; MAX_PUBLIC_KEY_LEN],
208+
pub pub_key_len: usize,
209+
pub binding_pub_key: [u8; MAX_PUBLIC_KEY_LEN],
210+
pub binding_pub_key_len: usize,
211+
pub remaining_lifespan_secs: u64,
212+
}
213+
214+
impl Default for KpsKeyInfo {
215+
fn default() -> Self {
216+
KpsKeyInfo {
217+
uuid: [0; 16],
218+
algorithm: [0; MAX_ALGORITHM_LEN],
219+
algorithm_len: 0,
220+
pub_key: [0; MAX_PUBLIC_KEY_LEN],
221+
pub_key_len: 0,
222+
binding_pub_key: [0; MAX_PUBLIC_KEY_LEN],
223+
binding_pub_key_len: 0,
224+
remaining_lifespan_secs: 0,
225+
}
226+
}
227+
}
228+
229+
fn enumerate_kem_keys_internal(
230+
entries: &mut [KpsKeyInfo],
231+
offset: usize,
232+
) -> Result<(usize, bool), i32> {
233+
let (metas, total_count) = KEY_REGISTRY.list_all_keys(offset, entries.len());
234+
let count = metas.len();
235+
let has_more = offset + count < total_count;
236+
237+
for (entry, meta) in entries.iter_mut().zip(metas.into_iter()) {
238+
let KeySpec::KemWithBindingPub {
239+
algo,
240+
kem_public_key: pub_key,
241+
binding_public_key: binding_pub_key,
242+
..
243+
} = &meta.spec
244+
else {
245+
return Err(-1); // Implementation error, KPS should only contain KEM keys.
246+
};
247+
248+
let algo_bytes = algo.encode_to_vec();
249+
250+
if pub_key.as_bytes().len() > MAX_PUBLIC_KEY_LEN || algo_bytes.len() > MAX_ALGORITHM_LEN {
251+
debug_assert!(
252+
false,
253+
"Implementation error: Key size exceeds buffer limits! (algo={}, pub={})",
254+
algo_bytes.len(),
255+
pub_key.as_bytes().len()
256+
);
257+
return Err(-2); // Buffer Limit Exceeded
258+
}
259+
if binding_pub_key.as_bytes().len() > MAX_PUBLIC_KEY_LEN {
260+
debug_assert!(
261+
false,
262+
"Implementation error: Binding Key size exceeds buffer limits! (bpk={})",
263+
binding_pub_key.as_bytes().len()
264+
);
265+
return Err(-2);
266+
}
267+
268+
let now = Instant::now();
269+
let remaining = meta.delete_after.saturating_duration_since(now).as_secs();
270+
271+
*entry = KpsKeyInfo::default();
272+
273+
entry.uuid.copy_from_slice(meta.id.as_bytes());
274+
275+
entry.algorithm[..algo_bytes.len()].copy_from_slice(&algo_bytes);
276+
entry.algorithm_len = algo_bytes.len();
277+
278+
entry.pub_key[..pub_key.as_bytes().len()].copy_from_slice(pub_key.as_bytes());
279+
entry.pub_key_len = pub_key.as_bytes().len();
280+
281+
entry.binding_pub_key[..binding_pub_key.as_bytes().len()]
282+
.copy_from_slice(binding_pub_key.as_bytes());
283+
entry.binding_pub_key_len = binding_pub_key.as_bytes().len();
284+
285+
entry.remaining_lifespan_secs = remaining;
286+
}
287+
288+
Ok((count, has_more))
289+
}
290+
291+
#[unsafe(no_mangle)]
292+
pub unsafe extern "C" fn key_manager_enumerate_kem_keys(
293+
out_entries: *mut KpsKeyInfo,
294+
max_entries: usize,
295+
offset: usize,
296+
out_has_more: Option<&mut bool>,
297+
) -> i32 {
298+
std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
299+
if out_entries.is_null() {
300+
return -1;
301+
}
302+
303+
let entries = unsafe { slice::from_raw_parts_mut(out_entries, max_entries) };
304+
305+
match enumerate_kem_keys_internal(entries, offset) {
306+
Ok((count, has_more)) => {
307+
if let Some(has_more_ref) = out_has_more {
308+
*has_more_ref = has_more;
309+
}
310+
count as i32
311+
}
312+
Err(e) => e,
313+
}
314+
}))
315+
.unwrap_or(-1)
316+
}
317+
199318
/// Decapsulates a shared secret using a stored KEM key and immediately reseals it using the associated binding public key.
200319
///
201320
/// ## Arguments
@@ -498,6 +617,150 @@ mod tests {
498617
assert_eq!(result, -1);
499618
}
500619

620+
#[test]
621+
fn test_enumerate_kem_keys_null_pointers() {
622+
let result = unsafe { key_manager_enumerate_kem_keys(std::ptr::null_mut(), 10, 0, None) };
623+
assert_eq!(result, -1);
624+
}
625+
626+
#[test]
627+
fn test_enumerate_kem_keys_after_generate() {
628+
let binding_pubkey = [7u8; 32];
629+
let mut uuid_bytes = [0u8; 16];
630+
let mut pubkey_bytes = [0u8; 32];
631+
let pubkey_len: usize = 32;
632+
let algo = HpkeAlgorithm {
633+
kem: KemAlgorithm::DhkemX25519HkdfSha256 as i32,
634+
kdf: KdfAlgorithm::HkdfSha256 as i32,
635+
aead: AeadAlgorithm::Aes256Gcm as i32,
636+
};
637+
// MUST encode to bytes
638+
let algo_bytes = algo.encode_to_vec();
639+
640+
// Generate a key first.
641+
let rc = unsafe {
642+
key_manager_generate_kem_keypair(
643+
algo_bytes.as_ptr(),
644+
algo_bytes.len(),
645+
binding_pubkey.as_ptr(),
646+
binding_pubkey.len(),
647+
3600,
648+
uuid_bytes.as_mut_ptr(),
649+
pubkey_bytes.as_mut_ptr(),
650+
pubkey_len,
651+
)
652+
};
653+
assert_eq!(rc, 0);
654+
655+
// Enumerate.
656+
let mut entries: Vec<KpsKeyInfo> = Vec::with_capacity(256);
657+
// Initialize with default/zero values. Note: Arrays are larger now.
658+
entries.resize_with(100, || KpsKeyInfo {
659+
uuid: [0; 16],
660+
algorithm: [0; MAX_ALGORITHM_LEN],
661+
algorithm_len: 0,
662+
pub_key: [0; MAX_PUBLIC_KEY_LEN],
663+
pub_key_len: 0,
664+
binding_pub_key: [0; MAX_PUBLIC_KEY_LEN],
665+
binding_pub_key_len: 0,
666+
remaining_lifespan_secs: 0,
667+
});
668+
let mut has_more = false;
669+
670+
let rc = unsafe {
671+
// max_entries=100, offset=0
672+
key_manager_enumerate_kem_keys(
673+
entries.as_mut_ptr(),
674+
entries.len(),
675+
0,
676+
Some(&mut has_more),
677+
)
678+
};
679+
assert!(rc >= 1);
680+
let count = rc as usize;
681+
682+
// Find our key in the results.
683+
let mut found = false;
684+
for i in 0..count {
685+
if entries[i].uuid == uuid_bytes {
686+
found = true;
687+
let encoded_algo = &entries[i].algorithm[..entries[i].algorithm_len];
688+
let decoded_algo = HpkeAlgorithm::decode(encoded_algo).unwrap();
689+
assert_eq!(decoded_algo.kem, KemAlgorithm::DhkemX25519HkdfSha256 as i32);
690+
assert_eq!(entries[i].pub_key_len, 32);
691+
assert!(entries[i].remaining_lifespan_secs > 0);
692+
break;
693+
}
694+
}
695+
assert!(found, "generated key not found in enumerate results");
696+
}
697+
698+
#[test]
699+
fn test_enumerate_kem_keys_has_more() {
700+
// Assume there is at least one key from initialization/other tests or we'll generate one
701+
let mut uuid_bytes = [0u8; 16];
702+
let mut pubkey_bytes = [0u8; 32];
703+
let algo = HpkeAlgorithm {
704+
kem: KemAlgorithm::DhkemX25519HkdfSha256 as i32,
705+
kdf: KdfAlgorithm::HkdfSha256 as i32,
706+
aead: AeadAlgorithm::Aes256Gcm as i32,
707+
};
708+
let algo_bytes = algo.encode_to_vec();
709+
710+
// Let's explicitly generate a key so we know there's at least one in the registry
711+
unsafe {
712+
key_manager_generate_kem_keypair(
713+
algo_bytes.as_ptr(),
714+
algo_bytes.len(),
715+
[7u8; 32].as_ptr(), // fake binding key
716+
32,
717+
3600,
718+
uuid_bytes.as_mut_ptr(),
719+
pubkey_bytes.as_mut_ptr(),
720+
32,
721+
);
722+
}
723+
724+
let mut entries: Vec<KpsKeyInfo> = Vec::with_capacity(256);
725+
entries.resize_with(100, || KpsKeyInfo {
726+
uuid: [0; 16],
727+
algorithm: [0; MAX_ALGORITHM_LEN],
728+
algorithm_len: 0,
729+
pub_key: [0; MAX_PUBLIC_KEY_LEN],
730+
pub_key_len: 0,
731+
binding_pub_key: [0; MAX_PUBLIC_KEY_LEN],
732+
binding_pub_key_len: 0,
733+
remaining_lifespan_secs: 0,
734+
});
735+
736+
// 1. Ask for 0 entries. We should get has_more = true.
737+
let mut has_more = false;
738+
let rc = unsafe {
739+
key_manager_enumerate_kem_keys(entries.as_mut_ptr(), 0, 0, Some(&mut has_more))
740+
};
741+
assert_eq!(rc, 0);
742+
assert!(
743+
has_more,
744+
"has_more should be true when max_entries is 0 and keys exist"
745+
);
746+
747+
// 2. Ask for 100 entries (which should cover all generated keys). has_more = false.
748+
has_more = true; // reset to true to ensure it gets set to false
749+
let rc = unsafe {
750+
key_manager_enumerate_kem_keys(
751+
entries.as_mut_ptr(),
752+
entries.len(),
753+
0,
754+
Some(&mut has_more),
755+
)
756+
};
757+
assert!(rc >= 1);
758+
assert!(
759+
!has_more,
760+
"has_more should be false when all keys are retrieved"
761+
);
762+
}
763+
501764
#[test]
502765
fn test_decap_and_seal_success() {
503766
// 1. Setup binding key (receiver for seal)
@@ -531,9 +794,8 @@ mod tests {
531794
// 3. Generate a "client" ciphertext/encapsulation targeting KEM key.
532795
let aad = b"test_aad";
533796
// We use `encap` to act as the client to generate a valid encapsulation
534-
let kem_pub_key_obj = PublicKey::try_from(kem_pubkey_bytes.to_vec()).unwrap();
535-
let (client_shared_secret, client_enc) =
536-
km_common::crypto::encap(&kem_pub_key_obj).unwrap();
797+
let pub_key_obj = PublicKey::try_from(kem_pubkey_bytes.to_vec()).unwrap();
798+
let (client_shared_secret, client_enc) = km_common::crypto::encap(&pub_key_obj).unwrap();
537799

538800
// Step 3: Call `decap_and_seal`.
539801
let mut out_enc_key = [0u8; 32];
@@ -704,10 +966,9 @@ mod tests {
704966
assert_eq!(res, 0, "Setup failed: key generation returned error");
705967

706968
// 3. Generate valid client encapsulation
707-
let kem_pub_key_obj = PublicKey::try_from(kem_pubkey_bytes.to_vec()).unwrap();
969+
let pub_key_obj = PublicKey::try_from(kem_pubkey_bytes.to_vec()).unwrap();
708970
let pt = km_common::crypto::secret_box::SecretBox::new(b"secret".to_vec());
709-
let (client_enc, _) =
710-
km_common::crypto::hpke_seal(&kem_pub_key_obj, &pt, b"", &algo).unwrap();
971+
let (client_enc, _) = km_common::crypto::hpke_seal(&pub_key_obj, &pt, b"", &algo).unwrap();
711972

712973
// 4. Call with small output buffers
713974
let mut out_enc_key = [0u8; 31]; // Small

keymanager/km_common/build.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ fn main() -> Result<()> {
77
}
88

99
let mut config = prost_build::Config::new();
10-
config.type_attribute("HpkeAlgorithm", "#[repr(C)]");
10+
1111
config.compile_protos(&["proto/algorithms.proto"], &["proto/"])?;
1212

1313
Ok(())

0 commit comments

Comments
 (0)