acvp: add HKDF support.

Change-Id: I26251ce85f2cb1b441ae415b1506161a90bd3efa
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/48585
Reviewed-by: David Benjamin <davidben@google.com>
diff --git a/util/fipstools/acvp/ACVP.md b/util/fipstools/acvp/ACVP.md
index 97ec423..c9570c6 100644
--- a/util/fipstools/acvp/ACVP.md
+++ b/util/fipstools/acvp/ACVP.md
@@ -72,6 +72,7 @@
 | ECDSA/sigGen         | Curve name, private key, hash name, message | R, S |
 | ECDSA/sigVer         | Curve name, hash name, message, X, Y, R, S | Single-byte validity flag |
 | FFDH                 | p, q, g, peer public key, local private key (or empty),  local public key (or empty) | Local public key, shared key |
+| HKDF/&lt;HASH&gt;    | key, salt, info, num output bytes | Key |
 | HMAC-SHA-1           | Value to hash, key        | Digest  |
 | HMAC-SHA2-224        | Value to hash, key        | Digest  |
 | HMAC-SHA2-256        | Value to hash, key        | Digest  |
diff --git a/util/fipstools/acvp/acvptool/subprocess/hkdf.go b/util/fipstools/acvp/acvptool/subprocess/hkdf.go
new file mode 100644
index 0000000..21ebca6
--- /dev/null
+++ b/util/fipstools/acvp/acvptool/subprocess/hkdf.go
@@ -0,0 +1,203 @@
+// Copyright (c) 2021, 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.
+
+package subprocess
+
+import (
+	"bytes"
+	"encoding/hex"
+	"encoding/json"
+	"fmt"
+	"strings"
+)
+
+// The following structures reflect the JSON of ACVP KAS KDF tests. See
+// https://pages.nist.gov/ACVP/draft-hammett-acvp-kas-kdf-twostep.html
+
+type hkdfTestVectorSet struct {
+	Groups []hkdfTestGroup `json:"testGroups"`
+}
+
+type hkdfTestGroup struct {
+	ID     uint64            `json:"tgId"`
+	Type   string            `json:"testType"` // AFT or VAL
+	Config hkdfConfiguration `json:"kdfConfiguration"`
+	Tests  []hkdfTest        `json:"tests"`
+}
+
+type hkdfTest struct {
+	ID          uint64         `json:"tcId"`
+	Params      hkdfParameters `json:"kdfParameter"`
+	PartyU      hkdfPartyInfo  `json:"fixedInfoPartyU"`
+	PartyV      hkdfPartyInfo  `json:"fixedInfoPartyV"`
+	ExpectedHex string         `json:"dkm"`
+}
+
+type hkdfConfiguration struct {
+	Type               string `json:"kdfType"`
+	AdditionalNonce    bool   `json:"requiresAdditionalNoncePair"`
+	OutputBits         uint32 `json:"l"`
+	FixedInfoPattern   string `json:"fixedInfoPattern"`
+	FixedInputEncoding string `json:"fixedInfoEncoding"`
+	KDFMode            string `json:"kdfMode"`
+	MACMode            string `json:"macMode"`
+	CounterLocation    string `json:"counterLocation"`
+	CounterBits        uint   `json:"counterLen"`
+}
+
+func (c *hkdfConfiguration) extract() (outBytes uint32, hashName string, err error) {
+	if c.Type != "twoStep" ||
+		c.AdditionalNonce ||
+		c.FixedInfoPattern != "uPartyInfo||vPartyInfo" ||
+		c.FixedInputEncoding != "concatenation" ||
+		c.KDFMode != "feedback" ||
+		c.CounterLocation != "after fixed data" ||
+		c.CounterBits != 8 ||
+		c.OutputBits%8 != 0 {
+		return 0, "", fmt.Errorf("KAS-KDF not configured for HKDF: %#v", c)
+	}
+
+	if !strings.HasPrefix(c.MACMode, "HMAC-") {
+		return 0, "", fmt.Errorf("MAC mode %q does't start with 'HMAC-'", c.MACMode)
+	}
+
+	return c.OutputBits / 8, c.MACMode[5:], nil
+}
+
+type hkdfParameters struct {
+	SaltHex string `json:"salt"`
+	KeyHex  string `json:"z"`
+}
+
+func (p *hkdfParameters) extract() (key, salt []byte, err error) {
+	salt, err = hex.DecodeString(p.SaltHex)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	key, err = hex.DecodeString(p.KeyHex)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	return key, salt, nil
+}
+
+type hkdfPartyInfo struct {
+	IDHex    string `json:"partyId"`
+	ExtraHex string `json:"ephemeralData"`
+}
+
+func (p *hkdfPartyInfo) data() ([]byte, error) {
+	ret, err := hex.DecodeString(p.IDHex)
+	if err != nil {
+		return nil, err
+	}
+
+	if len(p.ExtraHex) > 0 {
+		extra, err := hex.DecodeString(p.ExtraHex)
+		if err != nil {
+			return nil, err
+		}
+		ret = append(ret, extra...)
+	}
+
+	return ret, nil
+}
+
+type hkdfTestGroupResponse struct {
+	ID    uint64             `json:"tgId"`
+	Tests []hkdfTestResponse `json:"tests"`
+}
+
+type hkdfTestResponse struct {
+	ID     uint64 `json:"tcId"`
+	KeyOut string `json:"dkm,omitempty"`
+	Passed *bool  `json:"testPassed,omitempty"`
+}
+
+type hkdf struct{}
+
+func (k *hkdf) Process(vectorSet []byte, m Transactable) (interface{}, error) {
+	var parsed hkdfTestVectorSet
+	if err := json.Unmarshal(vectorSet, &parsed); err != nil {
+		return nil, err
+	}
+
+	var respGroups []hkdfTestGroupResponse
+	for _, group := range parsed.Groups {
+		groupResp := hkdfTestGroupResponse{ID: group.ID}
+
+		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)
+		}
+
+		outBytes, hashName, err := group.Config.extract()
+		if err != nil {
+			return nil, err
+		}
+
+		for _, test := range group.Tests {
+			testResp := hkdfTestResponse{ID: test.ID}
+
+			key, salt, err := test.Params.extract()
+			if err != nil {
+				return nil, err
+			}
+			uData, err := test.PartyU.data()
+			if err != nil {
+				return nil, err
+			}
+			vData, err := test.PartyV.data()
+			if err != nil {
+				return nil, err
+			}
+
+			var expected []byte
+			if isValidationTest {
+				expected, err = hex.DecodeString(test.ExpectedHex)
+				if err != nil {
+					return nil, err
+				}
+			}
+
+			info := make([]byte, 0, len(uData)+len(vData))
+			info = append(info, uData...)
+			info = append(info, vData...)
+
+			resp, err := m.Transact("HKDF/"+hashName, 1, key, salt, info, uint32le(outBytes))
+			if err != nil {
+				return nil, fmt.Errorf("HKDF operation failed: %s", err)
+			}
+
+			if isValidationTest {
+				passed := bytes.Equal(expected, resp[0])
+				testResp.Passed = &passed
+			} else {
+				testResp.KeyOut = hex.EncodeToString(resp[0])
+			}
+
+			groupResp.Tests = append(groupResp.Tests, testResp)
+		}
+		respGroups = append(respGroups, groupResp)
+	}
+
+	return respGroups, nil
+}
diff --git a/util/fipstools/acvp/acvptool/subprocess/subprocess.go b/util/fipstools/acvp/acvptool/subprocess/subprocess.go
index 5256b1e..fe74993 100644
--- a/util/fipstools/acvp/acvptool/subprocess/subprocess.go
+++ b/util/fipstools/acvp/acvptool/subprocess/subprocess.go
@@ -96,6 +96,7 @@
 		"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}},
 		"KDF":            &kdfPrimitive{},
+		"KAS-KDF":        &hkdf{},
 		"CMAC-AES":       &keyedMACPrimitive{"CMAC-AES"},
 		"RSA":            &rsa{},
 		"kdf-components": &tlsKDF{},
diff --git a/util/fipstools/acvp/acvptool/test/expected/KAS-KDF.bz2 b/util/fipstools/acvp/acvptool/test/expected/KAS-KDF.bz2
new file mode 100644
index 0000000..df3edf5
--- /dev/null
+++ b/util/fipstools/acvp/acvptool/test/expected/KAS-KDF.bz2
Binary files differ
diff --git a/util/fipstools/acvp/acvptool/test/tests.json b/util/fipstools/acvp/acvptool/test/tests.json
index 6cf549d..dfbaac5 100644
--- a/util/fipstools/acvp/acvptool/test/tests.json
+++ b/util/fipstools/acvp/acvptool/test/tests.json
@@ -18,6 +18,7 @@
 {"Wrapper": "modulewrapper", "In": "vectors/HMAC-SHA2-256.bz2", "Out": "expected/HMAC-SHA2-256.bz2"},
 {"Wrapper": "modulewrapper", "In": "vectors/HMAC-SHA2-384.bz2", "Out": "expected/HMAC-SHA2-384.bz2"},
 {"Wrapper": "modulewrapper", "In": "vectors/HMAC-SHA2-512.bz2", "Out": "expected/HMAC-SHA2-512.bz2"},
+{"Wrapper": "testmodulewrapper", "In": "vectors/KAS-KDF.bz2", "Out": "expected/KAS-KDF.bz2"},
 {"Wrapper": "modulewrapper", "In": "vectors/KAS-ECC-SSC.bz2"},
 {"Wrapper": "modulewrapper", "In": "vectors/KAS-FFC-SSC.bz2"},
 {"Wrapper": "testmodulewrapper", "In": "vectors/KDF.bz2"},
diff --git a/util/fipstools/acvp/acvptool/test/vectors/KAS-KDF.bz2 b/util/fipstools/acvp/acvptool/test/vectors/KAS-KDF.bz2
new file mode 100644
index 0000000..eadbc7e
--- /dev/null
+++ b/util/fipstools/acvp/acvptool/test/vectors/KAS-KDF.bz2
Binary files differ
diff --git a/util/fipstools/acvp/acvptool/testmodulewrapper/testmodulewrapper.go b/util/fipstools/acvp/acvptool/testmodulewrapper/testmodulewrapper.go
index 00c32ab..e989461 100644
--- a/util/fipstools/acvp/acvptool/testmodulewrapper/testmodulewrapper.go
+++ b/util/fipstools/acvp/acvptool/testmodulewrapper/testmodulewrapper.go
@@ -16,6 +16,7 @@
 	"io"
 	"os"
 
+	"golang.org/x/crypto/hkdf"
 	"golang.org/x/crypto/xts"
 )
 
@@ -24,6 +25,7 @@
 	"KDF-counter":     kdfCounter,
 	"AES-XTS/encrypt": xtsEncrypt,
 	"AES-XTS/decrypt": xtsDecrypt,
+	"HKDF/SHA2-256":   hkdfMAC,
 }
 
 func getConfig(args [][]byte) error {
@@ -69,6 +71,39 @@
 		"tweakMode": [
 		  "number"
 		]
+	}, {
+		"algorithm": "KAS-KDF",
+		"mode": "TwoStep",
+		"revision": "Sp800-56Cr2",
+		"capabilities": [{
+			"macSaltMethods": [
+				"random",
+				"default"
+			],
+			"fixedInfoPattern": "uPartyInfo||vPartyInfo",
+			"encoding": [
+				"concatenation"
+			],
+			"kdfMode": "feedback",
+			"macMode": [
+				"HMAC-SHA2-256"
+			],
+			"supportedLengths": [{
+				"min": 128,
+				"max": 512,
+				"increment": 64
+			}],
+			"fixedDataOrder": [
+				"after fixed data"
+			],
+			"counterLength": [
+				8
+			],
+			"requiresEmptyIv": true,
+			"supportsEmptyIv": true
+		}],
+		"l": 256,
+		"z": [256, 384]
 	}
 ]`))
 }
@@ -188,6 +223,29 @@
 	return reply(msg)
 }
 
+func hkdfMAC(args [][]byte) error {
+	if len(args) != 4 {
+		return fmt.Errorf("HKDF received %d args, wanted 4", len(args))
+	}
+
+	key := args[0]
+	salt := args[1]
+	info := args[2]
+	lengthBytes := args[3]
+
+	if len(lengthBytes) != 4 {
+		return fmt.Errorf("uint32 length was %d bytes long", len(lengthBytes))
+	}
+
+	length := binary.LittleEndian.Uint32(lengthBytes)
+
+	mac := hkdf.New(sha256.New, key, salt, info)
+	ret := make([]byte, length)
+	mac.Read(ret)
+
+	return reply(ret)
+}
+
 const (
 	maxArgs       = 8
 	maxArgLength  = 1 << 20