Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add KDF in counter mode ACVP Testing #1810

Merged
merged 2 commits into from
Aug 28, 2024
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
198 changes: 146 additions & 52 deletions util/fipstools/acvp/acvptool/subprocess/kdf.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,48 @@ package subprocess

import (
"crypto/rand"
"encoding/hex"
"encoding/json"
"fmt"
"strings"
)

type CounterLocation string

func (c *CounterLocation) UnmarshalJSON(v []byte) error {
var parsed string
if err := json.Unmarshal(v, &parsed); err != nil {
return err
}

var val CounterLocation

// Normalize to constants for easy comparisons later
switch {
case strings.EqualFold(parsed, string(NoneCounterLocation)):
val = NoneCounterLocation
case strings.EqualFold(parsed, string(AfterFixedDataCounterLocation)):
val = AfterFixedDataCounterLocation
case strings.EqualFold(parsed, string(BeforeFixedDataCounterLocation)):
val = BeforeFixedDataCounterLocation
case strings.EqualFold(parsed, string(MiddleFixedDataCounterLocation)):
val = MiddleFixedDataCounterLocation
case strings.EqualFold(parsed, string(BeforeIterator)):
val = BeforeIterator
default:
return fmt.Errorf("unknown KDF counterLocation: %v", parsed)
}

*c = val

return nil
}

const (
NoneCounterLocation CounterLocation = "none"
AfterFixedDataCounterLocation CounterLocation = "after fixed data"
MiddleFixedDataCounterLocation CounterLocation = "middle fixed data"
BeforeFixedDataCounterLocation CounterLocation = "before fixed data"
BeforeIterator CounterLocation = "before iterator"
)

// The following structures reflect the JSON of ACVP KDF-1.0 tests. See
Expand All @@ -32,17 +71,17 @@ type kdfTestGroup struct {
ID uint64 `json:"tgId"`
// KDFMode can take the values "counter", "feedback", or
// "double pipeline iteration".
KDFMode string `json:"kdfMode"`
MACMode string `json:"macMode"`
CounterLocation string `json:"counterLocation"`
OutputBits uint32 `json:"keyOutLength"`
CounterBits uint32 `json:"counterLength"`
ZeroIV bool `json:"zeroLengthIv"`
KDFMode string `json:"kdfMode"`
MACMode string `json:"macMode"`
CounterLocation CounterLocation `json:"counterLocation"`
OutputBits uint32 `json:"keyOutLength"`
CounterBits uint32 `json:"counterLength"`
ZeroIV bool `json:"zeroLengthIv"`

Tests []struct {
ID uint64 `json:"tcId"`
KeyHex string `json:"keyIn"`
IvHex string `json:"iv"`
ID uint64 `json:"tcId"`
Key hexEncodedByteString `json:"keyIn"`
Iv hexEncodedByteString `json:"iv"`
}
}

Expand All @@ -52,9 +91,9 @@ type kdfTestGroupResponse struct {
}

type kdfTestResponse struct {
ID uint64 `json:"tcId"`
FixedData string `json:"fixedData"`
KeyOut string `json:"keyOut"`
ID uint64 `json:"tcId"`
FixedData hexEncodedByteString `json:"fixedData"`
KeyOut hexEncodedByteString `json:"keyOut"`
}

type kdfPrimitive struct{}
Expand All @@ -67,59 +106,114 @@ func (k *kdfPrimitive) Process(vectorSet []byte, m Transactable) (interface{}, e

var respGroups []kdfTestGroupResponse
for _, group := range parsed.Groups {
group := group
groupResp := kdfTestGroupResponse{ID: group.ID}

if group.OutputBits%8 != 0 {
return nil, fmt.Errorf("%d bit key in test group %d: fractional bytes not supported", group.OutputBits, group.ID)
}

if group.KDFMode != "feedback" {
// We only support KBKDF in feedback mode
var groupProcessor func(kdfTestGroup, Transactable) (kdfTestGroupResponse, error)

// We only support KBKDF in feedback or counter modes
switch {
case strings.EqualFold(group.KDFMode, "feedback"):
groupProcessor = processFeedbackMode
case strings.EqualFold(group.KDFMode, "counter"):
groupProcessor = processCounterMode
default:
return nil, fmt.Errorf("KDF mode %q not supported", group.KDFMode)
}

if group.CounterLocation != "after fixed data" {
// We only support the counter location being after fixed data
return nil, fmt.Errorf("label location %q not supported", group.CounterLocation)
groupResp, err := groupProcessor(group, m)
if err != nil {
return nil, err
}
respGroups = append(respGroups, groupResp)
}

return respGroups, nil
}

func processFeedbackMode(group kdfTestGroup, m Transactable) (kdfTestGroupResponse, error) {
groupResp := kdfTestGroupResponse{ID: group.ID}

if group.OutputBits%8 != 0 {
return kdfTestGroupResponse{}, fmt.Errorf("%d bit key in test group %d: fractional bytes not supported", group.OutputBits, group.ID)
}

if group.CounterBits != 8 {
// We only support counter lengths of 8
return nil, fmt.Errorf("counter length %q not supported", group.CounterBits)
if group.CounterLocation != AfterFixedDataCounterLocation {
// We only support the counter location being after fixed data
return kdfTestGroupResponse{}, fmt.Errorf("label location %q not supported", group.CounterLocation)
skmcgrail marked this conversation as resolved.
Show resolved Hide resolved
}

if group.CounterBits != 8 {
// We only support counter lengths of 8
return kdfTestGroupResponse{}, fmt.Errorf("counter length %v not supported", group.CounterBits)
}

outputBytes := uint32le(group.OutputBits / 8)

// Fixed data variable is determined by the IUT according to the NIST specifications
// We send it as part of the response so that NIST can verify whether it is correct
fixedData := make([]byte, 4)
rand.Read(fixedData)

for _, test := range group.Tests {
test := test
testResp := kdfTestResponse{ID: test.ID}

// Make the call to the crypto module.
resp, err := m.Transact("KDF/Feedback/"+group.MACMode, 1, outputBytes, test.Key, fixedData)
if err != nil {
return kdfTestGroupResponse{}, fmt.Errorf("wrapper KDF operation failed: %s", err)
}

outputBytes := uint32le(group.OutputBits / 8)
// Parse results.
testResp.ID = test.ID
testResp.KeyOut = resp[0]
testResp.FixedData = fixedData

groupResp.Tests = append(groupResp.Tests, testResp)
}

return groupResp, nil
}

func processCounterMode(group kdfTestGroup, m Transactable) (kdfTestGroupResponse, error) {
groupResp := kdfTestGroupResponse{ID: group.ID}

// Fixed data variable is determined by the IUT according to the NIST specifications
// We send it as part of the response so that NIST can verify whether it is correct
fixedData := make([]byte, 4)
rand.Read(fixedData)
if group.OutputBits%8 != 0 {
return kdfTestGroupResponse{}, fmt.Errorf("%d bit key in test group %d: fractional bytes not supported", group.OutputBits, group.ID)
}

if group.CounterLocation != BeforeFixedDataCounterLocation {
// We only support the counter location being after fixed data
return kdfTestGroupResponse{}, fmt.Errorf("label location %q not supported", group.CounterLocation)
skmcgrail marked this conversation as resolved.
Show resolved Hide resolved
}

for _, test := range group.Tests {
test := test
testResp := kdfTestResponse{ID: test.ID}
if group.CounterBits != 32 {
// We only support counter lengths of 32
return kdfTestGroupResponse{}, fmt.Errorf("counter length %v not supported", group.CounterBits)
}

key, err := hex.DecodeString(test.KeyHex)
if err != nil {
return nil, fmt.Errorf("failed to decode Key in test case %d/%d: %v", group.ID, test.ID, err)
}
outputBytes := uint32le(group.OutputBits / 8)

// Make the call to the crypto module.
resp, err := m.Transact("KDF/Feedback/"+group.MACMode, 1, outputBytes, key, fixedData)
if err != nil {
return nil, fmt.Errorf("wrapper KDF operation failed: %s", err)
}
// Fixed data variable is determined by the IUT according to the NIST specifications
// We send it as part of the response so that NIST can verify whether it is correct
fixedData := make([]byte, 4)
rand.Read(fixedData)

// Parse results.
testResp.ID = test.ID
testResp.KeyOut = hex.EncodeToString(resp[0])
testResp.FixedData = hex.EncodeToString(fixedData)
for _, test := range group.Tests {
test := test
testResp := kdfTestResponse{ID: test.ID}

groupResp.Tests = append(groupResp.Tests, testResp)
// Make the call to the crypto module.
resp, err := m.Transact("KDF/Counter/"+group.MACMode, 1, outputBytes, test.Key, fixedData)
if err != nil {
return kdfTestGroupResponse{}, fmt.Errorf("wrapper KDF operation failed: %s", err)
}
respGroups = append(respGroups, groupResp)

// Parse results.
testResp.ID = test.ID
testResp.KeyOut = resp[0]
testResp.FixedData = fixedData

groupResp.Tests = append(groupResp.Tests, testResp)
}

return respGroups, nil
return groupResp, nil
}
Binary file modified util/fipstools/acvp/acvptool/test/vectors/KDF.bz2
Binary file not shown.
45 changes: 45 additions & 0 deletions util/fipstools/acvp/modulewrapper/modulewrapper.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1010,6 +1010,24 @@ static bool GetConfig(const Span<const uint8_t> args[],
"counterLength": [8],
"supportsEmptyIv": true,
"requiresEmptyIv": true
},{
"kdfMode": "counter",
"macMode": [
"HMAC-SHA-1",
"HMAC-SHA2-224",
"HMAC-SHA2-256",
"HMAC-SHA2-384",
"HMAC-SHA2-512",
"HMAC-SHA2-512/224",
"HMAC-SHA2-512/256"
],
"supportedLengths": [{
"min": 8,
"max": 4096,
"increment": 8
}],
"fixedDataOrder": ["before fixed data"],
"counterLength": [32]
}]
},
{
Expand Down Expand Up @@ -2658,6 +2676,26 @@ static bool HKDF_expand(const Span<const uint8_t> args[],
return write_reply({Span<const uint8_t>(out)});
}

template <const EVP_MD *(MDFunc)()>
static bool KBKDF_CTR_HMAC(const Span<const uint8_t> args[],
ReplyCallback write_reply) {
const Span<const uint8_t> out_bytes = args[0];
const Span<const uint8_t> key_in = args[1];
const Span<const uint8_t> fixed_data = args[2];
const EVP_MD *md = MDFunc();

unsigned int out_bytes_uint;
memcpy(&out_bytes_uint, out_bytes.data(), sizeof(out_bytes_uint));

std::vector<uint8_t> out(out_bytes_uint);
if (!::KBKDF_ctr_hmac(out.data(), out_bytes_uint, md, key_in.data(),
key_in.size(), fixed_data.data(), fixed_data.size())) {
return false;
}

return write_reply({Span<const uint8_t>(out)});
}

static struct {
char name[kMaxNameLength + 1];
uint8_t num_expected_args;
Expand Down Expand Up @@ -2865,6 +2903,13 @@ static struct {
{"KDF/Feedback/HMAC-SHA2-512", 3, HKDF_expand<EVP_sha512>},
{"KDF/Feedback/HMAC-SHA2-512/224", 3, HKDF_expand<EVP_sha512_224>},
{"KDF/Feedback/HMAC-SHA2-512/256", 3, HKDF_expand<EVP_sha512_256>},
{"KDF/Counter/HMAC-SHA-1", 3, KBKDF_CTR_HMAC<EVP_sha1>},
{"KDF/Counter/HMAC-SHA2-224", 3, KBKDF_CTR_HMAC<EVP_sha224>},
{"KDF/Counter/HMAC-SHA2-256", 3, KBKDF_CTR_HMAC<EVP_sha256>},
{"KDF/Counter/HMAC-SHA2-384", 3, KBKDF_CTR_HMAC<EVP_sha384>},
{"KDF/Counter/HMAC-SHA2-512", 3, KBKDF_CTR_HMAC<EVP_sha512>},
{"KDF/Counter/HMAC-SHA2-512/224", 3, KBKDF_CTR_HMAC<EVP_sha512_224>},
{"KDF/Counter/HMAC-SHA2-512/256", 3, KBKDF_CTR_HMAC<EVP_sha512_256>},
Copy link
Contributor

Choose a reason for hiding this comment

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

is Counter KDF specified for SHA3 or HMAC-SHA3?

Copy link
Member Author

Choose a reason for hiding this comment

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

Counter KDF specification technically support HMAC-SHA3, but AWS-LC's HMAC implementation does not support its usage currently. There is a separate internal discussions with some CryptoBR folk about whether this is something that should be added, or whether something like KMAC should be used instead.

};

Handler FindHandler(Span<const Span<const uint8_t>> args) {
Expand Down
Loading