mirror of
https://github.com/golang/go.git
synced 2026-06-28 03:40:37 +00:00
crypto: move Wycheproof test coverage from x/crypto
This commit adapts the x/crypto module's pre-existing Wycheproof test coverage, moving the tests adjacent to the standard library packages that are under test. In general the coverage and test driver code is left relatively unchanged, with the exception of: 1. Adapting to the crypto/internal/cryptotest/wycheproof generated schemas and helpers. 2. Adapting to the current Wycheproof testvectors_v1 vector files. (e.g. in some cases the vector file that was in-use by the x/crypto tests has been split into several test files by the upstream project). 3. Using parallel sub tests for faster execution speed. 4. Adding additional input files where it was trivial (e.g. for HMAC w/ truncated SHA512 digests, SHA3). 5. Using cryptotest.TestAllImplementations where applicable to get coverage of each registered impl. Change-Id: I820bf70d774f52040b2d0f8df1bc7d8ccc7e3186 Reviewed-on: https://go-review.googlesource.com/c/go/+/748640 LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: David Chase <drchase@google.com> Reviewed-by: Filippo Valsorda <filippo@golang.org> Reviewed-by: Roland Shoemaker <roland@golang.org> Auto-Submit: Roland Shoemaker <roland@golang.org>
This commit is contained in:
parent
caa4c72fee
commit
7df2a42f94
9 changed files with 947 additions and 0 deletions
67
src/crypto/cipher/cbc_aes_wycheproof_test.go
Normal file
67
src/crypto/cipher/cbc_aes_wycheproof_test.go
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
// 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 cipher_test
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/internal/cryptotest"
|
||||
"crypto/internal/cryptotest/wycheproof"
|
||||
"encoding/hex"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCBCAESWycheproof(t *testing.T) {
|
||||
cryptotest.TestAllImplementations(t, "aes", func(t *testing.T) {
|
||||
file := "aes_cbc_pkcs5_test.json"
|
||||
var testdata wycheproof.IndCpaTestSchemaV1Json
|
||||
wycheproof.LoadVectorFile(t, file, &testdata)
|
||||
|
||||
for _, tg := range testdata.TestGroups {
|
||||
for _, tv := range tg.Tests {
|
||||
t.Run(wycheproof.TestName(file, tv), func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
block, err := aes.NewCipher(wycheproof.MustDecodeHex(tv.Key))
|
||||
if err != nil {
|
||||
t.Fatalf("NewCipher: %v", err)
|
||||
}
|
||||
mode := cipher.NewCBCDecrypter(block, wycheproof.MustDecodeHex(tv.Iv))
|
||||
ct := wycheproof.MustDecodeHex(tv.Ct)
|
||||
if len(ct)%aes.BlockSize != 0 {
|
||||
t.Fatalf("ciphertext is not a multiple of the block size")
|
||||
}
|
||||
mode.CryptBlocks(ct, ct) // decrypt the block in place
|
||||
|
||||
// Test cases with bad/missing padding are expected to fail,
|
||||
// but cipher.CBCDecrypter doesn't validate padding. Skip these.
|
||||
// Fail loudly if there's an invalid test for any other reason,
|
||||
// so we can evaluate what to do with it.
|
||||
for _, flag := range tv.Flags {
|
||||
if flag == "BadPadding" || flag == "NoPadding" {
|
||||
return
|
||||
}
|
||||
}
|
||||
if !wycheproof.ShouldPass(t, tv.Result, tv.Flags, nil) {
|
||||
t.Fatalf("unexpected invalid test (not BadPadding/NoPadding)")
|
||||
}
|
||||
|
||||
// Remove the PKCS#5 padding from the given ciphertext to validate it
|
||||
padding := ct[len(ct)-1]
|
||||
paddingNum := int(padding)
|
||||
for i := paddingNum; i > 0; i-- {
|
||||
if ct[len(ct)-i] != padding { // panic if the padding is unexpectedly bad
|
||||
t.Fatalf("bad padding at index=%d of %v", i, ct)
|
||||
}
|
||||
}
|
||||
ct = ct[:len(ct)-paddingNum]
|
||||
|
||||
if got, want := hex.EncodeToString(ct), tv.Msg; got != want {
|
||||
t.Errorf("decoded ciphertext not equal: %s, want %s", got, want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
79
src/crypto/cipher/gcm_wycheproof_test.go
Normal file
79
src/crypto/cipher/gcm_wycheproof_test.go
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
// 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 cipher_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/internal/cryptotest"
|
||||
"crypto/internal/cryptotest/wycheproof"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGCMWycheproof(t *testing.T) {
|
||||
cryptotest.TestAllImplementations(t, "gcm", func(t *testing.T) {
|
||||
file := "aes_gcm_test.json"
|
||||
var testdata wycheproof.AeadTestSchemaV1Json
|
||||
wycheproof.LoadVectorFile(t, file, &testdata)
|
||||
|
||||
for _, tg := range testdata.TestGroups {
|
||||
for _, tv := range tg.Tests {
|
||||
t.Run(wycheproof.TestName(file, tv), func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
aesCipher, err := aes.NewCipher(wycheproof.MustDecodeHex(tv.Key))
|
||||
if err != nil {
|
||||
t.Fatalf("failed to construct cipher: %s", err)
|
||||
}
|
||||
aead, err := cipher.NewGCM(aesCipher)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to construct cipher: %s", err)
|
||||
}
|
||||
|
||||
iv := wycheproof.MustDecodeHex(tv.Iv)
|
||||
tag := wycheproof.MustDecodeHex(tv.Tag)
|
||||
ct := wycheproof.MustDecodeHex(tv.Ct)
|
||||
msg := wycheproof.MustDecodeHex(tv.Msg)
|
||||
aad := wycheproof.MustDecodeHex(tv.Aad)
|
||||
|
||||
// The Go implementation panics on invalid nonce sizes rather
|
||||
// than returning an error. Verify this behavior.
|
||||
if len(iv) != aead.NonceSize() {
|
||||
ctWithTag := append(ct, tag...)
|
||||
wycheproof.MustPanic(t, "Seal", func() { aead.Seal(nil, iv, msg, aad) })
|
||||
wycheproof.MustPanic(t, "Open", func() { aead.Open(nil, iv, ctWithTag, aad) })
|
||||
return
|
||||
}
|
||||
|
||||
genCT := aead.Seal(nil, iv, msg, aad)
|
||||
genMsg, err := aead.Open(nil, iv, genCT, aad)
|
||||
if err != nil {
|
||||
t.Errorf("failed to decrypt generated ciphertext: %s", err)
|
||||
}
|
||||
if !bytes.Equal(genMsg, msg) {
|
||||
t.Errorf("unexpected roundtripped plaintext: got %x, want %x", genMsg, msg)
|
||||
}
|
||||
|
||||
ctWithTag := append(ct, tag...)
|
||||
msg2, err := aead.Open(nil, iv, ctWithTag, aad)
|
||||
wantPass := wycheproof.ShouldPass(t, tv.Result, tv.Flags, nil)
|
||||
if !wantPass && err == nil {
|
||||
t.Error("decryption succeeded when it should've failed")
|
||||
} else if wantPass {
|
||||
if err != nil {
|
||||
t.Fatalf("decryption failed: %s", err)
|
||||
}
|
||||
if !bytes.Equal(genCT, ctWithTag) {
|
||||
t.Errorf("generated ciphertext doesn't match expected: got %x, want %x", genCT, ctWithTag)
|
||||
}
|
||||
if !bytes.Equal(msg, msg2) {
|
||||
t.Errorf("decrypted ciphertext doesn't match expected: got %x, want %x", msg2, msg)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
75
src/crypto/dsa/dsa_wycheproof_test.go
Normal file
75
src/crypto/dsa/dsa_wycheproof_test.go
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
// 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 dsa_test
|
||||
|
||||
import (
|
||||
"crypto/dsa"
|
||||
"crypto/internal/cryptotest/wycheproof"
|
||||
"crypto/x509"
|
||||
"math/big"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/crypto/cryptobyte"
|
||||
"golang.org/x/crypto/cryptobyte/asn1"
|
||||
)
|
||||
|
||||
func TestDSAWycheproof(t *testing.T) {
|
||||
flagsShouldPass := map[string]bool{
|
||||
// An encoded ASN.1 integer missing a leading zero is invalid,
|
||||
// but accepted by some implementations.
|
||||
"MissingZero": false,
|
||||
}
|
||||
|
||||
for _, file := range []string{
|
||||
"dsa_2048_224_sha224_test.json",
|
||||
"dsa_2048_224_sha256_test.json",
|
||||
"dsa_2048_256_sha256_test.json",
|
||||
"dsa_3072_256_sha256_test.json",
|
||||
} {
|
||||
var testdata wycheproof.DsaVerifySchemaV1Json
|
||||
wycheproof.LoadVectorFile(t, file, &testdata)
|
||||
|
||||
for _, tg := range testdata.TestGroups {
|
||||
rawPub, err := x509.ParsePKIXPublicKey(wycheproof.MustDecodeHex(tg.PublicKeyDer))
|
||||
if err != nil {
|
||||
t.Fatalf("failed to parse DER encoded public key: %v", err)
|
||||
}
|
||||
|
||||
pub := rawPub.(*dsa.PublicKey)
|
||||
h := wycheproof.ParseHash(tg.Sha)
|
||||
|
||||
for _, tv := range tg.Tests {
|
||||
t.Run(wycheproof.TestName(file, tv), func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
h := h.New()
|
||||
h.Write(wycheproof.MustDecodeHex(tv.Msg))
|
||||
hashed := h.Sum(nil)
|
||||
// Truncate to the byte-length of the subgroup (Q)
|
||||
hashed = hashed[:pub.Q.BitLen()/8]
|
||||
got := verifyASN1(pub, hashed, wycheproof.MustDecodeHex(tv.Sig))
|
||||
if want := wycheproof.ShouldPass(t, tv.Result, tv.Flags, flagsShouldPass); got != want {
|
||||
t.Errorf("wanted success: %t", want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
247
src/crypto/ecdh/ecdh_wycheproof_test.go
Normal file
247
src/crypto/ecdh/ecdh_wycheproof_test.go
Normal file
|
|
@ -0,0 +1,247 @@
|
|||
// 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 ecdh_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/ecdh"
|
||||
"crypto/ecdsa"
|
||||
"crypto/internal/cryptotest/wycheproof"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSecpECDHWycheproof(t *testing.T) {
|
||||
flagsShouldPass := map[string]bool{
|
||||
// We don't support compressed points or public keys.
|
||||
"CompressedPoint": false,
|
||||
"CompressedPublic": false,
|
||||
}
|
||||
|
||||
curveToCurve := map[string]ecdh.Curve{
|
||||
"secp256r1": ecdh.P256(),
|
||||
"secp384r1": ecdh.P384(),
|
||||
"secp521r1": ecdh.P521(),
|
||||
}
|
||||
|
||||
curveToKeySize := map[string]int{
|
||||
"secp256r1": 32,
|
||||
"secp384r1": 48,
|
||||
"secp521r1": 66,
|
||||
}
|
||||
|
||||
for _, file := range []string{
|
||||
"ecdh_secp256r1_ecpoint_test.json",
|
||||
"ecdh_secp384r1_ecpoint_test.json",
|
||||
"ecdh_secp521r1_ecpoint_test.json",
|
||||
} {
|
||||
var testdata wycheproof.EcdhEcpointTestSchemaV1Json
|
||||
wycheproof.LoadVectorFile(t, file, &testdata)
|
||||
|
||||
for _, tg := range testdata.TestGroups {
|
||||
if _, ok := curveToCurve[tg.Curve]; !ok {
|
||||
continue
|
||||
}
|
||||
curve := curveToCurve[tg.Curve]
|
||||
keySize := curveToKeySize[tg.Curve]
|
||||
|
||||
for _, tv := range tg.Tests {
|
||||
testName := wycheproof.TestName(file, tv)
|
||||
tv := ecdhWycheproofTV{
|
||||
tcID: tv.TcId,
|
||||
comment: tv.Comment,
|
||||
flags: tv.Flags,
|
||||
result: tv.Result,
|
||||
public: tv.Public,
|
||||
private: tv.Private,
|
||||
shared: tv.Shared,
|
||||
}
|
||||
t.Run(testName, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
runECDHWycheproofTest(t, curve, keySize, flagsShouldPass, tv, curve.NewPublicKey)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSecpECDHSPKIWycheproof(t *testing.T) {
|
||||
flagsShouldPass := map[string]bool{
|
||||
"CompressedPublic": false,
|
||||
"CompressedPoint": false,
|
||||
"UnnamedCurve": false,
|
||||
"WrongOrder": false,
|
||||
"UnusedParam": false,
|
||||
"ModifiedGenerator": false,
|
||||
"ModifiedCofactor": false,
|
||||
"ModifiedCurveParameter": false,
|
||||
"NoCofactor": false,
|
||||
"Modified curve parameter": false,
|
||||
"InvalidAsn": false,
|
||||
}
|
||||
|
||||
parseSPKIPub := func(p []byte) (*ecdh.PublicKey, error) {
|
||||
pubKeyAny, err := x509.ParsePKIXPublicKey(p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ecdsaPub, ok := pubKeyAny.(*ecdsa.PublicKey)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unexpected key type %T", pubKeyAny)
|
||||
}
|
||||
return ecdsaPub.ECDH()
|
||||
}
|
||||
|
||||
curveToCurve := map[string]ecdh.Curve{
|
||||
"secp256r1": ecdh.P256(),
|
||||
"secp384r1": ecdh.P384(),
|
||||
"secp521r1": ecdh.P521(),
|
||||
}
|
||||
|
||||
curveToKeySize := map[string]int{
|
||||
"secp256r1": 32,
|
||||
"secp384r1": 48,
|
||||
"secp521r1": 66,
|
||||
}
|
||||
|
||||
for _, file := range []string{
|
||||
"ecdh_secp256r1_test.json",
|
||||
"ecdh_secp384r1_test.json",
|
||||
"ecdh_secp521r1_test.json",
|
||||
} {
|
||||
var testdata wycheproof.EcdhTestSchemaV1Json
|
||||
wycheproof.LoadVectorFile(t, file, &testdata)
|
||||
|
||||
for _, tg := range testdata.TestGroups {
|
||||
if _, ok := curveToCurve[tg.Curve]; !ok {
|
||||
continue
|
||||
}
|
||||
curve := curveToCurve[tg.Curve]
|
||||
keySize := curveToKeySize[tg.Curve]
|
||||
|
||||
for _, tv := range tg.Tests {
|
||||
testName := wycheproof.TestName(file, tv)
|
||||
tv := ecdhWycheproofTV{
|
||||
tcID: tv.TcId,
|
||||
comment: tv.Comment,
|
||||
flags: tv.Flags,
|
||||
result: tv.Result,
|
||||
public: tv.Public,
|
||||
private: tv.Private,
|
||||
shared: tv.Shared,
|
||||
}
|
||||
t.Run(testName, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
runECDHWycheproofTest(t, curve, keySize, flagsShouldPass, tv, parseSPKIPub)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestX25519ECDHWycheproof(t *testing.T) {
|
||||
flagsShouldPass := map[string]bool{
|
||||
"Twist": true,
|
||||
"SmallPublicKey": false,
|
||||
"LowOrderPublic": false,
|
||||
"ZeroSharedSecret": false,
|
||||
"NonCanonicalPublic": true,
|
||||
"SpecialPublicKey": true,
|
||||
"EdgeCaseMultiplication": true,
|
||||
"EdgeCaseShared": true,
|
||||
"Ktv": true,
|
||||
}
|
||||
|
||||
file := "x25519_test.json"
|
||||
var testdata wycheproof.XdhCompSchemaV1Json
|
||||
wycheproof.LoadVectorFile(t, file, &testdata)
|
||||
|
||||
for _, tg := range testdata.TestGroups {
|
||||
if tg.Curve != "curve25519" {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, tv := range tg.Tests {
|
||||
testName := wycheproof.TestName(file, tv)
|
||||
tv := ecdhWycheproofTV{
|
||||
tcID: tv.TcId,
|
||||
comment: tv.Comment,
|
||||
flags: tv.Flags,
|
||||
result: tv.Result,
|
||||
public: tv.Public,
|
||||
private: tv.Private,
|
||||
shared: tv.Shared,
|
||||
}
|
||||
t.Run(testName, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
runECDHWycheproofTest(t, ecdh.X25519(), 32, flagsShouldPass, tv, ecdh.X25519().NewPublicKey)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ecdhWycheproofTV is a representation common to the three different schemas
|
||||
// we process in this test: wycheproof.XdhCompSchemaV1Json,
|
||||
// wycheproof.EcdhTestSchemaV1Json and wycheproof.EcdhEcpointTestSchemaV1Json
|
||||
type ecdhWycheproofTV struct {
|
||||
tcID int
|
||||
comment string
|
||||
flags []string
|
||||
result wycheproof.Result
|
||||
public string
|
||||
private string
|
||||
shared string
|
||||
}
|
||||
|
||||
// runECDHWycheproofTest runs test logic common to the three ECDH test schemas
|
||||
// we process in this file.
|
||||
func runECDHWycheproofTest(
|
||||
t *testing.T,
|
||||
curve ecdh.Curve,
|
||||
expectedKeySize int,
|
||||
flagsShouldPass map[string]bool,
|
||||
tv ecdhWycheproofTV,
|
||||
parsePub func([]byte) (*ecdh.PublicKey, error)) {
|
||||
t.Helper()
|
||||
|
||||
shouldPass := wycheproof.ShouldPass(t, tv.result, tv.flags, flagsShouldPass)
|
||||
|
||||
pub, err := parsePub(wycheproof.MustDecodeHex(tv.public))
|
||||
if err != nil {
|
||||
if shouldPass {
|
||||
t.Errorf("parsePub: %v", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
privBytes := wycheproof.MustDecodeHex(tv.private)
|
||||
priv, err := curve.NewPrivateKey(privBytes)
|
||||
if err != nil {
|
||||
if shouldPass && len(privBytes) == expectedKeySize {
|
||||
t.Errorf("NewPrivateKey: %v", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
x, err := priv.ECDH(pub)
|
||||
if err != nil {
|
||||
if shouldPass {
|
||||
t.Fatalf("ECDH: %v", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
shared := wycheproof.MustDecodeHex(tv.shared)
|
||||
if shouldPass {
|
||||
if !bytes.Equal(shared, x) {
|
||||
t.Errorf("ECDH = %x, want %x", x, shared)
|
||||
}
|
||||
} else if tv.result == "invalid" {
|
||||
// For invalid inputs, a correct ECDH result is a test failure.
|
||||
if bytes.Equal(shared, x) {
|
||||
t.Errorf("ECDH = %x, want anything else", x)
|
||||
}
|
||||
}
|
||||
}
|
||||
91
src/crypto/ecdsa/ecdsa_wycheproof_test.go
Normal file
91
src/crypto/ecdsa/ecdsa_wycheproof_test.go
Normal file
|
|
@ -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.
|
||||
package ecdsa_test
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"crypto/internal/cryptotest/wycheproof"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestECDSAWycheproof(t *testing.T) {
|
||||
// A map of supported curves to the list of hashes that Wycheproof has
|
||||
// test vector coverage for.
|
||||
curveAndHashes := map[string][]string{
|
||||
"secp224r1": {
|
||||
"sha224",
|
||||
"sha256",
|
||||
"sha512",
|
||||
"sha3_224",
|
||||
"sha3_256",
|
||||
"sha3_512",
|
||||
},
|
||||
"secp256r1": {
|
||||
"sha256",
|
||||
"sha512",
|
||||
"sha3_256",
|
||||
"sha3_512",
|
||||
},
|
||||
"secp384r1": {
|
||||
"sha256",
|
||||
"sha384",
|
||||
"sha512",
|
||||
"sha3_384",
|
||||
"sha3_512",
|
||||
},
|
||||
"secp521r1": {
|
||||
"sha512",
|
||||
"sha3_512",
|
||||
},
|
||||
}
|
||||
|
||||
var files []string
|
||||
for c, hashes := range curveAndHashes {
|
||||
for _, h := range hashes {
|
||||
files = append(files, fmt.Sprintf("ecdsa_%s_%s_test.json", c, h))
|
||||
}
|
||||
}
|
||||
|
||||
parseSPKIPub := func(p []byte) (*ecdsa.PublicKey, error) {
|
||||
pubKeyAny, err := x509.ParsePKIXPublicKey(p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ecdsaPub, ok := pubKeyAny.(*ecdsa.PublicKey)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unexpected key type %T", pubKeyAny)
|
||||
}
|
||||
return ecdsaPub, nil
|
||||
}
|
||||
|
||||
for _, file := range files {
|
||||
var testdata wycheproof.EcdsaVerifySchemaV1Json
|
||||
wycheproof.LoadVectorFile(t, file, &testdata)
|
||||
|
||||
for tgIdx, tg := range testdata.TestGroups {
|
||||
pubkey, err := parseSPKIPub(wycheproof.MustDecodeHex(tg.PublicKeyDer))
|
||||
if err != nil {
|
||||
t.Fatalf("test group %d invalid DER encoded public key: %v", tgIdx+1, err)
|
||||
}
|
||||
|
||||
h := wycheproof.ParseHash(tg.Sha)
|
||||
|
||||
for _, tv := range tg.Tests {
|
||||
t.Run(wycheproof.TestName(file, tv), func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
h := h.New()
|
||||
h.Write(wycheproof.MustDecodeHex(tv.Msg))
|
||||
got := ecdsa.VerifyASN1(pubkey, h.Sum(nil), wycheproof.MustDecodeHex(tv.Sig))
|
||||
|
||||
if want := wycheproof.ShouldPass(t, tv.Result, tv.Flags, nil); got != want {
|
||||
t.Errorf("VerifyASN1 wanted success: %t", want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
50
src/crypto/ed25519/ed25519_wycheproof_test.go
Normal file
50
src/crypto/ed25519/ed25519_wycheproof_test.go
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
// 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 ed25519_test
|
||||
|
||||
import (
|
||||
"crypto/ed25519"
|
||||
"crypto/internal/cryptotest/wycheproof"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestEd25519Wycheproof(t *testing.T) {
|
||||
file := "ed25519_test.json"
|
||||
var testdata wycheproof.EddsaVerifySchemaV1Json
|
||||
wycheproof.LoadVectorFile(t, file, &testdata)
|
||||
|
||||
parseSPKIPub := func(p []byte) (ed25519.PublicKey, error) {
|
||||
pubKeyAny, err := x509.ParsePKIXPublicKey(p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pub, ok := pubKeyAny.(ed25519.PublicKey)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unexpected key type %T", pubKeyAny)
|
||||
}
|
||||
return pub, nil
|
||||
}
|
||||
|
||||
for tgIdx, tg := range testdata.TestGroups {
|
||||
pubkey, err := parseSPKIPub(wycheproof.MustDecodeHex(tg.PublicKeyDer))
|
||||
if err != nil {
|
||||
t.Fatalf("test group %d invalid DER encoded public key: %v", tgIdx+1, err)
|
||||
}
|
||||
|
||||
for _, tv := range tg.Tests {
|
||||
t.Run(wycheproof.TestName(file, tv), func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
got := ed25519.Verify(
|
||||
pubkey, wycheproof.MustDecodeHex(tv.Msg), wycheproof.MustDecodeHex(tv.Sig))
|
||||
want := wycheproof.ShouldPass(t, tv.Result, tv.Flags, nil)
|
||||
if got != want {
|
||||
t.Errorf("Verify wanted success: %t", want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
56
src/crypto/hkdf/hkdf_wycheproof_test.go
Normal file
56
src/crypto/hkdf/hkdf_wycheproof_test.go
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
// 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 hkdf_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/hkdf"
|
||||
"crypto/internal/cryptotest/wycheproof"
|
||||
"crypto/sha1"
|
||||
"crypto/sha256"
|
||||
"crypto/sha512"
|
||||
"hash"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestHKDFWycheproof(t *testing.T) {
|
||||
filesToHash := map[string]func() hash.Hash{
|
||||
"hkdf_sha1_test.json": sha1.New,
|
||||
"hkdf_sha256_test.json": sha256.New,
|
||||
"hkdf_sha384_test.json": sha512.New384,
|
||||
"hkdf_sha512_test.json": sha512.New,
|
||||
}
|
||||
|
||||
for file, h := range filesToHash {
|
||||
var testdata wycheproof.HkdfTestSchemaV1Json
|
||||
wycheproof.LoadVectorFile(t, file, &testdata)
|
||||
|
||||
for _, tg := range testdata.TestGroups {
|
||||
for _, tv := range tg.Tests {
|
||||
t.Run(wycheproof.TestName(file, tv), func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ikm := wycheproof.MustDecodeHex(tv.Ikm)
|
||||
salt := wycheproof.MustDecodeHex(tv.Salt)
|
||||
info := string(wycheproof.MustDecodeHex(tv.Info))
|
||||
wantPass := wycheproof.ShouldPass(t, tv.Result, tv.Flags, nil)
|
||||
|
||||
okm, err := hkdf.Key(h, ikm, salt, info, tv.Size)
|
||||
if !wantPass {
|
||||
if err == nil {
|
||||
t.Errorf("Key succeeded for invalid vector")
|
||||
}
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("Key: %v", err)
|
||||
}
|
||||
if expectedOkm := wycheproof.MustDecodeHex(tv.Okm); !bytes.Equal(okm, expectedOkm) {
|
||||
t.Errorf("output key mismatch: got %x, want %x", okm, expectedOkm)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
60
src/crypto/hmac/hmac_wycheproof_test.go
Normal file
60
src/crypto/hmac/hmac_wycheproof_test.go
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
// 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 hmac_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/hmac"
|
||||
"crypto/internal/cryptotest/wycheproof"
|
||||
"crypto/sha1"
|
||||
"crypto/sha256"
|
||||
"crypto/sha3"
|
||||
"crypto/sha512"
|
||||
"hash"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestHMACWycheproof(t *testing.T) {
|
||||
filesToHash := map[string]func() hash.Hash{
|
||||
"hmac_sha1_test.json": sha1.New,
|
||||
"hmac_sha224_test.json": sha256.New224,
|
||||
"hmac_sha256_test.json": sha256.New,
|
||||
"hmac_sha3_224_test.json": func() hash.Hash { return sha3.New224() },
|
||||
"hmac_sha3_256_test.json": func() hash.Hash { return sha3.New256() },
|
||||
"hmac_sha3_384_test.json": func() hash.Hash { return sha3.New384() },
|
||||
"hmac_sha3_512_test.json": func() hash.Hash { return sha3.New512() },
|
||||
"hmac_sha384_test.json": sha512.New384,
|
||||
"hmac_sha512_test.json": sha512.New,
|
||||
"hmac_sha512_224_test.json": sha512.New512_224,
|
||||
"hmac_sha512_256_test.json": sha512.New512_256,
|
||||
}
|
||||
|
||||
for file, h := range filesToHash {
|
||||
var testdata wycheproof.MacTestSchemaV1Json
|
||||
wycheproof.LoadVectorFile(t, file, &testdata)
|
||||
|
||||
for _, tg := range testdata.TestGroups {
|
||||
// Skip test groups where the tag length does not equal the
|
||||
// hash length, since crypto/hmac does not support generating
|
||||
// these truncated tags.
|
||||
if tg.TagSize/8 != h().Size() {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, tv := range tg.Tests {
|
||||
t.Run(wycheproof.TestName(file, tv), func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
hm := hmac.New(h, wycheproof.MustDecodeHex(tv.Key))
|
||||
hm.Write(wycheproof.MustDecodeHex(tv.Msg))
|
||||
got := bytes.Equal(wycheproof.MustDecodeHex(tv.Tag), hm.Sum(nil))
|
||||
want := wycheproof.ShouldPass(t, tv.Result, tv.Flags, nil)
|
||||
if want != got {
|
||||
t.Errorf("unexpected result")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
222
src/crypto/rsa/rsa_wycheproof_test.go
Normal file
222
src/crypto/rsa/rsa_wycheproof_test.go
Normal file
|
|
@ -0,0 +1,222 @@
|
|||
// 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 rsa_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/internal/cryptotest/wycheproof"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"slices"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRSAOAEPDecryptWycheproof(t *testing.T) {
|
||||
flagsShouldPass := map[string]bool{
|
||||
"Constructed": true,
|
||||
"EncryptionWithLabel": true,
|
||||
// rsa.DecryptOAEP happily supports small key sizes
|
||||
"SmallIntegerCiphertext": true,
|
||||
}
|
||||
|
||||
// TODO(XXX): support test files with different hashes for MGF/label
|
||||
for _, file := range []string{
|
||||
"rsa_oaep_2048_sha1_mgf1sha1_test.json",
|
||||
"rsa_oaep_2048_sha224_mgf1sha224_test.json",
|
||||
"rsa_oaep_2048_sha256_mgf1sha256_test.json",
|
||||
"rsa_oaep_2048_sha384_mgf1sha384_test.json",
|
||||
"rsa_oaep_2048_sha512_mgf1sha512_test.json",
|
||||
"rsa_oaep_3072_sha256_mgf1sha256_test.json",
|
||||
"rsa_oaep_3072_sha512_mgf1sha512_test.json",
|
||||
"rsa_oaep_4096_sha256_mgf1sha256_test.json",
|
||||
"rsa_oaep_4096_sha512_mgf1sha512_test.json",
|
||||
"rsa_oaep_misc_test.json",
|
||||
} {
|
||||
var testdata wycheproof.RsaesOaepDecryptSchemaV1Json
|
||||
wycheproof.LoadVectorFile(t, file, &testdata)
|
||||
|
||||
for _, tg := range testdata.TestGroups {
|
||||
// TODO(XXX): support rsa_oaep_misc_test test cases with different hashes for MGF/label
|
||||
if tg.MgfSha != tg.Sha {
|
||||
t.Skip("test cases with different hashes for MGF/label not yet supported")
|
||||
}
|
||||
|
||||
rawPriv, err := x509.ParsePKCS8PrivateKey(wycheproof.MustDecodeHex(tg.PrivateKeyPkcs8))
|
||||
if err != nil {
|
||||
t.Fatalf("%s failed to parse PKCS #8 private key: %s", file, err)
|
||||
}
|
||||
priv := rawPriv.(*rsa.PrivateKey)
|
||||
hash := wycheproof.ParseHash(tg.Sha)
|
||||
|
||||
for _, tv := range tg.Tests {
|
||||
t.Run(wycheproof.TestName(file, tv), func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ct := wycheproof.MustDecodeHex(tv.Ct)
|
||||
label := wycheproof.MustDecodeHex(tv.Label)
|
||||
wantPass := wycheproof.ShouldPass(t, tv.Result, tv.Flags, flagsShouldPass)
|
||||
plaintext, err := rsa.DecryptOAEP(hash.New(), nil, priv, ct, label)
|
||||
if wantPass {
|
||||
if err != nil {
|
||||
t.Fatalf("expected success: %s", err)
|
||||
}
|
||||
if !bytes.Equal(plaintext, wycheproof.MustDecodeHex(tv.Msg)) {
|
||||
t.Errorf("unexpected plaintext: got %x, want %s", plaintext, tv.Msg)
|
||||
}
|
||||
} else if err == nil {
|
||||
t.Errorf("expected failure")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRSAPKCS1SignaturesWycheproof(t *testing.T) {
|
||||
// A map of supported modulus sizes to the list of hashes that Wycheproof has
|
||||
// test vector coverage for.
|
||||
modsAndHashes := map[int][]string{
|
||||
2048: {
|
||||
"sha224",
|
||||
"sha256",
|
||||
"sha384",
|
||||
"sha512",
|
||||
"sha512_224",
|
||||
"sha512_256",
|
||||
"sha3_224",
|
||||
"sha3_256",
|
||||
"sha3_384",
|
||||
"sha3_512",
|
||||
},
|
||||
3072: {
|
||||
"sha256",
|
||||
"sha384",
|
||||
"sha512",
|
||||
"sha512_256",
|
||||
"sha3_256",
|
||||
"sha3_384",
|
||||
"sha3_512",
|
||||
},
|
||||
4096: {
|
||||
"sha256",
|
||||
"sha384",
|
||||
"sha512",
|
||||
"sha512_256",
|
||||
},
|
||||
8192: {
|
||||
"sha256",
|
||||
"sha384",
|
||||
"sha512",
|
||||
},
|
||||
}
|
||||
|
||||
var files []string
|
||||
for m, hashes := range modsAndHashes {
|
||||
for _, h := range hashes {
|
||||
files = append(files, fmt.Sprintf("rsa_signature_%d_%s_test.json", m, h))
|
||||
}
|
||||
}
|
||||
|
||||
flagsShouldPass := map[string]bool{
|
||||
// Omitting the parameter field in an ASN encoded integer is a legacy behavior.
|
||||
"MissingNull": false,
|
||||
}
|
||||
|
||||
for _, file := range files {
|
||||
var testdata wycheproof.RsassaPkcs1VerifySchemaV1Json
|
||||
wycheproof.LoadVectorFile(t, file, &testdata)
|
||||
|
||||
for _, tg := range testdata.TestGroups {
|
||||
hash := wycheproof.ParseHash(tg.Sha)
|
||||
|
||||
pub, err := x509.ParsePKCS1PublicKey(wycheproof.MustDecodeHex(tg.PublicKeyAsn))
|
||||
if err != nil {
|
||||
t.Fatalf("failed to decode pubkey: %v", err)
|
||||
}
|
||||
|
||||
for _, tv := range tg.Tests {
|
||||
t.Run(wycheproof.TestName(file, tv), func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
sig := wycheproof.MustDecodeHex(tv.Sig)
|
||||
h := hash.New()
|
||||
h.Write(wycheproof.MustDecodeHex(tv.Msg))
|
||||
err := rsa.VerifyPKCS1v15(pub, hash, h.Sum(nil), sig)
|
||||
want := wycheproof.ShouldPass(t, tv.Result, tv.Flags, flagsShouldPass)
|
||||
if (err == nil) != want {
|
||||
t.Errorf("wanted success: %t err: %v", want, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRSAPSSSignaturesWycheproof(t *testing.T) {
|
||||
// filesOverrideToPassZeroSLen is a map of all test files
|
||||
// and which TcIds that should be overridden 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": {46, 47, 48, 49, 50, 51},
|
||||
"rsa_pss_2048_sha256_mgf1_0_test.json": {67, 68, 69, 70},
|
||||
"rsa_pss_2048_sha256_mgf1_32_test.json": {67, 68, 69, 70, 71, 72},
|
||||
"rsa_pss_3072_sha256_mgf1_32_test.json": {67, 68, 69, 70, 71, 72},
|
||||
"rsa_pss_4096_sha256_mgf1_32_test.json": {67, 68, 69, 70, 71, 72},
|
||||
"rsa_pss_4096_sha512_mgf1_32_test.json": {136, 137, 138, 139, 140, 141},
|
||||
// "rsa_pss_misc_test.json": nil, // TODO: This ones seems to be broken right now, but can enable later on.
|
||||
}
|
||||
|
||||
for file, overrideIDs := range filesOverrideToPassZeroSLen {
|
||||
var testdata wycheproof.RsassaPssVerifySchemaV1Json
|
||||
wycheproof.LoadVectorFile(t, file, &testdata)
|
||||
|
||||
for _, tg := range testdata.TestGroups {
|
||||
hash := wycheproof.ParseHash(tg.Sha)
|
||||
|
||||
pub, err := x509.ParsePKCS1PublicKey(wycheproof.MustDecodeHex(tg.PublicKeyAsn))
|
||||
if err != nil {
|
||||
t.Fatalf("failed to decode pubkey: %v", err)
|
||||
}
|
||||
|
||||
// Run all the tests twice: the first time with the salt length
|
||||
// as PSSSaltLengthAuto, and the second time with the salt length
|
||||
// explicitly set to tg.SLen.
|
||||
for i := 0; i < 2; i++ {
|
||||
saltLabel := "autoSalt"
|
||||
if i == 1 {
|
||||
saltLabel = "vecSalt"
|
||||
}
|
||||
opts := &rsa.PSSOptions{
|
||||
Hash: hash,
|
||||
SaltLength: rsa.PSSSaltLengthAuto,
|
||||
}
|
||||
|
||||
for _, tv := range tg.Tests {
|
||||
t.Run(wycheproof.TestName(file, tv)+" "+saltLabel, func(t *testing.T) {
|
||||
h := hash.New()
|
||||
h.Write(wycheproof.MustDecodeHex(tv.Msg))
|
||||
sig := wycheproof.MustDecodeHex(tv.Sig)
|
||||
err = rsa.VerifyPSS(pub, hash, h.Sum(nil), sig, opts)
|
||||
want := wycheproof.ShouldPass(t, tv.Result, tv.Flags, nil)
|
||||
if opts.SaltLength == 0 && slices.Contains(overrideIDs, tv.TcId) {
|
||||
want = true
|
||||
}
|
||||
if (err == nil) != want {
|
||||
t.Errorf("wanted success: %t err: %v", want, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Update opts.SaltLength for the second run of the tests.
|
||||
opts.SaltLength = tg.SLen
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue