mirror of
https://github.com/golang/go.git
synced 2026-06-27 03:11:23 +00:00
crypto/internal/cryptotest: add Wycheproof schema/helpers
To assist in porting the x/crypto Wycheproof test coverage of the standard library cryptography into the standard library this commit adds generated schema types and helper utilities for consuming Wycheproof test vectors. In x/crypto we hand-generated structures corresponding to the test vector data, but this both falls out of sync quickly and makes adding new vectors tedious. Instead, we create a sub module that can do this task automatically using the upstream project's Go module, and the atombender/go-jsonschema tool. We handle this part in a sub module to avoid new stdlib dependencies, instead vendoring the generated schema types that have no dependencies outside of the stdlib. Alongside the generated schema types we add helpers that the individual algorithm tests use to load JSON data into the schema types, and decide whether test cases should pass/fail based on the result and flags. Change-Id: I04b4d7307f11ac93deb175ec2d087004b6368af0 Reviewed-on: https://go-review.googlesource.com/c/go/+/748582 Auto-Submit: Roland Shoemaker <roland@golang.org> TryBot-Bypass: Filippo Valsorda <filippo@golang.org> Reviewed-by: Roland Shoemaker <roland@golang.org> Reviewed-by: David Chase <drchase@google.com> Reviewed-by: Filippo Valsorda <filippo@golang.org>
This commit is contained in:
parent
3e1c31701c
commit
d2095798a1
8 changed files with 9802 additions and 6 deletions
|
|
@ -355,9 +355,10 @@ func TestStdKen(t *testing.T) {
|
|||
var excluded = map[string]bool{
|
||||
"builtin": true,
|
||||
"cmd/compile/internal/ssa/_gen": true,
|
||||
"runtime/_mkmalloc": true,
|
||||
"simd/archsimd/_gen/simdgen": true,
|
||||
"simd/archsimd/_gen/unify": true,
|
||||
"crypto/internal/cryptotest/wycheproof/_schema": true,
|
||||
"runtime/_mkmalloc": true,
|
||||
"simd/archsimd/_gen/simdgen": true,
|
||||
"simd/archsimd/_gen/unify": true,
|
||||
}
|
||||
|
||||
// printPackageMu synchronizes the printing of type-checked package files in
|
||||
|
|
|
|||
17
src/crypto/internal/cryptotest/wycheproof/_schema/go.mod
Normal file
17
src/crypto/internal/cryptotest/wycheproof/_schema/go.mod
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
module crypto/internal/cryptotest/wycheproof/_schema
|
||||
|
||||
go 1.26
|
||||
|
||||
require (
|
||||
github.com/atombender/go-jsonschema v0.22.0
|
||||
github.com/c2sp/wycheproof v0.0.0-20260428174413-4d535535851f
|
||||
)
|
||||
|
||||
require (
|
||||
dario.cat/mergo v1.0.2 // indirect
|
||||
github.com/goccy/go-yaml v1.19.2 // indirect
|
||||
github.com/google/go-cmp v0.7.0 // indirect
|
||||
github.com/mitchellh/go-wordwrap v1.0.1 // indirect
|
||||
github.com/sanity-io/litter v1.5.8 // indirect
|
||||
github.com/sosodev/duration v1.3.1 // indirect
|
||||
)
|
||||
27
src/crypto/internal/cryptotest/wycheproof/_schema/go.sum
Normal file
27
src/crypto/internal/cryptotest/wycheproof/_schema/go.sum
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8=
|
||||
dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA=
|
||||
github.com/atombender/go-jsonschema v0.22.0 h1:7H48X5fUccsfsacar5UfP6nnOXuQzmnr6lQmH/Fj2pQ=
|
||||
github.com/atombender/go-jsonschema v0.22.0/go.mod h1:8Q281v0ozTIfvdnbwDoWQDIk0syH6F0Fpoq+Z1cs+rM=
|
||||
github.com/c2sp/wycheproof v0.0.0-20260428174413-4d535535851f h1:6z0faT+gbElCKVknQwZnn2qJbdJR9HtwztdczFm4/Ns=
|
||||
github.com/c2sp/wycheproof v0.0.0-20260428174413-4d535535851f/go.mod h1:Gab+FzXqmNn5Uzs1+9wyVUe/Dao7XiyY3xfx2Kg+IGM=
|
||||
github.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM=
|
||||
github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0=
|
||||
github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0=
|
||||
github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/sanity-io/litter v1.5.8 h1:uM/2lKrWdGbRXDrIq08Lh9XtVYoeGtcQxk9rtQ7+rYg=
|
||||
github.com/sanity-io/litter v1.5.8/go.mod h1:9gzJgR2i4ZpjZHsKvUXIRQVk7P+yM3e+jAF7bU2UI5U=
|
||||
github.com/sosodev/duration v1.3.1 h1:qtHBDMQ6lvMQsL15g4aopM4HEfOaYuhWBw3NPTtlqq4=
|
||||
github.com/sosodev/duration v1.3.1/go.mod h1:RQIBBX0+fMLc/D9+Jb/fwvVmo0eZvDDEERAikUR6SDg=
|
||||
github.com/stretchr/testify v0.0.0-20161117074351-18a02ba4a312/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
// Copyright 2026 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.
|
||||
|
||||
// A stand-alone Go module that generates ../schema.go using the
|
||||
// upstream Wycheproof JSON schema documents.
|
||||
//
|
||||
// We maintain this in a separate Go module and vendor the resulting
|
||||
// generated .go code to avoid the standard library taking a direct
|
||||
// dependency on the c2sp/wycheproof or atombender/go-jsonschema modules.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"io/fs"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/atombender/go-jsonschema/pkg/generator"
|
||||
"github.com/c2sp/wycheproof"
|
||||
)
|
||||
|
||||
func main() {
|
||||
ouputName := "schema.go"
|
||||
cfg := generator.Config{
|
||||
DefaultPackageName: "wycheproof",
|
||||
DefaultOutputName: ouputName,
|
||||
Tags: []string{"json"},
|
||||
Warner: func(message string) {
|
||||
log.Printf("go-jsonschema: %s", message)
|
||||
},
|
||||
}
|
||||
gen, err := generator.New(cfg)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Without upstream modifications we can't use the embedded Wycheproof
|
||||
// schema FS directly w/ go-jsonschema and instead make files in a tempdir
|
||||
// on the native FS. See https://github.com/omissis/go-jsonschema/issues/495
|
||||
schemaDir, err := os.MkdirTemp("", "*-wycheproof")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(schemaDir)
|
||||
|
||||
rawSchemas, _ := fs.ReadDir(wycheproof.Schemas, ".")
|
||||
for _, entry := range rawSchemas {
|
||||
entryName := entry.Name()
|
||||
schemaFile := filepath.Join(schemaDir, entryName)
|
||||
|
||||
schemaData, err := fs.ReadFile(wycheproof.Schemas, entryName)
|
||||
if err != nil {
|
||||
log.Fatalf("reading %s: %v", entryName, err)
|
||||
}
|
||||
err = os.WriteFile(schemaFile, schemaData, 0644)
|
||||
if err != nil {
|
||||
log.Fatalf("writing %s: %v", schemaFile, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Note: it's important we process schemas in a second pass after writing
|
||||
// **all** of the schema file content to disk so that go-jsonschema can
|
||||
// resolve cross-file references.
|
||||
for _, entry := range rawSchemas {
|
||||
entryName := entry.Name()
|
||||
schemaFile := filepath.Join(schemaDir, entryName)
|
||||
|
||||
err = gen.DoFile(schemaFile)
|
||||
if err != nil {
|
||||
log.Fatalf("processing %s: %v", schemaFile, err)
|
||||
}
|
||||
}
|
||||
|
||||
sources, err := gen.Sources()
|
||||
if err != nil {
|
||||
log.Fatalf("error generating sources: %v\n", err)
|
||||
}
|
||||
if sourceCount := len(sources); sourceCount != 1 {
|
||||
log.Fatalf("expected to generate 1 source file, got %d\n", sourceCount)
|
||||
}
|
||||
content, ok := sources[ouputName]
|
||||
if !ok {
|
||||
log.Fatalf("missing generated %q output file source", ouputName)
|
||||
}
|
||||
outFile := filepath.Join("../", ouputName)
|
||||
if err := os.WriteFile(outFile, content, 0644); err != nil {
|
||||
log.Fatalf("error writing file %s: %v\n", outFile, err)
|
||||
}
|
||||
}
|
||||
9465
src/crypto/internal/cryptotest/wycheproof/schema.go
Normal file
9465
src/crypto/internal/cryptotest/wycheproof/schema.go
Normal file
File diff suppressed because it is too large
Load diff
191
src/crypto/internal/cryptotest/wycheproof/wycheproof.go
Normal file
191
src/crypto/internal/cryptotest/wycheproof/wycheproof.go
Normal file
|
|
@ -0,0 +1,191 @@
|
|||
// Copyright 2026 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 provides helper utilities for writing tests that
|
||||
// rely on Wycheproof test vector schemas and JSON vector data.
|
||||
// See https://github.com/C2SP/wycheproof for more information.
|
||||
package wycheproof
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"crypto"
|
||||
"crypto/internal/cryptotest"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"internal/testenv"
|
||||
"os"
|
||||
"path"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// LoadVectorFile unmarshals Wycheproof JSON test vector file by name.
|
||||
//
|
||||
// Typically, the value argument will be a pointer to a Wycheproof schema
|
||||
// type representing the in-memory structure of the JSON data.
|
||||
//
|
||||
// Panics if there is an error reading the Wycheproof JSON vector data file,
|
||||
// or if it can't be unmarshalled into the provided value.
|
||||
func LoadVectorFile(t *testing.T, filename string, value any) {
|
||||
testenv.SkipIfShortAndSlow(t)
|
||||
|
||||
// We want to avoid a dependency on c2sp/wycheproof or the schema generator
|
||||
// in this stdlib code, so we fetch the module at runtime and read the
|
||||
// vector JSON from that module clone.
|
||||
wycheproofDir := cryptotest.FetchModule(
|
||||
t, "github.com/c2sp/wycheproof", findVersionFromSum(t))
|
||||
|
||||
content, err := os.ReadFile(path.Join(wycheproofDir, "testvectors_v1", filename))
|
||||
if err != nil {
|
||||
t.Fatalf("missing Wycheproof vector file %q: %v", filename, err)
|
||||
}
|
||||
|
||||
err = json.Unmarshal(content, value)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to unmarshal vector file %q: %v", filename, err)
|
||||
}
|
||||
}
|
||||
|
||||
// To make sure this code fetches the same module version as we used to
|
||||
// generate the vendored schema.go we parse the _schema Go module's
|
||||
// go.sum to find the Wycheproof version used.
|
||||
func findVersionFromSum(t *testing.T) string {
|
||||
testenv.MustHaveSource(t)
|
||||
|
||||
goSumPath := path.Join(
|
||||
testenv.GOROOT(t),
|
||||
"src/crypto/internal/cryptotest/wycheproof/_schema/go.sum")
|
||||
f, err := os.Open(goSumPath)
|
||||
if err != nil {
|
||||
t.Fatalf("_schema module go.sum read failed: %v", err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
var version string
|
||||
found := 0
|
||||
scanner := bufio.NewScanner(f)
|
||||
for scanner.Scan() {
|
||||
fields := strings.Fields(scanner.Text())
|
||||
if len(fields) == 3 && fields[0] == "github.com/c2sp/wycheproof" {
|
||||
version = strings.TrimSuffix(fields[1], "/go.mod")
|
||||
found++
|
||||
}
|
||||
}
|
||||
|
||||
// We expect 2 entries for a tidied sum file.
|
||||
if found > 2 {
|
||||
t.Fatalf(
|
||||
"_schema module requires 'go tidy' - found %d wycheproof occurrences in go.sum",
|
||||
found)
|
||||
} else if found == 0 {
|
||||
t.Fatal("_schema module go.sum missing wycheproof dependency")
|
||||
}
|
||||
|
||||
return version
|
||||
}
|
||||
|
||||
// ShouldPass returns true if a test should pass informed by expected result
|
||||
// and flags.
|
||||
//
|
||||
// flagsShouldPass is a map used to determine if an "acceptable" result test
|
||||
// case should pass based on test's flags.
|
||||
// Every possible flag value that's associated with an "acceptable" result
|
||||
// should be explicitly specified, otherwise ShouldPass will panic.
|
||||
func ShouldPass(t *testing.T, result Result, 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 {
|
||||
t.Fatalf("unspecified flag: %q", flag)
|
||||
}
|
||||
if !pass {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true // There are no flags, or all are meant to pass.
|
||||
default:
|
||||
t.Fatalf("unexpected result: %v", result)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// ParseHash maps from a Wycheproof hash name to a crypto.Hash implementation
|
||||
// It panics if the provided hash name is unknown.
|
||||
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
|
||||
case "SHA3-224":
|
||||
return crypto.SHA3_224
|
||||
case "SHA3-256":
|
||||
return crypto.SHA3_256
|
||||
case "SHA3-384":
|
||||
return crypto.SHA3_384
|
||||
case "SHA3-512":
|
||||
return crypto.SHA3_512
|
||||
default:
|
||||
panic(fmt.Sprintf("unknown hash algorithm: %q", h))
|
||||
}
|
||||
}
|
||||
|
||||
// TestName returns a t.Run subtest name for a Wycheproof test vector.
|
||||
func TestName(file string, tv any) string {
|
||||
v := reflect.ValueOf(tv)
|
||||
if v.Kind() == reflect.Pointer {
|
||||
v = v.Elem()
|
||||
}
|
||||
tcID := v.FieldByName("TcId").Int()
|
||||
comment := v.FieldByName("Comment").String()
|
||||
name := fmt.Sprintf("%s #%d", file, tcID)
|
||||
if comment != "" {
|
||||
name += " " + comment
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
// MustDecodeHex is a helper that decodes the provided string or panics.
|
||||
//
|
||||
// Many Wycheproof vector values are hex encoded strings and in a test context
|
||||
// we don't intend to handle decoding errors gracefully.
|
||||
func MustDecodeHex(h string) []byte {
|
||||
d, err := hex.DecodeString(h)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
// MustPanic calls fn and fails the test if fn does not panic.
|
||||
//
|
||||
// This is useful for testing that invalid inputs (like incorrect nonce sizes
|
||||
// for AEAD ciphers) properly trigger panics rather than silently accepting
|
||||
// the bad input.
|
||||
func MustPanic(t *testing.T, name string, fn func()) {
|
||||
t.Helper()
|
||||
defer func() {
|
||||
if r := recover(); r == nil {
|
||||
t.Errorf("%s: expected panic but didn't get one", name)
|
||||
}
|
||||
}()
|
||||
fn()
|
||||
}
|
||||
|
|
@ -774,6 +774,9 @@ var depsRules = `
|
|||
CRYPTO-MATH, testing, internal/testenv, internal/testhash, encoding/json, regexp
|
||||
< crypto/internal/cryptotest;
|
||||
|
||||
crypto/internal/cryptotest, encoding/hex
|
||||
< crypto/internal/cryptotest/wycheproof;
|
||||
|
||||
CGO, FMT
|
||||
< crypto/internal/sysrand/internal/seccomp;
|
||||
|
||||
|
|
|
|||
|
|
@ -357,9 +357,10 @@ func TestStdKen(t *testing.T) {
|
|||
var excluded = map[string]bool{
|
||||
"builtin": true,
|
||||
"cmd/compile/internal/ssa/_gen": true,
|
||||
"runtime/_mkmalloc": true,
|
||||
"simd/archsimd/_gen/simdgen": true,
|
||||
"simd/archsimd/_gen/unify": true,
|
||||
"crypto/internal/cryptotest/wycheproof/_schema": true,
|
||||
"runtime/_mkmalloc": true,
|
||||
"simd/archsimd/_gen/simdgen": true,
|
||||
"simd/archsimd/_gen/unify": true,
|
||||
}
|
||||
|
||||
// printPackageMu synchronizes the printing of type-checked package files in
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue