From 8840ffd4afc5839f591ff0e9ba9034af52b1643e Mon Sep 17 00:00:00 2001 From: lestrrat <49281+lestrrat@users.noreply.github.com> Date: Mon, 12 Jun 2023 16:24:20 +0900 Subject: [PATCH] merge for v2.0.10 (#932) * Update deps * Protect jws.Verify() and jwe.Encrypt() from panic on go1.19+ (#841) * Protect jws.Verify() from panic on go1.19+ * Same problem, but in jwe * Update Changes * fix example (#843) I have a feeling we inadvertently reverted some commit * Action updates, doc tweaks (#844) * Use tparse (#845) * Use tparse * s/all/alltags/ * fix typo (#846) * fix typo (#847) * Bump kentaro-m/auto-assign-action from 1.2.0 to 1.2.4 (#848) Bumps [kentaro-m/auto-assign-action](https://github.com/kentaro-m/auto-assign-action) from 1.2.0 to 1.2.4. - [Release notes](https://github.com/kentaro-m/auto-assign-action/releases) - [Commits](https://github.com/kentaro-m/auto-assign-action/compare/v1.2.0...v1.2.4) --- updated-dependencies: - dependency-name: kentaro-m/auto-assign-action dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Bump codecov/codecov-action from 1 to 3 (#849) Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 1 to 3. - [Release notes](https://github.com/codecov/codecov-action/releases) - [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/codecov/codecov-action/compare/v1...v3) --- updated-dependencies: - dependency-name: codecov/codecov-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Work with invalid JWT buffers better (#851) * Work with invalid JWT buffers better * spelling * Update Changes * typo * Tweak Changes * Update Changes * Bump github.com/goccy/go-json from 0.9.11 to 0.10.0 (#855) Bumps [github.com/goccy/go-json](https://github.com/goccy/go-json) from 0.9.11 to 0.10.0. - [Release notes](https://github.com/goccy/go-json/releases) - [Changelog](https://github.com/goccy/go-json/blob/master/CHANGELOG.md) - [Commits](https://github.com/goccy/go-json/compare/v0.9.11...v0.10.0) --- updated-dependencies: - dependency-name: github.com/goccy/go-json dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Bump github.com/lestrrat-go/option from 1.0.0 to 1.0.1 (#858) Bumps [github.com/lestrrat-go/option](https://github.com/lestrrat-go/option) from 1.0.0 to 1.0.1. - [Release notes](https://github.com/lestrrat-go/option/releases) - [Commits](https://github.com/lestrrat-go/option/compare/v1.0.0...v1.0.1) --- updated-dependencies: - dependency-name: github.com/lestrrat-go/option dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Bump actions/stale from 6 to 7 (#859) Bumps [actions/stale](https://github.com/actions/stale) from 6 to 7. - [Release notes](https://github.com/actions/stale/releases) - [Changelog](https://github.com/actions/stale/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/stale/compare/v6...v7) --- updated-dependencies: - dependency-name: actions/stale dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Tweak v2 tests (#863) * Port changes from #862 * Actually report errors * fix expected result * Unbeknownst to me, benchstat seems to have changed * Update Contribution Guidelines * Fix generated header file comments (#867) The generated file header should match regexp: ^// Code generated .* DO NOT EDIT\.$ See https://golang.org/s/generatedcode. * Remove unused variables in ReadFile (#866) * Bump kentaro-m/auto-assign-action from 1.2.4 to 1.2.5 (#868) Bumps [kentaro-m/auto-assign-action](https://github.com/kentaro-m/auto-assign-action) from 1.2.4 to 1.2.5. - [Release notes](https://github.com/kentaro-m/auto-assign-action/releases) - [Commits](https://github.com/kentaro-m/auto-assign-action/compare/v1.2.4...v1.2.5) --- updated-dependencies: - dependency-name: kentaro-m/auto-assign-action dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Update tool deps (#869) * Try updating tools for genjwt * Update genjws * Update genjwe * Update genjwk * Update genjwa * Update genjwk * Updage genoptions * Update genreadfile * Fix PEM armor for EC private keys when encoding (#876) * Incorporate #875 * Test PEM roundtrip for other key types * Use more constants * Bump golang.org/x/crypto from 0.0.0-20220427172511-eb4f295cb31f to 0.6.0 (#871) * Bump golang.org/x/crypto from 0.0.0-20220427172511-eb4f295cb31f to 0.6.0 Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.0.0-20220427172511-eb4f295cb31f to 0.6.0. - [Release notes](https://github.com/golang/crypto/releases) - [Commits](https://github.com/golang/crypto/commits/v0.6.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 appropriate `go get` and `go mod tidy` all over --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Daisuke Maki * Bump github.com/stretchr/testify from 1.8.1 to 1.8.2 (#873) * Bump github.com/stretchr/testify from 1.8.1 to 1.8.2 Bumps [github.com/stretchr/testify](https://github.com/stretchr/testify) from 1.8.1 to 1.8.2. - [Release notes](https://github.com/stretchr/testify/releases) - [Commits](https://github.com/stretchr/testify/compare/v1.8.1...v1.8.2) --- updated-dependencies: - dependency-name: github.com/stretchr/testify dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * run appropriate `go get` and `go mod tidy` all over --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Daisuke Maki * Update Changes * Create codeql.yml * Bump golang.org/x/crypto from 0.6.0 to 0.7.0 (#877) * Bump golang.org/x/crypto from 0.6.0 to 0.7.0 Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.6.0 to 0.7.0. - [Release notes](https://github.com/golang/crypto/releases) - [Commits](https://github.com/golang/crypto/compare/v0.6.0...v0.7.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 go get and make tidy --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Daisuke Maki * Add bazel support (#880) * Attempt to enable bazel * enable bazel building in smoke tests too * tweak order * Add explicit imports * Add deps.bzl * remove unused file reference * Add missing BUILD file * Add missing BUILD file * add missing BUILD.bazel files * add .bazelversion * Add aspect presets * Update Changes/README * Create an auto-merge action for dependabot (#884) * Create an auto-merge action for dependabot * approve and merge * indent * Bump github.com/goccy/go-json from 0.10.0 to 0.10.1 (#882) * Bump github.com/goccy/go-json from 0.10.0 to 0.10.1 Bumps [github.com/goccy/go-json](https://github.com/goccy/go-json) from 0.10.0 to 0.10.1. - [Release notes](https://github.com/goccy/go-json/releases) - [Changelog](https://github.com/goccy/go-json/blob/master/CHANGELOG.md) - [Commits](https://github.com/goccy/go-json/compare/v0.10.0...v0.10.1) --- updated-dependencies: - dependency-name: github.com/goccy/go-json dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * Run make tidy + bazel gazelle-update-repos --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Daisuke Maki * Fix jwk cache docs (#885) * Fix example comment * Upon re-reading, this sentence does not need to exist * autodoc updates (#886) Co-authored-by: lestrrat * Bump actions/setup-go from 3 to 4 (#887) Bumps [actions/setup-go](https://github.com/actions/setup-go) from 3 to 4. - [Release notes](https://github.com/actions/setup-go/releases) - [Commits](https://github.com/actions/setup-go/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/setup-go dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Allow "none" algorithm when signing with explicit option (#890) * Add test case for #888 * Catch the use of "none" when used in conjunction with jws.WithKey * first pass implementing (jwt/jws).Sign that allows alg="none" * regenerate jwt options * appease linter * Check for jws.Sign/Verify * OK to _sign_ using `none`, but no verification * Tweak Changes * typo (#893) * Bump github.com/goccy/go-json from 0.10.1 to 0.10.2 (#892) * Bump github.com/goccy/go-json from 0.10.1 to 0.10.2 Bumps [github.com/goccy/go-json](https://github.com/goccy/go-json) from 0.10.1 to 0.10.2. - [Release notes](https://github.com/goccy/go-json/releases) - [Changelog](https://github.com/goccy/go-json/blob/master/CHANGELOG.md) - [Commits](https://github.com/goccy/go-json/compare/v0.10.1...v0.10.2) --- updated-dependencies: - dependency-name: github.com/goccy/go-json dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * Run make tidy + bazel --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Daisuke Maki * Update Changes * Bump github.com/goccy/go-json from 0.9.11 to 0.10.0 (#855) Bumps [github.com/goccy/go-json](https://github.com/goccy/go-json) from 0.9.11 to 0.10.0. - [Release notes](https://github.com/goccy/go-json/releases) - [Changelog](https://github.com/goccy/go-json/blob/master/CHANGELOG.md) - [Commits](https://github.com/goccy/go-json/compare/v0.9.11...v0.10.0) --- updated-dependencies: - dependency-name: github.com/goccy/go-json dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Bump github.com/lestrrat-go/option from 1.0.0 to 1.0.1 (#858) Bumps [github.com/lestrrat-go/option](https://github.com/lestrrat-go/option) from 1.0.0 to 1.0.1. - [Release notes](https://github.com/lestrrat-go/option/releases) - [Commits](https://github.com/lestrrat-go/option/compare/v1.0.0...v1.0.1) --- updated-dependencies: - dependency-name: github.com/lestrrat-go/option dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Bump actions/stale from 6 to 7 (#859) Bumps [actions/stale](https://github.com/actions/stale) from 6 to 7. - [Release notes](https://github.com/actions/stale/releases) - [Changelog](https://github.com/actions/stale/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/stale/compare/v6...v7) --- updated-dependencies: - dependency-name: actions/stale dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Tweak v2 tests (#863) * Port changes from #862 * Actually report errors * fix expected result * Unbeknownst to me, benchstat seems to have changed * Update Contribution Guidelines * Fix generated header file comments (#867) The generated file header should match regexp: ^// Code generated .* DO NOT EDIT\.$ See https://golang.org/s/generatedcode. * Remove unused variables in ReadFile (#866) * Bump kentaro-m/auto-assign-action from 1.2.4 to 1.2.5 (#868) Bumps [kentaro-m/auto-assign-action](https://github.com/kentaro-m/auto-assign-action) from 1.2.4 to 1.2.5. - [Release notes](https://github.com/kentaro-m/auto-assign-action/releases) - [Commits](https://github.com/kentaro-m/auto-assign-action/compare/v1.2.4...v1.2.5) --- updated-dependencies: - dependency-name: kentaro-m/auto-assign-action dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Update tool deps (#869) * Try updating tools for genjwt * Update genjws * Update genjwe * Update genjwk * Update genjwa * Update genjwk * Updage genoptions * Update genreadfile * Fix PEM armor for EC private keys when encoding (#876) * Incorporate #875 * Test PEM roundtrip for other key types * Use more constants * Bump golang.org/x/crypto from 0.0.0-20220427172511-eb4f295cb31f to 0.6.0 (#871) * Bump golang.org/x/crypto from 0.0.0-20220427172511-eb4f295cb31f to 0.6.0 Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.0.0-20220427172511-eb4f295cb31f to 0.6.0. - [Release notes](https://github.com/golang/crypto/releases) - [Commits](https://github.com/golang/crypto/commits/v0.6.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 appropriate `go get` and `go mod tidy` all over --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Daisuke Maki * Bump github.com/stretchr/testify from 1.8.1 to 1.8.2 (#873) * Bump github.com/stretchr/testify from 1.8.1 to 1.8.2 Bumps [github.com/stretchr/testify](https://github.com/stretchr/testify) from 1.8.1 to 1.8.2. - [Release notes](https://github.com/stretchr/testify/releases) - [Commits](https://github.com/stretchr/testify/compare/v1.8.1...v1.8.2) --- updated-dependencies: - dependency-name: github.com/stretchr/testify dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * run appropriate `go get` and `go mod tidy` all over --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Daisuke Maki * Update Changes * Create codeql.yml * Bump golang.org/x/crypto from 0.6.0 to 0.7.0 (#877) * Bump golang.org/x/crypto from 0.6.0 to 0.7.0 Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.6.0 to 0.7.0. - [Release notes](https://github.com/golang/crypto/releases) - [Commits](https://github.com/golang/crypto/compare/v0.6.0...v0.7.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 go get and make tidy --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Daisuke Maki * Add bazel support (#880) * Attempt to enable bazel * enable bazel building in smoke tests too * tweak order * Add explicit imports * Add deps.bzl * remove unused file reference * Add missing BUILD file * Add missing BUILD file * add missing BUILD.bazel files * add .bazelversion * Add aspect presets * Update Changes/README * Create an auto-merge action for dependabot (#884) * Create an auto-merge action for dependabot * approve and merge * indent * Bump github.com/goccy/go-json from 0.10.0 to 0.10.1 (#882) * Bump github.com/goccy/go-json from 0.10.0 to 0.10.1 Bumps [github.com/goccy/go-json](https://github.com/goccy/go-json) from 0.10.0 to 0.10.1. - [Release notes](https://github.com/goccy/go-json/releases) - [Changelog](https://github.com/goccy/go-json/blob/master/CHANGELOG.md) - [Commits](https://github.com/goccy/go-json/compare/v0.10.0...v0.10.1) --- updated-dependencies: - dependency-name: github.com/goccy/go-json dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * Run make tidy + bazel gazelle-update-repos --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Daisuke Maki * Fix jwk cache docs (#885) * Fix example comment * Upon re-reading, this sentence does not need to exist * autodoc updates (#886) Co-authored-by: lestrrat * Bump actions/setup-go from 3 to 4 (#887) Bumps [actions/setup-go](https://github.com/actions/setup-go) from 3 to 4. - [Release notes](https://github.com/actions/setup-go/releases) - [Commits](https://github.com/actions/setup-go/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/setup-go dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Allow "none" algorithm when signing with explicit option (#890) * Add test case for #888 * Catch the use of "none" when used in conjunction with jws.WithKey * first pass implementing (jwt/jws).Sign that allows alg="none" * regenerate jwt options * appease linter * Check for jws.Sign/Verify * OK to _sign_ using `none`, but no verification * Tweak Changes * typo (#893) * Bump github.com/goccy/go-json from 0.10.1 to 0.10.2 (#892) * Bump github.com/goccy/go-json from 0.10.1 to 0.10.2 Bumps [github.com/goccy/go-json](https://github.com/goccy/go-json) from 0.10.1 to 0.10.2. - [Release notes](https://github.com/goccy/go-json/releases) - [Changelog](https://github.com/goccy/go-json/blob/master/CHANGELOG.md) - [Commits](https://github.com/goccy/go-json/compare/v0.10.1...v0.10.2) --- updated-dependencies: - dependency-name: github.com/goccy/go-json dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * Run make tidy + bazel --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Daisuke Maki * Update Changes * Bump actions/stale from 7 to 8 (#895) Bumps [actions/stale](https://github.com/actions/stale) from 7 to 8. - [Release notes](https://github.com/actions/stale/releases) - [Changelog](https://github.com/actions/stale/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/stale/compare/v7...v8) --- updated-dependencies: - dependency-name: actions/stale dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * tweak labels for dependabot (#899) * Bump golang.org/x/crypto from 0.7.0 to 0.8.0 (#897) * Bump golang.org/x/crypto from 0.7.0 to 0.8.0 Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.7.0 to 0.8.0. - [Release notes](https://github.com/golang/crypto/releases) - [Commits](https://github.com/golang/crypto/compare/v0.7.0...v0.8.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 //:gazelle-update-repos --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Daisuke Maki * Fix typo in "jwt.WithAudience" comment (#908) * Bump github.com/decred/dcrd/dcrec/secp256k1/v4 from 4.1.0 to 4.2.0 (#907) * Bump github.com/decred/dcrd/dcrec/secp256k1/v4 from 4.1.0 to 4.2.0 Bumps [github.com/decred/dcrd/dcrec/secp256k1/v4](https://github.com/decred/dcrd) from 4.1.0 to 4.2.0. - [Release notes](https://github.com/decred/dcrd/releases) - [Changelog](https://github.com/decred/dcrd/blob/master/CHANGES) - [Commits](https://github.com/decred/dcrd/compare/blockchain/v4.1.0...dcrec/secp256k1/v4.2.0) --- updated-dependencies: - dependency-name: github.com/decred/dcrd/dcrec/secp256k1/v4 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * run make tidy + bazel run //:gazelle-update-repos --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Daisuke Maki * Precompute RSA key values so that tests succeed (#913) * Use a symmetric key for example purposes (#914) * autodoc updates (#915) Co-authored-by: lestrrat * Hook in jwa.RegisterXXX functions with jws.Register(Signer|Verifier) (#911) * First pass at connecting jws.Register(Signer|Verifier) with jwa.RegisterXXX * Tweak CI * Tweak docs * fix docs * protect access to signer/verifierDB * Update Changes * Allow use of segmentio/asm/base64 (#916) * Enable segmentio/asm/base64 and some internal API for pluggable base64 * Enable asmbase64 in CI * Add missing commands * Update bazel repos * Mention jwx_asmbase64 (#917) * Update README.md (#918) Fixed typo: convetion -> convention * use proper function name (#921) * Bump golang.org/x/crypto from 0.8.0 to 0.9.0 (#919) * Bump golang.org/x/crypto from 0.8.0 to 0.9.0 Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.8.0 to 0.9.0. - [Commits](https://github.com/golang/crypto/compare/v0.8.0...v0.9.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 gaelle-update-repos --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Daisuke Maki * Bump github.com/cloudflare/circl from 1.1.0 to 1.3.3 in /examples (#923) Bumps [github.com/cloudflare/circl](https://github.com/cloudflare/circl) from 1.1.0 to 1.3.3. - [Release notes](https://github.com/cloudflare/circl/releases) - [Commits](https://github.com/cloudflare/circl/compare/v1.1.0...v1.3.3) --- updated-dependencies: - dependency-name: github.com/cloudflare/circl dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Bump github.com/stretchr/testify from 1.8.2 to 1.8.3 (#927) * Bump github.com/stretchr/testify from 1.8.2 to 1.8.3 Bumps [github.com/stretchr/testify](https://github.com/stretchr/testify) from 1.8.2 to 1.8.3. - [Release notes](https://github.com/stretchr/testify/releases) - [Commits](https://github.com/stretchr/testify/compare/v1.8.2...v1.8.3) --- updated-dependencies: - dependency-name: github.com/stretchr/testify dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * Run gazelle-update-repos --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Daisuke Maki * Implement jwk.SetGlobalFetcher (#929) * Implement SetGlobalFetcher * Avoid using atomic.Bool so that it works on older Gos * Appease GitHub code scanner * Bad ineffectual assignment * tweak docs * oops, wrong issue number * Bump github.com/stretchr/testify from 1.8.3 to 1.8.4 (#931) * Bump github.com/stretchr/testify from 1.8.3 to 1.8.4 Bumps [github.com/stretchr/testify](https://github.com/stretchr/testify) from 1.8.3 to 1.8.4. - [Release notes](https://github.com/stretchr/testify/releases) - [Commits](https://github.com/stretchr/testify/compare/v1.8.3...v1.8.4) --- updated-dependencies: - dependency-name: github.com/stretchr/testify dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * Update bazel repos * use specific tparse * change minimum go version in smoke tet --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Daisuke Maki * Implement jwe.KeyEncrypter and jwe.KeyDecrypter (#925) * Implement jwe.KeyEncrypter and jwe.KeyDecrypter This allows users to specify a key who can encrypt/decrypt by itself, much like the built-in crypto.Signer interface. * Add experimental label to this feature * Update Changes --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Oleksandr Redko Co-authored-by: Mitsuo Heijo <25817501+johejo@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: lestrrat Co-authored-by: Alessandro (Ale) Segala <43508+ItalyPaleAle@users.noreply.github.com> Co-authored-by: wscalf --- .github/dependabot.yml | 14 ++ .github/workflows/ci.yml | 10 +- .github/workflows/smoke.yml | 2 +- .github/workflows/stale.yml | 2 +- Changes | 36 ++++ Makefile | 10 +- README.md | 2 +- bench/comparison/Makefile | 3 + bench/performance/Makefile | 6 + bench/performance/jwt_benchmark_test.go | 6 +- deps.bzl | 40 ++-- docs/20-global-settings.md | 12 ++ examples/go.mod | 2 +- examples/go.sum | 13 +- examples/jwk_example_test.go | 33 ++- go.mod | 7 +- go.sum | 26 ++- internal/base64/asmbase64.go | 39 ++++ internal/base64/base64.go | 97 +++++++-- internal/base64/base64_test.go | 9 - internal/keyconv/keyconv_test.go | 7 + jwa/compression_gen.go | 58 +++-- jwa/content_encryption_gen.go | 66 ++++-- jwa/elliptic_gen.go | 68 ++++-- jwa/key_encryption_gen.go | 88 +++++--- jwa/key_type_gen.go | 62 ++++-- jwa/signature_gen.go | 84 +++++--- jwe/decrypt.go | 11 +- jwe/interface.go | 54 +++++ jwe/internal/keyenc/interface.go | 8 +- jwe/internal/keyenc/keyenc.go | 14 +- jwe/internal/keygen/interface.go | 3 - jwe/internal/keygen/keygen.go | 12 -- jwe/jwe.go | 269 +++++++++++++----------- jwe/jwe_test.go | 44 +++- jwk/README.md | 33 ++- jwk/cache.go | 3 + jwk/fetch.go | 74 ++++++- jwk/jwk.go | 2 +- jwk/jwk_test.go | 46 ++++ jws/jws_test.go | 55 +++++ jws/signer.go | 27 +++ jws/verifier.go | 27 +++ jwt/options.go | 2 +- tools/cmd/genjwa/main.go | 50 ++++- 45 files changed, 1128 insertions(+), 408 deletions(-) create mode 100644 internal/base64/asmbase64.go diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 96bc6b469..32efae2c4 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -10,13 +10,27 @@ updates: schedule: interval: "daily" target-branch: "develop/v2" + labels: + - "go" + - "dependencies" + - "dependabot" - package-ecosystem: "gomod" # See documentation for possible values directory: "/" # Location of package manifests schedule: interval: "daily" target-branch: "develop/v1" + labels: + - "go" + - "dependencies" + - "dependabot" - package-ecosystem: "github-actions" directory: "/" schedule: interval: "daily" target-branch: "develop/v2" + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "daily" + target-branch: "develop/v1" + diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1f59c0326..3311e3944 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,8 +10,8 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - go_tags: [ 'stdlib', 'goccy', 'es256k', 'alltags'] - go: [ '1.19', '1.18', '1.17' ] + go_tags: [ 'stdlib', 'goccy', 'es256k', 'asmbase64', 'alltags'] + go: [ '1.20', '1.19', '1.18'] name: "Test [ Go ${{ matrix.go }} / Tags ${{ matrix.go_tags }} ]" steps: - name: Checkout repository @@ -44,7 +44,7 @@ jobs: - name: Install stringer run: go install golang.org/x/tools/cmd/stringer@latest - name: Install tparse - run: go install github.com/mfridman/tparse@latest + run: go install github.com/mfridman/tparse@v0.12.2 - name: Install jose run: sudo apt-get install -y --no-install-recommends jose - run: make generate @@ -57,7 +57,7 @@ jobs: uses: codecov/codecov-action@v3 with: file: ./coverage.out + - uses: bazelbuild/setup-bazelisk@v2 + - run: bazel run //:gazelle-update-repos - name: Check difference between generation code and commit code run: make check_diffs - - uses: bazelbuild/setup-bazelisk@v2 - - run: bazel build //... diff --git a/.github/workflows/smoke.yml b/.github/workflows/smoke.yml index a7fb00b63..6e4fad8f5 100644 --- a/.github/workflows/smoke.yml +++ b/.github/workflows/smoke.yml @@ -14,7 +14,7 @@ jobs: strategy: matrix: go_tags: [ 'stdlib', 'goccy', 'es256k', 'alltags' ] - go: [ '1.19', '1.18', '1.17' ] + go: [ '1.20', '1.19', '1.18' ] name: "Smoke [ Go ${{ matrix.go }} / Tags ${{ matrix.go_tags }} ]" steps: - name: Checkout repository diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index d79f8e984..4277b638f 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -7,7 +7,7 @@ jobs: stale: runs-on: ubuntu-latest steps: - - uses: actions/stale@v7 + - uses: actions/stale@v8 with: stale-issue-message: 'This issue is stale because it has been open 14 days with no activity. Remove stale label or comment or this will be closed in 7 days.' stale-pr-message: 'This PR is stale because it has been open 14 days with no activity. Remove stale label or comment or this will be closed in 14 days.' diff --git a/Changes b/Changes index e20b75771..9f414a623 100644 --- a/Changes +++ b/Changes @@ -4,6 +4,42 @@ 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.10 - 12 Jun 2023 +[New Features] + * [jwe] (EXPERIMENTAL) Added `jwe.KeyEncrypter` and `jwe.KeyDecrypter` interfaces + that works in similar ways as how `crypto.Signer` works for signature + generation and verification. It can act as the interface for your encryption/decryption + keys that are for example stored in an hardware device. + + This feature is labeled experimental because the API for the above interfaces have not + been battle tested, and may need to changed yet. Please be aware that until the API + is deemed stable, you may have to adapat our code to these possible changes, + _even_ during minor version upgrades of this library. + +[Bug fixes] + * Registering JWS signers/verifiers did not work since v2.0.0, because the + way we handle algorithm names changed in 2aa98ce6884187180a7145b73da78c859dd46c84. + (We previously thought that this would be checked by the example code, but it + apparently failed to flag us properly) + + The logic behind managing the internal database has been fixed, and + `jws.RegisterSigner` and `jws.RegisterVerifier` now properly hooks into the new + `jwa.RegisterSignatureAlgorithm` to automatically register new algorithm names + (#910, #911) +[Miscellaneous] + * Added limited support for github.com/segmentio/asm/base64. Compile your code + with the `jwx_asmbase64` build tag. This feature is EXPERIMENTAL. + + Through limited testing, the use of a faster base64 library provide 1~5% increase + in throughput on average. It might make more difference if the input/output is large. + If you care about this performance improvement, you should probably enable + `goccy` JSON parser as well, by specifying `jwx_goccy,jwx_asmbase64` in your build call. + * Slightly changed the way global variables underneath `jwk.Fetch` are initialized and + configured. `jwk.Fetch` creates an object that spawns wokers to fetch JWKS when it's + first called. + You can now also use `jwk.SetGlobalFetcher()` to set a fetcher object which you can + control. + v2.0.9 - 21 Mar 2023 [Security Fixes] * Updated use of golang.org/x/crypto to v0.7.0 diff --git a/Makefile b/Makefile index f3247b25e..40add854f 100644 --- a/Makefile +++ b/Makefile @@ -25,8 +25,11 @@ test-goccy: test-es256k: $(MAKE) test-cmd TESTOPTS="-tags jwx_es256k" +test-asmbase64: + $(MAKE) test-cmd TESTOPTS="-tags jwx_asmbase64" + test-alltags: - $(MAKE) test-cmd TESTOPTS="-tags jwx_goccy,jwx_es256k" + $(MAKE) test-cmd TESTOPTS="-tags jwx_asmbase64,jwx_goccy,jwx_es256k" cover-cmd: env MODE=cover ./tools/test.sh @@ -43,8 +46,11 @@ cover-goccy: cover-es256k: $(MAKE) cover-cmd TESTOPTS="-tags jwx_es256k" +cover-asmbase64: + $(MAKE) cover-cmd TESTOPTS="-tags jwx_asmbase64" + cover-alltags: - $(MAKE) cover-cmd TESTOPTS="-tags jwx_goccy,jwx_es256k" + $(MAKE) cover-cmd TESTOPTS="-tags jwx_asmbase64,jwx_goccy,jwx_es256k" smoke-cmd: env MODE=short ./tools/test.sh diff --git a/README.md b/README.md index c71145642..2bfb71fbd 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ If you are using this module in your product or your company, please add your p * Supports JWS with unencoded payload (RFC7797) * Supports JWE messages with multiple recipients, both compact and JSON serialization * Most operations work with either JWK or raw keys e.g. *rsa.PrivateKey, *ecdsa.PrivateKey, etc). -* Opinionated, but very uniform API. Everything is symmetric, and follows a standard convetion +* Opinionated, but very uniform API. Everything is symmetric, and follows a standard convention * jws.Parse/Verify/Sign * jwe.Parse/Encrypt/Decrypt * Arguments are organized as explicit required paramters and optional WithXXXX() style options. diff --git a/bench/comparison/Makefile b/bench/comparison/Makefile index daa851db1..32b187de5 100644 --- a/bench/comparison/Makefile +++ b/bench/comparison/Makefile @@ -4,3 +4,6 @@ stdlib: goccy: go test -bench . -benchmem -tags jwx_goccy | tee goccy.txt +asmbase64: + go test -bench . -benchmem -tags jwx_asmbase64 | tee asmbase64.txt + diff --git a/bench/performance/Makefile b/bench/performance/Makefile index 7db68fe8b..1e64e2a77 100644 --- a/bench/performance/Makefile +++ b/bench/performance/Makefile @@ -4,5 +4,11 @@ stdlib: goccy: go test -bench . -benchmem -count 5 -timeout 60m -tags jwx_goccy | tee goccy.txt +asmbase64: + go test -bench . -benchmem -count 5 -timeout 60m -tags jwx_asmbase64 | tee asmbase64.txt + +goccy-asmbase64: + go test -bench . -benchmem -count 5 -timeout 60m -tags jwx_goccy,jwx_asmbase64 | tee goccy-asmbase64.txt + benchstat: benchstat stdlib.txt goccy.txt diff --git a/bench/performance/jwt_benchmark_test.go b/bench/performance/jwt_benchmark_test.go index 6635aeffc..0f9fded36 100644 --- a/bench/performance/jwt_benchmark_test.go +++ b/bench/performance/jwt_benchmark_test.go @@ -111,14 +111,14 @@ func BenchmarkJWT(b *testing.B) { Name: "jwt.ParseString", SkipShort: true, Test: func(b *testing.B) error { - _, err := jwt.ParseString(signedString) + _, err := jwt.ParseString(signedString, jwt.WithVerify(false), jwt.WithValidate(false)) return err }, }, { Name: "jwt.Parse", Test: func(b *testing.B) error { - _, err := jwt.Parse(signedBuf) + _, err := jwt.Parse(signedBuf, jwt.WithVerify(false), jwt.WithValidate(false)) return err }, }, @@ -130,7 +130,7 @@ func BenchmarkJWT(b *testing.B) { return err }, Test: func(b *testing.B) error { - _, err := jwt.ParseReader(signedReader) + _, err := jwt.ParseReader(signedReader, jwt.WithVerify(false), jwt.WithValidate(false)) return err }, }, diff --git a/deps.bzl b/deps.bzl index e9ca97161..4ecdfb6e7 100644 --- a/deps.bzl +++ b/deps.bzl @@ -12,15 +12,15 @@ def go_dependencies(): name = "com_github_decred_dcrd_crypto_blake256", build_file_proto_mode = "disable_global", importpath = "github.com/decred/dcrd/crypto/blake256", - sum = "h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0=", - version = "v1.0.0", + sum = "h1:7PltbUIQB7u/FfZ39+DGa/ShuMyJ5ilcvdfma9wOH6Y=", + version = "v1.0.1", ) go_repository( name = "com_github_decred_dcrd_dcrec_secp256k1_v4", build_file_proto_mode = "disable_global", importpath = "github.com/decred/dcrd/dcrec/secp256k1/v4", - sum = "h1:HbphB4TFFXpv7MNrT52FGrrgVXF1owhMVTHFZIlnvd4=", - version = "v4.1.0", + sum = "h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs=", + version = "v4.2.0", ) go_repository( name = "com_github_goccy_go_json", @@ -73,6 +73,14 @@ def go_dependencies(): sum = "h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=", version = "v1.0.0", ) + go_repository( + name = "com_github_segmentio_asm", + build_file_proto_mode = "disable_global", + importpath = "github.com/segmentio/asm", + sum = "h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys=", + version = "v1.2.0", + ) + go_repository( name = "com_github_stretchr_objx", build_file_proto_mode = "disable_global", @@ -84,8 +92,8 @@ def go_dependencies(): name = "com_github_stretchr_testify", build_file_proto_mode = "disable_global", importpath = "github.com/stretchr/testify", - sum = "h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=", - version = "v1.8.2", + sum = "h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=", + version = "v1.8.4", ) go_repository( name = "com_github_yuin_goldmark", @@ -114,8 +122,8 @@ def go_dependencies(): name = "org_golang_x_crypto", build_file_proto_mode = "disable_global", importpath = "golang.org/x/crypto", - sum = "h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A=", - version = "v0.7.0", + sum = "h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g=", + version = "v0.9.0", ) go_repository( name = "org_golang_x_mod", @@ -129,8 +137,8 @@ def go_dependencies(): name = "org_golang_x_net", build_file_proto_mode = "disable_global", importpath = "golang.org/x/net", - sum = "h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=", - version = "v0.8.0", + sum = "h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=", + version = "v0.10.0", ) go_repository( name = "org_golang_x_sync", @@ -144,23 +152,23 @@ def go_dependencies(): name = "org_golang_x_sys", build_file_proto_mode = "disable_global", importpath = "golang.org/x/sys", - sum = "h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=", - version = "v0.6.0", + sum = "h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=", + version = "v0.8.0", ) go_repository( name = "org_golang_x_term", build_file_proto_mode = "disable_global", importpath = "golang.org/x/term", - sum = "h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw=", - version = "v0.6.0", + sum = "h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols=", + version = "v0.8.0", ) go_repository( name = "org_golang_x_text", build_file_proto_mode = "disable_global", importpath = "golang.org/x/text", - sum = "h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68=", - version = "v0.8.0", + sum = "h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=", + version = "v0.9.0", ) go_repository( name = "org_golang_x_tools", diff --git a/docs/20-global-settings.md b/docs/20-global-settings.md index 717d1bc38..b006222f3 100644 --- a/docs/20-global-settings.md +++ b/docs/20-global-settings.md @@ -29,6 +29,18 @@ Disable the tag if you feel like it's not worth the hassle. And when you *do* enable [github.com/goccy/go-json](https://github.com/goccy/go-json) and you encounter some mysterious error, I also trust that you know to file an issue to [github.com/goccy/go-json](https://github.com/goccy/go-json) and **NOT** to this library. +## Enabling experimental base64 encoder/decoder + +This feature is currently considered experimental. + +Currently you can enable [github.com/segmentio/asm/base64](https://github.com/segmentio/asm/tree/main/base64) by specifying the `jwx_asmbase64` build tag + +```shell +% go build -tags jwx_goccy ... +``` + +In our limited testing, this does not seem to improve performance significantly: presumably the other bottlenecks are more dominant. If you care enough to use this option, you probably wantt o enable `jwx_goccy` build tag as well. + ## Using json.Number If you want to parse numbers in the incoming JSON objects as json.Number diff --git a/examples/go.mod b/examples/go.mod index b0c4e492e..1455d1215 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.1.0 + github.com/cloudflare/circl v1.3.3 github.com/lestrrat-go/jwx/v2 v2.0.8 github.com/stretchr/testify v1.8.2 // indirect golang.org/x/crypto v0.7.0 // indirect diff --git a/examples/go.sum b/examples/go.sum index 4e0f3e686..8265f5934 100644 --- a/examples/go.sum +++ b/examples/go.sum @@ -1,6 +1,6 @@ -github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= -github.com/cloudflare/circl v1.1.0 h1:bZgT/A+cikZnKIwn7xL2OBj012Bmvho/o6RpRvv3GKY= -github.com/cloudflare/circl v1.1.0/go.mod h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtMxxK7fi4I= +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/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= @@ -36,6 +36,7 @@ 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.0.0-20220427172511-eb4f295cb31f/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A= golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= @@ -44,6 +45,7 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 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.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -53,20 +55,23 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/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.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 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.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= 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.6/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.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/examples/jwk_example_test.go b/examples/jwk_example_test.go index 0d0307d92..2eb294c2c 100644 --- a/examples/jwk_example_test.go +++ b/examples/jwk_example_test.go @@ -1,10 +1,7 @@ package examples_test import ( - "bytes" "context" - "crypto/ecdsa" - "crypto/elliptic" "fmt" "log" @@ -69,22 +66,23 @@ func ExampleJWK_Usage() { //nolint:govet func ExampleJWK_MarshalJSON() { - // to get the same values every time, we need to create a static source - // of "randomness" - rdr := bytes.NewReader([]byte("01234567890123456789012345678901234567890123456789ABCDEF")) - raw, err := ecdsa.GenerateKey(elliptic.P384(), rdr) - if err != nil { - fmt.Printf("failed to generate new ECDSA private key: %s\n", err) - return - } + // JWKs that inherently involve randomness such as RSA and EC keys are + // not used in this example, because they may produce different results + // depending on the environment. + // + // (In fact, even if you use a static source of randomness, tests may fail + // because of internal changes in the Go runtime). + + raw := []byte("01234567890123456789012345678901234567890123456789ABCDEF") + // This would create a symmetric key key, err := jwk.FromRaw(raw) if err != nil { - fmt.Printf("failed to create ECDSA key: %s\n", err) + fmt.Printf("failed to create symmetric key: %s\n", err) return } - if _, ok := key.(jwk.ECDSAPrivateKey); !ok { - fmt.Printf("expected jwk.ECDSAPrivateKey, got %T\n", key) + if _, ok := key.(jwk.SymmetricKey); !ok { + fmt.Printf("expected jwk.SymmetricKey, got %T\n", key) return } @@ -99,11 +97,8 @@ func ExampleJWK_MarshalJSON() { // OUTPUT: // { - // "crv": "P-384", - // "d": "ODkwMTIzNDU2Nzg5MDEyMz7deMbyLt8g4cjcxozuIoygLLlAeoQ1AfM9TSvxkFHJ", + // "k": "MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODlBQkNERUY", // "kid": "mykey", - // "kty": "EC", - // "x": "gvvRMqm1w5aHn7sVNA2QUJeOVcedUnmiug6VhU834gzS9k87crVwu9dz7uLOdoQl", - // "y": "7fVF7b6J_6_g6Wu9RuJw8geWxEi5ja9Gp2TSdELm5u2E-M7IF-bsxqcdOj3n1n7N" + // "kty": "oct" // } } diff --git a/go.mod b/go.mod index 6e8b82c0a..6ebd4603a 100644 --- a/go.mod +++ b/go.mod @@ -3,12 +3,13 @@ module github.com/lestrrat-go/jwx/v2 go 1.16 require ( - github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 + 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.1 github.com/lestrrat-go/httprc v1.0.4 github.com/lestrrat-go/iter v1.0.2 github.com/lestrrat-go/option v1.0.1 - github.com/stretchr/testify v1.8.2 - golang.org/x/crypto v0.7.0 + github.com/segmentio/asm v1.2.0 + github.com/stretchr/testify v1.8.4 + golang.org/x/crypto v0.9.0 ) diff --git a/go.sum b/go.sum index 55d4f1da8..d61bcef15 100644 --- a/go.sum +++ b/go.sum @@ -1,9 +1,9 @@ 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= -github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 h1:HbphB4TFFXpv7MNrT52FGrrgVXF1owhMVTHFZIlnvd4= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0/go.mod h1:DZGJHZMqrU4JJqFAWUS2UO1+lbSKsdiOoYi9Zzey7Fc= +github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs= +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= @@ -19,45 +19,49 @@ github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNB github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys= +github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= 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/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.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= -github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +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/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.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A= -golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= +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/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.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= +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= 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= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +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.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.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/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.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= 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.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= 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/internal/base64/asmbase64.go b/internal/base64/asmbase64.go new file mode 100644 index 000000000..b151b229f --- /dev/null +++ b/internal/base64/asmbase64.go @@ -0,0 +1,39 @@ +//go:build jwx_asmbase64 + +package base64 + +import ( + "fmt" + + asmbase64 "github.com/segmentio/asm/base64" +) + +func init() { + SetEncoder(asmbase64.RawURLEncoding) + SetDecoder(asmDecoder{}) +} + +type asmDecoder struct{} + +func (d asmDecoder) Decode(src []byte) ([]byte, error) { + var enc *asmbase64.Encoding + switch Guess(src) { + case Std: + enc = asmbase64.StdEncoding + case RawStd: + enc = asmbase64.RawStdEncoding + case URL: + enc = asmbase64.URLEncoding + case RawURL: + enc = asmbase64.RawURLEncoding + default: + return nil, fmt.Errorf(`invalid encoding`) + } + + dst := make([]byte, enc.DecodedLen(len(src))) + n, err := enc.Decode(dst, src) + if err != nil { + return nil, fmt.Errorf(`failed to decode source: %w`, err) + } + return dst[:n], nil +} diff --git a/internal/base64/base64.go b/internal/base64/base64.go index bc494bcf8..b227bc91d 100644 --- a/internal/base64/base64.go +++ b/internal/base64/base64.go @@ -5,21 +5,57 @@ import ( "encoding/base64" "encoding/binary" "fmt" + "sync" ) -func Encode(src []byte) []byte { - enc := base64.RawURLEncoding - dst := make([]byte, enc.EncodedLen(len(src))) - enc.Encode(dst, src) - return dst +type Decoder interface { + Decode([]byte) ([]byte, error) +} + +type Encoder interface { + Encode([]byte, []byte) + EncodedLen(int) int + EncodeToString([]byte) string +} + +var muEncoder sync.RWMutex +var encoder Encoder = base64.RawURLEncoding +var muDecoder sync.RWMutex +var decoder Decoder = defaultDecoder{} + +func SetEncoder(enc Encoder) { + muEncoder.Lock() + defer muEncoder.Unlock() + encoder = enc +} + +func getEncoder() Encoder { + muEncoder.RLock() + defer muEncoder.RUnlock() + return encoder } -func EncodeToStringStd(src []byte) string { - return base64.StdEncoding.EncodeToString(src) +func SetDecoder(dec Decoder) { + muDecoder.Lock() + defer muDecoder.Unlock() + decoder = dec +} + +func getDecoder() Decoder { + muDecoder.RLock() + defer muDecoder.RUnlock() + return decoder +} + +func Encode(src []byte) []byte { + encoder := getEncoder() + dst := make([]byte, encoder.EncodedLen(len(src))) + encoder.Encode(dst, src) + return dst } func EncodeToString(src []byte) string { - return base64.RawURLEncoding.EncodeToString(src) + return getEncoder().EncodeToString(src) } func EncodeUint64ToString(v uint64) string { @@ -36,20 +72,49 @@ func EncodeUint64ToString(v uint64) string { return EncodeToString(data[i:]) } -func Decode(src []byte) ([]byte, error) { - var enc *base64.Encoding +const ( + InvalidEncoding = iota + Std + URL + RawStd + RawURL +) +func Guess(src []byte) int { var isRaw = !bytes.HasSuffix(src, []byte{'='}) var isURL = !bytes.ContainsAny(src, "+/") switch { case isRaw && isURL: - enc = base64.RawURLEncoding + return RawURL case isURL: - enc = base64.URLEncoding + return URL case isRaw: - enc = base64.RawStdEncoding + return RawStd default: + return Std + } +} + +// defaultDecoder is a Decoder that detects the encoding of the source and +// decodes it accordingly. This shouldn't really be required per the spec, but +// it exist because we have seen in the wild JWTs that are encoded using +// various versions of the base64 encoding. +type defaultDecoder struct{} + +func (defaultDecoder) Decode(src []byte) ([]byte, error) { + var enc *base64.Encoding + + switch Guess(src) { + case RawURL: + enc = base64.RawURLEncoding + case URL: + enc = base64.URLEncoding + case RawStd: + enc = base64.RawStdEncoding + case Std: enc = base64.StdEncoding + default: + return nil, fmt.Errorf(`invalid encoding`) } dst := make([]byte, enc.DecodedLen(len(src))) @@ -60,6 +125,10 @@ func Decode(src []byte) ([]byte, error) { return dst[:n], nil } +func Decode(src []byte) ([]byte, error) { + return getDecoder().Decode(src) +} + func DecodeString(src string) ([]byte, error) { - return Decode([]byte(src)) + return getDecoder().Decode([]byte(src)) } diff --git a/internal/base64/base64_test.go b/internal/base64/base64_test.go index e0e094889..b4214e749 100644 --- a/internal/base64/base64_test.go +++ b/internal/base64/base64_test.go @@ -47,12 +47,3 @@ func TestDecode(t *testing.T) { }) } } -func TestEncodeToStringStd(t *testing.T) { - t.Parallel() - t.Run("Encodes to StdEncoding with padding", func(t *testing.T) { - t.Parallel() - out, err := base64.StdEncoding.DecodeString(EncodeToStringStd([]byte("Hello, World!"))) - assert.NoError(t, err) - assert.NotNil(t, out) - }) -} diff --git a/internal/keyconv/keyconv_test.go b/internal/keyconv/keyconv_test.go index 5e908ca40..abb418fd5 100644 --- a/internal/keyconv/keyconv_test.go +++ b/internal/keyconv/keyconv_test.go @@ -44,7 +44,11 @@ func TestKeyconv(t *testing.T) { if !checker(t, keyconv.RSAPrivateKey(&dst, tc.Src), `keyconv.RSAPrivateKey should succeed`) { return } + if !tc.Error { + // From Go 1.20 on, for purposes of our test, we need the + // precomputed values as well + dst.Precompute() if !assert.Equal(t, key, &dst, `keyconv.RSAPrivateKey should produce same value`) { return } @@ -63,6 +67,9 @@ func TestKeyconv(t *testing.T) { return } if !tc.Error { + // From Go 1.20 on, for purposes of our test, we need the + // precomputed values as well + dst.Precompute() if !assert.Equal(t, key, dst, `keyconv.RSAPrivateKey should produce same value`) { return } diff --git a/jwa/compression_gen.go b/jwa/compression_gen.go index ad27ea3f3..9fb65220d 100644 --- a/jwa/compression_gen.go +++ b/jwa/compression_gen.go @@ -17,25 +17,55 @@ const ( NoCompress CompressionAlgorithm = "" // No compression ) -var allCompressionAlgorithms = map[CompressionAlgorithm]struct{}{ - Deflate: {}, - NoCompress: {}, +var muCompressionAlgorithms sync.RWMutex +var allCompressionAlgorithms map[CompressionAlgorithm]struct{} +var listCompressionAlgorithm []CompressionAlgorithm + +func init() { + muCompressionAlgorithms.Lock() + defer muCompressionAlgorithms.Unlock() + allCompressionAlgorithms = make(map[CompressionAlgorithm]struct{}) + allCompressionAlgorithms[Deflate] = struct{}{} + allCompressionAlgorithms[NoCompress] = struct{}{} + rebuildCompressionAlgorithm() } -var listCompressionAlgorithmOnce sync.Once -var listCompressionAlgorithm []CompressionAlgorithm +// RegisterCompressionAlgorithm registers a new CompressionAlgorithm so that the jwx can properly handle the new value. +// Duplicates will silently be ignored +func RegisterCompressionAlgorithm(v CompressionAlgorithm) { + muCompressionAlgorithms.Lock() + defer muCompressionAlgorithms.Unlock() + if _, ok := allCompressionAlgorithms[v]; !ok { + allCompressionAlgorithms[v] = struct{}{} + rebuildCompressionAlgorithm() + } +} + +// UnregisterCompressionAlgorithm unregisters a CompressionAlgorithm from its known database. +// Non-existentn entries will silently be ignored +func UnregisterCompressionAlgorithm(v CompressionAlgorithm) { + muCompressionAlgorithms.Lock() + defer muCompressionAlgorithms.Unlock() + if _, ok := allCompressionAlgorithms[v]; ok { + delete(allCompressionAlgorithms, v) + rebuildCompressionAlgorithm() + } +} + +func rebuildCompressionAlgorithm() { + listCompressionAlgorithm = make([]CompressionAlgorithm, 0, len(allCompressionAlgorithms)) + for v := range allCompressionAlgorithms { + listCompressionAlgorithm = append(listCompressionAlgorithm, v) + } + sort.Slice(listCompressionAlgorithm, func(i, j int) bool { + return string(listCompressionAlgorithm[i]) < string(listCompressionAlgorithm[j]) + }) +} // CompressionAlgorithms returns a list of all available values for CompressionAlgorithm func CompressionAlgorithms() []CompressionAlgorithm { - listCompressionAlgorithmOnce.Do(func() { - listCompressionAlgorithm = make([]CompressionAlgorithm, 0, len(allCompressionAlgorithms)) - for v := range allCompressionAlgorithms { - listCompressionAlgorithm = append(listCompressionAlgorithm, v) - } - sort.Slice(listCompressionAlgorithm, func(i, j int) bool { - return string(listCompressionAlgorithm[i]) < string(listCompressionAlgorithm[j]) - }) - }) + muCompressionAlgorithms.RLock() + defer muCompressionAlgorithms.RUnlock() return listCompressionAlgorithm } diff --git a/jwa/content_encryption_gen.go b/jwa/content_encryption_gen.go index bc82d5058..115fa18e0 100644 --- a/jwa/content_encryption_gen.go +++ b/jwa/content_encryption_gen.go @@ -21,29 +21,59 @@ const ( A256GCM ContentEncryptionAlgorithm = "A256GCM" // AES-GCM (256) ) -var allContentEncryptionAlgorithms = map[ContentEncryptionAlgorithm]struct{}{ - A128CBC_HS256: {}, - A128GCM: {}, - A192CBC_HS384: {}, - A192GCM: {}, - A256CBC_HS512: {}, - A256GCM: {}, +var muContentEncryptionAlgorithms sync.RWMutex +var allContentEncryptionAlgorithms map[ContentEncryptionAlgorithm]struct{} +var listContentEncryptionAlgorithm []ContentEncryptionAlgorithm + +func init() { + muContentEncryptionAlgorithms.Lock() + defer muContentEncryptionAlgorithms.Unlock() + allContentEncryptionAlgorithms = make(map[ContentEncryptionAlgorithm]struct{}) + allContentEncryptionAlgorithms[A128CBC_HS256] = struct{}{} + allContentEncryptionAlgorithms[A128GCM] = struct{}{} + allContentEncryptionAlgorithms[A192CBC_HS384] = struct{}{} + allContentEncryptionAlgorithms[A192GCM] = struct{}{} + allContentEncryptionAlgorithms[A256CBC_HS512] = struct{}{} + allContentEncryptionAlgorithms[A256GCM] = struct{}{} + rebuildContentEncryptionAlgorithm() } -var listContentEncryptionAlgorithmOnce sync.Once -var listContentEncryptionAlgorithm []ContentEncryptionAlgorithm +// RegisterContentEncryptionAlgorithm registers a new ContentEncryptionAlgorithm so that the jwx can properly handle the new value. +// Duplicates will silently be ignored +func RegisterContentEncryptionAlgorithm(v ContentEncryptionAlgorithm) { + muContentEncryptionAlgorithms.Lock() + defer muContentEncryptionAlgorithms.Unlock() + if _, ok := allContentEncryptionAlgorithms[v]; !ok { + allContentEncryptionAlgorithms[v] = struct{}{} + rebuildContentEncryptionAlgorithm() + } +} + +// UnregisterContentEncryptionAlgorithm unregisters a ContentEncryptionAlgorithm from its known database. +// Non-existentn entries will silently be ignored +func UnregisterContentEncryptionAlgorithm(v ContentEncryptionAlgorithm) { + muContentEncryptionAlgorithms.Lock() + defer muContentEncryptionAlgorithms.Unlock() + if _, ok := allContentEncryptionAlgorithms[v]; ok { + delete(allContentEncryptionAlgorithms, v) + rebuildContentEncryptionAlgorithm() + } +} + +func rebuildContentEncryptionAlgorithm() { + listContentEncryptionAlgorithm = make([]ContentEncryptionAlgorithm, 0, len(allContentEncryptionAlgorithms)) + for v := range allContentEncryptionAlgorithms { + listContentEncryptionAlgorithm = append(listContentEncryptionAlgorithm, v) + } + sort.Slice(listContentEncryptionAlgorithm, func(i, j int) bool { + return string(listContentEncryptionAlgorithm[i]) < string(listContentEncryptionAlgorithm[j]) + }) +} // ContentEncryptionAlgorithms returns a list of all available values for ContentEncryptionAlgorithm func ContentEncryptionAlgorithms() []ContentEncryptionAlgorithm { - listContentEncryptionAlgorithmOnce.Do(func() { - listContentEncryptionAlgorithm = make([]ContentEncryptionAlgorithm, 0, len(allContentEncryptionAlgorithms)) - for v := range allContentEncryptionAlgorithms { - listContentEncryptionAlgorithm = append(listContentEncryptionAlgorithm, v) - } - sort.Slice(listContentEncryptionAlgorithm, func(i, j int) bool { - return string(listContentEncryptionAlgorithm[i]) < string(listContentEncryptionAlgorithm[j]) - }) - }) + muContentEncryptionAlgorithms.RLock() + defer muContentEncryptionAlgorithms.RUnlock() return listContentEncryptionAlgorithm } diff --git a/jwa/elliptic_gen.go b/jwa/elliptic_gen.go index 6e813989e..fbfe466aa 100644 --- a/jwa/elliptic_gen.go +++ b/jwa/elliptic_gen.go @@ -23,30 +23,60 @@ const ( X448 EllipticCurveAlgorithm = "X448" ) -var allEllipticCurveAlgorithms = map[EllipticCurveAlgorithm]struct{}{ - Ed25519: {}, - Ed448: {}, - P256: {}, - P384: {}, - P521: {}, - X25519: {}, - X448: {}, +var muEllipticCurveAlgorithms sync.RWMutex +var allEllipticCurveAlgorithms map[EllipticCurveAlgorithm]struct{} +var listEllipticCurveAlgorithm []EllipticCurveAlgorithm + +func init() { + muEllipticCurveAlgorithms.Lock() + defer muEllipticCurveAlgorithms.Unlock() + allEllipticCurveAlgorithms = make(map[EllipticCurveAlgorithm]struct{}) + allEllipticCurveAlgorithms[Ed25519] = struct{}{} + allEllipticCurveAlgorithms[Ed448] = struct{}{} + allEllipticCurveAlgorithms[P256] = struct{}{} + allEllipticCurveAlgorithms[P384] = struct{}{} + allEllipticCurveAlgorithms[P521] = struct{}{} + allEllipticCurveAlgorithms[X25519] = struct{}{} + allEllipticCurveAlgorithms[X448] = struct{}{} + rebuildEllipticCurveAlgorithm() } -var listEllipticCurveAlgorithmOnce sync.Once -var listEllipticCurveAlgorithm []EllipticCurveAlgorithm +// RegisterEllipticCurveAlgorithm registers a new EllipticCurveAlgorithm so that the jwx can properly handle the new value. +// Duplicates will silently be ignored +func RegisterEllipticCurveAlgorithm(v EllipticCurveAlgorithm) { + muEllipticCurveAlgorithms.Lock() + defer muEllipticCurveAlgorithms.Unlock() + if _, ok := allEllipticCurveAlgorithms[v]; !ok { + allEllipticCurveAlgorithms[v] = struct{}{} + rebuildEllipticCurveAlgorithm() + } +} + +// UnregisterEllipticCurveAlgorithm unregisters a EllipticCurveAlgorithm from its known database. +// Non-existentn entries will silently be ignored +func UnregisterEllipticCurveAlgorithm(v EllipticCurveAlgorithm) { + muEllipticCurveAlgorithms.Lock() + defer muEllipticCurveAlgorithms.Unlock() + if _, ok := allEllipticCurveAlgorithms[v]; ok { + delete(allEllipticCurveAlgorithms, v) + rebuildEllipticCurveAlgorithm() + } +} + +func rebuildEllipticCurveAlgorithm() { + listEllipticCurveAlgorithm = make([]EllipticCurveAlgorithm, 0, len(allEllipticCurveAlgorithms)) + for v := range allEllipticCurveAlgorithms { + listEllipticCurveAlgorithm = append(listEllipticCurveAlgorithm, v) + } + sort.Slice(listEllipticCurveAlgorithm, func(i, j int) bool { + return string(listEllipticCurveAlgorithm[i]) < string(listEllipticCurveAlgorithm[j]) + }) +} // EllipticCurveAlgorithms returns a list of all available values for EllipticCurveAlgorithm func EllipticCurveAlgorithms() []EllipticCurveAlgorithm { - listEllipticCurveAlgorithmOnce.Do(func() { - listEllipticCurveAlgorithm = make([]EllipticCurveAlgorithm, 0, len(allEllipticCurveAlgorithms)) - for v := range allEllipticCurveAlgorithms { - listEllipticCurveAlgorithm = append(listEllipticCurveAlgorithm, v) - } - sort.Slice(listEllipticCurveAlgorithm, func(i, j int) bool { - return string(listEllipticCurveAlgorithm[i]) < string(listEllipticCurveAlgorithm[j]) - }) - }) + muEllipticCurveAlgorithms.RLock() + defer muEllipticCurveAlgorithms.RUnlock() return listEllipticCurveAlgorithm } diff --git a/jwa/key_encryption_gen.go b/jwa/key_encryption_gen.go index f85574ffa..49ed1f678 100644 --- a/jwa/key_encryption_gen.go +++ b/jwa/key_encryption_gen.go @@ -32,40 +32,70 @@ const ( RSA_OAEP_256 KeyEncryptionAlgorithm = "RSA-OAEP-256" // RSA-OAEP-SHA256 ) -var allKeyEncryptionAlgorithms = map[KeyEncryptionAlgorithm]struct{}{ - A128GCMKW: {}, - A128KW: {}, - A192GCMKW: {}, - A192KW: {}, - A256GCMKW: {}, - A256KW: {}, - DIRECT: {}, - ECDH_ES: {}, - ECDH_ES_A128KW: {}, - ECDH_ES_A192KW: {}, - ECDH_ES_A256KW: {}, - PBES2_HS256_A128KW: {}, - PBES2_HS384_A192KW: {}, - PBES2_HS512_A256KW: {}, - RSA1_5: {}, - RSA_OAEP: {}, - RSA_OAEP_256: {}, +var muKeyEncryptionAlgorithms sync.RWMutex +var allKeyEncryptionAlgorithms map[KeyEncryptionAlgorithm]struct{} +var listKeyEncryptionAlgorithm []KeyEncryptionAlgorithm + +func init() { + muKeyEncryptionAlgorithms.Lock() + defer muKeyEncryptionAlgorithms.Unlock() + allKeyEncryptionAlgorithms = make(map[KeyEncryptionAlgorithm]struct{}) + allKeyEncryptionAlgorithms[A128GCMKW] = struct{}{} + allKeyEncryptionAlgorithms[A128KW] = struct{}{} + allKeyEncryptionAlgorithms[A192GCMKW] = struct{}{} + allKeyEncryptionAlgorithms[A192KW] = struct{}{} + allKeyEncryptionAlgorithms[A256GCMKW] = struct{}{} + allKeyEncryptionAlgorithms[A256KW] = struct{}{} + allKeyEncryptionAlgorithms[DIRECT] = struct{}{} + allKeyEncryptionAlgorithms[ECDH_ES] = struct{}{} + allKeyEncryptionAlgorithms[ECDH_ES_A128KW] = struct{}{} + allKeyEncryptionAlgorithms[ECDH_ES_A192KW] = struct{}{} + allKeyEncryptionAlgorithms[ECDH_ES_A256KW] = struct{}{} + allKeyEncryptionAlgorithms[PBES2_HS256_A128KW] = struct{}{} + allKeyEncryptionAlgorithms[PBES2_HS384_A192KW] = struct{}{} + allKeyEncryptionAlgorithms[PBES2_HS512_A256KW] = struct{}{} + allKeyEncryptionAlgorithms[RSA1_5] = struct{}{} + allKeyEncryptionAlgorithms[RSA_OAEP] = struct{}{} + allKeyEncryptionAlgorithms[RSA_OAEP_256] = struct{}{} + rebuildKeyEncryptionAlgorithm() } -var listKeyEncryptionAlgorithmOnce sync.Once -var listKeyEncryptionAlgorithm []KeyEncryptionAlgorithm +// RegisterKeyEncryptionAlgorithm registers a new KeyEncryptionAlgorithm so that the jwx can properly handle the new value. +// Duplicates will silently be ignored +func RegisterKeyEncryptionAlgorithm(v KeyEncryptionAlgorithm) { + muKeyEncryptionAlgorithms.Lock() + defer muKeyEncryptionAlgorithms.Unlock() + if _, ok := allKeyEncryptionAlgorithms[v]; !ok { + allKeyEncryptionAlgorithms[v] = struct{}{} + rebuildKeyEncryptionAlgorithm() + } +} + +// UnregisterKeyEncryptionAlgorithm unregisters a KeyEncryptionAlgorithm from its known database. +// Non-existentn entries will silently be ignored +func UnregisterKeyEncryptionAlgorithm(v KeyEncryptionAlgorithm) { + muKeyEncryptionAlgorithms.Lock() + defer muKeyEncryptionAlgorithms.Unlock() + if _, ok := allKeyEncryptionAlgorithms[v]; ok { + delete(allKeyEncryptionAlgorithms, v) + rebuildKeyEncryptionAlgorithm() + } +} + +func rebuildKeyEncryptionAlgorithm() { + listKeyEncryptionAlgorithm = make([]KeyEncryptionAlgorithm, 0, len(allKeyEncryptionAlgorithms)) + for v := range allKeyEncryptionAlgorithms { + listKeyEncryptionAlgorithm = append(listKeyEncryptionAlgorithm, v) + } + sort.Slice(listKeyEncryptionAlgorithm, func(i, j int) bool { + return string(listKeyEncryptionAlgorithm[i]) < string(listKeyEncryptionAlgorithm[j]) + }) +} // KeyEncryptionAlgorithms returns a list of all available values for KeyEncryptionAlgorithm func KeyEncryptionAlgorithms() []KeyEncryptionAlgorithm { - listKeyEncryptionAlgorithmOnce.Do(func() { - listKeyEncryptionAlgorithm = make([]KeyEncryptionAlgorithm, 0, len(allKeyEncryptionAlgorithms)) - for v := range allKeyEncryptionAlgorithms { - listKeyEncryptionAlgorithm = append(listKeyEncryptionAlgorithm, v) - } - sort.Slice(listKeyEncryptionAlgorithm, func(i, j int) bool { - return string(listKeyEncryptionAlgorithm[i]) < string(listKeyEncryptionAlgorithm[j]) - }) - }) + muKeyEncryptionAlgorithms.RLock() + defer muKeyEncryptionAlgorithms.RUnlock() return listKeyEncryptionAlgorithm } diff --git a/jwa/key_type_gen.go b/jwa/key_type_gen.go index 2b602c67a..e1f9e3896 100644 --- a/jwa/key_type_gen.go +++ b/jwa/key_type_gen.go @@ -20,27 +20,57 @@ const ( RSA KeyType = "RSA" // RSA ) -var allKeyTypes = map[KeyType]struct{}{ - EC: {}, - OKP: {}, - OctetSeq: {}, - RSA: {}, +var muKeyTypes sync.RWMutex +var allKeyTypes map[KeyType]struct{} +var listKeyType []KeyType + +func init() { + muKeyTypes.Lock() + defer muKeyTypes.Unlock() + allKeyTypes = make(map[KeyType]struct{}) + allKeyTypes[EC] = struct{}{} + allKeyTypes[OKP] = struct{}{} + allKeyTypes[OctetSeq] = struct{}{} + allKeyTypes[RSA] = struct{}{} + rebuildKeyType() } -var listKeyTypeOnce sync.Once -var listKeyType []KeyType +// RegisterKeyType registers a new KeyType so that the jwx can properly handle the new value. +// Duplicates will silently be ignored +func RegisterKeyType(v KeyType) { + muKeyTypes.Lock() + defer muKeyTypes.Unlock() + if _, ok := allKeyTypes[v]; !ok { + allKeyTypes[v] = struct{}{} + rebuildKeyType() + } +} + +// UnregisterKeyType unregisters a KeyType from its known database. +// Non-existentn entries will silently be ignored +func UnregisterKeyType(v KeyType) { + muKeyTypes.Lock() + defer muKeyTypes.Unlock() + if _, ok := allKeyTypes[v]; ok { + delete(allKeyTypes, v) + rebuildKeyType() + } +} + +func rebuildKeyType() { + listKeyType = make([]KeyType, 0, len(allKeyTypes)) + for v := range allKeyTypes { + listKeyType = append(listKeyType, v) + } + sort.Slice(listKeyType, func(i, j int) bool { + return string(listKeyType[i]) < string(listKeyType[j]) + }) +} // KeyTypes returns a list of all available values for KeyType func KeyTypes() []KeyType { - listKeyTypeOnce.Do(func() { - listKeyType = make([]KeyType, 0, len(allKeyTypes)) - for v := range allKeyTypes { - listKeyType = append(listKeyType, v) - } - sort.Slice(listKeyType, func(i, j int) bool { - return string(listKeyType[i]) < string(listKeyType[j]) - }) - }) + muKeyTypes.RLock() + defer muKeyTypes.RUnlock() return listKeyType } diff --git a/jwa/signature_gen.go b/jwa/signature_gen.go index bc2cbb91c..eaa2f8662 100644 --- a/jwa/signature_gen.go +++ b/jwa/signature_gen.go @@ -30,38 +30,68 @@ const ( RS512 SignatureAlgorithm = "RS512" // RSASSA-PKCS-v1.5 using SHA-512 ) -var allSignatureAlgorithms = map[SignatureAlgorithm]struct{}{ - ES256: {}, - ES256K: {}, - ES384: {}, - ES512: {}, - EdDSA: {}, - HS256: {}, - HS384: {}, - HS512: {}, - NoSignature: {}, - PS256: {}, - PS384: {}, - PS512: {}, - RS256: {}, - RS384: {}, - RS512: {}, +var muSignatureAlgorithms sync.RWMutex +var allSignatureAlgorithms map[SignatureAlgorithm]struct{} +var listSignatureAlgorithm []SignatureAlgorithm + +func init() { + muSignatureAlgorithms.Lock() + defer muSignatureAlgorithms.Unlock() + allSignatureAlgorithms = make(map[SignatureAlgorithm]struct{}) + allSignatureAlgorithms[ES256] = struct{}{} + allSignatureAlgorithms[ES256K] = struct{}{} + allSignatureAlgorithms[ES384] = struct{}{} + allSignatureAlgorithms[ES512] = struct{}{} + allSignatureAlgorithms[EdDSA] = struct{}{} + allSignatureAlgorithms[HS256] = struct{}{} + allSignatureAlgorithms[HS384] = struct{}{} + allSignatureAlgorithms[HS512] = struct{}{} + allSignatureAlgorithms[NoSignature] = struct{}{} + allSignatureAlgorithms[PS256] = struct{}{} + allSignatureAlgorithms[PS384] = struct{}{} + allSignatureAlgorithms[PS512] = struct{}{} + allSignatureAlgorithms[RS256] = struct{}{} + allSignatureAlgorithms[RS384] = struct{}{} + allSignatureAlgorithms[RS512] = struct{}{} + rebuildSignatureAlgorithm() } -var listSignatureAlgorithmOnce sync.Once -var listSignatureAlgorithm []SignatureAlgorithm +// RegisterSignatureAlgorithm registers a new SignatureAlgorithm so that the jwx can properly handle the new value. +// Duplicates will silently be ignored +func RegisterSignatureAlgorithm(v SignatureAlgorithm) { + muSignatureAlgorithms.Lock() + defer muSignatureAlgorithms.Unlock() + if _, ok := allSignatureAlgorithms[v]; !ok { + allSignatureAlgorithms[v] = struct{}{} + rebuildSignatureAlgorithm() + } +} + +// UnregisterSignatureAlgorithm unregisters a SignatureAlgorithm from its known database. +// Non-existentn entries will silently be ignored +func UnregisterSignatureAlgorithm(v SignatureAlgorithm) { + muSignatureAlgorithms.Lock() + defer muSignatureAlgorithms.Unlock() + if _, ok := allSignatureAlgorithms[v]; ok { + delete(allSignatureAlgorithms, v) + rebuildSignatureAlgorithm() + } +} + +func rebuildSignatureAlgorithm() { + listSignatureAlgorithm = make([]SignatureAlgorithm, 0, len(allSignatureAlgorithms)) + for v := range allSignatureAlgorithms { + listSignatureAlgorithm = append(listSignatureAlgorithm, v) + } + sort.Slice(listSignatureAlgorithm, func(i, j int) bool { + return string(listSignatureAlgorithm[i]) < string(listSignatureAlgorithm[j]) + }) +} // SignatureAlgorithms returns a list of all available values for SignatureAlgorithm func SignatureAlgorithms() []SignatureAlgorithm { - listSignatureAlgorithmOnce.Do(func() { - listSignatureAlgorithm = make([]SignatureAlgorithm, 0, len(allSignatureAlgorithms)) - for v := range allSignatureAlgorithms { - listSignatureAlgorithm = append(listSignatureAlgorithm, v) - } - sort.Slice(listSignatureAlgorithm, func(i, j int) bool { - return string(listSignatureAlgorithm[i]) < string(listSignatureAlgorithm[j]) - }) - }) + muSignatureAlgorithms.RLock() + defer muSignatureAlgorithms.RUnlock() return listSignatureAlgorithm } diff --git a/jwe/decrypt.go b/jwe/decrypt.go index 1988f8095..387d4a999 100644 --- a/jwe/decrypt.go +++ b/jwe/decrypt.go @@ -137,8 +137,8 @@ func (d *decrypter) ContentCipher() (content_crypt.Cipher, error) { return d.cipher, nil } -func (d *decrypter) Decrypt(recipientKey, ciphertext []byte) (plaintext []byte, err error) { - cek, keyerr := d.DecryptKey(recipientKey) +func (d *decrypter) Decrypt(recipient Recipient, ciphertext []byte, msg *Message) (plaintext []byte, err error) { + cek, keyerr := d.DecryptKey(recipient, msg) if keyerr != nil { err = fmt.Errorf(`failed to decrypt key: %w`, keyerr) return @@ -226,7 +226,12 @@ func (d *decrypter) decryptSymmetricKey(recipientKey, cek []byte) ([]byte, error } } -func (d *decrypter) DecryptKey(recipientKey []byte) (cek []byte, err error) { +func (d *decrypter) DecryptKey(recipient Recipient, msg *Message) (cek []byte, err error) { + recipientKey := recipient.EncryptedKey() + if kd, ok := d.privkey.(KeyDecrypter); ok { + return kd.DecryptKey(d.keyalg, recipientKey, recipient, msg) + } + if d.keyalg.IsSymmetric() { var ok bool cek, ok = d.privkey.([]byte) diff --git a/jwe/interface.go b/jwe/interface.go index d1044ce1d..828412f67 100644 --- a/jwe/interface.go +++ b/jwe/interface.go @@ -3,9 +3,63 @@ package jwe import ( "github.com/lestrrat-go/iter/mapiter" "github.com/lestrrat-go/jwx/v2/internal/iter" + "github.com/lestrrat-go/jwx/v2/jwa" "github.com/lestrrat-go/jwx/v2/jwe/internal/keygen" ) +// KeyEncrypter is an interface for object that can encrypt a +// content encryption key. +// +// You can use this in place of a regular key (i.e. in jwe.WithKey()) +// to encrypt the content encryption key in a JWE message without +// having to expose the secret key in memory, for example, when you +// want to use hardware security modules (HSMs) to encrypt the key. +// +// This API is experimental and may change without notice, even +// in minor releases. +type KeyEncrypter interface { + // Algorithm returns the algorithm used to encrypt the key. + Algorithm() jwa.KeyEncryptionAlgorithm + + // EncryptKey encrypts the given content encryption key. + EncryptKey([]byte) ([]byte, error) +} + +// KeyIDer is an interface for things that can return a key ID. +// +// As of this writing, this is solely used to identify KeyEncrypter +// objects that also carry a key ID on its own. +type KeyIDer interface { + KeyID() string +} + +// KeyDecrypter is an interface for objects that can decrypt a content +// encryption key. +// +// You can use this in place of a regular key (i.e. in jwe.WithKey()) +// to decrypt the encrypted key in a JWE message without having to +// expose the secret key in memory, for example, when you want to use +// hardware security modules (HSMs) to decrypt the key. +// +// This API is experimental and may change without notice, even +// in minor releases. +type KeyDecrypter interface { + // Decrypt decrypts the encrypted key of a JWE message. + // + // Make sure you understand how JWE messages are structured. + // + // For example, while in most circumstances a JWE message will only have one recipient, + // a JWE message may contain multiple recipients, each with their own + // encrypted key. This method will be called for each recipient, instead of + // just once for a message. + // + // Also, header values could be found in either protected/unprotected headers + // of a JWE message, as well as in protected/unprotected headers for each recipient. + // When checking a header value, you can decide to use either one, or both, but you + // must be aware that there are multiple places to look for. + DecryptKey(alg jwa.KeyEncryptionAlgorithm, encryptedKey []byte, recipient Recipient, message *Message) ([]byte, error) +} + // Recipient holds the encrypted key and hints to decrypt the key type Recipient interface { Headers() Headers diff --git a/jwe/internal/keyenc/interface.go b/jwe/internal/keyenc/interface.go index 70fe7301e..4457538eb 100644 --- a/jwe/internal/keyenc/interface.go +++ b/jwe/internal/keyenc/interface.go @@ -11,13 +11,7 @@ import ( // Encrypter is an interface for things that can encrypt keys type Encrypter interface { Algorithm() jwa.KeyEncryptionAlgorithm - Encrypt([]byte) (keygen.ByteSource, error) - // KeyID returns the key id for this Encrypter. This exists so that - // you can pass in a Encrypter to MultiEncrypt, you can rest assured - // that the generated key will have the proper key ID. - KeyID() string - - SetKeyID(string) + EncryptKey([]byte) (keygen.ByteSource, error) } // Decrypter is an interface for things that can decrypt keys diff --git a/jwe/internal/keyenc/keyenc.go b/jwe/internal/keyenc/keyenc.go index 3e19e62b0..ce5e657cd 100644 --- a/jwe/internal/keyenc/keyenc.go +++ b/jwe/internal/keyenc/keyenc.go @@ -46,7 +46,7 @@ func (kw *Noop) KeyID() string { return kw.keyID } -func (kw *Noop) Encrypt(cek []byte) (keygen.ByteSource, error) { +func (kw *Noop) EncryptKey(cek []byte) (keygen.ByteSource, error) { return keygen.ByteKey(kw.sharedkey), nil } @@ -88,7 +88,7 @@ func (kw *AES) Decrypt(enckey []byte) ([]byte, error) { } // KeyEncrypt encrypts the given content encryption key -func (kw *AES) Encrypt(cek []byte) (keygen.ByteSource, error) { +func (kw *AES) EncryptKey(cek []byte) (keygen.ByteSource, error) { block, err := aes.NewCipher(kw.sharedkey) if err != nil { return nil, fmt.Errorf(`failed to create cipher from shared key: %w`, err) @@ -119,7 +119,7 @@ func (kw AESGCMEncrypt) KeyID() string { return kw.keyID } -func (kw AESGCMEncrypt) Encrypt(cek []byte) (keygen.ByteSource, error) { +func (kw AESGCMEncrypt) EncryptKey(cek []byte) (keygen.ByteSource, error) { block, err := aes.NewCipher(kw.sharedkey) if err != nil { return nil, fmt.Errorf(`failed to create cipher from shared key: %w`, err) @@ -181,7 +181,7 @@ func (kw PBES2Encrypt) KeyID() string { return kw.keyID } -func (kw PBES2Encrypt) Encrypt(cek []byte) (keygen.ByteSource, error) { +func (kw PBES2Encrypt) EncryptKey(cek []byte) (keygen.ByteSource, error) { count := 10000 salt := make([]byte, kw.keylen) _, err := io.ReadFull(rand.Reader, salt) @@ -245,7 +245,7 @@ func (kw ECDHESEncrypt) KeyID() string { } // KeyEncrypt encrypts the content encryption key using ECDH-ES -func (kw ECDHESEncrypt) Encrypt(cek []byte) (keygen.ByteSource, error) { +func (kw ECDHESEncrypt) EncryptKey(cek []byte) (keygen.ByteSource, error) { kg, err := kw.generator.Generate() if err != nil { return nil, fmt.Errorf(`failed to create key generator: %w`, err) @@ -443,7 +443,7 @@ func (e RSAOAEPEncrypt) KeyID() string { } // KeyEncrypt encrypts the content encryption key using RSA PKCS1v15 -func (e RSAPKCSEncrypt) Encrypt(cek []byte) (keygen.ByteSource, error) { +func (e RSAPKCSEncrypt) EncryptKey(cek []byte) (keygen.ByteSource, error) { if e.alg != jwa.RSA1_5 { return nil, fmt.Errorf("invalid RSA PKCS encrypt algorithm (%s)", e.alg) } @@ -455,7 +455,7 @@ func (e RSAPKCSEncrypt) Encrypt(cek []byte) (keygen.ByteSource, error) { } // KeyEncrypt encrypts the content encryption key using RSA OAEP -func (e RSAOAEPEncrypt) Encrypt(cek []byte) (keygen.ByteSource, error) { +func (e RSAOAEPEncrypt) EncryptKey(cek []byte) (keygen.ByteSource, error) { var hash hash.Hash switch e.alg { case jwa.RSA_OAEP: diff --git a/jwe/internal/keygen/interface.go b/jwe/internal/keygen/interface.go index 10543c056..ac019ddfa 100644 --- a/jwe/internal/keygen/interface.go +++ b/jwe/internal/keygen/interface.go @@ -12,9 +12,6 @@ type Generator interface { Generate() (ByteSource, error) } -// StaticKeyGenerate uses a static byte buffer to provide keys. -type Static []byte - // RandomKeyGenerate generates random keys type Random struct { keysize int diff --git a/jwe/internal/keygen/keygen.go b/jwe/internal/keygen/keygen.go index 0d9c7ece9..150cbf715 100644 --- a/jwe/internal/keygen/keygen.go +++ b/jwe/internal/keygen/keygen.go @@ -22,18 +22,6 @@ func (k ByteKey) Bytes() []byte { return []byte(k) } -// Size returns the size of the key -func (g Static) Size() int { - return len(g) -} - -// Generate returns the key -func (g Static) Generate() (ByteSource, error) { - buf := make([]byte, g.Size()) - copy(buf, g) - return ByteKey(buf), nil -} - // NewRandom creates a new Generator that returns // random bytes func NewRandom(n int) Random { diff --git a/jwe/jwe.go b/jwe/jwe.go index dfd86132e..67b8e97b3 100644 --- a/jwe/jwe.go +++ b/jwe/jwe.go @@ -37,6 +37,22 @@ var _ = fmtMax var registry = json.NewRegistry() +type keyEncrypterWrapper struct { + encrypter KeyEncrypter +} + +func (w *keyEncrypterWrapper) Algorithm() jwa.KeyEncryptionAlgorithm { + return w.encrypter.Algorithm() +} + +func (w *keyEncrypterWrapper) EncryptKey(cek []byte) (keygen.ByteSource, error) { + encrypted, err := w.encrypter.EncryptKey(cek) + if err != nil { + return nil, err + } + return keygen.ByteKey(encrypted), nil +} + type recipientBuilder struct { alg jwa.KeyEncryptionAlgorithm key interface{} @@ -44,11 +60,18 @@ type recipientBuilder struct { } func (b *recipientBuilder) Build(cek []byte, calg jwa.ContentEncryptionAlgorithm, cc *content_crypt.Generic) (Recipient, []byte, error) { - // we need the raw key + var enc keyenc.Encrypter + + // we need the raw key for later use rawKey := b.key var keyID string - if jwkKey, ok := b.key.(jwk.Key); ok { + if ke, ok := b.key.(KeyEncrypter); ok { + enc = &keyEncrypterWrapper{encrypter: ke} + if kider, ok := enc.(KeyIDer); ok { + keyID = kider.KeyID() + } + } else if jwkKey, ok := b.key.(jwk.Key); ok { // Meanwhile, grab the kid as well keyID = jwkKey.KeyID() @@ -60,114 +83,110 @@ func (b *recipientBuilder) Build(cek []byte, calg jwa.ContentEncryptionAlgorithm rawKey = raw } - // First, create a key encryptor - var enc keyenc.Encrypter - switch b.alg { - case jwa.RSA1_5: - var pubkey rsa.PublicKey - if err := keyconv.RSAPublicKey(&pubkey, rawKey); err != nil { - return nil, nil, fmt.Errorf(`failed to generate public key from key (%T): %w`, rawKey, err) - } - - v, err := keyenc.NewRSAPKCSEncrypt(b.alg, &pubkey) - if err != nil { - return nil, nil, fmt.Errorf(`failed to create RSA PKCS encrypter: %w`, err) - } - enc = v - case jwa.RSA_OAEP, jwa.RSA_OAEP_256: - var pubkey rsa.PublicKey - if err := keyconv.RSAPublicKey(&pubkey, rawKey); err != nil { - return nil, nil, fmt.Errorf(`failed to generate public key from key (%T): %w`, rawKey, err) - } - - v, err := keyenc.NewRSAOAEPEncrypt(b.alg, &pubkey) - if err != nil { - return nil, nil, fmt.Errorf(`failed to create RSA OAEP encrypter: %w`, err) - } - enc = v - case jwa.A128KW, jwa.A192KW, jwa.A256KW, - jwa.A128GCMKW, jwa.A192GCMKW, jwa.A256GCMKW, - jwa.PBES2_HS256_A128KW, jwa.PBES2_HS384_A192KW, jwa.PBES2_HS512_A256KW: - sharedkey, ok := rawKey.([]byte) - if !ok { - return nil, nil, fmt.Errorf(`invalid key: []byte required (%T)`, rawKey) - } - - var err error - switch b.alg { - case jwa.A128KW, jwa.A192KW, jwa.A256KW: - enc, err = keyenc.NewAES(b.alg, sharedkey) - case jwa.PBES2_HS256_A128KW, jwa.PBES2_HS384_A192KW, jwa.PBES2_HS512_A256KW: - enc, err = keyenc.NewPBES2Encrypt(b.alg, sharedkey) - default: - enc, err = keyenc.NewAESGCMEncrypt(b.alg, sharedkey) - } - if err != nil { - return nil, nil, fmt.Errorf(`failed to create key wrap encrypter: %w`, err) - } - // NOTE: there was formerly a restriction, introduced - // in PR #26, which disallowed certain key/content - // algorithm combinations. This seemed bogus, and - // interop with the jose tool demonstrates it. - case jwa.ECDH_ES, jwa.ECDH_ES_A128KW, jwa.ECDH_ES_A192KW, jwa.ECDH_ES_A256KW: - var keysize int + if enc == nil { switch b.alg { - case jwa.ECDH_ES: - // https://tools.ietf.org/html/rfc7518#page-15 - // In Direct Key Agreement mode, the output of the Concat KDF MUST be a - // key of the same length as that used by the "enc" algorithm. - keysize = cc.KeySize() - case jwa.ECDH_ES_A128KW: - keysize = 16 - case jwa.ECDH_ES_A192KW: - keysize = 24 - case jwa.ECDH_ES_A256KW: - keysize = 32 - } - - switch key := rawKey.(type) { - case x25519.PublicKey: - var apu, apv []byte - if hdrs := b.headers; hdrs != nil { - apu = hdrs.AgreementPartyUInfo() - apv = hdrs.AgreementPartyVInfo() + case jwa.RSA1_5: + var pubkey rsa.PublicKey + if err := keyconv.RSAPublicKey(&pubkey, rawKey); err != nil { + return nil, nil, fmt.Errorf(`failed to generate public key from key (%T): %w`, rawKey, err) } - v, err := keyenc.NewECDHESEncrypt(b.alg, calg, keysize, rawKey, apu, apv) + v, err := keyenc.NewRSAPKCSEncrypt(b.alg, &pubkey) if err != nil { - return nil, nil, fmt.Errorf(`failed to create ECDHS key wrap encrypter: %w`, err) + return nil, nil, fmt.Errorf(`failed to create RSA PKCS encrypter: %w`, err) } enc = v - default: - var pubkey ecdsa.PublicKey - if err := keyconv.ECDSAPublicKey(&pubkey, rawKey); err != nil { - return nil, nil, fmt.Errorf(`failed to generate public key from key (%T): %w`, key, err) + case jwa.RSA_OAEP, jwa.RSA_OAEP_256: + var pubkey rsa.PublicKey + if err := keyconv.RSAPublicKey(&pubkey, rawKey); err != nil { + return nil, nil, fmt.Errorf(`failed to generate public key from key (%T): %w`, rawKey, err) } - var apu, apv []byte - if hdrs := b.headers; hdrs != nil { - apu = hdrs.AgreementPartyUInfo() - apv = hdrs.AgreementPartyVInfo() + v, err := keyenc.NewRSAOAEPEncrypt(b.alg, &pubkey) + if err != nil { + return nil, nil, fmt.Errorf(`failed to create RSA OAEP encrypter: %w`, err) + } + enc = v + case jwa.A128KW, jwa.A192KW, jwa.A256KW, + jwa.A128GCMKW, jwa.A192GCMKW, jwa.A256GCMKW, + jwa.PBES2_HS256_A128KW, jwa.PBES2_HS384_A192KW, jwa.PBES2_HS512_A256KW: + sharedkey, ok := rawKey.([]byte) + if !ok { + return nil, nil, fmt.Errorf(`invalid key: []byte required (%T)`, rawKey) } - v, err := keyenc.NewECDHESEncrypt(b.alg, calg, keysize, &pubkey, apu, apv) + var err error + switch b.alg { + case jwa.A128KW, jwa.A192KW, jwa.A256KW: + enc, err = keyenc.NewAES(b.alg, sharedkey) + case jwa.PBES2_HS256_A128KW, jwa.PBES2_HS384_A192KW, jwa.PBES2_HS512_A256KW: + enc, err = keyenc.NewPBES2Encrypt(b.alg, sharedkey) + default: + enc, err = keyenc.NewAESGCMEncrypt(b.alg, sharedkey) + } if err != nil { - return nil, nil, fmt.Errorf(`failed to create ECDHS key wrap encrypter: %w`, err) + return nil, nil, fmt.Errorf(`failed to create key wrap encrypter: %w`, err) + } + // NOTE: there was formerly a restriction, introduced + // in PR #26, which disallowed certain key/content + // algorithm combinations. This seemed bogus, and + // interop with the jose tool demonstrates it. + case jwa.ECDH_ES, jwa.ECDH_ES_A128KW, jwa.ECDH_ES_A192KW, jwa.ECDH_ES_A256KW: + var keysize int + switch b.alg { + case jwa.ECDH_ES: + // https://tools.ietf.org/html/rfc7518#page-15 + // In Direct Key Agreement mode, the output of the Concat KDF MUST be a + // key of the same length as that used by the "enc" algorithm. + keysize = cc.KeySize() + case jwa.ECDH_ES_A128KW: + keysize = 16 + case jwa.ECDH_ES_A192KW: + keysize = 24 + case jwa.ECDH_ES_A256KW: + keysize = 32 } - enc = v - } - case jwa.DIRECT: - sharedkey, ok := rawKey.([]byte) - if !ok { - return nil, nil, fmt.Errorf("invalid key: []byte required") - } - enc, _ = keyenc.NewNoop(b.alg, sharedkey) - default: - return nil, nil, fmt.Errorf(`invalid key encryption algorithm (%s)`, b.alg) - } - if keyID != "" { - enc.SetKeyID(keyID) + switch key := rawKey.(type) { + case x25519.PublicKey: + var apu, apv []byte + if hdrs := b.headers; hdrs != nil { + apu = hdrs.AgreementPartyUInfo() + apv = hdrs.AgreementPartyVInfo() + } + + v, err := keyenc.NewECDHESEncrypt(b.alg, calg, keysize, rawKey, apu, apv) + if err != nil { + return nil, nil, fmt.Errorf(`failed to create ECDHS key wrap encrypter: %w`, err) + } + enc = v + default: + var pubkey ecdsa.PublicKey + if err := keyconv.ECDSAPublicKey(&pubkey, rawKey); err != nil { + return nil, nil, fmt.Errorf(`failed to generate public key from key (%T): %w`, key, err) + } + + var apu, apv []byte + if hdrs := b.headers; hdrs != nil { + apu = hdrs.AgreementPartyUInfo() + apv = hdrs.AgreementPartyVInfo() + } + + v, err := keyenc.NewECDHESEncrypt(b.alg, calg, keysize, &pubkey, apu, apv) + if err != nil { + return nil, nil, fmt.Errorf(`failed to create ECDHS key wrap encrypter: %w`, err) + } + enc = v + } + case jwa.DIRECT: + sharedkey, ok := rawKey.([]byte) + if !ok { + return nil, nil, fmt.Errorf("invalid key: []byte required") + } + enc, _ = keyenc.NewNoop(b.alg, sharedkey) + default: + return nil, nil, fmt.Errorf(`invalid key encryption algorithm (%s)`, b.alg) + } } r := NewRecipient() @@ -178,14 +197,15 @@ func (b *recipientBuilder) Build(cek []byte, calg jwa.ContentEncryptionAlgorithm if err := r.Headers().Set(AlgorithmKey, b.alg); err != nil { return nil, nil, fmt.Errorf(`failed to set header: %w`, err) } - if v := enc.KeyID(); v != "" { - if err := r.Headers().Set(KeyIDKey, v); err != nil { + + if keyID != "" { + if err := r.Headers().Set(KeyIDKey, keyID); err != nil { return nil, nil, fmt.Errorf(`failed to set header: %w`, err) } } var rawCEK []byte - enckey, err := enc.Encrypt(cek) + enckey, err := enc.EncryptKey(cek) if err != nil { return nil, nil, fmt.Errorf(`failed to encrypt key: %w`, err) } @@ -233,6 +253,7 @@ func Encrypt(payload []byte, options ...EncryptOption) ([]byte, error) { // default compression is "none" compression := jwa.NoCompress + // default format is compact serialization format := fmtCompact // builds each "recipient" with encrypted_key and headers @@ -532,7 +553,7 @@ func (dctx *decryptCtx) try(ctx context.Context, recipient Recipient, keyUsed in alg := pair.alg.(jwa.KeyEncryptionAlgorithm) key := pair.key - decrypted, err := dctx.decryptKey(ctx, alg, key, recipient) + decrypted, err := dctx.decryptContent(ctx, alg, key, recipient) if err != nil { lastError = err continue @@ -549,7 +570,7 @@ func (dctx *decryptCtx) try(ctx context.Context, recipient Recipient, keyUsed in return nil, fmt.Errorf(`jwe.Decrypt: tried %d keys, but failed to match any of the keys with recipient (last error = %s)`, tried, lastError) } -func (dctx *decryptCtx) decryptKey(ctx context.Context, alg jwa.KeyEncryptionAlgorithm, key interface{}, recipient Recipient) ([]byte, error) { +func (dctx *decryptCtx) decryptContent(ctx context.Context, alg jwa.KeyEncryptionAlgorithm, key interface{}, recipient Recipient) ([]byte, error) { if jwkKey, ok := key.(jwk.Key); ok { var raw interface{} if err := jwkKey.Raw(&raw); err != nil { @@ -610,31 +631,29 @@ func (dctx *decryptCtx) decryptKey(ctx context.Context, alg jwa.KeyEncryptionAlg } case jwa.A128GCMKW, jwa.A192GCMKW, jwa.A256GCMKW: ivB64, ok := h2.Get(InitializationVectorKey) - if !ok { - return nil, fmt.Errorf(`failed to get 'iv' field`) - } - ivB64Str, ok := ivB64.(string) - if !ok { - return nil, fmt.Errorf("unexpected type for 'iv': %T", ivB64) + if ok { + ivB64Str, ok := ivB64.(string) + if !ok { + return nil, fmt.Errorf("unexpected type for 'iv': %T", ivB64) + } + iv, err := base64.DecodeString(ivB64Str) + if err != nil { + return nil, fmt.Errorf(`failed to b64-decode 'iv': %w`, err) + } + dec.KeyInitializationVector(iv) } tagB64, ok := h2.Get(TagKey) - if !ok { - return nil, fmt.Errorf(`failed to get 'tag' field`) - } - tagB64Str, ok := tagB64.(string) - if !ok { - return nil, fmt.Errorf("unexpected type for 'tag': %T", tagB64) - } - iv, err := base64.DecodeString(ivB64Str) - if err != nil { - return nil, fmt.Errorf(`failed to b64-decode 'iv': %w`, err) - } - tag, err := base64.DecodeString(tagB64Str) - if err != nil { - return nil, fmt.Errorf(`failed to b64-decode 'tag': %w`, err) + if ok { + tagB64Str, ok := tagB64.(string) + if !ok { + return nil, fmt.Errorf("unexpected type for 'tag': %T", tagB64) + } + tag, err := base64.DecodeString(tagB64Str) + if err != nil { + return nil, fmt.Errorf(`failed to b64-decode 'tag': %w`, err) + } + dec.KeyTag(tag) } - dec.KeyInitializationVector(iv) - dec.KeyTag(tag) case jwa.PBES2_HS256_A128KW, jwa.PBES2_HS384_A192KW, jwa.PBES2_HS512_A256KW: saltB64, ok := h2.Get(SaltKey) if !ok { @@ -661,7 +680,7 @@ func (dctx *decryptCtx) decryptKey(ctx context.Context, alg jwa.KeyEncryptionAlg dec.KeyCount(int(countFlt)) } - plaintext, err := dec.Decrypt(recipient.EncryptedKey(), dctx.msg.cipherText) + plaintext, err := dec.Decrypt(recipient, dctx.msg.cipherText, dctx.msg) if err != nil { return nil, fmt.Errorf(`jwe.Decrypt: decryption failed: %w`, err) } diff --git a/jwe/jwe_test.go b/jwe/jwe_test.go index d3732ac82..64800de9d 100644 --- a/jwe/jwe_test.go +++ b/jwe/jwe_test.go @@ -1,6 +1,7 @@ package jwe_test import ( + "bytes" "context" "crypto" "crypto/ecdsa" @@ -674,7 +675,6 @@ func TestGHIssue230(t *testing.T) { func TestReadFile(t *testing.T) { const s = `eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkEyNTZHQ00ifQ.OKOawDo13gRp2ojaHV7LFpZcgV7T6DVZKTyKOMTYUmKoTCVJRgckCL9kiMT03JGeipsEdY3mx_etLbbWSrFr05kLzcSr4qKAq7YN7e9jwQRb23nfa6c9d-StnImGyFDbSv04uVuxIp5Zms1gNxKKK2Da14B8S4rzVRltdYwam_lDp5XnZAYpQdb76FdIKLaVmqgfwX7XWRxv2322i-vDxRfqNzo_tETKzpVLzfiwQyeyPGLBIO56YJ7eObdv0je81860ppamavo35UgoRdbYaBcoh9QcfylQr66oc6vFWXRcZ_ZT2LawVCWTIy3brGPi6UklfCpIMfIjf7iGdXKHzg.48V1_ALb6US04U3b.5eym8TW_c8SuK0ltJ3rpYIzOeDQz7TALvtu6UG9oMo4vpzs9tX_EFShS8iB7j6jiSdiwkIr3ajwQzaBtQD_A.XFBoMYUZodetZdvTiFvSkQ` - t.Parallel() f, err := os.CreateTemp("", "test-read-file-*.jwe") if !assert.NoError(t, err, `os.CreateTemp should succeed`) { @@ -841,3 +841,45 @@ func TestGH840(t *testing.T) { _, err = jwe.Encrypt([]byte(payload), jwe.WithKey(jwa.ECDH_ES_A128KW, pubkey)) require.Error(t, err, `jwe.Encrypt should fail (instead of panic)`) } + +type dummyKeyEncrypterDecrypter struct { + key []byte +} + +func (kd *dummyKeyEncrypterDecrypter) DecryptKey(alg jwa.KeyEncryptionAlgorithm, cek []byte, _ jwe.Recipient, _ *jwe.Message) ([]byte, error) { + return bytes.TrimSuffix(cek, kd.key), nil +} + +func (kd *dummyKeyEncrypterDecrypter) Algorithm() jwa.KeyEncryptionAlgorithm { + return jwa.A128GCMKW +} + +func (kd *dummyKeyEncrypterDecrypter) EncryptKey(key []byte) ([]byte, error) { + return append(key, kd.key...), nil +} + +var _ jwe.KeyEncrypter = (*dummyKeyEncrypterDecrypter)(nil) + +func TestGH924(t *testing.T) { + sharedKey := []byte("abra-kadabra") + + ked := &dummyKeyEncrypterDecrypter{key: sharedKey} + + payload := []byte("Lorem Ipsum") + encrypted, err := jwe.Encrypt( + payload, + jwe.WithJSON(), + jwe.WithKey(jwa.A128GCMKW, ked), + jwe.WithContentEncryption(jwa.A128GCM), + ) + require.NoError(t, err, `jwe.Encrypt should succeed`) + + var msg jwe.Message + decrypted, err := jwe.Decrypt( + encrypted, + jwe.WithKey(jwa.A128GCMKW, ked), + jwe.WithMessage(&msg), + ) + require.NoError(t, err, `jwe.Decrypt should succeed`) + require.Equal(t, payload, decrypted, `decrypt messages match`) +} diff --git a/jwk/README.md b/jwk/README.md index ff6b4a4f9..85fb0a4b4 100644 --- a/jwk/README.md +++ b/jwk/README.md @@ -111,10 +111,7 @@ Parse and use a JWK key: package examples_test import ( - "bytes" "context" - "crypto/ecdsa" - "crypto/elliptic" "fmt" "log" @@ -179,22 +176,23 @@ func ExampleJWK_Usage() { //nolint:govet func ExampleJWK_MarshalJSON() { - // to get the same values every time, we need to create a static source - // of "randomness" - rdr := bytes.NewReader([]byte("01234567890123456789012345678901234567890123456789ABCDEF")) - raw, err := ecdsa.GenerateKey(elliptic.P384(), rdr) - if err != nil { - fmt.Printf("failed to generate new ECDSA private key: %s\n", err) - return - } + // JWKs that inherently involve randomness such as RSA and EC keys are + // not used in this example, because they may produce different results + // depending on the environment. + // + // (In fact, even if you use a static source of randomness, tests may fail + // because of internal changes in the Go runtime). + + raw := []byte("01234567890123456789012345678901234567890123456789ABCDEF") + // This would create a symmetric key key, err := jwk.FromRaw(raw) if err != nil { - fmt.Printf("failed to create ECDSA key: %s\n", err) + fmt.Printf("failed to create symmetric key: %s\n", err) return } - if _, ok := key.(jwk.ECDSAPrivateKey); !ok { - fmt.Printf("expected jwk.ECDSAPrivateKey, got %T\n", key) + if _, ok := key.(jwk.SymmetricKey); !ok { + fmt.Printf("expected jwk.SymmetricKey, got %T\n", key) return } @@ -209,12 +207,9 @@ func ExampleJWK_MarshalJSON() { // OUTPUT: // { - // "crv": "P-384", - // "d": "ODkwMTIzNDU2Nzg5MDEyMz7deMbyLt8g4cjcxozuIoygLLlAeoQ1AfM9TSvxkFHJ", + // "k": "MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODlBQkNERUY", // "kid": "mykey", - // "kty": "EC", - // "x": "gvvRMqm1w5aHn7sVNA2QUJeOVcedUnmiug6VhU834gzS9k87crVwu9dz7uLOdoQl", - // "y": "7fVF7b6J_6_g6Wu9RuJw8geWxEi5ja9Gp2TSdELm5u2E-M7IF-bsxqcdOj3n1n7N" + // "kty": "oct" // } } ``` diff --git a/jwk/cache.go b/jwk/cache.go index 2230505d5..5d5b6b90b 100644 --- a/jwk/cache.go +++ b/jwk/cache.go @@ -113,6 +113,9 @@ type jwksTransform struct { var defaultTransform = &jwksTransform{} func (t *jwksTransform) Transform(u string, res *http.Response) (interface{}, error) { + if res.StatusCode != http.StatusOK { + return nil, fmt.Errorf(`failed to process response: non-200 response code %q`, res.Status) + } buf, err := io.ReadAll(res.Body) if err != nil { return nil, fmt.Errorf(`failed to read response body status: %w`, err) diff --git a/jwk/fetch.go b/jwk/fetch.go index daca17734..ddc75cd83 100644 --- a/jwk/fetch.go +++ b/jwk/fetch.go @@ -4,8 +4,11 @@ import ( "context" "fmt" "io" + "math" "os" "strconv" + "sync" + "sync/atomic" "github.com/lestrrat-go/httprc" ) @@ -21,18 +24,65 @@ func (f FetchFunc) Fetch(ctx context.Context, u string, options ...FetchOption) } var globalFetcher httprc.Fetcher +var muGlobalFetcher sync.Mutex +var fetcherChanged uint32 func init() { - var nworkers int - v := os.Getenv(`JWK_FETCHER_WORKER_COUNT`) - if c, err := strconv.ParseInt(v, 10, 64); err == nil { - nworkers = int(c) + atomic.StoreUint32(&fetcherChanged, 1) +} + +func getGlobalFetcher() httprc.Fetcher { + if v := atomic.LoadUint32(&fetcherChanged); v == 0 { + return globalFetcher } - if nworkers < 1 { - nworkers = 3 + + muGlobalFetcher.Lock() + defer muGlobalFetcher.Unlock() + if globalFetcher == nil { + var nworkers int + v := os.Getenv(`JWK_FETCHER_WORKER_COUNT`) + if c, err := strconv.ParseInt(v, 10, 64); err == nil { + if c > math.MaxInt { + nworkers = math.MaxInt + } else { + nworkers = int(c) + } + } + if nworkers < 1 { + nworkers = 3 + } + + globalFetcher = httprc.NewFetcher(context.Background(), httprc.WithFetcherWorkerCount(nworkers)) } - globalFetcher = httprc.NewFetcher(context.Background(), httprc.WithFetcherWorkerCount(nworkers)) + atomic.StoreUint32(&fetcherChanged, 0) + return globalFetcher +} + +// SetGlobalFetcher allows users to specify a custom global fetcher, +// which is used by the `Fetch` function. Assigning `nil` forces the +// the default fetcher to be (re)created when the next call to +// `jwk.Fetch` occurs +// +// You only need to call this function when you want to +// either change the fetching behavior (for example, you want to change +// how the default whitelist is handled), or when you want to control +// the lifetime of the global fetcher, for example for tests +// that require a clean shutdown. +// +// If you do use this function to set a custom fetcher and you +// control its termination, make sure that you call `jwk.SetGlobalFetcher()` +// one more time (possibly with `nil`) to assign a valid fetcher. +// Otherwise, once the fetcher is invalidated, subsequent calls to `jwk.Fetch` +// may hang, causing very hard to debug problems. +// +// If you are sure you no longer need `jwk.Fetch` after terminating the +// fetcher, then you the above caution is not necessary. +func SetGlobalFetcher(f httprc.Fetcher) { + muGlobalFetcher.Lock() + globalFetcher = f + muGlobalFetcher.Unlock() + atomic.StoreUint32(&fetcherChanged, 1) } // Fetch fetches a JWK resource specified by a URL. The url must be @@ -43,6 +93,14 @@ func init() { // contents of the object with the data at the remote resource, // consider using `jwk.Cache`, which automatically refreshes // jwk.Set objects asynchronously. +// +// Please note that underneath the `jwk.Fetch` function, it uses a global +// object that spawns goroutines that are present until the go runtime +// exits. Initially this global variable is uninitialized, but upon +// calling `jwk.Fetch` once, it is initialized and goroutines are spawned. +// If you want to control the lifetime of these goroutines, you can +// call `jwk.SetGlobalFetcher` with a custom fetcher which is tied to +// a `context.Context` object that you can control. func Fetch(ctx context.Context, u string, options ...FetchOption) (Set, error) { var hrfopts []httprc.FetchOption var parseOptions []ParseOption @@ -61,7 +119,7 @@ func Fetch(ctx context.Context, u string, options ...FetchOption) (Set, error) { } } - res, err := globalFetcher.Fetch(ctx, u, hrfopts...) + res, err := getGlobalFetcher().Fetch(ctx, u, hrfopts...) if err != nil { return nil, fmt.Errorf(`failed to fetch %q: %w`, u, err) } diff --git a/jwk/jwk.go b/jwk/jwk.go index 7aced4920..8521ba6e9 100644 --- a/jwk/jwk.go +++ b/jwk/jwk.go @@ -43,7 +43,7 @@ func bigIntToBytes(n *big.Int) ([]byte, error) { // - []byte creates a symmetric key func FromRaw(key interface{}) (Key, error) { if key == nil { - return nil, fmt.Errorf(`jwk.New requires a non-nil key`) + return nil, fmt.Errorf(`jwk.FromRaw requires a non-nil key`) } var ptr interface{} diff --git a/jwk/jwk_test.go b/jwk/jwk_test.go index f5c084c93..19d28dcfd 100644 --- a/jwk/jwk_test.go +++ b/jwk/jwk_test.go @@ -2130,3 +2130,49 @@ func TestECDSAPEM(t *testing.T) { t.Fatal(err) } } + +// This test is commented out because we did not want to include `go.uber.org/goleak` +// in our dependency. We agree that it's important to check for goroutine leaks, +// but 1) this is sort of expected in this library, and 2) we don't believe that +// forcing all of our users to use `go.uber.org/goleak` is prudent. +// +// However, we are leaving this test here so that users can learn how this function +// can be used. This is meant to show you the boilerplate code for when you/ want to make +// absolutely sure that no goroutine is left when you finish your program. +// +// For example, if you are writing an app, you can follow the pattern in the +// test below and stop the goroutines that are started by httprc.NewFetcher +// when your app terminates: +// +// func AppMain() { +// ctx, cancel := context.WithCancel(context.Background()) +// defer cancel() +// +// jwk.SetGlobalFetcher(http.NewFetcher(ctx)) +// // your app code goes here +// } +// +// Then, you can be sure that no goroutines are left when `AppMain()` is done. +// You can also verify this in your test: +// +// func TestApp(t *testing.T) { +// AppMain() +// goleak.VerifyNone(t) +// } +/* +func TestGH928(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + jwk.SetGlobalFetcher(httprc.NewFetcher(ctx)) + + // If you are using a custom fetcher like this in your test and you + // still have more tests to run, you probably want to include the + // following line to restore the default behavior after this test. + // Otherwise, calls to `jwk.Fetch` may hang. + defer jwk.SetGlobalFetcher(nil) // This must be included to restore default behavior + + cancel() // stop fetcher goroutines + + // At this point, not goroutines from httprc.Fetcher should be running + goleak.VerifyNone(t) +} +*/ diff --git a/jws/jws_test.go b/jws/jws_test.go index 06e9eb3b4..53d1803b1 100644 --- a/jws/jws_test.go +++ b/jws/jws_test.go @@ -8,8 +8,10 @@ import ( "crypto/ecdsa" "crypto/ed25519" "crypto/rsa" + "crypto/sha256" "crypto/sha512" "encoding/asn1" + "errors" "fmt" "io" "math/big" @@ -1989,3 +1991,56 @@ func TestGH888(t *testing.T) { require.NoError(t, err, `jws.Verify should succeed`) require.Equal(t, []byte(`foo`), verified) } + +// Some stuff required for testing #910 +// The original code used an external library to sign/verify, but here +// we just use a simple SHA256 digest here so that we don't force +// users to download an optional dependency +type s256SignerVerifier struct{} + +const sha256Algo jwa.SignatureAlgorithm = "SillyTest256" + +func (s256SignerVerifier) Algorithm() jwa.SignatureAlgorithm { + return sha256Algo +} + +func (s256SignerVerifier) Sign(payload []byte, _ interface{}) ([]byte, error) { + h := sha256.Sum256(payload) + return h[:], nil +} + +func (s256SignerVerifier) Verify(payload, signature []byte, _ interface{}) error { + h := sha256.Sum256(payload) + if !bytes.Equal(h[:], signature) { + return errors.New("invalid signature") + } + return nil +} + +func TestGH910(t *testing.T) { + // Note: This has global effect. You can't run this in parallel with other tests + jws.RegisterSigner(sha256Algo, jws.SignerFactoryFn(func() (jws.Signer, error) { + return s256SignerVerifier{}, nil + })) + defer jws.UnregisterSigner(sha256Algo) + + jws.RegisterVerifier(sha256Algo, jws.VerifierFactoryFn(func() (jws.Verifier, error) { + return s256SignerVerifier{}, nil + })) + defer jws.UnregisterVerifier(sha256Algo) + defer jwa.UnregisterSignatureAlgorithm(sha256Algo) + + var sa jwa.SignatureAlgorithm + require.NoError(t, sa.Accept(sha256Algo.String()), `jwa.SignatureAlgorithm.Accept should succeed`) + + // Now that we have established that the signature algorithm works, + // we can proceed with the test + const src = `Lorem Ipsum` + signed, err := jws.Sign([]byte(src), jws.WithKey(sha256Algo, nil)) + require.NoError(t, err, `jws.Sign should succeed`) + + verified, err := jws.Verify(signed, jws.WithKey(sha256Algo, nil)) + require.NoError(t, err, `jws.Verify should succeed`) + + require.Equal(t, src, string(verified), `verified payload should match`) +} diff --git a/jws/signer.go b/jws/signer.go index 279386c5f..44c8bfb76 100644 --- a/jws/signer.go +++ b/jws/signer.go @@ -2,6 +2,7 @@ package jws import ( "fmt" + "sync" "github.com/lestrrat-go/jwx/v2/jwa" ) @@ -15,6 +16,7 @@ func (fn SignerFactoryFn) Create() (Signer, error) { return fn() } +var muSignerDB sync.RWMutex var signerDB map[jwa.SignatureAlgorithm]SignerFactory // RegisterSigner is used to register a factory object that creates @@ -23,8 +25,30 @@ var signerDB map[jwa.SignatureAlgorithm]SignerFactory // For example, if you would like to provide a custom signer for // jwa.EdDSA, use this function to register a `SignerFactory` // (probably in your `init()`) +// +// Unlike the `UnregisterSigner` function, this function automatically +// calls `jwa.RegisterSignatureAlgorithm` to register the algorithm +// in the known algorithms database. func RegisterSigner(alg jwa.SignatureAlgorithm, f SignerFactory) { + jwa.RegisterSignatureAlgorithm(alg) + muSignerDB.Lock() signerDB[alg] = f + muSignerDB.Unlock() +} + +// UnregisterSigner removes the signer factory associated with +// the given algorithm. +// +// Note that when you call this function, the algorithm itself is +// not automatically unregistered from the known algorithms database. +// This is because the algorithm may still be required for verification or +// some other operation (however unlikely, it is still possible). +// Therefore, in order to completely remove the algorithm, you must +// call `jwa.UnregisterSignatureAlgorithm` yourself. +func UnregisterSigner(alg jwa.SignatureAlgorithm) { + muSignerDB.Lock() + delete(signerDB, alg) + muSignerDB.Unlock() } func init() { @@ -61,7 +85,10 @@ func init() { // NewSigner creates a signer that signs payloads using the given signature algorithm. func NewSigner(alg jwa.SignatureAlgorithm) (Signer, error) { + muSignerDB.RLock() f, ok := signerDB[alg] + muSignerDB.RUnlock() + if ok { return f.Create() } diff --git a/jws/verifier.go b/jws/verifier.go index 8093f8795..2dd29c848 100644 --- a/jws/verifier.go +++ b/jws/verifier.go @@ -2,6 +2,7 @@ package jws import ( "fmt" + "sync" "github.com/lestrrat-go/jwx/v2/jwa" ) @@ -15,6 +16,7 @@ func (fn VerifierFactoryFn) Create() (Verifier, error) { return fn() } +var muVerifierDB sync.RWMutex var verifierDB map[jwa.SignatureAlgorithm]VerifierFactory // RegisterVerifier is used to register a factory object that creates @@ -23,8 +25,30 @@ var verifierDB map[jwa.SignatureAlgorithm]VerifierFactory // For example, if you would like to provide a custom verifier for // jwa.EdDSA, use this function to register a `VerifierFactory` // (probably in your `init()`) +// +// Unlike the `UnregisterVerifier` function, this function automatically +// calls `jwa.RegisterSignatureAlgorithm` to register the algorithm +// in the known algorithms database. func RegisterVerifier(alg jwa.SignatureAlgorithm, f VerifierFactory) { + jwa.RegisterSignatureAlgorithm(alg) + muVerifierDB.Lock() verifierDB[alg] = f + muVerifierDB.Unlock() +} + +// UnregisterVerifier removes the signer factory associated with +// the given algorithm. +// +// Note that when you call this function, the algorithm itself is +// not automatically unregistered from the known algorithms database. +// This is because the algorithm may still be required for signing or +// some other operation (however unlikely, it is still possible). +// Therefore, in order to completely remove the algorithm, you must +// call `jwa.UnregisterSignatureAlgorithm` yourself. +func UnregisterVerifier(alg jwa.SignatureAlgorithm) { + muVerifierDB.Lock() + delete(verifierDB, alg) + muVerifierDB.Unlock() } func init() { @@ -61,7 +85,10 @@ func init() { // NewVerifier creates a verifier that signs payloads using the given signature algorithm. func NewVerifier(alg jwa.SignatureAlgorithm) (Verifier, error) { + muVerifierDB.RLock() f, ok := verifierDB[alg] + muVerifierDB.RUnlock() + if ok { return f.Create() } diff --git a/jwt/options.go b/jwt/options.go index b7f78fc84..19e30fc40 100644 --- a/jwt/options.go +++ b/jwt/options.go @@ -187,7 +187,7 @@ func WithJwtID(s string) ValidateOption { // WithAudience specifies that expected audience value. // `Validate()` will return true if one of the values in the `aud` element -// matches this value. If not specified, the value of issuer is not +// matches this value. If not specified, the value of `aud` is not // verified at all. func WithAudience(s string) ValidateOption { return WithValidator(audienceClaimContainsString(s)) diff --git a/tools/cmd/genjwa/main.go b/tools/cmd/genjwa/main.go index fd123c3e8..b7f8a7f66 100644 --- a/tools/cmd/genjwa/main.go +++ b/tools/cmd/genjwa/main.go @@ -400,19 +400,48 @@ func (t typ) Generate() error { } o.L(")") // end const - o.L("var all%[1]ss = map[%[1]s]struct{} {", t.name) + // Register%s and related tools are provided so users can register their own types. + // This triggers some re-building of data structures that are otherwise + // reused for efficiency + o.LL("var mu%[1]ss sync.RWMutex", t.name) + o.L("var all%[1]ss map[%[1]s]struct{}", t.name) + o.L("var list%[1]s []%[1]s", t.name) + + o.LL("func init() {") + o.L("mu%[1]ss.Lock()", t.name) + o.L("defer mu%[1]ss.Unlock()", t.name) + o.L("all%[1]ss = make(map[%[1]s]struct{})", t.name) for _, e := range t.elements { if !e.invalid { - o.L("%s: {},", e.name) + o.L("all%[1]ss[%[2]s] = struct{}{}", t.name, e.name) } } + o.L("rebuild%[1]s()", t.name) o.L("}") - o.LL("var list%sOnce sync.Once", t.name) - o.L("var list%[1]s []%[1]s", t.name) - o.LL("// %[1]ss returns a list of all available values for %[1]s", t.name) - o.L("func %[1]ss() []%[1]s {", t.name) - o.L("list%sOnce.Do(func() {", t.name) + o.LL("// Register%[1]s registers a new %[1]s so that the jwx can properly handle the new value.", t.name) + o.L("// Duplicates will silently be ignored") + o.L("func Register%[1]s(v %[1]s) {", t.name) + o.L("mu%[1]ss.Lock()", t.name) + o.L("defer mu%[1]ss.Unlock()", t.name) + o.L("if _, ok := all%[1]ss[v]; !ok {", t.name) + o.L("all%[1]ss[v] = struct{}{}", t.name) + o.L("rebuild%[1]s()", t.name) + o.L("}") + o.L("}") + + o.LL("// Unregister%[1]s unregisters a %[1]s from its known database.", t.name) + o.L("// Non-existentn entries will silently be ignored") + o.L("func Unregister%[1]s(v %[1]s) {", t.name) + o.L("mu%[1]ss.Lock()", t.name) + o.L("defer mu%[1]ss.Unlock()", t.name) + o.L("if _, ok := all%[1]ss[v]; ok {", t.name) + o.L("delete(all%[1]ss, v)", t.name) + o.L("rebuild%[1]s()", t.name) + o.L("}") + o.L("}") + + o.LL("func rebuild%[1]s() {", t.name) o.L("list%[1]s = make([]%[1]s, 0, len(all%[1]ss))", t.name) o.L("for v := range all%ss {", t.name) o.L("list%[1]s = append(list%[1]s, v)", t.name) @@ -420,7 +449,12 @@ func (t typ) Generate() error { o.L("sort.Slice(list%s, func(i, j int) bool {", t.name) o.L("return string(list%[1]s[i]) < string(list%[1]s[j])", t.name) o.L("})") - o.L("})") + o.L("}") + + o.LL("// %[1]ss returns a list of all available values for %[1]s", t.name) + o.L("func %[1]ss() []%[1]s {", t.name) + o.L("mu%[1]ss.RLock()", t.name) + o.L("defer mu%[1]ss.RUnlock()", t.name) o.L("return list%s", t.name) o.L("}")