diff --git a/Changes b/Changes index dd8c2465..33a5cb70 100644 --- a/Changes +++ b/Changes @@ -4,6 +4,19 @@ 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.21 07 Mar 2024 +[Security] + * [jwe] Added `jwe.Settings(jwe.WithMaxDecompressBufferSize(int64))` to specify the + maximum size of a decompressed JWE payload. The default value is 10MB. If you + are compressing payloads greater than this and want to decompress it during + a call to `jwe.Decrypt`, you need to explicitly set a value large enough to + hold that data. + + The same option can be passed to `jwe.Decrypt` to control this behavior on + a per-message basis. + * [jwe] Added documentation stating that `jwe.WithMaxBufferSize` option will be + renamed in future versions, i.e. v3 + v2.0.20 20 Feb 2024 [New Features] * [jwe] Added `jwe.Settings(WithMaxBufferSize(int64))` to set the maximum size of diff --git a/deps.bzl b/deps.bzl index badb900a..df224b0b 100644 --- a/deps.bzl +++ b/deps.bzl @@ -47,8 +47,8 @@ def go_dependencies(): name = "com_github_lestrrat_go_httprc", build_file_proto_mode = "disable_global", importpath = "github.com/lestrrat-go/httprc", - sum = "h1:bAZymwoZQb+Oq8MEbyipag7iSq6YIga8Wj6GOiJGdI8=", - version = "v1.0.4", + sum = "h1:bsTfiH8xaKOJPrg1R+E3iE/AWZr/x0Phj9PBTG/OLUk=", + version = "v1.0.5", ) go_repository( name = "com_github_lestrrat_go_iter", @@ -85,15 +85,15 @@ def go_dependencies(): name = "com_github_stretchr_objx", build_file_proto_mode = "disable_global", importpath = "github.com/stretchr/objx", - sum = "h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=", - version = "v0.5.0", + sum = "h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=", + version = "v0.5.2", ) go_repository( name = "com_github_stretchr_testify", build_file_proto_mode = "disable_global", importpath = "github.com/stretchr/testify", - sum = "h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=", - version = "v1.8.4", + sum = "h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=", + version = "v1.9.0", ) go_repository( @@ -115,31 +115,31 @@ def go_dependencies(): name = "org_golang_x_crypto", build_file_proto_mode = "disable_global", importpath = "golang.org/x/crypto", - sum = "h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo=", - version = "v0.19.0", + sum = "h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=", + version = "v0.21.0", ) go_repository( name = "org_golang_x_net", build_file_proto_mode = "disable_global", importpath = "golang.org/x/net", - sum = "h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=", - version = "v0.10.0", + sum = "h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=", + version = "v0.21.0", ) go_repository( name = "org_golang_x_sys", build_file_proto_mode = "disable_global", importpath = "golang.org/x/sys", - sum = "h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=", - version = "v0.17.0", + sum = "h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=", + version = "v0.18.0", ) go_repository( name = "org_golang_x_term", build_file_proto_mode = "disable_global", importpath = "golang.org/x/term", - sum = "h1:mkTF7LCd6WGJNL3K1Ad7kwxNfYAW6a8a8QqtMblp/4U=", - version = "v0.17.0", + sum = "h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8=", + version = "v0.18.0", ) go_repository( diff --git a/examples/go.sum b/examples/go.sum index 2e51a0e1..59cd89b1 100644 --- a/examples/go.sum +++ b/examples/go.sum @@ -13,8 +13,8 @@ github.com/lestrrat-go/blackmagic v1.0.2 h1:Cg2gVSc9h7sz9NOByczrbUvLopQmXrfFx//N github.com/lestrrat-go/blackmagic v1.0.2/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU= github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE= github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E= -github.com/lestrrat-go/httprc v1.0.4 h1:bAZymwoZQb+Oq8MEbyipag7iSq6YIga8Wj6GOiJGdI8= -github.com/lestrrat-go/httprc v1.0.4/go.mod h1:mwwz3JMTPBjHUkkDv/IGJ39aALInZLrhBp0X7KGUZlo= +github.com/lestrrat-go/httprc v1.0.5 h1:bsTfiH8xaKOJPrg1R+E3iE/AWZr/x0Phj9PBTG/OLUk= +github.com/lestrrat-go/httprc v1.0.5/go.mod h1:mwwz3JMTPBjHUkkDv/IGJ39aALInZLrhBp0X7KGUZlo= github.com/lestrrat-go/iter v1.0.2 h1:gMXo1q4c2pHmC3dn8LzRhJfP1ceCbgSiT9lUydIzltI= github.com/lestrrat-go/iter v1.0.2/go.mod h1:Momfcq3AnRlRjI5b5O8/G5/BvpzrhoFTZcn06fEOPt4= github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= @@ -27,17 +27,20 @@ github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 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.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= -golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= +golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= 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= @@ -45,6 +48,7 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -57,14 +61,16 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc 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.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= +golang.org/x/sys v0.18.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.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= +golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= 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 16d72ad6..d68e784d 100644 --- a/go.mod +++ b/go.mod @@ -6,18 +6,18 @@ require ( github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 github.com/goccy/go-json v0.10.2 github.com/lestrrat-go/blackmagic v1.0.2 - github.com/lestrrat-go/httprc v1.0.4 + github.com/lestrrat-go/httprc v1.0.5 github.com/lestrrat-go/iter v1.0.2 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.19.0 + github.com/stretchr/testify v1.9.0 + golang.org/x/crypto v0.21.0 ) require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/lestrrat-go/httpcc v1.0.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - golang.org/x/sys v0.17.0 // indirect + golang.org/x/sys v0.18.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index fda61a01..fe7dc05e 100644 --- a/go.sum +++ b/go.sum @@ -9,8 +9,8 @@ github.com/lestrrat-go/blackmagic v1.0.2 h1:Cg2gVSc9h7sz9NOByczrbUvLopQmXrfFx//N github.com/lestrrat-go/blackmagic v1.0.2/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU= github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE= github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E= -github.com/lestrrat-go/httprc v1.0.4 h1:bAZymwoZQb+Oq8MEbyipag7iSq6YIga8Wj6GOiJGdI8= -github.com/lestrrat-go/httprc v1.0.4/go.mod h1:mwwz3JMTPBjHUkkDv/IGJ39aALInZLrhBp0X7KGUZlo= +github.com/lestrrat-go/httprc v1.0.5 h1:bsTfiH8xaKOJPrg1R+E3iE/AWZr/x0Phj9PBTG/OLUk= +github.com/lestrrat-go/httprc v1.0.5/go.mod h1:mwwz3JMTPBjHUkkDv/IGJ39aALInZLrhBp0X7KGUZlo= github.com/lestrrat-go/iter v1.0.2 h1:gMXo1q4c2pHmC3dn8LzRhJfP1ceCbgSiT9lUydIzltI= github.com/lestrrat-go/iter v1.0.2/go.mod h1:Momfcq3AnRlRjI5b5O8/G5/BvpzrhoFTZcn06fEOPt4= github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU= @@ -22,12 +22,12 @@ github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo= -golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= -golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= -golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= +golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= +golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/jwe/compress.go b/jwe/compress.go index 0beba4a5..45b66613 100644 --- a/jwe/compress.go +++ b/jwe/compress.go @@ -9,8 +9,35 @@ import ( "github.com/lestrrat-go/jwx/v2/internal/pool" ) -func uncompress(plaintext []byte) ([]byte, error) { - return io.ReadAll(flate.NewReader(bytes.NewReader(plaintext))) +func uncompress(src []byte, maxBufferSize int64) ([]byte, error) { + var dst bytes.Buffer + r := flate.NewReader(bytes.NewReader(src)) + defer r.Close() + var buf [16384]byte + var sofar int64 + for { + n, readErr := r.Read(buf[:]) + sofar += int64(n) + if sofar > maxBufferSize { + return nil, fmt.Errorf(`compressed payload exceeds maximum allowed size`) + } + if readErr != nil { + // if we have a read error, and it's not EOF, then we need to stop + if readErr != io.EOF { + return nil, fmt.Errorf(`failed to read inflated data: %w`, readErr) + } + } + + if _, err := dst.Write(buf[:n]); err != nil { + return nil, fmt.Errorf(`failed to write inflated data: %w`, err) + } + + if readErr != nil { + // if it got here, then readErr == io.EOF, we're done + //nolint:nilerr + return dst.Bytes(), nil + } + } } func compress(plaintext []byte) ([]byte, error) { diff --git a/jwe/jwe.go b/jwe/jwe.go index ae1b8e3a..44909a26 100644 --- a/jwe/jwe.go +++ b/jwe/jwe.go @@ -28,6 +28,7 @@ import ( var muSettings sync.RWMutex var maxPBES2Count = 10000 +var maxDecompressBufferSize int64 = 10 * 1024 * 1024 // 10MB func Settings(options ...GlobalOption) { muSettings.Lock() @@ -37,6 +38,8 @@ func Settings(options ...GlobalOption) { switch option.Ident() { case identMaxPBES2Count{}: maxPBES2Count = option.Value().(int) + case identMaxDecompressBufferSize{}: + maxDecompressBufferSize = option.Value().(int64) case identMaxBufferSize{}: aescbc.SetMaxBufferSize(option.Value().(int64)) } @@ -463,28 +466,50 @@ func encrypt(payload, cek []byte, options ...EncryptOption) ([]byte, error) { } type decryptCtx struct { - msg *Message - aad []byte - cek *[]byte - computedAad []byte - keyProviders []KeyProvider - protectedHeaders Headers + msg *Message + aad []byte + cek *[]byte + computedAad []byte + keyProviders []KeyProvider + protectedHeaders Headers + maxDecompressBufferSize int64 } -// Decrypt takes the key encryption algorithm and the corresponding -// key to decrypt the JWE message, and returns the decrypted payload. +// Decrypt takes encrypted payload, and information required to decrypt the +// payload (e.g. the key encryption algorithm and the corresponding +// key to decrypt the JWE message) in its optional arguments. See +// the examples and list of options that return a DecryptOption for possible +// values. Upon successful decryptiond returns the decrypted payload. +// // The JWE message can be either compact or full JSON format. // -// `alg` accepts a `jwa.KeyAlgorithm` for convenience so you can directly pass -// the result of `(jwk.Key).Algorithm()`, but in practice it must be of type +// When using `jwe.WithKeyEncryptionAlgorithm()`, you can pass a `jwa.KeyAlgorithm` +// for convenience: this is mainly to allow you to directly pass the result of `(jwk.Key).Algorithm()`. +// However, do note that while `(jwk.Key).Algorithm()` could very well contain key encryption +// algorithms, it could also contain other types of values, such as _signature algorithms_. +// In order for `jwe.Decrypt` to work properly, the `alg` parameter must be of type // `jwa.KeyEncryptionAlgorithm` or otherwise it will cause an error. // -// `key` must be a private key. It can be either in its raw format (e.g. *rsa.PrivateKey) or a jwk.Key +// When using `jwe.WithKey()`, the value must be a private key. +// It can be either in its raw format (e.g. *rsa.PrivateKey) or a jwk.Key +// +// When the encrypted message is also compressed, the decompressed payload must be +// smaller than the size specified by the `jwe.WithMaxDecompressBufferSize` setting, +// which defaults to 10MB. If the decompressed payload is larger than this size, +// an error is returned. +// +// You can opt to change the MaxDecompressBufferSize setting globally, or on a +// per-call basis by passing the `jwe.WithMaxDecompressBufferSize` option to +// either `jwe.Settings()` or `jwe.Decrypt()`: +// +// jwe.Settings(jwe.WithMaxDecompressBufferSize(10*1024*1024)) // changes value globally +// jwe.Decrypt(..., jwe.WithMaxDecompressBufferSize(250*1024)) // changes just for this call func Decrypt(buf []byte, options ...DecryptOption) ([]byte, error) { var keyProviders []KeyProvider var keyUsed interface{} var cek *[]byte var dst *Message + perCallMaxDecompressBufferSize := maxDecompressBufferSize //nolint:forcetypeassert for _, option := range options { switch option.Ident() { @@ -506,6 +531,8 @@ func Decrypt(buf []byte, options ...DecryptOption) ([]byte, error) { }) case identCEK{}: cek = option.Value().(*[]byte) + case identMaxDecompressBufferSize{}: + perCallMaxDecompressBufferSize = option.Value().(int64) } } @@ -565,6 +592,7 @@ func Decrypt(buf []byte, options ...DecryptOption) ([]byte, error) { dctx.keyProviders = keyProviders dctx.protectedHeaders = h dctx.cek = cek + dctx.maxDecompressBufferSize = perCallMaxDecompressBufferSize var lastError error for _, recipient := range recipients { @@ -741,7 +769,7 @@ func (dctx *decryptCtx) decryptContent(ctx context.Context, alg jwa.KeyEncryptio } if h2.Compression() == jwa.Deflate { - buf, err := uncompress(plaintext) + buf, err := uncompress(plaintext, dctx.maxDecompressBufferSize) if err != nil { return nil, fmt.Errorf(`jwe.Derypt: failed to uncompress payload: %w`, err) } diff --git a/jwe/jwe_test.go b/jwe/jwe_test.go index 5f9a3bcb..d2a22ec9 100644 --- a/jwe/jwe_test.go +++ b/jwe/jwe_test.go @@ -971,3 +971,89 @@ func TestMaxBufferSize(t *testing.T) { _, err = jwe.Encrypt([]byte("Lorem Ipsum"), jwe.WithContentEncryption(jwa.A128CBC_HS256), jwe.WithKey(jwa.RSA_OAEP, key)) require.Error(t, err, `jwe.Encrypt should fail`) } + +func TestMaxDecompressBufferSize(t *testing.T) { + // This payload size is intentionally set to a small value to avoid + // causing problems for regular users and CI/CD systems. If you wish to + // verify that root issue is fixed, you may want to try increasing the + // payload size to a larger value. + const payloadSize = 1 << 16 + + privkey, err := rsa.GenerateKey(rand.Reader, 2048) + require.NoError(t, err, `rsa.GenerateKey should succeed`) + + pubkey := &privkey.PublicKey + + wrongPrivkey, err := rsa.GenerateKey(rand.Reader, 2048) + require.NoError(t, err, `rsa.GenerateKey should succeed`) + wrongPubkey := &wrongPrivkey.PublicKey + + payload := strings.Repeat("x", payloadSize) + + testcases := []struct { + Name string + GlobalMaxSize int64 + PublicKey *rsa.PublicKey + Error bool + ProcessDecryptOptions func([]jwe.DecryptOption) []jwe.DecryptOption + }{ + // This should work, because we set the MaxSize to be large (==payload size) + { + Name: "same as payload size", + GlobalMaxSize: payloadSize, + PublicKey: pubkey, + }, + // This should fail, because we set the GlobalMaxSize to be smaller than the payload size + { + Name: "smaller than payload size", + GlobalMaxSize: payloadSize - 1, + PublicKey: pubkey, + Error: true, + }, + // This should fail, because the public key does not match the + // private key used to decrypt the payload. In essence this way + // we do NOT trigger the root cause of this issue, but we bail out early + { + Name: "Wrong PublicKey", + GlobalMaxSize: payloadSize, + PublicKey: wrongPubkey, + Error: true, + }, + { + Name: "global=payloadSize-1, per-call=payloadSize", + GlobalMaxSize: payloadSize - 1, + PublicKey: pubkey, + ProcessDecryptOptions: func(options []jwe.DecryptOption) []jwe.DecryptOption { + return append(options, jwe.WithMaxDecompressBufferSize(payloadSize)) + }, + }, + // This should be the last test case to put the value back to default :) + { + Name: "Default 10MB globally", + GlobalMaxSize: 10 * 1024 * 1024, + PublicKey: pubkey, + }, + } + for _, tc := range testcases { + tc := tc + t.Run(tc.Name, func(t *testing.T) { + jwe.Settings(jwe.WithMaxDecompressBufferSize(tc.GlobalMaxSize)) + + encrypted, err := jwe.Encrypt([]byte(payload), jwe.WithKey(jwa.RSA_OAEP, tc.PublicKey), jwe.WithContentEncryption("A128CBC-HS256"), jwe.WithCompress(jwa.Deflate)) + + require.NoError(t, err, `jwe.Encrypt should succeed`) + + decryptOptions := []jwe.DecryptOption{jwe.WithKey(jwa.RSA_OAEP, privkey)} + + if fn := tc.ProcessDecryptOptions; fn != nil { + decryptOptions = fn(decryptOptions) + } + _, err = jwe.Decrypt(encrypted, decryptOptions...) + if tc.Error { + require.Error(t, err, `jwe.Decrypt should fail`) + } else { + require.NoError(t, err, `jwe.Decrypt should succeed`) + } + }) + } +} diff --git a/jwe/options.yaml b/jwe/options.yaml index 2de7fdd1..f465ab2e 100644 --- a/jwe/options.yaml +++ b/jwe/options.yaml @@ -4,6 +4,12 @@ interfaces: - name: GlobalOption comment: | GlobalOption describes options that changes global settings for this package + - name: GlobalDecryptOption + comment: | + GlobalDecryptOption describes options that changes global settings and for each call of the `jwe.Decrypt` function + methods: + - globalOption + - decryptOption - name: CompactOption comment: | CompactOption describes options that can be passed to `jwe.Compact` @@ -141,6 +147,18 @@ options: value of 10,000 is used. This option has a global effect. + - ident: MaxDecompressBufferSize + interface: GlobalDecryptOption + argument_type: int64 + comment: | + WithMaxDecompressBufferSize specifies the maximum buffer size for used when + decompressing the payload of a JWE message. If a compressed JWE payload + exceeds this amount when decompressed, jwe.Decrypt will return an error. + The default value is 10MB. + + This option can be used for `jwe.Settings()`, which changes the behavior + globally, or for `jwe.Decrypt()`, which changes the behavior for that + specific call. - ident: MaxBufferSize interface: GlobalOption argument_type: int64 @@ -149,4 +167,7 @@ options: calculations, such as when AES-CBC is performed. The default value is 256MB. If set to an invalid value, the default value is used. - This option has a global effect. \ No newline at end of file + This option has a global effect. + + Due to historical reasons this option has a vague name, but in future versions + it will be appropriately renamed. \ No newline at end of file diff --git a/jwe/options_gen.go b/jwe/options_gen.go index 3b74a8a9..6118fb4b 100644 --- a/jwe/options_gen.go +++ b/jwe/options_gen.go @@ -62,6 +62,21 @@ type encryptOption struct { func (*encryptOption) encryptOption() {} +// GlobalDecryptOption describes options that changes global settings and for each call of the `jwe.Decrypt` function +type GlobalDecryptOption interface { + Option + globalOption() + decryptOption() +} + +type globalDecryptOption struct { + Option +} + +func (*globalDecryptOption) globalOption() {} + +func (*globalDecryptOption) decryptOption() {} + // GlobalOption describes options that changes global settings for this package type GlobalOption interface { Option @@ -130,6 +145,7 @@ type identKey struct{} type identKeyProvider struct{} type identKeyUsed struct{} type identMaxBufferSize struct{} +type identMaxDecompressBufferSize struct{} type identMaxPBES2Count struct{} type identMergeProtectedHeaders struct{} type identMessage struct{} @@ -171,6 +187,10 @@ func (identMaxBufferSize) String() string { return "WithMaxBufferSize" } +func (identMaxDecompressBufferSize) String() string { + return "WithMaxDecompressBufferSize" +} + func (identMaxPBES2Count) String() string { return "WithMaxPBES2Count" } @@ -255,10 +275,25 @@ func WithKeyUsed(v interface{}) DecryptOption { // If set to an invalid value, the default value is used. // // This option has a global effect. +// +// Due to historical reasons this option has a vague name, but in future versions +// it will be appropriately renamed. func WithMaxBufferSize(v int64) GlobalOption { return &globalOption{option.New(identMaxBufferSize{}, v)} } +// WithMaxDecompressBufferSize specifies the maximum buffer size for used when +// decompressing the payload of a JWE message. If a compressed JWE payload +// exceeds this amount when decompressed, jwe.Decrypt will return an error. +// The default value is 10MB. +// +// This option can be used for `jwe.Settings()`, which changes the behavior +// globally, or for `jwe.Decrypt()`, which changes the behavior for that +// specific call. +func WithMaxDecompressBufferSize(v int64) GlobalDecryptOption { + return &globalDecryptOption{option.New(identMaxDecompressBufferSize{}, 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. diff --git a/jwe/options_gen_test.go b/jwe/options_gen_test.go index b58d2362..a872d45f 100644 --- a/jwe/options_gen_test.go +++ b/jwe/options_gen_test.go @@ -17,6 +17,7 @@ func TestOptionIdent(t *testing.T) { require.Equal(t, "WithKeyProvider", identKeyProvider{}.String()) require.Equal(t, "WithKeyUsed", identKeyUsed{}.String()) require.Equal(t, "WithMaxBufferSize", identMaxBufferSize{}.String()) + require.Equal(t, "WithMaxDecompressBufferSize", identMaxDecompressBufferSize{}.String()) require.Equal(t, "WithMaxPBES2Count", identMaxPBES2Count{}.String()) require.Equal(t, "WithMergeProtectedHeaders", identMergeProtectedHeaders{}.String()) require.Equal(t, "WithMessage", identMessage{}.String()) diff --git a/jws/options.go b/jws/options.go index a6cc472c..55578e6d 100644 --- a/jws/options.go +++ b/jws/options.go @@ -136,12 +136,12 @@ func WithKey(alg jwa.KeyAlgorithm, key interface{}, options ...WithKeySuboption) // specified by the caller. // // To work with keys/JWS messages not having a `kid` field, you may specify -// the suboption `WithKeySetRequired` via `jws.WithKeySetSuboption(jws.WithKeySetRequireKid(false))`. +// the suboption `WithKeySetRequired` via `jws.WithKey(key, jws.WithRequireKid(false))`. // This will allow the library to proceed without having to match the `kid` field. // // However, it will still check if the `alg` fields in the JWS message and the key(s) // match. If you must work with JWS messages that do not have an `alg` field, -// you will need to use `jws.WithKeySetSuboption(jws.WithInferAlgorithm(true))`. +// you will need to use `jws.WithKeySet(key, jws.WithInferAlgorithm(true))`. // // See the documentation for `WithInferAlgorithm()` for more details. func WithKeySet(set jwk.Set, options ...WithKeySetSuboption) VerifyOption {