From 6bc21dcfb6594592b4d211c086f8b8e3336b10c0 Mon Sep 17 00:00:00 2001
From: Tink Team <tink-dev@google.com>
Date: Thu, 21 Mar 2019 06:43:52 -0700
Subject: [PATCH] Add Go structure for running Wycheproof test cases

PiperOrigin-RevId: 239585715
GitOrigin-RevId: 30c96506a61203162d24c1107f7f0db3a7b360d6
---
 go/subtle/kwp/BUILD.bazel          |   4 +
 go/subtle/kwp/kwp_test.go          | 147 +++++++++++++++++++----------
 go/testutil/BUILD.bazel            |   9 +-
 go/testutil/wycheproofutil.go      |  56 +++++++++++
 go/testutil/wycheproofutil_test.go |  64 +++++++++++++
 5 files changed, 230 insertions(+), 50 deletions(-)
 create mode 100644 go/testutil/wycheproofutil.go
 create mode 100644 go/testutil/wycheproofutil_test.go

diff --git a/go/subtle/kwp/BUILD.bazel b/go/subtle/kwp/BUILD.bazel
index fa9ac4a50..481fed7c2 100644
--- a/go/subtle/kwp/BUILD.bazel
+++ b/go/subtle/kwp/BUILD.bazel
@@ -11,8 +11,12 @@ go_library(
 go_test(
     name = "go_default_test",
     srcs = ["kwp_test.go"],
+    data = [
+        "@wycheproof//testvectors:all",
+    ],
     deps = [
         ":go_default_library",
         "//go/subtle/random:go_default_library",
+        "//go/testutil:go_default_library",
     ],
 )
diff --git a/go/subtle/kwp/kwp_test.go b/go/subtle/kwp/kwp_test.go
index 9c0604fb2..2ffa4354d 100644
--- a/go/subtle/kwp/kwp_test.go
+++ b/go/subtle/kwp/kwp_test.go
@@ -17,11 +17,13 @@ package kwp_test
 import (
 	"bytes"
 	"encoding/hex"
+	"encoding/json"
 	"fmt"
 	"testing"
 
 	"github.com/google/tink/go/subtle/kwp"
 	"github.com/google/tink/go/subtle/random"
+	"github.com/google/tink/go/testutil"
 )
 
 func TestWrapUnwrap(t *testing.T) {
@@ -86,61 +88,108 @@ func TestInvalidWrappingSizes(t *testing.T) {
 	}
 }
 
-func TestKnownVectors(t *testing.T) {
-	// vectors from Wycheproof
-	vectors := []struct {
-		id           int
-		key, msg, ct string
-	}{
-		{
-			1,
-			"6f67486d1e914419cb43c28509c7c1ea",
-			"8dc0632d92ee0be4f740028410b08270",
-			"8cd63fa6788aa5edfa753fc87d645a672b14107c3b4519e7",
-		},
-		{
-			76,
-			"fce0429c610658ef8e7cfb0154c51de2239a8a317f5af5b6714f985fb5c4d75c",
-			"287326b5ed0078e7ca0164d748f667e7",
-			"e3eab96d9a2fda12f9e252053aff15e753e5ea6f5172c92b",
-		},
+type KwpCase struct {
+	testutil.WycheproofCase
+	Key        string `json:"key"`
+	Message    string `json:"msg"`
+	Ciphertext string `json:"ct"`
+}
+
+type KwpGroup struct {
+	testutil.WycheproofGroup
+	KeySize int        `json:"keySize"`
+	Tests   []*KwpCase `json:"tests"`
+}
+
+type KwpSuite struct {
+	testutil.WycheproofSuite
+	Groups []*KwpGroup `json:"testGroups"`
+}
+
+func TestWycheproofCases(t *testing.T) {
+	suiteBytes, err := testutil.ReadWycheproofTests("kwp_test.json")
+	if err != nil {
+		t.Fatal(err)
 	}
 
-	for _, v := range vectors {
-		t.Run(fmt.Sprintf("Vector%d", v.id), func(t *testing.T) {
-			kek, err := hex.DecodeString(v.key)
-			if err != nil {
-				t.Fatal("hex.DecodeString(v.key) => bad kek")
-			}
+	suite := new(KwpSuite)
+	err = json.Unmarshal(suiteBytes, suite)
+	if err != nil {
+		t.Fatal(err)
+	}
 
-			msg, err := hex.DecodeString(v.msg)
-			if err != nil {
-				t.Fatal("hex.DecodeString(v.msg) => bad msg")
-			}
+	for _, group := range suite.Groups {
+		if group.KeySize == 192 {
+			continue
+		}
 
-			ct, err := hex.DecodeString(v.ct)
-			if err != nil {
-				t.Fatal("hex.DecodeString(v.ct) => bad ciphertext")
-			}
+		for _, test := range group.Tests {
+			caseName := fmt.Sprintf("%s-%s(%d):Case-%d",
+				suite.Algorithm, group.Type, group.KeySize, test.CaseID)
+			t.Run(caseName, func(t *testing.T) { runWycheproofCase(t, test) })
+		}
+	}
+}
 
-			cipher, err := kwp.NewKWP(kek)
-			if err != nil {
-				t.Fatalf("cannot create kwp, error: %v", err)
-			}
+func runWycheproofCase(t *testing.T, testCase *KwpCase) {
+	kek, err := hex.DecodeString(testCase.Key)
+	if err != nil {
+		t.Fatalf("hex.DecodeString(testCase.Key) => %v", err)
+	}
 
-			wrapped, err := cipher.Wrap(msg)
-			if err != nil {
-				t.Errorf("cannot wrap, error: %v", err)
-			} else if !bytes.Equal(ct, wrapped) {
-				t.Error("wrapped key mismatches test vector")
-			}
+	msg, err := hex.DecodeString(testCase.Message)
+	if err != nil {
+		t.Fatalf("hex.DecodeString(testCase.Message) => %v", err)
+	}
 
-			unwrapped, err := cipher.Unwrap(ct)
-			if err != nil {
-				t.Errorf("cannot unwrap, error: %v", err)
-			} else if !bytes.Equal(msg, unwrapped) {
-				t.Error("unwrapped key mismatches test vector")
-			}
-		})
+	ct, err := hex.DecodeString(testCase.Ciphertext)
+	if err != nil {
+		t.Fatalf("hex.DecodeString(testCase.Ciphertext) => %v", err)
+	}
+
+	cipher, err := kwp.NewKWP(kek)
+	if err != nil {
+		switch testCase.Result {
+		case "valid":
+			t.Fatalf("cannot create kwp, error: %v", err)
+		case "invalid", "acceptable":
+			return
+		}
+	}
+
+	wrapped, err := cipher.Wrap(msg)
+	switch testCase.Result {
+	case "valid":
+		if err != nil {
+			t.Errorf("cannot wrap, error: %v", err)
+		} else if !bytes.Equal(ct, wrapped) {
+			t.Error("wrapped key mismatches test vector")
+		}
+	case "invalid":
+		if err == nil && bytes.Equal(ct, wrapped) {
+			t.Error("no error and wrapped key matches test vector for invalid case")
+		}
+	case "acceptable":
+		if err == nil && !bytes.Equal(ct, wrapped) {
+			t.Error("no error and wrapped key mismatches test vector for acceptable case")
+		}
+	}
+
+	unwrapped, err := cipher.Unwrap(ct)
+	switch testCase.Result {
+	case "valid":
+		if err != nil {
+			t.Errorf("cannot unwrap, error: %v", err)
+		} else if !bytes.Equal(msg, unwrapped) {
+			t.Error("unwrapped key mismatches test vector")
+		}
+	case "invalid":
+		if err == nil {
+			t.Error("no error unwrapping invalid case")
+		}
+	case "acceptable":
+		if err == nil && !bytes.Equal(msg, unwrapped) {
+			t.Error("no error and unwrapped key mismatches plaintext")
+		}
 	}
 }
diff --git a/go/testutil/BUILD.bazel b/go/testutil/BUILD.bazel
index f365bb056..84e448d7b 100644
--- a/go/testutil/BUILD.bazel
+++ b/go/testutil/BUILD.bazel
@@ -7,6 +7,7 @@ go_library(
     srcs = [
         "constant.go",
         "testutil.go",
+        "wycheproofutil.go",
     ],
     importpath = "github.com/google/tink/go/testutil",
     deps = [
@@ -36,7 +37,13 @@ go_library(
 
 go_test(
     name = "go_default_test",
-    srcs = ["testutil_test.go"],
+    srcs = [
+        "testutil_test.go",
+        "wycheproofutil_test.go",
+    ],
+    data = [
+        "@wycheproof//testvectors:all",
+    ],
     deps = [
         "//go/testutil:go_default_library",
         "//go/tink:go_default_library",
diff --git a/go/testutil/wycheproofutil.go b/go/testutil/wycheproofutil.go
new file mode 100644
index 000000000..2615cfa1c
--- /dev/null
+++ b/go/testutil/wycheproofutil.go
@@ -0,0 +1,56 @@
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+package testutil
+
+import (
+	"fmt"
+	"io/ioutil"
+)
+
+// WycheproofSuite represents the common elements of the top level
+// object in a Wycheproof json file. Implementations should embed
+// WycheproofSuite in a struct that strongly types the testGroups
+// field. See wycheproofutil_test.go for an example.
+type WycheproofSuite struct {
+	Algorithm        string            `json:"algorithm"`
+	GeneratorVersion string            `json:"generatorVersion"`
+	NumberOfTests    int               `json:"numberOfTests"`
+	Notes            map[string]string `json:"notes"`
+}
+
+// WycheproofGroup represents the common elements of a testGroups
+// object in a Wycheproof suite. Implementations should embed
+// WycheproofGroup in a struct that strongly types its list of cases.
+// See wycheproofutil_test.go for an example.
+type WycheproofGroup struct {
+	Type string `json:"type"`
+}
+
+// WycheproofCase represents the common elements of a tests object
+// in a Wycheproof group. Implementation should embed WycheproofCase
+// in a struct that contains fields specific to the test type.
+// See wycheproofutil_test.go for an example.
+type WycheproofCase struct {
+	CaseID  int      `json:"tcId"`
+	Comment string   `json:"comment"`
+	Result  string   `json:"result"`
+	Flags   []string `json:"flags"`
+}
+
+// ReadWycheproofTests loads the named document (for example, "aes_gcm_test.json")
+// from the Wycheproof test vectors directory.
+func ReadWycheproofTests(filename string) ([]byte, error) {
+	return ioutil.ReadFile(fmt.Sprintf("../../../wycheproof/testvectors/%s", filename))
+}
diff --git a/go/testutil/wycheproofutil_test.go b/go/testutil/wycheproofutil_test.go
new file mode 100644
index 000000000..2b74cc887
--- /dev/null
+++ b/go/testutil/wycheproofutil_test.go
@@ -0,0 +1,64 @@
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+package testutil_test
+
+import (
+	"encoding/json"
+	"testing"
+
+	"github.com/google/tink/go/testutil"
+)
+
+func TestWycheproofParsing(t *testing.T) {
+
+	type AeadTest struct {
+		testutil.WycheproofCase
+		Key        string `json:"key"`
+		IV         string `json:"iv"`
+		AAD        string `json:"aad"`
+		Message    string `json:"msg"`
+		Ciphertext string `json:"ct"`
+		Tag        string `json:"tag"`
+	}
+
+	type AeadGroup struct {
+		testutil.WycheproofGroup
+		Tests []*AeadTest `json:"tests"`
+	}
+
+	type AeadSuite struct {
+		testutil.WycheproofSuite
+		TestGroups []*AeadGroup `json:"testGroups"`
+	}
+
+	bytes, err := testutil.ReadWycheproofTests("aes_gcm_test.json")
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	suite := new(AeadSuite)
+	err = json.Unmarshal(bytes, suite)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if suite.Algorithm != "AES-GCM" {
+		t.Errorf("suite.Algorithm=%s, want AES-GCM", suite.Algorithm)
+	}
+
+	if suite.TestGroups[0].Tests[0].Key == "" {
+		t.Error("suite.TestGroups[0].Tests[0].Key is empty")
+	}
+}
-- 
GitLab