diff --git a/Changes b/Changes index 269593874..9026242bd 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 b33ddb728..620608b19 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 015a06620..046e430e6 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 3ec01f0b7..bcb6be230 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 de5d6be7a..5219e1c5f 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 387d4a999..8729e4e53 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 67b8e97b3..4a7cb19e2 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/jwe_test.go b/jwe/jwe_test.go index 4e8f6f80e..59796e615 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.yaml b/jwe/options.yaml index 84f89666d..623a18669 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 c22e2a5f0..cdb22befd 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/jwe/options_gen_test.go b/jwe/options_gen_test.go index da3bbbfe7..16984e255 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/jwk/interface.go b/jwk/interface.go index 1b9598a44..fa0cff023 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 a348c6186..4cf2c3175 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/jws_test.go b/jws/jws_test.go index bf29b5a8b..d0a311056 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) { diff --git a/jws/signer.go b/jws/signer.go index 44c8bfb76..434d51bc2 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() {