internal/wycheproof: add Wycheproof tests for verifying signatures

https://github.com/google/wycheproof provides test vectors exposing
vulnerabilities in crypto packages. This change creates a new package
called internal/wycheproof that runs these Wycheproof tests against
a number of pacakages in the standard library (and in the future,
x/crypto).

Directory structure:
 - interal/wycheproof/internal/ecdsa: internal version of ecdsa package which
includes a new function that verifies ASN encoded signatures directly
 - interal/wycheproof/internal/dsa: internal version of dsa package which
includes a new function that verifies ASN encoded signatures directly
 - internal/wycheproof: all tests

internal/wycheproof/wycheproof_test.go provides utility functions that are
common to many tests in the package, and contains the TestMain which
fetches github.com/google/wycheproof from the source.

This change includes tests for signature verification with dsa, ecdsa,
eddsa, and rsa (both PKCS#1 v1.5 and PSS signatures).

Note that these tests download testdata from github.com/google/wycheproof
by running `go mod download` in the TestMain. This means that internet
access will be necessary in order to run these tests if the testdata is
not already in your module cache.

More tests will be added incrementally.

Change-Id: I0378d4be24b5679fdc186e9fc94c1cc0068e81f7
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/209221
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/README.md b/internal/wycheproof/README.md
new file mode 100644
index 0000000..8ae6c6c
--- /dev/null
+++ b/internal/wycheproof/README.md
@@ -0,0 +1,12 @@
+This package runs a set of the Wycheproof tests provided by
+https://github.com/google/wycheproof.
+
+The JSON test files live in
+https://github.com/google/wycheproof/tree/master/testvectors
+and are being fetched and cached at a pinned version every time
+these tests are run. To change the version of the wycheproof
+repository that is being used for testing, update wycheproofModVer.
+
+The structs for these tests are generated from the
+schemas provided in https://github.com/google/wycheproof/tree/master/schemas
+using https://github.com/a-h/generate.
\ No newline at end of file
diff --git a/internal/wycheproof/dsa_test.go b/internal/wycheproof/dsa_test.go
new file mode 100644
index 0000000..e554708
--- /dev/null
+++ b/internal/wycheproof/dsa_test.go
@@ -0,0 +1,123 @@
+// 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/dsa"
+	"testing"
+
+	wdsa "golang.org/x/crypto/internal/wycheproof/internal/dsa"
+)
+
+func TestDsa(t *testing.T) {
+	// AsnSignatureTestVector
+	type AsnSignatureTestVector struct {
+
+		// A brief description of the test case
+		Comment string `json:"comment,omitempty"`
+
+		// A list of flags
+		Flags []string `json:"flags,omitempty"`
+
+		// The message to sign
+		Msg string `json:"msg,omitempty"`
+
+		// Test result
+		Result string `json:"result,omitempty"`
+
+		// An ASN encoded signature for msg
+		Sig string `json:"sig,omitempty"`
+
+		// Identifier of the test case
+		TcId int `json:"tcId,omitempty"`
+	}
+
+	// DsaPublicKey
+	type DsaPublicKey struct {
+
+		// the generator of the multiplicative subgroup
+		G string `json:"g,omitempty"`
+
+		// the key size in bits
+		KeySize int `json:"keySize,omitempty"`
+
+		// the modulus p
+		P string `json:"p,omitempty"`
+
+		// the order of the generator g
+		Q string `json:"q,omitempty"`
+
+		// the key type
+		Type string `json:"type,omitempty"`
+
+		// the public key value
+		Y string `json:"y,omitempty"`
+	}
+
+	// DsaTestGroup
+	type DsaTestGroup struct {
+
+		// unenocded DSA public key
+		Key *DsaPublicKey `json:"key,omitempty"`
+
+		// DER encoded public key
+		KeyDer string `json:"keyDer,omitempty"`
+
+		// Pem encoded public key
+		KeyPem string `json:"keyPem,omitempty"`
+
+		// the hash function used for DSA
+		Sha   string                    `json:"sha,omitempty"`
+		Tests []*AsnSignatureTestVector `json:"tests,omitempty"`
+		Type  interface{}               `json:"type,omitempty"`
+	}
+
+	// Notes a description of the labels used in the test vectors
+	type Notes struct {
+	}
+
+	// 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    []*DsaTestGroup `json:"testGroups,omitempty"`
+	}
+
+	flagsShouldPass := map[string]bool{
+		// An encoded ASN.1 integer missing a leading zero is invalid, but accepted by some implementations.
+		"NoLeadingZero": false,
+	}
+
+	var root Root
+	readTestVector(t, "dsa_test.json", &root)
+	for _, tg := range root.TestGroups {
+		pub := decodePublicKey(tg.KeyDer).(*dsa.PublicKey)
+		h := parseHash(tg.Sha).New()
+		for _, sig := range tg.Tests {
+			h.Reset()
+			h.Write(decodeHex(sig.Msg))
+			hashed := h.Sum(nil)
+			hashed = hashed[:pub.Q.BitLen()/8] // Truncate to the byte-length of the subgroup (Q)
+			got := wdsa.VerifyASN1(pub, hashed, decodeHex(sig.Sig))
+			if want := shouldPass(sig.Result, sig.Flags, flagsShouldPass); got != want {
+				t.Errorf("tcid: %d, type: %s, comment: %q, wanted success: %t", sig.TcId, sig.Result, sig.Comment, want)
+			}
+		}
+	}
+}
diff --git a/internal/wycheproof/ecdsa_test.go b/internal/wycheproof/ecdsa_test.go
new file mode 100644
index 0000000..00043a1
--- /dev/null
+++ b/internal/wycheproof/ecdsa_test.go
@@ -0,0 +1,166 @@
+// 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/ecdsa"
+	"testing"
+
+	wecdsa "golang.org/x/crypto/internal/wycheproof/internal/ecdsa"
+)
+
+func TestEcdsa(t *testing.T) {
+	// AsnSignatureTestVector
+	type AsnSignatureTestVector struct {
+
+		// A brief description of the test case
+		Comment string `json:"comment,omitempty"`
+
+		// A list of flags
+		Flags []string `json:"flags,omitempty"`
+
+		// The message to sign
+		Msg string `json:"msg,omitempty"`
+
+		// Test result
+		Result string `json:"result,omitempty"`
+
+		// An ASN encoded signature for msg
+		Sig string `json:"sig,omitempty"`
+
+		// Identifier of the test case
+		TcId int `json:"tcId,omitempty"`
+	}
+
+	// EcPublicKey
+	type EcPublicKey struct {
+
+		// the EC group used by this public key
+		Curve interface{} `json:"curve,omitempty"`
+
+		// the key size in bits
+		KeySize int `json:"keySize,omitempty"`
+
+		// the key type
+		Type string `json:"type,omitempty"`
+
+		// encoded public key point
+		Uncompressed string `json:"uncompressed,omitempty"`
+
+		// the x-coordinate of the public key point
+		Wx string `json:"wx,omitempty"`
+
+		// the y-coordinate of the public key point
+		Wy string `json:"wy,omitempty"`
+	}
+
+	// EcUnnamedGroup
+	type EcUnnamedGroup struct {
+
+		// coefficient a of the elliptic curve equation
+		A string `json:"a,omitempty"`
+
+		// coefficient b of the elliptic curve equation
+		B string `json:"b,omitempty"`
+
+		// the x-coordinate of the generator
+		Gx string `json:"gx,omitempty"`
+
+		// the y-coordinate of the generator
+		Gy string `json:"gy,omitempty"`
+
+		// the cofactor
+		H int `json:"h,omitempty"`
+
+		// the order of the generator
+		N string `json:"n,omitempty"`
+
+		// the order of the underlying field
+		P string `json:"p,omitempty"`
+
+		// an unnamed EC group over a prime field in Weierstrass form
+		Type string `json:"type,omitempty"`
+	}
+
+	// EcdsaTestGroup
+	type EcdsaTestGroup struct {
+
+		// unenocded EC public key
+		Key *EcPublicKey `json:"key,omitempty"`
+
+		// DER encoded public key
+		KeyDer string `json:"keyDer,omitempty"`
+
+		// Pem encoded public key
+		KeyPem string `json:"keyPem,omitempty"`
+
+		// the hash function used for ECDSA
+		Sha   string                    `json:"sha,omitempty"`
+		Tests []*AsnSignatureTestVector `json:"tests,omitempty"`
+		Type  interface{}               `json:"type,omitempty"`
+	}
+
+	// Notes a description of the labels used in the test vectors
+	type Notes struct {
+	}
+
+	// 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    []*EcdsaTestGroup `json:"testGroups,omitempty"`
+	}
+
+	flagsShouldPass := map[string]bool{
+		// An encoded ASN.1 integer missing a leading zero is invalid, but accepted by some implementations.
+		"MissingZero": false,
+		// A signature using a weaker hash than the EC params is not a security risk, as long as the hash is secure.
+		// https://www.imperialviolet.org/2014/05/25/strengthmatching.html
+		"WeakHash": true,
+	}
+
+	// supportedCurves is a map of all elliptic curves supported
+	// by crypto/elliptic, which can subsequently be parsed and tested.
+	supportedCurves := map[string]bool{
+		"secp224r1": true,
+		"secp256r1": true,
+		"secp384r1": true,
+		"secp521r1": true,
+	}
+
+	var root Root
+	readTestVector(t, "ecdsa_test.json", &root)
+	for _, tg := range root.TestGroups {
+		curve := tg.Key.Curve.(string)
+		if !supportedCurves[curve] {
+			continue
+		}
+		pub := decodePublicKey(tg.KeyDer).(*ecdsa.PublicKey)
+		h := parseHash(tg.Sha).New()
+		for _, sig := range tg.Tests {
+			h.Reset()
+			h.Write(decodeHex(sig.Msg))
+			hashed := h.Sum(nil)
+			got := wecdsa.VerifyASN1(pub, hashed, decodeHex(sig.Sig))
+			if want := shouldPass(sig.Result, sig.Flags, flagsShouldPass); got != want {
+				t.Errorf("tcid: %d, type: %s, comment: %q, wanted success: %t", sig.TcId, sig.Result, sig.Comment, want)
+			}
+		}
+	}
+}
diff --git a/internal/wycheproof/eddsa_test.go b/internal/wycheproof/eddsa_test.go
new file mode 100644
index 0000000..06c9829
--- /dev/null
+++ b/internal/wycheproof/eddsa_test.go
@@ -0,0 +1,100 @@
+// 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.
+
+// +build go1.13
+
+package wycheproof
+
+import (
+	"testing"
+
+	"golang.org/x/crypto/ed25519"
+)
+
+func TestEddsa(t *testing.T) {
+	// Jwk the private key in webcrypto format
+	type Jwk struct {
+	}
+
+	// Key unencoded key pair
+	type Key struct {
+	}
+
+	// Notes a description of the labels used in the test vectors
+	type Notes struct {
+	}
+
+	// SignatureTestVector
+	type SignatureTestVector struct {
+
+		// A brief description of the test case
+		Comment string `json:"comment,omitempty"`
+
+		// A list of flags
+		Flags []string `json:"flags,omitempty"`
+
+		// The message to sign
+		Msg string `json:"msg,omitempty"`
+
+		// Test result
+		Result string `json:"result,omitempty"`
+
+		// A signature for msg
+		Sig string `json:"sig,omitempty"`
+
+		// Identifier of the test case
+		TcId int `json:"tcId,omitempty"`
+	}
+
+	// EddsaTestGroup
+	type EddsaTestGroup struct {
+
+		// the private key in webcrypto format
+		Jwk *Jwk `json:"jwk,omitempty"`
+
+		// unencoded key pair
+		Key *Key `json:"key,omitempty"`
+
+		// Asn encoded public key
+		KeyDer string `json:"keyDer,omitempty"`
+
+		// Pem encoded public key
+		KeyPem string                 `json:"keyPem,omitempty"`
+		Tests  []*SignatureTestVector `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    []*EddsaTestGroup `json:"testGroups,omitempty"`
+	}
+
+	var root Root
+	readTestVector(t, "eddsa_test.json", &root)
+	for _, tg := range root.TestGroups {
+		pub := decodePublicKey(tg.KeyDer).(ed25519.PublicKey)
+		for _, sig := range tg.Tests {
+			got := ed25519.Verify(pub, decodeHex(sig.Msg), decodeHex(sig.Sig))
+			if want := shouldPass(sig.Result, sig.Flags, nil); got != want {
+				t.Errorf("tcid: %d, type: %s, comment: %q, wanted success: %t", sig.TcId, sig.Result, sig.Comment, want)
+			}
+		}
+	}
+}
diff --git a/internal/wycheproof/internal/dsa/dsa.go b/internal/wycheproof/internal/dsa/dsa.go
new file mode 100644
index 0000000..3101dfc
--- /dev/null
+++ b/internal/wycheproof/internal/dsa/dsa.go
@@ -0,0 +1,33 @@
+// 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 dsa provides an internal version of dsa.Verify
+// that is used for the Wycheproof tests.
+package dsa
+
+import (
+	"crypto/dsa"
+	"math/big"
+
+	"golang.org/x/crypto/cryptobyte"
+	"golang.org/x/crypto/cryptobyte/asn1"
+)
+
+// VerifyASN1 verifies the ASN1 encoded signature, sig, of hash using the
+// public key, pub. Its return value records whether the signature is valid.
+func VerifyASN1(pub *dsa.PublicKey, hash, sig []byte) bool {
+	var (
+		r, s  = &big.Int{}, &big.Int{}
+		inner cryptobyte.String
+	)
+	input := cryptobyte.String(sig)
+	if !input.ReadASN1(&inner, asn1.SEQUENCE) ||
+		!input.Empty() ||
+		!inner.ReadASN1Integer(r) ||
+		!inner.ReadASN1Integer(s) ||
+		!inner.Empty() {
+		return false
+	}
+	return dsa.Verify(pub, hash, r, s)
+}
diff --git a/internal/wycheproof/internal/ecdsa/ecdsa.go b/internal/wycheproof/internal/ecdsa/ecdsa.go
new file mode 100644
index 0000000..4246c94
--- /dev/null
+++ b/internal/wycheproof/internal/ecdsa/ecdsa.go
@@ -0,0 +1,33 @@
+// 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 ecdsa provides an internal version of ecdsa.Verify
+// that is used for the Wycheproof tests.
+package ecdsa
+
+import (
+	"crypto/ecdsa"
+	"math/big"
+
+	"golang.org/x/crypto/cryptobyte"
+	"golang.org/x/crypto/cryptobyte/asn1"
+)
+
+// VerifyASN1 verifies the ASN1 encoded signature, sig, of hash using the
+// public key, pub. Its return value records whether the signature is valid.
+func VerifyASN1(pub *ecdsa.PublicKey, hash, sig []byte) bool {
+	var (
+		r, s  = &big.Int{}, &big.Int{}
+		inner cryptobyte.String
+	)
+	input := cryptobyte.String(sig)
+	if !input.ReadASN1(&inner, asn1.SEQUENCE) ||
+		!input.Empty() ||
+		!inner.ReadASN1Integer(r) ||
+		!inner.ReadASN1Integer(s) ||
+		!inner.Empty() {
+		return false
+	}
+	return ecdsa.Verify(pub, hash, r, s)
+}
diff --git a/internal/wycheproof/rsa_pss_test.go b/internal/wycheproof/rsa_pss_test.go
new file mode 100644
index 0000000..fc8fa0e
--- /dev/null
+++ b/internal/wycheproof/rsa_pss_test.go
@@ -0,0 +1,164 @@
+// 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/rsa"
+	"testing"
+)
+
+func TestRsaPss(t *testing.T) {
+	// KeyJwk Public key in JWK format
+	type KeyJwk struct {
+	}
+
+	// Notes a description of the labels used in the test vectors
+	type Notes struct {
+	}
+
+	// SignatureTestVector
+	type SignatureTestVector struct {
+
+		// A brief description of the test case
+		Comment string `json:"comment,omitempty"`
+
+		// A list of flags
+		Flags []string `json:"flags,omitempty"`
+
+		// The message to sign
+		Msg string `json:"msg,omitempty"`
+
+		// Test result
+		Result string `json:"result,omitempty"`
+
+		// A signature for msg
+		Sig string `json:"sig,omitempty"`
+
+		// Identifier of the test case
+		TcId int `json:"tcId,omitempty"`
+	}
+
+	// RsassaPkcs1TestGroup
+	type RsassaPkcs1TestGroup struct {
+
+		// The private exponent
+		D string `json:"d,omitempty"`
+
+		// The public exponent
+		E string `json:"e,omitempty"`
+
+		// ASN encoding of the sequence [n, e]
+		KeyAsn string `json:"keyAsn,omitempty"`
+
+		// ASN encoding of the public key
+		KeyDer string `json:"keyDer,omitempty"`
+
+		// Public key in JWK format
+		KeyJwk *KeyJwk `json:"keyJwk,omitempty"`
+
+		// Pem encoded public key
+		KeyPem string `json:"keyPem,omitempty"`
+
+		// the size of the modulus in bits
+		KeySize int `json:"keySize,omitempty"`
+
+		// The modulus of the key
+		N string `json:"n,omitempty"`
+
+		// The salt length
+		SLen int `json:"sLen,omitempty"`
+
+		// the hash function used for the message
+		Sha   string                 `json:"sha,omitempty"`
+		Tests []*SignatureTestVector `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    []*RsassaPkcs1TestGroup `json:"testGroups,omitempty"`
+	}
+
+	flagsShouldPass := map[string]bool{
+		// A signature using a weaker hash than the EC params is not a security risk, as long as the hash is secure.
+		// https://www.imperialviolet.org/2014/05/25/strengthmatching.html
+		"WeakHash": true,
+	}
+
+	// filesOverrideToPassZeroSLen is a map of all test files
+	// and which TcIds that should be overriden to pass if the
+	// rsa.PSSOptions.SaltLength is zero.
+	// These tests expect a failure with a PSSOptions.SaltLength: 0
+	// and a signature that uses a different salt length. However,
+	// a salt length of 0 is defined as rsa.PSSSaltLengthAuto which
+	// works deterministically to auto-detect the length when
+	// verifying, so these tests actually pass as they should.
+	filesOverrideToPassZeroSLen := map[string][]int{
+		"rsa_pss_2048_sha1_mgf1_20_test.json":       []int{46, 47},
+		"rsa_pss_2048_sha256_mgf1_0_test.json":      []int{67, 68},
+		"rsa_pss_2048_sha256_mgf1_32_test.json":     []int{67, 68},
+		"rsa_pss_2048_sha512_256_mgf1_28_test.json": []int{13, 14, 15},
+		"rsa_pss_2048_sha512_256_mgf1_32_test.json": []int{13, 14},
+		"rsa_pss_3072_sha256_mgf1_32_test.json":     []int{67, 68},
+		"rsa_pss_4096_sha256_mgf1_32_test.json":     []int{67, 68},
+		"rsa_pss_4096_sha512_mgf1_32_test.json":     []int{136, 137},
+		// "rsa_pss_misc_test.json": nil,  // TODO: This ones seems to be broken right now, but can enable later on.
+	}
+
+	for f := range filesOverrideToPassZeroSLen {
+		var root Root
+		readTestVector(t, f, &root)
+		for _, tg := range root.TestGroups {
+			pub := decodePublicKey(tg.KeyDer).(*rsa.PublicKey)
+			ch := parseHash(tg.Sha)
+			h := ch.New()
+			opts := &rsa.PSSOptions{
+				Hash:       ch,
+				SaltLength: rsa.PSSSaltLengthAuto,
+			}
+			// Run all the tests twice: the first time with the salt length
+			// as PSSSaltLengthAuto, and the second time with the salt length
+			// explictily set to tg.SLen.
+			for i := 0; i < 2; i++ {
+				for _, sig := range tg.Tests {
+					h.Reset()
+					h.Write(decodeHex(sig.Msg))
+					hashed := h.Sum(nil)
+					err := rsa.VerifyPSS(pub, ch, hashed, decodeHex(sig.Sig), opts)
+					want := shouldPass(sig.Result, sig.Flags, flagsShouldPass)
+					if opts.SaltLength == 0 {
+						for _, id := range filesOverrideToPassZeroSLen[f] {
+							if sig.TcId == id {
+								want = true
+								break
+							}
+						}
+					}
+					if (err == nil) != want {
+						t.Errorf("file: %v, tcid: %d, type: %s, opts.SaltLength: %v, comment: %q, wanted success: %t", f, sig.TcId, sig.Result, opts.SaltLength, sig.Comment, want)
+					}
+				}
+				// Update opts.SaltLength for the second run of the tests.
+				opts.SaltLength = tg.SLen
+			}
+		}
+	}
+}
diff --git a/internal/wycheproof/rsa_signature_test.go b/internal/wycheproof/rsa_signature_test.go
new file mode 100644
index 0000000..3c31c22
--- /dev/null
+++ b/internal/wycheproof/rsa_signature_test.go
@@ -0,0 +1,123 @@
+// 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/rsa"
+	"testing"
+)
+
+func TestRsa(t *testing.T) {
+	// KeyJwk Public key in JWK format
+	type KeyJwk struct {
+	}
+
+	// Notes a description of the labels used in the test vectors
+	type Notes struct {
+	}
+
+	// SignatureTestVector
+	type SignatureTestVector struct {
+
+		// A brief description of the test case
+		Comment string `json:"comment,omitempty"`
+
+		// A list of flags
+		Flags []string `json:"flags,omitempty"`
+
+		// The message to sign
+		Msg string `json:"msg,omitempty"`
+
+		// Test result
+		Result string `json:"result,omitempty"`
+
+		// A signature for msg
+		Sig string `json:"sig,omitempty"`
+
+		// Identifier of the test case
+		TcId int `json:"tcId,omitempty"`
+	}
+
+	// RsassaPkcs1TestGroup
+	type RsassaPkcs1TestGroup struct {
+
+		// The private exponent
+		D string `json:"d,omitempty"`
+
+		// The public exponent
+		E string `json:"e,omitempty"`
+
+		// ASN encoding of the sequence [n, e]
+		KeyAsn string `json:"keyAsn,omitempty"`
+
+		// ASN encoding of the public key
+		KeyDer string `json:"keyDer,omitempty"`
+
+		// Public key in JWK format
+		KeyJwk *KeyJwk `json:"keyJwk,omitempty"`
+
+		// Pem encoded public key
+		KeyPem string `json:"keyPem,omitempty"`
+
+		// the size of the modulus in bits
+		KeySize int `json:"keySize,omitempty"`
+
+		// The modulus of the key
+		N string `json:"n,omitempty"`
+
+		// the hash function used for the message
+		Sha   string                 `json:"sha,omitempty"`
+		Tests []*SignatureTestVector `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    []*RsassaPkcs1TestGroup `json:"testGroups,omitempty"`
+	}
+
+	flagsShouldPass := map[string]bool{
+		// Omitting the parameter field in an ASN encoded integer is a legacy behavior.
+		"MissingNull": false,
+		// Keys with a modulus less than 2048 bits are supported by crypto/rsa.
+		"SmallModulus": true,
+		// Small public keys are supported by crypto/rsa.
+		"SmallPublicKey": true,
+	}
+
+	var root Root
+	readTestVector(t, "rsa_signature_test.json", &root)
+	for _, tg := range root.TestGroups {
+		pub := decodePublicKey(tg.KeyDer).(*rsa.PublicKey)
+		ch := parseHash(tg.Sha)
+		h := ch.New()
+		for _, sig := range tg.Tests {
+			h.Reset()
+			h.Write(decodeHex(sig.Msg))
+			hashed := h.Sum(nil)
+			err := rsa.VerifyPKCS1v15(pub, ch, hashed, decodeHex(sig.Sig))
+			want := shouldPass(sig.Result, sig.Flags, flagsShouldPass)
+			if (err == nil) != want {
+				t.Errorf("tcid: %d, type: %s, comment: %q, wanted success: %t", sig.TcId, sig.Result, sig.Comment, want)
+			}
+		}
+	}
+}
diff --git a/internal/wycheproof/wycheproof_test.go b/internal/wycheproof/wycheproof_test.go
new file mode 100644
index 0000000..be6557a
--- /dev/null
+++ b/internal/wycheproof/wycheproof_test.go
@@ -0,0 +1,138 @@
+// 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 runs a set of the Wycheproof tests
+// provided by https://github.com/google/wycheproof.
+package wycheproof
+
+import (
+	"crypto"
+	"crypto/x509"
+	"encoding/hex"
+	"encoding/json"
+	"fmt"
+	"io/ioutil"
+	"log"
+	"os"
+	"os/exec"
+	"path/filepath"
+	"runtime"
+	"testing"
+
+	_ "crypto/sha1"
+	_ "crypto/sha256"
+	_ "crypto/sha512"
+)
+
+const wycheproofModVer = "v0.0.0-20191126014559-06e5e105eeb9"
+
+var wycheproofTestVectorsDir string
+
+func TestMain(m *testing.M) {
+	if _, err := exec.LookPath("go"); err != nil {
+		log.Printf("skipping test because 'go' command is unavailable: %v", err)
+		os.Exit(0)
+	}
+	if runtime.GOARCH == "386" {
+		os.Exit(0) // skip tests
+	}
+
+	// Download the JSON test files from github.com/google/wycheproof
+	// using `go mod download -json` so the cached source of the testdata
+	// can be used in the following tests.
+	path := "github.com/google/wycheproof@" + wycheproofModVer
+	cmd := exec.Command("go", "mod", "download", "-json", path)
+	// TODO: enable the sumdb once the Trybots proxy supports it.
+	cmd.Env = append(os.Environ(), "GONOSUMDB=*")
+	output, err := cmd.Output()
+	if err != nil {
+		log.Fatalf("failed to run `go mod download -json %s`, output: %s", path, output)
+	}
+	var dm struct {
+		Dir string // absolute path to cached source root directory
+	}
+	if err := json.Unmarshal(output, &dm); err != nil {
+		log.Fatal(err)
+	}
+	// Now that the module has been downloaded, use the absolute path of the
+	// cached source as the root directory for all tests going forward.
+	wycheproofTestVectorsDir = filepath.Join(dm.Dir, "testvectors")
+	os.Exit(m.Run())
+}
+
+func readTestVector(t *testing.T, f string, dest interface{}) {
+	b, err := ioutil.ReadFile(filepath.Join(wycheproofTestVectorsDir, f))
+	if err != nil {
+		t.Fatalf("failed to read json file: %v", err)
+	}
+	if err := json.Unmarshal(b, &dest); err != nil {
+		t.Fatalf("failed to unmarshal json file: %v", err)
+	}
+}
+
+func decodeHex(s string) []byte {
+	b, err := hex.DecodeString(s)
+	if err != nil {
+		panic(err)
+	}
+	return b
+}
+
+func decodePublicKey(der string) interface{} {
+	d := decodeHex(der)
+	pub, err := x509.ParsePKIXPublicKey(d)
+	if err != nil {
+		panic(fmt.Sprintf("failed to parse DER encoded public key: %v", err))
+	}
+	return pub
+}
+
+func parseHash(h string) crypto.Hash {
+	switch h {
+	case "SHA-1":
+		return crypto.SHA1
+	case "SHA-256":
+		return crypto.SHA256
+	case "SHA-224":
+		return crypto.SHA224
+	case "SHA-384":
+		return crypto.SHA384
+	case "SHA-512":
+		return crypto.SHA512
+	case "SHA-512/224":
+		return crypto.SHA512_224
+	case "SHA-512/256":
+		return crypto.SHA512_256
+	default:
+		panic(fmt.Sprintf("could not identify SHA hash algorithm: %q", h))
+	}
+}
+
+// shouldPass returns whether or not the test should pass.
+// flagsShouldPass is a map associated with whether or not
+// a flag for an "acceptable" result should pass.
+// Every possible flag value that's associated with an
+// "acceptable" result should be explicitly specified,
+// otherwise the test will panic.
+func shouldPass(result string, flags []string, flagsShouldPass map[string]bool) bool {
+	switch result {
+	case "valid":
+		return true
+	case "invalid":
+		return false
+	case "acceptable":
+		for _, flag := range flags {
+			pass, ok := flagsShouldPass[flag]
+			if !ok {
+				panic(fmt.Sprintf("unspecified flag: %q", flag))
+			}
+			if !pass {
+				return false
+			}
+		}
+		return true // There are no flags, or all are meant to pass.
+	default:
+		panic(fmt.Sprintf("unexpected result: %v", result))
+	}
+}