Source file src/crypto/internal/cryptotest/wycheproof/wycheproof.go

     1  // Copyright 2026 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // Package wycheproof provides helper utilities for writing tests that
     6  // rely on Wycheproof test vector schemas and JSON vector data.
     7  // See https://github.com/C2SP/wycheproof for more information.
     8  package wycheproof
     9  
    10  import (
    11  	"crypto"
    12  	"crypto/internal/cryptotest"
    13  	"encoding/hex"
    14  	"encoding/json"
    15  	"flag"
    16  	"fmt"
    17  	"internal/testenv"
    18  	"os"
    19  	"path"
    20  	"reflect"
    21  	"testing"
    22  )
    23  
    24  // wycheproofDir, if set, points to a local Wycheproof checkout that
    25  // LoadVectorFile should read test vectors from.
    26  var wycheproofDir = flag.String("wycheproof-dir", "",
    27  	"path to a local Wycheproof checkout to load test vectors from.")
    28  
    29  // LoadVectorFile unmarshals Wycheproof JSON test vector file by name.
    30  //
    31  // Typically, the value argument will be a pointer to a Wycheproof schema
    32  // type representing the in-memory structure of the JSON data.
    33  //
    34  // Panics if there is an error reading the Wycheproof JSON vector data file,
    35  // or if it can't be unmarshalled into the provided value.
    36  func LoadVectorFile(t *testing.T, filename string, value any) {
    37  	testenv.SkipIfShortAndSlow(t)
    38  
    39  	// We want to avoid a dependency on c2sp/wycheproof or the schema generator
    40  	// in this stdlib code, so we fetch the module at runtime and read the
    41  	// vector JSON from that module clone. The version is pinned to whatever
    42  	// the _schema generator was last run against (see schemaversion.go), so
    43  	// the vectors match the generated schema.go.
    44  	//
    45  	// If -wycheproof-dir is set, read from that local checkout instead, to
    46  	// support testing local updates to Wycheproof.
    47  	dir := *wycheproofDir
    48  	if dir == "" {
    49  		dir = cryptotest.FetchModule(
    50  			t, "github.com/c2sp/wycheproof", wycheproofVersion)
    51  	}
    52  
    53  	content, err := os.ReadFile(path.Join(dir, "testvectors_v1", filename))
    54  	if err != nil {
    55  		t.Fatalf("missing Wycheproof vector file %q: %v", filename, err)
    56  	}
    57  
    58  	err = json.Unmarshal(content, value)
    59  	if err != nil {
    60  		t.Fatalf("failed to unmarshal vector file %q: %v", filename, err)
    61  	}
    62  }
    63  
    64  // ShouldPass returns true if a test should pass informed by expected result
    65  // and flags.
    66  //
    67  // flagsShouldPass is a map used to determine if an "acceptable" result test
    68  // case should pass based on test's flags.
    69  // Every possible flag value that's associated with an "acceptable" result
    70  // should be explicitly specified, otherwise ShouldPass will panic.
    71  func ShouldPass(t *testing.T, result Result, flags []string, flagsShouldPass map[string]bool) bool {
    72  	switch result {
    73  	case "valid":
    74  		return true
    75  	case "invalid":
    76  		return false
    77  	case "acceptable":
    78  		for _, flag := range flags {
    79  			pass, ok := flagsShouldPass[flag]
    80  			if !ok {
    81  				t.Fatalf("unspecified flag: %q", flag)
    82  			}
    83  			if !pass {
    84  				return false
    85  			}
    86  		}
    87  		return true // There are no flags, or all are meant to pass.
    88  	default:
    89  		t.Fatalf("unexpected result: %v", result)
    90  		return false
    91  	}
    92  }
    93  
    94  // ParseHash maps from a Wycheproof hash name to a crypto.Hash implementation
    95  // It panics if the provided hash name is unknown.
    96  func ParseHash(h string) crypto.Hash {
    97  	switch h {
    98  	case "SHA-1":
    99  		return crypto.SHA1
   100  	case "SHA-256":
   101  		return crypto.SHA256
   102  	case "SHA-224":
   103  		return crypto.SHA224
   104  	case "SHA-384":
   105  		return crypto.SHA384
   106  	case "SHA-512":
   107  		return crypto.SHA512
   108  	case "SHA-512/224":
   109  		return crypto.SHA512_224
   110  	case "SHA-512/256":
   111  		return crypto.SHA512_256
   112  	case "SHA3-224":
   113  		return crypto.SHA3_224
   114  	case "SHA3-256":
   115  		return crypto.SHA3_256
   116  	case "SHA3-384":
   117  		return crypto.SHA3_384
   118  	case "SHA3-512":
   119  		return crypto.SHA3_512
   120  	default:
   121  		panic(fmt.Sprintf("unknown hash algorithm: %q", h))
   122  	}
   123  }
   124  
   125  // TestName returns a t.Run subtest name for a Wycheproof test vector.
   126  func TestName(file string, tv any) string {
   127  	v := reflect.ValueOf(tv)
   128  	if v.Kind() == reflect.Pointer {
   129  		v = v.Elem()
   130  	}
   131  	tcID := v.FieldByName("TcId").Int()
   132  	comment := v.FieldByName("Comment").String()
   133  	name := fmt.Sprintf("%s #%d", file, tcID)
   134  	if comment != "" {
   135  		name += " " + comment
   136  	}
   137  	return name
   138  }
   139  
   140  // MustDecodeHex is a helper that decodes the provided string or panics.
   141  //
   142  // Many Wycheproof vector values are hex encoded strings and in a test context
   143  // we don't intend to handle decoding errors gracefully.
   144  func MustDecodeHex(h string) []byte {
   145  	d, err := hex.DecodeString(h)
   146  	if err != nil {
   147  		panic(err)
   148  	}
   149  	return d
   150  }
   151  
   152  // MustPanic calls fn and fails the test if fn does not panic.
   153  //
   154  // This is useful for testing that invalid inputs (like incorrect nonce sizes
   155  // for AEAD ciphers) properly trigger panics rather than silently accepting
   156  // the bad input.
   157  func MustPanic(t *testing.T, name string, fn func()) {
   158  	t.Helper()
   159  	defer func() {
   160  		if r := recover(); r == nil {
   161  			t.Errorf("%s: expected panic but didn't get one", name)
   162  		}
   163  	}()
   164  	fn()
   165  }
   166  

View as plain text