Skip to content

Commit 448b861

Browse files
committed
feat(keymanager): Implement Decap and Seal orchestration in Go
1 parent 0c7c4a6 commit 448b861

File tree

9 files changed

+502
-17
lines changed

9 files changed

+502
-17
lines changed

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
@@ -61,3 +61,47 @@ func GenerateKEMKeypair(algo *keymanager.HpkeAlgorithm, bindingPubKey []byte, li
6161
copy(pubkey, pubkeyBuf[:pubkeyLen])
6262
return id, pubkey, nil
6363
}
64+
65+
// DecapAndSeal decapsulates a shared secret using the stored KEM key and
66+
// reseals it with the associated binding public key via Rust FFI.
67+
// Returns the new encapsulated key and sealed ciphertext.
68+
func DecapAndSeal(kemUUID uuid.UUID, encapsulatedKey, aad []byte) ([]byte, []byte, error) {
69+
if len(encapsulatedKey) == 0 {
70+
return nil, nil, fmt.Errorf("encapsulated key must not be empty")
71+
}
72+
73+
uuidBytes := kemUUID[:]
74+
75+
var outEncKey [32]byte
76+
outEncKeyLen := C.size_t(len(outEncKey))
77+
var outCT [48]byte // 32-byte secret + 16-byte GCM tag
78+
outCTLen := C.size_t(len(outCT))
79+
80+
var aadPtr *C.uint8_t
81+
aadLen := C.size_t(0)
82+
if len(aad) > 0 {
83+
aadPtr = (*C.uint8_t)(unsafe.Pointer(&aad[0]))
84+
aadLen = C.size_t(len(aad))
85+
}
86+
87+
rc := C.key_manager_decap_and_seal(
88+
(*C.uint8_t)(unsafe.Pointer(&uuidBytes[0])),
89+
(*C.uint8_t)(unsafe.Pointer(&encapsulatedKey[0])),
90+
C.size_t(len(encapsulatedKey)),
91+
aadPtr,
92+
aadLen,
93+
(*C.uint8_t)(unsafe.Pointer(&outEncKey[0])),
94+
outEncKeyLen,
95+
(*C.uint8_t)(unsafe.Pointer(&outCT[0])),
96+
outCTLen,
97+
)
98+
if rc != 0 {
99+
return nil, nil, fmt.Errorf("key_manager_decap_and_seal failed with code %d", rc)
100+
}
101+
102+
sealEnc := make([]byte, outEncKeyLen)
103+
copy(sealEnc, outEncKey[:outEncKeyLen])
104+
sealedCT := make([]byte, outCTLen)
105+
copy(sealedCT, outCT[:outCTLen])
106+
return sealEnc, sealedCT, nil
107+
}

keymanager/key_protection_service/key_custody_core/kps_key_custody_core_stub.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,8 @@ import (
1414
func GenerateKEMKeypair(_ *algorithms.HpkeAlgorithm, _ []byte, _ uint64) (uuid.UUID, []byte, error) {
1515
return uuid.Nil, nil, fmt.Errorf("GenerateKEMKeypair is not supported on this architecture")
1616
}
17+
18+
// DecapAndSeal is a stub for architectures where the Rust library is not supported.
19+
func DecapAndSeal(_ uuid.UUID, _, _ []byte) ([]byte, []byte, error) {
20+
return nil, nil, fmt.Errorf("DecapAndSeal is not supported on this architecture")
21+
}

keymanager/key_protection_service/service.go

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// Package keyprotectionservice 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 operations.
44
package keyprotectionservice
55

66
import (
@@ -14,18 +14,36 @@ type KEMKeyGenerator interface {
1414
GenerateKEMKeypair(algo *keymanager.HpkeAlgorithm, bindingPubKey []byte, lifespanSecs uint64) ([]byte, []byte, error)
1515
}
1616

17-
// Service implements KEMKeyGenerator by delegating to the KPS KCC FFI.
17+
// DecapSealer decapsulates a shared secret and reseals it with the binding key.
18+
type DecapSealer interface {
19+
DecapAndSeal(kemUUID uuid.UUID, encapsulatedKey, aad []byte) (sealEnc []byte, sealedCT []byte, err error)
20+
}
21+
22+
// Service implements KEMKeyGenerator and DecapSealer by delegating to the KPS KCC FFI.
1823
type Service struct {
1924
generateKEMKeypairFn func(algo *keymanager.HpkeAlgorithm, bindingPubKey []byte, lifespanSecs uint64) (uuid.UUID, []byte, error)
25+
decapAndSealFn func(kemUUID uuid.UUID, encapsulatedKey, aad []byte) ([]byte, []byte, error)
2026
}
2127

22-
// NewService creates a new KPS KOL service with the given KCC function.
23-
func NewService(generateKEMKeypairFn func(algo *keymanager.HpkeAlgorithm, bindingPubKey []byte, lifespanSecs uint64) (uuid.UUID, []byte, error)) *Service {
24-
return &Service{generateKEMKeypairFn: generateKEMKeypairFn}
28+
// NewService creates a new KPS KOL service with the given KCC functions.
29+
func NewService(
30+
generateKEMKeypairFn func(algo *keymanager.HpkeAlgorithm, bindingPubKey []byte, lifespanSecs uint64) (uuid.UUID, []byte, error),
31+
decapAndSealFn func(kemUUID uuid.UUID, encapsulatedKey, aad []byte) ([]byte, []byte, error),
32+
) *Service {
33+
return &Service{
34+
generateKEMKeypairFn: generateKEMKeypairFn,
35+
decapAndSealFn: decapAndSealFn,
36+
}
2537
}
2638

2739
// GenerateKEMKeypair generates a KEM keypair linked to the provided binding
2840
// public key by calling the KPS KCC FFI.
2941
func (s *Service) GenerateKEMKeypair(algo *keymanager.HpkeAlgorithm, bindingPubKey []byte, lifespanSecs uint64) (uuid.UUID, []byte, error) {
3042
return s.generateKEMKeypairFn(algo, bindingPubKey, lifespanSecs)
3143
}
44+
45+
// DecapAndSeal decapsulates a shared secret using the stored KEM key and
46+
// reseals it with the associated binding public key by calling the KPS KCC FFI.
47+
func (s *Service) DecapAndSeal(kemUUID uuid.UUID, encapsulatedKey, aad []byte) ([]byte, []byte, error) {
48+
return s.decapAndSealFn(kemUUID, encapsulatedKey, aad)
49+
}

keymanager/key_protection_service/service_test.go

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

12+
// noopDecapAndSeal is a placeholder for tests that don't exercise DecapAndSeal.
13+
func noopDecapAndSeal(_ uuid.UUID, _, _ []byte) ([]byte, []byte, error) {
14+
return nil, nil, nil
15+
}
16+
1217
func TestServiceGenerateKEMKeypairSuccess(t *testing.T) {
1318
expectedUUID := uuid.New()
1419
expectedPubKey := make([]byte, 32)
@@ -24,7 +29,7 @@ func TestServiceGenerateKEMKeypairSuccess(t *testing.T) {
2429
t.Fatalf("expected lifespanSecs 7200, got %d", lifespanSecs)
2530
}
2631
return expectedUUID, expectedPubKey, nil
27-
})
32+
}, noopDecapAndSeal)
2833

2934
id, pubKey, err := svc.GenerateKEMKeypair(&keymanager.HpkeAlgorithm{}, make([]byte, 32), 7200)
3035
if err != nil {
@@ -41,10 +46,45 @@ func TestServiceGenerateKEMKeypairSuccess(t *testing.T) {
4146
func TestServiceGenerateKEMKeypairError(t *testing.T) {
4247
svc := NewService(func(_ *keymanager.HpkeAlgorithm, _ []byte, _ uint64) (uuid.UUID, []byte, error) {
4348
return uuid.Nil, nil, fmt.Errorf("FFI error")
44-
})
49+
}, noopDecapAndSeal)
4550

4651
_, _, err := svc.GenerateKEMKeypair(&keymanager.HpkeAlgorithm{}, make([]byte, 32), 3600)
4752
if err == nil {
4853
t.Fatal("expected error, got nil")
4954
}
5055
}
56+
57+
func TestServiceDecapAndSealSuccess(t *testing.T) {
58+
kemUUID := uuid.New()
59+
expectedSealEnc := []byte("seal-enc-key")
60+
expectedSealedCT := []byte("sealed-ciphertext")
61+
62+
svc := NewService(nil, func(id uuid.UUID, _, _ []byte) ([]byte, []byte, error) {
63+
if id != kemUUID {
64+
t.Fatalf("expected KEM UUID %s, got %s", kemUUID, id)
65+
}
66+
return expectedSealEnc, expectedSealedCT, nil
67+
})
68+
69+
sealEnc, sealedCT, err := svc.DecapAndSeal(kemUUID, []byte("enc-key"), []byte("aad"))
70+
if err != nil {
71+
t.Fatalf("unexpected error: %v", err)
72+
}
73+
if string(sealEnc) != string(expectedSealEnc) {
74+
t.Fatalf("expected seal enc %q, got %q", expectedSealEnc, sealEnc)
75+
}
76+
if string(sealedCT) != string(expectedSealedCT) {
77+
t.Fatalf("expected sealed CT %q, got %q", expectedSealedCT, sealedCT)
78+
}
79+
}
80+
81+
func TestServiceDecapAndSealError(t *testing.T) {
82+
svc := NewService(nil, func(_ uuid.UUID, _, _ []byte) ([]byte, []byte, error) {
83+
return nil, nil, fmt.Errorf("decap FFI error")
84+
})
85+
86+
_, _, err := svc.DecapAndSeal(uuid.New(), []byte("enc-key"), nil)
87+
if err == nil {
88+
t.Fatal("expected error, got nil")
89+
}
90+
}

keymanager/workload_service/integration_test.go

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

27+
// realOpener wraps the actual WSD KCC FFI for Open.
28+
type realOpener struct{}
29+
30+
func (r *realOpener) Open(bindingUUID uuid.UUID, enc, ciphertext, aad []byte) ([]byte, error) {
31+
return wskcc.Open(bindingUUID, enc, ciphertext, aad)
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(kpsSvc, &realWorkloadService{})
36+
kpsSvc := kps.NewService(kpskcc.GenerateKEMKeypair, kpskcc.DecapAndSeal)
37+
srv, _ := NewServer(kpsSvc, &realWorkloadService{}, "test.sock")
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(kpsSvc, &realWorkloadService{})
82+
kpsSvc := kps.NewService(kpskcc.GenerateKEMKeypair, kpskcc.DecapAndSeal)
83+
srv, _ := NewServer(kpsSvc, &realWorkloadService{}, "test.sock")
7784

7885
// Generate two key sets.
7986
var kemUUIDs [2]uuid.UUID

keymanager/workload_service/key_custody_core/ws_key_custody_core_cgo.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,3 +54,49 @@ func GenerateBindingKeypair(algo *keymanager.HpkeAlgorithm, lifespanSecs uint64)
5454
copy(pubkey, pubkeyBuf[:pubkeyLen])
5555
return id, pubkey, nil
5656
}
57+
58+
// Open decrypts a sealed ciphertext using the binding key identified by
59+
// bindingUUID via Rust FFI (HPKE Open).
60+
// Returns the decrypted plaintext (shared secret).
61+
func Open(bindingUUID uuid.UUID, enc, ciphertext, aad []byte) ([]byte, error) {
62+
if len(enc) == 0 {
63+
return nil, fmt.Errorf("enc must not be empty")
64+
}
65+
if len(ciphertext) == 0 {
66+
return nil, fmt.Errorf("ciphertext must not be empty")
67+
}
68+
69+
uuidBytes := bindingUUID[:]
70+
71+
var outPT [32]byte
72+
outPTLen := C.size_t(len(outPT))
73+
74+
// Rust key_manager_open requires non-null aad pointer.
75+
// Use a sentinel byte so the pointer is always valid.
76+
var aadSentinel [1]byte
77+
aadPtr := (*C.uint8_t)(unsafe.Pointer(&aadSentinel[0]))
78+
aadLen := C.size_t(0)
79+
if len(aad) > 0 {
80+
aadPtr = (*C.uint8_t)(unsafe.Pointer(&aad[0]))
81+
aadLen = C.size_t(len(aad))
82+
}
83+
84+
rc := C.key_manager_open(
85+
(*C.uint8_t)(unsafe.Pointer(&uuidBytes[0])),
86+
(*C.uint8_t)(unsafe.Pointer(&enc[0])),
87+
C.size_t(len(enc)),
88+
(*C.uint8_t)(unsafe.Pointer(&ciphertext[0])),
89+
C.size_t(len(ciphertext)),
90+
aadPtr,
91+
aadLen,
92+
(*C.uint8_t)(unsafe.Pointer(&outPT[0])),
93+
outPTLen,
94+
)
95+
if rc != 0 {
96+
return nil, fmt.Errorf("key_manager_open failed with code %d", rc)
97+
}
98+
99+
plaintext := make([]byte, outPTLen)
100+
copy(plaintext, outPT[:outPTLen])
101+
return plaintext, nil
102+
}

keymanager/workload_service/key_custody_core/ws_key_custody_core_stub.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,8 @@ import (
1414
func GenerateBindingKeypair(_ *algorithms.HpkeAlgorithm, _ uint64) (uuid.UUID, []byte, error) {
1515
return uuid.Nil, nil, fmt.Errorf("GenerateBindingKeypair is not supported on this architecture")
1616
}
17+
18+
// Open is a stub for architectures where the Rust library is not supported.
19+
func Open(_ uuid.UUID, _, _, _ []byte) ([]byte, error) {
20+
return nil, fmt.Errorf("Open is not supported on this architecture")
21+
}

0 commit comments

Comments
 (0)