Skip to content

Commit 64f2a22

Browse files
authored
Merge pull request from GHSA-7f9x-gw85-8grf
1 parent 55000b3 commit 64f2a22

File tree

6 files changed

+113
-0
lines changed

6 files changed

+113
-0
lines changed

Changes

+9
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,15 @@ Changes
44
v2 has many incompatibilities with v1. To see the full list of differences between
55
v1 and v2, please read the Changes-v2.md file (https://github.com/lestrrat-go/jwx/blob/develop/v2/Changes-v2.md)
66

7+
v2.0.18 UNRELEASED
8+
[Security Fixes]
9+
* [jwe] A large number in p2c parameter for PBKDF2 based encryptions could cause a DoS attack,
10+
similar to https://nvd.nist.gov/vuln/detail/CVE-2022-36083. All users who use JWE via this
11+
package should upgrade. While the JOSE spec allows for encryption using JWE on JWTs, users of
12+
the `jwt` package are not immediately susceptible unless they explicitly try to decrypt
13+
JWTs -- by default the `jwt` package verifies signatures, but does not decrypt messages.
14+
[GHSA-7f9x-gw85-8grf]
15+
716
v2.0.17 20 Nov 2023
817
[Bug Fixes]
918
* [jws] Previously, `jws.UnregisterSigner` did not remove the previous signer instance when

jwe/jwe.go

+21
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"crypto/rsa"
1111
"fmt"
1212
"io"
13+
"sync"
1314

1415
"github.com/lestrrat-go/blackmagic"
1516
"github.com/lestrrat-go/jwx/v2/internal/base64"
@@ -24,6 +25,20 @@ import (
2425
"github.com/lestrrat-go/jwx/v2/x25519"
2526
)
2627

28+
var muSettings sync.RWMutex
29+
var maxPBES2Count = 10000
30+
31+
func Settings(options ...GlobalOption) {
32+
muSettings.Lock()
33+
defer muSettings.Unlock()
34+
for _, option := range options {
35+
switch option.Ident() {
36+
case identMaxPBES2Count{}:
37+
maxPBES2Count = option.Value().(int)
38+
}
39+
}
40+
}
41+
2742
const (
2843
fmtInvalid = iota
2944
fmtCompact
@@ -702,6 +717,12 @@ func (dctx *decryptCtx) decryptContent(ctx context.Context, alg jwa.KeyEncryptio
702717
if !ok {
703718
return nil, fmt.Errorf("unexpected type for 'p2c': %T", count)
704719
}
720+
muSettings.RLock()
721+
maxCount := maxPBES2Count
722+
muSettings.RUnlock()
723+
if countFlt > float64(maxCount) {
724+
return nil, fmt.Errorf("invalid 'p2c' value")
725+
}
705726
salt, err := base64.DecodeString(saltB64Str)
706727
if err != nil {
707728
return nil, fmt.Errorf(`failed to b64-decode 'salt': %w`, err)

jwe/jwe_test.go

+48
Original file line numberDiff line numberDiff line change
@@ -911,3 +911,51 @@ func TestGH1001(t *testing.T) {
911911
require.Equal(t, "Lorem Ipsum", string(decrypted), `decrypted message should match`)
912912
require.NotNil(t, cek, `cek should not be nil`)
913913
}
914+
915+
func TestGHSA_7f9x_gw85_8grf(t *testing.T) {
916+
token := []byte("eyJhbGciOiJQQkVTMi1IUzI1NitBMTI4S1ciLCJlbmMiOiJBMjU2R0NNIiwicDJjIjoyMDAwMDAwMDAwLCJwMnMiOiJNNzczSnlmV2xlX2FsSXNrc0NOTU9BIn0=.S8B1kXdIR7BM6i_TaGsgqEOxU-1Sgdakp4mHq7UVhn-_REzOiGz2gg.gU_LfzhBXtQdwYjh.9QUIS-RWkLc.m9TudmzUoCzDhHsGGfzmCA")
917+
key, err := jwk.FromRaw([]byte(`abcdefg`))
918+
require.NoError(t, err, `jwk.FromRaw should succeed`)
919+
920+
{
921+
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
922+
defer cancel()
923+
924+
done := make(chan struct{})
925+
go func(t *testing.T, done chan struct{}) {
926+
_, err := jwe.Decrypt(token, jwe.WithKey(jwa.PBES2_HS256_A128KW, key))
927+
require.Error(t, err, `jwe.Decrypt should fail`)
928+
close(done)
929+
}(t, done)
930+
931+
select {
932+
case <-done:
933+
case <-ctx.Done():
934+
require.Fail(t, "jwe.Decrypt should not block")
935+
}
936+
}
937+
938+
// NOTE: HAS GLOBAL EFFECT
939+
// Should allow for timeout to occur
940+
jwe.Settings(jwe.WithMaxPBES2Count(100000000000000000))
941+
942+
// put it back to normal after the test
943+
defer jwe.Settings(jwe.WithMaxPBES2Count(10000))
944+
{
945+
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
946+
defer cancel()
947+
948+
done := make(chan struct{})
949+
go func(t *testing.T, done chan struct{}) {
950+
_, _ = jwe.Decrypt(token, jwe.WithKey(jwa.PBES2_HS256_A128KW, key))
951+
close(done)
952+
}(t, done)
953+
954+
select {
955+
case <-done:
956+
require.Fail(t, "jwe.Decrypt should block")
957+
case <-ctx.Done():
958+
// timeout occurred as it should
959+
}
960+
}
961+
}

jwe/options.yaml

+10
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
package_name: jwe
22
output: jwe/options_gen.go
33
interfaces:
4+
- name: GlobalOption
5+
comment: |
6+
GlobalOption describes options that changes global settings for this package
47
- name: CompactOption
58
comment: |
69
CompactOption describes options that can be passed to `jwe.Compact`
@@ -129,3 +132,10 @@ options:
129132
130133
This option is currently considered EXPERIMENTAL, and is subject to
131134
future changes across minor/micro versions.
135+
- ident: MaxPBES2Count
136+
interface: GlobalOption
137+
argument_type: int
138+
comment: |
139+
WithMaxPBES2Count specifies the maximum number of PBES2 iterations
140+
to use when decrypting a message. If not specified, the default
141+
value of 10,000 is used.

jwe/options_gen.go

+24
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

jwe/options_gen_test.go

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)