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>
139 lines
3.5 KiB
Go
139 lines
3.5 KiB
Go
// 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))
|
|
}
|
|
}
|