Skip to content

Commit e3727e5

Browse files
committed
Add KeyManager integration tests and schema validation
- Implemented comprehensive schema validation for KeyManager API responses. - Added new integration tests for GetCapabilities, EnumerateKeys, and Decapsulate APIs. - Refactored integration tests to use a shared setup helper and centralized schemas. - Extended TestIntegrationEnumerateKeys to validate response content against generated keys. - Implemented a DHKEM encapsulation helper in Go for testing the Decapsulate API. - Fixed minor lint errors in integration tests.
1 parent a07f836 commit e3727e5

File tree

3 files changed

+532
-26
lines changed

3 files changed

+532
-26
lines changed
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
//go:build integration
2+
3+
package workloadservice
4+
5+
import (
6+
"bytes"
7+
"crypto/ecdh"
8+
"crypto/rand"
9+
"crypto/sha256"
10+
"encoding/hex"
11+
"fmt"
12+
"io"
13+
"testing"
14+
15+
"golang.org/x/crypto/hkdf"
16+
)
17+
18+
const hpkeVersion = "HPKE-v1"
19+
20+
// hpkeLabeledExtract implements the HPKE LabeledExtract function.
21+
// It derives a pseudorandom key (PRK) from the input keying material (IKM).
22+
func hpkeLabeledExtract(suiteID []byte, label string, ikm []byte) []byte {
23+
labeledIKM := append([]byte(hpkeVersion), suiteID...)
24+
labeledIKM = append(labeledIKM, []byte(label)...)
25+
labeledIKM = append(labeledIKM, ikm...)
26+
return hkdf.Extract(sha256.New, labeledIKM, nil)
27+
}
28+
29+
// hpkeLabeledExpand implements the HPKE LabeledExpand function.
30+
// It expands a pseudorandom key (PRK) into a string of length `length`.
31+
func hpkeLabeledExpand(prk []byte, suiteID []byte, label string, info []byte, length int) ([]byte, error) {
32+
var labeledInfo []byte
33+
labeledInfo = append(labeledInfo, byte(length>>8), byte(length))
34+
labeledInfo = append(labeledInfo, []byte(hpkeVersion)...)
35+
labeledInfo = append(labeledInfo, suiteID...)
36+
labeledInfo = append(labeledInfo, []byte(label)...)
37+
labeledInfo = append(labeledInfo, info...)
38+
r := hkdf.Expand(sha256.New, prk, labeledInfo)
39+
k := make([]byte, length)
40+
if _, err := io.ReadFull(r, k); err != nil {
41+
return nil, err
42+
}
43+
return k, nil
44+
}
45+
46+
// encapsulateDHKEMX25519HKDFSHA256 performs DHKEM encapsulation for X25519-HKDF-SHA256.
47+
// It generates an ephemeral keypair, computes the DH shared secret with the recipient's
48+
// public key, and derives the HPKE shared secret using labeled extract and expand.
49+
// Returns the derived shared secret and its corresponding ephemeral public key (enc).
50+
func encapsulateDHKEMX25519HKDFSHA256(pkR []byte) (sharedSecret []byte, enc []byte, err error) {
51+
// Generate ephemeral keypair
52+
skE, err := ecdh.X25519().GenerateKey(rand.Reader)
53+
if err != nil {
54+
return nil, nil, fmt.Errorf("failed to generate ephemeral key: %v", err)
55+
}
56+
pkE := skE.PublicKey().Bytes()
57+
58+
// Compute DH shared secret
59+
dhPeer, err := ecdh.X25519().NewPublicKey(pkR)
60+
if err != nil {
61+
return nil, nil, fmt.Errorf("failed to parse recipient public key: %v", err)
62+
}
63+
dh, err := skE.ECDH(dhPeer)
64+
if err != nil {
65+
return nil, nil, fmt.Errorf("failed to compute DH: %v", err)
66+
}
67+
68+
// Compute kem_context = pkE || pkR
69+
kemContext := append(pkE, pkR...)
70+
71+
// Compute suite_id = "KEM" || I2OSP(32, 2)
72+
suiteID := []byte("KEM\x00\x20")
73+
74+
// Extract and Expand
75+
prk := hpkeLabeledExtract(suiteID, "eae_prk", dh)
76+
sharedSecret, err = hpkeLabeledExpand(prk, suiteID, "shared_secret", kemContext, 32)
77+
if err != nil {
78+
return nil, nil, fmt.Errorf("failed to expand shared secret: %v", err)
79+
}
80+
81+
return sharedSecret, pkE, nil
82+
}
83+
84+
// TestValidateDHKEMHelpers validates the HPKE helper functions using test vectors
85+
// from RFC 9180 Appendix A.1.
86+
func TestValidateDHKEMHelpers(t *testing.T) {
87+
// Values from RFC 9180 Appendix A.1 https://www.rfc-editor.org/rfc/rfc9180.html#appendix-A.1
88+
pkRmHex := "3948cfe0ad1ddb695d780e59077195da6c56506b027329794ab02bca80815c4d"
89+
// skRmHex := "4612c550263fc8ad58375df3f557aac531d26850903e55a9f23f21d8534e8ac8"
90+
pkEmHex := "37fda3567bdbd628e88668c3c8d7e97d1d1253b6d4ea6d44c150f741f1bf4431"
91+
skEmHex := "52c4a758a802cd8b936eceea314432798d5baf2d7e9235dc084ab1b9cfa2f736"
92+
sharedSecretHex := "fe0e18c9f024ce43799ae393c7e8fe8fce9d218875e8227b0187c04e7d2ea1fc"
93+
94+
pkRm, _ := hex.DecodeString(pkRmHex)
95+
// skRm, _ := hex.DecodeString(skRmHex)
96+
pkEm, _ := hex.DecodeString(pkEmHex)
97+
skEm, _ := hex.DecodeString(skEmHex)
98+
expectedSharedSecret, _ := hex.DecodeString(sharedSecretHex)
99+
100+
// Compute DH shared secret manually using test vector keys
101+
dhPeer, err := ecdh.X25519().NewPublicKey(pkRm)
102+
if err != nil {
103+
t.Fatalf("failed to parse pkRm: %v", err)
104+
}
105+
skE, err := ecdh.X25519().NewPrivateKey(skEm)
106+
if err != nil {
107+
t.Fatalf("failed to create skE: %v", err)
108+
}
109+
dh, err := skE.ECDH(dhPeer)
110+
if err != nil {
111+
t.Fatalf("failed to compute DH: %v", err)
112+
}
113+
114+
// Compute kem_context = pkE || pkR
115+
kemContext := append(pkEm, pkRm...)
116+
117+
// Compute suite_id = "KEM" || I2OSP(32, 2)
118+
suiteID := []byte("KEM\x00\x20")
119+
120+
// Extract and Expand
121+
prk := hpkeLabeledExtract(suiteID, "eae_prk", dh)
122+
calculatedSharedSecret, err := hpkeLabeledExpand(prk, suiteID, "shared_secret", kemContext, 32)
123+
if err != nil {
124+
t.Fatalf("failed to expand shared secret: %v", err)
125+
}
126+
127+
if !bytes.Equal(calculatedSharedSecret, expectedSharedSecret) {
128+
t.Errorf("shared secret mismatch.\nExpected: %s\nGot: %x", sharedSecretHex, calculatedSharedSecret)
129+
}
130+
}

0 commit comments

Comments
 (0)