acvp: add SP800-108 KDF support.

Based on a change from Dan Janni.

Change-Id: Ibe00e61cb43819ecad7c1376f8c013aca3667037
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/41964
Commit-Queue: Adam Langley <agl@google.com>
Reviewed-by: David Benjamin <davidben@google.com>
diff --git a/util/fipstools/acvp/acvptool/subprocess/kdf.go b/util/fipstools/acvp/acvptool/subprocess/kdf.go
new file mode 100644
index 0000000..e8e16a0
--- /dev/null
+++ b/util/fipstools/acvp/acvptool/subprocess/kdf.go
@@ -0,0 +1,132 @@
+// Copyright (c) 2020, 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"
+)
+
+// The following structures reflect the JSON of ACVP KDF tests. See
+// https://usnistgov.github.io/ACVP/artifacts/acvp_sub_kdf108.html#rfc.section.3
+
+type kdfTestVectorSet struct {
+	Groups []kdfTestGroup `json:"testGroups"`
+}
+
+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"`
+
+	Tests []struct {
+		ID       uint64 `json:"tcId"`
+		Key      string `json:"keyIn"`
+		Deferred bool   `json:"deferred"`
+	}
+}
+
+type kdfTestGroupResponse struct {
+	ID    uint64            `json:"tgId"`
+	Tests []kdfTestResponse `json:"tests"`
+}
+
+type kdfTestResponse struct {
+	ID        uint64 `json:"tcId"`
+	KeyIn     string `json:"keyIn,omitempty"`
+	FixedData string `json:"fixedData"`
+	KeyOut    string `json:"keyOut"`
+}
+
+type kdfPrimitive struct{}
+
+func (k *kdfPrimitive) Process(vectorSet []byte, m Transactable) (interface{}, error) {
+	var parsed kdfTestVectorSet
+	if err := json.Unmarshal(vectorSet, &parsed); err != nil {
+		return nil, err
+	}
+
+	var respGroups []kdfTestGroupResponse
+	for _, group := range parsed.Groups {
+		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 != "counter" {
+			// feedback mode would need the IV to be handled.
+			// double-pipeline mode is not useful.
+			return nil, fmt.Errorf("KDF mode %q not supported", group.KDFMode)
+		}
+
+		switch group.CounterLocation {
+		case "after fixed data", "before fixed data":
+			break
+		default:
+			return nil, fmt.Errorf("Label location %q not supported", group.CounterLocation)
+		}
+
+		counterBits := uint32le(group.CounterBits)
+		outputBytes := uint32le(group.OutputBits / 8)
+
+		for _, test := range group.Tests {
+			testResp := kdfTestResponse{ID: test.ID}
+
+			var key []byte
+			if test.Deferred {
+				if len(test.Key) != 0 {
+					return nil, fmt.Errorf("key provided in deferred test case %d/%d", group.ID, test.ID)
+				}
+			} else {
+				var err error
+				if key, err = hex.DecodeString(test.Key); err != nil {
+					return nil, fmt.Errorf("failed to decode Key in test case %d/%d: %v", group.ID, test.ID, err)
+				}
+			}
+
+			// Make the call to the crypto module.
+			resp, err := m.Transact("KDF-counter", 3, outputBytes, []byte(group.MACMode), []byte(group.CounterLocation), key, counterBits)
+			if err != nil {
+				return nil, fmt.Errorf("wrapper KDF operation failed: %s", err)
+			}
+
+			// Parse results.
+			testResp.ID = test.ID
+			if test.Deferred {
+				testResp.KeyIn = hex.EncodeToString(resp[0])
+			}
+			testResp.FixedData = hex.EncodeToString(resp[1])
+			testResp.KeyOut = hex.EncodeToString(resp[2])
+
+			if !test.Deferred && !bytes.Equal(resp[0], key) {
+				return nil, fmt.Errorf("wrapper returned a different key for non-deferred KDF operation")
+			}
+
+			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 d22f3d5..b9a82ee 100644
--- a/util/fipstools/acvp/acvptool/subprocess/subprocess.go
+++ b/util/fipstools/acvp/acvptool/subprocess/subprocess.go
@@ -85,6 +85,7 @@
 		"HMAC-SHA2-512": &hmacPrimitive{"HMAC-SHA2-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}},
+		"KDF":           &kdfPrimitive{},
 	}
 	m.primitives["ECDSA"] = &ecdsa{"ECDSA", map[string]bool{"P-224": true, "P-256": true, "P-384": true, "P-521": true}, m.primitives}
 
@@ -198,3 +199,9 @@
 type primitive interface {
 	Process(vectorSet []byte, t Transactable) (interface{}, error)
 }
+
+func uint32le(n uint32) []byte {
+	var ret [4]byte
+	binary.LittleEndian.PutUint32(ret[:], n)
+	return ret[:]
+}
diff --git a/util/fipstools/acvp/acvptool/subprocess/subprocess_test.go b/util/fipstools/acvp/acvptool/subprocess/subprocess_test.go
index eb70f9f..09214c4 100644
--- a/util/fipstools/acvp/acvptool/subprocess/subprocess_test.go
+++ b/util/fipstools/acvp/acvptool/subprocess/subprocess_test.go
@@ -96,6 +96,73 @@
   }]
 }`)
 
+var validKDFJSON = []byte(`{
+  "vsId": 1564,
+  "algorithm": "counterMode",
+  "revision": "1.0",
+  "testGroups": [{
+    "tgId": 1,
+    "kdfMode": "counter",
+    "macMode": "CMAC-AES128",
+    "counterLocation": "after fixed data",
+    "keyOutLength": 1024,
+    "counterLength": 8,
+    "tests": [{
+        "tcId": 1,
+        "keyIn": "5DA38931E8D9174BC3279C8942D2DB82",
+        "deferred": false
+      },
+      {
+        "tcId": 2,
+        "keyIn": "58F5426A40E3D5D2C94F0F97EB30C739",
+        "deferred": false
+      }
+    ]
+  }]
+}`)
+
+var callsKDF = []fakeTransactCall{
+	fakeTransactCall{cmd: "KDF-counter", expectedNumResults: 3, args: [][]byte{
+		uint32le(128),                               // outputBytes
+		[]byte("CMAC-AES128"),                       // macMode
+		[]byte("after fixed data"),                  // counterLocation
+		fromHex("5DA38931E8D9174BC3279C8942D2DB82"), // keyIn
+		uint32le(8),                                 // counterLength
+	}},
+	fakeTransactCall{cmd: "KDF-counter", expectedNumResults: 3, args: [][]byte{
+		uint32le(128),                               // outputBytes
+		[]byte("CMAC-AES128"),                       // macMode
+		[]byte("after fixed data"),                  // counterLocation
+		fromHex("58F5426A40E3D5D2C94F0F97EB30C739"), // keyIn
+		uint32le(8),                                 // counterLength
+	}},
+}
+
+var invalidKDFJSON = []byte(`{
+  "vsId": 1564,
+  "algorithm": "counterMode",
+  "revision": "1.0",
+  "testGroups": [{
+    "tgId": 1,
+    "kdfMode": "counter",
+    "macMode": "CMAC-AES128",
+    "counterLocation": "after fixed data",
+    "keyOutLength": 1024,
+    "counterLength": 8,
+    "tests": [{
+        "tcId": 1,
+        "keyIn": "5DA38931E8D9174BC3279C8942D2DB82",
+        "deferred": false
+      },
+      {
+        "tcId": abc,
+        "keyIn": "58F5426A40E3D5D2C94F0F97EB30C739",
+        "deferred": false
+      }
+    ]
+  }]
+}`)
+
 var validACVPAESECB = []byte(`{
   "vsId" : 181726,
   "algorithm" : "ACVP-AES-ECB",
@@ -293,6 +360,25 @@
 			expectedCalls: callsSHA2_256,
 		},
 		{
+			algo:          "kdf",
+			p:             &kdfPrimitive{},
+			validJSON:     validKDFJSON,
+			invalidJSON:   invalidKDFJSON,
+			expectedCalls: callsKDF,
+			results: []fakeTransactResult{
+				{bytes: [][]byte{
+					fromHex("5DA38931E8D9174BC3279C8942D2DB82"),
+					[]byte("data1"),
+					[]byte("keyOut1"),
+				}},
+				{bytes: [][]byte{
+					fromHex("58F5426A40E3D5D2C94F0F97EB30C739"),
+					[]byte("data2"),
+					[]byte("keyOut2"),
+				}},
+			},
+		},
+		{
 			algo:          "ACVP-AES-ECB",
 			p:             &blockCipher{"AES", 16, false},
 			validJSON:     validACVPAESECB,
diff --git a/util/fipstools/acvp/acvptool/testmodulewrapper/testmodulewrapper.go b/util/fipstools/acvp/acvptool/testmodulewrapper/testmodulewrapper.go
new file mode 100644
index 0000000..284b5d3
--- /dev/null
+++ b/util/fipstools/acvp/acvptool/testmodulewrapper/testmodulewrapper.go
@@ -0,0 +1,201 @@
+// testmodulewrapper is a modulewrapper binary that works with acvptool and
+// implements the primitives that BoringSSL's modulewrapper doesn't, so that
+// we have something that can exercise all the code in avcptool.
+
+package main
+
+import (
+	"bytes"
+	"crypto/hmac"
+	"crypto/rand"
+	"crypto/sha256"
+	"encoding/binary"
+	"errors"
+	"fmt"
+	"io"
+	"os"
+)
+
+var handlers = map[string]func([][]byte) error{
+	"getConfig":   getConfig,
+	"KDF-counter": kdfCounter,
+}
+
+func getConfig(args [][]byte) error {
+	if len(args) != 0 {
+		return fmt.Errorf("getConfig received %d args", len(args))
+	}
+
+	return reply([]byte(`[
+	{
+		"algorithm": "KDF",
+		"revision": "1.0",
+		"capabilities": [{
+			"kdfMode": "counter",
+			"macMode": [
+				"HMAC-SHA2-256"
+			],
+			"supportedLengths": [{
+				"min": 8,
+				"max": 4096,
+				"increment": 8
+			}],
+			"fixedDataOrder": [
+				"before fixed data"
+			],
+			"counterLength": [
+				32
+			]
+		}]
+	}
+]`))
+}
+
+func kdfCounter(args [][]byte) error {
+	if len(args) != 5 {
+		return fmt.Errorf("KDF received %d args", len(args))
+	}
+
+	outputBytes32, prf, counterLocation, key, counterBits32 := args[0], args[1], args[2], args[3], args[4]
+	outputBytes := binary.LittleEndian.Uint32(outputBytes32)
+	counterBits := binary.LittleEndian.Uint32(counterBits32)
+
+	if !bytes.Equal(prf, []byte("HMAC-SHA2-256")) {
+		return fmt.Errorf("KDF received unsupported PRF %q", string(prf))
+	}
+	if !bytes.Equal(counterLocation, []byte("before fixed data")) {
+		return fmt.Errorf("KDF received unsupported counter location %q", counterLocation)
+	}
+	if counterBits != 32 {
+		return fmt.Errorf("KDF received unsupported counter length %d", counterBits)
+	}
+
+	if len(key) == 0 {
+		key = make([]byte, 32)
+		rand.Reader.Read(key)
+	}
+
+	// See https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-108.pdf section 5.1
+	if outputBytes+31 < outputBytes {
+		return fmt.Errorf("KDF received excessive output length %d", outputBytes)
+	}
+
+	n := (outputBytes + 31) / 32
+	result := make([]byte, 0, 32*n)
+	mac := hmac.New(sha256.New, key)
+	var input [4 + 8]byte
+	var digest []byte
+	rand.Reader.Read(input[4:])
+	for i := uint32(1); i <= n; i++ {
+		mac.Reset()
+		binary.BigEndian.PutUint32(input[:4], i)
+		mac.Write(input[:])
+		digest = mac.Sum(digest[:0])
+		result = append(result, digest...)
+	}
+
+	return reply(key, input[4:], result[:outputBytes])
+}
+
+func reply(responses ...[]byte) error {
+	if len(responses) > maxArgs {
+		return fmt.Errorf("%d responses is too many", len(responses))
+	}
+
+	var lengths [4 * (1 + maxArgs)]byte
+	binary.LittleEndian.PutUint32(lengths[:4], uint32(len(responses)))
+	for i, response := range responses {
+		binary.LittleEndian.PutUint32(lengths[4*(i+1):4*(i+2)], uint32(len(response)))
+	}
+
+	lengthsLength := (1 + len(responses)) * 4
+	if n, err := os.Stdout.Write(lengths[:lengthsLength]); n != lengthsLength || err != nil {
+		return fmt.Errorf("write failed: %s", err)
+	}
+
+	for _, response := range responses {
+		if n, err := os.Stdout.Write(response); n != len(response) || err != nil {
+			return fmt.Errorf("write failed: %s", err)
+		}
+	}
+
+	return nil
+}
+
+const (
+	maxArgs       = 8
+	maxArgLength  = 1 << 20
+	maxNameLength = 30
+)
+
+func main() {
+	if err := do(); err != nil {
+		fmt.Fprintf(os.Stderr, "%s.\n", err)
+		os.Exit(1)
+	}
+}
+
+func do() error {
+	var nums [4 * (1 + maxArgs)]byte
+	var argLengths [maxArgs]uint32
+	var args [maxArgs][]byte
+	var argsData []byte
+
+	for {
+		if _, err := io.ReadFull(os.Stdin, nums[:8]); err != nil {
+			return err
+		}
+
+		numArgs := binary.LittleEndian.Uint32(nums[:4])
+		if numArgs == 0 {
+			return errors.New("Invalid, zero-argument operation requested")
+		} else if numArgs > maxArgs {
+			return fmt.Errorf("Operation requested with %d args, but %d is the limit", numArgs, maxArgs)
+		}
+
+		if numArgs > 1 {
+			if _, err := io.ReadFull(os.Stdin, nums[8:4+4*numArgs]); err != nil {
+				return err
+			}
+		}
+
+		input := nums[4:]
+		var need uint64
+		for i := uint32(0); i < numArgs; i++ {
+			argLength := binary.LittleEndian.Uint32(input[:4])
+			if i == 0 && argLength > maxNameLength {
+				return fmt.Errorf("Operation with name of length %d exceeded limit of %d", argLength, maxNameLength)
+			} else if argLength > maxArgLength {
+				return fmt.Errorf("Operation with argument of length %d exceeded limit of %d", argLength, maxArgLength)
+			}
+			need += uint64(argLength)
+			argLengths[i] = argLength
+			input = input[4:]
+		}
+
+		if need > uint64(cap(argsData)) {
+			argsData = make([]byte, need)
+		} else {
+			argsData = argsData[:need]
+		}
+
+		if _, err := io.ReadFull(os.Stdin, argsData); err != nil {
+			return err
+		}
+
+		input = argsData
+		for i := uint32(0); i < numArgs; i++ {
+			args[i] = input[:argLengths[i]]
+			input = input[argLengths[i]:]
+		}
+
+		name := string(args[0])
+		if handler, ok := handlers[name]; !ok {
+			return fmt.Errorf("unknown operation %q", name)
+		} else {
+			if err := handler(args[1:numArgs]); err != nil {
+				return err
+			}
+		}
+	}
+}