diff --git a/.github/workflows/assign-pr.yml b/.github/workflows/assign-pr.yml index 4405c6d07..3dcb2ed63 100644 --- a/.github/workflows/assign-pr.yml +++ b/.github/workflows/assign-pr.yml @@ -7,6 +7,6 @@ jobs: add-reviews: runs-on: ubuntu-latest steps: - - uses: kentaro-m/auto-assign-action@v1.2.5 + - uses: kentaro-m/auto-assign-action@v2.0.0 with: configuration-path: .github/auto-assign-pr.yml diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 00392dbec..ec9e9896e 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -15,7 +15,7 @@ jobs: - name: Checkout repository uses: actions/checkout@v4 - name: Cache Go modules - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: | ~/go/pkg/mod diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fa1866f67..a38daa1b8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,7 +17,7 @@ jobs: - name: Checkout repository uses: actions/checkout@v4 - name: Cache Go modules - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: | ~/go/pkg/mod @@ -54,10 +54,10 @@ jobs: run: make cover-${{ matrix.go_tags }} - name: Upload code coverage to codecov if: matrix.go == '1.19' - uses: codecov/codecov-action@v3 + uses: codecov/codecov-action@v4 with: file: ./coverage.out - - uses: bazelbuild/setup-bazelisk@v2 + - uses: bazelbuild/setup-bazelisk@v3 - run: bazel run //:gazelle-update-repos - name: Check difference between generation code and commit code run: make check_diffs diff --git a/.github/workflows/dependabot.yml b/.github/workflows/dependabot.yml index de9619f51..eb17563b5 100644 --- a/.github/workflows/dependabot.yml +++ b/.github/workflows/dependabot.yml @@ -19,10 +19,11 @@ jobs: - run: | bazel build //... - run: | + git config --local user.name 'Daisuke Maki' git config --local user.email '41898282+github-actions[bot]@users.noreply.github.com' git add . git commit -m "Run tidy / bazel+gazelle" - git push + git push origin ${{ github.ref_name }} gh pr review --approve "$PR_URL" gh pr merge --auto --merge "$PR_URL" env: diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 73fbb9002..11ba5ba52 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -10,7 +10,7 @@ jobs: with: go-version: 1.19 check-latest: true - - uses: golangci/golangci-lint-action@v3 + - uses: golangci/golangci-lint-action@v4 with: version: v1.54.2 - name: Run go vet diff --git a/.github/workflows/smoke.yml b/.github/workflows/smoke.yml index 7701c6c2c..4bd6130d5 100644 --- a/.github/workflows/smoke.yml +++ b/.github/workflows/smoke.yml @@ -23,7 +23,7 @@ jobs: run: | find . -name '*.md' | xargs env AUTODOC_DRYRUN=1 perl tools/autodoc.pl - name: Cache Go modules - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: | ~/go/pkg/mod @@ -50,6 +50,6 @@ jobs: run: make tidy - name: Run smoke tests run: make smoke-${{ matrix.go_tags }} - - uses: bazelbuild/setup-bazelisk@v2 + - uses: bazelbuild/setup-bazelisk@v3 - run: bazel build //... diff --git a/Changes b/Changes index ab8312ebf..dd8c2465a 100644 --- a/Changes +++ b/Changes @@ -4,7 +4,27 @@ 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.19 09 Jan 2023 +v2.0.20 20 Feb 2024 +[New Features] + * [jwe] Added `jwe.Settings(WithMaxBufferSize(int64))` to set the maximum size of + internal buffers. The default value is 256MB. Most users do not need to change + this value. + * [jws] Allow `jws.WithCompact()` and `jws.WithJSON()` to be passed to `jws.Parse()` and + `jws.Verify()`. These options control the expected serialization format for the + JWS message. + * [jwt] Add `jwt.WithCompactOnly()` to specify that only compact serialization can + be used for `jwt.Parse()`. Previously, by virtue of `jws.Parse()` allowing either + JSON or Compact serialization format, `jwt.Parse()` also alloed JSON serialization + where as RFC7519 explicitly states that only compact serialization should be + used. For backward compatibility the default behavior is not changed, but you + can set this global option for jwt: `jwt.Settings(jwt.WithCompactOnly(true))` + +[Miscellaneous] + * Internal key conversions should now allow private keys to be used in place of + public keys. This would allow you to pass private keys where public keys are + expected. + +v2.0.19 09 Jan 2024 [New Features] * [jws] Added jws.IsVerificationError to check if the error returned by `jws.Verify` was caused by actual verification step or something else, for example, while fetching diff --git a/bench/performance/go.mod b/bench/performance/go.mod index b3f8f9c40..d4696adc7 100644 --- a/bench/performance/go.mod +++ b/bench/performance/go.mod @@ -2,4 +2,4 @@ module github.com/lestrrat-go/jwx/v2/bench/performance go 1.16 -require github.com/lestrrat-go/jwx/v2 v2.0.11 +require github.com/lestrrat-go/jwx/v2 v2.0.19 diff --git a/bench/performance/go.sum b/bench/performance/go.sum index c96204dce..f908327d7 100644 --- a/bench/performance/go.sum +++ b/bench/performance/go.sum @@ -6,16 +6,16 @@ github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etly github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= -github.com/lestrrat-go/blackmagic v1.0.1 h1:lS5Zts+5HIC/8og6cGHb0uCcNCa3OUt1ygh3Qz2Fe80= -github.com/lestrrat-go/blackmagic v1.0.1/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU= +github.com/lestrrat-go/blackmagic v1.0.2 h1:Cg2gVSc9h7sz9NOByczrbUvLopQmXrfFx//N+AkAr5k= +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/iter v1.0.2 h1:gMXo1q4c2pHmC3dn8LzRhJfP1ceCbgSiT9lUydIzltI= github.com/lestrrat-go/iter v1.0.2/go.mod h1:Momfcq3AnRlRjI5b5O8/G5/BvpzrhoFTZcn06fEOPt4= -github.com/lestrrat-go/jwx/v2 v2.0.11 h1:ViHMnaMeaO0qV16RZWBHM7GTrAnX2aFLVKofc7FuKLQ= -github.com/lestrrat-go/jwx/v2 v2.0.11/go.mod h1:ZtPtMFlrfDrH2Y0iwfa3dRFn8VzwBrB+cyrm3IBWdDg= +github.com/lestrrat-go/jwx/v2 v2.0.19 h1:ekv1qEZE6BVct89QA+pRF6+4pCpfVrOnEJnTnT4RXoY= +github.com/lestrrat-go/jwx/v2 v2.0.19/go.mod h1:l3im3coce1lL2cDeAjqmaR+Awx+X8Ih+2k8BuHNJ4CU= github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU= github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= @@ -34,8 +34,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.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= -golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= +golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= +golang.org/x/crypto v0.17.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= @@ -53,17 +53,20 @@ golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 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 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +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.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= 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.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/cmd/jwx/go.mod b/cmd/jwx/go.mod index be396731d..480939214 100644 --- a/cmd/jwx/go.mod +++ b/cmd/jwx/go.mod @@ -3,7 +3,7 @@ module github.com/lestrrat-go/jwx/v2/cmd/jwx go 1.17 require ( - github.com/lestrrat-go/jwx/v2 v2.0.18 + github.com/lestrrat-go/jwx/v2 v2.0.19 github.com/urfave/cli/v2 v2.26.0 golang.org/x/crypto v0.17.0 ) diff --git a/cmd/jwx/go.sum b/cmd/jwx/go.sum index 3e6a405df..1420bf90d 100644 --- a/cmd/jwx/go.sum +++ b/cmd/jwx/go.sum @@ -18,8 +18,8 @@ github.com/lestrrat-go/httprc v1.0.4 h1:bAZymwoZQb+Oq8MEbyipag7iSq6YIga8Wj6GOiJG github.com/lestrrat-go/httprc v1.0.4/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/jwx/v2 v2.0.18 h1:HHZkYS5wWDDyAiNBwztEtDoX07WDhGEdixm8G06R50o= -github.com/lestrrat-go/jwx/v2 v2.0.18/go.mod h1:fAJ+k5eTgKdDqanzCuK6DAt3W7n3cs2/FX7JhQdk83U= +github.com/lestrrat-go/jwx/v2 v2.0.19 h1:ekv1qEZE6BVct89QA+pRF6+4pCpfVrOnEJnTnT4RXoY= +github.com/lestrrat-go/jwx/v2 v2.0.19/go.mod h1:l3im3coce1lL2cDeAjqmaR+Awx+X8Ih+2k8BuHNJ4CU= github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU= github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= @@ -45,7 +45,6 @@ github.com/xrash/smetrics v0.0.0-20231213231151-1d8dd44e695e/go.mod h1:N3UwUGtsr 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.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= diff --git a/cmd/jwx/jwk.go b/cmd/jwx/jwk.go index b0ea6d2fe..04dc73f26 100644 --- a/cmd/jwx/jwk.go +++ b/cmd/jwx/jwk.go @@ -156,7 +156,7 @@ func makeJwkGenerateCmd() *cli.Command { rawkey = v case jwa.OctetSeq: octets := make([]byte, c.Int("keysize")) - rand.Reader.Read(octets) + io.ReadFull(rand.Reader, octets) rawkey = octets case jwa.OKP: diff --git a/deps.bzl b/deps.bzl index 25d35d927..badb900a4 100644 --- a/deps.bzl +++ b/deps.bzl @@ -115,8 +115,8 @@ def go_dependencies(): name = "org_golang_x_crypto", build_file_proto_mode = "disable_global", importpath = "golang.org/x/crypto", - sum = "h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=", - version = "v0.17.0", + sum = "h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo=", + version = "v0.19.0", ) go_repository( @@ -131,15 +131,15 @@ def go_dependencies(): name = "org_golang_x_sys", build_file_proto_mode = "disable_global", importpath = "golang.org/x/sys", - sum = "h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=", - version = "v0.15.0", + sum = "h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=", + version = "v0.17.0", ) go_repository( name = "org_golang_x_term", build_file_proto_mode = "disable_global", importpath = "golang.org/x/term", - sum = "h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4=", - version = "v0.15.0", + sum = "h1:mkTF7LCd6WGJNL3K1Ad7kwxNfYAW6a8a8QqtMblp/4U=", + version = "v0.17.0", ) go_repository( diff --git a/examples/go.mod b/examples/go.mod index 5e3c0f5aa..bc2ea2f65 100644 --- a/examples/go.mod +++ b/examples/go.mod @@ -3,7 +3,7 @@ module github.com/lestrrat-go/jwx/v2/examples go 1.16 require ( - github.com/cloudflare/circl v1.3.3 + github.com/cloudflare/circl v1.3.7 github.com/lestrrat-go/jwx/v2 v2.0.11 ) diff --git a/examples/go.sum b/examples/go.sum index 67772b16c..2e51a0e14 100644 --- a/examples/go.sum +++ b/examples/go.sum @@ -1,6 +1,6 @@ github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= -github.com/cloudflare/circl v1.3.3 h1:fE/Qz0QdIGqeWfnwq0RE0R7MI51s0M2E4Ga9kq5AEMs= -github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= +github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= +github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -35,15 +35,14 @@ 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.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= -golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= 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/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= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= 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/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -55,22 +54,20 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -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.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= 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/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.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= 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.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= diff --git a/go.mod b/go.mod index e8fe7dc8f..16d72ad6f 100644 --- a/go.mod +++ b/go.mod @@ -11,13 +11,13 @@ 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.17.0 + golang.org/x/crypto v0.19.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.15.0 // indirect + golang.org/x/sys v0.17.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 6fabf7232..fda61a017 100644 --- a/go.sum +++ b/go.sum @@ -24,10 +24,10 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ 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.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= -golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= -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/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= 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/internal/keyconv/keyconv.go b/internal/keyconv/keyconv.go index 807da1dee..f0ecbae16 100644 --- a/internal/keyconv/keyconv.go +++ b/internal/keyconv/keyconv.go @@ -18,7 +18,7 @@ func RSAPrivateKey(dst, src interface{}) error { if jwkKey, ok := src.(jwk.Key); ok { var raw rsa.PrivateKey if err := jwkKey.Raw(&raw); err != nil { - return fmt.Errorf(`failed to produce rsa.PrivateKey from %T: %w`, src, err) + return fmt.Errorf(`keyconv: failed to produce rsa.PrivateKey from %T: %w`, src, err) } src = &raw } @@ -30,7 +30,7 @@ func RSAPrivateKey(dst, src interface{}) error { case *rsa.PrivateKey: ptr = src default: - return fmt.Errorf(`expected rsa.PrivateKey or *rsa.PrivateKey, got %T`, src) + return fmt.Errorf(`keyconv: expected rsa.PrivateKey or *rsa.PrivateKey, got %T`, src) } return blackmagic.AssignIfCompatible(dst, ptr) @@ -41,21 +41,25 @@ func RSAPrivateKey(dst, src interface{}) error { // `src` may be rsa.PublicKey, *rsa.PublicKey, or a jwk.Key func RSAPublicKey(dst, src interface{}) error { if jwkKey, ok := src.(jwk.Key); ok { - var raw rsa.PublicKey - if err := jwkKey.Raw(&raw); err != nil { - return fmt.Errorf(`failed to produce rsa.PublicKey from %T: %w`, src, err) + pk, err := jwk.PublicRawKeyOf(jwkKey) + if err != nil { + return fmt.Errorf(`keyconv: failed to produce public key from %T: %w`, src, err) } - src = &raw + src = pk } var ptr *rsa.PublicKey switch src := src.(type) { + case rsa.PrivateKey: + ptr = &src.PublicKey + case *rsa.PrivateKey: + ptr = &src.PublicKey case rsa.PublicKey: ptr = &src case *rsa.PublicKey: ptr = src default: - return fmt.Errorf(`expected rsa.PublicKey or *rsa.PublicKey, got %T`, src) + return fmt.Errorf(`keyconv: expected rsa.PublicKey/rsa.PrivateKey or *rsa.PublicKey/*rsa.PrivateKey, got %T`, src) } return blackmagic.AssignIfCompatible(dst, ptr) @@ -67,7 +71,7 @@ func ECDSAPrivateKey(dst, src interface{}) error { if jwkKey, ok := src.(jwk.Key); ok { var raw ecdsa.PrivateKey if err := jwkKey.Raw(&raw); err != nil { - return fmt.Errorf(`failed to produce ecdsa.PrivateKey from %T: %w`, src, err) + return fmt.Errorf(`keyconv: failed to produce ecdsa.PrivateKey from %T: %w`, src, err) } src = &raw } @@ -79,7 +83,7 @@ func ECDSAPrivateKey(dst, src interface{}) error { case *ecdsa.PrivateKey: ptr = src default: - return fmt.Errorf(`expected ecdsa.PrivateKey or *ecdsa.PrivateKey, got %T`, src) + return fmt.Errorf(`keyconv: expected ecdsa.PrivateKey or *ecdsa.PrivateKey, got %T`, src) } return blackmagic.AssignIfCompatible(dst, ptr) } @@ -88,21 +92,25 @@ func ECDSAPrivateKey(dst, src interface{}) error { // non-pointer to a pointer func ECDSAPublicKey(dst, src interface{}) error { if jwkKey, ok := src.(jwk.Key); ok { - var raw ecdsa.PublicKey - if err := jwkKey.Raw(&raw); err != nil { - return fmt.Errorf(`failed to produce ecdsa.PublicKey from %T: %w`, src, err) + pk, err := jwk.PublicRawKeyOf(jwkKey) + if err != nil { + return fmt.Errorf(`keyconv: failed to produce public key from %T: %w`, src, err) } - src = &raw + src = pk } var ptr *ecdsa.PublicKey switch src := src.(type) { + case ecdsa.PrivateKey: + ptr = &src.PublicKey + case *ecdsa.PrivateKey: + ptr = &src.PublicKey case ecdsa.PublicKey: ptr = &src case *ecdsa.PublicKey: ptr = src default: - return fmt.Errorf(`expected ecdsa.PublicKey or *ecdsa.PublicKey, got %T`, src) + return fmt.Errorf(`keyconv: expected ecdsa.PublicKey/ecdsa.PrivateKey or *ecdsa.PublicKey/*ecdsa.PrivateKey, got %T`, src) } return blackmagic.AssignIfCompatible(dst, ptr) } @@ -111,13 +119,13 @@ func ByteSliceKey(dst, src interface{}) error { if jwkKey, ok := src.(jwk.Key); ok { var raw []byte if err := jwkKey.Raw(&raw); err != nil { - return fmt.Errorf(`failed to produce []byte from %T: %w`, src, err) + return fmt.Errorf(`keyconv: failed to produce []byte from %T: %w`, src, err) } src = raw } if _, ok := src.([]byte); !ok { - return fmt.Errorf(`expected []byte, got %T`, src) + return fmt.Errorf(`keyconv: expected []byte, got %T`, src) } return blackmagic.AssignIfCompatible(dst, src) } @@ -145,11 +153,18 @@ func Ed25519PrivateKey(dst, src interface{}) error { func Ed25519PublicKey(dst, src interface{}) error { if jwkKey, ok := src.(jwk.Key); ok { - var raw ed25519.PublicKey - if err := jwkKey.Raw(&raw); err != nil { - return fmt.Errorf(`failed to produce ed25519.PublicKey from %T: %w`, src, err) + pk, err := jwk.PublicRawKeyOf(jwkKey) + if err != nil { + return fmt.Errorf(`keyconv: failed to produce public key from %T: %w`, src, err) } - src = &raw + src = pk + } + + switch key := src.(type) { + case ed25519.PrivateKey: + src = key.Public() + case *ed25519.PrivateKey: + src = key.Public() } var ptr *ed25519.PublicKey diff --git a/jwe/BUILD.bazel b/jwe/BUILD.bazel index a5e3f4e0f..d0a06f0d5 100644 --- a/jwe/BUILD.bazel +++ b/jwe/BUILD.bazel @@ -25,6 +25,7 @@ go_library( "//internal/keyconv", "//internal/pool", "//jwa", + "//jwe/internal/aescbc", "//jwe/internal/cipher", "//jwe/internal/content_crypt", "//jwe/internal/keyenc", diff --git a/jwe/internal/aescbc/aescbc.go b/jwe/internal/aescbc/aescbc.go index acb5a83a8..749277b9d 100644 --- a/jwe/internal/aescbc/aescbc.go +++ b/jwe/internal/aescbc/aescbc.go @@ -10,18 +10,42 @@ import ( "errors" "fmt" "hash" + "sync/atomic" ) const ( NonceSize = 16 ) +const defaultBufSize int64 = 256 * 1024 * 1024 + +// Grr, we would like to use atomic.Int64, but that's only available +// from Go 1.19. Yes, we will cut support for Go 1.19 at some point, +// but not today (probably going to up the minimum required Go version +// some time after 1.22 is released) +var maxBufSize int64 + +func init() { + atomic.StoreInt64(&maxBufSize, defaultBufSize) +} + +func SetMaxBufferSize(siz int64) { + if siz <= 0 { + siz = defaultBufSize + } + atomic.StoreInt64(&maxBufSize, siz) +} + func pad(buf []byte, n int) []byte { rem := n - len(buf)%n if rem == 0 { return buf } + mbs := atomic.LoadInt64(&maxBufSize) + if int64(len(buf)+rem) > mbs { + panic(fmt.Errorf("failed to allocate buffer")) + } newbuf := make([]byte, len(buf)+rem) copy(newbuf, buf) @@ -174,6 +198,11 @@ func ensureSize(dst []byte, n int) []byte { // Seal fulfills the crypto.AEAD interface func (c Hmac) Seal(dst, nonce, plaintext, data []byte) []byte { ctlen := len(plaintext) + bufsiz := ctlen + c.Overhead() + mbs := atomic.LoadInt64(&maxBufSize) + if int64(bufsiz) > mbs { + panic(fmt.Errorf("failed to allocate buffer")) + } ciphertext := make([]byte, ctlen+c.Overhead())[:ctlen] copy(ciphertext, plaintext) ciphertext = pad(ciphertext, c.blockCipher.BlockSize()) diff --git a/jwe/jwe.go b/jwe/jwe.go index 7c2905a06..ae1b8e3a3 100644 --- a/jwe/jwe.go +++ b/jwe/jwe.go @@ -19,6 +19,7 @@ import ( "github.com/lestrrat-go/jwx/v2/jwk" "github.com/lestrrat-go/jwx/v2/jwa" + "github.com/lestrrat-go/jwx/v2/jwe/internal/aescbc" "github.com/lestrrat-go/jwx/v2/jwe/internal/content_crypt" "github.com/lestrrat-go/jwx/v2/jwe/internal/keyenc" "github.com/lestrrat-go/jwx/v2/jwe/internal/keygen" @@ -36,6 +37,8 @@ func Settings(options ...GlobalOption) { switch option.Ident() { case identMaxPBES2Count{}: maxPBES2Count = option.Value().(int) + case identMaxBufferSize{}: + aescbc.SetMaxBufferSize(option.Value().(int64)) } } } diff --git a/jwe/jwe_test.go b/jwe/jwe_test.go index 9559abcd3..5f9a3bcbf 100644 --- a/jwe/jwe_test.go +++ b/jwe/jwe_test.go @@ -959,3 +959,15 @@ func TestGHSA_7f9x_gw85_8grf(t *testing.T) { } } } + +func TestMaxBufferSize(t *testing.T) { + // NOTE: This has GLOBAL EFFECT + jwe.Settings(jwe.WithMaxBufferSize(1)) + defer jwe.Settings(jwe.WithMaxBufferSize(0)) + + key, err := jwxtest.GenerateRsaJwk() + require.NoError(t, err, `jwxtest.GenerateRsaJwk should succeed`) + + _, 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`) +} diff --git a/jwe/options.yaml b/jwe/options.yaml index bf7e0a01e..2de7fdd14 100644 --- a/jwe/options.yaml +++ b/jwe/options.yaml @@ -138,4 +138,15 @@ options: 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 + value of 10,000 is used. + + This option has a global effect. + - ident: MaxBufferSize + interface: GlobalOption + argument_type: int64 + comment: | + WithMaxBufferSize specifies the maximum buffer size for internal + 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 diff --git a/jwe/options_gen.go b/jwe/options_gen.go index b3de13200..3b74a8a99 100644 --- a/jwe/options_gen.go +++ b/jwe/options_gen.go @@ -129,6 +129,7 @@ type identFS struct{} type identKey struct{} type identKeyProvider struct{} type identKeyUsed struct{} +type identMaxBufferSize struct{} type identMaxPBES2Count struct{} type identMergeProtectedHeaders struct{} type identMessage struct{} @@ -166,6 +167,10 @@ func (identKeyUsed) String() string { return "WithKeyUsed" } +func (identMaxBufferSize) String() string { + return "WithMaxBufferSize" +} + func (identMaxPBES2Count) String() string { return "WithMaxPBES2Count" } @@ -245,9 +250,20 @@ func WithKeyUsed(v interface{}) DecryptOption { return &decryptOption{option.New(identKeyUsed{}, v)} } +// WithMaxBufferSize specifies the maximum buffer size for internal +// 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. +func WithMaxBufferSize(v int64) GlobalOption { + return &globalOption{option.New(identMaxBufferSize{}, 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. +// +// This option has a global effect. func WithMaxPBES2Count(v int) GlobalOption { return &globalOption{option.New(identMaxPBES2Count{}, v)} } diff --git a/jwe/options_gen_test.go b/jwe/options_gen_test.go index d36b9765a..b58d2362c 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, "WithMaxBufferSize", identMaxBufferSize{}.String()) require.Equal(t, "WithMaxPBES2Count", identMaxPBES2Count{}.String()) require.Equal(t, "WithMergeProtectedHeaders", identMergeProtectedHeaders{}.String()) require.Equal(t, "WithMessage", identMessage{}.String()) diff --git a/jws/jws.go b/jws/jws.go index 4a9e81945..096e4f22d 100644 --- a/jws/jws.go +++ b/jws/jws.go @@ -103,7 +103,7 @@ func makeSigner(alg jwa.SignatureAlgorithm, key interface{}, public, protected H } const ( - fmtInvalid = iota + fmtInvalid = 1 << iota fmtCompact fmtJSON fmtJSONPretty @@ -314,6 +314,7 @@ var allowNoneWhitelist = jwk.WhitelistFunc(func(string) bool { // accept messages with "none" signature algorithm, use `jws.Parse` to get the // raw JWS message. func Verify(buf []byte, options ...VerifyOption) ([]byte, error) { + var parseOptions []ParseOption var dst *Message var detachedPayload []byte var keyProviders []KeyProvider @@ -347,6 +348,8 @@ func Verify(buf []byte, options ...VerifyOption) ([]byte, error) { ctx = option.Value().(context.Context) case identValidateKey{}: validateKey = option.Value().(bool) + case identSerialization{}: + parseOptions = append(parseOptions, option.(ParseOption)) default: return nil, fmt.Errorf(`invalid jws.VerifyOption %q passed`, `With`+strings.TrimPrefix(fmt.Sprintf(`%T`, option.Ident()), `jws.ident`)) } @@ -356,7 +359,7 @@ func Verify(buf []byte, options ...VerifyOption) ([]byte, error) { return nil, fmt.Errorf(`jws.Verify: no key providers have been provided (see jws.WithKey(), jws.WithKeySet(), jws.WithVerifyAuto(), and jws.WithKeyProvider()`) } - msg, err := Parse(buf) + msg, err := Parse(buf, parseOptions...) if err != nil { return nil, fmt.Errorf(`failed to parse jws: %w`, err) } @@ -523,23 +526,47 @@ func readAll(rdr io.Reader) ([]byte, bool) { } // Parse parses contents from the given source and creates a jws.Message -// struct. The input can be in either compact or full JSON serialization. +// struct. By default the input can be in either compact or full JSON serialization. // -// Parse() currently does not take any options, but the API accepts it -// in anticipation of future addition. -func Parse(src []byte, _ ...ParseOption) (*Message, error) { - for i := 0; i < len(src); i++ { - r := rune(src[i]) - if r >= utf8.RuneSelf { - r, _ = utf8.DecodeRune(src) +// You may pass `jws.WithJSON()` and/or `jws.WithCompact()` to specify +// explicitly which format to use. If neither or both is specified, the function +// will attempt to autodetect the format. If one or the other is specified, +// only the specified format will be attempted. +func Parse(src []byte, options ...ParseOption) (*Message, error) { + var formats int + for _, option := range options { + //nolint:forcetypeassert + switch option.Ident() { + case identSerialization{}: + switch option.Value().(int) { + case fmtJSON: + formats |= fmtJSON + case fmtCompact: + formats |= fmtCompact + } } - if !unicode.IsSpace(r) { - if r == '{' { - return parseJSON(src) + } + + // if format is 0 or both JSON/Compact, auto detect + if v := formats & (fmtJSON | fmtCompact); v == 0 || v == fmtJSON|fmtCompact { + for i := 0; i < len(src); i++ { + r := rune(src[i]) + if r >= utf8.RuneSelf { + r, _ = utf8.DecodeRune(src) + } + if !unicode.IsSpace(r) { + if r == '{' { + return parseJSON(src) + } + return parseCompact(src) } - return parseCompact(src) } + } else if formats&fmtCompact == fmtCompact { + return parseCompact(src) + } else if formats&fmtJSON == fmtJSON { + return parseJSON(src) } + return nil, fmt.Errorf(`invalid byte sequence`) } diff --git a/jws/jws_test.go b/jws/jws_test.go index 04fdb9d9a..ecf54085c 100644 --- a/jws/jws_test.go +++ b/jws/jws_test.go @@ -1887,3 +1887,53 @@ func TestEmptyProtectedField(t *testing.T) { _, err = jws.Parse(invalidMessage) require.Error(t, err, `jws.Parse should fail`) } + +func TestParseFormat(t *testing.T) { + privKey, err := jwxtest.GenerateRsaJwk() + require.NoError(t, err, `jwxtest.GenerateRsaJwk should succeed`) + + signedCompact, err := jws.Sign([]byte("Lorem Ipsum"), jws.WithKey(jwa.RS256, privKey), jws.WithValidateKey(true)) + require.NoError(t, err, `jws.Sign should succeed`) + + signedJSON, err := jws.Sign([]byte("Lorem Ipsum"), jws.WithKey(jwa.RS256, privKey), jws.WithValidateKey(true), jws.WithJSON()) + require.NoError(t, err, `jws.Sign should succeed`) + + // Only compact formats should succeed + _, err = jws.Verify(signedCompact, jws.WithKey(jwa.RS256, privKey), jws.WithCompact()) + require.NoError(t, err, `jws.Verify should succeed`) + _, err = jws.Verify(signedJSON, jws.WithKey(jwa.RS256, privKey), jws.WithCompact()) + require.Error(t, err, `jws.Verify should fail`) + _, err = jws.Parse(signedCompact, jws.WithCompact()) + require.NoError(t, err, `jws.Parse should succeed`) + _, err = jws.Parse(signedJSON, jws.WithCompact()) + require.Error(t, err, `jws.Parse should fail`) + + // Only JSON formats should succeed + _, err = jws.Verify(signedCompact, jws.WithKey(jwa.RS256, privKey), jws.WithJSON()) + require.Error(t, err, `jws.Verify should fail`) + _, err = jws.Verify(signedJSON, jws.WithKey(jwa.RS256, privKey), jws.WithJSON()) + require.NoError(t, err, `jws.Verify should succeed`) + _, err = jws.Parse(signedJSON, jws.WithJSON()) + require.NoError(t, err, `jws.Parse should succeed`) + _, err = jws.Parse(signedCompact, jws.WithJSON()) + require.Error(t, err, `jws.Parse should fail`) + + // Either format should succeed + _, err = jws.Verify(signedCompact, jws.WithKey(jwa.RS256, privKey)) + require.NoError(t, err, `jws.Verify should succeed`) + _, err = jws.Verify(signedCompact, jws.WithKey(jwa.RS256, privKey), jws.WithJSON(), jws.WithCompact()) + require.NoError(t, err, `jws.Verify should succeed`) + _, err = jws.Parse(signedCompact) + require.NoError(t, err, `jws.Parse should succeed`) + _, err = jws.Parse(signedCompact, jws.WithJSON(), jws.WithCompact()) + require.NoError(t, err, `jws.Parse should succeed`) + + _, err = jws.Verify(signedJSON, jws.WithKey(jwa.RS256, privKey)) + require.NoError(t, err, `jws.Verify should succeed`) + _, err = jws.Verify(signedJSON, jws.WithKey(jwa.RS256, privKey), jws.WithJSON(), jws.WithCompact()) + require.NoError(t, err, `jws.Verify should succeed`) + _, err = jws.Parse(signedJSON) + require.NoError(t, err, `jws.Parse should succeed`) + _, err = jws.Parse(signedJSON, jws.WithJSON(), jws.WithCompact()) + require.NoError(t, err, `jws.Parse should succeed`) +} diff --git a/jws/options.go b/jws/options.go index e374a85d0..a6cc472c9 100644 --- a/jws/options.go +++ b/jws/options.go @@ -20,7 +20,7 @@ func WithHeaders(h Headers) SignOption { // // If you pass multiple keys to `jws.Sign()`, it will fail unless // you also pass this option. -func WithJSON(options ...WithJSONSuboption) SignOption { +func WithJSON(options ...WithJSONSuboption) SignVerifyParseOption { var pretty bool for _, option := range options { //nolint:forcetypeassert @@ -34,7 +34,7 @@ func WithJSON(options ...WithJSONSuboption) SignOption { if pretty { format = fmtJSONPretty } - return &signOption{option.New(identSerialization{}, format)} + return &signVerifyParseOption{option.New(identSerialization{}, format)} } type withKey struct { diff --git a/jws/options.yaml b/jws/options.yaml index 65b5352c1..75a2de821 100644 --- a/jws/options.yaml +++ b/jws/options.yaml @@ -7,6 +7,9 @@ interfaces: - name: VerifyOption comment: | VerifyOption describes options that can be passed to `jws.Verify` + methods: + - verifyOption + - parseOption - name: SignOption comment: | SignOption describes options that can be passed to `jws.Sign` @@ -14,6 +17,7 @@ interfaces: methods: - signOption - verifyOption + - parseOption comment: | SignVerifyOption describes options that can be passed to either `jws.Verify` or `jws.Sign` - name: WithJSONSuboption @@ -35,6 +39,12 @@ interfaces: - name: ReadFileOption comment: | ReadFileOption is a type of `Option` that can be passed to `jws.ReadFile` + - name: SignVerifyParseOption + methods: + - signOption + - verifyOption + - parseOption + - readFileOption options: - ident: Key skip_option: true @@ -42,7 +52,7 @@ options: skip_option: true - ident: Serialization option_name: WithCompact - interface: SignOption + interface: SignVerifyParseOption constant_value: fmtCompact comment: | WithCompact specifies that the result of `jws.Sign()` is serialized in diff --git a/jws/options_gen.go b/jws/options_gen.go index ca834e103..fbef1ef3f 100644 --- a/jws/options_gen.go +++ b/jws/options_gen.go @@ -64,6 +64,7 @@ type SignVerifyOption interface { Option signOption() verifyOption() + parseOption() } type signVerifyOption struct { @@ -74,10 +75,33 @@ func (*signVerifyOption) signOption() {} func (*signVerifyOption) verifyOption() {} +func (*signVerifyOption) parseOption() {} + +type SignVerifyParseOption interface { + Option + signOption() + verifyOption() + parseOption() + readFileOption() +} + +type signVerifyParseOption struct { + Option +} + +func (*signVerifyParseOption) signOption() {} + +func (*signVerifyParseOption) verifyOption() {} + +func (*signVerifyParseOption) parseOption() {} + +func (*signVerifyParseOption) readFileOption() {} + // VerifyOption describes options that can be passed to `jws.Verify` type VerifyOption interface { Option verifyOption() + parseOption() } type verifyOption struct { @@ -86,6 +110,8 @@ type verifyOption struct { func (*verifyOption) verifyOption() {} +func (*verifyOption) parseOption() {} + // JSONSuboption describes suboptions that can be passed to `jws.WithJSON()` option type WithJSONSuboption interface { Option @@ -329,8 +355,8 @@ func WithRequireKid(v bool) WithKeySetSuboption { // // By default `jws.Sign()` will opt to use compact format, so you usually // do not need to specify this option other than to be explicit about it -func WithCompact() SignOption { - return &signOption{option.New(identSerialization{}, fmtCompact)} +func WithCompact() SignVerifyParseOption { + return &signVerifyParseOption{option.New(identSerialization{}, fmtCompact)} } // WithUseDefault specifies that if and only if a jwk.Key contains diff --git a/jwt/jwt.go b/jwt/jwt.go index fa0c20214..c09ea8bf4 100644 --- a/jwt/jwt.go +++ b/jwt/jwt.go @@ -17,6 +17,7 @@ import ( "github.com/lestrrat-go/jwx/v2/jwt/internal/types" ) +var compactOnly uint32 var errInvalidJWT = errors.New(`invalid JWT`) // ErrInvalidJWT returns the opaque error value that is returned when @@ -28,7 +29,8 @@ func ErrInvalidJWT() error { // Settings controls global settings that are specific to JWTs. func Settings(options ...GlobalOption) { - var flattenAudienceBool bool + var flattenAudience bool + var compactOnlyBool bool var parsePedantic bool var parsePrecision = types.MaxPrecision + 1 // illegal value, so we can detect nothing was set var formatPrecision = types.MaxPrecision + 1 // illegal value, so we can detect nothing was set @@ -37,7 +39,9 @@ func Settings(options ...GlobalOption) { for _, option := range options { switch option.Ident() { case identFlattenAudience{}: - flattenAudienceBool = option.Value().(bool) + flattenAudience = option.Value().(bool) + case identCompactOnly{}: + compactOnlyBool = option.Value().(bool) case identNumericDateParsePedantic{}: parsePedantic = option.Value().(bool) case identNumericDateParsePrecision{}: @@ -80,9 +84,20 @@ func Settings(options ...GlobalOption) { } } + { + v := atomic.LoadUint32(&compactOnly) + if (v == 1) != compactOnlyBool { + var newVal uint32 + if compactOnlyBool { + newVal = 1 + } + atomic.CompareAndSwapUint32(&compactOnly, v, newVal) + } + } + { defaultOptionsMu.Lock() - if flattenAudienceBool { + if flattenAudience { defaultOptions.Enable(FlattenAudience) } else { defaultOptions.Disable(FlattenAudience) @@ -244,7 +259,11 @@ func verifyJWS(ctx *parseCtx, payload []byte) ([]byte, int, error) { return nil, _JwsVerifySkipped, nil } - verified, err := jws.Verify(payload, ctx.verifyOpts...) + verifyOpts := ctx.verifyOpts + if atomic.LoadUint32(&compactOnly) == 1 { + verifyOpts = append(verifyOpts, jws.WithCompact()) + } + verified, err := jws.Verify(payload, verifyOpts...) return verified, _JwsVerifyDone, err } @@ -330,7 +349,11 @@ OUTER: } // No verification. - m, err := jws.Parse(data) + var parseOptions []jws.ParseOption + if atomic.LoadUint32(&compactOnly) == 1 { + parseOptions = append(parseOptions, jws.WithCompact()) + } + m, err := jws.Parse(data, parseOptions...) if err != nil { return nil, fmt.Errorf(`invalid jws message: %w`, err) } diff --git a/jwt/jwt_test.go b/jwt/jwt_test.go index 8e94faa34..86f323d72 100644 --- a/jwt/jwt_test.go +++ b/jwt/jwt_test.go @@ -1773,3 +1773,33 @@ func TestGH1007(t *testing.T) { _, err = jwt.ParseInsecure(signed, jwt.WithKey(jwa.RS256, wrongPubKey)) require.NoError(t, err, `jwt.ParseInsecure with jwt.WithKey() should succeed`) } + +func TestParseJSON(t *testing.T) { + // NOTE: jwt.Settings has global effect! + defer jwt.Settings(jwt.WithCompactOnly(false)) + for _, compactOnly := range []bool{true, false} { + t.Run("compactOnly="+strconv.FormatBool(compactOnly), func(t *testing.T) { + jwt.Settings(jwt.WithCompactOnly(compactOnly)) + + privKey, err := jwxtest.GenerateRsaJwk() + require.NoError(t, err, `jwxtest.GenerateRsaJwk should succeed`) + + signedJSON, err := jws.Sign([]byte(`{}`), jws.WithKey(jwa.RS256, privKey), jws.WithValidateKey(true), jws.WithJSON()) + require.NoError(t, err, `jws.Sign should succeed`) + + // jws.Verify should succeed + _, err = jws.Verify(signedJSON, jws.WithKey(jwa.RS256, privKey)) + require.NoError(t, err, `jws.Parse should succeed`) + + if compactOnly { + // jwt.Parse should fail + _, err = jwt.Parse(signedJSON, jwt.WithKey(jwa.RS256, privKey)) + require.Error(t, err, `jws.Parse should fail`) + } else { + // for backward compatibility, this should succeed + _, err = jwt.Parse(signedJSON, jwt.WithKey(jwa.RS256, privKey)) + require.NoError(t, err, `jws.Parse should succeed`) + } + }) + } +} diff --git a/jwt/openid/birthdate.go b/jwt/openid/birthdate.go index c356d7f04..a193b4034 100644 --- a/jwt/openid/birthdate.go +++ b/jwt/openid/birthdate.go @@ -4,6 +4,7 @@ import ( "bytes" "fmt" "io" + "math" "regexp" "strconv" @@ -57,9 +58,22 @@ func (b *BirthdateClaim) UnmarshalJSON(data []byte) error { return nil } -func tointptr(v int64) *int { - i := int(v) - return &i +var intSize int + +func init() { + switch math.MaxInt { + case math.MaxInt16: + intSize = 16 + case math.MaxInt32: + intSize = 32 + case math.MaxInt64: + intSize = 64 + } +} + +func parseBirthdayInt(s string) int { + i, _ := strconv.ParseInt(s, 10, intSize) + return int(i) } var birthdateRx = regexp.MustCompile(`^(\d{4})-(\d{2})-(\d{2})$`) @@ -100,23 +114,23 @@ func (b *BirthdateClaim) Accept(v interface{}) error { // we can assume that strconv.ParseInt always succeeds. // strconv.ParseInt (and strconv.ParseUint that it uses internally) // only returns range errors, so we should be safe. - year, _ := strconv.ParseInt(v[indices[2]:indices[3]], 10, 64) + year := parseBirthdayInt(v[indices[2]:indices[3]]) if year <= 0 { return fmt.Errorf(`failed to parse birthdate year`) } - tmp.year = tointptr(year) + tmp.year = &year - month, _ := strconv.ParseInt(v[indices[4]:indices[5]], 10, 64) + month := parseBirthdayInt(v[indices[4]:indices[5]]) if month <= 0 { return fmt.Errorf(`failed to parse birthdate month`) } - tmp.month = tointptr(month) + tmp.month = &month - day, _ := strconv.ParseInt(v[indices[6]:indices[7]], 10, 64) + day := parseBirthdayInt(v[indices[6]:indices[7]]) if day <= 0 { return fmt.Errorf(`failed to parse birthdate day`) } - tmp.day = tointptr(day) + tmp.day = &day *b = tmp return nil diff --git a/jwt/options.yaml b/jwt/options.yaml index 2a11b9b4c..5e257801e 100644 --- a/jwt/options.yaml +++ b/jwt/options.yaml @@ -82,6 +82,16 @@ options: See the documentation for `jwt.TokenOptionSet`, `(jwt.Token).Options`, and `jwt.FlattenAudience` for more details + - ident: CompactOnly + interface: GlobalOption + argument_type: bool + comment: | + WithCompactOnly option controls whether jwt.Parse should accept only tokens + that are in compact serialization format. RFC7519 specifies that JWTs + should be serialized in JWS compact form only, but historically this library + allowed for deserialization of JWTs in JWS's JSON serialization format. + Specifying this option will disable this behavior, and will report + errots if the token is not in compact serialization format. - ident: FormKey interface: ParseOption argument_type: string diff --git a/jwt/options_gen.go b/jwt/options_gen.go index ebde2d611..375d704e1 100644 --- a/jwt/options_gen.go +++ b/jwt/options_gen.go @@ -124,6 +124,7 @@ func (*validateOption) validateOption() {} type identAcceptableSkew struct{} type identClock struct{} +type identCompactOnly struct{} type identContext struct{} type identEncryptOption struct{} type identFS struct{} @@ -150,6 +151,10 @@ func (identClock) String() string { return "WithClock" } +func (identCompactOnly) String() string { + return "WithCompactOnly" +} + func (identContext) String() string { return "WithContext" } @@ -230,6 +235,16 @@ func WithClock(v Clock) ValidateOption { return &validateOption{option.New(identClock{}, v)} } +// WithCompactOnly option controls whether jwt.Parse should accept only tokens +// that are in compact serialization format. RFC7519 specifies that JWTs +// should be serialized in JWS compact form only, but historically this library +// allowed for deserialization of JWTs in JWS's JSON serialization format. +// Specifying this option will disable this behavior, and will report +// errots if the token is not in compact serialization format. +func WithCompactOnly(v bool) GlobalOption { + return &globalOption{option.New(identCompactOnly{}, v)} +} + // WithContext allows you to specify a context.Context object to be used // with `jwt.Validate()` option. // diff --git a/jwt/options_gen_test.go b/jwt/options_gen_test.go index bf7b55ed9..1ad69c2b7 100644 --- a/jwt/options_gen_test.go +++ b/jwt/options_gen_test.go @@ -11,6 +11,7 @@ import ( func TestOptionIdent(t *testing.T) { require.Equal(t, "WithAcceptableSkew", identAcceptableSkew{}.String()) require.Equal(t, "WithClock", identClock{}.String()) + require.Equal(t, "WithCompactOnly", identCompactOnly{}.String()) require.Equal(t, "WithContext", identContext{}.String()) require.Equal(t, "WithEncryptOption", identEncryptOption{}.String()) require.Equal(t, "WithFS", identFS{}.String())