Repository: lestrrat-go/jwx
Pull request #1019 (2023-11-20 03:55:59)

v2.0.17


Patch:
diff --git a/Changes b/Changes
index 26959387..9026242b 100644
--- a/Changes
+++ b/Changes
@@ -4,6 +4,17 @@ Changes
 v2 has many incompatibilities with v1. To see the full list of differences between
 v1 and v2, please read the Changes-v2.md file (https://github.com/lestrrat-go/jwx/blob/develop/v2/Changes-v2.md)
 
+v2.0.17 20 Nov 2023
+[Bug Fixes]
+  * [jws] Previously, `jws.UnregisterSigner` did not remove the previous signer instance when
+    the signer was registered and unregistered multiple times (#1016). This has been fixed.
+
+[New Features]
+  * [jwe] (EXPERIMENTAL) `jwe.WithCEK` has been added to extract the content encryption key (CEK) from the Decrypt operation.
+  * [jwe] (EXPERIMENTAL) `jwe.EncryptStatic` has been added to encrypt content using a static CEK.
+    Using static CEKs has serious security implications, and you should not use
+    this unless you completely understand the risks involved.
+
 v2.0.16 31 Oct 2023
 [Security]
   * [jws] ECDSA signature verification requires us to check if the signature
diff --git a/deps.bzl b/deps.bzl
index b33ddb72..620608b1 100644
--- a/deps.bzl
+++ b/deps.bzl
@@ -122,8 +122,8 @@ def go_dependencies():
         name = "org_golang_x_crypto",
         build_file_proto_mode = "disable_global",
         importpath = "golang.org/x/crypto",
-        sum = "h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=",
-        version = "v0.14.0",
+        sum = "h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA=",
+        version = "v0.15.0",
     )
     go_repository(
         name = "org_golang_x_mod",
@@ -152,23 +152,23 @@ def go_dependencies():
         name = "org_golang_x_sys",
         build_file_proto_mode = "disable_global",
         importpath = "golang.org/x/sys",
-        sum = "h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=",
-        version = "v0.13.0",
+        sum = "h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=",
+        version = "v0.14.0",
     )
     go_repository(
         name = "org_golang_x_term",
         build_file_proto_mode = "disable_global",
         importpath = "golang.org/x/term",
-        sum = "h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek=",
-        version = "v0.13.0",
+        sum = "h1:LGK9IlZ8T9jvdy6cTdfKUCltatMFOehAQo9SRC46UQ8=",
+        version = "v0.14.0",
     )
 
     go_repository(
         name = "org_golang_x_text",
         build_file_proto_mode = "disable_global",
         importpath = "golang.org/x/text",
-        sum = "h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=",
-        version = "v0.13.0",
+        sum = "h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=",
+        version = "v0.14.0",
     )
     go_repository(
         name = "org_golang_x_tools",
diff --git a/examples/go.sum b/examples/go.sum
index 015a0662..046e430e 100644
--- a/examples/go.sum
+++ b/examples/go.sum
@@ -36,8 +36,8 @@ github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5t
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
-golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
-golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
+golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA=
+golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g=
 golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
 golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@@ -59,21 +59,21 @@ golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
-golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
+golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
 golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
 golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
-golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
+golang.org/x/term v0.14.0/go.mod h1:TySc+nGkYR6qt8km8wUhuFRTVSMIX3XPR58y2lC8vww=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
 golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
 golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
 golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
-golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
+golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
diff --git a/go.mod b/go.mod
index 3ec01f0b..bcb6be23 100644
--- a/go.mod
+++ b/go.mod
@@ -11,5 +11,5 @@ require (
 	github.com/lestrrat-go/option v1.0.1
 	github.com/segmentio/asm v1.2.0
 	github.com/stretchr/testify v1.8.4
-	golang.org/x/crypto v0.14.0
+	golang.org/x/crypto v0.15.0
 )
diff --git a/go.sum b/go.sum
index de5d6be7..5219e1c5 100644
--- a/go.sum
+++ b/go.sum
@@ -32,8 +32,8 @@ github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXl
 github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
-golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
-golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
+golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA=
+golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g=
 golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
 golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@@ -52,19 +52,19 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
-golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
+golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
 golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
-golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
+golang.org/x/term v0.14.0/go.mod h1:TySc+nGkYR6qt8km8wUhuFRTVSMIX3XPR58y2lC8vww=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
 golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
 golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
-golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
+golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
diff --git a/jwe/decrypt.go b/jwe/decrypt.go
index 387d4a99..8729e4e5 100644
--- a/jwe/decrypt.go
+++ b/jwe/decrypt.go
@@ -28,6 +28,7 @@ type decrypter struct {
 	aad         []byte
 	apu         []byte
 	apv         []byte
+	cek         *[]byte
 	computedAad []byte
 	iv          []byte
 	keyiv       []byte
@@ -120,6 +121,11 @@ func (d *decrypter) Tag(tag []byte) *decrypter {
 	return d
 }
 
+func (d *decrypter) CEK(ptr *[]byte) *decrypter {
+	d.cek = ptr
+	return d
+}
+
 func (d *decrypter) ContentCipher() (content_crypt.Cipher, error) {
 	if d.cipher == nil {
 		switch d.ctalg {
@@ -161,6 +167,9 @@ func (d *decrypter) Decrypt(recipient Recipient, ciphertext []byte, msg *Message
 		return
 	}
 
+	if d.cek != nil {
+		*d.cek = cek
+	}
 	return plaintext, nil
 }
 
diff --git a/jwe/jwe.go b/jwe/jwe.go
index 67b8e97b..4a7cb19e 100644
--- a/jwe/jwe.go
+++ b/jwe/jwe.go
@@ -247,6 +247,29 @@ func (b *recipientBuilder) Build(cek []byte, calg jwa.ContentEncryptionAlgorithm
 // Look for options that return `jwe.EncryptOption` or `jws.EncryptDecryptOption`
 // for a complete list of options that can be passed to this function.
 func Encrypt(payload []byte, options ...EncryptOption) ([]byte, error) {
+	return encrypt(payload, nil, options...)
+}
+
+// Encryptstatic is exactly like Encrypt, except it accepts a static
+// content encryption key (CEK). It is separated out from the main
+// Encrypt function such that the latter does not accidentally use a static
+// CEK.
+//
+// DO NOT attempt to use this function unless you completely understand the
+// security implications to using static CEKs. You have been warned.
+//
+// This function is currently considered EXPERIMENTAL, and is subject to
+// future changes across minor/micro versions.
+func EncryptStatic(payload, cek []byte, options ...EncryptOption) ([]byte, error) {
+	if len(cek) <= 0 {
+		return nil, fmt.Errorf(`jwe.EncryptStatic: empty CEK`)
+	}
+	return encrypt(payload, cek, options...)
+}
+
+// encrypt is separate so it can receive cek from outside.
+// (but we don't want to receive it in the options slice)
+func encrypt(payload, cek []byte, options ...EncryptOption) ([]byte, error) {
 	// default content encryption algorithm
 	calg := jwa.A256GCM
 
@@ -327,12 +350,14 @@ func Encrypt(payload []byte, options ...EncryptOption) ([]byte, error) {
 		return nil, fmt.Errorf(`jwe.Encrypt: failed to create AES encrypter: %w`, err)
 	}
 
-	generator := keygen.NewRandom(contentcrypt.KeySize())
-	bk, err := generator.Generate()
-	if err != nil {
-		return nil, fmt.Errorf(`jwe.Encrypt: failed to generate key: %w`, err)
+	if len(cek) <= 0 {
+		generator := keygen.NewRandom(contentcrypt.KeySize())
+		bk, err := generator.Generate()
+		if err != nil {
+			return nil, fmt.Errorf(`jwe.Encrypt: failed to generate key: %w`, err)
+		}
+		cek = bk.Bytes()
 	}
-	cek := bk.Bytes()
 
 	recipients := make([]Recipient, len(builders))
 	for i, builder := range builders {
@@ -421,6 +446,7 @@ func Encrypt(payload []byte, options ...EncryptOption) ([]byte, error) {
 type decryptCtx struct {
 	msg              *Message
 	aad              []byte
+	cek              *[]byte
 	computedAad      []byte
 	keyProviders     []KeyProvider
 	protectedHeaders Headers
@@ -438,7 +464,7 @@ type decryptCtx struct {
 func Decrypt(buf []byte, options ...DecryptOption) ([]byte, error) {
 	var keyProviders []KeyProvider
 	var keyUsed interface{}
-
+	var cek *[]byte
 	var dst *Message
 	//nolint:forcetypeassert
 	for _, option := range options {
@@ -459,6 +485,8 @@ func Decrypt(buf []byte, options ...DecryptOption) ([]byte, error) {
 				alg: alg,
 				key: pair.key,
 			})
+		case identCEK{}:
+			cek = option.Value().(*[]byte)
 		}
 	}
 
@@ -517,6 +545,7 @@ func Decrypt(buf []byte, options ...DecryptOption) ([]byte, error) {
 	dctx.msg = msg
 	dctx.keyProviders = keyProviders
 	dctx.protectedHeaders = h
+	dctx.cek = cek
 
 	var lastError error
 	for _, recipient := range recipients {
@@ -583,7 +612,8 @@ func (dctx *decryptCtx) decryptContent(ctx context.Context, alg jwa.KeyEncryptio
 		AuthenticatedData(dctx.aad).
 		ComputedAuthenticatedData(dctx.computedAad).
 		InitializationVector(dctx.msg.initializationVector).
-		Tag(dctx.msg.tag)
+		Tag(dctx.msg.tag).
+		CEK(dctx.cek)
 
 	if recipient.Headers().Algorithm() != alg {
 		// algorithms don't match
diff --git a/jwe/options.yaml b/jwe/options.yaml
index 84f89666..623a1866 100644
--- a/jwe/options.yaml
+++ b/jwe/options.yaml
@@ -119,4 +119,13 @@ options:
       `jwk.Key` here unless you are 100% sure that all keys that you
       have provided are instances of `jwk.Key` (remember that the
       jwx API allows users to specify a raw key such as *rsa.PublicKey)
-
+  - ident: CEK
+    interface: DecryptOption
+    argument_type: '*[]byte'
+    comment: |
+      WithCEK allows users to specify a variable to store the CEK used in the
+      message upon successful decryption. The variable must be a pointer to
+      a byte slice, and it will only be populated if the decryption is successful.
+      
+      This option is currently considered EXPERIMENTAL, and is subject to
+      future changes across minor/micro versions.
diff --git a/jwe/options_gen.go b/jwe/options_gen.go
index c22e2a5f..cdb22bef 100644
--- a/jwe/options_gen.go
+++ b/jwe/options_gen.go
@@ -110,6 +110,7 @@ type withKeySetSuboption struct {
 
 func (*withKeySetSuboption) withKeySetSuboption() {}
 
+type identCEK struct{}
 type identCompress struct{}
 type identContentEncryptionAlgorithm struct{}
 type identFS struct{}
@@ -124,6 +125,10 @@ type identProtectedHeaders struct{}
 type identRequireKid struct{}
 type identSerialization struct{}
 
+func (identCEK) String() string {
+	return "WithCEK"
+}
+
 func (identCompress) String() string {
 	return "WithCompress"
 }
@@ -176,6 +181,16 @@ func (identSerialization) String() string {
 	return "WithSerialization"
 }
 
+// WithCEK allows users to specify a variable to store the CEK used in the
+// message upon successful decryption. The variable must be a pointer to
+// a byte slice, and it will only be populated if the decryption is successful.
+//
+// This option is currently considered EXPERIMENTAL, and is subject to
+// future changes across minor/micro versions.
+func WithCEK(v *[]byte) DecryptOption {
+	return &decryptOption{option.New(identCEK{}, v)}
+}
+
 // WithCompress specifies the compression algorithm to use when encrypting
 // a payload using `jwe.Encrypt` (Yes, we know it can only be "" or "DEF",
 // but the way the specification is written it could allow for more options,
diff --git a/jwk/interface.go b/jwk/interface.go
index 1b9598a4..fa0cff02 100644
--- a/jwk/interface.go
+++ b/jwk/interface.go
@@ -84,8 +84,9 @@ type Set interface {
 	// specify, and there is no way of knowing what type they could be.
 	Set(string, interface{}) error
 
-	// RemoveKey removes the specified non-key field from the set.
-	// Keys may not be removed using this method.
+	// Remove removes the specified non-key field from the set.
+	// Keys may not be removed using this method. See RemoveKey for
+	// removing keys.
 	Remove(string) error
 
 	// Index returns the index where the given key exists, -1 otherwise
@@ -101,6 +102,8 @@ type Set interface {
 	LookupKeyID(string) (Key, bool)
 
 	// RemoveKey removes the key from the set.
+	// RemoveKey returns an error when the specified key does not exist
+	// in set.
 	RemoveKey(Key) error
 
 	// Keys creates an iterator to iterate through all keys in the set.
diff --git a/jws/jws.go b/jws/jws.go
index a348c618..4cf2c317 100644
--- a/jws/jws.go
+++ b/jws/jws.go
@@ -74,6 +74,12 @@ func (s *payloadSigner) PublicHeader() Headers {
 var signers = make(map[jwa.SignatureAlgorithm]Signer)
 var muSigner = &sync.Mutex{}
 
+func removeSigner(alg jwa.SignatureAlgorithm) {
+	muSigner.Lock()
+	defer muSigner.Unlock()
+	delete(signers, alg)
+}
+
 func makeSigner(alg jwa.SignatureAlgorithm, key interface{}, public, protected Headers) (*payloadSigner, error) {
 	muSigner.Lock()
 	signer, ok := signers[alg]
diff --git a/jws/signer.go b/jws/signer.go
index 44c8bfb7..434d51bc 100644
--- a/jws/signer.go
+++ b/jws/signer.go
@@ -20,7 +20,8 @@ var muSignerDB sync.RWMutex
 var signerDB map[jwa.SignatureAlgorithm]SignerFactory
 
 // RegisterSigner is used to register a factory object that creates
-// Signer objects based on the given algorithm.
+// Signer objects based on the given algorithm. Previous object instantiated
+// by the factory is discarded.
 //
 // For example, if you would like to provide a custom signer for
 // jwa.EdDSA, use this function to register a `SignerFactory`
@@ -34,10 +35,14 @@ func RegisterSigner(alg jwa.SignatureAlgorithm, f SignerFactory) {
 	muSignerDB.Lock()
 	signerDB[alg] = f
 	muSignerDB.Unlock()
+
+	// Remove previous signer, if there was one
+	removeSigner(alg)
 }
 
 // UnregisterSigner removes the signer factory associated with
-// the given algorithm.
+// the given algorithm, as well as the signer instance created
+// by the factory.
 //
 // Note that when you call this function, the algorithm itself is
 // not automatically unregistered from the known algorithms database.
@@ -49,6 +54,8 @@ func UnregisterSigner(alg jwa.SignatureAlgorithm) {
 	muSignerDB.Lock()
 	delete(signerDB, alg)
 	muSignerDB.Unlock()
+	// Remove previous signer
+	removeSigner(alg)
 }
 
 func init() {


Test patch:
diff --git a/jwe/jwe_test.go b/jwe/jwe_test.go
index 4e8f6f80..59796e61 100644
--- a/jwe/jwe_test.go
+++ b/jwe/jwe_test.go
@@ -883,3 +883,31 @@ func TestGH924(t *testing.T) {
 	require.NoError(t, err, `jwe.Decrypt should succeed`)
 	require.Equal(t, payload, decrypted, `decrypt messages match`)
 }
+
+func TestGH1001(t *testing.T) {
+	rawKey, err := jwxtest.GenerateRsaKey()
+	require.NoError(t, err, `jwxtest.GenerateRsaKey should succeed`)
+
+	encrypted, err := jwe.Encrypt([]byte("Lorem Ipsum"), jwe.WithKey(jwa.RSA_OAEP, rawKey.PublicKey))
+	require.NoError(t, err, `jwe.Encrypt should succeed`)
+	var cek []byte
+	decrypted, err := jwe.Decrypt(encrypted, jwe.WithKey(jwa.RSA_OAEP, rawKey), jwe.WithCEK(&cek))
+	require.NoError(t, err, `jwe.Decrypt should succeed`)
+
+	require.Equal(t, "Lorem Ipsum", string(decrypted), `decrypted message should match`)
+	require.NotNil(t, cek, `cek should not be nil`)
+
+	reEncrypted, err := jwe.EncryptStatic([]byte("Lorem Ipsum"), cek, jwe.WithKey(jwa.RSA_OAEP, rawKey.PublicKey))
+	require.NoError(t, err, `jwe.EncryptStatic should succeed`)
+
+	// sanity. empty CEKs should be rejected
+	_, err = jwe.EncryptStatic([]byte("Lorem Ipsum"), nil, jwe.WithKey(jwa.RSA_OAEP, rawKey.PublicKey))
+	require.Error(t, err, `jwe.Encryptstatic should fail with empty cek`)
+
+	cek = []byte(nil)
+	decrypted, err = jwe.Decrypt(reEncrypted, jwe.WithKey(jwa.RSA_OAEP, rawKey), jwe.WithCEK(&cek))
+	require.NoError(t, err, `jwe.Decrypt should succeed`)
+
+	require.Equal(t, "Lorem Ipsum", string(decrypted), `decrypted message should match`)
+	require.NotNil(t, cek, `cek should not be nil`)
+}
diff --git a/jwe/options_gen_test.go b/jwe/options_gen_test.go
index da3bbbfe..16984e25 100644
--- a/jwe/options_gen_test.go
+++ b/jwe/options_gen_test.go
@@ -9,6 +9,7 @@ import (
 )
 
 func TestOptionIdent(t *testing.T) {
+	require.Equal(t, "WithCEK", identCEK{}.String())
 	require.Equal(t, "WithCompress", identCompress{}.String())
 	require.Equal(t, "WithContentEncryption", identContentEncryptionAlgorithm{}.String())
 	require.Equal(t, "WithFS", identFS{}.String())
diff --git a/jws/jws_test.go b/jws/jws_test.go
index bf29b5a8..d0a31105 100644
--- a/jws/jws_test.go
+++ b/jws/jws_test.go
@@ -2044,6 +2044,19 @@ func TestGH910(t *testing.T) {
 	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) {