package jws_test

import (
	"bufio"
	"bytes"
	"crypto"
	"crypto/ecdh"
	"crypto/ecdsa"
	"crypto/ed25519"
	"crypto/rsa"
	"crypto/sha256"
	"encoding/asn1"
	"errors"
	"fmt"
	"io"
	"maps"
	"math/big"
	"net/http"
	"net/http/httptest"
	"os"
	"sort"
	"strings"
	"testing"
	"time"

	"github.com/lestrrat-go/httprc/v3"
	"github.com/lestrrat-go/jwx/v3/internal/base64"
	"github.com/lestrrat-go/jwx/v3/internal/json"
	"github.com/lestrrat-go/jwx/v3/internal/jwxtest"
	"github.com/lestrrat-go/jwx/v3/internal/tokens"
	"github.com/lestrrat-go/jwx/v3/jwa"
	"github.com/lestrrat-go/jwx/v3/jwk"
	"github.com/lestrrat-go/jwx/v3/jws"
	"github.com/lestrrat-go/jwx/v3/jwt"
	"github.com/stretchr/testify/require"
)

const examplePayload = `{"iss":"joe",` + "\r\n" + ` "exp":1300819380,` + "\r\n" + ` "http://example.com/is_root":true}`
const exampleCompactSerialization = `eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk`
const badValue = "%badvalue%"

var hasES256K bool

func TestSanity(t *testing.T) {
	t.Run("sanity: Verify with single key", func(t *testing.T) {
		key, err := jwk.ParseKey([]byte(`{
    "kty": "oct",
    "k": "AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow"
  }`))
		require.NoError(t, err, `jwk.ParseKey should succeed`)
		payload, err := jws.Verify([]byte(exampleCompactSerialization), jws.WithKey(jwa.HS256(), key))
		require.NoError(t, err, `jws.Verify should succeed`)
		require.Equal(t, []byte(examplePayload), payload, `payloads should match`)
	})
	t.Run("sanity: Verification failure", func(t *testing.T) {
		key1, err := jwxtest.GenerateSymmetricJwk()
		require.NoError(t, err, `jwxtest.GenerateSymmetricJwk should succeed`)
		require.NoError(t, key1.Set(jwk.KeyIDKey, "key1"), `key1.Set should succeed`)
		key2, err := jwxtest.GenerateRsaJwk()
		require.NoError(t, err, `jwxtest.GenerateRsaJwk should succeed`)
		require.NoError(t, key2.Set(jwk.KeyIDKey, "key2"), `key2.Set should succeed`)
		key3, err := jwxtest.GenerateEcdsaJwk()
		require.NoError(t, err, `jwxtest.GenerateEcdsaJwk should succeed`)
		require.NoError(t, key3.Set(jwk.KeyIDKey, "key3"), `key3.Set should succeed`)

		payload := []byte(`Lorem Ipsum Dolor Sit Amet`)

		signed, err := jws.Sign(
			payload,
			jws.WithJSON(),
			jws.WithKey(jwa.HS256(), key1),
			jws.WithKey(jwa.RS256(), key2),
			jws.WithKey(jwa.ES256(), key3),
		)
		require.NoError(t, err, `jws.Sign should succeed`)

		t.Run("error type when parse fails", func(t *testing.T) {
			// try to verify a malformed jws message
			_, err = jws.Verify(
				[]byte(`this.is.not.a.ws.message`),
				jws.WithKey(jwa.HS256(), key1),
				jws.WithKey(jwa.RS256(), key2),
				jws.WithKey(jwa.ES256(), key3),
			)
			require.Error(t, err, `jws.Verify should fail`)

			// this should return true because it's an error returned from jws.Verify
			require.True(t, errors.Is(err, jws.VerifyError()), `errors.Is(jws.VerifyError()) should return true`)

			// this should return false because it's a parse error, not something from the verification process
			require.False(t, errors.Is(err, jws.VerificationError()), `errors.Is(jws.VerificationError()) should return false`)

			// this actually should be a parse error
			require.True(t, errors.Is(err, jws.ParseError()), `errors.Is(jws.ParseError()) should return true`)
		})

		t.Run("error type when verification fails", func(t *testing.T) {
			// Create new keys so that verification fails
			key1, err := jwxtest.GenerateSymmetricJwk()
			require.NoError(t, err, `jwxtest.GenerateSymmetricJwk should succeed`)
			require.NoError(t, key1.Set(jwk.KeyIDKey, "key1"), `key1.Set should succeed`)
			key2, err := jwxtest.GenerateRsaJwk()
			require.NoError(t, err, `jwxtest.GenerateRsaJwk should succeed`)
			require.NoError(t, key2.Set(jwk.KeyIDKey, "key2"), `key2.Set should succeed`)
			key3, err := jwxtest.GenerateEcdsaJwk()
			require.NoError(t, err, `jwxtest.GenerateEcdsaJwk should succeed`)
			require.NoError(t, key3.Set(jwk.KeyIDKey, "key3"), `key3.Set should succeed`)

			verified, err := jws.Verify(
				signed,
				jws.WithKey(jwa.HS256(), key1),
				jws.WithKey(jwa.RS256(), key2),
				jws.WithKey(jwa.ES256(), key3),
			)

			require.Error(t, err, `jws.Verify should fail`)
			require.Nil(t, verified, `verified should be nil`)

			// this should return true because it's an error returned from jws.Verify
			require.True(t, errors.Is(err, jws.VerifyError()), `errors.Is(jws.VerifyError()) should return true`)

			// this should also return true because it's an error returned from the verification process
			require.ErrorIs(t, err, jws.VerificationError(), `errors.Is(jws.VerificationError()) should return true (was %T)`, err)
		})
	})
}

func TestParseReader(t *testing.T) {
	t.Parallel()
	t.Run("Empty []byte", func(t *testing.T) {
		t.Parallel()
		_, err := jws.Parse(nil)
		require.Error(t, err, "Parsing an empty byte slice should result in an error")
	})
	t.Run("Empty bytes.Buffer", func(t *testing.T) {
		t.Parallel()
		_, err := jws.ParseReader(&bytes.Buffer{})
		require.Error(t, err, "Parsing an empty buffer should result in an error")
	})
	t.Run("Compact detached payload", func(t *testing.T) {
		t.Parallel()
		split := strings.Split(exampleCompactSerialization, ".")
		incoming := strings.Join([]string{split[0], "", split[2]}, ".")
		_, err := jws.ParseString(incoming)
		require.NoError(t, err, `jws.ParseString should succeed`)
	})
	t.Run("Compact missing header", func(t *testing.T) {
		t.Parallel()
		incoming := strings.Join(
			(strings.Split(
				exampleCompactSerialization,
				".",
			))[:2],
			".",
		)

		for _, useReader := range []bool{true, false} {
			var err error
			if useReader {
				// Force ParseReader() to choose un-optimized path by using bufio.NewReader
				_, err = jws.ParseReader(bufio.NewReader(strings.NewReader(incoming)))
			} else {
				_, err = jws.ParseString(incoming)
			}
			require.Error(t, err, "Parsing compact serialization with less than 3 parts should be an error")
		}
	})
	t.Run("Compact bad header", func(t *testing.T) {
		t.Parallel()
		parts := strings.Split(exampleCompactSerialization, ".")
		parts[0] = badValue
		incoming := strings.Join(parts, ".")

		for _, useReader := range []bool{true, false} {
			var err error
			if useReader {
				_, err = jws.ParseReader(bufio.NewReader(strings.NewReader(incoming)))
			} else {
				_, err = jws.ParseString(incoming)
			}
			require.Error(t, err, "Parsing compact serialization with bad header should be an error")
		}
	})
	t.Run("Compact bad payload", func(t *testing.T) {
		t.Parallel()
		parts := strings.Split(exampleCompactSerialization, ".")
		parts[1] = badValue
		incoming := strings.Join(parts, ".")

		for _, useReader := range []bool{true, false} {
			var err error
			if useReader {
				_, err = jws.ParseReader(bufio.NewReader(strings.NewReader(incoming)))
			} else {
				_, err = jws.ParseString(incoming)
			}
			require.Error(t, err, "Parsing compact serialization with bad payload should be an error")
		}
	})
	t.Run("Compact bad signature", func(t *testing.T) {
		t.Parallel()
		parts := strings.Split(exampleCompactSerialization, ".")
		parts[2] = badValue
		incoming := strings.Join(parts, ".")

		for _, useReader := range []bool{true, false} {
			var err error
			if useReader {
				_, err = jws.ParseReader(bufio.NewReader(strings.NewReader(incoming)))
			} else {
				_, err = jws.ParseString(incoming)
			}
			require.Error(t, err, "Parsing compact serialization with bad signature should be an error")
		}
	})
}

type dummyCryptoSigner struct {
	raw crypto.Signer
}

func (s *dummyCryptoSigner) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error) {
	return s.raw.Sign(rand, digest, opts)
}

func (s *dummyCryptoSigner) Public() crypto.PublicKey {
	return s.raw.Public()
}

var _ crypto.Signer = &dummyCryptoSigner{}

type dummyECDSACryptoSigner struct {
	raw *ecdsa.PrivateKey
}

func (es *dummyECDSACryptoSigner) Public() crypto.PublicKey {
	return es.raw.Public()
}

func (es *dummyECDSACryptoSigner) Sign(rand io.Reader, digest []byte, _ crypto.SignerOpts) ([]byte, error) {
	// The implementation is the same as ecdsaCryptoSigner.
	// This is just here to test the interface conversion
	r, s, err := ecdsa.Sign(rand, es.raw, digest)
	if err != nil {
		return nil, fmt.Errorf(`failed to sign payload using ecdsa: %w`, err)
	}

	return asn1.Marshal(struct {
		R *big.Int
		S *big.Int
	}{R: r, S: s})
}

var _ crypto.Signer = &dummyECDSACryptoSigner{}

func testRoundtrip(t *testing.T, payload []byte, alg jwa.SignatureAlgorithm, signKey any, keys map[string]any) {
	jwkKey, err := jwk.Import(signKey)
	require.NoError(t, err, `jwk.New should succeed`)
	signKeys := []struct {
		Name string
		Key  any
	}{
		{
			Name: "Raw Key",
			Key:  signKey,
		},
		{
			Name: "JWK Key",
			Key:  jwkKey,
		},
	}

	verifyKeys := make(map[string]any)

	maps.Copy(verifyKeys, keys)

	if es, ok := signKey.(*ecdsa.PrivateKey); ok {
		k := &dummyECDSACryptoSigner{raw: es}
		signKeys = append(signKeys, struct {
			Name string
			Key  any
		}{
			Name: "crypto.Signer",
			Key:  k,
		})
		verifyKeys["Verify(crypto.Signer)"] = k
	} else if cs, ok := signKey.(crypto.Signer); ok {
		k := &dummyCryptoSigner{raw: cs}
		signKeys = append(signKeys, struct {
			Name string
			Key  any
		}{
			Name: "crypto.Signer",
			Key:  k,
		})
		verifyKeys["Verify(crypto.Signer)"] = k
	}

	for _, key := range signKeys {
		t.Run(key.Name, func(t *testing.T) {
			signed, err := jws.Sign(payload, jws.WithKey(alg, key.Key))
			require.NoError(t, err, "jws.Sign should succeed")

			parsers := map[string]func([]byte) (*jws.Message, error){
				"ParseReader(io.Reader)": func(b []byte) (*jws.Message, error) { return jws.ParseReader(bufio.NewReader(bytes.NewReader(b))) },
				"Parse([]byte)":          func(b []byte) (*jws.Message, error) { return jws.Parse(b) },
				"ParseString(string)":    func(b []byte) (*jws.Message, error) { return jws.ParseString(string(b)) },
			}
			for name, f := range parsers {
				t.Run(name, func(t *testing.T) {
					t.Parallel()
					m, err := f(signed)
					require.NoError(t, err, "(%s) %s is successful", alg, name)
					require.Equal(t, payload, m.Payload(), "(%s) %s: Payload is decoded", alg, name)
				})
			}

			for name, testKey := range verifyKeys {
				t.Run(name, func(t *testing.T) {
					verified, err := jws.Verify(signed, jws.WithKey(alg, testKey))
					require.NoError(t, err, "(%s) Verify is successful", alg)
					require.Equal(t, payload, verified, "(%s) Verified payload is the same", alg)
				})
			}
		})
	}
}

func TestRoundtrip(t *testing.T) {
	t.Parallel()
	payload := []byte("Lorem ipsum")

	t.Run("HMAC", func(t *testing.T) {
		t.Parallel()
		sharedkey := []byte("Avracadabra")
		jwkKey, _ := jwk.Import(sharedkey)
		keys := map[string]any{
			"[]byte":  sharedkey,
			"jwk.Key": jwkKey,
		}
		hmacAlgorithms := []jwa.SignatureAlgorithm{jwa.HS256(), jwa.HS384(), jwa.HS512()}
		for _, alg := range hmacAlgorithms {
			t.Run(alg.String(), func(t *testing.T) {
				t.Parallel()
				testRoundtrip(t, payload, alg, sharedkey, keys)
			})
		}
	})
	t.Run("ECDSA", func(t *testing.T) {
		t.Parallel()
		key, err := jwxtest.GenerateEcdsaKey(jwa.P521())
		require.NoError(t, err, "ECDSA key generated")
		jwkKey, _ := jwk.Import(key.PublicKey)
		keys := map[string]any{
			"Verify(ecdsa.PublicKey)":  key.PublicKey,
			"Verify(*ecdsa.PublicKey)": &key.PublicKey,
			"Verify(jwk.Key)":          jwkKey,
		}
		for _, alg := range []jwa.SignatureAlgorithm{jwa.ES256(), jwa.ES384(), jwa.ES512()} {
			t.Run(alg.String(), func(t *testing.T) {
				t.Parallel()
				testRoundtrip(t, payload, alg, key, keys)
			})
		}
	})
	t.Run("RSA", func(t *testing.T) {
		t.Parallel()
		key, err := jwxtest.GenerateRsaKey()
		require.NoError(t, err, "RSA key generated")
		jwkKey, _ := jwk.Import(key.PublicKey)
		keys := map[string]any{
			"Verify(rsa.PublicKey)":  key.PublicKey,
			"Verify(*rsa.PublicKey)": &key.PublicKey,
			"Verify(jwk.Key)":        jwkKey,
		}
		for _, alg := range []jwa.SignatureAlgorithm{jwa.RS256(), jwa.RS384(), jwa.RS512(), jwa.PS256(), jwa.PS384(), jwa.PS512()} {
			t.Run(alg.String(), func(t *testing.T) {
				t.Parallel()
				testRoundtrip(t, payload, alg, key, keys)
			})
		}
	})
	t.Run("EdDSA", func(t *testing.T) {
		t.Parallel()
		key, err := jwxtest.GenerateEd25519Key()
		require.NoError(t, err, "ed25519 key generated")
		pubkey := key.Public()
		jwkKey, _ := jwk.Import(pubkey)
		keys := map[string]any{
			"Verify(ed25519.Public())": pubkey,
			// Meh, this doesn't work
			// "Verify(*ed25519.Public())": &pubkey,
			"Verify(jwk.Key)": jwkKey,
		}
		for _, alg := range []jwa.SignatureAlgorithm{jwa.EdDSA()} {
			t.Run(alg.String(), func(t *testing.T) {
				t.Parallel()
				testRoundtrip(t, payload, alg, key, keys)
			})
		}
	})
}

func TestSignMulti2(t *testing.T) {
	sharedkey := []byte("Avracadabra")
	payload := []byte("Lorem ipsum")
	hmacAlgorithms := []jwa.SignatureAlgorithm{jwa.HS256(), jwa.HS384(), jwa.HS512()}
	var options = []jws.SignOption{jws.WithJSON()}
	for _, alg := range hmacAlgorithms {
		options = append(options, jws.WithKey(alg, sharedkey)) // (signer, sharedkey, nil, nil))
	}
	signed, err := jws.Sign(payload, options...)
	require.NoError(t, err, `jws.Sign with multiple keys should succeed`)

	for _, alg := range hmacAlgorithms {
		m := jws.NewMessage()
		verified, err := jws.Verify(signed, jws.WithKey(alg, sharedkey), jws.WithMessage(m))
		require.NoError(t, err, "Verify succeeded")
		require.Equal(t, payload, verified, "verified payload matches")

		// XXX This actually doesn't really test much, but if there was anything
		// wrong, the process should have failed well before reaching here
		require.Equal(t, payload, m.Payload(), "message payload matches")
	}
}

func TestEncode(t *testing.T) {
	t.Parallel()

	t.Run("UnsecuredCompact", func(t *testing.T) {
		t.Parallel()
		s := `eyJhbGciOiJub25lIn0.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.`

		m, err := jws.ParseReader(strings.NewReader(s))
		require.NoError(t, err, "Parsing compact serialization")

		{
			v := map[string]any{}
			require.NoError(t, json.Unmarshal(m.Payload(), &v), "Unmarshal payload")
			require.Equal(t, v["iss"], "joe", "iss matches")
			require.Equal(t, int(v["exp"].(float64)), 1300819380, "exp matches")
			require.Equal(t, v["http://example.com/is_root"], true, "'http://example.com/is_root' matches")
		}

		require.Len(t, m.Signatures(), 1, "There should be 1 signature")

		signatures := m.Signatures()
		algorithm, ok := signatures[0].ProtectedHeaders().Algorithm()
		if !ok || algorithm != jwa.NoSignature() {
			t.Fatal("Algorithm in header does not match")
		}

		require.Empty(t, signatures[0].Signature(), "Signature should be empty")
	})
	t.Run("CompleteJSON", func(t *testing.T) {
		t.Parallel()
		s := `{
    "payload": "eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ",
    "signatures":[
      {
        "header": {"kid":"2010-12-29"},
        "protected":"eyJhbGciOiJSUzI1NiJ9",
        "signature": "cC4hiUPoj9Eetdgtv3hF80EGrhuB__dzERat0XF9g2VtQgr9PJbu3XOiZj5RZmh7AAuHIm4Bh-0Qc_lF5YKt_O8W2Fp5jujGbds9uJdbF9CUAr7t1dnZcAcQjbKBYNX4BAynRFdiuB--f_nZLgrnbyTyWzO75vRK5h6xBArLIARNPvkSjtQBMHlb1L07Qe7K0GarZRmB_eSN9383LcOLn6_dO--xi12jzDwusC-eOkHWEsqtFZESc6BfI7noOPqvhJ1phCnvWh6IeYI2w9QOYEUipUTI8np6LbgGY9Fs98rqVt5AXLIhWkWywlVmtVrBp0igcN_IoypGlUPQGe77Rw"
      },
      {
        "header": {"kid":"e9bc097a-ce51-4036-9562-d2ade882db0d"},
        "protected":"eyJhbGciOiJFUzI1NiJ9",
        "signature": "DtEhU3ljbEg8L38VWAfUAqOyKAM6-Xx-F4GawxaepmXFCgfTjDxw5djxLa8ISlSApmWQxfKTUJqPP3-Kg6NU1Q"
      }
    ]
  }`

		m, err := jws.ParseReader(strings.NewReader(s))
		require.NoError(t, err, "Unmarshal complete json serialization")
		require.Len(t, m.Signatures(), 2, "There should be 2 signatures")

		sigs := m.LookupSignature("2010-12-29")
		require.Len(t, sigs, 1, "There should be 1 signature with kid = '2010-12-29'")
	})
	t.Run("Protected Header lookup", func(t *testing.T) {
		t.Parallel()
		s := `{
    "payload": "eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ",
    "signatures":[
      {
        "header": {"cty":"example"},
        "protected":"eyJhbGciOiJFUzI1NiIsImtpZCI6ImU5YmMwOTdhLWNlNTEtNDAzNi05NTYyLWQyYWRlODgyZGIwZCJ9",
        "signature": "JcLb1udPAV72TayGv6eawZKlIQQ3K1NzB0fU7wwYoFypGxEczdCQU-V9jp4WwY2ueJKYeE4fF6jigB0PdSKR0Q"
      }
    ]
  }`

		// Protected Header is {"alg":"ES256","kid":"e9bc097a-ce51-4036-9562-d2ade882db0d"}
		// This protected header combination forces the parser/unmarshal to go trough the code path to populate and look for protected header fields.
		// The signature is valid.

		m, err := jws.ParseReader(strings.NewReader(s))
		require.NoError(t, err, "Unmarshal complete json serialization")
		require.Len(t, m.Signatures(), 1, "There should be 1 signature")

		sigs := m.LookupSignature("e9bc097a-ce51-4036-9562-d2ade882db0d")
		require.Len(t, sigs, 1, "There should be 1 signature with kid = '2010-12-29'")
	})
	t.Run("FlattenedJSON", func(t *testing.T) {
		t.Parallel()
		s := `{
    "payload": "eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ",
    "protected":"eyJhbGciOiJFUzI1NiJ9",
    "header": {
      "kid":"e9bc097a-ce51-4036-9562-d2ade882db0d"
    },
    "signature": "DtEhU3ljbEg8L38VWAfUAqOyKAM6-Xx-F4GawxaepmXFCgfTjDxw5djxLa8ISlSApmWQxfKTUJqPP3-Kg6NU1Q"
  }`

		m, err := jws.ParseReader(strings.NewReader(s))
		require.NoError(t, err, "Parsing flattened json serialization")
		require.Len(t, m.Signatures(), 1, "There should be 1 signature")

		jsonbuf, _ := json.MarshalIndent(m, "", "  ")
		t.Logf("%s", jsonbuf)
	})
	t.Run("SplitCompact", func(t *testing.T) {
		testcases := []struct {
			Name string
			Size int
		}{
			{Name: "Short", Size: 100},
			{Name: "Long", Size: 8000},
		}
		for _, tc := range testcases {
			size := tc.Size
			t.Run(tc.Name, func(t *testing.T) {
				t.Parallel()
				// Create payload with X.Y.Z
				var payload []byte
				for range size {
					payload = append(payload, 'X')
				}
				payload = append(payload, tokens.Period)
				for range size {
					payload = append(payload, 'Y')
				}
				payload = append(payload, tokens.Period)

				for range size {
					payload = append(payload, 'Y')
				}

				// Test using bytes, reader optimized and non-optimized path
				for _, method := range []int{0, 1, 2} {
					var x, y, z []byte
					var err error
					switch method {
					case 0: // bytes
						x, y, z, err = jws.SplitCompact(payload)
					case 1: // un-optimized io.Reader
						x, y, z, err = jws.SplitCompactReader(bytes.NewReader(payload))
					default: // optimized io.Reader
						x, y, z, err = jws.SplitCompactReader(bufio.NewReader(bytes.NewReader(payload)))
					}
					require.NoError(t, err, "SplitCompact should succeed")
					require.Len(t, x, size, "Length of header")
					require.Len(t, y, size, "Length of payload")
					require.Len(t, z, size, "Length of signature")
				}
			})
		}
	})
}

func TestReadFile(t *testing.T) {
	t.Parallel()

	f, err := os.CreateTemp(t.TempDir(), "test-read-file-*.jws")
	require.NoError(t, err, `io.CreateTemp should succeed`)
	defer f.Close()

	fmt.Fprintf(f, "%s", exampleCompactSerialization)

	_, err = jws.ReadFile(f.Name())
	require.NoError(t, err, `jws.ReadFile should succeed`)
}

func TestVerifyNonUniqueKid(t *testing.T) {
	const payload = "Lorem ipsum"
	const kid = "notUniqueKid"
	privateKey, err := jwxtest.GenerateRsaJwk()
	require.NoError(t, err, "jwxtest.GenerateJwk should succeed")
	_ = privateKey.Set(jwk.KeyIDKey, kid)
	signed, err := jws.Sign([]byte(payload), jws.WithKey(jwa.RS256(), privateKey))
	require.NoError(t, err, `jws.Sign should succeed`)
	correctKey, _ := jwk.PublicKeyOf(privateKey)
	_ = correctKey.Set(jwk.AlgorithmKey, jwa.RS256())

	makeSet := func(keys ...jwk.Key) jwk.Set {
		set := jwk.NewSet()
		for _, key := range keys {
			_ = set.AddKey(key)
		}
		return set
	}

	testcases := []struct {
		Name string
		Key  func() jwk.Key // Generates the "wrong" key
	}{
		{
			Name: `match 2 keys via same "kid"`,
			Key: func() jwk.Key {
				privateKey, _ := jwxtest.GenerateRsaJwk()
				wrongKey, _ := jwk.PublicKeyOf(privateKey)
				_ = wrongKey.Set(jwk.KeyIDKey, kid)
				_ = wrongKey.Set(jwk.AlgorithmKey, jwa.RS256())
				return wrongKey
			},
		},
		{
			Name: `match 2 keys via same "kid", same key value but different alg`,
			Key: func() jwk.Key {
				wrongKey, _ := correctKey.Clone()
				_ = wrongKey.Set(jwk.KeyIDKey, kid)
				_ = wrongKey.Set(jwk.AlgorithmKey, jwa.RS512())
				return wrongKey
			},
		},
		{
			Name: `match 2 keys via same "kid", same key type but different alg`,
			Key: func() jwk.Key {
				privateKey, _ := jwxtest.GenerateRsaJwk()
				wrongKey, _ := jwk.PublicKeyOf(privateKey)
				_ = wrongKey.Set(jwk.KeyIDKey, kid)
				_ = wrongKey.Set(jwk.AlgorithmKey, jwa.RS512())
				return wrongKey
			},
		},
		{
			Name: `match 2 keys via same "kid" and different key type / alg`,
			Key: func() jwk.Key {
				privateKey, _ := jwxtest.GenerateEcdsaKey(jwa.P256())
				wrongKey, err := jwk.PublicKeyOf(privateKey)
				require.NoError(t, err, `jwk.PublicKeyOf should succeed`)
				_ = wrongKey.Set(jwk.KeyIDKey, kid)
				_ = wrongKey.Set(jwk.AlgorithmKey, jwa.ES256K())
				return wrongKey
			},
		},
	}

	for _, tc := range testcases {
		wrongKey, err := tc.Key().Clone()
		require.NoError(t, err, `cloning wrong key should succeed`)
		for _, set := range []jwk.Set{makeSet(wrongKey, correctKey), makeSet(correctKey, wrongKey)} {
			t.Run(tc.Name, func(t *testing.T) {
				// Try matching in different orders
				var usedKey jwk.Key
				_, err = jws.Verify(signed, jws.WithKeySet(set, jws.WithMultipleKeysPerKeyID(true)), jws.WithKeyUsed(&usedKey))
				require.NoError(t, err, `jws.Verify should succeed`)
				require.Equal(t, usedKey, correctKey)
			})
		}
	}
}

func TestVerifySet(t *testing.T) {
	t.Parallel()
	const payload = "Lorem ipsum"

	makeSet := func(privkey jwk.Key) jwk.Set {
		set := jwk.NewSet()
		k1, err := jwk.Import([]byte("abracadabra"))
		require.NoError(t, err, `jwk.Import should succeed`)
		set.AddKey(k1)
		k2, err := jwk.Import([]byte("opensesame"))
		require.NoError(t, err, `jwk.Import should succeed`)
		set.AddKey(k2)
		pubkey, err := jwk.PublicKeyOf(privkey)
		require.NoError(t, err, `jwk.PublicKeyOf should succeed`)
		require.NoError(t, pubkey.Set(jwk.AlgorithmKey, jwa.RS256()), `setting algorithm should succeed`)
		set.AddKey(pubkey)
		return set
	}

	for _, useJSON := range []bool{true, false} {
		t.Run(fmt.Sprintf("useJSON=%t", useJSON), func(t *testing.T) {
			t.Parallel()
			t.Run(`match via "alg"`, func(t *testing.T) {
				t.Parallel()
				key, err := jwxtest.GenerateRsaJwk()
				require.NoError(t, err, "jwxtest.GenerateJwk should succeed")

				set := makeSet(key)
				signed, err := jws.Sign([]byte(payload), jws.WithKey(jwa.RS256(), key))
				require.NoError(t, err, `jws.Sign should succeed`)
				if useJSON {
					m, err := jws.Parse(signed)
					require.NoError(t, err, `jws.Parse should succeed`)
					signed, err = json.Marshal(m)
					require.NoError(t, err, `json.Marshal should succeed`)
				}

				var used jwk.Key
				verified, err := jws.Verify(signed, jws.WithKeySet(set, jws.WithRequireKid(false)), jws.WithKeyUsed(&used))
				require.NoError(t, err, `jws.Verify should succeed`)
				require.Equal(t, []byte(payload), verified, `payload should match`)
				expected, _ := jwk.PublicKeyOf(key)
				thumb1, _ := expected.Thumbprint(crypto.SHA1)
				thumb2, _ := used.Thumbprint(crypto.SHA1)
				require.Equal(t, thumb1, thumb2, `keys should match`)
			})
			t.Run(`match via "kid"`, func(t *testing.T) {
				t.Parallel()

				key, err := jwxtest.GenerateRsaJwk()
				require.NoError(t, err, "jwxtest.GenerateJwk should succeed")
				key.Set(jwk.KeyIDKey, `mykey`)

				set := makeSet(key)
				signed, err := jws.Sign([]byte(payload), jws.WithKey(jwa.RS256(), key))
				require.NoError(t, err, `jws.Sign should succeed`)
				if useJSON {
					m, err := jws.Parse(signed)
					require.NoError(t, err, `jws.Parse should succeed`)
					signed, err = json.Marshal(m)
					require.NoError(t, err, `json.Marshal should succeed`)
				}

				var used jwk.Key
				verified, err := jws.Verify(signed, jws.WithKeySet(set), jws.WithKeyUsed(&used))
				require.NoError(t, err, `jws.Verify should succeed`)
				require.Equal(t, []byte(payload), verified, `payload should match`)
				expected, _ := jwk.PublicKeyOf(key)
				thumb1, _ := expected.Thumbprint(crypto.SHA1)
				thumb2, _ := used.Thumbprint(crypto.SHA1)
				require.Equal(t, thumb1, thumb2, `keys should match`)
			})
		})
	}
}

func TestCustomField(t *testing.T) {
	// XXX has global effect!!!
	const rfc3339Key = `x-test-rfc3339`
	const rfc1123Key = `x-test-rfc1123`
	jws.RegisterCustomField(rfc3339Key, time.Time{})
	jws.RegisterCustomField(rfc1123Key, jws.CustomDecodeFunc(func(data []byte) (any, error) {
		var s string
		if err := json.Unmarshal(data, &s); err != nil {
			return nil, err
		}
		return time.Parse(time.RFC1123, s)
	}))

	defer jws.RegisterCustomField(rfc3339Key, nil)
	defer jws.RegisterCustomField(rfc1123Key, nil)

	expected := time.Date(2015, 11, 4, 5, 12, 52, 0, time.UTC)
	rfc3339bytes, _ := expected.MarshalText() // RFC3339
	rfc1123bytes := expected.Format(time.RFC1123)

	plaintext := []byte("Hello, World!")
	rsakey, err := jwxtest.GenerateRsaJwk()
	require.NoError(t, err, `jwxtest.GenerateRsaJwk() should succeed`)

	t.Run("jws.Parse", func(t *testing.T) {
		protected := jws.NewHeaders()
		protected.Set(rfc3339Key, string(rfc3339bytes))
		protected.Set(rfc1123Key, rfc1123bytes)

		encrypted, err := jws.Sign(plaintext, jws.WithKey(jwa.RS256(), rsakey, jws.WithProtectedHeaders(protected)))
		require.NoError(t, err, `jws.Sign should succeed`)
		msg, err := jws.Parse(encrypted)
		require.NoError(t, err, `jws.Parse should succeed`)
		for _, key := range []string{rfc3339Key, rfc1123Key} {
			var v time.Time
			require.NoError(t, msg.Signatures()[0].ProtectedHeaders().Get(key, &v), `msg.Get(%q) should succeed`, key)
			require.Equal(t, expected, v, `values should match`)
		}
	})
	t.Run("json.Unmarshal", func(t *testing.T) {
		protected := jws.NewHeaders()
		protected.Set(rfc3339Key, string(rfc3339bytes))
		protected.Set(rfc1123Key, rfc1123bytes)

		encrypted, err := jws.Sign(plaintext, jws.WithKey(jwa.RS256(), rsakey, jws.WithProtectedHeaders(protected)), jws.WithJSON())
		require.NoError(t, err, `jws.Sign should succeed`)
		msg := jws.NewMessage()
		require.NoError(t, json.Unmarshal(encrypted, msg), `json.Unmarshal should succeed`)

		for _, key := range []string{rfc3339Key, rfc1123Key} {
			var v time.Time
			require.NoError(t, msg.Signatures()[0].ProtectedHeaders().Get(key, &v), `msg.Get(%q) should succeed`, key)
			require.Equal(t, expected, v, `values should match`)
		}
	})

	/*
		// XXX has global effect!!!
		jws.RegisterCustomField(`x-birthday`, time.Time{})
		defer jws.RegisterCustomField(`x-birthday`, nil)

		expected := time.Date(2015, 11, 4, 5, 12, 52, 0, time.UTC)
		bdaybytes, _ := expected.MarshalText() // RFC3339

		payload := "Hello, World!"
		privkey, err := jwxtest.GenerateRsaJwk()
		require.NoError(t, err, `jwxtest.GenerateRsaJwk() should succeed`)

		hdrs := jws.NewHeaders()
		hdrs.Set(`x-birthday`, string(bdaybytes))

		signed, err := jws.Sign([]byte(payload), jws.WithKey(jwa.RS256(), privkey, jws.WithProtectedHeaders(hdrs)))
		require.NoError(t, err, `jws.Sign should succeed`)

		t.Run("jws.Parse + json.Unmarshal", func(t *testing.T) {
			msg, err := jws.Parse(signed)
			require.NoError(t, err, `jws.Parse should succeed`)

			var v any
			require.NoError(t, msg.Signatures()[0].ProtectedHeaders().Get(`x-birthday`, &v), `msg.Signatures()[0].ProtectedHeaders().Get("x-birthday") should succeed`)

			require.Equal(t, expected, v, `values should match`)

			// Create JSON from jws.Message
			buf, err := json.Marshal(msg)
			require.NoError(t, err, `json.Marshal should succeed`)

			var msg2 jws.Message
			require.NoError(t, json.Unmarshal(buf, &msg2), `json.Unmarshal should succeed`)

			v = nil
			require.NoError(t, msg2.Signatures()[0].ProtectedHeaders().Get(`x-birthday`, &v), `msg2.Signatures()[0].ProtectedHeaders().Get("x-birthday") should succeed`)

			require.Equal(t, expected, v, `values should match`)
		})
	*/
}

func TestWithMessage(t *testing.T) {
	key, err := jwxtest.GenerateRsaKey()
	require.NoError(t, err, "jwxtest.Generate should succeed")

	const text = "hello, world"
	signed, err := jws.Sign([]byte(text), jws.WithKey(jwa.RS256(), key))
	require.NoError(t, err, `jws.Sign should succeed`)

	m := jws.NewMessage()
	payload, err := jws.Verify(signed, jws.WithKey(jwa.RS256(), key.PublicKey), jws.WithMessage(m))
	require.NoError(t, err, `jws.Verify should succeed`)
	require.Equal(t, payload, []byte(text), `jws.Verify should produce the correct payload`)

	parsed, err := jws.Parse(signed)
	require.NoError(t, err, `jws.Parse should succeed`)

	// The result of using jws.WithMessage should match the result of jws.Parse
	buf1, _ := json.Marshal(m)
	buf2, _ := json.Marshal(parsed)

	require.Equal(t, buf1, buf2, `result of jws.PArse and jws.Verify(..., jws.WithMessage()) should match`)
}

func TestRFC7797(t *testing.T) {
	const keysrc = `{"kty":"oct",
      "k":"AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow"
     }`

	key, err := jwk.ParseKey([]byte(keysrc))
	require.NoError(t, err, `jwk.Parse should succeed`)

	t.Run("Invalid payload when b64 = false and NOT detached", func(t *testing.T) {
		const payload = `$.02`
		hdrs := jws.NewHeaders()
		hdrs.Set("b64", false)
		hdrs.Set("crit", "b64")

		_, err := jws.Sign([]byte(payload), jws.WithKey(jwa.HS256(), key, jws.WithProtectedHeaders(hdrs)))
		require.Error(t, err, `jws.Sign should fail`)
	})
	t.Run("Invalid usage when b64 = false and NOT detached", func(t *testing.T) {
		const payload = `$.02`
		hdrs := jws.NewHeaders()
		hdrs.Set("b64", false)
		hdrs.Set("crit", "b64")

		_, err := jws.Sign([]byte(payload), jws.WithKey(jwa.HS256(), key, jws.WithProtectedHeaders(hdrs)), jws.WithDetachedPayload([]byte(payload)))
		require.Error(t, err, `jws.Sign should fail`)
	})
	t.Run("Valid payload when b64 = false", func(t *testing.T) {
		testcases := []struct {
			Name     string
			Payload  []byte
			Detached bool
		}{
			{
				Name:     `(Detached) payload contains a period`,
				Payload:  []byte(`$.02`),
				Detached: true,
			},
			{
				Name:    `(NOT detached) payload does not contain a period`,
				Payload: []byte(`hell0w0rld`),
			},
		}

		for _, tc := range testcases {
			t.Run(tc.Name, func(t *testing.T) {
				hdrs := jws.NewHeaders()
				hdrs.Set("b64", false)
				hdrs.Set("crit", "b64")

				payload := tc.Payload
				signOptions := []jws.SignOption{jws.WithKey(jwa.HS256(), key, jws.WithProtectedHeaders(hdrs))}
				var verifyOptions []jws.VerifyOption
				verifyOptions = append(verifyOptions, jws.WithKey(jwa.HS256(), key))
				if tc.Detached {
					signOptions = append(signOptions, jws.WithDetachedPayload(payload))
					verifyOptions = append(verifyOptions, jws.WithDetachedPayload(payload))
					payload = nil
				}
				signed, err := jws.Sign(payload, signOptions...)
				require.NoError(t, err, `jws.Sign should succeed`)

				verified, err := jws.Verify(signed, verifyOptions...)
				require.NoError(t, err, `jws.Verify should succeed`)
				require.Equal(t, tc.Payload, verified, `payload should match`)
			})
		}
	})

	t.Run("Verify", func(t *testing.T) {
		detached := []byte(`$.02`)
		testcases := []struct {
			Name          string
			Input         []byte
			VerifyOptions []jws.VerifyOption
			Error         bool
		}{
			{
				Name: "JSON format",
				Input: []byte(`{
      "protected": "eyJhbGciOiJIUzI1NiIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19",
      "payload": "$.02",
      "signature": "A5dxf2s96_n5FLueVuW1Z_vh161FwXZC4YLPff6dmDY"
     }`),
			},
			{
				Name: "JSON format (detached payload)",
				VerifyOptions: []jws.VerifyOption{
					jws.WithDetachedPayload(detached),
				},
				Input: []byte(`{
      "protected": "eyJhbGciOiJIUzI1NiIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19",
      "signature": "A5dxf2s96_n5FLueVuW1Z_vh161FwXZC4YLPff6dmDY"
     }`),
			},
			{
				Name:  "JSON Format (b64 does not match)",
				Error: true,
				Input: []byte(`{
					"signatures": [
						{
							"protected": "eyJhbGciOiJIUzI1NiIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19",
				            "signature": "A5dxf2s96_n5FLueVuW1Z_vh161FwXZC4YLPff6dmDY"
						},
						{
							"protected": "eyJhbGciOiJIUzI1NiIsImI2NCI6dHJ1ZSwiY3JpdCI6WyJiNjQiXX0",
							"signature": "6BjugbC8MfrT_yy5WxWVFZrEHVPDtpdsV9u-wbzQDV8"
						}
					],
					"payload":"$.02"
				}`),
			},
			{
				Name:  "Compact (detached payload)",
				Input: []byte(`eyJhbGciOiJIUzI1NiIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19..A5dxf2s96_n5FLueVuW1Z_vh161FwXZC4YLPff6dmDY`),
				VerifyOptions: []jws.VerifyOption{
					jws.WithDetachedPayload(detached),
				},
			},
		}

		for _, tc := range testcases {
			t.Run(tc.Name, func(t *testing.T) {
				options := tc.VerifyOptions
				options = append(options, jws.WithKey(jwa.HS256(), key))
				payload, err := jws.Verify(tc.Input, options...)
				if tc.Error {
					require.Error(t, err, `jws.Verify should fail`)
					require.True(t, errors.Is(err, jws.VerifyError()), `jws.IsVerifyError should return true`)
					require.False(t, errors.Is(err, jws.VerificationError()), `jws.IsVerifyError should return false`)
				} else {
					require.NoError(t, err, `jws.Verify should succeed`)
					require.Equal(t, detached, payload, `payload should match`)
				}
			})
		}
	})
}

func TestGH485(t *testing.T) {
	const payload = `eyJhIjoiYiJ9`
	const protected = `eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiIsImNyaXQiOlsiZXhwIl0sImV4cCI6MCwiaXNzIjoiZm9vIiwibmJmIjowLCJpYXQiOjB9`
	const signature = `qM0CdRcyR4hw03J2ThJDat3Af40U87wVCF3Tp3xsyOg`
	const expected = `{"a":"b"}`
	signed := fmt.Sprintf(`{
    "payload": %q,
    "signatures": [{"protected": %q, "signature": %q}]
}`, payload, protected, signature)

	verified, err := jws.Verify([]byte(signed), jws.WithKey(jwa.HS256(), []byte("secret")))
	require.NoError(t, err, `jws.Verify should succeed`)
	require.Equal(t, expected, string(verified), `verified payload should match`)

	compact := strings.Join([]string{protected, payload, signature}, ".")
	verified, err = jws.Verify([]byte(compact), jws.WithKey(jwa.HS256(), []byte("secret")))
	require.NoError(t, err, `jws.Verify should succeed`)
	require.Equal(t, expected, string(verified), `verified payload should match`)
}

func TestJKU(t *testing.T) {
	ctx := t.Context()

	key, err := jwxtest.GenerateRsaJwk()
	require.NoError(t, err, `jwxtest.GenerateRsaJwk should succeed`)

	key.Set(jwk.KeyIDKey, `my-awesome-key`)

	pubkey, err := jwk.PublicKeyOf(key)
	require.NoError(t, err, `jwk.PublicKeyOf should succeed`)
	set := jwk.NewSet()
	set.AddKey(pubkey)
	srv := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusOK)
		json.NewEncoder(w).Encode(set)
	}))
	defer srv.Close()

	payload := []byte("Lorem Ipsum")

	t.Run("Compact", func(t *testing.T) {
		testcases := []struct {
			Name         string
			Error        bool
			Query        string
			Fetcher      func() jwk.Fetcher
			FetchOptions func() []jwk.FetchOption
		}{
			{
				Name:  "Fail without whitelist",
				Error: true,
				FetchOptions: func() []jwk.FetchOption {
					return []jwk.FetchOption{jwk.WithHTTPClient(srv.Client())}
				},
			},
			{
				Name: "Success",
				FetchOptions: func() []jwk.FetchOption {
					return []jwk.FetchOption{
						jwk.WithFetchWhitelist(jwk.InsecureWhitelist{}),
						jwk.WithHTTPClient(srv.Client()),
					}
				},
			},
			{
				Name:  "Rejected by whitelist",
				Error: true,
				FetchOptions: func() []jwk.FetchOption {
					wl := jwk.NewMapWhitelist().Add(`https://github.com/lestrrat-go/jwx/v3`)
					return []jwk.FetchOption{
						jwk.WithFetchWhitelist(wl),
						jwk.WithHTTPClient(srv.Client()),
					}
				},
			},
			{
				Name: "Cache",
				Fetcher: func() jwk.Fetcher {
					c, err := jwk.NewCache(ctx, httprc.NewClient())
					require.NoError(t, err, `jwk.NewCache should succeed`)
					require.NoError(t, c.Register(ctx, srv.URL, jwk.WithHTTPClient(srv.Client())), `c.Register should succeed`)
					return jwk.NewCachedFetcher(c)
				},
			},
		}

		for _, tc := range testcases {
			t.Run(tc.Name, func(t *testing.T) {
				hdr := jws.NewHeaders()
				u := srv.URL
				if tc.Query != "" {
					u += "?" + tc.Query
				}
				hdr.Set(jws.JWKSetURLKey, u)
				signed, err := jws.Sign(payload, jws.WithKey(jwa.RS256(), key, jws.WithProtectedHeaders(hdr)))
				require.NoError(t, err, `jws.Sign should succeed`)

				var options []jwk.FetchOption
				if f := tc.FetchOptions; f != nil {
					options = append(options, f()...)
				}

				var fetcher jwk.Fetcher
				if f := tc.Fetcher; f != nil {
					fetcher = f()
				}
				decoded, err := jws.Verify(signed, jws.WithVerifyAuto(fetcher, options...))
				if tc.Error {
					require.Error(t, err, `jws.Verify should fail`)
				} else {
					require.NoError(t, err, `jws.Verify should succeed`)
					require.Equal(t, payload, decoded, `decoded payload should match`)
				}
			})
		}
	})
	t.Run("JSON", func(t *testing.T) {
		// scenario: create a JSON message, which contains 3 signature entries.
		// 1st and 3rd signatures are valid, but signed using keys that are not
		// present in the JWKS.
		// Only the second signature uses a key found in the JWKS
		var keys []jwk.Key
		for i := range 3 {
			key, err := jwxtest.GenerateRsaJwk()
			require.NoError(t, err, `jwxtest.GenerateRsaJwk should succeed`)
			key.Set(jwk.KeyIDKey, fmt.Sprintf(`used-%d`, i))
			keys = append(keys, key)
		}

		var unusedKeys []jwk.Key
		for i := range 2 {
			key, err := jwxtest.GenerateRsaJwk()
			require.NoError(t, err, `jwxtest.GenerateRsaJwk should succeed`)
			key.Set(jwk.KeyIDKey, fmt.Sprintf(`unused-%d`, i))
			unusedKeys = append(unusedKeys, key)
		}

		// The set should contain unused key, used key, and unused key.
		// ...but they need to be public keys
		set := jwk.NewSet()
		for _, key := range []jwk.Key{unusedKeys[0], keys[1], unusedKeys[1]} {
			pubkey, err := jwk.PublicKeyOf(key)
			require.NoError(t, err, `jwk.PublicKeyOf should succeed`)

			kid, ok := key.KeyID()
			require.True(t, ok, `key ID should be populated`)

			pubkid, ok := pubkey.KeyID()
			require.True(t, ok, `key ID should be populated`)

			require.Equal(t, kid, pubkid, `key ID should be populated`)
			set.AddKey(pubkey)
		}
		srv := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
			w.WriteHeader(http.StatusOK)
			json.NewEncoder(w).Encode(set)
		}))
		defer srv.Close()

		// Sign the payload using the three keys
		var signOptions = []jws.SignOption{jws.WithJSON()}
		for _, key := range keys {
			hdr := jws.NewHeaders()
			hdr.Set(jws.JWKSetURLKey, srv.URL)
			signOptions = append(signOptions, jws.WithKey(jwa.RS256(), key, jws.WithProtectedHeaders(hdr)))
		}

		signed, err := jws.Sign(payload, signOptions...)
		require.NoError(t, err, `jws.SignMulti should succeed`)

		testcases := []struct {
			Name         string
			FetchOptions func() []jwk.FetchOption
			Error        bool
		}{
			{
				Name:  "Fail without whitelist",
				Error: true,
			},
			{
				Name: "Success",
				FetchOptions: func() []jwk.FetchOption {
					return []jwk.FetchOption{
						jwk.WithFetchWhitelist(jwk.InsecureWhitelist{}),
					}
				},
			},
			{
				Name:  "Rejected by whitelist",
				Error: true,
				FetchOptions: func() []jwk.FetchOption {
					wl := jwk.NewMapWhitelist().Add(`https://github.com/lestrrat-go/jwx/v3`)
					return []jwk.FetchOption{
						jwk.WithFetchWhitelist(wl),
					}
				},
			},
		}

		for _, tc := range testcases {
			t.Run(tc.Name, func(t *testing.T) {
				m := jws.NewMessage()
				var options []jwk.FetchOption
				if fn := tc.FetchOptions; fn != nil {
					options = fn()
				}
				options = append(options, jwk.WithHTTPClient(srv.Client()))

				decoded, err := jws.Verify(signed, jws.WithVerifyAuto(nil, options...), jws.WithMessage(m))
				if tc.Error {
					require.Error(t, err, `jws.Verify should fail`)
				} else {
					require.NoError(t, err, `jws.Verify should succeed`)
					require.Equal(t, payload, decoded, `decoded payload should match`)
					// XXX This actually doesn't really test much, but if there was anything
					// wrong, the process should have failed well before reaching here
					require.Equal(t, payload, m.Payload(), "message payload matches")
				}
			})
		}
	})
}

func TestAlgorithmsForKey(t *testing.T) {
	rsaprivkey, err := jwxtest.GenerateRsaJwk()
	require.NoError(t, err, `jwxtest.GenerateRsaPrivateKey should succeed`)
	rsapubkey, err := rsaprivkey.PublicKey()
	require.NoError(t, err, `jwk (RSA) PublicKey() should succeed`)

	ecdsaprivkey, err := jwxtest.GenerateEcdsaJwk()
	require.NoError(t, err, `jwxtest.GenerateEcdsaPrivateKey should succeed`)
	ecdsapubkey, err := ecdsaprivkey.PublicKey()
	require.NoError(t, err, `jwk (ECDSA) PublicKey() should succeed`)

	testcases := []struct {
		Name     string
		Key      any
		Expected []jwa.SignatureAlgorithm
	}{
		{
			Name:     "Octet sequence",
			Key:      []byte("hello"),
			Expected: []jwa.SignatureAlgorithm{jwa.HS256(), jwa.HS384(), jwa.HS512()},
		},
		{
			Name:     "rsa.PublicKey",
			Key:      rsa.PublicKey{},
			Expected: []jwa.SignatureAlgorithm{jwa.RS256(), jwa.RS384(), jwa.RS512(), jwa.PS256(), jwa.PS384(), jwa.PS512()},
		},
		{
			Name:     "*rsa.PublicKey",
			Key:      &rsa.PublicKey{},
			Expected: []jwa.SignatureAlgorithm{jwa.RS256(), jwa.RS384(), jwa.RS512(), jwa.PS256(), jwa.PS384(), jwa.PS512()},
		},
		{
			Name:     "jwk.RSAPublicKey",
			Key:      rsapubkey,
			Expected: []jwa.SignatureAlgorithm{jwa.RS256(), jwa.RS384(), jwa.RS512(), jwa.PS256(), jwa.PS384(), jwa.PS512()},
		},
		{
			Name:     "ecdsa.PublicKey",
			Key:      ecdsa.PublicKey{},
			Expected: []jwa.SignatureAlgorithm{jwa.ES256(), jwa.ES384(), jwa.ES512()},
		},
		{
			Name:     "*ecdsa.PublicKey",
			Key:      &ecdsa.PublicKey{},
			Expected: []jwa.SignatureAlgorithm{jwa.ES256(), jwa.ES384(), jwa.ES512()},
		},
		{
			Name:     "jwk.ECDSAPublicKey",
			Key:      ecdsapubkey,
			Expected: []jwa.SignatureAlgorithm{jwa.ES256(), jwa.ES384(), jwa.ES512()},
		},
		{
			Name:     "rsa.PrivateKey",
			Key:      rsa.PrivateKey{},
			Expected: []jwa.SignatureAlgorithm{jwa.RS256(), jwa.RS384(), jwa.RS512(), jwa.PS256(), jwa.PS384(), jwa.PS512()},
		},
		{
			Name:     "*rsa.PrivateKey",
			Key:      &rsa.PrivateKey{},
			Expected: []jwa.SignatureAlgorithm{jwa.RS256(), jwa.RS384(), jwa.RS512(), jwa.PS256(), jwa.PS384(), jwa.PS512()},
		},
		{
			Name:     "jwk.RSAPrivateKey",
			Key:      rsapubkey,
			Expected: []jwa.SignatureAlgorithm{jwa.RS256(), jwa.RS384(), jwa.RS512(), jwa.PS256(), jwa.PS384(), jwa.PS512()},
		},
		{
			Name:     "ecdsa.PrivateKey",
			Key:      ecdsa.PrivateKey{},
			Expected: []jwa.SignatureAlgorithm{jwa.ES256(), jwa.ES384(), jwa.ES512()},
		},
		{
			Name:     "*ecdsa.PrivateKey",
			Key:      &ecdsa.PrivateKey{},
			Expected: []jwa.SignatureAlgorithm{jwa.ES256(), jwa.ES384(), jwa.ES512()},
		},
		{
			Name:     "jwk.ECDSAPrivateKey",
			Key:      ecdsaprivkey,
			Expected: []jwa.SignatureAlgorithm{jwa.ES256(), jwa.ES384(), jwa.ES512()},
		},
		{
			Name:     "ed25519.PublicKey",
			Key:      ed25519.PublicKey(nil),
			Expected: []jwa.SignatureAlgorithm{jwa.EdDSA()},
		},
		{
			Name:     "x25519.PublicKey",
			Key:      &ecdh.PublicKey{},
			Expected: []jwa.SignatureAlgorithm{jwa.EdDSA()},
		},
	}

	for _, tc := range testcases {
		if hasES256K {
			if strings.Contains(strings.ToLower(tc.Name), `ecdsa`) {
				tc.Expected = append(tc.Expected, jwa.ES256K())
			}
		}

		sort.Slice(tc.Expected, func(i, j int) bool {
			return tc.Expected[i].String() < tc.Expected[j].String()
		})
		t.Run(tc.Name, func(t *testing.T) {
			algs, err := jws.AlgorithmsForKey(tc.Key)
			require.NoError(t, err, `jws.AlgorithmsForKey should succeed`)

			sort.Slice(algs, func(i, j int) bool {
				return algs[i].String() < algs[j].String()
			})
			require.Equal(t, tc.Expected, algs, `results should match`)
		})
	}
}

func TestGH681(t *testing.T) {
	privkey, err := jwxtest.GenerateRsaKey()
	require.NoError(t, err, "failed to create private key")

	buf, err := jws.Sign(nil, jws.WithKey(jwa.RS256(), privkey), jws.WithDetachedPayload([]byte("Lorem ipsum")))
	require.NoError(t, err, "failed to sign payload")

	t.Logf("%s", buf)

	_, err = jws.Verify(buf, jws.WithKey(jwa.RS256(), &privkey.PublicKey), jws.WithDetachedPayload([]byte("Lorem ipsum")))
	require.NoError(t, err, "failed to verify JWS message")
}

func TestGH840(t *testing.T) {
	// Go 1.19+ panics if elliptic curve operations are called against
	// a point that's _NOT_ on the curve
	untrustedJWK := []byte(`{
		"kty": "EC",
		"crv": "P-256",
		"x": "MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqx7D4",
		"y": "4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM",
		"d": "870MB6gfuTJ4HtUnUvYMyJpr5eUZNP4Bk43bVdj3eAE"
	}`)

	// Parse, serialize, slice and dice JWKs!
	privkey, err := jwk.ParseKey(untrustedJWK)
	require.NoError(t, err, `jwk.ParseKey should succeed`)

	pubkey, err := jwk.PublicKeyOf(privkey)
	require.NoError(t, err, `jwk.PublicKeyOf should succeed`)

	tok, err := jwt.NewBuilder().
		Issuer(`github.com/lestrrat-go/jwx`).
		IssuedAt(time.Now()).
		Build()
	require.NoError(t, err, `jwt.NewBuilder should succeed`)

	// As of go1.24.0, generating a signature with a private key that has
	// X/Y that's not on the curve will fail, but all go < 1.24 will succeed.
	// Instead of checking the version, we'll just check if the operation fails,
	// and if it does we won't run the check for jwt.Parse
	signed, err := jwt.Sign(tok, jwt.WithKey(jwa.ES256(), privkey))
	if err != nil {
		require.Error(t, err, `jwt.Sign should fail`)
		return
	}
	require.NoError(t, err, `jwt.Sign should succeed`)

	_, err = jwt.Parse(signed, jwt.WithKey(jwa.ES256(), pubkey))
	require.Error(t, err, `jwt.Parse should FAIL`) // pubkey's X/Y is not on the curve
}

func TestGH888(t *testing.T) {
	// This should fail because we're passing multiple keys (i.e. multiple signatures)
	// and yet we haven't specified JSON serialization
	_, err := jws.Sign([]byte(`foo`), jws.WithInsecureNoSignature(), jws.WithKey(jwa.HS256(), []byte(`bar`)))
	require.Error(t, err, `jws.Sign with multiple keys (including alg=none) should fail`)

	// This should pass because we can now have multiple signatures with JSON serialization
	signed, err := jws.Sign([]byte(`foo`), jws.WithInsecureNoSignature(), jws.WithKey(jwa.HS256(), []byte(`bar`)), jws.WithJSON())
	require.NoError(t, err, `jws.Sign should succeed`)

	message, err := jws.Parse(signed)
	require.NoError(t, err, `jws.Parse should succeed`)

	// Look for alg=none signature
	var foundNoSignature bool
	for _, sig := range message.Signatures() {
		if v, ok := sig.ProtectedHeaders().Algorithm(); !ok || v != jwa.NoSignature() {
			continue
		}

		require.Nil(t, sig.Signature(), `signature must be nil for alg=none`)
		foundNoSignature = true
	}
	require.True(t, foundNoSignature, `signature with no signature was found`)

	_, err = jws.Verify(signed)
	require.Error(t, err, `jws.Verify should fail`)

	_, err = jws.Verify(signed, jws.WithKey(jwa.NoSignature(), nil))
	require.Error(t, err, `jws.Verify should fail`)

	// Note: you can't do jws.Verify(..., jws.WithInsecureNoSignature())

	verified, err := jws.Verify(signed, jws.WithKey(jwa.HS256(), []byte(`bar`)))
	require.NoError(t, err, `jws.Verify should succeed`)
	require.Equal(t, []byte(`foo`), verified)
}

// Some stuff required for testing #910
// The original code used an external library to sign/verify, but here
// we just use a simple SHA256 digest here so that we don't force
// users to download an optional dependency
type s256SignerVerifier struct{}

var sha256Algo = jwa.NewSignatureAlgorithm("SillyTest256")

func (s256SignerVerifier) Algorithm() jwa.SignatureAlgorithm {
	return sha256Algo
}

func (s256SignerVerifier) Sign(payload []byte, _ any) ([]byte, error) {
	h := sha256.Sum256(payload)
	return h[:], nil
}

func (s256SignerVerifier) Verify(payload, signature []byte, _ any) error {
	h := sha256.Sum256(payload)
	if !bytes.Equal(h[:], signature) {
		return fmt.Errorf("invalid signature: expected %q, got %q", base64.EncodeToString(h[:]), base64.EncodeToString(signature))
	}
	return nil
}

func TestGH910(t *testing.T) {
	// Note: This has global effect. You can't run this in parallel with other tests
	jws.RegisterSigner(sha256Algo, jws.SignerFactoryFn(func() (jws.Signer, error) {
		return s256SignerVerifier{}, nil
	}))
	t.Cleanup(func() {
		jws.UnregisterSigner(sha256Algo)
	})

	jws.RegisterVerifier(sha256Algo, jws.VerifierFactoryFn(func() (jws.Verifier, error) {
		return s256SignerVerifier{}, nil
	}))
	t.Cleanup(func() {
		jws.UnregisterVerifier(sha256Algo)
		jwa.UnregisterSignatureAlgorithm(sha256Algo)
	})

	// Now that we have established that the signature algorithm works,
	// we can proceed with the test
	const src = `Lorem Ipsum`
	signed, err := jws.Sign([]byte(src), jws.WithKey(sha256Algo, nil))
	require.NoError(t, err, `jws.Sign should succeed`)

	verified, err := jws.Verify(signed, jws.WithKey(sha256Algo, nil))
	require.NoError(t, err, `jws.Verify should succeed`)

	require.Equal(t, src, string(verified), `verified payload should match`)

	jws.UnregisterSigner(sha256Algo)

	// Now try after unregistering the signer for the algorithm
	_, err = jws.Sign([]byte(src), jws.WithKey(sha256Algo, nil))
	require.Error(t, err, `jws.Sign should succeed`)

	jws.RegisterSigner(sha256Algo, jws.SignerFactoryFn(func() (jws.Signer, error) {
		return s256SignerVerifier{}, nil
	}))

	_, err = jws.Sign([]byte(src), jws.WithKey(sha256Algo, nil))
	require.NoError(t, err, `jws.Sign should succeed`)
}

func TestUnpaddedSignatureR(t *testing.T) {
	// I brute-forced generating a key and signature where the R portion
	// of the signature was not padded by using the following code in the
	// first run, then copied the result to the test
	/*
		for i := 0; i < 10000; i++ {
			rawKey, err := jwxtest.GenerateEcdsaKey(jwa.P256)
			require.NoError(t, err, `jwxtest.GenerateEcdsaJwk should succeed`)

			key, err := jwk.Import(rawKey)
			require.NoError(t, err, `jwk.Import should succeed`)

			pubkey, _ := key.PublicKey()

			signed, err := jws.Sign([]byte("Lorem Ipsum"), jws.WithKey(jwa.ES256(), key))
			require.NoError(t, err, `jws.Sign should succeed`)

			message, err := jws.Parse(signed)
			require.NoError(t, err, `jws.Parse should succeed`)

			asJson, _ := json.Marshal(message)
			t.Logf("%s", asJson)

			for _, sig := range message.Signatures() {
				sigBytes := sig.Signature()
				if sigBytes[0] == 0x00 {
					// Found it!
					t.Logf("Found signature that can be unpadded.")
					t.Logf("Original signature: %q", base64.EncodeToString(sigBytes))

					//				unpaddedSig := append(sigBytes[1:31], sigBytes[32:]...)
					unpaddedSig := sigBytes[1:]
					t.Logf("Signature with first byte of R removed: %q", base64.EncodeToString(unpaddedSig))
					t.Logf("Original JWS payload: %q", signed)
					require.Len(t, unpaddedSig, 63)

					i := bytes.LastIndexByte(signed, tokens.Period)
					modified := append(signed[:i+1], base64.Encode(unpaddedSig)...)
					t.Logf("JWS payload with unpadded signature: %q", modified)

					// jws.Verify for sanity
					verified, err := jws.Verify(modified, jws.WithKey(jwa.ES256(), pubkey))
					require.NoError(t, err, `jws.Verify should succeed`)
					t.Logf("verified payload: %q", verified)

					buf, _ := json.Marshal(key)
					t.Logf("Private JWK: %s", buf)
					return
				}
			}
		}
	*/
	// Padded has R with a leading 0 (as it should)
	padded := "eyJhbGciOiJFUzI1NiJ9.TG9yZW0gSXBzdW0.ALFru4CRZDiAlVKyyHtlLGtXIAWxC3lXIlZuYO8G8a5ePzCwyw6c2FzWBZwrLaoLFZb_TcYs3TcZ8mhONPaavQ"
	// Unpadded has R with a leading 0 removed (31 bytes, WRONG)
	unpadded := "eyJhbGciOiJFUzI1NiJ9.TG9yZW0gSXBzdW0.sWu7gJFkOICVUrLIe2Usa1cgBbELeVciVm5g7wbxrl4_MLDLDpzYXNYFnCstqgsVlv9NxizdNxnyaE409pq9"

	// This is the private key used to sign the payload
	keySrc := `{"crv":"P-256","d":"MqGwMl-dlJFrMnu7rFyslPV8EdsVC7I4V19N-ADVqaU","kty":"EC","x":"Anf1p2lRrcXgZKpVRRC1xLxPiw_45PbOlygfbxvD8Es","y":"d0HiZq-aurVVLLtK-xqXPpzpWloZJNwKNve7akBDuvg"}`

	privKey, err := jwk.ParseKey([]byte(keySrc))
	require.NoError(t, err, `jwk.ParseKey should succeed`)

	pubKey, err := jwk.PublicKeyOf(privKey)
	require.NoError(t, err, `jwk.PublicKeyOf should succeed`)

	// Should always succeed
	payload, err := jws.Verify([]byte(padded), jws.WithKey(jwa.ES256(), pubKey))
	require.NoError(t, err, `jws.Verify should succeed`)
	require.Equal(t, "Lorem Ipsum", string(payload))

	// Should fail
	_, err = jws.Verify([]byte(unpadded), jws.WithKey(jwa.ES256(), pubKey))
	require.Error(t, err, `jws.Verify should fail`)
}

func TestValidateKey(t *testing.T) {
	privKey, err := jwxtest.GenerateRsaJwk()
	require.NoError(t, err, `jwxtest.GenerateRsaJwk should succeed`)

	signed, err := jws.Sign([]byte("Lorem Ipsum"), jws.WithKey(jwa.RS256(), privKey), jws.WithValidateKey(true))
	require.NoError(t, err, `jws.Sign should succeed`)

	// This should fail because D is empty
	require.NoError(t, privKey.Set(jwk.RSADKey, []byte(nil)), `jwk.Set should succeed`)
	_, err = jws.Sign([]byte("Lorem Ipsum"), jws.WithKey(jwa.RS256(), privKey), jws.WithValidateKey(true))
	require.Error(t, err, `jws.Sign should fail`)

	pubKey, err := jwk.PublicKeyOf(privKey)
	require.NoError(t, err, `jwk.PublicKeyOf should succeed`)

	n, ok := pubKey.(jwk.RSAPublicKey).N()
	require.True(t, ok, `N should be present`)

	// Set N to an empty value
	require.NoError(t, pubKey.Set(jwk.RSANKey, []byte(nil)), `jwk.Set should succeed`)

	// This is going to fail regardless, because the public key is now
	// invalid (empty N), but we want to make sure that it fails because
	// of the validation failing
	_, err = jws.Verify(signed, jws.WithKey(jwa.RS256(), pubKey), jws.WithValidateKey(true))
	require.Error(t, err, `jws.Verify should fail`)
	require.True(t, jwk.IsKeyValidationError(err), `jwk.IsKeyValidationError should return true`)

	// The following should now succeed, because N has been reinstated
	require.NoError(t, pubKey.Set(jwk.RSANKey, n), `jwk.Set should succeed`)
	_, err = jws.Verify(signed, jws.WithKey(jwa.RS256(), pubKey), jws.WithValidateKey(true))
	require.NoError(t, err, `jws.Verify should succeed`)
}

func TestEmptyProtectedField(t *testing.T) {
	// MEMO: this was the only test case from the original report
	// This passes. It should produce an invalid JWS message, but
	// that's not `jws.Parse`'s problem.
	_, err := jws.Parse([]byte(`{"signature": ""}`))
	require.NoError(t, err, `jws.Parse should fail`)

	// Also test that non-flattened serialization passes.
	_, err = jws.Parse([]byte(`{"signatures": [{}]}`))
	require.NoError(t, err, `jws.Parse should fail`)

	// MEMO: rest of the cases are present to be extra pedantic about it

	privKey, err := jwxtest.GenerateRsaJwk()
	require.NoError(t, err, `jwxtest.GenerateRsaJwk should succeed`)

	// This fails. `jws.Parse` works, but the subsequent verification
	// workflow fails to verify anything without the presence of a signature or
	// a protected header.
	_, err = jws.Verify([]byte(`{"signature": ""}`), jws.WithKey(jwa.RS256(), privKey))
	require.Error(t, err, `jws.Parse should fail`)

	// Create a valid signatre.
	signed, err := jws.Sign([]byte("Lorem Ipsum"), jws.WithKey(jwa.RS256(), privKey))
	require.NoError(t, err, `jws.Sign should succeed`)

	_, payload, signature, err := jws.SplitCompact(signed)
	require.NoError(t, err, `jws.SplitCompact should succeed`)

	// This fails as well. we have a valid signature and a valid
	// key to verify it, but no protected headers
	_, err = jws.Verify(
		fmt.Appendf(nil, `{"signature": "%s"}`, signature),
		jws.WithKey(jwa.RS256(), privKey),
	)
	require.Error(t, err, `jws.Verify should fail`)

	// Test for cases when we have an incomplete compact form JWS
	var buf bytes.Buffer
	buf.WriteRune(tokens.Period)
	buf.Write(payload)
	buf.WriteRune(tokens.Period)
	buf.Write(signature)
	invalidMessage := buf.Bytes()

	// This is an error because the format is simply wrong.
	// Whereas in the other JSON-based JWS's case the lack of protected field
	// is not a SYNTAX error, this one is, and therefore we barf.
	_, err = jws.Parse(invalidMessage)
	require.Error(t, err, `jws.Parse should fail`)
}

func TestParseFormat(t *testing.T) {
	privKey, err := jwxtest.GenerateRsaJwk()
	require.NoError(t, err, `jwxtest.GenerateRsaJwk should succeed`)

	signedCompact, err := jws.Sign([]byte("Lorem Ipsum"), jws.WithKey(jwa.RS256(), privKey), jws.WithValidateKey(true))
	require.NoError(t, err, `jws.Sign should succeed`)

	signedJSON, err := jws.Sign([]byte("Lorem Ipsum"), jws.WithKey(jwa.RS256(), privKey), jws.WithValidateKey(true), jws.WithJSON())
	require.NoError(t, err, `jws.Sign should succeed`)

	// Only compact formats should succeed
	_, err = jws.Verify(signedCompact, jws.WithKey(jwa.RS256(), privKey), jws.WithCompact())
	require.NoError(t, err, `jws.Verify should succeed`)
	_, err = jws.Verify(signedJSON, jws.WithKey(jwa.RS256(), privKey), jws.WithCompact())
	require.Error(t, err, `jws.Verify should fail`)
	_, err = jws.Parse(signedCompact, jws.WithCompact())
	require.NoError(t, err, `jws.Parse should succeed`)
	_, err = jws.Parse(signedJSON, jws.WithCompact())
	require.Error(t, err, `jws.Parse should fail`)

	// Only JSON formats should succeed
	_, err = jws.Verify(signedCompact, jws.WithKey(jwa.RS256(), privKey), jws.WithJSON())
	require.Error(t, err, `jws.Verify should fail`)
	_, err = jws.Verify(signedJSON, jws.WithKey(jwa.RS256(), privKey), jws.WithJSON())
	require.NoError(t, err, `jws.Verify should succeed`)
	_, err = jws.Parse(signedJSON, jws.WithJSON())
	require.NoError(t, err, `jws.Parse should succeed`)
	_, err = jws.Parse(signedCompact, jws.WithJSON())
	require.Error(t, err, `jws.Parse should fail`)

	// Either format should succeed
	_, err = jws.Verify(signedCompact, jws.WithKey(jwa.RS256(), privKey))
	require.NoError(t, err, `jws.Verify should succeed`)
	_, err = jws.Verify(signedCompact, jws.WithKey(jwa.RS256(), privKey), jws.WithJSON(), jws.WithCompact())
	require.NoError(t, err, `jws.Verify should succeed`)
	_, err = jws.Parse(signedCompact)
	require.NoError(t, err, `jws.Parse should succeed`)
	_, err = jws.Parse(signedCompact, jws.WithJSON(), jws.WithCompact())
	require.NoError(t, err, `jws.Parse should succeed`)

	_, err = jws.Verify(signedJSON, jws.WithKey(jwa.RS256(), privKey))
	require.NoError(t, err, `jws.Verify should succeed`)
	_, err = jws.Verify(signedJSON, jws.WithKey(jwa.RS256(), privKey), jws.WithJSON(), jws.WithCompact())
	require.NoError(t, err, `jws.Verify should succeed`)
	_, err = jws.Parse(signedJSON)
	require.NoError(t, err, `jws.Parse should succeed`)
	_, err = jws.Parse(signedJSON, jws.WithJSON(), jws.WithCompact())
	require.NoError(t, err, `jws.Parse should succeed`)
}

func BenchmarkSplitCompat(b *testing.B) {
	for b.Loop() {
		_, _, _, err := jws.SplitCompact([]byte(exampleCompactSerialization))
		if err != nil {
			panic(err)
		}
	}
}

func BenchmarkSplitCompatString(b *testing.B) {
	for b.Loop() {
		_, _, _, err := jws.SplitCompactString(exampleCompactSerialization)
		if err != nil {
			panic(err)
		}
	}
}
