Skip to content

Commit 532ff23

Browse files
authored
Add ACVP support for multi-expansion HKDF (#3352)
### Issues: Resolves P465812657 ### Description of changes: Add ACVP support for multi-expansion HKDF ### Call-outs: I renamed `TLS13_HKDFExtract` to a generic `HKDFExtract` as it's reused between TLS 1.3 KDF and KDA/HKDF. Also renamed a few other commands/operations to make the naming convention more consistent in `modulewrapper.cc` ### Testing: Run the tool against new test vectors By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license and the ISC license.
1 parent 3ab37a8 commit 532ff23

6 files changed

Lines changed: 246 additions & 89 deletions

File tree

util/fipstools/acvp/acvptool/subprocess/hkdf.go

Lines changed: 190 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -21,18 +21,22 @@ type hkdfTestVectorSet struct {
2121
}
2222

2323
type hkdfTestGroup struct {
24-
ID uint64 `json:"tgId"`
25-
Type string `json:"testType"` // AFT or VAL
26-
Config hkdfConfiguration `json:"kdfConfiguration"`
27-
Tests []hkdfTest `json:"tests"`
24+
ID uint64 `json:"tgId"`
25+
Type string `json:"testType"` // AFT or VAL
26+
Config hkdfConfiguration `json:"kdfConfiguration"`
27+
MultiExpansion bool `json:"multiExpansion"`
28+
MultiConfig hkdfMultiExpansionConfiguration `json:"kdfMultiExpansionConfiguration"`
29+
Tests []hkdfTest `json:"tests"`
2830
}
2931

3032
type hkdfTest struct {
31-
ID uint64 `json:"tcId"`
32-
Params hkdfParameters `json:"kdfParameter"`
33-
PartyU hkdfPartyInfo `json:"fixedInfoPartyU"`
34-
PartyV hkdfPartyInfo `json:"fixedInfoPartyV"`
35-
ExpectedHex string `json:"dkm"`
33+
ID uint64 `json:"tcId"`
34+
Params hkdfParameters `json:"kdfParameter"`
35+
MultiParams hkdfMultiExpansionParameters `json:"kdfMultiExpansionParameter"`
36+
PartyU hkdfPartyInfo `json:"fixedInfoPartyU"`
37+
PartyV hkdfPartyInfo `json:"fixedInfoPartyV"`
38+
ExpectedHex string `json:"dkm"`
39+
ExpectedDkmsHex []string `json:"dkms"`
3640
}
3741

3842
type hkdfConfiguration struct {
@@ -57,6 +61,35 @@ func (c *hkdfConfiguration) extract() (outBytes uint32, hashName string, err err
5761
return c.OutputBits / 8, c.HmacAlg, nil
5862
}
5963

64+
type hkdfMultiExpansionConfiguration struct {
65+
Type string `json:"kdfType"`
66+
SaltMethod string `json:"saltMethod"`
67+
SaltLength uint64 `json:"saltLen"`
68+
HmacAlg string `json:"hmacAlg"`
69+
OutputBits uint32 `json:"l"`
70+
}
71+
72+
func (c *hkdfMultiExpansionConfiguration) extract() (hashName string, err error) {
73+
if c.Type != "hkdf" ||
74+
(c.SaltMethod != "default" && c.SaltMethod != "random") {
75+
return "", fmt.Errorf("Test group not configured for KDA HKDF multi-expansion")
76+
}
77+
return c.HmacAlg, nil
78+
}
79+
80+
type hkdfMultiExpansionParameters struct {
81+
KdfType string `json:"kdfType"`
82+
KeyHex string `json:"z"`
83+
HmacAlg string `json:"hmacAlg"`
84+
SaltHex string `json:"salt"`
85+
IterationParameters []hkdfMultiExpansionIteration `json:"iterationParameters"`
86+
}
87+
88+
type hkdfMultiExpansionIteration struct {
89+
OutputBits uint32 `json:"l"`
90+
FixedInfoHex string `json:"fixedInfo"`
91+
}
92+
6093
type hkdfParameters struct {
6194
KdfType string `json:"kdfType"`
6295
SaltHex string `json:"salt"`
@@ -117,9 +150,10 @@ type hkdfTestGroupResponse struct {
117150
}
118151

119152
type hkdfTestResponse struct {
120-
ID uint64 `json:"tcId"`
121-
KeyOut string `json:"dkm,omitempty"`
122-
Passed *bool `json:"testPassed,omitempty"`
153+
ID uint64 `json:"tcId"`
154+
KeyOut string `json:"dkm,omitempty"`
155+
KeyOuts []string `json:"dkms,omitempty"`
156+
Passed *bool `json:"testPassed,omitempty"`
123157
}
124158

125159
type kdaHkdfMode struct{}
@@ -135,70 +169,166 @@ func (k *kdaHkdfMode) ProcessKDA(vectorSet []byte, m Transactable) (interface{},
135169
group := group
136170
groupResp := hkdfTestGroupResponse{ID: group.ID}
137171

138-
// determine the test type
139-
var isValidationTest bool
140-
switch group.Type {
141-
case "VAL":
142-
isValidationTest = true
143-
case "AFT":
144-
isValidationTest = false
145-
default:
146-
return nil, fmt.Errorf("unknown test type %q", group.Type)
172+
if group.MultiExpansion {
173+
if err := processMultiExpansionGroup(&group, &groupResp, m); err != nil {
174+
return nil, err
175+
}
176+
} else {
177+
if err := processSingleExpansionGroup(&group, &groupResp, m); err != nil {
178+
return nil, err
179+
}
147180
}
148181

149-
// get the number of bytes to output and the hmac alg we're using
150-
outBytes, hashName, err := group.Config.extract()
182+
respGroups = append(respGroups, groupResp)
183+
}
184+
185+
return respGroups, nil
186+
}
187+
188+
func processSingleExpansionGroup(group *hkdfTestGroup, groupResp *hkdfTestGroupResponse, m Transactable) error {
189+
// determine the test type
190+
var isValidationTest bool
191+
switch group.Type {
192+
case "VAL":
193+
isValidationTest = true
194+
case "AFT":
195+
isValidationTest = false
196+
default:
197+
return fmt.Errorf("unknown test type %q", group.Type)
198+
}
199+
200+
// get the number of bytes to output and the hmac alg we're using
201+
outBytes, hashName, err := group.Config.extract()
202+
if err != nil {
203+
return err
204+
}
205+
206+
for _, test := range group.Tests {
207+
test := test
208+
testResp := hkdfTestResponse{ID: test.ID}
209+
210+
key, salt, err := test.Params.extract()
151211
if err != nil {
152-
return nil, err
212+
return err
153213
}
214+
uData, err := test.PartyU.data()
215+
if err != nil {
216+
return err
217+
}
218+
vData, err := test.PartyV.data()
219+
if err != nil {
220+
return err
221+
}
222+
lenData := test.Params.data()
154223

155-
for _, test := range group.Tests {
156-
test := test
157-
testResp := hkdfTestResponse{ID: test.ID}
158-
159-
key, salt, err := test.Params.extract()
160-
if err != nil {
161-
return nil, err
162-
}
163-
uData, err := test.PartyU.data()
164-
if err != nil {
165-
return nil, err
166-
}
167-
vData, err := test.PartyV.data()
224+
var expected []byte
225+
if isValidationTest {
226+
expected, err = hex.DecodeString(test.ExpectedHex)
168227
if err != nil {
169-
return nil, err
228+
return err
170229
}
171-
lenData := test.Params.data()
230+
}
172231

173-
var expected []byte
174-
if isValidationTest {
175-
expected, err = hex.DecodeString(test.ExpectedHex)
176-
if err != nil {
177-
return nil, err
178-
}
179-
}
232+
info := make([]byte, 0, len(uData)+len(vData)+len(lenData))
233+
info = append(info, uData...)
234+
info = append(info, vData...)
235+
info = append(info, lenData...)
236+
237+
resp, err := m.Transact("KDA/HKDF/"+hashName, 1, key, salt, info, uint32le(outBytes))
238+
if err != nil {
239+
return fmt.Errorf("KDA_HKDF operation failed: %s", err)
240+
}
241+
242+
if isValidationTest {
243+
passed := bytes.Equal(expected, resp[0])
244+
testResp.Passed = &passed
245+
} else {
246+
testResp.KeyOut = hex.EncodeToString(resp[0])
247+
}
248+
249+
groupResp.Tests = append(groupResp.Tests, testResp)
250+
}
251+
252+
return nil
253+
}
254+
255+
func processMultiExpansionGroup(group *hkdfTestGroup, groupResp *hkdfTestGroupResponse, m Transactable) error {
256+
// Multi-expansion supports both AFT and VAL
257+
var isValidationTest bool
258+
switch group.Type {
259+
case "VAL":
260+
isValidationTest = true
261+
case "AFT":
262+
isValidationTest = false
263+
default:
264+
return fmt.Errorf("unsupported test type %q for multi-expansion", group.Type)
265+
}
266+
267+
hashName, err := group.MultiConfig.extract()
268+
if err != nil {
269+
return err
270+
}
180271

181-
info := make([]byte, 0, len(uData)+len(vData)+len(lenData))
182-
info = append(info, uData...)
183-
info = append(info, vData...)
184-
info = append(info, lenData...)
272+
for _, test := range group.Tests {
273+
test := test
274+
testResp := hkdfTestResponse{ID: test.ID}
185275

186-
resp, err := m.Transact("KDA/HKDF/"+hashName, 1, key, salt, info, uint32le(outBytes))
276+
// Decode z (shared secret) and salt
277+
z, err := hex.DecodeString(test.MultiParams.KeyHex)
278+
if err != nil {
279+
return fmt.Errorf("tcId %d: failed to decode z: %s", test.ID, err)
280+
}
281+
salt, err := hex.DecodeString(test.MultiParams.SaltHex)
282+
if err != nil {
283+
return fmt.Errorf("tcId %d: failed to decode salt: %s", test.ID, err)
284+
}
285+
286+
// Step 1: Extract — PRK = HKDF-Extract(salt, z)
287+
extractResp, err := m.Transact("HKDF/"+hashName+"/extract", 1, z, salt)
288+
if err != nil {
289+
return fmt.Errorf("tcId %d: HKDF extract failed: %s", test.ID, err)
290+
}
291+
prk := extractResp[0]
292+
293+
// Step 2: Expand — for each iteration, derive a key
294+
dkms := make([]string, 0, len(test.MultiParams.IterationParameters))
295+
for i, iter := range test.MultiParams.IterationParameters {
296+
if iter.OutputBits%8 != 0 {
297+
return fmt.Errorf("tcId %d, iteration %d: output bits %d not a multiple of 8", test.ID, i, iter.OutputBits)
298+
}
299+
outBytes := iter.OutputBits / 8
300+
301+
fixedInfo, err := hex.DecodeString(iter.FixedInfoHex)
187302
if err != nil {
188-
return nil, fmt.Errorf("KDA_HKDF operation failed: %s", err)
303+
return fmt.Errorf("tcId %d, iteration %d: failed to decode fixedInfo: %s", test.ID, i, err)
189304
}
190305

191-
if isValidationTest {
192-
passed := bytes.Equal(expected, resp[0])
193-
testResp.Passed = &passed
194-
} else {
195-
testResp.KeyOut = hex.EncodeToString(resp[0])
306+
expandResp, err := m.Transact("HKDF/"+hashName+"/expand", 1, uint32le(outBytes), prk, fixedInfo)
307+
if err != nil {
308+
return fmt.Errorf("tcId %d, iteration %d: HKDF expand failed: %s", test.ID, i, err)
196309
}
197310

198-
groupResp.Tests = append(groupResp.Tests, testResp)
311+
dkms = append(dkms, strings.ToUpper(hex.EncodeToString(expandResp[0])))
199312
}
200-
respGroups = append(respGroups, groupResp)
313+
314+
if isValidationTest {
315+
// Compare computed dkms against expected
316+
passed := len(dkms) == len(test.ExpectedDkmsHex)
317+
if passed {
318+
for i := range dkms {
319+
if !strings.EqualFold(dkms[i], test.ExpectedDkmsHex[i]) {
320+
passed = false
321+
break
322+
}
323+
}
324+
}
325+
testResp.Passed = &passed
326+
} else {
327+
testResp.KeyOuts = dkms
328+
}
329+
330+
groupResp.Tests = append(groupResp.Tests, testResp)
201331
}
202332

203-
return respGroups, nil
333+
return nil
204334
}

util/fipstools/acvp/acvptool/subprocess/tls13.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -124,9 +124,9 @@ func (k *tls13) Process(vectorSet []byte, m Transactable) (interface{}, error) {
124124
}
125125

126126
zeros := make([]byte, hashLen)
127-
earlySecret, err := m.Transact("HKDFExtract/"+group.HashFunc, 1, psk, zeros)
127+
earlySecret, err := m.Transact("HKDF/"+group.HashFunc+"/extract", 1, psk, zeros)
128128
if err != nil {
129-
return nil, fmt.Errorf("HKDFExtract operation failed: %s", err)
129+
return nil, fmt.Errorf("HKDF extract operation failed: %s", err)
130130
}
131131

132132
hashedToClientHello, err := m.Transact(group.HashFunc, 1, clientHello)
@@ -163,9 +163,9 @@ func (k *tls13) Process(vectorSet []byte, m Transactable) (interface{}, error) {
163163
return nil, fmt.Errorf("HKDFExpandLabel operation failed: %s", err)
164164
}
165165

166-
handshakeSecret, err := m.Transact("HKDFExtract/"+group.HashFunc, 1, dhe, derivedSecret[0])
166+
handshakeSecret, err := m.Transact("HKDF/"+group.HashFunc+"/extract", 1, dhe, derivedSecret[0])
167167
if err != nil {
168-
return nil, fmt.Errorf("HKDFExtract operation failed: %s", err)
168+
return nil, fmt.Errorf("HKDF extract operation failed: %s", err)
169169
}
170170

171171
clientHandshakeTrafficSecret, err := m.Transact("HKDFExpandLabel/"+group.HashFunc, 1, hashLenBytes, handshakeSecret[0], []byte("c hs traffic"), hashedToServerHello[0])
@@ -185,9 +185,9 @@ func (k *tls13) Process(vectorSet []byte, m Transactable) (interface{}, error) {
185185
return nil, fmt.Errorf("HKDFExpandLabel operation failed: %s", err)
186186
}
187187

188-
masterSecret, err := m.Transact("HKDFExtract/"+group.HashFunc, 1, zeros, derivedSecret[0])
188+
masterSecret, err := m.Transact("HKDF/"+group.HashFunc+"/extract", 1, zeros, derivedSecret[0])
189189
if err != nil {
190-
return nil, fmt.Errorf("HKDFExtract operation failed: %s", err)
190+
return nil, fmt.Errorf("HKDF extract operation failed: %s", err)
191191
}
192192

193193
clientAppTrafficSecret, err := m.Transact("HKDFExpandLabel/"+group.HashFunc, 1, hashLenBytes, masterSecret[0], []byte("c ap traffic"), hashedToServerFinished[0])
Binary file not shown.

util/fipstools/acvp/acvptool/test/tests.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
{"Wrapper": "modulewrapper", "In": "vectors/TLS-v1.3.bz2", "Out": "expected/TLS-v1.3.bz2"},
4343
{"Wrapper": "modulewrapper", "In": "vectors/PBKDF.bz2", "Out": "expected/PBKDF.bz2"},
4444
{"Wrapper": "modulewrapper", "In": "vectors/KDA-HKDF.bz2", "Out": "expected/KDA-HKDF.bz2"},
45+
{"Wrapper": "modulewrapper", "In": "vectors/KDA-HKDF-multiExpansion.bz2", "Out": "expected/KDA-HKDF-multiExpansion.bz2"},
4546
{"Wrapper": "modulewrapper", "In": "vectors/KDA-OneStep.bz2", "Out": "expected/KDA-OneStep.bz2"},
4647
{"Wrapper": "modulewrapper", "In": "vectors/ML-KEM.bz2", "Out": "expected/ML-KEM.bz2"},
4748
{"Wrapper": "modulewrapper", "In": "vectors/EDDSA.bz2", "Out": "expected/EDDSA.bz2"},
Binary file not shown.

0 commit comments

Comments
 (0)