internal/wycheproof: add test for ChaCha20-Poly1305 AEAD encryption/decryption

Change-Id: I71d62f95954a39c476bee55e22b6fcf96a196de1
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/214939
Run-TryBot: Katie Hockman <katie@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Filippo Valsorda <filippo@golang.org>
diff --git a/internal/wycheproof/chacha20_poly1305_test.go b/internal/wycheproof/chacha20_poly1305_test.go
new file mode 100644
index 0000000..7fedbc4
--- /dev/null
+++ b/internal/wycheproof/chacha20_poly1305_test.go
@@ -0,0 +1,148 @@
+// Copyright 2019 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package wycheproof
+
+import (
+	"crypto/cipher"
+	"encoding/hex"
+	"testing"
+
+	"golang.org/x/crypto/chacha20poly1305"
+)
+
+func TestChaCha20Poly1305(t *testing.T) {
+	// AeadTestVector
+	type AeadTestVector struct {
+
+		// additional authenticated data
+		Aad string `json:"aad,omitempty"`
+
+		// A brief description of the test case
+		Comment string `json:"comment,omitempty"`
+
+		// the ciphertext (without iv and tag)
+		Ct string `json:"ct,omitempty"`
+
+		// A list of flags
+		Flags []string `json:"flags,omitempty"`
+
+		// the nonce
+		Iv string `json:"iv,omitempty"`
+
+		// the key
+		Key string `json:"key,omitempty"`
+
+		// the plaintext
+		Msg string `json:"msg,omitempty"`
+
+		// Test result
+		Result string `json:"result,omitempty"`
+
+		// the authentication tag
+		Tag string `json:"tag,omitempty"`
+
+		// Identifier of the test case
+		TcId int `json:"tcId,omitempty"`
+	}
+
+	// Notes a description of the labels used in the test vectors
+	type Notes struct {
+	}
+
+	// AeadTestGroup
+	type AeadTestGroup struct {
+
+		// the IV size in bits
+		IvSize int `json:"ivSize,omitempty"`
+
+		// the keySize in bits
+		KeySize int `json:"keySize,omitempty"`
+
+		// the expected size of the tag in bits
+		TagSize int               `json:"tagSize,omitempty"`
+		Tests   []*AeadTestVector `json:"tests,omitempty"`
+		Type    interface{}       `json:"type,omitempty"`
+	}
+
+	// Root
+	type Root struct {
+
+		// the primitive tested in the test file
+		Algorithm string `json:"algorithm,omitempty"`
+
+		// the version of the test vectors.
+		GeneratorVersion string `json:"generatorVersion,omitempty"`
+
+		// additional documentation
+		Header []string `json:"header,omitempty"`
+
+		// a description of the labels used in the test vectors
+		Notes *Notes `json:"notes,omitempty"`
+
+		// the number of test vectors in this test
+		NumberOfTests int              `json:"numberOfTests,omitempty"`
+		Schema        interface{}      `json:"schema,omitempty"`
+		TestGroups    []*AeadTestGroup `json:"testGroups,omitempty"`
+	}
+
+	testAeadSealOpen := func(t *testing.T, aead cipher.AEAD, tv *AeadTestVector, recoverBadNonce func()) {
+		defer recoverBadNonce()
+
+		// Encrypt the message, then decrypt the new ciphertext and validate
+		// the decrypted message.
+		ciphertext := aead.Seal(nil, decodeHex(tv.Iv), decodeHex(tv.Msg), decodeHex(tv.Aad))
+		msg, err := aead.Open(nil, decodeHex(tv.Iv), ciphertext, decodeHex(tv.Aad))
+		if err != nil {
+			t.Fatalf("#%d: decryption failed: %v", tv.TcId, err)
+		}
+		if got, want := hex.EncodeToString(msg), tv.Msg; got != want {
+			t.Errorf("#%d: bad message after encrypting and decrypting: %s, want %v", tv.TcId, got, want)
+		}
+
+		// Decrypt the provided ciphertext and validate the decrypted message.
+		tv.Ct += tv.Tag // append the tag to the ciphertext
+		msg2, err := aead.Open(nil, decodeHex(tv.Iv), decodeHex(tv.Ct), decodeHex(tv.Aad))
+		wantPass := shouldPass(tv.Result, tv.Flags, nil)
+		if wantPass {
+			if err != nil {
+				t.Errorf("#%d, type: %s, comment: %q, decryption wanted success, got err: %v", tv.TcId, tv.Result, tv.Comment, err)
+			}
+			if got, want := hex.EncodeToString(ciphertext), tv.Ct; got != want {
+				t.Errorf("#%d: ciphertext doesn't match: %s, want=%s", tv.TcId, got, want)
+			}
+			if got, want := hex.EncodeToString(msg2), tv.Msg; got != want {
+				t.Errorf("#%d: bad message after decrypting ciphertext: %s, want %v", tv.TcId, got, want)
+			}
+		} else {
+			if err == nil {
+				t.Errorf("#%d, type: %s, comment: %q, decryption wanted error", tv.TcId, tv.Result, tv.Comment)
+			}
+		}
+	}
+
+	var root Root
+	readTestVector(t, "chacha20_poly1305_test.json", &root)
+	for _, tg := range root.TestGroups {
+		for _, tv := range tg.Tests {
+			aead, err := chacha20poly1305.New(decodeHex(tv.Key))
+			if err != nil {
+				t.Fatalf("#%d: %v", tv.TcId, err)
+			}
+			if tg.TagSize/8 != aead.Overhead() {
+				t.Fatalf("#%d: bad tag length", tv.TcId)
+			}
+			testAeadSealOpen(t, aead, tv, func() {
+				// A bad nonce causes a panic in AEAD.Seal and AEAD.Open,
+				// so should be recovered. Fail the test if it broke for
+				// some other reason.
+				if r := recover(); r != nil {
+					if tg.IvSize/8 == chacha20poly1305.NonceSize {
+						t.Errorf("#%d: unexpected panic", tv.TcId)
+					}
+				}
+			})
+		}
+	}
+}