Skip to content

Commit ab9e5b2

Browse files
committed
feat(keymanager): implement enumerate KEM keys API in Go
1 parent 59e0e4f commit ab9e5b2

File tree

7 files changed

+516
-28
lines changed

7 files changed

+516
-28
lines changed

keymanager/key_protection_service/key_custody_core/kps_key_custody_core_cgo.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ package kpskcc
66
#cgo LDFLAGS: -L${SRCDIR}/../../target/release -L${SRCDIR}/../../target/debug -lkps_key_custody_core
77
#cgo LDFLAGS: -lcrypto -lssl
88
#cgo LDFLAGS: -lpthread -ldl -lm -lstdc++
9+
#include <stdbool.h>
910
#include "include/kps_key_custody_core.h"
1011
*/
1112
import "C"
@@ -60,3 +61,56 @@ func GenerateKEMKeypair(algo *algorithms.HpkeAlgorithm, bindingPubKey []byte, li
6061
copy(pubkey, pubkeyBuf[:pubkeyLen])
6162
return id, pubkey, nil
6263
}
64+
65+
// EnumerateKEMKeys retrieves active KEM key entries from the Rust KCC registry with pagination.
66+
// Returns a list of keys and a boolean indicating if there are more keys to fetch.
67+
func EnumerateKEMKeys(limit, offset int) ([]KEMKeyInfo, bool, error) {
68+
if limit <= 0 {
69+
return nil, false, fmt.Errorf("limit must be positive")
70+
}
71+
if offset < 0 {
72+
return nil, false, fmt.Errorf("offset must be non-negative")
73+
}
74+
75+
entries := make([]C.KpsKeyInfo, limit)
76+
var hasMore C.bool
77+
78+
rc := C.key_manager_enumerate_kem_keys(
79+
&entries[0],
80+
C.size_t(limit),
81+
C.size_t(offset),
82+
&hasMore,
83+
)
84+
if rc < 0 {
85+
return nil, false, fmt.Errorf("key_manager_enumerate_kem_keys failed with code %d", rc)
86+
}
87+
88+
count := int(rc)
89+
result := make([]KEMKeyInfo, count)
90+
for i := 0; i < count; i++ {
91+
e := entries[i]
92+
93+
id, err := uuid.FromBytes(C.GoBytes(unsafe.Pointer(&e.uuid[0]), 16))
94+
if err != nil {
95+
return nil, false, fmt.Errorf("invalid UUID at index %d: %w", i, err)
96+
}
97+
98+
kemPubKey := make([]byte, e.pub_key_len)
99+
copy(kemPubKey, C.GoBytes(unsafe.Pointer(&e.pub_key[0]), C.int(e.pub_key_len)))
100+
101+
algoBytes := C.GoBytes(unsafe.Pointer(&e.algorithm[0]), C.int(e.algorithm_len))
102+
algo := &algorithms.HpkeAlgorithm{}
103+
if err := proto.Unmarshal(algoBytes, algo); err != nil {
104+
return nil, false, fmt.Errorf("failed to unmarshal algorithm for key %d: %w", i, err)
105+
}
106+
107+
result[i] = KEMKeyInfo{
108+
ID: id,
109+
Algorithm: algo,
110+
KEMPubKey: kemPubKey,
111+
RemainingLifespanSecs: uint64(e.remaining_lifespan_secs),
112+
}
113+
}
114+
115+
return result, bool(hasMore), nil
116+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package kpskcc
2+
3+
import (
4+
algorithms "github.com/google/go-tpm-tools/keymanager/km_common/proto"
5+
"github.com/google/uuid"
6+
)
7+
8+
// KEMKeyInfo holds metadata for a single KEM key returned by EnumerateKEMKeys.
9+
type KEMKeyInfo struct {
10+
ID uuid.UUID
11+
Algorithm *algorithms.HpkeAlgorithm
12+
KEMPubKey []byte
13+
RemainingLifespanSecs uint64
14+
}
Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
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

66
import (
7+
kpskcc "github.com/google/go-tpm-tools/keymanager/key_protection_service/key_custody_core"
78
"github.com/google/uuid"
89

910
algorithms "github.com/google/go-tpm-tools/keymanager/km_common/proto"
@@ -14,18 +15,35 @@ type KEMKeyGenerator interface {
1415
GenerateKEMKeypair(algo *algorithms.HpkeAlgorithm, bindingPubKey []byte, lifespanSecs uint64) (uuid.UUID, []byte, error)
1516
}
1617

17-
// Service implements KEMKeyGenerator by delegating to the KPS KCC FFI.
18+
// KEMKeyEnumerator enumerates active KEM keys in the KPS registry.
19+
type KEMKeyEnumerator interface {
20+
EnumerateKEMKeys(limit, offset int) ([]kpskcc.KEMKeyInfo, bool, error)
21+
}
22+
23+
// Service implements KEMKeyGenerator and KEMKeyEnumerator by delegating to the KPS KCC FFI.
1824
type Service struct {
1925
generateKEMKeypairFn func(algo *algorithms.HpkeAlgorithm, bindingPubKey []byte, lifespanSecs uint64) (uuid.UUID, []byte, error)
26+
enumerateKEMKeysFn func(limit, offset int) ([]kpskcc.KEMKeyInfo, bool, error)
2027
}
2128

22-
// 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}
29+
// NewService creates a new KPS KOL service with the given KCC functions.
30+
func NewService(
31+
generateKEMKeypairFn func(algo *algorithms.HpkeAlgorithm, bindingPubKey []byte, lifespanSecs uint64) (uuid.UUID, []byte, error),
32+
enumerateKEMKeysFn func(limit, offset int) ([]kpskcc.KEMKeyInfo, bool, error),
33+
) *Service {
34+
return &Service{
35+
generateKEMKeypairFn: generateKEMKeypairFn,
36+
enumerateKEMKeysFn: enumerateKEMKeysFn,
37+
}
2538
}
2639

2740
// GenerateKEMKeypair generates a KEM keypair linked to the provided binding
2841
// public key by calling the KPS KCC FFI.
2942
func (s *Service) GenerateKEMKeypair(algo *algorithms.HpkeAlgorithm, bindingPubKey []byte, lifespanSecs uint64) (uuid.UUID, []byte, error) {
3043
return s.generateKEMKeypairFn(algo, bindingPubKey, lifespanSecs)
3144
}
45+
46+
// EnumerateKEMKeys retrieves all active KEM key entries from the KPS KCC registry.
47+
func (s *Service) EnumerateKEMKeys(limit, offset int) ([]kpskcc.KEMKeyInfo, bool, error) {
48+
return s.enumerateKEMKeysFn(limit, offset)
49+
}

keymanager/key_protection_service/service_test.go

Lines changed: 77 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"fmt"
55
"testing"
66

7+
kpskcc "github.com/google/go-tpm-tools/keymanager/key_protection_service/key_custody_core"
78
"github.com/google/uuid"
89

910
algorithms "github.com/google/go-tpm-tools/keymanager/km_common/proto"
@@ -16,15 +17,20 @@ func TestServiceGenerateKEMKeypairSuccess(t *testing.T) {
1617
expectedPubKey[i] = byte(i + 10)
1718
}
1819

19-
svc := NewService(func(algo *algorithms.HpkeAlgorithm, bindingPubKey []byte, lifespanSecs uint64) (uuid.UUID, []byte, error) {
20-
if len(bindingPubKey) != 32 {
21-
t.Fatalf("expected 32-byte binding public key, got %d", len(bindingPubKey))
22-
}
23-
if lifespanSecs != 7200 {
24-
t.Fatalf("expected lifespanSecs 7200, got %d", lifespanSecs)
25-
}
26-
return expectedUUID, expectedPubKey, nil
27-
})
20+
svc := NewService(
21+
func(algo *algorithms.HpkeAlgorithm, bindingPubKey []byte, lifespanSecs uint64) (uuid.UUID, []byte, error) {
22+
if len(bindingPubKey) != 32 {
23+
t.Fatalf("expected 32-byte binding public key, got %d", len(bindingPubKey))
24+
}
25+
if lifespanSecs != 7200 {
26+
t.Fatalf("expected lifespanSecs 7200, got %d", lifespanSecs)
27+
}
28+
return expectedUUID, expectedPubKey, nil
29+
},
30+
func(limit, offset int) ([]kpskcc.KEMKeyInfo, bool, error) {
31+
return nil, false, nil
32+
},
33+
)
2834

2935
id, pubKey, err := svc.GenerateKEMKeypair(&algorithms.HpkeAlgorithm{}, make([]byte, 32), 7200)
3036
if err != nil {
@@ -39,12 +45,71 @@ func TestServiceGenerateKEMKeypairSuccess(t *testing.T) {
3945
}
4046

4147
func TestServiceGenerateKEMKeypairError(t *testing.T) {
42-
svc := NewService(func(algo *algorithms.HpkeAlgorithm, bindingPubKey []byte, lifespanSecs uint64) (uuid.UUID, []byte, error) {
43-
return uuid.Nil, nil, fmt.Errorf("FFI error")
44-
})
48+
svc := NewService(
49+
func(algo *algorithms.HpkeAlgorithm, bindingPubKey []byte, lifespanSecs uint64) (uuid.UUID, []byte, error) {
50+
return uuid.Nil, nil, fmt.Errorf("FFI error")
51+
},
52+
func(limit, offset int) ([]kpskcc.KEMKeyInfo, bool, error) {
53+
return nil, false, nil
54+
},
55+
)
4556

4657
_, _, err := svc.GenerateKEMKeypair(&algorithms.HpkeAlgorithm{}, make([]byte, 32), 3600)
4758
if err == nil {
4859
t.Fatal("expected error, got nil")
4960
}
5061
}
62+
63+
func TestServiceEnumerateKEMKeysSuccess(t *testing.T) {
64+
expectedKeys := []kpskcc.KEMKeyInfo{
65+
{
66+
ID: uuid.New(),
67+
Algorithm: &algorithms.HpkeAlgorithm{
68+
Kem: algorithms.KemAlgorithm_KEM_ALGORITHM_DHKEM_X25519_HKDF_SHA256,
69+
Kdf: algorithms.KdfAlgorithm_KDF_ALGORITHM_HKDF_SHA256,
70+
Aead: algorithms.AeadAlgorithm_AEAD_ALGORITHM_AES_256_GCM,
71+
},
72+
KEMPubKey: make([]byte, 32),
73+
RemainingLifespanSecs: 3500,
74+
},
75+
}
76+
77+
svc := NewService(
78+
func(algo *algorithms.HpkeAlgorithm, bindingPubKey []byte, lifespanSecs uint64) (uuid.UUID, []byte, error) {
79+
return uuid.Nil, nil, nil
80+
},
81+
func(limit, offset int) ([]kpskcc.KEMKeyInfo, bool, error) {
82+
if limit != 100 || offset != 0 {
83+
return nil, false, fmt.Errorf("unexpected limit/offset: %d/%d", limit, offset)
84+
}
85+
return expectedKeys, false, nil
86+
},
87+
)
88+
89+
keys, _, err := svc.EnumerateKEMKeys(100, 0)
90+
if err != nil {
91+
t.Fatalf("unexpected error: %v", err)
92+
}
93+
if len(keys) != 1 {
94+
t.Fatalf("expected 1 key, got %d", len(keys))
95+
}
96+
if keys[0].ID != expectedKeys[0].ID {
97+
t.Fatalf("expected ID %s, got %s", expectedKeys[0].ID, keys[0].ID)
98+
}
99+
}
100+
101+
func TestServiceEnumerateKEMKeysError(t *testing.T) {
102+
svc := NewService(
103+
func(algo *algorithms.HpkeAlgorithm, bindingPubKey []byte, lifespanSecs uint64) (uuid.UUID, []byte, error) {
104+
return uuid.Nil, nil, nil
105+
},
106+
func(limit, offset int) ([]kpskcc.KEMKeyInfo, bool, error) {
107+
return nil, false, fmt.Errorf("enumerate error")
108+
},
109+
)
110+
111+
_, _, err := svc.EnumerateKEMKeys(100, 0)
112+
if err == nil {
113+
t.Fatal("expected error, got nil")
114+
}
115+
}

keymanager/workload_service/proto_enums.go

Lines changed: 95 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -57,18 +57,21 @@ func (k *KemAlgorithm) UnmarshalJSON(data []byte) error {
5757
type KeyProtectionMechanism int32
5858

5959
const (
60-
KeyProtectionMechanismDefault KeyProtectionMechanism = 1
61-
KeyProtectionMechanismVM KeyProtectionMechanism = 2
60+
KeyProtectionMechanismDefault KeyProtectionMechanism = 1
61+
KeyProtectionMechanismVM KeyProtectionMechanism = 2
62+
KeyProtectionMechanismVMEmulated KeyProtectionMechanism = 3
6263
)
6364

6465
var (
6566
keyProtectionMechanismToString = map[KeyProtectionMechanism]string{
66-
KeyProtectionMechanismDefault: "DEFAULT",
67-
KeyProtectionMechanismVM: "KEY_PROTECTION_VM",
67+
KeyProtectionMechanismDefault: "DEFAULT",
68+
KeyProtectionMechanismVM: "KEY_PROTECTION_VM",
69+
KeyProtectionMechanismVMEmulated: "KEY_PROTECTION_VM_EMULATED",
6870
}
6971
stringToKeyProtectionMechanism = map[string]KeyProtectionMechanism{
70-
"DEFAULT": KeyProtectionMechanismDefault,
71-
"KEY_PROTECTION_VM": KeyProtectionMechanismVM,
72+
"DEFAULT": KeyProtectionMechanismDefault,
73+
"KEY_PROTECTION_VM": KeyProtectionMechanismVM,
74+
"KEY_PROTECTION_VM_EMULATED": KeyProtectionMechanismVMEmulated,
7275
}
7376
)
7477

@@ -106,6 +109,7 @@ var (
106109
SupportedKeyProtectionMechanisms = []KeyProtectionMechanism{
107110
KeyProtectionMechanismDefault,
108111
KeyProtectionMechanismVM,
112+
KeyProtectionMechanismVMEmulated,
109113
}
110114
)
111115

@@ -151,3 +155,88 @@ func (k KemAlgorithm) ToHpkeAlgorithm() (*algorithms.HpkeAlgorithm, error) {
151155
return nil, fmt.Errorf("unsupported algorithm: %s", k)
152156
}
153157
}
158+
159+
// KdfAlgorithm represents the requested KDF algorithm.
160+
type KdfAlgorithm int32
161+
162+
const (
163+
KdfAlgorithmUnspecified KdfAlgorithm = 0
164+
// Corrected from HKDF_SHA384 to HKDF_SHA256 based on ToHpkeAlgorithm usage which maps to HKDF_SHA256 (val 1)
165+
KdfAlgorithmHKDFSHA256 KdfAlgorithm = 1
166+
)
167+
168+
var (
169+
kdfAlgorithmToString = map[KdfAlgorithm]string{
170+
KdfAlgorithmUnspecified: "KDF_ALGORITHM_UNSPECIFIED",
171+
KdfAlgorithmHKDFSHA256: "HKDF_SHA256",
172+
}
173+
stringToKdfAlgorithm = map[string]KdfAlgorithm{
174+
"KDF_ALGORITHM_UNSPECIFIED": KdfAlgorithmUnspecified,
175+
"HKDF_SHA256": KdfAlgorithmHKDFSHA256,
176+
}
177+
)
178+
179+
func (k KdfAlgorithm) String() string {
180+
if s, ok := kdfAlgorithmToString[k]; ok {
181+
return s
182+
}
183+
return fmt.Sprintf("KDF_ALGORITHM_UNKNOWN(%d)", k)
184+
}
185+
186+
func (k KdfAlgorithm) MarshalJSON() ([]byte, error) {
187+
return json.Marshal(k.String())
188+
}
189+
190+
func (k *KdfAlgorithm) UnmarshalJSON(data []byte) error {
191+
var s string
192+
if err := json.Unmarshal(data, &s); err != nil {
193+
return fmt.Errorf("KdfAlgorithm must be a string")
194+
}
195+
if v, ok := stringToKdfAlgorithm[s]; ok {
196+
*k = v
197+
return nil
198+
}
199+
return fmt.Errorf("unknown KdfAlgorithm: %q", s)
200+
}
201+
202+
// AeadAlgorithm represents the requested AEAD algorithm.
203+
type AeadAlgorithm int32
204+
205+
const (
206+
AeadAlgorithmUnspecified AeadAlgorithm = 0
207+
AeadAlgorithmAES256GCM AeadAlgorithm = 1
208+
)
209+
210+
var (
211+
aeadAlgorithmToString = map[AeadAlgorithm]string{
212+
AeadAlgorithmUnspecified: "AEAD_ALGORITHM_UNSPECIFIED",
213+
AeadAlgorithmAES256GCM: "AES_256_GCM",
214+
}
215+
stringToAeadAlgorithm = map[string]AeadAlgorithm{
216+
"AEAD_ALGORITHM_UNSPECIFIED": AeadAlgorithmUnspecified,
217+
"AES_256_GCM": AeadAlgorithmAES256GCM,
218+
}
219+
)
220+
221+
func (k AeadAlgorithm) String() string {
222+
if s, ok := aeadAlgorithmToString[k]; ok {
223+
return s
224+
}
225+
return fmt.Sprintf("AEAD_ALGORITHM_UNKNOWN(%d)", k)
226+
}
227+
228+
func (k AeadAlgorithm) MarshalJSON() ([]byte, error) {
229+
return json.Marshal(k.String())
230+
}
231+
232+
func (k *AeadAlgorithm) UnmarshalJSON(data []byte) error {
233+
var s string
234+
if err := json.Unmarshal(data, &s); err != nil {
235+
return fmt.Errorf("AeadAlgorithm must be a string")
236+
}
237+
if v, ok := stringToAeadAlgorithm[s]; ok {
238+
*k = v
239+
return nil
240+
}
241+
return fmt.Errorf("unknown AeadAlgorithm: %q", s)
242+
}

0 commit comments

Comments
 (0)