Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
298 changes: 298 additions & 0 deletions util/fipstools/acvp/acvptool/subprocess/kasEcc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,298 @@
// Copyright (c) 2020, Google Inc.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
// SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
// OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
Comment thread
nhatnghiho marked this conversation as resolved.
Outdated

package subprocess

import (
"bytes"
"encoding/binary"
"encoding/hex"
"encoding/json"
"fmt"
)

type kasEccVectorSet struct {
Groups []kasEccTestGroup `json:"testGroups"`
}

type kdfConfiguration struct {
KdfType string `json:"kdfType"`
AuxFunction string `json:"auxFunction"`
}

type kdfParameter struct {
KdfType string `json:"kdfType"`
AlgorithmId string `json:"algorithmId"`
}

type kasEccTestGroup struct {
ID uint64 `json:"tgId"`
Type string `json:"testType"`
Curve string `json:"domainParameterGenerationMode"`
Role string `json:"kasRole"`
Scheme string `json:"scheme"`
KdfConfiguration kdfConfiguration `json:"kdfConfiguration"`
L uint32 `json:"l"`
IutId string `json:"iutId"`
ServerId string `json:"serverId"`
Tests []kasEccTest `json:"tests"`
}

type kasEccTest struct {
Comment thread
nhatnghiho marked this conversation as resolved.
ID uint64 `json:"tcId"`

EphemeralXHex string `json:"ephemeralPublicServerX"`
EphemeralYHex string `json:"ephemeralPublicServerY"`
EphemeralPrivateKeyHex string `json:"ephemeralPrivateIut"`
EphemeralPublicIutXHex string `json:"ephemeralPublicIutX"`
EphemeralPublicIutYHex string `json:"ephemeralPublicIutY"`
KdfParameter kdfParameter `json:"kdfParameter"`
Dkm string `json:"dkm"`

StaticXHex string `json:"staticPublicServerX"`
StaticYHex string `json:"staticPublicServerY"`
StaticPrivateKeyHex string `json:"staticPrivateIut"`
}

type kasEccTestGroupResponse struct {
ID uint64 `json:"tgId"`
Tests []kasEccTestResponse `json:"tests"`
}

type kasEccResponse struct {
VsId uint64 `json:"vsId,omitempty"`
Algorithm string `json:"algorithm,omitempty"`
Revision string `json:"revision,omitempty"`
IsSample bool `json:"isSample,omitempty"`
TestGroups []kasEccTestGroupResponse `json:"testGroups"`
}

type kasEccTestResponse struct {
ID uint64 `json:"tcId"`

EphemeralXHex string `json:"ephemeralPublicIutX,omitempty"`
EphemeralYHex string `json:"ephemeralPublicIutY,omitempty"`
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
EphemeralXHex string `json:"ephemeralPublicIutX,omitempty"`
EphemeralYHex string `json:"ephemeralPublicIutY,omitempty"`
EphemeralXHex hexEncodedByteString `json:"ephemeralPublicIutX,omitempty"`
EphemeralYHex hexEncodedByteString `json:"ephemeralPublicIutY,omitempty"`

Then you don't need to call hex.EncodeString(result[0]) etc.


Dkm string `json:"dkm,omitempty"`
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Dkm string `json:"dkm,omitempty"`
Dkm hexEncodedByteString `json:"dkm,omitempty"`

Passed *bool `json:"testPassed,omitempty"`
}

type kasEcc struct{}

func (k *kasEcc) Process(vectorSet []byte, m Transactable) (interface{}, error) {
var parsed kasEccVectorSet
if err := json.Unmarshal(vectorSet, &parsed); err != nil {
return nil, err
}

// See https://pages.nist.gov/ACVP/draft-fussell-acvp-kas-ecc.html#name-test-vectors
var ret []kasEccTestGroupResponse
for _, group := range parsed.Groups {
group := group
Comment thread
nhatnghiho marked this conversation as resolved.
Outdated
response := kasEccTestGroupResponse{
ID: group.ID,
}

var privateKeyGiven bool
switch group.Type {
case "AFT":
privateKeyGiven = false
case "VAL":
privateKeyGiven = true
default:
return nil, fmt.Errorf("unknown test type %q", group.Type)
}

switch group.Curve {
case "P-224", "P-256", "P-384", "P-521":
break
default:
return nil, fmt.Errorf("unknown curve %q", group.Curve)
}
Comment thread
nhatnghiho marked this conversation as resolved.

switch group.Role {
case "initiator", "responder":
break
default:
return nil, fmt.Errorf("unknown role %q", group.Role)
}
Comment thread
nhatnghiho marked this conversation as resolved.

var useOnePassNameFields bool
switch group.Scheme {
case "ephemeralUnified":
break
case "onePassDh":
useOnePassNameFields = true
break
default:
return nil, fmt.Errorf("unknown scheme %q", group.Scheme)
}
Comment thread
nhatnghiho marked this conversation as resolved.
Outdated

switch group.KdfConfiguration.KdfType {
case "oneStep":
break
default:
return nil, fmt.Errorf("unknown KDF type %q", group.KdfConfiguration.KdfType)
}
Comment thread
nhatnghiho marked this conversation as resolved.

method := "KAS-ECC/OneStep/" + group.Curve + "/" + group.KdfConfiguration.AuxFunction

for _, test := range group.Tests {
test := test

Comment thread
nhatnghiho marked this conversation as resolved.
Outdated
var xHex, yHex, privateKeyHex string
if useOnePassNameFields {
if group.Role == "initiator" {
xHex, yHex, privateKeyHex = test.StaticXHex, test.StaticYHex, test.StaticPrivateKeyHex
} else {
xHex, yHex, privateKeyHex = test.EphemeralXHex, test.EphemeralYHex, test.StaticPrivateKeyHex
}
} else {
xHex, yHex, privateKeyHex = test.EphemeralXHex, test.EphemeralYHex, test.EphemeralPrivateKeyHex
}

if len(xHex) == 0 || len(yHex) == 0 {
return nil, fmt.Errorf("%d/%d is missing peer's point", group.ID, test.ID)
}

peerX, err := hex.DecodeString(xHex)
if err != nil {
return nil, err
}

peerY, err := hex.DecodeString(yHex)
if err != nil {
return nil, err
}

if (len(privateKeyHex) != 0) != privateKeyGiven {
return nil, fmt.Errorf("%d/%d incorrect private key presence", group.ID, test.ID)
}

var outLenBytes [4]byte
NativeEndian.PutUint32(outLenBytes[:], group.L/8) // Convert bits to bytes

// Build fixedInfo: algorithmId || l || uPartyInfo || vPartyInfo
// uPartyInfo = uPartyId || ephemeralKey (when available)
// vPartyInfo = vPartyId || ephemeralKey (when available)
algorithmId, err := hex.DecodeString(test.KdfParameter.AlgorithmId)
if err != nil {
return nil, err
}

var lBytes [4]byte
binary.BigEndian.PutUint32(lBytes[:], group.L)

iutId, err := hex.DecodeString(group.IutId)
if err != nil {
return nil, err
}

serverId, err := hex.DecodeString(group.ServerId)
if err != nil {
return nil, err
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm confused these aren't hex values so why are you calling hex.DecodeString?

Copy link
Copy Markdown
Contributor Author

@nhatnghiho nhatnghiho Feb 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As counter-intuitive as it sounds, they are hex values (as noted in the JSON specifications here). I'll change their type from string to hexEncodedByteString


fixedInfo := append([]byte{}, algorithmId...)
fixedInfo = append(fixedInfo, lBytes[:]...)

if group.Role == "initiator" {
// IUT is party U (initiator), Server is party V (responder)
fixedInfo = append(fixedInfo, iutId...)
if !useOnePassNameFields {
// ephemeralUnified: IUT has ephemeral key
if privateKeyGiven {
// VAL mode: use provided public key
iutPubX, err := hex.DecodeString(test.EphemeralPublicIutXHex)
if err != nil {
return nil, err
}
iutPubY, err := hex.DecodeString(test.EphemeralPublicIutYHex)
if err != nil {
return nil, err
}
fixedInfo = append(fixedInfo, iutPubX...)
fixedInfo = append(fixedInfo, iutPubY...)
}
}
fixedInfo = append(fixedInfo, serverId...)
fixedInfo = append(fixedInfo, peerX...)
fixedInfo = append(fixedInfo, peerY...)
} else {
// Server is U party (initiator), IUT is V party (responder)
fixedInfo = append(fixedInfo, serverId...)
fixedInfo = append(fixedInfo, peerX...)
fixedInfo = append(fixedInfo, peerY...)
fixedInfo = append(fixedInfo, iutId...)
if !useOnePassNameFields {
// ephemeralUnified: IUT has ephemeral key
if privateKeyGiven {
// VAL mode: use provided public key
iutPubX, err := hex.DecodeString(test.EphemeralPublicIutXHex)
if err != nil {
return nil, err
}
iutPubY, err := hex.DecodeString(test.EphemeralPublicIutYHex)
if err != nil {
return nil, err
}
fixedInfo = append(fixedInfo, iutPubX...)
fixedInfo = append(fixedInfo, iutPubY...)
}
}
}

if privateKeyGiven {
privateKey, err := hex.DecodeString(privateKeyHex)
if err != nil {
return nil, err
}

expectedOutput, err := hex.DecodeString(test.Dkm)
if err != nil {
return nil, err
}

result, err := m.Transact(method, 3, peerX, peerY, privateKey, fixedInfo, outLenBytes[:], nil)
if err != nil {
return nil, err
}

ok := bytes.Equal(result[2], expectedOutput)
response.Tests = append(response.Tests, kasEccTestResponse{
ID: test.ID,
Passed: &ok,
})
} else {
result, err := m.Transact(method, 3, peerX, peerY, nil, fixedInfo, outLenBytes[:], nil)
if err != nil {
return nil, err
}

testResponse := kasEccTestResponse{
ID: test.ID,
EphemeralXHex: hex.EncodeToString(result[0]),
EphemeralYHex: hex.EncodeToString(result[1]),
Dkm: hex.EncodeToString(result[2]),
}

response.Tests = append(response.Tests, testResponse)
}
}

ret = append(ret, response)
}

return ret, nil
}
1 change: 1 addition & 0 deletions util/fipstools/acvp/acvptool/subprocess/subprocess.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ func NewWithIO(cmd *exec.Cmd, in io.WriteCloser, out io.ReadCloser) *Subprocess
"HMAC-SHA3-512": &hmacPrimitive{"HMAC-SHA3-512", 64},
"ctrDRBG": &drbg{"ctrDRBG", map[string]bool{"AES-128": true, "AES-192": true, "AES-256": true}},
"hmacDRBG": &drbg{"hmacDRBG", map[string]bool{"SHA-1": true, "SHA2-224": true, "SHA2-256": true, "SHA2-384": true, "SHA2-512": true}},
"KAS-ECC": &kasEcc{},
"KDF": &kdfPrimitive{},
"KDA": &kdaPrimitive{
modes: map[string]kdaModePrimitive{
Expand Down
2 changes: 2 additions & 0 deletions util/fipstools/acvp/acvptool/test/tests.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
{"Wrapper": "modulewrapper", "In": "vectors/HMAC-SHA2-512-224.bz2", "Out": "expected/HMAC-SHA2-512-224.bz2"},
{"Wrapper": "modulewrapper", "In": "vectors/HMAC-SHA2-512-256.bz2", "Out": "expected/HMAC-SHA2-512-256.bz2"},
{"Wrapper": "testmodulewrapper", "In": "vectors/hmacDRBG.bz2", "Out": "expected/hmacDRBG.bz2"},
{"Wrapper": "modulewrapper", "In": "vectors/KAS-ECC_3195882.bz2"},
{"Wrapper": "modulewrapper", "In": "vectors/KAS-ECC_3195900.bz2"},
{"Wrapper": "modulewrapper", "In": "vectors/KAS-ECC-SSC.bz2"},
{"Wrapper": "modulewrapper", "In": "vectors/KAS-FFC-SSC.bz2"},
{"Wrapper": "modulewrapper", "In": "vectors/KDF.bz2"},
Expand Down
Binary file not shown.
Binary file not shown.
Loading
Loading