acvptool: add subprocess tests.

(Written by Dan Janni.)

Change-Id: Ice03bb3e717b361af367cce7425f43d65e79cadc
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/40724
Commit-Queue: David Benjamin <davidben@google.com>
Reviewed-by: David Benjamin <davidben@google.com>
diff --git a/util/fipstools/acvp/acvptool/acvp.go b/util/fipstools/acvp/acvptool/acvp.go
index 14ed6a9..c539b3b 100644
--- a/util/fipstools/acvp/acvptool/acvp.go
+++ b/util/fipstools/acvp/acvptool/acvp.go
@@ -432,6 +432,7 @@
 		}
 
 		if results.Passed {
+			log.Print("Test passed")
 			break
 		}
 
diff --git a/util/fipstools/acvp/acvptool/subprocess/block.go b/util/fipstools/acvp/acvptool/subprocess/block.go
index e365882..69a6517 100644
--- a/util/fipstools/acvp/acvptool/subprocess/block.go
+++ b/util/fipstools/acvp/acvptool/subprocess/block.go
@@ -26,7 +26,6 @@
 	algo      string
 	blockSize int
 	hasIV     bool
-	m         *Subprocess
 }
 
 type blockCipherVectorSet struct {
@@ -66,7 +65,7 @@
 	IVHex         string `json:"iv,omitempty"`
 }
 
-func (b *blockCipher) Process(vectorSet []byte) (interface{}, error) {
+func (b *blockCipher) Process(vectorSet []byte, m Transactable) (interface{}, error) {
 	var parsed blockCipherVectorSet
 	if err := json.Unmarshal(vectorSet, &parsed); err != nil {
 		return nil, err
@@ -153,9 +152,9 @@
 				var err error
 
 				if b.hasIV {
-					result, err = b.m.transact(op, 1, key, input, iv)
+					result, err = m.Transact(op, 1, key, input, iv)
 				} else {
-					result, err = b.m.transact(op, 1, key, input)
+					result, err = m.Transact(op, 1, key, input)
 				}
 				if err != nil {
 					panic("block operation failed: " + err.Error())
@@ -180,7 +179,7 @@
 					if !b.hasIV {
 						for j := 0; j < 1000; j++ {
 							prevResult = input
-							result, err := b.m.transact(op, 1, key, input)
+							result, err := m.Transact(op, 1, key, input)
 							if err != nil {
 								panic("block operation failed")
 							}
@@ -201,7 +200,7 @@
 								}
 							}
 
-							results, err := b.m.transact(op, 1, key, input, iv)
+							results, err := m.Transact(op, 1, key, input, iv)
 							if err != nil {
 								panic("block operation failed")
 							}
diff --git a/util/fipstools/acvp/acvptool/subprocess/drbg.go b/util/fipstools/acvp/acvptool/subprocess/drbg.go
index 83c13df..03268e4 100644
--- a/util/fipstools/acvp/acvptool/subprocess/drbg.go
+++ b/util/fipstools/acvp/acvptool/subprocess/drbg.go
@@ -69,10 +69,9 @@
 	// given to the subprocess to generate random bytes.
 	algo  string
 	modes map[string]bool // the supported underlying primitives for the DRBG
-	m     *Subprocess
 }
 
-func (d *drbg) Process(vectorSet []byte) (interface{}, error) {
+func (d *drbg) Process(vectorSet []byte, m Transactable) (interface{}, error) {
 	var parsed drbgTestVectorSet
 	if err := json.Unmarshal(vectorSet, &parsed); err != nil {
 		return nil, err
@@ -138,13 +137,13 @@
 			outLen := group.RetBits / 8
 			var outLenBytes [4]byte
 			binary.LittleEndian.PutUint32(outLenBytes[:], uint32(outLen))
-			result, err := d.m.transact(d.algo+"/"+group.Mode, 1, outLenBytes[:], ent, perso, additionalInputs[0], additionalInputs[1], nonce)
+			result, err := m.Transact(d.algo+"/"+group.Mode, 1, outLenBytes[:], ent, perso, additionalInputs[0], additionalInputs[1], nonce)
 			if err != nil {
 				return nil, fmt.Errorf("DRBG operation failed: %s", err)
 			}
 
 			if l := uint64(len(result[0])); l != outLen {
-				return nil, fmt.Errorf("wrong length DRBG result: %d bytes but wanted %d", l, outLenBytes)
+				return nil, fmt.Errorf("wrong length DRBG result: %d bytes but wanted %d", l, outLen)
 			}
 
 			// https://usnistgov.github.io/ACVP/artifacts/acvp_sub_drbg.html#rfc.section.4
diff --git a/util/fipstools/acvp/acvptool/subprocess/ecdsa.go b/util/fipstools/acvp/acvptool/subprocess/ecdsa.go
index 5b5b1d1..1ccae41 100644
--- a/util/fipstools/acvp/acvptool/subprocess/ecdsa.go
+++ b/util/fipstools/acvp/acvptool/subprocess/ecdsa.go
@@ -67,12 +67,12 @@
 type ecdsa struct {
 	// algo is the ACVP name for this algorithm and also the command name
 	// given to the subprocess to hash with this hash function.
-	algo   string
-	curves map[string]bool // supported curve names
-	m      *Subprocess
+	algo       string
+	curves     map[string]bool // supported curve names
+	primitives map[string]primitive
 }
 
-func (e *ecdsa) Process(vectorSet []byte) (interface{}, error) {
+func (e *ecdsa) Process(vectorSet []byte, m Transactable) (interface{}, error) {
 	var parsed ecdsaTestVectorSet
 	if err := json.Unmarshal(vectorSet, &parsed); err != nil {
 		return nil, err
@@ -100,7 +100,7 @@
 				if group.SecretGenerationMode != "testing candidates" {
 					return nil, fmt.Errorf("invalid secret generation mode in test group %d: %q", group.ID, group.SecretGenerationMode)
 				}
-				result, err := e.m.transact(e.algo+"/"+"keyGen", 3, []byte(group.Curve))
+				result, err := m.Transact(e.algo+"/"+"keyGen", 3, []byte(group.Curve))
 				if err != nil {
 					return nil, fmt.Errorf("key generation failed for test case %d/%d: %s", group.ID, test.ID, err)
 				}
@@ -117,7 +117,7 @@
 				if err != nil {
 					return nil, fmt.Errorf("failed to decode qy in test case %d/%d: %s", group.ID, test.ID, err)
 				}
-				result, err := e.m.transact(e.algo+"/"+"keyVer", 1, []byte(group.Curve), qx, qy)
+				result, err := m.Transact(e.algo+"/"+"keyVer", 1, []byte(group.Curve), qx, qy)
 				if err != nil {
 					return nil, fmt.Errorf("key verification failed for test case %d/%d: %s", group.ID, test.ID, err)
 				}
@@ -134,7 +134,7 @@
 				}
 
 			case "sigGen":
-				p := e.m.primitives[group.HashAlgo]
+				p := e.primitives[group.HashAlgo]
 				h, ok := p.(*hashPrimitive)
 				if !ok {
 					return nil, fmt.Errorf("unsupported hash algorithm %q in test group %d", group.HashAlgo, group.ID)
@@ -142,7 +142,7 @@
 
 				if len(sigGenPrivateKey) == 0 {
 					// Ask the subprocess to generate a key for this test group.
-					result, err := e.m.transact(e.algo+"/"+"keyGen", 3, []byte(group.Curve))
+					result, err := m.Transact(e.algo+"/"+"keyGen", 3, []byte(group.Curve))
 					if err != nil {
 						return nil, fmt.Errorf("key generation failed for test case %d/%d: %s", group.ID, test.ID, err)
 					}
@@ -163,7 +163,7 @@
 					}
 					op += "/componentTest"
 				}
-				result, err := e.m.transact(op, 2, []byte(group.Curve), sigGenPrivateKey, []byte(group.HashAlgo), msg)
+				result, err := m.Transact(op, 2, []byte(group.Curve), sigGenPrivateKey, []byte(group.HashAlgo), msg)
 				if err != nil {
 					return nil, fmt.Errorf("signature generation failed for test case %d/%d: %s", group.ID, test.ID, err)
 				}
@@ -171,7 +171,7 @@
 				testResp.SHex = hex.EncodeToString(result[1])
 
 			case "sigVer":
-				p := e.m.primitives[group.HashAlgo]
+				p := e.primitives[group.HashAlgo]
 				_, ok := p.(*hashPrimitive)
 				if !ok {
 					return nil, fmt.Errorf("unsupported hash algorithm %q in test group %d", group.HashAlgo, group.ID)
@@ -197,7 +197,7 @@
 				if err != nil {
 					return nil, fmt.Errorf("failed to decode S in test case %d/%d: %s", group.ID, test.ID, err)
 				}
-				result, err := e.m.transact(e.algo+"/"+"sigVer", 1, []byte(group.Curve), []byte(group.HashAlgo), msg, qx, qy, r, s)
+				result, err := m.Transact(e.algo+"/"+"sigVer", 1, []byte(group.Curve), []byte(group.HashAlgo), msg, qx, qy, r, s)
 				if err != nil {
 					return nil, fmt.Errorf("signature verification failed for test case %d/%d: %s", group.ID, test.ID, err)
 				}
diff --git a/util/fipstools/acvp/acvptool/subprocess/hash.go b/util/fipstools/acvp/acvptool/subprocess/hash.go
index ea9668c..f283352 100644
--- a/util/fipstools/acvp/acvptool/subprocess/hash.go
+++ b/util/fipstools/acvp/acvptool/subprocess/hash.go
@@ -60,19 +60,18 @@
 	algo string
 	// size is the number of bytes of digest that the hash produces.
 	size int
-	m    *Subprocess
 }
 
 // hash uses the subprocess to hash msg and returns the digest.
-func (h *hashPrimitive) hash(msg []byte) []byte {
-	result, err := h.m.transact(h.algo, 1, msg)
+func (h *hashPrimitive) hash(msg []byte, m Transactable) []byte {
+	result, err := m.Transact(h.algo, 1, msg)
 	if err != nil {
 		panic("hash operation failed: " + err.Error())
 	}
 	return result[0]
 }
 
-func (h *hashPrimitive) Process(vectorSet []byte) (interface{}, error) {
+func (h *hashPrimitive) Process(vectorSet []byte, m Transactable) (interface{}, error) {
 	var parsed hashTestVectorSet
 	if err := json.Unmarshal(vectorSet, &parsed); err != nil {
 		return nil, err
@@ -101,7 +100,7 @@
 			case "AFT":
 				response.Tests = append(response.Tests, hashTestResponse{
 					ID:        test.ID,
-					DigestHex: hex.EncodeToString(h.hash(msg)),
+					DigestHex: hex.EncodeToString(h.hash(msg, m)),
 				})
 
 			case "MCT":
@@ -118,7 +117,7 @@
 					copy(buf[h.size:], msg)
 					copy(buf[2*h.size:], msg)
 					for j := 0; j < 1000; j++ {
-						digest = h.hash(buf)
+						digest = h.hash(buf, m)
 						copy(buf, buf[h.size:])
 						copy(buf[2*h.size:], digest)
 					}
diff --git a/util/fipstools/acvp/acvptool/subprocess/hmac.go b/util/fipstools/acvp/acvptool/subprocess/hmac.go
index 09d5702..fb299c7 100644
--- a/util/fipstools/acvp/acvptool/subprocess/hmac.go
+++ b/util/fipstools/acvp/acvptool/subprocess/hmac.go
@@ -58,16 +58,15 @@
 	// given to the subprocess to HMAC with this hash function.
 	algo  string
 	mdLen int // mdLen is the number of bytes of output that the underlying hash produces.
-	m     *Subprocess
 }
 
 // hmac uses the subprocess to compute HMAC and returns the result.
-func (h *hmacPrimitive) hmac(msg []byte, key []byte, outBits int) []byte {
+func (h *hmacPrimitive) hmac(msg []byte, key []byte, outBits int, m Transactable) []byte {
 	if outBits%8 != 0 {
 		panic("fractional-byte output length requested: " + strconv.Itoa(outBits))
 	}
 	outBytes := outBits / 8
-	result, err := h.m.transact(h.algo, 1, msg, key)
+	result, err := m.Transact(h.algo, 1, msg, key)
 	if err != nil {
 		panic("HMAC operation failed: " + err.Error())
 	}
@@ -77,7 +76,7 @@
 	return result[0][:outBytes]
 }
 
-func (h *hmacPrimitive) Process(vectorSet []byte) (interface{}, error) {
+func (h *hmacPrimitive) Process(vectorSet []byte, m Transactable) (interface{}, error) {
 	var parsed hmacTestVectorSet
 	if err := json.Unmarshal(vectorSet, &parsed); err != nil {
 		return nil, err
@@ -115,7 +114,7 @@
 			// https://usnistgov.github.io/ACVP/artifacts/acvp_sub_mac.html#hmac_vector_responses
 			response.Tests = append(response.Tests, hmacTestResponse{
 				ID:     test.ID,
-				MACHex: hex.EncodeToString(h.hmac(msg, key, group.MACBits)),
+				MACHex: hex.EncodeToString(h.hmac(msg, key, group.MACBits, m)),
 			})
 		}
 
diff --git a/util/fipstools/acvp/acvptool/subprocess/subprocess.go b/util/fipstools/acvp/acvptool/subprocess/subprocess.go
index 6f3e6ad..d22f3d5 100644
--- a/util/fipstools/acvp/acvptool/subprocess/subprocess.go
+++ b/util/fipstools/acvp/acvptool/subprocess/subprocess.go
@@ -12,6 +12,8 @@
 // OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
 // CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 
+// Package subprocess contains functionality to talk to a modulewrapper for
+// testing of various algorithm implementations.
 package subprocess
 
 import (
@@ -24,6 +26,12 @@
 	"os/exec"
 )
 
+// Transactable provides an interface to allow test injection of transactions
+// that don't call a server.
+type Transactable interface {
+	Transact(cmd string, expectedResults int, args ...[]byte) ([][]byte, error)
+}
+
 // Subprocess is a "middle" layer that interacts with a FIPS module via running
 // a command and speaking a simple protocol over stdin/stdout.
 type Subprocess struct {
@@ -63,22 +71,22 @@
 	}
 
 	m.primitives = map[string]primitive{
-		"SHA-1":         &hashPrimitive{"SHA-1", 20, m},
-		"SHA2-224":      &hashPrimitive{"SHA2-224", 28, m},
-		"SHA2-256":      &hashPrimitive{"SHA2-256", 32, m},
-		"SHA2-384":      &hashPrimitive{"SHA2-384", 48, m},
-		"SHA2-512":      &hashPrimitive{"SHA2-512", 64, m},
-		"ACVP-AES-ECB":  &blockCipher{"AES", 16, false, m},
-		"ACVP-AES-CBC":  &blockCipher{"AES-CBC", 16, true, m},
-		"HMAC-SHA-1":    &hmacPrimitive{"HMAC-SHA-1", 20, m},
-		"HMAC-SHA2-224": &hmacPrimitive{"HMAC-SHA2-224", 28, m},
-		"HMAC-SHA2-256": &hmacPrimitive{"HMAC-SHA2-256", 32, m},
-		"HMAC-SHA2-384": &hmacPrimitive{"HMAC-SHA2-384", 48, m},
-		"HMAC-SHA2-512": &hmacPrimitive{"HMAC-SHA2-512", 64, m},
-		"ctrDRBG":       &drbg{"ctrDRBG", map[string]bool{"AES-128": true, "AES-192": true, "AES-256": true}, m},
-		"hmacDRBG":      &drbg{"hmacDRBG", map[string]bool{"SHA-1": true, "SHA2-224": true, "SHA2-256": true, "SHA2-384": true, "SHA2-512": true}, m},
-		"ECDSA":         &ecdsa{"ECDSA", map[string]bool{"P-224": true, "P-256": true, "P-384": true, "P-521": true}, m},
+		"SHA-1":         &hashPrimitive{"SHA-1", 20},
+		"SHA2-224":      &hashPrimitive{"SHA2-224", 28},
+		"SHA2-256":      &hashPrimitive{"SHA2-256", 32},
+		"SHA2-384":      &hashPrimitive{"SHA2-384", 48},
+		"SHA2-512":      &hashPrimitive{"SHA2-512", 64},
+		"ACVP-AES-ECB":  &blockCipher{"AES", 16, false},
+		"ACVP-AES-CBC":  &blockCipher{"AES-CBC", 16, true},
+		"HMAC-SHA-1":    &hmacPrimitive{"HMAC-SHA-1", 20},
+		"HMAC-SHA2-224": &hmacPrimitive{"HMAC-SHA2-224", 28},
+		"HMAC-SHA2-256": &hmacPrimitive{"HMAC-SHA2-256", 32},
+		"HMAC-SHA2-384": &hmacPrimitive{"HMAC-SHA2-384", 48},
+		"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}},
 	}
+	m.primitives["ECDSA"] = &ecdsa{"ECDSA", map[string]bool{"P-224": true, "P-256": true, "P-384": true, "P-521": true}, m.primitives}
 
 	return m
 }
@@ -90,8 +98,8 @@
 	m.cmd.Wait()
 }
 
-// transact performs a single request--response pair with the subprocess.
-func (m *Subprocess) transact(cmd string, expectedResults int, args ...[]byte) ([][]byte, error) {
+// Transact performs a single request--response pair with the subprocess.
+func (m *Subprocess) Transact(cmd string, expectedResults int, args ...[]byte) ([][]byte, error) {
 	argLength := len(cmd)
 	for _, arg := range args {
 		argLength += len(arg)
@@ -156,7 +164,7 @@
 // format of the blob is defined by ACVP. See
 // http://usnistgov.github.io/ACVP/artifacts/draft-fussell-acvp-spec-00.html#rfc.section.11.15.2.1
 func (m *Subprocess) Config() ([]byte, error) {
-	results, err := m.transact("getConfig", 1)
+	results, err := m.Transact("getConfig", 1)
 	if err != nil {
 		return nil, err
 	}
@@ -180,7 +188,7 @@
 	if !ok {
 		return nil, fmt.Errorf("unknown algorithm %q", algorithm)
 	}
-	ret, err := prim.Process(vectorSet)
+	ret, err := prim.Process(vectorSet, m)
 	if err != nil {
 		return nil, err
 	}
@@ -188,5 +196,5 @@
 }
 
 type primitive interface {
-	Process(vectorSet []byte) (interface{}, error)
+	Process(vectorSet []byte, t Transactable) (interface{}, error)
 }
diff --git a/util/fipstools/acvp/acvptool/subprocess/subprocess_test.go b/util/fipstools/acvp/acvptool/subprocess/subprocess_test.go
new file mode 100644
index 0000000..eb70f9f
--- /dev/null
+++ b/util/fipstools/acvp/acvptool/subprocess/subprocess_test.go
@@ -0,0 +1,353 @@
+// 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
+
+// NOTES:
+// - subprocess_test does not include testing for all subcomponents. It does
+//   include unit tests for the following:
+//   - hashPrimitive (for sha2-256 only)
+//   - blockCipher (for AES)
+//   - drbg (for ctrDRBG)
+// - All sample data (the valid & invalid strings) comes from calls to acvp as
+//   of 2020-04-02.
+
+import (
+	"encoding/hex"
+	"encoding/json"
+	"fmt"
+	"reflect"
+	"testing"
+)
+
+var validSHA2_256 = []byte(`{
+  "vsId" : 182183,
+  "algorithm" : "SHA2-256",
+  "revision" : "1.0",
+  "isSample" : true,
+  "testGroups" : [ {
+    "tgId" : 1,
+    "testType" : "AFT",
+    "tests" : [ {
+      "tcId" : 1,
+      "msg" : "",
+      "len" : 0
+    }, {
+      "tcId" : 2,
+      "msg" : "",
+      "len" : 0
+    }, {
+      "tcId" : 3,
+      "msg" : "8E",
+      "len" : 8
+    }, {
+      "tcId" : 4,
+      "msg" : "7F10",
+      "len" : 16
+    }, {
+      "tcId" : 5,
+      "msg" : "F4422F",
+      "len" : 24
+    }, {
+      "tcId" : 6,
+      "msg" : "B3EF9698",
+      "len" : 32
+    }]
+  }]
+}`)
+
+var callsSHA2_256 = []fakeTransactCall{
+	fakeTransactCall{cmd: "SHA2-256", expectedNumResults: 1, args: [][]byte{[]byte{}}},
+	fakeTransactCall{cmd: "SHA2-256", expectedNumResults: 1, args: [][]byte{[]byte{}}},
+	fakeTransactCall{cmd: "SHA2-256", expectedNumResults: 1, args: [][]byte{fromHex("8E")}},
+	fakeTransactCall{cmd: "SHA2-256", expectedNumResults: 1, args: [][]byte{fromHex("7F10")}},
+	fakeTransactCall{cmd: "SHA2-256", expectedNumResults: 1, args: [][]byte{fromHex("F4422F")}},
+	fakeTransactCall{cmd: "SHA2-256", expectedNumResults: 1, args: [][]byte{fromHex("B3EF9698")}},
+}
+
+var invalidSHA2_256 = []byte(`{
+  "vsId" : 180207,
+  "algorithm" : "SHA2-256",
+  "revision" : "1.0",
+  "isSample" : true,
+  "testGroups" : [ {
+    "tgId" : abc,
+    "testType" : "AFT",
+    "tests" : [ {
+      "tcId" : 1,
+      "msg" : "",
+      "len" : 0
+    }, {
+      "tcId" : 2,
+      "msg" : "",
+      "len" : 0
+    }]
+  }]
+}`)
+
+var validACVPAESECB = []byte(`{
+  "vsId" : 181726,
+  "algorithm" : "ACVP-AES-ECB",
+  "revision" : "1.0",
+  "isSample" : true,
+  "testGroups" : [ {
+    "tgId" : 1,
+    "testType" : "AFT",
+    "direction" : "encrypt",
+    "keyLen" : 128,
+    "tests" : [ {
+      "tcId" : 1,
+      "pt" : "F34481EC3CC627BACD5DC3FB08F273E6",
+      "key" : "00000000000000000000000000000000"
+    }, {
+      "tcId" : 2,
+      "pt" : "9798C4640BAD75C7C3227DB910174E72",
+      "key" : "00000000000000000000000000000000"
+    }]
+  }]
+}`)
+
+var invalidACVPAESECB = []byte(`{
+  "vsId" : 181726,
+  "algorithm" : "ACVP-AES-ECB",
+  "revision" : "1.0",
+  "isSample" : true,
+  "testGroups" : [ {
+    "tgId" : 1,
+    "testType" : "AFT",
+    "direction" : "encrypt",
+    "keyLen" : 128,
+    "tests" : [ {
+      "tcId" : abc,
+      "pt" : "F34481EC3CC627BACD5DC3FB08F273E6",
+      "key" : "00000000000000000000000000000000"
+    }, {
+      "tcId" : 2,
+      "pt" : "9798C4640BAD75C7C3227DB910174E72",
+      "key" : "00000000000000000000000000000000"
+    }]
+  }]
+}`)
+
+var callsACVPAESECB = []fakeTransactCall{
+	fakeTransactCall{cmd: "AES/encrypt", expectedNumResults: 1, args: [][]byte{
+		fromHex("00000000000000000000000000000000"),
+		fromHex("F34481EC3CC627BACD5DC3FB08F273E6"),
+	}},
+	fakeTransactCall{cmd: "AES/encrypt", expectedNumResults: 1, args: [][]byte{
+		fromHex("00000000000000000000000000000000"),
+		fromHex("9798C4640BAD75C7C3227DB910174E72"),
+	}},
+}
+
+var validCTRDRBG = []byte(`{
+  "vsId" : 181791,
+  "algorithm" : "ctrDRBG",
+  "revision" : "1.0",
+  "isSample" : true,
+  "testGroups" : [ {
+    "tgId" : 1,
+    "testType" : "AFT",
+    "derFunc" : false,
+    "reSeed" : false,
+    "predResistance" : false,
+    "entropyInputLen" : 384,
+    "nonceLen" : 0,
+    "persoStringLen" : 0,
+    "additionalInputLen" : 0,
+    "returnedBitsLen" : 2048,
+    "mode" : "AES-256",
+    "tests" : [ {
+      "tcId" : 1,
+      "entropyInput" : "0D9E8EB273307D95C616C7ACC65669C246265E8A850EDCF36990D8A6F7EC3AEA0A7DDB888EE8D7ECC19EA7830310782C",
+      "nonce" : "",
+      "persoString" : "",
+      "otherInput" : [ {
+        "intendedUse" : "generate",
+        "additionalInput" : "",
+        "entropyInput" : ""
+      }, {
+        "intendedUse" : "generate",
+        "additionalInput" : "",
+        "entropyInput" : ""
+      } ]
+    }]
+  }]
+}`)
+
+var callsCTRDRBG = []fakeTransactCall{
+	fakeTransactCall{cmd: "ctrDRBG/AES-256", expectedNumResults: 1, args: [][]byte{
+		fromHex("00010000"), // uint32(256)
+		fromHex("0D9E8EB273307D95C616C7ACC65669C246265E8A850EDCF36990D8A6F7EC3AEA0A7DDB888EE8D7ECC19EA7830310782C"),
+		[]byte{},
+		[]byte{},
+		[]byte{},
+		[]byte{},
+	}},
+}
+
+var invalidCTRDRBG = []byte(`{
+  "vsId" : 181791,
+  "algorithm" : "ctrDRBG",
+  "revision" : "1.0",
+  "isSample" : true,
+  "testGroups" : [ {
+    "tgId" : 1,
+    "testType" : "AFT",
+    "derFunc" : false,
+    "reSeed" : false,
+    "predResistance" : false,
+    "entropyInputLen" : 384,
+    "nonceLen" : 0,
+    "persoStringLen" : 0,
+    "additionalInputLen" : 0,
+    "returnedBitsLen" : 2048,
+    "mode" : "AES-256",
+    "tests" : [ {
+      "tcId" : abc,
+      "entropyInput" : "0D9E8EB273307D95C616C7ACC65669C246265E8A850EDCF36990D8A6F7EC3AEA0A7DDB888EE8D7ECC19EA7830310782C",
+      "nonce" : "",
+      "persoString" : "",
+      "otherInput" : [ {
+        "intendedUse" : "generate",
+        "additionalInput" : "",
+        "entropyInput" : ""
+      }, {
+        "intendedUse" : "generate",
+        "additionalInput" : "",
+        "entropyInput" : ""
+      } ]
+    }]
+  }]
+}`)
+
+// fakeTransactable provides a fake to return results that don't go to the ACVP
+// server.
+type fakeTransactable struct {
+	calls   []fakeTransactCall
+	results []fakeTransactResult
+}
+
+type fakeTransactCall struct {
+	cmd                string
+	expectedNumResults int
+	args               [][]byte
+}
+
+type fakeTransactResult struct {
+	bytes [][]byte
+	err   error
+}
+
+func (f *fakeTransactable) Transact(cmd string, expectedNumResults int, args ...[]byte) ([][]byte, error) {
+	f.calls = append(f.calls, fakeTransactCall{cmd, expectedNumResults, args})
+
+	if len(f.results) == 0 {
+		return nil, fmt.Errorf("Transact called but no TransactResults remain")
+	}
+
+	ret := f.results[0]
+	f.results = f.results[1:]
+	return ret.bytes, ret.err
+}
+
+func newFakeTransactable(name string, numResponses int) *fakeTransactable {
+	ret := new(fakeTransactable)
+
+	// Add results requested by caller.
+	dummyResult := [][]byte{[]byte("dummy result")}
+	for i := 0; i < numResponses; i++ {
+		ret.results = append(ret.results, fakeTransactResult{bytes: dummyResult, err: nil})
+	}
+
+	return ret
+}
+
+// TestPrimitiveParsesJSON verifies that basic JSON parsing with a
+// small passing case & a single failing case.
+func TestPrimitives(t *testing.T) {
+	var tests = []struct {
+		algo          string
+		p             primitive
+		validJSON     []byte
+		invalidJSON   []byte
+		expectedCalls []fakeTransactCall
+		results       []fakeTransactResult
+	}{
+		{
+			algo:          "SHA2-256",
+			p:             &hashPrimitive{"SHA2-256", 32},
+			validJSON:     validSHA2_256,
+			invalidJSON:   invalidSHA2_256,
+			expectedCalls: callsSHA2_256,
+		},
+		{
+			algo:          "ACVP-AES-ECB",
+			p:             &blockCipher{"AES", 16, false},
+			validJSON:     validACVPAESECB,
+			invalidJSON:   invalidACVPAESECB,
+			expectedCalls: callsACVPAESECB,
+		},
+		{
+			algo:          "ctrDRBG",
+			p:             &drbg{"ctrDRBG", map[string]bool{"AES-128": true, "AES-192": true, "AES-256": true}},
+			validJSON:     validCTRDRBG,
+			invalidJSON:   invalidCTRDRBG,
+			expectedCalls: callsCTRDRBG,
+			results: []fakeTransactResult{
+				fakeTransactResult{bytes: [][]byte{make([]byte, 256)}},
+			},
+		},
+	}
+
+	for _, test := range tests {
+		transactable := newFakeTransactable(test.algo, len(test.expectedCalls))
+		if len(test.results) > 0 {
+			transactable.results = test.results
+		}
+
+		if _, err := test.p.Process(test.validJSON, transactable); err != nil {
+			t.Errorf("%s: valid input failed unexpectedly: %v", test.algo, err)
+			continue
+		}
+
+		if len(transactable.calls) != len(test.expectedCalls) {
+			t.Errorf("%s: got %d results, but want %d", test.algo, len(transactable.calls), len(test.expectedCalls))
+			continue
+		}
+
+		if !reflect.DeepEqual(transactable.calls, test.expectedCalls) {
+			t.Errorf("%s: got:\n%#v\n\nwant:\n%#v", test.algo, transactable.calls, test.expectedCalls)
+		}
+
+		if _, err := test.p.Process(test.invalidJSON, transactable); !isJSONSyntaxError(err) {
+			t.Errorf("Test %v with invalid input either passed or failed with the wrong error (%v)", test.algo, err)
+		}
+	}
+}
+
+// isJSONSyntaxError returns true if the error is a json syntax error.
+func isJSONSyntaxError(err error) bool {
+	_, ok := err.(*json.SyntaxError)
+	return ok
+}
+
+// fromHex wraps hex.DecodeString so it can be used in initializers. Panics on error.
+func fromHex(s string) []byte {
+	key, err := hex.DecodeString(s)
+	if err != nil {
+		panic(fmt.Sprintf("Failed on hex.DecodeString(%q) with %v", s, err))
+	}
+	return key
+}