Skip to content

Commit

Permalink
Add KDA OneStep testing to ACVP (#1792)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
skmcgrail authored Aug 23, 2024
1 parent 51890da commit 398ae9c
Show file tree
Hide file tree
Showing 13 changed files with 622 additions and 187 deletions.
12 changes: 11 additions & 1 deletion util/fipstools/acvp/acvptool/acvp.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ var (
fetchFlag = flag.String("fetch", "", "Name of primitive to fetch vectors for")
expectedOutFlag = flag.String("expected-out", "", "Name of a file to write the expected results to")
wrapperPath = flag.String("wrapper", "../../../../build/util/fipstools/acvp/modulewrapper/modulewrapper", "Path to the wrapper binary")
waitForDebugger = flag.Bool("wait-for-debugger", false, "If true, jobs will run one at a time and pause for a debugger to attach")
)

type Config struct {
Expand Down Expand Up @@ -529,12 +530,21 @@ func uploadFromFile(file string, config *Config, sessionTokensCacheDir string) {
func main() {
flag.Parse()

middle, err := subprocess.New(*wrapperPath)
var args []string
if *waitForDebugger {
args = append(args, "--wait-for-debugger")
}

middle, err := subprocess.New(*wrapperPath, args...)
if err != nil {
log.Fatalf("failed to initialise middle: %s", err)
}
defer middle.Close()

if *waitForDebugger {
log.Printf("attach to process %d to continue", middle.PID())
}

configBytes, err := middle.Config()
if err != nil {
log.Fatalf("failed to get config from middle: %s", err)
Expand Down
44 changes: 22 additions & 22 deletions util/fipstools/acvp/acvptool/subprocess/aead.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,14 +92,14 @@ func (a *aead) Process(vectorSet []byte, m Transactable) (interface{}, error) {
// We automatically assume the IV is given (external) if the IV generation method is not defined.
var external_iv bool = true
if group.IvGen != "" {
switch group.IvGen {
case "external":
external_iv = true
case "internal":
external_iv = false
default:
return nil, fmt.Errorf("test group %d has unknown iv generation method %q", group.ID, group.IvGen)
}
switch group.IvGen {
case "external":
external_iv = true
case "internal":
external_iv = false
default:
return nil, fmt.Errorf("test group %d has unknown iv generation method %q", group.ID, group.IvGen)
}
}

op := a.algo + "/seal"
Expand Down Expand Up @@ -198,21 +198,21 @@ func (a *aead) Process(vectorSet []byte, m Transactable) (interface{}, error) {
testResp.CiphertextHex = &ciphertextHex
} else {
if external_iv {
ciphertext := result[0][:len(result[0])-tagBytes]
ciphertextHex := hex.EncodeToString(ciphertext)
testResp.CiphertextHex = &ciphertextHex
tag := result[0][len(result[0])-tagBytes:]
testResp.TagHex = hex.EncodeToString(tag)
ciphertext := result[0][:len(result[0])-tagBytes]
ciphertextHex := hex.EncodeToString(ciphertext)
testResp.CiphertextHex = &ciphertextHex
tag := result[0][len(result[0])-tagBytes:]
testResp.TagHex = hex.EncodeToString(tag)
} else {
// internal IV length is always 12 bytes long
var ivBytes int = 12
ciphertext := result[0][:len(result[0])-(tagBytes+ivBytes)]
ciphertextHex := hex.EncodeToString(ciphertext)
testResp.CiphertextHex = &ciphertextHex
tag := result[0][len(result[0])-(tagBytes+ivBytes):len(result[0])-ivBytes]
testResp.TagHex = hex.EncodeToString(tag)
iv := result[0][len(result[0])-ivBytes:]
testResp.IVHex = hex.EncodeToString(iv)
// internal IV length is always 12 bytes long
var ivBytes int = 12
ciphertext := result[0][:len(result[0])-(tagBytes+ivBytes)]
ciphertextHex := hex.EncodeToString(ciphertext)
testResp.CiphertextHex = &ciphertextHex
tag := result[0][len(result[0])-(tagBytes+ivBytes) : len(result[0])-ivBytes]
testResp.TagHex = hex.EncodeToString(tag)
iv := result[0][len(result[0])-ivBytes:]
testResp.IVHex = hex.EncodeToString(iv)
}
}
} else {
Expand Down
4 changes: 2 additions & 2 deletions util/fipstools/acvp/acvptool/subprocess/hkdf.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,9 +122,9 @@ type hkdfTestResponse struct {
Passed *bool `json:"testPassed,omitempty"`
}

type hkdf struct{}
type kdaHkdfMode struct{}

func (k *hkdf) Process(vectorSet []byte, m Transactable) (interface{}, error) {
func (k *kdaHkdfMode) ProcessKDA(vectorSet []byte, m Transactable) (interface{}, error) {
var parsed hkdfTestVectorSet
if err := json.Unmarshal(vectorSet, &parsed); err != nil {
return nil, err
Expand Down
33 changes: 33 additions & 0 deletions util/fipstools/acvp/acvptool/subprocess/kda.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 OR ISC
package subprocess

import (
"encoding/json"
"fmt"
)

type kdaPrimitive struct {
modes map[string]kdaModePrimitive
}

type kdaModePrimitive interface {
ProcessKDA(vectorSet []byte, t Transactable) (interface{}, error)
}

func (k *kdaPrimitive) Process(vectorSet []byte, t Transactable) (interface{}, error) {
var partial struct {
Mode string `json:"mode"`
}

if err := json.Unmarshal(vectorSet, &partial); err != nil {
return nil, err
}

prim, ok := k.modes[partial.Mode]
if !ok {
return nil, fmt.Errorf("unsupported KDA mode(%v)", partial.Mode)
}

return prim.ProcessKDA(vectorSet, t)
}
179 changes: 179 additions & 0 deletions util/fipstools/acvp/acvptool/subprocess/kda_onestep.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 OR ISC

package subprocess

import (
"bytes"
"encoding/json"
"fmt"
"strings"
)

// Processes Vectors defined by https://pages.nist.gov/ACVP/draft-hammett-acvp-kas-kdf-onestep.html
type kdaOneStepMode struct{}

func (k *kdaOneStepMode) ProcessKDA(vectorSet []byte, m Transactable) (interface{}, error) {
var parsed kdaOneStepTestVectorSet

if err := json.Unmarshal(vectorSet, &parsed); err != nil {
return nil, err
}

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

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

// get the number of bytes to output and the auxillary function we're using
outBytes, auxFunctionName, auxFuncType, err := group.Configuration.extract()
if err != nil {
return nil, err
}

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

info := test.fixedInfoBytes()

var args [][]byte

args = append(args, test.KDFParameter.Z)
if auxFuncType == hmacAuxFunction {
args = append(args, test.KDFParameter.Salt)
}
args = append(args, info)
args = append(args, uint32le(outBytes))

resp, err := m.Transact("KDA/OneStep/"+auxFunctionName, 1, args...)
if err != nil {
return nil, fmt.Errorf("KDA_OneStep operation failed: %s", err)
}

if isValidationTest {
passed := bytes.Equal(test.DerivedKeyMaterial, resp[0])
testResp.Passed = &passed
} else {
testResp.DerivedKeyMaterial = resp[0]
}

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

}

return respGroups, nil
}

type kdaOneStepTestVectorSet struct {
Groups []kdaOneStepTestGroup `json:"testGroups"`
}

type kdaOneStepTestGroup struct {
ID uint64 `json:"tgId"`
Type string `json:"testType"`
Configuration kdaOneStepConfiguration `json:"kdfConfiguration"`
Tests []kdaOneStepTest `json:"tests"`
}

type kdaOneStepConfiguration struct {
Type string `json:"kdfType"`
SaltMethod string `json:"saltMethod"`
FixedInfoPattern string `json:"fixedInfoPattern"`
FixedInfoEncoding string `json:"fixedInfoEncoding"`
AuxFunction string `json:"auxFunction"`
L uint32 `json:"l"`
}

func (k *kdaOneStepConfiguration) extract() (outLen uint32, auxFunction string, auxFuncType auxFunctionType, err error) {
if k.Type != "oneStep" {
return 0, "", unknownAuxFunction, fmt.Errorf("unexpected kdfType: %v", k.Type)
}

auxFuncType.setFromString(k.AuxFunction)

if auxFuncType == unknownAuxFunction {
return 0, "", unknownAuxFunction, fmt.Errorf("unknown auxillary function: %v", k.AuxFunction)
}

if k.FixedInfoPattern != "uPartyInfo||vPartyInfo" || k.FixedInfoEncoding != "concatenation" {
return 0, "", unknownAuxFunction, fmt.Errorf("unsupported FixedInfo construction, pattern(%v) with encoding(%v)", k.FixedInfoPattern, k.FixedInfoEncoding)
}

return k.L / 8, k.AuxFunction, auxFuncType, nil
}

type kdaOneStepTest struct {
ID uint64 `json:"tcId"`
KDFParameter struct {
Type string `json:"kdfType"`
Salt hexEncodedByteString `json:"salt,omitempty"`
Z hexEncodedByteString `json:"z"`
} `json:"kdfParameter"`
FixedInfoPartyU kdaOneStepFixedInfoParty `json:"fixedInfoPartyU"`
FixedInfoPartyV kdaOneStepFixedInfoParty `json:"fixedInfoPartyV"`
DerivedKeyMaterial hexEncodedByteString `json:"dkm,omitempty"`
}

func (k *kdaOneStepTest) fixedInfoBytes() []byte {
uBytes := k.FixedInfoPartyU.concatenatedBytes()
vBytes := k.FixedInfoPartyV.concatenatedBytes()
v := make([]byte, 0, len(uBytes)+len(vBytes))
v = append(v, uBytes...)
v = append(v, vBytes...)
return v
}

type kdaOneStepFixedInfoParty struct {
PartyID hexEncodedByteString `json:"partyId"`
EphemeralData hexEncodedByteString `json:"ephemeralData"`
}

func (k *kdaOneStepFixedInfoParty) concatenatedBytes() []byte {
v := make([]byte, 0, len(k.PartyID)+len(k.EphemeralData))
v = append(v, k.PartyID...)
v = append(v, k.EphemeralData...)
return v
}

type kdaOneStepTestGroupResponse struct {
ID uint64 `json:"tgId"`
Tests []kdaOneStepTestCaseResponse `json:"tests"`
}

type kdaOneStepTestCaseResponse struct {
ID uint64 `json:"tcId"`
Passed *bool `json:"testPassed,omitempty"`
DerivedKeyMaterial hexEncodedByteString `json:"dkm,omitempty"`
}

type auxFunctionType uint

func (a *auxFunctionType) setFromString(v string) {
if strings.HasPrefix(v, "HMAC-") {
*a = hmacAuxFunction
} else if strings.HasPrefix(v, "SHA-") || strings.HasPrefix(v, "SHA2-") || strings.HasPrefix(v, "SHA3-") {
*a = digestAuxFunction
} else {
*a = unknownAuxFunction
}
}

const (
unknownAuxFunction auxFunctionType = iota
digestAuxFunction
hmacAuxFunction
)
6 changes: 3 additions & 3 deletions util/fipstools/acvp/acvptool/subprocess/rsa.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ func processKeyGen(vectorSet []byte, m Transactable) (interface{}, error) {
var ret []rsaKeyGenTestGroupResponse

for _, group := range parsed.Groups {
group := group
group := group
// We support both GDT and AFT tests, which are formatted the same and expect the same output.
if !(group.Type == "GDT" || group.Type == "AFT") {
return nil, fmt.Errorf("RSA KeyGen test group has type %q, but only GDT and AFT tests are supported", group.Type)
Expand All @@ -137,7 +137,7 @@ func processKeyGen(vectorSet []byte, m Transactable) (interface{}, error) {
}

for _, test := range group.Tests {
test := test
test := test
results, err := m.Transact("RSA/keyGen", 5, uint32le(group.ModulusBits))
if err != nil {
return nil, err
Expand Down Expand Up @@ -289,7 +289,7 @@ func processSigVer(vectorSet []byte, m Transactable) (interface{}, error) {

type rsa struct{}

func (*rsa) Process(vectorSet []byte, m Transactable) (interface{}, error) {
func (r *rsa) Process(vectorSet []byte, m Transactable) (interface{}, error) {
var parsed rsaTestVectorSet
if err := json.Unmarshal(vectorSet, &parsed); err != nil {
return nil, err
Expand Down
Loading

0 comments on commit 398ae9c

Please sign in to comment.