Skip to content

Commit

Permalink
Add KDF in counter mode ACVP Testing (#1810)
Browse files Browse the repository at this point in the history
  • Loading branch information
skmcgrail authored Aug 28, 2024
1 parent f0002cb commit 90dd0b7
Show file tree
Hide file tree
Showing 3 changed files with 191 additions and 52 deletions.
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 counter location: %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("counter location %q not supported", group.CounterLocation)
}

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("counter location %q not supported", group.CounterLocation)
}

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>},
};

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

0 comments on commit 90dd0b7

Please sign in to comment.