Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
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
243 changes: 243 additions & 0 deletions util/fipstools/acvp/acvptool/subprocess/kasEcc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,243 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 OR ISC

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 hexEncodedByteString `json:"ephemeralPublicServerX"`
EphemeralYHex hexEncodedByteString `json:"ephemeralPublicServerY"`
EphemeralPrivateKeyHex hexEncodedByteString `json:"ephemeralPrivateIut"`
EphemeralPublicIutXHex hexEncodedByteString `json:"ephemeralPublicIutX"`
EphemeralPublicIutYHex hexEncodedByteString `json:"ephemeralPublicIutY"`
KdfParameter kdfParameter `json:"kdfParameter"`
ExpectedOutput hexEncodedByteString `json:"dkm"`

StaticXHex hexEncodedByteString `json:"staticPublicServerX"`
StaticYHex hexEncodedByteString `json:"staticPublicServerY"`
StaticPrivateKeyHex hexEncodedByteString `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 {
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":
default:
return nil, fmt.Errorf("unknown curve %q", group.Curve)
}
Comment thread
nhatnghiho marked this conversation as resolved.

switch group.Role {
case "initiator", "responder":
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":
case "onePassDh":
useOnePassNameFields = true
default:
return nil, fmt.Errorf("unknown scheme %q", group.Scheme)
}

switch group.KdfConfiguration.KdfType {
case "oneStep":
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 {
var peerX, peerY, privateKey hexEncodedByteString
if useOnePassNameFields {
if group.Role == "initiator" {
peerX, peerY, privateKey = test.StaticXHex, test.StaticYHex, test.StaticPrivateKeyHex
} else {
peerX, peerY, privateKey = test.EphemeralXHex, test.EphemeralYHex, test.StaticPrivateKeyHex
}
} else {
peerX, peerY, privateKey = test.EphemeralXHex, test.EphemeralYHex, test.EphemeralPrivateKeyHex
}

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

if (len(privateKey) != 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 keyreturn nil, err
fixedInfo = append(fixedInfo, test.EphemeralPublicIutXHex...)
fixedInfo = append(fixedInfo, test.EphemeralPublicIutYHex...)
}
}
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
fixedInfo = append(fixedInfo, test.EphemeralPublicIutXHex...)
fixedInfo = append(fixedInfo, test.EphemeralPublicIutYHex...)
}
}
}

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

ok := bytes.Equal(result[2], test.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.
95 changes: 95 additions & 0 deletions util/fipstools/acvp/modulewrapper/modulewrapper.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1335,6 +1335,31 @@ static bool GetConfig(const Span<const uint8_t> args[],
"P-521"
]
},
{
"algorithm": "KAS-ECC",
"revision": "Sp800-56Ar3",
"function": ["fullVal"],
"iutId": ["initiator", "responder"],
"scheme": {
"ephemeralUnified": {
"kasRole": ["initiator", "responder"],
"kdfMethods": {
"oneStepKdf": {
"auxFunctions": [
{"auxFunctionName": "SHA2-224"},
{"auxFunctionName": "SHA2-256"},
{"auxFunctionName": "SHA2-384"},
{"auxFunctionName": "SHA2-512"}
],
"fixedInfoPattern": "algorithmId",
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.

This doesn't match the fixedInfoPattern you are constructing in Go?

"encoding": ["concatenation"]
}
}
}
},
"domainParameterGenerationMethods": ["P-224", "P-256", "P-384", "P-521"],
"l": 2048
},
{
"algorithm": "KAS-FFC-SSC",
"revision": "Sp800-56Ar3",
Expand Down Expand Up @@ -2869,6 +2894,72 @@ static bool ECDH(const Span<const uint8_t> args[], ReplyCallback write_reply) {
return write_reply({BIGNUMBytes(x.get()), BIGNUMBytes(y.get()), output});
}

template <int Nid, const EVP_MD *(MDFunc)()>
static bool ECDH_SSKDF(const Span<const uint8_t> args[],
ReplyCallback write_reply) {
bssl::UniquePtr<BIGNUM> their_x(BytesToBIGNUM(args[0]));
bssl::UniquePtr<BIGNUM> their_y(BytesToBIGNUM(args[1]));
const Span<const uint8_t> private_key = args[2];
const Span<const uint8_t> info = args[3];
const Span<const uint8_t> out_len_bytes = args[4];

uint32_t out_len;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

warning: variable 'out_len' is not initialized [cppcoreguidelines-init-variables]

Suggested change
uint32_t out_len;
uint32_t out_len = 0;

memcpy(&out_len, out_len_bytes.data(), sizeof(out_len));

// Step 1: ECDH - compute shared secret Z
bssl::UniquePtr<EC_KEY> ec_key(EC_KEY_new_by_curve_name(Nid));
bssl::UniquePtr<BN_CTX> ctx(BN_CTX_new());
const EC_GROUP *group = EC_KEY_get0_group(ec_key.get());

bssl::UniquePtr<EC_POINT> their_point(EC_POINT_new(group));
if (!EC_POINT_set_affine_coordinates_GFp(
group, their_point.get(), their_x.get(), their_y.get(), ctx.get())) {
LOG_ERROR("Invalid peer point for KAS-ECC.\n");
return false;
}

if (!private_key.empty()) {
bssl::UniquePtr<BIGNUM> our_k(BytesToBIGNUM(private_key));
if (!EC_KEY_set_private_key(ec_key.get(), our_k.get())) {
return false;
}
bssl::UniquePtr<EC_POINT> our_pub(EC_POINT_new(group));
if (!EC_POINT_mul(group, our_pub.get(), our_k.get(), nullptr, nullptr,
ctx.get()) ||
!EC_KEY_set_public_key(ec_key.get(), our_pub.get())) {
return false;
}
} else if (!EC_KEY_generate_key_fips(ec_key.get())) {
return false;
}

std::vector<uint8_t> z(EC_MAX_BYTES + 1);
int z_len = ECDH_compute_key(z.data(), z.size(), their_point.get(),
ec_key.get(), nullptr);
if (z_len < 0 || static_cast<size_t>(z_len) == z.size()) {
return false;
}
z.resize(z_len);

// Step 2: One-Step KDF (SSKDF with digest)
std::vector<uint8_t> output(out_len);
if (!::SSKDF_digest(output.data(), out_len, MDFunc(), z.data(), z.size(),
info.data(), info.size())) {
return false;
}

// Return public key and DKM
const EC_POINT *pub = EC_KEY_get0_public_key(ec_key.get());
bssl::UniquePtr<BIGNUM> x(BN_new());
bssl::UniquePtr<BIGNUM> y(BN_new());
if (!EC_POINT_get_affine_coordinates_GFp(group, pub, x.get(), y.get(),
ctx.get())) {
return false;
}

return write_reply({BIGNUMBytes(x.get()), BIGNUMBytes(y.get()), output});
}

static bool FFDH(const Span<const uint8_t> args[], ReplyCallback write_reply) {
bssl::UniquePtr<BIGNUM> p(BytesToBIGNUM(args[0]));
bssl::UniquePtr<BIGNUM> q(BytesToBIGNUM(args[1]));
Expand Down Expand Up @@ -3647,6 +3738,10 @@ static struct {
{"ECDH/P-256", 3, ECDH<NID_X9_62_prime256v1>},
{"ECDH/P-384", 3, ECDH<NID_secp384r1>},
{"ECDH/P-521", 3, ECDH<NID_secp521r1>},
{"KAS-ECC/OneStep/P-224/SHA2-384", 6,
ECDH_SSKDF<NID_secp224r1, EVP_sha384>},
{"KAS-ECC/OneStep/P-384/SHA2-384", 6,
ECDH_SSKDF<NID_secp384r1, EVP_sha384>},
{"FFDH", 6, FFDH},
{"PBKDF", 5, PBKDF},
{"KDA/HKDF/SHA-1", 4, HKDF<EVP_sha1>},
Expand Down
Loading