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:
Daniel McCarney 2026-02-24 16:38:54 -05:00 committed by Gopher Robot
parent caa4c72fee
commit 7df2a42f94
9 changed files with 947 additions and 0 deletions

View 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)
}
})
}
}
})
}

View 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)
}
}
})
}
}
})
}

View 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)
}

View 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)
}
}
}

View 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)
}
})
}
}
}
}

View 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)
}
})
}
}
}

View 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)
}
})
}
}
}
}

View 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")
}
})
}
}
}
}

View 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
}
}
}
}