| // 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, |
| } |
| |
| for _, file := range []string{ |
| "rsa_oaep_2048_sha1_mgf1sha1_test.json", |
| "rsa_oaep_2048_sha224_mgf1sha1_test.json", |
| "rsa_oaep_2048_sha224_mgf1sha224_test.json", |
| "rsa_oaep_2048_sha256_mgf1sha1_test.json", |
| "rsa_oaep_2048_sha256_mgf1sha256_test.json", |
| "rsa_oaep_2048_sha384_mgf1sha1_test.json", |
| "rsa_oaep_2048_sha384_mgf1sha384_test.json", |
| "rsa_oaep_2048_sha512_224_mgf1sha1_test.json", |
| "rsa_oaep_2048_sha512_224_mgf1sha512_224_test.json", |
| "rsa_oaep_2048_sha512_mgf1sha1_test.json", |
| "rsa_oaep_2048_sha512_mgf1sha512_test.json", |
| "rsa_oaep_3072_sha256_mgf1sha1_test.json", |
| "rsa_oaep_3072_sha256_mgf1sha256_test.json", |
| "rsa_oaep_3072_sha512_256_mgf1sha1_test.json", |
| "rsa_oaep_3072_sha512_256_mgf1sha512_256_test.json", |
| "rsa_oaep_3072_sha512_mgf1sha1_test.json", |
| "rsa_oaep_3072_sha512_mgf1sha512_test.json", |
| "rsa_oaep_4096_sha256_mgf1sha1_test.json", |
| "rsa_oaep_4096_sha256_mgf1sha256_test.json", |
| "rsa_oaep_4096_sha512_mgf1sha1_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 { |
| 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) |
| mgfHash := wycheproof.ParseHash(tg.MgfSha) |
| |
| 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) |
| opts := &rsa.OAEPOptions{ |
| Hash: hash, |
| MGFHash: mgfHash, |
| Label: label, |
| } |
| plaintext, err := priv.Decrypt(nil, ct, opts) |
| 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 TestRSAPKCS1DecryptWycheproof(t *testing.T) { |
| for _, file := range []string{ |
| "rsa_pkcs1_2048_test.json", |
| "rsa_pkcs1_3072_test.json", |
| "rsa_pkcs1_4096_test.json", |
| } { |
| var testdata wycheproof.RsaesPkcs1DecryptSchemaV1Json |
| wycheproof.LoadVectorFile(t, file, &testdata) |
| |
| for _, tg := range testdata.TestGroups { |
| rawPriv, err := x509.ParsePKCS8PrivateKey(wycheproof.MustDecodeHex(tg.PrivateKeyPkcs8)) |
| if err != nil { |
| t.Fatalf("%s: failed to parse PKCS #8 private key: %v", file, err) |
| } |
| priv := rawPriv.(*rsa.PrivateKey) |
| |
| for _, tv := range tg.Tests { |
| t.Run(wycheproof.TestName(file, tv), func(t *testing.T) { |
| t.Parallel() |
| |
| ct := wycheproof.MustDecodeHex(tv.Ct) |
| expectedMsg := wycheproof.MustDecodeHex(tv.Msg) |
| wantPass := wycheproof.ShouldPass(t, tv.Result, tv.Flags, nil) |
| |
| plaintext, err := rsa.DecryptPKCS1v15(nil, priv, ct) |
| if err != nil { |
| if wantPass { |
| t.Fatalf("DecryptPKCS1v15: %v", err) |
| } |
| return |
| } |
| if !wantPass { |
| t.Errorf("DecryptPKCS1v15 unexpectedly succeeded") |
| return |
| } |
| if !bytes.Equal(plaintext, expectedMsg) { |
| t.Errorf("plaintext mismatch: got %x, want %x", plaintext, expectedMsg) |
| } |
| }) |
| } |
| } |
| } |
| } |
| |
| 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, |
| } |
| |
| for file, overrideIDs := range filesOverrideToPassZeroSLen { |
| var testdata wycheproof.RsassaPssVerifySchemaV1Json |
| wycheproof.LoadVectorFile(t, file, &testdata) |
| |
| for _, tg := range testdata.TestGroups { |
| // Go's PSS implementation doesn't support different hash |
| // algorithms for message digest and MGF1. See #46233. |
| // Skip the affected test groups in rsa_pss_misc_test.json. |
| if file == "rsa_pss_misc_test.json" && tg.Sha != tg.MgfSha { |
| continue |
| } |
| |
| 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 |
| } |
| } |
| } |
| } |