From 0a15b4d9a180b06c6924c4a03b39ae70d05514f4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 9 Nov 2023 07:32:09 +0000 Subject: [PATCH 01/11] Bump golang.org/x/crypto from 0.14.0 to 0.15.0 Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.14.0 to 0.15.0. - [Commits](https://github.com/golang/crypto/compare/v0.14.0...v0.15.0) --- updated-dependencies: - dependency-name: golang.org/x/crypto dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) 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= From 7430ba21391a4a64dbe3cbbba58dd55b29d87340 Mon Sep 17 00:00:00 2001 From: Daisuke Maki Date: Mon, 13 Nov 2023 08:40:24 +0900 Subject: [PATCH 02/11] Run gazelle-update-repos --- deps.bzl | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) 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", From ea2c6329cd8c7a954671fca2a9b373dd7c2f9f86 Mon Sep 17 00:00:00 2001 From: lestrrat <49281+lestrrat@users.noreply.github.com> Date: Wed, 15 Nov 2023 15:00:18 +0900 Subject: [PATCH 03/11] Add jwe.WithCEK (#1011) * Add jwe.WithCEK * Allow using a static CEK via EncryptStatic * appease linter * Update go.sum * Docs * Update generated options * Add test --- Changes | 7 +++++++ examples/go.sum | 12 +++++------ jwe/decrypt.go | 9 +++++++++ jwe/jwe.go | 44 ++++++++++++++++++++++++++++++++++------- jwe/jwe_test.go | 28 ++++++++++++++++++++++++++ jwe/options.yaml | 11 ++++++++++- jwe/options_gen.go | 15 ++++++++++++++ jwe/options_gen_test.go | 1 + 8 files changed, 113 insertions(+), 14 deletions(-) diff --git a/Changes b/Changes index 269593874..26dabe5ca 100644 --- a/Changes +++ b/Changes @@ -4,6 +4,13 @@ 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 UNRELEASED +[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/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/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()) From 52b176a878720b38452a74a3168744981d3f290b Mon Sep 17 00:00:00 2001 From: Shang Jian Ding Date: Thu, 16 Nov 2023 19:59:22 -0600 Subject: [PATCH 04/11] clarify when jwk.Set.RemoveKey can return error (#1015) --- jwk/interface.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) 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. From ee031a0086ff4b0a55fadef0a856bc772d513dc0 Mon Sep 17 00:00:00 2001 From: lestrrat <49281+lestrrat@users.noreply.github.com> Date: Sat, 18 Nov 2023 18:59:21 +0900 Subject: [PATCH 05/11] Remove signer instance upon call to jws.UnregisterSigner (#1017) * Delete signer instance upon call to jws.UnregisterSigner * Update Changes --- Changes | 4 ++++ jws/jws.go | 6 ++++++ jws/jws_test.go | 13 +++++++++++++ jws/signer.go | 5 +++++ 4 files changed, 28 insertions(+) diff --git a/Changes b/Changes index 26dabe5ca..14d26bd7f 100644 --- a/Changes +++ b/Changes @@ -5,6 +5,10 @@ v2 has many incompatibilities with v1. To see the full list of differences betwe 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 UNRELEASED +[Bug Fixes] + * [jws] Previously, `jws.UnregisterSigner` did not remove the previous signer instance when + the signer was registered and unregistered multiple times. 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. 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..39da72863 100644 --- a/jws/signer.go +++ b/jws/signer.go @@ -34,6 +34,9 @@ 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 @@ -49,6 +52,8 @@ func UnregisterSigner(alg jwa.SignatureAlgorithm) { muSignerDB.Lock() delete(signerDB, alg) muSignerDB.Unlock() + // Remove previous signer + removeSigner(alg) } func init() { From 9156743b2f37c03de6fde252880e6b86c5a4ed2a Mon Sep 17 00:00:00 2001 From: lestrrat <49281+lestrrat@users.noreply.github.com> Date: Mon, 20 Nov 2023 12:55:33 +0900 Subject: [PATCH 06/11] Tweak documentation (#1018) --- Changes | 4 ++-- jws/signer.go | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/Changes b/Changes index 14d26bd7f..9026242bd 100644 --- a/Changes +++ b/Changes @@ -4,10 +4,10 @@ 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 UNRELEASED +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. This has been fixed. + 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. diff --git a/jws/signer.go b/jws/signer.go index 39da72863..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` @@ -40,7 +41,8 @@ func RegisterSigner(alg jwa.SignatureAlgorithm, f SignerFactory) { } // 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. From 55000b3a8673fd1954bc5f30e187d9954660f84f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 1 Dec 2023 21:19:19 +0900 Subject: [PATCH 07/11] Bump golang.org/x/crypto from 0.15.0 to 0.16.0 (#1020) * Bump golang.org/x/crypto from 0.15.0 to 0.16.0 Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.15.0 to 0.16.0. - [Commits](https://github.com/golang/crypto/compare/v0.15.0...v0.16.0) --- updated-dependencies: - dependency-name: golang.org/x/crypto dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * Run bazel and tidy --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Daisuke Maki --- deps.bzl | 12 ++++++------ examples/go.sum | 10 +++++----- go.mod | 2 +- go.sum | 10 +++++----- 4 files changed, 17 insertions(+), 17 deletions(-) diff --git a/deps.bzl b/deps.bzl index 620608b19..ab3a455a6 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:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA=", - version = "v0.15.0", + sum = "h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY=", + version = "v0.16.0", ) go_repository( name = "org_golang_x_mod", @@ -152,15 +152,15 @@ def go_dependencies(): name = "org_golang_x_sys", build_file_proto_mode = "disable_global", importpath = "golang.org/x/sys", - sum = "h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=", - version = "v0.14.0", + sum = "h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=", + version = "v0.15.0", ) go_repository( name = "org_golang_x_term", build_file_proto_mode = "disable_global", importpath = "golang.org/x/term", - sum = "h1:LGK9IlZ8T9jvdy6cTdfKUCltatMFOehAQo9SRC46UQ8=", - version = "v0.14.0", + sum = "h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4=", + version = "v0.15.0", ) go_repository( diff --git a/examples/go.sum b/examples/go.sum index 046e430e6..aba67e4de 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.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA= -golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g= +golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY= +golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= 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,14 +59,14 @@ 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.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q= -golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= +golang.org/x/sys v0.15.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.14.0/go.mod h1:TySc+nGkYR6qt8km8wUhuFRTVSMIX3XPR58y2lC8vww= +golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= 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= diff --git a/go.mod b/go.mod index bcb6be230..6033b447d 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.15.0 + golang.org/x/crypto v0.16.0 ) diff --git a/go.sum b/go.sum index 5219e1c5f..4bd4eefc3 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.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA= -golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g= +golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY= +golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= 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,13 +52,13 @@ 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.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q= -golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= +golang.org/x/sys v0.15.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.14.0/go.mod h1:TySc+nGkYR6qt8km8wUhuFRTVSMIX3XPR58y2lC8vww= +golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= 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= From 64f2a229b8e18605f47361d292b526bdc4aee01c Mon Sep 17 00:00:00 2001 From: lestrrat <49281+lestrrat@users.noreply.github.com> Date: Sun, 3 Dec 2023 15:35:12 +0900 Subject: [PATCH 08/11] Merge pull request from GHSA-7f9x-gw85-8grf --- Changes | 9 ++++++++ jwe/jwe.go | 21 ++++++++++++++++++ jwe/jwe_test.go | 48 +++++++++++++++++++++++++++++++++++++++++ jwe/options.yaml | 10 +++++++++ jwe/options_gen.go | 24 +++++++++++++++++++++ jwe/options_gen_test.go | 1 + 6 files changed, 113 insertions(+) diff --git a/Changes b/Changes index 9026242bd..36d2963a0 100644 --- a/Changes +++ b/Changes @@ -4,6 +4,15 @@ 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.18 UNRELEASED +[Security Fixes] + * [jwe] A large number in p2c parameter for PBKDF2 based encryptions could cause a DoS attack, + similar to https://nvd.nist.gov/vuln/detail/CVE-2022-36083. All users who use JWE via this + package should upgrade. While the JOSE spec allows for encryption using JWE on JWTs, users of + the `jwt` package are not immediately susceptible unless they explicitly try to decrypt + JWTs -- by default the `jwt` package verifies signatures, but does not decrypt messages. + [GHSA-7f9x-gw85-8grf] + v2.0.17 20 Nov 2023 [Bug Fixes] * [jws] Previously, `jws.UnregisterSigner` did not remove the previous signer instance when diff --git a/jwe/jwe.go b/jwe/jwe.go index 4a7cb19e2..3b64db6a1 100644 --- a/jwe/jwe.go +++ b/jwe/jwe.go @@ -10,6 +10,7 @@ import ( "crypto/rsa" "fmt" "io" + "sync" "github.com/lestrrat-go/blackmagic" "github.com/lestrrat-go/jwx/v2/internal/base64" @@ -24,6 +25,20 @@ import ( "github.com/lestrrat-go/jwx/v2/x25519" ) +var muSettings sync.RWMutex +var maxPBES2Count = 10000 + +func Settings(options ...GlobalOption) { + muSettings.Lock() + defer muSettings.Unlock() + for _, option := range options { + switch option.Ident() { + case identMaxPBES2Count{}: + maxPBES2Count = option.Value().(int) + } + } +} + const ( fmtInvalid = iota fmtCompact @@ -702,6 +717,12 @@ func (dctx *decryptCtx) decryptContent(ctx context.Context, alg jwa.KeyEncryptio if !ok { return nil, fmt.Errorf("unexpected type for 'p2c': %T", count) } + muSettings.RLock() + maxCount := maxPBES2Count + muSettings.RUnlock() + if countFlt > float64(maxCount) { + return nil, fmt.Errorf("invalid 'p2c' value") + } salt, err := base64.DecodeString(saltB64Str) if err != nil { return nil, fmt.Errorf(`failed to b64-decode 'salt': %w`, err) diff --git a/jwe/jwe_test.go b/jwe/jwe_test.go index 59796e615..28cbb3df0 100644 --- a/jwe/jwe_test.go +++ b/jwe/jwe_test.go @@ -911,3 +911,51 @@ func TestGH1001(t *testing.T) { require.Equal(t, "Lorem Ipsum", string(decrypted), `decrypted message should match`) require.NotNil(t, cek, `cek should not be nil`) } + +func TestGHSA_7f9x_gw85_8grf(t *testing.T) { + token := []byte("eyJhbGciOiJQQkVTMi1IUzI1NitBMTI4S1ciLCJlbmMiOiJBMjU2R0NNIiwicDJjIjoyMDAwMDAwMDAwLCJwMnMiOiJNNzczSnlmV2xlX2FsSXNrc0NOTU9BIn0=.S8B1kXdIR7BM6i_TaGsgqEOxU-1Sgdakp4mHq7UVhn-_REzOiGz2gg.gU_LfzhBXtQdwYjh.9QUIS-RWkLc.m9TudmzUoCzDhHsGGfzmCA") + key, err := jwk.FromRaw([]byte(`abcdefg`)) + require.NoError(t, err, `jwk.FromRaw should succeed`) + + { + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + done := make(chan struct{}) + go func(t *testing.T, done chan struct{}) { + _, err := jwe.Decrypt(token, jwe.WithKey(jwa.PBES2_HS256_A128KW, key)) + require.Error(t, err, `jwe.Decrypt should fail`) + close(done) + }(t, done) + + select { + case <-done: + case <-ctx.Done(): + require.Fail(t, "jwe.Decrypt should not block") + } + } + + // NOTE: HAS GLOBAL EFFECT + // Should allow for timeout to occur + jwe.Settings(jwe.WithMaxPBES2Count(100000000000000000)) + + // put it back to normal after the test + defer jwe.Settings(jwe.WithMaxPBES2Count(10000)) + { + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + done := make(chan struct{}) + go func(t *testing.T, done chan struct{}) { + _, _ = jwe.Decrypt(token, jwe.WithKey(jwa.PBES2_HS256_A128KW, key)) + close(done) + }(t, done) + + select { + case <-done: + require.Fail(t, "jwe.Decrypt should block") + case <-ctx.Done(): + // timeout occurred as it should + } + } +} diff --git a/jwe/options.yaml b/jwe/options.yaml index 623a18669..bf7e0a01e 100644 --- a/jwe/options.yaml +++ b/jwe/options.yaml @@ -1,6 +1,9 @@ package_name: jwe output: jwe/options_gen.go interfaces: + - name: GlobalOption + comment: | + GlobalOption describes options that changes global settings for this package - name: CompactOption comment: | CompactOption describes options that can be passed to `jwe.Compact` @@ -129,3 +132,10 @@ options: This option is currently considered EXPERIMENTAL, and is subject to future changes across minor/micro versions. + - ident: MaxPBES2Count + interface: GlobalOption + argument_type: int + comment: | + WithMaxPBES2Count specifies the maximum number of PBES2 iterations + to use when decrypting a message. If not specified, the default + value of 10,000 is used. \ No newline at end of file diff --git a/jwe/options_gen.go b/jwe/options_gen.go index cdb22befd..b3de13200 100644 --- a/jwe/options_gen.go +++ b/jwe/options_gen.go @@ -62,6 +62,18 @@ type encryptOption struct { func (*encryptOption) encryptOption() {} +// GlobalOption describes options that changes global settings for this package +type GlobalOption interface { + Option + globalOption() +} + +type globalOption struct { + Option +} + +func (*globalOption) globalOption() {} + // ReadFileOption is a type of `Option` that can be passed to `jwe.Parse` type ParseOption interface { Option @@ -117,6 +129,7 @@ type identFS struct{} type identKey struct{} type identKeyProvider struct{} type identKeyUsed struct{} +type identMaxPBES2Count struct{} type identMergeProtectedHeaders struct{} type identMessage struct{} type identPerRecipientHeaders struct{} @@ -153,6 +166,10 @@ func (identKeyUsed) String() string { return "WithKeyUsed" } +func (identMaxPBES2Count) String() string { + return "WithMaxPBES2Count" +} + func (identMergeProtectedHeaders) String() string { return "WithMergeProtectedHeaders" } @@ -228,6 +245,13 @@ func WithKeyUsed(v interface{}) DecryptOption { return &decryptOption{option.New(identKeyUsed{}, v)} } +// WithMaxPBES2Count specifies the maximum number of PBES2 iterations +// to use when decrypting a message. If not specified, the default +// value of 10,000 is used. +func WithMaxPBES2Count(v int) GlobalOption { + return &globalOption{option.New(identMaxPBES2Count{}, v)} +} + // WithMergeProtectedHeaders specify that when given multiple headers // as options to `jwe.Encrypt`, these headers should be merged instead // of overwritten diff --git a/jwe/options_gen_test.go b/jwe/options_gen_test.go index 16984e255..d36b9765a 100644 --- a/jwe/options_gen_test.go +++ b/jwe/options_gen_test.go @@ -16,6 +16,7 @@ func TestOptionIdent(t *testing.T) { require.Equal(t, "WithKey", identKey{}.String()) require.Equal(t, "WithKeyProvider", identKeyProvider{}.String()) require.Equal(t, "WithKeyUsed", identKeyUsed{}.String()) + require.Equal(t, "WithMaxPBES2Count", identMaxPBES2Count{}.String()) require.Equal(t, "WithMergeProtectedHeaders", identMergeProtectedHeaders{}.String()) require.Equal(t, "WithMessage", identMessage{}.String()) require.Equal(t, "WithPerRecipientHeaders", identPerRecipientHeaders{}.String()) From dcf2e4f45ec74786ffdb174ed0334203be3583b2 Mon Sep 17 00:00:00 2001 From: Daisuke Maki Date: Sun, 3 Dec 2023 15:37:36 +0900 Subject: [PATCH 09/11] Update Changes --- Changes | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Changes b/Changes index 36d2963a0..2de87d4ce 100644 --- a/Changes +++ b/Changes @@ -4,7 +4,7 @@ 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.18 UNRELEASED +v2.0.18 03 Dec 2023 [Security Fixes] * [jwe] A large number in p2c parameter for PBKDF2 based encryptions could cause a DoS attack, similar to https://nvd.nist.gov/vuln/detail/CVE-2022-36083. All users who use JWE via this From 08bd41162e3e3da5c352de2adb74fe7c894d7bcc Mon Sep 17 00:00:00 2001 From: Daisuke Maki Date: Sun, 3 Dec 2023 15:40:04 +0900 Subject: [PATCH 10/11] Appease linter --- jwe/jwe.go | 1 + jwe/jwe_test.go | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/jwe/jwe.go b/jwe/jwe.go index 3b64db6a1..7c2905a06 100644 --- a/jwe/jwe.go +++ b/jwe/jwe.go @@ -31,6 +31,7 @@ var maxPBES2Count = 10000 func Settings(options ...GlobalOption) { muSettings.Lock() defer muSettings.Unlock() + //nolint:forcetypeassert for _, option := range options { switch option.Ident() { case identMaxPBES2Count{}: diff --git a/jwe/jwe_test.go b/jwe/jwe_test.go index 28cbb3df0..9559abcd3 100644 --- a/jwe/jwe_test.go +++ b/jwe/jwe_test.go @@ -946,10 +946,10 @@ func TestGHSA_7f9x_gw85_8grf(t *testing.T) { defer cancel() done := make(chan struct{}) - go func(t *testing.T, done chan struct{}) { + go func(done chan struct{}) { _, _ = jwe.Decrypt(token, jwe.WithKey(jwa.PBES2_HS256_A128KW, key)) close(done) - }(t, done) + }(done) select { case <-done: From d809cc50ffdd064a5a1a56204f567b132b96219f Mon Sep 17 00:00:00 2001 From: Daisuke Maki Date: Sun, 3 Dec 2023 15:52:26 +0900 Subject: [PATCH 11/11] fix deps.bzl --- deps.bzl | 2 -- 1 file changed, 2 deletions(-) diff --git a/deps.bzl b/deps.bzl index 38c1a84bb..ab3a455a6 100644 --- a/deps.bzl +++ b/deps.bzl @@ -169,8 +169,6 @@ def go_dependencies(): importpath = "golang.org/x/text", sum = "h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=", version = "v0.14.0", - sum = "h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=", - version = "v0.14.0", ) go_repository( name = "org_golang_x_tools",