pax_global_header00006660000000000000000000000064151506056640014522gustar00rootroot0000000000000052 comment=beb8a9923fc9d9e9f526cb18d44e107d9b30ce5e golang-github-lestrrat-go-jwx-3.0.13/000077500000000000000000000000001515060566400174045ustar00rootroot00000000000000golang-github-lestrrat-go-jwx-3.0.13/.aspect/000077500000000000000000000000001515060566400207415ustar00rootroot00000000000000golang-github-lestrrat-go-jwx-3.0.13/.aspect/bazelrc/000077500000000000000000000000001515060566400223635ustar00rootroot00000000000000golang-github-lestrrat-go-jwx-3.0.13/.aspect/bazelrc/BUILD.bazel000066400000000000000000000002311515060566400242350ustar00rootroot00000000000000load("@aspect_bazel_lib//lib:bazelrc_presets.bzl", "write_aspect_bazelrc_presets") write_aspect_bazelrc_presets(name = "update_aspect_bazelrc_presets") golang-github-lestrrat-go-jwx-3.0.13/.aspect/bazelrc/bazel5.bazelrc000066400000000000000000000004021515060566400251050ustar00rootroot00000000000000# Performance improvement for WORKSPACE evaluation # of slow rulesets, for example rules_k8s has been # observed to take 10 seconds without this flag. # See https://github.com/bazelbuild/bazel/issues/13907 common --incompatible_existing_rules_immutable_view golang-github-lestrrat-go-jwx-3.0.13/.aspect/bazelrc/bazel6.bazelrc000066400000000000000000000066751515060566400251300ustar00rootroot00000000000000# Speed up all builds by not checking if external repository files have been modified. # Docs: https://github.com/bazelbuild/bazel/blob/1af61b21df99edc2fc66939cdf14449c2661f873/src/main/java/com/google/devtools/build/lib/bazel/repository/RepositoryOptions.java#L244 build --noexperimental_check_external_repository_files fetch --noexperimental_check_external_repository_files query --noexperimental_check_external_repository_files # Directories used by sandboxed non-worker execution may be reused to avoid unnecessary setup costs. # Save time on Sandbox creation and deletion when many of the same kind of action run during the # build. # Docs: https://bazel.build/reference/command-line-reference#flag--reuse_sandbox_directories build --reuse_sandbox_directories # Avoid this flag being enabled by remote_download_minimal or remote_download_toplevel # See https://meroton.com/blog/bazel-6-errors-build-without-the-bytes/ build --noexperimental_action_cache_store_output_metadata # Speed up all builds by not checking if output files have been modified. Lets you make changes to # the output tree without triggering a build for local debugging. For example, you can modify # [rules_js](https://github.com/aspect-build/rules_js) 3rd party npm packages in the output tree # when local debugging. # Docs: https://github.com/bazelbuild/bazel/blob/1af61b21df99edc2fc66939cdf14449c2661f873/src/main/java/com/google/devtools/build/lib/pkgcache/PackageOptions.java#L185 # NB: This flag is in bazel6.bazelrc as when used in Bazel 7 is has been observed to break # "build without the bytes" --remote_download_outputs=toplevel. See https://github.com/bazel-contrib/bazel-lib/pull/711 # for more info. build --noexperimental_check_output_files fetch --noexperimental_check_output_files query --noexperimental_check_output_files # Don't apply `--noremote_upload_local_results` and `--noremote_accept_cached` to the disk cache. # If you have both `--noremote_upload_local_results` and `--disk_cache`, then this fixes a bug where # Bazel doesn't write to the local disk cache as it treats as a remote cache. # Docs: https://bazel.build/reference/command-line-reference#flag--incompatible_remote_results_ignore_disk # NB: This flag is in bazel6.bazelrc because it became a no-op in Bazel 7 and has been removed # in Bazel 8. build --incompatible_remote_results_ignore_disk # Propagate tags from a target declaration to the actions' execution requirements. # Ensures that tags applied in your BUILD file, like `tags=["no-remote"]` # get propagated to actions created by the rule. # Without this option, you rely on rules authors to manually check the tags you passed # and apply relevant ones to the actions they create. # See https://github.com/bazelbuild/bazel/issues/8830 for details. # Docs: https://bazel.build/reference/command-line-reference#flag--experimental_allow_tags_propagation build --experimental_allow_tags_propagation fetch --experimental_allow_tags_propagation query --experimental_allow_tags_propagation # Do not build runfiles symlink forests for external repositories under # `.runfiles/wsname/external/repo` (in addition to `.runfiles/repo`). This reduces runfiles & # sandbox creation times & prevents accidentally depending on this feature which may flip to off by # default in the future. Note, some rules may fail under this flag, please file issues with the rule # author. # Docs: https://bazel.build/reference/command-line-reference#flag--legacy_external_runfiles build --nolegacy_external_runfiles golang-github-lestrrat-go-jwx-3.0.13/.aspect/bazelrc/bazel7.bazelrc000066400000000000000000000030571515060566400251200ustar00rootroot00000000000000# Speed up all builds by not checking if external repository files have been modified. # Docs: https://github.com/bazelbuild/bazel/blob/1af61b21df99edc2fc66939cdf14449c2661f873/src/main/java/com/google/devtools/build/lib/bazel/repository/RepositoryOptions.java#L244 common --noexperimental_check_external_repository_files # Don't report when the root module's lower bound for a dependency happens to be less than the resolved version. # This is expected and should NOT prompt an engineer to update our lower bound to match. # WARNING: For repository 'aspect_bazel_lib', the root module requires module version aspect_bazel_lib@1.30.2, # but got aspect_bazel_lib@1.31.2 in the resolved dependency graph. common --check_direct_dependencies=off # Directories used by sandboxed non-worker execution may be reused to avoid unnecessary setup costs. # Save time on Sandbox creation and deletion when many of the same kind of action run during the # build. # Docs: https://bazel.build/reference/command-line-reference#flag--reuse_sandbox_directories build --reuse_sandbox_directories # Do not build runfiles symlink forests for external repositories under # `.runfiles/wsname/external/repo` (in addition to `.runfiles/repo`). This reduces runfiles & # sandbox creation times & prevents accidentally depending on this feature which may flip to off by # default in the future. Note, some rules may fail under this flag, please file issues with the rule # author. # Docs: https://bazel.build/reference/command-line-reference#flag--legacy_external_runfiles build --nolegacy_external_runfiles golang-github-lestrrat-go-jwx-3.0.13/.aspect/bazelrc/ci.bazelrc000066400000000000000000000066061515060566400243320ustar00rootroot00000000000000# Set this flag to enable re-tries of failed tests on CI. # When any test target fails, try one or more times. This applies regardless of whether the "flaky" # tag appears on the target definition. # This is a tradeoff: legitimately failing tests will take longer to report, # but we can paper over flaky tests that pass most of the time. # The alternative is to mark every flaky test with the `flaky = True` attribute, but this requires # the buildcop to make frequent code edits. # Not recommended for local builds so that the flakiness is observed during development and thus # is more likely to get fixed. # Note that when passing after the first attempt, Bazel will give a special "FLAKY" status. # Docs: https://bazel.build/docs/user-manual#flaky-test-attempts test --flaky_test_attempts=2 # Announce all announces command options read from the bazelrc file(s) when starting up at the # beginning of each Bazel invocation. This is very useful on CI to be able to inspect what Bazel rc # settings are being applied on each run. # Docs: https://bazel.build/docs/user-manual#announce-rc build --announce_rc # Add a timestamp to each message generated by Bazel specifying the time at which the message was # displayed. # Docs: https://bazel.build/docs/user-manual#show-timestamps build --show_timestamps # Only show progress every 60 seconds on CI. # We want to find a compromise between printing often enough to show that the build isn't stuck, # but not so often that we produce a long log file that requires a lot of scrolling. # https://bazel.build/reference/command-line-reference#flag--show_progress_rate_limit build --show_progress_rate_limit=60 # Use cursor controls in screen output. # Docs: https://bazel.build/docs/user-manual#curses build --curses=yes # Use colors to highlight output on the screen. Set to `no` if your CI does not display colors. # Docs: https://bazel.build/docs/user-manual#color build --color=yes # The terminal width in columns. Configure this to override the default value based on what your CI system renders. # Docs: https://github.com/bazelbuild/bazel/blob/1af61b21df99edc2fc66939cdf14449c2661f873/src/main/java/com/google/devtools/build/lib/runtime/UiOptions.java#L151 build --terminal_columns=143 ###################################### # Generic remote cache configuration # ###################################### # Only download remote outputs of top level targets to the local machine. # Docs: https://bazel.build/reference/command-line-reference#flag--remote_download_toplevel build --remote_download_toplevel # The maximum amount of time to wait for remote execution and cache calls. # https://bazel.build/reference/command-line-reference#flag--remote_timeout build --remote_timeout=3600 # Upload locally executed action results to the remote cache. # Docs: https://bazel.build/reference/command-line-reference#flag--remote_upload_local_results build --remote_upload_local_results # Fall back to standalone local execution strategy if remote execution fails. If the grpc remote # cache connection fails, it will fail the build, add this so it falls back to the local cache. # Docs: https://bazel.build/reference/command-line-reference#flag--remote_local_fallback build --remote_local_fallback # Fixes builds hanging on CI that get the TCP connection closed without sending RST packets. # Docs: https://bazel.build/reference/command-line-reference#flag--grpc_keepalive_time build --grpc_keepalive_time=30s golang-github-lestrrat-go-jwx-3.0.13/.aspect/bazelrc/convenience.bazelrc000066400000000000000000000030731515060566400262260ustar00rootroot00000000000000# Attempt to build & test every target whose prerequisites were successfully built. # Docs: https://bazel.build/docs/user-manual#keep-going build --keep_going # Output test errors to stderr so users don't have to `cat` or open test failure log files when test # fail. This makes the log noisier in exchange for reducing the time-to-feedback on test failures for # users. # Docs: https://bazel.build/docs/user-manual#test-output test --test_output=errors # Show the output files created by builds that requested more than one target. This helps users # locate the build outputs in more cases # Docs: https://bazel.build/docs/user-manual#show-result build --show_result=20 # Bazel picks up host-OS-specific config lines from bazelrc files. For example, if the host OS is # Linux and you run bazel build, Bazel picks up lines starting with build:linux. Supported OS # identifiers are `linux`, `macos`, `windows`, `freebsd`, and `openbsd`. Enabling this flag is # equivalent to using `--config=linux` on Linux, `--config=windows` on Windows, etc. # Docs: https://bazel.build/reference/command-line-reference#flag--enable_platform_specific_config common --enable_platform_specific_config # Output a heap dump if an OOM is thrown during a Bazel invocation # (including OOMs due to `--experimental_oom_more_eagerly_threshold`). # The dump will be written to `/.heapdump.hprof`. # You may need to configure CI to capture this artifact and upload for later use. # Docs: https://bazel.build/reference/command-line-reference#flag--heap_dump_on_oom common --heap_dump_on_oom golang-github-lestrrat-go-jwx-3.0.13/.aspect/bazelrc/correctness.bazelrc000066400000000000000000000102641515060566400262640ustar00rootroot00000000000000# Do not upload locally executed action results to the remote cache. # This should be the default for local builds so local builds cannot poison the remote cache. # It should be flipped to `--remote_upload_local_results` on CI # by using `--bazelrc=.aspect/bazelrc/ci.bazelrc`. # Docs: https://bazel.build/reference/command-line-reference#flag--remote_upload_local_results build --noremote_upload_local_results # Don't allow network access for build actions in the sandbox. # Ensures that you don't accidentally make non-hermetic actions/tests which depend on remote # services. # Developers should tag targets with `tags=["requires-network"]` to opt-out of the enforcement. # Docs: https://bazel.build/reference/command-line-reference#flag--sandbox_default_allow_network build --sandbox_default_allow_network=false # Warn if a test's timeout is significantly longer than the test's actual execution time. # Bazel's default for test_timeout is medium (5 min), but most tests should instead be short (1 min). # While a test's timeout should be set such that it is not flaky, a test that has a highly # over-generous timeout can hide real problems that crop up unexpectedly. # For instance, a test that normally executes in a minute or two should not have a timeout of # ETERNAL or LONG as these are much, much too generous. # Docs: https://bazel.build/docs/user-manual#test-verbose-timeout-warnings test --test_verbose_timeout_warnings # Allow the Bazel server to check directory sources for changes. Ensures that the Bazel server # notices when a directory changes, if you have a directory listed in the srcs of some target. # Recommended when using # [copy_directory](https://github.com/bazel-contrib/bazel-lib/blob/main/docs/copy_directory.md) and # [rules_js](https://github.com/aspect-build/rules_js) since npm package are source directories # inputs to copy_directory actions. # Docs: https://bazel.build/reference/command-line-reference#flag--host_jvm_args startup --host_jvm_args=-DBAZEL_TRACK_SOURCE_DIRECTORIES=1 # Allow exclusive tests to run in the sandbox. Fixes a bug where Bazel doesn't enable sandboxing for # tests with `tags=["exclusive"]`. # Docs: https://bazel.build/reference/command-line-reference#flag--incompatible_exclusive_test_sandboxed test --incompatible_exclusive_test_sandboxed # Use a static value for `PATH` and does not inherit `LD_LIBRARY_PATH`. Doesn't let environment # variables like `PATH` sneak into the build, which can cause massive cache misses when they change. # Use `--action_env=ENV_VARIABLE` if you want to inherit specific environment variables from the # client, but note that doing so can prevent cross-user caching if a shared cache is used. # Docs: https://bazel.build/reference/command-line-reference#flag--incompatible_strict_action_env build --incompatible_strict_action_env # Do not automatically create `__init__.py` files in the runfiles of Python targets. Fixes the wrong # default that comes from Google's internal monorepo by using `__init__.py` to delimit a Python # package. Precisely, when a `py_binary` or `py_test` target has `legacy_create_init` set to `auto (the # default), it is treated as false if and only if this flag is set. See # https://github.com/bazelbuild/bazel/issues/10076. # Docs: https://bazel.build/reference/command-line-reference#flag--incompatible_default_to_explicit_init_py build --incompatible_default_to_explicit_init_py # Set default value of `allow_empty` to `False` in `glob()`. This prevents a common mistake when # attempting to use `glob()` to match files in a subdirectory that is opaque to the current package # because it contains a BUILD file. See https://github.com/bazelbuild/bazel/issues/8195. # Docs: https://bazel.build/reference/command-line-reference#flag--incompatible_disallow_empty_glob common --incompatible_disallow_empty_glob # Always download coverage files for tests from the remote cache. By default, coverage files are not # downloaded on test result cache hits when --remote_download_minimal is enabled, making it impossible # to generate a full coverage report. # Docs: https://bazel.build/reference/command-line-reference#flag--experimental_fetch_all_coverage_outputs # detching remote cache results test --experimental_fetch_all_coverage_outputs golang-github-lestrrat-go-jwx-3.0.13/.aspect/bazelrc/debug.bazelrc000066400000000000000000000013771515060566400250250ustar00rootroot00000000000000############################################################ # Use `bazel test --config=debug` to enable these settings # ############################################################ # Stream stdout/stderr output from each test in real-time. # Docs: https://bazel.build/docs/user-manual#test-output test:debug --test_output=streamed # Run one test at a time. # Docs: https://bazel.build/reference/command-line-reference#flag--test_strategy test:debug --test_strategy=exclusive # Prevent long running tests from timing out. # Docs: https://bazel.build/docs/user-manual#test-timeout test:debug --test_timeout=9999 # Always run tests even if they have cached results. # Docs: https://bazel.build/docs/user-manual#cache-test-results test:debug --nocache_test_results golang-github-lestrrat-go-jwx-3.0.13/.aspect/bazelrc/java.bazelrc000066400000000000000000000024131515060566400246500ustar00rootroot00000000000000# Aspect recommended Bazel flags when using rules_java and rules_jvm_external # Pin java versions to desired language level # See https://bazel.build/docs/bazel-and-java#java-versions # and https://en.wikipedia.org/wiki/Java_version_history # What version of Java are the source files in this repo? # See https://bazel.build/docs/user-manual#java-language-version common --java_language_version=17 # The Java language version used to build tools that are executed during a build # See https://bazel.build/docs/user-manual#tool-java-language-version common --tool_java_language_version=17 # The version of JVM to use to execute the code and run the tests. # NB: The default value is local_jdk which is non-hermetic. # See https://bazel.build/docs/user-manual#java-runtime-version common --java_runtime_version=remotejdk_17 # The version of JVM used to execute tools that are needed during a build. # See https://bazel.build/docs/user-manual#tool-java-runtime-version common --tool_java_runtime_version=remotejdk_17 # Repository rules, such as rules_jvm_external: put Bazel's JDK on the path. # Avoids non-hermeticity from dependency on a JAVA_HOME pointing at a system JDK # see https://github.com/bazelbuild/rules_jvm_external/issues/445 common --repo_env=JAVA_HOME=../bazel_tools/jdk golang-github-lestrrat-go-jwx-3.0.13/.aspect/bazelrc/javascript.bazelrc000066400000000000000000000014411515060566400260750ustar00rootroot00000000000000# Aspect recommended Bazel flags when using Aspect's JavaScript rules: https://github.com/aspect-build/rules_js # Docs for Node.js flags: https://nodejs.org/en/docs/guides/debugging-getting-started/#command-line-options # Support for debugging Node.js tests. Use bazel run with `--config=debug` to turn on the NodeJS # inspector agent. The node process will break before user code starts and wait for the debugger to # connect. Pass the --inspect-brk option to all tests which enables the node inspector agent. See # https://nodejs.org/de/docs/guides/debugging-getting-started/#command-line-options for more # details. # Docs: https://nodejs.org/en/docs/guides/debugging-getting-started/#command-line-options run:debug -- --node_options=--inspect-brk test:debug --test_env=NODE_OPTIONS=--inspect-brk golang-github-lestrrat-go-jwx-3.0.13/.aspect/bazelrc/performance.bazelrc000066400000000000000000000021051515060566400262260ustar00rootroot00000000000000# Directories used by sandboxed non-worker execution may be reused to avoid unnecessary setup costs. # Save time on Sandbox creation and deletion when many of the same kind of action run during the # build. # No longer experimental in Bazel 6: https://github.com/bazelbuild/bazel/commit/c1a95501a5611878e5cc43a3cc531f2b9e47835b # Docs: https://bazel.build/reference/command-line-reference#flag--reuse_sandbox_directories build --experimental_reuse_sandbox_directories # Avoid creating a runfiles tree for binaries or tests until it is needed. # Docs: https://bazel.build/reference/command-line-reference#flag--build_runfile_links # See https://github.com/bazelbuild/bazel/issues/6627 # # This may break local workflows that `build` a binary target, then run the resulting program # outside of `bazel run`. In those cases, the script will need to call # `bazel build --build_runfile_links //my/binary:target` and then execute the resulting program. build --nobuild_runfile_links # Needed prior to Bazel 8; see # https://github.com/bazelbuild/bazel/issues/20577 coverage --build_runfile_links golang-github-lestrrat-go-jwx-3.0.13/.bazelignore000066400000000000000000000000311515060566400217000ustar00rootroot00000000000000cmd bench examples tools golang-github-lestrrat-go-jwx-3.0.13/.bazelrc000066400000000000000000000000621515060566400210250ustar00rootroot00000000000000import %workspace%/.aspect/bazelrc/bazel7.bazelrc golang-github-lestrrat-go-jwx-3.0.13/.bazelversion000066400000000000000000000000061515060566400221040ustar00rootroot000000000000008.3.1 golang-github-lestrrat-go-jwx-3.0.13/.github/000077500000000000000000000000001515060566400207445ustar00rootroot00000000000000golang-github-lestrrat-go-jwx-3.0.13/.github/CONTRIBUTING.md000066400000000000000000000156421515060566400232050ustar00rootroot00000000000000# CONTRIBUTING ❤❤❤🎉 Thank you for considering to contribute to this project! 🎉❤❤❤ The following is a set of guidelines that we ask you to follow when you contribute to this project. # Index * [tl;dr](#tldr) * [Please Be Nice](#please-be-nice) * [Please Use Correct Medium (GitHub Issues / Discussions)](#please-use-correct-medium-github-issues--discussions) * [Please Include (Pseudo)code for Any Technical Issues](#please-include-pseudocode-for-any-technical-issues) * [Reviewer/Reviewee Guidelines](#reviewerreviewee-guidelines) * [Brown M&M Clause](#brown-mm-clause) * [Pull Requests](#pull-requests) * [Branches](#branches) * [Generated Files](#generated-files) * [Test Cases](#test-cases) # tl;dr * 📕 Please read this Guideline in its entirety once, if at least to check the headings. * 🙋 Please be nice, and please be aware that we are not providing this software as a hobby. * đŸ’Ŧ Open-ended questions and inquiries go to [Discussions](https://github.com/lestrrat-go/jwx/discussions). * đŸ–Ĩī¸ Actionable, specific technical questions go to [Issues](https://github.com/lestrrat-go/jwx/issues). * 📝 Please always include (pseudo)code for any technical questions/issues. * 🔒 Issues, PR, and other posts may be closed or not addressed if you do not follow these guidelines # Please Be Nice [Main source; if wordings differ, the main source supersedes this copy](https://github.com/lestrrat-go/contributions/blob/main/Contributions.md) Please be nice when you contact us. We are very glad that you find this project useful, and we intend to provide software that help you. You do not have to thank us, but please bare in mind that this is an opensource project that is provided **as-is**. This means that we are **NOT** obligated to support you, work for you, do your homework/research for you, or otherwise heed to you needs. We do not owe you one bit of code, or a fix, even if it's a critical one. We write software because we're curious, we fix bugs because we have integrity. But we do not owe you anything. Please do not order us to work for you. We are not your support staff, and we are not here to do your research. We are willing to help, but only as long as you are being nice to us. # Please Read The Examples First [Main source; if wordings differ, the main source supersedes this copy](https://github.com/lestrrat-go/contributions/blob/main/Contributions.md) On most of the projects that we provide, we have example test code available, most likely in the [`examples/`](../examples) directory. Before asking questions or filing issues, please make sure to take a look at the examples. Specifically for Go projects, please first look for files with names `*_example_test.go`, which contain the runnable example code. If the examples do not solve your problems, feel free to proceed with your report. If there are missing examples or inaccuracies, please do not hesitate to contact us. # Please Use Correct Medium (GitHub Issues / Discussions) [Main source; this is a specialized version copied from the main source](https://github.com/lestrrat-go/contributions/blob/main/Contributions.md) This project uses [GitHub Issues](https://github.com/lestrrat-go/jwx/issues) to deal with technical issues including bug reports, proposing new API, and otherwise issues that are directly actionable. Inquiries, questions about the usage, maintenance policies, and other open-ended questions/discussions should be posted to [GitHub Discussions](https://github.com/lestrrat-go/jwx/discussions). # Please Include (Pseudo)code for Any Technical Issues [Main source; if wordings differ, the main source supersedes this copy](https://github.com/lestrrat-go/contributions/blob/main/Contributions.md) Your report should contain clear, concise description of the issue that you are facing. However, at the same time please always include (pseudo)code in report. English may not be your forte, but we all should speak the common language of code. Rather than trying to write an entire essay or beat around the bush, which will more than likely cost both you and the maintainers extra roundtrips to communicate, please use code to describe _exactly_ what you are trying to achieve. Good reports should contain (in order of preference): 1. Complete Go-style test code. 1. Code snippet that clearly shows the intent of your code. 1. Pseudocode that shows how you would want the API to work. As we are dealing with code, ultimately there is no better way to convey what you are trying to do than to provide your code. Please help us help you by providing us with a reproducible code. # Reviewer/Reviewee Guidelines If you are curious about what what gets reviewed and why some decisions are made the way they are, please read [this document](https://github.com/lestrrat-go/contributions/blob/main/Reviews.md) to get some insight into the thinking process. # Brown M&M Clause If you came here from an issue/PR template, please make sure to delete the section on "Contribution Guidelines" from the template. Failure to do so may result in the maintainers assuming that you have not fully read the guidelines. [(Reference)](https://www.insider.com/van-halen-brown-m-ms-contract-2016-9) # Pull Requests ## Branches ### `vXXX` branches Stable releases, such as `v1`, `v2`, etc. Please do not work against these branches. Use the `develop/vXXX` branches instead. ### `develop/vXXX` branches Development occurs on these branches. If you are wishing to make changes against `v2`, work on `develop/v2` branch. When you make a PR, fork this branch, make your changes and create a PR against these development branches. ```mermaid sequenceDiagram autonumber participant v1/v2/.. participant develop/v1/v2/.. participant feature_branch develop/v1/v2/..->>feature_branch: Fork development branch to your feature branch Note over feature_branch: Work on your feature feature_branch->>develop/v1/v2/..: File a PR against the development branch develop/v1/v2/..->>v1/v2/..: Merge changes ``` ## Generated Files All files with file names ending in `_gen.go` are generated by a tool. These files should not be modified directly. Instead, find out the tool that is generating the file by inspecting the file. Usually the tool that generated the file is listed in the comment section at the top of the file. Usually these files are generated based on a rule file (such as a YAML file). When you craft a pull request, you should include both changes to the rule file(s) and the generated file(s). The CI will run `go generate` and make sure that there are no extra `diff`s that have not been committed. ## Test Cases In general any code change must be accompanied with test case. It is obviously very important to test the functionality. But adding test cases also gives you the opportunity to check for yourself how the new code should/can be used in practice. Test cases also act as a great way to communicate any assumptions or requirements that your code needs in order to function properly. golang-github-lestrrat-go-jwx-3.0.13/.github/FUNDING.yml000066400000000000000000000012061515060566400225600ustar00rootroot00000000000000# These are supported funding model platforms github: - lestrrat patreon: # Replace with a single Patreon username open_collective: # Replace with a single Open Collective username ko_fi: # Replace with a single Ko-fi username tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry liberapay: # Replace with a single Liberapay username issuehunt: # Replace with a single IssueHunt username otechie: # Replace with a single Otechie username custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] golang-github-lestrrat-go-jwx-3.0.13/.github/ISSUE_TEMPLATE/000077500000000000000000000000001515060566400231275ustar00rootroot00000000000000golang-github-lestrrat-go-jwx-3.0.13/.github/ISSUE_TEMPLATE/bug_report.md000066400000000000000000000016661515060566400256320ustar00rootroot00000000000000--- name: Bug report about: Create a report to help us improve title: '' labels: '' assignees: '' --- **Contribution Guidelines** Before filing an issue, please read the contents of [CONTRIBUTING.md](https://github.com/lestrrat-go/jwx/blob/v2/.github/CONTRIBUTING.md), and follow its instructions. **Describe the bug** A clear and concise description of what the bug is. Please attach the output of `go version` **To Reproduce / Expected behavior** Please attach a standalone Go test code that shows the problem, and what you expected to happen. If you are asking for an API change or some such which inhibits you from providing a working code, please do your best to come up with a near-valid code. **Additional context** Add any other context or screenshots about the feature request here. Please delete this section if unnecessary. **Sponsors** Are you sponsoring the authors? If so, let us know. Otherwise, please delete this section. golang-github-lestrrat-go-jwx-3.0.13/.github/ISSUE_TEMPLATE/config.yml000066400000000000000000000000341515060566400251140ustar00rootroot00000000000000blank_issues_enabled: false golang-github-lestrrat-go-jwx-3.0.13/.github/ISSUE_TEMPLATE/feature_request.md000066400000000000000000000022211515060566400266510ustar00rootroot00000000000000--- name: Feature request about: Suggest an idea for this project title: '' labels: '' assignees: '' --- **Contribution Guidelines** Before filing an issue, please read the contents of [CONTRIBUTING.md](https://github.com/lestrrat-go/jwx/blob/v2/.github/CONTRIBUTING.md), and follow its instructions. **Abstract** Please describe concisely what you want to accomplish, including prerequisite information. Please remember that if _you_ cannot articulate the problem, we cannot guess what you are thinking. **Describe the proposed solution/change** Please attach a standalone Go test code that shows the problem, and what you expected to happen. If it's a behavior change, please include a failing (or would-be failing) test case. If it's a structural or an API change, we understand that you cannot create a complete compiling code, but please do your best to produce a a near-valid code that shows exactly what you want **Analysis** Please describe alternative solutions that you have considered, and pros/cons between them. **Additional context** Add any other context or screenshots about the feature request here. Please delete this section if unnecessary. golang-github-lestrrat-go-jwx-3.0.13/.github/ISSUE_TEMPLATE/others.md000066400000000000000000000004461515060566400247610ustar00rootroot00000000000000--- name: 'Other Issues' about: 'Other types of issues' title: '' labels: '' assignees: '' --- **Contribution Guidelines** Before filing an issue, please read the contents of [CONTRIBUTING.md](https://github.com/lestrrat-go/jwx/blob/v2/.github/CONTRIBUTING.md), and follow its instructions. golang-github-lestrrat-go-jwx-3.0.13/.github/auto-assign-pr.yml000066400000000000000000000000771515060566400243440ustar00rootroot00000000000000addReviewers: true addAssignees: false reviewers: - lestrrat golang-github-lestrrat-go-jwx-3.0.13/.github/dependabot.yml000066400000000000000000000012211515060566400235700ustar00rootroot00000000000000version: 2 updates: - package-ecosystem: "gomod" directory: "/" schedule: interval: "daily" target-branch: "develop/v3" labels: - "go" - "dependencies" - "dependabot" - package-ecosystem: "gomod" directory: "/" schedule: interval: "daily" target-branch: "develop/v2" labels: - "go" - "dependencies" - "dependabot" - package-ecosystem: "github-actions" directory: "/" schedule: interval: "daily" target-branch: "develop/v3" - package-ecosystem: "github-actions" directory: "/" schedule: interval: "daily" target-branch: "develop/v2" golang-github-lestrrat-go-jwx-3.0.13/.github/workflows/000077500000000000000000000000001515060566400230015ustar00rootroot00000000000000golang-github-lestrrat-go-jwx-3.0.13/.github/workflows/assign-issue.yml000066400000000000000000000004271515060566400261410ustar00rootroot00000000000000name: Assign Issue on: issues: types: [opened] jobs: auto-assign: runs-on: ubuntu-latest steps: - name: 'Auto-assign issue' uses: pozil/auto-assign-issue@39c06395cbac76e79afc4ad4e5c5c6db6ecfdd2e # v2.2.0 with: assignees: lestrrat golang-github-lestrrat-go-jwx-3.0.13/.github/workflows/assign-pr.yml000066400000000000000000000004561515060566400254340ustar00rootroot00000000000000name: 'Auto Assign' on: pull_request: types: [opened, ready_for_review] jobs: add-reviews: runs-on: ubuntu-latest steps: - uses: kentaro-m/auto-assign-action@f4648c0a9fdb753479e9e75fc251f507ce17bb7e # v2.0.0 with: configuration-path: .github/auto-assign-pr.yml golang-github-lestrrat-go-jwx-3.0.13/.github/workflows/autodoc.yml000066400000000000000000000010631515060566400251620ustar00rootroot00000000000000name: Auto-Doc on: pull_request: branches: - develop/v2 - develop/v3 types: - closed jobs: autodoc: runs-on: ubuntu-latest name: "Run commands to generate documentation" if: github.event.pull_request.merged == true steps: - name: Checkout repository uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - name: Process markdown files run: | find . -name '*.md' | xargs perl tools/autodoc.pl env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} golang-github-lestrrat-go-jwx-3.0.13/.github/workflows/benchmark.yml000066400000000000000000000023611515060566400254600ustar00rootroot00000000000000name: Benchmark on: schedule: - cron: '0 5 * * 1' workflow_dispatch: {} jobs: build: runs-on: ubuntu-latest strategy: matrix: go: [ '1.25', '1.24' ] name: "Test [ Go ${{ matrix.go }} / JSON Backend ${{ matrix.json_backend }} ]" steps: - name: Checkout repository uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - name: Cache Go modules uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1 with: path: | ~/go/pkg/mod ~/.cache/go-build key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} restore-keys: | ${{ runner.os }}-go- - name: Install Go stable version uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6.1.0 with: go-version: ${{ matrix.go }} check-latest: true - name: Install benchstat run: | go install golang.org/x/perf/cmd/benchstat@latest - name: Benchmark (comparison) run: | cd bench/comparison && make stdlib && make goccy - name: Benchmark (performance) run: | cd bench/performance && make stdlib && make goccy && make benchstat golang-github-lestrrat-go-jwx-3.0.13/.github/workflows/ci.yml000066400000000000000000000027611515060566400241250ustar00rootroot00000000000000name: CI on: pull_request: branches: - v* - develop/* jobs: build: runs-on: ubuntu-latest strategy: matrix: go_tags: [ 'stdlib', 'goccy', 'es256k', 'secp256k1-pem', 'asmbase64', 'alltags'] go: [ '1.25', '1.24' ] name: "Test [ Go ${{ matrix.go }} / Tags ${{ matrix.go_tags }} ]" steps: - name: Checkout repository uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - name: Cache Go modules uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1 with: path: | ~/go/pkg/mod ~/.cache/go-build ~/.cache/bazel key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} restore-keys: | ${{ runner.os }}-go- - name: Install Go stable version uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6.1.0 with: go-version: ${{ matrix.go }} check-latest: true - name: Install stringer run: go install golang.org/x/tools/cmd/stringer@latest - name: Install jose run: sudo apt-get install -y --no-install-recommends jose - run: make generate - name: make tidy run: make tidy - name: Test with coverage run: make cover-${{ matrix.go_tags }} - uses: bazelbuild/setup-bazelisk@v3 - run: bazel mod tidy - name: Check difference between generation code and commit code run: make check_diffs golang-github-lestrrat-go-jwx-3.0.13/.github/workflows/codeql.yml000066400000000000000000000057261515060566400250050ustar00rootroot00000000000000# For most projects, this workflow file will not need changing; you simply need # to commit it to your repository. # # You may wish to alter this file to override the set of languages analyzed, # or to provide custom queries or build logic. # # ******** NOTE ******** # We have attempted to detect the languages in your repository. Please check # the `language` matrix defined below to confirm you have the correct set of # supported CodeQL languages. # name: "CodeQL" on: push: branches: [ develop/*, v* ] pull_request: # The branches below must be a subset of the branches above branches: [ "develop/v3", "develop/v2", "develop/v1" ] schedule: - cron: '40 13 * * 5' jobs: analyze: name: Analyze runs-on: ubuntu-latest permissions: actions: read contents: read security-events: write strategy: fail-fast: false matrix: language: [ 'go' ] # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] # Use only 'java' to analyze code written in Java, Kotlin or both # Use only 'javascript' to analyze code written in JavaScript, TypeScript or both # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support steps: - name: Checkout repository uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL uses: github/codeql-action/init@v4 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. # By default, queries listed here will override any specified in a config file. # Prefix the list here with "+" to use these queries and those in the config file. # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs # queries: security-extended,security-and-quality # Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild uses: github/codeql-action/autobuild@v4 # â„šī¸ Command-line programs to run using the OS shell. # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun # If the Autobuild fails above, remove it and uncomment the following three lines. # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. # - run: | # echo "Run, Build Application using script" # ./location_of_script_within_repo/buildscript.sh - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v4 with: category: "/language:${{matrix.language}}" golang-github-lestrrat-go-jwx-3.0.13/.github/workflows/dependabot.yml000066400000000000000000000017021515060566400256310ustar00rootroot00000000000000name: merge dependabot on: pull_request: types: [labeled] permissions: contents: write pull-requests: write jobs: merge: if: ${{github.event.label.name == 'dependabot'}} runs-on: ubuntu-latest steps: - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - run: | make tidy - run: | make test - run: | bazel mod tidy - run: | bazel build //... - run: | git config --local user.name 'Daisuke Maki' git config --local user.email '41898282+github-actions[bot]@users.noreply.github.com' git add . git commit -m "Run tidy / bazel+gazelle" git push origin "HEAD:${GITHUB_HEAD_REF}" gh pr review --approve "$PR_URL" gh pr merge --auto --merge "$PR_URL" env: PR_URL: ${{github.event.pull_request.html_url}} GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} golang-github-lestrrat-go-jwx-3.0.13/.github/workflows/lint.yml000066400000000000000000000007661515060566400245030ustar00rootroot00000000000000name: lint on: [push] jobs: golangci: name: lint runs-on: ubuntu-latest steps: - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6.1.0 with: go-version-file: "go.mod" - uses: golangci/golangci-lint-action@1e7e51e771db61008b38414a730f564565cf7c20 # v9.2.0 with: version: v2.7.2 - name: Run go vet run: | go vet ./... golang-github-lestrrat-go-jwx-3.0.13/.github/workflows/smoke.yml000066400000000000000000000035451515060566400246510ustar00rootroot00000000000000# Smoke tests only run on non-master branches. Smoke tests cut # some corners by running selected tests in parallel (to shave off # some execution time) # Once a pull request is merged to master, workflows/ci.yml is run name: Smoke Tests on: push: branches-ignore: - main jobs: build: runs-on: ubuntu-latest strategy: matrix: go_tags: [ 'stdlib', 'goccy', 'es256k', 'alltags' ] go: [ '1.25', '1.24' ] name: "Smoke [ Go ${{ matrix.go }} / Tags ${{ matrix.go_tags }} ]" steps: - name: Checkout repository uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - name: Check documentation generator run: | find . -name '*.md' | xargs env AUTODOC_DRYRUN=1 perl tools/autodoc.pl - name: Cache Go modules uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1 with: path: | ~/go/pkg/mod ~/.cache/go-build ~/.cache/bazel key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} restore-keys: | ${{ runner.os }}-go- - name: Install Go stable version uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6.1.0 with: go-version: ${{ matrix.go }} check-latest: true - name: Install stringer run: go install golang.org/x/tools/cmd/stringer@latest - name: Install jose run: sudo apt-get install -y --no-install-recommends jose - run: make generate - name: Check difference between generation code and commit code run: make check_diffs - name: make tidy run: make tidy - name: Run smoke tests run: make smoke-${{ matrix.go_tags }} - uses: bazelbuild/setup-bazelisk@b39c379c82683a5f25d34f0d062761f62693e0b2 # v3.0.0 - run: bazel build //... golang-github-lestrrat-go-jwx-3.0.13/.github/workflows/stale.yml000066400000000000000000000025471515060566400246440ustar00rootroot00000000000000name: 'Close stale issues and PRs' on: schedule: - cron: '30 1 * * *' jobs: stale: runs-on: ubuntu-latest steps: - uses: actions/stale@997185467fa4f803885201cee163a9f38240193d # v10.1.1 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.' close-issue-message: 'This issue was closed because it has been stalled for 7 days with no activity. This does not mean your issue is rejected, but rather it is done to hide it from the view of the maintains for the time being. Feel free to reopen if you have new comments' close-pr-message: 'This PR was closed because it has been stalled for 14 days with no activity. This does not mean your PR is rejected, but rather it is done to hide it from the view of the maintainers for the time being. Feel free to reopen if you have new comments or changes that you would like to include. ' days-before-issue-stale: 14 days-before-pr-stale: 14 days-before-issue-close: 7 days-before-pr-close: 7 exempt-issue-labels: long-term exempt-pr-labels: long-term golang-github-lestrrat-go-jwx-3.0.13/.gitignore000066400000000000000000000006241515060566400213760ustar00rootroot00000000000000# Compiled Object files, Static and Dynamic libs (Shared Objects) *.o *.a *.so # Folders _obj _test # Architecture specific extensions/prefixes *.[568vq] [568vq].out *.cgo1.go *.cgo2.c _cgo_defun.c _cgo_gotypes.go _cgo_export.* _testmain.go *.exe *.test *.prof # IDE .idea .vscode .DS_Store *~ coverage.out # I redirect my test output to files named "out" way too often out cmd/jwx/jwx bazel-* golang-github-lestrrat-go-jwx-3.0.13/.golangci.yml000066400000000000000000000047361515060566400220020ustar00rootroot00000000000000version: "2" linters: default: all disable: - cyclop - depguard - dupl - err113 - errorlint - exhaustive - funcorder - funlen - gochecknoglobals - gochecknoinits - gocognit - gocritic - gocyclo - godot - godox - gosec - gosmopolitan - govet - inamedparam - ireturn - lll - maintidx - makezero - mnd - nakedret - nestif - nlreturn - noinlineerr - nonamedreturns - paralleltest - perfsprint - staticcheck - recvcheck - tagliatelle - testifylint - testpackage - thelper - varnamelen - wrapcheck - wsl - wsl_v5 settings: govet: disable: - shadow - fieldalignment enable-all: true exclusions: generated: lax presets: - comments - common-false-positives - legacy - std-error-handling rules: - linters: - staticcheck path: /*.go text: 'ST1003: should not use underscores in package names' - linters: - revive path: /*.go text: don't use an underscore in package name - linters: - staticcheck text: SA1019 - linters: - contextcheck - exhaustruct path: /*.go - linters: - errcheck path: /main.go - linters: - errcheck path: internal/codegen/codegen.go - linters: - errcheck - errchkjson - forcetypeassert path: internal/jwxtest/jwxtest.go - linters: - errcheck - errchkjson - forcetypeassert path: /*_test.go - linters: - forbidigo path: /*_example_test.go - linters: - forbidigo path: cmd/jwx/jwx.go - linters: - revive path: /*_test.go text: 'var-naming: ' - linters: - revive path: internal/tokens/jwe_tokens.go text: "don't use ALL_CAPS in Go names" - linters: - revive path: jwt/internal/types/ text: "var-naming: avoid meaningless package names" - linters: - godoclint path: (^|/)internal/ paths: - third_party$ - builtin$ - examples$ issues: max-issues-per-linter: 0 max-same-issues: 0 formatters: enable: - gofmt - goimports exclusions: generated: lax paths: - third_party$ - builtin$ - examples$ golang-github-lestrrat-go-jwx-3.0.13/BUILD000066400000000000000000000017651515060566400201770ustar00rootroot00000000000000load("@rules_go//go:def.bzl", "go_library", "go_test") load("@gazelle//:def.bzl", "gazelle") # gazelle:prefix github.com/lestrrat-go/jwx/v3 # gazelle:go_naming_convention import_alias gazelle(name = "gazelle") go_library( name = "jwx", srcs = [ "format.go", "formatkind_string_gen.go", "jwx.go", "options.go", ], importpath = "github.com/lestrrat-go/jwx/v3", visibility = ["//visibility:public"], deps = [ "//internal/json", "//internal/tokens", "@com_github_lestrrat_go_option_v2//:option", ], ) go_test( name = "jwx_test", srcs = ["jwx_test.go"], deps = [ ":jwx", "//internal/jose", "//internal/json", "//internal/jwxtest", "//jwa", "//jwe", "//jwk", "//jwk/ecdsa", "//jws", "@com_github_stretchr_testify//require", ], ) alias( name = "go_default_library", actual = ":jwx", visibility = ["//visibility:public"], ) golang-github-lestrrat-go-jwx-3.0.13/Changes000066400000000000000000000350741515060566400207100ustar00rootroot00000000000000Changes ======= v3 has many incompatibilities with v2. To see the full list of differences between v2 and v3, please read the Changes-v3.md file (https://github.com/lestrrat-go/jwx/blob/develop/v3/Changes-v3.md) v3.0.13 12 Jan 2026 * [jwt] The `jwt.WithContext()` option is now properly being passed to `jws.Verify()` from `jwt.Parse()`. * [jwx] github.com/lestrrat-go/httprc/v3 has been upgraded to remove dependency on github.com/lestrrat-go/option (v1) * [jwk] `jwk.Clone()` has been fixed to properly work with private fields. v3.0.12 20 Oct 2025 * [jwe] As part of the next change, now per-recipient headers that are empty are no longer serialized in flattened JSON serialization. * [jwe] Introduce `jwe.WithLegacyHeaderMerging(bool)` option to control header merging behavior in during JWE encryption. This only applies to flattened JSON serialization. Previously, when using flattened JSON serialization (i.e. you specified JSON serialization via `jwe.WithJSON()` and only supplied one key), per-recipient headers were merged into the protected headers during encryption, and then were left to be included in the final serialization as-is. This caused duplicate headers to be present in both the protected headers and the per-recipient headers. Since there may be users who rely on this behavior already, instead of changing the default behavior to fix this duplication, a new option to `jwe.Encrypt()` was added to allow clearing the per-recipient headers after merging to leave the `"headers"` field empty. This in effect makes the flattened JSON serialization more similar to the compact serialization, where there are no per-recipient headers present, and leaves the headers disjoint. Note that in compact mode, there are no per-recipient headers and thus the headers need to be merged regardless. In full JSON serialization, we never merge the headers, so it is left up to the user to keep the headers disjoint. * [jws] Calling the deprecated `jws.NewSigner()` function for the first time will cause legacy signers to be loaded automatically. Previously, you had to explicitly call `jws.Settings(jws.WithLegacySigners(true))` to enable legacy signers. We incorrectly assumed that users would not be using `jws.NewSigner()`, and thus disabled legacy signers by default. However, it turned out that some users were using `jws.NewSigner()` in their code, which lead to breakages in existing code. In hindsight we should have known that any API made public before will be used by _somebody_. As a side effect, jws.Settings(jws.WithLegacySigners(...)) is now a no-op. However, please do note that jws.Signer (and similar) objects were always intended to be used for _registering_ new signing/verifying algorithms, and not for end users to actually use them directly. If you are using them for other purposes, please consider changing your code, as it is more than likely that we will somehow deprecate/remove/discouraged their use in the future. v3.0.11 14 Sep 2025 * [jwk] Add `(jwk.Cache).Shutdown()` method that delegates to the httprc controller object, to shutdown the cache. * [jwk] Change timing of `res.Body.Close()` call * [jwe] Previously, ecdh.PrivateKey/ecdh.PublicKey were not properly handled when used for encryption, which has been fixed. * [jws/jwsbb] (EXPERIMENTAL/BREAKS COMPATIBILITY) Convert most functions into thin wrappers around functions from github.com/lestrrat-go/dsig package. As a related change, HAMCHashFuncFor/RSAHashFuncFor/ECDSAHashFuncFor/RSAPSSOptions have been removed or unexported. Users of this module should be using jwsbb.Sign() and jwsbb.Verify() instead of algorithm specific jwsbb.SignRSA()/jwsbb.VerifyRSA() and such. If you feel the need to use these functions, you should use github.com/lestrrat-go/dsig directly. v3.0.10 04 Aug 2025 * [jws/jwsbb] Add `jwsbb.ErrHeaderNotFound()` to return the same error type as when a non-existent header is requested. via `HeaderGetXXX()` functions. Previously, this function was called `jwsbb.ErrFieldNotFound()`, but it was a misnomer. * [jws/jwsbb] Fix a bug where error return values from `HeaderGetXXX()` functions could not be matched against `jwsbb.ErrHeaderNotFound()` using `errors.Is()`. v3.0.9 31 Jul 2025 * [jws/jwsbb] `HeaderGetXXX()` functions now return errors when the requested header is not found, or if the value cannot be converted to the requested type. * [jwt] `(jwt.Token).Get` methods now return specific types of errors depending on if a) the specified claim was not present, or b) the specified claim could not be assigned to the destination variable. You can distinguish these by using `errors.Is` against `jwt.ClaimNotFoundError()` or `jwt.ClaimAssignmentFailedError()` v3.0.8 27 Jun 2025 * [jwe/jwebb] (EXPERIMENTAL) Add low-level functions for JWE operations. * [jws/jwsbb] (EXPERIMENTAL/BREAKS COMPATIBILITY) Add io.Reader parameter so your choice of source of randomness can be passed. Defaults to crypto/rand.Reader. Function signatures around jwsbb.Sign() now accept an addition `rr io.Reader`, which can be nil for 99% of use cases. * [jws/jwsbb] Add HeaderParse([]byte), where it is expected that the header is already in its base64 decoded format. * misc: replace `interface{}` with `any` v3.0.7 16 Jun 2025 * [jws/jwsbb] (EXPERIMENTAL) Add low-level fast access to JWS headers in compact serialization form. * [jws] Fix error reporting when no key matched for a signature. * [jws] Refactor jws signer setup. * Known algorithms are now implemented completely in the jws/jwsbb package. * VerifierFor and SignerFor now always succeed, and will also return a Signer2 or Verifier2 that wraps the legacy Signer or Verifier if one is registered. v3.0.6 13 Jun 2025 * This release contains various performance improvements all over the code. No, this time for real. In particular, the most common case for signing a JWT with a key is approx 70% more efficient based on the number of allocations. Please read the entry for the (retracted) v3.0.4 for what else I have to say about performance improvements * [jwt] Added fast-path for token signing and verification. The fast path is triggered if you only pass `jwt.Sign()` and `jwt.Parse()` one options each (`jwt.WithKey()`), with no suboptions. * [jws] Major refactoring around basic operations: * How to work with Signer/Verifier have completely changed. Please take a look at examples/jws_custom_signer_verifier_example_test.go for how to do it the new way. The old way still works, but it WILL be removed when v4 arrives. * Related to the above, old code has been moved to `jws/legacy`. * A new package `jws/jwsbb` has been added. `bb` stands for building blocks. This package separates out the low-level JWS operations into its own package. So if you are looking for just the signing of a payload with a key, this is it. `jws/jwsbb` is currently considered to be EXPERIMENTAL. v3.0.5 11 Jun 2025 * Retract v3.0.4 * Code for v3.0.3 is the same as v3.0.3 v3.0.4 09 Jun 2025 * This release contains various performance improvements all over the code. Because of the direction that this library is taking, we have always been more focused on correctness and usability/flexibility over performance. It just so happens that I had a moment of inspiration and decided to see just how good our AI-based coding agents are in this sort of analysis-heavy tasks. Long story short, the AI was fairly good at identifying suspicious code with an okay accuracy, but completely failed to make any meaningful changes to the code in a way that both did not break the code _and_ improved performance. I am sure that they will get better in the near future, but for now, I had to do the changes myself. I should clarify to their defence that the AI was very helpful in writing cumbersome benchmark code for me. The end result is that we have anywhere from 10 to 30% performance improvements in various parts of the code that we touched, based on number of allocations. We believe that this would be a significant improvement for many users. For further improvements, we can see that there would be a clear benefit to writing optimized code path that is designed to serve the most common cases. For example, for the case of signing JWTs with a single key, we could provide a path that skips a lot of extra processing (we kind of did that in this change, but we _could_ go ever harder in this direction). However, it is a trade-off between maintainability and performance, and as I am currently the sole maintainer of this library for the time being, I only plan to pursue such a route where it requires minimal effort on my part. If you are interested in helping out in this area, I hereby thank you in advance. However, please be perfectly clear that unlike other types of changes, for performance related changes, the balance between the performance gains and maintainability is top priority. If you have good ideas and code, they will always be welcome, but please be prepared to justify your changes. Finally, thank you for using this library! v3.0.3 06 Jun 2025 * Update some dependencies * [jwe] Change some error messages to contain more context information v3.0.2 03 Jun 2025 * [transform] (EXPERIMENTAL) Add utility function `transform.AsMap` to convert a Mappable object to a map[string]interface{}. This is useful for converting objects such as `jws.Header`, `jwk.Key`, `jwt.Token`, etc. to a map that can be used with other libraries that expect a map. * [jwt] (EXPERIMENTAL) Added token filtering functionality through the TokenFilter interface. * [jwt/openid] (EXPERIMENTAL) Added StandardClaimsFilter() for filtering standard OpenID claims. * [jws] (EXPERIMENTAL) Added header filtering functionality through the HeaderFilter interface. * [jwe] (EXPERIMENTAL) Added header filtering functionality through the HeaderFilter interface. * [jwk] (EXPERIMENTAL) Added key filtering functionality through the KeyFilter interface. * [jwk] `jwk.Export` previously did not recognize third-party objects that implemented `jwk.Key`, as it was detecting what to do by checking if the object was one of our own unexported types. This caused some problems for consumers of this library that wanted to extend the features of the keys. Now `jwk.Export` checks types against interface types such as `jwk.RSAPrivateKey`, `jwk.ECDSAPrivateKey`, etc. It also uses some reflect blackmagic to detect if the given object implements the `jwk.Key` interface via embedding, so you should be able to embed a `jwk.Key` to another object to act as if it is a legitimate `jwk.Key`, as far as `jwk.Export` is concerned. v3.0.1 29 Apr 2025 * [jwe] Fixed a long standing bug that could lead to degraded encryption or failure to decrypt JWE messages when a very specific combination of inputs were used for JWE operations. This problem only manifested itself when the following conditions in content encryption or decryption were met: - Content encryption was specified to use DIRECT mode. - Contentn encryption algorithm is specified as A256CBC_HS512 - The key was erronously constructed with a 32-byte content encryption key (CEK) In this case, the user would be passing a mis-constructed key of 32-bytes instead of the intended 64-bytes. In all other cases, this construction would cause an error because `crypto/aes.NewCipher` would return an error when a key with length not matching 16, 24, and 32 bytes is used. However, due to use using a the provided 32-bytes as half CEK and half the hash, the `crypto/aes.NewCipher` was passed a 16-byte key, which is fine for AES-128. So internally `crypto/aes.NewCipher` would choose to use AES-128 instead of AES-256, and happily continue. Note that no other key lengths such as 48 and 128 would have worked. It had to be exactly 32. This does indeed result in a downgraded encryption, but we believe it is unlikely that this would cause a problem in the real world, as you would have to very specifically choose to use DIRECT mode, choose the specific content encryption algorithm, AND also use the wrong key size of exactly 32 bytes. However, in abandunce of caution, we recommend that you upgrade to v3.0.1 or later, or v2.1.6 or later if you are still on v2 series. * [jws] Improve performance of jws.SplitCompact and jws.SplitCompactString * [jwe] Improve performance of jwe.Parse v3.0.0 1 Apr 2025 * Release initial v3.0.0 series. Code is identical to v3.0.0-beta2, except for minor documentation changes. Please note that v1 will no longer be maintained. Going forward v2 will receive security updates but will no longer receive feature updates. Users are encouraged to migrate to v3. There is no hard-set guarantee as to how long v2 will be supported, but if/when v4 comes out, v2 support will be terminated then. v3.0.0-beta2 30 Mar 2025 * [jwk] Fix a bug where `jwk.Set`'s `Keys()` method did not return the proper non-standard fields. (#1322) * [jws][jwt] Implement `WithBase64Encoder()` options to pass base64 encoders to use during signing/verifying signatures. This useful when the token provider generates JWTs that don't follow the specification and uses base64 encoding other than raw url encoding (no padding), such as, apparently, AWS ALB. (#1324, #1328) v3.0.0-beta1 15 Mar 2025 * [jwt] Token validation no longer truncates time based fields by default. To restore old behavior, you can either change the global settings by calling `jwt.Settings(jwt.WithTruncation(time.Second))`, or you can change it by each invocation by using `jwt.Validate(..., jwt.WithTruncation(time.Second))` v3.0.0-alpha3 13 Mar 2025 * [jwk] Importing/Exporting from jwk.Key with P256/P386/P521 curves to ecdh.PrivateKey/ecdh.PublicKey should now work. Previously these keys were not properly recognized by the exporter/importer. Note that keys that use X25519 and P256/P384/P521 behave differently: X25519 keys can only be exported to/imported from OKP keys, while P256/P384/P521 can be exported to either ecdsa or ecdh keys. v3.0.0-alpha2 25 Feb 2025 * Update to work with go1.24 * Update tests to work with latest latchset/jose * Fix build pipeline to work with latest golangci-lint * Require go1.23 v3.0.0-alpha1 01 Nov 2024 * Initial release of v3 line. golang-github-lestrrat-go-jwx-3.0.13/Changes-v2.md000066400000000000000000000350401515060566400216250ustar00rootroot00000000000000# Incompatible Changes from v1 to v2 These are changes that are incompatible with the v1.x.x version. * [tl;dr](#tldr) - If you don't feel like reading the details -- but you will read the details, right? * [Detailed List of Changes](#detailed-list-of-changes) - A comprehensive list of changes from v1 to v2 # tl;dr ## JWT ```go // most basic jwt.Parse(serialized, jwt.WithKey(alg, key)) // NOTE: verification and validation are ENABLED by default! jwt.Sign(token, jwt.WithKey(alg,key)) // with a jwk.Set jwt.Parse(serialized, jwt.WithKeySet(set)) // UseDefault/InferAlgorithm with JWKS jwt.Parse(serialized, jwt.WithKeySet(set, jws.WithUseDefault(true), jws.WithInferAlgorithm(true)) // Use `jku` jwt.Parse(serialized, jwt.WithVerifyAuto(...)) // Any other custom key provisioning (using functions in this // example, but can be anything that fulfills jws.KeyProvider) jwt.Parse(serialized, jwt.WithKeyProvider(jws.KeyProviderFunc(...))) ``` ## JWK ```go // jwk.New() was confusing. Renamed to fit the actual implementation key, err := jwk.FromRaw(rawKey) // Algorithm() now returns jwa.KeyAlgorithm type. `jws.Sign()` // and other function that receive JWK algorithm names accept // this new type, so you can use the same key and do the following // (previously you needed to type assert) jws.Sign(payload, jws.WithKey(key.Algorithm(), key)) // If you need the specific type, type assert key.Algorithm().(jwa.SignatureAlgorithm) // jwk.AutoRefresh is no more. Use jwk.Cache cache := jwk.NewCache(ctx, options...) // Certificate chains are no longer jwk.CertificateChain type, but // *(github.com/lestrrat-go/jwx/cert).Chain cc := key.X509CertChain() // this is *cert.Chain now ``` ## JWS ```go // basic jws.Sign(payload, jws.WithKey(alg, key)) jws.Sign(payload, jws.WithKey(alg, key), jws.WithKey(alg, key), jws.WithJSON(true)) jws.Verify(signed, jws.WithKey(alg, key)) // other ways to pass the key jws.Sign(payload, jws.WithKeySet(jwks)) jws.Sign(payload, jws.WithKeyProvider(kp)) // retrieve the key that succeeded in verifying var keyUsed interface{} jws.Verify(signed, jws.WithKeySet(jwks), jws.WithKeyUsed(&keyUsed)) ``` ## JWE ```go // basic jwe.Encrypt(payload, jwe.WithKey(alg, key)) // other defaults are inferred jwe.Encrypt(payload, jwe.WithKey(alg, key), jwe.WithKey(alg, key), jwe.WithJSON(true)) jwe.Decrypt(encrypted, jwe.WithKey(alg, key)) // other ways to pass the key jwe.Encrypt(payload, jwe.WithKeySet(jwks)) jwe.Encrypt(payload, jwe.WithKeyProvider(kp)) // retrieve the key that succeeded in decrypting var keyUsed interface{} jwe.Verify(signed, jwe.WithKeySet(jwks), jwe.WithKeyUsed(&keyUsed)) ``` # Detailed List of Changes ## Module * Module now requires go 1.16 * Use of github.com/pkg/errors is no more. If you were relying on behavior that depends on the errors being an instance of github.com/pkg/errors then you need to change your code * File-generation tools have been moved out of internal/ directories. These files pre-dates Go modules, and they were in internal/ in order to avoid being listed in the `go doc` -- however, now that we can make them separate modules this is no longer necessary. * New package `cert` has been added to handle `x5c` certificate chains, and to work with certificates * cert.Chain to store base64 encoded ASN.1 DER format certificates * cert.EncodeBase64 to encode ASN.1 DER format certificate using base64 * cert.Create to create a base64 encoded ASN.1 DER format certificates * cert.Parse to parse base64 encoded ASN.1 DER format certificates ## JWE * `jwe.Compact()`'s signature has changed to `jwe.Compact(*jwe.Message, ...jwe.CompactOption)` * `jwe.JSON()` has been removed. You can generate JSON serialization using `jwe.Encrypt(jwe.WitJSON())` or `json.Marshal(jwe.Message)` * `(jwe.Message).Decrypt()` has been removed. Since formatting of the original serialized message matters (including whitespace), using a parsed object was inherently confusing. * `jwe.Encrypt()` can now generate JWE messages in either compact or JSON forms. By default, the compact form is used. JSON format can be enabled by using the `jwe.WithJSON` option. * `jwe.Encrypt()` can now accept multiple keys by passing multiple `jwe.WithKey()` options. This can be used with `jwe.WithJSON` to create JWE messages with multiple recipients. * `jwe.DecryptEncryptOption()` has been renamed to `jwe.EncryptDecryptOption()`. This is so that it is more uniform with `jws` equivalent of `jws.SignVerifyOption()` where the producer (`Sign`) comes before the consumer (`Verify`) in the naming * `jwe.WithCompact` and `jwe.WithJSON` options have been added to control the serialization format. * jwe.Decrypt()'s method signature has been changed to `jwt.Decrypt([]byte, ...jwe.DecryptOption) ([]byte, error)`. These options can be stacked. Therefore, you could configure the verification process to attempt a static key pair, a JWKS, and only try other forms if the first two fails, for example. - For static key pair, use `jwe.WithKey()` - For static JWKS, use `jwe.WithKeySet()` (NOTE: InferAlgorithmFromKey like in `jws` package is NOT supported) - For custom, possibly dynamic key provisioning, use `jwe.WithKeyProvider()` * jwe.Decrypter has been unexported. Users did not need this. * jwe.WithKeyProvider() has been added to specify arbitrary code to specify which keys to try. * jwe.KeyProvider interface has been added * jwe.KeyProviderFunc has been added * `WithPostParser()` has been removed. You can achieve the same effect by using `jwe.WithKeyProvider()`. Because this was the only consumer for `jwe.DecryptCtx`, this type has been removed as well. * `x5c` field type has been changed to `*cert.Chain` instead of `[]string` * Method signature for `jwe.Parse()` has been changed to include options, but options are currently not used * `jwe.ReadFile` now supports the option `jwe.WithFS` which allows you to read data from arbitrary `fs.FS` objects * jwe.WithKeyUsed has been added to allow users to retrieve the key used for decryption. This is useful in cases you provided multiple keys and you want to know which one was successful ## JWK * `jwk.New()` has been renamed to `jwk.FromRaw()`, which hopefully will make it easier for the users what the input should be. * `jwk.Set` has many interface changes: * Changed methods to match jwk.Key and its semantics: * Field is now Get() (returns values for arbitrary fields other than keys). Fetching a key is done via Key() * Remove() now removes arbitrary fields, not keys. to remove keys, use RemoveKey() * Iterate has been added to iterate through all non-key fields. * Add is now AddKey(Key) string, and returns an error when the same key is added * Get is now Key(int) (Key, bool) * Remove is now RemoveKey(Key) error * Iterate is now Keys(context.Context) KeyIterator * Clear is now Clear() error * `jwk.CachedSet` has been added. You can create a `jwk.Set` that is backed by `jwk.Cache` so you can do this: ```go cache := jkw.NewCache(ctx) cachedSet := jwk.NewCachedSet(cache, jwksURI) // cachedSet is always the refreshed, cached version from jwk.Cache jws.Verify(signed, jws.WithKeySet(cachedSet)) ``` * `jwk.NewRSAPRivateKey()`, `jwk.NewECDSAPrivateKey()`, etc have been removed. There is no longer any way to create concrete types of `jwk.Key` * `jwk.Key` type no longer supports direct unmarshaling via `json.Unmarshal()`, because you can no longer instantiate concrete `jwk.Key` types. You will need to use `jwk.ParseKey()`. See the documentation for ways to parse JWKs. * `(jwk.Key).Algorithm()` is now of `jwk.KeyAlgorithm` type. This field used to be `string` and therefore could not be passed directly to `jwt.Sign()` `jws.Sign()`, `jwe.Encrypt()`, et al. This is no longer the case, and now you can pass it directly. See https://github.com/lestrrat-go/jwx/blob/v2/docs/99-faq.md#why-is-jwkkeyalgorithm-and-jwakeyalgorithm-so-confusing for more details * `jwk.Fetcher` and `jwk.FetchFunc` has been added. They represent something that can fetch a `jwk.Set` * `jwk.CertificateChain` has been removed, use `*cert.Chain` * `x5c` field type has been changed to `*cert.Chain` instead of `[]*x509.Certificate` * `jwk.ReadFile` now supports the option `jwk.WithFS` which allows you to read data from arbitrary `fs.FS` objects * Added `jwk.PostFetcher`, `jwk.PostFetchFunc`, and `jwk.WithPostFetch` to allow users to get at the `jwk.Set` that was fetched in `jwk.Cache`. This will make it possible for users to supply extra information and edit `jwk.Set` after it has been fetched and parsed, but before it is cached. You could, for example, modify the `alg` field so that it's easier to work with when you use it in `jws.Verify` later. * Reworked `jwk.AutoRefresh` in terms of `github.com/lestrrat-go/httprc` and renamed it `jwk.Cache`. Major difference between `jwk.AutoRefresh` and `jwk.Cache` is that while former used one `time.Timer` per resource, the latter uses a static timer (based on `jwk.WithRefreshWindow()` value, default 15 minutes) that periodically refreshes all resources that were due to be refreshed within that time frame. This method may cause your updates to happen slightly later, but uses significantly less resources and is less prone to clogging. * Reimplemented `jwk.Fetch` in terms of `github.com/lestrrat-go/httprc`. * Previously `jwk.Fetch` and `jwk.AutoRefresh` respected backoff options, but this has been removed. This is to avoid unwanted clogging of the fetch workers which is the default processing mode in `github.com/lestrrat-go/httprc`. If you are using backoffs, you need to control your inputs more carefully so as not to clog your fetch queue, and therefore you should be writing custom code that suits your needs ## JWS * `jws.Sign()` can now generate JWS messages in either compact or JSON forms. By default, the compact form is used. JSON format can be enabled by using the `jws.WithJSON` option. * `jws.Sign()` can now accept multiple keys by passing multiple `jws.WithKey()` options. This can be used with `jws.WithJSON` to create JWS messages with multiple signatures. * `jws.WithCompact` and `jws.WithJSON` options have been added to control the serialization format. * jws.Verify()'s method signature has been changed to `jwt.Verify([]byte, ...jws.VerifyOption) ([]byte, error)`. These options can be stacked. Therefore, you could configure the verification process to attempt a static key pair, a JWKS, and only try other forms if the first two fails, for example. - For static key pair, use `jws.WithKey()` - For static JWKS, use `jws.WithKeySet()` - For enabling verification using `jku`, use `jws.WithVerifyAuto()` - For custom, possibly dynamic key provisioning, use `jws.WithKeyProvider()` * jws.WithVerify() has been removed. * jws.WithKey() has been added to specify an algorithm + key to verify the payload with. * jws.WithKeySet() has been added to specify a JWKS to be used for verification. By default `kid` AND `alg` must match between the signature and the key. The option can take further suboptions: ```go jws.Parse(serialized, jws.WithKeySet(set, // by default `kid` is required. set false to disable. jws.WithRequireKid(false), // optionally skip matching kid if there's exactly one key in set jws.WithUseDefault(true), // infer algorithm name from key type jws.WithInferAlgorithm(true), ), ) ``` * `jws.VerifuAuto` has been removed in favor of using `jws.WithVerifyAuto` option with `jws.Verify()` * `jws.WithVerifyAuto` has been added to enable verification using `jku`. The first argument must be a jwk.Fetcher object, but can be set to `nil` to use the default implementation which is `jwk.Fetch` The rest of the arguments are treated as options passed to the `(jwk.Fetcher).Fetch()` function. * Remove `jws.WithPayloadSigner()`. This should be completely replaceable using `jws.WithKey()` * jws.WithKeyProvider() has been added to specify arbitrary code to specify which keys to try. * jws.KeyProvider interface has been added * jws.KeyProviderFunc has been added * jws.WithKeyUsed has been added to allow users to retrieve the key used for verification. This is useful in cases you provided multiple keys and you want to know which one was successful * `x5c` field type has been changed to `*cert.Chain` instead of `[]string` * `jws.ReadFile` now supports the option `jws.WithFS` which allows you to read data from arbitrary `fs.FS` objects ## JWT * `jwt.Parse` now verifies the signature and validates the token by default. You must disable it explicitly using `jwt.WithValidate(false)` and/or `jwt.WithVerify(false)` if you only want to parse the JWT message. If you don't want either, a convenience function `jwt.ParseInsecure` has been added. * `jwt.Parse` can only parse raw JWT (JSON) or JWS (JSON or Compact). It no longer accepts JWE messages. * `jwt.WithDecrypt` has been removed * `jwt.WithJweHeaders` has been removed * `jwt.WithVerify()` has been renamed to `jwt.WithKey()`. The option can be used for signing, encryption, and parsing. * `jwt.Validator` has been changed to return `jwt.ValidationError`. If you provide a custom validator, you should wrap the error with `jwt.NewValidationError()` * `jwt.UseDefault()` has been removed. You should use `jws.WithUseDefault()` as a suboption in the `jwt.WithKeySet()` option. ```go jwt.Parse(serialized, jwt.WithKeySet(set, jws.WithUseDefault(true))) ``` * `jwt.InferAlgorithmFromKey()` has been removed. You should use `jws.WithInferAlgorithmFromKey()` as a suboption in the `jwt.WithKeySet()` option. ```go jwt.Parse(serialized, jwt.WithKeySet(set, jws.WithInferAlgorithmFromKey(true))) ``` * jwt.WithKeySetProvider has been removed. Use `jwt.WithKeyProvider()` instead. If jwt.WithKeyProvider seems a bit complicated, use a combination of JWS parse, no-verify/validate JWT parse, and an extra JWS verify: ```go msg, _ := jws.Parse(signed) token, _ := jwt.Parse(msg.Payload(), jwt.WithVerify(false), jwt.WithValidate(false)) // Get information out of token, for example, `iss` switch token.Issuer() { case ...: jws.Verify(signed, jwt.WithKey(...)) } ``` * `jwt.WithHeaders` and `jwt.WithJwsHeaders` have been removed. You should be able to use the new `jwt.WithKey` option to pass headers * `jwt.WithSignOption` and `jwt.WithEncryptOption` have been added as escape hatches for options that are declared in `jws` and `jwe` packages but not in `jwt` * `jwt.ReadFile` now supports the option `jwt.WithFS` which allows you to read data from arbitrary `fs.FS` objects * `jwt.Sign()` has been changed so that it works more like the new `jws.Sign()` golang-github-lestrrat-go-jwx-3.0.13/Changes-v3.md000066400000000000000000000146771515060566400216430ustar00rootroot00000000000000# Incompatible Changes from v2 to v3 These are changes that are incompatible with the v2.x.x version. # Detailed list of changes ## Module * This module now requires Go 1.23 * All `xxx.Get()` methods have been changed from `Get(string) (interface{}, error)` to `Get(string, interface{}) error`, where the second argument should be a pointer to the storage destination of the field. * All convenience accessors (e.g. `(jwt.Token).Subject`) now return `(T, bool)` instead of `T`. If you want an accessor that returns a single value, consider using `Get()` * Most major errors can now be differentiated using `errors.Is` ## JWA * All string constants have been renamed to equivalent functions that return a struct. You should rewrite `jwa.RS256` as `jwa.RS256()` and so forth. * By default, only known algorithm names are accepted. For example, in our JWK tests, there are tests that deal with "ECMR" algorithm, but this will now fail by default. If you want this algorithm to succeed parsing, you need to call `jwa.RegisterXXXX` functions before using them. * Previously, unmarshaling unquoted strings used to work (e.g. `var s = "RS256"`), but now they must conform to the JSON standard and be quoted (e.g. `var s = strconv.Quote("RS256")`) ## JWT * All convenience accessors (e.g. `Subject`) now return `(T, bool)` instead of just `T`. If you want a single return value accessor, use `Get(dst) error` instead. * Validation used to work for `iat`, `nbf`, `exp` fields where these fields were set to the explicit time.Time{} zero value, but now the _presence_ of these fields matter. * Validation of fields related to time used to be truncated to one second accuracy, but no longer does so. To restore old behavior, you can either change the global settings by calling `jwt.Settings(jwt.WithTruncation(time.Second))`, or you can change it by each invocation by using `jwt.Validate(..., jwt.WithTruncation(time.Second))` * Error names have been renamed. For example `jwt.ErrInvalidJWT` has been renamed to `jwt.UnknownPayloadTypeError` to better reflect what the error means. For other errors, `func ErrXXXX()` have generally been renamed to `func XXXError()` * Validation errors are now wrapped. While `Validate()` returns a `ValidateError()` type, it can also be matched against more specific error types such as `TokenExpierdError()` using `errors.Is` * `jwt.ErrMissingRequiredClaim` has been removed ## JWS * Iterators have been completely removed. * As a side effect of removing iterators, some methods such as `Copy()` lost the `context.Context` argument * All convenience accessors (e.g. `Algorithm`) now return `(T, bool)` instead of just `T`. If you want a single return value accessor, use `Get(dst) error` instead. * Errors from `jws.Sign` and `jws.Verify`, as well as `jws.Parse` (and friends) can now be differentiated by using `errors.Is`. All `jws.IsXXXXError` functions have been removed. ## JWE * Iterators have been completely removed. * As a side effect of removing iterators, some methods such as `Copy()` lost the `context.Context` argument * All convenience accessors (e.g. `Algorithm`) now return `(T, bool)` instead of just `T`. If you want a single return value accessor, use `Get(dst) error` instead. * Errors from `jwe.Decrypt` and `jwe.Encrypt`, as well as `jwe.Parse` (and friends) can now be differentiated by using `errors.Is`. All `jwe.IsXXXXrror` functions have been removed. ## JWK * All convenience accessors (e.g. `Algorithm`, `Crv`) now return `(T, bool)` instead of just `T`, except `KeyType`, which _always_ returns a valid value. If you want a single return value accessor, use `Get(dst) error` instead. * `jwk.KeyUsageType` can now be configured so that it's possible to assign values other than "sig" and "enc" via `jwk.RegisterKeyUsage()`. Furthermore, strict checks can be turned on/off against these registered values * `jwk.Cache` has been completely re-worked based on github.com/lestrrat-go/httprc/v3. In particular, the default whitelist mode has changed from "block everything" to "allow everything". * Experimental secp256k1 encoding/decoding for PEM encoded ASN.1 DER Format has been removed. Instead, `jwk.PEMDecoder` and `jwk.PEMEncoder` have been added to support those who want to perform non-standard PEM encoding/decoding * Iterators have been completely removed. * `jwk/x25519` has been removed. To use X25519 keys, use `(crypto/ecdh).PrivateKey` and `(crypto/ecdh).PublicKey`. Similarly, internals have been reworked to use `crypto/ecdh` * Parsing has completely been reworked. It is now possible to add your own `jwk.KeyParser` to generate a custom `jwk.Key` that this library may not natively support. Also see `jwk.RegisterKeyParser()` * `jwk.KeyProbe` has been added to aid probing the JSON message. This is used to guess the type of key described in the JSON message before deciding which concrete type to instantiate, and aids implementing your own `jwk.KeyParser`. Also see `jwk.RegisterKeyProbe()` * Conversion between raw keys and `jwk.Key` can be customized using `jwk.KeyImporter` and `jwk.KeyExporter`. Also see `jwk.RegisterKeyImporter()` and `jwk.RegisterKeyExporter()` * Added `jwk/ecdsa` to keep track of which curves are available for ECDSA keys. * `(jwk.Key).Raw()` has been deprecated. Use `jwk.Export()` instead to convert `jwk.Key` objects into their "raw" versions (e.g. `*rsa.PrivateKey`, `*ecdsa.PrivateKey`, etc). This is to allow third parties to register custom key types that this library does not natively support: Whereas a method must be bound to an object, and thus does not necessarily have a way to hook into a global settings (i.e. custom exporter/importer) for arbitrary key types, if the entrypoint is a function it's much easier and cleaner to for third-parties to take advantage and hook into the mechanisms. * `jwk.FromRaw()` has been derepcated. Use `jwk.Import()` instead to convert "raw" keys (e.g. `*rsa.PrivateKEy`, `*Ecdsa.PrivateKey`, etc) int `jwk.Key`s. * `(jwk.Key).FromRaw()` has been deprecated. The method `(jwk.Key).Import()` still exist for built-in types, but it is no longer part of any public API (`interface{}`). * `jwk.Fetch` is marked as a simple wrapper around `net/http` and `jwk.Parse`. * `jwk.SetGlobalFetcher` has been deprecated. * `jwk.Fetcher` has been clearly marked as something that has limited usage for `jws.WithVerifyAuto` * `jwk.Key` with P256/P386/P521 curves can be exporrted to `ecdh.PrivateKey`/`ecdh.PublicKey`golang-github-lestrrat-go-jwx-3.0.13/LICENSE000066400000000000000000000020641515060566400204130ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2015 lestrrat Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. golang-github-lestrrat-go-jwx-3.0.13/MODULE.bazel000066400000000000000000000020751515060566400214140ustar00rootroot00000000000000module( name = "com_github_lestrrat_go_jwx_v3", version = "3.0.0", repo_name = "com_github_lestrrat_go_jwx_v2", ) bazel_dep(name = "bazel_skylib", version = "1.7.1") bazel_dep(name = "rules_go", version = "0.55.1") bazel_dep(name = "gazelle", version = "0.44.0") bazel_dep(name = "aspect_bazel_lib", version = "2.11.0") # Go SDK setup from go.mod go_sdk = use_extension("@rules_go//go:extensions.bzl", "go_sdk") go_sdk.from_file(go_mod = "//:go.mod") # Go dependencies from go.mod go_deps = use_extension("@gazelle//:extensions.bzl", "go_deps") go_deps.from_file(go_mod = "//:go.mod") # Use repositories for external Go dependencies use_repo( go_deps, "com_github_decred_dcrd_dcrec_secp256k1_v4", "com_github_goccy_go_json", "com_github_lestrrat_go_blackmagic", "com_github_lestrrat_go_dsig", "com_github_lestrrat_go_dsig_secp256k1", "com_github_lestrrat_go_httprc_v3", "com_github_lestrrat_go_option_v2", "com_github_segmentio_asm", "com_github_stretchr_testify", "com_github_valyala_fastjson", "org_golang_x_crypto", ) golang-github-lestrrat-go-jwx-3.0.13/MODULE.bazel.lock000066400000000000000000000601441515060566400223440ustar00rootroot00000000000000{ "lockFileVersion": 18, "registryFileHashes": { "https://bcr.bazel.build/bazel_registry.json": "8a28e4aff06ee60aed2a8c281907fb8bcbf3b753c91fb5a5c57da3215d5b3497", "https://bcr.bazel.build/modules/abseil-cpp/20210324.2/MODULE.bazel": "7cd0312e064fde87c8d1cd79ba06c876bd23630c83466e9500321be55c96ace2", "https://bcr.bazel.build/modules/abseil-cpp/20211102.0/MODULE.bazel": "70390338f7a5106231d20620712f7cccb659cd0e9d073d1991c038eb9fc57589", "https://bcr.bazel.build/modules/abseil-cpp/20230125.1/MODULE.bazel": "89047429cb0207707b2dface14ba7f8df85273d484c2572755be4bab7ce9c3a0", "https://bcr.bazel.build/modules/abseil-cpp/20230802.0.bcr.1/MODULE.bazel": "1c8cec495288dccd14fdae6e3f95f772c1c91857047a098fad772034264cc8cb", "https://bcr.bazel.build/modules/abseil-cpp/20230802.0/MODULE.bazel": "d253ae36a8bd9ee3c5955384096ccb6baf16a1b1e93e858370da0a3b94f77c16", "https://bcr.bazel.build/modules/abseil-cpp/20230802.1/MODULE.bazel": "fa92e2eb41a04df73cdabeec37107316f7e5272650f81d6cc096418fe647b915", "https://bcr.bazel.build/modules/abseil-cpp/20240116.1/MODULE.bazel": "37bcdb4440fbb61df6a1c296ae01b327f19e9bb521f9b8e26ec854b6f97309ed", "https://bcr.bazel.build/modules/abseil-cpp/20240116.1/source.json": "9be551b8d4e3ef76875c0d744b5d6a504a27e3ae67bc6b28f46415fd2d2957da", "https://bcr.bazel.build/modules/aspect_bazel_lib/2.11.0/MODULE.bazel": "cb1ba9f9999ed0bc08600c221f532c1ddd8d217686b32ba7d45b0713b5131452", "https://bcr.bazel.build/modules/aspect_bazel_lib/2.11.0/source.json": "92494d5aa43b96665397dd13ee16023097470fa85e276b93674d62a244de47ee", "https://bcr.bazel.build/modules/bazel_features/1.1.0/MODULE.bazel": "cfd42ff3b815a5f39554d97182657f8c4b9719568eb7fded2b9135f084bf760b", "https://bcr.bazel.build/modules/bazel_features/1.1.1/MODULE.bazel": "27b8c79ef57efe08efccbd9dd6ef70d61b4798320b8d3c134fd571f78963dbcd", "https://bcr.bazel.build/modules/bazel_features/1.11.0/MODULE.bazel": "f9382337dd5a474c3b7d334c2f83e50b6eaedc284253334cf823044a26de03e8", "https://bcr.bazel.build/modules/bazel_features/1.15.0/MODULE.bazel": "d38ff6e517149dc509406aca0db3ad1efdd890a85e049585b7234d04238e2a4d", "https://bcr.bazel.build/modules/bazel_features/1.17.0/MODULE.bazel": "039de32d21b816b47bd42c778e0454217e9c9caac4a3cf8e15c7231ee3ddee4d", "https://bcr.bazel.build/modules/bazel_features/1.18.0/MODULE.bazel": "1be0ae2557ab3a72a57aeb31b29be347bcdc5d2b1eb1e70f39e3851a7e97041a", "https://bcr.bazel.build/modules/bazel_features/1.19.0/MODULE.bazel": "59adcdf28230d220f0067b1f435b8537dd033bfff8db21335ef9217919c7fb58", "https://bcr.bazel.build/modules/bazel_features/1.30.0/MODULE.bazel": "a14b62d05969a293b80257e72e597c2da7f717e1e69fa8b339703ed6731bec87", "https://bcr.bazel.build/modules/bazel_features/1.30.0/source.json": "b07e17f067fe4f69f90b03b36ef1e08fe0d1f3cac254c1241a1818773e3423bc", "https://bcr.bazel.build/modules/bazel_features/1.4.1/MODULE.bazel": "e45b6bb2350aff3e442ae1111c555e27eac1d915e77775f6fdc4b351b758b5d7", "https://bcr.bazel.build/modules/bazel_features/1.9.0/MODULE.bazel": "885151d58d90d8d9c811eb75e3288c11f850e1d6b481a8c9f766adee4712358b", "https://bcr.bazel.build/modules/bazel_features/1.9.1/MODULE.bazel": "8f679097876a9b609ad1f60249c49d68bfab783dd9be012faf9d82547b14815a", "https://bcr.bazel.build/modules/bazel_skylib/1.0.3/MODULE.bazel": "bcb0fd896384802d1ad283b4e4eb4d718eebd8cb820b0a2c3a347fb971afd9d8", "https://bcr.bazel.build/modules/bazel_skylib/1.1.1/MODULE.bazel": "1add3e7d93ff2e6998f9e118022c84d163917d912f5afafb3058e3d2f1545b5e", "https://bcr.bazel.build/modules/bazel_skylib/1.2.0/MODULE.bazel": "44fe84260e454ed94ad326352a698422dbe372b21a1ac9f3eab76eb531223686", "https://bcr.bazel.build/modules/bazel_skylib/1.2.1/MODULE.bazel": "f35baf9da0efe45fa3da1696ae906eea3d615ad41e2e3def4aeb4e8bc0ef9a7a", "https://bcr.bazel.build/modules/bazel_skylib/1.3.0/MODULE.bazel": "20228b92868bf5cfc41bda7afc8a8ba2a543201851de39d990ec957b513579c5", "https://bcr.bazel.build/modules/bazel_skylib/1.4.1/MODULE.bazel": "a0dcb779424be33100dcae821e9e27e4f2901d9dfd5333efe5ac6a8d7ab75e1d", "https://bcr.bazel.build/modules/bazel_skylib/1.4.2/MODULE.bazel": "3bd40978e7a1fac911d5989e6b09d8f64921865a45822d8b09e815eaa726a651", "https://bcr.bazel.build/modules/bazel_skylib/1.5.0/MODULE.bazel": "32880f5e2945ce6a03d1fbd588e9198c0a959bb42297b2cfaf1685b7bc32e138", "https://bcr.bazel.build/modules/bazel_skylib/1.6.1/MODULE.bazel": "8fdee2dbaace6c252131c00e1de4b165dc65af02ea278476187765e1a617b917", "https://bcr.bazel.build/modules/bazel_skylib/1.7.0/MODULE.bazel": "0db596f4563de7938de764cc8deeabec291f55e8ec15299718b93c4423e9796d", "https://bcr.bazel.build/modules/bazel_skylib/1.7.1/MODULE.bazel": "3120d80c5861aa616222ec015332e5f8d3171e062e3e804a2a0253e1be26e59b", "https://bcr.bazel.build/modules/bazel_skylib/1.7.1/source.json": "f121b43eeefc7c29efbd51b83d08631e2347297c95aac9764a701f2a6a2bb953", "https://bcr.bazel.build/modules/buildozer/7.1.2/MODULE.bazel": "2e8dd40ede9c454042645fd8d8d0cd1527966aa5c919de86661e62953cd73d84", "https://bcr.bazel.build/modules/buildozer/7.1.2/source.json": "c9028a501d2db85793a6996205c8de120944f50a0d570438fcae0457a5f9d1f8", "https://bcr.bazel.build/modules/gazelle/0.32.0/MODULE.bazel": "b499f58a5d0d3537f3cf5b76d8ada18242f64ec474d8391247438bf04f58c7b8", "https://bcr.bazel.build/modules/gazelle/0.33.0/MODULE.bazel": "a13a0f279b462b784fb8dd52a4074526c4a2afe70e114c7d09066097a46b3350", "https://bcr.bazel.build/modules/gazelle/0.34.0/MODULE.bazel": "abdd8ce4d70978933209db92e436deb3a8b737859e9354fb5fd11fb5c2004c8a", "https://bcr.bazel.build/modules/gazelle/0.36.0/MODULE.bazel": "e375d5d6e9a6ca59b0cb38b0540bc9a05b6aa926d322f2de268ad267a2ee74c0", "https://bcr.bazel.build/modules/gazelle/0.44.0/MODULE.bazel": "fd3177ca0938da57a1e416cad3f39b9c4334defbc717e89aba9d9ddbbb0341da", "https://bcr.bazel.build/modules/gazelle/0.44.0/source.json": "7fb65ef9c1ce470d099ca27fd478673d9d64c844af28d0d472b0874c7d590cb6", "https://bcr.bazel.build/modules/google_benchmark/1.8.2/MODULE.bazel": "a70cf1bba851000ba93b58ae2f6d76490a9feb74192e57ab8e8ff13c34ec50cb", "https://bcr.bazel.build/modules/googletest/1.11.0/MODULE.bazel": "3a83f095183f66345ca86aa13c58b59f9f94a2f81999c093d4eeaa2d262d12f4", "https://bcr.bazel.build/modules/googletest/1.14.0.bcr.1/MODULE.bazel": "22c31a561553727960057361aa33bf20fb2e98584bc4fec007906e27053f80c6", "https://bcr.bazel.build/modules/googletest/1.14.0.bcr.1/source.json": "41e9e129f80d8c8bf103a7acc337b76e54fad1214ac0a7084bf24f4cd924b8b4", "https://bcr.bazel.build/modules/googletest/1.14.0/MODULE.bazel": "cfbcbf3e6eac06ef9d85900f64424708cc08687d1b527f0ef65aa7517af8118f", "https://bcr.bazel.build/modules/jsoncpp/1.9.5/MODULE.bazel": "31271aedc59e815656f5736f282bb7509a97c7ecb43e927ac1a37966e0578075", "https://bcr.bazel.build/modules/jsoncpp/1.9.5/source.json": "4108ee5085dd2885a341c7fab149429db457b3169b86eb081fa245eadf69169d", "https://bcr.bazel.build/modules/libpfm/4.11.0/MODULE.bazel": "45061ff025b301940f1e30d2c16bea596c25b176c8b6b3087e92615adbd52902", "https://bcr.bazel.build/modules/package_metadata/0.0.2/MODULE.bazel": "fb8d25550742674d63d7b250063d4580ca530499f045d70748b1b142081ebb92", "https://bcr.bazel.build/modules/package_metadata/0.0.2/source.json": "e53a759a72488d2c0576f57491ef2da0cf4aab05ac0997314012495935531b73", "https://bcr.bazel.build/modules/platforms/0.0.10/MODULE.bazel": "8cb8efaf200bdeb2150d93e162c40f388529a25852b332cec879373771e48ed5", "https://bcr.bazel.build/modules/platforms/0.0.11/MODULE.bazel": "0daefc49732e227caa8bfa834d65dc52e8cc18a2faf80df25e8caea151a9413f", "https://bcr.bazel.build/modules/platforms/0.0.11/source.json": "f7e188b79ebedebfe75e9e1d098b8845226c7992b307e28e1496f23112e8fc29", "https://bcr.bazel.build/modules/platforms/0.0.4/MODULE.bazel": "9b328e31ee156f53f3c416a64f8491f7eb731742655a47c9eec4703a71644aee", "https://bcr.bazel.build/modules/platforms/0.0.5/MODULE.bazel": "5733b54ea419d5eaf7997054bb55f6a1d0b5ff8aedf0176fef9eea44f3acda37", "https://bcr.bazel.build/modules/platforms/0.0.6/MODULE.bazel": "ad6eeef431dc52aefd2d77ed20a4b353f8ebf0f4ecdd26a807d2da5aa8cd0615", "https://bcr.bazel.build/modules/platforms/0.0.7/MODULE.bazel": "72fd4a0ede9ee5c021f6a8dd92b503e089f46c227ba2813ff183b71616034814", "https://bcr.bazel.build/modules/platforms/0.0.8/MODULE.bazel": "9f142c03e348f6d263719f5074b21ef3adf0b139ee4c5133e2aa35664da9eb2d", "https://bcr.bazel.build/modules/protobuf/21.7/MODULE.bazel": "a5a29bb89544f9b97edce05642fac225a808b5b7be74038ea3640fae2f8e66a7", "https://bcr.bazel.build/modules/protobuf/27.0/MODULE.bazel": "7873b60be88844a0a1d8f80b9d5d20cfbd8495a689b8763e76c6372998d3f64c", "https://bcr.bazel.build/modules/protobuf/27.1/MODULE.bazel": "703a7b614728bb06647f965264967a8ef1c39e09e8f167b3ca0bb1fd80449c0d", "https://bcr.bazel.build/modules/protobuf/29.0-rc2.bcr.1/MODULE.bazel": "52f4126f63a2f0bbf36b99c2a87648f08467a4eaf92ba726bc7d6a500bbf770c", "https://bcr.bazel.build/modules/protobuf/29.0-rc2/MODULE.bazel": "6241d35983510143049943fc0d57937937122baf1b287862f9dc8590fc4c37df", "https://bcr.bazel.build/modules/protobuf/29.0/MODULE.bazel": "319dc8bf4c679ff87e71b1ccfb5a6e90a6dbc4693501d471f48662ac46d04e4e", "https://bcr.bazel.build/modules/protobuf/29.0/source.json": "b857f93c796750eef95f0d61ee378f3420d00ee1dd38627b27193aa482f4f981", "https://bcr.bazel.build/modules/protobuf/3.19.0/MODULE.bazel": "6b5fbb433f760a99a22b18b6850ed5784ef0e9928a72668b66e4d7ccd47db9b0", "https://bcr.bazel.build/modules/protobuf/3.19.2/MODULE.bazel": "532ffe5f2186b69fdde039efe6df13ba726ff338c6bc82275ad433013fa10573", "https://bcr.bazel.build/modules/protobuf/3.19.6/MODULE.bazel": "9233edc5e1f2ee276a60de3eaa47ac4132302ef9643238f23128fea53ea12858", "https://bcr.bazel.build/modules/pybind11_bazel/2.11.1/MODULE.bazel": "88af1c246226d87e65be78ed49ecd1e6f5e98648558c14ce99176da041dc378e", "https://bcr.bazel.build/modules/pybind11_bazel/2.11.1/source.json": "be4789e951dd5301282729fe3d4938995dc4c1a81c2ff150afc9f1b0504c6022", "https://bcr.bazel.build/modules/re2/2023-09-01/MODULE.bazel": "cb3d511531b16cfc78a225a9e2136007a48cf8a677e4264baeab57fe78a80206", "https://bcr.bazel.build/modules/re2/2023-09-01/source.json": "e044ce89c2883cd957a2969a43e79f7752f9656f6b20050b62f90ede21ec6eb4", "https://bcr.bazel.build/modules/rules_android/0.1.1/MODULE.bazel": "48809ab0091b07ad0182defb787c4c5328bd3a278938415c00a7b69b50c4d3a8", "https://bcr.bazel.build/modules/rules_android/0.1.1/source.json": "e6986b41626ee10bdc864937ffb6d6bf275bb5b9c65120e6137d56e6331f089e", "https://bcr.bazel.build/modules/rules_cc/0.0.1/MODULE.bazel": "cb2aa0747f84c6c3a78dad4e2049c154f08ab9d166b1273835a8174940365647", "https://bcr.bazel.build/modules/rules_cc/0.0.10/MODULE.bazel": "ec1705118f7eaedd6e118508d3d26deba2a4e76476ada7e0e3965211be012002", "https://bcr.bazel.build/modules/rules_cc/0.0.13/MODULE.bazel": "0e8529ed7b323dad0775ff924d2ae5af7640b23553dfcd4d34344c7e7a867191", "https://bcr.bazel.build/modules/rules_cc/0.0.14/MODULE.bazel": "5e343a3aac88b8d7af3b1b6d2093b55c347b8eefc2e7d1442f7a02dc8fea48ac", "https://bcr.bazel.build/modules/rules_cc/0.0.15/MODULE.bazel": "6704c35f7b4a72502ee81f61bf88706b54f06b3cbe5558ac17e2e14666cd5dcc", "https://bcr.bazel.build/modules/rules_cc/0.0.16/MODULE.bazel": "7661303b8fc1b4d7f532e54e9d6565771fea666fbdf839e0a86affcd02defe87", "https://bcr.bazel.build/modules/rules_cc/0.0.2/MODULE.bazel": "6915987c90970493ab97393024c156ea8fb9f3bea953b2f3ec05c34f19b5695c", "https://bcr.bazel.build/modules/rules_cc/0.0.6/MODULE.bazel": "abf360251023dfe3efcef65ab9d56beefa8394d4176dd29529750e1c57eaa33f", "https://bcr.bazel.build/modules/rules_cc/0.0.8/MODULE.bazel": "964c85c82cfeb6f3855e6a07054fdb159aced38e99a5eecf7bce9d53990afa3e", "https://bcr.bazel.build/modules/rules_cc/0.0.9/MODULE.bazel": "836e76439f354b89afe6a911a7adf59a6b2518fafb174483ad78a2a2fde7b1c5", "https://bcr.bazel.build/modules/rules_cc/0.1.1/MODULE.bazel": "2f0222a6f229f0bf44cd711dc13c858dad98c62d52bd51d8fc3a764a83125513", "https://bcr.bazel.build/modules/rules_cc/0.1.1/source.json": "d61627377bd7dd1da4652063e368d9366fc9a73920bfa396798ad92172cf645c", "https://bcr.bazel.build/modules/rules_foreign_cc/0.9.0/MODULE.bazel": "c9e8c682bf75b0e7c704166d79b599f93b72cfca5ad7477df596947891feeef6", "https://bcr.bazel.build/modules/rules_fuzzing/0.5.2/MODULE.bazel": "40c97d1144356f52905566c55811f13b299453a14ac7769dfba2ac38192337a8", "https://bcr.bazel.build/modules/rules_fuzzing/0.5.2/source.json": "c8b1e2c717646f1702290959a3302a178fb639d987ab61d548105019f11e527e", "https://bcr.bazel.build/modules/rules_go/0.41.0/MODULE.bazel": "55861d8e8bb0e62cbd2896f60ff303f62ffcb0eddb74ecb0e5c0cbe36fc292c8", "https://bcr.bazel.build/modules/rules_go/0.42.0/MODULE.bazel": "8cfa875b9aa8c6fce2b2e5925e73c1388173ea3c32a0db4d2b4804b453c14270", "https://bcr.bazel.build/modules/rules_go/0.46.0/MODULE.bazel": "3477df8bdcc49e698b9d25f734c4f3a9f5931ff34ee48a2c662be168f5f2d3fd", "https://bcr.bazel.build/modules/rules_go/0.51.0/MODULE.bazel": "b6920f505935bfd69381651c942496d99b16e2a12f3dd5263b90ded16f3b4d0f", "https://bcr.bazel.build/modules/rules_go/0.55.1/MODULE.bazel": "a57a6fc59a74326c0b440d07cca209edf13c7d1a641e48cfbeab56e79f873609", "https://bcr.bazel.build/modules/rules_go/0.55.1/source.json": "827a740c8959c9d20616889e7746cde4dcc6ee80d25146943627ccea0736328f", "https://bcr.bazel.build/modules/rules_java/4.0.0/MODULE.bazel": "5a78a7ae82cd1a33cef56dc578c7d2a46ed0dca12643ee45edbb8417899e6f74", "https://bcr.bazel.build/modules/rules_java/5.3.5/MODULE.bazel": "a4ec4f2db570171e3e5eb753276ee4b389bae16b96207e9d3230895c99644b86", "https://bcr.bazel.build/modules/rules_java/6.0.0/MODULE.bazel": "8a43b7df601a7ec1af61d79345c17b31ea1fedc6711fd4abfd013ea612978e39", "https://bcr.bazel.build/modules/rules_java/6.3.0/MODULE.bazel": "a97c7678c19f236a956ad260d59c86e10a463badb7eb2eda787490f4c969b963", "https://bcr.bazel.build/modules/rules_java/6.4.0/MODULE.bazel": "e986a9fe25aeaa84ac17ca093ef13a4637f6107375f64667a15999f77db6c8f6", "https://bcr.bazel.build/modules/rules_java/6.5.2/MODULE.bazel": "1d440d262d0e08453fa0c4d8f699ba81609ed0e9a9a0f02cd10b3e7942e61e31", "https://bcr.bazel.build/modules/rules_java/7.10.0/MODULE.bazel": "530c3beb3067e870561739f1144329a21c851ff771cd752a49e06e3dc9c2e71a", "https://bcr.bazel.build/modules/rules_java/7.12.2/MODULE.bazel": "579c505165ee757a4280ef83cda0150eea193eed3bef50b1004ba88b99da6de6", "https://bcr.bazel.build/modules/rules_java/7.2.0/MODULE.bazel": "06c0334c9be61e6cef2c8c84a7800cef502063269a5af25ceb100b192453d4ab", "https://bcr.bazel.build/modules/rules_java/7.3.2/MODULE.bazel": "50dece891cfdf1741ea230d001aa9c14398062f2b7c066470accace78e412bc2", "https://bcr.bazel.build/modules/rules_java/7.6.1/MODULE.bazel": "2f14b7e8a1aa2f67ae92bc69d1ec0fa8d9f827c4e17ff5e5f02e91caa3b2d0fe", "https://bcr.bazel.build/modules/rules_java/8.12.0/MODULE.bazel": "8e6590b961f2defdfc2811c089c75716cb2f06c8a4edeb9a8d85eaa64ee2a761", "https://bcr.bazel.build/modules/rules_java/8.12.0/source.json": "cbd5d55d9d38d4008a7d00bee5b5a5a4b6031fcd4a56515c9accbcd42c7be2ba", "https://bcr.bazel.build/modules/rules_jvm_external/4.4.2/MODULE.bazel": "a56b85e418c83eb1839819f0b515c431010160383306d13ec21959ac412d2fe7", "https://bcr.bazel.build/modules/rules_jvm_external/5.1/MODULE.bazel": "33f6f999e03183f7d088c9be518a63467dfd0be94a11d0055fe2d210f89aa909", "https://bcr.bazel.build/modules/rules_jvm_external/5.2/MODULE.bazel": "d9351ba35217ad0de03816ef3ed63f89d411349353077348a45348b096615036", "https://bcr.bazel.build/modules/rules_jvm_external/5.3/MODULE.bazel": "bf93870767689637164657731849fb887ad086739bd5d360d90007a581d5527d", "https://bcr.bazel.build/modules/rules_jvm_external/6.1/MODULE.bazel": "75b5fec090dbd46cf9b7d8ea08cf84a0472d92ba3585b476f44c326eda8059c4", "https://bcr.bazel.build/modules/rules_jvm_external/6.3/MODULE.bazel": "c998e060b85f71e00de5ec552019347c8bca255062c990ac02d051bb80a38df0", "https://bcr.bazel.build/modules/rules_jvm_external/6.3/source.json": "6f5f5a5a4419ae4e37c35a5bb0a6ae657ed40b7abc5a5189111b47fcebe43197", "https://bcr.bazel.build/modules/rules_kotlin/1.9.0/MODULE.bazel": "ef85697305025e5a61f395d4eaede272a5393cee479ace6686dba707de804d59", "https://bcr.bazel.build/modules/rules_kotlin/1.9.6/MODULE.bazel": "d269a01a18ee74d0335450b10f62c9ed81f2321d7958a2934e44272fe82dcef3", "https://bcr.bazel.build/modules/rules_kotlin/1.9.6/source.json": "2faa4794364282db7c06600b7e5e34867a564ae91bda7cae7c29c64e9466b7d5", "https://bcr.bazel.build/modules/rules_license/0.0.3/MODULE.bazel": "627e9ab0247f7d1e05736b59dbb1b6871373de5ad31c3011880b4133cafd4bd0", "https://bcr.bazel.build/modules/rules_license/0.0.7/MODULE.bazel": "088fbeb0b6a419005b89cf93fe62d9517c0a2b8bb56af3244af65ecfe37e7d5d", "https://bcr.bazel.build/modules/rules_license/1.0.0/MODULE.bazel": "a7fda60eefdf3d8c827262ba499957e4df06f659330bbe6cdbdb975b768bb65c", "https://bcr.bazel.build/modules/rules_license/1.0.0/source.json": "a52c89e54cc311196e478f8382df91c15f7a2bfdf4c6cd0e2675cc2ff0b56efb", "https://bcr.bazel.build/modules/rules_pkg/0.7.0/MODULE.bazel": "df99f03fc7934a4737122518bb87e667e62d780b610910f0447665a7e2be62dc", "https://bcr.bazel.build/modules/rules_pkg/1.0.1/MODULE.bazel": "5b1df97dbc29623bccdf2b0dcd0f5cb08e2f2c9050aab1092fd39a41e82686ff", "https://bcr.bazel.build/modules/rules_pkg/1.0.1/source.json": "bd82e5d7b9ce2d31e380dd9f50c111d678c3bdaca190cb76b0e1c71b05e1ba8a", "https://bcr.bazel.build/modules/rules_proto/4.0.0/MODULE.bazel": "a7a7b6ce9bee418c1a760b3d84f83a299ad6952f9903c67f19e4edd964894e06", "https://bcr.bazel.build/modules/rules_proto/5.3.0-21.7/MODULE.bazel": "e8dff86b0971688790ae75528fe1813f71809b5afd57facb44dad9e8eca631b7", "https://bcr.bazel.build/modules/rules_proto/6.0.0/MODULE.bazel": "b531d7f09f58dce456cd61b4579ce8c86b38544da75184eadaf0a7cb7966453f", "https://bcr.bazel.build/modules/rules_proto/6.0.2/MODULE.bazel": "ce916b775a62b90b61888052a416ccdda405212b6aaeb39522f7dc53431a5e73", "https://bcr.bazel.build/modules/rules_proto/7.0.2/MODULE.bazel": "bf81793bd6d2ad89a37a40693e56c61b0ee30f7a7fdbaf3eabbf5f39de47dea2", "https://bcr.bazel.build/modules/rules_proto/7.0.2/source.json": "1e5e7260ae32ef4f2b52fd1d0de8d03b606a44c91b694d2f1afb1d3b28a48ce1", "https://bcr.bazel.build/modules/rules_python/0.10.2/MODULE.bazel": "cc82bc96f2997baa545ab3ce73f196d040ffb8756fd2d66125a530031cd90e5f", "https://bcr.bazel.build/modules/rules_python/0.23.1/MODULE.bazel": "49ffccf0511cb8414de28321f5fcf2a31312b47c40cc21577144b7447f2bf300", "https://bcr.bazel.build/modules/rules_python/0.25.0/MODULE.bazel": "72f1506841c920a1afec76975b35312410eea3aa7b63267436bfb1dd91d2d382", "https://bcr.bazel.build/modules/rules_python/0.28.0/MODULE.bazel": "cba2573d870babc976664a912539b320cbaa7114cd3e8f053c720171cde331ed", "https://bcr.bazel.build/modules/rules_python/0.31.0/MODULE.bazel": "93a43dc47ee570e6ec9f5779b2e64c1476a6ce921c48cc9a1678a91dd5f8fd58", "https://bcr.bazel.build/modules/rules_python/0.4.0/MODULE.bazel": "9208ee05fd48bf09ac60ed269791cf17fb343db56c8226a720fbb1cdf467166c", "https://bcr.bazel.build/modules/rules_python/0.40.0/MODULE.bazel": "9d1a3cd88ed7d8e39583d9ffe56ae8a244f67783ae89b60caafc9f5cf318ada7", "https://bcr.bazel.build/modules/rules_python/0.40.0/source.json": "939d4bd2e3110f27bfb360292986bb79fd8dcefb874358ccd6cdaa7bda029320", "https://bcr.bazel.build/modules/rules_shell/0.2.0/MODULE.bazel": "fda8a652ab3c7d8fee214de05e7a9916d8b28082234e8d2c0094505c5268ed3c", "https://bcr.bazel.build/modules/rules_shell/0.3.0/MODULE.bazel": "de4402cd12f4cc8fda2354fce179fdb068c0b9ca1ec2d2b17b3e21b24c1a937b", "https://bcr.bazel.build/modules/rules_shell/0.3.0/source.json": "c55ed591aa5009401ddf80ded9762ac32c358d2517ee7820be981e2de9756cf3", "https://bcr.bazel.build/modules/stardoc/0.5.1/MODULE.bazel": "1a05d92974d0c122f5ccf09291442580317cdd859f07a8655f1db9a60374f9f8", "https://bcr.bazel.build/modules/stardoc/0.5.3/MODULE.bazel": "c7f6948dae6999bf0db32c1858ae345f112cacf98f174c7a8bb707e41b974f1c", "https://bcr.bazel.build/modules/stardoc/0.5.6/MODULE.bazel": "c43dabc564990eeab55e25ed61c07a1aadafe9ece96a4efabb3f8bf9063b71ef", "https://bcr.bazel.build/modules/stardoc/0.6.2/MODULE.bazel": "7060193196395f5dd668eda046ccbeacebfd98efc77fed418dbe2b82ffaa39fd", "https://bcr.bazel.build/modules/stardoc/0.7.0/MODULE.bazel": "05e3d6d30c099b6770e97da986c53bd31844d7f13d41412480ea265ac9e8079c", "https://bcr.bazel.build/modules/stardoc/0.7.1/MODULE.bazel": "3548faea4ee5dda5580f9af150e79d0f6aea934fc60c1cc50f4efdd9420759e7", "https://bcr.bazel.build/modules/stardoc/0.7.1/source.json": "b6500ffcd7b48cd72c29bb67bcac781e12701cc0d6d55d266a652583cfcdab01", "https://bcr.bazel.build/modules/upb/0.0.0-20220923-a547704/MODULE.bazel": "7298990c00040a0e2f121f6c32544bab27d4452f80d9ce51349b1a28f3005c43", "https://bcr.bazel.build/modules/zlib/1.2.11/MODULE.bazel": "07b389abc85fdbca459b69e2ec656ae5622873af3f845e1c9d80fe179f3effa0", "https://bcr.bazel.build/modules/zlib/1.2.12/MODULE.bazel": "3b1a8834ada2a883674be8cbd36ede1b6ec481477ada359cd2d3ddc562340b27", "https://bcr.bazel.build/modules/zlib/1.3.1.bcr.5/MODULE.bazel": "eec517b5bbe5492629466e11dae908d043364302283de25581e3eb944326c4ca", "https://bcr.bazel.build/modules/zlib/1.3.1.bcr.5/source.json": "22bc55c47af97246cfc093d0acf683a7869377de362b5d1c552c2c2e16b7a806", "https://bcr.bazel.build/modules/zlib/1.3.1/MODULE.bazel": "751c9940dcfe869f5f7274e1295422a34623555916eb98c174c1e945594bf198" }, "selectedYankedVersions": {}, "moduleExtensions": { "@@rules_kotlin+//src/main/starlark/core/repositories:bzlmod_setup.bzl%rules_kotlin_extensions": { "general": { "bzlTransitiveDigest": "hUTp2w+RUVdL7ma5esCXZJAFnX7vLbVfLd7FwnQI6bU=", "usagesDigest": "QI2z8ZUR+mqtbwsf2fLqYdJAkPOHdOV+tF2yVAUgRzw=", "recordedFileInputs": {}, "recordedDirentsInputs": {}, "envVariables": {}, "generatedRepoSpecs": { "com_github_jetbrains_kotlin_git": { "repoRuleId": "@@rules_kotlin+//src/main/starlark/core/repositories:compiler.bzl%kotlin_compiler_git_repository", "attributes": { "urls": [ "https://github.com/JetBrains/kotlin/releases/download/v1.9.23/kotlin-compiler-1.9.23.zip" ], "sha256": "93137d3aab9afa9b27cb06a824c2324195c6b6f6179d8a8653f440f5bd58be88" } }, "com_github_jetbrains_kotlin": { "repoRuleId": "@@rules_kotlin+//src/main/starlark/core/repositories:compiler.bzl%kotlin_capabilities_repository", "attributes": { "git_repository_name": "com_github_jetbrains_kotlin_git", "compiler_version": "1.9.23" } }, "com_github_google_ksp": { "repoRuleId": "@@rules_kotlin+//src/main/starlark/core/repositories:ksp.bzl%ksp_compiler_plugin_repository", "attributes": { "urls": [ "https://github.com/google/ksp/releases/download/1.9.23-1.0.20/artifacts.zip" ], "sha256": "ee0618755913ef7fd6511288a232e8fad24838b9af6ea73972a76e81053c8c2d", "strip_version": "1.9.23-1.0.20" } }, "com_github_pinterest_ktlint": { "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_file", "attributes": { "sha256": "01b2e0ef893383a50dbeb13970fe7fa3be36ca3e83259e01649945b09d736985", "urls": [ "https://github.com/pinterest/ktlint/releases/download/1.3.0/ktlint" ], "executable": true } }, "rules_android": { "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", "attributes": { "sha256": "cd06d15dd8bb59926e4d65f9003bfc20f9da4b2519985c27e190cddc8b7a7806", "strip_prefix": "rules_android-0.1.1", "urls": [ "https://github.com/bazelbuild/rules_android/archive/v0.1.1.zip" ] } } }, "recordedRepoMappingEntries": [ [ "rules_kotlin+", "bazel_tools", "bazel_tools" ] ] } } } } golang-github-lestrrat-go-jwx-3.0.13/Makefile000066400000000000000000000036131515060566400210470ustar00rootroot00000000000000.PHONY: generate realclean cover viewcover test lint check_diffs imports tidy jwx generate: @go generate @$(MAKE) generate-jwa generate-jwe generate-jwk generate-jws generate-jwt @./tools/cmd/gofmt.sh generate-%: @go generate $(shell pwd -P)/$(patsubst generate-%,%,$@) realclean: rm coverage.out test-cmd: env TESTOPTS="$(TESTOPTS)" ./tools/test.sh test: $(MAKE) test-stdlib TESTOPTS= test-stdlib: $(MAKE) test-cmd TESTOPTS= test-goccy: $(MAKE) test-cmd TESTOPTS="-tags jwx_goccy" test-es256k: $(MAKE) test-cmd TESTOPTS="-tags jwx_es256k" test-secp256k1-pem: $(MAKE) test-cmd TESTOPTS="-tags jwx_es256k,jwx_secp256k1_pem" test-asmbase64: $(MAKE) test-cmd TESTOPTS="-tags jwx_asmbase64" test-alltags: $(MAKE) test-cmd TESTOPTS="-tags jwx_asmbase64,jwx_goccy,jwx_es256k,jwx_secp256k1_pem" cover-cmd: env MODE=cover ./tools/test.sh cover: $(MAKE) cover-stdlib cover-stdlib: $(MAKE) cover-cmd TESTOPTS= cover-goccy: $(MAKE) cover-cmd TESTOPTS="-tags jwx_goccy" cover-es256k: $(MAKE) cover-cmd TESTOPTS="-tags jwx_es256k" cover-secp256k1-pem: $(MAKE) cover-cmd TESTOPTS="-tags jwx_es256k,jwx_secp256k1" cover-asmbase64: $(MAKE) cover-cmd TESTOPTS="-tags jwx_asmbase64" cover-alltags: $(MAKE) cover-cmd TESTOPTS="-tags jwx_asmbase64,jwx_goccy,jwx_es256k,jwx_secp256k1_pem" smoke-cmd: env MODE=short ./tools/test.sh smoke: $(MAKE) smoke-stdlib smoke-stdlib: $(MAKE) smoke-cmd TESTOPTS= smoke-goccy: $(MAKE) smoke-cmd TESTOPTS="-tags jwx_goccy" smoke-es256k: $(MAKE) smoke-cmd TESTOPTS="-tags jwx_es256k" smoke-secp256k1-pem: $(MAKE) smoke-cmd TESTOPTS="-tags jwx_es256k,jwx_secp256k1_pem" smoke-alltags: $(MAKE) smoke-cmd TESTOPTS="-tags jwx_goccy,jwx_es256k,jwx_secp256k1_pem" viewcover: go tool cover -html=coverage.out lint: golangci-lint run ./... check_diffs: ./scripts/check-diff.sh imports: goimports -w ./ tidy: ./scripts/tidy.sh jwx: @./tools/cmd/install-jwx.sh golang-github-lestrrat-go-jwx-3.0.13/README.md000066400000000000000000000273571515060566400207010ustar00rootroot00000000000000# github.com/lestrrat-go/jwx/v3 [![CI](https://github.com/lestrrat-go/jwx/actions/workflows/ci.yml/badge.svg)](https://github.com/lestrrat-go/jwx/actions/workflows/ci.yml) [![Go Reference](https://pkg.go.dev/badge/github.com/lestrrat-go/jwx/v3.svg)](https://pkg.go.dev/github.com/lestrrat-go/jwx/v3) [![codecov.io](https://codecov.io/github/lestrrat-go/jwx/coverage.svg?branch=v3)](https://codecov.io/github/lestrrat-go/jwx?branch=v3) Go module implementing various JWx (JWA/JWE/JWK/JWS/JWT, otherwise known as JOSE) technologies. If you are using this module in your product or your company, please add your product and/or company name in the [Wiki](https://github.com/lestrrat-go/jwx/wiki/Users)! It really helps keeping up our motivation. # Features * Complete coverage of JWA/JWE/JWK/JWS/JWT, not just JWT+minimum tool set. * Supports JWS messages with multiple signatures, both compact and JSON serialization * Supports JWS with detached payload * 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 convention * jws.Parse/Verify/Sign * jwe.Parse/Encrypt/Decrypt * Arguments are organized as explicit required parameters and optional WithXXXX() style options. * Extra utilities * `jwk.Cache` to always keep a JWKS up-to-date * [bazel](https://bazel.build)-ready Some more in-depth discussion on why you might want to use this library over others can be found in the [Description section](#description) If you are using v0 or v1, you are strongly encouraged to migrate to using v3 (the version that comes with the README you are reading). # SYNOPSIS ```go package examples_test import ( "bytes" "fmt" "net/http" "time" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/lestrrat-go/jwx/v3/jwe" "github.com/lestrrat-go/jwx/v3/jwk" "github.com/lestrrat-go/jwx/v3/jws" "github.com/lestrrat-go/jwx/v3/jwt" ) func Example() { // Parse, serialize, slice and dice JWKs! privkey, err := jwk.ParseKey(jsonRSAPrivateKey) if err != nil { fmt.Printf("failed to parse JWK: %s\n", err) return } pubkey, err := jwk.PublicKeyOf(privkey) if err != nil { fmt.Printf("failed to get public key: %s\n", err) return } // Work with JWTs! { // Build a JWT! tok, err := jwt.NewBuilder(). Issuer(`github.com/lestrrat-go/jwx`). IssuedAt(time.Now()). Build() if err != nil { fmt.Printf("failed to build token: %s\n", err) return } // Sign a JWT! signed, err := jwt.Sign(tok, jwt.WithKey(jwa.RS256(), privkey)) if err != nil { fmt.Printf("failed to sign token: %s\n", err) return } // Verify a JWT! { verifiedToken, err := jwt.Parse(signed, jwt.WithKey(jwa.RS256(), pubkey)) if err != nil { fmt.Printf("failed to verify JWS: %s\n", err) return } _ = verifiedToken } // Work with *http.Request! { req, err := http.NewRequest(http.MethodGet, `https://github.com/lestrrat-go/jwx`, nil) req.Header.Set(`Authorization`, fmt.Sprintf(`Bearer %s`, signed)) verifiedToken, err := jwt.ParseRequest(req, jwt.WithKey(jwa.RS256(), pubkey)) if err != nil { fmt.Printf("failed to verify token from HTTP request: %s\n", err) return } _ = verifiedToken } } // Encrypt and Decrypt arbitrary payload with JWE! { encrypted, err := jwe.Encrypt(payloadLoremIpsum, jwe.WithKey(jwa.RSA_OAEP(), jwkRSAPublicKey)) if err != nil { fmt.Printf("failed to encrypt payload: %s\n", err) return } decrypted, err := jwe.Decrypt(encrypted, jwe.WithKey(jwa.RSA_OAEP(), jwkRSAPrivateKey)) if err != nil { fmt.Printf("failed to decrypt payload: %s\n", err) return } if !bytes.Equal(decrypted, payloadLoremIpsum) { fmt.Printf("verified payload did not match\n") return } } // Sign and Verify arbitrary payload with JWS! { signed, err := jws.Sign(payloadLoremIpsum, jws.WithKey(jwa.RS256(), jwkRSAPrivateKey)) if err != nil { fmt.Printf("failed to sign payload: %s\n", err) return } verified, err := jws.Verify(signed, jws.WithKey(jwa.RS256(), jwkRSAPublicKey)) if err != nil { fmt.Printf("failed to verify payload: %s\n", err) return } if !bytes.Equal(verified, payloadLoremIpsum) { fmt.Printf("verified payload did not match\n") return } } // OUTPUT: } ``` source: [examples/jwx_readme_example_test.go](https://github.com/lestrrat-go/jwx/blob/v3/examples/jwx_readme_example_test.go) # How-to Documentation * [API documentation](https://pkg.go.dev/github.com/lestrrat-go/jwx/v3) * [How-to style documentation](./docs) * [Runnable Examples](./examples) # Description This Go module implements JWA, JWE, JWK, JWS, and JWT. Please see the following table for the list of available packages: | Package name | Notes | |-----------------------------------------------------------|-------------------------------------------------| | [jwt](https://github.com/lestrrat-go/jwx/tree/v3/jwt) | [RFC 7519](https://tools.ietf.org/html/rfc7519) | | [jwk](https://github.com/lestrrat-go/jwx/tree/v3/jwk) | [RFC 7517](https://tools.ietf.org/html/rfc7517) + [RFC 7638](https://tools.ietf.org/html/rfc7638) | | [jwa](https://github.com/lestrrat-go/jwx/tree/v3/jwa) | [RFC 7518](https://tools.ietf.org/html/rfc7518) | | [jws](https://github.com/lestrrat-go/jwx/tree/v3/jws) | [RFC 7515](https://tools.ietf.org/html/rfc7515) + [RFC 7797](https://tools.ietf.org/html/rfc7797) | | [jwe](https://github.com/lestrrat-go/jwx/tree/v3/jwe) | [RFC 7516](https://tools.ietf.org/html/rfc7516) | ## History My goal was to write a server that heavily uses JWK and JWT. At first glance the libraries that already exist seemed sufficient, but soon I realized that 1. To completely implement the protocols, I needed the entire JWT, JWK, JWS, JWE (and JWA, by necessity). 2. Most of the libraries that existed only deal with a subset of the various JWx specifications that were necessary to implement their specific needs For example, a certain library looks like it had most of JWS, JWE, JWK covered, but then it lacked the ability to include private claims in its JWT responses. Another library had support of all the private claims, but completely lacked in its flexibility to generate various different response formats. Because I was writing the server side (and the client side for testing), I needed the *entire* JOSE toolset to properly implement my server, **and** they needed to be *flexible* enough to fulfill the entire spec that I was writing. So here's `github.com/lestrrat-go/jwx/v3`. This library is extensible, customizable, and hopefully well organized to the point that it is easy for you to slice and dice it. ## Why would I use this library? There are several other major Go modules that handle JWT and related data formats, so why should you use this library? From a purely functional perspective, the only major difference is this: Whereas most other projects only deal with what they seem necessary to handle JWTs, this module handles the **_entire_** spectrum of JWS, JWE, JWK, and JWT. That is, if you need to not only parse JWTs, but also to control JWKs, or if you need to handle payloads that are NOT JWTs, you should probably consider using this module. You should also note that JWT is built _on top_ of those other technologies. You simply cannot have a complete JWT package without implementing the entirety of JWS/JWE/JWK, which this library does. Next, from an implementation perspective, this module differs significantly from others in that it tries very hard to expose only the APIs, and not the internal data. For example, individual JWT claims are not accessible through struct field lookups. You need to use one of the getter methods. This is because this library takes the stance that the end user is fully capable and even willing to shoot themselves on the foot when presented with a lax API. By making sure that users do not have access to open structs, we can protect users from doing silly things like creating _incomplete_ structs, or access the structs concurrently without any protection. This structure also allows us to put extra smarts in the structs, such as doing the right thing when you want to parse / write custom fields (this module does not require the user to specify alternate structs to parse objects with custom fields) In the end I think it comes down to your usage pattern, and priorities. Some general guidelines that come to mind are: * If you want a single library to handle everything JWx, such as using JWE, JWK, JWS, handling [auto-refreshing JWKs](https://github.com/lestrrat-go/jwx/blob/v3/docs/04-jwk.md#auto-refreshing-remote-keys), use this module. * If you want to honor all possible custom fields transparently, use this module. * If you want a standardized clean API, use this module. Otherwise, feel free to choose something else. # Contributions ## Issues For bug reports and feature requests, please try to follow the issue templates as much as possible. For either bug reports or feature requests, failing tests are even better. ## Pull Requests Please make sure to include tests that exercise the changes you made. If you are editing auto-generated files (those files with the `_gen.go` suffix, please make sure that you do the following: 1. Edit the generator, not the generated files (e.g. internal/cmd/genreadfile/main.go) 2. Run `make generate` (or `go generate`) to generate the new code 3. Commit _both_ the generator _and_ the generated files ## Discussions / Usage Please try [discussions](https://github.com/lestrrat-go/jwx/tree/v3/discussions) first. # Related Modules * [github.com/lestrrat-go/echo-middleware-jwx](https://github.com/lestrrat-go/echo-middleware-jwx) - Sample Echo middleware * [github.com/jwx-go/crypto-signer/gcp](https://github.com/jwx-go/crypto-signer/tree/main/gcp) - GCP KMS wrapper that implements [`crypto.Signer`](https://pkg.go.dev/crypto#Signer) * [github.com/jwx-go/crypto-signer/aws](https://github.com/jwx-go/crypto-signer/tree/main/aws) - AWS KMS wrapper that implements [`crypto.Signer`](https://pkg.go.dev/crypto#Signer) # Credits * Initial work on this library was generously sponsored by HDE Inc (https://www.hde.co.jp) * Lots of code, especially JWE was initially taken from go-jose library (https://github.com/square/go-jose) * Lots of individual contributors have helped this project over the years. Thank each and everyone of you very much. # Quid pro quo If you use this software to build products in a for-profit organization, we ask you to _consider_ contributing back to FOSS in the following manner: * For every 100 employees (direct hires) of your organization, please consider contributing minimum of $1 every year to either this project, **or** another FOSS projects that this project uses. For example, for 100 employees, we ask you contribute $100 yearly; for 10,000 employees, we ask you contribute $10,000 yearly. * If possible, please make this information public. You do not need to disclose the amount you are contributing, but please make the information that you are contributing to particular FOSS projects public. For this project, please consider writing your name on the [Wiki](https://github.com/lestrrat-go/jwx/wiki/Users) This is _NOT_ a licensing term: you are still free to use this software according to the license it comes with. This clause is only a plea for people to acknowledge the work from FOSS developers whose work you rely on each and everyday. golang-github-lestrrat-go-jwx-3.0.13/SECURITY.md000066400000000000000000000012761515060566400212030ustar00rootroot00000000000000# Security Policy ## Supported Versions Most recent two major versions will receive security updates | Version | Supported | | -------- | ------------------ | | v3.x.x | :white_check_mark: | | v2.x.x | :white_check_mark: | | < v2.0.0 | :x: | ## Reporting a Vulnerability If you think you found a vulnerability, please report it via [GitHub Security Advisory](https://github.com/lestrrat-go/jwx/security/advisories/new). Please include explicit steps to reproduce the security issue. We will do our best to respond in a timely manner, but please also be aware that this project is maintained by a very limited number of people. Please help us with test code and such. golang-github-lestrrat-go-jwx-3.0.13/WORKSPACE000066400000000000000000000001421515060566400206620ustar00rootroot00000000000000# Empty WORKSPACE file for bzlmod compatibility # All dependencies are now managed in MODULE.bazelgolang-github-lestrrat-go-jwx-3.0.13/bench/000077500000000000000000000000001515060566400204635ustar00rootroot00000000000000golang-github-lestrrat-go-jwx-3.0.13/bench/README.md000066400000000000000000000001641515060566400217430ustar00rootroot00000000000000# Benchmarks # Performance Benchmarks [Measures the performance of github.com/lestrrat-go/jwx/v3](./performance) golang-github-lestrrat-go-jwx-3.0.13/bench/comparison/000077500000000000000000000000001515060566400226355ustar00rootroot00000000000000golang-github-lestrrat-go-jwx-3.0.13/bench/comparison/go.mod000066400000000000000000000014731515060566400237500ustar00rootroot00000000000000module github.com/lestrrat-go/jwx/bench/comparison go 1.24.0 toolchain go1.24.4 require ( github.com/golang-jwt/jwt/v5 v5.2.2 github.com/lestrrat-go/jwx/v3 v3.0.3 ) require ( github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect github.com/goccy/go-json v0.10.3 // indirect github.com/lestrrat-go/blackmagic v1.0.4 // indirect github.com/lestrrat-go/dsig v1.0.0 // indirect github.com/lestrrat-go/dsig-secp256k1 v1.0.0 // indirect github.com/lestrrat-go/httpcc v1.0.1 // indirect github.com/lestrrat-go/httprc/v3 v3.0.2 // indirect github.com/lestrrat-go/option/v2 v2.0.0 // indirect github.com/segmentio/asm v1.2.1 // indirect github.com/valyala/fastjson v1.6.7 // indirect golang.org/x/crypto v0.46.0 // indirect golang.org/x/sys v0.39.0 // indirect ) replace github.com/lestrrat-go/jwx/v3 => ../.. golang-github-lestrrat-go-jwx-3.0.13/bench/comparison/go.sum000066400000000000000000000065661515060566400240050ustar00rootroot00000000000000github.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/dcrec/secp256k1/v4 v4.4.0 h1:NMZiJj8QnKe1LgsbDayM4UoHwbvwDRwnI3hwNaAHRnc= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40= github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA= github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8= github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/lestrrat-go/blackmagic v1.0.4 h1:IwQibdnf8l2KoO+qC3uT4OaTWsW7tuRQXy9TRN9QanA= github.com/lestrrat-go/blackmagic v1.0.4/go.mod h1:6AWFyKNNj0zEXQYfTMPfZrAXUWUfTIZ5ECEUEJaijtw= github.com/lestrrat-go/dsig v1.0.0 h1:OE09s2r9Z81kxzJYRn07TFM9XA4akrUdoMwr0L8xj38= github.com/lestrrat-go/dsig v1.0.0/go.mod h1:dEgoOYYEJvW6XGbLasr8TFcAxoWrKlbQvmJgCR0qkDo= github.com/lestrrat-go/dsig-secp256k1 v1.0.0 h1:JpDe4Aybfl0soBvoVwjqDbp+9S1Y2OM7gcrVVMFPOzY= github.com/lestrrat-go/dsig-secp256k1 v1.0.0/go.mod h1:CxUgAhssb8FToqbL8NjSPoGQlnO4w3LG1P0qPWQm/NU= github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE= github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E= github.com/lestrrat-go/httprc/v3 v3.0.2 h1:7u4HUaD0NQbf2/n5+fyp+T10hNCsAnwKfqn4A4Baif0= github.com/lestrrat-go/httprc/v3 v3.0.2/go.mod h1:mSMtkZW92Z98M5YoNNztbRGxbXHql7tSitCvaxvo9l0= github.com/lestrrat-go/option/v2 v2.0.0 h1:XxrcaJESE1fokHy3FpaQ/cXW8ZsIdWcdFzzLOcID3Ss= github.com/lestrrat-go/option/v2 v2.0.0/go.mod h1:oSySsmzMoR0iRzCDCaUfsCzxQHUEuhOViQObyy7S6Vg= 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.1 h1:DTNbBqs57ioxAD4PrArqftgypG4/qNpXoJx8TVXxPR0= github.com/segmentio/asm v1.2.1/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/valyala/fastjson v1.6.7 h1:ZE4tRy0CIkh+qDc5McjatheGX2czdn8slQjomexVpBM= github.com/valyala/fastjson v1.6.7/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY= golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU= golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0= golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= golang-github-lestrrat-go-jwx-3.0.13/bench/comparison/golang_jwt_bench_test.go000066400000000000000000000066121515060566400275220ustar00rootroot00000000000000package comparison import ( "crypto/rand" "crypto/rsa" "testing" "time" "github.com/golang-jwt/jwt/v5" ) var ( golangJwtRSAKey *rsa.PrivateKey golangJwtHMACKey []byte golangJwtToken string golangJwtTokenRS256 string ) func init() { // Generate RSA key for testing var err error golangJwtRSAKey, err = rsa.GenerateKey(rand.Reader, 2048) if err != nil { panic(err) } // Generate HMAC key golangJwtHMACKey = make([]byte, 32) if _, err := rand.Read(golangJwtHMACKey); err != nil { panic(err) } // Pre-generate tokens for parsing benchmarks claims := jwt.MapClaims{ "sub": "1234567890", "name": "John Doe", "iat": time.Now().Unix(), "exp": time.Now().Add(time.Hour).Unix(), } // HS256 token token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) golangJwtToken, err = token.SignedString(golangJwtHMACKey) if err != nil { panic(err) } // RS256 token tokenRS := jwt.NewWithClaims(jwt.SigningMethodRS256, claims) golangJwtTokenRS256, err = tokenRS.SignedString(golangJwtRSAKey) if err != nil { panic(err) } } func BenchmarkGolangJWT_SignHS256(b *testing.B) { claims := jwt.MapClaims{ "sub": "1234567890", "name": "John Doe", "iat": time.Now().Unix(), "exp": time.Now().Add(time.Hour).Unix(), } b.ResetTimer() for range b.N { token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) _, err := token.SignedString(golangJwtHMACKey) if err != nil { b.Fatal(err) } } } func BenchmarkGolangJWT_SignRS256(b *testing.B) { claims := jwt.MapClaims{ "sub": "1234567890", "name": "John Doe", "iat": time.Now().Unix(), "exp": time.Now().Add(time.Hour).Unix(), } b.ResetTimer() for range b.N { token := jwt.NewWithClaims(jwt.SigningMethodRS256, claims) _, err := token.SignedString(golangJwtRSAKey) if err != nil { b.Fatal(err) } } } func BenchmarkGolangJWT_ParseHS256(b *testing.B) { b.ResetTimer() for range b.N { token, err := jwt.Parse(golangJwtToken, func(_ *jwt.Token) (any, error) { return golangJwtHMACKey, nil }) if err != nil { b.Fatal(err) } if !token.Valid { b.Fatal("token is not valid") } } } func BenchmarkGolangJWT_ParseRS256(b *testing.B) { b.ResetTimer() for range b.N { token, err := jwt.Parse(golangJwtTokenRS256, func(_ *jwt.Token) (any, error) { return &golangJwtRSAKey.PublicKey, nil }) if err != nil { b.Fatal(err) } if !token.Valid { b.Fatal("token is not valid") } } } func BenchmarkGolangJWT_ParseWithClaims(b *testing.B) { claims := &jwt.MapClaims{} b.ResetTimer() for range b.N { token, err := jwt.ParseWithClaims(golangJwtToken, claims, func(_ *jwt.Token) (any, error) { return golangJwtHMACKey, nil }) if err != nil { b.Fatal(err) } if !token.Valid { b.Fatal("token is not valid") } } } func BenchmarkGolangJWT_CreateAndParseHS256(b *testing.B) { claims := jwt.MapClaims{ "sub": "1234567890", "name": "John Doe", "iat": time.Now().Unix(), "exp": time.Now().Add(time.Hour).Unix(), } b.ResetTimer() for range b.N { // Create token token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) tokenString, err := token.SignedString(golangJwtHMACKey) if err != nil { b.Fatal(err) } // Parse token parsedToken, err := jwt.Parse(tokenString, func(_ *jwt.Token) (any, error) { return golangJwtHMACKey, nil }) if err != nil { b.Fatal(err) } if !parsedToken.Valid { b.Fatal("token is not valid") } } } golang-github-lestrrat-go-jwx-3.0.13/bench/comparison/lestrrat_bench_test.go000066400000000000000000000102611515060566400272220ustar00rootroot00000000000000package comparison import ( "crypto/rand" "crypto/rsa" "testing" "time" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/lestrrat-go/jwx/v3/jwk" "github.com/lestrrat-go/jwx/v3/jws" "github.com/lestrrat-go/jwx/v3/jwt" ) var ( jwxRSAKey jwk.Key jwxHMACKey jwk.Key jwxToken []byte jwxTokenRS256 []byte ) func init() { // Generate RSA key for testing rsaKey, err := rsa.GenerateKey(rand.Reader, 2048) if err != nil { panic(err) } jwxRSAKey, err = jwk.Import(rsaKey) if err != nil { panic(err) } // Generate HMAC key hmacKeyBytes := make([]byte, 32) if _, err := rand.Read(hmacKeyBytes); err != nil { panic(err) } jwxHMACKey, err = jwk.Import(hmacKeyBytes) if err != nil { panic(err) } // Pre-generate tokens for parsing benchmarks tok := jwt.New() tok.Set(jwt.SubjectKey, "1234567890") tok.Set("name", "John Doe") tok.Set(jwt.IssuedAtKey, time.Now()) tok.Set(jwt.ExpirationKey, time.Now().Add(time.Hour)) // HS256 token jwxToken, err = jwt.Sign(tok, jwt.WithKey(jwa.HS256(), jwxHMACKey)) if err != nil { panic(err) } // RS256 token jwxTokenRS256, err = jwt.Sign(tok, jwt.WithKey(jwa.RS256(), jwxRSAKey)) if err != nil { panic(err) } } func BenchmarkJWX_JWTSignHS256(b *testing.B) { tok := jwt.New() tok.Set(jwt.SubjectKey, "1234567890") tok.Set("name", "John Doe") tok.Set(jwt.IssuedAtKey, time.Now()) tok.Set(jwt.ExpirationKey, time.Now().Add(time.Hour)) options := []jwt.SignOption{jwt.WithKey(jwa.HS256(), jwxHMACKey)} b.ResetTimer() for range b.N { _, err := jwt.Sign(tok, options...) if err != nil { b.Fatal(err) } } } func BenchmarkJWX_JWTSignRS256(b *testing.B) { tok := jwt.New() tok.Set(jwt.SubjectKey, "1234567890") tok.Set("name", "John Doe") tok.Set(jwt.IssuedAtKey, time.Now()) tok.Set(jwt.ExpirationKey, time.Now().Add(time.Hour)) options := []jwt.SignOption{jwt.WithKey(jwa.RS256(), jwxRSAKey)} b.ResetTimer() for range b.N { _, err := jwt.Sign(tok, options...) if err != nil { b.Fatal(err) } } } func BenchmarkJWX_JWTParseHS256(b *testing.B) { options := []jwt.ParseOption{jwt.WithKey(jwa.HS256(), jwxHMACKey)} b.ResetTimer() for range b.N { _, err := jwt.Parse(jwxToken, options...) if err != nil { b.Fatal(err) } } } func BenchmarkJWX_JWTParseRS256(b *testing.B) { publicKey, err := jwxRSAKey.PublicKey() if err != nil { b.Fatal(err) } options := []jwt.ParseOption{jwt.WithKey(jwa.RS256(), publicKey)} b.ResetTimer() for range b.N { _, err := jwt.Parse(jwxTokenRS256, options...) if err != nil { b.Fatal(err) } } } func BenchmarkJWX_JWTParseWithValidation(b *testing.B) { options := []jwt.ParseOption{jwt.WithKey(jwa.HS256(), jwxHMACKey), jwt.WithValidate(true)} b.ResetTimer() for range b.N { _, err := jwt.Parse(jwxToken, options...) if err != nil { b.Fatal(err) } } } func BenchmarkJWX_CreateAndParseHS256(b *testing.B) { tok := jwt.New() tok.Set(jwt.SubjectKey, "1234567890") tok.Set("name", "John Doe") tok.Set(jwt.IssuedAtKey, time.Now()) tok.Set(jwt.ExpirationKey, time.Now().Add(time.Hour)) b.ResetTimer() signoptions := []jwt.SignOption{jwt.WithKey(jwa.HS256(), jwxHMACKey)} parseoptions := []jwt.ParseOption{jwt.WithKey(jwa.HS256(), jwxHMACKey)} for range b.N { // Create token tokenBytes, err := jwt.Sign(tok, signoptions...) if err != nil { b.Fatal(err) } // Parse token _, err = jwt.Parse(tokenBytes, parseoptions...) if err != nil { b.Fatal(err) } } } func BenchmarkJWX_JWSSignHS256(b *testing.B) { payload := []byte(`{"sub":"1234567890","name":"John Doe","iat":1516239022}`) options := []jws.SignOption{jws.WithKey(jwa.HS256(), jwxHMACKey)} b.ResetTimer() for range b.N { _, err := jws.Sign(payload, options...) if err != nil { b.Fatal(err) } } } func BenchmarkJWX_JWSParseHS256(b *testing.B) { payload := []byte(`{"sub":"1234567890","name":"John Doe","iat":1516239022}`) signoptions := []jws.SignOption{jws.WithKey(jwa.HS256(), jwxHMACKey)} signature, err := jws.Sign(payload, signoptions...) if err != nil { b.Fatal(err) } parseoptions := []jws.VerifyOption{jws.WithKey(jwa.HS256(), jwxHMACKey)} b.ResetTimer() for range b.N { _, err := jws.Verify(signature, parseoptions...) if err != nil { b.Fatal(err) } } } golang-github-lestrrat-go-jwx-3.0.13/bench/performance/000077500000000000000000000000001515060566400227645ustar00rootroot00000000000000golang-github-lestrrat-go-jwx-3.0.13/bench/performance/Makefile000066400000000000000000000006631515060566400244310ustar00rootroot00000000000000stdlib: go test -bench . -benchmem -count 5 -timeout 60m | tee stdlib.txt 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 golang-github-lestrrat-go-jwx-3.0.13/bench/performance/README.md000066400000000000000000000357051515060566400242550ustar00rootroot00000000000000# Benchmarks ## Quick benchmark ``` go test -bench . -benchmem ``` ## Full benchmark ``` go test -timeout 60m -bench . -benchmem | tee stdlib.txt ``` ## Switch JSON backends ``` go test -timeout 60m -tags jwx_goccy -bench . -benchmem | tee goccy.txt ``` ## Comparison Go 1.6.2, github.com/goccy/go-json v0.7.4 ``` benchstat -sort -delta stdlib.txt goccy.txt name old time/op new time/op delta JWK/Serialization/RSA/PrivateKey/jwk.ParseString-24 75.1Âĩs Âą 3% 29.3Âĩs Âą 2% -60.98% (p=0.008 n=5+5) JWK/Serialization/RSA/PrivateKey/jwk.Parse-24 73.3Âĩs Âą 1% 28.8Âĩs Âą 1% -60.65% (p=0.008 n=5+5) JWK/Serialization/RSA/PrivateKey/jwk.ParseReader-24 75.2Âĩs Âą 0% 30.0Âĩs Âą 1% -60.09% (p=0.008 n=5+5) JWK/Serialization/EC/PrivateKey/jwk.ParseReader-24 29.6Âĩs Âą 2% 14.4Âĩs Âą 0% -51.38% (p=0.008 n=5+5) JWS/Serialization/JSON/jws.ParseReader-24 39.1Âĩs Âą 2% 19.1Âĩs Âą 1% -51.13% (p=0.008 n=5+5) JWK/Serialization/EC/PrivateKey/jwk.Parse-24 28.7Âĩs Âą 1% 14.3Âĩs Âą 1% -50.09% (p=0.008 n=5+5) JWS/Serialization/JSON/jws.Parse-24 38.6Âĩs Âą 5% 19.3Âĩs Âą 2% -49.90% (p=0.008 n=5+5) JWS/Serialization/JSON/jws.ParseString-24 38.5Âĩs Âą 0% 19.3Âĩs Âą 2% -49.87% (p=0.008 n=5+5) JWK/Serialization/EC/PrivateKey/jwk.ParseString-24 28.8Âĩs Âą 1% 14.6Âĩs Âą 2% -49.35% (p=0.008 n=5+5) JWK/Serialization/RSA/PublicKey/jwk.Parse-24 26.5Âĩs Âą 0% 13.5Âĩs Âą 0% -49.00% (p=0.008 n=5+5) JWK/Serialization/RSA/PublicKey/jwk.ParseString-24 26.8Âĩs Âą 1% 13.7Âĩs Âą 1% -48.76% (p=0.008 n=5+5) JWK/Serialization/RSA/PublicKey/jwk.ParseReader-24 26.9Âĩs Âą 0% 14.0Âĩs Âą 1% -48.00% (p=0.008 n=5+5) JWK/Serialization/EC/PublicKey/jwk.Parse-24 24.2Âĩs Âą 0% 12.7Âĩs Âą 2% -47.72% (p=0.008 n=5+5) JWK/Serialization/EC/PublicKey/jwk.ParseString-24 24.4Âĩs Âą 0% 12.8Âĩs Âą 2% -47.64% (p=0.008 n=5+5) JWK/Serialization/EC/PublicKey/jwk.ParseReader-24 24.5Âĩs Âą 0% 13.1Âĩs Âą 2% -46.70% (p=0.008 n=5+5) JWK/Serialization/Symmetric/PublicKey/jwk.Parse-24 19.0Âĩs Âą 1% 10.9Âĩs Âą 1% -42.51% (p=0.008 n=5+5) JWK/Serialization/Symmetric/PublicKey/jwk.ParseString-24 19.3Âĩs Âą 1% 11.3Âĩs Âą 3% -41.22% (p=0.008 n=5+5) JWK/Serialization/Symmetric/PrivateKey/jwk.ParseString-24 19.2Âĩs Âą 1% 11.4Âĩs Âą 1% -40.63% (p=0.008 n=5+5) JWK/Serialization/Symmetric/PublicKey/jwk.ParseReader-24 19.4Âĩs Âą 1% 11.6Âĩs Âą 2% -40.34% (p=0.008 n=5+5) JWK/Serialization/Symmetric/PrivateKey/jwk.Parse-24 18.9Âĩs Âą 2% 11.4Âĩs Âą 1% -39.91% (p=0.008 n=5+5) JWK/Serialization/Symmetric/PrivateKey/jwk.ParseReader-24 19.3Âĩs Âą 1% 11.7Âĩs Âą 1% -39.59% (p=0.008 n=5+5) JWT/Serialization/Sign/jwt.Parse-24 21.6Âĩs Âą 2% 15.2Âĩs Âą 0% -29.84% (p=0.008 n=5+5) JWS/Serialization/Compact/jws.Parse-24 11.1Âĩs Âą 1% 7.8Âĩs Âą 2% -29.51% (p=0.008 n=5+5) JWS/Serialization/Compact/jws.ParseReader-24 11.7Âĩs Âą 2% 8.3Âĩs Âą 3% -29.22% (p=0.008 n=5+5) JWT/Serialization/Sign/jwt.ParseReader-24 21.8Âĩs Âą 1% 15.6Âĩs Âą 1% -28.58% (p=0.008 n=5+5) JWS/Serialization/Compact/jws.ParseString-24 11.7Âĩs Âą 1% 8.3Âĩs Âą 2% -28.57% (p=0.008 n=5+5) JWT/Serialization/Sign/jwt.ParseString-24 21.8Âĩs Âą 1% 15.6Âĩs Âą 1% -28.25% (p=0.008 n=5+5) JWT/Serialization/JSON/jwt.Parse-24 14.6Âĩs Âą 1% 10.7Âĩs Âą 1% -26.92% (p=0.008 n=5+5) JWT/Serialization/JSON/jwt.ParseReader-24 14.9Âĩs Âą 1% 11.0Âĩs Âą 1% -26.36% (p=0.008 n=5+5) JWT/Serialization/JSON/jwt.ParseString-24 14.6Âĩs Âą 0% 10.8Âĩs Âą 2% -26.04% (p=0.016 n=4+5) JWS/Serialization/JSON/json.Marshal-24 29.8Âĩs Âą 4% 28.3Âĩs Âą 2% -4.98% (p=0.016 n=5+5) JWE/Serialization/JSON/json.Marshal-24 33.5Âĩs Âą 3% 34.4Âĩs Âą 1% ~ (p=0.056 n=5+5) JWK/Serialization/EC/PublicKey/json.Marshal-24 14.7Âĩs Âą 5% 15.3Âĩs Âą 1% ~ (p=0.095 n=5+5) JWK/Serialization/EC/PrivateKey/json.Marshal-24 16.0Âĩs Âą 5% 15.9Âĩs Âą 2% ~ (p=1.000 n=5+5) JWT/Serialization/Sign/jwt.Sign-24 1.22ms Âą 0% 1.22ms Âą 1% ~ (p=0.690 n=5+5) JWK/Serialization/RSA/PublicKey/json.Marshal-24 14.7Âĩs Âą 1% 15.0Âĩs Âą 1% +2.10% (p=0.016 n=5+5) JWK/Serialization/RSA/PrivateKey/json.Marshal-24 27.9Âĩs Âą 1% 28.8Âĩs Âą 1% +3.13% (p=0.008 n=5+5) JWT/Serialization/JSON/json.Unmarshal-24 4.80Âĩs Âą 0% 4.97Âĩs Âą 3% +3.62% (p=0.008 n=5+5) JWT/Serialization/JSON/json.Marshal-24 11.0Âĩs Âą 1% 11.5Âĩs Âą 3% +4.19% (p=0.016 n=5+5) JWK/Serialization/Symmetric/PublicKey/json.Marshal-24 12.1Âĩs Âą 2% 12.7Âĩs Âą 2% +4.68% (p=0.008 n=5+5) JWE/Serialization/JSON/json.Unmarshal-24 8.86Âĩs Âą 0% 9.29Âĩs Âą 1% +4.84% (p=0.008 n=5+5) JWK/Serialization/Symmetric/PrivateKey/json.Marshal-24 12.0Âĩs Âą 0% 12.9Âĩs Âą 4% +7.46% (p=0.008 n=5+5) name old alloc/op new alloc/op delta JWT/Serialization/JSON/jwt.Parse-24 7.80kB Âą 0% 3.48kB Âą 0% -55.38% (p=0.008 n=5+5) JWT/Serialization/JSON/jwt.ParseString-24 7.85kB Âą 0% 3.53kB Âą 0% -55.05% (p=0.008 n=5+5) JWT/Serialization/JSON/jwt.ParseReader-24 8.31kB Âą 0% 3.99kB Âą 0% -51.97% (p=0.008 n=5+5) JWT/Serialization/Sign/jwt.Parse-24 10.8kB Âą 0% 5.7kB Âą 0% -47.68% (p=0.008 n=5+5) JWT/Serialization/Sign/jwt.ParseString-24 11.3kB Âą 0% 6.1kB Âą 0% -45.78% (p=0.008 n=5+5) JWT/Serialization/Sign/jwt.ParseReader-24 11.4kB Âą 0% 6.2kB Âą 0% -45.53% (p=0.008 n=5+5) JWK/Serialization/RSA/PrivateKey/jwk.Parse-24 36.5kB Âą 0% 26.0kB Âą 0% -28.82% (p=0.008 n=5+5) JWS/Serialization/Compact/jws.Parse-24 2.83kB Âą 0% 2.02kB Âą 0% -28.81% (p=0.008 n=5+5) JWK/Serialization/RSA/PrivateKey/jwk.ParseString-24 38.3kB Âą 0% 27.8kB Âą 0% -27.47% (p=0.008 n=5+5) JWS/Serialization/Compact/jws.ParseString-24 3.02kB Âą 0% 2.21kB Âą 0% -26.98% (p=0.008 n=5+5) JWK/Serialization/EC/PublicKey/jwk.Parse-24 7.04kB Âą 0% 5.15kB Âą 0% -26.82% (p=0.008 n=5+5) JWK/Serialization/Symmetric/PublicKey/jwk.Parse-24 6.27kB Âą 0% 4.64kB Âą 0% -26.02% (p=0.008 n=5+5) JWK/Serialization/Symmetric/PrivateKey/jwk.Parse-24 6.27kB Âą 0% 4.64kB Âą 0% -26.02% (p=0.008 n=5+5) JWK/Serialization/EC/PublicKey/jwk.ParseString-24 7.28kB Âą 0% 5.39kB Âą 0% -25.93% (p=0.008 n=5+5) JWK/Serialization/RSA/PrivateKey/jwk.ParseReader-24 41.1kB Âą 0% 30.6kB Âą 0% -25.58% (p=0.008 n=5+5) JWK/Serialization/Symmetric/PublicKey/jwk.ParseString-24 6.40kB Âą 0% 4.77kB Âą 0% -25.50% (p=0.008 n=5+5) JWK/Serialization/Symmetric/PrivateKey/jwk.ParseString-24 6.40kB Âą 0% 4.77kB Âą 0% -25.50% (p=0.008 n=5+5) JWK/Serialization/EC/PublicKey/jwk.ParseReader-24 7.55kB Âą 0% 5.66kB Âą 0% -25.00% (p=0.008 n=5+5) JWS/Serialization/JSON/jws.Parse-24 16.3kB Âą 0% 12.3kB Âą 0% -24.65% (p=0.008 n=5+5) JWS/Serialization/Compact/jws.ParseReader-24 3.34kB Âą 0% 2.53kB Âą 0% -24.40% (p=0.008 n=5+5) JWK/Serialization/EC/PrivateKey/jwk.Parse-24 7.65kB Âą 0% 5.79kB Âą 0% -24.27% (p=0.008 n=5+5) JWK/Serialization/Symmetric/PublicKey/jwk.ParseReader-24 6.78kB Âą 0% 5.15kB Âą 0% -24.06% (p=0.008 n=5+5) JWK/Serialization/Symmetric/PrivateKey/jwk.ParseReader-24 6.78kB Âą 0% 5.15kB Âą 0% -24.06% (p=0.008 n=5+5) JWS/Serialization/JSON/jws.ParseString-24 17.2kB Âą 0% 13.2kB Âą 0% -23.36% (p=0.008 n=5+5) JWK/Serialization/EC/PrivateKey/jwk.ParseString-24 8.00kB Âą 0% 6.14kB Âą 0% -23.20% (p=0.008 n=5+5) JWK/Serialization/EC/PrivateKey/jwk.ParseReader-24 8.16kB Âą 0% 6.30kB Âą 0% -22.75% (p=0.008 n=5+5) JWS/Serialization/JSON/jws.ParseReader-24 17.9kB Âą 0% 13.9kB Âą 0% -22.52% (p=0.008 n=5+5) JWK/Serialization/RSA/PublicKey/jwk.Parse-24 7.22kB Âą 0% 5.71kB Âą 0% -20.84% (p=0.008 n=5+5) JWK/Serialization/RSA/PublicKey/jwk.ParseString-24 7.63kB Âą 0% 6.13kB Âą 0% -19.71% (p=0.008 n=5+5) JWK/Serialization/RSA/PublicKey/jwk.ParseReader-24 7.73kB Âą 0% 6.22kB Âą 0% -19.46% (p=0.008 n=5+5) JWE/Serialization/JSON/json.Unmarshal-24 1.58kB Âą 0% 1.58kB Âą 0% ~ (all equal) JWT/Serialization/Sign/jwt.Sign-24 36.6kB Âą 0% 36.6kB Âą 0% ~ (p=0.548 n=5+5) JWT/Serialization/JSON/json.Unmarshal-24 592B Âą 0% 592B Âą 0% ~ (all equal) JWE/Serialization/JSON/json.Marshal-24 3.98kB Âą 0% 3.99kB Âą 0% +0.24% (p=0.008 n=5+5) JWK/Serialization/EC/PublicKey/json.Marshal-24 1.80kB Âą 0% 1.80kB Âą 0% +0.26% (p=0.008 n=5+5) JWK/Serialization/Symmetric/PublicKey/json.Marshal-24 1.16kB Âą 0% 1.16kB Âą 0% +0.26% (p=0.008 n=5+5) JWK/Serialization/Symmetric/PrivateKey/json.Marshal-24 1.16kB Âą 0% 1.16kB Âą 0% +0.26% (p=0.008 n=5+5) JWK/Serialization/RSA/PrivateKey/json.Marshal-24 8.85kB Âą 0% 8.87kB Âą 0% +0.27% (p=0.008 n=5+5) JWT/Serialization/JSON/json.Marshal-24 722B Âą 0% 724B Âą 0% +0.28% (p=0.008 n=5+5) JWS/Serialization/JSON/json.Marshal-24 5.18kB Âą 0% 5.20kB Âą 0% +0.28% (p=0.008 n=5+5) JWK/Serialization/RSA/PublicKey/json.Marshal-24 2.34kB Âą 0% 2.35kB Âą 0% +0.28% (p=0.016 n=5+4) JWK/Serialization/EC/PrivateKey/json.Marshal-24 2.29kB Âą 0% 2.29kB Âą 0% +0.29% (p=0.016 n=4+5) name old allocs/op new allocs/op delta JWK/Serialization/RSA/PrivateKey/jwk.Parse-24 205 Âą 0% 89 Âą 0% -56.59% (p=0.008 n=5+5) JWK/Serialization/RSA/PrivateKey/jwk.ParseString-24 206 Âą 0% 90 Âą 0% -56.31% (p=0.008 n=5+5) JWK/Serialization/RSA/PrivateKey/jwk.ParseReader-24 209 Âą 0% 93 Âą 0% -55.50% (p=0.008 n=5+5) JWK/Serialization/EC/PrivateKey/jwk.Parse-24 129 Âą 0% 60 Âą 0% -53.49% (p=0.008 n=5+5) JWK/Serialization/EC/PrivateKey/jwk.ParseString-24 130 Âą 0% 61 Âą 0% -53.08% (p=0.008 n=5+5) JWK/Serialization/EC/PrivateKey/jwk.ParseReader-24 130 Âą 0% 61 Âą 0% -53.08% (p=0.008 n=5+5) JWK/Serialization/EC/PublicKey/jwk.Parse-24 112 Âą 0% 54 Âą 0% -51.79% (p=0.008 n=5+5) JWK/Serialization/EC/PublicKey/jwk.ParseString-24 113 Âą 0% 55 Âą 0% -51.33% (p=0.008 n=5+5) JWK/Serialization/EC/PublicKey/jwk.ParseReader-24 113 Âą 0% 55 Âą 0% -51.33% (p=0.008 n=5+5) JWK/Serialization/RSA/PublicKey/jwk.Parse-24 96.0 Âą 0% 50.0 Âą 0% -47.92% (p=0.008 n=5+5) JWK/Serialization/RSA/PublicKey/jwk.ParseString-24 97.0 Âą 0% 51.0 Âą 0% -47.42% (p=0.008 n=5+5) JWK/Serialization/RSA/PublicKey/jwk.ParseReader-24 97.0 Âą 0% 51.0 Âą 0% -47.42% (p=0.008 n=5+5) JWS/Serialization/Compact/jws.Parse-24 49.0 Âą 0% 27.0 Âą 0% -44.90% (p=0.008 n=5+5) JWS/Serialization/Compact/jws.ParseString-24 50.0 Âą 0% 28.0 Âą 0% -44.00% (p=0.008 n=5+5) JWS/Serialization/Compact/jws.ParseReader-24 50.0 Âą 0% 28.0 Âą 0% -44.00% (p=0.008 n=5+5) JWK/Serialization/Symmetric/PublicKey/jwk.Parse-24 81.0 Âą 0% 47.0 Âą 0% -41.98% (p=0.008 n=5+5) JWK/Serialization/Symmetric/PrivateKey/jwk.Parse-24 81.0 Âą 0% 47.0 Âą 0% -41.98% (p=0.008 n=5+5) JWK/Serialization/Symmetric/PublicKey/jwk.ParseString-24 82.0 Âą 0% 48.0 Âą 0% -41.46% (p=0.008 n=5+5) JWK/Serialization/Symmetric/PublicKey/jwk.ParseReader-24 82.0 Âą 0% 48.0 Âą 0% -41.46% (p=0.008 n=5+5) JWK/Serialization/Symmetric/PrivateKey/jwk.ParseString-24 82.0 Âą 0% 48.0 Âą 0% -41.46% (p=0.008 n=5+5) JWK/Serialization/Symmetric/PrivateKey/jwk.ParseReader-24 82.0 Âą 0% 48.0 Âą 0% -41.46% (p=0.008 n=5+5) JWT/Serialization/Sign/jwt.Parse-24 106 Âą 0% 66 Âą 0% -37.74% (p=0.008 n=5+5) JWT/Serialization/Sign/jwt.ParseString-24 107 Âą 0% 67 Âą 0% -37.38% (p=0.008 n=5+5) JWT/Serialization/Sign/jwt.ParseReader-24 107 Âą 0% 67 Âą 0% -37.38% (p=0.008 n=5+5) JWS/Serialization/JSON/jws.Parse-24 153 Âą 0% 100 Âą 0% -34.64% (p=0.008 n=5+5) JWS/Serialization/JSON/jws.ParseString-24 154 Âą 0% 101 Âą 0% -34.42% (p=0.008 n=5+5) JWS/Serialization/JSON/jws.ParseReader-24 155 Âą 0% 102 Âą 0% -34.19% (p=0.008 n=5+5) JWT/Serialization/JSON/jwt.Parse-24 57.0 Âą 0% 39.0 Âą 0% -31.58% (p=0.008 n=5+5) JWT/Serialization/JSON/jwt.ParseString-24 58.0 Âą 0% 40.0 Âą 0% -31.03% (p=0.008 n=5+5) JWT/Serialization/JSON/jwt.ParseReader-24 58.0 Âą 0% 40.0 Âą 0% -31.03% (p=0.008 n=5+5) JWE/Serialization/JSON/json.Marshal-24 45.0 Âą 0% 45.0 Âą 0% ~ (all equal) JWE/Serialization/JSON/json.Unmarshal-24 26.0 Âą 0% 26.0 Âą 0% ~ (all equal) JWK/Serialization/RSA/PublicKey/json.Marshal-24 24.0 Âą 0% 24.0 Âą 0% ~ (all equal) JWK/Serialization/RSA/PrivateKey/json.Marshal-24 51.0 Âą 0% 51.0 Âą 0% ~ (all equal) JWK/Serialization/EC/PublicKey/json.Marshal-24 27.0 Âą 0% 27.0 Âą 0% ~ (all equal) JWK/Serialization/EC/PrivateKey/json.Marshal-24 31.0 Âą 0% 31.0 Âą 0% ~ (all equal) JWK/Serialization/Symmetric/PublicKey/json.Marshal-24 20.0 Âą 0% 20.0 Âą 0% ~ (all equal) JWK/Serialization/Symmetric/PrivateKey/json.Marshal-24 20.0 Âą 0% 20.0 Âą 0% ~ (all equal) JWS/Serialization/JSON/json.Marshal-24 60.0 Âą 0% 60.0 Âą 0% ~ (all equal) JWT/Serialization/Sign/jwt.Sign-24 199 Âą 0% 199 Âą 0% ~ (all equal) JWT/Serialization/JSON/json.Unmarshal-24 10.0 Âą 0% 10.0 Âą 0% ~ (all equal) JWT/Serialization/JSON/json.Marshal-24 18.0 Âą 0% 18.0 Âą 0% ~ (all equal) ``` golang-github-lestrrat-go-jwx-3.0.13/bench/performance/go.mod000066400000000000000000000017231515060566400240750ustar00rootroot00000000000000module github.com/lestrrat-go/jwx/v3/bench/performance go 1.24.0 toolchain go1.24.4 require github.com/lestrrat-go/jwx/v3 v3.0.0 require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect github.com/goccy/go-json v0.10.3 // indirect github.com/lestrrat-go/blackmagic v1.0.4 // indirect github.com/lestrrat-go/dsig v1.0.0 // indirect github.com/lestrrat-go/dsig-secp256k1 v1.0.0 // indirect github.com/lestrrat-go/httpcc v1.0.1 // indirect github.com/lestrrat-go/httprc/v3 v3.0.2 // indirect github.com/lestrrat-go/option/v2 v2.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/segmentio/asm v1.2.1 // indirect github.com/stretchr/testify v1.11.1 // indirect github.com/valyala/fastjson v1.6.7 // indirect golang.org/x/crypto v0.46.0 // indirect golang.org/x/sys v0.39.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) replace github.com/lestrrat-go/jwx/v3 v3.0.0 => ../.. golang-github-lestrrat-go-jwx-3.0.13/bench/performance/go.sum000066400000000000000000000064541515060566400241300ustar00rootroot00000000000000github.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/dcrec/secp256k1/v4 v4.4.0 h1:NMZiJj8QnKe1LgsbDayM4UoHwbvwDRwnI3hwNaAHRnc= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40= github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA= github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/lestrrat-go/blackmagic v1.0.4 h1:IwQibdnf8l2KoO+qC3uT4OaTWsW7tuRQXy9TRN9QanA= github.com/lestrrat-go/blackmagic v1.0.4/go.mod h1:6AWFyKNNj0zEXQYfTMPfZrAXUWUfTIZ5ECEUEJaijtw= github.com/lestrrat-go/dsig v1.0.0 h1:OE09s2r9Z81kxzJYRn07TFM9XA4akrUdoMwr0L8xj38= github.com/lestrrat-go/dsig v1.0.0/go.mod h1:dEgoOYYEJvW6XGbLasr8TFcAxoWrKlbQvmJgCR0qkDo= github.com/lestrrat-go/dsig-secp256k1 v1.0.0 h1:JpDe4Aybfl0soBvoVwjqDbp+9S1Y2OM7gcrVVMFPOzY= github.com/lestrrat-go/dsig-secp256k1 v1.0.0/go.mod h1:CxUgAhssb8FToqbL8NjSPoGQlnO4w3LG1P0qPWQm/NU= github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE= github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E= github.com/lestrrat-go/httprc/v3 v3.0.2 h1:7u4HUaD0NQbf2/n5+fyp+T10hNCsAnwKfqn4A4Baif0= github.com/lestrrat-go/httprc/v3 v3.0.2/go.mod h1:mSMtkZW92Z98M5YoNNztbRGxbXHql7tSitCvaxvo9l0= github.com/lestrrat-go/option/v2 v2.0.0 h1:XxrcaJESE1fokHy3FpaQ/cXW8ZsIdWcdFzzLOcID3Ss= github.com/lestrrat-go/option/v2 v2.0.0/go.mod h1:oSySsmzMoR0iRzCDCaUfsCzxQHUEuhOViQObyy7S6Vg= 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.1 h1:DTNbBqs57ioxAD4PrArqftgypG4/qNpXoJx8TVXxPR0= github.com/segmentio/asm v1.2.1/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/valyala/fastjson v1.6.7 h1:ZE4tRy0CIkh+qDc5McjatheGX2czdn8slQjomexVpBM= github.com/valyala/fastjson v1.6.7/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY= golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU= golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0= golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= golang-github-lestrrat-go-jwx-3.0.13/bench/performance/jwe_benchmark_test.go000066400000000000000000000203041515060566400271500ustar00rootroot00000000000000package bench_test import ( "crypto/ecdsa" "crypto/elliptic" "crypto/rand" "crypto/rsa" "encoding/json" "testing" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/lestrrat-go/jwx/v3/jwe" ) func setupKeys() (*rsa.PrivateKey, *ecdsa.PrivateKey, []byte) { // RSA key for RSA-OAEP rsaKey, _ := rsa.GenerateKey(rand.Reader, 2048) // ECDSA key for ECDH-ES ecKey, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) // Symmetric key for AES-KW symKey := make([]byte, 32) rand.Read(symKey) return rsaKey, ecKey, symKey } func BenchmarkJWE_Encrypt(b *testing.B) { rsaKey, ecKey, symKey := setupKeys() payload := []byte("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.") testcases := []struct { name string alg jwa.KeyEncryptionAlgorithm enc jwa.ContentEncryptionAlgorithm key interface{} }{ { name: "RSA-OAEP/A256GCM", alg: jwa.RSA_OAEP(), enc: jwa.A256GCM(), key: &rsaKey.PublicKey, }, { name: "RSA1_5/A128CBC-HS256", alg: jwa.RSA1_5(), enc: jwa.A128CBC_HS256(), key: &rsaKey.PublicKey, }, { name: "A256KW/A256GCM", alg: jwa.A256KW(), enc: jwa.A256GCM(), key: symKey, }, { name: "A128GCMKW/A128GCM", alg: jwa.A128GCMKW(), enc: jwa.A128GCM(), key: symKey[:16], }, { name: "ECDH-ES/A256GCM", alg: jwa.ECDH_ES(), enc: jwa.A256GCM(), key: &ecKey.PublicKey, }, { name: "ECDH-ES+A256KW/A256GCM", alg: jwa.ECDH_ES_A256KW(), enc: jwa.A256GCM(), key: &ecKey.PublicKey, }, { name: "DIRECT/A256GCM", alg: jwa.DIRECT(), enc: jwa.A256GCM(), key: symKey, }, } for _, tc := range testcases { b.Run(tc.name, func(b *testing.B) { b.ResetTimer() b.ReportAllocs() for i := 0; i < b.N; i++ { _, err := jwe.Encrypt(payload, jwe.WithKey(tc.alg, tc.key), jwe.WithContentEncryption(tc.enc), ) if err != nil { b.Fatal(err) } } }) } } func BenchmarkJWE_Decrypt(b *testing.B) { rsaKey, ecKey, symKey := setupKeys() payload := []byte("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.") testcases := []struct { name string alg jwa.KeyEncryptionAlgorithm enc jwa.ContentEncryptionAlgorithm encKey interface{} decKey interface{} }{ { name: "RSA-OAEP/A256GCM", alg: jwa.RSA_OAEP(), enc: jwa.A256GCM(), encKey: &rsaKey.PublicKey, decKey: rsaKey, }, { name: "RSA1_5/A128CBC-HS256", alg: jwa.RSA1_5(), enc: jwa.A128CBC_HS256(), encKey: &rsaKey.PublicKey, decKey: rsaKey, }, { name: "A256KW/A256GCM", alg: jwa.A256KW(), enc: jwa.A256GCM(), encKey: symKey, decKey: symKey, }, { name: "A128GCMKW/A128GCM", alg: jwa.A128GCMKW(), enc: jwa.A128GCM(), encKey: symKey[:16], decKey: symKey[:16], }, { name: "ECDH-ES/A256GCM", alg: jwa.ECDH_ES(), enc: jwa.A256GCM(), encKey: &ecKey.PublicKey, decKey: ecKey, }, { name: "ECDH-ES+A256KW/A256GCM", alg: jwa.ECDH_ES_A256KW(), enc: jwa.A256GCM(), encKey: &ecKey.PublicKey, decKey: ecKey, }, { name: "DIRECT/A256GCM", alg: jwa.DIRECT(), enc: jwa.A256GCM(), encKey: symKey, decKey: symKey, }, } for _, tc := range testcases { b.Run(tc.name, func(b *testing.B) { // Pre-encrypt the payload encrypted, err := jwe.Encrypt(payload, jwe.WithKey(tc.alg, tc.encKey), jwe.WithContentEncryption(tc.enc), ) if err != nil { b.Fatal(err) } b.ResetTimer() b.ReportAllocs() for i := 0; i < b.N; i++ { _, err := jwe.Decrypt(encrypted, jwe.WithKey(tc.alg, tc.decKey)) if err != nil { b.Fatal(err) } } }) } } func BenchmarkJWE_RoundTrip(b *testing.B) { rsaKey, ecKey, symKey := setupKeys() payload := []byte("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.") testcases := []struct { name string alg jwa.KeyEncryptionAlgorithm enc jwa.ContentEncryptionAlgorithm encKey interface{} decKey interface{} }{ { name: "RSA-OAEP/A256GCM", alg: jwa.RSA_OAEP(), enc: jwa.A256GCM(), encKey: &rsaKey.PublicKey, decKey: rsaKey, }, { name: "A256KW/A256GCM", alg: jwa.A256KW(), enc: jwa.A256GCM(), encKey: symKey, decKey: symKey, }, { name: "ECDH-ES/A256GCM", alg: jwa.ECDH_ES(), enc: jwa.A256GCM(), encKey: &ecKey.PublicKey, decKey: ecKey, }, } for _, tc := range testcases { b.Run(tc.name, func(b *testing.B) { b.ResetTimer() b.ReportAllocs() for i := 0; i < b.N; i++ { // Encrypt encrypted, err := jwe.Encrypt(payload, jwe.WithKey(tc.alg, tc.encKey), jwe.WithContentEncryption(tc.enc), ) if err != nil { b.Fatal(err) } // Decrypt decrypted, err := jwe.Decrypt(encrypted, jwe.WithKey(tc.alg, tc.decKey)) if err != nil { b.Fatal(err) } // Verify payload if string(decrypted) != string(payload) { b.Fatal("payload mismatch") } } }) } } func BenchmarkJWE_PayloadSizes(b *testing.B) { rsaKey, _, _ := setupKeys() payloadSizes := []struct { name string size int }{ {"1KB", 1024}, {"10KB", 10 * 1024}, {"100KB", 100 * 1024}, {"1MB", 1024 * 1024}, } for _, ps := range payloadSizes { payload := make([]byte, ps.size) rand.Read(payload) b.Run("Encrypt_"+ps.name, func(b *testing.B) { b.ResetTimer() b.ReportAllocs() for i := 0; i < b.N; i++ { _, err := jwe.Encrypt(payload, jwe.WithKey(jwa.RSA_OAEP(), &rsaKey.PublicKey), jwe.WithContentEncryption(jwa.A256GCM()), ) if err != nil { b.Fatal(err) } } }) b.Run("Decrypt_"+ps.name, func(b *testing.B) { // Pre-encrypt encrypted, err := jwe.Encrypt(payload, jwe.WithKey(jwa.RSA_OAEP(), &rsaKey.PublicKey), jwe.WithContentEncryption(jwa.A256GCM()), ) if err != nil { b.Fatal(err) } b.ResetTimer() b.ReportAllocs() for i := 0; i < b.N; i++ { _, err := jwe.Decrypt(encrypted, jwe.WithKey(jwa.RSA_OAEP(), rsaKey)) if err != nil { b.Fatal(err) } } }) } } func BenchmarkJWE_Parallel(b *testing.B) { rsaKey, _, _ := setupKeys() payload := []byte("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.") b.Run("Encrypt_Parallel", func(b *testing.B) { b.ResetTimer() b.ReportAllocs() b.RunParallel(func(pb *testing.PB) { for pb.Next() { _, err := jwe.Encrypt(payload, jwe.WithKey(jwa.RSA_OAEP(), &rsaKey.PublicKey), jwe.WithContentEncryption(jwa.A256GCM()), ) if err != nil { b.Fatal(err) } } }) }) b.Run("Decrypt_Parallel", func(b *testing.B) { // Pre-encrypt encrypted, err := jwe.Encrypt(payload, jwe.WithKey(jwa.RSA_OAEP(), &rsaKey.PublicKey), jwe.WithContentEncryption(jwa.A256GCM()), ) if err != nil { b.Fatal(err) } b.ResetTimer() b.ReportAllocs() b.RunParallel(func(pb *testing.PB) { for pb.Next() { _, err := jwe.Decrypt(encrypted, jwe.WithKey(jwa.RSA_OAEP(), rsaKey)) if err != nil { b.Fatal(err) } } }) }) } // Legacy benchmark for comparison (serialization only) func BenchmarkJWE_Serialization(b *testing.B) { const s = `eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkEyNTZHQ00ifQ.OKOawDo13gRp2ojaHV7LFpZcgV7T6DVZKTyKOMTYUmKoTCVJRgckCL9kiMT03JGeipsEdY3mx_etLbbWSrFr05kLzcSr4qKAq7YN7e9jwQRb23nfa6c9d-StnImGyFDbSv04uVuxIp5Zms1gNxKKK2Da14B8S4rzVRltdYwam_lDp5XnZAYpQdb76FdIKLaVmqgfwX7XWRxv2322i-vDxRfqNzo_tETKzpVLzfiwQyeyPGLBIO56YJ7eObdv0je81860ppamavo35UgoRdbYaBcoh9QcfylQr66oc6vFWXRcZ_ZT2LawVCWTIy3brGPi6UklfCpIMfIjf7iGdXKHzg.48V1_ALb6US04U3b.5eym8TW_c8SuK0ltJ3rpYIzOeDQz7TALvtu6UG9oMo4vpzs9tX_EFShS8iB7j6jiSdiwkIr3ajwQzaBtQD_A.XFBoMYUZodetZdvTiFvSkQ` m, _ := jwe.Parse([]byte(s)) js, _ := json.Marshal(m) var v any b.Run("JSON_Marshal", func(b *testing.B) { testcases := []Case{ { Name: "json.Marshal", Test: func(b *testing.B) error { _, err := json.Marshal(m) return err }, }, { Name: "json.Unmarshal", Test: func(b *testing.B) error { return json.Unmarshal(js, &v) }, }, } for _, tc := range testcases { tc.Run(b) } }) } golang-github-lestrrat-go-jwx-3.0.13/bench/performance/jwk_benchmark_test.go000066400000000000000000000035171515060566400271650ustar00rootroot00000000000000package bench_test import ( "bytes" "encoding/json" "testing" "github.com/lestrrat-go/jwx/v3/internal/jwxtest" "github.com/lestrrat-go/jwx/v3/jwk" ) func runJSONBench(b *testing.B, privkey jwk.Key) { b.Helper() privkey.Set("mykey", "1234567890") pubkey, err := jwk.PublicKeyOf(privkey) if err != nil { b.Fatal(err) } keytypes := []struct { Name string Key jwk.Key }{ {Name: "PublicKey", Key: pubkey}, {Name: "PrivateKey", Key: privkey}, } for _, keytype := range keytypes { key := keytype.Key b.Run(keytype.Name, func(b *testing.B) { buf, _ := json.Marshal(key) s := string(buf) rdr := bytes.NewReader(buf) testcases := []Case{ { Name: "jwk.Parse", Test: func(b *testing.B) error { _, err := jwk.Parse(buf) return err }, }, { Name: "jwk.ParseString", SkipShort: true, Test: func(b *testing.B) error { _, err := jwk.ParseString(s) return err }, }, { Name: "jwk.ParseReader", SkipShort: true, Pretest: func(b *testing.B) error { _, err := rdr.Seek(0, 0) return err }, Test: func(b *testing.B) error { _, err := jwk.ParseReader(rdr) return err }, }, { Name: "json.Marshal", Test: func(b *testing.B) error { _, err := json.Marshal(key) return err }, }, } for _, tc := range testcases { tc.Run(b) } }) } } func BenchmarkJWK(b *testing.B) { b.Run("Serialization", func(b *testing.B) { b.Run("RSA", func(b *testing.B) { rsakey, _ := jwxtest.GenerateRsaJwk() runJSONBench(b, rsakey) }) b.Run("EC", func(b *testing.B) { eckey, _ := jwxtest.GenerateEcdsaJwk() runJSONBench(b, eckey) }) b.Run("Symmetric", func(b *testing.B) { symkey, _ := jwxtest.GenerateSymmetricJwk() runJSONBench(b, symkey) }) }) } golang-github-lestrrat-go-jwx-3.0.13/bench/performance/jws_benchmark_test.go000066400000000000000000000057021515060566400271730ustar00rootroot00000000000000package bench_test import ( "bytes" "encoding/json" "testing" "github.com/lestrrat-go/jwx/v3/jws" ) func BenchmarkJWS(b *testing.B) { const compactStr = `eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk` compactBuf := []byte(compactStr) compactRdr := bytes.NewReader(compactBuf) b.Run("Serialization", func(b *testing.B) { const jsonStr = `{ "payload": "eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ", "signatures":[ { "header": {"kid":"2010-12-29"}, "protected":"eyJhbGciOiJSUzI1NiJ9", "signature": "cC4hiUPoj9Eetdgtv3hF80EGrhuB__dzERat0XF9g2VtQgr9PJbu3XOiZj5RZmh7AAuHIm4Bh-0Qc_lF5YKt_O8W2Fp5jujGbds9uJdbF9CUAr7t1dnZcAcQjbKBYNX4BAynRFdiuB--f_nZLgrnbyTyWzO75vRK5h6xBArLIARNPvkSjtQBMHlb1L07Qe7K0GarZRmB_eSN9383LcOLn6_dO--xi12jzDwusC-eOkHWEsqtFZESc6BfI7noOPqvhJ1phCnvWh6IeYI2w9QOYEUipUTI8np6LbgGY9Fs98rqVt5AXLIhWkWywlVmtVrBp0igcN_IoypGlUPQGe77Rw" }, { "header": {"kid":"e9bc097a-ce51-4036-9562-d2ade882db0d"}, "protected":"eyJhbGciOiJFUzI1NiJ9", "signature": "DtEhU3ljbEg8L38VWAfUAqOyKAM6-Xx-F4GawxaepmXFCgfTjDxw5djxLa8ISlSApmWQxfKTUJqPP3-Kg6NU1Q" } ] }` jsonBuf := []byte(jsonStr) jsonRdr := bytes.NewReader(jsonBuf) b.Run("Compact", func(b *testing.B) { testcases := []Case{ { Name: "jws.Parse", Test: func(b *testing.B) error { _, err := jws.Parse(compactBuf) return err }, }, { Name: "jws.ParseString", SkipShort: true, Test: func(b *testing.B) error { _, err := jws.ParseString(compactStr) return err }, }, { Name: "jws.ParseReader", SkipShort: true, Pretest: func(b *testing.B) error { _, err := compactRdr.Seek(0, 0) return err }, Test: func(b *testing.B) error { _, err := jws.ParseReader(compactRdr) return err }, }, } for _, tc := range testcases { tc.Run(b) } }) b.Run("JSON", func(b *testing.B) { m, _ := jws.Parse([]byte(jsonStr)) testcases := []Case{ { Name: "jws.Parse", Test: func(b *testing.B) error { _, err := jws.Parse(jsonBuf) return err }, }, { Name: "jws.ParseString", SkipShort: true, Test: func(b *testing.B) error { _, err := jws.ParseString(jsonStr) return err }, }, { Name: "jws.ParseReader", SkipShort: true, Pretest: func(b *testing.B) error { _, err := jsonRdr.Seek(0, 0) return err }, Test: func(b *testing.B) error { _, err := jws.ParseReader(jsonRdr) return err }, }, { Name: "json.Marshal", Test: func(b *testing.B) error { _, err := json.Marshal(m) return err }, }, } for _, tc := range testcases { tc.Run(b) } }) }) } golang-github-lestrrat-go-jwx-3.0.13/bench/performance/jwt_benchmark_test.go000066400000000000000000000101401515060566400271640ustar00rootroot00000000000000package bench_test import ( "bytes" "encoding/json" "testing" "time" "github.com/lestrrat-go/jwx/v3/internal/jwxtest" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/lestrrat-go/jwx/v3/jwk" "github.com/lestrrat-go/jwx/v3/jwt" ) func BenchmarkJWT(b *testing.B) { alg := jwa.RS256() key, err := jwxtest.GenerateRsaJwk() if err != nil { b.Fatal(err) } pubkey, err := jwk.PublicKeyOf(key) if err != nil { b.Fatal(err) } t1 := jwt.New() t1.Set(jwt.IssuedAtKey, time.Now().Unix()) t1.Set(jwt.ExpirationKey, time.Now().Add(time.Hour).Unix()) b.Run("Serialization", func(b *testing.B) { b.Run("Compact", func(b *testing.B) { testcases := []Case{ { Name: "jwt.Sign", Test: func(b *testing.B) error { _, err := jwt.Sign(t1, jwt.WithKey(alg, key)) return err }, }, } for _, tc := range testcases { tc.Run(b) } }) b.Run("JSON", func(b *testing.B) { testcases := []Case{ { Name: "json.Marshal", Test: func(b *testing.B) error { _, err := json.Marshal(t1) return err }, }, } for _, tc := range testcases { tc.Run(b) } }) }) b.Run("Serialization", func(b *testing.B) { signedBuf, err := jwt.Sign(t1, jwt.WithKey(alg, key)) if err != nil { b.Fatal(err) } signedString := string(signedBuf) signedReader := bytes.NewReader(signedBuf) jsonBuf, _ := json.Marshal(t1) jsonString := string(jsonBuf) jsonReader := bytes.NewReader(jsonBuf) b.Run("Compact (With Verify)", func(b *testing.B) { testcases := []Case{ { Name: "jwt.ParseString", SkipShort: true, Test: func(b *testing.B) error { _, err := jwt.ParseString(signedString, jwt.WithKey(alg, pubkey)) return err }, }, { Name: "jwt.Parse", Test: func(b *testing.B) error { _, err := jwt.Parse(signedBuf, jwt.WithKey(alg, pubkey)) return err }, }, { Name: "jwt.ParseReader", SkipShort: true, Pretest: func(b *testing.B) error { _, err := signedReader.Seek(0, 0) return err }, Test: func(b *testing.B) error { _, err := jwt.ParseReader(signedReader, jwt.WithKey(alg, pubkey)) return err }, }, } for _, tc := range testcases { tc.Run(b) } }) b.Run("Compact (No Verify)", func(b *testing.B) { testcases := []Case{ { Name: "jwt.ParseString", SkipShort: true, Test: func(b *testing.B) error { _, 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, jwt.WithVerify(false), jwt.WithValidate(false)) return err }, }, { Name: "jwt.ParseReader", SkipShort: true, Pretest: func(b *testing.B) error { _, err := signedReader.Seek(0, 0) return err }, Test: func(b *testing.B) error { _, err := jwt.ParseReader(signedReader, jwt.WithVerify(false), jwt.WithValidate(false)) return err }, }, } for _, tc := range testcases { tc.Run(b) } }) b.Run("JSON", func(b *testing.B) { var v any testcases := []Case{ { Name: "jwt.ParseString", SkipShort: true, Test: func(b *testing.B) error { _, err := jwt.ParseString(jsonString, jwt.WithVerify(false), jwt.WithValidate(false)) return err }, }, { Name: "jwt.Parse", Test: func(b *testing.B) error { _, err := jwt.Parse(jsonBuf, jwt.WithVerify(false), jwt.WithValidate(false)) return err }, }, { Name: "jwt.ParseReader", SkipShort: true, Pretest: func(b *testing.B) error { _, err := jsonReader.Seek(0, 0) return err }, Test: func(b *testing.B) error { _, err := jwt.ParseReader(jsonReader, jwt.WithVerify(false), jwt.WithValidate(false)) return err }, }, { Name: "json.Unmarshal", Test: func(b *testing.B) error { return json.Unmarshal(jsonBuf, &v) }, }, } for _, tc := range testcases { tc.Run(b) } }) }) } golang-github-lestrrat-go-jwx-3.0.13/bench/performance/jwx_benchmark_test.go000066400000000000000000000013531515060566400271760ustar00rootroot00000000000000package bench_test import ( "testing" "github.com/lestrrat-go/jwx/v3/internal/json" ) func TestBackend(t *testing.T) { t.Logf("%s", json.Engine()) } // Case is a single benchmark case type Case struct { Name string Pretest func(*testing.B) error SkipShort bool // Skip benchmark on short mode Test func(*testing.B) error } func (c *Case) Run(b *testing.B) { b.Helper() b.Run(c.Name, func(b *testing.B) { if testing.Short() && c.SkipShort { b.SkipNow() } b.Helper() for i := 0; i < b.N; i++ { b.StopTimer() if pretest := c.Pretest; pretest != nil { if err := pretest(b); err != nil { b.Fatal(err) } } b.StartTimer() if err := c.Test(b); err != nil { b.Fatal(err) } } }) } golang-github-lestrrat-go-jwx-3.0.13/cert/000077500000000000000000000000001515060566400203415ustar00rootroot00000000000000golang-github-lestrrat-go-jwx-3.0.13/cert/BUILD.bazel000066400000000000000000000012051515060566400222150ustar00rootroot00000000000000load("@rules_go//go:def.bzl", "go_library", "go_test") go_library( name = "cert", srcs = [ "cert.go", "chain.go", ], importpath = "github.com/lestrrat-go/jwx/v3/cert", visibility = ["//visibility:public"], deps = [ "//internal/base64", "//internal/tokens", ], ) go_test( name = "cert_test", srcs = [ "cert_test.go", "chain_test.go", ], deps = [ ":cert", "//internal/jwxtest", "@com_github_stretchr_testify//require", ], ) alias( name = "go_default_library", actual = ":cert", visibility = ["//visibility:public"], ) golang-github-lestrrat-go-jwx-3.0.13/cert/cert.go000066400000000000000000000030531515060566400216260ustar00rootroot00000000000000package cert import ( "crypto/x509" stdlibb64 "encoding/base64" "fmt" "io" "github.com/lestrrat-go/jwx/v3/internal/base64" ) // Create is a wrapper around x509.CreateCertificate, but it additionally // encodes it in base64 so that it can be easily added to `x5c` fields func Create(rand io.Reader, template, parent *x509.Certificate, pub, priv any) ([]byte, error) { der, err := x509.CreateCertificate(rand, template, parent, pub, priv) if err != nil { return nil, fmt.Errorf(`failed to create x509 certificate: %w`, err) } return EncodeBase64(der) } // EncodeBase64 is a utility function to encode ASN.1 DER certificates // using base64 encoding. This operation is normally done by `pem.Encode` // but since PEM would include the markers (`-----BEGIN`, and the like) // while `x5c` fields do not need this, this function can be used to // shave off a few lines func EncodeBase64(der []byte) ([]byte, error) { enc := stdlibb64.StdEncoding dst := make([]byte, enc.EncodedLen(len(der))) enc.Encode(dst, der) return dst, nil } // Parse is a utility function to decode a base64 encoded // ASN.1 DER format certificate, and to parse the byte sequence. // The certificate must be in PKIX format, and it must not contain PEM markers func Parse(src []byte) (*x509.Certificate, error) { dst, err := base64.Decode(src) if err != nil { return nil, fmt.Errorf(`failed to base64 decode the certificate: %w`, err) } cert, err := x509.ParseCertificate(dst) if err != nil { return nil, fmt.Errorf(`failed to parse x509 certificate: %w`, err) } return cert, nil } golang-github-lestrrat-go-jwx-3.0.13/cert/cert_test.go000066400000000000000000000064641515060566400226760ustar00rootroot00000000000000package cert_test import ( "crypto/rand" "crypto/x509" "crypto/x509/pkix" "encoding/asn1" "math/big" "net" "net/url" "testing" "time" "github.com/lestrrat-go/jwx/v3/cert" "github.com/lestrrat-go/jwx/v3/internal/jwxtest" "github.com/stretchr/testify/require" ) func parseCIDR(s string) *net.IPNet { _, net, err := net.ParseCIDR(s) if err != nil { panic(err) } return net } func parseURI(s string) *url.URL { uri, err := url.Parse(s) if err != nil { panic(err) } return uri } func TestCert(t *testing.T) { privkey, err := jwxtest.GenerateRsaKey() require.NoError(t, err, `jwxtest.GenerateRsaKey`) testExtKeyUsage := []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth} testUnknownExtKeyUsage := []asn1.ObjectIdentifier{[]int{1, 2, 3}, []int{2, 59, 1}} extraExtensionData := []byte("extra extension") oidExtensionSubjectKeyID := []int{2, 5, 29, 14} commonName := "test.example.com" template := x509.Certificate{ SerialNumber: big.NewInt(1), // SerialNumbers must be non-negative since go1.19 Subject: pkix.Name{ CommonName: commonName, Organization: []string{"ÎŖ Acme Co"}, Country: []string{"US"}, ExtraNames: []pkix.AttributeTypeAndValue{ { Type: []int{2, 5, 4, 42}, Value: "Gopher", }, // This should override the Country, above. { Type: []int{2, 5, 4, 6}, Value: "NL", }, }, }, NotBefore: time.Unix(1000, 0), NotAfter: time.Unix(100000, 0), SignatureAlgorithm: x509.SHA384WithRSA, SubjectKeyId: []byte{1, 2, 3, 4}, KeyUsage: x509.KeyUsageCertSign, ExtKeyUsage: testExtKeyUsage, UnknownExtKeyUsage: testUnknownExtKeyUsage, BasicConstraintsValid: true, IsCA: true, OCSPServer: []string{"http://ocsp.example.com"}, IssuingCertificateURL: []string{"http://crt.example.com/ca1.crt"}, DNSNames: []string{"test.example.com"}, EmailAddresses: []string{"gopher@golang.org"}, IPAddresses: []net.IP{net.IPv4(127, 0, 0, 1).To4(), net.ParseIP("2001:4860:0:2001::68")}, URIs: []*url.URL{parseURI("https://foo.com/wibble#foo")}, PolicyIdentifiers: []asn1.ObjectIdentifier{[]int{1, 2, 3}}, PermittedDNSDomains: []string{".example.com", "example.com"}, ExcludedDNSDomains: []string{"bar.example.com"}, PermittedIPRanges: []*net.IPNet{parseCIDR("192.168.1.1/16"), parseCIDR("1.2.3.4/8")}, ExcludedIPRanges: []*net.IPNet{parseCIDR("2001:db8::/48")}, PermittedEmailAddresses: []string{"foo@example.com"}, ExcludedEmailAddresses: []string{".example.com", "example.com"}, PermittedURIDomains: []string{".bar.com", "bar.com"}, ExcludedURIDomains: []string{".bar2.com", "bar2.com"}, CRLDistributionPoints: []string{"http://crl1.example.com/ca1.crl", "http://crl2.example.com/ca1.crl"}, ExtraExtensions: []pkix.Extension{ { Id: []int{1, 2, 3, 4}, Value: extraExtensionData, }, // This extension should override the SubjectKeyId, above. { Id: oidExtensionSubjectKeyID, Critical: false, Value: []byte{0x04, 0x04, 4, 3, 2, 1}, }, }, } b64, err := cert.Create(rand.Reader, &template, &template, &privkey.PublicKey, privkey) require.NoError(t, err, `cert.Certificate should succeed`) _, err = cert.Parse(b64) require.NoError(t, err, `cert.Parse should succeed`) } golang-github-lestrrat-go-jwx-3.0.13/cert/chain.go000066400000000000000000000036601515060566400217570ustar00rootroot00000000000000package cert import ( "bytes" "encoding/json" "fmt" "github.com/lestrrat-go/jwx/v3/internal/tokens" ) // Chain represents a certificate chain as used in the `x5c` field of // various objects within JOSE. // // It stores the certificates as a list of base64 encoded []byte // sequence. By definition these values must PKIX encoded. type Chain struct { certificates [][]byte } func (cc Chain) MarshalJSON() ([]byte, error) { var buf bytes.Buffer buf.WriteByte(tokens.OpenSquareBracket) for i, cert := range cc.certificates { if i > 0 { buf.WriteByte(tokens.Comma) } buf.WriteByte('"') buf.Write(cert) buf.WriteByte('"') } buf.WriteByte(tokens.CloseSquareBracket) return buf.Bytes(), nil } func (cc *Chain) UnmarshalJSON(data []byte) error { var tmp []string if err := json.Unmarshal(data, &tmp); err != nil { return fmt.Errorf(`failed to unmarshal certificate chain: %w`, err) } certs := make([][]byte, len(tmp)) for i, cert := range tmp { certs[i] = []byte(cert) } cc.certificates = certs return nil } // Get returns the n-th ASN.1 DER + base64 encoded certificate // stored. `false` will be returned in the second argument if // the corresponding index is out of range. func (cc *Chain) Get(index int) ([]byte, bool) { if index < 0 || index >= len(cc.certificates) { return nil, false } return cc.certificates[index], true } // Len returns the number of certificates stored in this Chain func (cc *Chain) Len() int { return len(cc.certificates) } var pemStart = []byte("----- BEGIN CERTIFICATE -----") var pemEnd = []byte("----- END CERTIFICATE -----") func (cc *Chain) AddString(der string) error { return cc.Add([]byte(der)) } func (cc *Chain) Add(der []byte) error { // We're going to be nice and remove marker lines if they // give it to us der = bytes.TrimPrefix(der, pemStart) der = bytes.TrimSuffix(der, pemEnd) der = bytes.TrimSpace(der) cc.certificates = append(cc.certificates, der) return nil } golang-github-lestrrat-go-jwx-3.0.13/cert/chain_test.go000066400000000000000000000041141515060566400230110ustar00rootroot00000000000000package cert_test import ( "testing" "github.com/lestrrat-go/jwx/v3/cert" "github.com/stretchr/testify/require" ) var certBytes = []byte(`MIICdDCCAd2gAwIBAgIUEpq1vvAyaiEKhgEE/UKykUcnXi4wDQYJKoZIhvcNAQEL BQAwTDELMAkGA1UEBhMCSlAxDjAMBgNVBAgMBVRva3lvMREwDwYDVQQHDAhSb3Bw b25naTEMMAoGA1UECgwDSldYMQwwCgYDVQQDDANKV1gwHhcNMjIwMzEzMTMzOTIy WhcNMjMwMzEzMTMzOTIyWjBMMQswCQYDVQQGEwJKUDEOMAwGA1UECAwFVG9reW8x ETAPBgNVBAcMCFJvcHBvbmdpMQwwCgYDVQQKDANKV1gxDDAKBgNVBAMMA0pXWDCB nzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAwHm1AyeTpFWghI3PRTitSBMmbXqQ ccrmK+4RZkp4JRhRXH6dc6O0JvsesoMmONegeU3c/FNU7ZTdaXJHGZCo4IUil0gv rJRn52LAvCkodNwKG80+xHvGXix3LEaiTPbBmqGCttx5Q+2WsiBjZPHtQU2kOVs4 k90F++pImEd7Xl8CAwEAAaNTMFEwHQYDVR0OBBYEFN78aX+uEXMpDrZhtEY2e/vR jdgSMB8GA1UdIwQYMBaAFN78aX+uEXMpDrZhtEY2e/vRjdgSMA8GA1UdEwEB/wQF MAMBAf8wDQYJKoZIhvcNAQELBQADgYEAsrNkfe2+E9fpFkmIYPkxiOGMo0d6edlV Q0fW17ZhS1fuM3eQJr61IJvZ4hEP2KjsOEJzRvptxkpVFiDOZf8DbkUVNpeWxorK gPt3f4fzO4SIXu7fG89QkR5TJs6lxyZsr1V/IumL4LSx04LhIvMhHiUbbyVHgN8B KpDY+K+bsqw=`) func TestChain(t *testing.T) { goldenCert, err := cert.Parse(certBytes) require.NoError(t, err, `x509.ParseCertificate should succeed`) testcases := []struct { Name string Data []byte }{ { Name: `proper base64 in ASN.1 DER`, Data: certBytes, }, { Name: `proper base64 in ASN.1 DER, but with markers`, Data: append(append([]byte("----- BEGIN CERTIFICATE -----\n"), certBytes...), []byte("\n----- END CERTIFICATE -----")...), }, } var chain cert.Chain for _, tc := range testcases { t.Run(tc.Name, func(t *testing.T) { require.NoError(t, chain.Add(tc.Data), `chain.Add should succeed`) }) } require.Equal(t, len(testcases), chain.Len(), `certificates in chain should match`) for i := range chain.Len() { der, ok := chain.Get(i) require.True(t, ok, `chain.Get(%d) should succeed`, i) c, err := cert.Parse(der) require.NoError(t, err, `cert.Parse should match`) require.True(t, c.Equal(goldenCert), `certificates should match`) } for _, i := range []int{-1, chain.Len()} { _, ok := chain.Get(i) require.False(t, ok, `out of bounds should properly error`) } } golang-github-lestrrat-go-jwx-3.0.13/cmd/000077500000000000000000000000001515060566400201475ustar00rootroot00000000000000golang-github-lestrrat-go-jwx-3.0.13/cmd/jwx/000077500000000000000000000000001515060566400207575ustar00rootroot00000000000000golang-github-lestrrat-go-jwx-3.0.13/cmd/jwx/README.md000066400000000000000000000330241515060566400222400ustar00rootroot00000000000000# The `jwx` command line tool `jwx` command line tool performs set of common operations involving JSON Object Signing and Encryption (JOSE). This is provided as a sample application of sorts, and thus does not get much updates. As of this writing it is not possible to install this command using `go install`. Instead, install it by executing the following commands: ```sh git clone https://github.com/lestrrat-go/jwx.git cd jwx make jwx ``` This will install the `jwx` tool in $GOBIN (or $GOPATH/bin, if $GOBIN is not available). If you do a lot of JOSE related work on the command line, we highly recommend [github.com/latchset/jose](https://github.com/latchset/jose) for the same purpose, unless you might prefer to use `jwx` for its pure-Go implementation. # Usage All examples use the "full" name for command names and option names, but you can use the short forms interchangeably. # jwx jwk Work with JWKs ## jwx jwk generate Full form: ``` jwx jwk generate [options] ``` Short form: ``` jwx jwk gen [options] ``` ### Options | Name | Aliases | Description | |:--------------|:---------|:-------------| | --type | -t | Type of JWK | | --keysize | -s | Number of bits for RSA keys. Number of bytes for oct keys | | --curve | -c | Elliptic curve type for EC or OKP keys | | --template | (none) | Template to use to generate JWK. Must be a JSON object | | --set | (none) | Always output as JWK set | | --publick-key | -p | Generate a public key | | --output | -o | Write output to file ("-" for STDOUT) | ### Usage You can generate random JWKs for RSA/EC/oct/OKP key types: ```shell # output truncated for brevity % jwx jwk generate --type RSA --keysize 4096 { "d": "TGGiBzGzFEWQQPE32m...", "dp": "LjsdUBxJhshSa7FEBP...", "dq": "G4SPP5e5sp-k8iCEAa...", "e": "AQAB", "kty": "RSA", "n": "lgy17ssrTVUFKxFq5gO...", "p": "wEXZYzjrSbAn1bDpQpN...", "q": "x8hEaDhNND9mOqHD_xH...", "qi": "BVDWmgMEZ7QBC8ZSL9..." } % jwx jwk generate --type EC --curve P-521 % jwx jwk generate --type oct --keysize 128 % jwx jwk generate --type OKP --curve Ed25519 ``` To include extra information in the key such as a key ID, use the `--template` option ```shell # output truncated for brevity % jwx jwk generate --type EC --curve P-384 --template '{"kid":"myawesomekey"}' { "crv": "P-384", "d": "Q4JFCjI81uYC2T...", "kid": "myawesomekey", "kty": "EC", "x": "cm6GYmhtjYLr_B...", "y": "4_dIgUa68wytgg..." } ``` ## jwx jwk format Full form ``` jwx jwk format [options] [FILE] ``` Short form ``` jwx jwk fmt [options] [FILE] ``` You may specify "-" as `FILE` to tell the command to read from STDIN. ### Options | Name | Aliases | Description | |-----------------|---------|-------------| | --input-format | -I | JWK input format (json/pem) | | --output-format | -O | JWK output format (json/pem) | | --set | (none) | Always output as JWK set | | --publick-key | -p | Display the public key version of the input | | --output | -o | Write output to file ("-" for STDOUT) | ### Usage (Produce public key of a private key) Given a private key in file `ec.jwk` ```json {"kty":"EC","crv":"P-256","x":"SVqB4JcUD6lsfvqMr-OKUNUphdNn64Eay60978ZlL74","y":"lf0u0pMj4lGAzZix5u4Cm5CMQIgMNpkwy163wtKYVKI","d":"0g5vAEKzugrXaRbgKG0Tj2qJ5lMP4Bezds1_sTybkfk"} ``` You can issue the following command to produce the public key of the above key: ``` % jwx jwk fmt --public-key ec.jwk { "crv": "P-256", "kty": "EC", "x": "SVqB4JcUD6lsfvqMr-OKUNUphdNn64Eay60978ZlL74", "y": "lf0u0pMj4lGAzZix5u4Cm5CMQIgMNpkwy163wtKYVKI" } ``` ### Usage (Parse JSON) You can parse and make sure that the a given JWK is well-formatted. Given an unformatted key in file `ec.jwk` ```json {"kty":"EC","crv":"P-256","x":"SVqB4JcUD6lsfvqMr-OKUNUphdNn64Eay60978ZlL74","y":"lf0u0pMj4lGAzZix5u4Cm5CMQIgMNpkwy163wtKYVKI","d":"0g5vAEKzugrXaRbgKG0Tj2qJ5lMP4Bezds1_sTybkfk"} ``` You can produce a pretty formatted key: ```shell % jwx jwk format --input-format pem ec.jwk { "crv": "P-256", "d": "0g5vAEKzugrXaRbgKG0Tj2qJ5lMP4Bezds1_sTybkfk", "kty": "EC", "x": "SVqB4JcUD6lsfvqMr-OKUNUphdNn64Eay60978ZlL74", "y": "lf0u0pMj4lGAzZix5u4Cm5CMQIgMNpkwy163wtKYVKI" } ``` ### Usage (Parse PEM) You can parse a ASN.1 DER format key, encoded in PEM. Given a PEM encoded ASN.1 DER format key in a file `ec.pem`: ``` -----BEGIN PUBLIC KEY----- MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAESVqB4JcUD6lsfvqMr+OKUNUphdNn 64Eay60978ZlL76V/S7SkyPiUYDNmLHm7gKbkIxAiAw2mTDLXrfC0phUog== -----END PUBLIC KEY----- ``` You can get the JSON representation by: ```shell % jwx jwk format --input-format pem --output-format json ec.pem { "crv": "P-256", "d": "0g5vAEKzugrXaRbgKG0Tj2qJ5lMP4Bezds1_sTybkfk", "kty": "EC", "x": "SVqB4JcUD6lsfvqMr-OKUNUphdNn64Eay60978ZlL74", "y": "lf0u0pMj4lGAzZix5u4Cm5CMQIgMNpkwy163wtKYVKI" } ``` ## Usage (Format JSON to PEM) Formatting a JWK is equivalent to parsing, if the output format is `json`. However, if you specify the output format as `pem`, you can create PEM encoded ASN.1 DER format keys. Given the following key in file `rsa.jwk` ```json { "e": "AQAB", "kty": "RSA", "n": "zGH571rQvCHeWzymnucl0sUE7fmabpegJ52VnyNk7SGq74xwRVLV0aPesu4aC-FVjjyhgrEajBQ5K23lI0a8fIi_deP7K58n-rIfXPGZNOMRDqStcqbwc_irOLmTm7Y554rX9DQRnYzCsb3k6vlROwVlCMkI7UPJmwzrIiy74e8" } ``` You can produce a PEM encoded key: ```shell % jwx jwk format --format pem rsa.jwk -----BEGIN PUBLIC KEY----- MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDMYfnvWtC8Id5bPKae5yXSxQTt +Zpul6AnnZWfI2TtIarvjHBFUtXRo96y7hoL4VWOPKGCsRqMFDkrbeUjRrx8iL91 4/srnyf6sh9c8Zk04xEOpK1ypvBz+Ks4uZObtjnnitf0NBGdjMKxveTq+VE7BWUI yQjtQ8mbDOsiLLvh7wIDAQAB -----END PUBLIC KEY----- ``` # jwx jws ## jwx jws parse ``` jwx jws parse FILE ``` Parses the given JWS message, and prints out the content in a human-redable format. ### Usage (Parse and inspect a JWS message) Given a JWS message stored in `foo.jws` as follows: ``` eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk ``` You can inspect the contents of the JWS message by issuing the following command ``` % jwx jws parse foo.jws Signature: "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk" Protected Headers: "eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9" Decoded Protected Headers: { "alg": "HS256", "typ": "JWT" } Payload: {"iss":"joe", "exp":1300819380, "http://example.com/is_root":true} ``` ## jwx jws verify ``` jwx jws verify [options] FILE ``` You may specify "-" as `FILE` to tell the command to read from STDIN. ### Options | Name | Aliases | Description | |:-------------|:---------|:-------------| | --alg | -a | Algorithm to use in single key mode | | --key | -k | File name that contains the key to use. May be a single JWK or JWK set | | --key-format | (none) | Format of the store key (json/pem) | | --match-kid | (none) | If specified, attempts to verify using a key with a matching key ID ("kid") as the JWS | | --output | -o | Write output to file ("-" for STDOUT) | ### Usage (Verify using specific algorithm) ``` jwx jws verify --alg [algorithm] --key [keyfile] FILE ``` Suppose we have `symmetric.jwk` containing the following: ```json { "kty":"oct", "k":"AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow" } ``` And suppose we would like to verify the contents of the file `signed.jws`, with this message which has been signed using `HS256`. ``` eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk ``` Then the following command will verify the JWS message and display the decoded payload. ```shell % jwx jws verify --key symmetric.jwk --alg HS256 signed.jws {"iss":"joe", "exp":1300819380, "http://example.com/is_root":true} ``` ### Usage (Verify with matching key IDs) ``` jwx jws verify --key [keyfile] --match-kid FILE ``` Suppose we have `set.jwk` containing the following JWK set: ```json { "keys": [ { "kty": "EC", "kid": "otherkey", "crv": "P-256", "x": "SVqB4JcUD6lsfvqMr-OKUNUphdNn64Eay60978ZlL74", "y": "lf0u0pMj4lGAzZix5u4Cm5CMQIgMNpkwy163wtKYVKI", "d": "0g5vAEKzugrXaRbgKG0Tj2qJ5lMP4Bezds1_sTybkfk" }, { "kty": "oct", "kid": "mykey", "alg": "HS256", "k": "AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow" } ] } ``` Notice that the second key contains *both* "kid" and "alg" fields set to a proper values. Then the following command will verify the JWS message and display the decoded payload. ```shell % jwx jws verify --key set.jwk --match-kid signed.jws ``` ## jwx jws sign Creates a signed JWS message in compact format from a key and payload. ``` jwx jws sign [command options] FILE ``` You may specify "-" as `FILE` to tell the command to read from STDIN. ### Options | Name | Aliases | Description | |:-------------|:---------|:-------------| | --alg | -a | Algorithm to use in single key mode | | --key | -k | File name that contains the key to use. May be a single JWK or JWK set | | --key-format | (none) | Format of the store key (json/pem) | | --header | (none) | A string containing a template for additional header values. This must be a valid JSON object | | --output | -o | Write output to file ("-" for STDOUT) | ### Usage (Signing a payload) Given a file `payload.txt` containing the following payload: ``` Hello, World! ``` And JWK stored in `ec.jwk` as follows: ``` {"kty":"EC","crv":"P-256","x":"SVqB4JcUD6lsfvqMr-OKUNUphdNn64Eay60978ZlL74","y":"lf0u0pMj4lGAzZix5u4Cm5CMQIgMNpkwy163wtKYVKI","d":"0g5vAEKzugrXaRbgKG0Tj2qJ5lMP4Bezds1_sTybkfk"} ``` You can create a signed JWS in compact format by issuing the following command: ``` % jwx jws sign --key ec.jwk --alg ES256 payload.txt eyJhbGciOiJFUzI1NiJ9.SGVsbG8sIFdvcmxkIQo.SuzTiJ0yJmDkte-SyHQidvhKyHxXdQTM5iCOmURzB0pi4ySM8A303tcAZTa2TLnf9LUZ3yzPpQIyRMF2d8_5Lg ``` # jwx jwe Work with JWE messages. ## jwx jwe encrypt Full form: ``` jwx jwe encrypt [options] FILE ``` Short form: ``` jwx jwe enc [options] FILE ``` ### Options | Name | Aliases | Description | |:---------------------|:---------|:-------------| | --key | -k | JWK to encrypt with | | --key-format | (none) | JWK format: json or pem | | --key-encryption | -K | Key encryption algorithm name | | --content-encryption | -C | Content encryption algorithm name | | --compress | (none) | Enable compression | | --output | -o | Write output to file ("-" for STDOUT) | ### Usage (Encrypt a payload) Given a file `payload.txt` containing the following payload: ``` Hello, World! ``` And JWK stored in `ec.jwk` as follows (Note: a private key may be used as well): ``` {"kty":"EC","crv":"P-256","x":"SVqB4JcUD6lsfvqMr-OKUNUphdNn64Eay60978ZlL74","y":"lf0u0pMj4lGAzZix5u4Cm5CMQIgMNpkwy163wtKYVKI"} ``` You can generate an encrypted JWE message with ECDH-ES key encryption and A256CBC-HS512 content encryption by issuing the following command: ``` % jwx jwe encrypt --key ec.jwk --key-encryption ECDH-ES --content-encryption A256CBC-HS512 payload.txt eyJhbGciOiJFQ0RILUVTIiwiZW5jIjoiQTI1NkNCQy1IUzUxMiIsImVwayI6eyJjcnYiOiJQLTI1NiIsImt0eSI6IkVDIiwieCI6IllGeFZmTUZXQl9kcjhvUGgzWTdRMF9pYzllMjR5XzlleklPbG9WcjdHWVkiLCJ5Ijoiei1QZFB2cXdGU3A0ODYzbzRTWmQwSDdiVXhYUUJqckJ4bkxpaHduRVNKYyJ9fQ..MJFgvx7zMBzM47Is-brKXw.9UL2iAFuL4rjegaLhf3wPA.KGWzX-cmmGG1CQMMpQzyEncu64pkb6217HCFZfIynlE ``` ## jwx jwe decrypt Full form: ``` jwx jwe decrypt [options] FILE ``` Short form: ``` jwx jwe dec [options] FILE ``` ### Options | Name | Aliases | Description | |:---------------------|:---------|:-------------| | --key | -k | JWK to encrypt with | | --key-format | (none) | JWK format: json or pem | | --key-encryption | -K | Key encryption algorithm name. If unspecified, we will try the algorithms in the message| | --output | -o | Write output to file ("-" for STDOUT) | ### Usage (Decrypt a JWE message) Given a file `message.jwe` containing the following JWE message: ``` eyJhbGciOiJFQ0RILUVTIiwiZW5jIjoiQTI1NkNCQy1IUzUxMiIsImVwayI6eyJjcnYiOiJQLTI1NiIsImt0eSI6IkVDIiwieCI6IllGeFZmTUZXQl9kcjhvUGgzWTdRMF9pYzllMjR5XzlleklPbG9WcjdHWVkiLCJ5Ijoiei1QZFB2cXdGU3A0ODYzbzRTWmQwSDdiVXhYUUJqckJ4bkxpaHduRVNKYyJ9fQ..MJFgvx7zMBzM47Is-brKXw.9UL2iAFuL4rjegaLhf3wPA.KGWzX-cmmGG1CQMMpQzyEncu64pkb6217HCFZfIynl ``` And a private key in `ec.jwk`: ``` {"kty":"EC","crv":"P-256","x":"SVqB4JcUD6lsfvqMr-OKUNUphdNn64Eay60978ZlL74","y":"lf0u0pMj4lGAzZix5u4Cm5CMQIgMNpkwy163wtKYVKI","d":"0g5vAEKzugrXaRbgKG0Tj2qJ5lMP4Bezds1_sTybkfk"} ``` You can get the decrypted contents by issuing the following command: ``` % jwx jwe decrypt -k ec.jwk message.jwk Hello, World! ``` # jwx jwa List supported algorithms. ### Options | Name | Aliases | Description | |:---------------------|:---------|:-------------| | --key-type | -k | JWK key types | | --elliptic-curve | -E | Elliptic curve types | | --key-encryption | -K | Key encryption algorithms | | --content-encryption | -C | Content encryption algorithms | | --signature | -S | Signature algorithms | golang-github-lestrrat-go-jwx-3.0.13/cmd/jwx/go.mod000066400000000000000000000016531515060566400220720ustar00rootroot00000000000000module github.com/lestrrat-go/jwx/v3/cmd/jwx go 1.24.0 toolchain go1.24.4 require ( github.com/lestrrat-go/jwx/v3 v3.0.8 github.com/urfave/cli/v2 v2.26.0 golang.org/x/crypto v0.45.0 ) require ( github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect github.com/goccy/go-json v0.10.3 // indirect github.com/lestrrat-go/blackmagic v1.0.4 // indirect github.com/lestrrat-go/httpcc v1.0.1 // indirect github.com/lestrrat-go/httprc/v3 v3.0.0 // indirect github.com/lestrrat-go/option v1.0.1 // indirect github.com/lestrrat-go/option/v2 v2.0.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/segmentio/asm v1.2.0 // indirect github.com/valyala/fastjson v1.6.4 // indirect github.com/xrash/smetrics v0.0.0-20231213231151-1d8dd44e695e // indirect golang.org/x/sys v0.38.0 // indirect ) replace github.com/lestrrat-go/jwx/v3 v3.0.0 => ../.. golang-github-lestrrat-go-jwx-3.0.13/cmd/jwx/go.sum000066400000000000000000000100211515060566400221040ustar00rootroot00000000000000github.com/cpuguy83/go-md2man/v2 v2.0.3 h1:qMCsGGgs+MAzDFyp9LpAe1Lqy/fY/qCovCm0qnXZOBM= github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 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/dcrec/secp256k1/v4 v4.4.0 h1:NMZiJj8QnKe1LgsbDayM4UoHwbvwDRwnI3hwNaAHRnc= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40= github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA= github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/lestrrat-go/blackmagic v1.0.4 h1:IwQibdnf8l2KoO+qC3uT4OaTWsW7tuRQXy9TRN9QanA= github.com/lestrrat-go/blackmagic v1.0.4/go.mod h1:6AWFyKNNj0zEXQYfTMPfZrAXUWUfTIZ5ECEUEJaijtw= github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE= github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E= github.com/lestrrat-go/httprc/v3 v3.0.0 h1:nZUx/zFg5uc2rhlu1L1DidGr5Sj02JbXvGSpnY4LMrc= github.com/lestrrat-go/httprc/v3 v3.0.0/go.mod h1:k2U1QIiyVqAKtkffbg+cUmsyiPGQsb9aAfNQiNFuQ9Q= github.com/lestrrat-go/jwx/v3 v3.0.8 h1:lOCHy+k4/mgRI8FkgkHO+NsUx1GXHHktGx0CIkFToyI= github.com/lestrrat-go/jwx/v3 v3.0.8/go.mod h1:0P9rjqNMDOspNSetpKX86Go54jLSEwCh8ax4jQRGYL0= github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU= github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= github.com/lestrrat-go/option/v2 v2.0.0 h1:XxrcaJESE1fokHy3FpaQ/cXW8ZsIdWcdFzzLOcID3Ss= github.com/lestrrat-go/option/v2 v2.0.0/go.mod h1:oSySsmzMoR0iRzCDCaUfsCzxQHUEuhOViQObyy7S6Vg= 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/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 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/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.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/urfave/cli/v2 v2.26.0 h1:3f3AMg3HpThFNT4I++TKOejZO8yU55t3JnnSr4S4QEI= github.com/urfave/cli/v2 v2.26.0/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ= github.com/valyala/fastjson v1.6.4 h1:uAUNq9Z6ymTgGhcm0UynUAB6tlbakBrz6CQFax3BXVQ= github.com/valyala/fastjson v1.6.4/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY= github.com/xrash/smetrics v0.0.0-20231213231151-1d8dd44e695e h1:+SOyEddqYF09QP7vr7CgJ1eti3pY9Fn3LHO1M1r/0sI= github.com/xrash/smetrics v0.0.0-20231213231151-1d8dd44e695e/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= golang-github-lestrrat-go-jwx-3.0.13/cmd/jwx/jwa.go000066400000000000000000000030751515060566400220740ustar00rootroot00000000000000package main import ( "fmt" "os" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/urfave/cli/v2" ) func init() { topLevelCommands = append(topLevelCommands, makeJwaCmd()) } func makeJwaCmd() *cli.Command { var cmd cli.Command cmd.Name = "jwa" cmd.Usage = "List available algorithms and types" cmd.Flags = []cli.Flag{ &cli.BoolFlag{ Name: "key-type", Aliases: []string{"k"}, }, &cli.BoolFlag{ Name: "elliptic-curve", Aliases: []string{"E"}, }, &cli.BoolFlag{ Name: "key-encryption", Aliases: []string{"K"}, }, &cli.BoolFlag{ Name: "content-encryption", Aliases: []string{"C"}, }, &cli.BoolFlag{ Name: "signature", Aliases: []string{"S"}, }, } cmd.Action = func(c *cli.Context) error { output := os.Stdout if c.Bool("key-type") { for _, alg := range jwa.KeyTypes() { fmt.Fprintf(output, "%s\n", alg) } return nil } if c.Bool("elliptic-curve") { for _, alg := range jwa.EllipticCurveAlgorithms() { fmt.Fprintf(output, "%s\n", alg) } return nil } if c.Bool("key-encryption") { for _, alg := range jwa.KeyEncryptionAlgorithms() { fmt.Fprintf(output, "%s\n", alg) } return nil } if c.Bool("content-encryption") { for _, alg := range jwa.ContentEncryptionAlgorithms() { fmt.Fprintf(output, "%s\n", alg) } return nil } if c.Bool("signature") { for _, alg := range jwa.SignatureAlgorithms() { fmt.Fprintf(output, "%s\n", alg) } return nil } cli.ShowCommandHelpAndExit(c, "jwa", 1) return nil // should not reach here } return &cmd } golang-github-lestrrat-go-jwx-3.0.13/cmd/jwx/jwe.go000066400000000000000000000116131515060566400220750ustar00rootroot00000000000000package main import ( "context" "fmt" "io" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/lestrrat-go/jwx/v3/jwe" "github.com/lestrrat-go/jwx/v3/jwk" "github.com/urfave/cli/v2" ) func init() { topLevelCommands = append(topLevelCommands, makeJweCmd()) } func makeJweCmd() *cli.Command { var cmd cli.Command cmd.Name = "jwe" cmd.Usage = "Work with JWE messages" cmd.Subcommands = []*cli.Command{ makeJweEncryptCmd(), makeJweDecryptCmd(), } return &cmd } func keyEncryptionFlag(required bool) cli.Flag { return &cli.StringFlag{ Name: "key-encryption", Aliases: []string{"K"}, Usage: "Key encryption algorithm name `NAME` (e.g. RSA-OAEP, ECDH-ES, A128GCMKW, etc)", Required: required, } } func makeJweEncryptCmd() *cli.Command { var cmd cli.Command cmd.Name = "encrypt" cmd.Usage = "Encrypt payload to generate JWE message" cmd.UsageText = `jwx jwe encrypt [command options] FILE Encrypt contents of FILE and generate a JWE message using the specified algorithms and key. Use "-" as FILE to read from STDIN. ` cmd.Aliases = []string{"enc"} cmd.Flags = []cli.Flag{ keyFlag("encrypt"), keyFormatFlag(), keyEncryptionFlag(true), &cli.StringFlag{ Name: "content-encryption", Aliases: []string{"C"}, Usage: "Content encryption algorithm name `NAME` (e.g. A128CBC-HS256, A192GCM, A256GCM, etc)", Required: true, }, &cli.BoolFlag{ Name: "compress", Aliases: []string{"z"}, Usage: "Enable compression", }, outputFlag(), } cmd.Action = func(c *cli.Context) error { src, err := getSource(c.Args().Get(0)) if err != nil { return err } defer src.Close() buf, err := io.ReadAll(src) if err != nil { return fmt.Errorf(`failed to read data from source: %w`, err) } var keyenc jwa.KeyEncryptionAlgorithm { v, ok := jwa.LookupKeyEncryptionAlgorithm(c.String("key-encryption")) if !ok { return fmt.Errorf(`invalid key encryption algorithm %q`, c.String("key-encryption")) } keyenc = v } var cntenc jwa.ContentEncryptionAlgorithm { v, ok := jwa.LookupContentEncryptionAlgorithm(c.String("content-encryption")) if !ok { return fmt.Errorf(`invalid content encryption algorithm %q`, c.String("content-encryption")) } cntenc = v } compress := jwa.NoCompress() if c.Bool("compress") { compress = jwa.Deflate() } keyset, err := getKeyFile(c.String("key"), c.String("key-format")) if err != nil { return err } if keyset.Len() != 1 { return fmt.Errorf(`jwk file must contain exactly one key`) } key, _ := keyset.Key(0) pubkey, err := jwk.PublicKeyOf(key) if err != nil { return fmt.Errorf(`failed to retrieve public key of %T: %w`, key, err) } encrypted, err := jwe.Encrypt(buf, jwe.WithKey(keyenc, pubkey), jwe.WithContentEncryption(cntenc), jwe.WithCompress(compress)) if err != nil { return fmt.Errorf(`failed to encrypt message: %w`, err) } output, err := getOutput(c.String("output")) if err != nil { return err } defer output.Close() fmt.Fprintf(output, "%s", encrypted) return nil } return &cmd } func makeJweDecryptCmd() *cli.Command { var cmd cli.Command cmd.Name = "decrypt" cmd.Aliases = []string{"dec"} cmd.Flags = []cli.Flag{ keyFlag("decrypt"), keyFormatFlag(), keyEncryptionFlag(false), outputFlag(), } cmd.Action = func(c *cli.Context) error { src, err := getSource(c.Args().Get(0)) if err != nil { return err } defer src.Close() buf, err := io.ReadAll(src) if err != nil { return fmt.Errorf(`failed to read data from source: %w`, err) } keyset, err := getKeyFile(c.String("key"), c.String("key-format")) if err != nil { return err } if keyset.Len() != 1 { return fmt.Errorf(`jwk file must contain exactly one key`) } key, _ := keyset.Key(0) var decrypted []byte if keyencalg := c.String("key-encryption"); keyencalg != "" { var keyenc jwa.KeyEncryptionAlgorithm { v, ok := jwa.LookupKeyEncryptionAlgorithm(keyencalg) if !ok { return fmt.Errorf(`invalid key encryption algorithm %q`, keyencalg) } keyenc = v } // if we have an explicit key encryption algorithm, we don't have to // guess it. v, err := jwe.Decrypt(buf, jwe.WithKey(keyenc, key)) if err != nil { return fmt.Errorf(`failed to decrypt message: %w`, err) } decrypted = v } else { v, err := jwe.Decrypt(buf, jwe.WithKeyProvider(jwe.KeyProviderFunc(func(_ context.Context, sink jwe.KeySink, r jwe.Recipient, _ *jwe.Message) error { alg, ok := r.Headers().Algorithm() if !ok { return fmt.Errorf(`failed to determine key encryption algorithm`) } sink.Key(alg, key) return nil }))) if err != nil { return fmt.Errorf(`failed to decrypt message: %w`, err) } decrypted = v } output, err := getOutput(c.String("output")) if err != nil { return err } defer output.Close() fmt.Fprintf(output, "%s", decrypted) return nil } return &cmd } golang-github-lestrrat-go-jwx-3.0.13/cmd/jwx/jwk.go000066400000000000000000000160221515060566400221020ustar00rootroot00000000000000package main import ( "bytes" "crypto/ecdh" "crypto/ecdsa" "crypto/rand" "crypto/rsa" "encoding/json" "fmt" "io" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/lestrrat-go/jwx/v3/jwk" ourecdsa "github.com/lestrrat-go/jwx/v3/jwk/ecdsa" "github.com/urfave/cli/v2" "golang.org/x/crypto/ed25519" ) func init() { topLevelCommands = append(topLevelCommands, makeJwkCmd()) } func jwkSetFlag() cli.Flag { return &cli.BoolFlag{ Name: "set", Usage: "generate as a JWK set", } } func jwkOutputFormatFlag() cli.Flag { return &cli.StringFlag{ Name: "output-format", Aliases: []string{"O"}, Value: "json", Usage: "Output format `OUTPUT` (json/pem)", } } func publicKeyFlag() cli.Flag { return &cli.BoolFlag{ Name: "public-key", Aliases: []string{"p"}, Usage: "Display public key version of the key", } } func makeJwkCmd() *cli.Command { var cmd cli.Command cmd.Name = "jwk" cmd.Usage = "Work with JWK and JWK sets" cmd.Subcommands = []*cli.Command{ makeJwkGenerateCmd(), makeJwkFormatCmd(), } return &cmd } func dumpJWKSet(dst io.Writer, keyset jwk.Set, format string, preserve bool) error { if format == "pem" { buf, err := jwk.Pem(keyset) if err != nil { return fmt.Errorf(`failed to format key in PEM format: %w`, err) } if _, err := dst.Write(buf); err != nil { return fmt.Errorf(`failed to write to destination: %w`, err) } return nil } if format == "json" { if preserve || keyset.Len() != 1 { if err := dumpJSON(dst, keyset); err != nil { return fmt.Errorf(`failed to marshal keyset into JSON format: %w`, err) } } else { key, _ := keyset.Key(0) if err := dumpJSON(dst, key); err != nil { return fmt.Errorf(`failed to marshal key into JSON format: %w`, err) } } return nil } return fmt.Errorf(`invalid JWK format "%s"`, format) } func makeJwkGenerateCmd() *cli.Command { var crvnames bytes.Buffer for i, crv := range jwa.EllipticCurveAlgorithms() { if i > 0 { crvnames.WriteByte('/') } crvnames.WriteString(crv.String()) } var cmd cli.Command cmd.Name = "generate" cmd.Aliases = []string{"gen"} cmd.Usage = "Generate a new JWK private key" cmd.Flags = []cli.Flag{ &cli.StringFlag{ Name: "type", Aliases: []string{"t"}, Usage: "JWK type `TYPE` (RSA/EC/OKP/oct)", Required: true, }, &cli.StringFlag{ Name: "curve", Aliases: []string{"c"}, Usage: "Elliptic curve name `CURVE` (" + crvnames.String() + ") for ECDSA and OKP keys", }, &cli.StringFlag{ Name: "template", Usage: `Extra values in the JWK as JSON object`, }, &cli.IntFlag{ Name: "keysize", Aliases: []string{"s"}, Usage: "Integer `SIZE` for RSA and oct key sizes", Value: 2048, }, publicKeyFlag(), outputFlag(), jwkOutputFormatFlag(), jwkSetFlag(), } cmd.Action = func(c *cli.Context) error { var rawkey any typ, ok := jwa.LookupKeyType(c.String("type")) if !ok { return fmt.Errorf(`invalid key type %s`, c.String("type")) } switch typ { case jwa.RSA(): v, err := rsa.GenerateKey(rand.Reader, c.Int("keysize")) if err != nil { return fmt.Errorf(`failed to generate rsa private key: %w`, err) } rawkey = v case jwa.EC(): var crvalg jwa.EllipticCurveAlgorithm { v, ok := jwa.LookupEllipticCurveAlgorithm(c.String("curve")) if !ok { return fmt.Errorf(`invalid elliptic curve name %q`, c.String("curve")) } crvalg = v } crv, err := ourecdsa.CurveFromAlgorithm(crvalg) if err != nil { return fmt.Errorf(`invalid elliptic curve for ECDSA: %s (expected %s): %w`, crvalg, crvnames.String(), err) } v, err := ecdsa.GenerateKey(crv, rand.Reader) if err != nil { return fmt.Errorf(`failed to generate ECDSA private key: %w`, err) } rawkey = v case jwa.OctetSeq(): octets := make([]byte, c.Int("keysize")) io.ReadFull(rand.Reader, octets) rawkey = octets case jwa.OKP(): var crvalg jwa.EllipticCurveAlgorithm { v, ok := jwa.LookupEllipticCurveAlgorithm(c.String("curve")) if !ok { return fmt.Errorf(`invalid elliptic curve name %q`, c.String("curve")) } crvalg = v } switch crvalg { case jwa.Ed25519(): _, priv, err := ed25519.GenerateKey(rand.Reader) if err != nil { return fmt.Errorf(`failed to generate ed25519 private key: %w`, err) } rawkey = priv case jwa.X25519(): priv, err := ecdh.X25519().GenerateKey(rand.Reader) if err != nil { return fmt.Errorf(`failed to generate x25519 private key: %w`, err) } rawkey = priv default: return fmt.Errorf(`invalid elliptic curve for OKP: %s (expected %s/%s)`, crvalg, jwa.Ed25519(), jwa.X25519()) } default: return fmt.Errorf(`invalid key type %s`, typ) } var attrs map[string]any if tmpl := c.String("template"); tmpl != "" { if err := json.Unmarshal([]byte(tmpl), &attrs); err != nil { return fmt.Errorf(`failed to unmarshal template: %w`, err) } } key, err := jwk.Import(rawkey) if err != nil { return fmt.Errorf(`failed to create new JWK from raw key: %w`, err) } for k, v := range attrs { if err := key.Set(k, v); err != nil { return fmt.Errorf(`failed to set field %s: %w`, k, err) } } keyset := jwk.NewSet() keyset.AddKey(key) if c.Bool("public-key") { pubks, err := jwk.PublicSetOf(keyset) if err != nil { return fmt.Errorf(`failed to generate public keys: %w`, err) } keyset = pubks } output, err := getOutput(c.String("output")) if err != nil { return err } defer output.Close() return dumpJWKSet(output, keyset, c.String("output-format"), c.Bool("set")) } return &cmd } func makeJwkFormatCmd() *cli.Command { var cmd cli.Command cmd.Name = "format" cmd.Aliases = []string{"fmt"} cmd.Usage = "Format JWK" cmd.Flags = []cli.Flag{ publicKeyFlag(), &cli.StringFlag{ Name: "input-format", Aliases: []string{"I"}, Value: "json", Usage: "Input format `INPUT` (json/pem)", }, jwkOutputFormatFlag(), jwkSetFlag(), outputFlag(), } // jwx jwk format cmd.Action = func(c *cli.Context) error { if c.Args().Get(0) == "" { cli.ShowCommandHelpAndExit(c, "format", 1) } src, err := getSource(c.Args().Get(0)) if err != nil { return err } defer src.Close() buf, err := io.ReadAll(src) if err != nil { return fmt.Errorf(`failed to read data from source: %w`, err) } var options []jwk.ParseOption switch format := c.String("input-format"); format { case "json": case "pem": options = append(options, jwk.WithPEM(true)) default: return fmt.Errorf(`invalid input format %s`, format) } keyset, err := jwk.Parse(buf, options...) if err != nil { return fmt.Errorf(`failed to parse keyset: %w`, err) } output, err := getOutput(c.String("output")) if err != nil { return err } defer output.Close() if c.Bool("public-key") { pubks, err := jwk.PublicSetOf(keyset) if err != nil { return fmt.Errorf(`failed to generate public keys: %w`, err) } keyset = pubks } return dumpJWKSet(output, keyset, c.String("output-format"), c.Bool("set")) } return &cmd } golang-github-lestrrat-go-jwx-3.0.13/cmd/jwx/jws.go000066400000000000000000000152441515060566400221170ustar00rootroot00000000000000package main import ( "encoding/json" "fmt" "io" "os" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/lestrrat-go/jwx/v3/jwk" "github.com/lestrrat-go/jwx/v3/jws" "github.com/urfave/cli/v2" ) func init() { topLevelCommands = append(topLevelCommands, makeJwsCmd()) } func jwsAlgorithmFlag(use string) cli.Flag { return &cli.StringFlag{ Name: "alg", Aliases: []string{"a"}, Usage: "algorithm `ALG` to use to " + use + " the message with", } } func makeJwsCmd() *cli.Command { var cmd cli.Command cmd.Name = "jws" cmd.Usage = "Work with JWS messages" cmd.Subcommands = []*cli.Command{ makeJwsParseCmd(), makeJwsSignCmd(), makeJwsVerifyCmd(), } return &cmd } func makeJwsParseCmd() *cli.Command { var cmd cli.Command cmd.Name = "parse" cmd.Usage = "Parse JWS message" cmd.UsageText = `jwx jws parse [command options] FILE Parse FILE and display information about a JWS message. Use "-" as FILE to read from STDIN. ` // jwx jws parse cmd.Action = func(c *cli.Context) error { src, err := getSource(c.Args().Get(0)) if err != nil { return err } defer src.Close() buf, err := io.ReadAll(src) if err != nil { return fmt.Errorf(`failed to read data from source: %w`, err) } msg, err := jws.Parse(buf) if err != nil { return fmt.Errorf(`failed to parse message: %w`, err) } jsbuf, err := json.MarshalIndent(msg, "", " ") if err != nil { return fmt.Errorf(`failed to marshal message: %w`, err) } fmt.Fprintf(os.Stdout, "%s\n\n", jsbuf) for i, sig := range msg.Signatures() { sigbuf, err := json.MarshalIndent(sig.ProtectedHeaders(), "", " ") if err != nil { return fmt.Errorf(`failed to marshal signature %d: %w`, 1, err) } fmt.Fprintf(os.Stdout, "Signature %d: %s\n", i, sigbuf) } return nil } return &cmd } func makeJwsVerifyCmd() *cli.Command { var cmd cli.Command cmd.Name = "verify" cmd.Aliases = []string{"ver"} cmd.Usage = "Verify JWS messages." cmd.UsageText = `jwx jws verify [command options] FILE Parses a JWS message in FILE, and verifies using the specified method. Use "-" as FILE to read from STDIN. By default the user is responsible for providing the algorithm to use to verify the signature. This is because we can not safely rely on the "alg" field of the JWS message to deduce which key to use. See https://auth0.com/blog/critical-vulnerabilities-in-json-web-token-libraries/ The alternative is to match a key based on explicitly specified key ID ("kid"). In this case the following conditions must be met for a successful verification: (1) JWS message must list the key ID that it expects (2) At least one of the provided JWK must contain the same key ID (3) The same key must also contain the "alg" field Therefore, the following key may be able to successfully verify a JWS message using "--match-kid": { "typ": "oct", "alg": "H256", "kid": "mykey", .... } But the following two will never succeed because they lack either "alg" or "kid" { "typ": "oct", "kid": "mykey", .... } { "typ": "oct", "alg": "H256", .... } ` cmd.Flags = []cli.Flag{ jwsAlgorithmFlag("verify"), keyFlag("verify"), keyFormatFlag(), &cli.BoolFlag{ Name: "match-kid", Value: false, Usage: "instead of using alg, attempt to verify only if the key ID (kid) matches", }, outputFlag(), } // jwx jws verify cmd.Action = func(c *cli.Context) error { keyset, err := getKeyFile(c.String("key"), c.String("key-format")) if err != nil { return err } keyset, err = jwk.PublicSetOf(keyset) if err != nil { return fmt.Errorf(`failed to retrieve public key: %w`, err) } src, err := getSource(c.Args().Get(0)) if err != nil { return err } defer src.Close() buf, err := io.ReadAll(src) if err != nil { return fmt.Errorf(`failed to read data from source: %w`, err) } output, err := getOutput(c.String("output")) if err != nil { return err } defer output.Close() if c.Bool("match-kid") { payload, err := jws.Verify(buf, jws.WithKeySet(keyset)) if err == nil { fmt.Fprintf(output, "%s", payload) return nil } } else { var alg jwa.SignatureAlgorithm { v, ok := jwa.LookupSignatureAlgorithm(c.String("alg")) if !ok { return fmt.Errorf(`invalid algorithm %s`, c.String("alg")) } alg = v } for i := 0; i < keyset.Len(); i++ { key, ok := keyset.Key(i) if !ok { return fmt.Errorf(`failed to retrieve key %d`, i) } payload, err := jws.Verify(buf, jws.WithKey(alg, key)) if err == nil { fmt.Fprintf(output, "%s", payload) return nil } } } return fmt.Errorf(`could not verify with any of the keys`) } return &cmd } func makeJwsSignCmd() *cli.Command { var cmd cli.Command cmd.Name = "sign" cmd.Aliases = []string{"sig"} cmd.Usage = "Verify JWS message" cmd.UsageText = `jwx jws sign [command options] FILE Signs the payload in FILE and generates a JWS message in compact format. Use "-" as FILE to read from STDIN. Currently only single key signature mode is supported. ` cmd.Flags = []cli.Flag{ jwsAlgorithmFlag("sign"), keyFlag("sign"), keyFormatFlag(), &cli.StringFlag{ Name: "header", Usage: "header object to inject into JWS message protected header", }, outputFlag(), } // jwx jws verify cmd.Action = func(c *cli.Context) error { keyset, err := getKeyFile(c.String("key"), c.String("key-format")) if err != nil { return err } if keyset.Len() != 1 { return fmt.Errorf(`jwk file must contain exactly one key`) } key, _ := keyset.Key(0) src, err := getSource(c.Args().Get(0)) if err != nil { return err } defer src.Close() buf, err := io.ReadAll(src) if err != nil { return fmt.Errorf(`failed to read data from source: %w`, err) } var alg jwa.SignatureAlgorithm { v, ok := jwa.LookupSignatureAlgorithm(c.String("alg")) if !ok { return fmt.Errorf(`invalid algorithm %s`, c.String("alg")) } alg = v } // headers must go to WithKeySuboptions var suboptions []jws.WithKeySuboption if hdrbuf := c.String("header"); hdrbuf != "" { h := jws.NewHeaders() if err := json.Unmarshal([]byte(hdrbuf), h); err != nil { return fmt.Errorf(`failed to parse header: %w`, err) } suboptions = append(suboptions, jws.WithProtectedHeaders(h)) } var options []jws.SignOption options = append(options, jws.WithKey(alg, key, suboptions...)) signed, err := jws.Sign(buf, options...) if err != nil { return fmt.Errorf(`failed to sign payload: %w`, err) } output, err := getOutput(c.String("output")) if err != nil { return err } defer output.Close() fmt.Fprintf(output, "%s", signed) return nil } return &cmd } golang-github-lestrrat-go-jwx-3.0.13/cmd/jwx/jwx.go000066400000000000000000000050431515060566400221200ustar00rootroot00000000000000package main import ( "encoding/json" "fmt" "io" "os" "sort" "strings" "github.com/lestrrat-go/jwx/v3/jwk" "github.com/urfave/cli/v2" ) var topLevelCommands []*cli.Command type dummyWriteCloser struct { io.Writer } func (*dummyWriteCloser) Close() error { return nil } func outputFlag() cli.Flag { return &cli.StringFlag{ Name: "output", Aliases: []string{"o"}, Usage: "Write output to `FILE`", Value: "-", } } func keyFlag(use string) cli.Flag { return &cli.StringFlag{ Name: "key", Aliases: []string{"k"}, Usage: "`FILE` containing the key to " + use + " with", Required: true, } } func keyFormatFlag() cli.Flag { return &cli.StringFlag{ Name: "key-format", Usage: "JWK format: json or pem", Value: "json", } } func main() { var app cli.App app.Commands = topLevelCommands app.Usage = "Tools for various JWE/JWK/JWS/JWT operations" sort.Slice(app.Commands, func(i, j int) bool { return strings.Compare(app.Commands[i].Name, app.Commands[j].Name) < 0 }) if err := app.Run(os.Args); err != nil { fmt.Fprintf(os.Stderr, "%s\n", err) os.Exit(1) } } func dumpJSON(dst io.Writer, v any) error { buf, err := json.MarshalIndent(v, "", " ") if err != nil { return fmt.Errorf(`failed to serialize to JSON: %w`, err) } dst.Write(buf) return nil } func getSource(filename string) (io.ReadCloser, error) { var src io.ReadCloser if filename == "-" { src = io.NopCloser(os.Stdin) } else { if filename == "" { return nil, fmt.Errorf(`filename required (use "-" to read from stdin)`) } f, err := os.Open(filename) if err != nil { return nil, fmt.Errorf(`failed to open file %s: %w`, filename, err) } src = f } return src, nil } func getOutput(filename string) (io.WriteCloser, error) { var output io.WriteCloser switch filename { case "-": output = &dummyWriteCloser{os.Stdout} case "": return nil, fmt.Errorf(`output must be a file name, or "-" for STDOUT`) default: f, err := os.OpenFile(filename, os.O_CREATE|os.O_WRONLY, 0644) if err != nil { return nil, fmt.Errorf(`failed to create file %s: %w`, filename, err) } output = f } return output, nil } func getKeyFile(keyfile, format string) (jwk.Set, error) { var keyoptions []jwk.ReadFileOption switch format { case "json": case "pem": keyoptions = append(keyoptions, jwk.WithPEM(true)) default: return nil, fmt.Errorf(`invalid JWK format "%s"`, format) } keyset, err := jwk.ReadFile(keyfile, keyoptions...) if err != nil { return nil, fmt.Errorf(`failed to parse key: %w`, err) } return keyset, nil } golang-github-lestrrat-go-jwx-3.0.13/codecov.yml000066400000000000000000000000501515060566400215440ustar00rootroot00000000000000codecov: allow_coverage_offsets: true golang-github-lestrrat-go-jwx-3.0.13/docs/000077500000000000000000000000001515060566400203345ustar00rootroot00000000000000golang-github-lestrrat-go-jwx-3.0.13/docs/00-anatomy.md000066400000000000000000000132171515060566400225470ustar00rootroot00000000000000# 1. Anatomy of JOSE and JWX In this article we will briefly go over what JOSE is, and how each JWX libraries map to the respective RFCs. --- # JOSE Overview Javascript Object Signing and Encryption mainly consists of specifications that span over 5 RFCs: namely RFC7515, RFC7516, RFC7517, RFC7518, and RFC7519. * [RFC 7515 - JSON Web Signature (JWS)](https://tools.ietf.org/html/rfc7515) * [RFC 7516 - JSON Web Encryption (JWE)](https://tools.ietf.org/html/rfc7516) * [RFC 7517 - JSON Web Key (JWK)](https://tools.ietf.org/html/rfc7517) * [RFC 7518 - JSON Web Algorithms (JWA)](https://tools.ietf.org/html/rfc7518) * [RFC 7519 - JSON Web Token (JWT)](https://tools.ietf.org/html/rfc7519) As there are many RFCs involved, at first it could be daunting. But here is a breakdown for beginners. While there are other standards and RFCs available related to JOSE such as extensions like ES256K, the above 5 concepts are at the core. JWX is a Go specific implementation of the JOSE ecosystem, and attempts to do this as cleanly as possible. # JWT - RFC7519 First, you are probably here because you were trying to work with JWTs, which is described in RFC7519.  JWTs are implemented in github.com/lestrrat-go/jwx/v3/jwt package. There is also a package to deal with OpenID extensions in github.com/lestrrat-go/jwx/v3/jwt/openid.  In its essence, there is nothing special about JWTs: they are just a standardized format to exchange some piece of information. Most of the times this information must be integrity checked using signatures, securely encrypted, or both. While they are referenced from RFC7519, the standardized message formats for signed and/or encrypted JWTs are not in the same RFC. As a side note, many libraries bundle these signature/encryption features into one JWT package, and the API becomes tightly coupled with the JWT, which I find confusing and hard to fix/extend which is part of the reason why JWX was born. ## Documentation for `github.com/lestrrat-go/jwx/v3/jwt` * [FAQ-style HOW-TOs](./01-jwt.md) * [Documentation on pkg.go.dev](https://pkg.go.dev/github.com/lestrrat-go/jwx/v3/jwt) # JWS - RFC7515 RFC7515 describes JWS, which is used to sign payloads. The format is not necessarily for JWTs: it can sign any arbitrary piece of data. The signed content can be encoded in two different formats. The most common one is called the compact form, and takes the form of three base64 encoded strings, concatenated by a single period ("."). The other format takes the form of a JSON object. ``` # An example of a "compact" JWS message eyJhbGciOiJFUzI1NiJ9.SGVsbG8sIFdvcmxkCg.3q5N5JyFphiJolUZuBuUZhuWDfmLDR__rZe3lnuaxWe3bfrfvJS9HmUUhie56NqkyN7vjOl8hm6tzJKTc2oNsg ``` Please note that a JWS message may take three forms: compact, full JSON, and flattened JSON serialization. ```mermaid graph TD RawData[Raw Data] --> |"three base64 encoded segments,
concatenated with ."| Compact[Compact Serialization] RawData --> | JSON | JSON[JSON Serialization] JSON --> |"does NOT have 'signature'"| FullJSON[Full JSON Serialization] JSON --> |"has 'signature'"| Flat[Flattened JSON Serialization] ``` JWS is implemented in github.com/lestrrat-go/jwx/v3/jws package. This package provides ways to sign arbitrary payload into JWS message, and ways to verify them. ## Documentation for `github.com/lestrrat-go/jwx/v3/jws` * [FAQ-style HOW-TOs](./02-jws.md) * [Documentation on pkg.go.dev](https://pkg.go.dev/github.com/lestrrat-go/jwx/v3/jws) # JWE - RFC7516 RFC7516 describes JWE which is used to encrypt data. Similar to JWS, JWE describes the standardized format to encrypt any arbitrary data, not just JWTs. And again, similar to JWS, JWE can encode data into two different formats, one of which consists of 5 base64 encoded strings concatenated by a single period ("."). The other format takes the form of a JSON object. ``` # An example of JWE message eyJhbGciOiJFQ0RILUVTIiwiZW5jIjoiQTE5MkdDTSIsImVwayI6eyJjcnYiOiJQLTI1NiIsImt0eSI6IkVDIiwieCI6IndMckhLNnBTLXZzdmhQZUNfNTN0ZWpxYzZIZUFsMllRWDRmY1hPNGV1bmciLCJ5IjoiV2V3bFdKazJ4QWJYSXE3WFJ6aVlZa2lxMjJfOF9TQ0VsbTA1Vm1iUGhFWSJ9fQ..7UTcbVpz-Ed1Q0wq.sneVfeTeAvzZNSMGpQ.JNo1BbDaKB-Q1mWaBNmdow ``` JWE is implemented in github.com/lestrrat-go/jwe package. This package provides tools to encrypt payload into JWE messages, and tools to decrypt them. ## Documentation for `github.com/lestrrat-go/jwx/v3/jwe` * [Documentation on pkg.go.dev](https://pkg.go.dev/github.com/lestrrat-go/jwx/v3/jwe) # JWK - RFC7517 So JWTs can be signed and/or encrypted using JWS and JWE. However, in order to do either operation one needs some sort of key. This is where RFC757 which describes JWK comes into play. JWKs describe formats to describe keys, such as RSA keys, Elliptic Curve keys, symmetric keys, etc. Each family of keys have a slightly different, unique format, but they are all encoded as JSON objects.  ``` # An example of a JWK { "crv": "P-256", "d": "vVLuV63SADBsZrixRXs3hcteS7SaBrMPnxERz6G1xZ8", "kty": "EC", "x": "uNs7UU1KU9nug9QrfxLmxLtYZLQDnYcX7_J3zkTkhCk", "y": "IGsWjOsDHFbeiQg-Reih-a1BsijmV2RUPPGswl8TRTI" } ``` JWK is implemented in github.com/lestrrat-go/jwx/v3/jwk package. This package provides ways to generate, read, and manipulate JWKs, convert them to and from raw key formats (e.g. "crypto/rsa".PrivateKey to JWK and vice versa). It can also work with PEM-encoded keys. ## Documentation for `github.com/lestrrat-go/jwx/v3/jwk` * [FAQ-style HOW-TOs](./04-jwk.md) * [Documentation on pkg.go.dev](https://pkg.go.dev/github.com/lestrrat-go/jwx/v3/jwk) # JWA - RFC7518 And finally, an RFC exists to define commonly used algorithm names in JWT, JWS, JWE, and JWK. This is implemented in github.com/lestrrat-go/jwx/v3/jwa. golang-github-lestrrat-go-jwx-3.0.13/docs/01-jwt.md000066400000000000000000001530211515060566400217020ustar00rootroot00000000000000# Working with JWT In this document we describe how to work with JWT using `github.com/lestrrat-go/jwx/v3/jwt` - [Terminology](#terminology) - [Verification](#verification) - [Validation](#validation) - [Parsing](#parsing) - [Parse a JWT](#parse-a-jwt) - [Parse a JWT from file](#parse-a-jwt-from-file) - [Parse a JWT from a \*http.Request](#parse-a-jwt-from-a-httprequest) - [Programmatically Creating a JWT](#programmatically-creating-a-jwt) - [Using jwt.New](#using-jwtnew) - [Using Builder](#using-builder) - [Verification](#jwt-verification) - [Parse and Verify a JWT (with a single key)](#parse-and-verify-a-jwt-with-single-key) - [Parse and Verify a JWT (with a key set, matching `kid`)](#parse-and-verify-a-jwt-with-a-key-set-matching-kid) - [Parse and Verify a JWT (using arbitrary keys)](#parse-and-verify-a-jwt-using-arbitrary-keys) - [Parse and Verify a JWT (using key specified in `jku`)](#parse-and-verify-a-jwt-using-key-specified-in-jku) - [Validation](#jwt-validation) - [Validate for specific claim values](#validate-for-specific-claim-values) - [Use a custom validator](#use-a-custom-validator) - [Detecting error types](#detecting-error-types) - [Filtering Claims](#filtering-claims) - [Filtering Using Standard Claim Names](#filtering-using-standard-claim-names) - [Advanced filtering scenarios](#advanced-filtering-scenarios) - [Serialization](#serialization) - [Serialize using JWS](#serialize-using-jws) - [Serialize using JWE and JWS](#serialize-using-jwe-and-jws) - [Serialize the `aud` field as a single string](#serialize-the-aud-field-as-a-single-string) - [Working with JWT](#working-with-jwt-1) - [Performance](#performance) - [Access JWS headers](#access-jws-headers) - [Get/Set fields](#getset-fields) - [Using a custom base64 encoder](#using-a-custom-base64-encoder) --- # Terminology ## Verification We use the terms "verify" and "verification" to describe the process of ensuring the integrity of the JWT, namely the signature verification. ## Validation We use the terms "validate" and "validation" to describe the process of checking the contents of a JWT, for example if the values in fields such as "iss", "sub", "aud" match our expected values, and/or if the token has expired. # Parsing Parsing a payload as JWT consists of multiple distinct operations. Typically, your JWTs are signed and serialized as JWS messages. The JWT is _enveloped_ in JWS. The following is a [sample JWS message serialized in compact form](https://datatracker.ietf.org/doc/html/rfc7515#appendix-A.1): ``` eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjX ``` This message consists of three data segments encoded in `base64`, concatenated with a `.`. Each part reads as follows: - **Part 1**: The JWS protected headers. These are metadata required to verify the signed payload. - **Part 2**: The JWS payload. This can be any arbitrary data, but in our case it would be a JWT object. - **Part 3**: The JWS signature. This is the signature generated from the signing key, the headers, and the payload. It is important to realize that JWS in itself has nothing to do with JWT. The envelope and therefore the JWS mechanism itself does not care that the payload is JWT or not. Once we verify the integrity of the payload using JWS verification, the payload can then be trusted to be untampered. Therefore, while the JWS payload _could_ theoretically be decoded as a JWT object before verification, its contents should not be trusted -- e.g. it should not be used to store information that has to do with verification. The `jwt.Parse()` function in this package not only provides ways to decode a JWT object from JSON, but it also provides convenient ways to perform the above verification and decoding of the JWT object in one go, as well as validating the contents of the JWT object after it has been decoded. ## Parse a JWT To parse a JWT in either raw JSON or JWS compact serialization format, use [`jwt.Parse()`](https://pkg.go.dev/github.com/lestrrat-go/jwx/v3/jwt#Parse) ```go package examples_test import ( "fmt" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/lestrrat-go/jwx/v3/jwt" ) func Example_jwt_parse() { tok, err := jwt.Parse(jwtSignedWithHS256, jwt.WithKey(jwa.HS256(), jwkSymmetricKey)) if err != nil { fmt.Printf("%s\n", err) return } _ = tok // OUTPUT: } ``` source: [examples/jwt_parse_example_test.go](https://github.com/lestrrat-go/jwx/blob/v3/examples/jwt_parse_example_test.go) Note that the above form performs only signature verification and no validation of the JWT token itself. In order to perform validation, please use `Validate()`. ## Parse a JWT from file To parsea JWT stored in a file, use [`jwt.ReadFile()`](https://pkg.go.dev/github.com/lestrrat-go/jwx/v3/jwt#ReadFile). [`jwt.ReadFile()`](https://pkg.go.dev/github.com/lestrrat-go/jwx/v3/jwt#ReadFile) accepts the same options as [`jwt.Parse()`](https://pkg.go.dev/github.com/lestrrat-go/jwx/v3/jwt#Parse). ```go package examples_test import ( "fmt" "os" "github.com/lestrrat-go/jwx/v3/jwt" ) func Example_jwt_readfile() { f, err := os.CreateTemp(``, `jwt_readfile-*.jws`) if err != nil { fmt.Printf("failed to create temporary file: %s\n", err) return } defer os.Remove(f.Name()) fmt.Fprint(f, exampleJWTSignedHMAC) f.Close() // Note: this JWT has NOT been verified because we have not passed jwt.WithKey() and used // jwt.WithVerify(false). You need to pass jwt.WithKey() if you want the token to be parsed and // verified in one go. tok, err := jwt.ReadFile(f.Name(), jwt.WithVerify(false), jwt.WithValidate(false)) if err != nil { fmt.Printf("failed to read file %q: %s\n", f.Name(), err) return } _ = tok // OUTPUT: } ``` source: [examples/jwt_readfile_example_test.go](https://github.com/lestrrat-go/jwx/blob/v3/examples/jwt_readfile_example_test.go) ## Parse a JWT from a \*http.Request To parse a JWT stored within a \*http.Request object, use [`jwt.ParseRequest()`](https://pkg.go.dev/github.com/lestrrat-go/jwx/v3/jwt#ParseRequest). It by default looks for JWTs stored in the "Authorization" header, but can be configured to look under other headers and within the form fields. ```go package examples_test import ( "fmt" "net/http" "net/url" "github.com/lestrrat-go/jwx/v3/jwt" ) func Example_jwt_parse_request_authorization() { req, err := http.NewRequest(http.MethodGet, `https://github.com/lestrrat-go/jwx`, nil) if err != nil { fmt.Printf("failed to create request: %s\n", err) return } req.Form = url.Values{} req.Form.Add("access_token", exampleJWTSignedHMAC) req.Header.Set(`Authorization`, fmt.Sprintf(`Bearer %s`, exampleJWTSignedECDSA)) req.Header.Set(`X-JWT-Token`, exampleJWTSignedRSA) req.AddCookie(&http.Cookie{Name: "accessToken", Value: exampleJWTSignedHMAC}) var dst *http.Cookie testcases := []struct { options []jwt.ParseOption }{ // No options - looks under "Authorization" header {}, // Looks under "X-JWT-Token" header only { options: []jwt.ParseOption{jwt.WithHeaderKey(`X-JWT-Token`)}, }, // Looks under "Authorization" and "X-JWT-Token" headers { options: []jwt.ParseOption{jwt.WithHeaderKey(`Authorization`), jwt.WithHeaderKey(`X-JWT-Token`)}, }, // Looks under "Authorization" header and "access_token" form field { options: []jwt.ParseOption{jwt.WithFormKey(`access_token`)}, }, // Looks under "accessToken" cookie, and assigns the http.Cookie object // where the token came from to the variable `dst` { options: []jwt.ParseOption{jwt.WithCookieKey(`accessToken`), jwt.WithCookie(&dst)}, }, } for _, tc := range testcases { options := append(tc.options, []jwt.ParseOption{jwt.WithVerify(false), jwt.WithValidate(false)}...) tok, err := jwt.ParseRequest(req, options...) if err != nil { fmt.Print("jwt.ParseRequest with options [") for i, option := range tc.options { if i > 0 { fmt.Print(", ") } fmt.Printf("%s", option) } fmt.Printf("]: %s\n", err) return } _ = tok } if dst == nil { fmt.Printf("failed to assign cookie to dst\n") return } // OUTPUT: } ``` source: [examples/jwt_parse_request_example_test.go](https://github.com/lestrrat-go/jwx/blob/v3/examples/jwt_parse_request_example_test.go) # Programmatically Creating a JWT ## Using `jwt.New` The most straight forward way is to use the constructor `jwt.New()` and use `(jwt.Token).Set()`: ```go package examples_test import ( "encoding/json" "fmt" "os" "github.com/lestrrat-go/jwx/v3/jwt" ) func Example_jwt_construct() { tok := jwt.New() if err := tok.Set(jwt.IssuerKey, `github.com/lestrrat-go/jwx`); err != nil { fmt.Printf("failed to set claim: %s\n", err) return } if err := tok.Set(jwt.AudienceKey, `users`); err != nil { fmt.Printf("failed to set claim: %s\n", err) return } if err := json.NewEncoder(os.Stdout).Encode(tok); err != nil { fmt.Printf("failed to encode to JSON: %s\n", err) return } // OUTPUT: // {"aud":["users"],"iss":"github.com/lestrrat-go/jwx"} } ``` source: [examples/jwt_construct_example_test.go](https://github.com/lestrrat-go/jwx/blob/v3/examples/jwt_construct_example_test.go) If repeatedly checking for errors in `Set()` sounds like too much trouble, consider using the builder. ## Using Builder Since v1.2.12, the `jwt` package comes with a builder, which you can use to initialize a JWT token in (almost) one go. For known fields, you can use the special methods such as `Issuer()` and `Audience()`. For other claims you can use the `Claim()` method. One caveat that you should be aware about is that all calls to set a claim in the builder performs an _overwriting_ operation. If you set the same claim multiple times, the last value is used. ```go package examples_test import ( "encoding/json" "fmt" "os" "github.com/lestrrat-go/jwx/v3/jwt" ) func Example_jwt_builder() { tok, err := jwt.NewBuilder(). Claim(`claim1`, `value1`). Claim(`claim2`, `value2`). Issuer(`github.com/lestrrat-go/jwx`). Audience([]string{`users`}). Build() if err != nil { fmt.Printf("failed to build token: %s\n", err) return } if err := json.NewEncoder(os.Stdout).Encode(tok); err != nil { fmt.Printf("failed to encode to JSON: %s\n", err) return } // OUTPUT: // {"aud":["users"],"claim1":"value1","claim2":"value2","iss":"github.com/lestrrat-go/jwx"} } ``` source: [examples/jwt_builder_example_test.go](https://github.com/lestrrat-go/jwx/blob/v3/examples/jwt_builder_example_test.go) # JWT Verification ## Parse and Verify a JWT (with single key) To parse a JWT _and_ verify that its content matches the signature as described in the JWS message, you need to add some options when calling the [`jwt.Parse()`](https://pkg.go.dev/github.com/lestrrat-go/jwx/v3/jwt#Parse) function. ```go package examples_test import ( "fmt" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/lestrrat-go/jwx/v3/jwk" "github.com/lestrrat-go/jwx/v3/jwt" ) func Example_jwt_parse_with_key() { const keysrc = `{"kty":"oct","k":"AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow"}` key, err := jwk.ParseKey([]byte(keysrc)) if err != nil { fmt.Printf("jwk.ParseKey failed: %s\n", err) return } tok, err := jwt.Parse([]byte(exampleJWTSignedHMAC), jwt.WithKey(jwa.HS256(), key), jwt.WithValidate(false)) if err != nil { fmt.Printf("jwt.Parse failed: %s\n", err) return } _ = tok // OUTPUT: } ``` source: [examples/jwt_parse_with_key_example_test.go](https://github.com/lestrrat-go/jwx/blob/v3/examples/jwt_parse_with_key_example_test.go) In the above example, `key` may either be the raw key (i.e. "crypto/ecdsa".PublicKey, "crypto/ecdsa".PrivateKey) or an instance of [`jwk.Key`](https://pkg.go.dev/github.com/lestrrat-go/jwx/v3/jwk#Key) (i.e. [`jwk.ECDSAPrivateKey`](https://pkg.go.dev/github.com/lestrrat-go/jwx/v3/jwk#ECDSAPrivateKey), [`jwk.ECDSAPublicKey`](https://pkg.go.dev/github.com/lestrrat-go/jwx/v3/jwk#ECDSAPublicKey)). The key type must match the algorithm being used. ## Parse and Verify a JWT (with a key set, matching `kid`) To parse a JWT _and_ verify that its content matches the signature as described in the JWS message using a [`jwk.Set`](https://pkg.go.dev/github.com/lestrrat-go/jwx/v3/jwk#Set), you need to add some options when calling the [`jwt.Parse()`](https://pkg.go.dev/github.com/lestrrat-go/jwx/v3/jwt#Parse) function. The following code does a lot of preparation to mimic a real JWKS signed JWT, but the code required in the user side is located towards the end. In real life, the location of JWKS files are specified by the service that provided you with the signed JWT. The URL for these JWKS files often (but are not always guaranteed to be) take the form `https://DOMAIN/.well-known/jwks.json` and the like. If you need to fetch these in your code, [refer to the documentation on `jwk` package](04-jwk.md#fetching-jwk-sets). ```go package examples_test import ( "crypto/rand" "crypto/rsa" "fmt" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/lestrrat-go/jwx/v3/jwk" "github.com/lestrrat-go/jwx/v3/jwt" ) func Example_jwt_parse_with_key_set() { var serialized []byte var signingKey jwk.Key var keyset jwk.Set // Preparation: // // For demonstration purposes, we need to do some preparation // Create a JWK key to sign the token (and also give a KeyID), { privKey, err := rsa.GenerateKey(rand.Reader, 2048) if err != nil { fmt.Printf("failed to generate private key: %s\n", err) return } // This is the key we will use to sign realKey, err := jwk.Import(privKey) if err != nil { fmt.Printf("failed to create JWK: %s\n", err) return } realKey.Set(jwk.KeyIDKey, `mykey`) realKey.Set(jwk.AlgorithmKey, jwa.RS256()) // For demonstration purposes, we also create a bogus key bogusKey, err := jwk.Import([]byte("bogus")) if err != nil { fmt.Printf("failed to create bogus JWK: %s\n", err) return } bogusKey.Set(jwk.AlgorithmKey, jwa.NoSignature()) bogusKey.Set(jwk.KeyIDKey, "otherkey") // Now create a key set that users will use to verity the signed serialized against // Normally these keys are available somewhere like https://www.googleapis.com/oauth2/v3/certs // This key set contains two keys, the first one is the correct one // We can use the jwk.PublicSetOf() utility to get a JWKS of the public keys { privset := jwk.NewSet() privset.AddKey(realKey) privset.AddKey(bogusKey) v, err := jwk.PublicSetOf(privset) if err != nil { fmt.Printf("failed to create public JWKS: %s\n", err) return } keyset = v } signingKey = realKey } // Create the token token := jwt.New() token.Set(`foo`, `bar`) // Sign the token and generate a JWS message signed, err := jwt.Sign(token, jwt.WithKey(jwa.RS256(), signingKey)) if err != nil { fmt.Printf("failed to generate signed serialized: %s\n", err) return } // This is what you typically get as a signed JWT from a server serialized = signed // Actual verification: // FINALLY. This is how you Parse and verify the serialized. // Key IDs are automatically matched. // There was a lot of code above, but as a consumer, below is really all you need // to write in your code tok, err := jwt.Parse( serialized, // Tell the parser that you want to use this keyset jwt.WithKeySet(keyset), // Replace the above option with the following option if you know your key // does not have an "alg"/ field (which is apparently the case for Azure tokens) // jwt.WithKeySet(keyset, jws.WithInferAlgorithmFromKey(true)), ) if err != nil { fmt.Printf("failed to parse serialized: %s\n", err) } _ = tok // OUTPUT: } ``` source: [examples/jwt_parse_with_keyset_example_test.go](https://github.com/lestrrat-go/jwx/blob/v3/examples/jwt_parse_with_keyset_example_test.go) There are a couple of things to note. First is that the signing key is initialized with key ID (`kid`). By using a `jwk.Key` with `kid` field set, the resulting JWS message will also have the field `kid` set to the same value in the corresponding protected headers. This is set because the default behavior is to ONLY accept keys if they have matching `kid` fields in the JWS protected headers. You may override this behavior if you explicitly specify to turn this off using the `jws.WithRequireKid(false)` option, but this is not recommended. If you already know which is supposed to work beforehand, it is recommended that you parse the `jwk.Set` and modify it manually so that it has a proper `kid` field. Unlike using `jws.WithRequireKid(false)` option, this will not allow unintended keys to slip by and have the verification succeed. Second, notice that there's a commented out section in the above code where it uses an extra suboption `jws.WithInferAlgorithmFromKey()` in the `jwt.Parse()` call. The above examples will correctly verify the message as we explicitly set the `alg` with a proper value. However, if the key in your particular JWKS does not contain an `alg` field, the above example would fail. This is because we default on the side of safety and require the `alg` field of the key to contain the actual algorithm.The general stance that we take when verifying JWTs is that we don't really trust what the values on the JWT (or actually, the JWS message) says, so we don't just use their `alg` value. This is why we require that users specify the `alg` field in the `jwt.WithKey` option for single keys. The presence of `jws.WithInferAlgorithmFromKey(true)` tells the `jws.Verify()` routine to use heuristics to deduce the algorithm used. It's a brute-force approach, and does not always provide the best performance. But it will try all possible algorithms available for a given key type until one of them matches. For example, for an RSA key (either raw key or `jwk.Key`) algorithms such as RS256, RS384, RS512, PS256, PS384, and PS512 are tried. In most cases using this suboption would Just Work. However, this type of "try until something works" is not really recommended from a security perspective, and that is why the option is not enabled by default. ## Parse and Verify a JWT (using arbitrary keys) If you must switch the key to use for verification dynamically, you can load your keys from any arbitrary location using `jwt.WithKeySetProvider()` option: ```go package examples_test import ( "context" "crypto/rand" "crypto/rsa" "encoding/base64" "fmt" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/lestrrat-go/jwx/v3/jws" "github.com/lestrrat-go/jwx/v3/jwt" ) func Example_jwt_parse_with_key_provider_use_token() { // This example shows how one might use the information in the JWT to // load different keys. // Setup origIssuer := "me" tok, err := jwt.NewBuilder(). Issuer(origIssuer). Build() if err != nil { fmt.Printf("failed to build token: %s\n", err) return } symmetricKey := []byte("Abracadabra") alg := jwa.HS256() signed, err := jwt.Sign(tok, jwt.WithKey(alg, symmetricKey)) if err != nil { fmt.Printf("failed to sign token: %s\n", err) return } // This next example assumes that you want to minimize the number of // times you parse the JWT JSON { _, b64payload, _, err := jws.SplitCompact(signed) if err != nil { fmt.Printf("failed to split jws: %s\n", err) return } enc := base64.RawStdEncoding payload := make([]byte, enc.DecodedLen(len(b64payload))) _, err = enc.Decode(payload, b64payload) if err != nil { fmt.Printf("failed to decode base64 payload: %s\n", err) return } parsed, err := jwt.Parse(payload, jwt.WithVerify(false)) if err != nil { fmt.Printf("failed to parse JWT: %s\n", err) return } _, err = jws.Verify(signed, jws.WithKeyProvider(jws.KeyProviderFunc(func(_ context.Context, sink jws.KeySink, sig *jws.Signature, msg *jws.Message) error { iss, ok := parsed.Issuer() if !ok { return fmt.Errorf("no issuer found") } switch iss { case "me": sink.Key(alg, symmetricKey) return nil default: return fmt.Errorf("unknown issuer %q", iss) } }))) if err != nil { fmt.Printf("%s\n", err) return } if iss, ok := parsed.Issuer(); !ok || iss != origIssuer { fmt.Printf("issuers do not match\n") return } } // OUTPUT: // } func Example_jwt_parse_with_key_provider() { // Pretend that this is a storage somewhere (maybe a database) that maps // a signature algorithm to a key store := make(map[jwa.KeyAlgorithm]any) algorithms := []jwa.SignatureAlgorithm{ jwa.RS256(), jwa.RS384(), jwa.RS512(), } var signingKey *rsa.PrivateKey for _, alg := range algorithms { pk, err := rsa.GenerateKey(rand.Reader, 2048) if err != nil { fmt.Printf("failed to generate private key: %s\n", err) return } // too lazy to write a proper algorithm. just assign every // time, and signingKey will end up being the last key generated signingKey = pk store[alg] = pk.PublicKey } // Create a JWT token := jwt.New() token.Set(`foo`, `bar`) // Use the last private key in the list to sign the payload serialized, err := jwt.Sign(token, jwt.WithKey(algorithms[2], signingKey)) if err != nil { fmt.Printf(`failed to sign JWT: %s`, err) return } // This example uses jws.KeyProviderFunc, but for production use // you should probably use a reusable object that implements // jws.KeyProvider tok, err := jwt.Parse(serialized, jwt.WithKeyProvider(jws.KeyProviderFunc(func(_ context.Context, sink jws.KeySink, sig *jws.Signature, _ *jws.Message) error { alg, ok := sig.ProtectedHeaders().Algorithm() if !ok { return nil } key, ok := store[alg] if !ok { // nothing found return nil } // Note: we only send one key here, but we could potentially send _ALL_ // keys in the store and have `jws.Verify()` try each one (but it would // most likely be a waste if you did that) sink.Key(alg, key) return nil }))) if err != nil { fmt.Printf(`failed to verify JWT: %s`, err) return } _ = tok // OUTPUT: } ``` source: [examples/jwt_parse_with_key_provider_example_test.go](https://github.com/lestrrat-go/jwx/blob/v3/examples/jwt_parse_with_key_provider_example_test.go) ## Parse and Verify a JWT (using key specified in `jku`) You can parse JWTs using the JWK Set specified in the`jku` field in the JWS message by telling `jwt.Parse()` to use `jws.VerifyAuto()` instead of `jws.Verify()`. This would effectively allow a JWS to be self-validating. ```go package examples_test import ( "crypto/rand" "crypto/rsa" "encoding/json" "fmt" "net/http" "net/http/httptest" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/lestrrat-go/jwx/v3/jwk" "github.com/lestrrat-go/jwx/v3/jws" "github.com/lestrrat-go/jwx/v3/jwt" ) func Example_jwt_parse_with_jku() { set := jwk.NewSet() var signingKey jwk.Key // for _, alg := range algorithms { for i := 0; i < 3; i++ { pk, err := rsa.GenerateKey(rand.Reader, 2048) if err != nil { fmt.Printf("failed to generate private key: %s\n", err) return } // too lazy to write a proper algorithm. just assign every // time, and signingKey will end up being the last key generated privkey, err := jwk.Import(pk) if err != nil { fmt.Printf("failed to create jwk.Key: %s\n", err) return } privkey.Set(jwk.KeyIDKey, fmt.Sprintf(`key-%d`, i)) // It is important that we are using jwk.Key here instead of // rsa.PrivateKey, because this way `kid` is automatically // assigned when we sign the token signingKey = privkey pubkey, err := privkey.PublicKey() if err != nil { fmt.Printf("failed to create public key: %s\n", err) return } set.AddKey(pubkey) } srv := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(set) })) defer srv.Close() // Create a JWT token := jwt.New() token.Set(`foo`, `bar`) hdrs := jws.NewHeaders() hdrs.Set(jws.JWKSetURLKey, srv.URL) serialized, err := jwt.Sign(token, jwt.WithKey(jwa.RS256(), signingKey, jws.WithProtectedHeaders(hdrs))) if err != nil { fmt.Printf("failed to seign token: %s\n", err) return } // We need to pass jwk.WithHTTPClient because we are using HTTPS, // and we need the certificates setup // We also need to explicitly set up the whitelist, this is required tok, err := jwt.Parse(serialized, jwt.WithVerifyAuto(nil, jwk.WithHTTPClient(srv.Client()), jwk.WithFetchWhitelist(jwk.InsecureWhitelist{}))) if err != nil { fmt.Printf("failed to verify token: %s\n", err) return } _ = tok // OUTPUT: } ``` source: [examples/jwt_parse_with_jku_example_test.go](https://github.com/lestrrat-go/jwx/blob/v3/examples/jwt_parse_with_jku_example_test.go) This feature must be used with extreme caution. Please see the caveats and fine prints in the documentation for `jws.VerifyAuto()` # JWT Validation To validate if the JWT's contents, such as if the JWT contains the proper "iss","sub","aut", etc, or the expiration information and such, use the [`jwt.Validate()`](https://pkg.go.dev/github.com/lestrrat-go/jwx/v3/jwt#Validate) function. ```go package examples_test import ( "encoding/json" "fmt" "time" "github.com/lestrrat-go/jwx/v3/jwt" ) func Example_jwt_validate() { tok, err := jwt.NewBuilder(). Issuer(`github.com/lestrrat-go/jwx`). Expiration(time.Now().Add(-1 * time.Hour)). Build() if err != nil { fmt.Printf("failed to build token: %s\n", err) return } { // Case 1: Using jwt.Validate() err = jwt.Validate(tok) if err == nil { fmt.Printf("token should fail validation\n") return } fmt.Printf("%s\n", err) } { // Case 2: Using jwt.Parse() buf, err := json.Marshal(tok) if err != nil { fmt.Printf("failed to serialize token: %s\n", err) return } // NOTE: This token has NOT been verified for demonstration // purposes. Use `jwt.WithKey()` or the like in your production code _, err = jwt.Parse(buf, jwt.WithVerify(false), jwt.WithValidate(true)) if err == nil { fmt.Printf("token should fail validation\n") return } fmt.Printf("%s\n", err) } // OUTPUT: // jwt.Validate: validation failed: "exp" not satisfied: token is expired // jwt.Parse: failed to parse token: jwt.Validate: validation failed: "exp" not satisfied: token is expired } ``` source: [examples/jwt_validate_example_test.go](https://github.com/lestrrat-go/jwx/blob/v3/examples/jwt_validate_example_test.go) ## Validate for specific claim values By default we only check for the time-related components of a token, such as "iat", "exp", and "nbf". To tell [`jwt.Validate()`](https://pkg.go.dev/github.com/lestrrat-go/jwx/v3/jwt#Validate) to check for other fields, use one of the various [`jwt.ValidateOption`](https://pkg.go.dev/github.com/lestrrat-go/jwx/v3/jwt#ValidateOption) values, such as `jwt.WithClaimValue()`, `jwt.WithRequiredClaim()`, etc. ```go package examples_test import ( "fmt" "time" "github.com/lestrrat-go/jwx/v3/jwt" ) func Example_jwt_validate_issuer() { tok, err := jwt.NewBuilder(). Issuer(`github.com/lestrrat-go/jwx`). Expiration(time.Now().Add(time.Hour)). Build() if err != nil { fmt.Printf("failed to build token: %s\n", err) return } err = jwt.Validate(tok, jwt.WithIssuer(`nobody`)) if err == nil { fmt.Printf("token should fail validation\n") return } fmt.Printf("%s\n", err) // OUTPUT: // jwt.Validate: validation failed: "iss" not satisfied: claim "iss" does not have the expected value } ``` source: [examples/jwt_validate_issuer_example_test.go](https://github.com/lestrrat-go/jwx/blob/v3/examples/jwt_validate_issuer_example_test.go) ## Use a custom validator You may also create a custom validator that implements the `jwt.Validator` interface. These validators can be added as an option to `jwt.Validate()` using `jwt.WithValidator()`. Multiple validators can be specified. The error should be of type `jwt.ValidationError`. Use `jwt.NewValidationError` to create an error of appropriate type. ```go package examples_test import ( "context" "errors" "fmt" "time" "github.com/lestrrat-go/jwx/v3/jwt" ) func Example_jwt_validate_validator() { validator := jwt.ValidatorFunc(func(_ context.Context, t jwt.Token) error { iat, ok := t.IssuedAt() if !ok { return errors.New(`token does not have "iat" claim`) } if iat.Month() != 8 { return errors.New(`tokens are only valid if issued during August!`) } return nil }) tok, err := jwt.NewBuilder(). Issuer(`github.com/lestrrat-go/jwx`). IssuedAt(time.Unix(aLongLongTimeAgo, 0)). Build() if err != nil { fmt.Printf("failed to build token: %s\n", err) return } err = jwt.Validate(tok, jwt.WithValidator(validator)) if err == nil { fmt.Printf("token should fail validation\n") return } fmt.Printf("%s\n", err) // OUTPUT: // jwt.Validate: validation failed: tokens are only valid if issued during August! } ``` source: [examples/jwt_validate_validator_example_test.go](https://github.com/lestrrat-go/jwx/blob/v3/examples/jwt_validate_validator_example_test.go) ## Detecting error types If you enable validation during `jwt.Parse()`, you might sometimes want to differentiate between parsing errors and validation errors. To do this, you can use the function `jwt.IsValidationError()`. To further differentiate between specific errors, you can use `errors.Is()`: ```go package examples_test import ( "encoding/json" "errors" "fmt" "time" "github.com/lestrrat-go/jwx/v3/jwt" ) func Example_jwt_validate_detect_error_type() { tok, err := jwt.NewBuilder(). Issuer(`github.com/lestrrat-go/jwx`). Expiration(time.Now().Add(-1 * time.Hour)). Build() if err != nil { fmt.Printf("failed to build token: %s\n", err) return } buf, err := json.Marshal(tok) if err != nil { fmt.Printf("failed to serialize token: %s\n", err) return } { // Case 1: Parsing error. We're not showing verification failure, // but it is about the same in the context of wanting to know // if it's a validation error or not _, err := jwt.Parse(buf[:len(buf)-1], jwt.WithVerify(false), jwt.WithValidate(true)) if err == nil { fmt.Printf("token should fail parsing\n") return } if errors.Is(err, jwt.ValidateError()) { fmt.Printf("error should NOT be validation error\n") return } } { // Case 2: Parsing works, validation fails // NOTE: This token has NOT been verified for demonstration // purposes. Use `jwt.WithKey()` or the like in your production code _, err = jwt.Parse(buf, jwt.WithVerify(false), jwt.WithValidate(true)) if err == nil { fmt.Printf("token should fail parsing\n") return } if !errors.Is(err, jwt.ValidateError()) { fmt.Printf("error should be validation error\n") return } if !errors.Is(err, jwt.TokenExpiredError()) { fmt.Printf("error should be of token expired type\n") return } fmt.Printf("%s\n", err) } // OUTPUT: // jwt.Parse: failed to parse token: jwt.Validate: validation failed: "exp" not satisfied: token is expired } ``` source: [examples/jwt_validate_detect_error_type_example_test.go](https://github.com/lestrrat-go/jwx/blob/v3/examples/jwt_validate_detect_error_type_example_test.go) # Filtering Claims JWT tokens can contain many different types of claims - standard claims like `iss`, `aud`, `exp`, as well as custom application-specific claims. Sometimes you need to create modified versions of tokens that only contain certain claims, either for security purposes, API compatibility, or to create specialized token types. While [`jwt.Token`](https://pkg.go.dev/github.com/lestrrat-go/jwx/v3/jwt#Token) object itself does not offer ways to directly extract out these claims, you can use the [`jwt.TokenFilter`](https://pkg.go.dev/github.com/lestrrat-go/jwx/v3/jwt#TokenFilter) interface provides methods to filter JWT claims in a flexible way. ## Filtering Using Standard Claim Names The most common way to filter claims is by either excluding or including only the standard JWT claims. For convenience, this library provides `jwt.StandardClaimsFilter()` which filters standard JWT claims defined in RFC 7519. You can either use `(filter).Filter(token)` to create a `jwt.Token` that contains only the standard claims, or use `(filter).Reject(token)` to create a `jwt.Token` that contains only non-standard claims. ```go package examples_test import ( "fmt" "time" "github.com/lestrrat-go/jwx/v3/jwt" ) func Example_jwt_filter_basic_claims() { // Create a token with standard and custom claims token, err := jwt.NewBuilder(). Issuer("github.com/lestrrat-go/jwx"). Subject("jwt_filter_example"). Audience([]string{"developers", "users"}). IssuedAt(time.Unix(1234567890, 0)). Expiration(time.Unix(1234567890+3600, 0)). Claim("customClaim", "customValue"). Claim("applicationRole", "admin"). Claim("department", "engineering"). Build() if err != nil { fmt.Printf("failed to build token: %s\n", err) return } // Create a custom claim name filter customFilter := jwt.NewClaimNameFilter("customClaim", "applicationRole", "department") // Filter to get only custom claims if _, err := customFilter.Filter(token); err != nil { fmt.Printf("failed to filter custom claims: %s\n", err) return } // You could also use Reject to get all claims except the specified ones // Note that this may include other non-standard claims if _, err := customFilter.Reject(token); err != nil { fmt.Printf("failed to reject custom claims: %s\n", err) return } // Use StandardClaimsFilter to get only standard JWT claims if _, err = jwt.StandardClaimsFilter().Filter(token); err != nil { fmt.Printf("failed to filter standard claims: %s\n", err) return } // Use StandardClaimsFilter to reject standard claims, resulting // in every non-standard claim being retained if _, err = jwt.StandardClaimsFilter().Reject(token); err != nil { fmt.Printf("failed to reject standard claims: %s\n", err) return } // OUTPUT: } ``` source: [examples/jwt_filter_basic_example_test.go](https://github.com/lestrrat-go/jwx/blob/v3/examples/jwt_filter_basic_example_test.go) For OpenID tokens, you could also use `openid.StandardClaimsFilter()`. ## Advanced filtering scenarios If you want to control what gets filtered, you can create a [`jwt.TokenFilter`](https://pkg.go.dev/github.com/lestrrat-go/jwx/v3/jwt#TokenFilter) of your own. If all you want to do is filter by claim names, you can re-use the existing `jwt.ClaimNameFilter`. If you want you can also combine multiple filters to create sophisticated filtering logic. ```go package examples_test import ( "fmt" "time" "github.com/lestrrat-go/jwx/v3/jwt" ) func Example_jwt_filter_advanced_use_cases() { // Create a comprehensive token with various types of claims token, err := jwt.NewBuilder(). Issuer("auth-service.example.com"). Subject("user-456"). Audience([]string{"web-app", "mobile-app", "api-gateway"}). IssuedAt(time.Unix(1234567890, 0)). Expiration(time.Unix(1234567890+7200, 0)). NotBefore(time.Unix(1234567890, 0)). JwtID("session-xyz789"). Claim("userRole", "manager"). Claim("department", "sales"). Claim("permissions", []string{"read:reports", "write:orders", "approve:discounts"}). Claim("profile", map[string]any{ "name": "John Doe", "email": "john@example.com", "phone": "+1-555-0123", }). Claim("sessionInfo", map[string]any{ "loginIP": "10.0.1.100", "deviceType": "desktop", "browser": "Chrome/91.0", "lastActivity": "2023-01-01T12:30:00Z", }). Claim("features", []string{"beta-ui", "advanced-analytics", "mobile-push"}). Build() if err != nil { fmt.Printf("failed to build comprehensive token: %s\n", err) return } // Use case 1: Create a token for public APIs (remove sensitive information) sensitiveFilter := jwt.NewClaimNameFilter("sessionInfo", "profile") if _, err := sensitiveFilter.Reject(token); err != nil { fmt.Printf("failed to create public API token: %s\n", err) return } // Use case 2: Create an identity-only token (only user identification claims) identityFilter := jwt.NewClaimNameFilter("sub", "iss", "userRole", "department") if _, err := identityFilter.Filter(token); err != nil { fmt.Printf("failed to create identity token: %s\n", err) return } // Use case 3: Create a minimal security token (only time-based and security claims) securityFilter := jwt.NewClaimNameFilter("iss", "sub", "aud", "exp", "iat", "nbf", "jti") if _, err := securityFilter.Filter(token); err != nil { fmt.Printf("failed to create security token: %s\n", err) return } // Use case 4: Combine filters - remove both standard claims and specific custom claims standardFilter := jwt.StandardClaimsFilter() tempToken, err := standardFilter.Reject(token) // Remove standard claims first if err != nil { fmt.Printf("failed to remove standard claims: %s\n", err) return } // Then remove specific custom claims customSensitiveFilter := jwt.NewClaimNameFilter("sessionInfo", "profile") if _, err := customSensitiveFilter.Reject(tempToken); err != nil { fmt.Printf("failed to remove custom sensitive claims: %s\n", err) return } // OUTPUT: } ``` source: [examples/jwt_filter_advanced_example_test.go](https://github.com/lestrrat-go/jwx/blob/v3/examples/jwt_filter_advanced_example_test.go) # Serialization ## Serialize as JSON `jwt.Token` objects can safely be passed to `"encoding/json".Marshal()` and friends. In this case it will be marshaled as a JSON object rather than in the compact format. Since it will be just the raw token, no signing or encryption will be performed. ```go package examples_test import ( "encoding/json" "fmt" "os" "time" "github.com/lestrrat-go/jwx/v3/jwt" ) func Example_jwt_serialize_json() { tok, err := jwt.NewBuilder(). Issuer(`github.com/lestrrat-go/jwx`). IssuedAt(time.Unix(aLongLongTimeAgo, 0)). Build() if err != nil { fmt.Printf("failed to build token: %s\n", err) return } json.NewEncoder(os.Stdout).Encode(tok) // OUTPUT: // {"iat":233431200,"iss":"github.com/lestrrat-go/jwx"} } ``` source: [examples/jwt_serialize_json_example_test.go](https://github.com/lestrrat-go/jwx/blob/v3/examples/jwt_serialize_json_example_test.go) ## Serialize using JWS The `jwt` package provides a convenience function `jwt.Sign()` to serialize a token using JWS. If you need even further customization, consider using the `jws` package directly. ```go package examples_test import ( "fmt" "time" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/lestrrat-go/jwx/v3/jwk" "github.com/lestrrat-go/jwx/v3/jwt" ) func Example_jwt_serialize_jws() { tok, err := jwt.NewBuilder(). Issuer(`github.com/lestrrat-go/jwx`). IssuedAt(time.Unix(aLongLongTimeAgo, 0)). Build() if err != nil { fmt.Printf("failed to build token: %s\n", err) return } rawKey := []byte(`abracadabra`) jwkKey, err := jwk.Import(rawKey) if err != nil { fmt.Printf("failed to create symmetric key: %s\n", err) return } // This example shows you two ways to passing keys to // jwt.Sign() // // * The first key is the "raw" key. // * The second one is a jwk.Key that represents the raw key. // // If this were using RSA/ECDSA keys, you would be using // *rsa.PrivateKey/*ecdsa.PrivateKey as the raw key. for _, key := range []any{rawKey, jwkKey} { serialized, err := jwt.Sign(tok, jwt.WithKey(jwa.HS256(), key)) if err != nil { fmt.Printf("failed to sign token: %s\n", err) return } fmt.Printf("%s\n", serialized) } // OUTPUT: // eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjIzMzQzMTIwMCwiaXNzIjoiZ2l0aHViLmNvbS9sZXN0cnJhdC1nby9qd3gifQ.K1WVWaM6Dww9aNNFMjnyUfjaaHIs08-3Qb1b8eSEHOk // eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjIzMzQzMTIwMCwiaXNzIjoiZ2l0aHViLmNvbS9sZXN0cnJhdC1nby9qd3gifQ.K1WVWaM6Dww9aNNFMjnyUfjaaHIs08-3Qb1b8eSEHOk } ``` source: [examples/jwt_serialize_jws_example_test.go](https://github.com/lestrrat-go/jwx/blob/v3/examples/jwt_serialize_jws_example_test.go) ## Serialize using JWE and JWS The `jwt` package provides a `Serializer` object to allow users to serialize a token using an arbitrary combination of processors. If for whatever reason the built-in `(jwt.Serializer).Sign()` and `(jwt.Serializer).Encrypt()` do not work for you, you may choose to provider a custom serialization step using `(jwt.Serialize).Step()` -- but at this point it may just be easier if you hand-rolled your own serialization. The following example, encrypts a token using JWE, then uses JWS to sign the encrypted payload: ```go package examples_test import ( "crypto/rand" "crypto/rsa" "fmt" "time" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/lestrrat-go/jwx/v3/jwk" "github.com/lestrrat-go/jwx/v3/jwt" ) func Example_jwt_serialize_jwe_jws() { tok, err := jwt.NewBuilder(). Issuer(`github.com/lestrrat-go/jwx`). IssuedAt(time.Unix(aLongLongTimeAgo, 0)). Build() if err != nil { fmt.Printf("failed to build token: %s\n", err) return } privkey, err := rsa.GenerateKey(rand.Reader, 2048) if err != nil { fmt.Printf("failed to generate private key: %s\n", err) return } enckey, err := jwk.Import(privkey.PublicKey) if err != nil { fmt.Printf("failed to create symmetric key: %s\n", err) return } signkey, err := jwk.Import([]byte(`abracadabra`)) if err != nil { fmt.Printf("failed to create symmetric key: %s\n", err) return } serialized, err := jwt.NewSerializer(). Encrypt(jwt.WithKey(jwa.RSA_OAEP(), enckey)). Sign(jwt.WithKey(jwa.HS256(), signkey)). Serialize(tok) if err != nil { fmt.Printf("failed to encrypt and sign token: %s\n", err) return } _ = serialized // We don't use the result of serialization as it will always be // different because of randomness used in the encryption logic // OUTPUT: } ``` source: [examples/jwt_serialize_jwe_jws_example_test.go](https://github.com/lestrrat-go/jwx/blob/v3/examples/jwt_serialize_jwe_jws_example_test.go) ## Serialize the `aud` field as a single string When you marshal `jwt.Token` into JSON, by default the `aud` field is serialized as an array of strings. This field may take either a single string or array form, but apparently there are parsers that do not understand the array form. The examples below should both be valid, but apparently there are systems that do not understand the former ([AWS Cognito has been reported to be one such system](https://github.com/lestrrat-go/jwx/tree/v3/issues/368)). ``` { "aud": ["foo"], ... } ``` ``` { "aud": "foo", ... } ``` To work around these problematic parsers, you may use enable the option `jwt.FlattenAudience` on each token that you would like to see this behavior. If you do this for _all_ (or most) tokens, you may opt to change the global default value by settings `jwt.WithFlattenAudience(true)` option via `jwt.Settings()`. ```go package examples_test import ( "encoding/json" "fmt" "os" "github.com/lestrrat-go/jwx/v3/jwt" ) func Example_jwt_flatten_Audience() { // Sometimes you need to "flatten" the "aud" claim because of // parsers developed by people who apparently didn't read the RFC. // // In such cases, you can control the behavior of the JSON // emitted when tokens are converted to JSON by tweaking the // per-token options set. { // Case 1: the per-object way tok, err := jwt.NewBuilder(). Audience([]string{`foo`}). Build() if err != nil { fmt.Printf("failed to build token: %s\n", err) return } // Only this particular instance of the token is affected tok.Options().Enable(jwt.FlattenAudience) json.NewEncoder(os.Stdout).Encode(tok) } { // Case 2: globally enabling flattened audience // NOTE: This example DOES NOT flatten the audience // because the call to change this global settings has been // commented out. Setting this has GLOBAL effects, and would // alter the output of other examples. // // If you would like to try this, UNCOMMENT the line below // // // UNCOMMENT THIS LINE BELOW // jwt.Settings(jwt.WithFlattenAudience(true)) // // ...and if you are running from the examples directory, run // this example in isolation by invoking // // go test -run=ExampleJWT_FlattenAudience // // You may see the example fail, but that's because the OUTPUT line // expects the global settings to be DISABLED. In order to make // the example pass, change the second line from OUTPUT below // // from: {"aud":["foo"]} // to : {"aud":"foo"} // // Please note that it is recommended you ONLY set the jwt.Settings(jwt.WithFlattenedAudience(true)) // once at the beginning of your main program (probably in an `init()` function) // so that you do not need to worry about causing issues depending // on when tokens are created relative to the time when // the global setting is changed. tok, err := jwt.NewBuilder(). Audience([]string{`foo`}). Build() if err != nil { fmt.Printf("failed to build token: %s\n", err) return } // This would flatten the "aud" claim if the appropriate // line above has been uncommented json.NewEncoder(os.Stdout).Encode(tok) // This would force this particular object not to flatten the // "aud" claim. All other tokens would be constructed with the // option enabled tok.Options().Enable(jwt.FlattenAudience) json.NewEncoder(os.Stdout).Encode(tok) } // OUTPUT: // {"aud":"foo"} // {"aud":["foo"]} // {"aud":"foo"} } ``` source: [examples/jwt_flatten_audience_example_test.go](https://github.com/lestrrat-go/jwx/blob/v3/examples/jwt_flatten_audience_example_test.go) # Working with JWT ## Performance github.com/lestrrat-go/jwx is focused on usability / stable API. If you are worried about performance while processing JWTs, the best path is just to use a plain struct after handling JWS yourself: ```go package examples import ( "encoding/json" "fmt" "os" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/lestrrat-go/jwx/v3/jws" "github.com/lestrrat-go/jwx/v3/jwt" ) func Example_jwt_plain_struct() { t1, err := jwt.NewBuilder(). Issuer("https://github.com/lestrrat-go/jwx/v3/examples"). Subject("raw_struct"). Claim("private", "foobar"). Build() if err != nil { fmt.Fprintf(os.Stderr, "failed to build JWT: %s\n", err) } key := []byte("secret") signed, err := jwt.Sign(t1, jwt.WithKey(jwa.HS256(), key)) if err != nil { fmt.Printf("failed to sign JWT: %s\n", err) } rawJWT, err := jws.Verify(signed, jws.WithKey(jwa.HS256(), key)) if err != nil { fmt.Printf("failed to verify JWS: %s\n", err) } type MyToken struct { Issuer string `json:"iss"` Subject string `json:"sub"` Private string `json:"private"` } var t2 MyToken if err := json.Unmarshal(rawJWT, &t2); err != nil { fmt.Printf("failed to unmarshal JWT: %s\n", err) } fmt.Printf("%s\n", t2.Private) // OUTPUT: // foobar } ``` source: [examples/jwt_raw_struct_example_test.go](https://github.com/lestrrat-go/jwx/blob/v3/examples/jwt_raw_struct_example_test.go) This makes sure that you do not go through any extra layers of abstraction that causes performance penalties, and you get exactly the type of field that you want. ## Access JWS headers The RFC defines JWS as an envelope to JWT (JWS can carry any payload, you just happened to assign a JWT to it). A JWT is just a bag of arbitrary key/value pairs, where some of them are predefined for validation. This means that JWS headers are NOT part of a JWT -- and thus you will not be able to access them through the `jwt.Token` itself. If you need to access these JWS headers while parsing JWS signed JWT, you will need to reach into the tools defined in the `jws` package. - If you are considering using JWS header fields to decide on which key to use for verification, consider [using a `jwt.KeyProvider`](#parse-and-verify-a-jwt-using-arbitrary-keys). - If you are looking for ways to Please [look at the JWS documentation for it](./02-jws.md#parse-a-jws-message-and-access-jws-headers) . ## Get/Set fields Any field in the token can be accessed in a uniform away using `(jwt.Token).Get()` ```go var v interface{} // can be concrete type, if you know the type beforehand err := token.Get(name, &v) ``` If the field corresponding to `name` does not exist, the second return value will be `false`. The value `v` is returned as `interface{}`, as there is no way of knowing what the underlying type may be for user defined fields. For pre-defined fields whose types are known, you can use the convenience methods such as `Subject()`, `Issuer()`, `NotBefore()`, etc. ```go s := token.Subject() s := token.Issuer() t := token.NotBefore() ``` For setting field values, there is only one path, which is to use the `Set()` method. If you are initializing a token you may also [use the builder pattern](#using-builder) ```go err := token.Set(name, value) ``` For pre-defined fields, `Set()` will return an error when the value cannot be converted to a proper type that suits the specification. For example, fields for time data must be `time.Time` or number of seconds since epoch. See the `jwt.Token` interface and the getter methods for these fields to learn about the types for pre-defined fields. ## Using a custom base64 encoder Per specification JWT should be using URL base64 encoding with no padding when generating (and by nature of the process when verifying as well) signatures. However, some systems do not necessarily adhere to the standards ([there have been reports that AWS ALB is one such system, generating User Claims JWT with padding](https://github.com/lestrrat-go/jwx/discussions/1324)) In these situations, you will need to specify the base64 encoder to your `jwt.Sign` and `jwt.Parse` calls. Please note that this feature is available on v3 onwards only. ```go package examples_test import ( "encoding/base64" "encoding/json" "fmt" "os" "time" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/lestrrat-go/jwx/v3/jwt" ) func Example_jwt_sign_with_custom_base64_encoder() { const symmetricKey = "0123456789abcdef0123456789abcdef" token, err := jwt.NewBuilder(). Subject("github.com/lestrrat-go/jwx"). IssuedAt(time.Unix(aLongLongTimeAgo, 0)). Build() if err != nil { fmt.Printf("failed to create token: %s\n", err) return } signed, err := jwt.Sign(token, jwt.WithKey(jwa.HS256(), []byte(symmetricKey)), jwt.WithBase64Encoder(base64.URLEncoding)) if err != nil { fmt.Printf("failed to sign token: %s\n", err) return } fmt.Println(string(signed)) parsed, err := jwt.Parse(signed, jwt.WithKey(jwa.HS256(), []byte(symmetricKey)), jwt.WithBase64Encoder(base64.URLEncoding)) if err != nil { fmt.Printf("failed to parse token: %s\n", err) return } if err := json.NewEncoder(os.Stdout).Encode(parsed); err != nil { fmt.Printf("failed to encode token: %s\n", err) return } if !jwt.Equal(token, parsed) { fmt.Printf("parsed token does not match original token\n") return } // OUTPUT: // eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjIzMzQzMTIwMCwic3ViIjoiZ2l0aHViLmNvbS9sZXN0cnJhdC1nby9qd3gifQ==.qZu-ATTtmo9k1NedYgwwBzaEYEJA1Z6dlVzPpmzrrrw= // {"iat":233431200,"sub":"github.com/lestrrat-go/jwx"} } ``` source: [examples/jwt_sign_with_custom_base64_example_test.go](https://github.com/lestrrat-go/jwx/blob/v3/examples/jwt_sign_with_custom_base64_example_test.go) You can use these option for `jws.Sign` and `jws.Verify` as well. See the [JWS docs for an example](./02-jwt.md#using-a-custom-base64-encoder). golang-github-lestrrat-go-jwx-3.0.13/docs/02-jws.md000066400000000000000000001100621515060566400217000ustar00rootroot00000000000000# Working with JWS In this document we describe how to work with JWS using [`github.com/lestrrat-go/jwx/v3/jws`](https://pkg.go.dev/github.com/lestrrat-go/jwx/v3/jws) * [Parsing](#parsing) * [Parse a JWS message stored in memory](#parse-a-jws-message-stored-in-memory) * [Parse a JWS message stored in a file](#parse-a-jws-message-stored-in-a-file) * [Parse a JWS message and access JWS headers](#parse-a-jws-message-and-access-jws-headers) * [Signing](#signing) * [Generating a JWS message in compact serialization format](#generating-a-jws-message-in-compact-serialization-format) * [Generating a JWS message in JSON serialization format](#generating-a-jws-message-in-json-serialization-format) * [Generating a JWS message with detached payload](#generating-a-jws-message-with-detached-payload) * [Using cloud KMS services](#using-cloud-kms-services) * [Including arbitrary headers](#including-arbitrary-headers) * [Verifying](#verifying) * [Verification using a single key](#verification-using-a-single-key) * [Verification using a JWKS](#verification-using-a-jwks) * [Verification using a detached payload](#verification-using-a-detached-payload) * [Verification using `jku`](#verification-using-jku) * [Using a custom signing/verification algorithm](#using-a-custom-signingverification-algorithm) * [Enabling ES256K](#enabling-es256k) * [Using a custom base64 encoder](#using-a-custom-base64-encoder) * [Filtering JWS headers](#filtering-jws-headers) # Parsing Parsing a JWS message means taking either a JWS message serialized in JSON or Compact form and loading it into a `jws.Message` object. No verification is performed, and therefore you cannot "trust" the contents in the same way that a verified message could be trusted. Also, be aware that a `jws.Message` is not meant to be used for either signing or verification. It is only provided such that it can be inspected -- there is no way to sign or verify using a parsed `jws.Message`. To do this, you would need to use `jws.Sign()` or `jws.Message()`. ## Parse a JWS message stored in memory You can parse a JWS message in memory stored as `[]byte` into a [`jws.Message`](https://pkg.go.dev/github.com/lestrrat-go/jwx/v3/jws#Message) object. In this mode, there is no verification performed. ```go package examples_test import ( "encoding/json" "fmt" "os" "github.com/lestrrat-go/jwx/v3/jws" ) func Example_jws_parse() { const src = `eyJhbGciOiJIUzI1NiJ9.TG9yZW0gaXBzdW0.idbECxA8ZhQbU0ddZmzdRZxQmHjwvw77lT2bwqGgNMo` msg, err := jws.Parse([]byte(src)) if err != nil { fmt.Printf("failed to parse JWS message: %s\n", err) return } json.NewEncoder(os.Stdout).Encode(msg) // OUTPUT: // {"payload":"TG9yZW0gaXBzdW0","protected":"eyJhbGciOiJIUzI1NiJ9","signature":"idbECxA8ZhQbU0ddZmzdRZxQmHjwvw77lT2bwqGgNMo"} } ``` source: [examples/jws_parse_example_test.go](https://github.com/lestrrat-go/jwx/blob/v3/examples/jws_parse_example_test.go) ## Parse a JWS message stored in a file To parse a JWS stored in a file, use [`jws.ReadFile()`](https://pkg.go.dev/github.com/lestrrat-go/jwx/v3/jws#ReadFile). [`jws.ReadFile()`](https://pkg.go.dev/github.com/lestrrat-go/jwx/v3/jws#ReadFile) accepts the same options as [`jws.Parse()`](https://pkg.go.dev/github.com/lestrrat-go/jwx/v3/jws#Parse). ```go package examples_test import ( "encoding/json" "fmt" "os" "github.com/lestrrat-go/jwx/v3/jws" ) func Example_jws_readfile() { const src = `eyJhbGciOiJIUzI1NiJ9.TG9yZW0gaXBzdW0.idbECxA8ZhQbU0ddZmzdRZxQmHjwvw77lT2bwqGgNMo` f, err := os.CreateTemp(``, `jws_readfile-*.jws`) if err != nil { fmt.Printf("failed to create temporary file: %s\n", err) return } defer os.Remove(f.Name()) fmt.Fprint(f, src) f.Close() msg, err := jws.ReadFile(f.Name()) if err != nil { fmt.Printf("failed to parse JWS message: %s\n", err) return } json.NewEncoder(os.Stdout).Encode(msg) // OUTPUT: // {"payload":"TG9yZW0gaXBzdW0","protected":"eyJhbGciOiJIUzI1NiJ9","signature":"idbECxA8ZhQbU0ddZmzdRZxQmHjwvw77lT2bwqGgNMo"} } ``` source: [examples/jws_readfile_example_test.go](https://github.com/lestrrat-go/jwx/blob/v3/examples/jws_readfile_example_test.go) ## Parse a JWS message and access JWS headers Note: If you are considering using JWS header fields to decide on which key to use for verification, consider [using a `jwt.KeyProvider`](./01-jwt.md#parse-and-verify-a-jwt-using-arbitrary-keys). While a lot of documentation in the wild treats as if a JWT message encoded in base64 is... a JWT message, in truth it is a JWT message enveloped in a JWS message. Therefore, in order to access the JWS headers of a JWT message you will need to work with a `jws.Message` object, which you can obtain from parsing the JWS payload. You will need to understand [the structure of a generic JWS message](https://www.rfc-editor.org/rfc/rfc7515#section-7.2.1). Below sample code extracts the `kid` field of a single-signature JWS message: ```go package examples_test import ( "fmt" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/lestrrat-go/jwx/v3/jwk" "github.com/lestrrat-go/jwx/v3/jws" "github.com/lestrrat-go/jwx/v3/jwt" ) func Example_jws_use_jws_header() { key, err := jwk.Import([]byte(`abracadabra`)) if err != nil { fmt.Printf(`failed to create new symmetric key: %s`, err) return } key.Set(jws.KeyIDKey, `secret-key`) tok, err := jwt.NewBuilder(). Issuer(`github.com/lestrrat-go/jwx`). Build() if err != nil { fmt.Printf(`failed to build token: %s`, err) return } signed, err := jwt.Sign(tok, jwt.WithKey(jwa.HS256(), key)) if err != nil { fmt.Printf(`failed to sign token: %s`, err) return } msg, err := jws.Parse(signed) if err != nil { fmt.Printf(`failed to parse serialized JWT: %s`, err) return } // While JWT enveloped with JWS in compact format only has 1 signature, // a generic JWS message may have multiple signatures. Therefore, we // need to access the first element kid, ok := msg.Signatures()[0].ProtectedHeaders().KeyID() if !ok { fmt.Printf("failed to get key ID from protected headers") return } fmt.Printf("%q\n", kid) // OUTPUT: // "secret-key" } ``` source: [examples/jws_use_jws_header_test.go](https://github.com/lestrrat-go/jwx/blob/v3/examples/jws_use_jws_header_test.go) # Signing ## Generating a JWS message in compact serialization format To sign an arbitrary payload as a JWS message in compact serialization format, use `jwt.Sign()`. Note that this would be [slightly different if you are signing JWTs](01-jwt.md#serialize-using-jws), as you would be using functions from the `jwt` package instead of `jws`. ```go package examples_test import ( "fmt" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/lestrrat-go/jwx/v3/jwk" "github.com/lestrrat-go/jwx/v3/jws" ) func Example_jws_sign() { key, err := jwk.Import([]byte(`abracadabra`)) if err != nil { fmt.Printf("failed to create key: %s\n", err) return } buf, err := jws.Sign([]byte("Lorem ipsum"), jws.WithKey(jwa.HS256(), key)) if err != nil { fmt.Printf("failed to sign payload: %s\n", err) return } fmt.Printf("%s\n", buf) // OUTPUT: // eyJhbGciOiJIUzI1NiJ9.TG9yZW0gaXBzdW0.EjVtju0uXjSz6QevNgAqN1ESd9aNCP7-tJLifkQ0_C0 } ``` source: [examples/jws_sign_example_test.go](https://github.com/lestrrat-go/jwx/blob/v3/examples/jws_sign_example_test.go) ## Generating a JWS message in JSON serialization format Generally the only time you need to use a JSON serialization format is when you have to generate multiple signatures for a given payload using multiple signing algorithms and keys. When this need arises, use the [`jws.Sign()`](https://pkg.go.dev/github.com/lestrrat-go/jwx/v3/jws#Sign) function with the `jws.WithJSON()` option and multiple `jws.WithKey()` options: ```go package examples_test import ( "fmt" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/lestrrat-go/jwx/v3/jwk" "github.com/lestrrat-go/jwx/v3/jws" ) func Example_jws_sign_json() { var keys []jwk.Key for i := 0; i < 3; i++ { key, err := jwk.Import([]byte(fmt.Sprintf(`abracadabra-%d`, i))) if err != nil { fmt.Printf("failed to create key: %s\n", err) return } keys = append(keys, key) } options := []jws.SignOption{jws.WithJSON()} for _, key := range keys { options = append(options, jws.WithKey(jwa.HS256(), key)) } buf, err := jws.Sign([]byte("Lorem ipsum"), options...) if err != nil { fmt.Printf("failed to sign payload: %s\n", err) return } fmt.Printf("%s\n", buf) // OUTPUT: // {"payload":"TG9yZW0gaXBzdW0","signatures":[{"protected":"eyJhbGciOiJIUzI1NiJ9","signature":"bCQtU2y4PEnG78dUN-tXea8YEwhBAzLX7ZEYlRVtX_g"},{"protected":"eyJhbGciOiJIUzI1NiJ9","signature":"0ovW79M_bbaRDBrBLaNKN7rgJeXaSRAnu5rhAuRXBR4"},{"protected":"eyJhbGciOiJIUzI1NiJ9","signature":"ZkUzwlK5E6LFKsYEIyUvskOKLMDxE0MvvkvNrwINNWE"}]} } ``` source: [examples/jws_sign_json_example_test.go](https://github.com/lestrrat-go/jwx/blob/v3/examples/jws_sign_json_example_test.go) ## Generating a JWS message with detached payload JWS messages can be constructed with a detached payload. Use the `jws.WithDetachedPayload()` option to create a JWS message with the message detached from the result. ```go package examples_test import ( "fmt" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/lestrrat-go/jwx/v3/jwk" "github.com/lestrrat-go/jwx/v3/jws" ) func Example_jws_sign_detached_payload() { payload := `$.02` key, err := jwk.Import([]byte(`abracadabra`)) if err != nil { fmt.Printf("failed to create symmetric key: %s\n", err) return } // If you plan to transmit your payload without base64-encoding (RFC 7797), // it's best to set `b64: false` so libraries can act accordingly (e.g. the // popular NodeJS `jose` library requires it set to false in order to verify // a plaintext payload. hdrs := jws.NewHeaders() hdrs.Set("b64", false) hdrs.Set("crit", []string{"b64"}) serialized, err := jws.Sign(nil, jws.WithKey(jwa.HS256(), key, jws.WithProtectedHeaders(hdrs)), jws.WithDetachedPayload([]byte(payload))) if err != nil { fmt.Printf("failed to sign payload: %s\n", err) return } fmt.Printf("%s\n", serialized) // OUTPUT: // eyJhbGciOiJIUzI1NiIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19..lnRw_MSpQjARa5LWqPcu8Qls9p3wYGrC6tz4-nr0rkA } ``` source: [examples/jws_sign_detached_payload_example_test.go](https://github.com/lestrrat-go/jwx/blob/v3/examples/jws_sign_detached_payload_example_test.go) ## Including arbitrary headers By default, only some header fields are included in the result from `jws.Sign()`. If you want to include more header fields in the resulting JWS, you will have to provide them via the `jws.WithProtectedHeaders()` option. While `jws.WithPublicHeaders()` exists to keep API symmetric and complete, for most cases you only want to use `jws.WithProtectedHeaders()` ```go package examples_test import ( "fmt" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/lestrrat-go/jwx/v3/jwk" "github.com/lestrrat-go/jwx/v3/jws" ) func Example_jws_sign_with_headers() { key, err := jwk.Import([]byte(`abracadabra`)) if err != nil { fmt.Printf("failed to create key: %s\n", err) return } hdrs := jws.NewHeaders() hdrs.Set(`x-example`, true) buf, err := jws.Sign([]byte("Lorem ipsum"), jws.WithKey(jwa.HS256(), key, jws.WithProtectedHeaders(hdrs))) if err != nil { fmt.Printf("failed to sign payload: %s\n", err) return } fmt.Printf("%s\n", buf) // OUTPUT: // eyJhbGciOiJIUzI1NiIsIngtZXhhbXBsZSI6dHJ1ZX0.TG9yZW0gaXBzdW0.9nIX0hN7u1b97UcjmrVvd5y1ubkQp_1gz1V3Mkkcm14 } ``` source: [examples/jws_sign_with_headers_example_test.go](https://github.com/lestrrat-go/jwx/blob/v3/examples/jws_sign_with_headers_example_test.go) ## Using cloud KMS services If you want to use cloud KMSes such as AWS KMS to sign and verify payloads, look for an object that implements `crypto.Signer`. There are some [implementations written for this module](https://github.com/jwx-go/crypto-signer). NOTE: THESE WERE WRITTEN FOR OLDER RELEASES. PLEASE SEND PRs IF YOU WANT THEM UPDATED. Event if you cannot find an implementation that you are looking for in the above repository, any other implementation that implements `crypto.Signer` should work. # Verifying ## Verification using a single key To verify a JWS message using a single key, use `jws.Verify()` with the `jws.WithKey()` option. It will automatically do the right thing whether it's serialized in compact form or JSON form. The `alg` must be explicitly specified. See "[Why don't you automatically infer the algorithm for `jws.Verify`?](99-faq.md#why-dont-you-automatically-infer-the-algorithm-for-jwsverify-)" ```go package examples_test import ( "fmt" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/lestrrat-go/jwx/v3/jwk" "github.com/lestrrat-go/jwx/v3/jws" ) func Example_jws_verify_with_key() { const src = `eyJhbGciOiJIUzI1NiJ9.TG9yZW0gaXBzdW0.EjVtju0uXjSz6QevNgAqN1ESd9aNCP7-tJLifkQ0_C0` key, err := jwk.Import([]byte(`abracadabra`)) if err != nil { fmt.Printf("failed to create key: %s\n", err) return } buf, err := jws.Verify([]byte(src), jws.WithKey(jwa.HS256(), key)) if err != nil { fmt.Printf("failed to verify payload: %s\n", err) return } fmt.Printf("%s\n", buf) // OUTPUT: // Lorem ipsum } ``` source: [examples/jws_verify_with_key_example_test.go](https://github.com/lestrrat-go/jwx/blob/v3/examples/jws_verify_with_key_example_test.go) ## Verification using a JWKS To verify a payload using JWKS, by default you will need your payload and JWKS to have matching `kid` and `alg` fields. First the `alg` field's requirement is the same for using a single key. But you could, at a possible cost of trying multiple algorithms, let this module infer the algorithm to use by using the `jws.InferAlgorithmFromKey(true)` sub-option to `jws.WithKeySet()` (or `jwt.WithKeySet()`) See the example below for details. (ref: "[Why don't you automatically infer the algorithm for `jws.Verify`?](99-faq.md#why-dont-you-automatically-infer-the-algorithm-for-jwsverify-)"). And second, the `kid` field by default must match between the JWS signature and the key in JWKS. This can be explicitly disabled by specifying the `jws.WithRequireKid(false)` suboption when using the `jws.WithKeySet()` option (i.e.: `jws.WithKeySet(keyset, jws.WithRequireKid(false))`). For more discussion on why/how `alg`/`kid` values work, please read the [relevant section in the JWT documentation](01-jwt.md#parse-and-verify-a-jwt-with-a-key-set-matching-kid). ```go package examples_test import ( "fmt" "github.com/lestrrat-go/jwx/v3/internal/jwxtest" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/lestrrat-go/jwx/v3/jwk" "github.com/lestrrat-go/jwx/v3/jws" ) func Example_jws_verify_with_jwk_set() { // Setup payload, private key, and signed payload first... const payload = "Lorem ipsum" privkey, err := jwxtest.GenerateRsaJwk() if err != nil { fmt.Printf("failed to create private key: %s\n", err) return } const keyID = "correct-key" _ = privkey.Set(jwk.KeyIDKey, keyID) // Create a JWK Set set := jwk.NewSet() // Add some bogus keys k1, _ := jwk.Import([]byte("abracadabra")) _ = set.AddKey(k1) _ = k1.Set(jwk.KeyIDKey, "key-01") k2, _ := jwk.Import([]byte("opensesame")) _ = set.AddKey(k2) _ = k1.Set(jwk.KeyIDKey, "key-02") // AddKey the real thing. Note that k3 already contains the Key ID because // jwk.PublicKeyOf(jwk) automatically sets the Key ID if it is present in the private key. k3, _ := jwk.PublicKeyOf(privkey) _ = set.AddKey(k3) // Up to this point, you probably will replace with a simple jwk.Fetch() // or similar to obtain the JWKS // Sign with a key that has a Key ID. This forces jws.Sign() to include its // key ID in the JWS header. signed, err := jws.Sign([]byte(payload), jws.WithKey(jwa.RS256(), privkey)) if err != nil { fmt.Printf("failed to sign payload: %s\n", err) return } // Now let's try to verify the signed payload under various conditions { // No Key ID, nor algorithm present in the key} k3.Remove(jwk.KeyIDKey) // Remove Key ID so that it won't work // This fails, because it's going to try to lookup a key to use using the Key ID, // but it can't be found if _, err := jws.Verify(signed, jws.WithKeySet(set)); err == nil { fmt.Printf("Able to verify using jwk.Set, when it shouldn't: %s", err) return } // Now let's add a WithRequireKid(false). This will STILL fail, because the key // matching the key ID doesn't have an algorithm value set. if _, err := jws.Verify(signed, jws.WithKeySet(set, jws.WithRequireKid(false))); err == nil { fmt.Printf("Able to verify using jwk.Set, when it shouldn't: %s", err) return } // This works, because we're telling it to infer the algorithm by the // key type. if _, err := jws.Verify(signed, jws.WithKeySet(set, jws.WithInferAlgorithmFromKey(true), jws.WithRequireKid(false))); err != nil { fmt.Printf("Failed to verify using jwk.Set: %s", err) return } } { // Key ID present, but no algorithm k3.Set(jwk.KeyIDKey, keyID) // Add Key ID back // This does not work because the while the library can find // a key matching the key ID, it doesn't know what algorithm to use if _, err := jws.Verify(signed, jws.WithKeySet(set)); err == nil { fmt.Printf("Able to verify using jwk.Set, when it shouldn't: %s", err) return } // This works, because the library can find a key matching the key ID, // and it can infer the algorithm from the key type if _, err := jws.Verify(signed, jws.WithKeySet(set, jws.WithInferAlgorithmFromKey(true))); err != nil { fmt.Printf("Failed to verify using jwk.Set: %s", err) return } } { // both Key ID and algorithm present // And finally, the "cleanest" way. k3.Set(jwk.AlgorithmKey, jwa.RS256()) // Set algorithm if _, err := jws.Verify(signed, jws.WithKeySet(set)); err != nil { fmt.Printf("Failed to verify using jwk.Set: %s", err) return } } // If you just can't do this (e.g. because your token doesn't have a Key ID), // then you're better off using the single-key option jws.WithKey() (or the jwt.WithKey() option) // OUTPUT: } ``` source: [examples/jws_verify_with_keyset_example_test.go](https://github.com/lestrrat-go/jwx/blob/v3/examples/jws_verify_with_keyset_example_test.go) ## Verification using a detached payload To verify a JWS message with detached payload, use the `jws.WithDetachedPayload()` option: ```go package examples_test import ( "fmt" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/lestrrat-go/jwx/v3/jwk" "github.com/lestrrat-go/jwx/v3/jws" ) func Example_jws_verify_detached_payload() { serialized := `eyJhbGciOiJIUzI1NiIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19..lnRw_MSpQjARa5LWqPcu8Qls9p3wYGrC6tz4-nr0rkA` payload := `$.02` key, err := jwk.Import([]byte(`abracadabra`)) if err != nil { fmt.Printf("failed to create symmetric key: %s\n", err) return } verified, err := jws.Verify([]byte(serialized), jws.WithKey(jwa.HS256(), key), jws.WithDetachedPayload([]byte(payload))) if err != nil { fmt.Printf("failed to verify payload: %s\n", err) return } fmt.Printf("%s\n", verified) // OUTPUT: // $.02 } ``` source: [examples/jws_verify_detached_payload_example_test.go](https://github.com/lestrrat-go/jwx/blob/v3/examples/jws_verify_detached_payload_example_test.go) ## Verification using `jku` Regular calls to `jws.Verify()` does not respect the JWK Set referenced in the `jku` field. In order to verify the payload using the `jku` field, you must use the `jws.VerifyAuto()` function. ```go wl := ... // Create an appropriate whitelist payload, _ := jws.VerifyAuto(buf, jws.WithFetchWhitelist(wl)) ``` This will tell `jws` to verify the given buffer using the JWK Set presented at the URL specified in the `jku` field. If the buffer is a JSON message, then this is done for each of the signature in the `signatures` array. The URL in the `jku` field must have the `https` scheme, and the key ID in the JWK Set must match the key ID present in the JWS message. Because this operation will result in your program accessing remote resources, the default behavior is to NOT allow any URLs. You must specify a whitelist ```go wl := jwk.NewMapWhitelist(). Add(`https://white-listed-address`) payload, _ := jws.VerifyAuto(buf, jws.WithFetchWhitelist(wl)) ``` If you want to allow any URLs to be accessible, use the `jwk.InsecureWhitelist`. ```go wl := jwk.InsecureWhitelist{} payload, _ := jws.VerifyAuto(buf, jws.WithFetchWhitelist(wl)) ``` If you must configure the HTTP Client in a special way, use the `jws.WithHTTPClient()` option: ```go client := &http.Client{ ... } payload, _ := jws.VerifyAuto(buf, jws.WithHTTPClient(client)) ``` # Using a custom signing/verification algorithm Sometimes we do not offer a particular algorithm out of the box, but you have an implementation for it. In such scenarios, you can use the [`jws.RegisterSigner()`](https://pkg.go.dev/github.com/lestrrat-go/jwx/v3/jws#RegisterSigner) and [`jws.RegisterVerifier()`](https://pkg.go.dev/github.com/lestrrat-go/jwx/v3/jws#RegisterVerifier) functions to generate your own verifier instance. ```go package examples_test import ( "crypto/rand" "fmt" "github.com/cloudflare/circl/sign/ed25519" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/lestrrat-go/jwx/v3/jws" ) func Example_jws_custom_signer_verifier() { // Newer way of registering a custom signer/verifier if err := jws.RegisterSigner(jwa.EdDSA(), CirclEdDSASigner{}); err != nil { fmt.Printf(`failed to register signer: %s`, err) return } if err := jws.RegisterVerifier(jwa.EdDSA(), CirclEdDSAVerifier{}); err != nil { fmt.Printf(`failed to register verifier: %s`, err) return } pubkey, privkey, err := ed25519.GenerateKey(rand.Reader) if err != nil { fmt.Printf(`failed to generate keys: %s`, err) return } const payload = "Lorem Ipsum" signed, err := jws.Sign([]byte(payload), jws.WithKey(jwa.EdDSA(), privkey)) if err != nil { fmt.Printf(`failed to generate signed message: %s`, err) return } verified, err := jws.Verify(signed, jws.WithKey(jwa.EdDSA(), pubkey)) if err != nil { fmt.Printf(`failed to verify signed message: %s`, err) return } if string(verified) != payload { fmt.Printf(`got invalid payload: %s`, verified) return } // OUTPUT: // Custom signer called // Custom verifier called } type CirclEdDSASigner struct{} func (CirclEdDSASigner) Algorithm() jwa.SignatureAlgorithm { return jwa.EdDSA() } // Sign implements the jws.Signer2 interface for Circl's EdDSA signer. // // Signer2 is a relatively low-level API. It receives multiple parameters because of this. // // One thing you should do in your signer is to check the type of the key passed in. // We have no way of constricting the type of key that is passed in without knowing // the implementation details of your custom signer, and thus we cannot guarantee that // users will pass in the correct type of key. // // Those implementing the jws.Signer2 interface could construct the buffer to be signed // themselves and generate the signature, but it is often easier to use the jwsbb.Sign // function, which takes care of the constructiion. In this example, we would like to // tell jwsbb.Sign to construct the buffer and generate the signature using ed25519.Sign, // but since the function signatures do not match, we are providing an adapter // that implements the jwsbb.Signer interface. // // If you need to construct the buffer yourself, you can do so by using the // jwsbb.SignBuffer() function in combination with the jwsbb.SignRaw() function. func (CirclEdDSASigner) Sign(key any, payload []byte) ([]byte, error) { fmt.Println("Custom signer called") privkey, ok := key.(ed25519.PrivateKey) if !ok { return nil, fmt.Errorf(`jws.CirclEdDSASigner: invalid key type %T. ed25519.PrivateKey is required`, key) } return ed25519.Sign(privkey, payload), nil } type CirclEdDSAVerifier struct{} func (CirclEdDSAVerifier) Algorithm() jwa.SignatureAlgorithm { return jwa.EdDSA() } // Do implements the jws.Verifier interface for Circl's EdDSA verifier. // // See the comments for CirclECDSASigner.Do for more information on what this function does. func (CirclEdDSAVerifier) Verify(key any, payload, signature []byte) error { fmt.Println("Custom verifier called") pubkey, ok := key.(ed25519.PublicKey) if !ok { return fmt.Errorf(`jws.CirclECDSASignerVerifier: invalid key type %T. ed25519.PublicKey is required`, key) } if ed25519.Verify(pubkey, payload, signature) { return nil } return fmt.Errorf(`failed to verify EdDSA signature`) } type LegacyCirclEdDSASignerVerifier struct{} func LegacyNewCirclEdDSASigner() (jws.Signer, error) { return &LegacyCirclEdDSASignerVerifier{}, nil } func LegacyNewCirclEdDSAVerifier() (jws.Verifier, error) { return &LegacyCirclEdDSASignerVerifier{}, nil } func (s LegacyCirclEdDSASignerVerifier) Algorithm() jwa.SignatureAlgorithm { return jwa.EdDSA() } func (s LegacyCirclEdDSASignerVerifier) Sign(payload []byte, keyif any) ([]byte, error) { fmt.Println("Custom signer called (legacy)") switch key := keyif.(type) { case ed25519.PrivateKey: return ed25519.Sign(key, payload), nil default: return nil, fmt.Errorf(`invalid key type %T`, keyif) } } func (s LegacyCirclEdDSASignerVerifier) Verify(payload []byte, signature []byte, keyif any) error { fmt.Println("Custom verifier called (legacy)") switch key := keyif.(type) { case ed25519.PublicKey: if ed25519.Verify(key, payload, signature) { return nil } return fmt.Errorf(`failed to verify EdDSA signature`) default: return fmt.Errorf(`invalid key type %T`, keyif) } } ``` source: [examples/jws_custom_signer_verifier_example_test.go](https://github.com/lestrrat-go/jwx/blob/v3/examples/jws_custom_signer_verifier_example_test.go) # Enabling ES256K See [Enabling Optional Signature Methods](./20-global-settings.md#enabling-optional-signature-methods) # Using a custom base64 encoder Per specification JWS should be using URL base64 encoding with no padding when generating (and by nature of the process when verifying as well) signatures. However, some systems do not necessarily adhere to the standards ([there have been reports that AWS ALB is one such system, generating User Claims JWT with padding](https://github.com/lestrrat-go/jwx/discussions/1324)) In these situations, you will need to specify the base64 encoder to your `jws.Sign` and `jws.Verify` calls. Please note that this feature is available on v3 onwards only. ```go package examples_test import ( "bytes" "encoding/base64" "fmt" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/lestrrat-go/jwx/v3/jws" ) func Example_jws_sign_with_custom_base64() { const payload = "Lorem ipsum" const symmetricKey = "0123456789abcdef0123456789abcdef" signed, err := jws.Sign([]byte(payload), jws.WithKey(jwa.HS256(), []byte(symmetricKey)), jws.WithBase64Encoder(base64.URLEncoding)) if err != nil { fmt.Printf("failed to sign payload: %s\n", err) } fmt.Println(string(signed)) // This should fail because we're using a different base64 encoder if _, err := jws.Verify(signed, jws.WithKey(jwa.HS256(), []byte(symmetricKey))); err == nil { fmt.Printf("verification should have failed, but succeeded\n") return } verified, err := jws.Verify(signed, jws.WithKey(jwa.HS256(), []byte(symmetricKey)), jws.WithBase64Encoder(base64.URLEncoding)) if err != nil { fmt.Printf("failed to verify payload: %s\n", err) return } if !bytes.Equal([]byte(payload), verified) { fmt.Printf("verified content do not match: %s\n", err) return } // OUTPUT: // eyJhbGciOiJIUzI1NiJ9.TG9yZW0gaXBzdW0=.DahnsWNiVXmt23d2nUx_ePAZLy2nodC-Oh0bIB88cck= } ``` source: [examples/jws_sign_with_custom_base64_example_test.go](https://github.com/lestrrat-go/jwx/blob/v3/examples/jws_sign_with_custom_base64_example_test.go) You can use these option for `jwt.Sign` and `jwt.Parse` as well. See the [JWT docs for an example](./01-jwt.md#using-a-custom-base64-encoder). # Filtering JWS headers **Important:** The filtering functionality described in this section operates on JWS headers only, not on the JWS message itself, nor can you filter or modify the payload of a JWS message directly using these filters. The JWS library provides filtering capabilities that allow you to selectively include or exclude specific header fields from JWS headers. This is particularly useful when you need to: - Remove sensitive information from headers before logging or transmission - Extract only specific header fields for processing - Separate standard JWS headers from custom application-specific headers - Create environment-specific header configurations The filtering operates on parsed JWS messages and their headers, allowing you to create new header objects with only the fields you need. ## Basic header filtering You can filter JWS headers using the [`jws.HeaderNameFilter`](https://pkg.go.dev/github.com/lestrrat-go/jwx/v3/jws#HeaderNameFilter): ```go package examples_test import ( "fmt" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/lestrrat-go/jwx/v3/jwk" "github.com/lestrrat-go/jwx/v3/jws" ) func Example_jws_header_filter_basic() { // Create a key for signing key, err := jwk.Import([]byte(`my-secret-key`)) if err != nil { fmt.Printf("failed to create key: %s\n", err) return } // Create headers with both standard and custom fields headers := jws.NewHeaders() headers.Set(jws.AlgorithmKey, jwa.HS256()) headers.Set(jws.KeyIDKey, "key-2024") headers.Set(jws.TypeKey, "JWT") headers.Set("custom-claim", "important-data") headers.Set("app-version", "v1.2.3") headers.Set("environment", "production") // Sign with custom headers payload := []byte(`{"user": "alice", "role": "admin"}`) signed, err := jws.Sign(payload, jws.WithKey(jwa.HS256(), key, jws.WithProtectedHeaders(headers))) if err != nil { fmt.Printf("failed to sign: %s\n", err) return } // Parse the signed message to access headers msg, err := jws.Parse(signed) if err != nil { fmt.Printf("failed to parse: %s\n", err) return } originalHeaders := msg.Signatures()[0].ProtectedHeaders() // Filter 1: Extract only custom fields using HeaderNameFilter customFilter := jws.NewHeaderNameFilter("custom-claim", "app-version", "environment") _, err = customFilter.Filter(originalHeaders) if err != nil { fmt.Printf("failed to filter custom headers: %s\n", err) return } // Filter 2: Extract only standard fields using StandardHeadersFilter standardFilter := jws.StandardHeadersFilter() _, err = standardFilter.Filter(originalHeaders) if err != nil { fmt.Printf("failed to filter standard headers: %s\n", err) return } // Filter 3: Remove sensitive custom fields using Reject sensitiveFilter := jws.NewHeaderNameFilter("custom-claim") _, err = sensitiveFilter.Reject(originalHeaders) if err != nil { fmt.Printf("failed to reject sensitive headers: %s\n", err) return } // OUTPUT: } ``` source: [examples/jws_filter_basic_example_test.go](https://github.com/lestrrat-go/jwx/blob/v3/examples/jws_filter_basic_example_test.go) ## Advanced header filtering For more complex filtering scenarios, including multi-signature JWS messages: ```go package examples_test import ( "fmt" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/lestrrat-go/jwx/v3/jwk" "github.com/lestrrat-go/jwx/v3/jws" ) func Example_jws_header_filter_advanced() { // Create keys for multi-signature JWS key1, err := jwk.Import([]byte(`secret-key-1`)) if err != nil { fmt.Printf("failed to create key1: %s\n", err) return } key2, err := jwk.Import([]byte(`secret-key-2`)) if err != nil { fmt.Printf("failed to create key2: %s\n", err) return } // Create complex headers for first signature headers1 := jws.NewHeaders() headers1.Set(jws.KeyIDKey, "primary-key") headers1.Set("service", "auth-service") headers1.Set("version", "2.1") headers1.Set("security-level", "high") headers1.Set("internal-use", "true") // Create headers for second signature with different custom fields headers2 := jws.NewHeaders() headers2.Set(jws.KeyIDKey, "backup-key") headers2.Set("service", "backup-auth") headers2.Set("datacenter", "us-west") headers2.Set("backup-priority", "1") headers2.Set("internal-use", "false") payload := []byte(`{"action": "login", "timestamp": 1609459200}`) // Create a multi-signature JWS message using JSON serialization signed, err := jws.Sign(payload, jws.WithJSON(), jws.WithKey(jwa.HS256(), key1, jws.WithProtectedHeaders(headers1)), jws.WithKey(jwa.HS256(), key2, jws.WithProtectedHeaders(headers2))) if err != nil { fmt.Printf("failed to sign message: %s\n", err) return } // Parse the signed message parsedMsg, err := jws.Parse(signed) if err != nil { fmt.Printf("failed to parse message: %s\n", err) return } // Advanced filtering scenarios for i, sig := range parsedMsg.Signatures() { originalHeaders := sig.ProtectedHeaders() // Use case 1: Filter by service-related fields serviceFilter := jws.NewHeaderNameFilter("service", "datacenter", "backup-priority") _, err := serviceFilter.Filter(originalHeaders) if err != nil { fmt.Printf("failed to filter service headers: %s\n", err) continue } // Use case 2: Create public headers (remove internal fields) internalFilter := jws.NewHeaderNameFilter("internal-use", "security-level") _, err = internalFilter.Reject(originalHeaders) if err != nil { fmt.Printf("failed to create public headers: %s\n", err) continue } // Use case 3: Combine standard filter with custom filtering standardFilter := jws.StandardHeadersFilter() customFieldsOnly, err := standardFilter.Reject(originalHeaders) if err != nil { fmt.Printf("failed to extract custom fields: %s\n", err) continue } // Then filter custom fields for specific categories operationalFilter := jws.NewHeaderNameFilter("service", "version", "datacenter") _, err = operationalFilter.Filter(customFieldsOnly) if err != nil { fmt.Printf("failed to filter operational headers: %s\n", err) continue } if i == 0 { // Use case 4: Validate security requirements for first signature validateJWSSecurityHeaders(originalHeaders) } } // OUTPUT: } // Helper function to demonstrate validation using filtered JWS headers func validateJWSSecurityHeaders(headers jws.Headers) { // Check security level var secLevel string if err := headers.Get("security-level", &secLevel); err != nil { fmt.Println("✗ Security level not found") } // Check internal use flag var internalUse string if err := headers.Get("internal-use", &internalUse); err != nil { fmt.Println("✗ Internal use flag missing") } // Check service identification var service string if err := headers.Get("service", &service); err != nil { fmt.Println("✗ Service identification missing") } } ``` source: [examples/jws_filter_advanced_example_test.go](https://github.com/lestrrat-go/jwx/blob/v3/examples/jws_filter_advanced_example_test.go) golang-github-lestrrat-go-jwx-3.0.13/docs/03-jwe.md000066400000000000000000000645511515060566400216760ustar00rootroot00000000000000# Working with JWE In this document we describe how to work with JWK using `github.com/lestrrat-go/jwx/v3/jwe` * [Parsing](#parsing) * [Parse a JWE message stored in memory](#parse-a-jwe-message-stored-in-memory) * [Parse a JWE message stored in a file](#parse-a-jwe-message-stored-in-a-file) * [Encrypting](#encrypting) * [Generating a JWE message in compact serialization format](#generating-a-jwe-message-in-compact-serialization-format) * [Generating a JWE message in JSON serialization format](#generating-a-jwe-message-in-json-serialization-format) * [Including arbitrary headers](#including-arbitrary-headers) * [Decrypting](#decrypting) * [Decrypting using a single key](#decrypting-using-a-single-key) * [Decrypting using a JWKS](#decrypting-using-a-jwks) * [Filtering JWE headers](#filtering-jwe-headers) # Parsing Parsing a JWE message means taking either a JWE message serialized in JSON or Compact form and loading it into a `jwe.Message` object. No decryption is performed, and therefore you cannot access the raw payload like when you use `jwe.Decrypt()` to decrypt the message. Also, be aware that a `jwe.Message` is not meant to be used for either decryption nor encryption. It is only provided so that it can be inspected -- there is no way to decrypt or sign using an already parsed `jwe.Message`. ## Parse a JWE message stored in memory You can parse a JWE message in memory stored as `[]byte` into a [`jwe.Message`](https://pkg.go.dev/github.com/lestrrat-go/jwx/v3/jwe#Message) object. In this mode, there is no decryption performed. ```go package examples_test import ( "encoding/json" "fmt" "os" "github.com/lestrrat-go/jwx/v3/jwe" ) func Example_jwe_parse() { const src = `eyJhbGciOiJSU0ExXzUiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2In0.KrFTaMKVY_iUKYYk905QjbUf_fpBXvXCzIAfbPoPMGViDzxtgz5qnch8waV7wraVDfzpW7JfPOw6Nz_-XRwN3Vbud48bRYFw92GkC0M6kpKFpl_xgZxGN47ggNk9hzgqd7mFCuyufeYdn5c2fPoRZAV4UxvakLozEYcQo-eZaFmoYS4pyoC-IKKRikobW8n__LksMzXc_Vps1axn5kdpxsKQ4k1oayvUrgWX2PMxKn_TcLEKHtCN7qRlJ5hkKbZAXAdd34zGWcFV5gc1tcLs6HFhnebo8GUgItTYWBKSKzF6MyLJNRSUPFVq9q-Jxi1juXIlDrv_7rHVsdokQmBfvA.bK7z7Z3gEzFDgDQvNen0Ww.2hngnAVrmucUpJKLgIzYcg.CHs3ZP7JtG430Dl9YAKLMAl` msg, err := jwe.Parse([]byte(src)) if err != nil { fmt.Printf("failed to parse JWE message: %s\n", err) return } json.NewEncoder(os.Stdout).Encode(msg) // OUTPUT: // {"ciphertext":"2hngnAVrmucUpJKLgIzYcg","encrypted_key":"KrFTaMKVY_iUKYYk905QjbUf_fpBXvXCzIAfbPoPMGViDzxtgz5qnch8waV7wraVDfzpW7JfPOw6Nz_-XRwN3Vbud48bRYFw92GkC0M6kpKFpl_xgZxGN47ggNk9hzgqd7mFCuyufeYdn5c2fPoRZAV4UxvakLozEYcQo-eZaFmoYS4pyoC-IKKRikobW8n__LksMzXc_Vps1axn5kdpxsKQ4k1oayvUrgWX2PMxKn_TcLEKHtCN7qRlJ5hkKbZAXAdd34zGWcFV5gc1tcLs6HFhnebo8GUgItTYWBKSKzF6MyLJNRSUPFVq9q-Jxi1juXIlDrv_7rHVsdokQmBfvA","header":{"alg":"RSA1_5"},"iv":"bK7z7Z3gEzFDgDQvNen0Ww","protected":"eyJhbGciOiJSU0ExXzUiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2In0","tag":"CHs3ZP7JtG430Dl9YAKLMAk"} } ``` source: [examples/jwe_parse_example_test.go](https://github.com/lestrrat-go/jwx/blob/v3/examples/jwe_parse_example_test.go) ## Parse a JWE message stored in a file To parse a JWE stored in a file, use [`jwe.ReadFile()`](https://pkg.go.dev/github.com/lestrrat-go/jwx/v3/jwe#ReadFile). [`jwe.ReadFile()`](https://pkg.go.dev/github.com/lestrrat-go/jwx/v3/jwe#ReadFile) accepts the same options as [`jwe.Parse()`](https://pkg.go.dev/github.com/lestrrat-go/jwx/v3/jwe#Parse). ```go package examples_test import ( "encoding/json" "fmt" "os" "github.com/lestrrat-go/jwx/v3/jwe" ) func Example_jwe_readfile() { const src = `eyJhbGciOiJSU0ExXzUiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2In0.KrFTaMKVY_iUKYYk905QjbUf_fpBXvXCzIAfbPoPMGViDzxtgz5qnch8waV7wraVDfzpW7JfPOw6Nz_-XRwN3Vbud48bRYFw92GkC0M6kpKFpl_xgZxGN47ggNk9hzgqd7mFCuyufeYdn5c2fPoRZAV4UxvakLozEYcQo-eZaFmoYS4pyoC-IKKRikobW8n__LksMzXc_Vps1axn5kdpxsKQ4k1oayvUrgWX2PMxKn_TcLEKHtCN7qRlJ5hkKbZAXAdd34zGWcFV5gc1tcLs6HFhnebo8GUgItTYWBKSKzF6MyLJNRSUPFVq9q-Jxi1juXIlDrv_7rHVsdokQmBfvA.bK7z7Z3gEzFDgDQvNen0Ww.2hngnAVrmucUpJKLgIzYcg.CHs3ZP7JtG430Dl9YAKLMAl` f, err := os.CreateTemp(``, `jwe_readfile_example-*.jwe`) if err != nil { fmt.Printf("failed to create temporary file: %s\n", err) return } defer os.Remove(f.Name()) f.Write([]byte(src)) f.Close() msg, err := jwe.ReadFile(f.Name()) if err != nil { fmt.Printf("failed to parse JWE message from file %q: %s\n", f.Name(), err) return } json.NewEncoder(os.Stdout).Encode(msg) // OUTPUT: // {"ciphertext":"2hngnAVrmucUpJKLgIzYcg","encrypted_key":"KrFTaMKVY_iUKYYk905QjbUf_fpBXvXCzIAfbPoPMGViDzxtgz5qnch8waV7wraVDfzpW7JfPOw6Nz_-XRwN3Vbud48bRYFw92GkC0M6kpKFpl_xgZxGN47ggNk9hzgqd7mFCuyufeYdn5c2fPoRZAV4UxvakLozEYcQo-eZaFmoYS4pyoC-IKKRikobW8n__LksMzXc_Vps1axn5kdpxsKQ4k1oayvUrgWX2PMxKn_TcLEKHtCN7qRlJ5hkKbZAXAdd34zGWcFV5gc1tcLs6HFhnebo8GUgItTYWBKSKzF6MyLJNRSUPFVq9q-Jxi1juXIlDrv_7rHVsdokQmBfvA","header":{"alg":"RSA1_5"},"iv":"bK7z7Z3gEzFDgDQvNen0Ww","protected":"eyJhbGciOiJSU0ExXzUiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2In0","tag":"CHs3ZP7JtG430Dl9YAKLMAk"} } ``` source: [examples/jwe_readfile_example_test.go](https://github.com/lestrrat-go/jwx/blob/v3/examples/jwe_readfile_example_test.go) # Encrypting ## Generating a JWE message in compact serialization format To encrypt an arbitrary payload as a JWE message in compact serialization format, use `jwt.Encrypt()`. Note that this would be [slightly different if you are encrypting JWTs](01-jwt.md#serialize-using-jws), as you would be using functions from the `jwt` package instead of `jws`. ```go package examples_test import ( "crypto/rand" "crypto/rsa" "fmt" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/lestrrat-go/jwx/v3/jwe" "github.com/lestrrat-go/jwx/v3/jwk" ) func Example_jwe_encrypt() { rawprivkey, err := rsa.GenerateKey(rand.Reader, 2048) if err != nil { fmt.Printf("failed to create raw private key: %s\n", err) return } privkey, err := jwk.Import(rawprivkey) if err != nil { fmt.Printf("failed to create private key: %s\n", err) return } pubkey, err := privkey.PublicKey() if err != nil { fmt.Printf("failed to create public key:%s\n", err) return } const payload = `Lorem ipsum` encrypted, err := jwe.Encrypt([]byte(payload), jwe.WithKey(jwa.RSA_OAEP(), pubkey)) if err != nil { fmt.Printf("failed to encrypt payload: %s\n", err) return } decrypted, err := jwe.Decrypt(encrypted, jwe.WithKey(jwa.RSA_OAEP(), privkey)) if err != nil { fmt.Printf("failed to decrypt payload: %s\n", err) return } fmt.Printf("%s\n", decrypted) // OUTPUT: // Lorem ipsum } ``` source: [examples/jwe_encrypt_example_test.go](https://github.com/lestrrat-go/jwx/blob/v3/examples/jwe_encrypt_example_test.go) ## Generating a JWE message in JSON serialization format Generally the only time you need to use a JSON serialization format is when you have to generate multiple recipients (encrypted keys) for a given payload using multiple encryption algorithms and keys. When this need arises, use the [`jwe.Encrypt()`](https://pkg.go.dev/github.com/lestrrat-go/jwx/v3/jws#Encrypt) function with the `jwe.WithJSON()` option and multiple `jwe.WithKey()` options: ```go package examples_test import ( "crypto/rand" "crypto/rsa" "fmt" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/lestrrat-go/jwx/v3/jwe" "github.com/lestrrat-go/jwx/v3/jwk" ) func Example_jwe_encrypt_json() { rawprivkey, err := rsa.GenerateKey(rand.Reader, 2048) if err != nil { fmt.Printf("failed to create raw private key: %s\n", err) return } privkey, err := jwk.Import(rawprivkey) if err != nil { fmt.Printf("failed to create private key: %s\n", err) return } pubkey, err := privkey.PublicKey() if err != nil { fmt.Printf("failed to create public key:%s\n", err) return } const payload = `Lorem ipsum` encrypted, err := jwe.Encrypt( []byte(payload), jwe.WithJSON(), // Toggle JSON serialization. Because there's only one key (recipient), this will produce Flattened JSON serialization jwe.WithLegacyHeaderMerging(false), // Disable legacy header merging jwe.WithKey(jwa.RSA_OAEP(), pubkey), // Public key for encryption ) if err != nil { fmt.Printf("failed to encrypt payload: %s\n", err) return } decrypted, err := jwe.Decrypt(encrypted, jwe.WithKey(jwa.RSA_OAEP(), privkey)) if err != nil { fmt.Printf("failed to decrypt payload: %s\n", err) return } fmt.Printf("%s\n", decrypted) // OUTPUT: // Lorem ipsum } func Example_jwe_encrypt_json_multi() { var privkeys []jwk.Key var pubkeys []jwk.Key for i := 0; i < 3; i++ { rawprivkey, err := rsa.GenerateKey(rand.Reader, 2048) if err != nil { fmt.Printf("failed to create raw private key: %s\n", err) return } privkey, err := jwk.Import(rawprivkey) if err != nil { fmt.Printf("failed to create private key: %s\n", err) return } privkeys = append(privkeys, privkey) pubkey, err := privkey.PublicKey() if err != nil { fmt.Printf("failed to create public key:%s\n", err) return } pubkeys = append(pubkeys, pubkey) } options := []jwe.EncryptOption{jwe.WithJSON()} for _, key := range pubkeys { options = append(options, jwe.WithKey(jwa.RSA_OAEP(), key)) } const payload = `Lorem ipsum` encrypted, err := jwe.Encrypt([]byte(payload), options...) if err != nil { fmt.Printf("failed to encrypt payload: %s\n", err) return } for _, key := range privkeys { decrypted, err := jwe.Decrypt(encrypted, jwe.WithKey(jwa.RSA_OAEP(), key)) if err != nil { fmt.Printf("failed to decrypt payload: %s\n", err) return } fmt.Printf("%s\n", decrypted) } // OUTPUT: // Lorem ipsum // Lorem ipsum // Lorem ipsum } ``` source: [examples/jwe_encrypt_json_example_test.go](https://github.com/lestrrat-go/jwx/blob/v3/examples/jwe_encrypt_json_example_test.go) ## Including arbitrary headers By default, only some header fields are included in the result from `jwe.Encrypt()`. For global protected headers, you can use the `jwe.WithProtectedHeaders()` option. In order to provide extra headers to the encrypted message such as `apu` and `apv`, you will need to use `jwe.WithKey()` option with the `jwe.WithPerRecipientHeaders()` suboption. ```go package examples_test import ( "crypto/rand" "crypto/rsa" "fmt" "os" "github.com/lestrrat-go/jwx/v3/internal/json" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/lestrrat-go/jwx/v3/jwe" ) func Example_jwe_sign_with_headers() { privkey, err := rsa.GenerateKey(rand.Reader, 2048) if err != nil { fmt.Printf("failed to create private key: %s\n", err) return } const payload = "Lorem ipsum" hdrs := jwe.NewHeaders() hdrs.Set(`x-example`, true) encrypted, err := jwe.Encrypt([]byte(payload), jwe.WithKey(jwa.RSA_OAEP(), privkey.PublicKey, jwe.WithPerRecipientHeaders(hdrs))) if err != nil { fmt.Printf("failed to encrypt payload: %s\n", err) return } msg, err := jwe.Parse(encrypted) if err != nil { fmt.Printf("failed to parse message: %s\n", err) return } // NOTE: This is a bit tricky. Even though we specified a per-recipient // header when executing jwe.Encrypt, the headers end up being in the // global protected headers section. This is... by the books. JWE // in Compact serialization asks us to shove the per-recipient // headers in the protected header section, because there is nowhere // else to store this information. // // If this were a full JWE JSON message, you might have to juggle // between the global protected headers, global unprotected headers, // and per-recipient unprotected headers json.NewEncoder(os.Stdout).Encode(msg.ProtectedHeaders()) // OUTPUT: // {"alg":"RSA-OAEP","enc":"A256GCM","x-example":true} } ``` source: [examples/jwe_encrypt_with_headers_example_test.go](https://github.com/lestrrat-go/jwx/blob/v3/examples/jwe_encrypt_with_headers_example_test.go) # Decrypting ## Decrypting using a single key To decrypt a JWE message using a single key, use `jwe.Decrypt()` with the `jwe.WithKey()` option. It will automatically do the right thing whether it's serialized in compact form or JSON form. The `alg` must be explicitly specified. ```go package examples_test import ( "fmt" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/lestrrat-go/jwx/v3/jwe" ) func Example_jwe_verify_with_key() { const payload = "Lorem ipsum" encrypted, err := jwe.Encrypt([]byte(payload), jwe.WithKey(jwa.RSA_OAEP(), jwkRSAPublicKey)) if err != nil { fmt.Printf("failed to sign payload: %s\n", err) return } decrypted, err := jwe.Decrypt(encrypted, jwe.WithKey(jwa.RSA_OAEP(), jwkRSAPrivateKey)) if err != nil { fmt.Printf("failed to sign payload: %s\n", err) return } fmt.Printf("%s\n", decrypted) // OUTPUT: // Lorem ipsum } ``` source: [examples/jwe_decrypt_with_key_example_test.go](https://github.com/lestrrat-go/jwx/blob/v3/examples/jwe_decrypt_with_key_example_test.go) ## Decrypting using a JWKS To decrypt a payload using JWKS, by default you will need your payload and JWKS to have matching `alg` field. The `alg` field's requirement is the same for using a single key. See "[Why don't you automatically infer the algorithm for `jws.Verify`?](99-faq.md#why-dont-you-automatically-infer-the-algorithm-for-jwsverify-)", it's the same for `jwe.Decrypt()`. Note that unlike in JWT, the `kid` is not required by default, although you _can_ make it so by passing `jwe.WithRequireKid(true)`. For more discussion on why/how `alg`/`kid` values work, please read the [relevant section in the JWT documentation](01-jwt.md#parse-and-verify-a-jwt-with-a-key-set-matching-kid). ```go package examples_test import ( "crypto/rand" "crypto/rsa" "fmt" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/lestrrat-go/jwx/v3/jwe" "github.com/lestrrat-go/jwx/v3/jwk" ) func Example_jwe_verify_with_jwk_set() { privkey, err := rsa.GenerateKey(rand.Reader, 2048) if err != nil { fmt.Printf("failed to create private key: %s\n", err) return } const payload = "Lorem ipsum" encrypted, err := jwe.Encrypt([]byte(payload), jwe.WithKey(jwa.RSA_OAEP(), privkey.PublicKey)) if err != nil { fmt.Printf("failed to sign payload: %s\n", err) return } // Create a JWK Set set := jwk.NewSet() // Add some bogus keys k1, _ := jwk.Import([]byte("abracadabra")) set.AddKey(k1) k2, _ := jwk.Import([]byte("opensesame")) set.AddKey(k2) // Add the real thing k3, _ := jwk.Import(privkey) k3.Set(jwk.AlgorithmKey, jwa.RSA_OAEP()) set.AddKey(k3) // Up to this point, you probably will replace with a simple jwk.Fetch() if _, err := jwe.Decrypt(encrypted, jwe.WithKeySet(set, jwe.WithRequireKid(false))); err != nil { fmt.Printf("Failed to decrypt using jwk.Set: %s", err) } // OUTPUT: } ``` source: [examples/jwe_decrypt_with_keyset_example_test.go](https://github.com/lestrrat-go/jwx/blob/v3/examples/jwe_decrypt_with_keyset_example_test.go) # Filtering JWE headers **Important**: Filtering operates on JWE headers only, not the JWE message or encrypted payload itself. When working with JWE messages, you may need to filter or manipulate the headers for various purposes while leaving the encrypted content intact. Header filtering is particularly useful for: - Removing sensitive information from headers before logging or transmission - Extract only specific header fields for processing - Separate standard JWE headers from custom application-specific headers - Create environment-specific header configurations The filtering operates on parsed JWE messages and their headers, allowing you to create new header objects with only the fields you need. ## Basic header filtering You can filter JWE headers using the [`jwe.HeaderNameFilter`](https://pkg.go.dev/github.com/lestrrat-go/jwx/v3/jwe#HeaderNameFilter): ```go package examples_test import ( "fmt" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/lestrrat-go/jwx/v3/jwe" ) // Example_jwe_filter_basic demonstrates basic JWE HeaderFilter functionality // with HeaderNameFilter.Filter(), StandardHeadersFilter(), and HeaderNameFilter.Reject() methods. func Example_jwe_filter_basic() { // Create JWE headers with custom headers for filtering demonstration protectedHeaders := jwe.NewHeaders() protectedHeaders.Set(jwe.AlgorithmKey, jwa.RSA_OAEP_256()) protectedHeaders.Set(jwe.ContentEncryptionKey, jwa.A256GCM) protectedHeaders.Set(jwe.ContentTypeKey, "application/json") protectedHeaders.Set(jwe.KeyIDKey, "example-key-1") protectedHeaders.Set("custom-header", "custom-value") protectedHeaders.Set("app-id", "my-app") protectedHeaders.Set("version", "1.0") // Use the headers directly for filtering examples headers := protectedHeaders // Example 1: HeaderNameFilter.Filter() - Include only specific headers customFilter := jwe.NewHeaderNameFilter("custom-header", "app-id", jwe.KeyIDKey) filteredHeaders, err := customFilter.Filter(headers) if err != nil { fmt.Printf("HeaderNameFilter.Filter failed: %s\n", err) return } // Use filteredHeaders variable by checking its length if len(filteredHeaders.Keys()) == 0 { fmt.Printf("No filtered headers found\n") return } // Example 2: StandardHeadersFilter() - Include only standard JWE headers stdFilter := jwe.StandardHeadersFilter() standardHeaders, err := stdFilter.Filter(headers) if err != nil { fmt.Printf("StandardHeadersFilter.Filter failed: %s\n", err) return } // Use standardHeaders variable by checking its length if len(standardHeaders.Keys()) == 0 { fmt.Printf("No standard headers found\n") return } // Example 3: HeaderNameFilter.Reject() - Exclude specific headers rejectFilter := jwe.NewHeaderNameFilter("version", "custom-header") rejectedHeaders, err := rejectFilter.Reject(headers) if err != nil { fmt.Printf("HeaderNameFilter.Reject failed: %s\n", err) return } // Use rejectedHeaders variable by checking its length if len(rejectedHeaders.Keys()) == 0 { fmt.Printf("No rejected headers found\n") return } // Example 4: StandardHeadersFilter().Reject() - Exclude standard headers, keep custom customOnlyHeaders, err := stdFilter.Reject(headers) if err != nil { fmt.Printf("StandardHeadersFilter.Reject failed: %s\n", err) return } // Use customOnlyHeaders variable by checking its length if len(customOnlyHeaders.Keys()) == 0 { fmt.Printf("No custom only headers found\n") return } // OUTPUT: } ``` source: [examples/jwe_filter_basic_example_test.go](https://github.com/lestrrat-go/jwx/blob/v3/examples/jwe_filter_basic_example_test.go) ## Advanced header filtering For more complex filtering scenarios, including multi-recipient JWE messages: ```go package examples_test import ( "fmt" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/lestrrat-go/jwx/v3/jwe" ) // Example_jwe_filter_advanced demonstrates advanced JWE HeaderFilter functionality // with security filtering, service integration scenarios, and header manipulation. func Example_jwe_filter_advanced() { // Create JWE headers with comprehensive metadata including security and service information protectedHeaders := jwe.NewHeaders() protectedHeaders.Set(jwe.AlgorithmKey, jwa.RSA_OAEP_256()) protectedHeaders.Set(jwe.ContentEncryptionKey, jwa.A256GCM) protectedHeaders.Set(jwe.ContentTypeKey, "application/json") protectedHeaders.Set(jwe.KeyIDKey, "service-key-001") // Security headers protectedHeaders.Set("security_level", "high") protectedHeaders.Set("access_control", "restricted") protectedHeaders.Set("encryption_version", "v2.1") // Service integration headers protectedHeaders.Set("service_name", "user-service") protectedHeaders.Set("api_version", "v1.2.3") protectedHeaders.Set("request_id", "req-789abc") protectedHeaders.Set("correlation_id", "corr-456def") // Operational headers protectedHeaders.Set("environment", "production") protectedHeaders.Set("region", "us-east-1") protectedHeaders.Set("trace_id", "trace-123xyz") // Use the headers directly for filtering examples headers := protectedHeaders // Advanced Example 1: Service Integration - Filter service-related headers serviceFilter := jwe.NewHeaderNameFilter("service_name", "api_version", "request_id", "correlation_id", jwe.KeyIDKey) serviceHeaders, err := serviceFilter.Filter(headers) if err != nil { fmt.Printf("Failed to filter service headers: %s\n", err) return } // Advanced Example 2: Security Headers - Filter security-related metadata securityFilter := jwe.NewHeaderNameFilter("security_level", "access_control", "encryption_version", jwe.AlgorithmKey, jwe.ContentEncryptionKey) securityHeaders, err := securityFilter.Filter(headers) if err != nil { fmt.Printf("Failed to filter security headers: %s\n", err) return } // Advanced Example 3: Operational Headers - Filter operational metadata operationalFilter := jwe.NewHeaderNameFilter("environment", "region", "trace_id") operationalHeaders, err := operationalFilter.Filter(headers) if err != nil { fmt.Printf("Failed to filter operational headers: %s\n", err) return } // Use operationalHeaders variable by checking its length if len(operationalHeaders.Keys()) == 0 { fmt.Printf("No operational headers found\n") return } // Advanced Example 4: Public Headers - Remove sensitive headers for public APIs sensitiveFilter := jwe.NewHeaderNameFilter("security_level", "access_control", "encryption_version", "trace_id") publicHeaders, err := sensitiveFilter.Reject(headers) if err != nil { fmt.Printf("Failed to create public headers: %s\n", err) return } // Use publicHeaders variable by checking its length if len(publicHeaders.Keys()) == 0 { fmt.Printf("No public headers found\n") return } // Advanced Example 5: Minimal Headers - Keep only essential headers for bandwidth optimization essentialFilter := jwe.NewHeaderNameFilter(jwe.AlgorithmKey, jwe.ContentEncryptionKey, jwe.KeyIDKey) minimalHeaders, err := essentialFilter.Filter(headers) if err != nil { fmt.Printf("Failed to filter minimal headers: %s\n", err) return } // Use minimalHeaders variable by checking its length if len(minimalHeaders.Keys()) == 0 { fmt.Printf("No minimal headers found\n") return } // Advanced Example 6: Custom Validation - Filter headers based on security requirements isValidSecurityLevel := validateJWESecurityHeaders(securityHeaders) if !isValidSecurityLevel { fmt.Printf("Security validation failed\n") return } isValidServiceConfig := validateJWEServiceHeaders(serviceHeaders) if !isValidServiceConfig { fmt.Printf("Service configuration validation failed\n") return } // Advanced Example 7: Header transformation for different environments prodHeaders := createJWEEnvironmentHeaders(headers, "production") if len(prodHeaders.Keys()) == 0 { fmt.Printf("Failed to create production headers\n") return } testHeaders := createJWEEnvironmentHeaders(headers, "testing") if len(testHeaders.Keys()) == 0 { fmt.Printf("Failed to create testing headers\n") return } // OUTPUT: } // validateJWESecurityHeaders checks if security headers meet requirements func validateJWESecurityHeaders(headers jwe.Headers) bool { // Check security level var securityLevel string if err := headers.Get("security_level", &securityLevel); err != nil || securityLevel != "high" { return false } // Check access control var accessControl string if err := headers.Get("access_control", &accessControl); err != nil || accessControl != "restricted" { return false } // Check encryption algorithm if algValue, ok := headers.Algorithm(); !ok || algValue != jwa.RSA_OAEP_256() { return false } return true } // validateJWEServiceHeaders checks if service headers are properly configured func validateJWEServiceHeaders(headers jwe.Headers) bool { requiredHeaders := []string{"service_name", "api_version", "request_id", "correlation_id"} for _, header := range requiredHeaders { if !headers.Has(header) { return false } } // Validate API version format var apiVersion string if err := headers.Get("api_version", &apiVersion); err != nil || len(apiVersion) < 5 { return false } return true } // createJWEEnvironmentHeaders creates environment-specific header configurations func createJWEEnvironmentHeaders(originalHeaders jwe.Headers, environment string) jwe.Headers { switch environment { case "production": // Production: Include security and service headers, exclude debug info prodFilter := jwe.NewHeaderNameFilter( jwe.AlgorithmKey, jwe.ContentEncryptionKey, jwe.ContentTypeKey, jwe.KeyIDKey, "security_level", "access_control", "service_name", "api_version", "environment", "region", ) filtered, err := prodFilter.Filter(originalHeaders) if err != nil { fmt.Printf("Failed to create production headers: %s\n", err) return jwe.NewHeaders() } return filtered case "testing": // Testing: Include debug headers, exclude some security headers testFilter := jwe.NewHeaderNameFilter( jwe.AlgorithmKey, jwe.ContentEncryptionKey, jwe.ContentTypeKey, jwe.KeyIDKey, "service_name", "api_version", "request_id", "correlation_id", "trace_id", "environment", ) filtered, err := testFilter.Filter(originalHeaders) if err != nil { fmt.Printf("Failed to create testing headers: %s\n", err) return jwe.NewHeaders() } return filtered default: // Default: Use standard headers only stdFilter := jwe.StandardHeadersFilter() filtered, err := stdFilter.Filter(originalHeaders) if err != nil { fmt.Printf("Failed to create default headers: %s\n", err) return jwe.NewHeaders() } return filtered } } ``` source: [examples/jwe_filter_advanced_example_test.go](https://github.com/lestrrat-go/jwx/blob/v3/examples/jwe_filter_advanced_example_test.go) golang-github-lestrrat-go-jwx-3.0.13/docs/04-jwk.md000066400000000000000000001077441515060566400217070ustar00rootroot00000000000000# Working with JWK In this document we describe how to work with JWK using `github.com/lestrrat-go/jwx/v3/jwk` * [Terminology](#terminology) * [JWK / Key](#jwk--key) * [JWK Set / Set](#jwk-set--set) * [Raw Key](#raw-key) * [Parsing](#parsing) * [Parse a set](#parse-a-set) * [Parse a key](#parse-a-key) * [Parse a key or set in PEM format](#parse-a-key-or-a-set-in-pem-format) * [Parse a key from a file](#parse-a-key-from-a-file) * [Parse a key as a struct field](#parse-a-key-as-a-struct-field) * [Construction](#construction) * [Using jwk.Import()](#using-jwkfromraw) * [Fetching JWK Sets](#fetching-jwk-sets) * [Parse a key from a remote resource](#parse-a-key-from-a-remote-resource) * [Auto-refreshing remote keys](#auto-refreshing-remote-keys) * [Using Whitelists](#using-whitelists) * [Working with jwk.Key](#working-with-jwkkey) * [Working with key-specific methods](#working-with-key-specific-methods) * [Setting values to fields](#setting-values-to-fields) * [Converting a jwk.Key to a raw key](#converting-a-jwkkey-to-a-raw-key) * [Filtering Keys with KeyFilter](#filtering-keys-with-keyfilter) --- # Terminology ## JWK / Key Used to describe a JWK key, possibly of type RSA, ECDSA, OKP, or Symmetric. ## JWK Set / Set A "jwk" resource on the web can either contain a single JWK or an array of multiple JWKs. The latter is called a JWK Set. It is impossible to know what the resource contains beforehand, so functions like [`jwk.Parse()`](https://pkg.go.dev/github.com/lestrrat-go/jwx/v3/jwk#Parse) and [`jwk.ReadFile()`](https://pkg.go.dev/github.com/lestrrat-go/jwx/v3/jwk#ReadFile) returns a [`jwk.Set`](https://pkg.go.dev/github.com/lestrrat-go/jwx/v3/jwk#Set) by default. ## Raw Key Used to describe the underlying raw key that a JWK represents. For example, an RSA JWK can represent rsa.PrivateKey/rsa.PublicKey, ECDSA JWK can represent ecdsa.PrivateKey/ecdsa.PublicKey, and so forth. --- The table below shows the matrix of key types and their respective `jwk.Key` and "raw" types. If given anything else, `jwk.Import` will return an error. | | `jwk.Key` Type | Raw Key Type | |-----------|----------------------------------------------|-------------------------------------------| | RSA | `jwk.RSAPublicKey` / `jwk.RSAPrivateKey` | `*rsa.PublicKey` / `*rsa.PublicKey` | | ECDSA | `jwk.ECDSAPublicKey` / `jwk.ECDSAPrivateKey` | `*ecdsa.PublicKey` / `*ecdsa.PublicKey` | | OKP | `jwk.OKPPublicKey` / `jwk.OKPPrivateKey` | `ed25519.PublicKey` / `ed25519.PublicKey` | | Symmetric | `jwk.SymmetricKey` | []byte | # Parsing ## Parse a set If you have a key set, or are unsure if the source is a set or a single key, you should use [`jwk.Parse()`](https://pkg.go.dev/github.com/lestrrat-go/jwx/v3/jwk#Parse) ```go package examples_test import ( "encoding/json" "fmt" "os" "github.com/lestrrat-go/jwx/v3/jwk" ) func Example_jwk_parse_jwks() { const src = `{ "keys": [ {"kty":"EC", "crv":"P-256", "x":"MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4", "y":"4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM", "use":"enc", "kid":"1"}, {"kty":"RSA", "n": "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw", "e":"AQAB", "alg":"RS256", "kid":"2011-04-29"} ] }` set, err := jwk.Parse([]byte(src)) if err != nil { fmt.Printf("failed to parse JWKS: %s\n", err) return } json.NewEncoder(os.Stdout).Encode(set) // OUTPUT: // {"keys":[{"crv":"P-256","kid":"1","kty":"EC","use":"enc","x":"MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4","y":"4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM"},{"alg":"RS256","e":"AQAB","kid":"2011-04-29","kty":"RSA","n":"0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw"}]} } ``` source: [examples/jwk_parse_jwks_example_test.go](https://github.com/lestrrat-go/jwx/blob/v3/examples/jwk_parse_jwks_example_test.go) ## Parse a key If you are sure that the source only contains a single key, you can use [`jwk.ParseKey()`](https://pkg.go.dev/github.com/lestrrat-go/jwx/v3/jwk#ParseKey) ```go package examples_test import ( "encoding/json" "fmt" "os" "github.com/lestrrat-go/jwx/v3/jwk" ) func Example_jwk_parse_key() { const src = `{ "kty":"EC", "crv":"P-256", "x":"MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4", "y":"4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM", "use":"enc", "kid":"1" }` key, err := jwk.ParseKey([]byte(src)) if err != nil { fmt.Printf("failed parse key: %s\n", err) return } json.NewEncoder(os.Stdout).Encode(key) // OUTPUT: // {"crv":"P-256","kid":"1","kty":"EC","use":"enc","x":"MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4","y":"4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM"} } ``` source: [examples/jwk_parse_key_example_test.go](https://github.com/lestrrat-go/jwx/blob/v3/examples/jwk_parse_key_example_test.go) ## Parse a key or a set in PEM format Sometimes keys come in ASN.1 DER PEM format. To parse these files, use the [`jwk.WithPEM()`](https://pkg.go.dev/github.com/lestrrat-go/jwx/v3/jwk#WithPEM) option. ```go package examples_test import ( "fmt" "os" "github.com/lestrrat-go/jwx/v3/internal/json" "github.com/lestrrat-go/jwx/v3/jwk" ) func Example_jwk_parse_with_pem() { const src = `-----BEGIN CERTIFICATE----- MIIEljCCAn4CCQCTQBoGDvUbQTANBgkqhkiG9w0BAQsFADANMQswCQYDVQQGEwJK UDAeFw0yMTA0MDEwMDE4MjhaFw0yMjA0MDEwMDE4MjhaMA0xCzAJBgNVBAYTAkpQ MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAvws4H/OxVS3CW1zvUgjs H443df9zCAblLVPPdeRD11Jl1OZmGS7rtQNjQyT5xGpeuk77ZJcfDNLx+mSEtiYQ V37GD5MPz+RX3hP2azuLvxoBseaHE6kC8tkDed8buQLl1hgms15KmKnt7E8B+EK2 1YRj0w6ZzehIllTbbj6gDJ39kZ2VHdLf5+4W0Kyh9cM4aA0si2jQJQsohW2rpt89 b+IagFau+sxP3GFUjSEvyXIamXhS0NLWuAW9UvY/RwhnIo5BzmWZd/y2R305T+QT rHtb/8aGav8mP3uDx6AMDp/0UMKFUO4mpoOusMnrplUPS4Lz6RNpffmrrglOEuRZ /eSFzGL35OeL12aYSyrbFIVsc/aLs6MkoplsuSG6Zhx345h/dA2a8Ub5khr6bksP zGLer+bpBrQQsy21unvCIUz5y7uaYhV3Ql+aIZ+dwpEgZ3xxAvdKKeoCGQlhH/4J 0sSuutUtuTLfrBSgLHJEv2HIzeynChL2CYR8aku/nL68VTdmSt9UY2JGMOf9U8BI fGRpkWBvI8hddMxNm8wF+09WScaZ2JWu7qW/l2jOdgesPIWRg+Hm3NaRSHqAWCOq VUJk9WkCAye0FPALqSvH0ApDKxNtGZb5JZRCW19TqmhgXbAqIf5hsxDaGIXZcW9S CqapZPw7Ccs7BOKSFvmM9p0CAwEAATANBgkqhkiG9w0BAQsFAAOCAgEAVfLzKRdA 0vFpAAp3K+CDth7mag2WWFOXjlWZ+4pxfEBX3k7erJbj6+qYuCvCHXqIZnK1kZzD p4zwsu8t8RfSmPvxcm/jkvecG4DAIGTdhBVtAf/9PU3e4kZFQCqizicQABh+ZFKV dDtkRebUA5EAvP8E/OrvrjYU5xnOxOZU3arVXJfKFjVD619qLuF8XXW5700Gdqwn wBgasTCCg9+tniiscKaET1m9C4PdrlXuAIscV9tGcJ7yEAao1BXokyJ+mK6K2Zv1 z/vvUJA/rGMBJoUjnWrRHON1JMNou2KyRO6z37GpRnfPiNgFpGv2x3ZNeix7H4bP 6+x4KZWQir5047p9hV4YrqMXeULEj3uG2GnOgdR7+hiN39arFVr11DMgABmx19SM VQpTHrC8a605wwCBWnkiYdNojLa5WgeEHdBghKVpWnx9frYgZcz2UP861el5Lg9R j04wkGL4IORYiM7VHSHNU4u/dlgfQE1y0T+1CzXwquy4csvbBzBKnZ1o9ZBsOtWS ox0RaBsMD70mvTwKKmlCSD5HgZZTC0CfGWk4dQp/Mct5Z0x0HJMEJCJzpgTn3CRX z8CjezfckLs7UKJOlhu3OU9TFsiGDzSDBZdDWO1/uciJ/AAWeSmsBt8cKL0MirIr c4wOvhbalcX0FqTM3mXCgMFRbibquhwdxbU= -----END CERTIFICATE-----` key, err := jwk.ParseKey([]byte(src), jwk.WithPEM(true)) if err != nil { fmt.Printf("failed to parse key in PEM format: %s\n", err) return } json.NewEncoder(os.Stdout).Encode(key) // OUTPUT: // {"e":"AQAB","kty":"RSA","n":"vws4H_OxVS3CW1zvUgjsH443df9zCAblLVPPdeRD11Jl1OZmGS7rtQNjQyT5xGpeuk77ZJcfDNLx-mSEtiYQV37GD5MPz-RX3hP2azuLvxoBseaHE6kC8tkDed8buQLl1hgms15KmKnt7E8B-EK21YRj0w6ZzehIllTbbj6gDJ39kZ2VHdLf5-4W0Kyh9cM4aA0si2jQJQsohW2rpt89b-IagFau-sxP3GFUjSEvyXIamXhS0NLWuAW9UvY_RwhnIo5BzmWZd_y2R305T-QTrHtb_8aGav8mP3uDx6AMDp_0UMKFUO4mpoOusMnrplUPS4Lz6RNpffmrrglOEuRZ_eSFzGL35OeL12aYSyrbFIVsc_aLs6MkoplsuSG6Zhx345h_dA2a8Ub5khr6bksPzGLer-bpBrQQsy21unvCIUz5y7uaYhV3Ql-aIZ-dwpEgZ3xxAvdKKeoCGQlhH_4J0sSuutUtuTLfrBSgLHJEv2HIzeynChL2CYR8aku_nL68VTdmSt9UY2JGMOf9U8BIfGRpkWBvI8hddMxNm8wF-09WScaZ2JWu7qW_l2jOdgesPIWRg-Hm3NaRSHqAWCOqVUJk9WkCAye0FPALqSvH0ApDKxNtGZb5JZRCW19TqmhgXbAqIf5hsxDaGIXZcW9SCqapZPw7Ccs7BOKSFvmM9p0"} } ``` source: [examples/jwk_parse_with_pem_example_test.go](https://github.com/lestrrat-go/jwx/blob/v3/examples/jwk_parse_with_pem_example_test.go) ## Parse a key from a file To parse keys stored in a file, [`jwk.ReadFile()`](https://pkg.go.dev/github.com/lestrrat-go/jwx/v3/jwk#ReadFile) can be used. ```go package examples_test import ( "encoding/json" "fmt" "os" "github.com/lestrrat-go/jwx/v3/jwk" ) func Example_jwk_readfile() { const src = `{ "keys": [ {"kty":"EC", "crv":"P-256", "x":"MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4", "y":"4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM", "use":"enc", "kid":"1"}, {"kty":"RSA", "n": "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw", "e":"AQAB", "alg":"RS256", "kid":"2011-04-29"} ] }` f, err := os.CreateTemp(``, `jwk_readfile-*.jwk`) if err != nil { fmt.Printf("failed to create temporary file: %s\n", err) return } defer os.Remove(f.Name()) fmt.Fprint(f, src) f.Close() key, err := jwk.ReadFile(f.Name()) if err != nil { fmt.Printf("failed to parse key: %s\n", err) return } json.NewEncoder(os.Stdout).Encode(key) // OUTPUT: // {"keys":[{"crv":"P-256","kid":"1","kty":"EC","use":"enc","x":"MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4","y":"4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM"},{"alg":"RS256","e":"AQAB","kid":"2011-04-29","kty":"RSA","n":"0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw"}]} } ``` source: [examples/jwk_readfile_example_test.go](https://github.com/lestrrat-go/jwx/blob/v3/examples/jwk_readfile_example_test.go) `jwk.ReadFile()` accepts the same options as [`jwk.Parse()`](https://pkg.go.dev/github.com/lestrrat-go/jwx/v3/jwk#Parse), therefore you can read a PEM-encoded file via the following incantation: ```go package examples_test import ( "fmt" "os" "github.com/lestrrat-go/jwx/v3/internal/json" "github.com/lestrrat-go/jwx/v3/jwk" ) func Example_jwk_readfile_with_pem() { const src = `-----BEGIN CERTIFICATE----- MIIEljCCAn4CCQCTQBoGDvUbQTANBgkqhkiG9w0BAQsFADANMQswCQYDVQQGEwJK UDAeFw0yMTA0MDEwMDE4MjhaFw0yMjA0MDEwMDE4MjhaMA0xCzAJBgNVBAYTAkpQ MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAvws4H/OxVS3CW1zvUgjs H443df9zCAblLVPPdeRD11Jl1OZmGS7rtQNjQyT5xGpeuk77ZJcfDNLx+mSEtiYQ V37GD5MPz+RX3hP2azuLvxoBseaHE6kC8tkDed8buQLl1hgms15KmKnt7E8B+EK2 1YRj0w6ZzehIllTbbj6gDJ39kZ2VHdLf5+4W0Kyh9cM4aA0si2jQJQsohW2rpt89 b+IagFau+sxP3GFUjSEvyXIamXhS0NLWuAW9UvY/RwhnIo5BzmWZd/y2R305T+QT rHtb/8aGav8mP3uDx6AMDp/0UMKFUO4mpoOusMnrplUPS4Lz6RNpffmrrglOEuRZ /eSFzGL35OeL12aYSyrbFIVsc/aLs6MkoplsuSG6Zhx345h/dA2a8Ub5khr6bksP zGLer+bpBrQQsy21unvCIUz5y7uaYhV3Ql+aIZ+dwpEgZ3xxAvdKKeoCGQlhH/4J 0sSuutUtuTLfrBSgLHJEv2HIzeynChL2CYR8aku/nL68VTdmSt9UY2JGMOf9U8BI fGRpkWBvI8hddMxNm8wF+09WScaZ2JWu7qW/l2jOdgesPIWRg+Hm3NaRSHqAWCOq VUJk9WkCAye0FPALqSvH0ApDKxNtGZb5JZRCW19TqmhgXbAqIf5hsxDaGIXZcW9S CqapZPw7Ccs7BOKSFvmM9p0CAwEAATANBgkqhkiG9w0BAQsFAAOCAgEAVfLzKRdA 0vFpAAp3K+CDth7mag2WWFOXjlWZ+4pxfEBX3k7erJbj6+qYuCvCHXqIZnK1kZzD p4zwsu8t8RfSmPvxcm/jkvecG4DAIGTdhBVtAf/9PU3e4kZFQCqizicQABh+ZFKV dDtkRebUA5EAvP8E/OrvrjYU5xnOxOZU3arVXJfKFjVD619qLuF8XXW5700Gdqwn wBgasTCCg9+tniiscKaET1m9C4PdrlXuAIscV9tGcJ7yEAao1BXokyJ+mK6K2Zv1 z/vvUJA/rGMBJoUjnWrRHON1JMNou2KyRO6z37GpRnfPiNgFpGv2x3ZNeix7H4bP 6+x4KZWQir5047p9hV4YrqMXeULEj3uG2GnOgdR7+hiN39arFVr11DMgABmx19SM VQpTHrC8a605wwCBWnkiYdNojLa5WgeEHdBghKVpWnx9frYgZcz2UP861el5Lg9R j04wkGL4IORYiM7VHSHNU4u/dlgfQE1y0T+1CzXwquy4csvbBzBKnZ1o9ZBsOtWS ox0RaBsMD70mvTwKKmlCSD5HgZZTC0CfGWk4dQp/Mct5Z0x0HJMEJCJzpgTn3CRX z8CjezfckLs7UKJOlhu3OU9TFsiGDzSDBZdDWO1/uciJ/AAWeSmsBt8cKL0MirIr c4wOvhbalcX0FqTM3mXCgMFRbibquhwdxbU= -----END CERTIFICATE-----` f, err := os.CreateTemp(``, `jwk_readfile_with_pem-*.jwk`) if err != nil { fmt.Printf("failed to create temporary file: %s\n", err) return } defer os.Remove(f.Name()) fmt.Fprint(f, src) f.Close() key, err := jwk.ReadFile(f.Name(), jwk.WithPEM(true)) if err != nil { fmt.Printf("failed to parse key in PEM format: %s\n", err) return } json.NewEncoder(os.Stdout).Encode(key) // OUTPUT: // {"keys":[{"e":"AQAB","kty":"RSA","n":"vws4H_OxVS3CW1zvUgjsH443df9zCAblLVPPdeRD11Jl1OZmGS7rtQNjQyT5xGpeuk77ZJcfDNLx-mSEtiYQV37GD5MPz-RX3hP2azuLvxoBseaHE6kC8tkDed8buQLl1hgms15KmKnt7E8B-EK21YRj0w6ZzehIllTbbj6gDJ39kZ2VHdLf5-4W0Kyh9cM4aA0si2jQJQsohW2rpt89b-IagFau-sxP3GFUjSEvyXIamXhS0NLWuAW9UvY_RwhnIo5BzmWZd_y2R305T-QTrHtb_8aGav8mP3uDx6AMDp_0UMKFUO4mpoOusMnrplUPS4Lz6RNpffmrrglOEuRZ_eSFzGL35OeL12aYSyrbFIVsc_aLs6MkoplsuSG6Zhx345h_dA2a8Ub5khr6bksPzGLer-bpBrQQsy21unvCIUz5y7uaYhV3Ql-aIZ-dwpEgZ3xxAvdKKeoCGQlhH_4J0sSuutUtuTLfrBSgLHJEv2HIzeynChL2CYR8aku_nL68VTdmSt9UY2JGMOf9U8BIfGRpkWBvI8hddMxNm8wF-09WScaZ2JWu7qW_l2jOdgesPIWRg-Hm3NaRSHqAWCOqVUJk9WkCAye0FPALqSvH0ApDKxNtGZb5JZRCW19TqmhgXbAqIf5hsxDaGIXZcW9SCqapZPw7Ccs7BOKSFvmM9p0"}]} } ``` source: [examples/jwk_readfile_with_pem_example_test.go](https://github.com/lestrrat-go/jwx/blob/v3/examples/jwk_readfile_with_pem_example_test.go) ## Parse a key as a struct field As `jwk.Key` is an interface, it can't directly be used as an argument in `json.Unmarshal`. For example, the following would fail: ```go var key jwk.Key json.Unmarshal(data, &key) // error ``` This poses a problem when you want to use `jwk.Key` as a struct field in another struct that needs to handle `json.Unmarshal`. To overcome this, you can either define a custom `UnmarshalJSON([]byte) error` for your container struct, or you can use a "proxy" struct that will intercept the field holding the `jwk.Key`. ```go package examples_test import ( "encoding/json" "fmt" "os" "github.com/lestrrat-go/jwx/v3/jwk" ) type Container struct { Key jwk.Key `json:"key"` } // This is only one way to parse a struct field whose dynamic // type is unknown at compile time. In this example we use // a proxy/wrapper to trick `Container` from attempting to // parse the `.Key` field, and intercept the value that // would have gone into the `Container` struct into // `Proxy` struct's `.Key` struct field type Proxy struct { Container Key json.RawMessage `json:"key"` } func Example_jwk_struct_field() { const src = `{ "key": { "kty":"EC", "crv":"P-256", "x":"MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4", "y":"4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM", "use":"enc", "kid":"1" } }` var p Proxy if err := json.Unmarshal([]byte(src), &p); err != nil { fmt.Printf("failed to unmarshal from JSON: %s\n", err) return } // Parse the intercepted `Proxy.Key` as a `jwk.Key` // and assign it to `Container.Key` key, err := jwk.ParseKey(p.Key) if err != nil { fmt.Printf("failed to parse key: %s\n", err) return } p.Container.Key = key json.NewEncoder(os.Stdout).Encode(p.Container) // OUTPUT: // {"key":{"crv":"P-256","kid":"1","kty":"EC","use":"enc","x":"MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4","y":"4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM"}} } ``` source: [examples/jwk_struct_field_example_test.go](https://github.com/lestrrat-go/jwx/blob/v3/examples/jwk_struct_field_example_test.go) # Construction ## Using jwk.Import() Users can create a new key from scratch using [`jwk.Import()`](https://pkg.go.dev/github.com/lestrrat-go/jwx/v3/jwk#Import). [`jwk.Import()`](https://pkg.go.dev/github.com/lestrrat-go/jwx/v3/jwk#Import) requires the raw key as its argument. There are other ways to creating keys from a raw key, but they require knowing its type in advance. Use [`jwk.Import()`](https://pkg.go.dev/github.com/lestrrat-go/jwx/v3/jwk#Import) when you have a key type which you do not know its underlying type in advance. It automatically creates the appropriate underlying key based on the given argument type. | Argument Type | Key Type | Note | |---------------|----------|------| | []byte | Symmetric Key | | | ecdsa.PrivateKey | ECDSA Private Key | Argument may also be a pointer | | ecdsa.PubliKey | ECDSA Public Key | Argument may also be a pointer | | rsa.PrivateKey | RSA Private Key | Argument may also be a pointer | | rsa.PubliKey | RSA Public Key | Argument may also be a pointer | | x25519.PrivateKey | OKP Private Key | | | x25519.PubliKey | OKP Public Key | | ```go package examples_test import ( "crypto/ecdsa" "crypto/elliptic" "crypto/rand" "crypto/rsa" "fmt" "github.com/lestrrat-go/jwx/v3/jwk" ) func Example_jwk_import() { // First, THIS IS THE WRONG WAY TO USE jwk.Import(). // // Assume that the file contains a JWK in JSON format // // buf, _ := os.ReadFile(file) // key, _ := jwk.Import(buf) // // This is not right, because the jwk.Import() function determines // the type of `jwk.Key` to create based on the TYPE of the argument. // In this case the type of `buf` is always []byte, and therefore // it will always create a symmetric key. // // What you want to do is to _parse_ `buf`. // // keyset, _ := jwk.Parse(buf) // key, _ := jwk.ParseKey(buf) // // See other examples in examples/jwk_parse_key_example_test.go and // examples/jwk_parse_jwks_example_test.go // []byte -> jwk.SymmetricKey { raw := []byte("Lorem Ipsum") key, err := jwk.Import(raw) if err != nil { fmt.Printf("failed to create symmetric key: %s\n", err) return } if _, ok := key.(jwk.SymmetricKey); !ok { fmt.Printf("expected jwk.SymmetricKey, got %T\n", key) return } } // *rsa.PrivateKey -> jwk.RSAPrivateKey // *rsa.PublicKey -> jwk.RSAPublicKey { raw, err := rsa.GenerateKey(rand.Reader, 2048) if err != nil { fmt.Printf("failed to generate new RSA private key: %s\n", err) return } key, err := jwk.Import(raw) if err != nil { fmt.Printf("failed to create symmetric key: %s\n", err) return } if _, ok := key.(jwk.RSAPrivateKey); !ok { fmt.Printf("expected jwk.SymmetricKey, got %T\n", key) return } // PublicKey is omitted for brevity } // *ecdsa.PrivateKey -> jwk.ECDSAPrivateKey // *ecdsa.PublicKey -> jwk.ECDSAPublicKey { raw, err := ecdsa.GenerateKey(elliptic.P384(), rand.Reader) if err != nil { fmt.Printf("failed to generate new ECDSA private key: %s\n", err) return } key, err := jwk.Import(raw) if err != nil { fmt.Printf("failed to create symmetric key: %s\n", err) return } if _, ok := key.(jwk.ECDSAPrivateKey); !ok { fmt.Printf("expected jwk.SymmetricKey, got %T\n", key) return } // PublicKey is omitted for brevity } // OUTPUT: } ``` source: [examples/jwk_import_example_test.go](https://github.com/lestrrat-go/jwx/blob/v3/examples/jwk_import_example_test.go) # Fetching JWK Sets ## Parse a key from a remote resource To parse keys stored in a remote location pointed by a HTTP(s) URL, use [`jwk.Fetch()`](https://pkg.go.dev/github.com/lestrrat-go/jwx/v3/jwk#Fetch) If you are going to be using this key repeatedly in a long running process, consider using [`jwk.Cache`](https://pkg.go.dev/github.com/lestrrat-go/jwx/v3/jwk#Cache) or [`jwk.CachedSet`](https://pkg.go.dev/github.com/lestrrat-go/jwx/v3/jwk#CachedSet) described elsewhere in this document. ```go package examples_test import ( "context" "encoding/json" "fmt" "net/http" "net/http/httptest" "os" "github.com/lestrrat-go/jwx/v3/jwk" ) func Example_jwk_fetch() { srv := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) fmt.Fprintf(w, `{ "keys": [ {"kty":"EC", "crv":"P-256", "x":"MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4", "y":"4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM", "use":"enc", "kid":"1"}, {"kty":"RSA", "n": "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw", "e":"AQAB", "alg":"RS256", "kid":"2011-04-29"} ] }`) })) defer srv.Close() set, err := jwk.Fetch( context.Background(), srv.URL, // This is necessary because httptest.Server is using a custom certificate jwk.WithHTTPClient(srv.Client()), ) if err != nil { fmt.Printf("failed to fetch JWKS: %s\n", err) return } json.NewEncoder(os.Stdout).Encode(set) // OUTPUT: // {"keys":[{"crv":"P-256","kid":"1","kty":"EC","use":"enc","x":"MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4","y":"4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM"},{"alg":"RS256","e":"AQAB","kid":"2011-04-29","kty":"RSA","n":"0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw"}]} } ``` source: [examples/jwk_fetch_example_test.go](https://github.com/lestrrat-go/jwx/blob/v3/examples/jwk_fetch_example_test.go) ## Auto-refreshing remote keys Sometimes you need to fetch a remote JWK, and use it multiple times in a long-running process. For example, you may act as an intermediary to some other service, and you may need to verify incoming JWT tokens against the tokens in said other service. Normally, you should be able to simply fetch the JWK using [`jwk.Fetch()`](https://pkg.go.dev/github.com/lestrrat-go/jwx/v3/jwk#Fetch), but keys are usually routinely expired and rotated due to security reasons. In such cases you would need to refetch the JWK periodically, which is a pain. `github.com/lestrrat-go/jwx/v3/jwk` provides the [`jwk.Cache`](https://pkg.go.dev/github.com/lestrrat-go/jwx/v3/jwk#Cache) and [`jwk.CachedSet`](https://pkg.go.dev/github.com/lestrrat-go/jwx/v3/jwk#CachedSet) to do this for you. ```go package examples_test import ( "context" "fmt" "time" "github.com/lestrrat-go/httprc/v3" "github.com/lestrrat-go/jwx/v3/jwk" ) func Example_jwk_cache() { ctx, cancel := context.WithCancel(context.Background()) defer cancel() const googleCerts = `https://www.googleapis.com/oauth2/v3/certs` // First, set up the `jwk.Cache` object. You need to pass it a // `context.Context` object to control the lifecycle of the background fetching goroutine. c, err := jwk.NewCache(ctx, httprc.NewClient()) if err != nil { fmt.Printf("failed to create cache: %s\n", err) return } // Tell *jwk.Cache that we only want to refresh this JWKS periodically. if err := c.Register(ctx, googleCerts); err != nil { fmt.Printf("failed to register google JWKS: %s\n", err) return } // Pretend that this is your program's main loop MAIN: for { select { case <-ctx.Done(): break MAIN default: } keyset, err := c.Lookup(ctx, googleCerts) if err != nil { fmt.Printf("failed to fetch google JWKS: %s\n", err) return } _ = keyset // The returned `keyset` will always be "reasonably" new. // // By "reasonably" we mean that we cannot guarantee that the keys will be refreshed // immediately after it has been rotated in the remote source. But it should be close\ // enough, and should you need to forcefully refresh the token using the `(jwk.Cache).Refresh()` method. // // If refetching the keyset fails, a cached version will be returned from the previous // successful sync // Do interesting stuff with the keyset... but here, we just // sleep for a bit time.Sleep(time.Second) // Because we're a dummy program, we just cancel the loop now. // If this were a real program, you presumably loop forever cancel() } // OUTPUT: } ``` source: [examples/jwk_cache_example_test.go](https://github.com/lestrrat-go/jwx/blob/v3/examples/jwk_cache_example_test.go) ```go package examples_test import ( "context" "fmt" "log/slog" "os" "time" "github.com/lestrrat-go/httprc/v3" "github.com/lestrrat-go/httprc/v3/tracesink" "github.com/lestrrat-go/jwx/v3/jwk" "github.com/lestrrat-go/jwx/v3/jws" ) func Example_jwk_cached_set() { ctx, cancel := context.WithCancel(context.Background()) defer cancel() const googleCerts = `https://www.googleapis.com/oauth2/v3/certs` // The first steps are the same as examples/jwk_cache_example_test.go c, err := jwk.NewCache( ctx, httprc.NewClient( httprc.WithTraceSink(tracesink.NewSlog(slog.New(slog.NewJSONHandler(os.Stderr, nil)))), ), ) if err != nil { fmt.Printf("failed to create cache: %s\n", err) return } // Register the URL to fetch the JWKS from. In this case, we're saying that // the cache can dynamically decide how often to refresh the keyset based on // the HTTP headers returned by the server, but the value must be at least // 1 hour, and at most 7 days. if err := c.Register( ctx, googleCerts, jwk.WithMaxInterval(24*time.Hour*7), jwk.WithMinInterval(15*time.Minute), ); err != nil { fmt.Printf("failed to register google JWKS: %s\n", err) return } cached, err := c.CachedSet(googleCerts) if err != nil { fmt.Printf("failed to get cached keyset: %s\n", err) return } // cached fulfills the jwk.Set interface. var _ jwk.Set = cached // That means you can pass it to things like jws.WithKeySet, // allowing you to pretend as if you are using the result of // // jwk.Fetch(ctx, googleCerts) // // But you are instead using a cached (and periodically refreshed) set // for each operation. _ = jws.WithKeySet(cached) // OUTPUT: } ``` source: [examples/jwk_cached_set_example_test.go](https://github.com/lestrrat-go/jwx/blob/v3/examples/jwk_cached_set_example_test.go) ## Using Whitelists If you are fetching JWK Sets from a possibly untrusted source such as the URL in the `jku` field of a JWS message, you may have to perform some sort of whitelist checking. You can provide a `jwk.Whitelist` object to either `jwk.Fetch()` or `(*jwk.Cache).Register()` methods to specify the use of a whitelist. Currently the package provides `jwk.MapWhitelist` and `jwk.RegexpWhitelist` types for simpler cases, as well as `jwk.InsecureWhitelist` for when you explicitly want to allow all URLs. If you would like to implement something more complex, you can provide a function via `jwk.WhitelistFunc` or implement your own type of `jwk.Whitelist`. ```go package examples_test import ( "context" "encoding/json" "fmt" "net/http" "net/http/httptest" "os" "regexp" "github.com/lestrrat-go/jwx/v3/jwk" ) func Example_jwk_whitelist() { srv := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) fmt.Fprintf(w, `{ "keys": [ {"kty":"EC", "crv":"P-256", "x":"MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4", "y":"4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM", "use":"enc", "kid":"1"}, {"kty":"RSA", "n": "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw", "e":"AQAB", "alg":"RS256", "kid":"2011-04-29"} ] }`) })) defer srv.Close() testcases := []struct { Whitelist jwk.Whitelist Error bool }{ // The first two whitelists are meant to prevent access to any other // URLs other than www.google.com { Whitelist: jwk.NewMapWhitelist().Add(`https://www.googleapis.com/oauth2/v3/certs`), Error: true, }, { Whitelist: jwk.NewRegexpWhitelist().Add(regexp.MustCompile(`^https://www\.googleapis\.com/`)), Error: true, }, // This whitelist allows anything { Whitelist: jwk.InsecureWhitelist{}, }, } for _, tc := range testcases { set, err := jwk.Fetch( context.Background(), srv.URL, // This is necessary because httptest.Server is using a custom certificate jwk.WithHTTPClient(srv.Client()), // Pass the whitelist! jwk.WithFetchWhitelist(tc.Whitelist), ) if tc.Error { if err == nil { fmt.Printf("expected fetch to fail, but got no error\n") return } } else { if err != nil { fmt.Printf("failed to fetch JWKS: %s\n", err) return } json.NewEncoder(os.Stdout).Encode(set) } } // OUTPUT: // {"keys":[{"crv":"P-256","kid":"1","kty":"EC","use":"enc","x":"MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4","y":"4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM"},{"alg":"RS256","e":"AQAB","kid":"2011-04-29","kty":"RSA","n":"0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw"}]} } ``` source: [examples/jwk_whitelist_example_test.go](https://github.com/lestrrat-go/jwx/blob/v3/examples/jwk_whitelist_example_test.go) # Working with jwk.Key ## [Working with key-specific methods] While you would almost always be able to get away with working with just the `jwk.Key` interface, there might be times when you want to get to methods that are specific to a particular key type, such as an RSA key. In these cases it is possible to convert their types and get a more specific interface, such as `jwk.RSAPrivateKey` ```go package examples_test import ( "crypto/rand" "crypto/rsa" "fmt" "github.com/lestrrat-go/jwx/v3/jwk" ) func Example_jwk_key_specific_metehods() { raw, err := rsa.GenerateKey(rand.Reader, 2048) if err != nil { fmt.Printf("failed to generate RSA private key: %s\n", err) return } key, err := jwk.Import(raw) if err != nil { fmt.Printf("failed to create jwk.Key from RSA private key: %s\n", err) return } rsakey, ok := key.(jwk.RSAPrivateKey) if !ok { fmt.Printf("failed to convert jwk.Key into jwk.RSAPrivateKey (was %T)\n", key) return } // We won't print these values, because each time they are // generated the contents will be different, and thus our // tests would fail. But here you can see that once you // convert the type you can access the RSA-specific methods _, _ = rsakey.D() _, _ = rsakey.DP() _, _ = rsakey.DQ() _, _ = rsakey.E() _, _ = rsakey.N() _, _ = rsakey.P() _, _ = rsakey.Q() _, _ = rsakey.QI() // OUTPUT: // } ``` source: [examples/jwk_key_specific_methods_example_test.go](https://github.com/lestrrat-go/jwx/blob/v3/examples/jwk_key_specific_methods_example_test.go) ## Setting values to fields Using [`jwk.Import()`](https://pkg.go.dev/github.com/lestrrat-go/jwx/v3/jwk#Import) allows you to create a key whose fields have been properly populated, but sometimes there are other fields that you may want to populate in a key, such as`kid`, or other custom fields. These fields can all be set using the [`jwk.Set()`](https://pkg.go.dev/github.com/lestrrat-go/jwx/v3/jwk#Set) method. The [`jwk.Set()`](https://pkg.go.dev/github.com/lestrrat-go/jwx/v3/jwk#Set) method takes the name of the key and a value to be associated with it. Some predefined keys have specific types (in which type checks are enforced), and others don't. [`jwk.Set()`](https://pkg.go.dev/github.com/lestrrat-go/jwx/v3/jwk#Set) may not alter the Key Type (`kty`) field of a key. The `jwk` package defines field key names for predefined keys as constants, so you won't ever have to bang your head against the wall after finding out that you have a typo. ```go key.Set(jwk.KeyIDKey, `my-awesome-key`) key.Set(`my-custom-field`, `unbelievable-value`) ``` ## Converting a jwk.Key to a raw key As discussed in [Terminology](#terminology), this package calls the "original" keys (e.g. `rsa.PublicKey`, `ecdsa.PrivateKey`, etc.) "raw" keys. To obtain a raw key from a [`jwk.Key`](https://pkg.go.dev/github.com/lestrrat-go/jwx/v3/jwk#Key) object, use the [`Raw()`](https://github.com/github.com/lestrrat-go/jwx/v3/jwk#Raw) method. ```go key, _ := jwk.ParseKey(src) var raw interface{} if err := key.Raw(&raw); err != nil { ... } ``` In the above example, `raw` contains whatever the [`jwk.Key`](https://pkg.go.dev/github.com/lestrrat-go/jwx/v3/jwk#Key) represents. If `key` represents an RSA key, it will contain either a `rsa.PublicKey` or `rsa.PrivateKey`. If it represents an ECDSA key, an `ecdsa.PublicKey`, or `ecdsa.PrivateKey`, etc. If the only operation that you are performing is to grab the raw key out of a JSON JWK, use [`jwk.ParseRawKey`](https://pkg.go.dev/github.com/lestrrat-go/jwx/v3/jwk#ParseRawKey) ```go var raw interface{} if err := jwk.ParseRawKey(src, &raw); err != nil { ... } ``` ## Filtering Keys with KeyFilter The [`jwk.KeyFilter`](https://pkg.go.dev/github.com/lestrrat-go/jwx/v3/jwk#KeyFilter) interface provides a mechanism to selectively include or exclude specific fields when working with JWK keys. This is useful when you need to serialize keys with only certain fields, or when you want to create clean representations of keys for specific purposes. KeyFilter objects provide two methods: - `Filter()`: Returns a new key containing only the fields that should be included - `Reject()`: Returns a new key with specified fields excluded ### Standard Field Filters For convenience, the library provides pre-defined filters that include standard fields for each key type: - [`jwk.RSAStandardFieldsFilter()`](https://pkg.go.dev/github.com/lestrrat-go/jwx/v3/jwk#RSAStandardFieldsFilter) - for RSA keys - [`jwk.ECDSAStandardFieldsFilter()`](https://pkg.go.dev/github.com/lestrrat-go/jwx/v3/jwk#ECDSAStandardFieldsFilter) - for ECDSA keys - [`jwk.OKPStandardFieldsFilter()`](https://pkg.go.dev/github.com/lestrrat-go/jwx/v3/jwk#OKPStandardFieldsFilter) - for OKP keys - [`jwk.SymmetricStandardFieldsFilter()`](https://pkg.go.dev/github.com/lestrrat-go/jwx/v3/jwk#SymmetricStandardFieldsFilter) - for symmetric keys These functions return filters configured to include the standard fields defined in the JWK specification for each key type. golang-github-lestrrat-go-jwx-3.0.13/docs/20-global-settings.md000066400000000000000000000076321515060566400242030ustar00rootroot00000000000000# Global Settings ## Enabling Optional Signature Methods Some algorithms are intentionally left out because they are not as common in the wild, and you may want to avoid compiling this extra information in. To enable these, you must explicitly provide a build tag. | Algorithm | Build Tag | |:-----------------|:-----------| | secp256k1/ES256K | jwx_es256k | If you do not provide these tags, the program will still compile, but it will return an error during runtime saying that these algorithms are not supported. ## Switching to a faster JSON library By default, we use the standard library's `encoding/json` for all of our JSON needs. However, if performance for parsing/serializing JSON is really important to you, you might want to enable [github.com/goccy/go-json](https://github.com/goccy/go-json) by enabling the `jwx_goccy` tag. ```shell % go build -tags jwx_goccy ... ``` [github.com/goccy/go-json](https://github.com/goccy/go-json) is *disabled* by default because it uses some really advanced black magic, and I really do not feel like debugging it **IF** it breaks. Please note that that's a big "if". As of github.com/goccy/go-json@v0.3.3 I haven't seen any problems, and I would say that it is mostly stable. However, it is a dependency that you can go without, and I won't be of much help if it breaks -- therefore it is not the default. If you know what you are doing, I highly recommend enabling this module -- all you need to do is to enable this tag. 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 want to enable `jwx_goccy` build tag as well. ## Using json.Number If you want to parse numbers in the incoming JSON objects as json.Number instead of floats, you can use the following call to globally affect the behavior of JSON parsing. ```go func init() { jwx.DecoderSettings(jwx.WithUseNumber(true)) } ``` Do be aware that this has *global* effect. All code that calls in to `encoding/json` within `jwx` *will* use your settings. ## Decode private fields to objects Packages within `github.com/lestrrat-go/jwx/v3` parses known fields into pre-defined types, but for everything else (usually called private fields/headers/claims) are decoded into whatever `"encoding/json".Unmarshal` deems appropriate. For example, JSON objects are converted to `map[string]interface{}`, JSON arrays into `[]interface{}`, and so on. Sometimes you know beforehand that it makes sense for certain fields to be decoded into proper objects instead of generic maps or arrays. When you encounter this, you can use the `RegisterCustomField()` method in each of `jwe`, `jwk`, `jws`, and `jwt` packages. ```go func init() { jwt.RegisterCustomField(`x-foo-bar`, mypkg.FooBar{}) } ``` This tells the decoder that when it encounters a JWT token with the field named `"x-foo-bar"`, it should be decoded to an instance of `mypkg.FooBar`. Then you can access this value by using `Get()` ```go var v mypkg.FooBar _ = token.Get(`x-foo-bar`, &v) ``` Do be aware that this has *global* effect. In the above example, all JWT tokens containing the `"x-foo-bar"` key will decode in the same way. If you need this behavior from `jwe`, `jwk`, or `jws` packages, you need to do the same thing for each package. golang-github-lestrrat-go-jwx-3.0.13/docs/21-frameworks.md000066400000000000000000000056321515060566400232640ustar00rootroot00000000000000## JWT with net/http Integrating this library with net/http is simple. In this example, we will assume that you are using a `Server` object that is defined as follows: ```go type Server struct { alg jwa.SignatureAlgorithm signKey jwk.Key verifyKey jwk.Key } ``` The first step is to decide on the signature algorithm. Here we will show examples for using `jwa.HS256` and `jwa.RS256`. Choose the appropriate signature for your particular use case. You can find the full list of supported signature algorithms in the documentation or the source code for the [`jwa`](../jwa) package (remember that there are some [optional algorithms](./20-global-settings.md#enabling-optional-signature-methods)). ### Using HS256 `jwa.HS256` is a symmetric algorithm, therefore the signing key should be exactly the same as the verifying key. ```go s.alg = jwa.HS256 s.signKey = jwk.New([]byte("Hello, World!")) s.verifyKey = s.signKey ``` ### Using RS256 In this example we assume that your keys are stored in PEM-encoded files `private-key.pem` and `public-key.pem. ```go s.alg = jwa.RS256 { v, err := jwk.ReadFile(`private-key.pem`, jwk.WithPEM(true)) if err != nil { // handle error } s.signKey = v } { v, err := jwk.ReadFile(`public-key.pem`, jwk.WithPEM(true)) if err != nil { // handle error } s.verifyKey = v } ``` ### Reading JWT JWTs can be stored in HTTP headers, form values, etc, and you need to decide where to fetch the JWT payload from. The `jwt` package provides several ways to retrieve JWT data from an HTTP request. `jwt.ParseRequest` is the most generic front end, and the user will be able to dynamically change where to fetch the data from. By default, the "Authorization" header is checked. If you want to check for more places, you can specify additional options. Please read the manual for `jwt.ParseRequest` for more details. The option `jwt.WithKey` is added to validate the JWS message. You will need to execute `jwt.Validate` to validate the content of the JWT message. You can control what gets validated by passing options to `jwt.Validate`. Please read the manual for `jwt.Validate` for more details. ```go func (s *Server) HandleFoo(w http.ResponseWriter, req *http.Request) { token, err := jwt.ParseRequest(req, jwt.WithKey(s.alg, s.verifyKey)) if err != nil { // handle error } if err := jwt.Validate(token); err != nil { // handle error } // ... additional code ... } ``` ### Writing JWT In this example we are writing the token to the response body of the response. ```go func (s *Server) HandleBar(w http.ResponseWriter, req *http.Request) { var token jwt.Token signed, err := jwt.Sign(token, jwt.WithKey(s.alg, s.signKey)) if err != nil { // handle errors } w.WriteHeader(http.StatusOK) w.Write(signed) } ``` ## JWT with Echo There is no official middleware, but [a simple port can be found here](https://github.com/lestrrat-go/echo-middleware-jwx) golang-github-lestrrat-go-jwx-3.0.13/docs/99-faq.md000066400000000000000000000333451515060566400216740ustar00rootroot00000000000000# Frequently asked questions ## I want to use this with a Web Framework ### Echo Consider using [github.com/lestrrat-go/echo-middleware-jwx](github.com/lestrrat-go/echo-middleware-jwx), although as of this writing it has not been widely tested. ## I get a "no Go files in ..." error You are using Go in GOPATH mode. Short answer: use Go modules. [A slightly more elaborate version of the answer can be found in github.com/lestrrat-go/backoff FAQ](https://github.com/lestrrat-go/backoff#im-getting-package-githubcomlestrrat-gobackoffv2-no-go-files-in-gosrcgithubcomlestrrat-gobackoffv2) And no, I do not intend to support GOPATH mode as of 2021. There are ways to manually workaround it, but do not expect this library to do that for you. ## Why don't you automatically infer the algorithm for `jws.Verify` ? Please read https://auth0.com/blog/critical-vulnerabilities-in-json-web-token-libraries/. Despite this article's publish date, the original had been published sometime around 2015. It's a well known problem with JWS libraries. ## Why did you change the API? Presumably you are asking this because your code broke when we bumped the version and broke backwards compatibility. Then the short answer is: "You wouldn't have had to worry about it if you were properly using `go.mod`" The longer answer is as follows: From time to time, we introduce API changes, because we learn of mistakes in our old ways. Maybe we used the wrong terminology. Maybe we made public something that should have been internal. Maybe we intended an API to be used one way, but it was confusing. So then we introduce API changes. Sorry if breaks your builds, but it's done because we deem it necessary. You should also know that we do not introduce API changes between micro versions. And on top of that, Go provides extremely good support for idempotent builds via Go modules. If you are in an environment where API changes disrupts your environment, you should definitely migrate to using Go modules now. ## "Why can't I create my jwk.Key?" ### 1. You are passing the wrong parameter to `jwk.New()`. As stated in the documentation, `jwk.New()` creates different types of keys depending on the type of the input. Use `jwk.New()` to construct a JWK from the [*raw* key](./04-jwk.md#raw-key). Use `jwk.Parse()` or `jwk.ParseKey()` to parse a piece of data (`[]byte` and the like) and create the appropriate key type from its contents. See ["Using jwk.New()"](./04-jwk.md#using-jwknew) for more details. ### 2. You are not decoding PEM. When you read from a PEM encoded file (e.g. `key.pem`), you cannot just parse it using `jwk.Parse()` as by default we do not expect the data to be PEM encoded. Use `jwk.WithPEM(true)` for this. See ["Parse a key or set in PEM format"](./04-jwk.md#parse-a-key-or-a-set-in-pem-format) for more details. ## "Why is my code to call `jwt.Sign()`/`jws.Verify()` failing?" ### 1. Your algorithm and key type do not match. Any given signature algorithm requires a particular type of key. If the pair is not setup correctly, the operation will fail. Below is a table of general algorithm to key type pair. Note that this table may not be updated regularly. Use common sense and search online to find out if the algorithm/key type you would like to use is not listed in the table. | Algorithm Family | Key Type | |------------------|-----------| | jwa.HS\*\*\* | Symmetric | | jwa.RS\*\*\* | RSA | | jwa.ES\*\*\* | Elliptic | ### 2. You are mixing up when to use private/public keys. You sign using a private key. You verify using a public key (although, it is possible to verify using the private key, but is not really a common operation). So, for example, a service like Google will sign their JWTs using their private keys which are not publicly available, but will provide the public keys somewhere so that you can verify their JWTs using those public keys. ### 3. You are parsing the wrong token. Often times we have people asking us about github.com/lestrrat-go/jwx/v3/jwt not being able to parse a token... except, they are not JWTs. For example, when a provider says they will give you an "access token" ... well, it *may* be a JWT, but often times they are just some sort of string key (which will definitely parse if you pass it to `jwt.Parse`). Sometimes what you really want is stored in a different token, and it may be called an "ID token". Who knows, these things vary between implementation to implementation. After all, the only thing we can say is that you should check that you are parsing. ## Why are you generating so many fields? Because a lot of the code is repetitive. For example, maintaining the 15 fields in a JWE header in all parts of the code (getter methods, setter methods, marshaling/unmarshaling) is doable but very very very cumbersome. We think that resources used for watching out for typos and other minor problems that may arise during maintenance is better spent elsewhere by automating generation of consistent code. ## Why is (jwk.Key).Algorithm() and jwa.KeyAlgorithm so confusing? To start, we sympathize. Please read on for the reason(s) why things are the way they are. First you must understand that JWKs can be used for multiple different purposes, including but not limited to JWS and JWE (key encryption). And the `alg` field is supposed to carry to what purpose the JWK is supposed to be used. This means that a JWK can, in jwx terms, carry either `jwa.SignatureAlgorithm` or `jwa.KeyEncryptionAlgorithm`. In order to allow passing either `jwa.SignatureAlgorithm` or `jwa.KeyEncrypionAlgorithm`, we initially implemented `(jwk.Key).Algorithm()` as a string, so it was possible to just change the type depending on the situation. This caused a bit of confusion for some users because this field was the only "untyped" field that potentially could have been typed. Most notably, some people wanted to do the following, but couldn't: ```go jwt.Verify(token, jwt.WithKey(key.Algorithm(), key)) ``` Since version 2.0.0 `jwk.Key` now stores the `alg` field as a `jwa.KeyAlgorithm` type, which is just an interface that covers `jwa.SignatureAlgorithm`, `jwa.KeyEncryptionAlgorithm`, or any other type that we may need to represent in the future. Now you should be able to just pass the `alg` value to most high-level functions and methods such as `jwt.Verify`, `jws.Sign`, and `jwe.Encrypt` ### When do we use `jwa.KeyAlgorithm` There are some functions that accept `jwa.KeyAlgorithm`, while there are others that expect `jwa.SignatureAlgorithm` or `jwa.KeyEncryptionAlgorithm`. So when do we use which? The guideline is as follows: If it's a high-level function/method that the users regularly use, use `jwa.KeyAlgorithm`. For example, almost everybody who use `jwt` will want to verify the JWS signed payload, so `jwt.Sign()`, and `jwt.Verify()` expect `jwa.KeyAlgorithm`. On the other hand, `jwt.Serializer` uses `jwa.SignatureAlgorithm` and such. This is a low-level utility, and users are not really meant to use it for their most basic needs: therefore they use the specific algorithm type. <<<<<<< HEAD ## Why are your options objects, and not callbacks? We get this one a lot. The short answer is that 1) the options API is not designed to be extended by users, and 2) callbacks introduce tight coupling between the options and the consumers. (1) should be obvious. We just never intended it to be extensible for end-users. That's a design choice, and at the point of writing this, we have no intention to change this. (2) is subtler: consider a case where you are setting a few instance variables on an object: ```go func MyOption(obj *Object) { obj.FieldA = ... obj.FieldB = ... } ``` From this design you can see that we need to make a few assumptions. First, the callback must have a specific signature. This is not a deal-breaker, but you have to be conscious of the fact that you are tying your option to this object type specifically, and you will not be able to change this. Second, you are setting values to an object. The library can either provide exported fields, or it can provide setter methods, but either way, it will need to expose those knobs to the end user. This means that internal details of the object _will_ have to be visible, even if this option is the only logical place that detail is to be used. You could maybe use a state (or a config) variable to avoid assigning to the object itself, and localize the effect of the option for the method: ```go func (obj *Object) Method(options ...Options) error { var cfg MethodConfig for _, option := range options { option(obj, cfg) } ... } func MyOption(obj *Object, cfg *MethodConfig) { cfg.FieldA = ... cfg.FieldB = ... } ``` But this means that you will have to have a state/config object for _each_ method call that takes options, and we have a lot of methods. And finally, as you have seen above, with a callback based option object you will have to change its signature for each use case. It's just a lot of hassle to remember which option uses which signature. Based on the reasons above, we decided to **decouple the option data** and the **option handling logic**. Our option objects are simply data containers. They have an identity (`Ident()`), and they have a value (`Value()`). The option objects themselves do not know how they are going to be used. The consumers (the methods) are the ones who know how to deal with the data the options carry. Yes, we understand that this way we introduce more boilerplate code in each method's starting section. But our design choice is that this way fulfills our goals better, namely that **the overall structure is simpler**, **we do not expose unnecessary internal data to the end-user**, and because the options are all data, it's far **easier to re-use the same options for multiple methods** (as we do in cases such as `jws.WithKey()`, for example). ======= <<<<<<< HEAD >>>>>>> 666d74d (Add FAQ about option format (#1102)) ## Design ### Why does this library not provide a method similar to `Range()`? A method such as `(*sync.Map).Range` requires that the callback function does not block the execution of the `Range` method itself. However, we would like to avoid modifications to the objects while our `Range`, if provided, is being executed, leading us to use at least a read lock a la `(*sync.RWMutex).RLock`. That causes problems in code like the following: ``` obj.Range(func(k string, v interface{}) bool) { // This needs a read lock obj.Remove(k) // This needs a write lock } ``` `sync.Map` actually solves this problem, but it involves a fairly complicated workaround. While it is theoretically possible to implement the same logic in this package, we have decided that it is not worth the effort for our purposes. If you would like to iterate through the keys, use `Keys()` to retireve the list of keys. This also requires a read lock, but by necessity you can only proceed to execute the next piece of code only after the read lock has been released. <<<<<<< HEAD ======= ======= ## Why are your options objects, and not callbacks? We get this one a lot. The short answer is that 1) the options API is not designed to be extended by users, and 2) callbacks introduce tight coupling between the options and the consumers. (1) should be obvious. We just never intended it to be extensible for end-users. That's a design choice, and at the point of writing this, we have no intention to change this. (2) is subtler: consider a case where you are setting a few instance variables on an object: ```go func MyOption(obj *Object) { obj.FieldA = ... obj.FieldB = ... } ``` From this design you can see that we need to make a few assumptions. First, the callback must have a specific signature. This is not a deal breaker, but you have to be conscious of the fact that you are tying your option to this object type specifically, and you will not be able to change this. Second, you are setting values to an object. The library can either provide exported fields, or it can provide setter methods, but either way, it will need to expose those knobs to the end user. This means that internal details of the object _will_ have to be visible, even if this option is the only logical place that detail is to be used. You could maybe use a state (or a config) variable to avoid assigning to the object itself, and localize the effect of the option for the method: ```go func (obj *Object) Method(options ...Options) error { var cfg MethodConfig for _, option := range options { option(obj, cfg) } ... } func MyOption(obj *Object, cfg *MethodConfig) { cfg.FieldA = ... cfg.FieldB = ... } ``` But this means that you will have to have a state/config object for _each_ method call that takes options, and we have a lot of methods. And finally, as you have seen above, with a callback based option object you will have to change its signature for each usecase. It's just a lot of hassle to remember which option uses which signature. Based on the reasons above, we decided to **decouple the option data** and the **option handling logic**. Our option objects are simply data containers. They have an identity (`Ident()`), and they have a value (`Value()`). The option objects themselves do not know how they are going to be used. The consumers (the methods) are the ones who know how to deal with the data the options carry. Yes, we understand that this way we introduce more boilerplate code in each method's starting section. But our design choice is that this way fulfills our goals better, namely that **the overall structure is simpler**, **we do not expose unnecessary internal data to the end-user**, and because the options are all data, it's far **easier to re-use the same options for multiple methods** (as we do in cases such as `jws.WithKey()`, for example). >>>>>>> 548968cd (Add FAQ about option format (#1102)) >>>>>>> 666d74d (Add FAQ about option format (#1102)) golang-github-lestrrat-go-jwx-3.0.13/docs/README.md000066400000000000000000000011641515060566400216150ustar00rootroot00000000000000# How to JWx That Here you will find bits and pieces of code explaining how to perform certain Javascript Object Signing and Encryption (JOSE) operations using [`github.com/lestrrat-go/jwx/v3`](https://github.com/lestrrat-go/jwx/tree/v3). If you would rather see code, try the [examples directory](../examples) * [Anatomy of JOSE](./00-anatomy.md) * [Working with JWT](./01-jwt.md) * [Working with JWS](./02-jws.md) * [Working with JWE](./03-jwe.md) * [Working with JWK](./04-jwk.md) * [Global Settings](./20-global-settings.md) * [Integrating With Frameworks](./21-frameworks.md) * [Frequently Asked Questions](./99-faq.md) golang-github-lestrrat-go-jwx-3.0.13/examples/000077500000000000000000000000001515060566400212225ustar00rootroot00000000000000golang-github-lestrrat-go-jwx-3.0.13/examples/README.md000066400000000000000000000005071515060566400225030ustar00rootroot00000000000000# Examples (and Benchmarks) * [github.com/lestrrat-go/jwx/v3](./jwx_example_test.go): Library-wide operations * [github.com/lestrrat-go/jwe](./jwe_example_test.go) * [github.com/lestrrat-go/jwk](./jwk_example_test.go) * [github.com/lestrrat-go/jws](./jws_example_test.go) * [github.com/lestrrat-go/jwt](./jwt_example_test.go) golang-github-lestrrat-go-jwx-3.0.13/examples/go.mod000066400000000000000000000021671515060566400223360ustar00rootroot00000000000000module github.com/lestrrat-go/jwx/v3/examples go 1.24.0 toolchain go1.24.4 require ( github.com/cloudflare/circl v1.6.1 github.com/emmansun/gmsm v0.21.5 github.com/lestrrat-go/httprc/v3 v3.0.2 github.com/lestrrat-go/jwx/v3 v3.0.0 ) require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect github.com/goccy/go-json v0.10.3 // indirect github.com/lestrrat-go/blackmagic v1.0.4 // indirect github.com/lestrrat-go/dsig v1.0.0 // indirect github.com/lestrrat-go/dsig-secp256k1 v1.0.0 // indirect github.com/lestrrat-go/httpcc v1.0.1 // indirect github.com/lestrrat-go/option/v2 v2.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/segmentio/asm v1.2.1 // indirect github.com/stretchr/testify v1.11.1 // indirect github.com/valyala/fastjson v1.6.7 // indirect golang.org/x/crypto v0.46.0 // indirect golang.org/x/sys v0.39.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) replace github.com/cloudflare/circl v1.0.0 => github.com/cloudflare/circl v1.0.1-0.20210104183656-96a0695de3c3 replace github.com/lestrrat-go/jwx/v3 v3.0.0 => ../ golang-github-lestrrat-go-jwx-3.0.13/examples/go.sum000066400000000000000000000161541515060566400223640ustar00rootroot00000000000000github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0= github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= 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/dcrec/secp256k1/v4 v4.4.0 h1:NMZiJj8QnKe1LgsbDayM4UoHwbvwDRwnI3hwNaAHRnc= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40= github.com/emmansun/gmsm v0.21.5 h1:G4HwuiqNQGZmAlZi233iwDPcfWKcoax0/GzS3eR+l7o= github.com/emmansun/gmsm v0.21.5/go.mod h1:5hRB+YZ3dy/llu3dcKyBHieRe5Z2V6sqvNJOWEsIcqQ= github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA= github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/lestrrat-go/blackmagic v1.0.4 h1:IwQibdnf8l2KoO+qC3uT4OaTWsW7tuRQXy9TRN9QanA= github.com/lestrrat-go/blackmagic v1.0.4/go.mod h1:6AWFyKNNj0zEXQYfTMPfZrAXUWUfTIZ5ECEUEJaijtw= github.com/lestrrat-go/dsig v1.0.0 h1:OE09s2r9Z81kxzJYRn07TFM9XA4akrUdoMwr0L8xj38= github.com/lestrrat-go/dsig v1.0.0/go.mod h1:dEgoOYYEJvW6XGbLasr8TFcAxoWrKlbQvmJgCR0qkDo= github.com/lestrrat-go/dsig-secp256k1 v1.0.0 h1:JpDe4Aybfl0soBvoVwjqDbp+9S1Y2OM7gcrVVMFPOzY= github.com/lestrrat-go/dsig-secp256k1 v1.0.0/go.mod h1:CxUgAhssb8FToqbL8NjSPoGQlnO4w3LG1P0qPWQm/NU= github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE= github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E= github.com/lestrrat-go/httprc/v3 v3.0.2 h1:7u4HUaD0NQbf2/n5+fyp+T10hNCsAnwKfqn4A4Baif0= github.com/lestrrat-go/httprc/v3 v3.0.2/go.mod h1:mSMtkZW92Z98M5YoNNztbRGxbXHql7tSitCvaxvo9l0= github.com/lestrrat-go/option/v2 v2.0.0 h1:XxrcaJESE1fokHy3FpaQ/cXW8ZsIdWcdFzzLOcID3Ss= github.com/lestrrat-go/option/v2 v2.0.0/go.mod h1:oSySsmzMoR0iRzCDCaUfsCzxQHUEuhOViQObyy7S6Vg= 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.1 h1:DTNbBqs57ioxAD4PrArqftgypG4/qNpXoJx8TVXxPR0= github.com/segmentio/asm v1.2.1/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/valyala/fastjson v1.6.7 h1:ZE4tRy0CIkh+qDc5McjatheGX2czdn8slQjomexVpBM= github.com/valyala/fastjson v1.6.7/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU= golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0= 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.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-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/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= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= golang-github-lestrrat-go-jwx-3.0.13/examples/jwe_decrypt_with_key_example_test.go000066400000000000000000000011141515060566400305420ustar00rootroot00000000000000package examples_test import ( "fmt" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/lestrrat-go/jwx/v3/jwe" ) func Example_jwe_verify_with_key() { const payload = "Lorem ipsum" encrypted, err := jwe.Encrypt([]byte(payload), jwe.WithKey(jwa.RSA_OAEP(), jwkRSAPublicKey)) if err != nil { fmt.Printf("failed to sign payload: %s\n", err) return } decrypted, err := jwe.Decrypt(encrypted, jwe.WithKey(jwa.RSA_OAEP(), jwkRSAPrivateKey)) if err != nil { fmt.Printf("failed to sign payload: %s\n", err) return } fmt.Printf("%s\n", decrypted) // OUTPUT: // Lorem ipsum } golang-github-lestrrat-go-jwx-3.0.13/examples/jwe_decrypt_with_keyset_example_test.go000066400000000000000000000021321515060566400312570ustar00rootroot00000000000000package examples_test import ( "crypto/rand" "crypto/rsa" "fmt" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/lestrrat-go/jwx/v3/jwe" "github.com/lestrrat-go/jwx/v3/jwk" ) func Example_jwe_verify_with_jwk_set() { privkey, err := rsa.GenerateKey(rand.Reader, 2048) if err != nil { fmt.Printf("failed to create private key: %s\n", err) return } const payload = "Lorem ipsum" encrypted, err := jwe.Encrypt([]byte(payload), jwe.WithKey(jwa.RSA_OAEP(), privkey.PublicKey)) if err != nil { fmt.Printf("failed to sign payload: %s\n", err) return } // Create a JWK Set set := jwk.NewSet() // Add some bogus keys k1, _ := jwk.Import([]byte("abracadabra")) set.AddKey(k1) k2, _ := jwk.Import([]byte("opensesame")) set.AddKey(k2) // Add the real thing k3, _ := jwk.Import(privkey) k3.Set(jwk.AlgorithmKey, jwa.RSA_OAEP()) set.AddKey(k3) // Up to this point, you probably will replace with a simple jwk.Fetch() if _, err := jwe.Decrypt(encrypted, jwe.WithKeySet(set, jwe.WithRequireKid(false))); err != nil { fmt.Printf("Failed to decrypt using jwk.Set: %s", err) } // OUTPUT: } golang-github-lestrrat-go-jwx-3.0.13/examples/jwe_encrypt_example_test.go000066400000000000000000000020001515060566400266440ustar00rootroot00000000000000package examples_test import ( "crypto/rand" "crypto/rsa" "fmt" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/lestrrat-go/jwx/v3/jwe" "github.com/lestrrat-go/jwx/v3/jwk" ) func Example_jwe_encrypt() { rawprivkey, err := rsa.GenerateKey(rand.Reader, 2048) if err != nil { fmt.Printf("failed to create raw private key: %s\n", err) return } privkey, err := jwk.Import(rawprivkey) if err != nil { fmt.Printf("failed to create private key: %s\n", err) return } pubkey, err := privkey.PublicKey() if err != nil { fmt.Printf("failed to create public key:%s\n", err) return } const payload = `Lorem ipsum` encrypted, err := jwe.Encrypt([]byte(payload), jwe.WithKey(jwa.RSA_OAEP(), pubkey)) if err != nil { fmt.Printf("failed to encrypt payload: %s\n", err) return } decrypted, err := jwe.Decrypt(encrypted, jwe.WithKey(jwa.RSA_OAEP(), privkey)) if err != nil { fmt.Printf("failed to decrypt payload: %s\n", err) return } fmt.Printf("%s\n", decrypted) // OUTPUT: // Lorem ipsum } golang-github-lestrrat-go-jwx-3.0.13/examples/jwe_encrypt_json_example_test.go000066400000000000000000000047111515060566400277100ustar00rootroot00000000000000package examples_test import ( "crypto/rand" "crypto/rsa" "fmt" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/lestrrat-go/jwx/v3/jwe" "github.com/lestrrat-go/jwx/v3/jwk" ) func Example_jwe_encrypt_json() { rawprivkey, err := rsa.GenerateKey(rand.Reader, 2048) if err != nil { fmt.Printf("failed to create raw private key: %s\n", err) return } privkey, err := jwk.Import(rawprivkey) if err != nil { fmt.Printf("failed to create private key: %s\n", err) return } pubkey, err := privkey.PublicKey() if err != nil { fmt.Printf("failed to create public key:%s\n", err) return } const payload = `Lorem ipsum` encrypted, err := jwe.Encrypt( []byte(payload), jwe.WithJSON(), // Toggle JSON serialization. Because there's only one key (recipient), this will produce Flattened JSON serialization jwe.WithLegacyHeaderMerging(false), // Disable legacy header merging jwe.WithKey(jwa.RSA_OAEP(), pubkey), // Public key for encryption ) if err != nil { fmt.Printf("failed to encrypt payload: %s\n", err) return } decrypted, err := jwe.Decrypt(encrypted, jwe.WithKey(jwa.RSA_OAEP(), privkey)) if err != nil { fmt.Printf("failed to decrypt payload: %s\n", err) return } fmt.Printf("%s\n", decrypted) // OUTPUT: // Lorem ipsum } func Example_jwe_encrypt_json_multi() { var privkeys []jwk.Key var pubkeys []jwk.Key for i := 0; i < 3; i++ { rawprivkey, err := rsa.GenerateKey(rand.Reader, 2048) if err != nil { fmt.Printf("failed to create raw private key: %s\n", err) return } privkey, err := jwk.Import(rawprivkey) if err != nil { fmt.Printf("failed to create private key: %s\n", err) return } privkeys = append(privkeys, privkey) pubkey, err := privkey.PublicKey() if err != nil { fmt.Printf("failed to create public key:%s\n", err) return } pubkeys = append(pubkeys, pubkey) } options := []jwe.EncryptOption{jwe.WithJSON()} for _, key := range pubkeys { options = append(options, jwe.WithKey(jwa.RSA_OAEP(), key)) } const payload = `Lorem ipsum` encrypted, err := jwe.Encrypt([]byte(payload), options...) if err != nil { fmt.Printf("failed to encrypt payload: %s\n", err) return } for _, key := range privkeys { decrypted, err := jwe.Decrypt(encrypted, jwe.WithKey(jwa.RSA_OAEP(), key)) if err != nil { fmt.Printf("failed to decrypt payload: %s\n", err) return } fmt.Printf("%s\n", decrypted) } // OUTPUT: // Lorem ipsum // Lorem ipsum // Lorem ipsum } golang-github-lestrrat-go-jwx-3.0.13/examples/jwe_encrypt_with_headers_example_test.go000066400000000000000000000027151515060566400314070ustar00rootroot00000000000000package examples_test import ( "crypto/rand" "crypto/rsa" "fmt" "os" "github.com/lestrrat-go/jwx/v3/internal/json" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/lestrrat-go/jwx/v3/jwe" ) func Example_jwe_sign_with_headers() { privkey, err := rsa.GenerateKey(rand.Reader, 2048) if err != nil { fmt.Printf("failed to create private key: %s\n", err) return } const payload = "Lorem ipsum" hdrs := jwe.NewHeaders() hdrs.Set(`x-example`, true) encrypted, err := jwe.Encrypt([]byte(payload), jwe.WithKey(jwa.RSA_OAEP(), privkey.PublicKey, jwe.WithPerRecipientHeaders(hdrs))) if err != nil { fmt.Printf("failed to encrypt payload: %s\n", err) return } msg, err := jwe.Parse(encrypted) if err != nil { fmt.Printf("failed to parse message: %s\n", err) return } // NOTE: This is a bit tricky. Even though we specified a per-recipient // header when executing jwe.Encrypt, the headers end up being in the // global protected headers section. This is... by the books. JWE // in Compact serialization asks us to shove the per-recipient // headers in the protected header section, because there is nowhere // else to store this information. // // If this were a full JWE JSON message, you might have to juggle // between the global protected headers, global unprotected headers, // and per-recipient unprotected headers json.NewEncoder(os.Stdout).Encode(msg.ProtectedHeaders()) // OUTPUT: // {"alg":"RSA-OAEP","enc":"A256GCM","x-example":true} } golang-github-lestrrat-go-jwx-3.0.13/examples/jwe_example_test.go000066400000000000000000000066321515060566400251170ustar00rootroot00000000000000package examples_test import ( "context" "crypto/rand" "crypto/rsa" "fmt" "log" "github.com/lestrrat-go/jwx/v3/internal/jwxtest" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/lestrrat-go/jwx/v3/jwe" ) func exampleGenPayload() (*rsa.PrivateKey, []byte, error) { privkey, err := rsa.GenerateKey(rand.Reader, 2048) if err != nil { return nil, nil, err } payload := []byte("Lorem Ipsum") encrypted, err := jwe.Encrypt(payload, jwe.WithKey(jwa.RSA1_5(), &privkey.PublicKey), jwe.WithContentEncryption(jwa.A128CBC_HS256())) if err != nil { return nil, nil, err } return privkey, encrypted, nil } func Example_jwe_decrypt() { privkey, encrypted, err := exampleGenPayload() if err != nil { log.Printf("failed to generate encrypted payload: %s", err) return } decrypted, err := jwe.Decrypt(encrypted, jwe.WithKey(jwa.RSA1_5(), privkey)) if err != nil { log.Printf("failed to decrypt: %s", err) return } if string(decrypted) != "Lorem Ipsum" { log.Printf("WHAT?!") return } // OUTPUT: } func Example_jwe_complex_decrypt() { // WARNING: THIS USAGE IS NOT FOR A CASUAL USER. ONLY use it when you must. // Only use it when you understand how JWE is supposed to work. Only use it // when you understand the inner workings of this code. // In this example, the caller wants to determine the key to use by checking // the value of a protected header called `jwx-hints`. const payload = "Hello, World!" privkey, err := jwxtest.GenerateRsaKey() if err != nil { fmt.Printf("failed to generate key: %s\n", err) return } // First we will create a sample JWE payload protected := jwe.NewHeaders() protected.Set(`jwx-hints`, `foobar`) // in real life this would a more meaningful value encrypted, err := jwe.Encrypt( []byte(payload), jwe.WithKey(jwa.RSA_OAEP(), privkey.PublicKey), jwe.WithProtectedHeaders(protected), ) if err != nil { fmt.Printf("failed to encrypt message\n") return } // The party responsible to determining the key is the jwe.KeyProvider hook. // // Here we are using a function turned into an interface for brevity, but in real life // I would personally recommend creating a real type for your specific needs // instead of passing adhoc closures. YMMV. kp := func(ctx context.Context, sink jwe.KeySink, _ jwe.Recipient, msg *jwe.Message) error { var hint string if err := msg.ProtectedHeaders().Get(`jwx-hints`, &hint); err == nil { if hint == `foobar` { // This is where we are setting the key to be used. // // In real life you would look up the key or something. // Here we just assign the key to use. // // You may opt to set both the algorithm and key here as well. // BUT BE CAREFUL so that you don't accidentally create a // vulnerability sink.Key(jwa.RSA_OAEP(), privkey) return nil } } // If there were errors, just return it, and the whole jwe.Decrypt will fail. return fmt.Errorf(`invalid value for jwx-hints`) } // Calling jwe.Decrypt with the extra argument of jwe.WithPostParser(). // Here we pass a nil key to jwe.Decrypt, because the PostParser will be // determining the key to use when its PostParse() method is called decrypted, err := jwe.Decrypt(encrypted, jwe.WithKeyProvider(jwe.KeyProviderFunc(kp))) if err != nil { fmt.Printf("failed to decrypt message: %s\n", err) return } if string(decrypted) != payload { fmt.Printf("wrong decrypted payload: %s\n", decrypted) return } // OUTPUT: } golang-github-lestrrat-go-jwx-3.0.13/examples/jwe_filter_advanced_example_test.go000066400000000000000000000154401515060566400303060ustar00rootroot00000000000000package examples_test import ( "fmt" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/lestrrat-go/jwx/v3/jwe" ) // Example_jwe_filter_advanced demonstrates advanced JWE HeaderFilter functionality // with security filtering, service integration scenarios, and header manipulation. func Example_jwe_filter_advanced() { // Create JWE headers with comprehensive metadata including security and service information protectedHeaders := jwe.NewHeaders() protectedHeaders.Set(jwe.AlgorithmKey, jwa.RSA_OAEP_256()) protectedHeaders.Set(jwe.ContentEncryptionKey, jwa.A256GCM) protectedHeaders.Set(jwe.ContentTypeKey, "application/json") protectedHeaders.Set(jwe.KeyIDKey, "service-key-001") // Security headers protectedHeaders.Set("security_level", "high") protectedHeaders.Set("access_control", "restricted") protectedHeaders.Set("encryption_version", "v2.1") // Service integration headers protectedHeaders.Set("service_name", "user-service") protectedHeaders.Set("api_version", "v1.2.3") protectedHeaders.Set("request_id", "req-789abc") protectedHeaders.Set("correlation_id", "corr-456def") // Operational headers protectedHeaders.Set("environment", "production") protectedHeaders.Set("region", "us-east-1") protectedHeaders.Set("trace_id", "trace-123xyz") // Use the headers directly for filtering examples headers := protectedHeaders // Advanced Example 1: Service Integration - Filter service-related headers serviceFilter := jwe.NewHeaderNameFilter("service_name", "api_version", "request_id", "correlation_id", jwe.KeyIDKey) serviceHeaders, err := serviceFilter.Filter(headers) if err != nil { fmt.Printf("Failed to filter service headers: %s\n", err) return } // Advanced Example 2: Security Headers - Filter security-related metadata securityFilter := jwe.NewHeaderNameFilter("security_level", "access_control", "encryption_version", jwe.AlgorithmKey, jwe.ContentEncryptionKey) securityHeaders, err := securityFilter.Filter(headers) if err != nil { fmt.Printf("Failed to filter security headers: %s\n", err) return } // Advanced Example 3: Operational Headers - Filter operational metadata operationalFilter := jwe.NewHeaderNameFilter("environment", "region", "trace_id") operationalHeaders, err := operationalFilter.Filter(headers) if err != nil { fmt.Printf("Failed to filter operational headers: %s\n", err) return } // Use operationalHeaders variable by checking its length if len(operationalHeaders.Keys()) == 0 { fmt.Printf("No operational headers found\n") return } // Advanced Example 4: Public Headers - Remove sensitive headers for public APIs sensitiveFilter := jwe.NewHeaderNameFilter("security_level", "access_control", "encryption_version", "trace_id") publicHeaders, err := sensitiveFilter.Reject(headers) if err != nil { fmt.Printf("Failed to create public headers: %s\n", err) return } // Use publicHeaders variable by checking its length if len(publicHeaders.Keys()) == 0 { fmt.Printf("No public headers found\n") return } // Advanced Example 5: Minimal Headers - Keep only essential headers for bandwidth optimization essentialFilter := jwe.NewHeaderNameFilter(jwe.AlgorithmKey, jwe.ContentEncryptionKey, jwe.KeyIDKey) minimalHeaders, err := essentialFilter.Filter(headers) if err != nil { fmt.Printf("Failed to filter minimal headers: %s\n", err) return } // Use minimalHeaders variable by checking its length if len(minimalHeaders.Keys()) == 0 { fmt.Printf("No minimal headers found\n") return } // Advanced Example 6: Custom Validation - Filter headers based on security requirements isValidSecurityLevel := validateJWESecurityHeaders(securityHeaders) if !isValidSecurityLevel { fmt.Printf("Security validation failed\n") return } isValidServiceConfig := validateJWEServiceHeaders(serviceHeaders) if !isValidServiceConfig { fmt.Printf("Service configuration validation failed\n") return } // Advanced Example 7: Header transformation for different environments prodHeaders := createJWEEnvironmentHeaders(headers, "production") if len(prodHeaders.Keys()) == 0 { fmt.Printf("Failed to create production headers\n") return } testHeaders := createJWEEnvironmentHeaders(headers, "testing") if len(testHeaders.Keys()) == 0 { fmt.Printf("Failed to create testing headers\n") return } // OUTPUT: } // validateJWESecurityHeaders checks if security headers meet requirements func validateJWESecurityHeaders(headers jwe.Headers) bool { // Check security level var securityLevel string if err := headers.Get("security_level", &securityLevel); err != nil || securityLevel != "high" { return false } // Check access control var accessControl string if err := headers.Get("access_control", &accessControl); err != nil || accessControl != "restricted" { return false } // Check encryption algorithm if algValue, ok := headers.Algorithm(); !ok || algValue != jwa.RSA_OAEP_256() { return false } return true } // validateJWEServiceHeaders checks if service headers are properly configured func validateJWEServiceHeaders(headers jwe.Headers) bool { requiredHeaders := []string{"service_name", "api_version", "request_id", "correlation_id"} for _, header := range requiredHeaders { if !headers.Has(header) { return false } } // Validate API version format var apiVersion string if err := headers.Get("api_version", &apiVersion); err != nil || len(apiVersion) < 5 { return false } return true } // createJWEEnvironmentHeaders creates environment-specific header configurations func createJWEEnvironmentHeaders(originalHeaders jwe.Headers, environment string) jwe.Headers { switch environment { case "production": // Production: Include security and service headers, exclude debug info prodFilter := jwe.NewHeaderNameFilter( jwe.AlgorithmKey, jwe.ContentEncryptionKey, jwe.ContentTypeKey, jwe.KeyIDKey, "security_level", "access_control", "service_name", "api_version", "environment", "region", ) filtered, err := prodFilter.Filter(originalHeaders) if err != nil { fmt.Printf("Failed to create production headers: %s\n", err) return jwe.NewHeaders() } return filtered case "testing": // Testing: Include debug headers, exclude some security headers testFilter := jwe.NewHeaderNameFilter( jwe.AlgorithmKey, jwe.ContentEncryptionKey, jwe.ContentTypeKey, jwe.KeyIDKey, "service_name", "api_version", "request_id", "correlation_id", "trace_id", "environment", ) filtered, err := testFilter.Filter(originalHeaders) if err != nil { fmt.Printf("Failed to create testing headers: %s\n", err) return jwe.NewHeaders() } return filtered default: // Default: Use standard headers only stdFilter := jwe.StandardHeadersFilter() filtered, err := stdFilter.Filter(originalHeaders) if err != nil { fmt.Printf("Failed to create default headers: %s\n", err) return jwe.NewHeaders() } return filtered } } golang-github-lestrrat-go-jwx-3.0.13/examples/jwe_filter_basic_example_test.go000066400000000000000000000050401515060566400276150ustar00rootroot00000000000000package examples_test import ( "fmt" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/lestrrat-go/jwx/v3/jwe" ) // Example_jwe_filter_basic demonstrates basic JWE HeaderFilter functionality // with HeaderNameFilter.Filter(), StandardHeadersFilter(), and HeaderNameFilter.Reject() methods. func Example_jwe_filter_basic() { // Create JWE headers with custom headers for filtering demonstration protectedHeaders := jwe.NewHeaders() protectedHeaders.Set(jwe.AlgorithmKey, jwa.RSA_OAEP_256()) protectedHeaders.Set(jwe.ContentEncryptionKey, jwa.A256GCM) protectedHeaders.Set(jwe.ContentTypeKey, "application/json") protectedHeaders.Set(jwe.KeyIDKey, "example-key-1") protectedHeaders.Set("custom-header", "custom-value") protectedHeaders.Set("app-id", "my-app") protectedHeaders.Set("version", "1.0") // Use the headers directly for filtering examples headers := protectedHeaders // Example 1: HeaderNameFilter.Filter() - Include only specific headers customFilter := jwe.NewHeaderNameFilter("custom-header", "app-id", jwe.KeyIDKey) filteredHeaders, err := customFilter.Filter(headers) if err != nil { fmt.Printf("HeaderNameFilter.Filter failed: %s\n", err) return } // Use filteredHeaders variable by checking its length if len(filteredHeaders.Keys()) == 0 { fmt.Printf("No filtered headers found\n") return } // Example 2: StandardHeadersFilter() - Include only standard JWE headers stdFilter := jwe.StandardHeadersFilter() standardHeaders, err := stdFilter.Filter(headers) if err != nil { fmt.Printf("StandardHeadersFilter.Filter failed: %s\n", err) return } // Use standardHeaders variable by checking its length if len(standardHeaders.Keys()) == 0 { fmt.Printf("No standard headers found\n") return } // Example 3: HeaderNameFilter.Reject() - Exclude specific headers rejectFilter := jwe.NewHeaderNameFilter("version", "custom-header") rejectedHeaders, err := rejectFilter.Reject(headers) if err != nil { fmt.Printf("HeaderNameFilter.Reject failed: %s\n", err) return } // Use rejectedHeaders variable by checking its length if len(rejectedHeaders.Keys()) == 0 { fmt.Printf("No rejected headers found\n") return } // Example 4: StandardHeadersFilter().Reject() - Exclude standard headers, keep custom customOnlyHeaders, err := stdFilter.Reject(headers) if err != nil { fmt.Printf("StandardHeadersFilter.Reject failed: %s\n", err) return } // Use customOnlyHeaders variable by checking its length if len(customOnlyHeaders.Keys()) == 0 { fmt.Printf("No custom only headers found\n") return } // OUTPUT: } golang-github-lestrrat-go-jwx-3.0.13/examples/jwe_parse_example_test.go000066400000000000000000000025011515060566400263000ustar00rootroot00000000000000package examples_test import ( "encoding/json" "fmt" "os" "github.com/lestrrat-go/jwx/v3/jwe" ) func Example_jwe_parse() { const src = `eyJhbGciOiJSU0ExXzUiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2In0.KrFTaMKVY_iUKYYk905QjbUf_fpBXvXCzIAfbPoPMGViDzxtgz5qnch8waV7wraVDfzpW7JfPOw6Nz_-XRwN3Vbud48bRYFw92GkC0M6kpKFpl_xgZxGN47ggNk9hzgqd7mFCuyufeYdn5c2fPoRZAV4UxvakLozEYcQo-eZaFmoYS4pyoC-IKKRikobW8n__LksMzXc_Vps1axn5kdpxsKQ4k1oayvUrgWX2PMxKn_TcLEKHtCN7qRlJ5hkKbZAXAdd34zGWcFV5gc1tcLs6HFhnebo8GUgItTYWBKSKzF6MyLJNRSUPFVq9q-Jxi1juXIlDrv_7rHVsdokQmBfvA.bK7z7Z3gEzFDgDQvNen0Ww.2hngnAVrmucUpJKLgIzYcg.CHs3ZP7JtG430Dl9YAKLMAl` msg, err := jwe.Parse([]byte(src)) if err != nil { fmt.Printf("failed to parse JWE message: %s\n", err) return } json.NewEncoder(os.Stdout).Encode(msg) // OUTPUT: // {"ciphertext":"2hngnAVrmucUpJKLgIzYcg","encrypted_key":"KrFTaMKVY_iUKYYk905QjbUf_fpBXvXCzIAfbPoPMGViDzxtgz5qnch8waV7wraVDfzpW7JfPOw6Nz_-XRwN3Vbud48bRYFw92GkC0M6kpKFpl_xgZxGN47ggNk9hzgqd7mFCuyufeYdn5c2fPoRZAV4UxvakLozEYcQo-eZaFmoYS4pyoC-IKKRikobW8n__LksMzXc_Vps1axn5kdpxsKQ4k1oayvUrgWX2PMxKn_TcLEKHtCN7qRlJ5hkKbZAXAdd34zGWcFV5gc1tcLs6HFhnebo8GUgItTYWBKSKzF6MyLJNRSUPFVq9q-Jxi1juXIlDrv_7rHVsdokQmBfvA","header":{"alg":"RSA1_5"},"iv":"bK7z7Z3gEzFDgDQvNen0Ww","protected":"eyJhbGciOiJSU0ExXzUiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2In0","tag":"CHs3ZP7JtG430Dl9YAKLMAk"} } golang-github-lestrrat-go-jwx-3.0.13/examples/jwe_readfile_example_test.go000066400000000000000000000030541515060566400267450ustar00rootroot00000000000000package examples_test import ( "encoding/json" "fmt" "os" "github.com/lestrrat-go/jwx/v3/jwe" ) func Example_jwe_readfile() { const src = `eyJhbGciOiJSU0ExXzUiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2In0.KrFTaMKVY_iUKYYk905QjbUf_fpBXvXCzIAfbPoPMGViDzxtgz5qnch8waV7wraVDfzpW7JfPOw6Nz_-XRwN3Vbud48bRYFw92GkC0M6kpKFpl_xgZxGN47ggNk9hzgqd7mFCuyufeYdn5c2fPoRZAV4UxvakLozEYcQo-eZaFmoYS4pyoC-IKKRikobW8n__LksMzXc_Vps1axn5kdpxsKQ4k1oayvUrgWX2PMxKn_TcLEKHtCN7qRlJ5hkKbZAXAdd34zGWcFV5gc1tcLs6HFhnebo8GUgItTYWBKSKzF6MyLJNRSUPFVq9q-Jxi1juXIlDrv_7rHVsdokQmBfvA.bK7z7Z3gEzFDgDQvNen0Ww.2hngnAVrmucUpJKLgIzYcg.CHs3ZP7JtG430Dl9YAKLMAl` f, err := os.CreateTemp(``, `jwe_readfile_example-*.jwe`) if err != nil { fmt.Printf("failed to create temporary file: %s\n", err) return } defer os.Remove(f.Name()) f.Write([]byte(src)) f.Close() msg, err := jwe.ReadFile(f.Name()) if err != nil { fmt.Printf("failed to parse JWE message from file %q: %s\n", f.Name(), err) return } json.NewEncoder(os.Stdout).Encode(msg) // OUTPUT: // {"ciphertext":"2hngnAVrmucUpJKLgIzYcg","encrypted_key":"KrFTaMKVY_iUKYYk905QjbUf_fpBXvXCzIAfbPoPMGViDzxtgz5qnch8waV7wraVDfzpW7JfPOw6Nz_-XRwN3Vbud48bRYFw92GkC0M6kpKFpl_xgZxGN47ggNk9hzgqd7mFCuyufeYdn5c2fPoRZAV4UxvakLozEYcQo-eZaFmoYS4pyoC-IKKRikobW8n__LksMzXc_Vps1axn5kdpxsKQ4k1oayvUrgWX2PMxKn_TcLEKHtCN7qRlJ5hkKbZAXAdd34zGWcFV5gc1tcLs6HFhnebo8GUgItTYWBKSKzF6MyLJNRSUPFVq9q-Jxi1juXIlDrv_7rHVsdokQmBfvA","header":{"alg":"RSA1_5"},"iv":"bK7z7Z3gEzFDgDQvNen0Ww","protected":"eyJhbGciOiJSU0ExXzUiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2In0","tag":"CHs3ZP7JtG430Dl9YAKLMAk"} } golang-github-lestrrat-go-jwx-3.0.13/examples/jwk_cache_example_test.go000066400000000000000000000033701515060566400262440ustar00rootroot00000000000000package examples_test import ( "context" "fmt" "time" "github.com/lestrrat-go/httprc/v3" "github.com/lestrrat-go/jwx/v3/jwk" ) func Example_jwk_cache() { ctx, cancel := context.WithCancel(context.Background()) defer cancel() const googleCerts = `https://www.googleapis.com/oauth2/v3/certs` // First, set up the `jwk.Cache` object. You need to pass it a // `context.Context` object to control the lifecycle of the background fetching goroutine. c, err := jwk.NewCache(ctx, httprc.NewClient()) if err != nil { fmt.Printf("failed to create cache: %s\n", err) return } // Tell *jwk.Cache that we only want to refresh this JWKS periodically. if err := c.Register(ctx, googleCerts); err != nil { fmt.Printf("failed to register google JWKS: %s\n", err) return } // Pretend that this is your program's main loop MAIN: for { select { case <-ctx.Done(): break MAIN default: } keyset, err := c.Lookup(ctx, googleCerts) if err != nil { fmt.Printf("failed to fetch google JWKS: %s\n", err) return } _ = keyset // The returned `keyset` will always be "reasonably" new. // // By "reasonably" we mean that we cannot guarantee that the keys will be refreshed // immediately after it has been rotated in the remote source. But it should be close\ // enough, and should you need to forcefully refresh the token using the `(jwk.Cache).Refresh()` method. // // If refetching the keyset fails, a cached version will be returned from the previous // successful sync // Do interesting stuff with the keyset... but here, we just // sleep for a bit time.Sleep(time.Second) // Because we're a dummy program, we just cancel the loop now. // If this were a real program, you presumably loop forever cancel() } // OUTPUT: } golang-github-lestrrat-go-jwx-3.0.13/examples/jwk_cached_set_example_test.go000066400000000000000000000032241515060566400272610ustar00rootroot00000000000000package examples_test import ( "context" "fmt" "log/slog" "os" "time" "github.com/lestrrat-go/httprc/v3" "github.com/lestrrat-go/httprc/v3/tracesink" "github.com/lestrrat-go/jwx/v3/jwk" "github.com/lestrrat-go/jwx/v3/jws" ) func Example_jwk_cached_set() { ctx, cancel := context.WithCancel(context.Background()) defer cancel() const googleCerts = `https://www.googleapis.com/oauth2/v3/certs` // The first steps are the same as examples/jwk_cache_example_test.go c, err := jwk.NewCache( ctx, httprc.NewClient( httprc.WithTraceSink(tracesink.NewSlog(slog.New(slog.NewJSONHandler(os.Stderr, nil)))), ), ) if err != nil { fmt.Printf("failed to create cache: %s\n", err) return } // Register the URL to fetch the JWKS from. In this case, we're saying that // the cache can dynamically decide how often to refresh the keyset based on // the HTTP headers returned by the server, but the value must be at least // 1 hour, and at most 7 days. if err := c.Register( ctx, googleCerts, jwk.WithMaxInterval(24*time.Hour*7), jwk.WithMinInterval(15*time.Minute), ); err != nil { fmt.Printf("failed to register google JWKS: %s\n", err) return } cached, err := c.CachedSet(googleCerts) if err != nil { fmt.Printf("failed to get cached keyset: %s\n", err) return } // cached fulfills the jwk.Set interface. var _ jwk.Set = cached // That means you can pass it to things like jws.WithKeySet, // allowing you to pretend as if you are using the result of // // jwk.Fetch(ctx, googleCerts) // // But you are instead using a cached (and periodically refreshed) set // for each operation. _ = jws.WithKeySet(cached) // OUTPUT: } golang-github-lestrrat-go-jwx-3.0.13/examples/jwk_comparison_example_test.go000066400000000000000000000022731515060566400273540ustar00rootroot00000000000000package examples import ( "crypto/rand" "crypto/rsa" "fmt" "github.com/lestrrat-go/jwx/v3/jwk" ) func Example_jwk_comparison() { genKey := func() (jwk.Key, error) { raw, err := rsa.GenerateKey(rand.Reader, 2048) if err != nil { return nil, fmt.Errorf("failed to generate new RSA private key: %s", err) } key, err := jwk.Import(raw) if err != nil { return nil, fmt.Errorf("failed to create RSA key: %s", err) } if _, ok := key.(jwk.RSAPrivateKey); !ok { return nil, fmt.Errorf("expected jwk.SymmetricKey, got %T", key) } return key, nil } k1, err := genKey() if err != nil { fmt.Printf("failed to generate key 1: %T", err) return } k2, err := genKey() if err != nil { fmt.Printf("failed to generate key 2: %T", err) return } // This comparison only compares Thumbprints of each key. It does NOT take into // account fields that could differ even when thumbprints match. For example, // it is totally possible to have a key with the same thumbprint, but different // Key IDs, or key usages. if jwk.Equal(k1, k2) { fmt.Printf("k1 and k2 should be different") return } if !jwk.Equal(k1, k1) { fmt.Printf("k1 and k1 should be equal") return } } golang-github-lestrrat-go-jwx-3.0.13/examples/jwk_example_test.go000066400000000000000000000053371515060566400251260ustar00rootroot00000000000000package examples_test import ( "context" "fmt" "log" "github.com/lestrrat-go/jwx/v3/internal/json" "github.com/lestrrat-go/jwx/v3/jwk" ) func Example_jwk_usage() { // Use jwk.Cache if you intend to keep reuse the JWKS over and over set, err := jwk.Fetch(context.Background(), "https://www.googleapis.com/oauth2/v3/certs") if err != nil { log.Printf("failed to parse JWK: %s", err) return } // Key sets can be serialized back to JSON { jsonbuf, err := json.Marshal(set) if err != nil { log.Printf("failed to marshal key set into JSON: %s", err) return } log.Printf("%s", jsonbuf) } for i := 0; i < set.Len(); i++ { var rawkey any // This is where we would like to store the raw key, like *rsa.PrivateKey or *ecdsa.PrivateKey key, ok := set.Key(i) // This retrieves the corresponding jwk.Key if !ok { log.Printf("failed to get key at index %d", i) return } // jws and jwe operations can be performed using jwk.Key, but you could also // covert it to their "raw" forms, such as *rsa.PrivateKey or *ecdsa.PrivateKey if err := jwk.Export(key, &rawkey); err != nil { log.Printf("failed to create public key: %s", err) return } _ = rawkey // You can create jwk.Key from a raw key, too fromRawKey, err := jwk.Import(rawkey) if err != nil { log.Printf("failed to acquire raw key from jwk.Key: %s", err) return } // Keys can be serialized back to JSON jsonbuf, err := json.Marshal(key) if err != nil { log.Printf("failed to marshal key into JSON: %s", err) return } fromJSONKey, err := jwk.Parse(jsonbuf) if err != nil { log.Printf("failed to parse json: %s", err) return } _ = fromJSONKey _ = fromRawKey } // OUTPUT: } //nolint:govet func Example_jwk_marshal_json() { // 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.Import(raw) if err != nil { fmt.Printf("failed to create symmetric key: %s\n", err) return } if _, ok := key.(jwk.SymmetricKey); !ok { fmt.Printf("expected jwk.SymmetricKey, got %T\n", key) return } key.Set(jwk.KeyIDKey, "mykey") buf, err := json.MarshalIndent(key, "", " ") if err != nil { fmt.Printf("failed to marshal key into JSON: %s\n", err) return } fmt.Printf("%s\n", buf) // OUTPUT: // { // "k": "MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODlBQkNERUY", // "kid": "mykey", // "kty": "oct" // } } golang-github-lestrrat-go-jwx-3.0.13/examples/jwk_fetch_example_test.go000066400000000000000000000035741515060566400263000ustar00rootroot00000000000000package examples_test import ( "context" "encoding/json" "fmt" "net/http" "net/http/httptest" "os" "github.com/lestrrat-go/jwx/v3/jwk" ) func Example_jwk_fetch() { srv := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) fmt.Fprintf(w, `{ "keys": [ {"kty":"EC", "crv":"P-256", "x":"MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4", "y":"4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM", "use":"enc", "kid":"1"}, {"kty":"RSA", "n": "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw", "e":"AQAB", "alg":"RS256", "kid":"2011-04-29"} ] }`) })) defer srv.Close() set, err := jwk.Fetch( context.Background(), srv.URL, // This is necessary because httptest.Server is using a custom certificate jwk.WithHTTPClient(srv.Client()), ) if err != nil { fmt.Printf("failed to fetch JWKS: %s\n", err) return } json.NewEncoder(os.Stdout).Encode(set) // OUTPUT: // {"keys":[{"crv":"P-256","kid":"1","kty":"EC","use":"enc","x":"MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4","y":"4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM"},{"alg":"RS256","e":"AQAB","kid":"2011-04-29","kty":"RSA","n":"0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw"}]} } golang-github-lestrrat-go-jwx-3.0.13/examples/jwk_filter_advanced_example_test.go000066400000000000000000000152601515060566400303140ustar00rootroot00000000000000package examples_test import ( "crypto/ecdsa" "crypto/elliptic" "crypto/rand" "fmt" "github.com/lestrrat-go/jwx/v3/jwk" ) func Example_jwk_filter_advanced_use_cases() { // Create multiple keys with different security classifications // 1. High-security production key prodKey, err := ecdsa.GenerateKey(elliptic.P384(), rand.Reader) if err != nil { fmt.Printf("failed to generate production key: %s\n", err) return } prodJWK, err := jwk.Import(prodKey) if err != nil { fmt.Printf("failed to import production key: %s\n", err) return } prodJWK.Set(jwk.KeyIDKey, "prod-ecdsa-384-2024") prodJWK.Set(jwk.AlgorithmKey, "ES384") prodJWK.Set(jwk.KeyUsageKey, "sig") prodJWK.Set("securityLevel", "high") prodJWK.Set("environment", "production") prodJWK.Set("classification", "confidential") prodJWK.Set("owner", "security-team") prodJWK.Set("contactEmail", "security@company.com") prodJWK.Set("purpose", "payment-processing") prodJWK.Set("dataTypes", []string{"pii", "financial", "authentication"}) prodJWK.Set("compliance", map[string]any{ "pci-dss": "level-1", "sox": true, "gdpr": true, "hipaa": false, }) prodJWK.Set("auditRequired", true) prodJWK.Set("backupLocation", "hsm-cluster-primary") prodJWK.Set("lastAudit", "2024-03-15T09:00:00Z") // 2. Development key devKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) if err != nil { fmt.Printf("failed to generate dev key: %s\n", err) return } devJWK, err := jwk.Import(devKey) if err != nil { fmt.Printf("failed to import dev key: %s\n", err) return } devJWK.Set(jwk.KeyIDKey, "dev-ecdsa-256-2024") devJWK.Set(jwk.AlgorithmKey, "ES256") devJWK.Set(jwk.KeyUsageKey, "sig") devJWK.Set("securityLevel", "low") devJWK.Set("environment", "development") devJWK.Set("classification", "public") devJWK.Set("owner", "dev-team") devJWK.Set("contactEmail", "dev@company.com") devJWK.Set("purpose", "testing") devJWK.Set("dataTypes", []string{"test-data", "mock-data"}) devJWK.Set("compliance", map[string]any{ "pci-dss": "not-applicable", "sox": false, "gdpr": false, "hipaa": false, }) devJWK.Set("auditRequired", false) devJWK.Set("backupLocation", "local-storage") devJWK.Set("lastAudit", "never") // 3. Staging key stagingKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) if err != nil { fmt.Printf("failed to generate staging key: %s\n", err) return } stagingJWK, err := jwk.Import(stagingKey) if err != nil { fmt.Printf("failed to import staging key: %s\n", err) return } stagingJWK.Set(jwk.KeyIDKey, "staging-ecdsa-256-2024") stagingJWK.Set(jwk.AlgorithmKey, "ES256") stagingJWK.Set(jwk.KeyUsageKey, "sig") stagingJWK.Set("securityLevel", "medium") stagingJWK.Set("environment", "staging") stagingJWK.Set("classification", "internal") stagingJWK.Set("owner", "qa-team") stagingJWK.Set("contactEmail", "qa@company.com") stagingJWK.Set("purpose", "integration-testing") stagingJWK.Set("dataTypes", []string{"sanitized-production-data"}) stagingJWK.Set("compliance", map[string]any{ "pci-dss": "level-3", "sox": true, "gdpr": true, "hipaa": false, }) stagingJWK.Set("auditRequired", true) stagingJWK.Set("backupLocation", "cloud-backup-encrypted") stagingJWK.Set("lastAudit", "2024-02-28T14:30:00Z") // Advanced Use Case 1: Security classification filter // Create different filters based on security levels publicFieldsFilter := jwk.NewFieldNameFilter( "securityLevel", "environment", "classification", "owner", "contactEmail", "purpose", "auditRequired", ) confidentialFieldsFilter := jwk.NewFieldNameFilter( "dataTypes", "compliance", "backupLocation", "lastAudit", ) // Apply public fields filter to production key publicProdKey, err := publicFieldsFilter.Filter(prodJWK) if err != nil { fmt.Printf("failed to create public key: %s\n", err) return } // Apply confidential fields filter to production key confidentialProdKey, err := confidentialFieldsFilter.Filter(prodJWK) if err != nil { fmt.Printf("failed to create confidential key: %s\n", err) return } // Advanced Use Case 2: Compliance-specific filtering complianceFilter := jwk.NewFieldNameFilter( jwk.KeyIDKey, "environment", "purpose", "compliance", "auditRequired", "lastAudit", "dataTypes", ) // Apply compliance filter to production key for demonstration if _, err := complianceFilter.Filter(prodJWK); err != nil { fmt.Printf("failed to create compliance key: %s\n", err) return } // Apply compliance filter to dev key for demonstration if _, err := complianceFilter.Filter(devJWK); err != nil { fmt.Printf("failed to create compliance dev key: %s\n", err) return } // Advanced Use Case 3: Operational monitoring filter opsFilter := jwk.NewFieldNameFilter( jwk.KeyIDKey, "environment", "owner", "contactEmail", "backupLocation", "lastAudit", "auditRequired", ) // Apply ops filter to staging key for demonstration if _, err := opsFilter.Filter(stagingJWK); err != nil { fmt.Printf("failed to create ops key: %s\n", err) return } // Advanced Use Case 4: Remove all custom metadata for pure cryptographic use stdFilter := jwk.ECDSAStandardFieldsFilter() cryptoDevKey, err := stdFilter.Filter(devJWK) if err != nil { fmt.Printf("failed to create crypto key: %s\n", err) return } // Validate that filtered keys have the correct structure // Check original production key has all expected fields if !prodJWK.Has("d") || !prodJWK.Has("x") || !prodJWK.Has("y") { fmt.Printf("missing cryptographic fields in production key\n") return } if !prodJWK.Has("environment") || !prodJWK.Has("classification") { fmt.Printf("missing metadata fields in production key\n") return } // Check public key excludes cryptographic data if publicProdKey.Has("d") || publicProdKey.Has("x") || publicProdKey.Has("y") { fmt.Printf("public key should not contain cryptographic fields\n") return } if !publicProdKey.Has("securityLevel") || !publicProdKey.Has("environment") { fmt.Printf("public key missing expected fields\n") return } // Check confidential key has restricted fields only if confidentialProdKey.Has("contactEmail") || confidentialProdKey.Has("owner") { fmt.Printf("confidential key should not contain public fields\n") return } if !confidentialProdKey.Has("dataTypes") || !confidentialProdKey.Has("compliance") { fmt.Printf("confidential key missing expected fields\n") return } // Check crypto-only key has standard fields but no metadata if cryptoDevKey.Has("environment") || cryptoDevKey.Has("classification") { fmt.Printf("crypto-only key should not contain metadata\n") return } if !cryptoDevKey.Has("kty") || !cryptoDevKey.Has("use") { fmt.Printf("crypto-only key missing standard JWK fields\n") return } // OUTPUT: } golang-github-lestrrat-go-jwx-3.0.13/examples/jwk_filter_basic_example_test.go000066400000000000000000000042121515060566400276230ustar00rootroot00000000000000package examples_test import ( "crypto/rand" "crypto/rsa" "fmt" "github.com/lestrrat-go/jwx/v3/jwk" ) func Example_jwk_filter_basic_fields() { // Create an RSA key and import it as a JWK with custom fields rsaPrivateKey, err := rsa.GenerateKey(rand.Reader, 2048) if err != nil { fmt.Printf("failed to generate RSA key: %s\n", err) return } key, err := jwk.Import(rsaPrivateKey) if err != nil { fmt.Printf("failed to import RSA key: %s\n", err) return } // Add standard fields key.Set(jwk.KeyIDKey, "my-rsa-key-001") key.Set(jwk.KeyUsageKey, "sig") key.Set(jwk.AlgorithmKey, "RS256") // Add custom fields key.Set("keyOwner", "alice@example.com") key.Set("environment", "production") key.Set("department", "security") key.Set("rotationPolicy", "quarterly") // Create a custom field name filter customFilter := jwk.NewFieldNameFilter("keyOwner", "environment", "department", "rotationPolicy") // Filter to get only custom fields customOnlyKey, err := customFilter.Filter(key) if err != nil { fmt.Printf("failed to filter custom fields: %s\n", err) return } // Use RSAStandardFieldsFilter to get only standard RSA JWK fields standardFilter := jwk.RSAStandardFieldsFilter() standardOnlyKey, err := standardFilter.Filter(key) if err != nil { fmt.Printf("failed to filter standard fields: %s\n", err) return } // Create a copy for display purposes, replacing long values with "..." displayKey, err := standardOnlyKey.Clone() if err != nil { fmt.Printf("failed to clone standard key: %s\n", err) return } // Validate that the filtering worked correctly // Check custom-only key has expected custom fields and no sensitive data if !customOnlyKey.Has("keyOwner") || !customOnlyKey.Has("environment") { fmt.Printf("custom key missing expected fields\n") return } if customOnlyKey.Has("d") || customOnlyKey.Has("n") { fmt.Printf("custom key should not contain cryptographic fields\n") return } // Check that display key has expected standard fields if !displayKey.Has("alg") || !displayKey.Has("kty") || !displayKey.Has("use") { fmt.Printf("display key missing standard JWK fields\n") return } // Output: } golang-github-lestrrat-go-jwx-3.0.13/examples/jwk_import_example_test.go000066400000000000000000000042511515060566400265120ustar00rootroot00000000000000package examples_test import ( "crypto/ecdsa" "crypto/elliptic" "crypto/rand" "crypto/rsa" "fmt" "github.com/lestrrat-go/jwx/v3/jwk" ) func Example_jwk_import() { // First, THIS IS THE WRONG WAY TO USE jwk.Import(). // // Assume that the file contains a JWK in JSON format // // buf, _ := os.ReadFile(file) // key, _ := jwk.Import(buf) // // This is not right, because the jwk.Import() function determines // the type of `jwk.Key` to create based on the TYPE of the argument. // In this case the type of `buf` is always []byte, and therefore // it will always create a symmetric key. // // What you want to do is to _parse_ `buf`. // // keyset, _ := jwk.Parse(buf) // key, _ := jwk.ParseKey(buf) // // See other examples in examples/jwk_parse_key_example_test.go and // examples/jwk_parse_jwks_example_test.go // []byte -> jwk.SymmetricKey { raw := []byte("Lorem Ipsum") key, err := jwk.Import(raw) if err != nil { fmt.Printf("failed to create symmetric key: %s\n", err) return } if _, ok := key.(jwk.SymmetricKey); !ok { fmt.Printf("expected jwk.SymmetricKey, got %T\n", key) return } } // *rsa.PrivateKey -> jwk.RSAPrivateKey // *rsa.PublicKey -> jwk.RSAPublicKey { raw, err := rsa.GenerateKey(rand.Reader, 2048) if err != nil { fmt.Printf("failed to generate new RSA private key: %s\n", err) return } key, err := jwk.Import(raw) if err != nil { fmt.Printf("failed to create symmetric key: %s\n", err) return } if _, ok := key.(jwk.RSAPrivateKey); !ok { fmt.Printf("expected jwk.SymmetricKey, got %T\n", key) return } // PublicKey is omitted for brevity } // *ecdsa.PrivateKey -> jwk.ECDSAPrivateKey // *ecdsa.PublicKey -> jwk.ECDSAPublicKey { raw, err := ecdsa.GenerateKey(elliptic.P384(), rand.Reader) if err != nil { fmt.Printf("failed to generate new ECDSA private key: %s\n", err) return } key, err := jwk.Import(raw) if err != nil { fmt.Printf("failed to create symmetric key: %s\n", err) return } if _, ok := key.(jwk.ECDSAPrivateKey); !ok { fmt.Printf("expected jwk.SymmetricKey, got %T\n", key) return } // PublicKey is omitted for brevity } // OUTPUT: } golang-github-lestrrat-go-jwx-3.0.13/examples/jwk_key_specific_methods_example_test.go000066400000000000000000000017211515060566400313570ustar00rootroot00000000000000package examples_test import ( "crypto/rand" "crypto/rsa" "fmt" "github.com/lestrrat-go/jwx/v3/jwk" ) func Example_jwk_key_specific_metehods() { raw, err := rsa.GenerateKey(rand.Reader, 2048) if err != nil { fmt.Printf("failed to generate RSA private key: %s\n", err) return } key, err := jwk.Import(raw) if err != nil { fmt.Printf("failed to create jwk.Key from RSA private key: %s\n", err) return } rsakey, ok := key.(jwk.RSAPrivateKey) if !ok { fmt.Printf("failed to convert jwk.Key into jwk.RSAPrivateKey (was %T)\n", key) return } // We won't print these values, because each time they are // generated the contents will be different, and thus our // tests would fail. But here you can see that once you // convert the type you can access the RSA-specific methods _, _ = rsakey.D() _, _ = rsakey.DP() _, _ = rsakey.DQ() _, _ = rsakey.E() _, _ = rsakey.N() _, _ = rsakey.P() _, _ = rsakey.Q() _, _ = rsakey.QI() // OUTPUT: // } golang-github-lestrrat-go-jwx-3.0.13/examples/jwk_parse_jwks_example_test.go000066400000000000000000000030241515060566400273450ustar00rootroot00000000000000package examples_test import ( "encoding/json" "fmt" "os" "github.com/lestrrat-go/jwx/v3/jwk" ) func Example_jwk_parse_jwks() { const src = `{ "keys": [ {"kty":"EC", "crv":"P-256", "x":"MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4", "y":"4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM", "use":"enc", "kid":"1"}, {"kty":"RSA", "n": "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw", "e":"AQAB", "alg":"RS256", "kid":"2011-04-29"} ] }` set, err := jwk.Parse([]byte(src)) if err != nil { fmt.Printf("failed to parse JWKS: %s\n", err) return } json.NewEncoder(os.Stdout).Encode(set) // OUTPUT: // {"keys":[{"crv":"P-256","kid":"1","kty":"EC","use":"enc","x":"MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4","y":"4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM"},{"alg":"RS256","e":"AQAB","kid":"2011-04-29","kty":"RSA","n":"0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw"}]} } golang-github-lestrrat-go-jwx-3.0.13/examples/jwk_parse_key_example_test.go000066400000000000000000000012121515060566400271540ustar00rootroot00000000000000package examples_test import ( "encoding/json" "fmt" "os" "github.com/lestrrat-go/jwx/v3/jwk" ) func Example_jwk_parse_key() { const src = `{ "kty":"EC", "crv":"P-256", "x":"MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4", "y":"4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM", "use":"enc", "kid":"1" }` key, err := jwk.ParseKey([]byte(src)) if err != nil { fmt.Printf("failed parse key: %s\n", err) return } json.NewEncoder(os.Stdout).Encode(key) // OUTPUT: // {"crv":"P-256","kid":"1","kty":"EC","use":"enc","x":"MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4","y":"4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM"} } golang-github-lestrrat-go-jwx-3.0.13/examples/jwk_parse_with_pem_example_test.go000066400000000000000000000053061515060566400302100ustar00rootroot00000000000000package examples_test import ( "fmt" "os" "github.com/lestrrat-go/jwx/v3/internal/json" "github.com/lestrrat-go/jwx/v3/jwk" ) func Example_jwk_parse_with_pem() { const src = `-----BEGIN CERTIFICATE----- MIIEljCCAn4CCQCTQBoGDvUbQTANBgkqhkiG9w0BAQsFADANMQswCQYDVQQGEwJK UDAeFw0yMTA0MDEwMDE4MjhaFw0yMjA0MDEwMDE4MjhaMA0xCzAJBgNVBAYTAkpQ MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAvws4H/OxVS3CW1zvUgjs H443df9zCAblLVPPdeRD11Jl1OZmGS7rtQNjQyT5xGpeuk77ZJcfDNLx+mSEtiYQ V37GD5MPz+RX3hP2azuLvxoBseaHE6kC8tkDed8buQLl1hgms15KmKnt7E8B+EK2 1YRj0w6ZzehIllTbbj6gDJ39kZ2VHdLf5+4W0Kyh9cM4aA0si2jQJQsohW2rpt89 b+IagFau+sxP3GFUjSEvyXIamXhS0NLWuAW9UvY/RwhnIo5BzmWZd/y2R305T+QT rHtb/8aGav8mP3uDx6AMDp/0UMKFUO4mpoOusMnrplUPS4Lz6RNpffmrrglOEuRZ /eSFzGL35OeL12aYSyrbFIVsc/aLs6MkoplsuSG6Zhx345h/dA2a8Ub5khr6bksP zGLer+bpBrQQsy21unvCIUz5y7uaYhV3Ql+aIZ+dwpEgZ3xxAvdKKeoCGQlhH/4J 0sSuutUtuTLfrBSgLHJEv2HIzeynChL2CYR8aku/nL68VTdmSt9UY2JGMOf9U8BI fGRpkWBvI8hddMxNm8wF+09WScaZ2JWu7qW/l2jOdgesPIWRg+Hm3NaRSHqAWCOq VUJk9WkCAye0FPALqSvH0ApDKxNtGZb5JZRCW19TqmhgXbAqIf5hsxDaGIXZcW9S CqapZPw7Ccs7BOKSFvmM9p0CAwEAATANBgkqhkiG9w0BAQsFAAOCAgEAVfLzKRdA 0vFpAAp3K+CDth7mag2WWFOXjlWZ+4pxfEBX3k7erJbj6+qYuCvCHXqIZnK1kZzD p4zwsu8t8RfSmPvxcm/jkvecG4DAIGTdhBVtAf/9PU3e4kZFQCqizicQABh+ZFKV dDtkRebUA5EAvP8E/OrvrjYU5xnOxOZU3arVXJfKFjVD619qLuF8XXW5700Gdqwn wBgasTCCg9+tniiscKaET1m9C4PdrlXuAIscV9tGcJ7yEAao1BXokyJ+mK6K2Zv1 z/vvUJA/rGMBJoUjnWrRHON1JMNou2KyRO6z37GpRnfPiNgFpGv2x3ZNeix7H4bP 6+x4KZWQir5047p9hV4YrqMXeULEj3uG2GnOgdR7+hiN39arFVr11DMgABmx19SM VQpTHrC8a605wwCBWnkiYdNojLa5WgeEHdBghKVpWnx9frYgZcz2UP861el5Lg9R j04wkGL4IORYiM7VHSHNU4u/dlgfQE1y0T+1CzXwquy4csvbBzBKnZ1o9ZBsOtWS ox0RaBsMD70mvTwKKmlCSD5HgZZTC0CfGWk4dQp/Mct5Z0x0HJMEJCJzpgTn3CRX z8CjezfckLs7UKJOlhu3OU9TFsiGDzSDBZdDWO1/uciJ/AAWeSmsBt8cKL0MirIr c4wOvhbalcX0FqTM3mXCgMFRbibquhwdxbU= -----END CERTIFICATE-----` key, err := jwk.ParseKey([]byte(src), jwk.WithPEM(true)) if err != nil { fmt.Printf("failed to parse key in PEM format: %s\n", err) return } json.NewEncoder(os.Stdout).Encode(key) // OUTPUT: // {"e":"AQAB","kty":"RSA","n":"vws4H_OxVS3CW1zvUgjsH443df9zCAblLVPPdeRD11Jl1OZmGS7rtQNjQyT5xGpeuk77ZJcfDNLx-mSEtiYQV37GD5MPz-RX3hP2azuLvxoBseaHE6kC8tkDed8buQLl1hgms15KmKnt7E8B-EK21YRj0w6ZzehIllTbbj6gDJ39kZ2VHdLf5-4W0Kyh9cM4aA0si2jQJQsohW2rpt89b-IagFau-sxP3GFUjSEvyXIamXhS0NLWuAW9UvY_RwhnIo5BzmWZd_y2R305T-QTrHtb_8aGav8mP3uDx6AMDp_0UMKFUO4mpoOusMnrplUPS4Lz6RNpffmrrglOEuRZ_eSFzGL35OeL12aYSyrbFIVsc_aLs6MkoplsuSG6Zhx345h_dA2a8Ub5khr6bksPzGLer-bpBrQQsy21unvCIUz5y7uaYhV3Ql-aIZ-dwpEgZ3xxAvdKKeoCGQlhH_4J0sSuutUtuTLfrBSgLHJEv2HIzeynChL2CYR8aku_nL68VTdmSt9UY2JGMOf9U8BIfGRpkWBvI8hddMxNm8wF-09WScaZ2JWu7qW_l2jOdgesPIWRg-Hm3NaRSHqAWCOqVUJk9WkCAye0FPALqSvH0ApDKxNtGZb5JZRCW19TqmhgXbAqIf5hsxDaGIXZcW9SCqapZPw7Ccs7BOKSFvmM9p0"} } golang-github-lestrrat-go-jwx-3.0.13/examples/jwk_readfile_example_test.go000066400000000000000000000033311515060566400267510ustar00rootroot00000000000000package examples_test import ( "encoding/json" "fmt" "os" "github.com/lestrrat-go/jwx/v3/jwk" ) func Example_jwk_readfile() { const src = `{ "keys": [ {"kty":"EC", "crv":"P-256", "x":"MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4", "y":"4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM", "use":"enc", "kid":"1"}, {"kty":"RSA", "n": "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw", "e":"AQAB", "alg":"RS256", "kid":"2011-04-29"} ] }` f, err := os.CreateTemp(``, `jwk_readfile-*.jwk`) if err != nil { fmt.Printf("failed to create temporary file: %s\n", err) return } defer os.Remove(f.Name()) fmt.Fprint(f, src) f.Close() key, err := jwk.ReadFile(f.Name()) if err != nil { fmt.Printf("failed to parse key: %s\n", err) return } json.NewEncoder(os.Stdout).Encode(key) // OUTPUT: // {"keys":[{"crv":"P-256","kid":"1","kty":"EC","use":"enc","x":"MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4","y":"4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM"},{"alg":"RS256","e":"AQAB","kid":"2011-04-29","kty":"RSA","n":"0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw"}]} } golang-github-lestrrat-go-jwx-3.0.13/examples/jwk_readfile_with_pem_example_test.go000066400000000000000000000056411515060566400306530ustar00rootroot00000000000000package examples_test import ( "fmt" "os" "github.com/lestrrat-go/jwx/v3/internal/json" "github.com/lestrrat-go/jwx/v3/jwk" ) func Example_jwk_readfile_with_pem() { const src = `-----BEGIN CERTIFICATE----- MIIEljCCAn4CCQCTQBoGDvUbQTANBgkqhkiG9w0BAQsFADANMQswCQYDVQQGEwJK UDAeFw0yMTA0MDEwMDE4MjhaFw0yMjA0MDEwMDE4MjhaMA0xCzAJBgNVBAYTAkpQ MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAvws4H/OxVS3CW1zvUgjs H443df9zCAblLVPPdeRD11Jl1OZmGS7rtQNjQyT5xGpeuk77ZJcfDNLx+mSEtiYQ V37GD5MPz+RX3hP2azuLvxoBseaHE6kC8tkDed8buQLl1hgms15KmKnt7E8B+EK2 1YRj0w6ZzehIllTbbj6gDJ39kZ2VHdLf5+4W0Kyh9cM4aA0si2jQJQsohW2rpt89 b+IagFau+sxP3GFUjSEvyXIamXhS0NLWuAW9UvY/RwhnIo5BzmWZd/y2R305T+QT rHtb/8aGav8mP3uDx6AMDp/0UMKFUO4mpoOusMnrplUPS4Lz6RNpffmrrglOEuRZ /eSFzGL35OeL12aYSyrbFIVsc/aLs6MkoplsuSG6Zhx345h/dA2a8Ub5khr6bksP zGLer+bpBrQQsy21unvCIUz5y7uaYhV3Ql+aIZ+dwpEgZ3xxAvdKKeoCGQlhH/4J 0sSuutUtuTLfrBSgLHJEv2HIzeynChL2CYR8aku/nL68VTdmSt9UY2JGMOf9U8BI fGRpkWBvI8hddMxNm8wF+09WScaZ2JWu7qW/l2jOdgesPIWRg+Hm3NaRSHqAWCOq VUJk9WkCAye0FPALqSvH0ApDKxNtGZb5JZRCW19TqmhgXbAqIf5hsxDaGIXZcW9S CqapZPw7Ccs7BOKSFvmM9p0CAwEAATANBgkqhkiG9w0BAQsFAAOCAgEAVfLzKRdA 0vFpAAp3K+CDth7mag2WWFOXjlWZ+4pxfEBX3k7erJbj6+qYuCvCHXqIZnK1kZzD p4zwsu8t8RfSmPvxcm/jkvecG4DAIGTdhBVtAf/9PU3e4kZFQCqizicQABh+ZFKV dDtkRebUA5EAvP8E/OrvrjYU5xnOxOZU3arVXJfKFjVD619qLuF8XXW5700Gdqwn wBgasTCCg9+tniiscKaET1m9C4PdrlXuAIscV9tGcJ7yEAao1BXokyJ+mK6K2Zv1 z/vvUJA/rGMBJoUjnWrRHON1JMNou2KyRO6z37GpRnfPiNgFpGv2x3ZNeix7H4bP 6+x4KZWQir5047p9hV4YrqMXeULEj3uG2GnOgdR7+hiN39arFVr11DMgABmx19SM VQpTHrC8a605wwCBWnkiYdNojLa5WgeEHdBghKVpWnx9frYgZcz2UP861el5Lg9R j04wkGL4IORYiM7VHSHNU4u/dlgfQE1y0T+1CzXwquy4csvbBzBKnZ1o9ZBsOtWS ox0RaBsMD70mvTwKKmlCSD5HgZZTC0CfGWk4dQp/Mct5Z0x0HJMEJCJzpgTn3CRX z8CjezfckLs7UKJOlhu3OU9TFsiGDzSDBZdDWO1/uciJ/AAWeSmsBt8cKL0MirIr c4wOvhbalcX0FqTM3mXCgMFRbibquhwdxbU= -----END CERTIFICATE-----` f, err := os.CreateTemp(``, `jwk_readfile_with_pem-*.jwk`) if err != nil { fmt.Printf("failed to create temporary file: %s\n", err) return } defer os.Remove(f.Name()) fmt.Fprint(f, src) f.Close() key, err := jwk.ReadFile(f.Name(), jwk.WithPEM(true)) if err != nil { fmt.Printf("failed to parse key in PEM format: %s\n", err) return } json.NewEncoder(os.Stdout).Encode(key) // OUTPUT: // {"keys":[{"e":"AQAB","kty":"RSA","n":"vws4H_OxVS3CW1zvUgjsH443df9zCAblLVPPdeRD11Jl1OZmGS7rtQNjQyT5xGpeuk77ZJcfDNLx-mSEtiYQV37GD5MPz-RX3hP2azuLvxoBseaHE6kC8tkDed8buQLl1hgms15KmKnt7E8B-EK21YRj0w6ZzehIllTbbj6gDJ39kZ2VHdLf5-4W0Kyh9cM4aA0si2jQJQsohW2rpt89b-IagFau-sxP3GFUjSEvyXIamXhS0NLWuAW9UvY_RwhnIo5BzmWZd_y2R305T-QTrHtb_8aGav8mP3uDx6AMDp_0UMKFUO4mpoOusMnrplUPS4Lz6RNpffmrrglOEuRZ_eSFzGL35OeL12aYSyrbFIVsc_aLs6MkoplsuSG6Zhx345h_dA2a8Ub5khr6bksPzGLer-bpBrQQsy21unvCIUz5y7uaYhV3Ql-aIZ-dwpEgZ3xxAvdKKeoCGQlhH_4J0sSuutUtuTLfrBSgLHJEv2HIzeynChL2CYR8aku_nL68VTdmSt9UY2JGMOf9U8BIfGRpkWBvI8hddMxNm8wF-09WScaZ2JWu7qW_l2jOdgesPIWRg-Hm3NaRSHqAWCOqVUJk9WkCAye0FPALqSvH0ApDKxNtGZb5JZRCW19TqmhgXbAqIf5hsxDaGIXZcW9SCqapZPw7Ccs7BOKSFvmM9p0"}]} } golang-github-lestrrat-go-jwx-3.0.13/examples/jwk_struct_field_example_test.go000066400000000000000000000025701515060566400276710ustar00rootroot00000000000000package examples_test import ( "encoding/json" "fmt" "os" "github.com/lestrrat-go/jwx/v3/jwk" ) type Container struct { Key jwk.Key `json:"key"` } // This is only one way to parse a struct field whose dynamic // type is unknown at compile time. In this example we use // a proxy/wrapper to trick `Container` from attempting to // parse the `.Key` field, and intercept the value that // would have gone into the `Container` struct into // `Proxy` struct's `.Key` struct field type Proxy struct { Container Key json.RawMessage `json:"key"` } func Example_jwk_struct_field() { const src = `{ "key": { "kty":"EC", "crv":"P-256", "x":"MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4", "y":"4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM", "use":"enc", "kid":"1" } }` var p Proxy if err := json.Unmarshal([]byte(src), &p); err != nil { fmt.Printf("failed to unmarshal from JSON: %s\n", err) return } // Parse the intercepted `Proxy.Key` as a `jwk.Key` // and assign it to `Container.Key` key, err := jwk.ParseKey(p.Key) if err != nil { fmt.Printf("failed to parse key: %s\n", err) return } p.Container.Key = key json.NewEncoder(os.Stdout).Encode(p.Container) // OUTPUT: // {"key":{"crv":"P-256","kid":"1","kty":"EC","use":"enc","x":"MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4","y":"4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM"}} } golang-github-lestrrat-go-jwx-3.0.13/examples/jwk_whitelist_example_test.go000066400000000000000000000051751515060566400272220ustar00rootroot00000000000000package examples_test import ( "context" "encoding/json" "fmt" "net/http" "net/http/httptest" "os" "regexp" "github.com/lestrrat-go/jwx/v3/jwk" ) func Example_jwk_whitelist() { srv := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) fmt.Fprintf(w, `{ "keys": [ {"kty":"EC", "crv":"P-256", "x":"MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4", "y":"4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM", "use":"enc", "kid":"1"}, {"kty":"RSA", "n": "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw", "e":"AQAB", "alg":"RS256", "kid":"2011-04-29"} ] }`) })) defer srv.Close() testcases := []struct { Whitelist jwk.Whitelist Error bool }{ // The first two whitelists are meant to prevent access to any other // URLs other than www.google.com { Whitelist: jwk.NewMapWhitelist().Add(`https://www.googleapis.com/oauth2/v3/certs`), Error: true, }, { Whitelist: jwk.NewRegexpWhitelist().Add(regexp.MustCompile(`^https://www\.googleapis\.com/`)), Error: true, }, // This whitelist allows anything { Whitelist: jwk.InsecureWhitelist{}, }, } for _, tc := range testcases { set, err := jwk.Fetch( context.Background(), srv.URL, // This is necessary because httptest.Server is using a custom certificate jwk.WithHTTPClient(srv.Client()), // Pass the whitelist! jwk.WithFetchWhitelist(tc.Whitelist), ) if tc.Error { if err == nil { fmt.Printf("expected fetch to fail, but got no error\n") return } } else { if err != nil { fmt.Printf("failed to fetch JWKS: %s\n", err) return } json.NewEncoder(os.Stdout).Encode(set) } } // OUTPUT: // {"keys":[{"crv":"P-256","kid":"1","kty":"EC","use":"enc","x":"MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4","y":"4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM"},{"alg":"RS256","e":"AQAB","kid":"2011-04-29","kty":"RSA","n":"0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw"}]} } golang-github-lestrrat-go-jwx-3.0.13/examples/jws_custom_signer_verifier_example_test.go000066400000000000000000000106271515060566400317700ustar00rootroot00000000000000package examples_test import ( "crypto/rand" "fmt" "github.com/cloudflare/circl/sign/ed25519" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/lestrrat-go/jwx/v3/jws" ) func Example_jws_custom_signer_verifier() { // Newer way of registering a custom signer/verifier if err := jws.RegisterSigner(jwa.EdDSA(), CirclEdDSASigner{}); err != nil { fmt.Printf(`failed to register signer: %s`, err) return } if err := jws.RegisterVerifier(jwa.EdDSA(), CirclEdDSAVerifier{}); err != nil { fmt.Printf(`failed to register verifier: %s`, err) return } pubkey, privkey, err := ed25519.GenerateKey(rand.Reader) if err != nil { fmt.Printf(`failed to generate keys: %s`, err) return } const payload = "Lorem Ipsum" signed, err := jws.Sign([]byte(payload), jws.WithKey(jwa.EdDSA(), privkey)) if err != nil { fmt.Printf(`failed to generate signed message: %s`, err) return } verified, err := jws.Verify(signed, jws.WithKey(jwa.EdDSA(), pubkey)) if err != nil { fmt.Printf(`failed to verify signed message: %s`, err) return } if string(verified) != payload { fmt.Printf(`got invalid payload: %s`, verified) return } // OUTPUT: // Custom signer called // Custom verifier called } type CirclEdDSASigner struct{} func (CirclEdDSASigner) Algorithm() jwa.SignatureAlgorithm { return jwa.EdDSA() } // Sign implements the jws.Signer2 interface for Circl's EdDSA signer. // // Signer2 is a relatively low-level API. It receives multiple parameters because of this. // // One thing you should do in your signer is to check the type of the key passed in. // We have no way of constricting the type of key that is passed in without knowing // the implementation details of your custom signer, and thus we cannot guarantee that // users will pass in the correct type of key. // // Those implementing the jws.Signer2 interface could construct the buffer to be signed // themselves and generate the signature, but it is often easier to use the jwsbb.Sign // function, which takes care of the constructiion. In this example, we would like to // tell jwsbb.Sign to construct the buffer and generate the signature using ed25519.Sign, // but since the function signatures do not match, we are providing an adapter // that implements the jwsbb.Signer interface. // // If you need to construct the buffer yourself, you can do so by using the // jwsbb.SignBuffer() function in combination with the jwsbb.SignRaw() function. func (CirclEdDSASigner) Sign(key any, payload []byte) ([]byte, error) { fmt.Println("Custom signer called") privkey, ok := key.(ed25519.PrivateKey) if !ok { return nil, fmt.Errorf(`jws.CirclEdDSASigner: invalid key type %T. ed25519.PrivateKey is required`, key) } return ed25519.Sign(privkey, payload), nil } type CirclEdDSAVerifier struct{} func (CirclEdDSAVerifier) Algorithm() jwa.SignatureAlgorithm { return jwa.EdDSA() } // Do implements the jws.Verifier interface for Circl's EdDSA verifier. // // See the comments for CirclECDSASigner.Do for more information on what this function does. func (CirclEdDSAVerifier) Verify(key any, payload, signature []byte) error { fmt.Println("Custom verifier called") pubkey, ok := key.(ed25519.PublicKey) if !ok { return fmt.Errorf(`jws.CirclECDSASignerVerifier: invalid key type %T. ed25519.PublicKey is required`, key) } if ed25519.Verify(pubkey, payload, signature) { return nil } return fmt.Errorf(`failed to verify EdDSA signature`) } type LegacyCirclEdDSASignerVerifier struct{} func LegacyNewCirclEdDSASigner() (jws.Signer, error) { return &LegacyCirclEdDSASignerVerifier{}, nil } func LegacyNewCirclEdDSAVerifier() (jws.Verifier, error) { return &LegacyCirclEdDSASignerVerifier{}, nil } func (s LegacyCirclEdDSASignerVerifier) Algorithm() jwa.SignatureAlgorithm { return jwa.EdDSA() } func (s LegacyCirclEdDSASignerVerifier) Sign(payload []byte, keyif any) ([]byte, error) { fmt.Println("Custom signer called (legacy)") switch key := keyif.(type) { case ed25519.PrivateKey: return ed25519.Sign(key, payload), nil default: return nil, fmt.Errorf(`invalid key type %T`, keyif) } } func (s LegacyCirclEdDSASignerVerifier) Verify(payload []byte, signature []byte, keyif any) error { fmt.Println("Custom verifier called (legacy)") switch key := keyif.(type) { case ed25519.PublicKey: if ed25519.Verify(key, payload, signature) { return nil } return fmt.Errorf(`failed to verify EdDSA signature`) default: return fmt.Errorf(`invalid key type %T`, keyif) } } golang-github-lestrrat-go-jwx-3.0.13/examples/jws_example_test.go000066400000000000000000000056531515060566400251370ustar00rootroot00000000000000package examples_test import ( "fmt" "github.com/lestrrat-go/jwx/v3/internal/base64" "github.com/lestrrat-go/jwx/v3/internal/json" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/lestrrat-go/jwx/v3/jws" ) func Example_jws_message() { const payload = `eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ` const encodedSig1 = `cC4hiUPoj9Eetdgtv3hF80EGrhuB__dzERat0XF9g2VtQgr9PJbu3XOiZj5RZmh7AAuHIm4Bh-0Qc_lF5YKt_O8W2Fp5jujGbds9uJdbF9CUAr7t1dnZcAcQjbKBYNX4BAynRFdiuB--f_nZLgrnbyTyWzO75vRK5h6xBArLIARNPvkSjtQBMHlb1L07Qe7K0GarZRmB_eSN9383LcOLn6_dO--xi12jzDwusC-eOkHWEsqtFZESc6BfI7noOPqvhJ1phCnvWh6IeYI2w9QOYEUipUTI8np6LbgGY9Fs98rqVt5AXLIhWkWywlVmtVrBp0igcN_IoypGlUPQGe77Rw` const encodedSig2 = "DtEhU3ljbEg8L38VWAfUAqOyKAM6-Xx-F4GawxaepmXFCgfTjDxw5djxLa8ISlSApmWQxfKTUJqPP3-Kg6NU1Q" decodedPayload, err := base64.DecodeString(payload) if err != nil { fmt.Printf("%s\n", err) return } decodedSig1, err := base64.DecodeString(encodedSig1) if err != nil { fmt.Printf("%s\n", err) return } decodedSig2, err := base64.DecodeString(encodedSig2) if err != nil { fmt.Printf("%s\n", err) return } public1 := jws.NewHeaders() _ = public1.Set(jws.AlgorithmKey, jwa.RS256()) protected1 := jws.NewHeaders() _ = protected1.Set(jws.KeyIDKey, "2010-12-29") public2 := jws.NewHeaders() _ = public2.Set(jws.AlgorithmKey, jwa.ES256()) protected2 := jws.NewHeaders() _ = protected2.Set(jws.KeyIDKey, "e9bc097a-ce51-4036-9562-d2ade882db0d") // Construct a message. DO NOT use values that are base64 encoded m := jws.NewMessage(). SetPayload(decodedPayload). AppendSignature( jws.NewSignature(). SetSignature(decodedSig1). SetProtectedHeaders(public1). SetPublicHeaders(protected1), ). AppendSignature( jws.NewSignature(). SetSignature(decodedSig2). SetProtectedHeaders(public2). SetPublicHeaders(protected2), ) buf, err := json.MarshalIndent(m, "", " ") if err != nil { fmt.Printf("%s\n", err) return } fmt.Printf("%s", buf) // OUTPUT: // { // "payload": "eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ", // "signatures": [ // { // "header": { // "kid": "2010-12-29" // }, // "protected": "eyJhbGciOiJSUzI1NiJ9", // "signature": "cC4hiUPoj9Eetdgtv3hF80EGrhuB__dzERat0XF9g2VtQgr9PJbu3XOiZj5RZmh7AAuHIm4Bh-0Qc_lF5YKt_O8W2Fp5jujGbds9uJdbF9CUAr7t1dnZcAcQjbKBYNX4BAynRFdiuB--f_nZLgrnbyTyWzO75vRK5h6xBArLIARNPvkSjtQBMHlb1L07Qe7K0GarZRmB_eSN9383LcOLn6_dO--xi12jzDwusC-eOkHWEsqtFZESc6BfI7noOPqvhJ1phCnvWh6IeYI2w9QOYEUipUTI8np6LbgGY9Fs98rqVt5AXLIhWkWywlVmtVrBp0igcN_IoypGlUPQGe77Rw" // }, // { // "header": { // "kid": "e9bc097a-ce51-4036-9562-d2ade882db0d" // }, // "protected": "eyJhbGciOiJFUzI1NiJ9", // "signature": "DtEhU3ljbEg8L38VWAfUAqOyKAM6-Xx-F4GawxaepmXFCgfTjDxw5djxLa8ISlSApmWQxfKTUJqPP3-Kg6NU1Q" // } // ] // } } golang-github-lestrrat-go-jwx-3.0.13/examples/jws_filter_advanced_example_test.go000066400000000000000000000071051515060566400303230ustar00rootroot00000000000000package examples_test import ( "fmt" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/lestrrat-go/jwx/v3/jwk" "github.com/lestrrat-go/jwx/v3/jws" ) func Example_jws_header_filter_advanced() { // Create keys for multi-signature JWS key1, err := jwk.Import([]byte(`secret-key-1`)) if err != nil { fmt.Printf("failed to create key1: %s\n", err) return } key2, err := jwk.Import([]byte(`secret-key-2`)) if err != nil { fmt.Printf("failed to create key2: %s\n", err) return } // Create complex headers for first signature headers1 := jws.NewHeaders() headers1.Set(jws.KeyIDKey, "primary-key") headers1.Set("service", "auth-service") headers1.Set("version", "2.1") headers1.Set("security-level", "high") headers1.Set("internal-use", "true") // Create headers for second signature with different custom fields headers2 := jws.NewHeaders() headers2.Set(jws.KeyIDKey, "backup-key") headers2.Set("service", "backup-auth") headers2.Set("datacenter", "us-west") headers2.Set("backup-priority", "1") headers2.Set("internal-use", "false") payload := []byte(`{"action": "login", "timestamp": 1609459200}`) // Create a multi-signature JWS message using JSON serialization signed, err := jws.Sign(payload, jws.WithJSON(), jws.WithKey(jwa.HS256(), key1, jws.WithProtectedHeaders(headers1)), jws.WithKey(jwa.HS256(), key2, jws.WithProtectedHeaders(headers2))) if err != nil { fmt.Printf("failed to sign message: %s\n", err) return } // Parse the signed message parsedMsg, err := jws.Parse(signed) if err != nil { fmt.Printf("failed to parse message: %s\n", err) return } // Advanced filtering scenarios for i, sig := range parsedMsg.Signatures() { originalHeaders := sig.ProtectedHeaders() // Use case 1: Filter by service-related fields serviceFilter := jws.NewHeaderNameFilter("service", "datacenter", "backup-priority") _, err := serviceFilter.Filter(originalHeaders) if err != nil { fmt.Printf("failed to filter service headers: %s\n", err) continue } // Use case 2: Create public headers (remove internal fields) internalFilter := jws.NewHeaderNameFilter("internal-use", "security-level") _, err = internalFilter.Reject(originalHeaders) if err != nil { fmt.Printf("failed to create public headers: %s\n", err) continue } // Use case 3: Combine standard filter with custom filtering standardFilter := jws.StandardHeadersFilter() customFieldsOnly, err := standardFilter.Reject(originalHeaders) if err != nil { fmt.Printf("failed to extract custom fields: %s\n", err) continue } // Then filter custom fields for specific categories operationalFilter := jws.NewHeaderNameFilter("service", "version", "datacenter") _, err = operationalFilter.Filter(customFieldsOnly) if err != nil { fmt.Printf("failed to filter operational headers: %s\n", err) continue } if i == 0 { // Use case 4: Validate security requirements for first signature validateJWSSecurityHeaders(originalHeaders) } } // OUTPUT: } // Helper function to demonstrate validation using filtered JWS headers func validateJWSSecurityHeaders(headers jws.Headers) { // Check security level var secLevel string if err := headers.Get("security-level", &secLevel); err != nil { fmt.Println("✗ Security level not found") } // Check internal use flag var internalUse string if err := headers.Get("internal-use", &internalUse); err != nil { fmt.Println("✗ Internal use flag missing") } // Check service identification var service string if err := headers.Get("service", &service); err != nil { fmt.Println("✗ Service identification missing") } } golang-github-lestrrat-go-jwx-3.0.13/examples/jws_filter_basic_example_test.go000066400000000000000000000036601515060566400276410ustar00rootroot00000000000000package examples_test import ( "fmt" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/lestrrat-go/jwx/v3/jwk" "github.com/lestrrat-go/jwx/v3/jws" ) func Example_jws_header_filter_basic() { // Create a key for signing key, err := jwk.Import([]byte(`my-secret-key`)) if err != nil { fmt.Printf("failed to create key: %s\n", err) return } // Create headers with both standard and custom fields headers := jws.NewHeaders() headers.Set(jws.AlgorithmKey, jwa.HS256()) headers.Set(jws.KeyIDKey, "key-2024") headers.Set(jws.TypeKey, "JWT") headers.Set("custom-claim", "important-data") headers.Set("app-version", "v1.2.3") headers.Set("environment", "production") // Sign with custom headers payload := []byte(`{"user": "alice", "role": "admin"}`) signed, err := jws.Sign(payload, jws.WithKey(jwa.HS256(), key, jws.WithProtectedHeaders(headers))) if err != nil { fmt.Printf("failed to sign: %s\n", err) return } // Parse the signed message to access headers msg, err := jws.Parse(signed) if err != nil { fmt.Printf("failed to parse: %s\n", err) return } originalHeaders := msg.Signatures()[0].ProtectedHeaders() // Filter 1: Extract only custom fields using HeaderNameFilter customFilter := jws.NewHeaderNameFilter("custom-claim", "app-version", "environment") _, err = customFilter.Filter(originalHeaders) if err != nil { fmt.Printf("failed to filter custom headers: %s\n", err) return } // Filter 2: Extract only standard fields using StandardHeadersFilter standardFilter := jws.StandardHeadersFilter() _, err = standardFilter.Filter(originalHeaders) if err != nil { fmt.Printf("failed to filter standard headers: %s\n", err) return } // Filter 3: Remove sensitive custom fields using Reject sensitiveFilter := jws.NewHeaderNameFilter("custom-claim") _, err = sensitiveFilter.Reject(originalHeaders) if err != nil { fmt.Printf("failed to reject sensitive headers: %s\n", err) return } // OUTPUT: } golang-github-lestrrat-go-jwx-3.0.13/examples/jws_parse_example_test.go000066400000000000000000000010211515060566400263120ustar00rootroot00000000000000package examples_test import ( "encoding/json" "fmt" "os" "github.com/lestrrat-go/jwx/v3/jws" ) func Example_jws_parse() { const src = `eyJhbGciOiJIUzI1NiJ9.TG9yZW0gaXBzdW0.idbECxA8ZhQbU0ddZmzdRZxQmHjwvw77lT2bwqGgNMo` msg, err := jws.Parse([]byte(src)) if err != nil { fmt.Printf("failed to parse JWS message: %s\n", err) return } json.NewEncoder(os.Stdout).Encode(msg) // OUTPUT: // {"payload":"TG9yZW0gaXBzdW0","protected":"eyJhbGciOiJIUzI1NiJ9","signature":"idbECxA8ZhQbU0ddZmzdRZxQmHjwvw77lT2bwqGgNMo"} } golang-github-lestrrat-go-jwx-3.0.13/examples/jws_readfile_example_test.go000066400000000000000000000013331515060566400267610ustar00rootroot00000000000000package examples_test import ( "encoding/json" "fmt" "os" "github.com/lestrrat-go/jwx/v3/jws" ) func Example_jws_readfile() { const src = `eyJhbGciOiJIUzI1NiJ9.TG9yZW0gaXBzdW0.idbECxA8ZhQbU0ddZmzdRZxQmHjwvw77lT2bwqGgNMo` f, err := os.CreateTemp(``, `jws_readfile-*.jws`) if err != nil { fmt.Printf("failed to create temporary file: %s\n", err) return } defer os.Remove(f.Name()) fmt.Fprint(f, src) f.Close() msg, err := jws.ReadFile(f.Name()) if err != nil { fmt.Printf("failed to parse JWS message: %s\n", err) return } json.NewEncoder(os.Stdout).Encode(msg) // OUTPUT: // {"payload":"TG9yZW0gaXBzdW0","protected":"eyJhbGciOiJIUzI1NiJ9","signature":"idbECxA8ZhQbU0ddZmzdRZxQmHjwvw77lT2bwqGgNMo"} } golang-github-lestrrat-go-jwx-3.0.13/examples/jws_sign_detached_payload_example_test.go000066400000000000000000000020521515060566400314770ustar00rootroot00000000000000package examples_test import ( "fmt" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/lestrrat-go/jwx/v3/jwk" "github.com/lestrrat-go/jwx/v3/jws" ) func Example_jws_sign_detached_payload() { payload := `$.02` key, err := jwk.Import([]byte(`abracadabra`)) if err != nil { fmt.Printf("failed to create symmetric key: %s\n", err) return } // If you plan to transmit your payload without base64-encoding (RFC 7797), // it's best to set `b64: false` so libraries can act accordingly (e.g. the // popular NodeJS `jose` library requires it set to false in order to verify // a plaintext payload. hdrs := jws.NewHeaders() hdrs.Set("b64", false) hdrs.Set("crit", []string{"b64"}) serialized, err := jws.Sign(nil, jws.WithKey(jwa.HS256(), key, jws.WithProtectedHeaders(hdrs)), jws.WithDetachedPayload([]byte(payload))) if err != nil { fmt.Printf("failed to sign payload: %s\n", err) return } fmt.Printf("%s\n", serialized) // OUTPUT: // eyJhbGciOiJIUzI1NiIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19..lnRw_MSpQjARa5LWqPcu8Qls9p3wYGrC6tz4-nr0rkA } golang-github-lestrrat-go-jwx-3.0.13/examples/jws_sign_example_test.go000066400000000000000000000011101515060566400261370ustar00rootroot00000000000000package examples_test import ( "fmt" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/lestrrat-go/jwx/v3/jwk" "github.com/lestrrat-go/jwx/v3/jws" ) func Example_jws_sign() { key, err := jwk.Import([]byte(`abracadabra`)) if err != nil { fmt.Printf("failed to create key: %s\n", err) return } buf, err := jws.Sign([]byte("Lorem ipsum"), jws.WithKey(jwa.HS256(), key)) if err != nil { fmt.Printf("failed to sign payload: %s\n", err) return } fmt.Printf("%s\n", buf) // OUTPUT: // eyJhbGciOiJIUzI1NiJ9.TG9yZW0gaXBzdW0.EjVtju0uXjSz6QevNgAqN1ESd9aNCP7-tJLifkQ0_C0 } golang-github-lestrrat-go-jwx-3.0.13/examples/jws_sign_json_example_test.go000066400000000000000000000020401515060566400271730ustar00rootroot00000000000000package examples_test import ( "fmt" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/lestrrat-go/jwx/v3/jwk" "github.com/lestrrat-go/jwx/v3/jws" ) func Example_jws_sign_json() { var keys []jwk.Key for i := 0; i < 3; i++ { key, err := jwk.Import([]byte(fmt.Sprintf(`abracadabra-%d`, i))) if err != nil { fmt.Printf("failed to create key: %s\n", err) return } keys = append(keys, key) } options := []jws.SignOption{jws.WithJSON()} for _, key := range keys { options = append(options, jws.WithKey(jwa.HS256(), key)) } buf, err := jws.Sign([]byte("Lorem ipsum"), options...) if err != nil { fmt.Printf("failed to sign payload: %s\n", err) return } fmt.Printf("%s\n", buf) // OUTPUT: // {"payload":"TG9yZW0gaXBzdW0","signatures":[{"protected":"eyJhbGciOiJIUzI1NiJ9","signature":"bCQtU2y4PEnG78dUN-tXea8YEwhBAzLX7ZEYlRVtX_g"},{"protected":"eyJhbGciOiJIUzI1NiJ9","signature":"0ovW79M_bbaRDBrBLaNKN7rgJeXaSRAnu5rhAuRXBR4"},{"protected":"eyJhbGciOiJIUzI1NiJ9","signature":"ZkUzwlK5E6LFKsYEIyUvskOKLMDxE0MvvkvNrwINNWE"}]} } golang-github-lestrrat-go-jwx-3.0.13/examples/jws_sign_with_custom_base64_example_test.go000066400000000000000000000022341515060566400317400ustar00rootroot00000000000000package examples_test import ( "bytes" "encoding/base64" "fmt" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/lestrrat-go/jwx/v3/jws" ) func Example_jws_sign_with_custom_base64() { const payload = "Lorem ipsum" const symmetricKey = "0123456789abcdef0123456789abcdef" signed, err := jws.Sign([]byte(payload), jws.WithKey(jwa.HS256(), []byte(symmetricKey)), jws.WithBase64Encoder(base64.URLEncoding)) if err != nil { fmt.Printf("failed to sign payload: %s\n", err) } fmt.Println(string(signed)) // This should fail because we're using a different base64 encoder if _, err := jws.Verify(signed, jws.WithKey(jwa.HS256(), []byte(symmetricKey))); err == nil { fmt.Printf("verification should have failed, but succeeded\n") return } verified, err := jws.Verify(signed, jws.WithKey(jwa.HS256(), []byte(symmetricKey)), jws.WithBase64Encoder(base64.URLEncoding)) if err != nil { fmt.Printf("failed to verify payload: %s\n", err) return } if !bytes.Equal([]byte(payload), verified) { fmt.Printf("verified content do not match: %s\n", err) return } // OUTPUT: // eyJhbGciOiJIUzI1NiJ9.TG9yZW0gaXBzdW0=.DahnsWNiVXmt23d2nUx_ePAZLy2nodC-Oh0bIB88cck= } golang-github-lestrrat-go-jwx-3.0.13/examples/jws_sign_with_headers_example_test.go000066400000000000000000000013031515060566400306710ustar00rootroot00000000000000package examples_test import ( "fmt" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/lestrrat-go/jwx/v3/jwk" "github.com/lestrrat-go/jwx/v3/jws" ) func Example_jws_sign_with_headers() { key, err := jwk.Import([]byte(`abracadabra`)) if err != nil { fmt.Printf("failed to create key: %s\n", err) return } hdrs := jws.NewHeaders() hdrs.Set(`x-example`, true) buf, err := jws.Sign([]byte("Lorem ipsum"), jws.WithKey(jwa.HS256(), key, jws.WithProtectedHeaders(hdrs))) if err != nil { fmt.Printf("failed to sign payload: %s\n", err) return } fmt.Printf("%s\n", buf) // OUTPUT: // eyJhbGciOiJIUzI1NiIsIngtZXhhbXBsZSI6dHJ1ZX0.TG9yZW0gaXBzdW0.9nIX0hN7u1b97UcjmrVvd5y1ubkQp_1gz1V3Mkkcm14 } golang-github-lestrrat-go-jwx-3.0.13/examples/jws_use_jws_header_test.go000066400000000000000000000022461515060566400264660ustar00rootroot00000000000000package examples_test import ( "fmt" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/lestrrat-go/jwx/v3/jwk" "github.com/lestrrat-go/jwx/v3/jws" "github.com/lestrrat-go/jwx/v3/jwt" ) func Example_jws_use_jws_header() { key, err := jwk.Import([]byte(`abracadabra`)) if err != nil { fmt.Printf(`failed to create new symmetric key: %s`, err) return } key.Set(jws.KeyIDKey, `secret-key`) tok, err := jwt.NewBuilder(). Issuer(`github.com/lestrrat-go/jwx`). Build() if err != nil { fmt.Printf(`failed to build token: %s`, err) return } signed, err := jwt.Sign(tok, jwt.WithKey(jwa.HS256(), key)) if err != nil { fmt.Printf(`failed to sign token: %s`, err) return } msg, err := jws.Parse(signed) if err != nil { fmt.Printf(`failed to parse serialized JWT: %s`, err) return } // While JWT enveloped with JWS in compact format only has 1 signature, // a generic JWS message may have multiple signatures. Therefore, we // need to access the first element kid, ok := msg.Signatures()[0].ProtectedHeaders().KeyID() if !ok { fmt.Printf("failed to get key ID from protected headers") return } fmt.Printf("%q\n", kid) // OUTPUT: // "secret-key" } golang-github-lestrrat-go-jwx-3.0.13/examples/jws_verify_detached_payload_example_test.go000066400000000000000000000013321515060566400320430ustar00rootroot00000000000000package examples_test import ( "fmt" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/lestrrat-go/jwx/v3/jwk" "github.com/lestrrat-go/jwx/v3/jws" ) func Example_jws_verify_detached_payload() { serialized := `eyJhbGciOiJIUzI1NiIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19..lnRw_MSpQjARa5LWqPcu8Qls9p3wYGrC6tz4-nr0rkA` payload := `$.02` key, err := jwk.Import([]byte(`abracadabra`)) if err != nil { fmt.Printf("failed to create symmetric key: %s\n", err) return } verified, err := jws.Verify([]byte(serialized), jws.WithKey(jwa.HS256(), key), jws.WithDetachedPayload([]byte(payload))) if err != nil { fmt.Printf("failed to verify payload: %s\n", err) return } fmt.Printf("%s\n", verified) // OUTPUT: // $.02 } golang-github-lestrrat-go-jwx-3.0.13/examples/jws_verify_with_key_example_test.go000066400000000000000000000011511515060566400304130ustar00rootroot00000000000000package examples_test import ( "fmt" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/lestrrat-go/jwx/v3/jwk" "github.com/lestrrat-go/jwx/v3/jws" ) func Example_jws_verify_with_key() { const src = `eyJhbGciOiJIUzI1NiJ9.TG9yZW0gaXBzdW0.EjVtju0uXjSz6QevNgAqN1ESd9aNCP7-tJLifkQ0_C0` key, err := jwk.Import([]byte(`abracadabra`)) if err != nil { fmt.Printf("failed to create key: %s\n", err) return } buf, err := jws.Verify([]byte(src), jws.WithKey(jwa.HS256(), key)) if err != nil { fmt.Printf("failed to verify payload: %s\n", err) return } fmt.Printf("%s\n", buf) // OUTPUT: // Lorem ipsum } golang-github-lestrrat-go-jwx-3.0.13/examples/jws_verify_with_keyset_example_test.go000066400000000000000000000070671515060566400311430ustar00rootroot00000000000000package examples_test import ( "fmt" "github.com/lestrrat-go/jwx/v3/internal/jwxtest" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/lestrrat-go/jwx/v3/jwk" "github.com/lestrrat-go/jwx/v3/jws" ) func Example_jws_verify_with_jwk_set() { // Setup payload, private key, and signed payload first... const payload = "Lorem ipsum" privkey, err := jwxtest.GenerateRsaJwk() if err != nil { fmt.Printf("failed to create private key: %s\n", err) return } const keyID = "correct-key" _ = privkey.Set(jwk.KeyIDKey, keyID) // Create a JWK Set set := jwk.NewSet() // Add some bogus keys k1, _ := jwk.Import([]byte("abracadabra")) _ = set.AddKey(k1) _ = k1.Set(jwk.KeyIDKey, "key-01") k2, _ := jwk.Import([]byte("opensesame")) _ = set.AddKey(k2) _ = k1.Set(jwk.KeyIDKey, "key-02") // AddKey the real thing. Note that k3 already contains the Key ID because // jwk.PublicKeyOf(jwk) automatically sets the Key ID if it is present in the private key. k3, _ := jwk.PublicKeyOf(privkey) _ = set.AddKey(k3) // Up to this point, you probably will replace with a simple jwk.Fetch() // or similar to obtain the JWKS // Sign with a key that has a Key ID. This forces jws.Sign() to include its // key ID in the JWS header. signed, err := jws.Sign([]byte(payload), jws.WithKey(jwa.RS256(), privkey)) if err != nil { fmt.Printf("failed to sign payload: %s\n", err) return } // Now let's try to verify the signed payload under various conditions { // No Key ID, nor algorithm present in the key} k3.Remove(jwk.KeyIDKey) // Remove Key ID so that it won't work // This fails, because it's going to try to lookup a key to use using the Key ID, // but it can't be found if _, err := jws.Verify(signed, jws.WithKeySet(set)); err == nil { fmt.Printf("Able to verify using jwk.Set, when it shouldn't: %s", err) return } // Now let's add a WithRequireKid(false). This will STILL fail, because the key // matching the key ID doesn't have an algorithm value set. if _, err := jws.Verify(signed, jws.WithKeySet(set, jws.WithRequireKid(false))); err == nil { fmt.Printf("Able to verify using jwk.Set, when it shouldn't: %s", err) return } // This works, because we're telling it to infer the algorithm by the // key type. if _, err := jws.Verify(signed, jws.WithKeySet(set, jws.WithInferAlgorithmFromKey(true), jws.WithRequireKid(false))); err != nil { fmt.Printf("Failed to verify using jwk.Set: %s", err) return } } { // Key ID present, but no algorithm k3.Set(jwk.KeyIDKey, keyID) // Add Key ID back // This does not work because the while the library can find // a key matching the key ID, it doesn't know what algorithm to use if _, err := jws.Verify(signed, jws.WithKeySet(set)); err == nil { fmt.Printf("Able to verify using jwk.Set, when it shouldn't: %s", err) return } // This works, because the library can find a key matching the key ID, // and it can infer the algorithm from the key type if _, err := jws.Verify(signed, jws.WithKeySet(set, jws.WithInferAlgorithmFromKey(true))); err != nil { fmt.Printf("Failed to verify using jwk.Set: %s", err) return } } { // both Key ID and algorithm present // And finally, the "cleanest" way. k3.Set(jwk.AlgorithmKey, jwa.RS256()) // Set algorithm if _, err := jws.Verify(signed, jws.WithKeySet(set)); err != nil { fmt.Printf("Failed to verify using jwk.Set: %s", err) return } } // If you just can't do this (e.g. because your token doesn't have a Key ID), // then you're better off using the single-key option jws.WithKey() (or the jwt.WithKey() option) // OUTPUT: } golang-github-lestrrat-go-jwx-3.0.13/examples/jwt_builder_example_test.go000066400000000000000000000011471515060566400266400ustar00rootroot00000000000000package examples_test import ( "encoding/json" "fmt" "os" "github.com/lestrrat-go/jwx/v3/jwt" ) func Example_jwt_builder() { tok, err := jwt.NewBuilder(). Claim(`claim1`, `value1`). Claim(`claim2`, `value2`). Issuer(`github.com/lestrrat-go/jwx`). Audience([]string{`users`}). Build() if err != nil { fmt.Printf("failed to build token: %s\n", err) return } if err := json.NewEncoder(os.Stdout).Encode(tok); err != nil { fmt.Printf("failed to encode to JSON: %s\n", err) return } // OUTPUT: // {"aud":["users"],"claim1":"value1","claim2":"value2","iss":"github.com/lestrrat-go/jwx"} } golang-github-lestrrat-go-jwx-3.0.13/examples/jwt_construct_example_test.go000066400000000000000000000011371515060566400272350ustar00rootroot00000000000000package examples_test import ( "encoding/json" "fmt" "os" "github.com/lestrrat-go/jwx/v3/jwt" ) func Example_jwt_construct() { tok := jwt.New() if err := tok.Set(jwt.IssuerKey, `github.com/lestrrat-go/jwx`); err != nil { fmt.Printf("failed to set claim: %s\n", err) return } if err := tok.Set(jwt.AudienceKey, `users`); err != nil { fmt.Printf("failed to set claim: %s\n", err) return } if err := json.NewEncoder(os.Stdout).Encode(tok); err != nil { fmt.Printf("failed to encode to JSON: %s\n", err) return } // OUTPUT: // {"aud":["users"],"iss":"github.com/lestrrat-go/jwx"} } golang-github-lestrrat-go-jwx-3.0.13/examples/jwt_example_test.go000066400000000000000000000231021515060566400251250ustar00rootroot00000000000000package examples_test import ( "crypto/rand" "crypto/rsa" "fmt" "log" "time" "github.com/lestrrat-go/jwx/v3/internal/json" "github.com/lestrrat-go/jwx/v3/jwk" "github.com/lestrrat-go/jwx/v3/jws" "github.com/lestrrat-go/jwx/v3/jwt/openid" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/lestrrat-go/jwx/v3/jwt" ) const aLongLongTimeAgo = 233431200 const exampleJWTSignedHMAC = `eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk` const exampleJWTSignedRSA = `eyJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.cC4hiUPoj9Eetdgtv3hF80EGrhuB__dzERat0XF9g2VtQgr9PJbu3XOiZj5RZmh7AAuHIm4Bh-0Qc_lF5YKt_O8W2Fp5jujGbds9uJdbF9CUAr7t1dnZcAcQjbKBYNX4BAynRFdiuB--f_nZLgrnbyTyWzO75vRK5h6xBArLIARNPvkSjtQBMHlb1L07Qe7K0GarZRmB_eSN9383LcOLn6_dO--xi12jzDwusC-eOkHWEsqtFZESc6BfI7noOPqvhJ1phCnvWh6IeYI2w9QOYEUipUTI8np6LbgGY9Fs98rqVt5AXLIhWkWywlVmtVrBp0igcN_IoypGlUPQGe77Rw` const exampleJWTSignedECDSA = `eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.DtEhU3ljbEg8L38VWAfUAqOyKAM6-Xx-F4GawxaepmXFCgfTjDxw5djxLa8ISlSA` //nolint:govet func Example_jwt_parse_with_jwks() { privKey, err := rsa.GenerateKey(rand.Reader, 2048) if err != nil { fmt.Printf("failed to generate private key: %s\n", err) return } { // Case 2: For whatever reason, we don't have a "kid" specified. // Normally, this is an error, because we don't know how to select a key. // But if we have only one key in the KeySet, you can explicitly ask // jwt.Parse to "trust" the KeySet, and use the single key in the // key set. It would be an error if you have multiple keys in the KeySet. var payload []byte var keyset jwk.Set { // Preparation: // Unlike our previous example, we DO NOT want to sign the payload. // Therefore we do NOT set the "kid" value realKey, err := jwk.Import(privKey) if err != nil { fmt.Printf("failed to create JWK: %s\n", err) return } // Create the token token := jwt.New() token.Set(`foo`, `bar`) // Sign the token and generate a payload signed, err := jwt.Sign(token, jwt.WithKey(jwa.RS256(), realKey)) if err != nil { fmt.Printf("failed to generate signed payload: %s\n", err) return } // This is what you typically get as a signed JWT from a server payload = signed // Now create a key set that users will use to verity the signed payload against // Normally these keys are available somewhere like https://www.googleapis.com/oauth2/v3/certs pubKey, err := jwk.Import(privKey.PublicKey) if err != nil { fmt.Printf("failed to create JWK: %s\n", err) return } pubKey.Set(jwk.AlgorithmKey, jwa.RS256()) // This JWKS can *only* have 1 key. keyset = jwk.NewSet() keyset.AddKey(pubKey) } { token, err := jwt.Parse( payload, // Tell the parser that you want to use this keyset jwt.WithKeySet(keyset, // Tell the parser that you can trust this KeySet, and that // you want to use the sole key in it jws.WithUseDefault(true), ), ) if err != nil { fmt.Printf("failed to parse payload: %s\n", err) } _ = token } } // OUTPUT: } // This example return a signed jwt func Example_jwt_sign_with_import_jwk() { // your JWK jwkStr := `{ "kty": "RSA", "n": "mmO0OvOPQ53HRxV4eHOkTTxLVfk6zcq8KAD86gbnydYBNO_Si4Q1twyvefd58-BaO4N4NCEA97QrYm57ThKCe8agLGwWPHhxgbu_SAuYQehXxkf4sWy7Q17kGFG5k5AfQGZBqTY-YaawQqLlF6ILVbWab_AoEF4yB7pI3AnNnXs", "e": "AQAB", "d": "RzsrI2vONJcuIyjPzVslehEQfRkhPWOFTjuudNc8yA25vs_LZ11XXx42M-KvXIqtdvngUsTLan2w6pgowcuecX3t_2wUx0GJJgARfkN7gsWIS3CyXZBEEMjLGVU4vHt5zNE3GJKo3hb1TwEiulpL_Ix6hfcTSJpEaBWrBxjxV-E", "p": "5EA0bi6ui1H1wsG85oc7i9O7UH58WPIK_ytzBWXFIwcaSFFBqqNYNnZaHFsMe4cbHSBgShWHO3UueGVgOKmB8Q", "q": "rSi7CosQZmj_RFIYW10ef7XTZsdpIdOXV9-1dThAJUvkslKiTfdU7T0IYYsJ2K58ekJqdpcoKAVLB2SZVvdqKw", "dp": "S9yjEHPng1qsShzGQgB0ZBbtTOWdQpq_2OuCAStACFJWA-8t2h8MNJ3FeWMxlOTkuBuIpVbeaX6bAV0ATBTaoQ", "dq": "ZssMJhkh1jm0d-FoVix0Y4oUAiqUzaDnciH6faiz47AnBnkporEV-HPH2ugII1qJyKZOvzHCg-eIf84HfWoI2w", "qi": "lyVz1HI2b1IjzOMENkmUTaVEO6DM6usZi3c3_MobUUM05yyBhnHtPjWzqWn1uJ_Gt5bkJDdcpfvmkPAhKWEU9Q" }` // create a new jwt t := jwt.New() t.Set(jwt.SubjectKey, `https://github.com/lestrrat-go/jwx/v3/jwt`) t.Set(jwt.AudienceKey, `Golang Users`) t.Set(jwt.IssuedAtKey, time.Unix(500, 0)) buf, err := json.MarshalIndent(t, "", " ") if err != nil { fmt.Printf("failed to generate JSON: %s\n", err) return } fmt.Printf("%s\n", buf) var pc string if err := t.Get(`privateClaimKey`, &pc); err != nil { fmt.Printf("failed to fetch private claim\n") return } fmt.Printf("privateClaimKey -> '%s'\n", pc) //convert jwk in bytes and return a new key jwkey, err := jwk.ParseKey([]byte(jwkStr)) if err != nil { fmt.Printf("failed to parse key: %s\n", err) return } // signed and return a jwt signed, _ := jwt.Sign(t, jwt.WithKey(jwa.RS256(), jwkey)) fmt.Println(string(signed[:])) // output // a signed jwt based on jwk } func Example_jwt_sign() { privKey, err := rsa.GenerateKey(rand.Reader, 2048) if err != nil { fmt.Printf("failed to generate private key: %s\n", err) return } var payload []byte { // Create signed payload token := jwt.New() token.Set(`foo`, `bar`) payload, err = jwt.Sign(token, jwt.WithKey(jwa.RS256(), privKey)) if err != nil { fmt.Printf("failed to generate signed payload: %s\n", err) return } } { // Parse signed payload, and perform (1) verification of the signature // and (2) validation of the JWT token // Validation can be performed in a separate step using `jwt.Validate` token, err := jwt.Parse( payload, jwt.WithValidate(true), jwt.WithKey(jwa.RS256(), &privKey.PublicKey), ) if err != nil { fmt.Printf("failed to parse JWT token: %s\n", err) return } buf, err := json.MarshalIndent(token, "", " ") if err != nil { fmt.Printf("failed to generate JSON: %s\n", err) return } fmt.Printf("%s\n", buf) } // OUTPUT: // { // "foo": "bar" // } } func Example_jwt_token() { t := jwt.New() t.Set(jwt.SubjectKey, `https://github.com/lestrrat-go/jwx/v3/jwt`) t.Set(jwt.AudienceKey, `Golang Users`) t.Set(jwt.IssuedAtKey, time.Unix(aLongLongTimeAgo, 0)) t.Set(`privateClaimKey`, `Hello, World!`) buf, err := json.MarshalIndent(t, "", " ") if err != nil { fmt.Printf("failed to generate JSON: %s\n", err) return } fmt.Printf("%s\n", buf) aud, ok := t.Audience() if !ok { fmt.Printf("failed to fetch audience\n") return } fmt.Printf("aud -> '%s'\n", aud) iat, ok := t.IssuedAt() if !ok { fmt.Printf("failed to fetch issued at\n") return } fmt.Printf("iat -> '%s'\n", iat.Format(time.RFC3339)) var pc string if err := t.Get(`privateClaimKey`, &pc); err != nil { fmt.Printf("failed to fetch private claim\n") return } fmt.Printf("privateClaimKey -> '%s'\n", pc) sub, ok := t.Subject() if !ok { fmt.Printf("failed to fetch subject\n") return } fmt.Printf("sub -> '%s'\n", sub) // OUTPUT: // { // "aud": [ // "Golang Users" // ], // "iat": 233431200, // "privateClaimKey": "Hello, World!", // "sub": "https://github.com/lestrrat-go/jwx/v3/jwt" // } // aud -> '[Golang Users]' // iat -> '1977-05-25T18:00:00Z' // privateClaimKey -> 'Hello, World!' // sub -> 'https://github.com/lestrrat-go/jwx/v3/jwt' } func Example_jwt_sign_token() { key, err := rsa.GenerateKey(rand.Reader, 2048) if err != nil { log.Printf("failed to generate private key: %s", err) return } t := jwt.New() { // Signing a token (using raw rsa.PrivateKey) signed, err := jwt.Sign(t, jwt.WithKey(jwa.RS256(), key)) if err != nil { log.Printf("failed to sign token: %s", err) return } _ = signed } { // Signing a token (using JWK) jwkKey, err := jwk.Import(key) if err != nil { log.Printf("failed to create JWK key: %s", err) return } signed, err := jwt.Sign(t, jwt.WithKey(jwa.RS256(), jwkKey)) if err != nil { log.Printf("failed to sign token: %s", err) return } _ = signed } // OUTPUT: } func Example_jwt_openid_token() { t := openid.New() t.Set(jwt.SubjectKey, `https://github.com/lestrrat-go/jwx/v3/jwt`) t.Set(jwt.AudienceKey, `Golang Users`) t.Set(jwt.IssuedAtKey, time.Unix(aLongLongTimeAgo, 0)) t.Set(`privateClaimKey`, `Hello, World!`) addr := openid.NewAddress() addr.Set(openid.AddressPostalCodeKey, `105-0011`) addr.Set(openid.AddressCountryKey, `æ—ĨæœŦ`) addr.Set(openid.AddressRegionKey, `æąäēŦéƒŊ`) addr.Set(openid.AddressLocalityKey, `港åŒē`) addr.Set(openid.AddressStreetAddressKey, `芝å…Ŧ園 4-2-8`) if err := t.Set(openid.AddressKey, addr); err != nil { fmt.Printf("failed to set address: %s\n", err) return } buf, err := json.MarshalIndent(t, "", " ") if err != nil { fmt.Printf("failed to generate JSON: %s\n", err) return } fmt.Printf("%s\n", buf) t2, err := jwt.Parse(buf, jwt.WithToken(openid.New()), jwt.WithVerify(false), jwt.WithValidate(false)) if err != nil { fmt.Printf("failed to parse JSON: %s\n", err) return } if _, ok := t2.(openid.Token); !ok { fmt.Printf("using jwt.WithToken(openid.New()) creates an openid.Token instance") return } // OUTPUT: // { // "address": { // "country": "æ—ĨæœŦ", // "locality": "港åŒē", // "postal_code": "105-0011", // "region": "æąäēŦéƒŊ", // "street_address": "芝å…Ŧ園 4-2-8" // }, // "aud": [ // "Golang Users" // ], // "iat": 233431200, // "privateClaimKey": "Hello, World!", // "sub": "https://github.com/lestrrat-go/jwx/v3/jwt" // } } golang-github-lestrrat-go-jwx-3.0.13/examples/jwt_filter_advanced_example_test.go000066400000000000000000000050521515060566400303230ustar00rootroot00000000000000package examples_test import ( "fmt" "time" "github.com/lestrrat-go/jwx/v3/jwt" ) func Example_jwt_filter_advanced_use_cases() { // Create a comprehensive token with various types of claims token, err := jwt.NewBuilder(). Issuer("auth-service.example.com"). Subject("user-456"). Audience([]string{"web-app", "mobile-app", "api-gateway"}). IssuedAt(time.Unix(1234567890, 0)). Expiration(time.Unix(1234567890+7200, 0)). NotBefore(time.Unix(1234567890, 0)). JwtID("session-xyz789"). Claim("userRole", "manager"). Claim("department", "sales"). Claim("permissions", []string{"read:reports", "write:orders", "approve:discounts"}). Claim("profile", map[string]any{ "name": "John Doe", "email": "john@example.com", "phone": "+1-555-0123", }). Claim("sessionInfo", map[string]any{ "loginIP": "10.0.1.100", "deviceType": "desktop", "browser": "Chrome/91.0", "lastActivity": "2023-01-01T12:30:00Z", }). Claim("features", []string{"beta-ui", "advanced-analytics", "mobile-push"}). Build() if err != nil { fmt.Printf("failed to build comprehensive token: %s\n", err) return } // Use case 1: Create a token for public APIs (remove sensitive information) sensitiveFilter := jwt.NewClaimNameFilter("sessionInfo", "profile") if _, err := sensitiveFilter.Reject(token); err != nil { fmt.Printf("failed to create public API token: %s\n", err) return } // Use case 2: Create an identity-only token (only user identification claims) identityFilter := jwt.NewClaimNameFilter("sub", "iss", "userRole", "department") if _, err := identityFilter.Filter(token); err != nil { fmt.Printf("failed to create identity token: %s\n", err) return } // Use case 3: Create a minimal security token (only time-based and security claims) securityFilter := jwt.NewClaimNameFilter("iss", "sub", "aud", "exp", "iat", "nbf", "jti") if _, err := securityFilter.Filter(token); err != nil { fmt.Printf("failed to create security token: %s\n", err) return } // Use case 4: Combine filters - remove both standard claims and specific custom claims standardFilter := jwt.StandardClaimsFilter() tempToken, err := standardFilter.Reject(token) // Remove standard claims first if err != nil { fmt.Printf("failed to remove standard claims: %s\n", err) return } // Then remove specific custom claims customSensitiveFilter := jwt.NewClaimNameFilter("sessionInfo", "profile") if _, err := customSensitiveFilter.Reject(tempToken); err != nil { fmt.Printf("failed to remove custom sensitive claims: %s\n", err) return } // OUTPUT: } golang-github-lestrrat-go-jwx-3.0.13/examples/jwt_filter_basic_example_test.go000066400000000000000000000031411515060566400276340ustar00rootroot00000000000000package examples_test import ( "fmt" "time" "github.com/lestrrat-go/jwx/v3/jwt" ) func Example_jwt_filter_basic_claims() { // Create a token with standard and custom claims token, err := jwt.NewBuilder(). Issuer("github.com/lestrrat-go/jwx"). Subject("jwt_filter_example"). Audience([]string{"developers", "users"}). IssuedAt(time.Unix(1234567890, 0)). Expiration(time.Unix(1234567890+3600, 0)). Claim("customClaim", "customValue"). Claim("applicationRole", "admin"). Claim("department", "engineering"). Build() if err != nil { fmt.Printf("failed to build token: %s\n", err) return } // Create a custom claim name filter customFilter := jwt.NewClaimNameFilter("customClaim", "applicationRole", "department") // Filter to get only custom claims if _, err := customFilter.Filter(token); err != nil { fmt.Printf("failed to filter custom claims: %s\n", err) return } // You could also use Reject to get all claims except the specified ones // Note that this may include other non-standard claims if _, err := customFilter.Reject(token); err != nil { fmt.Printf("failed to reject custom claims: %s\n", err) return } // Use StandardClaimsFilter to get only standard JWT claims if _, err = jwt.StandardClaimsFilter().Filter(token); err != nil { fmt.Printf("failed to filter standard claims: %s\n", err) return } // Use StandardClaimsFilter to reject standard claims, resulting // in every non-standard claim being retained if _, err = jwt.StandardClaimsFilter().Reject(token); err != nil { fmt.Printf("failed to reject standard claims: %s\n", err) return } // OUTPUT: } golang-github-lestrrat-go-jwx-3.0.13/examples/jwt_flatten_audience_example_test.go000066400000000000000000000050171515060566400305040ustar00rootroot00000000000000package examples_test import ( "encoding/json" "fmt" "os" "github.com/lestrrat-go/jwx/v3/jwt" ) func Example_jwt_flatten_Audience() { // Sometimes you need to "flatten" the "aud" claim because of // parsers developed by people who apparently didn't read the RFC. // // In such cases, you can control the behavior of the JSON // emitted when tokens are converted to JSON by tweaking the // per-token options set. { // Case 1: the per-object way tok, err := jwt.NewBuilder(). Audience([]string{`foo`}). Build() if err != nil { fmt.Printf("failed to build token: %s\n", err) return } // Only this particular instance of the token is affected tok.Options().Enable(jwt.FlattenAudience) json.NewEncoder(os.Stdout).Encode(tok) } { // Case 2: globally enabling flattened audience // NOTE: This example DOES NOT flatten the audience // because the call to change this global settings has been // commented out. Setting this has GLOBAL effects, and would // alter the output of other examples. // // If you would like to try this, UNCOMMENT the line below // // // UNCOMMENT THIS LINE BELOW // jwt.Settings(jwt.WithFlattenAudience(true)) // // ...and if you are running from the examples directory, run // this example in isolation by invoking // // go test -run=ExampleJWT_FlattenAudience // // You may see the example fail, but that's because the OUTPUT line // expects the global settings to be DISABLED. In order to make // the example pass, change the second line from OUTPUT below // // from: {"aud":["foo"]} // to : {"aud":"foo"} // // Please note that it is recommended you ONLY set the jwt.Settings(jwt.WithFlattenedAudience(true)) // once at the beginning of your main program (probably in an `init()` function) // so that you do not need to worry about causing issues depending // on when tokens are created relative to the time when // the global setting is changed. tok, err := jwt.NewBuilder(). Audience([]string{`foo`}). Build() if err != nil { fmt.Printf("failed to build token: %s\n", err) return } // This would flatten the "aud" claim if the appropriate // line above has been uncommented json.NewEncoder(os.Stdout).Encode(tok) // This would force this particular object not to flatten the // "aud" claim. All other tokens would be constructed with the // option enabled tok.Options().Enable(jwt.FlattenAudience) json.NewEncoder(os.Stdout).Encode(tok) } // OUTPUT: // {"aud":"foo"} // {"aud":["foo"]} // {"aud":"foo"} } golang-github-lestrrat-go-jwx-3.0.13/examples/jwt_get_claims_example_test.go000066400000000000000000000051231515060566400273170ustar00rootroot00000000000000package examples_test import ( "encoding/json" "fmt" "time" "github.com/lestrrat-go/jwx/v3/jwk" "github.com/lestrrat-go/jwx/v3/jwt" ) func Example_jwt_get_claims() { tok, err := jwt.NewBuilder(). IssuedAt(time.Now()). Issuer(`github.com/lestrrat-go/jwx`). Subject(`example`). Claim(`claim1`, `value1`). Claim(`claim2`, `2022-05-16T07:35:56+00:00`). Claim(`claim3`, `{"kty": "oct", "alg":"A128KW", "k":"GawgguFyGrWKav7AX4VKUg"}`). Build() if err != nil { fmt.Printf("failed to build token: %s\n", err) return } // Pre-defined fields have typed accessors. iat, _ := tok.IssuedAt() iss, _ := tok.Issuer() sub, _ := tok.Subject() var _ time.Time = iat var _ string = iss var _ string = sub // But you can also get them via the generic `.Get()` method. // However, you would need to decide for yourself what the // return type is. If you don't need the exact type, you could // use any, or you could use the specific time.Time // type // // For the key name you could also use jwt.IssuedAtKey constant _ = tok.Get(`iat`, &iat) // var iat any would also work, but you would need to // convert the type if you need time.Time specific behavior // Private claims var dummy any _ = tok.Get(`claim1`, &dummy) _ = tok.Get(`claim2`, &dummy) _ = tok.Get(`claim3`, &dummy) // However, it is possible to globally specify that a private // claim should be parsed into a custom type. // In the sample below `claim2` is to be an instance of time.Time jwt.RegisterCustomField(`claim2`, time.Time{}) tok = jwt.New() if err := json.Unmarshal([]byte(`{"claim2":"2022-05-16T07:35:56+00:00"}`), tok); err != nil { fmt.Printf(`failed to parse token: %s`, err) return } // now you can use the exact type var claim2 time.Time if err := tok.Get(`claim2`, &claim2); err != nil { fmt.Printf("failed to get private claim \"claim2\": %s\n", err) return } // It's also possible to specify a custom decoder for a private claim. // For example, in the case of `claim3`, it needs to call `jwk.ParseKey` // which returns an interface that can't be instantiated like the // `time.Time` value for `claim2`. jwt.RegisterCustomField(`claim3`, jwt.CustomDecodeFunc(func(data []byte) (any, error) { return jwk.ParseKey(data) })) tok = jwt.New() if err := json.Unmarshal([]byte(`{"claim3": {"kty": "oct", "alg":"A128KW", "k":"GawgguFyGrWKav7AX4VKUg"}}`), tok); err != nil { fmt.Printf(`failed to parse token: %s`, err) return } var claim3 jwk.Key if err := tok.Get(`claim3`, &claim3); err != nil { fmt.Printf("failed to get private claim \"claim3\": %s\n", err) return } // OUTPUT: } golang-github-lestrrat-go-jwx-3.0.13/examples/jwt_parse_example_test.go000066400000000000000000000004641515060566400263250ustar00rootroot00000000000000package examples_test import ( "fmt" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/lestrrat-go/jwx/v3/jwt" ) func Example_jwt_parse() { tok, err := jwt.Parse(jwtSignedWithHS256, jwt.WithKey(jwa.HS256(), jwkSymmetricKey)) if err != nil { fmt.Printf("%s\n", err) return } _ = tok // OUTPUT: } golang-github-lestrrat-go-jwx-3.0.13/examples/jwt_parse_request_example_test.go000066400000000000000000000036021515060566400300720ustar00rootroot00000000000000package examples_test import ( "fmt" "net/http" "net/url" "github.com/lestrrat-go/jwx/v3/jwt" ) func Example_jwt_parse_request_authorization() { req, err := http.NewRequest(http.MethodGet, `https://github.com/lestrrat-go/jwx`, nil) if err != nil { fmt.Printf("failed to create request: %s\n", err) return } req.Form = url.Values{} req.Form.Add("access_token", exampleJWTSignedHMAC) req.Header.Set(`Authorization`, fmt.Sprintf(`Bearer %s`, exampleJWTSignedECDSA)) req.Header.Set(`X-JWT-Token`, exampleJWTSignedRSA) req.AddCookie(&http.Cookie{Name: "accessToken", Value: exampleJWTSignedHMAC}) var dst *http.Cookie testcases := []struct { options []jwt.ParseOption }{ // No options - looks under "Authorization" header {}, // Looks under "X-JWT-Token" header only { options: []jwt.ParseOption{jwt.WithHeaderKey(`X-JWT-Token`)}, }, // Looks under "Authorization" and "X-JWT-Token" headers { options: []jwt.ParseOption{jwt.WithHeaderKey(`Authorization`), jwt.WithHeaderKey(`X-JWT-Token`)}, }, // Looks under "Authorization" header and "access_token" form field { options: []jwt.ParseOption{jwt.WithFormKey(`access_token`)}, }, // Looks under "accessToken" cookie, and assigns the http.Cookie object // where the token came from to the variable `dst` { options: []jwt.ParseOption{jwt.WithCookieKey(`accessToken`), jwt.WithCookie(&dst)}, }, } for _, tc := range testcases { options := append(tc.options, []jwt.ParseOption{jwt.WithVerify(false), jwt.WithValidate(false)}...) tok, err := jwt.ParseRequest(req, options...) if err != nil { fmt.Print("jwt.ParseRequest with options [") for i, option := range tc.options { if i > 0 { fmt.Print(", ") } fmt.Printf("%s", option) } fmt.Printf("]: %s\n", err) return } _ = tok } if dst == nil { fmt.Printf("failed to assign cookie to dst\n") return } // OUTPUT: } golang-github-lestrrat-go-jwx-3.0.13/examples/jwt_parse_with_jku_example_test.go000066400000000000000000000040731515060566400302310ustar00rootroot00000000000000package examples_test import ( "crypto/rand" "crypto/rsa" "encoding/json" "fmt" "net/http" "net/http/httptest" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/lestrrat-go/jwx/v3/jwk" "github.com/lestrrat-go/jwx/v3/jws" "github.com/lestrrat-go/jwx/v3/jwt" ) func Example_jwt_parse_with_jku() { set := jwk.NewSet() var signingKey jwk.Key // for _, alg := range algorithms { for i := 0; i < 3; i++ { pk, err := rsa.GenerateKey(rand.Reader, 2048) if err != nil { fmt.Printf("failed to generate private key: %s\n", err) return } // too lazy to write a proper algorithm. just assign every // time, and signingKey will end up being the last key generated privkey, err := jwk.Import(pk) if err != nil { fmt.Printf("failed to create jwk.Key: %s\n", err) return } privkey.Set(jwk.KeyIDKey, fmt.Sprintf(`key-%d`, i)) // It is important that we are using jwk.Key here instead of // rsa.PrivateKey, because this way `kid` is automatically // assigned when we sign the token signingKey = privkey pubkey, err := privkey.PublicKey() if err != nil { fmt.Printf("failed to create public key: %s\n", err) return } set.AddKey(pubkey) } srv := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(set) })) defer srv.Close() // Create a JWT token := jwt.New() token.Set(`foo`, `bar`) hdrs := jws.NewHeaders() hdrs.Set(jws.JWKSetURLKey, srv.URL) serialized, err := jwt.Sign(token, jwt.WithKey(jwa.RS256(), signingKey, jws.WithProtectedHeaders(hdrs))) if err != nil { fmt.Printf("failed to seign token: %s\n", err) return } // We need to pass jwk.WithHTTPClient because we are using HTTPS, // and we need the certificates setup // We also need to explicitly set up the whitelist, this is required tok, err := jwt.Parse(serialized, jwt.WithVerifyAuto(nil, jwk.WithHTTPClient(srv.Client()), jwk.WithFetchWhitelist(jwk.InsecureWhitelist{}))) if err != nil { fmt.Printf("failed to verify token: %s\n", err) return } _ = tok // OUTPUT: } golang-github-lestrrat-go-jwx-3.0.13/examples/jwt_parse_with_key_example_test.go000066400000000000000000000012001515060566400302150ustar00rootroot00000000000000package examples_test import ( "fmt" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/lestrrat-go/jwx/v3/jwk" "github.com/lestrrat-go/jwx/v3/jwt" ) func Example_jwt_parse_with_key() { const keysrc = `{"kty":"oct","k":"AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow"}` key, err := jwk.ParseKey([]byte(keysrc)) if err != nil { fmt.Printf("jwk.ParseKey failed: %s\n", err) return } tok, err := jwt.Parse([]byte(exampleJWTSignedHMAC), jwt.WithKey(jwa.HS256(), key), jwt.WithValidate(false)) if err != nil { fmt.Printf("jwt.Parse failed: %s\n", err) return } _ = tok // OUTPUT: } golang-github-lestrrat-go-jwx-3.0.13/examples/jwt_parse_with_key_provider_example_test.go000066400000000000000000000071221515060566400321400ustar00rootroot00000000000000package examples_test import ( "context" "crypto/rand" "crypto/rsa" "encoding/base64" "fmt" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/lestrrat-go/jwx/v3/jws" "github.com/lestrrat-go/jwx/v3/jwt" ) func Example_jwt_parse_with_key_provider_use_token() { // This example shows how one might use the information in the JWT to // load different keys. // Setup origIssuer := "me" tok, err := jwt.NewBuilder(). Issuer(origIssuer). Build() if err != nil { fmt.Printf("failed to build token: %s\n", err) return } symmetricKey := []byte("Abracadabra") alg := jwa.HS256() signed, err := jwt.Sign(tok, jwt.WithKey(alg, symmetricKey)) if err != nil { fmt.Printf("failed to sign token: %s\n", err) return } // This next example assumes that you want to minimize the number of // times you parse the JWT JSON { _, b64payload, _, err := jws.SplitCompact(signed) if err != nil { fmt.Printf("failed to split jws: %s\n", err) return } enc := base64.RawStdEncoding payload := make([]byte, enc.DecodedLen(len(b64payload))) _, err = enc.Decode(payload, b64payload) if err != nil { fmt.Printf("failed to decode base64 payload: %s\n", err) return } parsed, err := jwt.Parse(payload, jwt.WithVerify(false)) if err != nil { fmt.Printf("failed to parse JWT: %s\n", err) return } _, err = jws.Verify(signed, jws.WithKeyProvider(jws.KeyProviderFunc(func(_ context.Context, sink jws.KeySink, sig *jws.Signature, msg *jws.Message) error { iss, ok := parsed.Issuer() if !ok { return fmt.Errorf("no issuer found") } switch iss { case "me": sink.Key(alg, symmetricKey) return nil default: return fmt.Errorf("unknown issuer %q", iss) } }))) if err != nil { fmt.Printf("%s\n", err) return } if iss, ok := parsed.Issuer(); !ok || iss != origIssuer { fmt.Printf("issuers do not match\n") return } } // OUTPUT: // } func Example_jwt_parse_with_key_provider() { // Pretend that this is a storage somewhere (maybe a database) that maps // a signature algorithm to a key store := make(map[jwa.KeyAlgorithm]any) algorithms := []jwa.SignatureAlgorithm{ jwa.RS256(), jwa.RS384(), jwa.RS512(), } var signingKey *rsa.PrivateKey for _, alg := range algorithms { pk, err := rsa.GenerateKey(rand.Reader, 2048) if err != nil { fmt.Printf("failed to generate private key: %s\n", err) return } // too lazy to write a proper algorithm. just assign every // time, and signingKey will end up being the last key generated signingKey = pk store[alg] = pk.PublicKey } // Create a JWT token := jwt.New() token.Set(`foo`, `bar`) // Use the last private key in the list to sign the payload serialized, err := jwt.Sign(token, jwt.WithKey(algorithms[2], signingKey)) if err != nil { fmt.Printf(`failed to sign JWT: %s`, err) return } // This example uses jws.KeyProviderFunc, but for production use // you should probably use a reusable object that implements // jws.KeyProvider tok, err := jwt.Parse(serialized, jwt.WithKeyProvider(jws.KeyProviderFunc(func(_ context.Context, sink jws.KeySink, sig *jws.Signature, _ *jws.Message) error { alg, ok := sig.ProtectedHeaders().Algorithm() if !ok { return nil } key, ok := store[alg] if !ok { // nothing found return nil } // Note: we only send one key here, but we could potentially send _ALL_ // keys in the store and have `jws.Verify()` try each one (but it would // most likely be a waste if you did that) sink.Key(alg, key) return nil }))) if err != nil { fmt.Printf(`failed to verify JWT: %s`, err) return } _ = tok // OUTPUT: } golang-github-lestrrat-go-jwx-3.0.13/examples/jwt_parse_with_keyset_example_test.go000066400000000000000000000053051515060566400307430ustar00rootroot00000000000000package examples_test import ( "crypto/rand" "crypto/rsa" "fmt" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/lestrrat-go/jwx/v3/jwk" "github.com/lestrrat-go/jwx/v3/jwt" ) func Example_jwt_parse_with_key_set() { var serialized []byte var signingKey jwk.Key var keyset jwk.Set // Preparation: // // For demonstration purposes, we need to do some preparation // Create a JWK key to sign the token (and also give a KeyID), { privKey, err := rsa.GenerateKey(rand.Reader, 2048) if err != nil { fmt.Printf("failed to generate private key: %s\n", err) return } // This is the key we will use to sign realKey, err := jwk.Import(privKey) if err != nil { fmt.Printf("failed to create JWK: %s\n", err) return } realKey.Set(jwk.KeyIDKey, `mykey`) realKey.Set(jwk.AlgorithmKey, jwa.RS256()) // For demonstration purposes, we also create a bogus key bogusKey, err := jwk.Import([]byte("bogus")) if err != nil { fmt.Printf("failed to create bogus JWK: %s\n", err) return } bogusKey.Set(jwk.AlgorithmKey, jwa.NoSignature()) bogusKey.Set(jwk.KeyIDKey, "otherkey") // Now create a key set that users will use to verity the signed serialized against // Normally these keys are available somewhere like https://www.googleapis.com/oauth2/v3/certs // This key set contains two keys, the first one is the correct one // We can use the jwk.PublicSetOf() utility to get a JWKS of the public keys { privset := jwk.NewSet() privset.AddKey(realKey) privset.AddKey(bogusKey) v, err := jwk.PublicSetOf(privset) if err != nil { fmt.Printf("failed to create public JWKS: %s\n", err) return } keyset = v } signingKey = realKey } // Create the token token := jwt.New() token.Set(`foo`, `bar`) // Sign the token and generate a JWS message signed, err := jwt.Sign(token, jwt.WithKey(jwa.RS256(), signingKey)) if err != nil { fmt.Printf("failed to generate signed serialized: %s\n", err) return } // This is what you typically get as a signed JWT from a server serialized = signed // Actual verification: // FINALLY. This is how you Parse and verify the serialized. // Key IDs are automatically matched. // There was a lot of code above, but as a consumer, below is really all you need // to write in your code tok, err := jwt.Parse( serialized, // Tell the parser that you want to use this keyset jwt.WithKeySet(keyset), // Replace the above option with the following option if you know your key // does not have an "alg"/ field (which is apparently the case for Azure tokens) // jwt.WithKeySet(keyset, jws.WithInferAlgorithmFromKey(true)), ) if err != nil { fmt.Printf("failed to parse serialized: %s\n", err) } _ = tok // OUTPUT: } golang-github-lestrrat-go-jwx-3.0.13/examples/jwt_raw_struct_example_test.go000066400000000000000000000020061515060566400274020ustar00rootroot00000000000000package examples import ( "encoding/json" "fmt" "os" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/lestrrat-go/jwx/v3/jws" "github.com/lestrrat-go/jwx/v3/jwt" ) func Example_jwt_plain_struct() { t1, err := jwt.NewBuilder(). Issuer("https://github.com/lestrrat-go/jwx/v3/examples"). Subject("raw_struct"). Claim("private", "foobar"). Build() if err != nil { fmt.Fprintf(os.Stderr, "failed to build JWT: %s\n", err) } key := []byte("secret") signed, err := jwt.Sign(t1, jwt.WithKey(jwa.HS256(), key)) if err != nil { fmt.Printf("failed to sign JWT: %s\n", err) } rawJWT, err := jws.Verify(signed, jws.WithKey(jwa.HS256(), key)) if err != nil { fmt.Printf("failed to verify JWS: %s\n", err) } type MyToken struct { Issuer string `json:"iss"` Subject string `json:"sub"` Private string `json:"private"` } var t2 MyToken if err := json.Unmarshal(rawJWT, &t2); err != nil { fmt.Printf("failed to unmarshal JWT: %s\n", err) } fmt.Printf("%s\n", t2.Private) // OUTPUT: // foobar } golang-github-lestrrat-go-jwx-3.0.13/examples/jwt_readfile_example_test.go000066400000000000000000000013441515060566400267640ustar00rootroot00000000000000package examples_test import ( "fmt" "os" "github.com/lestrrat-go/jwx/v3/jwt" ) func Example_jwt_readfile() { f, err := os.CreateTemp(``, `jwt_readfile-*.jws`) if err != nil { fmt.Printf("failed to create temporary file: %s\n", err) return } defer os.Remove(f.Name()) fmt.Fprint(f, exampleJWTSignedHMAC) f.Close() // Note: this JWT has NOT been verified because we have not passed jwt.WithKey() and used // jwt.WithVerify(false). You need to pass jwt.WithKey() if you want the token to be parsed and // verified in one go. tok, err := jwt.ReadFile(f.Name(), jwt.WithVerify(false), jwt.WithValidate(false)) if err != nil { fmt.Printf("failed to read file %q: %s\n", f.Name(), err) return } _ = tok // OUTPUT: } golang-github-lestrrat-go-jwx-3.0.13/examples/jwt_serialize_json_example_test.go000066400000000000000000000007161515060566400302330ustar00rootroot00000000000000package examples_test import ( "encoding/json" "fmt" "os" "time" "github.com/lestrrat-go/jwx/v3/jwt" ) func Example_jwt_serialize_json() { tok, err := jwt.NewBuilder(). Issuer(`github.com/lestrrat-go/jwx`). IssuedAt(time.Unix(aLongLongTimeAgo, 0)). Build() if err != nil { fmt.Printf("failed to build token: %s\n", err) return } json.NewEncoder(os.Stdout).Encode(tok) // OUTPUT: // {"iat":233431200,"iss":"github.com/lestrrat-go/jwx"} } golang-github-lestrrat-go-jwx-3.0.13/examples/jwt_serialize_jwe_jws_example_test.go000066400000000000000000000023361515060566400307320ustar00rootroot00000000000000package examples_test import ( "crypto/rand" "crypto/rsa" "fmt" "time" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/lestrrat-go/jwx/v3/jwk" "github.com/lestrrat-go/jwx/v3/jwt" ) func Example_jwt_serialize_jwe_jws() { tok, err := jwt.NewBuilder(). Issuer(`github.com/lestrrat-go/jwx`). IssuedAt(time.Unix(aLongLongTimeAgo, 0)). Build() if err != nil { fmt.Printf("failed to build token: %s\n", err) return } privkey, err := rsa.GenerateKey(rand.Reader, 2048) if err != nil { fmt.Printf("failed to generate private key: %s\n", err) return } enckey, err := jwk.Import(privkey.PublicKey) if err != nil { fmt.Printf("failed to create symmetric key: %s\n", err) return } signkey, err := jwk.Import([]byte(`abracadabra`)) if err != nil { fmt.Printf("failed to create symmetric key: %s\n", err) return } serialized, err := jwt.NewSerializer(). Encrypt(jwt.WithKey(jwa.RSA_OAEP(), enckey)). Sign(jwt.WithKey(jwa.HS256(), signkey)). Serialize(tok) if err != nil { fmt.Printf("failed to encrypt and sign token: %s\n", err) return } _ = serialized // We don't use the result of serialization as it will always be // different because of randomness used in the encryption logic // OUTPUT: } golang-github-lestrrat-go-jwx-3.0.13/examples/jwt_serialize_jws_example_test.go000066400000000000000000000025741515060566400300710ustar00rootroot00000000000000package examples_test import ( "fmt" "time" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/lestrrat-go/jwx/v3/jwk" "github.com/lestrrat-go/jwx/v3/jwt" ) func Example_jwt_serialize_jws() { tok, err := jwt.NewBuilder(). Issuer(`github.com/lestrrat-go/jwx`). IssuedAt(time.Unix(aLongLongTimeAgo, 0)). Build() if err != nil { fmt.Printf("failed to build token: %s\n", err) return } rawKey := []byte(`abracadabra`) jwkKey, err := jwk.Import(rawKey) if err != nil { fmt.Printf("failed to create symmetric key: %s\n", err) return } // This example shows you two ways to passing keys to // jwt.Sign() // // * The first key is the "raw" key. // * The second one is a jwk.Key that represents the raw key. // // If this were using RSA/ECDSA keys, you would be using // *rsa.PrivateKey/*ecdsa.PrivateKey as the raw key. for _, key := range []any{rawKey, jwkKey} { serialized, err := jwt.Sign(tok, jwt.WithKey(jwa.HS256(), key)) if err != nil { fmt.Printf("failed to sign token: %s\n", err) return } fmt.Printf("%s\n", serialized) } // OUTPUT: // eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjIzMzQzMTIwMCwiaXNzIjoiZ2l0aHViLmNvbS9sZXN0cnJhdC1nby9qd3gifQ.K1WVWaM6Dww9aNNFMjnyUfjaaHIs08-3Qb1b8eSEHOk // eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjIzMzQzMTIwMCwiaXNzIjoiZ2l0aHViLmNvbS9sZXN0cnJhdC1nby9qd3gifQ.K1WVWaM6Dww9aNNFMjnyUfjaaHIs08-3Qb1b8eSEHOk } golang-github-lestrrat-go-jwx-3.0.13/examples/jwt_sign_with_custom_base64_example_test.go000066400000000000000000000025531515060566400317450ustar00rootroot00000000000000package examples_test import ( "encoding/base64" "encoding/json" "fmt" "os" "time" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/lestrrat-go/jwx/v3/jwt" ) func Example_jwt_sign_with_custom_base64_encoder() { const symmetricKey = "0123456789abcdef0123456789abcdef" token, err := jwt.NewBuilder(). Subject("github.com/lestrrat-go/jwx"). IssuedAt(time.Unix(aLongLongTimeAgo, 0)). Build() if err != nil { fmt.Printf("failed to create token: %s\n", err) return } signed, err := jwt.Sign(token, jwt.WithKey(jwa.HS256(), []byte(symmetricKey)), jwt.WithBase64Encoder(base64.URLEncoding)) if err != nil { fmt.Printf("failed to sign token: %s\n", err) return } fmt.Println(string(signed)) parsed, err := jwt.Parse(signed, jwt.WithKey(jwa.HS256(), []byte(symmetricKey)), jwt.WithBase64Encoder(base64.URLEncoding)) if err != nil { fmt.Printf("failed to parse token: %s\n", err) return } if err := json.NewEncoder(os.Stdout).Encode(parsed); err != nil { fmt.Printf("failed to encode token: %s\n", err) return } if !jwt.Equal(token, parsed) { fmt.Printf("parsed token does not match original token\n") return } // OUTPUT: // eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjIzMzQzMTIwMCwic3ViIjoiZ2l0aHViLmNvbS9sZXN0cnJhdC1nby9qd3gifQ==.qZu-ATTtmo9k1NedYgwwBzaEYEJA1Z6dlVzPpmzrrrw= // {"iat":233431200,"sub":"github.com/lestrrat-go/jwx"} } golang-github-lestrrat-go-jwx-3.0.13/examples/jwt_validate_detect_error_type_example_test.go000066400000000000000000000031651515060566400326070ustar00rootroot00000000000000package examples_test import ( "encoding/json" "errors" "fmt" "time" "github.com/lestrrat-go/jwx/v3/jwt" ) func Example_jwt_validate_detect_error_type() { tok, err := jwt.NewBuilder(). Issuer(`github.com/lestrrat-go/jwx`). Expiration(time.Now().Add(-1 * time.Hour)). Build() if err != nil { fmt.Printf("failed to build token: %s\n", err) return } buf, err := json.Marshal(tok) if err != nil { fmt.Printf("failed to serialize token: %s\n", err) return } { // Case 1: Parsing error. We're not showing verification failure, // but it is about the same in the context of wanting to know // if it's a validation error or not _, err := jwt.Parse(buf[:len(buf)-1], jwt.WithVerify(false), jwt.WithValidate(true)) if err == nil { fmt.Printf("token should fail parsing\n") return } if errors.Is(err, jwt.ValidateError()) { fmt.Printf("error should NOT be validation error\n") return } } { // Case 2: Parsing works, validation fails // NOTE: This token has NOT been verified for demonstration // purposes. Use `jwt.WithKey()` or the like in your production code _, err = jwt.Parse(buf, jwt.WithVerify(false), jwt.WithValidate(true)) if err == nil { fmt.Printf("token should fail parsing\n") return } if !errors.Is(err, jwt.ValidateError()) { fmt.Printf("error should be validation error\n") return } if !errors.Is(err, jwt.TokenExpiredError()) { fmt.Printf("error should be of token expired type\n") return } fmt.Printf("%s\n", err) } // OUTPUT: // jwt.Parse: failed to parse token: jwt.Validate: validation failed: "exp" not satisfied: token is expired } golang-github-lestrrat-go-jwx-3.0.13/examples/jwt_validate_example_test.go000066400000000000000000000022341515060566400270010ustar00rootroot00000000000000package examples_test import ( "encoding/json" "fmt" "time" "github.com/lestrrat-go/jwx/v3/jwt" ) func Example_jwt_validate() { tok, err := jwt.NewBuilder(). Issuer(`github.com/lestrrat-go/jwx`). Expiration(time.Now().Add(-1 * time.Hour)). Build() if err != nil { fmt.Printf("failed to build token: %s\n", err) return } { // Case 1: Using jwt.Validate() err = jwt.Validate(tok) if err == nil { fmt.Printf("token should fail validation\n") return } fmt.Printf("%s\n", err) } { // Case 2: Using jwt.Parse() buf, err := json.Marshal(tok) if err != nil { fmt.Printf("failed to serialize token: %s\n", err) return } // NOTE: This token has NOT been verified for demonstration // purposes. Use `jwt.WithKey()` or the like in your production code _, err = jwt.Parse(buf, jwt.WithVerify(false), jwt.WithValidate(true)) if err == nil { fmt.Printf("token should fail validation\n") return } fmt.Printf("%s\n", err) } // OUTPUT: // jwt.Validate: validation failed: "exp" not satisfied: token is expired // jwt.Parse: failed to parse token: jwt.Validate: validation failed: "exp" not satisfied: token is expired } golang-github-lestrrat-go-jwx-3.0.13/examples/jwt_validate_issuer_example_test.go000066400000000000000000000011231515060566400303670ustar00rootroot00000000000000package examples_test import ( "fmt" "time" "github.com/lestrrat-go/jwx/v3/jwt" ) func Example_jwt_validate_issuer() { tok, err := jwt.NewBuilder(). Issuer(`github.com/lestrrat-go/jwx`). Expiration(time.Now().Add(time.Hour)). Build() if err != nil { fmt.Printf("failed to build token: %s\n", err) return } err = jwt.Validate(tok, jwt.WithIssuer(`nobody`)) if err == nil { fmt.Printf("token should fail validation\n") return } fmt.Printf("%s\n", err) // OUTPUT: // jwt.Validate: validation failed: "iss" not satisfied: claim "iss" does not have the expected value } golang-github-lestrrat-go-jwx-3.0.13/examples/jwt_validate_validator_example_test.go000066400000000000000000000016021515060566400310440ustar00rootroot00000000000000package examples_test import ( "context" "errors" "fmt" "time" "github.com/lestrrat-go/jwx/v3/jwt" ) func Example_jwt_validate_validator() { validator := jwt.ValidatorFunc(func(_ context.Context, t jwt.Token) error { iat, ok := t.IssuedAt() if !ok { return errors.New(`token does not have "iat" claim`) } if iat.Month() != 8 { return errors.New(`tokens are only valid if issued during August!`) } return nil }) tok, err := jwt.NewBuilder(). Issuer(`github.com/lestrrat-go/jwx`). IssuedAt(time.Unix(aLongLongTimeAgo, 0)). Build() if err != nil { fmt.Printf("failed to build token: %s\n", err) return } err = jwt.Validate(tok, jwt.WithValidator(validator)) if err == nil { fmt.Printf("token should fail validation\n") return } fmt.Printf("%s\n", err) // OUTPUT: // jwt.Validate: validation failed: tokens are only valid if issued during August! } golang-github-lestrrat-go-jwx-3.0.13/examples/jwx_example_test.go000066400000000000000000000042441515060566400251370ustar00rootroot00000000000000package examples_test import ( "crypto/rand" "crypto/rsa" "fmt" "time" "github.com/lestrrat-go/jwx/v3/internal/json" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/lestrrat-go/jwx/v3/jwk" "github.com/lestrrat-go/jwx/v3/jwt" ) var payloadLoremIpsum []byte var jwtSignedWithHS256 []byte var rawRSAPrivateKey *rsa.PrivateKey var rawRSAPublicKey *rsa.PublicKey var jwkRSAPrivateKey jwk.Key var jwkRSAPublicKey jwk.Key var jwkSymmetricKey jwk.Key var jsonRSAPrivateKey []byte var jsonRSAPublicKey []byte func init() { if err := Setup(); err != nil { panic(err.Error()) } } // Create some variables that would be repeatedly used in the examples func Setup() error { payloadLoremIpsum = []byte(`Lorem ipsum`) tok, err := jwt.NewBuilder(). Issuer(`github.com/lestrrat-go/jwx`). IssuedAt(time.Now().Add(-5 * time.Minute)). Expiration(time.Now().Add(time.Hour)). Build() if err != nil { return fmt.Errorf(`failed to build token: %w`, err) } { v, err := rsa.GenerateKey(rand.Reader, 2048) if err != nil { return fmt.Errorf(`failed to create RSA private key: %w`, err) } rawRSAPrivateKey = v rawRSAPublicKey = &v.PublicKey } { v, err := jwk.Import(rawRSAPrivateKey) if err != nil { return fmt.Errorf(`failed to create jwk.Key from RSA private key: %w`, err) } jwkRSAPrivateKey = v } { v, err := json.Marshal(jwkRSAPrivateKey) if err != nil { return fmt.Errorf(`failed to marshal RSA private jwk.Key: %w`, err) } jsonRSAPrivateKey = v } { v, err := jwk.Import(rawRSAPublicKey) if err != nil { return fmt.Errorf(`failed to create jwk.Key from RSA public key: %w`, err) } jwkRSAPublicKey = v } { v, err := json.Marshal(jwkRSAPublicKey) if err != nil { return fmt.Errorf(`failed to marshal RSA public jwk.Key: %w`, err) } jsonRSAPublicKey = v } { v, err := jwk.Import([]byte(`abracadabra`)) if err != nil { return fmt.Errorf(`failed to create jwk.Key from symmetric key: %w`, err) } jwkSymmetricKey = v } { v, err := jwt.Sign(tok, jwt.WithKey(jwa.HS256(), jwkSymmetricKey)) if err != nil { return fmt.Errorf(`failed to sign token with HS256: %w`, err) } jwtSignedWithHS256 = v } return nil } golang-github-lestrrat-go-jwx-3.0.13/examples/jwx_readme_example_test.go000066400000000000000000000051171515060566400264540ustar00rootroot00000000000000package examples_test import ( "bytes" "fmt" "net/http" "time" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/lestrrat-go/jwx/v3/jwe" "github.com/lestrrat-go/jwx/v3/jwk" "github.com/lestrrat-go/jwx/v3/jws" "github.com/lestrrat-go/jwx/v3/jwt" ) func Example() { // Parse, serialize, slice and dice JWKs! privkey, err := jwk.ParseKey(jsonRSAPrivateKey) if err != nil { fmt.Printf("failed to parse JWK: %s\n", err) return } pubkey, err := jwk.PublicKeyOf(privkey) if err != nil { fmt.Printf("failed to get public key: %s\n", err) return } // Work with JWTs! { // Build a JWT! tok, err := jwt.NewBuilder(). Issuer(`github.com/lestrrat-go/jwx`). IssuedAt(time.Now()). Build() if err != nil { fmt.Printf("failed to build token: %s\n", err) return } // Sign a JWT! signed, err := jwt.Sign(tok, jwt.WithKey(jwa.RS256(), privkey)) if err != nil { fmt.Printf("failed to sign token: %s\n", err) return } // Verify a JWT! { verifiedToken, err := jwt.Parse(signed, jwt.WithKey(jwa.RS256(), pubkey)) if err != nil { fmt.Printf("failed to verify JWS: %s\n", err) return } _ = verifiedToken } // Work with *http.Request! { req, err := http.NewRequest(http.MethodGet, `https://github.com/lestrrat-go/jwx`, nil) req.Header.Set(`Authorization`, fmt.Sprintf(`Bearer %s`, signed)) verifiedToken, err := jwt.ParseRequest(req, jwt.WithKey(jwa.RS256(), pubkey)) if err != nil { fmt.Printf("failed to verify token from HTTP request: %s\n", err) return } _ = verifiedToken } } // Encrypt and Decrypt arbitrary payload with JWE! { encrypted, err := jwe.Encrypt(payloadLoremIpsum, jwe.WithKey(jwa.RSA_OAEP(), jwkRSAPublicKey)) if err != nil { fmt.Printf("failed to encrypt payload: %s\n", err) return } decrypted, err := jwe.Decrypt(encrypted, jwe.WithKey(jwa.RSA_OAEP(), jwkRSAPrivateKey)) if err != nil { fmt.Printf("failed to decrypt payload: %s\n", err) return } if !bytes.Equal(decrypted, payloadLoremIpsum) { fmt.Printf("verified payload did not match\n") return } } // Sign and Verify arbitrary payload with JWS! { signed, err := jws.Sign(payloadLoremIpsum, jws.WithKey(jwa.RS256(), jwkRSAPrivateKey)) if err != nil { fmt.Printf("failed to sign payload: %s\n", err) return } verified, err := jws.Verify(signed, jws.WithKey(jwa.RS256(), jwkRSAPublicKey)) if err != nil { fmt.Printf("failed to verify payload: %s\n", err) return } if !bytes.Equal(verified, payloadLoremIpsum) { fmt.Printf("verified payload did not match\n") return } } // OUTPUT: } golang-github-lestrrat-go-jwx-3.0.13/examples/jwx_register_ec_and_key_example_test.go000066400000000000000000000116741515060566400312110ustar00rootroot00000000000000package examples_test import ( "bytes" "crypto/ecdsa" "crypto/elliptic" "crypto/rand" "fmt" "math/big" "github.com/emmansun/gmsm/sm2" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/lestrrat-go/jwx/v3/jwk" ourecdsa "github.com/lestrrat-go/jwx/v3/jwk/ecdsa" "github.com/lestrrat-go/jwx/v3/jws" ) // Setup. This is something that you probably should do in your adapter // library, or in your application's init() function. // // I could not readily find what the exact curve notation is for ShangMi SM2 // (either I'm just bad at researching or it's not in an RFC as of this writing) // so I'm faking it as "SM2". // // For demonstration purposes, it could as well be a random string, as long // as its consistent in your usage. var SM2 = jwa.NewEllipticCurveAlgorithm("SM2") func init() { // Register the algorithm name so it can be looked up jwa.RegisterEllipticCurveAlgorithm(SM2) // Register the actual ECDSA curve. Notice that we need to tell this // to our jwk library, so that the JWK lookup can be done properly // when a raw SM2 key is passed to various key operations. ourecdsa.RegisterCurve(SM2, sm2.P256()) // We only need one converter for the private key, because the public key // is exactly the same type as *ecdsa.PublicKey jwk.RegisterKeyImporter(&sm2.PrivateKey{}, jwk.KeyImportFunc(convertShangMiSm2)) jwk.RegisterKeyExporter(jwa.EC(), jwk.KeyExportFunc(convertJWKToShangMiSm2)) } func convertShangMiSm2(key any) (jwk.Key, error) { shangmi2pk, ok := key.(*sm2.PrivateKey) if !ok { return nil, fmt.Errorf("invalid SM2 private key") } return jwk.Import(shangmi2pk.PrivateKey) } func convertJWKToShangMiSm2(key jwk.Key, hint any) (any, error) { ecdsaKey, ok := key.(jwk.ECDSAPrivateKey) if !ok { return nil, fmt.Errorf(`invalid key type %T: %w`, key, jwk.ContinueError()) } if v, ok := ecdsaKey.Crv(); !ok || v != SM2 { return nil, fmt.Errorf(`cannote convert curve of type %s to ShangMi key: %w`, v, jwk.ContinueError()) } switch hint.(type) { case *sm2.PrivateKey, *any: default: return nil, fmt.Errorf(`can only convert SM2 key to *sm2.PrivateKey (got %T): %w`, hint, jwk.ContinueError()) } var ret sm2.PrivateKey ret.PublicKey.Curve = sm2.P256() d, ok := ecdsaKey.D() if !ok { return nil, fmt.Errorf(`missing D field in ECDSA private key: %w`, jwk.ContinueError()) } ret.D = (&big.Int{}).SetBytes(d) x, ok := ecdsaKey.X() if !ok { return nil, fmt.Errorf(`missing X field in ECDSA private key: %w`, jwk.ContinueError()) } ret.PublicKey.X = (&big.Int{}).SetBytes(x) y, ok := ecdsaKey.Y() if !ok { return nil, fmt.Errorf(`missing Y field in ECDSA private key: %w`, jwk.ContinueError()) } ret.PublicKey.Y = (&big.Int{}).SetBytes(y) return &ret, nil } // End setup func Example_shang_mi_sm2() { shangmi2pk, _ := sm2.GenerateKey(rand.Reader) // Create a jwk.Key from ShangMi SM2 private key shangmi2JWK, err := jwk.Import(shangmi2pk) if err != nil { fmt.Printf("failed to create jwk.Key from raw ShangMi private key: %s\n", err) return } { // Create a ShangMi SM2 private key back from the jwk.Key var clone sm2.PrivateKey if err := jwk.Export(shangmi2JWK, &clone); err != nil { fmt.Printf("failed to create ShangMi private key from jwk.Key: %s\n", err) return } // Clone should have same Crv, D, X, and Y values if clone.Curve != shangmi2pk.Curve { fmt.Println("curve does not match") return } if clone.D.Cmp(shangmi2pk.D) != 0 { fmt.Println("D does not match") return } if clone.X.Cmp(shangmi2pk.X) != 0 { fmt.Println("X does not match") return } if clone.Y.Cmp(shangmi2pk.Y) != 0 { fmt.Println("Y does not match") return } } { // Can do the same thing for any var clone any if err := jwk.Export(shangmi2JWK, &clone); err != nil { fmt.Printf("failed to create ShangMi private key from jwk.Key (via any): %s\n", err) return } } { // Of course, ecdsa.PrivateKeys are also supported separately ecprivkey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) if err != nil { fmt.Println(err) return } eckjwk, err := jwk.Import(ecprivkey) if err != nil { fmt.Printf("failed to create jwk.Key from raw ShangMi public key: %s\n", err) return } var clone ecdsa.PrivateKey if err := jwk.Export(eckjwk, &clone); err != nil { fmt.Printf("failed to create ShangMi public key from jwk.Key: %s\n", err) return } } payload := []byte("Lorem ipsum") signed, err := jws.Sign(payload, jws.WithKey(jwa.ES256(), shangmi2JWK)) if err != nil { fmt.Printf("Failed to sign using ShangMi key: %s\n", err) return } shangmi2PubJWK, err := jwk.PublicKeyOf(shangmi2JWK) if err != nil { fmt.Printf("Failed to create public JWK using ShangMi key: %s\n", err) return } verified, err := jws.Verify(signed, jws.WithKey(jwa.ES256(), shangmi2PubJWK)) if err != nil { fmt.Printf("Failed to verify using ShangMi key: %s\n", err) return } if !bytes.Equal(payload, verified) { fmt.Println("payload does not match") return } //OUTPUT: } golang-github-lestrrat-go-jwx-3.0.13/examples/jwx_with_number_example_test.go000066400000000000000000000010701515060566400275340ustar00rootroot00000000000000//go:build ignore // +build ignore package examples_test import ( "crypto/rand" "crypto/rsa" "log" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/lestrrat-go/jwx/v3/jwe" "github.com/lestrrat-go/jwx/v3/jws" ) func ExampleJWX_DecoderSettings() { // This has not been enabled in this example, but if you want to // parse numbers in the incoming JSON objects as json.Number // instead of floats, you can use the following call to globally // affect the behavior of JSON parsing. // func init() { // jwx.DecoderSettings(jwx.WithUseNumber(true)) // } } golang-github-lestrrat-go-jwx-3.0.13/format.go000066400000000000000000000056561515060566400212370ustar00rootroot00000000000000package jwx import ( "bytes" "encoding/json" "github.com/lestrrat-go/jwx/v3/internal/tokens" ) type FormatKind int // These constants describe the result from guessing the format // of the incoming buffer. const ( // InvalidFormat is returned when the format of the incoming buffer // has been deemed conclusively invalid InvalidFormat FormatKind = iota // UnknownFormat is returned when GuessFormat was not able to conclusively // determine the format of the UnknownFormat JWE JWS JWK JWKS JWT ) type formatHint struct { Payload json.RawMessage `json:"payload"` // Only in JWS Signatures json.RawMessage `json:"signatures"` // Only in JWS Ciphertext json.RawMessage `json:"ciphertext"` // Only in JWE KeyType json.RawMessage `json:"kty"` // Only in JWK Keys json.RawMessage `json:"keys"` // Only in JWKS Audience json.RawMessage `json:"aud"` // Only in JWT } // GuessFormat is used to guess the format the given payload is in // using heuristics. See the type FormatKind for a full list of // possible types. // // This may be useful in determining your next action when you may // encounter a payload that could either be a JWE, JWS, or a plain JWT. // // Because JWTs are almost always JWS signed, you may be thrown off // if you pass what you think is a JWT payload to this function. // If the function is in the "Compact" format, it means it's a JWS // signed message, and its payload is the JWT. Therefore this function // will return JWS, not JWT. // // This function requires an extra parsing of the payload, and therefore // may be inefficient if you call it every time before parsing. func GuessFormat(payload []byte) FormatKind { // The check against kty, keys, and aud are something this library // made up. for the distinctions between JWE and JWS, we used // https://datatracker.ietf.org/doc/html/rfc7516#section-9. // // The above RFC described several ways to distinguish between // a JWE and JWS JSON, but we're only using one of them payload = bytes.TrimSpace(payload) if len(payload) <= 0 { return UnknownFormat } if payload[0] != tokens.OpenCurlyBracket { // Compact format. It's probably a JWS or JWE sep := []byte{tokens.Period} // I want to const this :/ // Note: this counts the number of occurrences of the // separator, but the RFC talks about the number of segments. // number of tokens.Period == segments - 1, so that's why we have 2 and 4 here switch count := bytes.Count(payload, sep); count { case 2: return JWS case 4: return JWE default: return InvalidFormat } } // If we got here, we probably have JSON. var h formatHint if err := json.Unmarshal(payload, &h); err != nil { return UnknownFormat } if h.Audience != nil { return JWT } if h.KeyType != nil { return JWK } if h.Keys != nil { return JWKS } if h.Ciphertext != nil { return JWE } if h.Signatures != nil && h.Payload != nil { return JWS } return UnknownFormat } golang-github-lestrrat-go-jwx-3.0.13/formatkind_string_gen.go000066400000000000000000000014141515060566400243100ustar00rootroot00000000000000// Code generated by "stringer -type=FormatKind"; DO NOT EDIT. package jwx import "strconv" func _() { // An "invalid array index" compiler error signifies that the constant values have changed. // Re-run the stringer command to generate them again. var x [1]struct{} _ = x[InvalidFormat-0] _ = x[UnknownFormat-1] _ = x[JWE-2] _ = x[JWS-3] _ = x[JWK-4] _ = x[JWKS-5] _ = x[JWT-6] } const _FormatKind_name = "InvalidFormatUnknownFormatJWEJWSJWKJWKSJWT" var _FormatKind_index = [...]uint8{0, 13, 26, 29, 32, 35, 39, 42} func (i FormatKind) String() string { idx := int(i) - 0 if i < 0 || idx >= len(_FormatKind_index)-1 { return "FormatKind(" + strconv.FormatInt(int64(i), 10) + ")" } return _FormatKind_name[_FormatKind_index[idx]:_FormatKind_index[idx+1]] } golang-github-lestrrat-go-jwx-3.0.13/go.mod000066400000000000000000000014341515060566400205140ustar00rootroot00000000000000module github.com/lestrrat-go/jwx/v3 go 1.24.0 toolchain go1.24.4 require ( github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 github.com/goccy/go-json v0.10.3 github.com/lestrrat-go/blackmagic v1.0.4 github.com/lestrrat-go/dsig v1.0.0 github.com/lestrrat-go/dsig-secp256k1 v1.0.0 github.com/lestrrat-go/httprc/v3 v3.0.2 github.com/lestrrat-go/option/v2 v2.0.0 github.com/segmentio/asm v1.2.1 github.com/stretchr/testify v1.11.1 github.com/valyala/fastjson v1.6.7 golang.org/x/crypto v0.46.0 ) require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/lestrrat-go/httpcc v1.0.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect golang.org/x/sys v0.39.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) retract v3.0.4 // Accidentally introduced data races. golang-github-lestrrat-go-jwx-3.0.13/go.sum000066400000000000000000000064541515060566400205500ustar00rootroot00000000000000github.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/dcrec/secp256k1/v4 v4.4.0 h1:NMZiJj8QnKe1LgsbDayM4UoHwbvwDRwnI3hwNaAHRnc= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40= github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA= github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/lestrrat-go/blackmagic v1.0.4 h1:IwQibdnf8l2KoO+qC3uT4OaTWsW7tuRQXy9TRN9QanA= github.com/lestrrat-go/blackmagic v1.0.4/go.mod h1:6AWFyKNNj0zEXQYfTMPfZrAXUWUfTIZ5ECEUEJaijtw= github.com/lestrrat-go/dsig v1.0.0 h1:OE09s2r9Z81kxzJYRn07TFM9XA4akrUdoMwr0L8xj38= github.com/lestrrat-go/dsig v1.0.0/go.mod h1:dEgoOYYEJvW6XGbLasr8TFcAxoWrKlbQvmJgCR0qkDo= github.com/lestrrat-go/dsig-secp256k1 v1.0.0 h1:JpDe4Aybfl0soBvoVwjqDbp+9S1Y2OM7gcrVVMFPOzY= github.com/lestrrat-go/dsig-secp256k1 v1.0.0/go.mod h1:CxUgAhssb8FToqbL8NjSPoGQlnO4w3LG1P0qPWQm/NU= github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE= github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E= github.com/lestrrat-go/httprc/v3 v3.0.2 h1:7u4HUaD0NQbf2/n5+fyp+T10hNCsAnwKfqn4A4Baif0= github.com/lestrrat-go/httprc/v3 v3.0.2/go.mod h1:mSMtkZW92Z98M5YoNNztbRGxbXHql7tSitCvaxvo9l0= github.com/lestrrat-go/option/v2 v2.0.0 h1:XxrcaJESE1fokHy3FpaQ/cXW8ZsIdWcdFzzLOcID3Ss= github.com/lestrrat-go/option/v2 v2.0.0/go.mod h1:oSySsmzMoR0iRzCDCaUfsCzxQHUEuhOViQObyy7S6Vg= 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.1 h1:DTNbBqs57ioxAD4PrArqftgypG4/qNpXoJx8TVXxPR0= github.com/segmentio/asm v1.2.1/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/valyala/fastjson v1.6.7 h1:ZE4tRy0CIkh+qDc5McjatheGX2czdn8slQjomexVpBM= github.com/valyala/fastjson v1.6.7/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY= golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU= golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0= golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= golang-github-lestrrat-go-jwx-3.0.13/internal/000077500000000000000000000000001515060566400212205ustar00rootroot00000000000000golang-github-lestrrat-go-jwx-3.0.13/internal/base64/000077500000000000000000000000001515060566400223045ustar00rootroot00000000000000golang-github-lestrrat-go-jwx-3.0.13/internal/base64/BUILD.bazel000066400000000000000000000007401515060566400241630ustar00rootroot00000000000000load("@rules_go//go:def.bzl", "go_library", "go_test") go_library( name = "base64", srcs = ["base64.go"], importpath = "github.com/lestrrat-go/jwx/v3/internal/base64", visibility = ["//:__subpackages__"], ) go_test( name = "base64_test", srcs = ["base64_test.go"], embed = [":base64"], deps = ["@com_github_stretchr_testify//require"], ) alias( name = "go_default_library", actual = ":base64", visibility = ["//:__subpackages__"], ) golang-github-lestrrat-go-jwx-3.0.13/internal/base64/asmbase64.go000066400000000000000000000017561515060566400244310ustar00rootroot00000000000000//go:build jwx_asmbase64 package base64 import ( "fmt" "slices" asmbase64 "github.com/segmentio/asm/base64" ) func init() { SetEncoder(asmEncoder{asmbase64.RawURLEncoding}) SetDecoder(asmDecoder{}) } type asmEncoder struct { *asmbase64.Encoding } func (e asmEncoder) AppendEncode(dst, src []byte) []byte { n := e.Encoding.EncodedLen(len(src)) dst = slices.Grow(dst, n) e.Encoding.Encode(dst[len(dst):][:n], src) return dst[:len(dst)+n] } 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 } golang-github-lestrrat-go-jwx-3.0.13/internal/base64/base64.go000066400000000000000000000050601515060566400237200ustar00rootroot00000000000000package base64 import ( "bytes" "encoding/base64" "encoding/binary" "fmt" "sync" ) type Decoder interface { Decode([]byte) ([]byte, error) } type Encoder interface { Encode([]byte, []byte) EncodedLen(int) int EncodeToString([]byte) string AppendEncode([]byte, []byte) []byte } 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 DefaultEncoder() Encoder { return getEncoder() } 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 getEncoder().EncodeToString(src) } func EncodeUint64ToString(v uint64) string { data := make([]byte, 8) binary.BigEndian.PutUint64(data, v) i := 0 for ; i < len(data); i++ { if data[i] != 0x0 { break } } return EncodeToString(data[i:]) } 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: return RawURL case isURL: return URL case isRaw: 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))) n, err := enc.Decode(dst, src) if err != nil { return nil, fmt.Errorf(`failed to decode source: %w`, err) } return dst[:n], nil } func Decode(src []byte) ([]byte, error) { return getDecoder().Decode(src) } func DecodeString(src string) ([]byte, error) { return getDecoder().Decode([]byte(src)) } golang-github-lestrrat-go-jwx-3.0.13/internal/base64/base64_test.go000066400000000000000000000016021515060566400247550ustar00rootroot00000000000000package base64 import ( "encoding/base64" "testing" "github.com/stretchr/testify/require" ) func TestDecode(t *testing.T) { testcases := []struct { Name string Encoding *base64.Encoding }{ { Name: "base64.RawURLEncoding", Encoding: base64.RawURLEncoding, }, { Name: "base64.URLEncoding", Encoding: base64.URLEncoding, }, { Name: "base64.RawStdEncoding", Encoding: base64.RawStdEncoding, }, { Name: "base64.StdEncoding", Encoding: base64.StdEncoding, }, } var payload = []byte("Hello, World") for _, tc := range testcases { t.Run(tc.Name, func(t *testing.T) { dst := make([]byte, tc.Encoding.EncodedLen(len(payload))) tc.Encoding.Encode(dst, payload) decoded, err := Decode(dst) require.NoError(t, err, `Decode should succeed`) require.Equal(t, payload, decoded, `decoded content should match`) }) } } golang-github-lestrrat-go-jwx-3.0.13/internal/ecutil/000077500000000000000000000000001515060566400225055ustar00rootroot00000000000000golang-github-lestrrat-go-jwx-3.0.13/internal/ecutil/BUILD.bazel000066400000000000000000000005011515060566400243570ustar00rootroot00000000000000load("@rules_go//go:def.bzl", "go_library") go_library( name = "ecutil", srcs = ["ecutil.go"], importpath = "github.com/lestrrat-go/jwx/v3/internal/ecutil", visibility = ["//:__subpackages__"], ) alias( name = "go_default_library", actual = ":ecutil", visibility = ["//:__subpackages__"], ) golang-github-lestrrat-go-jwx-3.0.13/internal/ecutil/ecutil.go000066400000000000000000000034741515060566400243310ustar00rootroot00000000000000// Package ecutil defines tools that help with elliptic curve related // computation package ecutil import ( "crypto/elliptic" "math/big" "sync" ) const ( // size of buffer that needs to be allocated for EC521 curve ec521BufferSize = 66 // (521 / 8) + 1 ) var ecpointBufferPool = sync.Pool{ New: func() any { // In most cases the curve bit size will be less than this length // so allocate the maximum, and keep reusing buf := make([]byte, 0, ec521BufferSize) return &buf }, } func getCrvFixedBuffer(size int) []byte { //nolint:forcetypeassert buf := *(ecpointBufferPool.Get().(*[]byte)) if size > ec521BufferSize && cap(buf) < size { buf = append(buf, make([]byte, size-cap(buf))...) } return buf[:size] } // ReleaseECPointBuffer releases the []byte buffer allocated. func ReleaseECPointBuffer(buf []byte) { buf = buf[:cap(buf)] buf[0] = 0x0 for i := 1; i < len(buf); i *= 2 { copy(buf[i:], buf[:i]) } buf = buf[:0] ecpointBufferPool.Put(&buf) } func CalculateKeySize(crv elliptic.Curve) int { // We need to create a buffer that fits the entire curve. // If the curve size is 66, that fits in 9 bytes. If the curve // size is 64, it fits in 8 bytes. bits := crv.Params().BitSize // For most common cases we know before hand what the byte length // is going to be. optimize var inBytes int switch bits { case 224, 256, 384: // TODO: use constant? inBytes = bits / 8 case 521: inBytes = ec521BufferSize default: inBytes = bits / 8 if (bits % 8) != 0 { inBytes++ } } return inBytes } // AllocECPointBuffer allocates a buffer for the given point in the given // curve. This buffer should be released using the ReleaseECPointBuffer // function. func AllocECPointBuffer(v *big.Int, crv elliptic.Curve) []byte { buf := getCrvFixedBuffer(CalculateKeySize(crv)) v.FillBytes(buf) return buf } golang-github-lestrrat-go-jwx-3.0.13/internal/jose/000077500000000000000000000000001515060566400221605ustar00rootroot00000000000000golang-github-lestrrat-go-jwx-3.0.13/internal/jose/BUILD.bazel000066400000000000000000000005341515060566400240400ustar00rootroot00000000000000load("@rules_go//go:def.bzl", "go_library") go_library( name = "jose", srcs = ["jose.go"], importpath = "github.com/lestrrat-go/jwx/v3/internal/jose", visibility = ["//:__subpackages__"], deps = ["//internal/jwxtest"], ) alias( name = "go_default_library", actual = ":jose", visibility = ["//:__subpackages__"], ) golang-github-lestrrat-go-jwx-3.0.13/internal/jose/jose.go000066400000000000000000000156421515060566400234570ustar00rootroot00000000000000package jose import ( "bufio" "bytes" "context" "fmt" "io" "os/exec" "strings" "sync" "testing" "github.com/lestrrat-go/jwx/v3/internal/jwxtest" ) var executablePath string var muExecutablePath sync.RWMutex func init() { findExecutable() } func SetExecutable(path string) { muExecutablePath.Lock() defer muExecutablePath.Unlock() executablePath = path } func findExecutable() { p, err := exec.LookPath("jose") if err == nil { SetExecutable(p) } } func ExecutablePath() string { muExecutablePath.RLock() defer muExecutablePath.RUnlock() return executablePath } func Available() bool { muExecutablePath.RLock() defer muExecutablePath.RUnlock() return executablePath != "" } func RunJoseCommand(ctx context.Context, t *testing.T, args []string, outw, errw io.Writer) error { var errout bytes.Buffer var capout bytes.Buffer cmd := exec.CommandContext(ctx, ExecutablePath(), args...) if outw == nil { cmd.Stdout = &capout } else { cmd.Stdout = io.MultiWriter(outw, &capout) } if errw == nil { cmd.Stderr = &errout } else { cmd.Stderr = io.MultiWriter(outw, &errout) } t.Logf("Executing `%s %s`\n", ExecutablePath(), strings.Join(args, " ")) if err := cmd.Run(); err != nil { t.Logf(`failed to execute command: %s`, err) if capout.Len() > 0 { t.Logf("captured output: %s", capout.String()) } if errout.Len() > 0 { t.Logf("captured error: %s", errout.String()) } return fmt.Errorf(`failed to execute command: %w`, err) } return nil } type AlgorithmSet struct { data map[string]struct{} } func NewAlgorithmSet() *AlgorithmSet { return &AlgorithmSet{ data: make(map[string]struct{}), } } func (set *AlgorithmSet) Add(s string) { set.data[s] = struct{}{} } func (set *AlgorithmSet) Has(s string) bool { _, ok := set.data[s] return ok } func Algorithms(ctx context.Context, t *testing.T) (*AlgorithmSet, error) { var buf bytes.Buffer if err := RunJoseCommand(ctx, t, []string{"alg"}, &buf, nil); err != nil { return nil, fmt.Errorf(`failed to generate jose tool's supported algorithms: %w`, err) } set := NewAlgorithmSet() scanner := bufio.NewScanner(&buf) for scanner.Scan() { alg := scanner.Text() set.Add(alg) } return set, nil } // GenerateJwk creates a new key using the jose tool, and returns its filename and // a cleanup function. // The caller is responsible for calling the cleanup // function and make sure all resources are released func GenerateJwk(ctx context.Context, t *testing.T, template string) (string, func(), error) { t.Helper() file, cleanup, err := jwxtest.CreateTempFile(t.TempDir(), "jwx-jose-key-*.jwk") if err != nil { return "", nil, fmt.Errorf(`failed to create temporary file: %w`, err) } if err := RunJoseCommand(ctx, t, []string{"jwk", "gen", "-i", template, "-o", file.Name()}, nil, nil); err != nil { return "", nil, fmt.Errorf(`failed to generate key: %w`, err) } return file.Name(), cleanup, nil } // EncryptJwe creates an encrypted JWE message and returns its filename and // a cleanup function. // The caller is responsible for calling the cleanup // function and make sure all resources are released func EncryptJwe(ctx context.Context, t *testing.T, payload []byte, alg string, keyfile string, enc string, compact bool) (string, func(), error) { t.Helper() var arg string if alg == "dir" { arg = fmt.Sprintf(`{"protected":{"alg":"dir","enc":"%s"}}`, enc) } else { arg = fmt.Sprintf(`{"protected":{"enc":"%s"}}`, enc) } cmdargs := []string{"jwe", "enc", "-k", keyfile, "-i", arg} if compact { cmdargs = append(cmdargs, "-c") } var pfile string if len(payload) > 0 { fn, pcleanup, perr := jwxtest.WriteFile(t.TempDir(), "jwx-jose-payload-*", bytes.NewReader(payload)) if perr != nil { return "", nil, fmt.Errorf(`failed to write payload to file: %w`, perr) } cmdargs = append(cmdargs, "-I", fn) pfile = fn defer pcleanup() } ofile, ocleanup, oerr := jwxtest.CreateTempFile(t.TempDir(), `jwx-jose-key-*.jwe`) if oerr != nil { return "", nil, fmt.Errorf(`failed to create temporary file: %w`, oerr) } cmdargs = append(cmdargs, "-o", ofile.Name()) if err := RunJoseCommand(ctx, t, cmdargs, nil, nil); err != nil { defer ocleanup() if pfile != "" { jwxtest.DumpFile(t, pfile) } jwxtest.DumpFile(t, keyfile) return "", nil, fmt.Errorf(`failed to encrypt message: %w`, err) } return ofile.Name(), ocleanup, nil } func DecryptJwe(ctx context.Context, t *testing.T, cfile, kfile string) ([]byte, error) { t.Helper() cmdargs := []string{"jwe", "dec", "-i", cfile, "-k", kfile} var output bytes.Buffer if err := RunJoseCommand(ctx, t, cmdargs, &output, nil); err != nil { jwxtest.DumpFile(t, cfile) jwxtest.DumpFile(t, kfile) return nil, fmt.Errorf(`failed to decrypt message: %w`, err) } return output.Bytes(), nil } func FmtJwe(ctx context.Context, t *testing.T, data []byte) ([]byte, error) { t.Helper() fn, pcleanup, perr := jwxtest.WriteFile(t.TempDir(), "jwx-jose-fmt-data-*", bytes.NewReader(data)) if perr != nil { return nil, fmt.Errorf(`failed to write data to file: %w`, perr) } defer pcleanup() cmdargs := []string{"jwe", "fmt", "-i", fn} var output bytes.Buffer if err := RunJoseCommand(ctx, t, cmdargs, &output, nil); err != nil { jwxtest.DumpFile(t, fn) return nil, fmt.Errorf(`failed to format JWE message: %w`, err) } return output.Bytes(), nil } // SignJws signs a message and returns its filename and // a cleanup function. // The caller is responsible for calling the cleanup // function and make sure all resources are released func SignJws(ctx context.Context, t *testing.T, payload []byte, keyfile string, compact bool) (string, func(), error) { t.Helper() cmdargs := []string{"jws", "sig", "-k", keyfile} if compact { cmdargs = append(cmdargs, "-c") } var pfile string if len(payload) > 0 { fn, pcleanup, perr := jwxtest.WriteFile(t.TempDir(), "jwx-jose-payload-*", bytes.NewReader(payload)) if perr != nil { return "", nil, fmt.Errorf(`failed to write payload to file: %w`, perr) } cmdargs = append(cmdargs, "-I", fn) pfile = fn defer pcleanup() } ofile, ocleanup, oerr := jwxtest.CreateTempFile(t.TempDir(), `jwx-jose-sig-*.jws`) if oerr != nil { return "", nil, fmt.Errorf(`failed to create temporary file: %w`, oerr) } cmdargs = append(cmdargs, "-o", ofile.Name()) if err := RunJoseCommand(ctx, t, cmdargs, nil, nil); err != nil { defer ocleanup() if pfile != "" { jwxtest.DumpFile(t, pfile) } jwxtest.DumpFile(t, keyfile) return "", nil, fmt.Errorf(`failed to sign message: %w`, err) } return ofile.Name(), ocleanup, nil } func VerifyJws(ctx context.Context, t *testing.T, cfile, kfile string) ([]byte, error) { t.Helper() cmdargs := []string{"jws", "ver", "-i", cfile, "-k", kfile, "-O-"} var output bytes.Buffer if err := RunJoseCommand(ctx, t, cmdargs, &output, nil); err != nil { jwxtest.DumpFile(t, cfile) jwxtest.DumpFile(t, kfile) return nil, fmt.Errorf(`failed to decrypt message: %w`, err) } return output.Bytes(), nil } golang-github-lestrrat-go-jwx-3.0.13/internal/json/000077500000000000000000000000001515060566400221715ustar00rootroot00000000000000golang-github-lestrrat-go-jwx-3.0.13/internal/json/BUILD.bazel000066400000000000000000000006261515060566400240530ustar00rootroot00000000000000load("@rules_go//go:def.bzl", "go_library") go_library( name = "json", srcs = [ "json.go", "registry.go", "stdlib.go", ], importpath = "github.com/lestrrat-go/jwx/v3/internal/json", visibility = ["//:__subpackages__"], deps = ["//internal/base64"], ) alias( name = "go_default_library", actual = ":json", visibility = ["//:__subpackages__"], ) golang-github-lestrrat-go-jwx-3.0.13/internal/json/goccy.go000066400000000000000000000020301515060566400236170ustar00rootroot00000000000000//go:build jwx_goccy package json import ( "io" "github.com/goccy/go-json" ) type Decoder = json.Decoder type Delim = json.Delim type Encoder = json.Encoder type Marshaler = json.Marshaler type Number = json.Number type RawMessage = json.RawMessage type Unmarshaler = json.Unmarshaler func Engine() string { return "github.com/goccy/go-json" } // NewDecoder respects the values specified in DecoderSettings, // and creates a Decoder that has certain features turned on/off func NewDecoder(r io.Reader) *json.Decoder { dec := json.NewDecoder(r) if UseNumber() { dec.UseNumber() } return dec } // NewEncoder is just a proxy for "encoding/json".NewEncoder func NewEncoder(w io.Writer) *json.Encoder { return json.NewEncoder(w) } // Marshal is just a proxy for "encoding/json".Marshal func Marshal(v any) ([]byte, error) { return json.Marshal(v) } // MarshalIndent is just a proxy for "encoding/json".MarshalIndent func MarshalIndent(v any, prefix, indent string) ([]byte, error) { return json.MarshalIndent(v, prefix, indent) } golang-github-lestrrat-go-jwx-3.0.13/internal/json/json.go000066400000000000000000000055751515060566400235050ustar00rootroot00000000000000package json import ( "bytes" "fmt" "os" "sync/atomic" "github.com/lestrrat-go/jwx/v3/internal/base64" ) var useNumber uint32 // TODO: at some point, change to atomic.Bool func UseNumber() bool { return atomic.LoadUint32(&useNumber) == 1 } // Sets the global configuration for json decoding func DecoderSettings(inUseNumber bool) { var val uint32 if inUseNumber { val = 1 } atomic.StoreUint32(&useNumber, val) } // Unmarshal respects the values specified in DecoderSettings, // and uses a Decoder that has certain features turned on/off func Unmarshal(b []byte, v any) error { dec := NewDecoder(bytes.NewReader(b)) return dec.Decode(v) } func AssignNextBytesToken(dst *[]byte, dec *Decoder) error { var val string if err := dec.Decode(&val); err != nil { return fmt.Errorf(`error reading next value: %w`, err) } buf, err := base64.DecodeString(val) if err != nil { return fmt.Errorf(`expected base64 encoded []byte (%T)`, val) } *dst = buf return nil } func ReadNextStringToken(dec *Decoder) (string, error) { var val string if err := dec.Decode(&val); err != nil { return "", fmt.Errorf(`error reading next value: %w`, err) } return val, nil } func AssignNextStringToken(dst **string, dec *Decoder) error { val, err := ReadNextStringToken(dec) if err != nil { return err } *dst = &val return nil } // FlattenAudience is a flag to specify if we should flatten the "aud" // entry to a string when there's only one entry. // In jwx < 1.1.8 we just dumped everything as an array of strings, // but apparently AWS Cognito doesn't handle this well. // // So now we have the ability to dump "aud" as a string if there's // only one entry, but we need to retain the old behavior so that // we don't accidentally break somebody else's code. (e.g. messing // up how signatures are calculated) var FlattenAudience uint32 func MarshalAudience(aud []string, flatten bool) ([]byte, error) { var val any if len(aud) == 1 && flatten { val = aud[0] } else { val = aud } return Marshal(val) } func EncodeAudience(enc *Encoder, aud []string, flatten bool) error { var val any if len(aud) == 1 && flatten { val = aud[0] } else { val = aud } return enc.Encode(val) } // DecodeCtx is an interface for objects that needs that extra something // when decoding JSON into an object. type DecodeCtx interface { Registry() *Registry } // DecodeCtxContainer is used to differentiate objects that can carry extra // decoding hints and those who can't. type DecodeCtxContainer interface { DecodeCtx() DecodeCtx SetDecodeCtx(DecodeCtx) } // stock decodeCtx. should cover 80% of the cases type decodeCtx struct { registry *Registry } func NewDecodeCtx(r *Registry) DecodeCtx { return &decodeCtx{registry: r} } func (dc *decodeCtx) Registry() *Registry { return dc.registry } func Dump(v any) { enc := NewEncoder(os.Stdout) enc.SetIndent("", " ") //nolint:errchkjson _ = enc.Encode(v) } golang-github-lestrrat-go-jwx-3.0.13/internal/json/registry.go000066400000000000000000000040541515060566400243730ustar00rootroot00000000000000package json import ( "fmt" "reflect" "sync" ) // CustomDecoder is the interface we expect from RegisterCustomField in jws, jwe, jwk, and jwt packages. type CustomDecoder interface { // Decode takes a JSON encoded byte slice and returns the desired // decoded value,which will be used as the value for that field // registered through RegisterCustomField Decode([]byte) (any, error) } // CustomDecodeFunc is a stateless, function-based implementation of CustomDecoder type CustomDecodeFunc func([]byte) (any, error) func (fn CustomDecodeFunc) Decode(data []byte) (any, error) { return fn(data) } type objectTypeDecoder struct { typ reflect.Type name string } func (dec *objectTypeDecoder) Decode(data []byte) (any, error) { ptr := reflect.New(dec.typ).Interface() if err := Unmarshal(data, ptr); err != nil { return nil, fmt.Errorf(`failed to decode field %s: %w`, dec.name, err) } return reflect.ValueOf(ptr).Elem().Interface(), nil } type Registry struct { mu *sync.RWMutex ctrs map[string]CustomDecoder } func NewRegistry() *Registry { return &Registry{ mu: &sync.RWMutex{}, ctrs: make(map[string]CustomDecoder), } } func (r *Registry) Register(name string, object any) { if object == nil { r.mu.Lock() defer r.mu.Unlock() delete(r.ctrs, name) return } r.mu.Lock() defer r.mu.Unlock() if ctr, ok := object.(CustomDecoder); ok { r.ctrs[name] = ctr } else { r.ctrs[name] = &objectTypeDecoder{ typ: reflect.TypeOf(object), name: name, } } } func (r *Registry) Decode(dec *Decoder, name string) (any, error) { r.mu.RLock() defer r.mu.RUnlock() if ctr, ok := r.ctrs[name]; ok { var raw RawMessage if err := dec.Decode(&raw); err != nil { return nil, fmt.Errorf(`failed to decode field %s: %w`, name, err) } v, err := ctr.Decode([]byte(raw)) if err != nil { return nil, fmt.Errorf(`failed to decode field %s: %w`, name, err) } return v, nil } var decoded any if err := dec.Decode(&decoded); err != nil { return nil, fmt.Errorf(`failed to decode field %s: %w`, name, err) } return decoded, nil } golang-github-lestrrat-go-jwx-3.0.13/internal/json/stdlib.go000066400000000000000000000017251515060566400240060ustar00rootroot00000000000000//go:build !jwx_goccy //nolint:revive package json import ( "encoding/json" "io" ) type Decoder = json.Decoder type Delim = json.Delim type Encoder = json.Encoder type Marshaler = json.Marshaler type Number = json.Number type RawMessage = json.RawMessage type Unmarshaler = json.Unmarshaler func Engine() string { return "encoding/json" } // NewDecoder respects the values specified in DecoderSettings, // and creates a Decoder that has certain features turned on/off func NewDecoder(r io.Reader) *json.Decoder { dec := json.NewDecoder(r) if UseNumber() { dec.UseNumber() } return dec } func NewEncoder(w io.Writer) *json.Encoder { return json.NewEncoder(w) } // Marshal is just a proxy for "encoding/json".Marshal func Marshal(v any) ([]byte, error) { return json.Marshal(v) } // MarshalIndent is just a proxy for "encoding/json".MarshalIndent func MarshalIndent(v any, prefix, indent string) ([]byte, error) { return json.MarshalIndent(v, prefix, indent) } golang-github-lestrrat-go-jwx-3.0.13/internal/jwxio/000077500000000000000000000000001515060566400223605ustar00rootroot00000000000000golang-github-lestrrat-go-jwx-3.0.13/internal/jwxio/BUILD.bazel000066400000000000000000000003221515060566400242330ustar00rootroot00000000000000load("@rules_go//go:def.bzl", "go_library") go_library( name = "jwxio", srcs = ["jwxio.go"], importpath = "github.com/lestrrat-go/jwx/v3/internal/jwxio", visibility = ["//:__subpackages__"], ) golang-github-lestrrat-go-jwx-3.0.13/internal/jwxio/jwxio.go000066400000000000000000000011011515060566400240400ustar00rootroot00000000000000package jwxio import ( "bytes" "errors" "io" "strings" ) var errNonFiniteSource = errors.New(`cannot read from non-finite source`) func NonFiniteSourceError() error { return errNonFiniteSource } // ReadAllFromFiniteSource reads all data from a io.Reader _if_ it comes from a // finite source. func ReadAllFromFiniteSource(rdr io.Reader) ([]byte, error) { switch rdr.(type) { case *bytes.Reader, *bytes.Buffer, *strings.Reader: data, err := io.ReadAll(rdr) if err != nil { return nil, err } return data, nil default: return nil, errNonFiniteSource } } golang-github-lestrrat-go-jwx-3.0.13/internal/jwxtest/000077500000000000000000000000001515060566400227305ustar00rootroot00000000000000golang-github-lestrrat-go-jwx-3.0.13/internal/jwxtest/BUILD.bazel000066400000000000000000000007731515060566400246150ustar00rootroot00000000000000load("@rules_go//go:def.bzl", "go_library") go_library( name = "jwxtest", srcs = ["jwxtest.go"], importpath = "github.com/lestrrat-go/jwx/v3/internal/jwxtest", visibility = ["//:__subpackages__"], deps = [ "//jwa", "//jwe", "//jwk", "//jwk/ecdsa", "//jws", "//internal/tokens", "@com_github_stretchr_testify//require", ], ) alias( name = "go_default_library", actual = ":jwxtest", visibility = ["//:__subpackages__"], ) golang-github-lestrrat-go-jwx-3.0.13/internal/jwxtest/jwxtest.go000066400000000000000000000223071515060566400247730ustar00rootroot00000000000000package jwxtest import ( "bytes" "context" "crypto/ecdh" "crypto/ecdsa" "crypto/ed25519" "crypto/rand" "crypto/rsa" "encoding/json" "fmt" "io" "os" "strings" "testing" "github.com/lestrrat-go/jwx/v3/internal/tokens" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/lestrrat-go/jwx/v3/jwe" "github.com/lestrrat-go/jwx/v3/jwk" ourecdsa "github.com/lestrrat-go/jwx/v3/jwk/ecdsa" "github.com/lestrrat-go/jwx/v3/jws" "github.com/stretchr/testify/require" ) func GenerateRsaKey() (*rsa.PrivateKey, error) { return rsa.GenerateKey(rand.Reader, 2048) } func GenerateRsaJwk() (jwk.Key, error) { key, err := GenerateRsaKey() if err != nil { return nil, fmt.Errorf(`failed to generate RSA private key: %w`, err) } k, err := jwk.Import(key) if err != nil { return nil, fmt.Errorf(`failed to generate jwk.RSAPrivateKey: %w`, err) } return k, nil } func GenerateRsaPublicJwk() (jwk.Key, error) { key, err := GenerateRsaJwk() if err != nil { return nil, fmt.Errorf(`failed to generate jwk.RSAPrivateKey: %w`, err) } return jwk.PublicKeyOf(key) } func GenerateEcdsaKey(alg jwa.EllipticCurveAlgorithm) (*ecdsa.PrivateKey, error) { crv, err := ourecdsa.CurveFromAlgorithm(alg) if err != nil { return nil, fmt.Errorf(`unknown elliptic curve algorithm: %w`, err) } return ecdsa.GenerateKey(crv, rand.Reader) } func GenerateEcdsaJwk() (jwk.Key, error) { key, err := GenerateEcdsaKey(jwa.P521()) if err != nil { return nil, fmt.Errorf(`failed to generate ECDSA private key: %w`, err) } k, err := jwk.Import(key) if err != nil { return nil, fmt.Errorf(`failed to generate jwk.ECDSAPrivateKey: %w`, err) } return k, nil } func GenerateEcdsaPublicJwk() (jwk.Key, error) { key, err := GenerateEcdsaJwk() if err != nil { return nil, fmt.Errorf(`failed to generate jwk.ECDSAPrivateKey: %w`, err) } return jwk.PublicKeyOf(key) } func GenerateSymmetricKey() []byte { sharedKey := make([]byte, 64) rand.Read(sharedKey) return sharedKey } func GenerateSymmetricJwk() (jwk.Key, error) { key, err := jwk.Import(GenerateSymmetricKey()) if err != nil { return nil, fmt.Errorf(`failed to generate jwk.SymmetricKey: %w`, err) } return key, nil } func GenerateEd25519Key() (ed25519.PrivateKey, error) { _, priv, err := ed25519.GenerateKey(rand.Reader) return priv, err } func GenerateEd25519Jwk() (jwk.Key, error) { key, err := GenerateEd25519Key() if err != nil { return nil, fmt.Errorf(`failed to generate Ed25519 private key: %w`, err) } k, err := jwk.Import(key) if err != nil { return nil, fmt.Errorf(`failed to generate jwk.OKPPrivateKey: %w`, err) } return k, nil } func GenerateX25519Key() (*ecdh.PrivateKey, error) { priv, err := ecdh.X25519().GenerateKey(rand.Reader) return priv, err } func GenerateX25519Jwk() (jwk.Key, error) { key, err := GenerateX25519Key() if err != nil { return nil, fmt.Errorf(`failed to generate X25519 private key: %w`, err) } k, err := jwk.Import(key) if err != nil { return nil, fmt.Errorf(`failed to generate jwk.OKPPrivateKey: %w`, err) } return k, nil } func WriteFile(dir, template string, src io.Reader) (string, func(), error) { file, cleanup, err := CreateTempFile(dir, template) if err != nil { return "", nil, fmt.Errorf(`failed to create temporary file: %w`, err) } if _, err := io.Copy(file, src); err != nil { defer cleanup() return "", nil, fmt.Errorf(`failed to copy content to temporary file: %w`, err) } if err := file.Sync(); err != nil { defer cleanup() return "", nil, fmt.Errorf(`failed to sync file: %w`, err) } return file.Name(), cleanup, nil } func WriteJSONFile(dir, template string, v any) (string, func(), error) { var buf bytes.Buffer enc := json.NewEncoder(&buf) if err := enc.Encode(v); err != nil { return "", nil, fmt.Errorf(`failed to encode object to JSON: %w`, err) } return WriteFile(dir, template, &buf) } func DumpFile(t *testing.T, file string) { buf, err := os.ReadFile(file) require.NoError(t, err, `failed to read file %s for debugging`, file) if isHash, isArray := bytes.ContainsRune(buf, tokens.OpenCurlyBracket), bytes.ContainsRune(buf, tokens.OpenSquareBracket); isHash || isArray { // Looks like a JSON-like thing. Dump that in a formatted manner, and // be done with it var v any if isHash { v = map[string]any{} } else { v = []any{} } require.NoError(t, json.Unmarshal(buf, &v), `failed to parse contents as JSON`) buf, _ = json.MarshalIndent(v, "", " ") t.Logf("=== BEGIN %s (formatted JSON) ===", file) t.Logf("%s", buf) t.Logf("=== END %s (formatted JSON) ===", file) return } // If the contents do not look like JSON, then we attempt to parse each content // based on heuristics (from its file name) and do our best t.Logf("=== BEGIN %s (raw) ===", file) t.Logf("%s", buf) t.Logf("=== END %s (raw) ===", file) if strings.HasSuffix(file, ".jwe") { // cross our fingers our jwe implementation works m, err := jwe.Parse(buf) require.NoError(t, err, `failed to parse JWE encrypted message`) buf, _ = json.MarshalIndent(m, "", " ") } t.Logf("=== BEGIN %s (formatted JSON) ===", file) t.Logf("%s", buf) t.Logf("=== END %s (formatted JSON) ===", file) } func CreateTempFile(dir, template string) (*os.File, func(), error) { file, err := os.CreateTemp(dir, template) if err != nil { return nil, nil, fmt.Errorf(`failed to create temporary file: %w`, err) } cleanup := func() { file.Close() os.Remove(file.Name()) } return file, cleanup, nil } func ReadFile(file string) ([]byte, error) { f, err := os.Open(file) if err != nil { return nil, fmt.Errorf(`failed to open file %s: %w`, file, err) } defer f.Close() buf, err := io.ReadAll(f) if err != nil { return nil, fmt.Errorf(`failed to read from key file %s: %w`, file, err) } return buf, nil } func ParseJwkFile(_ context.Context, file string) (jwk.Key, error) { buf, err := ReadFile(file) if err != nil { return nil, fmt.Errorf(`failed to read from key file %s: %w`, file, err) } key, err := jwk.ParseKey(buf) if err != nil { return nil, fmt.Errorf(`filed to parse JWK in key file %s: %w`, file, err) } return key, nil } func DecryptJweFile(ctx context.Context, file string, alg jwa.KeyEncryptionAlgorithm, jwkfile string) ([]byte, error) { key, err := ParseJwkFile(ctx, jwkfile) if err != nil { return nil, fmt.Errorf(`failed to parse keyfile %s: %w`, file, err) } buf, err := ReadFile(file) if err != nil { return nil, fmt.Errorf(`failed to read from encrypted file %s: %w`, file, err) } var rawkey any if err := jwk.Export(key, &rawkey); err != nil { return nil, fmt.Errorf(`failed to obtain raw key from JWK: %w`, err) } return jwe.Decrypt(buf, jwe.WithKey(alg, rawkey)) } func EncryptJweFile(ctx context.Context, dir string, payload []byte, keyalg jwa.KeyEncryptionAlgorithm, keyfile string, contentalg jwa.ContentEncryptionAlgorithm, compressalg jwa.CompressionAlgorithm) (string, func(), error) { key, err := ParseJwkFile(ctx, keyfile) if err != nil { return "", nil, fmt.Errorf(`failed to parse keyfile %s: %w`, keyfile, err) } var keyif any switch keyalg { case jwa.RSA1_5(), jwa.RSA_OAEP(), jwa.RSA_OAEP_256(), jwa.RSA_OAEP_384(), jwa.RSA_OAEP_512(): var rawkey rsa.PrivateKey if err := jwk.Export(key, &rawkey); err != nil { return "", nil, fmt.Errorf(`failed to obtain raw key: %w`, err) } keyif = rawkey.PublicKey case jwa.ECDH_ES(), jwa.ECDH_ES_A128KW(), jwa.ECDH_ES_A192KW(), jwa.ECDH_ES_A256KW(): var rawkey ecdsa.PrivateKey if err := jwk.Export(key, &rawkey); err != nil { return "", nil, fmt.Errorf(`failed to obtain raw key: %w`, err) } keyif = rawkey.PublicKey default: var rawkey []byte if err := jwk.Export(key, &rawkey); err != nil { return "", nil, fmt.Errorf(`failed to obtain raw key: %w`, err) } keyif = rawkey } buf, err := jwe.Encrypt(payload, jwe.WithKey(keyalg, keyif), jwe.WithContentEncryption(contentalg), jwe.WithCompress(compressalg)) if err != nil { return "", nil, fmt.Errorf(`failed to encrypt payload: %w`, err) } return WriteFile(dir, "jwx-test-*.jwe", bytes.NewReader(buf)) } func VerifyJwsFile(ctx context.Context, file string, alg jwa.SignatureAlgorithm, jwkfile string) ([]byte, error) { key, err := ParseJwkFile(ctx, jwkfile) if err != nil { return nil, fmt.Errorf(`failed to parse keyfile %s: %w`, file, err) } buf, err := ReadFile(file) if err != nil { return nil, fmt.Errorf(`failed to read from encrypted file %s: %w`, file, err) } var rawkey, pubkey any if err := jwk.Export(key, &rawkey); err != nil { return nil, fmt.Errorf(`failed to obtain raw key from JWK: %w`, err) } pubkey = rawkey switch tkey := rawkey.(type) { case *ecdsa.PrivateKey: pubkey = tkey.PublicKey case *rsa.PrivateKey: pubkey = tkey.PublicKey case *ed25519.PrivateKey: pubkey = tkey.Public() } return jws.Verify(buf, jws.WithKey(alg, pubkey)) } func SignJwsFile(ctx context.Context, dir string, payload []byte, alg jwa.SignatureAlgorithm, keyfile string) (string, func(), error) { key, err := ParseJwkFile(ctx, keyfile) if err != nil { return "", nil, fmt.Errorf(`failed to parse keyfile %s: %w`, keyfile, err) } buf, err := jws.Sign(payload, jws.WithKey(alg, key)) if err != nil { return "", nil, fmt.Errorf(`failed to sign payload: %w`, err) } return WriteFile(dir, "jwx-test-*.jws", bytes.NewReader(buf)) } golang-github-lestrrat-go-jwx-3.0.13/internal/keyconv/000077500000000000000000000000001515060566400226765ustar00rootroot00000000000000golang-github-lestrrat-go-jwx-3.0.13/internal/keyconv/BUILD.bazel000066400000000000000000000012711515060566400245550ustar00rootroot00000000000000load("@rules_go//go:def.bzl", "go_library", "go_test") go_library( name = "keyconv", srcs = ["keyconv.go"], importpath = "github.com/lestrrat-go/jwx/v3/internal/keyconv", visibility = ["//:__subpackages__"], deps = [ "//jwk", "@com_github_lestrrat_go_blackmagic//:blackmagic", "@org_golang_x_crypto//ed25519", ], ) go_test( name = "keyconv_test", srcs = ["keyconv_test.go"], deps = [ ":keyconv", "//internal/jwxtest", "//jwa", "//jwk", "@com_github_stretchr_testify//require", ], ) alias( name = "go_default_library", actual = ":keyconv", visibility = ["//:__subpackages__"], ) golang-github-lestrrat-go-jwx-3.0.13/internal/keyconv/keyconv.go000066400000000000000000000222561515060566400247120ustar00rootroot00000000000000package keyconv import ( "crypto" "crypto/ecdh" "crypto/ecdsa" "crypto/ed25519" "crypto/elliptic" "crypto/rsa" "fmt" "math/big" "github.com/lestrrat-go/blackmagic" "github.com/lestrrat-go/jwx/v3/jwk" ) // RSAPrivateKey assigns src to dst. // `dst` should be a pointer to a rsa.PrivateKey. // `src` may be rsa.PrivateKey, *rsa.PrivateKey, or a jwk.Key func RSAPrivateKey(dst, src any) error { if jwkKey, ok := src.(jwk.Key); ok { var raw rsa.PrivateKey if err := jwk.Export(jwkKey, &raw); err != nil { return fmt.Errorf(`failed to produce rsa.PrivateKey from %T: %w`, src, err) } src = &raw } var ptr *rsa.PrivateKey switch src := src.(type) { case rsa.PrivateKey: ptr = &src case *rsa.PrivateKey: ptr = src default: return fmt.Errorf(`keyconv: expected rsa.PrivateKey or *rsa.PrivateKey, got %T`, src) } return blackmagic.AssignIfCompatible(dst, ptr) } // RSAPublicKey assigns src to dst // `dst` should be a pointer to a non-zero rsa.PublicKey. // `src` may be rsa.PublicKey, *rsa.PublicKey, or a jwk.Key func RSAPublicKey(dst, src any) error { if jwkKey, ok := src.(jwk.Key); ok { pk, err := jwk.PublicRawKeyOf(jwkKey) if err != nil { return fmt.Errorf(`keyconv: failed to produce public key from %T: %w`, src, err) } src = pk } var ptr *rsa.PublicKey switch src := src.(type) { case rsa.PrivateKey: ptr = &src.PublicKey case *rsa.PrivateKey: ptr = &src.PublicKey case rsa.PublicKey: ptr = &src case *rsa.PublicKey: ptr = src default: return fmt.Errorf(`keyconv: expected rsa.PublicKey/rsa.PrivateKey or *rsa.PublicKey/*rsa.PrivateKey, got %T`, src) } return blackmagic.AssignIfCompatible(dst, ptr) } // ECDSAPrivateKey assigns src to dst, converting its type from a // non-pointer to a pointer func ECDSAPrivateKey(dst, src any) error { if jwkKey, ok := src.(jwk.Key); ok { var raw ecdsa.PrivateKey if err := jwk.Export(jwkKey, &raw); err != nil { return fmt.Errorf(`keyconv: failed to produce ecdsa.PrivateKey from %T: %w`, src, err) } src = &raw } var ptr *ecdsa.PrivateKey switch src := src.(type) { case ecdsa.PrivateKey: ptr = &src case *ecdsa.PrivateKey: ptr = src default: return fmt.Errorf(`keyconv: expected ecdsa.PrivateKey or *ecdsa.PrivateKey, got %T`, src) } return blackmagic.AssignIfCompatible(dst, ptr) } // ECDSAPublicKey assigns src to dst, converting its type from a // non-pointer to a pointer func ECDSAPublicKey(dst, src any) error { if jwkKey, ok := src.(jwk.Key); ok { pk, err := jwk.PublicRawKeyOf(jwkKey) if err != nil { return fmt.Errorf(`keyconv: failed to produce public key from %T: %w`, src, err) } src = pk } var ptr *ecdsa.PublicKey switch src := src.(type) { case ecdsa.PrivateKey: ptr = &src.PublicKey case *ecdsa.PrivateKey: ptr = &src.PublicKey case ecdsa.PublicKey: ptr = &src case *ecdsa.PublicKey: ptr = src default: return fmt.Errorf(`keyconv: expected ecdsa.PublicKey/ecdsa.PrivateKey or *ecdsa.PublicKey/*ecdsa.PrivateKey, got %T`, src) } return blackmagic.AssignIfCompatible(dst, ptr) } func ByteSliceKey(dst, src any) error { if jwkKey, ok := src.(jwk.Key); ok { var raw []byte if err := jwk.Export(jwkKey, &raw); err != nil { return fmt.Errorf(`keyconv: failed to produce []byte from %T: %w`, src, err) } src = raw } if _, ok := src.([]byte); !ok { return fmt.Errorf(`keyconv: expected []byte, got %T`, src) } return blackmagic.AssignIfCompatible(dst, src) } func Ed25519PrivateKey(dst, src any) error { if jwkKey, ok := src.(jwk.Key); ok { var raw ed25519.PrivateKey if err := jwk.Export(jwkKey, &raw); err != nil { return fmt.Errorf(`failed to produce ed25519.PrivateKey from %T: %w`, src, err) } src = &raw } var ptr *ed25519.PrivateKey switch src := src.(type) { case ed25519.PrivateKey: ptr = &src case *ed25519.PrivateKey: ptr = src default: return fmt.Errorf(`expected ed25519.PrivateKey or *ed25519.PrivateKey, got %T`, src) } return blackmagic.AssignIfCompatible(dst, ptr) } func Ed25519PublicKey(dst, src any) error { if jwkKey, ok := src.(jwk.Key); ok { pk, err := jwk.PublicRawKeyOf(jwkKey) if err != nil { return fmt.Errorf(`keyconv: failed to produce public key from %T: %w`, src, err) } src = pk } switch key := src.(type) { case ed25519.PrivateKey: src = key.Public() case *ed25519.PrivateKey: src = key.Public() } var ptr *ed25519.PublicKey switch src := src.(type) { case ed25519.PublicKey: ptr = &src case *ed25519.PublicKey: ptr = src case *crypto.PublicKey: tmp, ok := (*src).(ed25519.PublicKey) if !ok { return fmt.Errorf(`failed to retrieve ed25519.PublicKey out of *crypto.PublicKey`) } ptr = &tmp case crypto.PublicKey: tmp, ok := src.(ed25519.PublicKey) if !ok { return fmt.Errorf(`failed to retrieve ed25519.PublicKey out of crypto.PublicKey`) } ptr = &tmp default: return fmt.Errorf(`expected ed25519.PublicKey or *ed25519.PublicKey, got %T`, src) } return blackmagic.AssignIfCompatible(dst, ptr) } type privECDHer interface { ECDH() (*ecdh.PrivateKey, error) } func ECDHPrivateKey(dst, src any) error { var privECDH *ecdh.PrivateKey if jwkKey, ok := src.(jwk.Key); ok { var rawECDH ecdh.PrivateKey if err := jwk.Export(jwkKey, &rawECDH); err == nil { privECDH = &rawECDH } else { // If we cannot export the key as an ecdh.PrivateKey, we try to export it as an ecdsa.PrivateKey var rawECDSA ecdsa.PrivateKey if err := jwk.Export(jwkKey, &rawECDSA); err != nil { return fmt.Errorf(`keyconv: failed to produce ecdh.PrivateKey or ecdsa.PrivateKey from %T: %w`, src, err) } src = &rawECDSA } } switch src := src.(type) { case ecdh.PrivateKey: privECDH = &src case *ecdh.PrivateKey: privECDH = src case privECDHer: priv, err := src.ECDH() if err != nil { return fmt.Errorf(`keyconv: failed to convert ecdsa.PrivateKey to ecdh.PrivateKey: %w`, err) } privECDH = priv } return blackmagic.AssignIfCompatible(dst, privECDH) } type pubECDHer interface { ECDH() (*ecdh.PublicKey, error) } func ECDHPublicKey(dst, src any) error { var pubECDH *ecdh.PublicKey if jwkKey, ok := src.(jwk.Key); ok { var rawECDH ecdh.PublicKey if err := jwk.Export(jwkKey, &rawECDH); err == nil { pubECDH = &rawECDH } else { // If we cannot export the key as an ecdh.PublicKey, we try to export it as an ecdsa.PublicKey var rawECDSA ecdsa.PublicKey if err := jwk.Export(jwkKey, &rawECDSA); err != nil { return fmt.Errorf(`keyconv: failed to produce ecdh.PublicKey or ecdsa.PublicKey from %T: %w`, src, err) } src = &rawECDSA } } switch src := src.(type) { case ecdh.PublicKey: pubECDH = &src case *ecdh.PublicKey: pubECDH = src case pubECDHer: pub, err := src.ECDH() if err != nil { return fmt.Errorf(`keyconv: failed to convert ecdsa.PublicKey to ecdh.PublicKey: %w`, err) } pubECDH = pub } return blackmagic.AssignIfCompatible(dst, pubECDH) } // ecdhCurveToElliptic maps ECDH curves to elliptic curves func ecdhCurveToElliptic(ecdhCurve ecdh.Curve) (elliptic.Curve, error) { switch ecdhCurve { case ecdh.P256(): return elliptic.P256(), nil case ecdh.P384(): return elliptic.P384(), nil case ecdh.P521(): return elliptic.P521(), nil default: return nil, fmt.Errorf(`keyconv: unsupported ECDH curve: %v`, ecdhCurve) } } // ecdhPublicKeyToECDSA converts an ECDH public key to an ECDSA public key func ecdhPublicKeyToECDSA(ecdhPubKey *ecdh.PublicKey) (*ecdsa.PublicKey, error) { curve, err := ecdhCurveToElliptic(ecdhPubKey.Curve()) if err != nil { return nil, err } pubBytes := ecdhPubKey.Bytes() // Parse the uncompressed point format (0x04 prefix + X + Y coordinates) if len(pubBytes) == 0 || pubBytes[0] != 0x04 { return nil, fmt.Errorf(`keyconv: invalid ECDH public key format`) } keyLen := (len(pubBytes) - 1) / 2 if len(pubBytes) != 1+2*keyLen { return nil, fmt.Errorf(`keyconv: invalid ECDH public key length`) } x := new(big.Int).SetBytes(pubBytes[1 : 1+keyLen]) y := new(big.Int).SetBytes(pubBytes[1+keyLen:]) return &ecdsa.PublicKey{ Curve: curve, X: x, Y: y, }, nil } func ECDHToECDSA(dst, src any) error { // convert ecdh.PublicKey to ecdsa.PublicKey, ecdh.PrivateKey to ecdsa.PrivateKey // First, handle value types by converting to pointers switch s := src.(type) { case ecdh.PrivateKey: src = &s case ecdh.PublicKey: src = &s } var privBytes []byte var pubkey *ecdh.PublicKey // Now handle the actual conversion with pointer types switch src := src.(type) { case *ecdh.PrivateKey: pubkey = src.PublicKey() privBytes = src.Bytes() case *ecdh.PublicKey: pubkey = src default: return fmt.Errorf(`keyconv: expected ecdh.PrivateKey, *ecdh.PrivateKey, ecdh.PublicKey, or *ecdh.PublicKey, got %T`, src) } // convert the public key ecdsaPubKey, err := ecdhPublicKeyToECDSA(pubkey) if err != nil { return fmt.Errorf(`keyconv.ECDHToECDSA: failed to convert ECDH public key to ECDSA public key: %w`, err) } // return if we were being asked to convert *ecdh.PublicKey if privBytes == nil { return blackmagic.AssignIfCompatible(dst, ecdsaPubKey) } // Then create the private key with the public key embedded ecdsaPrivKey := &ecdsa.PrivateKey{ D: new(big.Int).SetBytes(privBytes), PublicKey: *ecdsaPubKey, } return blackmagic.AssignIfCompatible(dst, ecdsaPrivKey) } golang-github-lestrrat-go-jwx-3.0.13/internal/keyconv/keyconv_test.go000066400000000000000000000241461515060566400257510ustar00rootroot00000000000000package keyconv_test import ( "crypto/ecdh" "crypto/ecdsa" "crypto/rand" "crypto/rsa" "testing" "github.com/lestrrat-go/jwx/v3/internal/jwxtest" "github.com/lestrrat-go/jwx/v3/internal/keyconv" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/lestrrat-go/jwx/v3/jwk" "github.com/stretchr/testify/require" ) func TestKeyconv(t *testing.T) { t.Run("RSA", func(t *testing.T) { key, err := jwxtest.GenerateRsaKey() require.NoError(t, err, `rsa.GenerateKey should succeed`) t.Run("PrivateKey", func(t *testing.T) { jwkKey, _ := jwk.Import(key) testcases := []struct { Src any Error bool }{ {Src: key}, {Src: *key}, {Src: jwkKey}, {Src: struct{}{}, Error: true}, } for _, tc := range testcases { t.Run("Assign to rsa.PrivateKey", func(t *testing.T) { var dst rsa.PrivateKey var checker func(require.TestingT, error, ...any) if tc.Error { checker = require.Error } else { checker = require.NoError } checker(t, keyconv.RSAPrivateKey(&dst, tc.Src), `keyconv.RSAPrivateKey should succeed`) if !tc.Error { // Reset precomputed values; they will be computed as necessary, // and their values are not necessarily stable across runs key.Precomputed = rsa.PrecomputedValues{} dst.Precomputed = rsa.PrecomputedValues{} require.Equal(t, key, &dst, `keyconv.RSAPrivateKey should produce same value`) } }) t.Run("Assign to *rsa.PrivateKey", func(t *testing.T) { dst := &rsa.PrivateKey{} var checker func(require.TestingT, error, ...any) if tc.Error { checker = require.Error } else { checker = require.NoError } checker(t, keyconv.RSAPrivateKey(dst, tc.Src), `keyconv.RSAPrivateKey should succeed`) if !tc.Error { // Reset precomputed values; they will be computed as necessary, // and their values are not necessarily stable across runs key.Precomputed = rsa.PrecomputedValues{} dst.Precomputed = rsa.PrecomputedValues{} require.Equal(t, key, dst, `keyconv.RSAPrivateKey should produce same value`) } }) } }) t.Run("PublicKey", func(t *testing.T) { pubkey := &key.PublicKey jwkKey, _ := jwk.Import(pubkey) testcases := []struct { Src any Error bool }{ {Src: pubkey}, {Src: *pubkey}, {Src: jwkKey}, {Src: struct{}{}, Error: true}, } for _, tc := range testcases { t.Run("Assign to rsa.PublicKey", func(t *testing.T) { var dst rsa.PublicKey var checker func(require.TestingT, error, ...any) if tc.Error { checker = require.Error } else { checker = require.NoError } checker(t, keyconv.RSAPublicKey(&dst, tc.Src), `keyconv.RSAPublicKey should succeed`) if !tc.Error { require.Equal(t, pubkey, &dst, `keyconv.RSAPublicKey should produce same value`) } }) t.Run("Assign to *rsa.PublicKey", func(t *testing.T) { dst := &rsa.PublicKey{} var checker func(require.TestingT, error, ...any) if tc.Error { checker = require.Error } else { checker = require.NoError } checker(t, keyconv.RSAPublicKey(dst, tc.Src), `keyconv.RSAPublicKey should succeed`) if !tc.Error { require.Equal(t, pubkey, dst, `keyconv.RSAPublicKey should produce same value`) } }) } }) }) t.Run("ECDSA", func(t *testing.T) { key, err := jwxtest.GenerateEcdsaKey(jwa.P521()) require.NoError(t, err, `ecdsa.GenerateKey should succeed`) t.Run("PrivateKey", func(t *testing.T) { jwkKey, _ := jwk.Import(key) testcases := []struct { Src any Error bool }{ {Src: key}, {Src: *key}, {Src: jwkKey}, {Src: struct{}{}, Error: true}, } for _, tc := range testcases { t.Run("Assign to ecdsa.PrivateKey", func(t *testing.T) { var dst ecdsa.PrivateKey var checker func(require.TestingT, error, ...any) if tc.Error { checker = require.Error } else { checker = require.NoError } checker(t, keyconv.ECDSAPrivateKey(&dst, tc.Src), `keyconv.ECDSAPrivateKey should succeed`) if !tc.Error { require.Equal(t, key, &dst, `keyconv.ECDSAPrivateKey should produce same value`) } }) t.Run("Assign to *ecdsa.PrivateKey", func(t *testing.T) { dst := &ecdsa.PrivateKey{} var checker func(require.TestingT, error, ...any) if tc.Error { checker = require.Error } else { checker = require.NoError } checker(t, keyconv.ECDSAPrivateKey(dst, tc.Src), `keyconv.ECDSAPrivateKey should succeed`) if !tc.Error { require.Equal(t, key, dst, `keyconv.ECDSAPrivateKey should produce same value`) } }) } }) t.Run("PublicKey", func(t *testing.T) { pubkey := &key.PublicKey jwkKey, _ := jwk.Import(pubkey) testcases := []struct { Src any Error bool }{ {Src: pubkey}, {Src: *pubkey}, {Src: jwkKey}, {Src: struct{}{}, Error: true}, } for _, tc := range testcases { t.Run("Assign to ecdsa.PublicKey", func(t *testing.T) { var dst ecdsa.PublicKey var checker func(require.TestingT, error, ...any) if tc.Error { checker = require.Error } else { checker = require.NoError } checker(t, keyconv.ECDSAPublicKey(&dst, tc.Src), `keyconv.ECDSAPublicKey should succeed`) if !tc.Error { require.Equal(t, pubkey, &dst, `keyconv.ECDSAPublicKey should produce same value`) } }) t.Run("Assign to *ecdsa.PublicKey", func(t *testing.T) { dst := &ecdsa.PublicKey{} var checker func(require.TestingT, error, ...any) if tc.Error { checker = require.Error } else { checker = require.NoError } checker(t, keyconv.ECDSAPublicKey(dst, tc.Src), `keyconv.ECDSAPublicKey should succeed`) if !tc.Error { require.Equal(t, pubkey, dst, `keyconv.ECDSAPublicKey should produce same value`) } }) } }) }) } func TestECDHToECDSA(t *testing.T) { curves := []struct { name string ecdhCurve ecdh.Curve jwaAlg jwa.EllipticCurveAlgorithm }{ {"P256", ecdh.P256(), jwa.P256()}, {"P384", ecdh.P384(), jwa.P384()}, {"P521", ecdh.P521(), jwa.P521()}, } for _, curve := range curves { t.Run(curve.name, func(t *testing.T) { // Generate an ECDSA key for comparison ecdsaKey, err := jwxtest.GenerateEcdsaKey(curve.jwaAlg) require.NoError(t, err, `ecdsa.GenerateKey should succeed`) // Convert ECDSA key to ECDH key ecdhPrivKey, err := ecdsaKey.ECDH() require.NoError(t, err, `ECDSA to ECDH conversion should succeed`) ecdhPubKey := ecdhPrivKey.PublicKey() t.Run("PrivateKey", func(t *testing.T) { testcases := []struct { name string src any error bool }{ {"*ecdh.PrivateKey", ecdhPrivKey, false}, {"invalid type", "not a key", true}, } for _, tc := range testcases { t.Run(tc.name, func(t *testing.T) { var dst *ecdsa.PrivateKey err := keyconv.ECDHToECDSA(&dst, tc.src) if tc.error { require.Error(t, err, `ECDHToECDSA should fail for invalid input`) } else { require.NoError(t, err, `ECDHToECDSA should succeed`) require.NotNil(t, dst, `destination should not be nil`) // Verify the converted key has the same curve require.Equal(t, ecdsaKey.Curve, dst.Curve, `curves should match`) // Verify the private key values match require.Equal(t, ecdsaKey.D, dst.D, `private key values should match`) // Verify the public key coordinates match require.Equal(t, ecdsaKey.PublicKey.X, dst.PublicKey.X, `X coordinates should match`) require.Equal(t, ecdsaKey.PublicKey.Y, dst.PublicKey.Y, `Y coordinates should match`) } }) } }) t.Run("PublicKey", func(t *testing.T) { testcases := []struct { name string src any error bool }{ {"*ecdh.PublicKey", ecdhPubKey, false}, {"ecdh.PublicKey", *ecdhPubKey, false}, {"invalid type", "not a key", true}, } for _, tc := range testcases { t.Run(tc.name, func(t *testing.T) { var dst *ecdsa.PublicKey err := keyconv.ECDHToECDSA(&dst, tc.src) if tc.error { require.Error(t, err, `ECDHToECDSA should fail for invalid input`) } else { require.NoError(t, err, `ECDHToECDSA should succeed`) require.NotNil(t, dst, `destination should not be nil`) // Verify the converted key has the same curve require.Equal(t, ecdsaKey.PublicKey.Curve, dst.Curve, `curves should match`) // Verify the public key coordinates match require.Equal(t, ecdsaKey.PublicKey.X, dst.X, `X coordinates should match`) require.Equal(t, ecdsaKey.PublicKey.Y, dst.Y, `Y coordinates should match`) } }) } }) t.Run("RoundTrip", func(t *testing.T) { // Test that ECDSA -> ECDH -> ECDSA produces the same key var convertedPrivKey *ecdsa.PrivateKey err := keyconv.ECDHToECDSA(&convertedPrivKey, ecdhPrivKey) require.NoError(t, err, `ECDHToECDSA should succeed`) var convertedPubKey *ecdsa.PublicKey err = keyconv.ECDHToECDSA(&convertedPubKey, ecdhPubKey) require.NoError(t, err, `ECDHToECDSA should succeed`) // Verify the keys are equivalent require.Equal(t, ecdsaKey.D, convertedPrivKey.D, `private key values should match`) require.Equal(t, ecdsaKey.PublicKey.X, convertedPrivKey.PublicKey.X, `private key X coordinates should match`) require.Equal(t, ecdsaKey.PublicKey.Y, convertedPrivKey.PublicKey.Y, `private key Y coordinates should match`) require.Equal(t, ecdsaKey.PublicKey.X, convertedPubKey.X, `public key X coordinates should match`) require.Equal(t, ecdsaKey.PublicKey.Y, convertedPubKey.Y, `public key Y coordinates should match`) }) }) } t.Run("UnsupportedCurve", func(t *testing.T) { // Create a mock ECDH key with X25519 curve (not supported for ECDSA) x25519Key, err := ecdh.X25519().GenerateKey(rand.Reader) require.NoError(t, err, `X25519 key generation should succeed`) var dst *ecdsa.PrivateKey err = keyconv.ECDHToECDSA(&dst, x25519Key) require.Error(t, err, `ECDHToECDSA should fail for unsupported curve`) require.Contains(t, err.Error(), "unsupported ECDH curve", `error should mention unsupported curve`) }) } golang-github-lestrrat-go-jwx-3.0.13/internal/pool/000077500000000000000000000000001515060566400221715ustar00rootroot00000000000000golang-github-lestrrat-go-jwx-3.0.13/internal/pool/BUILD.bazel000066400000000000000000000012011515060566400240410ustar00rootroot00000000000000load("@rules_go//go:def.bzl", "go_library", "go_test") go_library( name = "pool", srcs = [ "big_int.go", "byte_slice.go", "bytes_buffer.go", "error_slice.go", "key_to_error_map.go", "pool.go", ], importpath = "github.com/lestrrat-go/jwx/v3/internal/pool", visibility = ["//:__subpackages__"], ) alias( name = "go_default_library", actual = ":pool", visibility = ["//:__subpackages__"], ) go_test( name = "pool_test", srcs = [ "byte_slice_test.go", ], deps = [ ":pool", "@com_github_stretchr_testify//require", ], )golang-github-lestrrat-go-jwx-3.0.13/internal/pool/big_int.go000066400000000000000000000005211515060566400241310ustar00rootroot00000000000000package pool import "math/big" var bigIntPool = New[*big.Int](allocBigInt, freeBigInt) func allocBigInt() *big.Int { return &big.Int{} } func freeBigInt(b *big.Int) *big.Int { b.SetInt64(0) // Reset the value to zero return b } // BigInt returns a pool of *big.Int instances. func BigInt() *Pool[*big.Int] { return bigIntPool } golang-github-lestrrat-go-jwx-3.0.13/internal/pool/byte_slice.go000066400000000000000000000005541515060566400246460ustar00rootroot00000000000000package pool var byteSlicePool = SlicePool[byte]{ pool: New[[]byte](allocByteSlice, freeByteSlice), } func allocByteSlice() []byte { return make([]byte, 0, 64) // Default capacity of 64 bytes } func freeByteSlice(b []byte) []byte { clear(b) b = b[:0] // Reset the slice to zero length return b } func ByteSlice() SlicePool[byte] { return byteSlicePool } golang-github-lestrrat-go-jwx-3.0.13/internal/pool/byte_slice_test.go000066400000000000000000000043251515060566400257050ustar00rootroot00000000000000// Package pool provides tests for ByteSlicePool to ensure it behaves correctly under // both sequential and concurrent use. package pool_test import ( "bytes" "sync" "testing" "github.com/lestrrat-go/jwx/v3/internal/pool" "github.com/stretchr/testify/require" ) // TestByteSlicePoolSequential verifies basic Get and Put behavior of ByteSlicePool. func TestByteSlicePoolSequential(t *testing.T) { bs := pool.ByteSlice() // First Get should provide a slice with default capacity and zero length b := bs.Get() require.Equal(t, 0, len(b), "initial slice should have length 0") require.GreaterOrEqual(t, cap(b), 64, "initial capacity should be at least 64") // Append data, then put back and get again b = append(b, 1, 2, 3) require.Equal(t, 3, len(b), "slice length after append should reflect appended items") bs.Put(b) b2 := bs.Get() // After Put, slice should be reset to zero length require.Equal(t, 0, len(b2), "slice length after Put should be reset to 0") require.GreaterOrEqual(t, cap(b2), 64, "capacity should remain at least 64 after reset") } // TestByteSlicePoolConcurrent verifies that ByteSlicePool can be used safely // from multiple goroutines without data corruption or overlapping usage. func TestByteSlicePoolConcurrent(t *testing.T) { const n = 30 const capacity = 128 // Ensure capacity is sufficient for all goroutines bs := pool.ByteSlice() var wg sync.WaitGroup contents := make([]string, n) // Concurrently Get slices and write unique data into each wg.Add(n) for i := range n { go func() { defer wg.Done() b := bs.GetCapacity(capacity) defer bs.Put(b) // capacity should be sufficient require.GreaterOrEqual(t, cap(b), capacity, "capacity should be at least default for goroutine %d", i) require.Len(t, b, 0, "slice should be empty at start for goroutine %d", i) for range capacity { b = append(b, byte(i+0x21)) } contents[i] = string(b) // create a copy so it doesn't get modified }() } wg.Wait() require.Len(t, contents, n, "should have collected results from all goroutines") for i, s := range contents { expected := bytes.Repeat([]byte{byte(i + 0x21)}, capacity) require.Equal(t, string(expected), s, "content should match for goroutine %d", i) } } golang-github-lestrrat-go-jwx-3.0.13/internal/pool/bytes_buffer.go000066400000000000000000000005001515060566400251720ustar00rootroot00000000000000package pool import "bytes" var bytesBufferPool = New[*bytes.Buffer](allocBytesBuffer, freeBytesBuffer) func allocBytesBuffer() *bytes.Buffer { return &bytes.Buffer{} } func freeBytesBuffer(b *bytes.Buffer) *bytes.Buffer { b.Reset() return b } func BytesBuffer() *Pool[*bytes.Buffer] { return bytesBufferPool } golang-github-lestrrat-go-jwx-3.0.13/internal/pool/error_slice.go000066400000000000000000000004561515060566400250350ustar00rootroot00000000000000package pool var errorSlicePool = New[[]error](allocErrorSlice, freeErrorSlice) func allocErrorSlice() []error { return make([]error, 0, 1) } func freeErrorSlice(s []error) []error { // Reset the slice to its zero value return s[:0] } func ErrorSlice() *Pool[[]error] { return errorSlicePool } golang-github-lestrrat-go-jwx-3.0.13/internal/pool/key_to_error_map.go000066400000000000000000000006771515060566400260720ustar00rootroot00000000000000package pool var keyToErrorMapPool = New[map[string]error](allocKeyToErrorMap, freeKeyToErrorMap) func allocKeyToErrorMap() map[string]error { return make(map[string]error) } func freeKeyToErrorMap(m map[string]error) map[string]error { for k := range m { delete(m, k) // Clear the map } return m } // KeyToErrorMap returns a pool of map[string]error instances. func KeyToErrorMap() *Pool[map[string]error] { return keyToErrorMapPool } golang-github-lestrrat-go-jwx-3.0.13/internal/pool/pool.go000066400000000000000000000033631515060566400234760ustar00rootroot00000000000000package pool import ( "sync" ) type Pool[T any] struct { pool sync.Pool destructor func(T) T } // New creates a new Pool instance for the type T. // The allocator function is used to create new instances of T when the pool is empty. // The destructor function is used to clean up instances of T before they are returned to the pool. // The destructor should reset the state of T to a clean state, so it can be reused, and // return the modified instance of T. This is required for cases when you reset operations // can modify the underlying data structure, such as slices or maps. func New[T any](allocator func() T, destructor func(T) T) *Pool[T] { return &Pool[T]{ pool: sync.Pool{ New: func() any { return allocator() }, }, destructor: destructor, } } // Get retrieves an item of type T from the pool. func (p *Pool[T]) Get() T { //nolint:forcetypeassert return p.pool.Get().(T) } // Put returns an item of type T to the pool. // The item is first processed by the destructor function to ensure it is in a clean state. func (p *Pool[T]) Put(item T) { p.pool.Put(p.destructor(item)) } // SlicePool is a specialized pool for slices of type T. It is identical to Pool[T] but // provides additional functionality to get slices with a specific capacity. type SlicePool[T any] struct { pool *Pool[[]T] } func NewSlicePool[T any](allocator func() []T, destructor func([]T) []T) SlicePool[T] { return SlicePool[T]{ pool: New(allocator, destructor), } } func (p SlicePool[T]) Get() []T { return p.pool.Get() } func (p SlicePool[T]) GetCapacity(capacity int) []T { if capacity <= 0 { return p.Get() } s := p.Get() if cap(s) < capacity { s = make([]T, 0, capacity) } return s } func (p SlicePool[T]) Put(s []T) { p.pool.Put(s) } golang-github-lestrrat-go-jwx-3.0.13/internal/tokens/000077500000000000000000000000001515060566400225235ustar00rootroot00000000000000golang-github-lestrrat-go-jwx-3.0.13/internal/tokens/BUILD.bazel000066400000000000000000000005511515060566400244020ustar00rootroot00000000000000load("@rules_go//go:def.bzl", "go_library") go_library( name = "tokens", srcs = [ "jwe_tokens.go", "tokens.go", ], importpath = "github.com/lestrrat-go/jwx/v3/internal/tokens", visibility = ["//:__subpackages__"], ) alias( name = "go_default_library", actual = ":tokens", visibility = ["//:__subpackages__"], ) golang-github-lestrrat-go-jwx-3.0.13/internal/tokens/jwe_tokens.go000066400000000000000000000020071515060566400252210ustar00rootroot00000000000000package tokens // JWE Key Encryption Algorithms const ( // RSA algorithms RSA1_5 = "RSA1_5" RSA_OAEP = "RSA-OAEP" RSA_OAEP_256 = "RSA-OAEP-256" RSA_OAEP_384 = "RSA-OAEP-384" RSA_OAEP_512 = "RSA-OAEP-512" // AES Key Wrap algorithms A128KW = "A128KW" A192KW = "A192KW" A256KW = "A256KW" // AES GCM Key Wrap algorithms A128GCMKW = "A128GCMKW" A192GCMKW = "A192GCMKW" A256GCMKW = "A256GCMKW" // ECDH-ES algorithms ECDH_ES = "ECDH-ES" ECDH_ES_A128KW = "ECDH-ES+A128KW" ECDH_ES_A192KW = "ECDH-ES+A192KW" ECDH_ES_A256KW = "ECDH-ES+A256KW" // PBES2 algorithms PBES2_HS256_A128KW = "PBES2-HS256+A128KW" PBES2_HS384_A192KW = "PBES2-HS384+A192KW" PBES2_HS512_A256KW = "PBES2-HS512+A256KW" // Direct key agreement DIRECT = "dir" ) // JWE Content Encryption Algorithms const ( // AES GCM algorithms A128GCM = "A128GCM" A192GCM = "A192GCM" A256GCM = "A256GCM" // AES CBC + HMAC algorithms A128CBC_HS256 = "A128CBC-HS256" A192CBC_HS384 = "A192CBC-HS384" A256CBC_HS512 = "A256CBC-HS512" ) golang-github-lestrrat-go-jwx-3.0.13/internal/tokens/tokens.go000066400000000000000000000020341515060566400243540ustar00rootroot00000000000000package tokens const ( CloseCurlyBracket = '}' CloseSquareBracket = ']' Colon = ':' Comma = ',' DoubleQuote = '"' OpenCurlyBracket = '{' OpenSquareBracket = '[' Period = '.' ) // Cryptographic key sizes const ( KeySize16 = 16 KeySize24 = 24 KeySize32 = 32 KeySize48 = 48 // A192CBC_HS384 key size KeySize64 = 64 // A256CBC_HS512 key size ) // Bit/byte conversion factors const ( BitsPerByte = 8 BytesPerBit = 1.0 / 8 ) // Key wrapping constants const ( KeywrapChunkLen = 8 KeywrapRounds = 6 // RFC 3394 key wrap rounds KeywrapBlockSize = 8 // Key wrap block size in bytes ) // AES-GCM constants const ( GCMIVSize = 12 // GCM IV size in bytes (96 bits) GCMTagSize = 16 // GCM tag size in bytes (128 bits) ) // PBES2 constants const ( PBES2DefaultIterations = 10000 // Default PBKDF2 iteration count PBES2NullByteSeparator = 0 // Null byte separator for PBES2 ) // RSA key generation constants const ( RSAKeyGenMultiplier = 2 // RSA key generation size multiplier ) golang-github-lestrrat-go-jwx-3.0.13/jwa/000077500000000000000000000000001515060566400201655ustar00rootroot00000000000000golang-github-lestrrat-go-jwx-3.0.13/jwa/BUILD.bazel000066400000000000000000000020541515060566400220440ustar00rootroot00000000000000load("@rules_go//go:def.bzl", "go_library", "go_test") go_library( name = "jwa", srcs = [ "compression_gen.go", "content_encryption_gen.go", "elliptic_gen.go", "jwa.go", "key_encryption_gen.go", "key_type_gen.go", "options_gen.go", "signature_gen.go", ], importpath = "github.com/lestrrat-go/jwx/v3/jwa", visibility = ["//visibility:public"], deps = [ "//internal/tokens", "@com_github_lestrrat_go_option_v2//:option", ], ) go_test( name = "jwa_test", srcs = [ "compression_gen_test.go", "content_encryption_gen_test.go", "elliptic_gen_test.go", "jwa_test.go", "key_encryption_gen_test.go", "key_type_gen_test.go", "signature_gen_test.go", ], deps = [ ":jwa", "@com_github_stretchr_testify//require", "@com_github_lestrrat_go_option_v2//:option", ], ) alias( name = "go_default_library", actual = ":jwa", visibility = ["//visibility:public"], ) golang-github-lestrrat-go-jwx-3.0.13/jwa/README.md000066400000000000000000000004341515060566400214450ustar00rootroot00000000000000# JWA [![Go Reference](https://pkg.go.dev/badge/github.com/lestrrat-go/jwx/v3/jwa.svg)](https://pkg.go.dev/github.com/lestrrat-go/jwx/v3/jwa) Package [github.com/lestrrat-go/jwx/v3/jwa](./jwa) defines the various algorithm described in [RFC7518](https://tools.ietf.org/html/rfc7518) golang-github-lestrrat-go-jwx-3.0.13/jwa/compression_gen.go000066400000000000000000000120451515060566400237100ustar00rootroot00000000000000// Code generated by tools/cmd/genjwa/main.go. DO NOT EDIT. package jwa import ( "encoding/json" "fmt" "sort" "sync" ) var muAllCompressionAlgorithm sync.RWMutex var allCompressionAlgorithm = map[string]CompressionAlgorithm{} var muListCompressionAlgorithm sync.RWMutex var listCompressionAlgorithm []CompressionAlgorithm var builtinCompressionAlgorithm = map[string]struct{}{} func init() { // builtin values for CompressionAlgorithm algorithms := make([]CompressionAlgorithm, 2) algorithms[0] = NewCompressionAlgorithm("DEF") algorithms[1] = NewCompressionAlgorithm("") RegisterCompressionAlgorithm(algorithms...) } // Deflate returns an object representing the "DEF" content compression algorithm value. Using this value specifies that the content should be compressed using DEFLATE (RFC 1951). func Deflate() CompressionAlgorithm { return lookupBuiltinCompressionAlgorithm("DEF") } // NoCompress returns an object representing an empty compression algorithm value. Using this value specifies that the content should not be compressed. func NoCompress() CompressionAlgorithm { return lookupBuiltinCompressionAlgorithm("") } func lookupBuiltinCompressionAlgorithm(name string) CompressionAlgorithm { muAllCompressionAlgorithm.RLock() v, ok := allCompressionAlgorithm[name] muAllCompressionAlgorithm.RUnlock() if !ok { panic(fmt.Sprintf(`jwa: CompressionAlgorithm %q not registered`, name)) } return v } // CompressionAlgorithm represents the compression algorithms as described in https://tools.ietf.org/html/rfc7518#section-7.3 type CompressionAlgorithm struct { name string deprecated bool } func (s CompressionAlgorithm) String() string { return s.name } // IsDeprecated returns true if the CompressionAlgorithm object is deprecated. func (s CompressionAlgorithm) IsDeprecated() bool { return s.deprecated } // EmptyCompressionAlgorithm returns an empty CompressionAlgorithm object, used as a zero value. func EmptyCompressionAlgorithm() CompressionAlgorithm { return CompressionAlgorithm{} } // NewCompressionAlgorithm creates a new CompressionAlgorithm object with the given name. func NewCompressionAlgorithm(name string, options ...NewAlgorithmOption) CompressionAlgorithm { var deprecated bool for _, option := range options { switch option.Ident() { case identDeprecated{}: if err := option.Value(&deprecated); err != nil { panic("jwa.NewCompressionAlgorithm: WithDeprecated option must be a boolean") } } } return CompressionAlgorithm{name: name, deprecated: deprecated} } // LookupCompressionAlgorithm returns the CompressionAlgorithm object for the given name. func LookupCompressionAlgorithm(name string) (CompressionAlgorithm, bool) { muAllCompressionAlgorithm.RLock() v, ok := allCompressionAlgorithm[name] muAllCompressionAlgorithm.RUnlock() return v, ok } // RegisterCompressionAlgorithm registers a new CompressionAlgorithm. The signature value must be immutable // and safe to be used by multiple goroutines, as it is going to be shared with all other users of this library. func RegisterCompressionAlgorithm(algorithms ...CompressionAlgorithm) { muAllCompressionAlgorithm.Lock() for _, alg := range algorithms { allCompressionAlgorithm[alg.String()] = alg } muAllCompressionAlgorithm.Unlock() rebuildCompressionAlgorithm() } // UnregisterCompressionAlgorithm unregisters a CompressionAlgorithm from its known database. // Non-existent entries, as well as built-in algorithms will silently be ignored. func UnregisterCompressionAlgorithm(algorithms ...CompressionAlgorithm) { muAllCompressionAlgorithm.Lock() for _, alg := range algorithms { if _, ok := builtinCompressionAlgorithm[alg.String()]; ok { continue } delete(allCompressionAlgorithm, alg.String()) } muAllCompressionAlgorithm.Unlock() rebuildCompressionAlgorithm() } func rebuildCompressionAlgorithm() { list := make([]CompressionAlgorithm, 0, len(allCompressionAlgorithm)) muAllCompressionAlgorithm.RLock() for _, v := range allCompressionAlgorithm { list = append(list, v) } muAllCompressionAlgorithm.RUnlock() sort.Slice(list, func(i, j int) bool { return list[i].String() < list[j].String() }) muListCompressionAlgorithm.Lock() listCompressionAlgorithm = list muListCompressionAlgorithm.Unlock() } // CompressionAlgorithms returns a list of all available values for CompressionAlgorithm. func CompressionAlgorithms() []CompressionAlgorithm { muListCompressionAlgorithm.RLock() defer muListCompressionAlgorithm.RUnlock() return listCompressionAlgorithm } // MarshalJSON serializes the CompressionAlgorithm object to a JSON string. func (s CompressionAlgorithm) MarshalJSON() ([]byte, error) { return json.Marshal(s.String()) } // UnmarshalJSON deserializes the JSON string to a CompressionAlgorithm object. func (s *CompressionAlgorithm) UnmarshalJSON(data []byte) error { var name string if err := json.Unmarshal(data, &name); err != nil { return fmt.Errorf(`failed to unmarshal CompressionAlgorithm: %w`, err) } v, ok := LookupCompressionAlgorithm(name) if !ok { return fmt.Errorf(`unknown CompressionAlgorithm: %q`, name) } *s = v return nil } golang-github-lestrrat-go-jwx-3.0.13/jwa/compression_gen_test.go000066400000000000000000000076661515060566400247640ustar00rootroot00000000000000// Code generated by tools/cmd/genjwa/main.go. DO NOT EDIT package jwa_test import ( "encoding/json" "strconv" "testing" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/stretchr/testify/require" ) func TestCompressionAlgorithm(t *testing.T) { t.Parallel() t.Run(`Lookup the object`, func(t *testing.T) { t.Parallel() v, ok := jwa.LookupCompressionAlgorithm("DEF") require.True(t, ok, `Lookup should succeed`) require.Equal(t, jwa.Deflate(), v, `Lookup value should be equal to constant`) }) t.Run(`Unmarshal the string DEF`, func(t *testing.T) { t.Parallel() var dst jwa.CompressionAlgorithm require.NoError(t, json.Unmarshal([]byte(strconv.Quote("DEF")), &dst), `UnmarshalJSON is successful`) require.Equal(t, jwa.Deflate(), dst, `unmarshaled value should be equal to constant`) }) t.Run(`stringification for DEF`, func(t *testing.T) { t.Parallel() require.Equal(t, "DEF", jwa.Deflate().String(), `stringified value matches`) }) t.Run(`Lookup the object`, func(t *testing.T) { t.Parallel() v, ok := jwa.LookupCompressionAlgorithm("") require.True(t, ok, `Lookup should succeed`) require.Equal(t, jwa.NoCompress(), v, `Lookup value should be equal to constant`) }) t.Run(`Unmarshal the string `, func(t *testing.T) { t.Parallel() var dst jwa.CompressionAlgorithm require.NoError(t, json.Unmarshal([]byte(strconv.Quote("")), &dst), `UnmarshalJSON is successful`) require.Equal(t, jwa.NoCompress(), dst, `unmarshaled value should be equal to constant`) }) t.Run(`stringification for `, func(t *testing.T) { t.Parallel() require.Equal(t, "", jwa.NoCompress().String(), `stringified value matches`) }) t.Run(`Unmarshal should fail for invalid value (totally made up) string value`, func(t *testing.T) { t.Parallel() var dst jwa.CompressionAlgorithm require.Error(t, json.Unmarshal([]byte(`totallyInvalidValue`), &dst), `Unmarshal should fail`) }) t.Run(`check list of elements`, func(t *testing.T) { t.Parallel() var expected = map[jwa.CompressionAlgorithm]struct{}{ jwa.Deflate(): {}, jwa.NoCompress(): {}, } for _, v := range jwa.CompressionAlgorithms() { _, ok := expected[v] require.True(t, ok, `%q should be in the list for CompressionAlgorithm`, v) delete(expected, v) } require.Len(t, expected, 0) }) } // Note: this test can NOT be run in parallel as it uses options with global effect. func TestCompressionAlgorithmCustomAlgorithm(t *testing.T) { // These subtests can NOT be run in parallel as options with global effect change. const customAlgorithmValue = `custom-algorithm` customAlgorithm := jwa.NewCompressionAlgorithm(customAlgorithmValue) // Unregister the custom algorithm, in case tests fail. t.Cleanup(func() { jwa.UnregisterCompressionAlgorithm(customAlgorithm) }) t.Run(`with custom algorithm registered`, func(t *testing.T) { jwa.RegisterCompressionAlgorithm(customAlgorithm) t.Run(`Lookup the object`, func(t *testing.T) { t.Parallel() v, ok := jwa.LookupCompressionAlgorithm(customAlgorithmValue) require.True(t, ok, `Lookup should succeed`) require.Equal(t, customAlgorithm, v, `Lookup value should be equal to constant`) }) t.Run(`Unmarshal custom algorithm`, func(t *testing.T) { t.Parallel() var dst jwa.CompressionAlgorithm require.NoError(t, json.Unmarshal([]byte(strconv.Quote(customAlgorithmValue)), &dst), `Unmarshal is successful`) require.Equal(t, customAlgorithm, dst, `accepted value should be equal to variable`) }) }) t.Run(`with custom algorithm deregistered`, func(t *testing.T) { jwa.UnregisterCompressionAlgorithm(customAlgorithm) t.Run(`Lookup the object`, func(t *testing.T) { t.Parallel() _, ok := jwa.LookupCompressionAlgorithm(customAlgorithmValue) require.False(t, ok, `Lookup should fail`) }) t.Run(`Unmarshal custom algorithm`, func(t *testing.T) { t.Parallel() var dst jwa.CompressionAlgorithm require.Error(t, json.Unmarshal([]byte(customAlgorithmValue), &dst), `Unmarshal should fail`) }) }) } golang-github-lestrrat-go-jwx-3.0.13/jwa/content_encryption_gen.go000066400000000000000000000156401515060566400252770ustar00rootroot00000000000000// Code generated by tools/cmd/genjwa/main.go. DO NOT EDIT. package jwa import ( "encoding/json" "fmt" "sort" "sync" "github.com/lestrrat-go/jwx/v3/internal/tokens" ) var muAllContentEncryptionAlgorithm sync.RWMutex var allContentEncryptionAlgorithm = map[string]ContentEncryptionAlgorithm{} var muListContentEncryptionAlgorithm sync.RWMutex var listContentEncryptionAlgorithm []ContentEncryptionAlgorithm var builtinContentEncryptionAlgorithm = map[string]struct{}{} func init() { // builtin values for ContentEncryptionAlgorithm algorithms := make([]ContentEncryptionAlgorithm, 6) algorithms[0] = NewContentEncryptionAlgorithm(tokens.A128CBC_HS256) algorithms[1] = NewContentEncryptionAlgorithm(tokens.A128GCM) algorithms[2] = NewContentEncryptionAlgorithm(tokens.A192CBC_HS384) algorithms[3] = NewContentEncryptionAlgorithm(tokens.A192GCM) algorithms[4] = NewContentEncryptionAlgorithm(tokens.A256CBC_HS512) algorithms[5] = NewContentEncryptionAlgorithm(tokens.A256GCM) RegisterContentEncryptionAlgorithm(algorithms...) } // A128CBC_HS256 returns an object representing A128CBC-HS256. Using this value specifies that the content should be encrypted using AES-CBC + HMAC-SHA256 (128). func A128CBC_HS256() ContentEncryptionAlgorithm { return lookupBuiltinContentEncryptionAlgorithm(tokens.A128CBC_HS256) } // A128GCM returns an object representing A128GCM. Using this value specifies that the content should be encrypted using AES-GCM (128). func A128GCM() ContentEncryptionAlgorithm { return lookupBuiltinContentEncryptionAlgorithm(tokens.A128GCM) } // A192CBC_HS384 returns an object representing A192CBC-HS384. Using this value specifies that the content should be encrypted using AES-CBC + HMAC-SHA384 (192). func A192CBC_HS384() ContentEncryptionAlgorithm { return lookupBuiltinContentEncryptionAlgorithm(tokens.A192CBC_HS384) } // A192GCM returns an object representing A192GCM. Using this value specifies that the content should be encrypted using AES-GCM (192). func A192GCM() ContentEncryptionAlgorithm { return lookupBuiltinContentEncryptionAlgorithm(tokens.A192GCM) } // A256CBC_HS512 returns an object representing A256CBC-HS512. Using this value specifies that the content should be encrypted using AES-CBC + HMAC-SHA512 (256). func A256CBC_HS512() ContentEncryptionAlgorithm { return lookupBuiltinContentEncryptionAlgorithm(tokens.A256CBC_HS512) } // A256GCM returns an object representing A256GCM. Using this value specifies that the content should be encrypted using AES-GCM (256). func A256GCM() ContentEncryptionAlgorithm { return lookupBuiltinContentEncryptionAlgorithm(tokens.A256GCM) } func lookupBuiltinContentEncryptionAlgorithm(name string) ContentEncryptionAlgorithm { muAllContentEncryptionAlgorithm.RLock() v, ok := allContentEncryptionAlgorithm[name] muAllContentEncryptionAlgorithm.RUnlock() if !ok { panic(fmt.Sprintf(`jwa: ContentEncryptionAlgorithm %q not registered`, name)) } return v } // ContentEncryptionAlgorithm represents the various encryption algorithms as described in https://tools.ietf.org/html/rfc7518#section-5 type ContentEncryptionAlgorithm struct { name string deprecated bool } func (s ContentEncryptionAlgorithm) String() string { return s.name } // IsDeprecated returns true if the ContentEncryptionAlgorithm object is deprecated. func (s ContentEncryptionAlgorithm) IsDeprecated() bool { return s.deprecated } // EmptyContentEncryptionAlgorithm returns an empty ContentEncryptionAlgorithm object, used as a zero value. func EmptyContentEncryptionAlgorithm() ContentEncryptionAlgorithm { return ContentEncryptionAlgorithm{} } // NewContentEncryptionAlgorithm creates a new ContentEncryptionAlgorithm object with the given name. func NewContentEncryptionAlgorithm(name string, options ...NewAlgorithmOption) ContentEncryptionAlgorithm { var deprecated bool for _, option := range options { switch option.Ident() { case identDeprecated{}: if err := option.Value(&deprecated); err != nil { panic("jwa.NewContentEncryptionAlgorithm: WithDeprecated option must be a boolean") } } } return ContentEncryptionAlgorithm{name: name, deprecated: deprecated} } // LookupContentEncryptionAlgorithm returns the ContentEncryptionAlgorithm object for the given name. func LookupContentEncryptionAlgorithm(name string) (ContentEncryptionAlgorithm, bool) { muAllContentEncryptionAlgorithm.RLock() v, ok := allContentEncryptionAlgorithm[name] muAllContentEncryptionAlgorithm.RUnlock() return v, ok } // RegisterContentEncryptionAlgorithm registers a new ContentEncryptionAlgorithm. The signature value must be immutable // and safe to be used by multiple goroutines, as it is going to be shared with all other users of this library. func RegisterContentEncryptionAlgorithm(algorithms ...ContentEncryptionAlgorithm) { muAllContentEncryptionAlgorithm.Lock() for _, alg := range algorithms { allContentEncryptionAlgorithm[alg.String()] = alg } muAllContentEncryptionAlgorithm.Unlock() rebuildContentEncryptionAlgorithm() } // UnregisterContentEncryptionAlgorithm unregisters a ContentEncryptionAlgorithm from its known database. // Non-existent entries, as well as built-in algorithms will silently be ignored. func UnregisterContentEncryptionAlgorithm(algorithms ...ContentEncryptionAlgorithm) { muAllContentEncryptionAlgorithm.Lock() for _, alg := range algorithms { if _, ok := builtinContentEncryptionAlgorithm[alg.String()]; ok { continue } delete(allContentEncryptionAlgorithm, alg.String()) } muAllContentEncryptionAlgorithm.Unlock() rebuildContentEncryptionAlgorithm() } func rebuildContentEncryptionAlgorithm() { list := make([]ContentEncryptionAlgorithm, 0, len(allContentEncryptionAlgorithm)) muAllContentEncryptionAlgorithm.RLock() for _, v := range allContentEncryptionAlgorithm { list = append(list, v) } muAllContentEncryptionAlgorithm.RUnlock() sort.Slice(list, func(i, j int) bool { return list[i].String() < list[j].String() }) muListContentEncryptionAlgorithm.Lock() listContentEncryptionAlgorithm = list muListContentEncryptionAlgorithm.Unlock() } // ContentEncryptionAlgorithms returns a list of all available values for ContentEncryptionAlgorithm. func ContentEncryptionAlgorithms() []ContentEncryptionAlgorithm { muListContentEncryptionAlgorithm.RLock() defer muListContentEncryptionAlgorithm.RUnlock() return listContentEncryptionAlgorithm } // MarshalJSON serializes the ContentEncryptionAlgorithm object to a JSON string. func (s ContentEncryptionAlgorithm) MarshalJSON() ([]byte, error) { return json.Marshal(s.String()) } // UnmarshalJSON deserializes the JSON string to a ContentEncryptionAlgorithm object. func (s *ContentEncryptionAlgorithm) UnmarshalJSON(data []byte) error { var name string if err := json.Unmarshal(data, &name); err != nil { return fmt.Errorf(`failed to unmarshal ContentEncryptionAlgorithm: %w`, err) } v, ok := LookupContentEncryptionAlgorithm(name) if !ok { return fmt.Errorf(`unknown ContentEncryptionAlgorithm: %q`, name) } *s = v return nil } golang-github-lestrrat-go-jwx-3.0.13/jwa/content_encryption_gen_test.go000066400000000000000000000163061515060566400263360ustar00rootroot00000000000000// Code generated by tools/cmd/genjwa/main.go. DO NOT EDIT package jwa_test import ( "encoding/json" "strconv" "testing" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/stretchr/testify/require" ) func TestContentEncryptionAlgorithm(t *testing.T) { t.Parallel() t.Run(`Lookup the object`, func(t *testing.T) { t.Parallel() v, ok := jwa.LookupContentEncryptionAlgorithm("A128CBC-HS256") require.True(t, ok, `Lookup should succeed`) require.Equal(t, jwa.A128CBC_HS256(), v, `Lookup value should be equal to constant`) }) t.Run(`Unmarshal the string A128CBC-HS256`, func(t *testing.T) { t.Parallel() var dst jwa.ContentEncryptionAlgorithm require.NoError(t, json.Unmarshal([]byte(strconv.Quote("A128CBC-HS256")), &dst), `UnmarshalJSON is successful`) require.Equal(t, jwa.A128CBC_HS256(), dst, `unmarshaled value should be equal to constant`) }) t.Run(`stringification for A128CBC-HS256`, func(t *testing.T) { t.Parallel() require.Equal(t, "A128CBC-HS256", jwa.A128CBC_HS256().String(), `stringified value matches`) }) t.Run(`Lookup the object`, func(t *testing.T) { t.Parallel() v, ok := jwa.LookupContentEncryptionAlgorithm("A128GCM") require.True(t, ok, `Lookup should succeed`) require.Equal(t, jwa.A128GCM(), v, `Lookup value should be equal to constant`) }) t.Run(`Unmarshal the string A128GCM`, func(t *testing.T) { t.Parallel() var dst jwa.ContentEncryptionAlgorithm require.NoError(t, json.Unmarshal([]byte(strconv.Quote("A128GCM")), &dst), `UnmarshalJSON is successful`) require.Equal(t, jwa.A128GCM(), dst, `unmarshaled value should be equal to constant`) }) t.Run(`stringification for A128GCM`, func(t *testing.T) { t.Parallel() require.Equal(t, "A128GCM", jwa.A128GCM().String(), `stringified value matches`) }) t.Run(`Lookup the object`, func(t *testing.T) { t.Parallel() v, ok := jwa.LookupContentEncryptionAlgorithm("A192CBC-HS384") require.True(t, ok, `Lookup should succeed`) require.Equal(t, jwa.A192CBC_HS384(), v, `Lookup value should be equal to constant`) }) t.Run(`Unmarshal the string A192CBC-HS384`, func(t *testing.T) { t.Parallel() var dst jwa.ContentEncryptionAlgorithm require.NoError(t, json.Unmarshal([]byte(strconv.Quote("A192CBC-HS384")), &dst), `UnmarshalJSON is successful`) require.Equal(t, jwa.A192CBC_HS384(), dst, `unmarshaled value should be equal to constant`) }) t.Run(`stringification for A192CBC-HS384`, func(t *testing.T) { t.Parallel() require.Equal(t, "A192CBC-HS384", jwa.A192CBC_HS384().String(), `stringified value matches`) }) t.Run(`Lookup the object`, func(t *testing.T) { t.Parallel() v, ok := jwa.LookupContentEncryptionAlgorithm("A192GCM") require.True(t, ok, `Lookup should succeed`) require.Equal(t, jwa.A192GCM(), v, `Lookup value should be equal to constant`) }) t.Run(`Unmarshal the string A192GCM`, func(t *testing.T) { t.Parallel() var dst jwa.ContentEncryptionAlgorithm require.NoError(t, json.Unmarshal([]byte(strconv.Quote("A192GCM")), &dst), `UnmarshalJSON is successful`) require.Equal(t, jwa.A192GCM(), dst, `unmarshaled value should be equal to constant`) }) t.Run(`stringification for A192GCM`, func(t *testing.T) { t.Parallel() require.Equal(t, "A192GCM", jwa.A192GCM().String(), `stringified value matches`) }) t.Run(`Lookup the object`, func(t *testing.T) { t.Parallel() v, ok := jwa.LookupContentEncryptionAlgorithm("A256CBC-HS512") require.True(t, ok, `Lookup should succeed`) require.Equal(t, jwa.A256CBC_HS512(), v, `Lookup value should be equal to constant`) }) t.Run(`Unmarshal the string A256CBC-HS512`, func(t *testing.T) { t.Parallel() var dst jwa.ContentEncryptionAlgorithm require.NoError(t, json.Unmarshal([]byte(strconv.Quote("A256CBC-HS512")), &dst), `UnmarshalJSON is successful`) require.Equal(t, jwa.A256CBC_HS512(), dst, `unmarshaled value should be equal to constant`) }) t.Run(`stringification for A256CBC-HS512`, func(t *testing.T) { t.Parallel() require.Equal(t, "A256CBC-HS512", jwa.A256CBC_HS512().String(), `stringified value matches`) }) t.Run(`Lookup the object`, func(t *testing.T) { t.Parallel() v, ok := jwa.LookupContentEncryptionAlgorithm("A256GCM") require.True(t, ok, `Lookup should succeed`) require.Equal(t, jwa.A256GCM(), v, `Lookup value should be equal to constant`) }) t.Run(`Unmarshal the string A256GCM`, func(t *testing.T) { t.Parallel() var dst jwa.ContentEncryptionAlgorithm require.NoError(t, json.Unmarshal([]byte(strconv.Quote("A256GCM")), &dst), `UnmarshalJSON is successful`) require.Equal(t, jwa.A256GCM(), dst, `unmarshaled value should be equal to constant`) }) t.Run(`stringification for A256GCM`, func(t *testing.T) { t.Parallel() require.Equal(t, "A256GCM", jwa.A256GCM().String(), `stringified value matches`) }) t.Run(`Unmarshal should fail for invalid value (totally made up) string value`, func(t *testing.T) { t.Parallel() var dst jwa.ContentEncryptionAlgorithm require.Error(t, json.Unmarshal([]byte(`totallyInvalidValue`), &dst), `Unmarshal should fail`) }) t.Run(`check list of elements`, func(t *testing.T) { t.Parallel() var expected = map[jwa.ContentEncryptionAlgorithm]struct{}{ jwa.A128CBC_HS256(): {}, jwa.A128GCM(): {}, jwa.A192CBC_HS384(): {}, jwa.A192GCM(): {}, jwa.A256CBC_HS512(): {}, jwa.A256GCM(): {}, } for _, v := range jwa.ContentEncryptionAlgorithms() { _, ok := expected[v] require.True(t, ok, `%q should be in the list for ContentEncryptionAlgorithm`, v) delete(expected, v) } require.Len(t, expected, 0) }) } // Note: this test can NOT be run in parallel as it uses options with global effect. func TestContentEncryptionAlgorithmCustomAlgorithm(t *testing.T) { // These subtests can NOT be run in parallel as options with global effect change. const customAlgorithmValue = `custom-algorithm` customAlgorithm := jwa.NewContentEncryptionAlgorithm(customAlgorithmValue) // Unregister the custom algorithm, in case tests fail. t.Cleanup(func() { jwa.UnregisterContentEncryptionAlgorithm(customAlgorithm) }) t.Run(`with custom algorithm registered`, func(t *testing.T) { jwa.RegisterContentEncryptionAlgorithm(customAlgorithm) t.Run(`Lookup the object`, func(t *testing.T) { t.Parallel() v, ok := jwa.LookupContentEncryptionAlgorithm(customAlgorithmValue) require.True(t, ok, `Lookup should succeed`) require.Equal(t, customAlgorithm, v, `Lookup value should be equal to constant`) }) t.Run(`Unmarshal custom algorithm`, func(t *testing.T) { t.Parallel() var dst jwa.ContentEncryptionAlgorithm require.NoError(t, json.Unmarshal([]byte(strconv.Quote(customAlgorithmValue)), &dst), `Unmarshal is successful`) require.Equal(t, customAlgorithm, dst, `accepted value should be equal to variable`) }) }) t.Run(`with custom algorithm deregistered`, func(t *testing.T) { jwa.UnregisterContentEncryptionAlgorithm(customAlgorithm) t.Run(`Lookup the object`, func(t *testing.T) { t.Parallel() _, ok := jwa.LookupContentEncryptionAlgorithm(customAlgorithmValue) require.False(t, ok, `Lookup should fail`) }) t.Run(`Unmarshal custom algorithm`, func(t *testing.T) { t.Parallel() var dst jwa.ContentEncryptionAlgorithm require.Error(t, json.Unmarshal([]byte(customAlgorithmValue), &dst), `Unmarshal should fail`) }) }) } golang-github-lestrrat-go-jwx-3.0.13/jwa/elliptic_gen.go000066400000000000000000000144661515060566400231650ustar00rootroot00000000000000// Code generated by tools/cmd/genjwa/main.go. DO NOT EDIT. package jwa import ( "encoding/json" "fmt" "sort" "sync" ) var muAllEllipticCurveAlgorithm sync.RWMutex var allEllipticCurveAlgorithm = map[string]EllipticCurveAlgorithm{} var muListEllipticCurveAlgorithm sync.RWMutex var listEllipticCurveAlgorithm []EllipticCurveAlgorithm var builtinEllipticCurveAlgorithm = map[string]struct{}{} func init() { // builtin values for EllipticCurveAlgorithm algorithms := make([]EllipticCurveAlgorithm, 7) algorithms[0] = NewEllipticCurveAlgorithm("Ed25519") algorithms[1] = NewEllipticCurveAlgorithm("Ed448") algorithms[2] = NewEllipticCurveAlgorithm("P-256") algorithms[3] = NewEllipticCurveAlgorithm("P-384") algorithms[4] = NewEllipticCurveAlgorithm("P-521") algorithms[5] = NewEllipticCurveAlgorithm("X25519") algorithms[6] = NewEllipticCurveAlgorithm("X448") RegisterEllipticCurveAlgorithm(algorithms...) } // Ed25519 returns an object representing Ed25519 algorithm for EdDSA operations. func Ed25519() EllipticCurveAlgorithm { return lookupBuiltinEllipticCurveAlgorithm("Ed25519") } // Ed448 returns an object representing Ed448 algorithm for EdDSA operations. func Ed448() EllipticCurveAlgorithm { return lookupBuiltinEllipticCurveAlgorithm("Ed448") } var invalidEllipticCurve = NewEllipticCurveAlgorithm("P-invalid") // InvalidEllipticCurve returns an object representing an invalid elliptic curve. func InvalidEllipticCurve() EllipticCurveAlgorithm { return invalidEllipticCurve } // P256 returns an object representing P-256 algorithm for ECDSA operations. func P256() EllipticCurveAlgorithm { return lookupBuiltinEllipticCurveAlgorithm("P-256") } // P384 returns an object representing P-384 algorithm for ECDSA operations. func P384() EllipticCurveAlgorithm { return lookupBuiltinEllipticCurveAlgorithm("P-384") } // P521 returns an object representing P-521 algorithm for ECDSA operations. func P521() EllipticCurveAlgorithm { return lookupBuiltinEllipticCurveAlgorithm("P-521") } // X25519 returns an object representing X25519 algorithm for ECDH operations. func X25519() EllipticCurveAlgorithm { return lookupBuiltinEllipticCurveAlgorithm("X25519") } // X448 returns an object representing X448 algorithm for ECDH operations. func X448() EllipticCurveAlgorithm { return lookupBuiltinEllipticCurveAlgorithm("X448") } func lookupBuiltinEllipticCurveAlgorithm(name string) EllipticCurveAlgorithm { muAllEllipticCurveAlgorithm.RLock() v, ok := allEllipticCurveAlgorithm[name] muAllEllipticCurveAlgorithm.RUnlock() if !ok { panic(fmt.Sprintf(`jwa: EllipticCurveAlgorithm %q not registered`, name)) } return v } // EllipticCurveAlgorithm represents the algorithms used for EC keys type EllipticCurveAlgorithm struct { name string deprecated bool } func (s EllipticCurveAlgorithm) String() string { return s.name } // IsDeprecated returns true if the EllipticCurveAlgorithm object is deprecated. func (s EllipticCurveAlgorithm) IsDeprecated() bool { return s.deprecated } // EmptyEllipticCurveAlgorithm returns an empty EllipticCurveAlgorithm object, used as a zero value. func EmptyEllipticCurveAlgorithm() EllipticCurveAlgorithm { return EllipticCurveAlgorithm{} } // NewEllipticCurveAlgorithm creates a new EllipticCurveAlgorithm object with the given name. func NewEllipticCurveAlgorithm(name string, options ...NewAlgorithmOption) EllipticCurveAlgorithm { var deprecated bool for _, option := range options { switch option.Ident() { case identDeprecated{}: if err := option.Value(&deprecated); err != nil { panic("jwa.NewEllipticCurveAlgorithm: WithDeprecated option must be a boolean") } } } return EllipticCurveAlgorithm{name: name, deprecated: deprecated} } // LookupEllipticCurveAlgorithm returns the EllipticCurveAlgorithm object for the given name. func LookupEllipticCurveAlgorithm(name string) (EllipticCurveAlgorithm, bool) { muAllEllipticCurveAlgorithm.RLock() v, ok := allEllipticCurveAlgorithm[name] muAllEllipticCurveAlgorithm.RUnlock() return v, ok } // RegisterEllipticCurveAlgorithm registers a new EllipticCurveAlgorithm. The signature value must be immutable // and safe to be used by multiple goroutines, as it is going to be shared with all other users of this library. func RegisterEllipticCurveAlgorithm(algorithms ...EllipticCurveAlgorithm) { muAllEllipticCurveAlgorithm.Lock() for _, alg := range algorithms { allEllipticCurveAlgorithm[alg.String()] = alg } muAllEllipticCurveAlgorithm.Unlock() rebuildEllipticCurveAlgorithm() } // UnregisterEllipticCurveAlgorithm unregisters a EllipticCurveAlgorithm from its known database. // Non-existent entries, as well as built-in algorithms will silently be ignored. func UnregisterEllipticCurveAlgorithm(algorithms ...EllipticCurveAlgorithm) { muAllEllipticCurveAlgorithm.Lock() for _, alg := range algorithms { if _, ok := builtinEllipticCurveAlgorithm[alg.String()]; ok { continue } delete(allEllipticCurveAlgorithm, alg.String()) } muAllEllipticCurveAlgorithm.Unlock() rebuildEllipticCurveAlgorithm() } func rebuildEllipticCurveAlgorithm() { list := make([]EllipticCurveAlgorithm, 0, len(allEllipticCurveAlgorithm)) muAllEllipticCurveAlgorithm.RLock() for _, v := range allEllipticCurveAlgorithm { list = append(list, v) } muAllEllipticCurveAlgorithm.RUnlock() sort.Slice(list, func(i, j int) bool { return list[i].String() < list[j].String() }) muListEllipticCurveAlgorithm.Lock() listEllipticCurveAlgorithm = list muListEllipticCurveAlgorithm.Unlock() } // EllipticCurveAlgorithms returns a list of all available values for EllipticCurveAlgorithm. func EllipticCurveAlgorithms() []EllipticCurveAlgorithm { muListEllipticCurveAlgorithm.RLock() defer muListEllipticCurveAlgorithm.RUnlock() return listEllipticCurveAlgorithm } // MarshalJSON serializes the EllipticCurveAlgorithm object to a JSON string. func (s EllipticCurveAlgorithm) MarshalJSON() ([]byte, error) { return json.Marshal(s.String()) } // UnmarshalJSON deserializes the JSON string to a EllipticCurveAlgorithm object. func (s *EllipticCurveAlgorithm) UnmarshalJSON(data []byte) error { var name string if err := json.Unmarshal(data, &name); err != nil { return fmt.Errorf(`failed to unmarshal EllipticCurveAlgorithm: %w`, err) } v, ok := LookupEllipticCurveAlgorithm(name) if !ok { return fmt.Errorf(`unknown EllipticCurveAlgorithm: %q`, name) } *s = v return nil } golang-github-lestrrat-go-jwx-3.0.13/jwa/elliptic_gen_test.go000066400000000000000000000173131515060566400242160ustar00rootroot00000000000000// Code generated by tools/cmd/genjwa/main.go. DO NOT EDIT package jwa_test import ( "encoding/json" "strconv" "testing" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/stretchr/testify/require" ) func TestEllipticCurveAlgorithm(t *testing.T) { t.Parallel() t.Run(`Lookup the object`, func(t *testing.T) { t.Parallel() v, ok := jwa.LookupEllipticCurveAlgorithm("Ed25519") require.True(t, ok, `Lookup should succeed`) require.Equal(t, jwa.Ed25519(), v, `Lookup value should be equal to constant`) }) t.Run(`Unmarshal the string Ed25519`, func(t *testing.T) { t.Parallel() var dst jwa.EllipticCurveAlgorithm require.NoError(t, json.Unmarshal([]byte(strconv.Quote("Ed25519")), &dst), `UnmarshalJSON is successful`) require.Equal(t, jwa.Ed25519(), dst, `unmarshaled value should be equal to constant`) }) t.Run(`stringification for Ed25519`, func(t *testing.T) { t.Parallel() require.Equal(t, "Ed25519", jwa.Ed25519().String(), `stringified value matches`) }) t.Run(`Lookup the object`, func(t *testing.T) { t.Parallel() v, ok := jwa.LookupEllipticCurveAlgorithm("Ed448") require.True(t, ok, `Lookup should succeed`) require.Equal(t, jwa.Ed448(), v, `Lookup value should be equal to constant`) }) t.Run(`Unmarshal the string Ed448`, func(t *testing.T) { t.Parallel() var dst jwa.EllipticCurveAlgorithm require.NoError(t, json.Unmarshal([]byte(strconv.Quote("Ed448")), &dst), `UnmarshalJSON is successful`) require.Equal(t, jwa.Ed448(), dst, `unmarshaled value should be equal to constant`) }) t.Run(`stringification for Ed448`, func(t *testing.T) { t.Parallel() require.Equal(t, "Ed448", jwa.Ed448().String(), `stringified value matches`) }) t.Run(`Lookup the object`, func(t *testing.T) { t.Parallel() v, ok := jwa.LookupEllipticCurveAlgorithm("P-256") require.True(t, ok, `Lookup should succeed`) require.Equal(t, jwa.P256(), v, `Lookup value should be equal to constant`) }) t.Run(`Unmarshal the string P-256`, func(t *testing.T) { t.Parallel() var dst jwa.EllipticCurveAlgorithm require.NoError(t, json.Unmarshal([]byte(strconv.Quote("P-256")), &dst), `UnmarshalJSON is successful`) require.Equal(t, jwa.P256(), dst, `unmarshaled value should be equal to constant`) }) t.Run(`stringification for P-256`, func(t *testing.T) { t.Parallel() require.Equal(t, "P-256", jwa.P256().String(), `stringified value matches`) }) t.Run(`Lookup the object`, func(t *testing.T) { t.Parallel() v, ok := jwa.LookupEllipticCurveAlgorithm("P-384") require.True(t, ok, `Lookup should succeed`) require.Equal(t, jwa.P384(), v, `Lookup value should be equal to constant`) }) t.Run(`Unmarshal the string P-384`, func(t *testing.T) { t.Parallel() var dst jwa.EllipticCurveAlgorithm require.NoError(t, json.Unmarshal([]byte(strconv.Quote("P-384")), &dst), `UnmarshalJSON is successful`) require.Equal(t, jwa.P384(), dst, `unmarshaled value should be equal to constant`) }) t.Run(`stringification for P-384`, func(t *testing.T) { t.Parallel() require.Equal(t, "P-384", jwa.P384().String(), `stringified value matches`) }) t.Run(`Lookup the object`, func(t *testing.T) { t.Parallel() v, ok := jwa.LookupEllipticCurveAlgorithm("P-521") require.True(t, ok, `Lookup should succeed`) require.Equal(t, jwa.P521(), v, `Lookup value should be equal to constant`) }) t.Run(`Unmarshal the string P-521`, func(t *testing.T) { t.Parallel() var dst jwa.EllipticCurveAlgorithm require.NoError(t, json.Unmarshal([]byte(strconv.Quote("P-521")), &dst), `UnmarshalJSON is successful`) require.Equal(t, jwa.P521(), dst, `unmarshaled value should be equal to constant`) }) t.Run(`stringification for P-521`, func(t *testing.T) { t.Parallel() require.Equal(t, "P-521", jwa.P521().String(), `stringified value matches`) }) t.Run(`Lookup the object`, func(t *testing.T) { t.Parallel() v, ok := jwa.LookupEllipticCurveAlgorithm("X25519") require.True(t, ok, `Lookup should succeed`) require.Equal(t, jwa.X25519(), v, `Lookup value should be equal to constant`) }) t.Run(`Unmarshal the string X25519`, func(t *testing.T) { t.Parallel() var dst jwa.EllipticCurveAlgorithm require.NoError(t, json.Unmarshal([]byte(strconv.Quote("X25519")), &dst), `UnmarshalJSON is successful`) require.Equal(t, jwa.X25519(), dst, `unmarshaled value should be equal to constant`) }) t.Run(`stringification for X25519`, func(t *testing.T) { t.Parallel() require.Equal(t, "X25519", jwa.X25519().String(), `stringified value matches`) }) t.Run(`Lookup the object`, func(t *testing.T) { t.Parallel() v, ok := jwa.LookupEllipticCurveAlgorithm("X448") require.True(t, ok, `Lookup should succeed`) require.Equal(t, jwa.X448(), v, `Lookup value should be equal to constant`) }) t.Run(`Unmarshal the string X448`, func(t *testing.T) { t.Parallel() var dst jwa.EllipticCurveAlgorithm require.NoError(t, json.Unmarshal([]byte(strconv.Quote("X448")), &dst), `UnmarshalJSON is successful`) require.Equal(t, jwa.X448(), dst, `unmarshaled value should be equal to constant`) }) t.Run(`stringification for X448`, func(t *testing.T) { t.Parallel() require.Equal(t, "X448", jwa.X448().String(), `stringified value matches`) }) t.Run(`Unmarshal should fail for invalid value (totally made up) string value`, func(t *testing.T) { t.Parallel() var dst jwa.EllipticCurveAlgorithm require.Error(t, json.Unmarshal([]byte(`totallyInvalidValue`), &dst), `Unmarshal should fail`) }) t.Run(`check list of elements`, func(t *testing.T) { t.Parallel() var expected = map[jwa.EllipticCurveAlgorithm]struct{}{ jwa.Ed25519(): {}, jwa.Ed448(): {}, jwa.P256(): {}, jwa.P384(): {}, jwa.P521(): {}, jwa.X25519(): {}, jwa.X448(): {}, } for _, v := range jwa.EllipticCurveAlgorithms() { // There is no good way to detect from a test if es256k (secp256k1) // is supported, so just allow it if v.String() == `secp256k1` { continue } _, ok := expected[v] require.True(t, ok, `%q should be in the list for EllipticCurveAlgorithm`, v) delete(expected, v) } require.Len(t, expected, 0) }) } // Note: this test can NOT be run in parallel as it uses options with global effect. func TestEllipticCurveAlgorithmCustomAlgorithm(t *testing.T) { // These subtests can NOT be run in parallel as options with global effect change. const customAlgorithmValue = `custom-algorithm` customAlgorithm := jwa.NewEllipticCurveAlgorithm(customAlgorithmValue) // Unregister the custom algorithm, in case tests fail. t.Cleanup(func() { jwa.UnregisterEllipticCurveAlgorithm(customAlgorithm) }) t.Run(`with custom algorithm registered`, func(t *testing.T) { jwa.RegisterEllipticCurveAlgorithm(customAlgorithm) t.Run(`Lookup the object`, func(t *testing.T) { t.Parallel() v, ok := jwa.LookupEllipticCurveAlgorithm(customAlgorithmValue) require.True(t, ok, `Lookup should succeed`) require.Equal(t, customAlgorithm, v, `Lookup value should be equal to constant`) }) t.Run(`Unmarshal custom algorithm`, func(t *testing.T) { t.Parallel() var dst jwa.EllipticCurveAlgorithm require.NoError(t, json.Unmarshal([]byte(strconv.Quote(customAlgorithmValue)), &dst), `Unmarshal is successful`) require.Equal(t, customAlgorithm, dst, `accepted value should be equal to variable`) }) }) t.Run(`with custom algorithm deregistered`, func(t *testing.T) { jwa.UnregisterEllipticCurveAlgorithm(customAlgorithm) t.Run(`Lookup the object`, func(t *testing.T) { t.Parallel() _, ok := jwa.LookupEllipticCurveAlgorithm(customAlgorithmValue) require.False(t, ok, `Lookup should fail`) }) t.Run(`Unmarshal custom algorithm`, func(t *testing.T) { t.Parallel() var dst jwa.EllipticCurveAlgorithm require.Error(t, json.Unmarshal([]byte(customAlgorithmValue), &dst), `Unmarshal should fail`) }) }) } golang-github-lestrrat-go-jwx-3.0.13/jwa/jwa.go000066400000000000000000000036621515060566400213040ustar00rootroot00000000000000//go:generate ../tools/cmd/genjwa.sh // Package jwa defines the various algorithm described in https://tools.ietf.org/html/rfc7518 package jwa import ( "errors" "fmt" ) // KeyAlgorithm is a workaround for jwk.Key being able to contain different // types of algorithms in its `alg` field. // // Previously the storage for the `alg` field was represented as a string, // but this caused some users to wonder why the field was not typed appropriately // like other fields. // // Ideally we would like to keep track of Signature Algorithms and // Key Encryption Algorithms separately, and force the APIs to // type-check at compile time, but this allows users to pass a value from a // jwk.Key directly type KeyAlgorithm interface { String() string IsDeprecated() bool } var errInvalidKeyAlgorithm = errors.New(`invalid key algorithm`) func ErrInvalidKeyAlgorithm() error { return errInvalidKeyAlgorithm } // KeyAlgorithmFrom takes either a string, `jwa.SignatureAlgorithm`, // `jwa.KeyEncryptionAlgorithm`, or `jwa.ContentEncryptionAlgorithm`. // and returns a `jwa.KeyAlgorithm`. // // If the value cannot be handled, it returns an `jwa.InvalidKeyAlgorithm` // object instead of returning an error. This design choice was made to allow // users to directly pass the return value to functions such as `jws.Sign()` func KeyAlgorithmFrom(v any) (KeyAlgorithm, error) { switch v := v.(type) { case SignatureAlgorithm: return v, nil case KeyEncryptionAlgorithm: return v, nil case ContentEncryptionAlgorithm: return v, nil case string: salg, ok := LookupSignatureAlgorithm(v) if ok { return salg, nil } kalg, ok := LookupKeyEncryptionAlgorithm(v) if ok { return kalg, nil } calg, ok := LookupContentEncryptionAlgorithm(v) if ok { return calg, nil } return nil, fmt.Errorf(`invalid key value: %q: %w`, v, errInvalidKeyAlgorithm) default: return nil, fmt.Errorf(`invalid key type: %T: %w`, v, errInvalidKeyAlgorithm) } } golang-github-lestrrat-go-jwx-3.0.13/jwa/jwa_test.go000066400000000000000000000024741515060566400223430ustar00rootroot00000000000000package jwa_test import ( "fmt" "testing" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/stretchr/testify/require" ) func TestSanity(t *testing.T) { var k1 jwa.KeyAlgorithm = jwa.RS256() _, ok := k1.(jwa.SignatureAlgorithm) require.True(t, ok, `converting k1 to jws.SignatureAlgorithm should succeed`) _, ok = k1.(jwa.KeyEncryptionAlgorithm) require.False(t, ok, `converting k1 to jws.KeyEncryptionAlgorithm should fail`) var k2 jwa.KeyAlgorithm = jwa.DIRECT() _, ok = k2.(jwa.SignatureAlgorithm) require.False(t, ok, `converting k2 to jws.SignatureAlgorithm should fail`) _, ok = k2.(jwa.KeyEncryptionAlgorithm) require.True(t, ok, `converting k2 to jws.KeyEncryptionAlgorithm should succeed`) } func TestKeyAlgorithmFrom(t *testing.T) { testcases := []struct { Input any Error bool }{ { Input: jwa.RS256(), }, { Input: jwa.DIRECT(), }, { Input: jwa.A128CBC_HS256(), }, { Input: "dummy", Error: true, }, } for _, tc := range testcases { t.Run(fmt.Sprintf("%T", tc.Input), func(t *testing.T) { alg, err := jwa.KeyAlgorithmFrom(tc.Input) if tc.Error { require.Error(t, err, `creating key alrgorithm should fail`) } else { require.NoError(t, err, `creating key alrgorithm should succeed`) require.Equal(t, alg, tc.Input, `key should be valid`) } }) } } golang-github-lestrrat-go-jwx-3.0.13/jwa/key_encryption_gen.go000066400000000000000000000245401515060566400244140ustar00rootroot00000000000000// Code generated by tools/cmd/genjwa/main.go. DO NOT EDIT. package jwa import ( "encoding/json" "fmt" "sort" "sync" "github.com/lestrrat-go/jwx/v3/internal/tokens" ) var muAllKeyEncryptionAlgorithm sync.RWMutex var allKeyEncryptionAlgorithm = map[string]KeyEncryptionAlgorithm{} var muListKeyEncryptionAlgorithm sync.RWMutex var listKeyEncryptionAlgorithm []KeyEncryptionAlgorithm var builtinKeyEncryptionAlgorithm = map[string]struct{}{} func init() { // builtin values for KeyEncryptionAlgorithm algorithms := make([]KeyEncryptionAlgorithm, 19) algorithms[0] = NewKeyEncryptionAlgorithm(tokens.A128GCMKW, WithIsSymmetric(true)) algorithms[1] = NewKeyEncryptionAlgorithm(tokens.A128KW, WithIsSymmetric(true)) algorithms[2] = NewKeyEncryptionAlgorithm(tokens.A192GCMKW, WithIsSymmetric(true)) algorithms[3] = NewKeyEncryptionAlgorithm(tokens.A192KW, WithIsSymmetric(true)) algorithms[4] = NewKeyEncryptionAlgorithm(tokens.A256GCMKW, WithIsSymmetric(true)) algorithms[5] = NewKeyEncryptionAlgorithm(tokens.A256KW, WithIsSymmetric(true)) algorithms[6] = NewKeyEncryptionAlgorithm(tokens.DIRECT, WithIsSymmetric(true)) algorithms[7] = NewKeyEncryptionAlgorithm(tokens.ECDH_ES) algorithms[8] = NewKeyEncryptionAlgorithm(tokens.ECDH_ES_A128KW) algorithms[9] = NewKeyEncryptionAlgorithm(tokens.ECDH_ES_A192KW) algorithms[10] = NewKeyEncryptionAlgorithm(tokens.ECDH_ES_A256KW) algorithms[11] = NewKeyEncryptionAlgorithm(tokens.PBES2_HS256_A128KW, WithIsSymmetric(true)) algorithms[12] = NewKeyEncryptionAlgorithm(tokens.PBES2_HS384_A192KW, WithIsSymmetric(true)) algorithms[13] = NewKeyEncryptionAlgorithm(tokens.PBES2_HS512_A256KW, WithIsSymmetric(true)) algorithms[14] = NewKeyEncryptionAlgorithm(tokens.RSA1_5, WithDeprecated(true)) algorithms[15] = NewKeyEncryptionAlgorithm(tokens.RSA_OAEP) algorithms[16] = NewKeyEncryptionAlgorithm(tokens.RSA_OAEP_256) algorithms[17] = NewKeyEncryptionAlgorithm(tokens.RSA_OAEP_384) algorithms[18] = NewKeyEncryptionAlgorithm(tokens.RSA_OAEP_512) RegisterKeyEncryptionAlgorithm(algorithms...) } // A128GCMKW returns an object representing AES-GCM key wrap (128) key encryption algorithm. func A128GCMKW() KeyEncryptionAlgorithm { return lookupBuiltinKeyEncryptionAlgorithm(tokens.A128GCMKW) } // A128KW returns an object representing AES key wrap (128) key encryption algorithm. func A128KW() KeyEncryptionAlgorithm { return lookupBuiltinKeyEncryptionAlgorithm(tokens.A128KW) } // A192GCMKW returns an object representing AES-GCM key wrap (192) key encryption algorithm. func A192GCMKW() KeyEncryptionAlgorithm { return lookupBuiltinKeyEncryptionAlgorithm(tokens.A192GCMKW) } // A192KW returns an object representing AES key wrap (192) key encryption algorithm. func A192KW() KeyEncryptionAlgorithm { return lookupBuiltinKeyEncryptionAlgorithm(tokens.A192KW) } // A256GCMKW returns an object representing AES-GCM key wrap (256) key encryption algorithm. func A256GCMKW() KeyEncryptionAlgorithm { return lookupBuiltinKeyEncryptionAlgorithm(tokens.A256GCMKW) } // A256KW returns an object representing AES key wrap (256) key encryption algorithm. func A256KW() KeyEncryptionAlgorithm { return lookupBuiltinKeyEncryptionAlgorithm(tokens.A256KW) } // DIRECT returns an object representing Direct key encryption algorithm. func DIRECT() KeyEncryptionAlgorithm { return lookupBuiltinKeyEncryptionAlgorithm(tokens.DIRECT) } // ECDH_ES returns an object representing ECDH-ES key encryption algorithm. func ECDH_ES() KeyEncryptionAlgorithm { return lookupBuiltinKeyEncryptionAlgorithm(tokens.ECDH_ES) } // ECDH_ES_A128KW returns an object representing ECDH-ES + AES key wrap (128) key encryption algorithm. func ECDH_ES_A128KW() KeyEncryptionAlgorithm { return lookupBuiltinKeyEncryptionAlgorithm(tokens.ECDH_ES_A128KW) } // ECDH_ES_A192KW returns an object representing ECDH-ES + AES key wrap (192) key encryption algorithm. func ECDH_ES_A192KW() KeyEncryptionAlgorithm { return lookupBuiltinKeyEncryptionAlgorithm(tokens.ECDH_ES_A192KW) } // ECDH_ES_A256KW returns an object representing ECDH-ES + AES key wrap (256) key encryption algorithm. func ECDH_ES_A256KW() KeyEncryptionAlgorithm { return lookupBuiltinKeyEncryptionAlgorithm(tokens.ECDH_ES_A256KW) } // PBES2_HS256_A128KW returns an object representing PBES2 + HMAC-SHA256 + AES key wrap (128) key encryption algorithm. func PBES2_HS256_A128KW() KeyEncryptionAlgorithm { return lookupBuiltinKeyEncryptionAlgorithm(tokens.PBES2_HS256_A128KW) } // PBES2_HS384_A192KW returns an object representing PBES2 + HMAC-SHA384 + AES key wrap (192) key encryption algorithm. func PBES2_HS384_A192KW() KeyEncryptionAlgorithm { return lookupBuiltinKeyEncryptionAlgorithm(tokens.PBES2_HS384_A192KW) } // PBES2_HS512_A256KW returns an object representing PBES2 + HMAC-SHA512 + AES key wrap (256) key encryption algorithm. func PBES2_HS512_A256KW() KeyEncryptionAlgorithm { return lookupBuiltinKeyEncryptionAlgorithm(tokens.PBES2_HS512_A256KW) } // RSA1_5 returns an object representing RSA-PKCS1v1.5 key encryption algorithm. func RSA1_5() KeyEncryptionAlgorithm { return lookupBuiltinKeyEncryptionAlgorithm(tokens.RSA1_5) } // RSA_OAEP returns an object representing RSA-OAEP-SHA1 key encryption algorithm. func RSA_OAEP() KeyEncryptionAlgorithm { return lookupBuiltinKeyEncryptionAlgorithm(tokens.RSA_OAEP) } // RSA_OAEP_256 returns an object representing RSA-OAEP-SHA256 key encryption algorithm. func RSA_OAEP_256() KeyEncryptionAlgorithm { return lookupBuiltinKeyEncryptionAlgorithm(tokens.RSA_OAEP_256) } // RSA_OAEP_384 returns an object representing RSA-OAEP-SHA384 key encryption algorithm. func RSA_OAEP_384() KeyEncryptionAlgorithm { return lookupBuiltinKeyEncryptionAlgorithm(tokens.RSA_OAEP_384) } // RSA_OAEP_512 returns an object representing RSA-OAEP-SHA512 key encryption algorithm. func RSA_OAEP_512() KeyEncryptionAlgorithm { return lookupBuiltinKeyEncryptionAlgorithm(tokens.RSA_OAEP_512) } func lookupBuiltinKeyEncryptionAlgorithm(name string) KeyEncryptionAlgorithm { muAllKeyEncryptionAlgorithm.RLock() v, ok := allKeyEncryptionAlgorithm[name] muAllKeyEncryptionAlgorithm.RUnlock() if !ok { panic(fmt.Sprintf(`jwa: KeyEncryptionAlgorithm %q not registered`, name)) } return v } // KeyEncryptionAlgorithm represents the various encryption algorithms as described in https://tools.ietf.org/html/rfc7518#section-4.1 type KeyEncryptionAlgorithm struct { name string deprecated bool isSymmetric bool } func (s KeyEncryptionAlgorithm) String() string { return s.name } // IsDeprecated returns true if the KeyEncryptionAlgorithm object is deprecated. func (s KeyEncryptionAlgorithm) IsDeprecated() bool { return s.deprecated } // IsSymmetric returns true if the KeyEncryptionAlgorithm object is symmetric. Symmetric algorithms use the same key for both encryption and decryption. func (s KeyEncryptionAlgorithm) IsSymmetric() bool { return s.isSymmetric } // EmptyKeyEncryptionAlgorithm returns an empty KeyEncryptionAlgorithm object, used as a zero value. func EmptyKeyEncryptionAlgorithm() KeyEncryptionAlgorithm { return KeyEncryptionAlgorithm{} } // NewKeyEncryptionAlgorithm creates a new KeyEncryptionAlgorithm object with the given name. func NewKeyEncryptionAlgorithm(name string, options ...NewKeyEncryptionAlgorithmOption) KeyEncryptionAlgorithm { var deprecated bool var isSymmetric bool for _, option := range options { switch option.Ident() { case identIsSymmetric{}: if err := option.Value(&isSymmetric); err != nil { panic("jwa.NewKeyEncryptionAlgorithm: WithIsSymmetric option must be a boolean") } case identDeprecated{}: if err := option.Value(&deprecated); err != nil { panic("jwa.NewKeyEncryptionAlgorithm: WithDeprecated option must be a boolean") } } } return KeyEncryptionAlgorithm{name: name, deprecated: deprecated, isSymmetric: isSymmetric} } // LookupKeyEncryptionAlgorithm returns the KeyEncryptionAlgorithm object for the given name. func LookupKeyEncryptionAlgorithm(name string) (KeyEncryptionAlgorithm, bool) { muAllKeyEncryptionAlgorithm.RLock() v, ok := allKeyEncryptionAlgorithm[name] muAllKeyEncryptionAlgorithm.RUnlock() return v, ok } // RegisterKeyEncryptionAlgorithm registers a new KeyEncryptionAlgorithm. The signature value must be immutable // and safe to be used by multiple goroutines, as it is going to be shared with all other users of this library. func RegisterKeyEncryptionAlgorithm(algorithms ...KeyEncryptionAlgorithm) { muAllKeyEncryptionAlgorithm.Lock() for _, alg := range algorithms { allKeyEncryptionAlgorithm[alg.String()] = alg } muAllKeyEncryptionAlgorithm.Unlock() rebuildKeyEncryptionAlgorithm() } // UnregisterKeyEncryptionAlgorithm unregisters a KeyEncryptionAlgorithm from its known database. // Non-existent entries, as well as built-in algorithms will silently be ignored. func UnregisterKeyEncryptionAlgorithm(algorithms ...KeyEncryptionAlgorithm) { muAllKeyEncryptionAlgorithm.Lock() for _, alg := range algorithms { if _, ok := builtinKeyEncryptionAlgorithm[alg.String()]; ok { continue } delete(allKeyEncryptionAlgorithm, alg.String()) } muAllKeyEncryptionAlgorithm.Unlock() rebuildKeyEncryptionAlgorithm() } func rebuildKeyEncryptionAlgorithm() { list := make([]KeyEncryptionAlgorithm, 0, len(allKeyEncryptionAlgorithm)) muAllKeyEncryptionAlgorithm.RLock() for _, v := range allKeyEncryptionAlgorithm { list = append(list, v) } muAllKeyEncryptionAlgorithm.RUnlock() sort.Slice(list, func(i, j int) bool { return list[i].String() < list[j].String() }) muListKeyEncryptionAlgorithm.Lock() listKeyEncryptionAlgorithm = list muListKeyEncryptionAlgorithm.Unlock() } // KeyEncryptionAlgorithms returns a list of all available values for KeyEncryptionAlgorithm. func KeyEncryptionAlgorithms() []KeyEncryptionAlgorithm { muListKeyEncryptionAlgorithm.RLock() defer muListKeyEncryptionAlgorithm.RUnlock() return listKeyEncryptionAlgorithm } // MarshalJSON serializes the KeyEncryptionAlgorithm object to a JSON string. func (s KeyEncryptionAlgorithm) MarshalJSON() ([]byte, error) { return json.Marshal(s.String()) } // UnmarshalJSON deserializes the JSON string to a KeyEncryptionAlgorithm object. func (s *KeyEncryptionAlgorithm) UnmarshalJSON(data []byte) error { var name string if err := json.Unmarshal(data, &name); err != nil { return fmt.Errorf(`failed to unmarshal KeyEncryptionAlgorithm: %w`, err) } v, ok := LookupKeyEncryptionAlgorithm(name) if !ok { return fmt.Errorf(`unknown KeyEncryptionAlgorithm: %q`, name) } *s = v return nil } golang-github-lestrrat-go-jwx-3.0.13/jwa/key_encryption_gen_test.go000066400000000000000000000502311515060566400254470ustar00rootroot00000000000000// Code generated by tools/cmd/genjwa/main.go. DO NOT EDIT package jwa_test import ( "encoding/json" "strconv" "testing" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/stretchr/testify/require" ) func TestKeyEncryptionAlgorithm(t *testing.T) { t.Parallel() t.Run(`Lookup the object`, func(t *testing.T) { t.Parallel() v, ok := jwa.LookupKeyEncryptionAlgorithm("A128GCMKW") require.True(t, ok, `Lookup should succeed`) require.Equal(t, jwa.A128GCMKW(), v, `Lookup value should be equal to constant`) }) t.Run(`Unmarshal the string A128GCMKW`, func(t *testing.T) { t.Parallel() var dst jwa.KeyEncryptionAlgorithm require.NoError(t, json.Unmarshal([]byte(strconv.Quote("A128GCMKW")), &dst), `UnmarshalJSON is successful`) require.Equal(t, jwa.A128GCMKW(), dst, `unmarshaled value should be equal to constant`) }) t.Run(`stringification for A128GCMKW`, func(t *testing.T) { t.Parallel() require.Equal(t, "A128GCMKW", jwa.A128GCMKW().String(), `stringified value matches`) }) t.Run(`Lookup the object`, func(t *testing.T) { t.Parallel() v, ok := jwa.LookupKeyEncryptionAlgorithm("A128KW") require.True(t, ok, `Lookup should succeed`) require.Equal(t, jwa.A128KW(), v, `Lookup value should be equal to constant`) }) t.Run(`Unmarshal the string A128KW`, func(t *testing.T) { t.Parallel() var dst jwa.KeyEncryptionAlgorithm require.NoError(t, json.Unmarshal([]byte(strconv.Quote("A128KW")), &dst), `UnmarshalJSON is successful`) require.Equal(t, jwa.A128KW(), dst, `unmarshaled value should be equal to constant`) }) t.Run(`stringification for A128KW`, func(t *testing.T) { t.Parallel() require.Equal(t, "A128KW", jwa.A128KW().String(), `stringified value matches`) }) t.Run(`Lookup the object`, func(t *testing.T) { t.Parallel() v, ok := jwa.LookupKeyEncryptionAlgorithm("A192GCMKW") require.True(t, ok, `Lookup should succeed`) require.Equal(t, jwa.A192GCMKW(), v, `Lookup value should be equal to constant`) }) t.Run(`Unmarshal the string A192GCMKW`, func(t *testing.T) { t.Parallel() var dst jwa.KeyEncryptionAlgorithm require.NoError(t, json.Unmarshal([]byte(strconv.Quote("A192GCMKW")), &dst), `UnmarshalJSON is successful`) require.Equal(t, jwa.A192GCMKW(), dst, `unmarshaled value should be equal to constant`) }) t.Run(`stringification for A192GCMKW`, func(t *testing.T) { t.Parallel() require.Equal(t, "A192GCMKW", jwa.A192GCMKW().String(), `stringified value matches`) }) t.Run(`Lookup the object`, func(t *testing.T) { t.Parallel() v, ok := jwa.LookupKeyEncryptionAlgorithm("A192KW") require.True(t, ok, `Lookup should succeed`) require.Equal(t, jwa.A192KW(), v, `Lookup value should be equal to constant`) }) t.Run(`Unmarshal the string A192KW`, func(t *testing.T) { t.Parallel() var dst jwa.KeyEncryptionAlgorithm require.NoError(t, json.Unmarshal([]byte(strconv.Quote("A192KW")), &dst), `UnmarshalJSON is successful`) require.Equal(t, jwa.A192KW(), dst, `unmarshaled value should be equal to constant`) }) t.Run(`stringification for A192KW`, func(t *testing.T) { t.Parallel() require.Equal(t, "A192KW", jwa.A192KW().String(), `stringified value matches`) }) t.Run(`Lookup the object`, func(t *testing.T) { t.Parallel() v, ok := jwa.LookupKeyEncryptionAlgorithm("A256GCMKW") require.True(t, ok, `Lookup should succeed`) require.Equal(t, jwa.A256GCMKW(), v, `Lookup value should be equal to constant`) }) t.Run(`Unmarshal the string A256GCMKW`, func(t *testing.T) { t.Parallel() var dst jwa.KeyEncryptionAlgorithm require.NoError(t, json.Unmarshal([]byte(strconv.Quote("A256GCMKW")), &dst), `UnmarshalJSON is successful`) require.Equal(t, jwa.A256GCMKW(), dst, `unmarshaled value should be equal to constant`) }) t.Run(`stringification for A256GCMKW`, func(t *testing.T) { t.Parallel() require.Equal(t, "A256GCMKW", jwa.A256GCMKW().String(), `stringified value matches`) }) t.Run(`Lookup the object`, func(t *testing.T) { t.Parallel() v, ok := jwa.LookupKeyEncryptionAlgorithm("A256KW") require.True(t, ok, `Lookup should succeed`) require.Equal(t, jwa.A256KW(), v, `Lookup value should be equal to constant`) }) t.Run(`Unmarshal the string A256KW`, func(t *testing.T) { t.Parallel() var dst jwa.KeyEncryptionAlgorithm require.NoError(t, json.Unmarshal([]byte(strconv.Quote("A256KW")), &dst), `UnmarshalJSON is successful`) require.Equal(t, jwa.A256KW(), dst, `unmarshaled value should be equal to constant`) }) t.Run(`stringification for A256KW`, func(t *testing.T) { t.Parallel() require.Equal(t, "A256KW", jwa.A256KW().String(), `stringified value matches`) }) t.Run(`Lookup the object`, func(t *testing.T) { t.Parallel() v, ok := jwa.LookupKeyEncryptionAlgorithm("dir") require.True(t, ok, `Lookup should succeed`) require.Equal(t, jwa.DIRECT(), v, `Lookup value should be equal to constant`) }) t.Run(`Unmarshal the string dir`, func(t *testing.T) { t.Parallel() var dst jwa.KeyEncryptionAlgorithm require.NoError(t, json.Unmarshal([]byte(strconv.Quote("dir")), &dst), `UnmarshalJSON is successful`) require.Equal(t, jwa.DIRECT(), dst, `unmarshaled value should be equal to constant`) }) t.Run(`stringification for dir`, func(t *testing.T) { t.Parallel() require.Equal(t, "dir", jwa.DIRECT().String(), `stringified value matches`) }) t.Run(`Lookup the object`, func(t *testing.T) { t.Parallel() v, ok := jwa.LookupKeyEncryptionAlgorithm("ECDH-ES") require.True(t, ok, `Lookup should succeed`) require.Equal(t, jwa.ECDH_ES(), v, `Lookup value should be equal to constant`) }) t.Run(`Unmarshal the string ECDH-ES`, func(t *testing.T) { t.Parallel() var dst jwa.KeyEncryptionAlgorithm require.NoError(t, json.Unmarshal([]byte(strconv.Quote("ECDH-ES")), &dst), `UnmarshalJSON is successful`) require.Equal(t, jwa.ECDH_ES(), dst, `unmarshaled value should be equal to constant`) }) t.Run(`stringification for ECDH-ES`, func(t *testing.T) { t.Parallel() require.Equal(t, "ECDH-ES", jwa.ECDH_ES().String(), `stringified value matches`) }) t.Run(`Lookup the object`, func(t *testing.T) { t.Parallel() v, ok := jwa.LookupKeyEncryptionAlgorithm("ECDH-ES+A128KW") require.True(t, ok, `Lookup should succeed`) require.Equal(t, jwa.ECDH_ES_A128KW(), v, `Lookup value should be equal to constant`) }) t.Run(`Unmarshal the string ECDH-ES+A128KW`, func(t *testing.T) { t.Parallel() var dst jwa.KeyEncryptionAlgorithm require.NoError(t, json.Unmarshal([]byte(strconv.Quote("ECDH-ES+A128KW")), &dst), `UnmarshalJSON is successful`) require.Equal(t, jwa.ECDH_ES_A128KW(), dst, `unmarshaled value should be equal to constant`) }) t.Run(`stringification for ECDH-ES+A128KW`, func(t *testing.T) { t.Parallel() require.Equal(t, "ECDH-ES+A128KW", jwa.ECDH_ES_A128KW().String(), `stringified value matches`) }) t.Run(`Lookup the object`, func(t *testing.T) { t.Parallel() v, ok := jwa.LookupKeyEncryptionAlgorithm("ECDH-ES+A192KW") require.True(t, ok, `Lookup should succeed`) require.Equal(t, jwa.ECDH_ES_A192KW(), v, `Lookup value should be equal to constant`) }) t.Run(`Unmarshal the string ECDH-ES+A192KW`, func(t *testing.T) { t.Parallel() var dst jwa.KeyEncryptionAlgorithm require.NoError(t, json.Unmarshal([]byte(strconv.Quote("ECDH-ES+A192KW")), &dst), `UnmarshalJSON is successful`) require.Equal(t, jwa.ECDH_ES_A192KW(), dst, `unmarshaled value should be equal to constant`) }) t.Run(`stringification for ECDH-ES+A192KW`, func(t *testing.T) { t.Parallel() require.Equal(t, "ECDH-ES+A192KW", jwa.ECDH_ES_A192KW().String(), `stringified value matches`) }) t.Run(`Lookup the object`, func(t *testing.T) { t.Parallel() v, ok := jwa.LookupKeyEncryptionAlgorithm("ECDH-ES+A256KW") require.True(t, ok, `Lookup should succeed`) require.Equal(t, jwa.ECDH_ES_A256KW(), v, `Lookup value should be equal to constant`) }) t.Run(`Unmarshal the string ECDH-ES+A256KW`, func(t *testing.T) { t.Parallel() var dst jwa.KeyEncryptionAlgorithm require.NoError(t, json.Unmarshal([]byte(strconv.Quote("ECDH-ES+A256KW")), &dst), `UnmarshalJSON is successful`) require.Equal(t, jwa.ECDH_ES_A256KW(), dst, `unmarshaled value should be equal to constant`) }) t.Run(`stringification for ECDH-ES+A256KW`, func(t *testing.T) { t.Parallel() require.Equal(t, "ECDH-ES+A256KW", jwa.ECDH_ES_A256KW().String(), `stringified value matches`) }) t.Run(`Lookup the object`, func(t *testing.T) { t.Parallel() v, ok := jwa.LookupKeyEncryptionAlgorithm("PBES2-HS256+A128KW") require.True(t, ok, `Lookup should succeed`) require.Equal(t, jwa.PBES2_HS256_A128KW(), v, `Lookup value should be equal to constant`) }) t.Run(`Unmarshal the string PBES2-HS256+A128KW`, func(t *testing.T) { t.Parallel() var dst jwa.KeyEncryptionAlgorithm require.NoError(t, json.Unmarshal([]byte(strconv.Quote("PBES2-HS256+A128KW")), &dst), `UnmarshalJSON is successful`) require.Equal(t, jwa.PBES2_HS256_A128KW(), dst, `unmarshaled value should be equal to constant`) }) t.Run(`stringification for PBES2-HS256+A128KW`, func(t *testing.T) { t.Parallel() require.Equal(t, "PBES2-HS256+A128KW", jwa.PBES2_HS256_A128KW().String(), `stringified value matches`) }) t.Run(`Lookup the object`, func(t *testing.T) { t.Parallel() v, ok := jwa.LookupKeyEncryptionAlgorithm("PBES2-HS384+A192KW") require.True(t, ok, `Lookup should succeed`) require.Equal(t, jwa.PBES2_HS384_A192KW(), v, `Lookup value should be equal to constant`) }) t.Run(`Unmarshal the string PBES2-HS384+A192KW`, func(t *testing.T) { t.Parallel() var dst jwa.KeyEncryptionAlgorithm require.NoError(t, json.Unmarshal([]byte(strconv.Quote("PBES2-HS384+A192KW")), &dst), `UnmarshalJSON is successful`) require.Equal(t, jwa.PBES2_HS384_A192KW(), dst, `unmarshaled value should be equal to constant`) }) t.Run(`stringification for PBES2-HS384+A192KW`, func(t *testing.T) { t.Parallel() require.Equal(t, "PBES2-HS384+A192KW", jwa.PBES2_HS384_A192KW().String(), `stringified value matches`) }) t.Run(`Lookup the object`, func(t *testing.T) { t.Parallel() v, ok := jwa.LookupKeyEncryptionAlgorithm("PBES2-HS512+A256KW") require.True(t, ok, `Lookup should succeed`) require.Equal(t, jwa.PBES2_HS512_A256KW(), v, `Lookup value should be equal to constant`) }) t.Run(`Unmarshal the string PBES2-HS512+A256KW`, func(t *testing.T) { t.Parallel() var dst jwa.KeyEncryptionAlgorithm require.NoError(t, json.Unmarshal([]byte(strconv.Quote("PBES2-HS512+A256KW")), &dst), `UnmarshalJSON is successful`) require.Equal(t, jwa.PBES2_HS512_A256KW(), dst, `unmarshaled value should be equal to constant`) }) t.Run(`stringification for PBES2-HS512+A256KW`, func(t *testing.T) { t.Parallel() require.Equal(t, "PBES2-HS512+A256KW", jwa.PBES2_HS512_A256KW().String(), `stringified value matches`) }) t.Run(`Lookup the object`, func(t *testing.T) { t.Parallel() v, ok := jwa.LookupKeyEncryptionAlgorithm("RSA1_5") require.True(t, ok, `Lookup should succeed`) require.Equal(t, jwa.RSA1_5(), v, `Lookup value should be equal to constant`) }) t.Run(`Unmarshal the string RSA1_5`, func(t *testing.T) { t.Parallel() var dst jwa.KeyEncryptionAlgorithm require.NoError(t, json.Unmarshal([]byte(strconv.Quote("RSA1_5")), &dst), `UnmarshalJSON is successful`) require.Equal(t, jwa.RSA1_5(), dst, `unmarshaled value should be equal to constant`) }) t.Run(`stringification for RSA1_5`, func(t *testing.T) { t.Parallel() require.Equal(t, "RSA1_5", jwa.RSA1_5().String(), `stringified value matches`) }) t.Run(`Lookup the object`, func(t *testing.T) { t.Parallel() v, ok := jwa.LookupKeyEncryptionAlgorithm("RSA-OAEP") require.True(t, ok, `Lookup should succeed`) require.Equal(t, jwa.RSA_OAEP(), v, `Lookup value should be equal to constant`) }) t.Run(`Unmarshal the string RSA-OAEP`, func(t *testing.T) { t.Parallel() var dst jwa.KeyEncryptionAlgorithm require.NoError(t, json.Unmarshal([]byte(strconv.Quote("RSA-OAEP")), &dst), `UnmarshalJSON is successful`) require.Equal(t, jwa.RSA_OAEP(), dst, `unmarshaled value should be equal to constant`) }) t.Run(`stringification for RSA-OAEP`, func(t *testing.T) { t.Parallel() require.Equal(t, "RSA-OAEP", jwa.RSA_OAEP().String(), `stringified value matches`) }) t.Run(`Lookup the object`, func(t *testing.T) { t.Parallel() v, ok := jwa.LookupKeyEncryptionAlgorithm("RSA-OAEP-256") require.True(t, ok, `Lookup should succeed`) require.Equal(t, jwa.RSA_OAEP_256(), v, `Lookup value should be equal to constant`) }) t.Run(`Unmarshal the string RSA-OAEP-256`, func(t *testing.T) { t.Parallel() var dst jwa.KeyEncryptionAlgorithm require.NoError(t, json.Unmarshal([]byte(strconv.Quote("RSA-OAEP-256")), &dst), `UnmarshalJSON is successful`) require.Equal(t, jwa.RSA_OAEP_256(), dst, `unmarshaled value should be equal to constant`) }) t.Run(`stringification for RSA-OAEP-256`, func(t *testing.T) { t.Parallel() require.Equal(t, "RSA-OAEP-256", jwa.RSA_OAEP_256().String(), `stringified value matches`) }) t.Run(`Lookup the object`, func(t *testing.T) { t.Parallel() v, ok := jwa.LookupKeyEncryptionAlgorithm("RSA-OAEP-384") require.True(t, ok, `Lookup should succeed`) require.Equal(t, jwa.RSA_OAEP_384(), v, `Lookup value should be equal to constant`) }) t.Run(`Unmarshal the string RSA-OAEP-384`, func(t *testing.T) { t.Parallel() var dst jwa.KeyEncryptionAlgorithm require.NoError(t, json.Unmarshal([]byte(strconv.Quote("RSA-OAEP-384")), &dst), `UnmarshalJSON is successful`) require.Equal(t, jwa.RSA_OAEP_384(), dst, `unmarshaled value should be equal to constant`) }) t.Run(`stringification for RSA-OAEP-384`, func(t *testing.T) { t.Parallel() require.Equal(t, "RSA-OAEP-384", jwa.RSA_OAEP_384().String(), `stringified value matches`) }) t.Run(`Lookup the object`, func(t *testing.T) { t.Parallel() v, ok := jwa.LookupKeyEncryptionAlgorithm("RSA-OAEP-512") require.True(t, ok, `Lookup should succeed`) require.Equal(t, jwa.RSA_OAEP_512(), v, `Lookup value should be equal to constant`) }) t.Run(`Unmarshal the string RSA-OAEP-512`, func(t *testing.T) { t.Parallel() var dst jwa.KeyEncryptionAlgorithm require.NoError(t, json.Unmarshal([]byte(strconv.Quote("RSA-OAEP-512")), &dst), `UnmarshalJSON is successful`) require.Equal(t, jwa.RSA_OAEP_512(), dst, `unmarshaled value should be equal to constant`) }) t.Run(`stringification for RSA-OAEP-512`, func(t *testing.T) { t.Parallel() require.Equal(t, "RSA-OAEP-512", jwa.RSA_OAEP_512().String(), `stringified value matches`) }) t.Run(`Unmarshal should fail for invalid value (totally made up) string value`, func(t *testing.T) { t.Parallel() var dst jwa.KeyEncryptionAlgorithm require.Error(t, json.Unmarshal([]byte(`totallyInvalidValue`), &dst), `Unmarshal should fail`) }) t.Run(`check symmetric values`, func(t *testing.T) { t.Parallel() t.Run(`A128GCMKW`, func(t *testing.T) { require.True(t, jwa.A128GCMKW().IsSymmetric(), `jwa.A128GCMKW returns expected value`) }) t.Run(`A128KW`, func(t *testing.T) { require.True(t, jwa.A128KW().IsSymmetric(), `jwa.A128KW returns expected value`) }) t.Run(`A192GCMKW`, func(t *testing.T) { require.True(t, jwa.A192GCMKW().IsSymmetric(), `jwa.A192GCMKW returns expected value`) }) t.Run(`A192KW`, func(t *testing.T) { require.True(t, jwa.A192KW().IsSymmetric(), `jwa.A192KW returns expected value`) }) t.Run(`A256GCMKW`, func(t *testing.T) { require.True(t, jwa.A256GCMKW().IsSymmetric(), `jwa.A256GCMKW returns expected value`) }) t.Run(`A256KW`, func(t *testing.T) { require.True(t, jwa.A256KW().IsSymmetric(), `jwa.A256KW returns expected value`) }) t.Run(`DIRECT`, func(t *testing.T) { require.True(t, jwa.DIRECT().IsSymmetric(), `jwa.DIRECT returns expected value`) }) t.Run(`ECDH_ES`, func(t *testing.T) { require.False(t, jwa.ECDH_ES().IsSymmetric(), `jwa.ECDH_ES returns expected value`) }) t.Run(`ECDH_ES_A128KW`, func(t *testing.T) { require.False(t, jwa.ECDH_ES_A128KW().IsSymmetric(), `jwa.ECDH_ES_A128KW returns expected value`) }) t.Run(`ECDH_ES_A192KW`, func(t *testing.T) { require.False(t, jwa.ECDH_ES_A192KW().IsSymmetric(), `jwa.ECDH_ES_A192KW returns expected value`) }) t.Run(`ECDH_ES_A256KW`, func(t *testing.T) { require.False(t, jwa.ECDH_ES_A256KW().IsSymmetric(), `jwa.ECDH_ES_A256KW returns expected value`) }) t.Run(`PBES2_HS256_A128KW`, func(t *testing.T) { require.True(t, jwa.PBES2_HS256_A128KW().IsSymmetric(), `jwa.PBES2_HS256_A128KW returns expected value`) }) t.Run(`PBES2_HS384_A192KW`, func(t *testing.T) { require.True(t, jwa.PBES2_HS384_A192KW().IsSymmetric(), `jwa.PBES2_HS384_A192KW returns expected value`) }) t.Run(`PBES2_HS512_A256KW`, func(t *testing.T) { require.True(t, jwa.PBES2_HS512_A256KW().IsSymmetric(), `jwa.PBES2_HS512_A256KW returns expected value`) }) t.Run(`RSA1_5`, func(t *testing.T) { require.False(t, jwa.RSA1_5().IsSymmetric(), `jwa.RSA1_5 returns expected value`) }) t.Run(`RSA_OAEP`, func(t *testing.T) { require.False(t, jwa.RSA_OAEP().IsSymmetric(), `jwa.RSA_OAEP returns expected value`) }) t.Run(`RSA_OAEP_256`, func(t *testing.T) { require.False(t, jwa.RSA_OAEP_256().IsSymmetric(), `jwa.RSA_OAEP_256 returns expected value`) }) t.Run(`RSA_OAEP_384`, func(t *testing.T) { require.False(t, jwa.RSA_OAEP_384().IsSymmetric(), `jwa.RSA_OAEP_384 returns expected value`) }) t.Run(`RSA_OAEP_512`, func(t *testing.T) { require.False(t, jwa.RSA_OAEP_512().IsSymmetric(), `jwa.RSA_OAEP_512 returns expected value`) }) }) t.Run(`check list of elements`, func(t *testing.T) { t.Parallel() var expected = map[jwa.KeyEncryptionAlgorithm]struct{}{ jwa.A128GCMKW(): {}, jwa.A128KW(): {}, jwa.A192GCMKW(): {}, jwa.A192KW(): {}, jwa.A256GCMKW(): {}, jwa.A256KW(): {}, jwa.DIRECT(): {}, jwa.ECDH_ES(): {}, jwa.ECDH_ES_A128KW(): {}, jwa.ECDH_ES_A192KW(): {}, jwa.ECDH_ES_A256KW(): {}, jwa.PBES2_HS256_A128KW(): {}, jwa.PBES2_HS384_A192KW(): {}, jwa.PBES2_HS512_A256KW(): {}, jwa.RSA1_5(): {}, jwa.RSA_OAEP(): {}, jwa.RSA_OAEP_256(): {}, jwa.RSA_OAEP_384(): {}, jwa.RSA_OAEP_512(): {}, } for _, v := range jwa.KeyEncryptionAlgorithms() { _, ok := expected[v] require.True(t, ok, `%q should be in the list for KeyEncryptionAlgorithm`, v) delete(expected, v) } require.Len(t, expected, 0) }) } // Note: this test can NOT be run in parallel as it uses options with global effect. func TestKeyEncryptionAlgorithmCustomAlgorithm(t *testing.T) { // These subtests can NOT be run in parallel as options with global effect change. const customAlgorithmValue = `custom-algorithm` for _, symmetric := range []bool{true, false} { customAlgorithm := jwa.NewKeyEncryptionAlgorithm(customAlgorithmValue, jwa.WithIsSymmetric(symmetric)) // Unregister the custom algorithm, in case tests fail. t.Cleanup(func() { jwa.UnregisterKeyEncryptionAlgorithm(customAlgorithm) }) t.Run(`with custom algorithm registered`, func(t *testing.T) { jwa.RegisterKeyEncryptionAlgorithm(customAlgorithm) t.Run(`Lookup the object`, func(t *testing.T) { t.Parallel() v, ok := jwa.LookupKeyEncryptionAlgorithm(customAlgorithmValue) require.True(t, ok, `Lookup should succeed`) require.Equal(t, customAlgorithm, v, `Lookup value should be equal to constant`) }) t.Run(`Unmarshal custom algorithm`, func(t *testing.T) { t.Parallel() var dst jwa.KeyEncryptionAlgorithm require.NoError(t, json.Unmarshal([]byte(strconv.Quote(customAlgorithmValue)), &dst), `Unmarshal is successful`) require.Equal(t, customAlgorithm, dst, `accepted value should be equal to variable`) }) t.Run(`check symmetric`, func(t *testing.T) { t.Parallel() require.Equal(t, symmetric, customAlgorithm.IsSymmetric(), `custom algorithm's symmetric attribute should match`) }) }) t.Run(`with custom algorithm deregistered`, func(t *testing.T) { jwa.UnregisterKeyEncryptionAlgorithm(customAlgorithm) t.Run(`Lookup the object`, func(t *testing.T) { t.Parallel() _, ok := jwa.LookupKeyEncryptionAlgorithm(customAlgorithmValue) require.False(t, ok, `Lookup should fail`) }) t.Run(`Unmarshal custom algorithm`, func(t *testing.T) { t.Parallel() var dst jwa.KeyEncryptionAlgorithm require.Error(t, json.Unmarshal([]byte(customAlgorithmValue), &dst), `Unmarshal should fail`) }) }) } } golang-github-lestrrat-go-jwx-3.0.13/jwa/key_type_gen.go000066400000000000000000000103141515060566400231750ustar00rootroot00000000000000// Code generated by tools/cmd/genjwa/main.go. DO NOT EDIT. package jwa import ( "encoding/json" "fmt" "sort" "sync" ) var muAllKeyType sync.RWMutex var allKeyType = map[string]KeyType{} var muListKeyType sync.RWMutex var listKeyType []KeyType var builtinKeyType = map[string]struct{}{} func init() { // builtin values for KeyType algorithms := make([]KeyType, 4) algorithms[0] = NewKeyType("EC") algorithms[1] = NewKeyType("OKP") algorithms[2] = NewKeyType("oct") algorithms[3] = NewKeyType("RSA") RegisterKeyType(algorithms...) } // EC returns an object representing EC. Elliptic Curve func EC() KeyType { return lookupBuiltinKeyType("EC") } var invalidKeyType = NewKeyType("") // InvalidKeyType returns an object representing invalid key type. Invalid KeyType func InvalidKeyType() KeyType { return invalidKeyType } // OKP returns an object representing OKP. Octet string key pairs func OKP() KeyType { return lookupBuiltinKeyType("OKP") } // OctetSeq returns an object representing oct. Octet sequence (used to represent symmetric keys) func OctetSeq() KeyType { return lookupBuiltinKeyType("oct") } // RSA returns an object representing RSA. RSA func RSA() KeyType { return lookupBuiltinKeyType("RSA") } func lookupBuiltinKeyType(name string) KeyType { muAllKeyType.RLock() v, ok := allKeyType[name] muAllKeyType.RUnlock() if !ok { panic(fmt.Sprintf(`jwa: KeyType %q not registered`, name)) } return v } // KeyType represents the key type ("kty") that are supported type KeyType struct { name string deprecated bool } func (s KeyType) String() string { return s.name } // IsDeprecated returns true if the KeyType object is deprecated. func (s KeyType) IsDeprecated() bool { return s.deprecated } // EmptyKeyType returns an empty KeyType object, used as a zero value. func EmptyKeyType() KeyType { return KeyType{} } // NewKeyType creates a new KeyType object with the given name. func NewKeyType(name string, options ...NewAlgorithmOption) KeyType { var deprecated bool for _, option := range options { switch option.Ident() { case identDeprecated{}: if err := option.Value(&deprecated); err != nil { panic("jwa.NewKeyType: WithDeprecated option must be a boolean") } } } return KeyType{name: name, deprecated: deprecated} } // LookupKeyType returns the KeyType object for the given name. func LookupKeyType(name string) (KeyType, bool) { muAllKeyType.RLock() v, ok := allKeyType[name] muAllKeyType.RUnlock() return v, ok } // RegisterKeyType registers a new KeyType. The signature value must be immutable // and safe to be used by multiple goroutines, as it is going to be shared with all other users of this library. func RegisterKeyType(algorithms ...KeyType) { muAllKeyType.Lock() for _, alg := range algorithms { allKeyType[alg.String()] = alg } muAllKeyType.Unlock() rebuildKeyType() } // UnregisterKeyType unregisters a KeyType from its known database. // Non-existent entries, as well as built-in algorithms will silently be ignored. func UnregisterKeyType(algorithms ...KeyType) { muAllKeyType.Lock() for _, alg := range algorithms { if _, ok := builtinKeyType[alg.String()]; ok { continue } delete(allKeyType, alg.String()) } muAllKeyType.Unlock() rebuildKeyType() } func rebuildKeyType() { list := make([]KeyType, 0, len(allKeyType)) muAllKeyType.RLock() for _, v := range allKeyType { list = append(list, v) } muAllKeyType.RUnlock() sort.Slice(list, func(i, j int) bool { return list[i].String() < list[j].String() }) muListKeyType.Lock() listKeyType = list muListKeyType.Unlock() } // KeyTypes returns a list of all available values for KeyType. func KeyTypes() []KeyType { muListKeyType.RLock() defer muListKeyType.RUnlock() return listKeyType } // MarshalJSON serializes the KeyType object to a JSON string. func (s KeyType) MarshalJSON() ([]byte, error) { return json.Marshal(s.String()) } // UnmarshalJSON deserializes the JSON string to a KeyType object. func (s *KeyType) UnmarshalJSON(data []byte) error { var name string if err := json.Unmarshal(data, &name); err != nil { return fmt.Errorf(`failed to unmarshal KeyType: %w`, err) } v, ok := LookupKeyType(name) if !ok { return fmt.Errorf(`unknown KeyType: %q`, name) } *s = v return nil } golang-github-lestrrat-go-jwx-3.0.13/jwa/key_type_gen_test.go000066400000000000000000000120271515060566400242370ustar00rootroot00000000000000// Code generated by tools/cmd/genjwa/main.go. DO NOT EDIT package jwa_test import ( "encoding/json" "strconv" "testing" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/stretchr/testify/require" ) func TestKeyType(t *testing.T) { t.Parallel() t.Run(`Lookup the object`, func(t *testing.T) { t.Parallel() v, ok := jwa.LookupKeyType("EC") require.True(t, ok, `Lookup should succeed`) require.Equal(t, jwa.EC(), v, `Lookup value should be equal to constant`) }) t.Run(`Unmarshal the string EC`, func(t *testing.T) { t.Parallel() var dst jwa.KeyType require.NoError(t, json.Unmarshal([]byte(strconv.Quote("EC")), &dst), `UnmarshalJSON is successful`) require.Equal(t, jwa.EC(), dst, `unmarshaled value should be equal to constant`) }) t.Run(`stringification for EC`, func(t *testing.T) { t.Parallel() require.Equal(t, "EC", jwa.EC().String(), `stringified value matches`) }) t.Run(`Lookup the object`, func(t *testing.T) { t.Parallel() v, ok := jwa.LookupKeyType("OKP") require.True(t, ok, `Lookup should succeed`) require.Equal(t, jwa.OKP(), v, `Lookup value should be equal to constant`) }) t.Run(`Unmarshal the string OKP`, func(t *testing.T) { t.Parallel() var dst jwa.KeyType require.NoError(t, json.Unmarshal([]byte(strconv.Quote("OKP")), &dst), `UnmarshalJSON is successful`) require.Equal(t, jwa.OKP(), dst, `unmarshaled value should be equal to constant`) }) t.Run(`stringification for OKP`, func(t *testing.T) { t.Parallel() require.Equal(t, "OKP", jwa.OKP().String(), `stringified value matches`) }) t.Run(`Lookup the object`, func(t *testing.T) { t.Parallel() v, ok := jwa.LookupKeyType("oct") require.True(t, ok, `Lookup should succeed`) require.Equal(t, jwa.OctetSeq(), v, `Lookup value should be equal to constant`) }) t.Run(`Unmarshal the string oct`, func(t *testing.T) { t.Parallel() var dst jwa.KeyType require.NoError(t, json.Unmarshal([]byte(strconv.Quote("oct")), &dst), `UnmarshalJSON is successful`) require.Equal(t, jwa.OctetSeq(), dst, `unmarshaled value should be equal to constant`) }) t.Run(`stringification for oct`, func(t *testing.T) { t.Parallel() require.Equal(t, "oct", jwa.OctetSeq().String(), `stringified value matches`) }) t.Run(`Lookup the object`, func(t *testing.T) { t.Parallel() v, ok := jwa.LookupKeyType("RSA") require.True(t, ok, `Lookup should succeed`) require.Equal(t, jwa.RSA(), v, `Lookup value should be equal to constant`) }) t.Run(`Unmarshal the string RSA`, func(t *testing.T) { t.Parallel() var dst jwa.KeyType require.NoError(t, json.Unmarshal([]byte(strconv.Quote("RSA")), &dst), `UnmarshalJSON is successful`) require.Equal(t, jwa.RSA(), dst, `unmarshaled value should be equal to constant`) }) t.Run(`stringification for RSA`, func(t *testing.T) { t.Parallel() require.Equal(t, "RSA", jwa.RSA().String(), `stringified value matches`) }) t.Run(`Unmarshal should fail for invalid value (totally made up) string value`, func(t *testing.T) { t.Parallel() var dst jwa.KeyType require.Error(t, json.Unmarshal([]byte(`totallyInvalidValue`), &dst), `Unmarshal should fail`) }) t.Run(`check list of elements`, func(t *testing.T) { t.Parallel() var expected = map[jwa.KeyType]struct{}{ jwa.EC(): {}, jwa.OKP(): {}, jwa.OctetSeq(): {}, jwa.RSA(): {}, } for _, v := range jwa.KeyTypes() { _, ok := expected[v] require.True(t, ok, `%q should be in the list for KeyType`, v) delete(expected, v) } require.Len(t, expected, 0) }) } // Note: this test can NOT be run in parallel as it uses options with global effect. func TestKeyTypeCustomAlgorithm(t *testing.T) { // These subtests can NOT be run in parallel as options with global effect change. const customAlgorithmValue = `custom-algorithm` customAlgorithm := jwa.NewKeyType(customAlgorithmValue) // Unregister the custom algorithm, in case tests fail. t.Cleanup(func() { jwa.UnregisterKeyType(customAlgorithm) }) t.Run(`with custom algorithm registered`, func(t *testing.T) { jwa.RegisterKeyType(customAlgorithm) t.Run(`Lookup the object`, func(t *testing.T) { t.Parallel() v, ok := jwa.LookupKeyType(customAlgorithmValue) require.True(t, ok, `Lookup should succeed`) require.Equal(t, customAlgorithm, v, `Lookup value should be equal to constant`) }) t.Run(`Unmarshal custom algorithm`, func(t *testing.T) { t.Parallel() var dst jwa.KeyType require.NoError(t, json.Unmarshal([]byte(strconv.Quote(customAlgorithmValue)), &dst), `Unmarshal is successful`) require.Equal(t, customAlgorithm, dst, `accepted value should be equal to variable`) }) }) t.Run(`with custom algorithm deregistered`, func(t *testing.T) { jwa.UnregisterKeyType(customAlgorithm) t.Run(`Lookup the object`, func(t *testing.T) { t.Parallel() _, ok := jwa.LookupKeyType(customAlgorithmValue) require.False(t, ok, `Lookup should fail`) }) t.Run(`Unmarshal custom algorithm`, func(t *testing.T) { t.Parallel() var dst jwa.KeyType require.Error(t, json.Unmarshal([]byte(customAlgorithmValue), &dst), `Unmarshal should fail`) }) }) } golang-github-lestrrat-go-jwx-3.0.13/jwa/options.yaml000066400000000000000000000031531515060566400225460ustar00rootroot00000000000000package_name: jwa output: jwa/options_gen.go interfaces: - name: NewAlgorithmOption methods: - newSignatureAlgorithmOption - newKeyEncryptionAlgorithmOption - newSignatureKeyEncryptionAlgorithmOption comment: | NewAlgorithmOption represents an option that can be passed to any of the constructor functions - name: NewSignatureAlgorithmOption methods: - newSignatureAlgorithmOption comment: | NewSignatureAlgorithmOption represents an option that can be passed to the NewSignatureAlgorithm - name: NewKeyEncryptionAlgorithmOption methods: - newKeyEncryptionAlgorithmOption comment: | NewKeyEncryptionAlgorithmOption represents an option that can be passed to the NewKeyEncryptionAlgorithm - name: NewSignatureKeyEncryptionAlgorithmOption comment: | NewSignatureKeyEncryptionAlgorithmOption represents an option that can be passed to both NewSignatureAlgorithm and NewKeyEncryptionAlgorithm methods: - newSignatureAlgorithmOption - newKeyEncryptionAlgorithmOption options: - ident: IsSymmetric interface: NewSignatureKeyEncryptionAlgorithmOption argument_type: bool comment: | IsSymmetric specifies that the algorithm is symmetric - ident: Deprecated interface: NewAlgorithmOption argument_type: bool comment: | WithDeprecated specifies that the algorithm is deprecated. In order to un-deprecate an algorithm, you will have to create a new algorithm with the same values but with the Deprecated option set to false, and then call RegisterXXXXAlgorithm with the new algorithm.golang-github-lestrrat-go-jwx-3.0.13/jwa/options_gen.go000066400000000000000000000051071515060566400230430ustar00rootroot00000000000000// Code generated by tools/cmd/genoptions/main.go. DO NOT EDIT. package jwa import ( "github.com/lestrrat-go/option/v2" ) type Option = option.Interface // NewAlgorithmOption represents an option that can be passed to any of the constructor functions type NewAlgorithmOption interface { Option newSignatureAlgorithmOption() newKeyEncryptionAlgorithmOption() newSignatureKeyEncryptionAlgorithmOption() } type newAlgorithmOption struct { Option } func (*newAlgorithmOption) newSignatureAlgorithmOption() {} func (*newAlgorithmOption) newKeyEncryptionAlgorithmOption() {} func (*newAlgorithmOption) newSignatureKeyEncryptionAlgorithmOption() {} // NewKeyEncryptionAlgorithmOption represents an option that can be passed to the NewKeyEncryptionAlgorithm type NewKeyEncryptionAlgorithmOption interface { Option newKeyEncryptionAlgorithmOption() } type newKeyEncryptionAlgorithmOption struct { Option } func (*newKeyEncryptionAlgorithmOption) newKeyEncryptionAlgorithmOption() {} // NewSignatureAlgorithmOption represents an option that can be passed to the NewSignatureAlgorithm type NewSignatureAlgorithmOption interface { Option newSignatureAlgorithmOption() } type newSignatureAlgorithmOption struct { Option } func (*newSignatureAlgorithmOption) newSignatureAlgorithmOption() {} // NewSignatureKeyEncryptionAlgorithmOption represents an option that can be passed to both // NewSignatureAlgorithm and NewKeyEncryptionAlgorithm type NewSignatureKeyEncryptionAlgorithmOption interface { Option newSignatureAlgorithmOption() newKeyEncryptionAlgorithmOption() } type newSignatureKeyEncryptionAlgorithmOption struct { Option } func (*newSignatureKeyEncryptionAlgorithmOption) newSignatureAlgorithmOption() {} func (*newSignatureKeyEncryptionAlgorithmOption) newKeyEncryptionAlgorithmOption() {} type identDeprecated struct{} type identIsSymmetric struct{} func (identDeprecated) String() string { return "WithDeprecated" } func (identIsSymmetric) String() string { return "WithIsSymmetric" } // WithDeprecated specifies that the algorithm is deprecated. In order to // un-deprecate an algorithm, you will have to create a new algorithm // with the same values but with the Deprecated option set to false, and // then call RegisterXXXXAlgorithm with the new algorithm. func WithDeprecated(v bool) NewAlgorithmOption { return &newAlgorithmOption{option.New(identDeprecated{}, v)} } // IsSymmetric specifies that the algorithm is symmetric func WithIsSymmetric(v bool) NewSignatureKeyEncryptionAlgorithmOption { return &newSignatureKeyEncryptionAlgorithmOption{option.New(identIsSymmetric{}, v)} } golang-github-lestrrat-go-jwx-3.0.13/jwa/options_gen_test.go000066400000000000000000000004661515060566400241050ustar00rootroot00000000000000// Code generated by tools/cmd/genoptions/main.go. DO NOT EDIT. package jwa import ( "testing" "github.com/stretchr/testify/require" ) func TestOptionIdent(t *testing.T) { require.Equal(t, "WithDeprecated", identDeprecated{}.String()) require.Equal(t, "WithIsSymmetric", identIsSymmetric{}.String()) } golang-github-lestrrat-go-jwx-3.0.13/jwa/secp2561k.go000066400000000000000000000004721515060566400221420ustar00rootroot00000000000000//go:build jwx_es256k package jwa var secp256k1Algorithm = NewEllipticCurveAlgorithm("secp256k1") // This constant is only available if compiled with jwx_es256k build tag func Secp256k1() EllipticCurveAlgorithm { return secp256k1Algorithm } func init() { RegisterEllipticCurveAlgorithm(secp256k1Algorithm) } golang-github-lestrrat-go-jwx-3.0.13/jwa/secp2561k_test.go000066400000000000000000000013061515060566400231760ustar00rootroot00000000000000//go:build jwx_es256k package jwa_test import ( "encoding/json" "fmt" "testing" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/stretchr/testify/require" ) func TestSecp256k1(t *testing.T) { t.Parallel() t.Run(`accept jwa constant Secp256k1`, func(t *testing.T) { t.Parallel() var dst jwa.EllipticCurveAlgorithm require.NoError(t, json.Unmarshal([]byte(fmt.Sprintf("%q", jwa.Secp256k1().String())), &dst), `Unmarshal is successful`) require.Equal(t, jwa.Secp256k1(), dst, `accepted value should be equal to constant`) }) t.Run(`stringification for secp256k1`, func(t *testing.T) { t.Parallel() require.Equal(t, "secp256k1", jwa.Secp256k1().String(), `stringified value matches`) }) } golang-github-lestrrat-go-jwx-3.0.13/jwa/signature_gen.go000066400000000000000000000203421515060566400233470ustar00rootroot00000000000000// Code generated by tools/cmd/genjwa/main.go. DO NOT EDIT. package jwa import ( "encoding/json" "fmt" "sort" "sync" ) var muAllSignatureAlgorithm sync.RWMutex var allSignatureAlgorithm = map[string]SignatureAlgorithm{} var muListSignatureAlgorithm sync.RWMutex var listSignatureAlgorithm []SignatureAlgorithm var builtinSignatureAlgorithm = map[string]struct{}{} func init() { // builtin values for SignatureAlgorithm algorithms := make([]SignatureAlgorithm, 15) algorithms[0] = NewSignatureAlgorithm("ES256") algorithms[1] = NewSignatureAlgorithm("ES256K") algorithms[2] = NewSignatureAlgorithm("ES384") algorithms[3] = NewSignatureAlgorithm("ES512") algorithms[4] = NewSignatureAlgorithm("EdDSA") algorithms[5] = NewSignatureAlgorithm("HS256", WithIsSymmetric(true)) algorithms[6] = NewSignatureAlgorithm("HS384", WithIsSymmetric(true)) algorithms[7] = NewSignatureAlgorithm("HS512", WithIsSymmetric(true)) algorithms[8] = NewSignatureAlgorithm("none") algorithms[9] = NewSignatureAlgorithm("PS256") algorithms[10] = NewSignatureAlgorithm("PS384") algorithms[11] = NewSignatureAlgorithm("PS512") algorithms[12] = NewSignatureAlgorithm("RS256") algorithms[13] = NewSignatureAlgorithm("RS384") algorithms[14] = NewSignatureAlgorithm("RS512") RegisterSignatureAlgorithm(algorithms...) } // ES256 returns an object representing ECDSA signature algorithm using P-256 curve and SHA-256. func ES256() SignatureAlgorithm { return lookupBuiltinSignatureAlgorithm("ES256") } // ES256K returns an object representing ECDSA signature algorithm using secp256k1 curve and SHA-256. func ES256K() SignatureAlgorithm { return lookupBuiltinSignatureAlgorithm("ES256K") } // ES384 returns an object representing ECDSA signature algorithm using P-384 curve and SHA-384. func ES384() SignatureAlgorithm { return lookupBuiltinSignatureAlgorithm("ES384") } // ES512 returns an object representing ECDSA signature algorithm using P-521 curve and SHA-512. func ES512() SignatureAlgorithm { return lookupBuiltinSignatureAlgorithm("ES512") } // EdDSA returns an object representing EdDSA signature algorithms. func EdDSA() SignatureAlgorithm { return lookupBuiltinSignatureAlgorithm("EdDSA") } // HS256 returns an object representing HMAC signature algorithm using SHA-256. func HS256() SignatureAlgorithm { return lookupBuiltinSignatureAlgorithm("HS256") } // HS384 returns an object representing HMAC signature algorithm using SHA-384. func HS384() SignatureAlgorithm { return lookupBuiltinSignatureAlgorithm("HS384") } // HS512 returns an object representing HMAC signature algorithm using SHA-512. func HS512() SignatureAlgorithm { return lookupBuiltinSignatureAlgorithm("HS512") } // NoSignature returns an object representing the lack of a signature algorithm. Using this value specifies that the content should not be signed, which you should avoid doing. func NoSignature() SignatureAlgorithm { return lookupBuiltinSignatureAlgorithm("none") } // PS256 returns an object representing RSASSA-PSS signature algorithm using SHA-256 and MGF1-SHA256. func PS256() SignatureAlgorithm { return lookupBuiltinSignatureAlgorithm("PS256") } // PS384 returns an object representing RSASSA-PSS signature algorithm using SHA-384 and MGF1-SHA384. func PS384() SignatureAlgorithm { return lookupBuiltinSignatureAlgorithm("PS384") } // PS512 returns an object representing RSASSA-PSS signature algorithm using SHA-512 and MGF1-SHA512. func PS512() SignatureAlgorithm { return lookupBuiltinSignatureAlgorithm("PS512") } // RS256 returns an object representing RSASSA-PKCS-v1.5 signature algorithm using SHA-256. func RS256() SignatureAlgorithm { return lookupBuiltinSignatureAlgorithm("RS256") } // RS384 returns an object representing RSASSA-PKCS-v1.5 signature algorithm using SHA-384. func RS384() SignatureAlgorithm { return lookupBuiltinSignatureAlgorithm("RS384") } // RS512 returns an object representing RSASSA-PKCS-v1.5 signature algorithm using SHA-512. func RS512() SignatureAlgorithm { return lookupBuiltinSignatureAlgorithm("RS512") } func lookupBuiltinSignatureAlgorithm(name string) SignatureAlgorithm { muAllSignatureAlgorithm.RLock() v, ok := allSignatureAlgorithm[name] muAllSignatureAlgorithm.RUnlock() if !ok { panic(fmt.Sprintf(`jwa: SignatureAlgorithm %q not registered`, name)) } return v } // SignatureAlgorithm represents the various signature algorithms as described in https://tools.ietf.org/html/rfc7518#section-3.1 type SignatureAlgorithm struct { name string deprecated bool isSymmetric bool } func (s SignatureAlgorithm) String() string { return s.name } // IsDeprecated returns true if the SignatureAlgorithm object is deprecated. func (s SignatureAlgorithm) IsDeprecated() bool { return s.deprecated } // IsSymmetric returns true if the SignatureAlgorithm object is symmetric. Symmetric algorithms use the same key for both encryption and decryption. func (s SignatureAlgorithm) IsSymmetric() bool { return s.isSymmetric } // EmptySignatureAlgorithm returns an empty SignatureAlgorithm object, used as a zero value. func EmptySignatureAlgorithm() SignatureAlgorithm { return SignatureAlgorithm{} } // NewSignatureAlgorithm creates a new SignatureAlgorithm object with the given name. func NewSignatureAlgorithm(name string, options ...NewSignatureAlgorithmOption) SignatureAlgorithm { var deprecated bool var isSymmetric bool for _, option := range options { switch option.Ident() { case identIsSymmetric{}: if err := option.Value(&isSymmetric); err != nil { panic("jwa.NewSignatureAlgorithm: WithIsSymmetric option must be a boolean") } case identDeprecated{}: if err := option.Value(&deprecated); err != nil { panic("jwa.NewSignatureAlgorithm: WithDeprecated option must be a boolean") } } } return SignatureAlgorithm{name: name, deprecated: deprecated, isSymmetric: isSymmetric} } // LookupSignatureAlgorithm returns the SignatureAlgorithm object for the given name. func LookupSignatureAlgorithm(name string) (SignatureAlgorithm, bool) { muAllSignatureAlgorithm.RLock() v, ok := allSignatureAlgorithm[name] muAllSignatureAlgorithm.RUnlock() return v, ok } // RegisterSignatureAlgorithm registers a new SignatureAlgorithm. The signature value must be immutable // and safe to be used by multiple goroutines, as it is going to be shared with all other users of this library. func RegisterSignatureAlgorithm(algorithms ...SignatureAlgorithm) { muAllSignatureAlgorithm.Lock() for _, alg := range algorithms { allSignatureAlgorithm[alg.String()] = alg } muAllSignatureAlgorithm.Unlock() rebuildSignatureAlgorithm() } // UnregisterSignatureAlgorithm unregisters a SignatureAlgorithm from its known database. // Non-existent entries, as well as built-in algorithms will silently be ignored. func UnregisterSignatureAlgorithm(algorithms ...SignatureAlgorithm) { muAllSignatureAlgorithm.Lock() for _, alg := range algorithms { if _, ok := builtinSignatureAlgorithm[alg.String()]; ok { continue } delete(allSignatureAlgorithm, alg.String()) } muAllSignatureAlgorithm.Unlock() rebuildSignatureAlgorithm() } func rebuildSignatureAlgorithm() { list := make([]SignatureAlgorithm, 0, len(allSignatureAlgorithm)) muAllSignatureAlgorithm.RLock() for _, v := range allSignatureAlgorithm { list = append(list, v) } muAllSignatureAlgorithm.RUnlock() sort.Slice(list, func(i, j int) bool { return list[i].String() < list[j].String() }) muListSignatureAlgorithm.Lock() listSignatureAlgorithm = list muListSignatureAlgorithm.Unlock() } // SignatureAlgorithms returns a list of all available values for SignatureAlgorithm. func SignatureAlgorithms() []SignatureAlgorithm { muListSignatureAlgorithm.RLock() defer muListSignatureAlgorithm.RUnlock() return listSignatureAlgorithm } // MarshalJSON serializes the SignatureAlgorithm object to a JSON string. func (s SignatureAlgorithm) MarshalJSON() ([]byte, error) { return json.Marshal(s.String()) } // UnmarshalJSON deserializes the JSON string to a SignatureAlgorithm object. func (s *SignatureAlgorithm) UnmarshalJSON(data []byte) error { var name string if err := json.Unmarshal(data, &name); err != nil { return fmt.Errorf(`failed to unmarshal SignatureAlgorithm: %w`, err) } v, ok := LookupSignatureAlgorithm(name) if !ok { return fmt.Errorf(`unknown SignatureAlgorithm: %q`, name) } *s = v return nil } golang-github-lestrrat-go-jwx-3.0.13/jwa/signature_gen_test.go000066400000000000000000000366051515060566400244170ustar00rootroot00000000000000// Code generated by tools/cmd/genjwa/main.go. DO NOT EDIT package jwa_test import ( "encoding/json" "strconv" "testing" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/stretchr/testify/require" ) func TestSignatureAlgorithm(t *testing.T) { t.Parallel() t.Run(`Lookup the object`, func(t *testing.T) { t.Parallel() v, ok := jwa.LookupSignatureAlgorithm("ES256") require.True(t, ok, `Lookup should succeed`) require.Equal(t, jwa.ES256(), v, `Lookup value should be equal to constant`) }) t.Run(`Unmarshal the string ES256`, func(t *testing.T) { t.Parallel() var dst jwa.SignatureAlgorithm require.NoError(t, json.Unmarshal([]byte(strconv.Quote("ES256")), &dst), `UnmarshalJSON is successful`) require.Equal(t, jwa.ES256(), dst, `unmarshaled value should be equal to constant`) }) t.Run(`stringification for ES256`, func(t *testing.T) { t.Parallel() require.Equal(t, "ES256", jwa.ES256().String(), `stringified value matches`) }) t.Run(`Lookup the object`, func(t *testing.T) { t.Parallel() v, ok := jwa.LookupSignatureAlgorithm("ES256K") require.True(t, ok, `Lookup should succeed`) require.Equal(t, jwa.ES256K(), v, `Lookup value should be equal to constant`) }) t.Run(`Unmarshal the string ES256K`, func(t *testing.T) { t.Parallel() var dst jwa.SignatureAlgorithm require.NoError(t, json.Unmarshal([]byte(strconv.Quote("ES256K")), &dst), `UnmarshalJSON is successful`) require.Equal(t, jwa.ES256K(), dst, `unmarshaled value should be equal to constant`) }) t.Run(`stringification for ES256K`, func(t *testing.T) { t.Parallel() require.Equal(t, "ES256K", jwa.ES256K().String(), `stringified value matches`) }) t.Run(`Lookup the object`, func(t *testing.T) { t.Parallel() v, ok := jwa.LookupSignatureAlgorithm("ES384") require.True(t, ok, `Lookup should succeed`) require.Equal(t, jwa.ES384(), v, `Lookup value should be equal to constant`) }) t.Run(`Unmarshal the string ES384`, func(t *testing.T) { t.Parallel() var dst jwa.SignatureAlgorithm require.NoError(t, json.Unmarshal([]byte(strconv.Quote("ES384")), &dst), `UnmarshalJSON is successful`) require.Equal(t, jwa.ES384(), dst, `unmarshaled value should be equal to constant`) }) t.Run(`stringification for ES384`, func(t *testing.T) { t.Parallel() require.Equal(t, "ES384", jwa.ES384().String(), `stringified value matches`) }) t.Run(`Lookup the object`, func(t *testing.T) { t.Parallel() v, ok := jwa.LookupSignatureAlgorithm("ES512") require.True(t, ok, `Lookup should succeed`) require.Equal(t, jwa.ES512(), v, `Lookup value should be equal to constant`) }) t.Run(`Unmarshal the string ES512`, func(t *testing.T) { t.Parallel() var dst jwa.SignatureAlgorithm require.NoError(t, json.Unmarshal([]byte(strconv.Quote("ES512")), &dst), `UnmarshalJSON is successful`) require.Equal(t, jwa.ES512(), dst, `unmarshaled value should be equal to constant`) }) t.Run(`stringification for ES512`, func(t *testing.T) { t.Parallel() require.Equal(t, "ES512", jwa.ES512().String(), `stringified value matches`) }) t.Run(`Lookup the object`, func(t *testing.T) { t.Parallel() v, ok := jwa.LookupSignatureAlgorithm("EdDSA") require.True(t, ok, `Lookup should succeed`) require.Equal(t, jwa.EdDSA(), v, `Lookup value should be equal to constant`) }) t.Run(`Unmarshal the string EdDSA`, func(t *testing.T) { t.Parallel() var dst jwa.SignatureAlgorithm require.NoError(t, json.Unmarshal([]byte(strconv.Quote("EdDSA")), &dst), `UnmarshalJSON is successful`) require.Equal(t, jwa.EdDSA(), dst, `unmarshaled value should be equal to constant`) }) t.Run(`stringification for EdDSA`, func(t *testing.T) { t.Parallel() require.Equal(t, "EdDSA", jwa.EdDSA().String(), `stringified value matches`) }) t.Run(`Lookup the object`, func(t *testing.T) { t.Parallel() v, ok := jwa.LookupSignatureAlgorithm("HS256") require.True(t, ok, `Lookup should succeed`) require.Equal(t, jwa.HS256(), v, `Lookup value should be equal to constant`) }) t.Run(`Unmarshal the string HS256`, func(t *testing.T) { t.Parallel() var dst jwa.SignatureAlgorithm require.NoError(t, json.Unmarshal([]byte(strconv.Quote("HS256")), &dst), `UnmarshalJSON is successful`) require.Equal(t, jwa.HS256(), dst, `unmarshaled value should be equal to constant`) }) t.Run(`stringification for HS256`, func(t *testing.T) { t.Parallel() require.Equal(t, "HS256", jwa.HS256().String(), `stringified value matches`) }) t.Run(`Lookup the object`, func(t *testing.T) { t.Parallel() v, ok := jwa.LookupSignatureAlgorithm("HS384") require.True(t, ok, `Lookup should succeed`) require.Equal(t, jwa.HS384(), v, `Lookup value should be equal to constant`) }) t.Run(`Unmarshal the string HS384`, func(t *testing.T) { t.Parallel() var dst jwa.SignatureAlgorithm require.NoError(t, json.Unmarshal([]byte(strconv.Quote("HS384")), &dst), `UnmarshalJSON is successful`) require.Equal(t, jwa.HS384(), dst, `unmarshaled value should be equal to constant`) }) t.Run(`stringification for HS384`, func(t *testing.T) { t.Parallel() require.Equal(t, "HS384", jwa.HS384().String(), `stringified value matches`) }) t.Run(`Lookup the object`, func(t *testing.T) { t.Parallel() v, ok := jwa.LookupSignatureAlgorithm("HS512") require.True(t, ok, `Lookup should succeed`) require.Equal(t, jwa.HS512(), v, `Lookup value should be equal to constant`) }) t.Run(`Unmarshal the string HS512`, func(t *testing.T) { t.Parallel() var dst jwa.SignatureAlgorithm require.NoError(t, json.Unmarshal([]byte(strconv.Quote("HS512")), &dst), `UnmarshalJSON is successful`) require.Equal(t, jwa.HS512(), dst, `unmarshaled value should be equal to constant`) }) t.Run(`stringification for HS512`, func(t *testing.T) { t.Parallel() require.Equal(t, "HS512", jwa.HS512().String(), `stringified value matches`) }) t.Run(`Lookup the object`, func(t *testing.T) { t.Parallel() v, ok := jwa.LookupSignatureAlgorithm("none") require.True(t, ok, `Lookup should succeed`) require.Equal(t, jwa.NoSignature(), v, `Lookup value should be equal to constant`) }) t.Run(`Unmarshal the string none`, func(t *testing.T) { t.Parallel() var dst jwa.SignatureAlgorithm require.NoError(t, json.Unmarshal([]byte(strconv.Quote("none")), &dst), `UnmarshalJSON is successful`) require.Equal(t, jwa.NoSignature(), dst, `unmarshaled value should be equal to constant`) }) t.Run(`stringification for none`, func(t *testing.T) { t.Parallel() require.Equal(t, "none", jwa.NoSignature().String(), `stringified value matches`) }) t.Run(`Lookup the object`, func(t *testing.T) { t.Parallel() v, ok := jwa.LookupSignatureAlgorithm("PS256") require.True(t, ok, `Lookup should succeed`) require.Equal(t, jwa.PS256(), v, `Lookup value should be equal to constant`) }) t.Run(`Unmarshal the string PS256`, func(t *testing.T) { t.Parallel() var dst jwa.SignatureAlgorithm require.NoError(t, json.Unmarshal([]byte(strconv.Quote("PS256")), &dst), `UnmarshalJSON is successful`) require.Equal(t, jwa.PS256(), dst, `unmarshaled value should be equal to constant`) }) t.Run(`stringification for PS256`, func(t *testing.T) { t.Parallel() require.Equal(t, "PS256", jwa.PS256().String(), `stringified value matches`) }) t.Run(`Lookup the object`, func(t *testing.T) { t.Parallel() v, ok := jwa.LookupSignatureAlgorithm("PS384") require.True(t, ok, `Lookup should succeed`) require.Equal(t, jwa.PS384(), v, `Lookup value should be equal to constant`) }) t.Run(`Unmarshal the string PS384`, func(t *testing.T) { t.Parallel() var dst jwa.SignatureAlgorithm require.NoError(t, json.Unmarshal([]byte(strconv.Quote("PS384")), &dst), `UnmarshalJSON is successful`) require.Equal(t, jwa.PS384(), dst, `unmarshaled value should be equal to constant`) }) t.Run(`stringification for PS384`, func(t *testing.T) { t.Parallel() require.Equal(t, "PS384", jwa.PS384().String(), `stringified value matches`) }) t.Run(`Lookup the object`, func(t *testing.T) { t.Parallel() v, ok := jwa.LookupSignatureAlgorithm("PS512") require.True(t, ok, `Lookup should succeed`) require.Equal(t, jwa.PS512(), v, `Lookup value should be equal to constant`) }) t.Run(`Unmarshal the string PS512`, func(t *testing.T) { t.Parallel() var dst jwa.SignatureAlgorithm require.NoError(t, json.Unmarshal([]byte(strconv.Quote("PS512")), &dst), `UnmarshalJSON is successful`) require.Equal(t, jwa.PS512(), dst, `unmarshaled value should be equal to constant`) }) t.Run(`stringification for PS512`, func(t *testing.T) { t.Parallel() require.Equal(t, "PS512", jwa.PS512().String(), `stringified value matches`) }) t.Run(`Lookup the object`, func(t *testing.T) { t.Parallel() v, ok := jwa.LookupSignatureAlgorithm("RS256") require.True(t, ok, `Lookup should succeed`) require.Equal(t, jwa.RS256(), v, `Lookup value should be equal to constant`) }) t.Run(`Unmarshal the string RS256`, func(t *testing.T) { t.Parallel() var dst jwa.SignatureAlgorithm require.NoError(t, json.Unmarshal([]byte(strconv.Quote("RS256")), &dst), `UnmarshalJSON is successful`) require.Equal(t, jwa.RS256(), dst, `unmarshaled value should be equal to constant`) }) t.Run(`stringification for RS256`, func(t *testing.T) { t.Parallel() require.Equal(t, "RS256", jwa.RS256().String(), `stringified value matches`) }) t.Run(`Lookup the object`, func(t *testing.T) { t.Parallel() v, ok := jwa.LookupSignatureAlgorithm("RS384") require.True(t, ok, `Lookup should succeed`) require.Equal(t, jwa.RS384(), v, `Lookup value should be equal to constant`) }) t.Run(`Unmarshal the string RS384`, func(t *testing.T) { t.Parallel() var dst jwa.SignatureAlgorithm require.NoError(t, json.Unmarshal([]byte(strconv.Quote("RS384")), &dst), `UnmarshalJSON is successful`) require.Equal(t, jwa.RS384(), dst, `unmarshaled value should be equal to constant`) }) t.Run(`stringification for RS384`, func(t *testing.T) { t.Parallel() require.Equal(t, "RS384", jwa.RS384().String(), `stringified value matches`) }) t.Run(`Lookup the object`, func(t *testing.T) { t.Parallel() v, ok := jwa.LookupSignatureAlgorithm("RS512") require.True(t, ok, `Lookup should succeed`) require.Equal(t, jwa.RS512(), v, `Lookup value should be equal to constant`) }) t.Run(`Unmarshal the string RS512`, func(t *testing.T) { t.Parallel() var dst jwa.SignatureAlgorithm require.NoError(t, json.Unmarshal([]byte(strconv.Quote("RS512")), &dst), `UnmarshalJSON is successful`) require.Equal(t, jwa.RS512(), dst, `unmarshaled value should be equal to constant`) }) t.Run(`stringification for RS512`, func(t *testing.T) { t.Parallel() require.Equal(t, "RS512", jwa.RS512().String(), `stringified value matches`) }) t.Run(`Unmarshal should fail for invalid value (totally made up) string value`, func(t *testing.T) { t.Parallel() var dst jwa.SignatureAlgorithm require.Error(t, json.Unmarshal([]byte(`totallyInvalidValue`), &dst), `Unmarshal should fail`) }) t.Run(`check symmetric values`, func(t *testing.T) { t.Parallel() t.Run(`ES256`, func(t *testing.T) { require.False(t, jwa.ES256().IsSymmetric(), `jwa.ES256 returns expected value`) }) t.Run(`ES256K`, func(t *testing.T) { require.False(t, jwa.ES256K().IsSymmetric(), `jwa.ES256K returns expected value`) }) t.Run(`ES384`, func(t *testing.T) { require.False(t, jwa.ES384().IsSymmetric(), `jwa.ES384 returns expected value`) }) t.Run(`ES512`, func(t *testing.T) { require.False(t, jwa.ES512().IsSymmetric(), `jwa.ES512 returns expected value`) }) t.Run(`EdDSA`, func(t *testing.T) { require.False(t, jwa.EdDSA().IsSymmetric(), `jwa.EdDSA returns expected value`) }) t.Run(`HS256`, func(t *testing.T) { require.True(t, jwa.HS256().IsSymmetric(), `jwa.HS256 returns expected value`) }) t.Run(`HS384`, func(t *testing.T) { require.True(t, jwa.HS384().IsSymmetric(), `jwa.HS384 returns expected value`) }) t.Run(`HS512`, func(t *testing.T) { require.True(t, jwa.HS512().IsSymmetric(), `jwa.HS512 returns expected value`) }) t.Run(`NoSignature`, func(t *testing.T) { require.False(t, jwa.NoSignature().IsSymmetric(), `jwa.NoSignature returns expected value`) }) t.Run(`PS256`, func(t *testing.T) { require.False(t, jwa.PS256().IsSymmetric(), `jwa.PS256 returns expected value`) }) t.Run(`PS384`, func(t *testing.T) { require.False(t, jwa.PS384().IsSymmetric(), `jwa.PS384 returns expected value`) }) t.Run(`PS512`, func(t *testing.T) { require.False(t, jwa.PS512().IsSymmetric(), `jwa.PS512 returns expected value`) }) t.Run(`RS256`, func(t *testing.T) { require.False(t, jwa.RS256().IsSymmetric(), `jwa.RS256 returns expected value`) }) t.Run(`RS384`, func(t *testing.T) { require.False(t, jwa.RS384().IsSymmetric(), `jwa.RS384 returns expected value`) }) t.Run(`RS512`, func(t *testing.T) { require.False(t, jwa.RS512().IsSymmetric(), `jwa.RS512 returns expected value`) }) }) t.Run(`check list of elements`, func(t *testing.T) { t.Parallel() var expected = map[jwa.SignatureAlgorithm]struct{}{ jwa.ES256(): {}, jwa.ES256K(): {}, jwa.ES384(): {}, jwa.ES512(): {}, jwa.EdDSA(): {}, jwa.HS256(): {}, jwa.HS384(): {}, jwa.HS512(): {}, jwa.NoSignature(): {}, jwa.PS256(): {}, jwa.PS384(): {}, jwa.PS512(): {}, jwa.RS256(): {}, jwa.RS384(): {}, jwa.RS512(): {}, } for _, v := range jwa.SignatureAlgorithms() { _, ok := expected[v] require.True(t, ok, `%q should be in the list for SignatureAlgorithm`, v) delete(expected, v) } require.Len(t, expected, 0) }) } // Note: this test can NOT be run in parallel as it uses options with global effect. func TestSignatureAlgorithmCustomAlgorithm(t *testing.T) { // These subtests can NOT be run in parallel as options with global effect change. const customAlgorithmValue = `custom-algorithm` for _, symmetric := range []bool{true, false} { customAlgorithm := jwa.NewSignatureAlgorithm(customAlgorithmValue, jwa.WithIsSymmetric(symmetric)) // Unregister the custom algorithm, in case tests fail. t.Cleanup(func() { jwa.UnregisterSignatureAlgorithm(customAlgorithm) }) t.Run(`with custom algorithm registered`, func(t *testing.T) { jwa.RegisterSignatureAlgorithm(customAlgorithm) t.Run(`Lookup the object`, func(t *testing.T) { t.Parallel() v, ok := jwa.LookupSignatureAlgorithm(customAlgorithmValue) require.True(t, ok, `Lookup should succeed`) require.Equal(t, customAlgorithm, v, `Lookup value should be equal to constant`) }) t.Run(`Unmarshal custom algorithm`, func(t *testing.T) { t.Parallel() var dst jwa.SignatureAlgorithm require.NoError(t, json.Unmarshal([]byte(strconv.Quote(customAlgorithmValue)), &dst), `Unmarshal is successful`) require.Equal(t, customAlgorithm, dst, `accepted value should be equal to variable`) }) t.Run(`check symmetric`, func(t *testing.T) { t.Parallel() require.Equal(t, symmetric, customAlgorithm.IsSymmetric(), `custom algorithm's symmetric attribute should match`) }) }) t.Run(`with custom algorithm deregistered`, func(t *testing.T) { jwa.UnregisterSignatureAlgorithm(customAlgorithm) t.Run(`Lookup the object`, func(t *testing.T) { t.Parallel() _, ok := jwa.LookupSignatureAlgorithm(customAlgorithmValue) require.False(t, ok, `Lookup should fail`) }) t.Run(`Unmarshal custom algorithm`, func(t *testing.T) { t.Parallel() var dst jwa.SignatureAlgorithm require.Error(t, json.Unmarshal([]byte(customAlgorithmValue), &dst), `Unmarshal should fail`) }) }) } } golang-github-lestrrat-go-jwx-3.0.13/jwe/000077500000000000000000000000001515060566400201715ustar00rootroot00000000000000golang-github-lestrrat-go-jwx-3.0.13/jwe/BUILD.bazel000066400000000000000000000031161515060566400220500ustar00rootroot00000000000000load("@rules_go//go:def.bzl", "go_library", "go_test") go_library( name = "jwe", srcs = [ "compress.go", "decrypt.go", "encrypt.go", "errors.go", "filter.go", "headers.go", "headers_gen.go", "interface.go", "io.go", "jwe.go", "key_provider.go", "message.go", "options.go", "options_gen.go", ], importpath = "github.com/lestrrat-go/jwx/v3/jwe", visibility = ["//visibility:public"], deps = [ "//cert", "//internal/base64", "//transform", "//internal/json", "//internal/tokens", "//internal/keyconv", "//internal/pool", "//jwa", "//jwe/internal/aescbc", "//jwe/internal/cipher", "//jwe/internal/content_crypt", "//jwe/internal/keygen", "//jwe/jwebb", "//jwk", "@com_github_lestrrat_go_blackmagic//:blackmagic", "@com_github_lestrrat_go_option_v2//:option", "@org_golang_x_crypto//pbkdf2", ], ) go_test( name = "jwe_test", srcs = [ "filter_test.go", "gh402_test.go", "headers_test.go", "jwe_test.go", "message_test.go", "options_gen_test.go", "speed_test.go", ], embed = [":jwe"], deps = [ "//cert", "//internal/json", "//internal/jwxtest", "//jwa", "//jwk", "@com_github_stretchr_testify//require", ], ) alias( name = "go_default_library", actual = ":jwe", visibility = ["//visibility:public"], ) golang-github-lestrrat-go-jwx-3.0.13/jwe/README.md000066400000000000000000000073261515060566400214600ustar00rootroot00000000000000# JWE [![Go Reference](https://pkg.go.dev/badge/github.com/lestrrat-go/jwx/v3/jwe.svg)](https://pkg.go.dev/github.com/lestrrat-go/jwx/v3/jwe) Package jwe implements JWE as described in [RFC7516](https://tools.ietf.org/html/rfc7516) * Encrypt and Decrypt arbitrary data * Content compression and decompression * Add arbitrary fields in the JWE header object How-to style documentation can be found in the [docs directory](../docs). Examples are located in the examples directory ([jwe_example_test.go](../examples/jwe_example_test.go)) Supported key encryption algorithm: | Algorithm | Supported? | Constant in [jwa](../jwa) | |:-----------------------------------------|:-----------|:-------------------------| | RSA-PKCS1v1.5 | YES | jwa.RSA1_5 | | RSA-OAEP-SHA1 | YES | jwa.RSA_OAEP | | RSA-OAEP-SHA256 | YES | jwa.RSA_OAEP_256 | | AES key wrap (128) | YES | jwa.A128KW | | AES key wrap (192) | YES | jwa.A192KW | | AES key wrap (256) | YES | jwa.A256KW | | Direct encryption | YES (1) | jwa.DIRECT | | ECDH-ES | YES (1) | jwa.ECDH_ES | | ECDH-ES + AES key wrap (128) | YES | jwa.ECDH_ES_A128KW | | ECDH-ES + AES key wrap (192) | YES | jwa.ECDH_ES_A192KW | | ECDH-ES + AES key wrap (256) | YES | jwa.ECDH_ES_A256KW | | AES-GCM key wrap (128) | YES | jwa.A128GCMKW | | AES-GCM key wrap (192) | YES | jwa.A192GCMKW | | AES-GCM key wrap (256) | YES | jwa.A256GCMKW | | PBES2 + HMAC-SHA256 + AES key wrap (128) | YES | jwa.PBES2_HS256_A128KW | | PBES2 + HMAC-SHA384 + AES key wrap (192) | YES | jwa.PBES2_HS384_A192KW | | PBES2 + HMAC-SHA512 + AES key wrap (256) | YES | jwa.PBES2_HS512_A256KW | * Note 1: Single-recipient only Supported content encryption algorithm: | Algorithm | Supported? | Constant in [jwa](../jwa) | |:----------------------------|:-----------|:--------------------------| | AES-CBC + HMAC-SHA256 (128) | YES | jwa.A128CBC_HS256 | | AES-CBC + HMAC-SHA384 (192) | YES | jwa.A192CBC_HS384 | | AES-CBC + HMAC-SHA512 (256) | YES | jwa.A256CBC_HS512 | | AES-GCM (128) | YES | jwa.A128GCM | | AES-GCM (192) | YES | jwa.A192GCM | | AES-GCM (256) | YES | jwa.A256GCM | # SYNOPSIS ## Encrypt data ```go func ExampleEncrypt() { privkey, err := rsa.GenerateKey(rand.Reader, 2048) if err != nil { log.Printf("failed to generate private key: %s", err) return } payload := []byte("Lorem Ipsum") encrypted, err := jwe.Encrypt(payload, jwe.WithKey(jwa.RSA1_5, &privkey.PublicKey), jwe.WithContentEncryption(jwa.A128CBC_HS256)) if err != nil { log.Printf("failed to encrypt payload: %s", err) return } _ = encrypted // OUTPUT: } ``` ## Decrypt data ```go func ExampleDecrypt() { privkey, encrypted, err := exampleGenPayload() if err != nil { log.Printf("failed to generate encrypted payload: %s", err) return } decrypted, err := jwe.Decrypt(encrypted, jwe.WithKey(jwa.RSA1_5, privkey)) if err != nil { log.Printf("failed to decrypt: %s", err) return } if string(decrypted) != "Lorem Ipsum" { log.Printf("WHAT?!") return } // OUTPUT: } ``` golang-github-lestrrat-go-jwx-3.0.13/jwe/compress.go000066400000000000000000000026131515060566400223550ustar00rootroot00000000000000package jwe import ( "bytes" "compress/flate" "fmt" "io" "github.com/lestrrat-go/jwx/v3/internal/pool" ) func uncompress(src []byte, maxBufferSize int64) ([]byte, error) { var dst bytes.Buffer r := flate.NewReader(bytes.NewReader(src)) defer r.Close() var buf [16384]byte var sofar int64 for { n, readErr := r.Read(buf[:]) sofar += int64(n) if sofar > maxBufferSize { return nil, fmt.Errorf(`compressed payload exceeds maximum allowed size`) } if readErr != nil { // if we have a read error, and it's not EOF, then we need to stop if readErr != io.EOF { return nil, fmt.Errorf(`failed to read inflated data: %w`, readErr) } } if _, err := dst.Write(buf[:n]); err != nil { return nil, fmt.Errorf(`failed to write inflated data: %w`, err) } if readErr != nil { // if it got here, then readErr == io.EOF, we're done return dst.Bytes(), nil } } } func compress(plaintext []byte) ([]byte, error) { buf := pool.BytesBuffer().Get() defer pool.BytesBuffer().Put(buf) w, _ := flate.NewWriter(buf, 1) in := plaintext for len(in) > 0 { n, err := w.Write(in) if err != nil { return nil, fmt.Errorf(`failed to write to compression writer: %w`, err) } in = in[n:] } if err := w.Close(); err != nil { return nil, fmt.Errorf(`failed to close compression writer: %w`, err) } ret := make([]byte, buf.Len()) copy(ret, buf.Bytes()) return ret, nil } golang-github-lestrrat-go-jwx-3.0.13/jwe/decrypt.go000066400000000000000000000141431515060566400221750ustar00rootroot00000000000000package jwe import ( "fmt" "github.com/lestrrat-go/jwx/v3/internal/tokens" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/lestrrat-go/jwx/v3/jwe/internal/content_crypt" "github.com/lestrrat-go/jwx/v3/jwe/jwebb" ) // decrypter is responsible for taking various components to decrypt a message. // its operation is not concurrency safe. You must provide locking yourself // //nolint:govet type decrypter struct { aad []byte apu []byte apv []byte cek *[]byte computedAad []byte iv []byte keyiv []byte keysalt []byte keytag []byte tag []byte privkey any pubkey any ctalg jwa.ContentEncryptionAlgorithm keyalg jwa.KeyEncryptionAlgorithm cipher content_crypt.Cipher keycount int } // newDecrypter Creates a new Decrypter instance. You must supply the // rest of parameters via their respective setter methods before // calling Decrypt(). // // privkey must be a private key in its "raw" format (i.e. something like // *rsa.PrivateKey, instead of jwk.Key) // // You should consider this object immutable once you assign values to it. func newDecrypter(keyalg jwa.KeyEncryptionAlgorithm, ctalg jwa.ContentEncryptionAlgorithm, privkey any) *decrypter { return &decrypter{ ctalg: ctalg, keyalg: keyalg, privkey: privkey, } } func (d *decrypter) AgreementPartyUInfo(apu []byte) *decrypter { d.apu = apu return d } func (d *decrypter) AgreementPartyVInfo(apv []byte) *decrypter { d.apv = apv return d } func (d *decrypter) AuthenticatedData(aad []byte) *decrypter { d.aad = aad return d } func (d *decrypter) ComputedAuthenticatedData(aad []byte) *decrypter { d.computedAad = aad return d } func (d *decrypter) ContentEncryptionAlgorithm(ctalg jwa.ContentEncryptionAlgorithm) *decrypter { d.ctalg = ctalg return d } func (d *decrypter) InitializationVector(iv []byte) *decrypter { d.iv = iv return d } func (d *decrypter) KeyCount(keycount int) *decrypter { d.keycount = keycount return d } func (d *decrypter) KeyInitializationVector(keyiv []byte) *decrypter { d.keyiv = keyiv return d } func (d *decrypter) KeySalt(keysalt []byte) *decrypter { d.keysalt = keysalt return d } func (d *decrypter) KeyTag(keytag []byte) *decrypter { d.keytag = keytag return d } // PublicKey sets the public key to be used in decoding EC based encryptions. // The key must be in its "raw" format (i.e. *ecdsa.PublicKey, instead of jwk.Key) func (d *decrypter) PublicKey(pubkey any) *decrypter { d.pubkey = pubkey return d } func (d *decrypter) Tag(tag []byte) *decrypter { d.tag = tag return d } func (d *decrypter) CEK(ptr *[]byte) *decrypter { d.cek = ptr return d } func (d *decrypter) ContentCipher() (content_crypt.Cipher, error) { if d.cipher == nil { cipher, err := jwebb.CreateContentCipher(d.ctalg.String()) if err != nil { return nil, err } d.cipher = cipher } return d.cipher, nil } 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 } cipher, ciphererr := d.ContentCipher() if ciphererr != nil { err = fmt.Errorf(`failed to fetch content crypt cipher: %w`, ciphererr) return } computedAad := d.computedAad if d.aad != nil { computedAad = append(append(computedAad, tokens.Period), d.aad...) } plaintext, err = cipher.Decrypt(cek, d.iv, ciphertext, d.tag, computedAad) if err != nil { err = fmt.Errorf(`failed to decrypt payload: %w`, err) return } if d.cek != nil { *d.cek = cek } return plaintext, nil } 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 jwebb.IsDirect(d.keyalg.String()) { cek, ok := d.privkey.([]byte) if !ok { return nil, fmt.Errorf("decrypt key: []byte is required as the key for %s (got %T)", d.keyalg, d.privkey) } return jwebb.KeyDecryptDirect(recipientKey, recipientKey, d.keyalg.String(), cek) } if jwebb.IsPBES2(d.keyalg.String()) { password, ok := d.privkey.([]byte) if !ok { return nil, fmt.Errorf("decrypt key: []byte is required as the password for %s (got %T)", d.keyalg, d.privkey) } salt := []byte(d.keyalg.String()) salt = append(salt, byte(0)) salt = append(salt, d.keysalt...) return jwebb.KeyDecryptPBES2(recipientKey, recipientKey, d.keyalg.String(), password, salt, d.keycount) } if jwebb.IsAESGCMKW(d.keyalg.String()) { sharedkey, ok := d.privkey.([]byte) if !ok { return nil, fmt.Errorf("decrypt key: []byte is required as the key for %s (got %T)", d.keyalg, d.privkey) } return jwebb.KeyDecryptAESGCMKW(recipientKey, recipientKey, d.keyalg.String(), sharedkey, d.keyiv, d.keytag) } if jwebb.IsECDHES(d.keyalg.String()) { alg, keysize, keywrap, err := jwebb.KeyEncryptionECDHESKeySize(d.keyalg.String(), d.ctalg.String()) if err != nil { return nil, fmt.Errorf(`failed to determine ECDH-ES key size: %w`, err) } if !keywrap { return jwebb.KeyDecryptECDHES(recipientKey, cek, alg, d.apu, d.apv, d.privkey, d.pubkey, keysize) } return jwebb.KeyDecryptECDHESKeyWrap(recipientKey, recipientKey, d.keyalg.String(), d.apu, d.apv, d.privkey, d.pubkey, keysize) } if jwebb.IsRSA15(d.keyalg.String()) { cipher, err := d.ContentCipher() if err != nil { return nil, fmt.Errorf(`failed to fetch content crypt cipher: %w`, err) } keysize := cipher.KeySize() / 2 return jwebb.KeyDecryptRSA15(recipientKey, recipientKey, d.privkey, keysize) } if jwebb.IsRSAOAEP(d.keyalg.String()) { return jwebb.KeyDecryptRSAOAEP(recipientKey, recipientKey, d.keyalg.String(), d.privkey) } if jwebb.IsAESKW(d.keyalg.String()) { sharedkey, ok := d.privkey.([]byte) if !ok { return nil, fmt.Errorf("[]byte is required as the key to decrypt %s", d.keyalg.String()) } return jwebb.KeyDecryptAESKW(recipientKey, recipientKey, d.keyalg.String(), sharedkey) } return nil, fmt.Errorf(`unsupported algorithm for key decryption (%s)`, d.keyalg) } golang-github-lestrrat-go-jwx-3.0.13/jwe/encrypt.go000066400000000000000000000130411515060566400222030ustar00rootroot00000000000000package jwe import ( "crypto/ecdh" "crypto/ecdsa" "crypto/rsa" "fmt" "github.com/lestrrat-go/jwx/v3/internal/keyconv" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/lestrrat-go/jwx/v3/jwe/internal/content_crypt" "github.com/lestrrat-go/jwx/v3/jwe/internal/keygen" "github.com/lestrrat-go/jwx/v3/jwe/jwebb" ) // encrypter is responsible for taking various components to encrypt a key. // its operation is not concurrency safe. You must provide locking yourself // //nolint:govet type encrypter struct { apu []byte apv []byte ctalg jwa.ContentEncryptionAlgorithm keyalg jwa.KeyEncryptionAlgorithm pubkey any rawKey any cipher content_crypt.Cipher } // newEncrypter creates a new Encrypter instance with all required parameters. // The content cipher is built internally during construction. // // pubkey must be a public key in its "raw" format (i.e. something like // *rsa.PublicKey, instead of jwk.Key) // // You should consider this object immutable once created. func newEncrypter(keyalg jwa.KeyEncryptionAlgorithm, ctalg jwa.ContentEncryptionAlgorithm, pubkey any, rawKey any, apu, apv []byte) (*encrypter, error) { cipher, err := jwebb.CreateContentCipher(ctalg.String()) if err != nil { return nil, fmt.Errorf(`failed to create content cipher: %w`, err) } return &encrypter{ apu: apu, apv: apv, ctalg: ctalg, keyalg: keyalg, pubkey: pubkey, rawKey: rawKey, cipher: cipher, }, nil } func (e *encrypter) EncryptKey(cek []byte) (keygen.ByteSource, error) { if ke, ok := e.pubkey.(KeyEncrypter); ok { encrypted, err := ke.EncryptKey(cek) if err != nil { return nil, err } return keygen.ByteKey(encrypted), nil } if jwebb.IsDirect(e.keyalg.String()) { sharedkey, ok := e.rawKey.([]byte) if !ok { return nil, fmt.Errorf("encrypt key: []byte is required as the key for %s (got %T)", e.keyalg, e.rawKey) } return jwebb.KeyEncryptDirect(cek, e.keyalg.String(), sharedkey) } if jwebb.IsPBES2(e.keyalg.String()) { password, ok := e.rawKey.([]byte) if !ok { return nil, fmt.Errorf("encrypt key: []byte is required as the password for %s (got %T)", e.keyalg, e.rawKey) } return jwebb.KeyEncryptPBES2(cek, e.keyalg.String(), password) } if jwebb.IsAESGCMKW(e.keyalg.String()) { sharedkey, ok := e.rawKey.([]byte) if !ok { return nil, fmt.Errorf("encrypt key: []byte is required as the key for %s (got %T)", e.keyalg, e.rawKey) } return jwebb.KeyEncryptAESGCMKW(cek, e.keyalg.String(), sharedkey) } if jwebb.IsECDHES(e.keyalg.String()) { _, keysize, keywrap, err := jwebb.KeyEncryptionECDHESKeySize(e.keyalg.String(), e.ctalg.String()) if err != nil { return nil, fmt.Errorf(`failed to determine ECDH-ES key size: %w`, err) } // Use rawKey for ECDH-ES operations - it should contain the actual key material keyToUse := e.rawKey if keyToUse == nil { keyToUse = e.pubkey } switch key := keyToUse.(type) { case *ecdsa.PublicKey: // no op case ecdsa.PublicKey: keyToUse = &key case *ecdsa.PrivateKey: keyToUse = &key.PublicKey case ecdsa.PrivateKey: keyToUse = &key.PublicKey case *ecdh.PublicKey: // no op case ecdh.PublicKey: keyToUse = &key case ecdh.PrivateKey: keyToUse = key.PublicKey() case *ecdh.PrivateKey: keyToUse = key.PublicKey() } // Determine key type and call appropriate function switch key := keyToUse.(type) { case *ecdh.PublicKey: if key.Curve() == ecdh.X25519() { if !keywrap { return jwebb.KeyEncryptECDHESX25519(cek, e.keyalg.String(), e.apu, e.apv, key, keysize, e.ctalg.String()) } return jwebb.KeyEncryptECDHESKeyWrapX25519(cek, e.keyalg.String(), e.apu, e.apv, key, keysize, e.ctalg.String()) } var ecdsaKey *ecdsa.PublicKey if err := keyconv.ECDHToECDSA(&ecdsaKey, key); err != nil { return nil, fmt.Errorf(`encrypt: failed to convert ECDH public key to ECDSA: %w`, err) } keyToUse = ecdsaKey } switch key := keyToUse.(type) { case *ecdsa.PublicKey: if !keywrap { return jwebb.KeyEncryptECDHESECDSA(cek, e.keyalg.String(), e.apu, e.apv, key, keysize, e.ctalg.String()) } return jwebb.KeyEncryptECDHESKeyWrapECDSA(cek, e.keyalg.String(), e.apu, e.apv, key, keysize, e.ctalg.String()) default: return nil, fmt.Errorf(`encrypt: unsupported key type for ECDH-ES: %T`, keyToUse) } } if jwebb.IsRSA15(e.keyalg.String()) { keyToUse := e.rawKey if keyToUse == nil { keyToUse = e.pubkey } // Handle rsa.PublicKey by value - convert to pointer if pk, ok := keyToUse.(rsa.PublicKey); ok { keyToUse = &pk } var pubkey *rsa.PublicKey if err := keyconv.RSAPublicKey(&pubkey, keyToUse); err != nil { return nil, fmt.Errorf(`encrypt: failed to convert to RSA public key: %w`, err) } return jwebb.KeyEncryptRSA15(cek, e.keyalg.String(), pubkey) } if jwebb.IsRSAOAEP(e.keyalg.String()) { keyToUse := e.rawKey if keyToUse == nil { keyToUse = e.pubkey } // Handle rsa.PublicKey by value - convert to pointer if pk, ok := keyToUse.(rsa.PublicKey); ok { keyToUse = &pk } var pubkey *rsa.PublicKey if err := keyconv.RSAPublicKey(&pubkey, keyToUse); err != nil { return nil, fmt.Errorf(`encrypt: failed to convert to RSA public key: %w`, err) } return jwebb.KeyEncryptRSAOAEP(cek, e.keyalg.String(), pubkey) } if jwebb.IsAESKW(e.keyalg.String()) { sharedkey, ok := e.rawKey.([]byte) if !ok { return nil, fmt.Errorf("[]byte is required as the key to encrypt %s", e.keyalg.String()) } return jwebb.KeyEncryptAESKW(cek, e.keyalg.String(), sharedkey) } return nil, fmt.Errorf(`unsupported algorithm for key encryption (%s)`, e.keyalg) } golang-github-lestrrat-go-jwx-3.0.13/jwe/errors.go000066400000000000000000000042631515060566400220410ustar00rootroot00000000000000package jwe import "errors" type encryptError struct { error } func (e encryptError) Unwrap() error { return e.error } func (encryptError) Is(err error) bool { _, ok := err.(encryptError) return ok } var errDefaultEncryptError = encryptError{errors.New(`encrypt error`)} // EncryptError returns an error that can be passed to `errors.Is` to check if the error is an error returned by `jwe.Encrypt`. func EncryptError() error { return errDefaultEncryptError } type decryptError struct { error } func (e decryptError) Unwrap() error { return e.error } func (decryptError) Is(err error) bool { _, ok := err.(decryptError) return ok } var errDefaultDecryptError = decryptError{errors.New(`decrypt error`)} // DecryptError returns an error that can be passed to `errors.Is` to check if the error is an error returned by `jwe.Decrypt`. func DecryptError() error { return errDefaultDecryptError } type recipientError struct { error } func (e recipientError) Unwrap() error { return e.error } func (recipientError) Is(err error) bool { _, ok := err.(recipientError) return ok } var errDefaultRecipientError = recipientError{errors.New(`recipient error`)} // RecipientError returns an error that can be passed to `errors.Is` to check if the error is // an error that occurred while attempting to decrypt a JWE message for a particular recipient. // // For example, if the JWE message failed to parse during `jwe.Decrypt`, it will be a // `jwe.DecryptError`, but NOT `jwe.RecipientError`. However, if the JWE message could not // be decrypted for any of the recipients, then it will be a `jwe.RecipientError` // (actually, it will be _multiple_ `jwe.RecipientError` errors, one for each recipient) func RecipientError() error { return errDefaultRecipientError } type parseError struct { error } func (e parseError) Unwrap() error { return e.error } func (parseError) Is(err error) bool { _, ok := err.(parseError) return ok } var errDefaultParseError = parseError{errors.New(`parse error`)} // ParseError returns an error that can be passed to `errors.Is` to check if the error // is an error returned by `jwe.Parse` and related functions. func ParseError() error { return errDefaultParseError } golang-github-lestrrat-go-jwx-3.0.13/jwe/filter.go000066400000000000000000000024401515060566400220050ustar00rootroot00000000000000package jwe import ( "github.com/lestrrat-go/jwx/v3/transform" ) // HeaderFilter is an interface that allows users to filter JWE header fields. // It provides two methods: Filter and Reject; Filter returns a new header with only // the fields that match the filter criteria, while Reject returns a new header with // only the fields that DO NOT match the filter. // // EXPERIMENTAL: This API is experimental and its interface and behavior is // subject to change in future releases. This API is not subject to semver // compatibility guarantees. type HeaderFilter interface { Filter(header Headers) (Headers, error) Reject(header Headers) (Headers, error) } // StandardHeadersFilter returns a HeaderFilter that filters out standard JWE header fields. // // You can use this filter to create headers that either only have standard fields // or only custom fields. // // If you need to configure the filter more precisely, consider // using the HeaderNameFilter directly. func StandardHeadersFilter() HeaderFilter { return stdHeadersFilter } var stdHeadersFilter = NewHeaderNameFilter(stdHeaderNames...) // NewHeaderNameFilter creates a new HeaderNameFilter with the specified field names. func NewHeaderNameFilter(names ...string) HeaderFilter { return transform.NewNameBasedFilter[Headers](names...) } golang-github-lestrrat-go-jwx-3.0.13/jwe/filter_test.go000066400000000000000000000140451515060566400230500ustar00rootroot00000000000000package jwe_test import ( "sync" "testing" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/lestrrat-go/jwx/v3/jwe" "github.com/stretchr/testify/require" ) func TestHeaderNameFilter(t *testing.T) { t.Run("Basic functionality", func(t *testing.T) { // Create a filter that includes specific fields fn := jwe.NewHeaderNameFilter("custom1", "custom3") // Create a header with standard and custom fields headers := jwe.NewHeaders() // Headers in JWE might need special handling to be recognized by Keys() and Has() // so we'll primarily test with custom fields that are stored in privateParams headers.Set("custom1", "value1") headers.Set("custom2", "value2") headers.Set("custom3", "value3") t.Run("Filter specific fields", func(t *testing.T) { // Filter should return a header with only specified fields filtered, err := fn.Filter(headers) require.NoError(t, err, "fn.Filter should succeed") // Debug info t.Logf("Original headers keys: %v", headers.Keys()) t.Logf("Filtered headers keys: %v", filtered.Keys()) // Verify included fields are present require.True(t, filtered.Has("custom1"), "filtered header should have custom1 field") require.True(t, filtered.Has("custom3"), "filtered header should have custom3 field") // Verify excluded fields are not present require.False(t, filtered.Has("custom2"), "filtered header should not have custom2 field") }) t.Run("Reject specific fields", func(t *testing.T) { // Reject should return a header without specified fields rejected, err := fn.Reject(headers) require.NoError(t, err, "fn.Reject should succeed") // Verify included fields are not present require.False(t, rejected.Has("custom1"), "rejected header should not have custom1 field") require.False(t, rejected.Has("custom3"), "rejected header should not have custom3 field") // Verify excluded fields are present require.True(t, rejected.Has("custom2"), "rejected header should have custom2 field") }) }) t.Run("Empty filter", func(t *testing.T) { // Create an empty filter (no fields) fn := jwe.NewHeaderNameFilter() // Create a header with some fields headers := jwe.NewHeaders() headers.Set(jwe.AlgorithmKey, jwa.RSA_OAEP_256) headers.Set("custom", "value") // Filter with empty HeaderNameFilter should result in an empty header filtered, err := fn.Filter(headers) require.NoError(t, err, "fn.Filter should succeed") require.Empty(t, filtered.Keys(), "filtered header should have no fields") // Reject with empty HeaderNameFilter should result in a copy of the original header rejected, err := fn.Reject(headers) require.NoError(t, err, "fn.Reject should succeed") // Check that rejected header has the same keys as original originalKeys := headers.Keys() rejectedKeys := rejected.Keys() require.ElementsMatch(t, originalKeys, rejectedKeys, "rejected header should have the same fields as the original") }) t.Run("Concurrency safety", func(t *testing.T) { // This is more of a logical test than an actual concurrency test // but it ensures the filter is being used correctly fn := jwe.NewHeaderNameFilter("custom1") // Create a header headers := jwe.NewHeaders() headers.Set("custom1", "value1") headers.Set("custom2", "value2") // Run filter and reject operations concurrently var wg sync.WaitGroup const iterations = 10 const numGoroutines = 2 wg.Add(iterations * numGoroutines) for range iterations { go func() { defer wg.Done() filtered, err := fn.Filter(headers) require.NoError(t, err, "fn.Filter should succeed") require.True(t, filtered.Has("custom1"), "filtered header should have custom1 field") require.False(t, filtered.Has("custom2"), "filtered header should not have custom2 field") }() go func() { defer wg.Done() rejected, err := fn.Reject(headers) require.NoError(t, err, "fn.Reject should succeed") require.False(t, rejected.Has("custom1"), "rejected header should not have custom1 field") require.True(t, rejected.Has("custom2"), "rejected header should have custom2 field") }() } wg.Wait() }) } func TestStandardHeadersFilter(t *testing.T) { t.Run("Filter standard headers", func(t *testing.T) { // Create a header with standard and custom fields headers := jwe.NewHeaders() // Standard headers may not be properly reflected in Keys() // so we focus on testing custom fields which are stored in privateParams headers.Set("custom1", "value1") headers.Set("custom2", "value2") stdFilter := jwe.StandardHeadersFilter() t.Run("Filter standard headers", func(t *testing.T) { // Filter should return a header with only standard fields filtered, err := stdFilter.Filter(headers) require.NoError(t, err, "filter.Filter should succeed") // Since our test only uses custom fields (non-standard), // the filtered result should be empty require.Empty(t, filtered.Keys(), "filtered header should have no fields") // Verify custom fields are not present require.False(t, filtered.Has("custom1"), "filtered header should not have custom1 field") require.False(t, filtered.Has("custom2"), "filtered header should not have custom2 field") }) t.Run("Reject standard headers", func(t *testing.T) { // Reject should return a header with only custom fields rejected, err := stdFilter.Reject(headers) require.NoError(t, err, "filter.Reject should succeed") // Verify custom fields are present (all original fields should be present) require.True(t, rejected.Has("custom1"), "rejected header should have custom1 field") require.True(t, rejected.Has("custom2"), "rejected header should have custom2 field") // Check that all original keys are present originalKeys := headers.Keys() rejectedKeys := rejected.Keys() require.ElementsMatch(t, originalKeys, rejectedKeys, "rejected should have all original fields") // Verify values are preserved var customValue string require.NoError(t, rejected.Get("custom1", &customValue), "rejected.Get should succeed") require.Equal(t, "value1", customValue, "value for custom1 field should be preserved") }) }) } golang-github-lestrrat-go-jwx-3.0.13/jwe/gh402_test.go000066400000000000000000000270621515060566400224120ustar00rootroot00000000000000package jwe_test import ( "context" "testing" "github.com/lestrrat-go/jwx/v3/internal/json" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/lestrrat-go/jwx/v3/jwe" "github.com/stretchr/testify/require" ) // Pin represents the structured clevis data which can be used to decrypt the jwe message type Pin struct { Pin string `json:"pin"` Tang *TangPin `json:"tang,omitempty"` Tpm2 *Tpm2Pin `json:"tpm2,omitempty"` Sss *SssPin `json:"sss,omitempty"` Yubikey *YubikeyPin `json:"yubikey,omitempty"` } type TangPin struct { Advertisement *json.RawMessage `json:"adv,omitempty"` URL string `json:"url"` } type Tpm2Pin struct { Hash string `json:"hash,omitempty"` Key string `json:"key,omitempty"` JwkPub string `json:"jwk_pub,omitempty"` JwkPriv string `json:"jwk_priv,omitempty"` PcrBank string `json:"pcr_bank,omitempty"` PcrIds string `json:"pcr_ids,omitempty"` } type SssPin struct { Jwe []string `json:"jwe"` Threshold int `json:"t"` Prime string `json:"p"` } type YubikeyPin struct { Type string `json:"type"` Challenge string `json:"chalelenge"` Slot int `json:"slot"` Kdf YubikeyKdf `json:"kdf"` } type YubikeyKdf struct { Type string `json:"type"` Hash string `json:"hash"` Iterations int `json:"iter"` Salt string `json:"salt"` } func TestGH402(t *testing.T) { key := []byte{195, 170, 42, 171, 98, 176, 98, 162, 57, 170, 62, 69, 175, 209, 200, 151, 81, 135, 63, 43, 93, 20, 16, 111, 13, 26, 138, 188, 15, 19, 26, 242} data := "eyJhbGciOiJkaXIiLCJjbGV2aXMiOnsicGluIjoic3NzIiwic3NzIjp7Imp3ZSI6WyJleUpoYkdjaU9pSkZRMFJJTFVWVElpd2lZMnhsZG1seklqcDdJbkJwYmlJNkluUmhibWNpTENKMFlXNW5JanA3SW1Ga2RpSTZleUpyWlhseklqcGJleUpoYkdjaU9pSkZRMDFTSWl3aVkzSjJJam9pVUMwMU1qRWlMQ0pyWlhsZmIzQnpJanBiSW1SbGNtbDJaVXRsZVNKZExDSnJkSGtpT2lKRlF5SXNJbmdpT2lKQlZqbEplVmszZDNCamNtMVpZMDkxZWsxTE9VSjJaMDFDZDNJd09FNXhURXBtUW5Ca1IzVXpNa1ZDWjJORmRITnpNRGswVEdweFdXSlFhVmhQTFhWTWMyeDVValZUVlRsdlNHbGhaRlI1VlhWb1UzUTJhbXQzSWl3aWVTSTZJa0ZIVkRseVpYcHlkV0pITFU1eFgxWkxPRUl3UXpGTE9WSTJNV05YU1U1UFNVaEZSMU5MYlZKclUxZEtjVEJRWHpkaFVHaDJlV3hhWDNaTFdHSk5TMGhrU1d0VFFrd3dSMWRRTmxaaVdqbExZVEJHVkZGSFpqTWlmU3g3SW1Gc1p5STZJa1ZUTlRFeUlpd2lZM0oySWpvaVVDMDFNakVpTENKclpYbGZiM0J6SWpwYkluWmxjbWxtZVNKZExDSnJkSGtpT2lKRlF5SXNJbmdpT2lKQlNIZFFhR2hPZDBac1ptc3RNR2d6VDNGS1JFVXpkekpVWDJGcExUbGlZWGhXY0VKa05TMWtNbXhIU0VseFgyTXhVSEZrTW1RMlYxSkRXbEZVYkc5cWQyUmhOblJUUkVndFUxTmhaVE5aTmxGVlZIVkVla1puSWl3aWVTSTZJa0ZrYVhkdVozaDVlamt4UVMxUFRGODFhekIwVEcwMlZtSTNSVkZxWVcxQ1NWUlhlVzVuUldSRVpuRlRSbm8xWlRkdVkycFdlR3BpWDFCSVMwbFNaRzFPT1hCa2RFMTRlRUpPYTFkV2RsOWZUbkZxVTI5SU5UUWlmVjE5TENKMWNtd2lPaUpvZEhSd09pOHZiRzlqWVd4b2IzTjBPak16TURJeEluMTlMQ0psYm1NaU9pSkJNalUyUjBOTklpd2laWEJySWpwN0ltTnlkaUk2SWxBdE5USXhJaXdpYTNSNUlqb2lSVU1pTENKNElqb2lRVWR1YjNrMFQyMTBhRWt3TjNKV1QxbFNTVGhxUTIxVGMxQjZXVVYwUkcwNWNqRkRaa0ptVmxCa2RscEdVVE0wY2taVmJFTTFRbGRrVlZweVMyOXNYMHh5UVhsdFlrY3pWVEZhY1VRMU9YSnBlVGQzV1hGWVV5SXNJbmtpT2lKQlNHaHNSVGx0T1hoR04xVkVVVXd4WjJSd2RGaE5Zbm94VUhNMloweDRObWx1ZGpodU4xVXpUamhpU0dSTlJIRldhalZKYVdGVGFGbDRZM1kyYzFSQ2VuUXpNMmxrUlRaZlYyUm1Oell3WVhkV1VVUkRXa1oxSW4wc0ltdHBaQ0k2SWpWQlNVaHJjblpDYzFOZmQzRjFMVGN5TUd0Q1dtMTBWblpxYkdsVGVWSmpUVXREVFU5dmJuWlFSVWtpZlEuLlR0SzJzWTE2bDJnSmx6c18uRFZqcGVkbGUxY29xZkFkLUZ3bnM5RmtRVkR0ak4zWU1NWjc4VjNLVmI3VWpFd2p1eTBXYjZ0eUxzTmZsVnJaVU85dDM1X2pfSlpobU0ycllfV1RyOHcuVkZMYjFvcTFqZzRsdnJpY3laaXF6USIsImV5SmhiR2NpT2lKRlEwUklMVVZUSWl3aVkyeGxkbWx6SWpwN0luQnBiaUk2SW5SaGJtY2lMQ0owWVc1bklqcDdJbUZrZGlJNmV5SnJaWGx6SWpwYmV5SmhiR2NpT2lKRlEwMVNJaXdpWTNKMklqb2lVQzAxTWpFaUxDSnJaWGxmYjNCeklqcGJJbVJsY21sMlpVdGxlU0pkTENKcmRIa2lPaUpGUXlJc0luZ2lPaUpCVUZWTVMxVllhMUp2TjJOdGFXOXFVRUV3ZFVZMGVIcFFXakJ0V21sQ1NFeFBhVWRyV0hkQ1pGTkVibVpNV1ZaTmJFeFZWR0UzTXkxNWJYSnVWME5mUlRZdE9DMWpUbnA0U1RaMWVtMTRhVFphTXpCUU1EaHhJaXdpZVNJNklrRlBPVTFWTVdGQmRWRnJNVEJrY1dGbVIwRlFYMlpKVGkwelRXTldNRkpGT0drNGFVdG5UbGhuWm14SlFtbDZNRGMxVmpCWVJIZEZNbkpSU2xka1VsRktlWGczT0hFeVJrTndkekZGTTFCMk1YRnZZMmhCZDJnaWZTeDdJbUZzWnlJNklrVlROVEV5SWl3aVkzSjJJam9pVUMwMU1qRWlMQ0pyWlhsZmIzQnpJanBiSW5abGNtbG1lU0pkTENKcmRIa2lPaUpGUXlJc0luZ2lPaUpCVUUxSFZYWTFjSFZUZWt0MU1HRjRZVFJRYldOeFRsQkxiMjR0TW1oYVVGSjJXbmRwV2xkc05sOVNNREJ2YlZsMldESXplRlJ4UldGUVdFMXphemxFYm1SdGNrUnpWbEZvTlRFMFEzTTBSbGszV0VaTlkyUkVJaXdpZVNJNklrRlZNa051VUV0RlJVeDBhSFZaYlZaeWJWZEJORFZLYjNocE1FRkhjM2w1T0daak5UTlJiMHA2UjA4NWNYVnRXVVpRTTFWbVoyWjZibkZsTnpWcE5uUXpkSFJPUW0xSWNGVlhTa1JuUkRkdGRGbEdVbXRzYzFjaWZWMTlMQ0oxY213aU9pSm9kSFJ3T2k4dmJHOWpZV3hvYjNOME9qUXhPRFl4SW4xOUxDSmxibU1pT2lKQk1qVTJSME5OSWl3aVpYQnJJanA3SW1OeWRpSTZJbEF0TlRJeElpd2lhM1I1SWpvaVJVTWlMQ0o0SWpvaVFWazJVMmxoUzFaRFMwUnFRMjUwTFdWNFJFZzJTa3RhYVdkaWRXOVpZVzV3Y0hWRVkxOXNOVXBxY1ROR05GWnpMV3hJYzFGbVYzQmFOVXRPWTNCUWFITmZObFpVUmt4ZlYwMVdja2xCZG5wWWVFeExZMmhKY0NJc0lua2lPaUpCU1RCVFZVSTFUMG80ZGtkR05YRndXRzVFZVRkd1pteFBNVjlsZW14WWQyTTNValpxY25RelJrSklPRTEwZG0xcFRHVkZNRzR0VEhZNFMyNWZMVWMzUlhJM1MwRklNSEoyWW5kMlVFZzBjbXhrU1VrM2RETmxJbjBzSW10cFpDSTZJa1JNT1ZaclRHcFVaVXBKTVhRd2ExUnNUbnBzVGxrM1RHcDFTWGxLZHpGUE1HTkdYMHN0YW5GdGFHOGlmUS4uSjJ4c2VjeTN3c2FGRjFfdi5uQzdPSzVrdXAwbkgxVGwwOFBRUE82QTllNjFYMXNGY1RFUVZjTjNkd0NWYTFMZ2p2STFfSWZqTU5TX2cweTI4WGM1ZThQZ2hFS01UQ0hkQk44UzFBdy5yV0s5Qi0yT2RtWUlIbmQzMzcxeHpRIiwiZXlKaGJHY2lPaUpGUTBSSUxVVlRJaXdpWTJ4bGRtbHpJanA3SW5CcGJpSTZJblJoYm1jaUxDSjBZVzVuSWpwN0ltRmtkaUk2ZXlKclpYbHpJanBiZXlKaGJHY2lPaUpGUTAxU0lpd2lZM0oySWpvaVVDMDFNakVpTENKclpYbGZiM0J6SWpwYkltUmxjbWwyWlV0bGVTSmRMQ0pyZEhraU9pSkZReUlzSW5naU9pSkJRbVZ1WVRaWVVUQkNTMmQwU1U5c05FMXZUakZEZFhKc2REVk1lVEZ6YUZNMFRFbHdkV05aUkVaWE9VNDVNRWgxY1RSUk4wcENPR3RNVDNabVdEUnBhSFJwTFdWQ1JXdGpVR3BCYzNWWU9HWm5kbVEyVEZSM0lpd2llU0k2SWtGUlRFWnVZMmhmZVRCaVdFNTRTRGt6UW10NWQwSmFhbXB6T1ROaFptOXFXWGhyV0d0dFFXZFRVbnA1ZGxaWGFrbHJRMEYwVWtoRVZuVmhlbkUxTWtSRFYxVnRlRU5YWm1OR05qWkdSaTB5Y1ZrellsTk9Na01pZlN4N0ltRnNaeUk2SWtWVE5URXlJaXdpWTNKMklqb2lVQzAxTWpFaUxDSnJaWGxmYjNCeklqcGJJblpsY21sbWVTSmRMQ0pyZEhraU9pSkZReUlzSW5naU9pSkJaR2hHZW05RE5FZG5WRk5CWlUxTFVYSlhjRWRCTWtndFEyeEVNVE52U1d4SVprczFTWEI2ZVZseWRuVm1Sbk5DZDJkYWJWYzRWR0ZXUkdSYVltbGlkM3BtTldSMk1rOHRkVkpmU2pSS2IyNDRNekJ0ZFVGa0lpd2llU0k2SWtGUlpFMVdka3RGWW5obWMwbHRiVFJtVHpZMlFsOHdVMnhPWDBaRmVEQkthVlpyVjFKQlFrRnBhRVExV1VKUWVVbEpjM1pGUjJoR1dqaFpOVXBWTXpoQ1JWVlJUWGhyVkZsT1pEVlpOR1UxVG1STVVVbDNObDhpZlYxOUxDSjFjbXdpT2lKb2RIUndPaTh2Ykc5allXeG9iM04wT2pNME1qVTVJbjE5TENKbGJtTWlPaUpCTWpVMlIwTk5JaXdpWlhCcklqcDdJbU55ZGlJNklsQXROVEl4SWl3aWEzUjVJam9pUlVNaUxDSjRJam9pUVZaU1praEpWVVJ2U0RCb1FYRnhOSFpJYVVGMmNVOVNXblJUVFZkS2VGVmtPWEozV2tWdVZFMUVWbWt4VG5KbGRWTnZWWHBNWmxkcFVVcDBiR3BxU21ZM1NqRnpUbGRVUmpKaVFtTnJNSEJrUnpkcFJHVnhVeUlzSW5raU9pSkJSak5aUms5dlNXNW1jVU5yVFRKa1pISnlkV1JyY21zMmN6Wk1SMUpNU0U5YVl6QlhhMmRhZFZsb1FsRk9ZbVZXY25sWFZTMW9aMTgwYkhCRE5EUjRaRmh3ZUc4NWFGOWhSVlZ0U0V0UGFWOWpVM2xhY1VjM0luMHNJbXRwWkNJNkluQnNablF4UW5WR2EyeGZaRGhyZFZOaWNVVlVSbXhxYjJwM01WZDBMVFpLYTJ0VGVXOWtWa1JEZDNjaWZRLi5CUUVtRXFhc1FHNHNKY1d2Lmo5RzBweVEwbTY0TU5OTEZicm4wT01BbmZaOTJ5bWNxV19ZM2ZIMWJEd2JFbnI1U1FHWF9jWjNfbUpGSS0zMFVJOEZuZzRUaXRxMU9JT2JySktETTh3LndRTmVJVzhzZnNua1V1aTUwTWpYd0EiLCJleUpoYkdjaU9pSkZRMFJJTFVWVElpd2lZMnhsZG1seklqcDdJbkJwYmlJNkluUmhibWNpTENKMFlXNW5JanA3SW1Ga2RpSTZleUpyWlhseklqcGJleUpoYkdjaU9pSkZRMDFTSWl3aVkzSjJJam9pVUMwMU1qRWlMQ0pyWlhsZmIzQnpJanBiSW1SbGNtbDJaVXRsZVNKZExDSnJkSGtpT2lKRlF5SXNJbmdpT2lKQlJVdzRjbkZ3UjE4dE9FRXRiM1UwYTJaMFJXbFNjbVZ5VldOb1ozWmlVRzFXYUV0bVJVNWtXVXd0ZDNOVlRucGZNMlpRZW5GUVVtWkhiRzltTkZkR2RFMTNNM0I1YnpaSVFUaFFTM0JHVlc5VWJ6ZHRjbWRSSWl3aWVTSTZJa0ZDUWxsVU9WOWpWRnBuT1RCWlJXWk9jWFV4V2tkSGFtaFphMWxwTTFkWlRqWmpialZJUmtjdGJ5MUdOR3BIWWpkVFpEazFTbWsxTkhkVWRUQmxOVmM1UjE5cGVFRlZYMWh0Wm1GSmQyZFRXVXN3WDJScVpEVWlmU3g3SW1Gc1p5STZJa1ZUTlRFeUlpd2lZM0oySWpvaVVDMDFNakVpTENKclpYbGZiM0J6SWpwYkluWmxjbWxtZVNKZExDSnJkSGtpT2lKRlF5SXNJbmdpT2lKQlZYSktNME5GUzFZd2FGWlNNamhLWTE5eWRXUkpUWEF0YmxCUmRtdFNSemxGVlZsVVRrTjJiVEZrY2tWUlJVNXVWMjB3VXpNelMycEtTMWx1VFhObVJrTlRPR3BYV0hJMFkxUTNTMFV4V2xkWmFGRnJkRzF3SWl3aWVTSTZJa0ZrYUVkWGFFVjJNREE1VjJwdFRUQjFZWFpFTm5sR01HczNMVXR0WkRCTmNHNTNRelZtWVVReFZuTkhWSGhLVG5SYVptSmlWbGhGVTIxeVJYZ3pYMVJoVERZMFpVeFhObFZwTFVveWJscHZhbEpUU0hWWmFtTWlmVjE5TENKMWNtd2lPaUpvZEhSd09pOHZiRzlqWVd4b2IzTjBPalF6TVRVMUluMTlMQ0psYm1NaU9pSkJNalUyUjBOTklpd2laWEJySWpwN0ltTnlkaUk2SWxBdE5USXhJaXdpYTNSNUlqb2lSVU1pTENKNElqb2lRV1JrT0Rnd05FdFlRbGhuWmpacU1EQkdVbEIyUzBsWmFGaE5ZMnRpWjE5c1NXbHdhbVF6TlhGUk9FVk9MVEZGUTNOUVZrNHRTa1I1ZW1sVmVuSkhjVTVOUkRGSmJGOUlTMUIyVGpaUmFVMU9kVXB0VVcxcWJpSXNJbmtpT2lKQlF5MW9Tak5DUTBvMU9VSnZhbWR1Ym5oZlZuaHBWekZNTVU5UlYxTXRZMmxzV2xkRGVIaGhZVUpyYjA0MVV6RnpiMnh4Y1ZwbVFuQkNiakZSWW5OTlJEZGZiVFEwVm1weGRWQlFjMjQwVmpFNFFqQlZkV2hDSW4wc0ltdHBaQ0k2SWtaUmNWZDBUWGQ0V2pWbVEyWjFSekp1WldWT1lreEpkQzF6Tm1Ka2RUQXpaMUZSU0Vvd00wY3RNMDBpZlEuLmduekZoT21WQk41UFNWT2kuZlBkQWJ2YlgtLUFXV1pPREZEdy1Yd2VvZHJQamc5UGM4SlFQQjIwZTlPQ2Y5RTZmSEtNYUJlR2xVSWRyOHViRjdRUjllM3Vnalk2ZEdLOEoyeklyYXcuTlV6T3M4a01HVVhMa3BXNWxja3BnZyIsImV5SmhiR2NpT2lKRlEwUklMVVZUSWl3aVkyeGxkbWx6SWpwN0luQnBiaUk2SW5SaGJtY2lMQ0owWVc1bklqcDdJbUZrZGlJNmV5SnJaWGx6SWpwYmV5SmhiR2NpT2lKRlEwMVNJaXdpWTNKMklqb2lVQzAxTWpFaUxDSnJaWGxmYjNCeklqcGJJbVJsY21sMlpVdGxlU0pkTENKcmRIa2lPaUpGUXlJc0luZ2lPaUpCWTNVeWFFOTVXSGRPVG1OYVpXRnJkVTFRVW01aGFqSnJXbGxJUldkMVFuRmZiR0ZMU0VZeFUwbzFjMVp3UmswMVVsZFJYMVp5U2pKVmVtMHpXa0k0T1Y5Nk5HZExVVzVETFU1TFJHTk5NMHcxUkVsdlh6UlNJaXdpZVNJNklrRmlOVXBTU1hOeU5HSm9kRXhvV1dabVVITlpaMUZqT0hocGNXcFplak5wUm1GeVpIbEVkVFZRZHpSVFRIZExWR1JuV2tSMGNtODVNSHByTVRKVFpERmhYemgxZVRkV1drMHRTRVkzUWs5bGNXeG9RV3R2VEdnaWZTeDdJbUZzWnlJNklrVlROVEV5SWl3aVkzSjJJam9pVUMwMU1qRWlMQ0pyWlhsZmIzQnpJanBiSW5abGNtbG1lU0pkTENKcmRIa2lPaUpGUXlJc0luZ2lPaUpCVm1Sa1ZFWklNRWxwTm1OWk1sQk9XVVp1WVZkT2IwaENlRzVuVFZaR1kzZFlObVYzU25vd1VVUlZjbkF5UmxsT1pVNHpaQzFxYWxwcVdYQmZkRkJ0Ukdjd05YcG1iSHAyU0VoSWJscFlUM2RLT1RSSU5VOHhJaXdpZVNJNklrRllaRkZ5VDBFdFJXWjFTM2xHU0drdE5sVlFSRGMwUnkxaVdVdFhNbFV4YVZselpFTkRSakp5WWs5T1JXeDBTVFowT0ZSZk56bHZjR1pLTkcxU1gySnliMVZqVVhBNU1GOWFZM05RY3poZlZtRndSbmhyVmpJaWZWMTlMQ0oxY213aU9pSm9kSFJ3T2k4dmJHOWpZV3hvYjNOME9qTTBPRGM1SW4xOUxDSmxibU1pT2lKQk1qVTJSME5OSWl3aVpYQnJJanA3SW1OeWRpSTZJbEF0TlRJeElpd2lhM1I1SWpvaVJVTWlMQ0o0SWpvaVFXUkNVVlJKUkdjNGNEWm5NVzh4T0d4R1dYRm1kMXBhTWxaaFJteG9VMlF0YVhkV09EQnRXalZaWm5wWU5qbFNWV3BxYzNKc1ZHZEtjbXMwY2pKaWRrSlZUbE5YY0cxbVEyeHBYek0xYm1Wb2NrZHpRbW94VUNJc0lua2lPaUpCVTJGVVYxY3RSRk5ZVVhObFgxUmxibDg0VkdkVmFrWTNVRWxRTm5wcVpUUmxSbk55YkY5WFRESkhNRGwzTFROalYzVlRlVlZmUzFvemF6VndTVlp0TlZkaU1tcGlXREptV2sxWmVFNVpRbDh5Vm10d1QxWnVJbjBzSW10cFpDSTZJbEk0YWtWSk1YQlRNVWxoY0hWVVZHTktObGhJWkROWFUzbGZRbmhXU1hWb1VVeDFVekJwYjJkU09UUWlmUS4uTEk2ZC1TNUlYSDRIY2lxeS5ESWl3M1hFeXlaMVZWZDFPcWYwS1pJeGZrUWtnZXo4bXl2N0dfZGFMOWxIdjVFVlFMQzREVG9PMWF6UDQ5SDNGUk5PRHZIcXBmaWp1a3VDX2JmLVZldy41NGpyZnZWdEYxR3ItYU9TVEFjSWV3Il0sInAiOiI0NERteG1EZVFiWTJ3eHpIYm9PODFkbTBCbENNcVJzeEtLQ0NDQWo4TVVjIiwidCI6M319LCJlbmMiOiJBMjU2R0NNIn0..zz3fUXsiaME2cSoy.LTQovHUvDP4MXT2_sHgf_cM2gicobD5kGXEl5eY.MK3Lf6IwaoVUvCTp1Q5VOA" decrypt := func(customField bool) { t.Helper() m := jwe.NewMessage() // Test WithPostParse while we're at it plain, err := jwe.Decrypt([]byte(data), // This is a really cheesy way of creating a jwa.KeyEncryptionAlgorithm // but a bogus one. jwe.WithKey(jwa.NewKeyEncryptionAlgorithm("invalid algorithm"), nil), jwe.WithMessage(m), jwe.WithKeyProvider(jwe.KeyProviderFunc(func(_ context.Context, sink jwe.KeySink, _ jwe.Recipient, _ *jwe.Message) error { sink.Key(jwa.DIRECT(), key) return nil })), ) require.NoError(t, err, `jwe.Decrypt should succeed`) if string(plain) != "testing Shamir Secret Sharing" { t.Errorf("expected 'testing Shamir Secret Sharing', got %s", string(plain)) return } if customField { require.NotNil(t, m.ProtectedHeaders(), `m.ProtectedHeaders should be non-nil`) var v Pin require.NoError(t, m.ProtectedHeaders().Get("clevis", &v), `m.Get("clevis") should be succeed`) } } decrypt(false) // register field deserialized and run decryption again jwe.RegisterCustomField("clevis", Pin{}) decrypt(true) // used to fail before, but this should pass } golang-github-lestrrat-go-jwx-3.0.13/jwe/headers.go000066400000000000000000000042501515060566400221340ustar00rootroot00000000000000package jwe import ( "fmt" "github.com/lestrrat-go/jwx/v3/internal/base64" "github.com/lestrrat-go/jwx/v3/internal/json" ) type isZeroer interface { isZero() bool } func (h *stdHeaders) isZero() bool { return h.agreementPartyUInfo == nil && h.agreementPartyVInfo == nil && h.algorithm == nil && h.compression == nil && h.contentEncryption == nil && h.contentType == nil && h.critical == nil && h.ephemeralPublicKey == nil && h.jwk == nil && h.jwkSetURL == nil && h.keyID == nil && h.typ == nil && h.x509CertChain == nil && h.x509CertThumbprint == nil && h.x509CertThumbprintS256 == nil && h.x509URL == nil && len(h.privateParams) == 0 } func (h *stdHeaders) Clone() (Headers, error) { dst := NewHeaders() if err := h.Copy(dst); err != nil { return nil, fmt.Errorf(`failed to copy header contents to new object: %w`, err) } return dst, nil } func (h *stdHeaders) Copy(dst Headers) error { for _, key := range h.Keys() { var v any if err := h.Get(key, &v); err != nil { return fmt.Errorf(`jwe.Headers: Copy: failed to get header %q: %w`, key, err) } if err := dst.Set(key, v); err != nil { return fmt.Errorf(`jwe.Headers: Copy: failed to set header %q: %w`, key, err) } } return nil } func (h *stdHeaders) Merge(h2 Headers) (Headers, error) { h3 := NewHeaders() if h != nil { if err := h.Copy(h3); err != nil { return nil, fmt.Errorf(`failed to copy headers from receiver: %w`, err) } } if h2 != nil { if err := h2.Copy(h3); err != nil { return nil, fmt.Errorf(`failed to copy headers from argument: %w`, err) } } return h3, nil } func (h *stdHeaders) Encode() ([]byte, error) { buf, err := json.Marshal(h) if err != nil { return nil, fmt.Errorf(`failed to marshal headers to JSON prior to encoding: %w`, err) } return base64.Encode(buf), nil } func (h *stdHeaders) Decode(buf []byte) error { // base64 json string -> json object representation of header decoded, err := base64.Decode(buf) if err != nil { return fmt.Errorf(`failed to unmarshal base64 encoded buffer: %w`, err) } if err := json.Unmarshal(decoded, h); err != nil { return fmt.Errorf(`failed to unmarshal buffer: %w`, err) } return nil } golang-github-lestrrat-go-jwx-3.0.13/jwe/headers_gen.go000066400000000000000000000634431515060566400227760ustar00rootroot00000000000000// Code generated by tools/cmd/genjwe/main.go. DO NOT EDIT. package jwe import ( "bytes" "fmt" "sort" "sync" "github.com/lestrrat-go/blackmagic" "github.com/lestrrat-go/jwx/v3/cert" "github.com/lestrrat-go/jwx/v3/internal/base64" "github.com/lestrrat-go/jwx/v3/internal/json" "github.com/lestrrat-go/jwx/v3/internal/pool" "github.com/lestrrat-go/jwx/v3/internal/tokens" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/lestrrat-go/jwx/v3/jwk" ) const ( AgreementPartyUInfoKey = "apu" AgreementPartyVInfoKey = "apv" AlgorithmKey = "alg" CompressionKey = "zip" ContentEncryptionKey = "enc" ContentTypeKey = "cty" CriticalKey = "crit" EphemeralPublicKeyKey = "epk" JWKKey = "jwk" JWKSetURLKey = "jku" KeyIDKey = "kid" TypeKey = "typ" X509CertChainKey = "x5c" X509CertThumbprintKey = "x5t" X509CertThumbprintS256Key = "x5t#S256" X509URLKey = "x5u" ) // Headers describe a standard JWE Header set. It is part of the JWE message // and is used to represent both Protected and Unprotected headers, // which in turn can be found in each Recipient object. // If you are not sure how this works, it is strongly recommended that // you read RFC7516, especially the section // that describes the full JSON serialization format of JWE messages. // // In most cases, you likely want to use the protected headers, as this is the part of the encrypted content type Headers interface { AgreementPartyUInfo() ([]byte, bool) AgreementPartyVInfo() ([]byte, bool) Algorithm() (jwa.KeyEncryptionAlgorithm, bool) Compression() (jwa.CompressionAlgorithm, bool) ContentEncryption() (jwa.ContentEncryptionAlgorithm, bool) ContentType() (string, bool) Critical() ([]string, bool) EphemeralPublicKey() (jwk.Key, bool) JWK() (jwk.Key, bool) JWKSetURL() (string, bool) KeyID() (string, bool) Type() (string, bool) X509CertChain() (*cert.Chain, bool) X509CertThumbprint() (string, bool) X509CertThumbprintS256() (string, bool) X509URL() (string, bool) // Get is used to extract the value of any field, including non-standard fields, out of the header. // // The first argument is the name of the field. The second argument is a pointer // to a variable that will receive the value of the field. The method returns // an error if the field does not exist, or if the value cannot be assigned to // the destination variable. Note that a field is considered to "exist" even if // the value is empty-ish (e.g. 0, false, ""), as long as it is explicitly set. Get(string, any) error Set(string, any) error Remove(string) error // Has returns true if the specified header has a value, even if // the value is empty-ish (e.g. 0, false, "") as long as it has been // explicitly set. Has(string) bool Encode() ([]byte, error) Decode([]byte) error Clone() (Headers, error) Copy(Headers) error Merge(Headers) (Headers, error) // Keys returns a list of the keys contained in this header. Keys() []string } // stdHeaderNames is a list of all standard header names defined in the JWE specification. var stdHeaderNames = []string{AgreementPartyUInfoKey, AgreementPartyVInfoKey, AlgorithmKey, CompressionKey, ContentEncryptionKey, ContentTypeKey, CriticalKey, EphemeralPublicKeyKey, JWKKey, JWKSetURLKey, KeyIDKey, TypeKey, X509CertChainKey, X509CertThumbprintKey, X509CertThumbprintS256Key, X509URLKey} type stdHeaders struct { agreementPartyUInfo []byte agreementPartyVInfo []byte algorithm *jwa.KeyEncryptionAlgorithm compression *jwa.CompressionAlgorithm contentEncryption *jwa.ContentEncryptionAlgorithm contentType *string critical []string ephemeralPublicKey jwk.Key jwk jwk.Key jwkSetURL *string keyID *string typ *string x509CertChain *cert.Chain x509CertThumbprint *string x509CertThumbprintS256 *string x509URL *string privateParams map[string]any mu *sync.RWMutex } func NewHeaders() Headers { return &stdHeaders{ mu: &sync.RWMutex{}, privateParams: map[string]any{}, } } func (h *stdHeaders) AgreementPartyUInfo() ([]byte, bool) { h.mu.RLock() defer h.mu.RUnlock() return h.agreementPartyUInfo, h.agreementPartyUInfo != nil } func (h *stdHeaders) AgreementPartyVInfo() ([]byte, bool) { h.mu.RLock() defer h.mu.RUnlock() return h.agreementPartyVInfo, h.agreementPartyVInfo != nil } func (h *stdHeaders) Algorithm() (jwa.KeyEncryptionAlgorithm, bool) { h.mu.RLock() defer h.mu.RUnlock() if h.algorithm == nil { return jwa.EmptyKeyEncryptionAlgorithm(), false } return *(h.algorithm), true } func (h *stdHeaders) Compression() (jwa.CompressionAlgorithm, bool) { h.mu.RLock() defer h.mu.RUnlock() if h.compression == nil { return jwa.NoCompress(), false } return *(h.compression), true } func (h *stdHeaders) ContentEncryption() (jwa.ContentEncryptionAlgorithm, bool) { h.mu.RLock() defer h.mu.RUnlock() if h.contentEncryption == nil { return jwa.EmptyContentEncryptionAlgorithm(), false } return *(h.contentEncryption), true } func (h *stdHeaders) ContentType() (string, bool) { h.mu.RLock() defer h.mu.RUnlock() if h.contentType == nil { return "", false } return *(h.contentType), true } func (h *stdHeaders) Critical() ([]string, bool) { h.mu.RLock() defer h.mu.RUnlock() return h.critical, h.critical != nil } func (h *stdHeaders) EphemeralPublicKey() (jwk.Key, bool) { h.mu.RLock() defer h.mu.RUnlock() return h.ephemeralPublicKey, h.ephemeralPublicKey != nil } func (h *stdHeaders) JWK() (jwk.Key, bool) { h.mu.RLock() defer h.mu.RUnlock() return h.jwk, h.jwk != nil } func (h *stdHeaders) JWKSetURL() (string, bool) { h.mu.RLock() defer h.mu.RUnlock() if h.jwkSetURL == nil { return "", false } return *(h.jwkSetURL), true } func (h *stdHeaders) KeyID() (string, bool) { h.mu.RLock() defer h.mu.RUnlock() if h.keyID == nil { return "", false } return *(h.keyID), true } func (h *stdHeaders) Type() (string, bool) { h.mu.RLock() defer h.mu.RUnlock() if h.typ == nil { return "", false } return *(h.typ), true } func (h *stdHeaders) X509CertChain() (*cert.Chain, bool) { h.mu.RLock() defer h.mu.RUnlock() return h.x509CertChain, h.x509CertChain != nil } func (h *stdHeaders) X509CertThumbprint() (string, bool) { h.mu.RLock() defer h.mu.RUnlock() if h.x509CertThumbprint == nil { return "", false } return *(h.x509CertThumbprint), true } func (h *stdHeaders) X509CertThumbprintS256() (string, bool) { h.mu.RLock() defer h.mu.RUnlock() if h.x509CertThumbprintS256 == nil { return "", false } return *(h.x509CertThumbprintS256), true } func (h *stdHeaders) X509URL() (string, bool) { h.mu.RLock() defer h.mu.RUnlock() if h.x509URL == nil { return "", false } return *(h.x509URL), true } func (h *stdHeaders) PrivateParams() map[string]any { h.mu.RLock() defer h.mu.RUnlock() return h.privateParams } func (h *stdHeaders) Has(name string) bool { h.mu.RLock() defer h.mu.RUnlock() switch name { case AgreementPartyUInfoKey: return h.agreementPartyUInfo != nil case AgreementPartyVInfoKey: return h.agreementPartyVInfo != nil case AlgorithmKey: return h.algorithm != nil case CompressionKey: return h.compression != nil case ContentEncryptionKey: return h.contentEncryption != nil case ContentTypeKey: return h.contentType != nil case CriticalKey: return h.critical != nil case EphemeralPublicKeyKey: return h.ephemeralPublicKey != nil case JWKKey: return h.jwk != nil case JWKSetURLKey: return h.jwkSetURL != nil case KeyIDKey: return h.keyID != nil case TypeKey: return h.typ != nil case X509CertChainKey: return h.x509CertChain != nil case X509CertThumbprintKey: return h.x509CertThumbprint != nil case X509CertThumbprintS256Key: return h.x509CertThumbprintS256 != nil case X509URLKey: return h.x509URL != nil default: _, ok := h.privateParams[name] return ok } } func (h *stdHeaders) Get(name string, dst any) error { h.mu.RLock() defer h.mu.RUnlock() switch name { case AgreementPartyUInfoKey: if h.agreementPartyUInfo == nil { return fmt.Errorf(`field %q not found`, name) } if err := blackmagic.AssignIfCompatible(dst, h.agreementPartyUInfo); err != nil { return fmt.Errorf(`failed to assign value for field %q: %w`, name, err) } case AgreementPartyVInfoKey: if h.agreementPartyVInfo == nil { return fmt.Errorf(`field %q not found`, name) } if err := blackmagic.AssignIfCompatible(dst, h.agreementPartyVInfo); err != nil { return fmt.Errorf(`failed to assign value for field %q: %w`, name, err) } case AlgorithmKey: if h.algorithm == nil { return fmt.Errorf(`field %q not found`, name) } if err := blackmagic.AssignIfCompatible(dst, *(h.algorithm)); err != nil { return fmt.Errorf(`failed to assign value for field %q: %w`, name, err) } case CompressionKey: if h.compression == nil { return fmt.Errorf(`field %q not found`, name) } if err := blackmagic.AssignIfCompatible(dst, *(h.compression)); err != nil { return fmt.Errorf(`failed to assign value for field %q: %w`, name, err) } case ContentEncryptionKey: if h.contentEncryption == nil { return fmt.Errorf(`field %q not found`, name) } if err := blackmagic.AssignIfCompatible(dst, *(h.contentEncryption)); err != nil { return fmt.Errorf(`failed to assign value for field %q: %w`, name, err) } case ContentTypeKey: if h.contentType == nil { return fmt.Errorf(`field %q not found`, name) } if err := blackmagic.AssignIfCompatible(dst, *(h.contentType)); err != nil { return fmt.Errorf(`failed to assign value for field %q: %w`, name, err) } case CriticalKey: if h.critical == nil { return fmt.Errorf(`field %q not found`, name) } if err := blackmagic.AssignIfCompatible(dst, h.critical); err != nil { return fmt.Errorf(`failed to assign value for field %q: %w`, name, err) } case EphemeralPublicKeyKey: if h.ephemeralPublicKey == nil { return fmt.Errorf(`field %q not found`, name) } if err := blackmagic.AssignIfCompatible(dst, h.ephemeralPublicKey); err != nil { return fmt.Errorf(`failed to assign value for field %q: %w`, name, err) } case JWKKey: if h.jwk == nil { return fmt.Errorf(`field %q not found`, name) } if err := blackmagic.AssignIfCompatible(dst, h.jwk); err != nil { return fmt.Errorf(`failed to assign value for field %q: %w`, name, err) } case JWKSetURLKey: if h.jwkSetURL == nil { return fmt.Errorf(`field %q not found`, name) } if err := blackmagic.AssignIfCompatible(dst, *(h.jwkSetURL)); err != nil { return fmt.Errorf(`failed to assign value for field %q: %w`, name, err) } case KeyIDKey: if h.keyID == nil { return fmt.Errorf(`field %q not found`, name) } if err := blackmagic.AssignIfCompatible(dst, *(h.keyID)); err != nil { return fmt.Errorf(`failed to assign value for field %q: %w`, name, err) } case TypeKey: if h.typ == nil { return fmt.Errorf(`field %q not found`, name) } if err := blackmagic.AssignIfCompatible(dst, *(h.typ)); err != nil { return fmt.Errorf(`failed to assign value for field %q: %w`, name, err) } case X509CertChainKey: if h.x509CertChain == nil { return fmt.Errorf(`field %q not found`, name) } if err := blackmagic.AssignIfCompatible(dst, h.x509CertChain); err != nil { return fmt.Errorf(`failed to assign value for field %q: %w`, name, err) } case X509CertThumbprintKey: if h.x509CertThumbprint == nil { return fmt.Errorf(`field %q not found`, name) } if err := blackmagic.AssignIfCompatible(dst, *(h.x509CertThumbprint)); err != nil { return fmt.Errorf(`failed to assign value for field %q: %w`, name, err) } case X509CertThumbprintS256Key: if h.x509CertThumbprintS256 == nil { return fmt.Errorf(`field %q not found`, name) } if err := blackmagic.AssignIfCompatible(dst, *(h.x509CertThumbprintS256)); err != nil { return fmt.Errorf(`failed to assign value for field %q: %w`, name, err) } case X509URLKey: if h.x509URL == nil { return fmt.Errorf(`field %q not found`, name) } if err := blackmagic.AssignIfCompatible(dst, *(h.x509URL)); err != nil { return fmt.Errorf(`failed to assign value for field %q: %w`, name, err) } default: v, ok := h.privateParams[name] if !ok { return fmt.Errorf(`field %q not found`, name) } if err := blackmagic.AssignIfCompatible(dst, v); err != nil { return fmt.Errorf(`failed to assign value for field %q: %w`, name, err) } } return nil } func (h *stdHeaders) Set(name string, value any) error { h.mu.Lock() defer h.mu.Unlock() return h.setNoLock(name, value) } func (h *stdHeaders) setNoLock(name string, value any) error { switch name { case AgreementPartyUInfoKey: if v, ok := value.([]byte); ok { h.agreementPartyUInfo = v return nil } return fmt.Errorf(`invalid value for %s key: %T`, AgreementPartyUInfoKey, value) case AgreementPartyVInfoKey: if v, ok := value.([]byte); ok { h.agreementPartyVInfo = v return nil } return fmt.Errorf(`invalid value for %s key: %T`, AgreementPartyVInfoKey, value) case AlgorithmKey: if v, ok := value.(jwa.KeyEncryptionAlgorithm); ok { h.algorithm = &v return nil } return fmt.Errorf(`invalid value for %s key: %T`, AlgorithmKey, value) case CompressionKey: if v, ok := value.(jwa.CompressionAlgorithm); ok { h.compression = &v return nil } return fmt.Errorf(`invalid value for %s key: %T`, CompressionKey, value) case ContentEncryptionKey: if v, ok := value.(jwa.ContentEncryptionAlgorithm); ok { if v == jwa.EmptyContentEncryptionAlgorithm() { return fmt.Errorf(`"enc" field cannot be an empty string`) } h.contentEncryption = &v return nil } return fmt.Errorf(`invalid value for %s key: %T`, ContentEncryptionKey, value) case ContentTypeKey: if v, ok := value.(string); ok { h.contentType = &v return nil } return fmt.Errorf(`invalid value for %s key: %T`, ContentTypeKey, value) case CriticalKey: if v, ok := value.([]string); ok { h.critical = v return nil } return fmt.Errorf(`invalid value for %s key: %T`, CriticalKey, value) case EphemeralPublicKeyKey: if v, ok := value.(jwk.Key); ok { h.ephemeralPublicKey = v return nil } return fmt.Errorf(`invalid value for %s key: %T`, EphemeralPublicKeyKey, value) case JWKKey: if v, ok := value.(jwk.Key); ok { h.jwk = v return nil } return fmt.Errorf(`invalid value for %s key: %T`, JWKKey, value) case JWKSetURLKey: if v, ok := value.(string); ok { h.jwkSetURL = &v return nil } return fmt.Errorf(`invalid value for %s key: %T`, JWKSetURLKey, value) case KeyIDKey: if v, ok := value.(string); ok { h.keyID = &v return nil } return fmt.Errorf(`invalid value for %s key: %T`, KeyIDKey, value) case TypeKey: if v, ok := value.(string); ok { h.typ = &v return nil } return fmt.Errorf(`invalid value for %s key: %T`, TypeKey, value) case X509CertChainKey: if v, ok := value.(*cert.Chain); ok { h.x509CertChain = v return nil } return fmt.Errorf(`invalid value for %s key: %T`, X509CertChainKey, value) case X509CertThumbprintKey: if v, ok := value.(string); ok { h.x509CertThumbprint = &v return nil } return fmt.Errorf(`invalid value for %s key: %T`, X509CertThumbprintKey, value) case X509CertThumbprintS256Key: if v, ok := value.(string); ok { h.x509CertThumbprintS256 = &v return nil } return fmt.Errorf(`invalid value for %s key: %T`, X509CertThumbprintS256Key, value) case X509URLKey: if v, ok := value.(string); ok { h.x509URL = &v return nil } return fmt.Errorf(`invalid value for %s key: %T`, X509URLKey, value) default: if h.privateParams == nil { h.privateParams = map[string]any{} } h.privateParams[name] = value } return nil } func (h *stdHeaders) Remove(key string) error { h.mu.Lock() defer h.mu.Unlock() switch key { case AgreementPartyUInfoKey: h.agreementPartyUInfo = nil case AgreementPartyVInfoKey: h.agreementPartyVInfo = nil case AlgorithmKey: h.algorithm = nil case CompressionKey: h.compression = nil case ContentEncryptionKey: h.contentEncryption = nil case ContentTypeKey: h.contentType = nil case CriticalKey: h.critical = nil case EphemeralPublicKeyKey: h.ephemeralPublicKey = nil case JWKKey: h.jwk = nil case JWKSetURLKey: h.jwkSetURL = nil case KeyIDKey: h.keyID = nil case TypeKey: h.typ = nil case X509CertChainKey: h.x509CertChain = nil case X509CertThumbprintKey: h.x509CertThumbprint = nil case X509CertThumbprintS256Key: h.x509CertThumbprintS256 = nil case X509URLKey: h.x509URL = nil default: delete(h.privateParams, key) } return nil } func (h *stdHeaders) UnmarshalJSON(buf []byte) error { h.agreementPartyUInfo = nil h.agreementPartyVInfo = nil h.algorithm = nil h.compression = nil h.contentEncryption = nil h.contentType = nil h.critical = nil h.ephemeralPublicKey = nil h.jwk = nil h.jwkSetURL = nil h.keyID = nil h.typ = nil h.x509CertChain = nil h.x509CertThumbprint = nil h.x509CertThumbprintS256 = nil h.x509URL = nil dec := json.NewDecoder(bytes.NewReader(buf)) LOOP: for { tok, err := dec.Token() if err != nil { return fmt.Errorf(`error reading token: %w`, err) } switch tok := tok.(type) { case json.Delim: // Assuming we're doing everything correctly, we should ONLY // get either tokens.OpenCurlyBracket or tokens.CloseCurlyBracket here. if tok == tokens.CloseCurlyBracket { // End of object break LOOP } else if tok != tokens.OpenCurlyBracket { return fmt.Errorf(`expected '%c' but got '%c'`, tokens.OpenCurlyBracket, tok) } case string: // Objects can only have string keys switch tok { case AgreementPartyUInfoKey: if err := json.AssignNextBytesToken(&h.agreementPartyUInfo, dec); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, AgreementPartyUInfoKey, err) } case AgreementPartyVInfoKey: if err := json.AssignNextBytesToken(&h.agreementPartyVInfo, dec); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, AgreementPartyVInfoKey, err) } case AlgorithmKey: var decoded jwa.KeyEncryptionAlgorithm if err := dec.Decode(&decoded); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, AlgorithmKey, err) } h.algorithm = &decoded case CompressionKey: var decoded jwa.CompressionAlgorithm if err := dec.Decode(&decoded); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, CompressionKey, err) } h.compression = &decoded case ContentEncryptionKey: var decoded jwa.ContentEncryptionAlgorithm if err := dec.Decode(&decoded); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, ContentEncryptionKey, err) } h.contentEncryption = &decoded case ContentTypeKey: if err := json.AssignNextStringToken(&h.contentType, dec); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, ContentTypeKey, err) } case CriticalKey: var decoded []string if err := dec.Decode(&decoded); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, CriticalKey, err) } h.critical = decoded case EphemeralPublicKeyKey: var buf json.RawMessage if err := dec.Decode(&buf); err != nil { return fmt.Errorf(`failed to decode value for key %s:%w`, EphemeralPublicKeyKey, err) } key, err := jwk.ParseKey(buf) if err != nil { return fmt.Errorf(`failed to parse JWK for key %s: %w`, EphemeralPublicKeyKey, err) } h.ephemeralPublicKey = key case JWKKey: var buf json.RawMessage if err := dec.Decode(&buf); err != nil { return fmt.Errorf(`failed to decode value for key %s:%w`, JWKKey, err) } key, err := jwk.ParseKey(buf) if err != nil { return fmt.Errorf(`failed to parse JWK for key %s: %w`, JWKKey, err) } h.jwk = key case JWKSetURLKey: if err := json.AssignNextStringToken(&h.jwkSetURL, dec); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, JWKSetURLKey, err) } case KeyIDKey: if err := json.AssignNextStringToken(&h.keyID, dec); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, KeyIDKey, err) } case TypeKey: if err := json.AssignNextStringToken(&h.typ, dec); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, TypeKey, err) } case X509CertChainKey: var decoded cert.Chain if err := dec.Decode(&decoded); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, X509CertChainKey, err) } h.x509CertChain = &decoded case X509CertThumbprintKey: if err := json.AssignNextStringToken(&h.x509CertThumbprint, dec); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, X509CertThumbprintKey, err) } case X509CertThumbprintS256Key: if err := json.AssignNextStringToken(&h.x509CertThumbprintS256, dec); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, X509CertThumbprintS256Key, err) } case X509URLKey: if err := json.AssignNextStringToken(&h.x509URL, dec); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, X509URLKey, err) } default: decoded, err := registry.Decode(dec, tok) if err != nil { return err } h.setNoLock(tok, decoded) } default: return fmt.Errorf(`invalid token %T`, tok) } } return nil } func (h *stdHeaders) Keys() []string { h.mu.RLock() defer h.mu.RUnlock() keys := make([]string, 0, 16+len(h.privateParams)) if h.agreementPartyUInfo != nil { keys = append(keys, AgreementPartyUInfoKey) } if h.agreementPartyVInfo != nil { keys = append(keys, AgreementPartyVInfoKey) } if h.algorithm != nil { keys = append(keys, AlgorithmKey) } if h.compression != nil { keys = append(keys, CompressionKey) } if h.contentEncryption != nil { keys = append(keys, ContentEncryptionKey) } if h.contentType != nil { keys = append(keys, ContentTypeKey) } if h.critical != nil { keys = append(keys, CriticalKey) } if h.ephemeralPublicKey != nil { keys = append(keys, EphemeralPublicKeyKey) } if h.jwk != nil { keys = append(keys, JWKKey) } if h.jwkSetURL != nil { keys = append(keys, JWKSetURLKey) } if h.keyID != nil { keys = append(keys, KeyIDKey) } if h.typ != nil { keys = append(keys, TypeKey) } if h.x509CertChain != nil { keys = append(keys, X509CertChainKey) } if h.x509CertThumbprint != nil { keys = append(keys, X509CertThumbprintKey) } if h.x509CertThumbprintS256 != nil { keys = append(keys, X509CertThumbprintS256Key) } if h.x509URL != nil { keys = append(keys, X509URLKey) } for k := range h.privateParams { keys = append(keys, k) } return keys } func (h stdHeaders) MarshalJSON() ([]byte, error) { data := make(map[string]any) keys := make([]string, 0, 16+len(h.privateParams)) h.mu.RLock() if h.agreementPartyUInfo != nil { data[AgreementPartyUInfoKey] = h.agreementPartyUInfo keys = append(keys, AgreementPartyUInfoKey) } if h.agreementPartyVInfo != nil { data[AgreementPartyVInfoKey] = h.agreementPartyVInfo keys = append(keys, AgreementPartyVInfoKey) } if h.algorithm != nil { data[AlgorithmKey] = *(h.algorithm) keys = append(keys, AlgorithmKey) } if h.compression != nil { data[CompressionKey] = *(h.compression) keys = append(keys, CompressionKey) } if h.contentEncryption != nil { data[ContentEncryptionKey] = *(h.contentEncryption) keys = append(keys, ContentEncryptionKey) } if h.contentType != nil { data[ContentTypeKey] = *(h.contentType) keys = append(keys, ContentTypeKey) } if h.critical != nil { data[CriticalKey] = h.critical keys = append(keys, CriticalKey) } if h.ephemeralPublicKey != nil { data[EphemeralPublicKeyKey] = h.ephemeralPublicKey keys = append(keys, EphemeralPublicKeyKey) } if h.jwk != nil { data[JWKKey] = h.jwk keys = append(keys, JWKKey) } if h.jwkSetURL != nil { data[JWKSetURLKey] = *(h.jwkSetURL) keys = append(keys, JWKSetURLKey) } if h.keyID != nil { data[KeyIDKey] = *(h.keyID) keys = append(keys, KeyIDKey) } if h.typ != nil { data[TypeKey] = *(h.typ) keys = append(keys, TypeKey) } if h.x509CertChain != nil { data[X509CertChainKey] = h.x509CertChain keys = append(keys, X509CertChainKey) } if h.x509CertThumbprint != nil { data[X509CertThumbprintKey] = *(h.x509CertThumbprint) keys = append(keys, X509CertThumbprintKey) } if h.x509CertThumbprintS256 != nil { data[X509CertThumbprintS256Key] = *(h.x509CertThumbprintS256) keys = append(keys, X509CertThumbprintS256Key) } if h.x509URL != nil { data[X509URLKey] = *(h.x509URL) keys = append(keys, X509URLKey) } for k, v := range h.privateParams { data[k] = v keys = append(keys, k) } h.mu.RUnlock() sort.Strings(keys) buf := pool.BytesBuffer().Get() defer pool.BytesBuffer().Put(buf) enc := json.NewEncoder(buf) buf.WriteByte(tokens.OpenCurlyBracket) for i, k := range keys { if i > 0 { buf.WriteRune(tokens.Comma) } buf.WriteRune(tokens.DoubleQuote) buf.WriteString(k) buf.WriteString(`":`) v := data[k] switch v := v.(type) { case []byte: buf.WriteRune(tokens.DoubleQuote) buf.WriteString(base64.EncodeToString(v)) buf.WriteRune(tokens.DoubleQuote) default: if err := enc.Encode(v); err != nil { return nil, fmt.Errorf(`failed to encode value for field %s`, k) } buf.Truncate(buf.Len() - 1) } } buf.WriteByte(tokens.CloseCurlyBracket) ret := make([]byte, buf.Len()) copy(ret, buf.Bytes()) return ret, nil } func (h *stdHeaders) clear() { h.mu.Lock() h.agreementPartyUInfo = nil h.agreementPartyVInfo = nil h.algorithm = nil h.compression = nil h.contentEncryption = nil h.contentType = nil h.critical = nil h.ephemeralPublicKey = nil h.jwk = nil h.jwkSetURL = nil h.keyID = nil h.typ = nil h.x509CertChain = nil h.x509CertThumbprint = nil h.x509CertThumbprintS256 = nil h.x509URL = nil h.privateParams = map[string]any{} h.mu.Unlock() } golang-github-lestrrat-go-jwx-3.0.13/jwe/headers_test.go000066400000000000000000000211701515060566400231730ustar00rootroot00000000000000package jwe_test import ( "reflect" "testing" "github.com/lestrrat-go/jwx/v3/cert" "github.com/lestrrat-go/jwx/v3/internal/jwxtest" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/lestrrat-go/jwx/v3/jwe" "github.com/lestrrat-go/jwx/v3/jwk" "github.com/stretchr/testify/require" ) var zeroval reflect.Value func TestHeaders(t *testing.T) { certSrc := []string{ "MIIE3jCCA8agAwIBAgICAwEwDQYJKoZIhvcNAQEFBQAwYzELMAkGA1UEBhMCVVMxITAfBgNVBAoTGFRoZSBHbyBEYWRkeSBHcm91cCwgSW5jLjExMC8GA1UECxMoR28gRGFkZHkgQ2xhc3MgMiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjExMTYwMTU0MzdaFw0yNjExMTYwMTU0MzdaMIHKMQswCQYDVQQGEwJVUzEQMA4GA1UECBMHQXJpem9uYTETMBEGA1UEBxMKU2NvdHRzZGFsZTEaMBgGA1UEChMRR29EYWRkeS5jb20sIEluYy4xMzAxBgNVBAsTKmh0dHA6Ly9jZXJ0aWZpY2F0ZXMuZ29kYWRkeS5jb20vcmVwb3NpdG9yeTEwMC4GA1UEAxMnR28gRGFkZHkgU2VjdXJlIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MREwDwYDVQQFEwgwNzk2OTI4NzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMQt1RWMnCZM7DI161+4WQFapmGBWTtwY6vj3D3HKrjJM9N55DrtPDAjhI6zMBS2sofDPZVUBJ7fmd0LJR4h3mUpfjWoqVTr9vcyOdQmVZWt7/v+WIbXnvQAjYwqDL1CBM6nPwT27oDyqu9SoWlm2r4arV3aLGbqGmu75RpRSgAvSMeYddi5Kcju+GZtCpyz8/x4fKL4o/K1w/O5epHBp+YlLpyo7RJlbmr2EkRTcDCVw5wrWCs9CHRK8r5RsL+H0EwnWGu1NcWdrxcx+AuP7q2BNgWJCJjPOq8lh8BJ6qf9Z/dFjpfMFDniNoW1fho3/Rb2cRGadDAW/hOUoz+EDU8CAwEAAaOCATIwggEuMB0GA1UdDgQWBBT9rGEyk2xF1uLuhV+auud2mWjM5zAfBgNVHSMEGDAWgBTSxLDSkdRMEXGzYcs9of7dqGrU4zASBgNVHRMBAf8ECDAGAQH/AgEAMDMGCCsGAQUFBwEBBCcwJTAjBggrBgEFBQcwAYYXaHR0cDovL29jc3AuZ29kYWRkeS5jb20wRgYDVR0fBD8wPTA7oDmgN4Y1aHR0cDovL2NlcnRpZmljYXRlcy5nb2RhZGR5LmNvbS9yZXBvc2l0b3J5L2dkcm9vdC5jcmwwSwYDVR0gBEQwQjBABgRVHSAAMDgwNgYIKwYBBQUHAgEWKmh0dHA6Ly9jZXJ0aWZpY2F0ZXMuZ29kYWRkeS5jb20vcmVwb3NpdG9yeTAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQEFBQADggEBANKGwOy9+aG2Z+5mC6IGOgRQjhVyrEp0lVPLN8tESe8HkGsz2ZbwlFalEzAFPIUyIXvJxwqoJKSQ3kbTJSMUA2fCENZvD117esyfxVgqwcSeIaha86ykRvOe5GPLL5CkKSkB2XIsKd83ASe8T+5o0yGPwLPk9Qnt0hCqU7S+8MxZC9Y7lhyVJEnfzuz9p0iRFEUOOjZv2kWzRaJBydTXRE4+uXR21aITVSzGh6O1mawGhId/dQb8vxRMDsxuxN89txJx9OjxUUAiKEngHUuHqDTMBqLdElrRhjZkAzVvb3du6/KFUJheqwNTrZEjYx8WnM25sgVjOuH0aBsXBTWVU+4=", "MIIE+zCCBGSgAwIBAgICAQ0wDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENsYXNzIDIgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTA0MDYyOTE3MDYyMFoXDTI0MDYyOTE3MDYyMFowYzELMAkGA1UEBhMCVVMxITAfBgNVBAoTGFRoZSBHbyBEYWRkeSBHcm91cCwgSW5jLjExMC8GA1UECxMoR28gRGFkZHkgQ2xhc3MgMiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASAwDQYJKoZIhvcNAQEBBQADggENADCCAQgCggEBAN6d1+pXGEmhW+vXX0iG6r7d/+TvZxz0ZWizV3GgXne77ZtJ6XCAPVYYYwhv2vLM0D9/AlQiVBDYsoHUwHU9S3/Hd8M+eKsaA7Ugay9qK7HFiH7Eux6wwdhFJ2+qN1j3hybX2C32qRe3H3I2TqYXP2WYktsqbl2i/ojgC95/5Y0V4evLOtXiEqITLdiOr18SPaAIBQi2XKVlOARFmR6jYGB0xUGlcmIbYsUfb18aQr4CUWWoriMYavx4A6lNf4DD+qta/KFApMoZFv6yyO9ecw3ud72a9nmYvLEHZ6IVDd2gWMZEewo+YihfukEHU1jPEX44dMX4/7VpkI+EdOqXG68CAQOjggHhMIIB3TAdBgNVHQ4EFgQU0sSw0pHUTBFxs2HLPaH+3ahq1OMwgdIGA1UdIwSByjCBx6GBwaSBvjCBuzEkMCIGA1UEBxMbVmFsaUNlcnQgVmFsaWRhdGlvbiBOZXR3b3JrMRcwFQYDVQQKEw5WYWxpQ2VydCwgSW5jLjE1MDMGA1UECxMsVmFsaUNlcnQgQ2xhc3MgMiBQb2xpY3kgVmFsaWRhdGlvbiBBdXRob3JpdHkxITAfBgNVBAMTGGh0dHA6Ly93d3cudmFsaWNlcnQuY29tLzEgMB4GCSqGSIb3DQEJARYRaW5mb0B2YWxpY2VydC5jb22CAQEwDwYDVR0TAQH/BAUwAwEB/zAzBggrBgEFBQcBAQQnMCUwIwYIKwYBBQUHMAGGF2h0dHA6Ly9vY3NwLmdvZGFkZHkuY29tMEQGA1UdHwQ9MDswOaA3oDWGM2h0dHA6Ly9jZXJ0aWZpY2F0ZXMuZ29kYWRkeS5jb20vcmVwb3NpdG9yeS9yb290LmNybDBLBgNVHSAERDBCMEAGBFUdIAAwODA2BggrBgEFBQcCARYqaHR0cDovL2NlcnRpZmljYXRlcy5nb2RhZGR5LmNvbS9yZXBvc2l0b3J5MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOBgQC1QPmnHfbq/qQaQlpE9xXUhUaJwL6e4+PrxeNYiY+Sn1eocSxI0YGyeR+sBjUZsE4OWBsUs5iB0QQeyAfJg594RAoYC5jcdnplDQ1tgMQLARzLrUc+cb53S8wGd9D0VmsfSxOaFIqII6hR8INMqzW/Rn453HWkrugp++85j09VZw==", "MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENsYXNzIDIgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNjAwMTk1NFoXDTE5MDYyNjAwMTk1NFowgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENsYXNzIDIgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDOOnHK5avIWZJV16vYdA757tn2VUdZZUcOBVXc65g2PFxTXdMwzzjsvUGJ7SVCCSRrCl6zfN1SLUzm1NZ9WlmpZdRJEy0kTRxQb7XBhVQ7/nHk01xC+YDgkRoKWzk2Z/M/VXwbP7RfZHM047QSv4dk+NoS/zcnwbNDu+97bi5p9wIDAQABMA0GCSqGSIb3DQEBBQUAA4GBADt/UG9vUJSZSWI4OB9L+KXIPqeCgfYrx+jFzug6EILLGACOTb2oWH+heQC1u+mNr0HZDzTuIYEZoDJJKPTEjlbVUjP9UNV+mWwD5MlM/Mtsq2azSiGM5bUMMj4QssxsodyamEwCW/POuZ6lcg5Ktz885hZo+L7tdEy8W9ViH0Pd", } var certs cert.Chain for _, src := range certSrc { _ = certs.AddString(src) } rawKey, err := jwxtest.GenerateEcdsaKey(jwa.P521()) require.NoError(t, err, `jwxtest.GenerateEcdsaKey should succeed`) privKey, err := jwk.Import(rawKey) require.NoError(t, err, `jwk.Import should succeed`) pubKey, err := jwk.Import(rawKey.PublicKey) require.NoError(t, err, `jwk.Import should succeed`) data := []struct { Key string Value any Expected any Method string }{ { Key: jwe.AgreementPartyUInfoKey, Value: []byte("apu foobarbaz"), Method: "AgreementPartyUInfo", }, {Key: jwe.AgreementPartyVInfoKey, Value: []byte("apv foobarbaz")}, {Key: jwe.CompressionKey, Value: jwa.Deflate()}, {Key: jwe.ContentEncryptionKey, Value: jwa.A128GCM()}, { Key: jwe.ContentTypeKey, Value: "application/json", Method: "ContentType", }, { Key: jwe.CriticalKey, Value: []string{"crit blah"}, Method: "Critical", }, { Key: jwe.EphemeralPublicKeyKey, Value: pubKey, Method: "EphemeralPublicKey", }, { Key: jwe.JWKKey, Value: privKey, Method: "JWK", }, { Key: jwe.JWKSetURLKey, Value: "http://github.com/lestrrat-go/jwx/v3", Method: "JWKSetURL", }, { Key: jwe.KeyIDKey, Value: "kid blah", Method: "KeyID", }, { Key: jwe.TypeKey, Value: "typ blah", Method: "Type", }, { Key: jwe.X509CertChainKey, Value: &certs, Method: "X509CertChain", }, { Key: jwe.X509CertThumbprintKey, Value: "x5t blah", Method: "X509CertThumbprint", }, { Key: jwe.X509CertThumbprintS256Key, Value: "x5t#256 blah", Method: "X509CertThumbprintS256", }, { Key: jwe.X509URLKey, Value: "http://github.com/lestrrat-go/jwx/v3", Method: "X509URL", }, {Key: "private", Value: "boofoo"}, } base := jwe.NewHeaders() t.Run("Set values", func(t *testing.T) { // DO NOT RUN THIS IN PARALLEL. THIS IS AN INITIALIZER for _, tc := range data { require.NoError(t, base.Set(tc.Key, tc.Value), "Headers.Set should succeed") } }) t.Run("Set/Get", func(t *testing.T) { h := jwe.NewHeaders() for _, k := range base.Keys() { var v any require.NoError(t, base.Get(k, &v), `base.Get should succeed for key %#v`, k) require.NoError(t, h.Set(k, v), `h.Set should succeed for key %#v`, k) } for _, tc := range data { var values []any var viaGet any require.NoError(t, h.Get(tc.Key, &viaGet), `h.Get should be successful`) values = append(values, viaGet) if method := tc.Method; method != "" { m := reflect.ValueOf(h).MethodByName(method) require.NotEqual(t, m, zeroval, "method %s should be available", method) ret := m.Call(nil) require.Len(t, ret, 2, `should get exactly 1 value as return value`) values = append(values, ret[0].Interface()) } expected := tc.Expected if expected == nil { expected = tc.Value } for i, got := range values { require.Equal(t, expected, got, "value %d should match", i) } } }) t.Run("PrivateParams", func(t *testing.T) { h := base var v any require.NoError(t, h.Get(`private`, &v), `h.Get should succeed`) require.Equal(t, v, "boofoo", `value for 'private' should match`) }) t.Run("Encode", func(t *testing.T) { h1 := jwe.NewHeaders() h1.Set(jwe.AlgorithmKey, jwa.A128GCMKW) h1.Set("foo", "bar") buf, err := h1.Encode() require.NoError(t, err, `h1.Encode should succeed`) h2 := jwe.NewHeaders() require.NoError(t, h2.Decode(buf), `h2.Decode should succeed`) require.Equal(t, h1, h2, `objects should match`) }) t.Run("Range", func(t *testing.T) { expected := map[string]any{} for _, tc := range data { v := tc.Value if expected := tc.Expected; expected != nil { v = expected } expected[tc.Key] = v } t.Run("Remove", func(t *testing.T) { h := base for _, k := range h.Keys() { require.NoError(t, h.Remove(k), `h.Remove should succeed`) } require.Len(t, h.Keys(), 0, `len should be zero`) }) }) } golang-github-lestrrat-go-jwx-3.0.13/jwe/interface.go000066400000000000000000000205401515060566400224610ustar00rootroot00000000000000package jwe import ( "github.com/lestrrat-go/jwx/v3/jwa" "github.com/lestrrat-go/jwx/v3/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, bool) } // 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 EncryptedKey() []byte SetHeaders(Headers) error SetEncryptedKey([]byte) error } type stdRecipient struct { // Comments on each field are taken from https://datatracker.ietf.org/doc/html/rfc7516 // // header // The "header" member MUST be present and contain the value JWE Per- // Recipient Unprotected Header when the JWE Per-Recipient // Unprotected Header value is non-empty; otherwise, it MUST be // absent. This value is represented as an unencoded JSON object, // rather than as a string. These Header Parameter values are not // integrity protected. // // At least one of the "header", "protected", and "unprotected" members // MUST be present so that "alg" and "enc" Header Parameter values are // conveyed for each recipient computation. // // JWX note: see Message.unprotectedHeaders headers Headers // encrypted_key // The "encrypted_key" member MUST be present and contain the value // BASE64URL(JWE Encrypted Key) when the JWE Encrypted Key value is // non-empty; otherwise, it MUST be absent. encryptedKey []byte } // Message contains the entire encrypted JWE message. You should not // expect to use Message for anything other than inspecting the // state of an encrypted message. This is because encryption is // highly context-sensitive, and once we parse the original payload // into an object, we may not always be able to recreate the exact // context in which the encryption happened. // // For example, it is totally valid for if the protected header's // integrity was calculated using a non-standard line breaks: // // {"a dummy": // "protected header"} // // Once parsed, though, we can only serialize the protected header as: // // {"a dummy":"protected header"} // // which would obviously result in a contradicting integrity value // if we tried to re-calculate it from a parsed message. // //nolint:govet type Message struct { // Comments on each field are taken from https://datatracker.ietf.org/doc/html/rfc7516 // // protected // The "protected" member MUST be present and contain the value // BASE64URL(UTF8(JWE Protected Header)) when the JWE Protected // Header value is non-empty; otherwise, it MUST be absent. These // Header Parameter values are integrity protected. protectedHeaders Headers // unprotected // The "unprotected" member MUST be present and contain the value JWE // Shared Unprotected Header when the JWE Shared Unprotected Header // value is non-empty; otherwise, it MUST be absent. This value is // represented as an unencoded JSON object, rather than as a string. // These Header Parameter values are not integrity protected. // // JWX note: This field is NOT mutually exclusive with per-recipient // headers within the implementation because... it's too much work. // It is _never_ populated (we don't provide a way to do this) upon encryption. // When decrypting, if present its values are always merged with // per-recipient header. unprotectedHeaders Headers // iv // The "iv" member MUST be present and contain the value // BASE64URL(JWE Initialization Vector) when the JWE Initialization // Vector value is non-empty; otherwise, it MUST be absent. initializationVector []byte // aad // The "aad" member MUST be present and contain the value // BASE64URL(JWE AAD)) when the JWE AAD value is non-empty; // otherwise, it MUST be absent. A JWE AAD value can be included to // supply a base64url-encoded value to be integrity protected but not // encrypted. authenticatedData []byte // ciphertext // The "ciphertext" member MUST be present and contain the value // BASE64URL(JWE Ciphertext). cipherText []byte // tag // The "tag" member MUST be present and contain the value // BASE64URL(JWE Authentication Tag) when the JWE Authentication Tag // value is non-empty; otherwise, it MUST be absent. tag []byte // recipients // The "recipients" member value MUST be an array of JSON objects. // Each object contains information specific to a single recipient. // This member MUST be present with exactly one array element per // recipient, even if some or all of the array element values are the // empty JSON object "{}" (which can happen when all Header Parameter // values are shared between all recipients and when no encrypted key // is used, such as when doing Direct Encryption). // // Some Header Parameters, including the "alg" parameter, can be shared // among all recipient computations. Header Parameters in the JWE // Protected Header and JWE Shared Unprotected Header values are shared // among all recipients. // // The Header Parameter values used when creating or validating per- // recipient ciphertext and Authentication Tag values are the union of // the three sets of Header Parameter values that may be present: (1) // the JWE Protected Header represented in the "protected" member, (2) // the JWE Shared Unprotected Header represented in the "unprotected" // member, and (3) the JWE Per-Recipient Unprotected Header represented // in the "header" member of the recipient's array element. The union // of these sets of Header Parameters comprises the JOSE Header. The // Header Parameter names in the three locations MUST be disjoint. recipients []Recipient // TODO: Additional members can be present in both the JSON objects defined // above; if not understood by implementations encountering them, they // MUST be ignored. // privateParams map[string]any // These two fields below are not available for the public consumers of this object. // rawProtectedHeaders stores the original protected header buffer rawProtectedHeaders []byte // storeProtectedHeaders is a hint to be used in UnmarshalJSON(). // When this flag is true, UnmarshalJSON() will populate the // rawProtectedHeaders field storeProtectedHeaders bool } // populater is an interface for things that may modify the // JWE header. e.g. ByteWithECPrivateKey type populater interface { Populate(keygen.Setter) error } golang-github-lestrrat-go-jwx-3.0.13/jwe/internal/000077500000000000000000000000001515060566400220055ustar00rootroot00000000000000golang-github-lestrrat-go-jwx-3.0.13/jwe/internal/aescbc/000077500000000000000000000000001515060566400232255ustar00rootroot00000000000000golang-github-lestrrat-go-jwx-3.0.13/jwe/internal/aescbc/BUILD.bazel000066400000000000000000000010061515060566400251000ustar00rootroot00000000000000load("@rules_go//go:def.bzl", "go_library", "go_test") go_library( name = "aescbc", srcs = ["aescbc.go"], importpath = "github.com/lestrrat-go/jwx/v3/jwe/internal/aescbc", visibility = ["//:__subpackages__"], deps = ["//internal/pool"], ) go_test( name = "aescbc_test", srcs = ["aescbc_test.go"], embed = [":aescbc"], deps = ["@com_github_stretchr_testify//require"] ) alias( name = "go_default_library", actual = ":aescbc", visibility = ["//jwe:__subpackages__"], ) golang-github-lestrrat-go-jwx-3.0.13/jwe/internal/aescbc/aescbc.go000066400000000000000000000156341515060566400250050ustar00rootroot00000000000000package aescbc import ( "crypto/cipher" "crypto/hmac" "crypto/sha256" "crypto/sha512" "crypto/subtle" "encoding/binary" "errors" "fmt" "hash" "sync/atomic" "github.com/lestrrat-go/jwx/v3/internal/pool" ) const ( NonceSize = 16 ) const defaultBufSize int64 = 256 * 1024 * 1024 var maxBufSize atomic.Int64 func init() { SetMaxBufferSize(defaultBufSize) } func SetMaxBufferSize(siz int64) { if siz <= 0 { siz = defaultBufSize } maxBufSize.Store(siz) } func pad(buf []byte, n int) []byte { rem := n - len(buf)%n if rem == 0 { return buf } bufsiz := len(buf) + rem mbs := maxBufSize.Load() if int64(bufsiz) > mbs { panic(fmt.Errorf("failed to allocate buffer")) } newbuf := make([]byte, bufsiz) copy(newbuf, buf) for i := len(buf); i < len(newbuf); i++ { newbuf[i] = byte(rem) } return newbuf } // ref. https://github.com/golang/go/blob/c3db64c0f45e8f2d75c5b59401e0fc925701b6f4/src/crypto/tls/conn.go#L279-L324 // // extractPadding returns, in constant time, the length of the padding to remove // from the end of payload. It also returns a byte which is equal to 255 if the // padding was valid and 0 otherwise. See RFC 2246, Section 6.2.3.2. func extractPadding(payload []byte) (toRemove int, good byte) { if len(payload) < 1 { return 0, 0 } paddingLen := payload[len(payload)-1] t := uint(len(payload)) - uint(paddingLen) // if len(payload) > paddingLen then the MSB of t is zero good = byte(int32(^t) >> 31) // The maximum possible padding length plus the actual length field toCheck := 256 // The length of the padded data is public, so we can use an if here toCheck = min(toCheck, len(payload)) for i := 1; i <= toCheck; i++ { t := uint(paddingLen) - uint(i) // if i <= paddingLen then the MSB of t is zero mask := byte(int32(^t) >> 31) b := payload[len(payload)-i] good &^= mask&paddingLen ^ mask&b } // We AND together the bits of good and replicate the result across // all the bits. good &= good << 4 good &= good << 2 good &= good << 1 good = uint8(int8(good) >> 7) // Zero the padding length on error. This ensures any unchecked bytes // are included in the MAC. Otherwise, an attacker that could // distinguish MAC failures from padding failures could mount an attack // similar to POODLE in SSL 3.0: given a good ciphertext that uses a // full block's worth of padding, replace the final block with another // block. If the MAC check passed but the padding check failed, the // last byte of that block decrypted to the block size. // // See also macAndPaddingGood logic below. paddingLen &= good toRemove = int(paddingLen) return } type Hmac struct { blockCipher cipher.Block hash func() hash.Hash keysize int tagsize int integrityKey []byte } type BlockCipherFunc func([]byte) (cipher.Block, error) func New(key []byte, f BlockCipherFunc) (hmac *Hmac, err error) { keysize := len(key) / 2 ikey := key[:keysize] ekey := key[keysize:] bc, ciphererr := f(ekey) if ciphererr != nil { err = fmt.Errorf(`failed to execute block cipher function: %w`, ciphererr) return } var hfunc func() hash.Hash switch keysize { case 16: hfunc = sha256.New case 24: hfunc = sha512.New384 case 32: hfunc = sha512.New default: return nil, fmt.Errorf("unsupported key size %d", keysize) } return &Hmac{ blockCipher: bc, hash: hfunc, integrityKey: ikey, keysize: keysize, tagsize: keysize, // NonceSize, // While investigating GH #207, I stumbled upon another problem where // the computed tags don't match on decrypt. After poking through the // code using a bunch of debug statements, I've finally found out that // tagsize = keysize makes the whole thing work. }, nil } // NonceSize fulfills the crypto.AEAD interface func (c Hmac) NonceSize() int { return NonceSize } // Overhead fulfills the crypto.AEAD interface func (c Hmac) Overhead() int { return c.blockCipher.BlockSize() + c.tagsize } func (c Hmac) ComputeAuthTag(aad, nonce, ciphertext []byte) ([]byte, error) { var buf [8]byte binary.BigEndian.PutUint64(buf[:], uint64(len(aad)*8)) h := hmac.New(c.hash, c.integrityKey) // compute the tag // no need to check errors because Write never returns an error: https://pkg.go.dev/hash#Hash // // > Write (via the embedded io.Writer interface) adds more data to the running hash. // > It never returns an error. h.Write(aad) h.Write(nonce) h.Write(ciphertext) h.Write(buf[:]) s := h.Sum(nil) return s[:c.tagsize], nil } func ensureSize(dst []byte, n int) []byte { // if the dst buffer has enough length just copy the relevant parts to it. // Otherwise create a new slice that's big enough, and operate on that // Note: I think go-jose has a bug in that it checks for cap(), but not len(). ret := dst if diff := n - len(dst); diff > 0 { // dst is not big enough ret = make([]byte, n) copy(ret, dst) } return ret } // Seal fulfills the crypto.AEAD interface func (c Hmac) Seal(dst, nonce, plaintext, data []byte) []byte { ctlen := len(plaintext) bufsiz := ctlen + c.Overhead() mbs := maxBufSize.Load() if int64(bufsiz) > mbs { panic(fmt.Errorf("failed to allocate buffer")) } ciphertext := make([]byte, bufsiz)[:ctlen] copy(ciphertext, plaintext) ciphertext = pad(ciphertext, c.blockCipher.BlockSize()) cbc := cipher.NewCBCEncrypter(c.blockCipher, nonce) cbc.CryptBlocks(ciphertext, ciphertext) authtag, err := c.ComputeAuthTag(data, nonce, ciphertext) if err != nil { // Hmac implements cipher.AEAD interface. Seal can't return error. // But currently it never reach here because of Hmac.ComputeAuthTag doesn't return error. panic(fmt.Errorf("failed to seal on hmac: %v", err)) } retlen := len(dst) + len(ciphertext) + len(authtag) ret := ensureSize(dst, retlen) out := ret[len(dst):] n := copy(out, ciphertext) copy(out[n:], authtag) return ret } // Open fulfills the crypto.AEAD interface func (c Hmac) Open(dst, nonce, ciphertext, data []byte) ([]byte, error) { if len(ciphertext) < c.keysize { return nil, fmt.Errorf(`invalid ciphertext (too short)`) } tagOffset := len(ciphertext) - c.tagsize if tagOffset%c.blockCipher.BlockSize() != 0 { return nil, fmt.Errorf( "invalid ciphertext (invalid length: %d %% %d != 0)", tagOffset, c.blockCipher.BlockSize(), ) } tag := ciphertext[tagOffset:] ciphertext = ciphertext[:tagOffset] expectedTag, err := c.ComputeAuthTag(data, nonce, ciphertext[:tagOffset]) if err != nil { return nil, fmt.Errorf(`failed to compute auth tag: %w`, err) } cbc := cipher.NewCBCDecrypter(c.blockCipher, nonce) buf := pool.ByteSlice().GetCapacity(tagOffset) defer pool.ByteSlice().Put(buf) buf = buf[:tagOffset] cbc.CryptBlocks(buf, ciphertext) toRemove, good := extractPadding(buf) cmp := subtle.ConstantTimeCompare(expectedTag, tag) & int(good) if cmp != 1 { return nil, errors.New(`invalid ciphertext`) } plaintext := buf[:len(buf)-toRemove] ret := ensureSize(dst, len(plaintext)) out := ret[len(dst):] copy(out, plaintext) return ret, nil } golang-github-lestrrat-go-jwx-3.0.13/jwe/internal/aescbc/aescbc_test.go000066400000000000000000000037061515060566400260410ustar00rootroot00000000000000package aescbc import ( "crypto/aes" "testing" "github.com/stretchr/testify/require" ) func TestVectorsAESCBC128(t *testing.T) { // Source: http://tools.ietf.org/html/draft-ietf-jose-json-web-encryption-29#appendix-A.2 plaintext := []byte{ 76, 105, 118, 101, 32, 108, 111, 110, 103, 32, 97, 110, 100, 32, 112, 114, 111, 115, 112, 101, 114, 46} aad := []byte{ 101, 121, 74, 104, 98, 71, 99, 105, 79, 105, 74, 83, 85, 48, 69, 120, 88, 122, 85, 105, 76, 67, 74, 108, 98, 109, 77, 105, 79, 105, 74, 66, 77, 84, 73, 52, 81, 48, 74, 68, 76, 85, 104, 84, 77, 106, 85, 50, 73, 110, 48} ciphertext := []byte{ 40, 57, 83, 181, 119, 33, 133, 148, 198, 185, 243, 24, 152, 230, 6, 75, 129, 223, 127, 19, 210, 82, 183, 230, 168, 33, 215, 104, 143, 112, 56, 102} authtag := []byte{ 246, 17, 244, 190, 4, 95, 98, 3, 231, 0, 115, 157, 242, 203, 100, 191} key := []byte{ 4, 211, 31, 197, 84, 157, 252, 254, 11, 100, 157, 250, 63, 170, 106, 206, 107, 124, 212, 45, 111, 107, 9, 219, 200, 177, 0, 240, 143, 156, 44, 207} nonce := []byte{ 3, 22, 60, 12, 43, 67, 104, 105, 108, 108, 105, 99, 111, 116, 104, 101} enc, err := New(key, aes.NewCipher) require.NoError(t, err, "aescbc.New") out := enc.Seal(nil, nonce, plaintext, aad) require.NoError(t, err, "enc.Seal") require.Equal(t, ciphertext, out[:len(out)-enc.keysize], "Ciphertext tag should match") require.Equal(t, authtag, out[len(out)-enc.keysize:], "Auth tag should match") out, err = enc.Open(nil, nonce, out, aad) require.NoError(t, err, "Open should succeed") require.Equal(t, plaintext, out, "Open should get us original text") } func TestPad(t *testing.T) { for i := range 256 { buf := make([]byte, i) pb := pad(buf, 16) require.Equal(t, len(pb)%16, 0, "pb should be multiple of 16") toRemove, good := extractPadding(pb) require.Equal(t, 1, int(good)&1, "padding should be good") pb = pb[:len(pb)-toRemove] require.Len(t, pb, i, "Unpad should result in len = %d", i) } } golang-github-lestrrat-go-jwx-3.0.13/jwe/internal/cipher/000077500000000000000000000000001515060566400232575ustar00rootroot00000000000000golang-github-lestrrat-go-jwx-3.0.13/jwe/internal/cipher/BUILD.bazel000066400000000000000000000013111515060566400251310ustar00rootroot00000000000000load("@rules_go//go:def.bzl", "go_library", "go_test") go_library( name = "cipher", srcs = [ "cipher.go", "interface.go", ], importpath = "github.com/lestrrat-go/jwx/v3/jwe/internal/cipher", visibility = ["//:__subpackages__"], deps = [ "//jwa", "//jwe/internal/aescbc", "//jwe/internal/keygen", "//internal/tokens", ], ) go_test( name = "cipher_test", srcs = ["cipher_test.go"], deps = [ ":cipher", "//jwa", "//internal/tokens", "@com_github_stretchr_testify//require", ], ) alias( name = "go_default_library", actual = ":cipher", visibility = ["//jwe:__subpackages__"], ) golang-github-lestrrat-go-jwx-3.0.13/jwe/internal/cipher/cipher.go000066400000000000000000000076711515060566400250730ustar00rootroot00000000000000package cipher import ( "crypto/aes" "crypto/cipher" "fmt" "github.com/lestrrat-go/jwx/v3/internal/tokens" "github.com/lestrrat-go/jwx/v3/jwe/internal/aescbc" "github.com/lestrrat-go/jwx/v3/jwe/internal/keygen" ) var gcm = &gcmFetcher{} var cbc = &cbcFetcher{} func (f gcmFetcher) Fetch(key []byte, size int) (cipher.AEAD, error) { if len(key) != size { return nil, fmt.Errorf(`key size (%d) does not match expected key size (%d)`, len(key), size) } aescipher, err := aes.NewCipher(key) if err != nil { return nil, fmt.Errorf(`cipher: failed to create AES cipher for GCM: %w`, err) } aead, err := cipher.NewGCM(aescipher) if err != nil { return nil, fmt.Errorf(`failed to create GCM for cipher: %w`, err) } return aead, nil } func (f cbcFetcher) Fetch(key []byte, size int) (cipher.AEAD, error) { if len(key) != size { return nil, fmt.Errorf(`key size (%d) does not match expected key size (%d)`, len(key), size) } aead, err := aescbc.New(key, aes.NewCipher) if err != nil { return nil, fmt.Errorf(`cipher: failed to create AES cipher for CBC: %w`, err) } return aead, nil } func (c AesContentCipher) KeySize() int { return c.keysize } func (c AesContentCipher) TagSize() int { return c.tagsize } func NewAES(alg string) (*AesContentCipher, error) { var keysize int var tagsize int var fetcher Fetcher switch alg { case tokens.A128GCM: keysize = 16 tagsize = 16 fetcher = gcm case tokens.A192GCM: keysize = 24 tagsize = 16 fetcher = gcm case tokens.A256GCM: keysize = 32 tagsize = 16 fetcher = gcm case tokens.A128CBC_HS256: tagsize = 16 keysize = tagsize * 2 fetcher = cbc case tokens.A192CBC_HS384: tagsize = 24 keysize = tagsize * 2 fetcher = cbc case tokens.A256CBC_HS512: tagsize = 32 keysize = tagsize * 2 fetcher = cbc default: return nil, fmt.Errorf("failed to create AES content cipher: invalid algorithm (%s)", alg) } return &AesContentCipher{ keysize: keysize, tagsize: tagsize, fetch: fetcher, }, nil } func (c AesContentCipher) Encrypt(cek, plaintext, aad []byte) (iv, ciphertxt, tag []byte, err error) { var aead cipher.AEAD aead, err = c.fetch.Fetch(cek, c.keysize) if err != nil { return nil, nil, nil, fmt.Errorf(`failed to fetch AEAD: %w`, err) } // Seal may panic (argh!), so protect ourselves from that defer func() { if e := recover(); e != nil { switch e := e.(type) { case error: err = e default: err = fmt.Errorf("%s", e) } err = fmt.Errorf(`failed to encrypt: %w`, err) } }() if c.NonceGenerator != nil { iv, err = c.NonceGenerator(aead.NonceSize()) if err != nil { return nil, nil, nil, fmt.Errorf(`failed to generate nonce: %w`, err) } } else { bs, err := keygen.Random(aead.NonceSize()) if err != nil { return nil, nil, nil, fmt.Errorf(`failed to generate random nonce: %w`, err) } iv = bs.Bytes() } combined := aead.Seal(nil, iv, plaintext, aad) tagoffset := len(combined) - c.TagSize() if tagoffset < 0 { panic(fmt.Sprintf("tag offset is less than 0 (combined len = %d, tagsize = %d)", len(combined), c.TagSize())) } tag = combined[tagoffset:] ciphertxt = make([]byte, tagoffset) copy(ciphertxt, combined[:tagoffset]) return } func (c AesContentCipher) Decrypt(cek, iv, ciphertxt, tag, aad []byte) (plaintext []byte, err error) { aead, err := c.fetch.Fetch(cek, c.keysize) if err != nil { return nil, fmt.Errorf(`failed to fetch AEAD data: %w`, err) } // Open may panic (argh!), so protect ourselves from that defer func() { if e := recover(); e != nil { switch e := e.(type) { case error: err = e default: err = fmt.Errorf(`%s`, e) } err = fmt.Errorf(`failed to decrypt: %w`, err) return } }() combined := make([]byte, len(ciphertxt)+len(tag)) copy(combined, ciphertxt) copy(combined[len(ciphertxt):], tag) buf, aeaderr := aead.Open(nil, iv, combined, aad) if aeaderr != nil { err = fmt.Errorf(`aead.Open failed: %w`, aeaderr) return } plaintext = buf return } golang-github-lestrrat-go-jwx-3.0.13/jwe/internal/cipher/cipher_test.go000066400000000000000000000010171515060566400261160ustar00rootroot00000000000000package cipher_test import ( "testing" "github.com/lestrrat-go/jwx/v3/internal/tokens" "github.com/lestrrat-go/jwx/v3/jwe/internal/cipher" "github.com/stretchr/testify/require" ) func TestAES(t *testing.T) { algs := []string{ tokens.A128GCM, tokens.A192GCM, tokens.A256GCM, tokens.A128CBC_HS256, tokens.A192CBC_HS384, tokens.A256CBC_HS512, } for _, alg := range algs { c, err := cipher.NewAES(alg) require.NoError(t, err, "BuildCipher for %s succeeds", alg) t.Logf("keysize = %d", c.KeySize()) } } golang-github-lestrrat-go-jwx-3.0.13/jwe/internal/cipher/interface.go000066400000000000000000000012361515060566400255500ustar00rootroot00000000000000package cipher import ( "crypto/cipher" ) const ( TagSize = 16 ) // ContentCipher knows how to encrypt/decrypt the content given a content // encryption key and other data type ContentCipher interface { KeySize() int Encrypt(cek, aad, plaintext []byte) ([]byte, []byte, []byte, error) Decrypt(cek, iv, aad, ciphertext, tag []byte) ([]byte, error) } type Fetcher interface { Fetch([]byte, int) (cipher.AEAD, error) } type gcmFetcher struct{} type cbcFetcher struct{} // AesContentCipher represents a cipher based on AES type AesContentCipher struct { NonceGenerator func(int) ([]byte, error) fetch Fetcher keysize int tagsize int } golang-github-lestrrat-go-jwx-3.0.13/jwe/internal/concatkdf/000077500000000000000000000000001515060566400237415ustar00rootroot00000000000000golang-github-lestrrat-go-jwx-3.0.13/jwe/internal/concatkdf/BUILD.bazel000066400000000000000000000010341515060566400256150ustar00rootroot00000000000000load("@rules_go//go:def.bzl", "go_library", "go_test") go_library( name = "concatkdf", srcs = ["concatkdf.go"], importpath = "github.com/lestrrat-go/jwx/v3/jwe/internal/concatkdf", visibility = ["//:__subpackages__"], ) go_test( name = "concatkdf_test", srcs = ["concatkdf_test.go"], embed = [":concatkdf"], deps = [ "//jwa", "@com_github_stretchr_testify//require", ], ) alias( name = "go_default_library", actual = ":concatkdf", visibility = ["//jwe:__subpackages__"], ) golang-github-lestrrat-go-jwx-3.0.13/jwe/internal/concatkdf/concatkdf.go000066400000000000000000000026041515060566400262260ustar00rootroot00000000000000package concatkdf import ( "crypto" "encoding/binary" "fmt" ) type KDF struct { buf []byte otherinfo []byte z []byte hash crypto.Hash } func ndata(src []byte) []byte { buf := make([]byte, 4+len(src)) binary.BigEndian.PutUint32(buf, uint32(len(src))) copy(buf[4:], src) return buf } func New(hash crypto.Hash, alg, Z, apu, apv, pubinfo, privinfo []byte) *KDF { algbuf := ndata(alg) apubuf := ndata(apu) apvbuf := ndata(apv) concat := make([]byte, len(algbuf)+len(apubuf)+len(apvbuf)+len(pubinfo)+len(privinfo)) n := copy(concat, algbuf) n += copy(concat[n:], apubuf) n += copy(concat[n:], apvbuf) n += copy(concat[n:], pubinfo) copy(concat[n:], privinfo) return &KDF{ hash: hash, otherinfo: concat, z: Z, } } func (k *KDF) Read(out []byte) (int, error) { var round uint32 = 1 h := k.hash.New() for len(out) > len(k.buf) { h.Reset() if err := binary.Write(h, binary.BigEndian, round); err != nil { return 0, fmt.Errorf(`failed to write round using kdf: %w`, err) } if _, err := h.Write(k.z); err != nil { return 0, fmt.Errorf(`failed to write z using kdf: %w`, err) } if _, err := h.Write(k.otherinfo); err != nil { return 0, fmt.Errorf(`failed to write other info using kdf: %w`, err) } k.buf = append(k.buf, h.Sum(nil)...) round++ } n := copy(out, k.buf[:len(out)]) k.buf = k.buf[len(out):] return n, nil } golang-github-lestrrat-go-jwx-3.0.13/jwe/internal/concatkdf/concatkdf_test.go000066400000000000000000000020351515060566400272630ustar00rootroot00000000000000package concatkdf import ( "crypto" "testing" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/stretchr/testify/require" ) // https://tools.ietf.org/html/rfc7518#appendix-C func TestAppendix(t *testing.T) { z := []byte{158, 86, 217, 29, 129, 113, 53, 211, 114, 131, 66, 131, 191, 132, 38, 156, 251, 49, 110, 163, 218, 128, 106, 72, 246, 218, 167, 121, 140, 254, 144, 196} alg := []byte(jwa.A128GCM().String()) apu := []byte{65, 108, 105, 99, 101} apv := []byte{66, 111, 98} pub := []byte{0, 0, 0, 128} priv := []byte(nil) expected := []byte{86, 170, 141, 234, 248, 35, 109, 32, 92, 34, 40, 205, 113, 167, 16, 26} kdf := New(crypto.SHA256, alg, z, apu, apv, pub, priv) out := make([]byte, 16) // 128bits n, err := kdf.Read(out[:5]) require.Equal(t, 5, n, "first read bytes matches") require.NoError(t, err, "first read successful") n, err = kdf.Read(out[5:]) require.Equal(t, 11, n, "second read bytes matches") require.NoError(t, err, "second read successful") require.Equal(t, expected, out, "generated value matches") } golang-github-lestrrat-go-jwx-3.0.13/jwe/internal/content_crypt/000077500000000000000000000000001515060566400247005ustar00rootroot00000000000000golang-github-lestrrat-go-jwx-3.0.13/jwe/internal/content_crypt/BUILD.bazel000066400000000000000000000007211515060566400265560ustar00rootroot00000000000000load("@rules_go//go:def.bzl", "go_library") go_library( name = "content_crypt", srcs = [ "content_crypt.go", "interface.go", ], importpath = "github.com/lestrrat-go/jwx/v3/jwe/internal/content_crypt", visibility = ["//:__subpackages__"], deps = [ "//jwa", "//jwe/internal/cipher", ], ) alias( name = "go_default_library", actual = ":content_crypt", visibility = ["//jwe:__subpackages__"], ) golang-github-lestrrat-go-jwx-3.0.13/jwe/internal/content_crypt/content_crypt.go000066400000000000000000000017631515060566400301310ustar00rootroot00000000000000package content_crypt //nolint:golint import ( "fmt" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/lestrrat-go/jwx/v3/jwe/internal/cipher" ) func (c Generic) Algorithm() jwa.ContentEncryptionAlgorithm { return c.alg } func (c Generic) Encrypt(cek, plaintext, aad []byte) ([]byte, []byte, []byte, error) { iv, encrypted, tag, err := c.cipher.Encrypt(cek, plaintext, aad) if err != nil { return nil, nil, nil, fmt.Errorf(`failed to crypt content: %w`, err) } return iv, encrypted, tag, nil } func (c Generic) Decrypt(cek, iv, ciphertext, tag, aad []byte) ([]byte, error) { return c.cipher.Decrypt(cek, iv, ciphertext, tag, aad) } func NewGeneric(alg jwa.ContentEncryptionAlgorithm) (*Generic, error) { c, err := cipher.NewAES(alg.String()) if err != nil { return nil, fmt.Errorf(`aes crypt: failed to create content cipher: %w`, err) } return &Generic{ alg: alg, cipher: c, keysize: c.KeySize(), tagsize: 16, }, nil } func (c Generic) KeySize() int { return c.keysize } golang-github-lestrrat-go-jwx-3.0.13/jwe/internal/content_crypt/interface.go000066400000000000000000000007321515060566400271710ustar00rootroot00000000000000package content_crypt //nolint:golint import ( "github.com/lestrrat-go/jwx/v3/jwa" "github.com/lestrrat-go/jwx/v3/jwe/internal/cipher" ) // Generic encrypts a message by applying all the necessary // modifications to the keys and the contents type Generic struct { alg jwa.ContentEncryptionAlgorithm keysize int tagsize int cipher cipher.ContentCipher } type Cipher interface { Decrypt([]byte, []byte, []byte, []byte, []byte) ([]byte, error) KeySize() int } golang-github-lestrrat-go-jwx-3.0.13/jwe/internal/keygen/000077500000000000000000000000001515060566400232675ustar00rootroot00000000000000golang-github-lestrrat-go-jwx-3.0.13/jwe/internal/keygen/BUILD.bazel000066400000000000000000000010031515060566400251370ustar00rootroot00000000000000load("@rules_go//go:def.bzl", "go_library") go_library( name = "keygen", srcs = [ "interface.go", "keygen.go", ], importpath = "github.com/lestrrat-go/jwx/v3/jwe/internal/keygen", visibility = ["//:__subpackages__"], deps = [ "//internal/ecutil", "//jwa", "//jwe/internal/concatkdf", "//internal/tokens", "//jwk", ], ) alias( name = "go_default_library", actual = ":keygen", visibility = ["//jwe:__subpackages__"], ) golang-github-lestrrat-go-jwx-3.0.13/jwe/internal/keygen/interface.go000066400000000000000000000016031515060566400255560ustar00rootroot00000000000000package keygen // ByteKey is a generated key that only has the key's byte buffer // as its instance data. If a key needs to do more, such as providing // values to be set in a JWE header, that key type wraps a ByteKey type ByteKey []byte // ByteWithECPublicKey holds the EC private key that generated // the key along with the key itself. This is required to set the // proper values in the JWE headers type ByteWithECPublicKey struct { ByteKey PublicKey any } type ByteWithIVAndTag struct { ByteKey IV []byte Tag []byte } type ByteWithSaltAndCount struct { ByteKey Salt []byte Count int } // ByteSource is an interface for things that return a byte sequence. // This is used for KeyGenerator so that the result of computations can // carry more than just the generate byte sequence. type ByteSource interface { Bytes() []byte } type Setter interface { Set(string, any) error } golang-github-lestrrat-go-jwx-3.0.13/jwe/internal/keygen/keygen.go000066400000000000000000000073731515060566400251120ustar00rootroot00000000000000package keygen import ( "crypto" "crypto/ecdh" "crypto/ecdsa" "crypto/rand" "encoding/binary" "fmt" "io" "github.com/lestrrat-go/jwx/v3/internal/ecutil" "github.com/lestrrat-go/jwx/v3/internal/tokens" "github.com/lestrrat-go/jwx/v3/jwe/internal/concatkdf" "github.com/lestrrat-go/jwx/v3/jwk" ) // Bytes returns the byte from this ByteKey func (k ByteKey) Bytes() []byte { return []byte(k) } func Random(n int) (ByteSource, error) { buf := make([]byte, n) if _, err := io.ReadFull(rand.Reader, buf); err != nil { return nil, fmt.Errorf(`failed to read from rand.Reader: %w`, err) } return ByteKey(buf), nil } // Ecdhes generates a new key using ECDH-ES func Ecdhes(alg string, enc string, keysize int, pubkey *ecdsa.PublicKey, apu, apv []byte) (ByteSource, error) { priv, err := ecdsa.GenerateKey(pubkey.Curve, rand.Reader) if err != nil { return nil, fmt.Errorf(`failed to generate key for ECDH-ES: %w`, err) } var algorithm string if alg == tokens.ECDH_ES { algorithm = enc } else { algorithm = alg } pubinfo := make([]byte, 4) binary.BigEndian.PutUint32(pubinfo, uint32(keysize)*8) if !priv.PublicKey.Curve.IsOnCurve(pubkey.X, pubkey.Y) { return nil, fmt.Errorf(`public key used does not contain a point (X,Y) on the curve`) } z, _ := priv.PublicKey.Curve.ScalarMult(pubkey.X, pubkey.Y, priv.D.Bytes()) zBytes := ecutil.AllocECPointBuffer(z, priv.PublicKey.Curve) defer ecutil.ReleaseECPointBuffer(zBytes) kdf := concatkdf.New(crypto.SHA256, []byte(algorithm), zBytes, apu, apv, pubinfo, []byte{}) kek := make([]byte, keysize) if _, err := kdf.Read(kek); err != nil { return nil, fmt.Errorf(`failed to read kdf: %w`, err) } return ByteWithECPublicKey{ PublicKey: &priv.PublicKey, ByteKey: ByteKey(kek), }, nil } // X25519 generates a new key using ECDH-ES with X25519 func X25519(alg string, enc string, keysize int, pubkey *ecdh.PublicKey) (ByteSource, error) { priv, err := ecdh.X25519().GenerateKey(rand.Reader) if err != nil { return nil, fmt.Errorf(`failed to generate key for X25519: %w`, err) } var algorithm string if alg == tokens.ECDH_ES { algorithm = enc } else { algorithm = alg } pubinfo := make([]byte, 4) binary.BigEndian.PutUint32(pubinfo, uint32(keysize)*8) zBytes, err := priv.ECDH(pubkey) if err != nil { return nil, fmt.Errorf(`failed to compute Z: %w`, err) } kdf := concatkdf.New(crypto.SHA256, []byte(algorithm), zBytes, []byte{}, []byte{}, pubinfo, []byte{}) kek := make([]byte, keysize) if _, err := kdf.Read(kek); err != nil { return nil, fmt.Errorf(`failed to read kdf: %w`, err) } return ByteWithECPublicKey{ PublicKey: priv.PublicKey(), ByteKey: ByteKey(kek), }, nil } // HeaderPopulate populates the header with the required EC-DSA public key // information ('epk' key) func (k ByteWithECPublicKey) Populate(h Setter) error { key, err := jwk.Import(k.PublicKey) if err != nil { return fmt.Errorf(`failed to create JWK: %w`, err) } if err := h.Set("epk", key); err != nil { return fmt.Errorf(`failed to write header: %w`, err) } return nil } // HeaderPopulate populates the header with the required AES GCM // parameters ('iv' and 'tag') func (k ByteWithIVAndTag) Populate(h Setter) error { if err := h.Set("iv", k.IV); err != nil { return fmt.Errorf(`failed to write header: %w`, err) } if err := h.Set("tag", k.Tag); err != nil { return fmt.Errorf(`failed to write header: %w`, err) } return nil } // HeaderPopulate populates the header with the required PBES2 // parameters ('p2s' and 'p2c') func (k ByteWithSaltAndCount) Populate(h Setter) error { if err := h.Set("p2c", k.Count); err != nil { return fmt.Errorf(`failed to write header: %w`, err) } if err := h.Set("p2s", k.Salt); err != nil { return fmt.Errorf(`failed to write header: %w`, err) } return nil } golang-github-lestrrat-go-jwx-3.0.13/jwe/io.go000066400000000000000000000011521515060566400211260ustar00rootroot00000000000000// Code generated by tools/cmd/genreadfile/main.go. DO NOT EDIT. package jwe import ( "fmt" "io/fs" "os" ) type sysFS struct{} func (sysFS) Open(path string) (fs.File, error) { return os.Open(path) } func ReadFile(path string, options ...ReadFileOption) (*Message, error) { var srcFS fs.FS = sysFS{} for _, option := range options { switch option.Ident() { case identFS{}: if err := option.Value(&srcFS); err != nil { return nil, fmt.Errorf("failed to set fs.FS: %w", err) } } } f, err := srcFS.Open(path) if err != nil { return nil, err } defer f.Close() return ParseReader(f) } golang-github-lestrrat-go-jwx-3.0.13/jwe/jwe.go000066400000000000000000001051561515060566400213150ustar00rootroot00000000000000//go:generate ../tools/cmd/genjwe.sh // Package jwe implements JWE as described in https://tools.ietf.org/html/rfc7516 package jwe // #region imports import ( "bytes" "context" "crypto/ecdsa" "errors" "fmt" "io" "sync" "github.com/lestrrat-go/blackmagic" "github.com/lestrrat-go/jwx/v3/internal/base64" "github.com/lestrrat-go/jwx/v3/internal/json" "github.com/lestrrat-go/jwx/v3/internal/pool" "github.com/lestrrat-go/jwx/v3/internal/tokens" "github.com/lestrrat-go/jwx/v3/jwk" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/lestrrat-go/jwx/v3/jwe/internal/aescbc" "github.com/lestrrat-go/jwx/v3/jwe/internal/content_crypt" "github.com/lestrrat-go/jwx/v3/jwe/internal/keygen" ) // #region globals var muSettings sync.RWMutex var maxPBES2Count = 10000 var maxDecompressBufferSize int64 = 10 * 1024 * 1024 // 10MB func Settings(options ...GlobalOption) { muSettings.Lock() defer muSettings.Unlock() for _, option := range options { switch option.Ident() { case identMaxPBES2Count{}: if err := option.Value(&maxPBES2Count); err != nil { panic(fmt.Sprintf("jwe.Settings: value for option WithMaxPBES2Count must be an int: %s", err)) } case identMaxDecompressBufferSize{}: if err := option.Value(&maxDecompressBufferSize); err != nil { panic(fmt.Sprintf("jwe.Settings: value for option WithMaxDecompressBufferSize must be an int64: %s", err)) } case identCBCBufferSize{}: var v int64 if err := option.Value(&v); err != nil { panic(fmt.Sprintf("jwe.Settings: value for option WithCBCBufferSize must be an int64: %s", err)) } aescbc.SetMaxBufferSize(v) } } } const ( fmtInvalid = iota fmtCompact fmtJSON fmtJSONPretty fmtMax ) var _ = fmtInvalid var _ = fmtMax var registry = json.NewRegistry() type recipientBuilder struct { alg jwa.KeyEncryptionAlgorithm key any headers Headers } func (b *recipientBuilder) Build(r Recipient, cek []byte, calg jwa.ContentEncryptionAlgorithm, _ *content_crypt.Generic) ([]byte, error) { // we need the raw key for later use rawKey := b.key var keyID string if ke, ok := b.key.(KeyEncrypter); ok { if kider, ok := ke.(KeyIDer); ok { if v, ok := kider.KeyID(); ok { keyID = v } } } else if jwkKey, ok := b.key.(jwk.Key); ok { // Meanwhile, grab the kid as well if v, ok := jwkKey.KeyID(); ok { keyID = v } var raw any if err := jwk.Export(jwkKey, &raw); err != nil { return nil, fmt.Errorf(`jwe.Encrypt: recipientBuilder: failed to retrieve raw key out of %T: %w`, b.key, err) } rawKey = raw } // Extract ECDH-ES specific parameters if needed. var apu, apv []byte hdr := b.headers if hdr == nil { hdr = NewHeaders() } if val, ok := hdr.AgreementPartyUInfo(); ok { apu = val } if val, ok := hdr.AgreementPartyVInfo(); ok { apv = val } // Create the encrypter using the new jwebb pattern enc, err := newEncrypter(b.alg, calg, b.key, rawKey, apu, apv) if err != nil { return nil, fmt.Errorf(`jwe.Encrypt: recipientBuilder: failed to create encrypter: %w`, err) } _ = r.SetHeaders(hdr) // Populate headers with stuff that we automatically set if err := hdr.Set(AlgorithmKey, b.alg); err != nil { return nil, fmt.Errorf(`failed to set header: %w`, err) } if keyID != "" { if err := hdr.Set(KeyIDKey, keyID); err != nil { return nil, fmt.Errorf(`failed to set header: %w`, err) } } // Handle the encrypted key var rawCEK []byte enckey, err := enc.EncryptKey(cek) if err != nil { return nil, fmt.Errorf(`failed to encrypt key: %w`, err) } if b.alg == jwa.ECDH_ES() || b.alg == jwa.DIRECT() { rawCEK = enckey.Bytes() } else { if err := r.SetEncryptedKey(enckey.Bytes()); err != nil { return nil, fmt.Errorf(`failed to set encrypted key: %w`, err) } } // finally, anything specific should go here if hp, ok := enckey.(populater); ok { if err := hp.Populate(hdr); err != nil { return nil, fmt.Errorf(`failed to populate: %w`, err) } } return rawCEK, nil } // Encrypt generates a JWE message for the given payload and returns // it in serialized form, which can be in either compact or // JSON format. Default is compact. When JSON format is specified and // there is only one recipient, the resulting serialization is // automatically converted to flattened JSON serialization format. // // You must pass at least one key to `jwe.Encrypt()` by using `jwe.WithKey()` // option. // // jwe.Encrypt(payload, jwe.WithKey(alg, key)) // jwe.Encrypt(payload, jws.WithJSON(), jws.WithKey(alg1, key1), jws.WithKey(alg2, key2)) // // Note that in the second example the `jws.WithJSON()` option is // specified as well. This is because the compact serialization // format does not support multiple recipients, and users must // specifically ask for the JSON serialization format. // // Read the documentation for `jwe.WithKey()` to learn more about the // possible values that can be used for `alg` and `key`. // // Look for options that return `jwe.EncryptOption` or `jws.EncryptDecryptOption` // for a complete list of options that can be passed to this function. // // As of v3.0.12, users can specify `jwe.WithLegacyHeaderMerging()` to // disable header merging behavior that was the default prior to v3.0.12. // Read the documentation for `jwe.WithLegacyHeaderMerging()` for more information. func Encrypt(payload []byte, options ...EncryptOption) ([]byte, error) { ec := encryptContextPool.Get() defer encryptContextPool.Put(ec) if err := ec.ProcessOptions(options); err != nil { return nil, encryptError{fmt.Errorf(`jwe.Encrypt: failed to process options: %w`, err)} } ret, err := ec.EncryptMessage(payload, nil) if err != nil { return nil, encryptError{fmt.Errorf(`jwe.Encrypt: %w`, err)} } return ret, nil } // EncryptStatic is exactly like Encrypt, except it accepts a static // content encryption key (CEK). It is separated out from the main // Encrypt function such that the latter does not accidentally use a static // CEK. // // DO NOT attempt to use this function unless you completely understand the // security implications to using static CEKs. You have been warned. // // This function is currently considered EXPERIMENTAL, and is subject to // future changes across minor/micro versions. func EncryptStatic(payload, cek []byte, options ...EncryptOption) ([]byte, error) { if len(cek) <= 0 { return nil, encryptError{fmt.Errorf(`jwe.EncryptStatic: empty CEK`)} } ec := encryptContextPool.Get() defer encryptContextPool.Put(ec) if err := ec.ProcessOptions(options); err != nil { return nil, encryptError{fmt.Errorf(`jwe.EncryptStatic: failed to process options: %w`, err)} } ret, err := ec.EncryptMessage(payload, cek) if err != nil { return nil, encryptError{fmt.Errorf(`jwe.EncryptStatic: %w`, err)} } return ret, nil } // decryptContext holds the state during JWE decryption, similar to JWS verifyContext type decryptContext struct { keyProviders []KeyProvider keyUsed any cek *[]byte dst *Message maxDecompressBufferSize int64 //nolint:containedctx ctx context.Context } var decryptContextPool = pool.New(allocDecryptContext, freeDecryptContext) func allocDecryptContext() *decryptContext { return &decryptContext{ ctx: context.Background(), } } func freeDecryptContext(dc *decryptContext) *decryptContext { dc.keyProviders = dc.keyProviders[:0] dc.keyUsed = nil dc.cek = nil dc.dst = nil dc.maxDecompressBufferSize = 0 dc.ctx = context.Background() return dc } func (dc *decryptContext) ProcessOptions(options []DecryptOption) error { // Set default max decompress buffer size muSettings.RLock() dc.maxDecompressBufferSize = maxDecompressBufferSize muSettings.RUnlock() for _, option := range options { switch option.Ident() { case identMessage{}: if err := option.Value(&dc.dst); err != nil { return fmt.Errorf("jwe.decrypt: WithMessage must be a *jwe.Message: %w", err) } case identKeyProvider{}: var kp KeyProvider if err := option.Value(&kp); err != nil { return fmt.Errorf("jwe.decrypt: WithKeyProvider must be a KeyProvider: %w", err) } dc.keyProviders = append(dc.keyProviders, kp) case identKeyUsed{}: if err := option.Value(&dc.keyUsed); err != nil { return fmt.Errorf("jwe.decrypt: WithKeyUsed must be an any: %w", err) } case identKey{}: var pair *withKey if err := option.Value(&pair); err != nil { return fmt.Errorf("jwe.decrypt: WithKey must be a *withKey: %w", err) } alg, ok := pair.alg.(jwa.KeyEncryptionAlgorithm) if !ok { return fmt.Errorf("jwe.decrypt: WithKey() option must be specified using jwa.KeyEncryptionAlgorithm (got %T)", pair.alg) } dc.keyProviders = append(dc.keyProviders, &staticKeyProvider{alg: alg, key: pair.key}) case identCEK{}: if err := option.Value(&dc.cek); err != nil { return fmt.Errorf("jwe.decrypt: WithCEK must be a *[]byte: %w", err) } case identMaxDecompressBufferSize{}: if err := option.Value(&dc.maxDecompressBufferSize); err != nil { return fmt.Errorf("jwe.decrypt: WithMaxDecompressBufferSize must be int64: %w", err) } case identContext{}: if err := option.Value(&dc.ctx); err != nil { return fmt.Errorf("jwe.decrypt: WithContext must be a context.Context: %w", err) } } } if len(dc.keyProviders) < 1 { return fmt.Errorf(`jwe.Decrypt: no key providers have been provided (see jwe.WithKey(), jwe.WithKeySet(), and jwe.WithKeyProvider()`) } return nil } func (dc *decryptContext) DecryptMessage(buf []byte) ([]byte, error) { msg, err := parseJSONOrCompact(buf, true) if err != nil { return nil, fmt.Errorf(`failed to parse buffer for Decrypt: %w`, err) } // Process things that are common to the message h, err := msg.protectedHeaders.Clone() if err != nil { return nil, fmt.Errorf(`failed to copy protected headers: %w`, err) } h, err = h.Merge(msg.unprotectedHeaders) if err != nil { return nil, fmt.Errorf(`failed to merge headers for message decryption: %w`, err) } var aad []byte if aadContainer := msg.authenticatedData; aadContainer != nil { aad = base64.Encode(aadContainer) } var computedAad []byte if len(msg.rawProtectedHeaders) > 0 { computedAad = msg.rawProtectedHeaders } else { // this is probably not required once msg.Decrypt is deprecated var err error computedAad, err = msg.protectedHeaders.Encode() if err != nil { return nil, fmt.Errorf(`failed to encode protected headers: %w`, err) } } // for each recipient, attempt to match the key providers // if we have no recipients, pretend like we only have one recipients := msg.recipients if len(recipients) == 0 { r := NewRecipient() if err := r.SetHeaders(msg.protectedHeaders); err != nil { return nil, fmt.Errorf(`failed to set headers to recipient: %w`, err) } recipients = append(recipients, r) } errs := make([]error, 0, len(recipients)) for _, recipient := range recipients { decrypted, err := dc.tryRecipient(msg, recipient, h, aad, computedAad) if err != nil { errs = append(errs, recipientError{err}) continue } if dc.dst != nil { *dc.dst = *msg dc.dst.rawProtectedHeaders = nil dc.dst.storeProtectedHeaders = false } return decrypted, nil } return nil, fmt.Errorf(`failed to decrypt any of the recipients: %w`, errors.Join(errs...)) } func (dc *decryptContext) tryRecipient(msg *Message, recipient Recipient, protectedHeaders Headers, aad, computedAad []byte) ([]byte, error) { var tried int var lastError error for i, kp := range dc.keyProviders { var sink algKeySink if err := kp.FetchKeys(dc.ctx, &sink, recipient, msg); err != nil { return nil, fmt.Errorf(`key provider %d failed: %w`, i, err) } for _, pair := range sink.list { tried++ // alg is converted here because pair.alg is of type jwa.KeyAlgorithm. // this may seem ugly, but we're trying to avoid declaring separate // structs for `alg jwa.KeyEncryptionAlgorithm` and `alg jwa.SignatureAlgorithm` //nolint:forcetypeassert alg := pair.alg.(jwa.KeyEncryptionAlgorithm) key := pair.key decrypted, err := dc.decryptContent(msg, alg, key, recipient, protectedHeaders, aad, computedAad) if err != nil { lastError = err continue } if dc.keyUsed != nil { if err := blackmagic.AssignIfCompatible(dc.keyUsed, key); err != nil { return nil, fmt.Errorf(`failed to assign used key (%T) to %T: %w`, key, dc.keyUsed, err) } } return decrypted, nil } } 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 (dc *decryptContext) decryptContent(msg *Message, alg jwa.KeyEncryptionAlgorithm, key any, recipient Recipient, protectedHeaders Headers, aad, computedAad []byte) ([]byte, error) { if jwkKey, ok := key.(jwk.Key); ok { var raw any if err := jwk.Export(jwkKey, &raw); err != nil { return nil, fmt.Errorf(`failed to retrieve raw key from %T: %w`, key, err) } key = raw } ce, ok := msg.protectedHeaders.ContentEncryption() if !ok { return nil, fmt.Errorf(`jwe.Decrypt: failed to retrieve content encryption algorithm from protected headers`) } dec := newDecrypter(alg, ce, key). AuthenticatedData(aad). ComputedAuthenticatedData(computedAad). InitializationVector(msg.initializationVector). Tag(msg.tag). CEK(dc.cek) // The "alg" header can be in either protected/unprotected headers. // prefer per-recipient headers (as it might be the case that the algorithm differs // by each recipient), then look at protected headers. var algMatched bool for _, hdr := range []Headers{recipient.Headers(), protectedHeaders} { v, ok := hdr.Algorithm() if !ok { continue } if v == alg { algMatched = true break } // if we found something but didn't match, it's a failure return nil, fmt.Errorf(`jwe.Decrypt: key (%q) and recipient (%q) algorithms do not match`, alg, v) } if !algMatched { return nil, fmt.Errorf(`jwe.Decrypt: failed to find "alg" header in either protected or per-recipient headers`) } h2, err := protectedHeaders.Clone() if err != nil { return nil, fmt.Errorf(`jwe.Decrypt: failed to copy headers (1): %w`, err) } h2, err = h2.Merge(recipient.Headers()) if err != nil { return nil, fmt.Errorf(`failed to copy headers (2): %w`, err) } switch alg { case jwa.ECDH_ES(), jwa.ECDH_ES_A128KW(), jwa.ECDH_ES_A192KW(), jwa.ECDH_ES_A256KW(): var epk any if err := h2.Get(EphemeralPublicKeyKey, &epk); err != nil { return nil, fmt.Errorf(`failed to get 'epk' field: %w`, err) } switch epk := epk.(type) { case jwk.ECDSAPublicKey: var pubkey ecdsa.PublicKey if err := jwk.Export(epk, &pubkey); err != nil { return nil, fmt.Errorf(`failed to get public key: %w`, err) } dec.PublicKey(&pubkey) case jwk.OKPPublicKey: var pubkey any if err := jwk.Export(epk, &pubkey); err != nil { return nil, fmt.Errorf(`failed to get public key: %w`, err) } dec.PublicKey(pubkey) default: return nil, fmt.Errorf("unexpected 'epk' type %T for alg %s", epk, alg) } if apu, ok := h2.AgreementPartyUInfo(); ok && len(apu) > 0 { dec.AgreementPartyUInfo(apu) } if apv, ok := h2.AgreementPartyVInfo(); ok && len(apv) > 0 { dec.AgreementPartyVInfo(apv) } case jwa.A128GCMKW(), jwa.A192GCMKW(), jwa.A256GCMKW(): var ivB64 string if err := h2.Get(InitializationVectorKey, &ivB64); err == nil { iv, err := base64.DecodeString(ivB64) if err != nil { return nil, fmt.Errorf(`failed to b64-decode 'iv': %w`, err) } dec.KeyInitializationVector(iv) } var tagB64 string if err := h2.Get(TagKey, &tagB64); err == nil { tag, err := base64.DecodeString(tagB64) if err != nil { return nil, fmt.Errorf(`failed to b64-decode 'tag': %w`, err) } dec.KeyTag(tag) } case jwa.PBES2_HS256_A128KW(), jwa.PBES2_HS384_A192KW(), jwa.PBES2_HS512_A256KW(): var saltB64 string if err := h2.Get(SaltKey, &saltB64); err != nil { return nil, fmt.Errorf(`failed to get %q field`, SaltKey) } // check if WithUseNumber is effective, because it will change the // type of the underlying value (#1140) var countFlt float64 if json.UseNumber() { var count json.Number if err := h2.Get(CountKey, &count); err != nil { return nil, fmt.Errorf(`failed to get %q field`, CountKey) } v, err := count.Float64() if err != nil { return nil, fmt.Errorf("failed to convert 'p2c' to float64: %w", err) } countFlt = v } else { var count float64 if err := h2.Get(CountKey, &count); err != nil { return nil, fmt.Errorf(`failed to get %q field`, CountKey) } countFlt = count } muSettings.RLock() maxCount := maxPBES2Count muSettings.RUnlock() if countFlt > float64(maxCount) { return nil, fmt.Errorf("invalid 'p2c' value") } salt, err := base64.DecodeString(saltB64) if err != nil { return nil, fmt.Errorf(`failed to b64-decode 'salt': %w`, err) } dec.KeySalt(salt) dec.KeyCount(int(countFlt)) } plaintext, err := dec.Decrypt(recipient, msg.cipherText, msg) if err != nil { return nil, fmt.Errorf(`jwe.Decrypt: decryption failed: %w`, err) } if v, ok := h2.Compression(); ok && v == jwa.Deflate() { buf, err := uncompress(plaintext, dc.maxDecompressBufferSize) if err != nil { return nil, fmt.Errorf(`jwe.Derypt: failed to uncompress payload: %w`, err) } plaintext = buf } if plaintext == nil { return nil, fmt.Errorf(`failed to find matching recipient`) } return plaintext, nil } // encryptContext holds the state during JWE encryption, similar to JWS signContext type encryptContext struct { calg jwa.ContentEncryptionAlgorithm compression jwa.CompressionAlgorithm format int builders []*recipientBuilder protected Headers legacyHeaderMerging bool } var encryptContextPool = pool.New(allocEncryptContext, freeEncryptContext) func allocEncryptContext() *encryptContext { return &encryptContext{ calg: jwa.A256GCM(), compression: jwa.NoCompress(), format: fmtCompact, } } func freeEncryptContext(ec *encryptContext) *encryptContext { ec.calg = jwa.A256GCM() ec.compression = jwa.NoCompress() ec.format = fmtCompact ec.builders = ec.builders[:0] ec.protected = nil return ec } func (ec *encryptContext) ProcessOptions(options []EncryptOption) error { ec.legacyHeaderMerging = true var mergeProtected bool var useRawCEK bool for _, option := range options { switch option.Ident() { case identKey{}: var wk *withKey if err := option.Value(&wk); err != nil { return fmt.Errorf("jwe.encrypt: WithKey must be a *withKey: %w", err) } v, ok := wk.alg.(jwa.KeyEncryptionAlgorithm) if !ok { return fmt.Errorf("jwe.encrypt: WithKey() option must be specified using jwa.KeyEncryptionAlgorithm (got %T)", wk.alg) } if v == jwa.DIRECT() || v == jwa.ECDH_ES() { useRawCEK = true } ec.builders = append(ec.builders, &recipientBuilder{ alg: v, key: wk.key, headers: wk.headers, }) case identContentEncryptionAlgorithm{}: var c jwa.ContentEncryptionAlgorithm if err := option.Value(&c); err != nil { return err } ec.calg = c case identCompress{}: var comp jwa.CompressionAlgorithm if err := option.Value(&comp); err != nil { return err } ec.compression = comp case identMergeProtectedHeaders{}: var mp bool if err := option.Value(&mp); err != nil { return err } mergeProtected = mp case identProtectedHeaders{}: var hdrs Headers if err := option.Value(&hdrs); err != nil { return err } if !mergeProtected || ec.protected == nil { ec.protected = hdrs } else { merged, err := ec.protected.Merge(hdrs) if err != nil { return fmt.Errorf(`failed to merge headers: %w`, err) } ec.protected = merged } case identSerialization{}: var fmtOpt int if err := option.Value(&fmtOpt); err != nil { return err } ec.format = fmtOpt case identLegacyHeaderMerging{}: var v bool if err := option.Value(&v); err != nil { return err } ec.legacyHeaderMerging = v } } // We need to have at least one builder switch l := len(ec.builders); { case l == 0: return fmt.Errorf(`missing key encryption builders: use jwe.WithKey() to specify one`) case l > 1: if ec.format == fmtCompact { return fmt.Errorf(`cannot use compact serialization when multiple recipients exist (check the number of WithKey() argument, or use WithJSON())`) } } if useRawCEK { if len(ec.builders) != 1 { return fmt.Errorf(`multiple recipients for ECDH-ES/DIRECT mode supported`) } } return nil } var msgPool = pool.New(allocMessage, freeMessage) func allocMessage() *Message { return &Message{ recipients: make([]Recipient, 0, 1), } } func freeMessage(msg *Message) *Message { msg.cipherText = nil msg.initializationVector = nil if hdr := msg.protectedHeaders; hdr != nil { headerPool.Put(hdr) } msg.protectedHeaders = nil msg.unprotectedHeaders = nil msg.recipients = nil // reuse should be done elsewhere msg.authenticatedData = nil msg.tag = nil msg.rawProtectedHeaders = nil msg.storeProtectedHeaders = false return msg } var headerPool = pool.New(NewHeaders, freeHeaders) func freeHeaders(h Headers) Headers { if c, ok := h.(interface{ clear() }); ok { c.clear() } return h } var recipientPool = pool.New(NewRecipient, freeRecipient) func freeRecipient(r Recipient) Recipient { if h := r.Headers(); h != nil { if c, ok := h.(interface{ clear() }); ok { c.clear() } } if sr, ok := r.(*stdRecipient); ok { sr.encryptedKey = nil } return r } var recipientSlicePool = pool.NewSlicePool(allocRecipientSlice, freeRecipientSlice) func allocRecipientSlice() []Recipient { return make([]Recipient, 0, 1) } func freeRecipientSlice(rs []Recipient) []Recipient { for _, r := range rs { recipientPool.Put(r) } return rs[:0] } func (ec *encryptContext) EncryptMessage(payload []byte, cek []byte) ([]byte, error) { // Get protected headers from pool and copy contents from context protected := headerPool.Get() if userSupplied := ec.protected; userSupplied != nil { ec.protected = nil // Clear from context if err := userSupplied.Copy(protected); err != nil { return nil, fmt.Errorf(`failed to copy protected headers: %w`, err) } } // There is exactly one content encrypter. contentcrypt, err := content_crypt.NewGeneric(ec.calg) if err != nil { return nil, fmt.Errorf(`failed to create AES encrypter: %w`, err) } // Generate CEK if not provided if len(cek) <= 0 { bk, err := keygen.Random(contentcrypt.KeySize()) if err != nil { return nil, fmt.Errorf(`failed to generate key: %w`, err) } cek = bk.Bytes() } var useRawCEK bool for _, builder := range ec.builders { if builder.alg == jwa.DIRECT() || builder.alg == jwa.ECDH_ES() { useRawCEK = true break } } lbuilders := len(ec.builders) recipients := recipientSlicePool.GetCapacity(lbuilders) defer recipientSlicePool.Put(recipients) for i, builder := range ec.builders { r := recipientPool.Get() defer recipientPool.Put(r) // some builders require hint from the contentcrypt object rawCEK, err := builder.Build(r, cek, ec.calg, contentcrypt) if err != nil { return nil, fmt.Errorf(`failed to create recipient #%d: %w`, i, err) } recipients = append(recipients, r) // Kinda feels weird, but if useRawCEK == true, we asserted earlier // that len(builders) == 1, so this is OK if useRawCEK { cek = rawCEK } } if err := protected.Set(ContentEncryptionKey, ec.calg); err != nil { return nil, fmt.Errorf(`failed to set "enc" in protected header: %w`, err) } if ec.compression != jwa.NoCompress() { payload, err = compress(payload) if err != nil { return nil, fmt.Errorf(`failed to compress payload before encryption: %w`, err) } if err := protected.Set(CompressionKey, ec.compression); err != nil { return nil, fmt.Errorf(`failed to set "zip" in protected header: %w`, err) } } // fmtCompact does not have per-recipient headers, nor a "header" field. // In this mode, we're going to have to merge everything to the protected // header. if ec.format == fmtCompact { // We have already established that the number of builders is 1 in // ec.ProcessOptions(). But we're going to be pedantic if lbuilders != 1 { return nil, fmt.Errorf(`internal error: expected exactly one recipient builder (got %d)`, lbuilders) } // when we're using compact format, we can safely merge per-recipient // headers into the protected header, if any h, err := protected.Merge(recipients[0].Headers()) if err != nil { return nil, fmt.Errorf(`failed to merge protected headers for compact serialization: %w`, err) } protected = h // per-recipient headers, if any, will be ignored in compact format } else { // If it got here, it's JSON (could be pretty mode, too). if lbuilders == 1 { // If it got here, then we're doing flattened JSON serialization. // In this mode, we should merge per-recipient headers into the protected header, // but we also need to make sure that the "header" field is reset so that // it does not contain the same fields as the protected header. // // However, old behavior was to merge per-recipient headers into the // protected header when there was only one recipient, AND leave the // original "header" field as is, so we need to support that for backwards compatibility. // // The legacy merging only takes effect when there is exactly one recipient. // // This behavior can be disabled by passing jwe.WithLegacyHeaderMerging(false) // If the user has explicitly asked for merging, do it h, err := protected.Merge(recipients[0].Headers()) if err != nil { return nil, fmt.Errorf(`failed to merge protected headers for flattenend JSON format: %w`, err) } protected = h if !ec.legacyHeaderMerging { // Clear per-recipient headers, since they have been merged. // But we only do it when legacy merging is disabled. // Note: we should probably introduce a Reset() method in v4 if err := recipients[0].SetHeaders(NewHeaders()); err != nil { return nil, fmt.Errorf(`failed to clear per-recipient headers after merging: %w`, err) } } } } aad, err := protected.Encode() if err != nil { return nil, fmt.Errorf(`failed to base64 encode protected headers: %w`, err) } iv, ciphertext, tag, err := contentcrypt.Encrypt(cek, payload, aad) if err != nil { return nil, fmt.Errorf(`failed to encrypt payload: %w`, err) } msg := msgPool.Get() defer msgPool.Put(msg) if err := msg.Set(CipherTextKey, ciphertext); err != nil { return nil, fmt.Errorf(`failed to set %s: %w`, CipherTextKey, err) } if err := msg.Set(InitializationVectorKey, iv); err != nil { return nil, fmt.Errorf(`failed to set %s: %w`, InitializationVectorKey, err) } if err := msg.Set(ProtectedHeadersKey, protected); err != nil { return nil, fmt.Errorf(`failed to set %s: %w`, ProtectedHeadersKey, err) } if err := msg.Set(RecipientsKey, recipients); err != nil { return nil, fmt.Errorf(`failed to set %s: %w`, RecipientsKey, err) } if err := msg.Set(TagKey, tag); err != nil { return nil, fmt.Errorf(`failed to set %s: %w`, TagKey, err) } switch ec.format { case fmtCompact: return Compact(msg) case fmtJSON: return json.Marshal(msg) case fmtJSONPretty: return json.MarshalIndent(msg, "", " ") default: return nil, fmt.Errorf(`invalid serialization`) } } // Decrypt takes encrypted payload, and information required to decrypt the // payload (e.g. the key encryption algorithm and the corresponding // key to decrypt the JWE message) in its optional arguments. See // the examples and list of options that return a DecryptOption for possible // values. Upon successful decryptiond returns the decrypted payload. // // The JWE message can be either compact or full JSON format. // // When using `jwe.WithKeyEncryptionAlgorithm()`, you can pass a `jwa.KeyAlgorithm` // for convenience: this is mainly to allow you to directly pass the result of `(jwk.Key).Algorithm()`. // However, do note that while `(jwk.Key).Algorithm()` could very well contain key encryption // algorithms, it could also contain other types of values, such as _signature algorithms_. // In order for `jwe.Decrypt` to work properly, the `alg` parameter must be of type // `jwa.KeyEncryptionAlgorithm` or otherwise it will cause an error. // // When using `jwe.WithKey()`, the value must be a private key. // It can be either in its raw format (e.g. *rsa.PrivateKey) or a jwk.Key // // When the encrypted message is also compressed, the decompressed payload must be // smaller than the size specified by the `jwe.WithMaxDecompressBufferSize` setting, // which defaults to 10MB. If the decompressed payload is larger than this size, // an error is returned. // // You can opt to change the MaxDecompressBufferSize setting globally, or on a // per-call basis by passing the `jwe.WithMaxDecompressBufferSize` option to // either `jwe.Settings()` or `jwe.Decrypt()`: // // jwe.Settings(jwe.WithMaxDecompressBufferSize(10*1024*1024)) // changes value globally // jwe.Decrypt(..., jwe.WithMaxDecompressBufferSize(250*1024)) // changes just for this call func Decrypt(buf []byte, options ...DecryptOption) ([]byte, error) { dc := decryptContextPool.Get() defer decryptContextPool.Put(dc) if err := dc.ProcessOptions(options); err != nil { return nil, decryptError{fmt.Errorf(`jwe.Decrypt: failed to process options: %w`, err)} } ret, err := dc.DecryptMessage(buf) if err != nil { return nil, decryptError{fmt.Errorf(`jwe.Decrypt: %w`, err)} } return ret, nil } // Parse parses the JWE message into a Message object. The JWE message // can be either compact or full JSON format. // // Parse() currently does not take any options, but the API accepts it // in anticipation of future addition. func Parse(buf []byte, _ ...ParseOption) (*Message, error) { return parseJSONOrCompact(buf, false) } // errors are wrapped within this function, because we call it directly // from Decrypt as well. func parseJSONOrCompact(buf []byte, storeProtectedHeaders bool) (*Message, error) { buf = bytes.TrimSpace(buf) if len(buf) == 0 { return nil, parseError{fmt.Errorf(`jwe.Parse: empty buffer`)} } var msg *Message var err error if buf[0] == tokens.OpenCurlyBracket { msg, err = parseJSON(buf, storeProtectedHeaders) } else { msg, err = parseCompact(buf, storeProtectedHeaders) } if err != nil { return nil, parseError{fmt.Errorf(`jwe.Parse: %w`, err)} } return msg, nil } // ParseString is the same as Parse, but takes a string. func ParseString(s string) (*Message, error) { msg, err := Parse([]byte(s)) if err != nil { return nil, parseError{fmt.Errorf(`jwe.ParseString: %w`, err)} } return msg, nil } // ParseReader is the same as Parse, but takes an io.Reader. func ParseReader(src io.Reader) (*Message, error) { buf, err := io.ReadAll(src) if err != nil { return nil, parseError{fmt.Errorf(`jwe.ParseReader: failed to read from io.Reader: %w`, err)} } msg, err := Parse(buf) if err != nil { return nil, parseError{fmt.Errorf(`jwe.ParseReader: %w`, err)} } return msg, nil } func parseJSON(buf []byte, storeProtectedHeaders bool) (*Message, error) { m := NewMessage() m.storeProtectedHeaders = storeProtectedHeaders if err := json.Unmarshal(buf, &m); err != nil { return nil, fmt.Errorf(`failed to parse JSON: %w`, err) } return m, nil } func parseCompact(buf []byte, storeProtectedHeaders bool) (*Message, error) { var parts [5][]byte var ok bool for i := range 4 { parts[i], buf, ok = bytes.Cut(buf, []byte{tokens.Period}) if !ok { return nil, fmt.Errorf(`compact JWE format must have five parts (%d)`, i+1) } } // Validate that the last part does not contain more dots if bytes.ContainsRune(buf, tokens.Period) { return nil, errors.New(`compact JWE format must have five parts, not more`) } parts[4] = buf hdrbuf, err := base64.Decode(parts[0]) if err != nil { return nil, fmt.Errorf(`failed to parse first part of compact form: %w`, err) } protected := NewHeaders() if err := json.Unmarshal(hdrbuf, protected); err != nil { return nil, fmt.Errorf(`failed to parse header JSON: %w`, err) } ivbuf, err := base64.Decode(parts[2]) if err != nil { return nil, fmt.Errorf(`failed to base64 decode iv: %w`, err) } ctbuf, err := base64.Decode(parts[3]) if err != nil { return nil, fmt.Errorf(`failed to base64 decode content: %w`, err) } tagbuf, err := base64.Decode(parts[4]) if err != nil { return nil, fmt.Errorf(`failed to base64 decode tag: %w`, err) } m := NewMessage() if err := m.Set(CipherTextKey, ctbuf); err != nil { return nil, fmt.Errorf(`failed to set %s: %w`, CipherTextKey, err) } if err := m.Set(InitializationVectorKey, ivbuf); err != nil { return nil, fmt.Errorf(`failed to set %s: %w`, InitializationVectorKey, err) } if err := m.Set(ProtectedHeadersKey, protected); err != nil { return nil, fmt.Errorf(`failed to set %s: %w`, ProtectedHeadersKey, err) } if err := m.makeDummyRecipient(string(parts[1]), protected); err != nil { return nil, fmt.Errorf(`failed to setup recipient: %w`, err) } if err := m.Set(TagKey, tagbuf); err != nil { return nil, fmt.Errorf(`failed to set %s: %w`, TagKey, err) } if storeProtectedHeaders { // This is later used for decryption. m.rawProtectedHeaders = parts[0] } return m, nil } type CustomDecoder = json.CustomDecoder type CustomDecodeFunc = json.CustomDecodeFunc // RegisterCustomField allows users to specify that a private field // be decoded as an instance of the specified type. This option has // a global effect. // // For example, suppose you have a custom field `x-birthday`, which // you want to represent as a string formatted in RFC3339 in JSON, // but want it back as `time.Time`. // // In such case you would register a custom field as follows // // jws.RegisterCustomField(`x-birthday`, time.Time{}) // // Then you can use a `time.Time` variable to extract the value // of `x-birthday` field, instead of having to use `any` // and later convert it to `time.Time` // // var bday time.Time // _ = hdr.Get(`x-birthday`, &bday) // // If you need a more fine-tuned control over the decoding process, // you can register a `CustomDecoder`. For example, below shows // how to register a decoder that can parse RFC1123 format string: // // jwe.RegisterCustomField(`x-birthday`, jwe.CustomDecodeFunc(func(data []byte) (any, error) { // return time.Parse(time.RFC1123, string(data)) // })) // // Please note that use of custom fields can be problematic if you // are using a library that does not implement MarshalJSON/UnmarshalJSON // and you try to roundtrip from an object to JSON, and then back to an object. // For example, in the above example, you can _parse_ time values formatted // in the format specified in RFC822, but when you convert an object into // JSON, it will be formatted in RFC3339, because that's what `time.Time` // likes to do. To avoid this, it's always better to use a custom type // that wraps your desired type (in this case `time.Time`) and implement // MarshalJSON and UnmashalJSON. func RegisterCustomField(name string, object any) { registry.Register(name, object) } golang-github-lestrrat-go-jwx-3.0.13/jwe/jwe_test.go000066400000000000000000001355051515060566400223550ustar00rootroot00000000000000package jwe_test import ( "bytes" "context" "crypto" "crypto/ecdh" "crypto/ecdsa" "crypto/elliptic" "crypto/rand" "crypto/rsa" "encoding/base64" "fmt" "math" "os" "strings" "testing" "time" "github.com/lestrrat-go/jwx/v3/internal/json" "github.com/lestrrat-go/jwx/v3/internal/jwxtest" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/lestrrat-go/jwx/v3/jwe" "github.com/lestrrat-go/jwx/v3/jwk" "github.com/stretchr/testify/require" ) const ( examplePayload = `The true sign of intelligence is not knowledge but imagination.` ) var rsaPrivKey rsa.PrivateKey func init() { var jwkstr = []byte(` {"kty":"RSA", "n":"oahUIoWw0K0usKNuOR6H4wkf4oBUXHTxRvgb48E-BVvxkeDNjbC4he8rUWcJoZmds2h7M70imEVhRU5djINXtqllXI4DFqcI1DgjT9LewND8MW2Krf3Spsk_ZkoFnilakGygTwpZ3uesH-PFABNIUYpOiN15dsQRkgr0vEhxN92i2asbOenSZeyaxziK72UwxrrKoExv6kc5twXTq4h-QChLOln0_mtUZwfsRaMStPs6mS6XrgxnxbWhojf663tuEQueGC-FCMfra36C9knDFGzKsNa7LZK2djYgyD3JR_MB_4NUJW_TqOQtwHYbxevoJArm-L5StowjzGy-_bq6Gw", "e":"AQAB", "d":"kLdtIj6GbDks_ApCSTYQtelcNttlKiOyPzMrXHeI-yk1F7-kpDxY4-WY5NWV5KntaEeXS1j82E375xxhWMHXyvjYecPT9fpwR_M9gV8n9Hrh2anTpTD93Dt62ypW3yDsJzBnTnrYu1iwWRgBKrEYY46qAZIrA2xAwnm2X7uGR1hghkqDp0Vqj3kbSCz1XyfCs6_LehBwtxHIyh8Ripy40p24moOAbgxVw3rxT_vlt3UVe4WO3JkJOzlpUf-KTVI2Ptgm-dARxTEtE-id-4OJr0h-K-VFs3VSndVTIznSxfyrj8ILL6MG_Uv8YAu7VILSB3lOW085-4qE3DzgrTjgyQ", "p":"1r52Xk46c-LsfB5P442p7atdPUrxQSy4mti_tZI3Mgf2EuFVbUoDBvaRQ-SWxkbkmoEzL7JXroSBjSrK3YIQgYdMgyAEPTPjXv_hI2_1eTSPVZfzL0lffNn03IXqWF5MDFuoUYE0hzb2vhrlN_rKrbfDIwUbTrjjgieRbwC6Cl0", "q":"wLb35x7hmQWZsWJmB_vle87ihgZ19S8lBEROLIsZG4ayZVe9Hi9gDVCOBmUDdaDYVTSNx_8Fyw1YYa9XGrGnDew00J28cRUoeBB_jKI1oma0Orv1T9aXIWxKwd4gvxFImOWr3QRL9KEBRzk2RatUBnmDZJTIAfwTs0g68UZHvtc", "dp":"ZK-YwE7diUh0qR1tR7w8WHtolDx3MZ_OTowiFvgfeQ3SiresXjm9gZ5KLhMXvo-uz-KUJWDxS5pFQ_M0evdo1dKiRTjVw_x4NyqyXPM5nULPkcpU827rnpZzAJKpdhWAgqrXGKAECQH0Xt4taznjnd_zVpAmZZq60WPMBMfKcuE", "dq":"Dq0gfgJ1DdFGXiLvQEZnuKEN0UUmsJBxkjydc3j4ZYdBiMRAy86x0vHCjywcMlYYg4yoC4YZa9hNVcsjqA3FeiL19rk8g6Qn29Tt0cj8qqyFpz9vNDBUfCAiJVeESOjJDZPYHdHY8v1b-o-Z2X5tvLx-TCekf7oxyeKDUqKWjis", "qi":"VIMpMYbPf47dT1w_zDUXfPimsSegnMOA1zTaX7aGk_8urY6R8-ZW1FxU7AlWAyLWybqq6t16VFd7hQd0y6flUK4SlOydB61gwanOsXGOAOv82cHq0E3eL4HrtZkUuKvnPrMnsUUFlfUdybVzxyjz9JF_XyaY14ardLSjf4L_FNY" }`) privkey, err := jwk.ParseKey(jwkstr) if err != nil { panic(err) } if err := jwk.Export(privkey, &rsaPrivKey); err != nil { panic(err) } } func TestSanityCheck_JWEExamplePayload(t *testing.T) { expected := []byte{ 84, 104, 101, 32, 116, 114, 117, 101, 32, 115, 105, 103, 110, 32, 111, 102, 32, 105, 110, 116, 101, 108, 108, 105, 103, 101, 110, 99, 101, 32, 105, 115, 32, 110, 111, 116, 32, 107, 110, 111, 119, 108, 101, 100, 103, 101, 32, 98, 117, 116, 32, 105, 109, 97, 103, 105, 110, 97, 116, 105, 111, 110, 46, } require.Equal(t, expected, []byte(examplePayload), "examplePayload OK") } func TestParse(t *testing.T) { const s = `eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkEyNTZHQ00ifQ.OKOawDo13gRp2ojaHV7LFpZcgV7T6DVZKTyKOMTYUmKoTCVJRgckCL9kiMT03JGeipsEdY3mx_etLbbWSrFr05kLzcSr4qKAq7YN7e9jwQRb23nfa6c9d-StnImGyFDbSv04uVuxIp5Zms1gNxKKK2Da14B8S4rzVRltdYwam_lDp5XnZAYpQdb76FdIKLaVmqgfwX7XWRxv2322i-vDxRfqNzo_tETKzpVLzfiwQyeyPGLBIO56YJ7eObdv0je81860ppamavo35UgoRdbYaBcoh9QcfylQr66oc6vFWXRcZ_ZT2LawVCWTIy3brGPi6UklfCpIMfIjf7iGdXKHzg.48V1_ALb6US04U3b.5eym8TW_c8SuK0ltJ3rpYIzOeDQz7TALvtu6UG9oMo4vpzs9tX_EFShS8iB7j6jiSdiwkIr3ajwQzaBtQD_A.XFBoMYUZodetZdvTiFvSkQ` t.Run("Compact format", func(t *testing.T) { t.Run("Normal", func(t *testing.T) { msg, err := jwe.Parse([]byte(s)) require.NoError(t, err, "Parsing JWE is successful") require.Len(t, msg.Recipients(), 1, "There is exactly 1 recipient") }) parts := strings.Split(s, ".") t.Run("Missing parts", func(t *testing.T) { s2 := strings.Join(parts[:4], ".") _, err := jwe.Parse([]byte(s2)) require.Error(t, err, `should fail to parse compact format with missing parts`) }) t.Run("Invalid header", func(t *testing.T) { s2 := strings.Join(append(append([]string(nil), "!!invalidheader!!"), parts[1:]...), ".") _, err := jwe.Parse([]byte(s2)) require.Error(t, err, `should fail to parse compact format with invalid header`) }) t.Run("Invalid encrypted key", func(t *testing.T) { s2 := strings.Join(append(append(append([]string(nil), parts[0]), "!!invalidenckey!!"), parts[2:]...), ".") _, err := jwe.Parse([]byte(s2)) require.Error(t, err, `should fail to parse compact format with invalid encrypted key`) }) t.Run("Invalid initialization vector", func(t *testing.T) { s2 := strings.Join(append(append(append([]string(nil), parts[:2]...), "!!invalidiv!!"), parts[3:]...), ".") _, err := jwe.Parse([]byte(s2)) require.Error(t, err, `should fail to parse compact format with invalid initialization vector`) }) t.Run("Invalid content", func(t *testing.T) { s2 := strings.Join(append(append(append([]string(nil), parts[:3]...), "!!invalidcontent!!"), parts[4:]...), ".") _, err := jwe.Parse([]byte(s2)) require.Error(t, err, `should fail to parse compact format with invalid content`) }) t.Run("Invalid tag", func(t *testing.T) { s2 := strings.Join(append(parts[:4], "!!invalidtag!!"), ".") _, err := jwe.Parse([]byte(s2)) require.Error(t, err, `should fail to parse compact format with invalid tag`) }) t.Run("Too many parts", func(t *testing.T) { s2 := s + "." _, err := jwe.Parse([]byte(s2)) require.Error(t, err, `should fail to parse compact format with too many parts`) }) }) t.Run("JSON format", func(t *testing.T) { msg, err := jwe.Parse([]byte(s)) require.NoError(t, err, "Parsing JWE is successful") buf, err := json.Marshal(msg) require.NoError(t, err, "Serializing to JSON format should succeed") msg2, err := jwe.Parse(buf) require.NoError(t, err, "Parsing JWE in JSON format should succeed") require.Equal(t, msg, msg2, "messages should match") }) } // This test parses the example found in https://tools.ietf.org/html/rfc7516#appendix-A.1, // and checks if we can roundtrip to the same compact serialization format. func TestParse_RSAES_OAEP_AES_GCM(t *testing.T) { const payload = `The true sign of intelligence is not knowledge but imagination.` const serialized = `eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkEyNTZHQ00ifQ.OKOawDo13gRp2ojaHV7LFpZcgV7T6DVZKTyKOMTYUmKoTCVJRgckCL9kiMT03JGeipsEdY3mx_etLbbWSrFr05kLzcSr4qKAq7YN7e9jwQRb23nfa6c9d-StnImGyFDbSv04uVuxIp5Zms1gNxKKK2Da14B8S4rzVRltdYwam_lDp5XnZAYpQdb76FdIKLaVmqgfwX7XWRxv2322i-vDxRfqNzo_tETKzpVLzfiwQyeyPGLBIO56YJ7eObdv0je81860ppamavo35UgoRdbYaBcoh9QcfylQr66oc6vFWXRcZ_ZT2LawVCWTIy3brGPi6UklfCpIMfIjf7iGdXKHzg.48V1_ALb6US04U3b.5eym8TW_c8SuK0ltJ3rpYIzOeDQz7TALvtu6UG9oMo4vpzs9tX_EFShS8iB7j6jiSdiwkIr3ajwQzaBtQD_A.XFBoMYUZodetZdvTiFvSkQ` var jwkstr = []byte(` {"kty":"RSA", "n":"oahUIoWw0K0usKNuOR6H4wkf4oBUXHTxRvgb48E-BVvxkeDNjbC4he8rUWcJoZmds2h7M70imEVhRU5djINXtqllXI4DFqcI1DgjT9LewND8MW2Krf3Spsk_ZkoFnilakGygTwpZ3uesH-PFABNIUYpOiN15dsQRkgr0vEhxN92i2asbOenSZeyaxziK72UwxrrKoExv6kc5twXTq4h-QChLOln0_mtUZwfsRaMStPs6mS6XrgxnxbWhojf663tuEQueGC-FCMfra36C9knDFGzKsNa7LZK2djYgyD3JR_MB_4NUJW_TqOQtwHYbxevoJArm-L5StowjzGy-_bq6Gw", "e":"AQAB", "d":"kLdtIj6GbDks_ApCSTYQtelcNttlKiOyPzMrXHeI-yk1F7-kpDxY4-WY5NWV5KntaEeXS1j82E375xxhWMHXyvjYecPT9fpwR_M9gV8n9Hrh2anTpTD93Dt62ypW3yDsJzBnTnrYu1iwWRgBKrEYY46qAZIrA2xAwnm2X7uGR1hghkqDp0Vqj3kbSCz1XyfCs6_LehBwtxHIyh8Ripy40p24moOAbgxVw3rxT_vlt3UVe4WO3JkJOzlpUf-KTVI2Ptgm-dARxTEtE-id-4OJr0h-K-VFs3VSndVTIznSxfyrj8ILL6MG_Uv8YAu7VILSB3lOW085-4qE3DzgrTjgyQ", "p":"1r52Xk46c-LsfB5P442p7atdPUrxQSy4mti_tZI3Mgf2EuFVbUoDBvaRQ-SWxkbkmoEzL7JXroSBjSrK3YIQgYdMgyAEPTPjXv_hI2_1eTSPVZfzL0lffNn03IXqWF5MDFuoUYE0hzb2vhrlN_rKrbfDIwUbTrjjgieRbwC6Cl0", "q":"wLb35x7hmQWZsWJmB_vle87ihgZ19S8lBEROLIsZG4ayZVe9Hi9gDVCOBmUDdaDYVTSNx_8Fyw1YYa9XGrGnDew00J28cRUoeBB_jKI1oma0Orv1T9aXIWxKwd4gvxFImOWr3QRL9KEBRzk2RatUBnmDZJTIAfwTs0g68UZHvtc", "dp":"ZK-YwE7diUh0qR1tR7w8WHtolDx3MZ_OTowiFvgfeQ3SiresXjm9gZ5KLhMXvo-uz-KUJWDxS5pFQ_M0evdo1dKiRTjVw_x4NyqyXPM5nULPkcpU827rnpZzAJKpdhWAgqrXGKAECQH0Xt4taznjnd_zVpAmZZq60WPMBMfKcuE", "dq":"Dq0gfgJ1DdFGXiLvQEZnuKEN0UUmsJBxkjydc3j4ZYdBiMRAy86x0vHCjywcMlYYg4yoC4YZa9hNVcsjqA3FeiL19rk8g6Qn29Tt0cj8qqyFpz9vNDBUfCAiJVeESOjJDZPYHdHY8v1b-o-Z2X5tvLx-TCekf7oxyeKDUqKWjis", "qi":"VIMpMYbPf47dT1w_zDUXfPimsSegnMOA1zTaX7aGk_8urY6R8-ZW1FxU7AlWAyLWybqq6t16VFd7hQd0y6flUK4SlOydB61gwanOsXGOAOv82cHq0E3eL4HrtZkUuKvnPrMnsUUFlfUdybVzxyjz9JF_XyaY14ardLSjf4L_FNY" }`) privkey, err := jwk.ParseKey(jwkstr) require.NoError(t, err, `parsing jwk should succeed`) var rawkey rsa.PrivateKey require.NoError(t, jwk.Export(privkey, &rawkey), `obtaining raw key should succeed`) msg := jwe.NewMessage() plaintext, err := jwe.Decrypt([]byte(serialized), jwe.WithKey(jwa.RSA_OAEP(), rawkey), jwe.WithMessage(msg)) require.NoError(t, err, "jwe.Decrypt should be successful") require.Equal(t, 1, len(msg.Recipients()), "message recipients header length is 1") require.Equal(t, payload, string(plaintext), "decrypted value does not match") templates := []*struct { Name string Options []jwe.EncryptOption Expected string }{ { Name: "Compact", Options: []jwe.EncryptOption{jwe.WithCompact()}, Expected: serialized, }, { Name: "JSON", Options: []jwe.EncryptOption{jwe.WithJSON()}, Expected: `{"ciphertext":"5eym8TW_c8SuK0ltJ3rpYIzOeDQz7TALvtu6UG9oMo4vpzs9tX_EFShS8iB7j6jiSdiwkIr3ajwQzaBtQD_A","iv":"48V1_ALb6US04U3b","protected":"eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkEyNTZHQ00ifQ","header":{"alg":"RSA-OAEP"},"encrypted_key":"OKOawDo13gRp2ojaHV7LFpZcgV7T6DVZKTyKOMTYUmKoTCVJRgckCL9kiMT03JGeipsEdY3mx_etLbbWSrFr05kLzcSr4qKAq7YN7e9jwQRb23nfa6c9d-StnImGyFDbSv04uVuxIp5Zms1gNxKKK2Da14B8S4rzVRltdYwam_lDp5XnZAYpQdb76FdIKLaVmqgfwX7XWRxv2322i-vDxRfqNzo_tETKzpVLzfiwQyeyPGLBIO56YJ7eObdv0je81860ppamavo35UgoRdbYaBcoh9QcfylQr66oc6vFWXRcZ_ZT2LawVCWTIy3brGPi6UklfCpIMfIjf7iGdXKHzg","tag":"XFBoMYUZodetZdvTiFvSkQ"}`, }, { Name: "JSON (Pretty)", Options: []jwe.EncryptOption{jwe.WithJSON(jwe.WithPretty(true))}, Expected: `{ "ciphertext": "5eym8TW_c8SuK0ltJ3rpYIzOeDQz7TALvtu6UG9oMo4vpzs9tX_EFShS8iB7j6jiSdiwkIr3ajwQzaBtQD_A", "iv": "48V1_ALb6US04U3b", "protected": "eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkEyNTZHQ00ifQ", "header": { "alg": "RSA-OAEP" }, "encrypted_key": "OKOawDo13gRp2ojaHV7LFpZcgV7T6DVZKTyKOMTYUmKoTCVJRgckCL9kiMT03JGeipsEdY3mx_etLbbWSrFr05kLzcSr4qKAq7YN7e9jwQRb23nfa6c9d-StnImGyFDbSv04uVuxIp5Zms1gNxKKK2Da14B8S4rzVRltdYwam_lDp5XnZAYpQdb76FdIKLaVmqgfwX7XWRxv2322i-vDxRfqNzo_tETKzpVLzfiwQyeyPGLBIO56YJ7eObdv0je81860ppamavo35UgoRdbYaBcoh9QcfylQr66oc6vFWXRcZ_ZT2LawVCWTIy3brGPi6UklfCpIMfIjf7iGdXKHzg", "tag": "XFBoMYUZodetZdvTiFvSkQ" }`, }, } var testcases []struct { Name string Options []jwe.EncryptOption Expected string } for _, tmpl := range templates { options := make([]jwe.EncryptOption, len(tmpl.Options)) copy(options, tmpl.Options) for _, compression := range []jwa.CompressionAlgorithm{jwa.NoCompress(), jwa.Deflate()} { compName := compression.String() if compName == "" { compName = "none" } testcases = append(testcases, struct { Name string Options []jwe.EncryptOption Expected string }{ Name: tmpl.Name + " (compression=" + compName + ")", Options: append(options, jwe.WithCompress(compression)), Expected: tmpl.Expected, }) } } for _, tc := range testcases { t.Run(tc.Name, func(t *testing.T) { options := tc.Options options = append(options, jwe.WithKey(jwa.RSA_OAEP(), rawkey.PublicKey)) encrypted, err := jwe.Encrypt(plaintext, options...) require.NoError(t, err, "jwe.Encrypt should succeed") t.Logf("%s", encrypted) t.Run("WithKey", func(t *testing.T) { plaintext, err = jwe.Decrypt(encrypted, jwe.WithKey(jwa.RSA_OAEP(), rawkey)) require.NoError(t, err, "jwe.Decrypt should succeed") require.Equal(t, payload, string(plaintext), "jwe.Decrypt should produce the same plaintext") }) t.Run("WithKeySet", func(t *testing.T) { pkJwk, err := jwk.Import(rawkey) require.NoError(t, err, `jwk.New should succeed`) // Keys are not going to be selected without an algorithm require.NoError(t, pkJwk.Set(jwe.AlgorithmKey, jwa.RSA_OAEP()), `jwk.Set should succeed`) set := jwk.NewSet() set.AddKey(pkJwk) var used any plaintext, err = jwe.Decrypt(encrypted, jwe.WithKeySet(set, jwe.WithRequireKid(false)), jwe.WithKeyUsed(&used)) require.NoError(t, err) require.Equal(t, payload, string(plaintext), "jwe.Decrypt should produce the same plaintext") require.Equal(t, pkJwk, used) }) }) } // Test direct marshaling and unmarshaling t.Run("Marshal/Unmarshal", func(t *testing.T) { buf, err := json.Marshal(msg) require.NoError(t, err, `json.Marshal should succeed`) m2 := jwe.NewMessage() require.NoError(t, json.Unmarshal(buf, m2), `json.Unmarshal should succeed`) require.Equal(t, msg, m2, `messages should be the same after roundtrip`) }) } // https://tools.ietf.org/html/rfc7516#appendix-A.1. func TestRoundtrip_RSAES_OAEP_AES_GCM(t *testing.T) { var plaintext = []byte{ 84, 104, 101, 32, 116, 114, 117, 101, 32, 115, 105, 103, 110, 32, 111, 102, 32, 105, 110, 116, 101, 108, 108, 105, 103, 101, 110, 99, 101, 32, 105, 115, 32, 110, 111, 116, 32, 107, 110, 111, 119, 108, 101, 100, 103, 101, 32, 98, 117, 116, 32, 105, 109, 97, 103, 105, 110, 97, 116, 105, 111, 110, 46, } iterations := 100 if testing.Short() { iterations = 1 } for range iterations { encrypted, err := jwe.Encrypt(plaintext, jwe.WithKey(jwa.RSA_OAEP(), &rsaPrivKey.PublicKey)) require.NoError(t, err, "Encrypt should succeed") decrypted, err := jwe.Decrypt(encrypted, jwe.WithKey(jwa.RSA_OAEP(), rsaPrivKey)) require.NoError(t, err, "Decrypt should succeed") require.Equal(t, plaintext, decrypted, "Decrypted content should match") } } func TestRoundtrip_RSA1_5_A128CBC_HS256(t *testing.T) { var plaintext = []byte{ 76, 105, 118, 101, 32, 108, 111, 110, 103, 32, 97, 110, 100, 32, 112, 114, 111, 115, 112, 101, 114, 46, } iterations := 100 if testing.Short() { iterations = 1 } for range iterations { encrypted, err := jwe.Encrypt(plaintext, jwe.WithKey(jwa.RSA1_5(), &rsaPrivKey.PublicKey), jwe.WithContentEncryption(jwa.A128CBC_HS256())) require.NoError(t, err, "Encrypt is successful") decrypted, err := jwe.Decrypt(encrypted, jwe.WithKey(jwa.RSA1_5(), rsaPrivKey)) require.NoError(t, err, "Decrypt successful") require.Equal(t, plaintext, decrypted, "Decrypted correct plaintext") } } // https://tools.ietf.org/html/rfc7516#appendix-A.3. Note that cek is dynamically // generated, so the encrypted values will NOT match that of the RFC. func TestEncode_A128KW_A128CBC_HS256(t *testing.T) { var plaintext = []byte{ 76, 105, 118, 101, 32, 108, 111, 110, 103, 32, 97, 110, 100, 32, 112, 114, 111, 115, 112, 101, 114, 46, } var sharedkey = []byte{ 25, 172, 32, 130, 225, 114, 26, 181, 138, 106, 254, 192, 95, 133, 74, 82, } iterations := 100 if testing.Short() { iterations = 1 } for range iterations { encrypted, err := jwe.Encrypt(plaintext, jwe.WithKey(jwa.A128KW(), sharedkey), jwe.WithContentEncryption(jwa.A128CBC_HS256())) require.NoError(t, err, "Encrypt is successful") decrypted, err := jwe.Decrypt(encrypted, jwe.WithKey(jwa.A128KW(), sharedkey)) require.NoError(t, err, "Decrypt successful") require.Equal(t, plaintext, decrypted, "Decrypted correct plaintext") } } //nolint:thelper func testEncodeECDHWithKey(t *testing.T, privkey any, pubkey any) { plaintext := []byte("Lorem ipsum") algorithms := []jwa.KeyEncryptionAlgorithm{ jwa.ECDH_ES(), jwa.ECDH_ES_A256KW(), jwa.ECDH_ES_A192KW(), jwa.ECDH_ES_A128KW(), } for _, alg := range algorithms { t.Run(alg.String(), func(t *testing.T) { encrypted, err := jwe.Encrypt(plaintext, jwe.WithKey(alg, pubkey)) require.NoError(t, err, "Encrypt succeeds") _, err = jwe.Parse(encrypted) require.NoError(t, err, `jwe.Parse should succeed`) decrypted, err := jwe.Decrypt(encrypted, jwe.WithKey(alg, privkey)) require.NoError(t, err, "Decrypt succeeds") t.Logf("%s", decrypted) }) } } func TestEncode_ECDH(t *testing.T) { curves := []elliptic.Curve{ elliptic.P256(), elliptic.P384(), elliptic.P521(), } for _, crv := range curves { t.Run(crv.Params().Name, func(t *testing.T) { privkey, err := ecdsa.GenerateKey(crv, rand.Reader) require.NoError(t, err, `ecdsa.GenerateKey should succeed`) testEncodeECDHWithKey(t, privkey, &privkey.PublicKey) }) } } func TestEncode_X25519(t *testing.T) { priv, err := ecdh.X25519().GenerateKey(rand.Reader) require.NoError(t, err, `ecdh.X25519().GenerateKey should succeed`) testEncodeECDHWithKey(t, priv, priv.Public()) } func Test_GHIssue207(t *testing.T) { const plaintext = "hi\n" var testcases = []struct { Algorithm jwa.KeyEncryptionAlgorithm Key string Data string Thumbprint string Name string }{ { Name: `ECDH-ES`, Key: `{"alg":"ECDH-ES","crv":"P-521","d":"ARxUkIjnB7pjFzM2OIIFcclR-4qbZwv7DoC96cksPKyvVWOkEsZ0CK6deM4AC6G5GClR5TXWMQVC_bNDmfuwPPqF","key_ops":["wrapKey","unwrapKey"],"kty":"EC","x":"ACewmG5j0POUDQw3rIqFQozK_6yXUsfNjiZtWqQOU7MXsSKK9RsRS8ySmeTG14heUpbbnrC9VdYKSOUGkYnYUl2Y","y":"ACkXSOma_FP93R3u5uYX7gUOlM0LDkNsij9dVFPbafF8hlfYEnUGit2o-tt7W0Zq3t38jEhpjUoGgM04JDJ6_m0x"}`, Data: `{"ciphertext":"sp0cLt4Rx1p0Ax0Q1OZj7w","header":{"alg":"ECDH-ES","epk":{"crv":"P-521","kty":"EC","x":"APMKQpje5vu39-eS_LX_g15stqbNZ37GgYimW8PZf7d_OOuAygK2YlINYnPoUybrxkoaLRPhbmxc9MBWFdaY8SXx","y":"AMpq4DFi6w-pfnprO58CkfX-ncXtJ8fvox2Ej8Ey3ZY1xjVUtbDJCDCjY53snYaNCEjnFQPAn-IkAG90p2Xcm8ut"}},"iv":"Fjnb5uUWp9euqp1MK_hT4A","protected":"eyJlbmMiOiJBMjU2Q0JDLUhTNTEyIn0","tag":"6nhiy-vyqwVjpy08jrorTpWqvam66HdKxU36XsE3Z3s"}`, Thumbprint: `0_6x6e2sZKeq3ka0QV0PEkJagqg`, }, { Name: `ECDH-ES+A256KW`, Key: `{"alg":"ECDH-ES+A256KW","crv":"P-521","d":"AcH8h_ctsMnopTiCH7wiuM-nAb1CNikC0ubcOZQDLYSVEw93h6_D57aD7DLWbjIsVNzn7Qq8P-kRiTYVoH5GTQVg","key_ops":["wrapKey","unwrapKey"],"kty":"EC","x":"AAQoEbNeiG3ExYj9bJLGFn4h_bFjERfIcmpQMW5KWlFhqcXTFg0g8-5YWjdJXdNmO_2EuaKe7zOvEq8dCFCb12-R","y":"Ad8E2jp6FSCSd8laERqIt67A2T-MIqQE5301jNYb5SMsCSV1rs1McyvhzHaclYcqTUptoA-rW5kNS9N5124XPHky"}`, Data: `{"ciphertext":"evXmzoQ5TWQvEXdpv9ZCBQ","encrypted_key":"ceVsjF-0LhziK75oHRC-C539hlFJMSbub015a3YtIBgCt7c0IRzkzwoOvo_Jf44FXZi0Vd-4fvDjRkZDzx9DcuDd4ASYDLvW","header":{"alg":"ECDH-ES+A256KW","epk":{"crv":"P-521","kty":"EC","x":"Aad7PFl9cct7WcfM3b_LNkhCHfCotW_nRuarX7GACDyyZkr2dd1g6r3rz-8r2-AyOGD9gc2nhrTEjVHT2W7eu65U","y":"Ab0Mj6BK8g3Fok6oyFlkvKOyquEVxeeJOlsyXKYBputPxFT5Gljr2FoJdViAxVspoSiw1K5oG1h59UBJgPWG4GQV"}},"iv":"KsJgq2xyzE1dZi2BM2xf5g","protected":"eyJlbmMiOiJBMjU2Q0JDLUhTNTEyIn0","tag":"b6m_nW9vfk6xJugm_-Uuj4cbAQh9ECelLc1ZBfO86L0"}`, Thumbprint: `G4OtKQL_qr9Q57atNOU6SJnJxB8`, }, } for _, tc := range testcases { t.Run(tc.Name, func(t *testing.T) { webKey, err := jwk.ParseKey([]byte(tc.Key)) require.NoError(t, err, `jwk.ParseKey should succeed`) thumbprint, err := webKey.Thumbprint(crypto.SHA1) require.NoError(t, err, `jwk.Thumbprint should succeed`) require.Equal(t, base64.RawURLEncoding.EncodeToString(thumbprint), tc.Thumbprint, `thumbprints should match`) var key ecdsa.PrivateKey require.NoError(t, jwk.Export(webKey, &key), `jwk.Export should succeed`) decrypted, err := jwe.Decrypt([]byte(tc.Data), jwe.WithKeyProvider(jwe.KeyProviderFunc(func(_ context.Context, sink jwe.KeySink, r jwe.Recipient, _ *jwe.Message) error { alg, ok := r.Headers().Algorithm() if !ok { return fmt.Errorf(`attempted to fetch algorithm, could not find it`) } sink.Key(alg, &key) return nil }))) require.NoError(t, err, `jwe.Decrypt should succeed`) require.Equal(t, string(decrypted), plaintext, `plaintext should match`) }) } } // tests direct key encryption by encrypting-decrypting a plaintext func TestEncode_Direct(t *testing.T) { testcases := []*struct { Algorithm jwa.ContentEncryptionAlgorithm KeySize int // in bytes Error bool }{ { Algorithm: jwa.A128CBC_HS256(), KeySize: 32, }, { Algorithm: jwa.A128GCM(), KeySize: 16, }, { Algorithm: jwa.A192CBC_HS384(), KeySize: 48, }, { Algorithm: jwa.A192GCM(), KeySize: 24, }, { Algorithm: jwa.A256CBC_HS512(), KeySize: 64, }, { Algorithm: jwa.A256GCM(), KeySize: 32, }, // Test with wrong alg <-> key size combinations. // Test for gh-1366 // Prior to v3.0.1, this did not error. It only passed because the original // code was doing `actual_keysize/2` and passing 16 bytes out of the total // 32 bytes, resulting in the aes.NewCipher function using AES-128 instead of AES-256. // // It wouldn't have worked for A192CBC_HS384, because if you // provided 24 bytes instead of 48, the key size would be 12, and // aes.NewCipher would barf. { Algorithm: jwa.A256CBC_HS512(), KeySize: 32, Error: true, }, // Sanity for HS384. This never went through, but just in case { Algorithm: jwa.A192CBC_HS384(), KeySize: 24, Error: true, }, } plaintext := []byte("Lorem ipsum") for _, tc := range testcases { t.Run(tc.Algorithm.String(), func(t *testing.T) { key := make([]byte, tc.KeySize) for n := 0; n < len(key); { w := copy(key[n:], []byte(`12345678`)) n += w } encrypted, err := jwe.Encrypt(plaintext, jwe.WithKey(jwa.DIRECT(), key), jwe.WithContentEncryption(tc.Algorithm)) if tc.Error { require.Error(t, err, `jwe.Encrypt should fail`) // we can't continue return } require.NoError(t, err, `jwe.Encrypt should succeed`) decrypted, err := jwe.Decrypt(encrypted, jwe.WithKey(jwa.DIRECT(), key)) require.NoError(t, err, `jwe.Decrypt should succeed`) require.Equal(t, plaintext, decrypted, `jwe.Decrypt should match input plaintext`) }) } } // Decrypts messages generated by `jose` tool. It helps check compatibility with other jwx implementations. func TestDecodePredefined_Direct(t *testing.T) { var testcases = []struct { Algorithm jwa.ContentEncryptionAlgorithm Key string // generated with 'jose jwk gen -i '{"alg":"A128GCM"}' -o key.jwk' Thumbprint string // generated with 'jose jwk thp -i key.jwk` Data string // generated with 'jose jwe enc -I msg.txt -k key.jwk -o msg.jwe' }{ { jwa.A128CBC_HS256(), `{"alg":"A128GCM","k":"9hexZKVSV9pZhPNzgXiD8g","key_ops":["encrypt","decrypt"],"kty":"oct"}`, `RwW22IemrIJLFwlqZ-OQUe_Lnbo`, `{"ciphertext":"FX_px9cuyO_hZfo","encrypted_key":"","header":{"alg":"dir"},"iv":"Z9CRJCFPtpEI5Pwq","protected":"eyJlbmMiOiJBMTI4R0NNIn0","tag":"1iq0MNDX40XVtqGYinhUtQ"}`, }, { jwa.A128GCM(), `{"alg":"A128GCM","k":"9hexZKVSV9pZhPNzgXiD8g","key_ops":["encrypt","decrypt"],"kty":"oct"}`, `RwW22IemrIJLFwlqZ-OQUe_Lnbo`, `{"ciphertext":"FX_px9cuyO_hZfo","encrypted_key":"","header":{"alg":"dir"},"iv":"Z9CRJCFPtpEI5Pwq","protected":"eyJlbmMiOiJBMTI4R0NNIn0","tag":"1iq0MNDX40XVtqGYinhUtQ"}`, }, { jwa.A192CBC_HS384(), `{"alg":"A128GCM","k":"9hexZKVSV9pZhPNzgXiD8g","key_ops":["encrypt","decrypt"],"kty":"oct"}`, `RwW22IemrIJLFwlqZ-OQUe_Lnbo`, `{"ciphertext":"FX_px9cuyO_hZfo","encrypted_key":"","header":{"alg":"dir"},"iv":"Z9CRJCFPtpEI5Pwq","protected":"eyJlbmMiOiJBMTI4R0NNIn0","tag":"1iq0MNDX40XVtqGYinhUtQ"}`, }, { jwa.A192GCM(), `{"alg":"A128GCM","k":"9hexZKVSV9pZhPNzgXiD8g","key_ops":["encrypt","decrypt"],"kty":"oct"}`, `RwW22IemrIJLFwlqZ-OQUe_Lnbo`, `{"ciphertext":"FX_px9cuyO_hZfo","encrypted_key":"","header":{"alg":"dir"},"iv":"Z9CRJCFPtpEI5Pwq","protected":"eyJlbmMiOiJBMTI4R0NNIn0","tag":"1iq0MNDX40XVtqGYinhUtQ"}`, }, { jwa.A256CBC_HS512(), `{"alg":"A128GCM","k":"9hexZKVSV9pZhPNzgXiD8g","key_ops":["encrypt","decrypt"],"kty":"oct"}`, `RwW22IemrIJLFwlqZ-OQUe_Lnbo`, `{"ciphertext":"FX_px9cuyO_hZfo","encrypted_key":"","header":{"alg":"dir"},"iv":"Z9CRJCFPtpEI5Pwq","protected":"eyJlbmMiOiJBMTI4R0NNIn0","tag":"1iq0MNDX40XVtqGYinhUtQ"}`, }, { jwa.A256GCM(), `{"alg":"A128GCM","k":"9hexZKVSV9pZhPNzgXiD8g","key_ops":["encrypt","decrypt"],"kty":"oct"}`, `RwW22IemrIJLFwlqZ-OQUe_Lnbo`, `{"ciphertext":"FX_px9cuyO_hZfo","encrypted_key":"","header":{"alg":"dir"},"iv":"Z9CRJCFPtpEI5Pwq","protected":"eyJlbmMiOiJBMTI4R0NNIn0","tag":"1iq0MNDX40XVtqGYinhUtQ"}`, }, } plaintext := "Lorem ipsum" for _, tc := range testcases { t.Run(tc.Algorithm.String(), func(t *testing.T) { webKey, err := jwk.ParseKey([]byte(tc.Key)) require.NoError(t, err, `jwk.ParseKey should succeed`) thumbprint, err := webKey.Thumbprint(crypto.SHA1) require.NoError(t, err, `jwk.Thumbprint should succeed`) require.Equal(t, base64.RawURLEncoding.EncodeToString(thumbprint), tc.Thumbprint, `thumbprints should match`) var key []byte require.NoError(t, jwk.Export(webKey, &key), `jwk.Export should succeed`) decrypted, err := jwe.Decrypt([]byte(tc.Data), jwe.WithKey(jwa.DIRECT(), key)) require.NoError(t, err, `jwe.Decrypt should succeed`) require.Equal(t, plaintext, string(decrypted), `plaintext should match`) }) } } func TestGHIssue230(t *testing.T) { t.Parallel() const data = `{"ciphertext":"wko","encrypted_key":"","iv":"y-wj7nfa-T8XG58z","protected":"eyJhbGciOiJkaXIiLCJjbGV2aXMiOnsicGluIjoidHBtMiIsInRwbTIiOnsiaGFzaCI6InNoYTI1NiIsImp3a19wcml2IjoiQU80QUlCSTFRYjQ2SHZXUmNSRHVxRXdoN2ZWc3hSNE91MVhsOHBRX2hMMTlPeUc3QUJDVG80S2RqWEZYcEFUOWtLeWptVVJPOTVBaXc4U1o4MGZXRmtDMGdEazJLTXEtamJTZU1wcFZFaFJaWEpxQmhWNXVGZ1V0T0J4eUFjRzFZRjhFMW5Ob1dPWk9Eek5EUkRrOE1ZVWZrWVNpS0ZKb2pPZ0UxSjRIZkRoM0lBelY2MFR6V2NWcXJ0QnlwX2EyZ1V2a0JqcGpTeVF2Nmc2amJMSXpEaG10VnZLMmxDazhlMjUzdG1MSDNPQWk0Q0tZcWFZY0tjTTltSTdTRXBpVldlSjZZVFBEdmtORndpa0tNRjE3czVYQUlFUjZpczNNTVBpNkZTOWQ3ZmdMV25hUkpabDVNNUJDMldxN2NsVmYiLCJqd2tfcHViIjoiQUM0QUNBQUxBQUFFMGdBQUFCQUFJREpTSVhRSVVocjVPaDVkNXZWaWVGUDBmZG9pVFd3S1RicXJRRVRhVmx4QyIsImtleSI6ImVjYyJ9fSwiZW5jIjoiQTI1NkdDTSJ9","tag":"lir7v9YbCCZQKf5-yJ0BTQ"}` msg, err := jwe.Parse([]byte(data)) require.NoError(t, err, `jwe.Parse should succeed`) compact, err := jwe.Compact(msg) require.NoError(t, err, `jwe.Compact should succeed`) msg2, err := jwe.Parse(compact) require.NoError(t, err, `jwe.Parse should succeed`) require.Equal(t, msg, msg2, `data -> msg -> compact -> msg2 produces msg == msg2`) } func TestReadFile(t *testing.T) { const s = `eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkEyNTZHQ00ifQ.OKOawDo13gRp2ojaHV7LFpZcgV7T6DVZKTyKOMTYUmKoTCVJRgckCL9kiMT03JGeipsEdY3mx_etLbbWSrFr05kLzcSr4qKAq7YN7e9jwQRb23nfa6c9d-StnImGyFDbSv04uVuxIp5Zms1gNxKKK2Da14B8S4rzVRltdYwam_lDp5XnZAYpQdb76FdIKLaVmqgfwX7XWRxv2322i-vDxRfqNzo_tETKzpVLzfiwQyeyPGLBIO56YJ7eObdv0je81860ppamavo35UgoRdbYaBcoh9QcfylQr66oc6vFWXRcZ_ZT2LawVCWTIy3brGPi6UklfCpIMfIjf7iGdXKHzg.48V1_ALb6US04U3b.5eym8TW_c8SuK0ltJ3rpYIzOeDQz7TALvtu6UG9oMo4vpzs9tX_EFShS8iB7j6jiSdiwkIr3ajwQzaBtQD_A.XFBoMYUZodetZdvTiFvSkQ` f, err := os.CreateTemp(t.TempDir(), "test-read-file-*.jwe") require.NoError(t, err, `os.CreateTemp should succeed`) defer f.Close() fmt.Fprintf(f, "%s", s) _, err = jwe.ReadFile(f.Name()) require.NoError(t, err, `jwe.ReadFile should succeed`) } func TestCustomField(t *testing.T) { // XXX has global effect!!! const rfc3339Key = `x-test-rfc3339` const rfc1123Key = `x-test-rfc1123` jwe.RegisterCustomField(rfc3339Key, time.Time{}) jwe.RegisterCustomField(rfc1123Key, jwe.CustomDecodeFunc(func(data []byte) (any, error) { var s string if err := json.Unmarshal(data, &s); err != nil { return nil, err } return time.Parse(time.RFC1123, s) })) defer jwe.RegisterCustomField(rfc3339Key, nil) defer jwe.RegisterCustomField(rfc1123Key, nil) expected := time.Date(2015, 11, 4, 5, 12, 52, 0, time.UTC) rfc3339bytes, _ := expected.MarshalText() // RFC3339 rfc1123bytes := expected.Format(time.RFC1123) plaintext := []byte("Hello, World!") rsakey, err := jwxtest.GenerateRsaJwk() require.NoError(t, err, `jwxtest.GenerateRsaJwk() should succeed`) pubkey, err := jwk.PublicKeyOf(rsakey) require.NoError(t, err, `jwk.PublicKeyOf() should succeed`) t.Run("jwe.Parse", func(t *testing.T) { protected := jwe.NewHeaders() protected.Set(rfc3339Key, string(rfc3339bytes)) protected.Set(rfc1123Key, rfc1123bytes) encrypted, err := jwe.Encrypt(plaintext, jwe.WithKey(jwa.RSA_OAEP(), pubkey), jwe.WithProtectedHeaders(protected)) require.NoError(t, err, `jwe.Encrypt should succeed`) msg, err := jwe.Parse(encrypted) require.NoError(t, err, `jwe.Parse should succeed`) for _, key := range []string{rfc3339Key, rfc1123Key} { var v time.Time require.NoError(t, msg.ProtectedHeaders().Get(key, &v), `msg.Get(%q) should succeed`, key) require.Equal(t, expected, v, `values should match`) } }) t.Run("json.Unmarshal", func(t *testing.T) { protected := jwe.NewHeaders() protected.Set(rfc3339Key, string(rfc3339bytes)) protected.Set(rfc1123Key, rfc1123bytes) encrypted, err := jwe.Encrypt(plaintext, jwe.WithKey(jwa.RSA_OAEP(), pubkey), jwe.WithProtectedHeaders(protected), jwe.WithJSON()) require.NoError(t, err, `jwe.Encrypt should succeed`) msg := jwe.NewMessage() require.NoError(t, json.Unmarshal(encrypted, msg), `json.Unmarshal should succeed`) for _, key := range []string{rfc3339Key, rfc1123Key} { var v time.Time require.NoError(t, msg.ProtectedHeaders().Get(key, &v), `msg.Get(%q) should succeed`, key) require.Equal(t, expected, v, `values should match`) } }) } func TestGH554(t *testing.T) { const keyID = `very-secret-key` const plaintext = `hello world!` privkey, err := jwxtest.GenerateEcdsaJwk() require.NoError(t, err, `jwxtest.GenerateEcdsaJwk() should succeed`) _ = privkey.Set(jwk.KeyIDKey, keyID) pubkey, err := jwk.PublicKeyOf(privkey) require.NoError(t, err, `jwk.PublicKeyOf() should succeed`) kid, ok := pubkey.KeyID() require.True(t, ok, `key ID should be present`) require.Equal(t, keyID, kid, `key ID should match`) encrypted, err := jwe.Encrypt([]byte(plaintext), jwe.WithKey(jwa.ECDH_ES(), pubkey)) require.NoError(t, err, `jwk.Encrypt() should succeed`) msg, err := jwe.Parse(encrypted) require.NoError(t, err, `jwe.Parse() should succeed`) recipients := msg.Recipients() // The epk must have the same key ID as the original epkKid, ok := recipients[0].Headers().KeyID() require.True(t, ok, `key ID should be present`) require.Equal(t, keyID, epkKid, `key ID in epk should match`) } func TestGH803(t *testing.T) { privateKey, err := ecdsa.GenerateKey(elliptic.P384(), rand.Reader) require.NoError(t, err, `ecdsa.GenerateKey should succeed`) payload := []byte("Lorem Ipsum") apu := []byte(`Alice`) apv := []byte(`Bob`) hdrs := jwe.NewHeaders() hdrs.Set(jwe.AgreementPartyUInfoKey, apu) hdrs.Set(jwe.AgreementPartyVInfoKey, apv) encrypted, err := jwe.Encrypt( payload, jwe.WithJSON(), jwe.WithKey(jwa.ECDH_ES(), privateKey.PublicKey, jwe.WithPerRecipientHeaders(hdrs)), jwe.WithContentEncryption(jwa.A128GCM()), ) require.NoError(t, err, `jwe.Encrypt should succeed`) var msg jwe.Message decrypted, err := jwe.Decrypt( encrypted, jwe.WithKey(jwa.ECDH_ES(), privateKey), jwe.WithMessage(&msg), ) require.NoError(t, err, `jwe.Decrypt should succeed`) require.Equal(t, payload, decrypted, `decrypt messages match`) gotapu, ok := msg.ProtectedHeaders().AgreementPartyUInfo() require.True(t, ok, `apu should be present`) require.Equal(t, apu, gotapu, `apu should match`) gotapv, ok := msg.ProtectedHeaders().AgreementPartyVInfo() require.True(t, ok, `apv should be present`) require.Equal(t, apv, gotapv, `apv should match`) } func TestGH840(t *testing.T) { // Go 1.19+ panics if elliptic curve operations are called against // a point that's _NOT_ on the curve untrustedJWK := []byte(`{ "kty": "EC", "crv": "P-256", "x": "MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqx7D4", "y": "4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM", "d": "870MB6gfuTJ4HtUnUvYMyJpr5eUZNP4Bk43bVdj3eAE" }`) privkey, err := jwk.ParseKey(untrustedJWK) require.NoError(t, err, `jwk.ParseKey should succeed`) pubkey, err := privkey.PublicKey() require.NoError(t, err, `privkey.PublicKey should succeed`) const payload = `Lorem ipsum` _, 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(_ 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`) } func TestGH1001(t *testing.T) { rawKey, err := jwxtest.GenerateRsaKey() require.NoError(t, err, `jwxtest.GenerateRsaKey should succeed`) encrypted, err := jwe.Encrypt([]byte("Lorem Ipsum"), jwe.WithKey(jwa.RSA_OAEP(), rawKey.PublicKey)) require.NoError(t, err, `jwe.Encrypt should succeed`) var cek []byte decrypted, err := jwe.Decrypt(encrypted, jwe.WithKey(jwa.RSA_OAEP(), rawKey), jwe.WithCEK(&cek)) require.NoError(t, err, `jwe.Decrypt should succeed`) require.Equal(t, "Lorem Ipsum", string(decrypted), `decrypted message should match`) require.NotNil(t, cek, `cek should not be nil`) reEncrypted, err := jwe.EncryptStatic([]byte("Lorem Ipsum"), cek, jwe.WithKey(jwa.RSA_OAEP(), rawKey.PublicKey)) require.NoError(t, err, `jwe.EncryptStatic should succeed`) // sanity. empty CEKs should be rejected _, err = jwe.EncryptStatic([]byte("Lorem Ipsum"), nil, jwe.WithKey(jwa.RSA_OAEP(), rawKey.PublicKey)) require.Error(t, err, `jwe.Encryptstatic should fail with empty cek`) cek = []byte(nil) decrypted, err = jwe.Decrypt(reEncrypted, jwe.WithKey(jwa.RSA_OAEP(), rawKey), jwe.WithCEK(&cek)) require.NoError(t, err, `jwe.Decrypt should succeed`) require.Equal(t, "Lorem Ipsum", string(decrypted), `decrypted message should match`) require.NotNil(t, cek, `cek should not be nil`) } func TestGHSA_7f9x_gw85_8grf(t *testing.T) { token := []byte("eyJhbGciOiJQQkVTMi1IUzI1NitBMTI4S1ciLCJlbmMiOiJBMjU2R0NNIiwicDJjIjoyMDAwMDAwMDAwLCJwMnMiOiJNNzczSnlmV2xlX2FsSXNrc0NOTU9BIn0=.S8B1kXdIR7BM6i_TaGsgqEOxU-1Sgdakp4mHq7UVhn-_REzOiGz2gg.gU_LfzhBXtQdwYjh.9QUIS-RWkLc.m9TudmzUoCzDhHsGGfzmCA") key, err := jwk.Import([]byte(`abcdefg`)) require.NoError(t, err, `jwk.Import should succeed`) { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() done := make(chan struct{}) go func(t *testing.T, done chan struct{}) { _, err := jwe.Decrypt(token, jwe.WithKey(jwa.PBES2_HS256_A128KW(), key)) require.Error(t, err, `jwe.Decrypt should fail`) close(done) }(t, done) select { case <-done: case <-ctx.Done(): require.Fail(t, "jwe.Decrypt should not block") } } // NOTE: HAS GLOBAL EFFECT // Should allow for timeout to occur jwe.Settings(jwe.WithMaxPBES2Count(math.MaxInt32)) // put it back to normal after the test defer jwe.Settings(jwe.WithMaxPBES2Count(10000)) { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() done := make(chan struct{}) go func(done chan struct{}) { _, _ = jwe.Decrypt(token, jwe.WithKey(jwa.PBES2_HS256_A128KW(), key)) close(done) }(done) select { case <-done: require.Fail(t, "jwe.Decrypt should block") case <-ctx.Done(): // timeout occurred as it should } } } func TestCBCBufferSize(t *testing.T) { // NOTE: This has GLOBAL EFFECT jwe.Settings(jwe.WithCBCBufferSize(1)) defer jwe.Settings(jwe.WithCBCBufferSize(0)) key, err := jwxtest.GenerateRsaJwk() require.NoError(t, err, `jwxtest.GenerateRsaJwk should succeed`) _, err = jwe.Encrypt([]byte("Lorem Ipsum"), jwe.WithContentEncryption(jwa.A128CBC_HS256()), jwe.WithKey(jwa.RSA_OAEP(), key)) require.Error(t, err, `jwe.Encrypt should fail`) } func TestMaxDecompressBufferSize(t *testing.T) { // This payload size is intentionally set to a small value to avoid // causing problems for regular users and CI/CD systems. If you wish to // verify that root issue is fixed, you may want to try increasing the // payload size to a larger value. const payloadSize = 1 << 16 privkey, err := rsa.GenerateKey(rand.Reader, 2048) require.NoError(t, err, `rsa.GenerateKey should succeed`) pubkey := &privkey.PublicKey wrongPrivkey, err := rsa.GenerateKey(rand.Reader, 2048) require.NoError(t, err, `rsa.GenerateKey should succeed`) wrongPubkey := &wrongPrivkey.PublicKey payload := strings.Repeat("x", payloadSize) testcases := []struct { Name string GlobalMaxSize int64 PublicKey *rsa.PublicKey Error bool ProcessDecryptOptions func([]jwe.DecryptOption) []jwe.DecryptOption }{ // This should work, because we set the MaxSize to be large (==payload size) { Name: "same as payload size", GlobalMaxSize: payloadSize, PublicKey: pubkey, }, // This should fail, because we set the GlobalMaxSize to be smaller than the payload size { Name: "smaller than payload size", GlobalMaxSize: payloadSize - 1, PublicKey: pubkey, Error: true, }, // This should fail, because the public key does not match the // private key used to decrypt the payload. In essence this way // we do NOT trigger the root cause of this issue, but we bail out early { Name: "Wrong PublicKey", GlobalMaxSize: payloadSize, PublicKey: wrongPubkey, Error: true, }, { Name: "global=payloadSize-1, per-call=payloadSize", GlobalMaxSize: payloadSize - 1, PublicKey: pubkey, ProcessDecryptOptions: func(options []jwe.DecryptOption) []jwe.DecryptOption { return append(options, jwe.WithMaxDecompressBufferSize(payloadSize)) }, }, // This should be the last test case to put the value back to default :) { Name: "Default 10MB globally", GlobalMaxSize: 10 * 1024 * 1024, PublicKey: pubkey, }, } for _, tc := range testcases { t.Run(tc.Name, func(t *testing.T) { jwe.Settings(jwe.WithMaxDecompressBufferSize(tc.GlobalMaxSize)) encrypted, err := jwe.Encrypt([]byte(payload), jwe.WithKey(jwa.RSA_OAEP(), tc.PublicKey), jwe.WithContentEncryption(jwa.A128CBC_HS256()), jwe.WithCompress(jwa.Deflate())) require.NoError(t, err, `jwe.Encrypt should succeed`) decryptOptions := []jwe.DecryptOption{jwe.WithKey(jwa.RSA_OAEP(), privkey)} if fn := tc.ProcessDecryptOptions; fn != nil { decryptOptions = fn(decryptOptions) } _, err = jwe.Decrypt(encrypted, decryptOptions...) if tc.Error { require.Error(t, err, `jwe.Decrypt should fail`) } else { require.NoError(t, err, `jwe.Decrypt should succeed`) } }) } } func TestEncrypt_fail(t *testing.T) { _, err := jwe.Encrypt([]byte("Lorem Ipsum"), jwe.WithKey(jwa.RSA_OAEP(), nil)) require.Error(t, err, `jwe.Encrypt should fail`) require.ErrorIs(t, err, jwe.EncryptError(), `error should be of type jwe.EncryptError`) } func TestDecrypt_fail(t *testing.T) { t.Run("no key", func(t *testing.T) { _, err := jwe.Decrypt([]byte("Lorem Ipsum"), jwe.WithKey(jwa.RSA_OAEP(), nil)) require.Error(t, err, `jwe.Decrypt should fail`) require.ErrorIs(t, err, jwe.DecryptError(), `error should be of type jwe.DecryptError`) require.NotErrorIs(t, err, jwe.RecipientError(), `error should NOT be of type jwe.RecipientError`) }) t.Run("wrong key", func(t *testing.T) { privkey, err := jwxtest.GenerateRsaJwk() require.NoError(t, err, `jwxtest.GenerateRsaJwk should succeed`) encrypted, err := jwe.Encrypt([]byte("Lorem Ipsum"), jwe.WithKey(jwa.RSA_OAEP(), privkey)) require.NoError(t, err, `jwe.Encrypt should succeed`) wrongPrivkey, err := jwxtest.GenerateRsaJwk() require.NoError(t, err, `jwxtest.GenerateRsaJwk should succeed`) _, err = jwe.Decrypt(encrypted, jwe.WithKey(jwa.RSA_OAEP(), wrongPrivkey)) require.Error(t, err, `jwe.Decrypt should fail`) require.ErrorIs(t, err, jwe.DecryptError(), `error should be of type jwe.DecryptError`) require.ErrorIs(t, err, jwe.RecipientError(), `error should be of type jwe.RecipientError`) }) t.Run("malformed JSON", func(t *testing.T) { privkey, err := jwxtest.GenerateRsaJwk() require.NoError(t, err, `jwxtest.GenerateRsaJwk should succeed`) encrypted, err := jwe.Encrypt([]byte("Lorem Ipsum"), jwe.WithKey(jwa.RSA_OAEP(), privkey)) require.NoError(t, err, `jwe.Encrypt should succeed`) encrypted = encrypted[1:] _, err = jwe.Decrypt(encrypted, jwe.WithKey(jwa.RSA_OAEP(), privkey)) require.Error(t, err, `jwe.Decrypt should fail`) require.ErrorIs(t, err, jwe.DecryptError(), `error should be of type jwe.DecryptError`) require.ErrorIs(t, err, jwe.ParseError(), `error should be of type jwe.ParseError`) }) } func BenchmarkParseCompat(b *testing.B) { buf := []byte(`eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkEyNTZHQ00ifQ.OKOawDo13gRp2ojaHV7LFpZcgV7T6DVZKTyKOMTYUmKoTCVJRgckCL9kiMT03JGeipsEdY3mx_etLbbWSrFr05kLzcSr4qKAq7YN7e9jwQRb23nfa6c9d-StnImGyFDbSv04uVuxIp5Zms1gNxKKK2Da14B8S4rzVRltdYwam_lDp5XnZAYpQdb76FdIKLaVmqgfwX7XWRxv2322i-vDxRfqNzo_tETKzpVLzfiwQyeyPGLBIO56YJ7eObdv0je81860ppamavo35UgoRdbYaBcoh9QcfylQr66oc6vFWXRcZ_ZT2LawVCWTIy3brGPi6UklfCpIMfIjf7iGdXKHzg.48V1_ALb6US04U3b.5eym8TW_c8SuK0ltJ3rpYIzOeDQz7TALvtu6UG9oMo4vpzs9tX_EFShS8iB7j6jiSdiwkIr3ajwQzaBtQD_A.XFBoMYUZodetZdvTiFvSkQ`) for b.Loop() { _, err := jwe.Parse(buf) if err != nil { panic(err) } } } func TestGH1434(t *testing.T) { // Test if we can use ecdh.PrivateKey to encrypt and decrypt key, err := ecdh.P256().GenerateKey(rand.Reader) require.NoError(t, err, `ecdh.P256().GenerateKey should succeed`) const payload = `hello, world!` encrypted, err := jwe.Encrypt([]byte(payload), jwe.WithKey(jwa.ECDH_ES_A256KW(), key)) require.NoError(t, err, `jwe.Encrypt should succeed`) decrypted, err := jwe.Decrypt(encrypted, jwe.WithKey(jwa.ECDH_ES_A256KW(), key)) require.NoError(t, err, `jwe.Decrypt should succeed`) require.Equal(t, []byte(payload), decrypted, `decrypted payload should match original payload`) } type GH1470JWEFlattened struct { Protected string `json:"protected,omitempty"` Unprotected map[string]any `json:"unprotected,omitempty"` Header map[string]any `json:"header,omitempty"` } func TestGH1470(t *testing.T) { privkey, err := jwxtest.GenerateRsaJwk() require.NoError(t, err) pubkey, err := jwk.PublicKeyOf(privkey) require.NoError(t, err) const payload = "Lorem ipsum" TRUE := true FALSE := false t.Run("Flattened JSON serialization", func(t *testing.T) { // Per-recipient unprotected header includes kid recipientHeaders := jwe.NewHeaders() require.NoError(t, recipientHeaders.Set("kid", "recipient1")) recipient := jwe.WithKey( jwa.RSA_OAEP_256(), pubkey, jwe.WithPerRecipientHeaders(recipientHeaders), ) for _, lhm := range []*bool{nil, &TRUE, &FALSE} { var title string if lhm == nil { title = "legacy header merging left as default (nil)" } else if *lhm { title = "legacy header merging explicitly set to true" } else { title = "legacy header merging explicitly set to false" } t.Run(title, func(t *testing.T) { options := []jwe.EncryptOption{jwe.WithJSON(), recipient} if lhm != nil { options = append(options, jwe.WithLegacyHeaderMerging(*lhm)) } // Produce JSON Serialization (flattened for single recipient) encrypted, err := jwe.Encrypt([]byte(payload), options...) require.NoError(t, err) var object GH1470JWEFlattened require.NoError(t, json.Unmarshal(encrypted, &object), `json.Unmarshal should succeed`) protectedJSON, err := base64.RawURLEncoding.DecodeString(object.Protected) require.NoError(t, err) var protected map[string]any require.NoError(t, json.Unmarshal(protectedJSON, &protected)) for _, key := range []string{"alg", "kid"} { // Fail if the same names appear in both places (violates RFC 7516 §7.2.1) if _, has := protected[key]; has { _, also := object.Header[key] if lhm == nil || *lhm { require.True(t, also, `lhm = true, %q should exist`, key) } else { require.False(t, also, `lhm = false, %q should NOT exist`, key) } } } }) } }) // one more test: we need to make sure what happens when we're using compact serialization t.Run("Compact serialization", func(t *testing.T) { for _, lhm := range []*bool{nil, &TRUE, &FALSE} { var title string if lhm == nil { title = "legacy header merging left as default (nil)" } else if *lhm { title = "legacy header merging explicitly set to true" } else { title = "legacy header merging explicitly set to false" } for _, prh := range []bool{true, false} { if prh { title += " + per-recipient header is set" } else { title += " + per-recipient header is NOT set" } t.Run(title, func(t *testing.T) { var options []jwe.EncryptOption if prh { hdr := jwe.NewHeaders() _ = hdr.Set(jwe.KeyIDKey, "recipient1") options = append(options, jwe.WithKey( jwa.RSA_OAEP_256(), pubkey, jwe.WithPerRecipientHeaders(hdr), )) } else { options = append(options, jwe.WithKey(jwa.RSA_OAEP_256(), pubkey)) } if lhm != nil { options = append(options, jwe.WithLegacyHeaderMerging(*lhm)) } _, err := jwe.Encrypt([]byte(payload), options...) require.NoError(t, err, `jwe.Encrypt should succeed regardless`) }) } } }) // Check for empty "`headers"`, for both parsing and serializing to // flattened JSON serialization t.Run("Make sure flattened JSON serialization with non-legacy header merging does not contain `headers`", func(t *testing.T) { const payload = "Lorem ipsum" privkey, err := jwxtest.GenerateRsaKey() require.NoError(t, err, `jwxtest.GenerateRsaJwk should succeed`) encrypted, err := jwe.Encrypt([]byte(payload), jwe.WithJSON(), jwe.WithLegacyHeaderMerging(false), jwe.WithKey(jwa.RSA_OAEP_256(), privkey.PublicKey)) require.NoError(t, err, `jwe.Encrypt should succeed`) require.NotContains(t, string(encrypted), `"header":`, `flattened JSON serialization should NOT contain "header"`) }) t.Run("Parse Without Header for Flatted Token", func(t *testing.T) { token := `{ "ciphertext": "NR5BBCPc", "encrypted_key": "PENNLqJYzXYbKgiZCTe61Qhasls8wrqGQ7ytsvvm5_E6Yya79QyrtDaX_lYm2FitLPMkw4K-63NJf1Ltp9Ur-zZUJB2rQmGkhB4toBO-SkLSkSPhUr6lILKIFWrnbBjjy63g_Awfqb7tsWT1kD0rqO_xPhhN9s0xoHMpgFYY80QQxUBVYiADiu01H_o9tsh66Npch3HAI6t6bxUfVwyMjpWK7N6k74vc95l3-ZcAGxl6aeWAtUTD37bE5bVbvILfcb6HdK0Z81fmPcRe2o9W4wZ_SP2fEV67I-jrsx73rgL1a1c_9TB4vZX3XxppvBhwKHUBAksaq3aWQXr8gkWt-g", "iv": "TPrbYWe0oUeC9Eop", "protected": "eyJhbGciOiJSU0EtT0FFUC0yNTYiLCJlbmMiOiJBMjU2R0NNIiwia2lkIjoicmVjaXBpZW50MSJ9", "tag": "tOk33fmFy80qZVNyb22s0g" }` _, err := jwe.Parse([]byte(token)) require.NoError(t, err) }) } golang-github-lestrrat-go-jwx-3.0.13/jwe/jwebb/000077500000000000000000000000001515060566400212625ustar00rootroot00000000000000golang-github-lestrrat-go-jwx-3.0.13/jwe/jwebb/BUILD.bazel000066400000000000000000000020511515060566400231360ustar00rootroot00000000000000load("@rules_go//go:def.bzl", "go_library", "go_test") go_library( name = "jwebb", srcs = [ "content_cipher.go", "key_decrypt_asymmetric.go", "key_decrypt_symmetric.go", "key_encrypt_asymmetric.go", "key_encrypt_symmetric.go", "key_encryption.go", "keywrap.go", ], importpath = "github.com/lestrrat-go/jwx/v3/jwe/jwebb", visibility = ["//jwe:__subpackages__"], deps = [ "//internal/keyconv", "//internal/pool", "//jwe/internal/cipher", "//jwe/internal/concatkdf", "//jwe/internal/content_crypt", "//jwe/internal/keygen", "//internal/tokens", "@org_golang_x_crypto//pbkdf2", ], ) go_test( name = "jwebb_test", srcs = [ "decrypt_test.go", "jwebb_test.go", "keywrap_test.go", ], embed = [":jwebb"], deps = [ "//internal/jwxtest", "//jwa", "//jwe/internal/keygen", "//internal/tokens", "@com_github_stretchr_testify//require", ], )golang-github-lestrrat-go-jwx-3.0.13/jwe/jwebb/content_cipher.go000066400000000000000000000016701515060566400246210ustar00rootroot00000000000000package jwebb import ( "fmt" "github.com/lestrrat-go/jwx/v3/internal/tokens" "github.com/lestrrat-go/jwx/v3/jwe/internal/cipher" "github.com/lestrrat-go/jwx/v3/jwe/internal/content_crypt" ) // ContentEncryptionIsSupported checks if the content encryption algorithm is supported func ContentEncryptionIsSupported(alg string) bool { switch alg { case tokens.A128GCM, tokens.A192GCM, tokens.A256GCM, tokens.A128CBC_HS256, tokens.A192CBC_HS384, tokens.A256CBC_HS512: return true default: return false } } // CreateContentCipher creates a content encryption cipher for the given algorithm string func CreateContentCipher(alg string) (content_crypt.Cipher, error) { if !ContentEncryptionIsSupported(alg) { return nil, fmt.Errorf(`invalid content cipher algorithm (%s)`, alg) } cipher, err := cipher.NewAES(alg) if err != nil { return nil, fmt.Errorf(`failed to build content cipher for %s: %w`, alg, err) } return cipher, nil } golang-github-lestrrat-go-jwx-3.0.13/jwe/jwebb/decrypt_test.go000066400000000000000000000137271515060566400243340ustar00rootroot00000000000000package jwebb_test import ( "testing" "github.com/lestrrat-go/jwx/v3/internal/jwxtest" "github.com/lestrrat-go/jwx/v3/internal/tokens" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/lestrrat-go/jwx/v3/jwe/internal/keygen" "github.com/lestrrat-go/jwx/v3/jwe/jwebb" "github.com/stretchr/testify/require" ) func TestKeyDecryptAESKW(t *testing.T) { cek := testCEK sharedkey := testSharedKey32 // 32 bytes for A256KW // First encrypt to get encrypted key encrypted, err := jwebb.KeyEncryptAESKW(cek, tokens.A256KW, sharedkey) require.NoError(t, err) // Then decrypt it back decrypted, err := jwebb.KeyDecryptAESKW(encrypted.Bytes(), encrypted.Bytes(), tokens.A256KW, sharedkey) require.NoError(t, err) require.Equal(t, cek, decrypted) } func TestKeyDecryptDirect(t *testing.T) { sharedkey := testSharedKeyStr // For direct key agreement, the sharedkey is returned as is decrypted, err := jwebb.KeyDecryptDirect(nil, nil, tokens.DIRECT, sharedkey) require.NoError(t, err) require.Equal(t, sharedkey, decrypted) } func TestKeyDecryptPBES2(t *testing.T) { // PBES2 round-trip testing requires complex setup with matching salt/count // The function exists and is tested through integration tests elsewhere // For now we verify that the encryption works (which indirectly tests decryption logic) cek := testCEK password := testPassword encrypted, err := jwebb.KeyEncryptPBES2(cek, tokens.PBES2_HS256_A128KW, password) require.NoError(t, err) require.NotNil(t, encrypted) // Verify the result has the expected type with salt and count _, ok := encrypted.(keygen.ByteWithSaltAndCount) require.True(t, ok, "PBES2 encryption should return ByteWithSaltAndCount") } func TestKeyDecryptAESGCMKW(t *testing.T) { cek := testCEK sharedkey := testSharedKey16 // 16 bytes for A128GCMKW // First encrypt to get the proper encrypted data with IV and tag encrypted, err := jwebb.KeyEncryptAESGCMKW(cek, tokens.A128GCMKW, sharedkey) require.NoError(t, err) // Extract the IV and tag from the encrypted result // AES-GCMKW encryption returns a ByteWithIVAndTag encryptedWithIVTag, ok := encrypted.(keygen.ByteWithIVAndTag) require.True(t, ok, "encrypted result should have IV and tag") // Now decrypt using the proper IV and tag decrypted, err := jwebb.KeyDecryptAESGCMKW(encryptedWithIVTag.Bytes(), encryptedWithIVTag.Bytes(), tokens.A128GCMKW, sharedkey, encryptedWithIVTag.IV, encryptedWithIVTag.Tag) require.NoError(t, err) require.Equal(t, cek, decrypted) } func TestKeyDecryptRSA15(t *testing.T) { cek := testCEK // Generate RSA key pair privkey, err := jwxtest.GenerateRsaKey() require.NoError(t, err) pubkey := &privkey.PublicKey require.NoError(t, err) // First encrypt encrypted, err := jwebb.KeyEncryptRSA15(cek, tokens.RSA1_5, pubkey) require.NoError(t, err) // Then decrypt decrypted, err := jwebb.KeyDecryptRSA15(encrypted.Bytes(), encrypted.Bytes(), privkey, 8) // 8 bytes keysize for test require.NoError(t, err) require.NotNil(t, decrypted) require.Len(t, decrypted, 16) // Should be 8*2 = 16 bytes } func TestKeyDecryptRSAOAEP(t *testing.T) { cek := testCEK // Generate RSA key pair privkey, err := jwxtest.GenerateRsaKey() require.NoError(t, err) pubkey := &privkey.PublicKey require.NoError(t, err) // First encrypt encrypted, err := jwebb.KeyEncryptRSAOAEP(cek, tokens.RSA_OAEP, pubkey) require.NoError(t, err) // Then decrypt decrypted, err := jwebb.KeyDecryptRSAOAEP(encrypted.Bytes(), encrypted.Bytes(), tokens.RSA_OAEP, privkey) require.NoError(t, err) require.Equal(t, cek, decrypted) } func TestKeyDecryptECDHES(t *testing.T) { // Generate ECDSA key pairs for Alice and Bob alicePriv, err := jwxtest.GenerateEcdsaKey(jwa.P256()) require.NoError(t, err) bobPriv, err := jwxtest.GenerateEcdsaKey(jwa.P256()) require.NoError(t, err) apu := testAPU apv := testAPV // Test ECDH-ES direct key agreement (no key wrapping) decrypted, err := jwebb.KeyDecryptECDHES(nil, nil, tokens.ECDH_ES, apu, apv, alicePriv, &bobPriv.PublicKey, 16) require.NoError(t, err) require.NotNil(t, decrypted) require.Len(t, decrypted, 16) } func TestKeyDecryptECDHESKeyWrap(t *testing.T) { cek := testCEK // Generate ECDSA key pairs - use the same key for both sides to ensure compatibility privkey, err := jwxtest.GenerateEcdsaKey(jwa.P256()) require.NoError(t, err) pubkey := &privkey.PublicKey apu := testAPU apv := testAPV // First encrypt using ECDH-ES+A128KW encrypted, err := jwebb.KeyEncryptECDHESKeyWrapECDSA(cek, tokens.ECDH_ES_A128KW, apu, apv, pubkey, 16, tokens.A128GCM) require.NoError(t, err) // Extract the public key from the encrypted result // ECDH-ES encryption returns a ByteWithECPublicKey encryptedWithPubKey, ok := encrypted.(keygen.ByteWithECPublicKey) require.True(t, ok, "encrypted result should have public key") // Now decrypt using the corresponding private key and the ephemeral public key decrypted, err := jwebb.KeyDecryptECDHESKeyWrap(encryptedWithPubKey.Bytes(), encryptedWithPubKey.Bytes(), tokens.ECDH_ES_A128KW, apu, apv, privkey, encryptedWithPubKey.PublicKey, 16) require.NoError(t, err) require.Equal(t, cek, decrypted) } func TestDeriveECDHES(t *testing.T) { // Generate ECDSA key pairs alicePriv, err := jwxtest.GenerateEcdsaKey(jwa.P256()) require.NoError(t, err) bobPriv, err := jwxtest.GenerateEcdsaKey(jwa.P256()) require.NoError(t, err) apu := testAPU apv := testAPV // Test key derivation key1, err := jwebb.DeriveECDHES(tokens.A128GCM, apu, apv, alicePriv, &bobPriv.PublicKey, 16) require.NoError(t, err) require.Len(t, key1, 16) // Test that the same inputs produce the same key key2, err := jwebb.DeriveECDHES(tokens.A128GCM, apu, apv, alicePriv, &bobPriv.PublicKey, 16) require.NoError(t, err) require.Equal(t, key1, key2) // Test that different private keys produce different results charliePriv, err := jwxtest.GenerateEcdsaKey(jwa.P256()) require.NoError(t, err) key3, err := jwebb.DeriveECDHES(tokens.A128GCM, apu, apv, charliePriv, &bobPriv.PublicKey, 16) require.NoError(t, err) require.NotEqual(t, key1, key3) } golang-github-lestrrat-go-jwx-3.0.13/jwe/jwebb/jwebb.go000066400000000000000000000020201515060566400226740ustar00rootroot00000000000000// Package jwebb provides the building blocks (hence the name "bb") for JWE operations. // It should be thought of as a low-level API, almost akin to internal packages // that should not be used directly by users of the jwx package. However, these exist // to provide a more efficient way to perform JWE operations without the overhead of // the higher-level jwe package to power-users who know what they are doing. // // This package is currently considered EXPERIMENTAL, and the API may change // without notice. It is not recommended to use this package unless you are // fully aware of the implications of using it. // // All bb packages in jwx follow the same design principles: // 1. Does minimal checking of input parameters (for performance); callers need to ensure that the parameters are valid. // 2. All exported functions are stringly typed (i.e. they do not take any parameters unless they absolutely have to). // 3. Does not rely on other public jwx packages (they are standalone, except for internal packages). package jwebb golang-github-lestrrat-go-jwx-3.0.13/jwe/jwebb/jwebb_test.go000066400000000000000000000161611515060566400237460ustar00rootroot00000000000000package jwebb_test import ( "testing" "github.com/lestrrat-go/jwx/v3/internal/jwxtest" "github.com/lestrrat-go/jwx/v3/internal/tokens" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/lestrrat-go/jwx/v3/jwe/jwebb" "github.com/stretchr/testify/require" ) // Common test data var ( // Test CEK (Content Encryption Key) testCEK = []byte("0123456789abcdef") // Test shared keys of various sizes testSharedKey16 = []byte("0123456789abcdef") // 16 bytes for A128KW/A128GCMKW testSharedKey32 = []byte("0123456789abcdef0123456789abcdef") // 32 bytes for A256KW testSharedKeyStr = []byte("shared-key-bytes") // Generic shared key // Test password testPassword = []byte("password123") // ECDH-ES party info testAPU = []byte("Alice") testAPV = []byte("Bob") // Common test cases for algorithm validation invalidAlgTestCase = struct { name string alg string want bool }{"invalid", "invalid-alg", false} emptyAlgTestCase = struct { name string alg string want bool }{"empty", "", false} aeskwFalseTestCase = struct { name string alg string want bool }{"A128KW", tokens.A128KW, false} ) func TestKeyEncryptionIsAESKW(t *testing.T) { tests := []struct { name string alg string want bool }{ {"A128KW", tokens.A128KW, true}, {"A192KW", tokens.A192KW, true}, {"A256KW", tokens.A256KW, true}, invalidAlgTestCase, emptyAlgTestCase, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { require.Equal(t, tt.want, jwebb.IsAESKW(tt.alg)) }) } } func TestKeyEncryptionIsDirect(t *testing.T) { tests := []struct { name string alg string want bool }{ {"dir", tokens.DIRECT, true}, invalidAlgTestCase, aeskwFalseTestCase, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { require.Equal(t, tt.want, jwebb.IsDirect(tt.alg)) }) } } func TestKeyEncryptionIsPBES2(t *testing.T) { tests := []struct { name string alg string want bool }{ {"PBES2_HS256_A128KW", tokens.PBES2_HS256_A128KW, true}, {"PBES2_HS384_A192KW", tokens.PBES2_HS384_A192KW, true}, {"PBES2_HS512_A256KW", tokens.PBES2_HS512_A256KW, true}, invalidAlgTestCase, aeskwFalseTestCase, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { require.Equal(t, tt.want, jwebb.IsPBES2(tt.alg)) }) } } func TestKeyEncryptionIsAESGCMKW(t *testing.T) { tests := []struct { name string alg string want bool }{ {"A128GCMKW", tokens.A128GCMKW, true}, {"A192GCMKW", tokens.A192GCMKW, true}, {"A256GCMKW", tokens.A256GCMKW, true}, invalidAlgTestCase, aeskwFalseTestCase, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { require.Equal(t, tt.want, jwebb.IsAESGCMKW(tt.alg)) }) } } func TestKeyEncryptionIsECDHES(t *testing.T) { tests := []struct { name string alg string want bool }{ {"ECDH_ES", tokens.ECDH_ES, true}, {"ECDH_ES_A128KW", tokens.ECDH_ES_A128KW, true}, {"ECDH_ES_A192KW", tokens.ECDH_ES_A192KW, true}, {"ECDH_ES_A256KW", tokens.ECDH_ES_A256KW, true}, invalidAlgTestCase, aeskwFalseTestCase, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { require.Equal(t, tt.want, jwebb.IsECDHES(tt.alg)) }) } } func TestKeyEncryptionIsRSA15(t *testing.T) { tests := []struct { name string alg string want bool }{ {"RSA1_5", tokens.RSA1_5, true}, invalidAlgTestCase, {"RSA_OAEP", tokens.RSA_OAEP, false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { require.Equal(t, tt.want, jwebb.IsRSA15(tt.alg)) }) } } func TestKeyEncryptionIsRSAOAEP(t *testing.T) { tests := []struct { name string alg string want bool }{ {"RSA_OAEP", tokens.RSA_OAEP, true}, {"RSA_OAEP_256", tokens.RSA_OAEP_256, true}, {"RSA_OAEP_384", tokens.RSA_OAEP_384, true}, {"RSA_OAEP_512", tokens.RSA_OAEP_512, true}, invalidAlgTestCase, {"RSA1_5", tokens.RSA1_5, false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { require.Equal(t, tt.want, jwebb.IsRSAOAEP(tt.alg)) }) } } func TestKeyEncryptAESKW(t *testing.T) { cek := testCEK sharedkey := testSharedKey32 // 32 bytes for A256KW result, err := jwebb.KeyEncryptAESKW(cek, tokens.A256KW, sharedkey) require.NoError(t, err) require.NotNil(t, result) require.NotEqual(t, cek, result.Bytes()) } func TestKeyEncryptDirect(t *testing.T) { cek := testCEK sharedkey := testSharedKeyStr result, err := jwebb.KeyEncryptDirect(cek, tokens.DIRECT, sharedkey) require.NoError(t, err) require.Equal(t, sharedkey, result.Bytes()) } func TestKeyEncryptPBES2(t *testing.T) { cek := testCEK password := testPassword result, err := jwebb.KeyEncryptPBES2(cek, tokens.PBES2_HS256_A128KW, password) require.NoError(t, err) require.NotNil(t, result) require.NotEqual(t, cek, result.Bytes()) } func TestKeyEncryptAESGCMKW(t *testing.T) { cek := testCEK sharedkey := testSharedKey16 // 16 bytes for A128GCMKW result, err := jwebb.KeyEncryptAESGCMKW(cek, tokens.A128GCMKW, sharedkey) require.NoError(t, err) require.NotNil(t, result) require.NotEqual(t, cek, result.Bytes()) } func TestKeyEncryptRSA15(t *testing.T) { cek := testCEK // Generate RSA key pair privkey, err := jwxtest.GenerateRsaKey() require.NoError(t, err) pubkey := &privkey.PublicKey result, err := jwebb.KeyEncryptRSA15(cek, tokens.RSA1_5, pubkey) require.NoError(t, err) require.NotNil(t, result) require.NotEqual(t, cek, result.Bytes()) } func TestKeyEncryptRSAOAEP(t *testing.T) { cek := testCEK // Generate RSA key pair privkey, err := jwxtest.GenerateRsaKey() require.NoError(t, err) pubkey := &privkey.PublicKey result, err := jwebb.KeyEncryptRSAOAEP(cek, tokens.RSA_OAEP, pubkey) require.NoError(t, err) require.NotNil(t, result) require.NotEqual(t, cek, result.Bytes()) } func TestKeyEncryptECDHESECDSA(t *testing.T) { cek := testCEK // Generate ECDSA key pair privkey, err := jwxtest.GenerateEcdsaKey(jwa.P256()) require.NoError(t, err) pubkey := &privkey.PublicKey apu := testAPU apv := testAPV result, err := jwebb.KeyEncryptECDHESECDSA(cek, tokens.ECDH_ES, apu, apv, pubkey, 16, tokens.A128GCM) require.NoError(t, err) require.NotNil(t, result) } func TestContentEncryptionIsSupported(t *testing.T) { tests := []struct { name string alg string want bool }{ {"A128GCM", tokens.A128GCM, true}, {"A192GCM", tokens.A192GCM, true}, {"A256GCM", tokens.A256GCM, true}, {"A128CBC_HS256", tokens.A128CBC_HS256, true}, {"A192CBC_HS384", tokens.A192CBC_HS384, true}, {"A256CBC_HS512", tokens.A256CBC_HS512, true}, invalidAlgTestCase, emptyAlgTestCase, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { require.Equal(t, tt.want, jwebb.ContentEncryptionIsSupported(tt.alg)) }) } } func TestCreateContentCipher(t *testing.T) { supportedAlgs := []string{ tokens.A128GCM, tokens.A192GCM, tokens.A256GCM, tokens.A128CBC_HS256, tokens.A192CBC_HS384, tokens.A256CBC_HS512, } for _, alg := range supportedAlgs { t.Run(alg, func(t *testing.T) { cipher, err := jwebb.CreateContentCipher(alg) require.NoError(t, err) require.NotNil(t, cipher) }) } // Test unsupported algorithm _, err := jwebb.CreateContentCipher("invalid-alg") require.Error(t, err) } golang-github-lestrrat-go-jwx-3.0.13/jwe/jwebb/key_decrypt_asymmetric.go000066400000000000000000000131371515060566400263750ustar00rootroot00000000000000package jwebb import ( "crypto" "crypto/aes" "crypto/ecdh" "crypto/rand" "crypto/rsa" "crypto/sha1" "crypto/sha256" "crypto/sha512" "encoding/binary" "fmt" "hash" "github.com/lestrrat-go/jwx/v3/internal/keyconv" "github.com/lestrrat-go/jwx/v3/internal/tokens" "github.com/lestrrat-go/jwx/v3/jwe/internal/concatkdf" "github.com/lestrrat-go/jwx/v3/jwe/internal/keygen" ) func contentEncryptionKeySize(ctalg string) (uint32, error) { switch ctalg { case tokens.A128GCM: return tokens.KeySize16, nil case tokens.A192GCM: return tokens.KeySize24, nil case tokens.A256GCM: return tokens.KeySize32, nil case tokens.A128CBC_HS256: return tokens.KeySize32, nil case tokens.A192CBC_HS384: return tokens.KeySize48, nil case tokens.A256CBC_HS512: return tokens.KeySize64, nil default: return 0, fmt.Errorf(`unsupported content encryption algorithm %s`, ctalg) } } func KeyEncryptionECDHESKeySize(alg, ctalg string) (string, uint32, bool, error) { switch alg { case tokens.ECDH_ES: keysize, err := contentEncryptionKeySize(ctalg) if err != nil { return "", 0, false, err } return ctalg, keysize, false, nil case tokens.ECDH_ES_A128KW: return alg, tokens.KeySize16, true, nil case tokens.ECDH_ES_A192KW: return alg, tokens.KeySize24, true, nil case tokens.ECDH_ES_A256KW: return alg, tokens.KeySize32, true, nil default: return "", 0, false, fmt.Errorf(`unsupported key encryption algorithm %s`, alg) } } func DeriveECDHES(alg string, apu, apv []byte, privkeyif, pubkeyif any, keysize uint32) ([]byte, error) { pubinfo := make([]byte, 4) binary.BigEndian.PutUint32(pubinfo, keysize*tokens.BitsPerByte) var privkey *ecdh.PrivateKey var pubkey *ecdh.PublicKey if err := keyconv.ECDHPrivateKey(&privkey, privkeyif); err != nil { return nil, fmt.Errorf(`jwebb.DeriveECDHES: %w`, err) } if err := keyconv.ECDHPublicKey(&pubkey, pubkeyif); err != nil { return nil, fmt.Errorf(`jwebb.DeriveECDHES: %w`, err) } zBytes, err := privkey.ECDH(pubkey) if err != nil { return nil, fmt.Errorf(`jwebb.DeriveECDHES: unable to determine Z: %w`, err) } kdf := concatkdf.New(crypto.SHA256, []byte(alg), zBytes, apu, apv, pubinfo, []byte{}) key := make([]byte, keysize) if _, err := kdf.Read(key); err != nil { return nil, fmt.Errorf(`jwebb.DeriveECDHES: failed to read kdf: %w`, err) } return key, nil } func KeyDecryptECDHESKeyWrap(_, enckey []byte, alg string, apu, apv []byte, privkey, pubkey any, keysize uint32) ([]byte, error) { key, err := DeriveECDHES(alg, apu, apv, privkey, pubkey, keysize) if err != nil { return nil, fmt.Errorf(`failed to derive ECDHES encryption key: %w`, err) } block, err := aes.NewCipher(key) if err != nil { return nil, fmt.Errorf(`failed to create cipher for ECDH-ES key wrap: %w`, err) } return Unwrap(block, enckey) } func KeyDecryptECDHES(_, _ []byte, alg string, apu, apv []byte, privkey, pubkey any, keysize uint32) ([]byte, error) { key, err := DeriveECDHES(alg, apu, apv, privkey, pubkey, keysize) if err != nil { return nil, fmt.Errorf(`failed to derive ECDHES encryption key: %w`, err) } return key, nil } // RSA key decryption functions func KeyDecryptRSA15(_, enckey []byte, privkeyif any, keysize int) ([]byte, error) { var privkey *rsa.PrivateKey if err := keyconv.RSAPrivateKey(&privkey, privkeyif); err != nil { return nil, fmt.Errorf(`jwebb.KeyDecryptRSA15: %w`, err) } // Perform some input validation. expectedlen := privkey.PublicKey.N.BitLen() / tokens.BitsPerByte if expectedlen != len(enckey) { // Input size is incorrect, the encrypted payload should always match // the size of the public modulus (e.g. using a 2048 bit key will // produce 256 bytes of output). Reject this since it's invalid input. return nil, fmt.Errorf( "input size for key decrypt is incorrect (expected %d, got %d)", expectedlen, len(enckey), ) } // Generate a random CEK of the required size bk, err := keygen.Random(keysize * tokens.RSAKeyGenMultiplier) if err != nil { return nil, fmt.Errorf(`failed to generate key`) } cek := bk.Bytes() // Use a defer/recover pattern to handle potential panics from DecryptPKCS1v15SessionKey defer func() { // DecryptPKCS1v15SessionKey sometimes panics on an invalid payload // because of an index out of bounds error, which we want to ignore. // This has been fixed in Go 1.3.1 (released 2014/08/13), the recover() // only exists for preventing crashes with unpatched versions. // See: https://groups.google.com/forum/#!topic/golang-dev/7ihX6Y6kx9k // See: https://code.google.com/p/go/source/detail?r=58ee390ff31602edb66af41ed10901ec95904d33 _ = recover() }() // When decrypting an RSA-PKCS1v1.5 payload, we must take precautions to // prevent chosen-ciphertext attacks as described in RFC 3218, "Preventing // the Million Message Attack on Cryptographic Message Syntax". We are // therefore deliberately ignoring errors here. _ = rsa.DecryptPKCS1v15SessionKey(rand.Reader, privkey, enckey, cek) return cek, nil } func KeyDecryptRSAOAEP(_, enckey []byte, alg string, privkeyif any) ([]byte, error) { var privkey *rsa.PrivateKey if err := keyconv.RSAPrivateKey(&privkey, privkeyif); err != nil { return nil, fmt.Errorf(`jwebb.KeyDecryptRSAOAEP: %w`, err) } var hash hash.Hash switch alg { case tokens.RSA_OAEP: hash = sha1.New() case tokens.RSA_OAEP_256: hash = sha256.New() case tokens.RSA_OAEP_384: hash = sha512.New384() case tokens.RSA_OAEP_512: hash = sha512.New() default: return nil, fmt.Errorf(`failed to generate key encrypter for RSA-OAEP: RSA_OAEP/RSA_OAEP_256/RSA_OAEP_384/RSA_OAEP_512 required`) } return rsa.DecryptOAEP(hash, rand.Reader, privkey, enckey, []byte{}) } golang-github-lestrrat-go-jwx-3.0.13/jwe/jwebb/key_decrypt_symmetric.go000066400000000000000000000045041515060566400262320ustar00rootroot00000000000000package jwebb import ( "crypto/aes" cryptocipher "crypto/cipher" "crypto/sha256" "crypto/sha512" "fmt" "hash" "golang.org/x/crypto/pbkdf2" "github.com/lestrrat-go/jwx/v3/internal/tokens" ) // AES key wrap decryption functions // Use constants from tokens package // No need to redefine them here func KeyDecryptAESKW(_, enckey []byte, _ string, sharedkey []byte) ([]byte, error) { block, err := aes.NewCipher(sharedkey) if err != nil { return nil, fmt.Errorf(`failed to create cipher from shared key: %w`, err) } cek, err := Unwrap(block, enckey) if err != nil { return nil, fmt.Errorf(`failed to unwrap data: %w`, err) } return cek, nil } func KeyDecryptDirect(_, _ []byte, _ string, cek []byte) ([]byte, error) { return cek, nil } func KeyDecryptPBES2(_, enckey []byte, alg string, password []byte, salt []byte, count int) ([]byte, error) { var hashFunc func() hash.Hash var keylen int switch alg { case tokens.PBES2_HS256_A128KW: hashFunc = sha256.New keylen = tokens.KeySize16 case tokens.PBES2_HS384_A192KW: hashFunc = sha512.New384 keylen = tokens.KeySize24 case tokens.PBES2_HS512_A256KW: hashFunc = sha512.New keylen = tokens.KeySize32 default: return nil, fmt.Errorf(`unsupported PBES2 algorithm: %s`, alg) } // Derive key using PBKDF2 derivedKey := pbkdf2.Key(password, salt, count, keylen, hashFunc) // Use the derived key for AES key wrap return KeyDecryptAESKW(nil, enckey, alg, derivedKey) } func KeyDecryptAESGCMKW(recipientKey, _ []byte, _ string, sharedkey []byte, iv []byte, tag []byte) ([]byte, error) { if len(iv) != tokens.GCMIVSize { return nil, fmt.Errorf("GCM requires 96-bit iv, got %d", len(iv)*tokens.BitsPerByte) } if len(tag) != tokens.GCMTagSize { return nil, fmt.Errorf("GCM requires 128-bit tag, got %d", len(tag)*tokens.BitsPerByte) } block, err := aes.NewCipher(sharedkey) if err != nil { return nil, fmt.Errorf(`failed to create new AES cipher: %w`, err) } aesgcm, err := cryptocipher.NewGCM(block) if err != nil { return nil, fmt.Errorf(`failed to create new GCM wrap: %w`, err) } // Combine recipient key and tag for GCM decryption ciphertext := recipientKey[:] ciphertext = append(ciphertext, tag...) jek, err := aesgcm.Open(nil, iv, ciphertext, nil) if err != nil { return nil, fmt.Errorf(`failed to decode key: %w`, err) } return jek, nil } golang-github-lestrrat-go-jwx-3.0.13/jwe/jwebb/key_encrypt_asymmetric.go000066400000000000000000000115161515060566400264060ustar00rootroot00000000000000package jwebb import ( "crypto/aes" "crypto/ecdh" "crypto/ecdsa" "crypto/rand" "crypto/rsa" "crypto/sha1" "crypto/sha256" "crypto/sha512" "fmt" "hash" "github.com/lestrrat-go/jwx/v3/internal/tokens" "github.com/lestrrat-go/jwx/v3/jwe/internal/keygen" ) // KeyEncryptRSA15 encrypts the CEK using RSA PKCS#1 v1.5 func KeyEncryptRSA15(cek []byte, _ string, pubkey *rsa.PublicKey) (keygen.ByteSource, error) { encrypted, err := rsa.EncryptPKCS1v15(rand.Reader, pubkey, cek) if err != nil { return nil, fmt.Errorf(`failed to encrypt using PKCS1v15: %w`, err) } return keygen.ByteKey(encrypted), nil } // KeyEncryptRSAOAEP encrypts the CEK using RSA OAEP func KeyEncryptRSAOAEP(cek []byte, alg string, pubkey *rsa.PublicKey) (keygen.ByteSource, error) { var hash hash.Hash switch alg { case tokens.RSA_OAEP: hash = sha1.New() case tokens.RSA_OAEP_256: hash = sha256.New() case tokens.RSA_OAEP_384: hash = sha512.New384() case tokens.RSA_OAEP_512: hash = sha512.New() default: return nil, fmt.Errorf(`failed to generate key encrypter for RSA-OAEP: RSA_OAEP/RSA_OAEP_256/RSA_OAEP_384/RSA_OAEP_512 required`) } encrypted, err := rsa.EncryptOAEP(hash, rand.Reader, pubkey, cek, []byte{}) if err != nil { return nil, fmt.Errorf(`failed to OAEP encrypt: %w`, err) } return keygen.ByteKey(encrypted), nil } // generateECDHESKeyECDSA generates the key material for ECDSA keys using ECDH-ES func generateECDHESKeyECDSA(alg string, calg string, keysize uint32, pubkey *ecdsa.PublicKey, apu, apv []byte) (keygen.ByteWithECPublicKey, error) { // Generate the key directly kg, err := keygen.Ecdhes(alg, calg, int(keysize), pubkey, apu, apv) if err != nil { return keygen.ByteWithECPublicKey{}, fmt.Errorf(`failed to generate ECDSA key: %w`, err) } bwpk, ok := kg.(keygen.ByteWithECPublicKey) if !ok { return keygen.ByteWithECPublicKey{}, fmt.Errorf(`key generator generated invalid key (expected ByteWithECPublicKey)`) } return bwpk, nil } // generateECDHESKeyX25519 generates the key material for X25519 keys using ECDH-ES func generateECDHESKeyX25519(alg string, calg string, keysize uint32, pubkey *ecdh.PublicKey) (keygen.ByteWithECPublicKey, error) { // Generate the key directly kg, err := keygen.X25519(alg, calg, int(keysize), pubkey) if err != nil { return keygen.ByteWithECPublicKey{}, fmt.Errorf(`failed to generate X25519 key: %w`, err) } bwpk, ok := kg.(keygen.ByteWithECPublicKey) if !ok { return keygen.ByteWithECPublicKey{}, fmt.Errorf(`key generator generated invalid key (expected ByteWithECPublicKey)`) } return bwpk, nil } // KeyEncryptECDHESKeyWrapECDSA encrypts the CEK using ECDH-ES with key wrapping for ECDSA keys func KeyEncryptECDHESKeyWrapECDSA(cek []byte, alg string, apu, apv []byte, pubkey *ecdsa.PublicKey, keysize uint32, calg string) (keygen.ByteSource, error) { bwpk, err := generateECDHESKeyECDSA(alg, calg, keysize, pubkey, apu, apv) if err != nil { return nil, err } // For key wrapping algorithms, wrap the CEK with the generated key block, err := aes.NewCipher(bwpk.Bytes()) if err != nil { return nil, fmt.Errorf(`failed to generate cipher from generated key: %w`, err) } jek, err := Wrap(block, cek) if err != nil { return nil, fmt.Errorf(`failed to wrap data: %w`, err) } bwpk.ByteKey = keygen.ByteKey(jek) return bwpk, nil } // KeyEncryptECDHESKeyWrapX25519 encrypts the CEK using ECDH-ES with key wrapping for X25519 keys func KeyEncryptECDHESKeyWrapX25519(cek []byte, alg string, _ []byte, _ []byte, pubkey *ecdh.PublicKey, keysize uint32, calg string) (keygen.ByteSource, error) { bwpk, err := generateECDHESKeyX25519(alg, calg, keysize, pubkey) if err != nil { return nil, err } // For key wrapping algorithms, wrap the CEK with the generated key block, err := aes.NewCipher(bwpk.Bytes()) if err != nil { return nil, fmt.Errorf(`failed to generate cipher from generated key: %w`, err) } jek, err := Wrap(block, cek) if err != nil { return nil, fmt.Errorf(`failed to wrap data: %w`, err) } bwpk.ByteKey = keygen.ByteKey(jek) return bwpk, nil } // KeyEncryptECDHESECDSA encrypts using ECDH-ES direct (no key wrapping) for ECDSA keys func KeyEncryptECDHESECDSA(_ []byte, alg string, apu, apv []byte, pubkey *ecdsa.PublicKey, keysize uint32, calg string) (keygen.ByteSource, error) { bwpk, err := generateECDHESKeyECDSA(alg, calg, keysize, pubkey, apu, apv) if err != nil { return nil, err } // For direct ECDH-ES, return the generated key directly return bwpk, nil } // KeyEncryptECDHESX25519 encrypts using ECDH-ES direct (no key wrapping) for X25519 keys func KeyEncryptECDHESX25519(_ []byte, alg string, _, _ []byte, pubkey *ecdh.PublicKey, keysize uint32, calg string) (keygen.ByteSource, error) { bwpk, err := generateECDHESKeyX25519(alg, calg, keysize, pubkey) if err != nil { return nil, err } // For direct ECDH-ES, return the generated key directly return bwpk, nil } golang-github-lestrrat-go-jwx-3.0.13/jwe/jwebb/key_encrypt_symmetric.go000066400000000000000000000060741515060566400262500ustar00rootroot00000000000000package jwebb import ( "crypto/aes" cryptocipher "crypto/cipher" "crypto/rand" "crypto/sha256" "crypto/sha512" "fmt" "hash" "io" "golang.org/x/crypto/pbkdf2" "github.com/lestrrat-go/jwx/v3/internal/tokens" "github.com/lestrrat-go/jwx/v3/jwe/internal/keygen" ) // KeyEncryptAESKW encrypts the CEK using AES key wrap func KeyEncryptAESKW(cek []byte, _ string, sharedkey []byte) (keygen.ByteSource, error) { block, err := aes.NewCipher(sharedkey) if err != nil { return nil, fmt.Errorf(`failed to create cipher from shared key: %w`, err) } encrypted, err := Wrap(block, cek) if err != nil { return nil, fmt.Errorf(`failed to wrap data: %w`, err) } return keygen.ByteKey(encrypted), nil } // KeyEncryptDirect returns the CEK directly for DIRECT algorithm func KeyEncryptDirect(_ []byte, _ string, sharedkey []byte) (keygen.ByteSource, error) { return keygen.ByteKey(sharedkey), nil } // KeyEncryptPBES2 encrypts the CEK using PBES2 password-based encryption func KeyEncryptPBES2(cek []byte, alg string, password []byte) (keygen.ByteSource, error) { var hashFunc func() hash.Hash var keylen int switch alg { case tokens.PBES2_HS256_A128KW: hashFunc = sha256.New keylen = tokens.KeySize16 case tokens.PBES2_HS384_A192KW: hashFunc = sha512.New384 keylen = tokens.KeySize24 case tokens.PBES2_HS512_A256KW: hashFunc = sha512.New keylen = tokens.KeySize32 default: return nil, fmt.Errorf(`unsupported PBES2 algorithm: %s`, alg) } count := tokens.PBES2DefaultIterations salt := make([]byte, keylen) _, err := io.ReadFull(rand.Reader, salt) if err != nil { return nil, fmt.Errorf(`failed to get random salt: %w`, err) } fullsalt := []byte(alg) fullsalt = append(fullsalt, byte(tokens.PBES2NullByteSeparator)) fullsalt = append(fullsalt, salt...) // Derive key using PBKDF2 derivedKey := pbkdf2.Key(password, fullsalt, count, keylen, hashFunc) // Use the derived key for AES key wrap block, err := aes.NewCipher(derivedKey) if err != nil { return nil, fmt.Errorf(`failed to create cipher from derived key: %w`, err) } encrypted, err := Wrap(block, cek) if err != nil { return nil, fmt.Errorf(`failed to wrap data: %w`, err) } return keygen.ByteWithSaltAndCount{ ByteKey: encrypted, Salt: salt, Count: count, }, nil } // KeyEncryptAESGCMKW encrypts the CEK using AES GCM key wrap func KeyEncryptAESGCMKW(cek []byte, _ string, sharedkey []byte) (keygen.ByteSource, error) { block, err := aes.NewCipher(sharedkey) if err != nil { return nil, fmt.Errorf(`failed to create new AES cipher: %w`, err) } aesgcm, err := cryptocipher.NewGCM(block) if err != nil { return nil, fmt.Errorf(`failed to create new GCM wrap: %w`, err) } iv := make([]byte, aesgcm.NonceSize()) _, err = io.ReadFull(rand.Reader, iv) if err != nil { return nil, fmt.Errorf(`failed to get random iv: %w`, err) } encrypted := aesgcm.Seal(nil, iv, cek, nil) tag := encrypted[len(encrypted)-aesgcm.Overhead():] ciphertext := encrypted[:len(encrypted)-aesgcm.Overhead()] return keygen.ByteWithIVAndTag{ ByteKey: ciphertext, IV: iv, Tag: tag, }, nil } golang-github-lestrrat-go-jwx-3.0.13/jwe/jwebb/key_encryption.go000066400000000000000000000032001515060566400246460ustar00rootroot00000000000000package jwebb import ( "github.com/lestrrat-go/jwx/v3/internal/tokens" ) // IsECDHES checks if the algorithm is an ECDH-ES based algorithm func IsECDHES(alg string) bool { switch alg { case tokens.ECDH_ES, tokens.ECDH_ES_A128KW, tokens.ECDH_ES_A192KW, tokens.ECDH_ES_A256KW: return true default: return false } } // IsRSA15 checks if the algorithm is RSA1_5 func IsRSA15(alg string) bool { return alg == tokens.RSA1_5 } // IsRSAOAEP checks if the algorithm is an RSA-OAEP based algorithm func IsRSAOAEP(alg string) bool { switch alg { case tokens.RSA_OAEP, tokens.RSA_OAEP_256, tokens.RSA_OAEP_384, tokens.RSA_OAEP_512: return true default: return false } } // IsAESKW checks if the algorithm is an AES key wrap algorithm func IsAESKW(alg string) bool { switch alg { case tokens.A128KW, tokens.A192KW, tokens.A256KW: return true default: return false } } // IsAESGCMKW checks if the algorithm is an AES-GCM key wrap algorithm func IsAESGCMKW(alg string) bool { switch alg { case tokens.A128GCMKW, tokens.A192GCMKW, tokens.A256GCMKW: return true default: return false } } // IsPBES2 checks if the algorithm is a PBES2 based algorithm func IsPBES2(alg string) bool { switch alg { case tokens.PBES2_HS256_A128KW, tokens.PBES2_HS384_A192KW, tokens.PBES2_HS512_A256KW: return true default: return false } } // IsDirect checks if the algorithm is direct encryption func IsDirect(alg string) bool { return alg == tokens.DIRECT } // IsSymmetric checks if the algorithm is a symmetric key encryption algorithm func IsSymmetric(alg string) bool { return IsAESKW(alg) || IsAESGCMKW(alg) || IsPBES2(alg) || IsDirect(alg) } golang-github-lestrrat-go-jwx-3.0.13/jwe/jwebb/keywrap.go000066400000000000000000000057551515060566400233070ustar00rootroot00000000000000package jwebb import ( "crypto/cipher" "crypto/subtle" "encoding/binary" "fmt" "github.com/lestrrat-go/jwx/v3/internal/pool" "github.com/lestrrat-go/jwx/v3/internal/tokens" ) var keywrapDefaultIV = []byte{0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6} func Wrap(kek cipher.Block, cek []byte) ([]byte, error) { if len(cek)%tokens.KeywrapBlockSize != 0 { return nil, fmt.Errorf(`keywrap input must be %d byte blocks`, tokens.KeywrapBlockSize) } n := len(cek) / tokens.KeywrapChunkLen r := make([][]byte, n) for i := range n { r[i] = make([]byte, tokens.KeywrapChunkLen) copy(r[i], cek[i*tokens.KeywrapChunkLen:]) } buffer := pool.ByteSlice().GetCapacity(tokens.KeywrapChunkLen * 2) defer pool.ByteSlice().Put(buffer) // the byte slice has the capacity, but len is 0 buffer = buffer[:tokens.KeywrapChunkLen*2] tBytes := pool.ByteSlice().GetCapacity(tokens.KeywrapChunkLen) defer pool.ByteSlice().Put(tBytes) // the byte slice has the capacity, but len is 0 tBytes = tBytes[:tokens.KeywrapChunkLen] copy(buffer, keywrapDefaultIV) for t := range tokens.KeywrapRounds * n { copy(buffer[tokens.KeywrapChunkLen:], r[t%n]) kek.Encrypt(buffer, buffer) binary.BigEndian.PutUint64(tBytes, uint64(t+1)) for i := range tokens.KeywrapChunkLen { buffer[i] = buffer[i] ^ tBytes[i] } copy(r[t%n], buffer[tokens.KeywrapChunkLen:]) } out := make([]byte, (n+1)*tokens.KeywrapChunkLen) copy(out, buffer[:tokens.KeywrapChunkLen]) for i := range r { copy(out[(i+1)*tokens.KeywrapBlockSize:], r[i]) } return out, nil } func Unwrap(block cipher.Block, ciphertxt []byte) ([]byte, error) { if len(ciphertxt)%tokens.KeywrapChunkLen != 0 { return nil, fmt.Errorf(`keyunwrap input must be %d byte blocks`, tokens.KeywrapChunkLen) } n := (len(ciphertxt) / tokens.KeywrapChunkLen) - 1 r := make([][]byte, n) for i := range r { r[i] = make([]byte, tokens.KeywrapChunkLen) copy(r[i], ciphertxt[(i+1)*tokens.KeywrapChunkLen:]) } buffer := pool.ByteSlice().GetCapacity(tokens.KeywrapChunkLen * 2) defer pool.ByteSlice().Put(buffer) // the byte slice has the capacity, but len is 0 buffer = buffer[:tokens.KeywrapChunkLen*2] tBytes := pool.ByteSlice().GetCapacity(tokens.KeywrapChunkLen) defer pool.ByteSlice().Put(tBytes) // the byte slice has the capacity, but len is 0 tBytes = tBytes[:tokens.KeywrapChunkLen] copy(buffer[:tokens.KeywrapChunkLen], ciphertxt[:tokens.KeywrapChunkLen]) for t := tokens.KeywrapRounds*n - 1; t >= 0; t-- { binary.BigEndian.PutUint64(tBytes, uint64(t+1)) for i := range tokens.KeywrapChunkLen { buffer[i] = buffer[i] ^ tBytes[i] } copy(buffer[tokens.KeywrapChunkLen:], r[t%n]) block.Decrypt(buffer, buffer) copy(r[t%n], buffer[tokens.KeywrapChunkLen:]) } if subtle.ConstantTimeCompare(buffer[:tokens.KeywrapChunkLen], keywrapDefaultIV) == 0 { return nil, fmt.Errorf(`key unwrap: failed to unwrap key`) } out := make([]byte, n*tokens.KeywrapChunkLen) for i := range r { copy(out[i*tokens.KeywrapChunkLen:], r[i]) } return out, nil } golang-github-lestrrat-go-jwx-3.0.13/jwe/jwebb/keywrap_test.go000066400000000000000000000044121515060566400243330ustar00rootroot00000000000000package jwebb_test import ( "crypto/aes" "encoding/hex" "testing" "github.com/lestrrat-go/jwx/v3/jwe/jwebb" "github.com/stretchr/testify/require" ) // RFC 3394 test vectors for key wrapping type keyWrapVector struct { Kek string Data string Expected string } var rfc3394Vectors = []keyWrapVector{ { Kek: "000102030405060708090A0B0C0D0E0F", Data: "00112233445566778899AABBCCDDEEFF", Expected: "1FA68B0A8112B447AEF34BD8FB5A7B829D3E862371D2CFE5", }, { Kek: "000102030405060708090A0B0C0D0E0F1011121314151617", Data: "00112233445566778899AABBCCDDEEFF", Expected: "96778B25AE6CA435F92B5B97C050AED2468AB8A17AD84E5D", }, { Kek: "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F", Data: "00112233445566778899AABBCCDDEEFF0001020304050607", Expected: "A8F9BC1612C68B3FF6E6F4FBE30E71E4769C8B80A32CB8958CD5D17D6B254DA1", }, } func mustHexDecode(s string) []byte { b, err := hex.DecodeString(s) if err != nil { panic(err) } return b } func TestRFC3394_Wrap(t *testing.T) { for _, v := range rfc3394Vectors { t.Logf("kek = %s", v.Kek) t.Logf("data = %s", v.Data) t.Logf("expected = %s", v.Expected) kek := mustHexDecode(v.Kek) data := mustHexDecode(v.Data) expected := mustHexDecode(v.Expected) block, err := aes.NewCipher(kek) require.NoError(t, err, "NewCipher is successful") out, err := jwebb.Wrap(block, data) require.NoError(t, err, "Wrap is successful") require.Equal(t, expected, out, "Wrap generates expected output") unwrapped, err := jwebb.Unwrap(block, out) require.NoError(t, err, "Unwrap is successful") require.Equal(t, data, unwrapped, "Unwrapped data matches") } } func TestKeyWrap(t *testing.T) { // Test vectors from: http://csrc.nist.gov/groups/ST/toolkit/documents/kms/key-wrap.pdf for i, v := range rfc3394Vectors { kek := mustHexDecode(v.Kek) cek := mustHexDecode(v.Data) expected := mustHexDecode(v.Expected) block, err := aes.NewCipher(kek) require.NoError(t, err) out, err := jwebb.Wrap(block, cek) require.NoError(t, err) require.Equal(t, expected, out, "output %d not as expected", i) unwrapped, err := jwebb.Unwrap(block, out) require.NoError(t, err) require.Equal(t, cek, unwrapped, "unwrap %d did not return original input", i) } } golang-github-lestrrat-go-jwx-3.0.13/jwe/key_provider.go000066400000000000000000000111461515060566400232250ustar00rootroot00000000000000package jwe import ( "context" "fmt" "sync" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/lestrrat-go/jwx/v3/jwk" ) // KeyProvider is responsible for providing key(s) to encrypt or decrypt a payload. // Multiple `jwe.KeyProvider`s can be passed to `jwe.Encrypt()` or `jwe.Decrypt()` // // `jwe.Encrypt()` can only accept static key providers via `jwe.WithKey()`, // while `jwe.Decrypt()` can accept `jwe.WithKey()`, `jwe.WithKeySet()`, // and `jwe.WithKeyProvider()`. // // Understanding how this works is crucial to learn how this package works. // Here we will use `jwe.Decrypt()` as an example to show how the `KeyProvider` // works. // // `jwe.Encrypt()` is straightforward: the content encryption key is encrypted // using the provided keys, and JWS recipient objects are created for each. // // `jwe.Decrypt()` is a bit more involved, because there are cases you // will want to compute/deduce/guess the keys that you would like to // use for decryption. // // The first thing that `jwe.Decrypt()` needs to do is to collect the // KeyProviders from the option list that the user provided (presented in pseudocode): // // keyProviders := filterKeyProviders(options) // // Then, remember that a JWE message may contain multiple recipients in the // message. For each recipient, we call on the KeyProviders to give us // the key(s) to use on this CEK: // // for r in msg.Recipients { // for kp in keyProviders { // kp.FetchKeys(ctx, sink, r, msg) // ... // } // } // // The `sink` argument passed to the KeyProvider is a temporary storage // for the keys (either a jwk.Key or a "raw" key). The `KeyProvider` // is responsible for sending keys into the `sink`. // // When called, the `KeyProvider` created by `jwe.WithKey()` sends the same key, // `jwe.WithKeySet()` sends keys that matches a particular `kid` and `alg`, // and finally `jwe.WithKeyProvider()` allows you to execute arbitrary // logic to provide keys. If you are providing a custom `KeyProvider`, // you should execute the necessary checks or retrieval of keys, and // then send the key(s) to the sink: // // sink.Key(alg, key) // // These keys are then retrieved and tried for each recipient, until // a match is found: // // keys := sink.Keys() // for key in keys { // if decryptJWEKey(recipient.EncryptedKey(), key) { // return OK // } // } type KeyProvider interface { FetchKeys(context.Context, KeySink, Recipient, *Message) error } // KeySink is a data storage where `jwe.KeyProvider` objects should // send their keys to. type KeySink interface { Key(jwa.KeyEncryptionAlgorithm, any) } type algKeyPair struct { alg jwa.KeyAlgorithm key any } type algKeySink struct { mu sync.Mutex list []algKeyPair } func (s *algKeySink) Key(alg jwa.KeyEncryptionAlgorithm, key any) { s.mu.Lock() s.list = append(s.list, algKeyPair{alg, key}) s.mu.Unlock() } type staticKeyProvider struct { alg jwa.KeyEncryptionAlgorithm key any } func (kp *staticKeyProvider) FetchKeys(_ context.Context, sink KeySink, _ Recipient, _ *Message) error { sink.Key(kp.alg, kp.key) return nil } type keySetProvider struct { set jwk.Set requireKid bool } func (kp *keySetProvider) selectKey(sink KeySink, key jwk.Key, _ Recipient, _ *Message) error { if usage, ok := key.KeyUsage(); ok { if usage != "" && usage != jwk.ForEncryption.String() { return nil } } if v, ok := key.Algorithm(); ok { kalg, ok := jwa.LookupKeyEncryptionAlgorithm(v.String()) if !ok { return fmt.Errorf(`invalid key encryption algorithm %s`, v) } sink.Key(kalg, key) return nil } return nil } func (kp *keySetProvider) FetchKeys(_ context.Context, sink KeySink, r Recipient, msg *Message) error { if kp.requireKid { var key jwk.Key wantedKid, ok := r.Headers().KeyID() if !ok || wantedKid == "" { return fmt.Errorf(`failed to find matching key: no key ID ("kid") specified in token but multiple keys available in key set`) } // Otherwise we better be able to look up the key, baby. v, ok := kp.set.LookupKeyID(wantedKid) if !ok { return fmt.Errorf(`failed to find key with key ID %q in key set`, wantedKid) } key = v return kp.selectKey(sink, key, r, msg) } for i := range kp.set.Len() { key, _ := kp.set.Key(i) if err := kp.selectKey(sink, key, r, msg); err != nil { continue } } return nil } // KeyProviderFunc is a type of KeyProvider that is implemented by // a single function. You can use this to create ad-hoc `KeyProvider` // instances. type KeyProviderFunc func(context.Context, KeySink, Recipient, *Message) error func (kp KeyProviderFunc) FetchKeys(ctx context.Context, sink KeySink, r Recipient, msg *Message) error { return kp(ctx, sink, r, msg) } golang-github-lestrrat-go-jwx-3.0.13/jwe/message.go000066400000000000000000000353771515060566400221630ustar00rootroot00000000000000package jwe import ( "fmt" "sort" "strings" "github.com/lestrrat-go/jwx/v3/internal/base64" "github.com/lestrrat-go/jwx/v3/internal/json" "github.com/lestrrat-go/jwx/v3/internal/pool" "github.com/lestrrat-go/jwx/v3/internal/tokens" ) // NewRecipient creates a Recipient object func NewRecipient() Recipient { return &stdRecipient{ headers: NewHeaders(), } } func (r *stdRecipient) SetHeaders(h Headers) error { r.headers = h return nil } func (r *stdRecipient) SetEncryptedKey(v []byte) error { r.encryptedKey = v return nil } func (r *stdRecipient) Headers() Headers { return r.headers } func (r *stdRecipient) EncryptedKey() []byte { return r.encryptedKey } type recipientMarshalProxy struct { Headers Headers `json:"header"` EncryptedKey string `json:"encrypted_key"` } func (r *stdRecipient) UnmarshalJSON(buf []byte) error { var proxy recipientMarshalProxy proxy.Headers = NewHeaders() if err := json.Unmarshal(buf, &proxy); err != nil { return fmt.Errorf(`failed to unmarshal json into recipient: %w`, err) } r.headers = proxy.Headers decoded, err := base64.DecodeString(proxy.EncryptedKey) if err != nil { return fmt.Errorf(`failed to decode "encrypted_key": %w`, err) } r.encryptedKey = decoded return nil } func (r *stdRecipient) MarshalJSON() ([]byte, error) { buf := pool.BytesBuffer().Get() defer pool.BytesBuffer().Put(buf) buf.WriteString(`{"header":`) hdrbuf, err := json.Marshal(r.headers) if err != nil { return nil, fmt.Errorf(`failed to marshal recipient header: %w`, err) } buf.Write(hdrbuf) buf.WriteString(`,"encrypted_key":"`) buf.WriteString(base64.EncodeToString(r.encryptedKey)) buf.WriteString(`"}`) ret := make([]byte, buf.Len()) copy(ret, buf.Bytes()) return ret, nil } // NewMessage creates a new message func NewMessage() *Message { return &Message{} } func (m *Message) AuthenticatedData() []byte { return m.authenticatedData } func (m *Message) CipherText() []byte { return m.cipherText } func (m *Message) InitializationVector() []byte { return m.initializationVector } func (m *Message) Tag() []byte { return m.tag } func (m *Message) ProtectedHeaders() Headers { return m.protectedHeaders } func (m *Message) Recipients() []Recipient { return m.recipients } func (m *Message) UnprotectedHeaders() Headers { return m.unprotectedHeaders } const ( AuthenticatedDataKey = "aad" CipherTextKey = "ciphertext" CountKey = "p2c" InitializationVectorKey = "iv" ProtectedHeadersKey = "protected" RecipientsKey = "recipients" SaltKey = "p2s" TagKey = "tag" UnprotectedHeadersKey = "unprotected" HeadersKey = "header" EncryptedKeyKey = "encrypted_key" ) func (m *Message) Set(k string, v any) error { switch k { case AuthenticatedDataKey: buf, ok := v.([]byte) if !ok { return fmt.Errorf(`invalid value %T for %s key`, v, AuthenticatedDataKey) } m.authenticatedData = buf case CipherTextKey: buf, ok := v.([]byte) if !ok { return fmt.Errorf(`invalid value %T for %s key`, v, CipherTextKey) } m.cipherText = buf case InitializationVectorKey: buf, ok := v.([]byte) if !ok { return fmt.Errorf(`invalid value %T for %s key`, v, InitializationVectorKey) } m.initializationVector = buf case ProtectedHeadersKey: cv, ok := v.(Headers) if !ok { return fmt.Errorf(`invalid value %T for %s key`, v, ProtectedHeadersKey) } m.protectedHeaders = cv case RecipientsKey: cv, ok := v.([]Recipient) if !ok { return fmt.Errorf(`invalid value %T for %s key`, v, RecipientsKey) } m.recipients = cv case TagKey: buf, ok := v.([]byte) if !ok { return fmt.Errorf(`invalid value %T for %s key`, v, TagKey) } m.tag = buf case UnprotectedHeadersKey: cv, ok := v.(Headers) if !ok { return fmt.Errorf(`invalid value %T for %s key`, v, UnprotectedHeadersKey) } m.unprotectedHeaders = cv default: if m.unprotectedHeaders == nil { m.unprotectedHeaders = NewHeaders() } return m.unprotectedHeaders.Set(k, v) } return nil } type messageMarshalProxy struct { AuthenticatedData string `json:"aad,omitempty"` CipherText string `json:"ciphertext"` InitializationVector string `json:"iv,omitempty"` ProtectedHeaders json.RawMessage `json:"protected"` Recipients []json.RawMessage `json:"recipients,omitempty"` Tag string `json:"tag,omitempty"` UnprotectedHeaders Headers `json:"unprotected,omitempty"` // For flattened structure. Headers is NOT a Headers type, // so that we can detect its presence by checking proxy.Headers != nil Headers json.RawMessage `json:"header,omitempty"` EncryptedKey string `json:"encrypted_key,omitempty"` } type jsonKV struct { Key string Value string } func (m *Message) MarshalJSON() ([]byte, error) { // This is slightly convoluted, but we need to encode the // protected headers, so we do it by hand buf := pool.BytesBuffer().Get() defer pool.BytesBuffer().Put(buf) enc := json.NewEncoder(buf) var fields []jsonKV if cipherText := m.CipherText(); len(cipherText) > 0 { buf.Reset() if err := enc.Encode(base64.EncodeToString(cipherText)); err != nil { return nil, fmt.Errorf(`failed to encode %s field: %w`, CipherTextKey, err) } fields = append(fields, jsonKV{ Key: CipherTextKey, Value: strings.TrimSpace(buf.String()), }) } if iv := m.InitializationVector(); len(iv) > 0 { buf.Reset() if err := enc.Encode(base64.EncodeToString(iv)); err != nil { return nil, fmt.Errorf(`failed to encode %s field: %w`, InitializationVectorKey, err) } fields = append(fields, jsonKV{ Key: InitializationVectorKey, Value: strings.TrimSpace(buf.String()), }) } var encodedProtectedHeaders []byte if h := m.ProtectedHeaders(); h != nil { v, err := h.Encode() if err != nil { return nil, fmt.Errorf(`failed to encode protected headers: %w`, err) } encodedProtectedHeaders = v if len(encodedProtectedHeaders) <= 2 { // '{}' encodedProtectedHeaders = nil } else { fields = append(fields, jsonKV{ Key: ProtectedHeadersKey, Value: fmt.Sprintf("%q", encodedProtectedHeaders), }) } } if aad := m.AuthenticatedData(); len(aad) > 0 { aad = base64.Encode(aad) if encodedProtectedHeaders != nil { tmp := append(encodedProtectedHeaders, tokens.Period) aad = append(tmp, aad...) } buf.Reset() if err := enc.Encode(aad); err != nil { return nil, fmt.Errorf(`failed to encode %s field: %w`, AuthenticatedDataKey, err) } fields = append(fields, jsonKV{ Key: AuthenticatedDataKey, Value: strings.TrimSpace(buf.String()), }) } if recipients := m.Recipients(); len(recipients) > 0 { if len(recipients) == 1 { // Use flattened format if hdrs := recipients[0].Headers(); hdrs != nil { var skipHeaders bool if zeroer, ok := hdrs.(isZeroer); ok { if zeroer.isZero() { skipHeaders = true } } if !skipHeaders { buf.Reset() if err := enc.Encode(hdrs); err != nil { return nil, fmt.Errorf(`failed to encode %s field: %w`, HeadersKey, err) } fields = append(fields, jsonKV{ Key: HeadersKey, Value: strings.TrimSpace(buf.String()), }) } } if ek := recipients[0].EncryptedKey(); len(ek) > 0 { buf.Reset() if err := enc.Encode(base64.EncodeToString(ek)); err != nil { return nil, fmt.Errorf(`failed to encode %s field: %w`, EncryptedKeyKey, err) } fields = append(fields, jsonKV{ Key: EncryptedKeyKey, Value: strings.TrimSpace(buf.String()), }) } } else { buf.Reset() if err := enc.Encode(recipients); err != nil { return nil, fmt.Errorf(`failed to encode %s field: %w`, RecipientsKey, err) } fields = append(fields, jsonKV{ Key: RecipientsKey, Value: strings.TrimSpace(buf.String()), }) } } if tag := m.Tag(); len(tag) > 0 { buf.Reset() if err := enc.Encode(base64.EncodeToString(tag)); err != nil { return nil, fmt.Errorf(`failed to encode %s field: %w`, TagKey, err) } fields = append(fields, jsonKV{ Key: TagKey, Value: strings.TrimSpace(buf.String()), }) } if h := m.UnprotectedHeaders(); h != nil { unprotected, err := json.Marshal(h) if err != nil { return nil, fmt.Errorf(`failed to encode unprotected headers: %w`, err) } if len(unprotected) > 2 { fields = append(fields, jsonKV{ Key: UnprotectedHeadersKey, Value: fmt.Sprintf("%q", unprotected), }) } } sort.Slice(fields, func(i, j int) bool { return fields[i].Key < fields[j].Key }) buf.Reset() fmt.Fprintf(buf, `{`) for i, kv := range fields { if i > 0 { fmt.Fprintf(buf, `,`) } fmt.Fprintf(buf, `%q:%s`, kv.Key, kv.Value) } fmt.Fprintf(buf, `}`) ret := make([]byte, buf.Len()) copy(ret, buf.Bytes()) return ret, nil } func (m *Message) UnmarshalJSON(buf []byte) error { var proxy messageMarshalProxy proxy.UnprotectedHeaders = NewHeaders() if err := json.Unmarshal(buf, &proxy); err != nil { return fmt.Errorf(`failed to unmashal JSON into message: %w`, err) } // Get the string value var protectedHeadersStr string if err := json.Unmarshal(proxy.ProtectedHeaders, &protectedHeadersStr); err != nil { return fmt.Errorf(`failed to decode protected headers (1): %w`, err) } // It's now in _quoted_ base64 string. Decode it protectedHeadersRaw, err := base64.DecodeString(protectedHeadersStr) if err != nil { return fmt.Errorf(`failed to base64 decoded protected headers buffer: %w`, err) } h := NewHeaders() if err := json.Unmarshal(protectedHeadersRaw, h); err != nil { return fmt.Errorf(`failed to decode protected headers (2): %w`, err) } // if this were a flattened message, we would see a "header" and "ciphertext" // field. TODO: do both of these conditions need to meet, or just one? if proxy.Headers != nil || len(proxy.EncryptedKey) > 0 { recipient := NewRecipient() // `"heders"` could be empty. If that's the case, just skip the // following unmarshaling step if proxy.Headers != nil { hdrs := NewHeaders() if err := json.Unmarshal(proxy.Headers, hdrs); err != nil { return fmt.Errorf(`failed to decode headers field: %w`, err) } if err := recipient.SetHeaders(hdrs); err != nil { return fmt.Errorf(`failed to set new headers: %w`, err) } } if v := proxy.EncryptedKey; len(v) > 0 { buf, err := base64.DecodeString(v) if err != nil { return fmt.Errorf(`failed to decode encrypted key: %w`, err) } if err := recipient.SetEncryptedKey(buf); err != nil { return fmt.Errorf(`failed to set encrypted key: %w`, err) } } m.recipients = append(m.recipients, recipient) } else { for i, recipientbuf := range proxy.Recipients { recipient := NewRecipient() if err := json.Unmarshal(recipientbuf, recipient); err != nil { return fmt.Errorf(`failed to decode recipient at index %d: %w`, i, err) } m.recipients = append(m.recipients, recipient) } } if src := proxy.AuthenticatedData; len(src) > 0 { v, err := base64.DecodeString(src) if err != nil { return fmt.Errorf(`failed to decode "aad": %w`, err) } m.authenticatedData = v } if src := proxy.CipherText; len(src) > 0 { v, err := base64.DecodeString(src) if err != nil { return fmt.Errorf(`failed to decode "ciphertext": %w`, err) } m.cipherText = v } if src := proxy.InitializationVector; len(src) > 0 { v, err := base64.DecodeString(src) if err != nil { return fmt.Errorf(`failed to decode "iv": %w`, err) } m.initializationVector = v } if src := proxy.Tag; len(src) > 0 { v, err := base64.DecodeString(src) if err != nil { return fmt.Errorf(`failed to decode "tag": %w`, err) } m.tag = v } m.protectedHeaders = h if m.storeProtectedHeaders { // this is later used for decryption m.rawProtectedHeaders = base64.Encode(protectedHeadersRaw) } if iz, ok := proxy.UnprotectedHeaders.(isZeroer); ok { if !iz.isZero() { m.unprotectedHeaders = proxy.UnprotectedHeaders } } if len(m.recipients) == 0 { if err := m.makeDummyRecipient(proxy.EncryptedKey, m.protectedHeaders); err != nil { return fmt.Errorf(`failed to setup recipient: %w`, err) } } return nil } func (m *Message) makeDummyRecipient(enckeybuf string, protected Headers) error { // Recipients in this case should not contain the content encryption key, // so move that out hdrs, err := protected.Clone() if err != nil { return fmt.Errorf(`failed to clone headers: %w`, err) } if err := hdrs.Remove(ContentEncryptionKey); err != nil { return fmt.Errorf(`failed to remove %#v from public header: %w`, ContentEncryptionKey, err) } enckey, err := base64.DecodeString(enckeybuf) if err != nil { return fmt.Errorf(`failed to decode encrypted key: %w`, err) } if err := m.Set(RecipientsKey, []Recipient{ &stdRecipient{ headers: hdrs, encryptedKey: enckey, }, }); err != nil { return fmt.Errorf(`failed to set %s: %w`, RecipientsKey, err) } return nil } // Compact generates a JWE message in compact serialization format from a // `*jwe.Message` object. The object contain exactly one recipient, or // an error is returned. // // This function currently does not take any options, but the function // signature contains `options` for possible future expansion of the API func Compact(m *Message, _ ...CompactOption) ([]byte, error) { if len(m.recipients) != 1 { return nil, fmt.Errorf(`wrong number of recipients for compact serialization`) } recipient := m.recipients[0] // The protected header must be a merge between the message-wide // protected header AND the recipient header // There's something wrong if m.protectedHeaders is nil, but // it could happen if m.protectedHeaders == nil { return nil, fmt.Errorf(`invalid protected header`) } hcopy, err := m.protectedHeaders.Clone() if err != nil { return nil, fmt.Errorf(`failed to copy protected header: %w`, err) } hcopy, err = hcopy.Merge(m.unprotectedHeaders) if err != nil { return nil, fmt.Errorf(`failed to merge unprotected header: %w`, err) } hcopy, err = hcopy.Merge(recipient.Headers()) if err != nil { return nil, fmt.Errorf(`failed to merge recipient header: %w`, err) } protected, err := hcopy.Encode() if err != nil { return nil, fmt.Errorf(`failed to encode header: %w`, err) } encryptedKey := base64.Encode(recipient.EncryptedKey()) iv := base64.Encode(m.initializationVector) cipher := base64.Encode(m.cipherText) tag := base64.Encode(m.tag) buf := pool.BytesBuffer().Get() defer pool.BytesBuffer().Put(buf) buf.Grow(len(protected) + len(encryptedKey) + len(iv) + len(cipher) + len(tag) + 4) buf.Write(protected) buf.WriteByte(tokens.Period) buf.Write(encryptedKey) buf.WriteByte(tokens.Period) buf.Write(iv) buf.WriteByte(tokens.Period) buf.Write(cipher) buf.WriteByte(tokens.Period) buf.Write(tag) result := make([]byte, buf.Len()) copy(result, buf.Bytes()) return result, nil } golang-github-lestrrat-go-jwx-3.0.13/jwe/message_test.go000066400000000000000000000011331515060566400232010ustar00rootroot00000000000000package jwe_test import ( "testing" "github.com/lestrrat-go/jwx/v3/internal/json" "github.com/stretchr/testify/require" "github.com/lestrrat-go/jwx/v3/jwe" ) func TestRecipient(t *testing.T) { t.Run("JSON Marshaling", func(t *testing.T) { const src = `{"header":{"foo":"bar"},"encrypted_key":"Zm9vYmFyYmF6"}` r1 := jwe.NewRecipient() require.NoError(t, json.Unmarshal([]byte(src), r1), `json.Unmarshal should succeed`) buf, err := json.Marshal(r1) require.NoError(t, err, `json.Marshal should succeed`) require.Equal(t, []byte(src), buf, `json representation should match`) }) } golang-github-lestrrat-go-jwx-3.0.13/jwe/options.go000066400000000000000000000062661515060566400222250ustar00rootroot00000000000000package jwe import ( "github.com/lestrrat-go/jwx/v3/jwa" "github.com/lestrrat-go/jwx/v3/jwk" "github.com/lestrrat-go/option/v2" ) // WithProtectedHeaders is used to specify contents of the protected header. // Some fields such as "enc" and "zip" will be overwritten when encryption is // performed. // // There is no equivalent for unprotected headers in this implementation func WithProtectedHeaders(h Headers) EncryptOption { cloned, _ := h.Clone() return &encryptOption{option.New(identProtectedHeaders{}, cloned)} } type withKey struct { alg jwa.KeyAlgorithm key any headers Headers } type WithKeySuboption interface { Option withKeySuboption() } type withKeySuboption struct { Option } func (*withKeySuboption) withKeySuboption() {} // WithPerRecipientHeaders is used to pass header values for each recipient. // Note that these headers are by definition _unprotected_. func WithPerRecipientHeaders(hdr Headers) WithKeySuboption { return &withKeySuboption{option.New(identPerRecipientHeaders{}, hdr)} } // WithKey is used to pass a static algorithm/key pair to either `jwe.Encrypt()` or `jwe.Decrypt()`. // either a raw key or `jwk.Key` may be passed as `key`. // // The `alg` parameter is the identifier for the key encryption algorithm that should be used. // It is of type `jwa.KeyAlgorithm` but in reality you can only pass `jwa.KeyEncryptionAlgorithm` // types. It is this way so that the value in `(jwk.Key).Algorithm()` can be directly // passed to the option. If you specify other algorithm types such as `jwa.SignatureAlgorithm`, // then you will get an error when `jwe.Encrypt()` or `jwe.Decrypt()` is executed. // // Unlike `jwe.WithKeySet()`, the `kid` field does not need to match for the key // to be tried. func WithKey(alg jwa.KeyAlgorithm, key any, options ...WithKeySuboption) EncryptDecryptOption { var hdr Headers for _, option := range options { switch option.Ident() { case identPerRecipientHeaders{}: if err := option.Value(&hdr); err != nil { panic(`jwe.WithKey() requires Headers value for WithPerRecipientHeaders option`) } } } return &encryptDecryptOption{option.New(identKey{}, &withKey{ alg: alg, key: key, headers: hdr, })} } func WithKeySet(set jwk.Set, options ...WithKeySetSuboption) DecryptOption { requireKid := true for _, option := range options { switch option.Ident() { case identRequireKid{}: if err := option.Value(&requireKid); err != nil { panic(`jwe.WithKeySet() requires bool value for WithRequireKid option`) } } } return WithKeyProvider(&keySetProvider{ set: set, requireKid: requireKid, }) } // WithJSON specifies that the result of `jwe.Encrypt()` is serialized in // JSON format. // // If you pass multiple keys to `jwe.Encrypt()`, it will fail unless // you also pass this option. func WithJSON(options ...WithJSONSuboption) EncryptOption { var pretty bool for _, option := range options { switch option.Ident() { case identPretty{}: if err := option.Value(&pretty); err != nil { panic(`jwe.WithJSON() requires bool value for WithPretty option`) } } } format := fmtJSON if pretty { format = fmtJSONPretty } return &encryptOption{option.New(identSerialization{}, format)} } golang-github-lestrrat-go-jwx-3.0.13/jwe/options.yaml000066400000000000000000000212531515060566400225530ustar00rootroot00000000000000package_name: jwe output: jwe/options_gen.go interfaces: - name: GlobalOption comment: | GlobalOption describes options that changes global settings for this package - name: GlobalDecryptOption comment: | GlobalDecryptOption describes options that changes global settings and for each call of the `jwe.Decrypt` function methods: - globalOption - decryptOption - name: CompactOption comment: | CompactOption describes options that can be passed to `jwe.Compact` - name: DecryptOption comment: | DecryptOption describes options that can be passed to `jwe.Decrypt` - name: EncryptOption comment: | EncryptOption describes options that can be passed to `jwe.Encrypt` - name: EncryptDecryptOption methods: - encryptOption - decryptOption comment: | EncryptDecryptOption describes options that can be passed to either `jwe.Encrypt` or `jwe.Decrypt` - name: WithJSONSuboption concrete_type: withJSONSuboption comment: | JSONSuboption describes suboptions that can be passed to `jwe.WithJSON()` option - name: WithKeySetSuboption comment: | WithKeySetSuboption is a suboption passed to the WithKeySet() option - name: ParseOption methods: - readFileOption comment: | ReadFileOption is a type of `Option` that can be passed to `jwe.Parse` - name: ReadFileOption comment: | ReadFileOption is a type of `Option` that can be passed to `jwe.ReadFile` options: - ident: Key skip_option: true - ident: Pretty skip_option: true - ident: ProtectedHeaders skip_option: true - ident: PerRecipientHeaders skip_option: true - ident: KeyProvider interface: DecryptOption argument_type: KeyProvider - ident: Context interface: DecryptOption argument_type: context.Context comment: | WithContext specifies the context.Context object to use when decrypting a JWE message. If not provided, context.Background() will be used. - ident: Serialization option_name: WithCompact interface: EncryptOption constant_value: fmtCompact comment: | WithCompact specifies that the result of `jwe.Encrypt()` is serialized in compact format. By default `jwe.Encrypt()` will opt to use compact format, so you usually do not need to specify this option other than to be explicit about it - ident: Compress interface: EncryptOption argument_type: jwa.CompressionAlgorithm comment: | WithCompress specifies the compression algorithm to use when encrypting a payload using `jwe.Encrypt` (Yes, we know it can only be "" or "DEF", but the way the specification is written it could allow for more options, and therefore this option takes an argument) - ident: ContentEncryptionAlgorithm interface: EncryptOption option_name: WithContentEncryption argument_type: jwa.ContentEncryptionAlgorithm comment: | WithContentEncryptionAlgorithm specifies the algorithm to encrypt the JWE message content with. If not provided, `jwa.A256GCM` is used. - ident: Message interface: DecryptOption argument_type: '*Message' comment: | WithMessage provides a message object to be populated by `jwe.Decrypt` Using this option allows you to decrypt AND obtain the `jwe.Message` in one go. - ident: RequireKid interface: WithKeySetSuboption argument_type: bool comment: | WithRequiredKid specifies whether the keys in the jwk.Set should only be matched if the target JWE message's Key ID and the Key ID in the given key matches. - ident: Pretty interface: WithJSONSuboption argument_type: bool comment: | WithPretty specifies whether the JSON output should be formatted and indented - ident: MergeProtectedHeaders interface: EncryptOption argument_type: bool comment: | WithMergeProtectedHeaders specify that when given multiple headers as options to `jwe.Encrypt`, these headers should be merged instead of overwritten - ident: FS interface: ReadFileOption argument_type: fs.FS comment: | WithFS specifies the source `fs.FS` object to read the file from. - ident: KeyUsed interface: DecryptOption argument_type: 'any' comment: | WithKeyUsed allows you to specify the `jwe.Decrypt()` function to return the key used for decryption. This may be useful when you specify multiple key sources or if you pass a `jwk.Set` and you want to know which key was successful at decrypting the CEK. `v` must be a pointer to an empty `any`. Do not use `jwk.Key` here unless you are 100% sure that all keys that you have provided are instances of `jwk.Key` (remember that the jwx API allows users to specify a raw key such as *rsa.PublicKey) - ident: CEK interface: DecryptOption argument_type: '*[]byte' comment: | WithCEK allows users to specify a variable to store the CEK used in the message upon successful decryption. The variable must be a pointer to a byte slice, and it will only be populated if the decryption is successful. This option is currently considered EXPERIMENTAL, and is subject to future changes across minor/micro versions. - ident: MaxPBES2Count interface: GlobalOption argument_type: int comment: | WithMaxPBES2Count specifies the maximum number of PBES2 iterations to use when decrypting a message. If not specified, the default value of 10,000 is used. This option has a global effect. - ident: MaxDecompressBufferSize interface: GlobalDecryptOption argument_type: int64 comment: | WithMaxDecompressBufferSize specifies the maximum buffer size for used when decompressing the payload of a JWE message. If a compressed JWE payload exceeds this amount when decompressed, jwe.Decrypt will return an error. The default value is 10MB. This option can be used for `jwe.Settings()`, which changes the behavior globally, or for `jwe.Decrypt()`, which changes the behavior for that specific call. - ident: CBCBufferSize interface: GlobalOption argument_type: int64 comment: | WithCBCBufferSize specifies the maximum buffer size for internal calculations, such as when AES-CBC is performed. The default value is 256MB. If set to an invalid value, the default value is used. In v2, this option was called MaxBufferSize. This option has a global effect. - ident: LegacyHeaderMerging interface: EncryptOption argument_type: bool option_name: WithLegacyHeaderMerging comment: | WithLegacyHeaderMerging specifies whether to perform legacy header merging when encrypting a JWE message in JSON serialization, when there is a single recipient. This behavior is enabled by default for backwards compatibility. When a JWE message is encrypted in JSON serialization, and there is only one recipient, this library automatically serializes the message in flattened JSON serialization format. In older versions of this library, the protected headers and the per-recipient headers were merged together before computing the AAD (Additional Authenticated Data), but the per-recipient headers were kept as-is in the `header` field of the recipient object. This behavior is not compliant with the JWE specification, which states that the headers must be disjoint. Passing this option with a value of `false` disables this legacy behavior, and while the per-recipient headers and protected headers are still merged for the purpose of computing AAD, the per-recipient headers are cleared after merging, so that the resulting JWE message is compliant with the specification. This option has no effect when there are multiple recipients, or when the serialization format is compact serialization. For multiple recipients (i.e. full JSON serialization), the protected headers and per-recipient headers are never merged, and it is the caller's responsibility to ensure that the headers are disjoint. In compact serialization, there are no per-recipient headers; in fact, the protected headers are the only headers that exist, and therefore there is no possibility of header collision after merging (note: while per-recipient headers do not make sense in compact serialization, this library does not prevent you from setting them -- they are all just merged into the protected headers). In future versions, the new behavior will be the default. New users are encouraged to set this option to `false` now to avoid future issues.golang-github-lestrrat-go-jwx-3.0.13/jwe/options_gen.go000066400000000000000000000267131515060566400230550ustar00rootroot00000000000000// Code generated by tools/cmd/genoptions/main.go. DO NOT EDIT. package jwe import ( "context" "io/fs" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/lestrrat-go/option/v2" ) type Option = option.Interface // CompactOption describes options that can be passed to `jwe.Compact` type CompactOption interface { Option compactOption() } type compactOption struct { Option } func (*compactOption) compactOption() {} // DecryptOption describes options that can be passed to `jwe.Decrypt` type DecryptOption interface { Option decryptOption() } type decryptOption struct { Option } func (*decryptOption) decryptOption() {} // EncryptDecryptOption describes options that can be passed to either `jwe.Encrypt` or `jwe.Decrypt` type EncryptDecryptOption interface { Option encryptOption() decryptOption() } type encryptDecryptOption struct { Option } func (*encryptDecryptOption) encryptOption() {} func (*encryptDecryptOption) decryptOption() {} // EncryptOption describes options that can be passed to `jwe.Encrypt` type EncryptOption interface { Option encryptOption() } type encryptOption struct { Option } func (*encryptOption) encryptOption() {} // GlobalDecryptOption describes options that changes global settings and for each call of the `jwe.Decrypt` function type GlobalDecryptOption interface { Option globalOption() decryptOption() } type globalDecryptOption struct { Option } func (*globalDecryptOption) globalOption() {} func (*globalDecryptOption) decryptOption() {} // GlobalOption describes options that changes global settings for this package type GlobalOption interface { Option globalOption() } type globalOption struct { Option } func (*globalOption) globalOption() {} // ReadFileOption is a type of `Option` that can be passed to `jwe.Parse` type ParseOption interface { Option readFileOption() } type parseOption struct { Option } func (*parseOption) readFileOption() {} // ReadFileOption is a type of `Option` that can be passed to `jwe.ReadFile` type ReadFileOption interface { Option readFileOption() } type readFileOption struct { Option } func (*readFileOption) readFileOption() {} // JSONSuboption describes suboptions that can be passed to `jwe.WithJSON()` option type WithJSONSuboption interface { Option withJSONSuboption() } type withJSONSuboption struct { Option } func (*withJSONSuboption) withJSONSuboption() {} // WithKeySetSuboption is a suboption passed to the WithKeySet() option type WithKeySetSuboption interface { Option withKeySetSuboption() } type withKeySetSuboption struct { Option } func (*withKeySetSuboption) withKeySetSuboption() {} type identCBCBufferSize struct{} type identCEK struct{} type identCompress struct{} type identContentEncryptionAlgorithm struct{} type identContext struct{} type identFS struct{} type identKey struct{} type identKeyProvider struct{} type identKeyUsed struct{} type identLegacyHeaderMerging struct{} type identMaxDecompressBufferSize struct{} type identMaxPBES2Count struct{} type identMergeProtectedHeaders struct{} type identMessage struct{} type identPerRecipientHeaders struct{} type identPretty struct{} type identProtectedHeaders struct{} type identRequireKid struct{} type identSerialization struct{} func (identCBCBufferSize) String() string { return "WithCBCBufferSize" } func (identCEK) String() string { return "WithCEK" } func (identCompress) String() string { return "WithCompress" } func (identContentEncryptionAlgorithm) String() string { return "WithContentEncryption" } func (identContext) String() string { return "WithContext" } func (identFS) String() string { return "WithFS" } func (identKey) String() string { return "WithKey" } func (identKeyProvider) String() string { return "WithKeyProvider" } func (identKeyUsed) String() string { return "WithKeyUsed" } func (identLegacyHeaderMerging) String() string { return "WithLegacyHeaderMerging" } func (identMaxDecompressBufferSize) String() string { return "WithMaxDecompressBufferSize" } func (identMaxPBES2Count) String() string { return "WithMaxPBES2Count" } func (identMergeProtectedHeaders) String() string { return "WithMergeProtectedHeaders" } func (identMessage) String() string { return "WithMessage" } func (identPerRecipientHeaders) String() string { return "WithPerRecipientHeaders" } func (identPretty) String() string { return "WithPretty" } func (identProtectedHeaders) String() string { return "WithProtectedHeaders" } func (identRequireKid) String() string { return "WithRequireKid" } func (identSerialization) String() string { return "WithSerialization" } // WithCBCBufferSize specifies the maximum buffer size for internal // calculations, such as when AES-CBC is performed. The default value is 256MB. // If set to an invalid value, the default value is used. // In v2, this option was called MaxBufferSize. // // This option has a global effect. func WithCBCBufferSize(v int64) GlobalOption { return &globalOption{option.New(identCBCBufferSize{}, v)} } // WithCEK allows users to specify a variable to store the CEK used in the // message upon successful decryption. The variable must be a pointer to // a byte slice, and it will only be populated if the decryption is successful. // // This option is currently considered EXPERIMENTAL, and is subject to // future changes across minor/micro versions. func WithCEK(v *[]byte) DecryptOption { return &decryptOption{option.New(identCEK{}, v)} } // WithCompress specifies the compression algorithm to use when encrypting // a payload using `jwe.Encrypt` (Yes, we know it can only be "" or "DEF", // but the way the specification is written it could allow for more options, // and therefore this option takes an argument) func WithCompress(v jwa.CompressionAlgorithm) EncryptOption { return &encryptOption{option.New(identCompress{}, v)} } // WithContentEncryptionAlgorithm specifies the algorithm to encrypt the // JWE message content with. If not provided, `jwa.A256GCM` is used. func WithContentEncryption(v jwa.ContentEncryptionAlgorithm) EncryptOption { return &encryptOption{option.New(identContentEncryptionAlgorithm{}, v)} } // WithContext specifies the context.Context object to use when decrypting a JWE message. // If not provided, context.Background() will be used. func WithContext(v context.Context) DecryptOption { return &decryptOption{option.New(identContext{}, v)} } // WithFS specifies the source `fs.FS` object to read the file from. func WithFS(v fs.FS) ReadFileOption { return &readFileOption{option.New(identFS{}, v)} } func WithKeyProvider(v KeyProvider) DecryptOption { return &decryptOption{option.New(identKeyProvider{}, v)} } // WithKeyUsed allows you to specify the `jwe.Decrypt()` function to // return the key used for decryption. This may be useful when // you specify multiple key sources or if you pass a `jwk.Set` // and you want to know which key was successful at decrypting the // CEK. // // `v` must be a pointer to an empty `any`. Do not use // `jwk.Key` here unless you are 100% sure that all keys that you // have provided are instances of `jwk.Key` (remember that the // jwx API allows users to specify a raw key such as *rsa.PublicKey) func WithKeyUsed(v any) DecryptOption { return &decryptOption{option.New(identKeyUsed{}, v)} } // WithLegacyHeaderMerging specifies whether to perform legacy header merging // when encrypting a JWE message in JSON serialization, when there is a single recipient. // This behavior is enabled by default for backwards compatibility. // // When a JWE message is encrypted in JSON serialization, and there is only // one recipient, this library automatically serializes the message in // flattened JSON serialization format. In older versions of this library, // the protected headers and the per-recipient headers were merged together // before computing the AAD (Additional Authenticated Data), but the per-recipient // headers were kept as-is in the `header` field of the recipient object. // // This behavior is not compliant with the JWE specification, which states that // the headers must be disjoint. // // Passing this option with a value of `false` disables this legacy behavior, // and while the per-recipient headers and protected headers are still merged // for the purpose of computing AAD, the per-recipient headers are cleared // after merging, so that the resulting JWE message is compliant with the // specification. // // This option has no effect when there are multiple recipients, or when // the serialization format is compact serialization. For multiple recipients // (i.e. full JSON serialization), the protected headers and per-recipient // headers are never merged, and it is the caller's responsibility to ensure // that the headers are disjoint. In compact serialization, there are no per-recipient // headers; in fact, the protected headers are the only headers that exist, // and therefore there is no possibility of header collision after merging // (note: while per-recipient headers do not make sense in compact serialization, // this library does not prevent you from setting them -- they are all just // merged into the protected headers). // // In future versions, the new behavior will be the default. New users are // encouraged to set this option to `false` now to avoid future issues. func WithLegacyHeaderMerging(v bool) EncryptOption { return &encryptOption{option.New(identLegacyHeaderMerging{}, v)} } // WithMaxDecompressBufferSize specifies the maximum buffer size for used when // decompressing the payload of a JWE message. If a compressed JWE payload // exceeds this amount when decompressed, jwe.Decrypt will return an error. // The default value is 10MB. // // This option can be used for `jwe.Settings()`, which changes the behavior // globally, or for `jwe.Decrypt()`, which changes the behavior for that // specific call. func WithMaxDecompressBufferSize(v int64) GlobalDecryptOption { return &globalDecryptOption{option.New(identMaxDecompressBufferSize{}, v)} } // WithMaxPBES2Count specifies the maximum number of PBES2 iterations // to use when decrypting a message. If not specified, the default // value of 10,000 is used. // // This option has a global effect. func WithMaxPBES2Count(v int) GlobalOption { return &globalOption{option.New(identMaxPBES2Count{}, v)} } // WithMergeProtectedHeaders specify that when given multiple headers // as options to `jwe.Encrypt`, these headers should be merged instead // of overwritten func WithMergeProtectedHeaders(v bool) EncryptOption { return &encryptOption{option.New(identMergeProtectedHeaders{}, v)} } // WithMessage provides a message object to be populated by `jwe.Decrypt` // Using this option allows you to decrypt AND obtain the `jwe.Message` // in one go. func WithMessage(v *Message) DecryptOption { return &decryptOption{option.New(identMessage{}, v)} } // WithPretty specifies whether the JSON output should be formatted and // indented func WithPretty(v bool) WithJSONSuboption { return &withJSONSuboption{option.New(identPretty{}, v)} } // WithRequiredKid specifies whether the keys in the jwk.Set should // only be matched if the target JWE message's Key ID and the Key ID // in the given key matches. func WithRequireKid(v bool) WithKeySetSuboption { return &withKeySetSuboption{option.New(identRequireKid{}, v)} } // WithCompact specifies that the result of `jwe.Encrypt()` is serialized in // compact format. // // By default `jwe.Encrypt()` will opt to use compact format, so you usually // do not need to specify this option other than to be explicit about it func WithCompact() EncryptOption { return &encryptOption{option.New(identSerialization{}, fmtCompact)} } golang-github-lestrrat-go-jwx-3.0.13/jwe/options_gen_test.go000066400000000000000000000026651515060566400241140ustar00rootroot00000000000000// Code generated by tools/cmd/genoptions/main.go. DO NOT EDIT. package jwe import ( "testing" "github.com/stretchr/testify/require" ) func TestOptionIdent(t *testing.T) { require.Equal(t, "WithCBCBufferSize", identCBCBufferSize{}.String()) require.Equal(t, "WithCEK", identCEK{}.String()) require.Equal(t, "WithCompress", identCompress{}.String()) require.Equal(t, "WithContentEncryption", identContentEncryptionAlgorithm{}.String()) require.Equal(t, "WithContext", identContext{}.String()) require.Equal(t, "WithFS", identFS{}.String()) require.Equal(t, "WithKey", identKey{}.String()) require.Equal(t, "WithKeyProvider", identKeyProvider{}.String()) require.Equal(t, "WithKeyUsed", identKeyUsed{}.String()) require.Equal(t, "WithLegacyHeaderMerging", identLegacyHeaderMerging{}.String()) require.Equal(t, "WithMaxDecompressBufferSize", identMaxDecompressBufferSize{}.String()) require.Equal(t, "WithMaxPBES2Count", identMaxPBES2Count{}.String()) require.Equal(t, "WithMergeProtectedHeaders", identMergeProtectedHeaders{}.String()) require.Equal(t, "WithMessage", identMessage{}.String()) require.Equal(t, "WithPerRecipientHeaders", identPerRecipientHeaders{}.String()) require.Equal(t, "WithPretty", identPretty{}.String()) require.Equal(t, "WithProtectedHeaders", identProtectedHeaders{}.String()) require.Equal(t, "WithRequireKid", identRequireKid{}.String()) require.Equal(t, "WithSerialization", identSerialization{}.String()) } golang-github-lestrrat-go-jwx-3.0.13/jwe/speed_test.go000066400000000000000000000023441515060566400226620ustar00rootroot00000000000000package jwe import ( "bytes" "testing" "github.com/lestrrat-go/jwx/v3/internal/tokens" ) var s = []byte(`eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkEyNTZHQ00ifQ.OKOawDo13gRp2ojaHV7LFpZcgV7T6DVZKTyKOMTYUmKoTCVJRgckCL9kiMT03JGeipsEdY3mx_etLbbWSrFr05kLzcSr4qKAq7YN7e9jwQRb23nfa6c9d-StnImGyFDbSv04uVuxIp5Zms1gNxKKK2Da14B8S4rzVRltdYwam_lDp5XnZAYpQdb76FdIKLaVmqgfwX7XWRxv2322i-vDxRfqNzo_tETKzpVLzfiwQyeyPGLBIO56YJ7eObdv0je81860ppamavo35UgoRdbYaBcoh9QcfylQr66oc6vFWXRcZ_ZT2LawVCWTIy3brGPi6UklfCpIMfIjf7iGdXKHzg.48V1_ALb6US04U3b.5eym8TW_c8SuK0ltJ3rpYIzOeDQz7TALvtu6UG9oMo4vpzs9tX_EFShS8iB7j6jiSdiwkIr3ajwQzaBtQD_A.XFBoMYUZodetZdvTiFvSkQ`) func BenchmarkSplitLib(b *testing.B) { for b.Loop() { SplitLib(s) } } func BenchmarkSplitManual(b *testing.B) { ret := make([][]byte, 5) for b.Loop() { SplitManual(ret, s) } } func SplitLib(buf []byte) [][]byte { return bytes.Split(buf, []byte{tokens.Period}) } func SplitManual(parts [][]byte, buf []byte) { bufi := 0 for len(buf) > 0 { i := bytes.IndexByte(buf, tokens.Period) if i == -1 { return } parts[bufi] = buf[:i] bufi++ if len(buf) > i { buf = buf[i+1:] } if bufi == 4 { break } } if i := bytes.IndexByte(buf, tokens.Period); i != -1 { return } parts[4] = buf } golang-github-lestrrat-go-jwx-3.0.13/jwk/000077500000000000000000000000001515060566400201775ustar00rootroot00000000000000golang-github-lestrrat-go-jwx-3.0.13/jwk/BUILD.bazel000066400000000000000000000040641515060566400220610ustar00rootroot00000000000000load("@rules_go//go:def.bzl", "go_library", "go_test") go_library( name = "jwk", srcs = [ "cache.go", "convert.go", "ecdsa.go", "ecdsa_gen.go", "errors.go", "fetch.go", "filter.go", "interface.go", "interface_gen.go", "io.go", "jwk.go", "key_ops.go", "okp.go", "okp_gen.go", "options.go", "options_gen.go", "parser.go", "rsa.go", "rsa_gen.go", "set.go", "symmetric.go", "symmetric_gen.go", "usage.go", "whitelist.go", "x509.go", ], importpath = "github.com/lestrrat-go/jwx/v3/jwk", visibility = ["//visibility:public"], deps = [ "//cert", "//internal/base64", "//internal/ecutil", "//transform", "//internal/json", "//internal/pool", "//internal/tokens", "//jwa", "//jwk/ecdsa", "//jwk/jwkbb", "@com_github_lestrrat_go_blackmagic//:blackmagic", "@com_github_lestrrat_go_httprc_v3//:httprc", "@com_github_lestrrat_go_option_v2//:option", ], ) go_test( name = "jwk_test", srcs = [ "filter_test.go", "headers_test.go", "jwk_internal_test.go", "jwk_test.go", "options_gen_test.go", "refresh_test.go", "set_test.go", "x5c_test.go", ], data = glob(["testdata/**"]), embed = [":jwk"], deps = [ "//cert", "//internal/base64", "//internal/jose", "//internal/json", "//internal/jwxtest", "//internal/tokens", "//jwa", "//jwk/ecdsa", "//jws", "@com_github_lestrrat_go_blackmagic//:blackmagic", "@com_github_lestrrat_go_httprc_v3//:httprc", "@com_github_lestrrat_go_httprc_v3//tracesink", "@com_github_stretchr_testify//assert", "@com_github_stretchr_testify//require", ], ) alias( name = "go_default_library", actual = ":jwk", visibility = ["//visibility:public"], ) golang-github-lestrrat-go-jwx-3.0.13/jwk/README.md000066400000000000000000000151241515060566400214610ustar00rootroot00000000000000# JWK [![Go Reference](https://pkg.go.dev/badge/github.com/lestrrat-go/jwx/v3/jwk.svg)](https://pkg.go.dev/github.com/lestrrat-go/jwx/v3/jwk) Package jwk implements JWK as described in [RFC7517](https://tools.ietf.org/html/rfc7517). If you are looking to use JWT wit JWKs, look no further than [github.com/lestrrat-go/jwx](../jwt). * Parse and work with RSA/EC/Symmetric/OKP JWK types * Convert to and from JSON * Convert to and from raw key types (e.g. *rsa.PrivateKey) * Ability to keep a JWKS fresh using *jwk.AutoRefresh ## Supported key types: | kty | Curve | Go Key Type | |:----|:------------------------|:----------------------------------------------| | RSA | N/A | rsa.PrivateKey / rsa.PublicKey (2) | | EC | P-256
P-384
P-521
secp256k1 (1) | ecdsa.PrivateKey / ecdsa.PublicKey (2) | | oct | N/A | []byte | | OKP | Ed25519 (1) | ed25519.PrivateKey / ed25519.PublicKey (2) | | | X25519 (1) | (jwx/)x25519.PrivateKey / x25519.PublicKey (2)| * Note 1: Experimental * Note 2: Either value or pointers accepted (e.g. rsa.PrivateKey or *rsa.PrivateKey) # Documentation Please read the [API reference](https://pkg.go.dev/github.com/lestrrat-go/jwx/v3/jwk), or the how-to style documentation on how to use JWK can be found in the [docs directory](../docs/04-jwk.md). # Auto-Refresh a key during a long-running process ```go package examples_test import ( "context" "fmt" "time" "github.com/lestrrat-go/httprc/v3" "github.com/lestrrat-go/jwx/v3/jwk" ) func Example_jwk_cache() { ctx, cancel := context.WithCancel(context.Background()) defer cancel() const googleCerts = `https://www.googleapis.com/oauth2/v3/certs` // First, set up the `jwk.Cache` object. You need to pass it a // `context.Context` object to control the lifecycle of the background fetching goroutine. c, err := jwk.NewCache(ctx, httprc.NewClient()) if err != nil { fmt.Printf("failed to create cache: %s\n", err) return } // Tell *jwk.Cache that we only want to refresh this JWKS periodically. if err := c.Register(ctx, googleCerts); err != nil { fmt.Printf("failed to register google JWKS: %s\n", err) return } // Pretend that this is your program's main loop MAIN: for { select { case <-ctx.Done(): break MAIN default: } keyset, err := c.Lookup(ctx, googleCerts) if err != nil { fmt.Printf("failed to fetch google JWKS: %s\n", err) return } _ = keyset // The returned `keyset` will always be "reasonably" new. // // By "reasonably" we mean that we cannot guarantee that the keys will be refreshed // immediately after it has been rotated in the remote source. But it should be close\ // enough, and should you need to forcefully refresh the token using the `(jwk.Cache).Refresh()` method. // // If refetching the keyset fails, a cached version will be returned from the previous // successful sync // Do interesting stuff with the keyset... but here, we just // sleep for a bit time.Sleep(time.Second) // Because we're a dummy program, we just cancel the loop now. // If this were a real program, you presumably loop forever cancel() } // OUTPUT: } ``` source: [examples/jwk_cache_example_test.go](https://github.com/lestrrat-go/jwx/blob/v3/examples/jwk_cache_example_test.go) Parse and use a JWK key: ```go package examples_test import ( "context" "fmt" "log" "github.com/lestrrat-go/jwx/v3/internal/json" "github.com/lestrrat-go/jwx/v3/jwk" ) func Example_jwk_usage() { // Use jwk.Cache if you intend to keep reuse the JWKS over and over set, err := jwk.Fetch(context.Background(), "https://www.googleapis.com/oauth2/v3/certs") if err != nil { log.Printf("failed to parse JWK: %s", err) return } // Key sets can be serialized back to JSON { jsonbuf, err := json.Marshal(set) if err != nil { log.Printf("failed to marshal key set into JSON: %s", err) return } log.Printf("%s", jsonbuf) } for i := 0; i < set.Len(); i++ { var rawkey any // This is where we would like to store the raw key, like *rsa.PrivateKey or *ecdsa.PrivateKey key, ok := set.Key(i) // This retrieves the corresponding jwk.Key if !ok { log.Printf("failed to get key at index %d", i) return } // jws and jwe operations can be performed using jwk.Key, but you could also // covert it to their "raw" forms, such as *rsa.PrivateKey or *ecdsa.PrivateKey if err := jwk.Export(key, &rawkey); err != nil { log.Printf("failed to create public key: %s", err) return } _ = rawkey // You can create jwk.Key from a raw key, too fromRawKey, err := jwk.Import(rawkey) if err != nil { log.Printf("failed to acquire raw key from jwk.Key: %s", err) return } // Keys can be serialized back to JSON jsonbuf, err := json.Marshal(key) if err != nil { log.Printf("failed to marshal key into JSON: %s", err) return } fromJSONKey, err := jwk.Parse(jsonbuf) if err != nil { log.Printf("failed to parse json: %s", err) return } _ = fromJSONKey _ = fromRawKey } // OUTPUT: } //nolint:govet func Example_jwk_marshal_json() { // 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.Import(raw) if err != nil { fmt.Printf("failed to create symmetric key: %s\n", err) return } if _, ok := key.(jwk.SymmetricKey); !ok { fmt.Printf("expected jwk.SymmetricKey, got %T\n", key) return } key.Set(jwk.KeyIDKey, "mykey") buf, err := json.MarshalIndent(key, "", " ") if err != nil { fmt.Printf("failed to marshal key into JSON: %s\n", err) return } fmt.Printf("%s\n", buf) // OUTPUT: // { // "k": "MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODlBQkNERUY", // "kid": "mykey", // "kty": "oct" // } } ``` source: [examples/jwk_example_test.go](https://github.com/lestrrat-go/jwx/blob/v3/examples/jwk_example_test.go) golang-github-lestrrat-go-jwx-3.0.13/jwk/cache.go000066400000000000000000000264511515060566400216010ustar00rootroot00000000000000package jwk import ( "context" "fmt" "io" "net/http" "github.com/lestrrat-go/httprc/v3" ) type HTTPClient = httprc.HTTPClient type ErrorSink = httprc.ErrorSink type TraceSink = httprc.TraceSink // Cache is a container built on top of github.com/lestrrat-go/httprc/v3 // that keeps track of Set object by their source URLs. // The Set objects are stored in memory, and are refreshed automatically // behind the scenes. // // Before retrieving the Set objects, the user must pre-register the // URLs they intend to use by calling `Register()` // // c := jwk.NewCache(ctx, httprc.NewClient()) // c.Register(ctx, url, options...) // // Once registered, you can call `Get()` to retrieve the Set object. // // All JWKS objects that are retrieved via this mechanism should be // treated read-only, as they are shared among all consumers, as well // as the `jwk.Cache` object. // // There are cases where `jwk.Cache` and `jwk.CachedSet` should and // should not be used. // // First and foremost, do NOT use a cache for those JWKS objects that // need constant checking. For example, unreliable or user-provided JWKS (i.e. those // JWKS that are not from a well-known provider) should not be fetched // through a `jwk.Cache` or `jwk.CachedSet`. // // For example, if you have a flaky JWKS server for development // that can go down often, you should consider alternatives such as // providing `http.Client` with a caching `http.RoundTripper` configured // (see `jwk.WithHTTPClient`), setting up a reverse proxy, etc. // These techniques allow you to set up a more robust way to both cache // and report precise causes of the problems than using `jwk.Cache` or // `jwk.CachedSet`. If you handle the caching at the HTTP level like this, // you will be able to use a simple `jwk.Fetch` call and not worry about the cache. // // User-provided JWKS objects may also be problematic, as it may go down // unexpectedly (and frequently!), and it will be hard to detect when // the URLs or its contents are swapped. // // A good use-case for `jwk.Cache` and `jwk.CachedSet` are for "stable" // JWKS objects. // // When we say "stable", we are thinking of JWKS that should mostly be // ALWAYS available. A good example are those JWKS objects provided by // major cloud providers such as Google Cloud, AWS, or Azure. // Stable JWKS may still experience intermittent network connectivity problems, // but you can expect that they will eventually recover in relatively // short period of time. They rarely change URLs, and the contents are // expected to be valid or otherwise it would cause havoc to those providers // // We also know that these stable JWKS objects are rotated periodically, // which is a perfect use for `jwk.Cache` and `jwk.CachedSet`. The caches // can be configured to periodically refresh the JWKS thereby keeping them // fresh without extra intervention from the developer. // // Notice that for these recommended use-cases the requirement to check // the validity or the availability of the JWKS objects are non-existent, // as it is expected that they will be available and will be valid. The // caching mechanism can hide intermittent connectivity problems as well // as keep the objects mostly fresh. type Cache struct { ctrl httprc.Controller } // Transformer is a specialized version of `httprc.Transformer` that implements // conversion from a `http.Response` object to a `jwk.Set` object. Use this in // conjection with `httprc.NewResource` to create a `httprc.Resource` object // to auto-update `jwk.Set` objects. type Transformer struct { parseOptions []ParseOption } func (t Transformer) Transform(_ context.Context, res *http.Response) (Set, error) { buf, err := io.ReadAll(res.Body) if err != nil { return nil, fmt.Errorf(`failed to read response body status: %w`, err) } set, err := Parse(buf, t.parseOptions...) if err != nil { return nil, fmt.Errorf(`failed to parse JWK set at %q: %w`, res.Request.URL.String(), err) } return set, nil } // NewCache creates a new `jwk.Cache` object. // // Under the hood, `jwk.Cache` uses `httprc.Client` manage the // fetching and caching of JWKS objects, and thus spawns multiple goroutines // per `jwk.Cache` object. // // The provided `httprc.Client` object must NOT be started prior to // passing it to `jwk.NewCache`. The `jwk.Cache` object will start // the `httprc.Client` object on its own. func NewCache(ctx context.Context, client *httprc.Client) (*Cache, error) { ctrl, err := client.Start(ctx) if err != nil { return nil, fmt.Errorf(`failed to start httprc.Client: %w`, err) } return &Cache{ ctrl: ctrl, }, nil } // Register registers a URL to be managed by the cache. URLs must // be registered before issuing `Get` // // The `Register` method is a thin wrapper around `(httprc.Controller).Add` func (c *Cache) Register(ctx context.Context, u string, options ...RegisterOption) error { var parseOptions []ParseOption var resourceOptions []httprc.NewResourceOption waitReady := true for _, option := range options { switch option := option.(type) { case ParseOption: parseOptions = append(parseOptions, option) case ResourceOption: var v httprc.NewResourceOption if err := option.Value(&v); err != nil { return fmt.Errorf(`failed to retrieve NewResourceOption option value: %w`, err) } resourceOptions = append(resourceOptions, v) default: switch option.Ident() { case identHTTPClient{}: var cli HTTPClient if err := option.Value(&cli); err != nil { return fmt.Errorf(`failed to retrieve HTTPClient option value: %w`, err) } resourceOptions = append(resourceOptions, httprc.WithHTTPClient(cli)) case identWaitReady{}: if err := option.Value(&waitReady); err != nil { return fmt.Errorf(`failed to retrieve WaitReady option value: %w`, err) } } } } r, err := httprc.NewResource[Set](u, &Transformer{ parseOptions: parseOptions, }, resourceOptions...) if err != nil { return fmt.Errorf(`failed to create httprc.Resource: %w`, err) } if err := c.ctrl.Add(ctx, r, httprc.WithWaitReady(waitReady)); err != nil { return fmt.Errorf(`failed to add resource to httprc.Client: %w`, err) } return nil } // LookupResource returns the `httprc.Resource` object associated with the // given URL `u`. If the URL has not been registered, an error is returned. func (c *Cache) LookupResource(ctx context.Context, u string) (*httprc.ResourceBase[Set], error) { r, err := c.ctrl.Lookup(ctx, u) if err != nil { return nil, fmt.Errorf(`failed to lookup resource %q: %w`, u, err) } //nolint:forcetypeassert return r.(*httprc.ResourceBase[Set]), nil } func (c *Cache) Lookup(ctx context.Context, u string) (Set, error) { r, err := c.LookupResource(ctx, u) if err != nil { return nil, fmt.Errorf(`failed to lookup resource %q: %w`, u, err) } set := r.Resource() if set == nil { return nil, fmt.Errorf(`resource %q is not ready`, u) } return set, nil } func (c *Cache) Ready(ctx context.Context, u string) bool { r, err := c.LookupResource(ctx, u) if err != nil { return false } if err := r.Ready(ctx); err != nil { return false } return true } // Refresh is identical to Get(), except it always fetches the // specified resource anew, and updates the cached content // // Please refer to the documentation for `(httprc.Cache).Refresh` for // more details func (c *Cache) Refresh(ctx context.Context, u string) (Set, error) { if err := c.ctrl.Refresh(ctx, u); err != nil { return nil, fmt.Errorf(`failed to refresh resource %q: %w`, u, err) } return c.Lookup(ctx, u) } // IsRegistered returns true if the given URL `u` has already been registered // in the cache. func (c *Cache) IsRegistered(ctx context.Context, u string) bool { _, err := c.LookupResource(ctx, u) return err == nil } // Unregister removes the given URL `u` from the cache. func (c *Cache) Unregister(ctx context.Context, u string) error { return c.ctrl.Remove(ctx, u) } func (c *Cache) Shutdown(ctx context.Context) error { return c.ctrl.ShutdownContext(ctx) } // CachedSet is a thin shim over jwk.Cache that allows the user to cloak // jwk.Cache as if it's a `jwk.Set`. Behind the scenes, the `jwk.Set` is // retrieved from the `jwk.Cache` for every operation. // // Since `jwk.CachedSet` always deals with a cached version of the `jwk.Set`, // all operations that mutate the object (such as AddKey(), RemoveKey(), et. al) // are no-ops and return an error. // // Note that since this is a utility shim over `jwk.Cache`, you _will_ lose // the ability to control the finer details (such as controlling how long to // wait for in case of a fetch failure using `context.Context`) // // Make sure that you read the documentation for `jwk.Cache` as well. type CachedSet interface { Set cached() (Set, error) // used as a marker } type cachedSet struct { r *httprc.ResourceBase[Set] } func (c *Cache) CachedSet(u string) (CachedSet, error) { r, err := c.LookupResource(context.Background(), u) if err != nil { return nil, fmt.Errorf(`failed to lookup resource %q: %w`, u, err) } return NewCachedSet(r), nil } func NewCachedSet(r *httprc.ResourceBase[Set]) CachedSet { return &cachedSet{ r: r, } } func (cs *cachedSet) cached() (Set, error) { if err := cs.r.Ready(context.Background()); err != nil { return nil, fmt.Errorf(`failed to fetch resource: %w`, err) } return cs.r.Resource(), nil } // AddKey is a no-op for `jwk.CachedSet`, as the `jwk.Set` should be treated read-only func (*cachedSet) AddKey(_ Key) error { return fmt.Errorf(`(jwk.Cachedset).AddKey: jwk.CachedSet is immutable`) } // Clear is a no-op for `jwk.CachedSet`, as the `jwk.Set` should be treated read-only func (*cachedSet) Clear() error { return fmt.Errorf(`(jwk.cachedSet).Clear: jwk.CachedSet is immutable`) } // Set is a no-op for `jwk.CachedSet`, as the `jwk.Set` should be treated read-only func (*cachedSet) Set(_ string, _ any) error { return fmt.Errorf(`(jwk.cachedSet).Set: jwk.CachedSet is immutable`) } // Remove is a no-op for `jwk.CachedSet`, as the `jwk.Set` should be treated read-only func (*cachedSet) Remove(_ string) error { // TODO: Remove() should be renamed to Remove(string) error return fmt.Errorf(`(jwk.cachedSet).Remove: jwk.CachedSet is immutable`) } // RemoveKey is a no-op for `jwk.CachedSet`, as the `jwk.Set` should be treated read-only func (*cachedSet) RemoveKey(_ Key) error { return fmt.Errorf(`(jwk.cachedSet).RemoveKey: jwk.CachedSet is immutable`) } func (cs *cachedSet) Clone() (Set, error) { set, err := cs.cached() if err != nil { return nil, fmt.Errorf(`failed to get cached jwk.Set: %w`, err) } return set.Clone() } // Get returns the value of non-Key field stored in the jwk.Set func (cs *cachedSet) Get(name string, dst any) error { set, err := cs.cached() if err != nil { return err } return set.Get(name, dst) } // Key returns the Key at the specified index func (cs *cachedSet) Key(idx int) (Key, bool) { set, err := cs.cached() if err != nil { return nil, false } return set.Key(idx) } func (cs *cachedSet) Index(key Key) int { set, err := cs.cached() if err != nil { return -1 } return set.Index(key) } func (cs *cachedSet) Keys() []string { set, err := cs.cached() if err != nil { return nil } return set.Keys() } func (cs *cachedSet) Len() int { set, err := cs.cached() if err != nil { return -1 } return set.Len() } func (cs *cachedSet) LookupKeyID(kid string) (Key, bool) { set, err := cs.cached() if err != nil { return nil, false } return set.LookupKeyID(kid) } golang-github-lestrrat-go-jwx-3.0.13/jwk/convert.go000066400000000000000000000267171515060566400222230ustar00rootroot00000000000000package jwk import ( "crypto/ecdh" "crypto/ecdsa" "crypto/ed25519" "crypto/elliptic" "crypto/rsa" "errors" "fmt" "math/big" "reflect" "sync" "github.com/lestrrat-go/blackmagic" "github.com/lestrrat-go/jwx/v3/internal/ecutil" "github.com/lestrrat-go/jwx/v3/jwa" ) // # Converting between Raw Keys and `jwk.Key`s // // A converter that converts from a raw key to a `jwk.Key` is called a KeyImporter. // A converter that converts from a `jwk.Key` to a raw key is called a KeyExporter. var keyImporters = make(map[reflect.Type]KeyImporter) var keyExporters = make(map[jwa.KeyType][]KeyExporter) var muKeyImporters sync.RWMutex var muKeyExporters sync.RWMutex // RegisterKeyImporter registers a KeyImporter for the given raw key. When `jwk.Import()` is called, // the library will look up the appropriate KeyImporter for the given raw key type (via `reflect`) // and execute the KeyImporters in succession until either one of them succeeds, or all of them fail. func RegisterKeyImporter(from any, conv KeyImporter) { muKeyImporters.Lock() defer muKeyImporters.Unlock() keyImporters[reflect.TypeOf(from)] = conv } // RegisterKeyExporter registers a KeyExporter for the given key type. When `key.Raw()` is called, // the library will look up the appropriate KeyExporter for the given key type and execute the // KeyExporters in succession until either one of them succeeds, or all of them fail. func RegisterKeyExporter(kty jwa.KeyType, conv KeyExporter) { muKeyExporters.Lock() defer muKeyExporters.Unlock() convs, ok := keyExporters[kty] if !ok { convs = []KeyExporter{conv} } else { convs = append([]KeyExporter{conv}, convs...) } keyExporters[kty] = convs } // KeyImporter is used to convert from a raw key to a `jwk.Key`. mneumonic: from the PoV of the `jwk.Key`, // we're _importing_ a raw key. type KeyImporter interface { // Import takes the raw key to be converted, and returns a `jwk.Key` or an error if the conversion fails. Import(any) (Key, error) } // KeyImportFunc is a convenience type to implement KeyImporter as a function. type KeyImportFunc func(any) (Key, error) func (f KeyImportFunc) Import(raw any) (Key, error) { return f(raw) } // KeyExporter is used to convert from a `jwk.Key` to a raw key. mneumonic: from the PoV of the `jwk.Key`, // we're _exporting_ it to a raw key. type KeyExporter interface { // Export takes the `jwk.Key` to be converted, and a hint (the raw key to be converted to). // The hint is the object that the user requested the result to be assigned to. // The method should return the converted raw key, or an error if the conversion fails. // // Third party modules MUST NOT modifiy the hint object. // // When the user calls `key.Export(dst)`, the `dst` object is a _pointer_ to the // object that the user wants the result to be assigned to, but the converter // receives the _value_ that this pointer points to, to make it easier to // detect the type of the result. // // Note that the second argument may be an `any` (which means that the // user has delegated the type detection to the converter). // // Export must NOT modify the hint object, and should return jwk.ContinueError // if the hint object is not compatible with the converter. Export(Key, any) (any, error) } // KeyExportFunc is a convenience type to implement KeyExporter as a function. type KeyExportFunc func(Key, any) (any, error) func (f KeyExportFunc) Export(key Key, hint any) (any, error) { return f(key, hint) } func init() { { f := KeyImportFunc(rsaPrivateKeyToJWK) k := rsa.PrivateKey{} RegisterKeyImporter(k, f) RegisterKeyImporter(&k, f) } { f := KeyImportFunc(rsaPublicKeyToJWK) k := rsa.PublicKey{} RegisterKeyImporter(k, f) RegisterKeyImporter(&k, f) } { f := KeyImportFunc(ecdsaPrivateKeyToJWK) k := ecdsa.PrivateKey{} RegisterKeyImporter(k, f) RegisterKeyImporter(&k, f) } { f := KeyImportFunc(ecdsaPublicKeyToJWK) k := ecdsa.PublicKey{} RegisterKeyImporter(k, f) RegisterKeyImporter(&k, f) } { f := KeyImportFunc(okpPrivateKeyToJWK) for _, k := range []any{ed25519.PrivateKey(nil)} { RegisterKeyImporter(k, f) } } { f := KeyImportFunc(ecdhPrivateKeyToJWK) for _, k := range []any{ecdh.PrivateKey{}, &ecdh.PrivateKey{}} { RegisterKeyImporter(k, f) } } { f := KeyImportFunc(okpPublicKeyToJWK) for _, k := range []any{ed25519.PublicKey(nil)} { RegisterKeyImporter(k, f) } } { f := KeyImportFunc(ecdhPublicKeyToJWK) for _, k := range []any{ecdh.PublicKey{}, &ecdh.PublicKey{}} { RegisterKeyImporter(k, f) } } RegisterKeyImporter([]byte(nil), KeyImportFunc(bytesToKey)) } func ecdhPrivateKeyToJWK(src any) (Key, error) { var raw *ecdh.PrivateKey switch src := src.(type) { case *ecdh.PrivateKey: raw = src case ecdh.PrivateKey: raw = &src default: return nil, fmt.Errorf(`cannot convert key type '%T' to ECDH jwk.Key`, src) } switch raw.Curve() { case ecdh.X25519(): return okpPrivateKeyToJWK(raw) case ecdh.P256(): return ecdhPrivateKeyToECJWK(raw, elliptic.P256()) case ecdh.P384(): return ecdhPrivateKeyToECJWK(raw, elliptic.P384()) case ecdh.P521(): return ecdhPrivateKeyToECJWK(raw, elliptic.P521()) default: return nil, fmt.Errorf(`unsupported curve %s`, raw.Curve()) } } func ecdhPrivateKeyToECJWK(raw *ecdh.PrivateKey, crv elliptic.Curve) (Key, error) { pub := raw.PublicKey() rawpub := pub.Bytes() size := ecutil.CalculateKeySize(crv) var x, y, d big.Int x.SetBytes(rawpub[1 : 1+size]) y.SetBytes(rawpub[1+size:]) d.SetBytes(raw.Bytes()) var ecdsaPriv ecdsa.PrivateKey ecdsaPriv.Curve = crv ecdsaPriv.D = &d ecdsaPriv.X = &x ecdsaPriv.Y = &y return ecdsaPrivateKeyToJWK(&ecdsaPriv) } func ecdhPublicKeyToJWK(src any) (Key, error) { var raw *ecdh.PublicKey switch src := src.(type) { case *ecdh.PublicKey: raw = src case ecdh.PublicKey: raw = &src default: return nil, fmt.Errorf(`cannot convert key type '%T' to ECDH jwk.Key`, src) } switch raw.Curve() { case ecdh.X25519(): return okpPublicKeyToJWK(raw) case ecdh.P256(): return ecdhPublicKeyToECJWK(raw, elliptic.P256()) case ecdh.P384(): return ecdhPublicKeyToECJWK(raw, elliptic.P384()) case ecdh.P521(): return ecdhPublicKeyToECJWK(raw, elliptic.P521()) default: return nil, fmt.Errorf(`unsupported curve %s`, raw.Curve()) } } func ecdhPublicKeyToECJWK(raw *ecdh.PublicKey, crv elliptic.Curve) (Key, error) { rawbytes := raw.Bytes() size := ecutil.CalculateKeySize(crv) var x, y big.Int x.SetBytes(rawbytes[1 : 1+size]) y.SetBytes(rawbytes[1+size:]) var ecdsaPriv ecdsa.PublicKey ecdsaPriv.Curve = crv ecdsaPriv.X = &x ecdsaPriv.Y = &y return ecdsaPublicKeyToJWK(&ecdsaPriv) } // These may seem a bit repetitive and redandunt, but the problem is that // each key type has its own Import method -- for example, Import(*ecdsa.PrivateKey) // vs Import(*rsa.PrivateKey), and therefore they can't just be bundled into // a single function. func rsaPrivateKeyToJWK(src any) (Key, error) { var raw *rsa.PrivateKey switch src := src.(type) { case *rsa.PrivateKey: raw = src case rsa.PrivateKey: raw = &src default: return nil, fmt.Errorf(`cannot convert key type '%T' to RSA jwk.Key`, src) } k := newRSAPrivateKey() if err := k.Import(raw); err != nil { return nil, fmt.Errorf(`failed to initialize %T from %T: %w`, k, raw, err) } return k, nil } func rsaPublicKeyToJWK(src any) (Key, error) { var raw *rsa.PublicKey switch src := src.(type) { case *rsa.PublicKey: raw = src case rsa.PublicKey: raw = &src default: return nil, fmt.Errorf(`cannot convert key type '%T' to RSA jwk.Key`, src) } k := newRSAPublicKey() if err := k.Import(raw); err != nil { return nil, fmt.Errorf(`failed to initialize %T from %T: %w`, k, raw, err) } return k, nil } func ecdsaPrivateKeyToJWK(src any) (Key, error) { var raw *ecdsa.PrivateKey switch src := src.(type) { case *ecdsa.PrivateKey: raw = src case ecdsa.PrivateKey: raw = &src default: return nil, fmt.Errorf(`cannot convert key type '%T' to ECDSA jwk.Key`, src) } k := newECDSAPrivateKey() if err := k.Import(raw); err != nil { return nil, fmt.Errorf(`failed to initialize %T from %T: %w`, k, raw, err) } return k, nil } func ecdsaPublicKeyToJWK(src any) (Key, error) { var raw *ecdsa.PublicKey switch src := src.(type) { case *ecdsa.PublicKey: raw = src case ecdsa.PublicKey: raw = &src default: return nil, fmt.Errorf(`cannot convert key type '%T' to ECDSA jwk.Key`, src) } k := newECDSAPublicKey() if err := k.Import(raw); err != nil { return nil, fmt.Errorf(`failed to initialize %T from %T: %w`, k, raw, err) } return k, nil } func okpPrivateKeyToJWK(src any) (Key, error) { var raw any switch src.(type) { case ed25519.PrivateKey, *ecdh.PrivateKey: raw = src case ecdh.PrivateKey: raw = &src default: return nil, fmt.Errorf(`cannot convert key type '%T' to OKP jwk.Key`, src) } k := newOKPPrivateKey() if err := k.Import(raw); err != nil { return nil, fmt.Errorf(`failed to initialize %T from %T: %w`, k, raw, err) } return k, nil } func okpPublicKeyToJWK(src any) (Key, error) { var raw any switch src.(type) { case ed25519.PublicKey, *ecdh.PublicKey: raw = src case ecdh.PublicKey: raw = &src default: return nil, fmt.Errorf(`jwk: convert raw to OKP jwk.Key: cannot convert key type '%T' to OKP jwk.Key`, src) } k := newOKPPublicKey() if err := k.Import(raw); err != nil { return nil, fmt.Errorf(`failed to initialize %T from %T: %w`, k, raw, err) } return k, nil } func bytesToKey(src any) (Key, error) { var raw []byte switch src := src.(type) { case []byte: raw = src default: return nil, fmt.Errorf(`cannot convert key type '%T' to symmetric jwk.Key`, src) } k := newSymmetricKey() if err := k.Import(raw); err != nil { return nil, fmt.Errorf(`failed to initialize %T from %T: %w`, k, raw, err) } return k, nil } // Export converts a `jwk.Key` to a Export key. The dst argument must be a pointer to the // object that the user wants the result to be assigned to. // // Normally you would pass a pointer to the zero value of the raw key type // such as &(*rsa.PrivateKey) or &(*ecdsa.PublicKey), which gets assigned // the converted key. // // If you do not know the exact type of a jwk.Key before attempting // to obtain the raw key, you can simply pass a pointer to an // empty interface as the second argument // // If you already know the exact type, it is recommended that you // pass a pointer to the zero value of the actual key type for efficiency. // // Be careful when/if you are using a third party key type that implements // the `jwk.Key` interface, as the first argument. This function tries hard // to Do The Right Thing, but it is not guaranteed to work in all cases, // especially when the object implements the `jwk.Key` interface via // embedding. func Export(key Key, dst any) error { // dst better be a pointer rv := reflect.ValueOf(dst) if rv.Kind() != reflect.Ptr { return fmt.Errorf(`jwk.Export: destination object must be a pointer`) } muKeyExporters.RLock() exporters, ok := keyExporters[key.KeyType()] muKeyExporters.RUnlock() if !ok { return fmt.Errorf(`jwk.Export: no exporters registered for key type '%T'`, key) } for _, conv := range exporters { v, err := conv.Export(key, dst) if err != nil { if errors.Is(err, ContinueError()) { continue } return fmt.Errorf(`jwk.Export: failed to export jwk.Key to raw format: %w`, err) } if err := blackmagic.AssignIfCompatible(dst, v); err != nil { return fmt.Errorf(`jwk.Export: failed to assign key: %w`, err) } return nil } return fmt.Errorf(`jwk.Export: no suitable exporter found for key type '%T'`, key) } golang-github-lestrrat-go-jwx-3.0.13/jwk/doc.go000066400000000000000000000304241515060566400212760ustar00rootroot00000000000000// Package jwk implements JWK as described in https://tools.ietf.org/html/rfc7517 // // This package implements jwk.Key to represent a single JWK, and jwk.Set to represent // a set of JWKs. // // The `jwk.Key` type is an interface, which hides the underlying implementation for // each key type. Each key type can further be converted to interfaces for known // types, such as `jwk.ECDSAPrivateKey`, `jwk.RSAPublicKey`, etc. This may not necessarily // work for third party key types (see section on "Registering a key type" below). // // Users can create a JWK in two ways. One is to unmarshal a JSON representation of a // key. The second one is to use `jwk.Import()` to import a raw key and convert it to // a jwk.Key. // // # Simple Usage // // You can parse a JWK from a JSON payload: // // jwk.ParseKey([]byte(`{"kty":"EC",...}`)) // // You can go back and forth between raw key types and JWKs: // // jwkKey, _ := jwk.Import(rsaPrivateKey) // var rawKey *rsa.PRrivateKey // jwkKey.Raw(&rawKey) // // You can use them to sign/verify/encrypt/decrypt: // // jws.Sign([]byte(`...`), jws.WithKey(jwa.RS256, jwkKey)) // jwe.Encrypt([]byte(`...`), jwe.WithKey(jwa.RSA_OAEP, jwkKey)) // // See examples/jwk_parse_example_test.go and other files in the exmaples/ directory for more. // // # Advanced Usage: Registering a custom key type and conversion routines // // Caveat Emptor: Functionality around registering keys // (KeyProbe/KeyParser/KeyImporter/KeyExporter) should be considered experimental. // While we expect that the functionality itself will remain, the API may // change in backward incompatible ways, even during minor version // releases. // // ## tl;dr // // * KeyProbe: Used for parsing JWKs in JSON format. Probes hint fields to be used for later parsing by KeyParser // * KeyParser: Used for parsing JWKs in JSON format. Parses the JSON payload into a jwk.Key using the KeyProbe as hint // * KeyImporter: Used for converting raw key into jwk.Key. // * KeyExporter: Used for converting jwk.Key into raw key. // // ## Overview // // You can add the ability to use a JWK type that this library does not // implement out of the box. You can do this by registering your own // KeyParser, KeyImporter, and KeyExporter instances. // // func init() { // jwk.RegiserProbeField(reflect.StructField{Name: "SomeHint", Type: reflect.TypeOf(""), Tag: `json:"some_hint"`}) // jwk.RegisterKeyParser(&MyKeyParser{}) // jwk.RegisterKeyImporter(&MyKeyImporter{}) // jwk.RegisterKeyExporter(&MyKeyExporter{}) // } // // The KeyParser is used to parse JSON payloads and conver them into a jwk.Key. // The KeyImporter is used to convert a raw key (e.g. *rsa.PrivateKey, *ecdsa.PrivateKey, etc) into a jwk.Key. // The KeyExporter is used to convert a jwk.Key into a raw key. // // Although we believe the mechanism has been streamline quite a lot, it is also true // that the entire process of parsing and converting keys are much more convoluted than you might // think. Please know before hand that if you intend to add support for a new key type, // it _WILL_ require you to learn this module pretty much in-and-out. // // Read on for more explanation. // // ## Registering a KeyParser // // In order to understand how parsing works, we need to explain how the `jwk.ParseKey()` works. // // The first thing that occurs when parsing a key is a partial // unmarshaling of the payload into a hint / probe object. // // Because the `json.Unmarshal` works by calling the `UnmarshalJSON` // method on a concrete object, we need to create a concrete object first. // In order/ to create the appropriate Go object, we need to know which concrete // object to create from the JSON payload, meaning we need to peek into the // payload and figure out what type of key it is. // // In order to do this, we effectively need to parse the JSON payload twice. // First, we "probe" the payload to figure out what kind of key it is, then // we parse it again to create the actual key object. // // For probing, we create a new "probe" object (KeyProbe, which is not // directly available to end users) to populate the object with hints from the payload. // For example, a JWK representing an RSA key would look like: // // { "kty": "RSA", "n": ..., "e": ..., ... } // // The default KeyProbe is constructed to unmarshal "kty" and "d" fields, // because that is enough information to determine what kind of key to // construct. // // For example, if the payload contains "kty" field with the value "RSA", // we know that it's an RSA key. If it contains "EC", we know that it's // an EC key. Furthermore, if the payload contains some value in the "d" field, we can // also tell that this is a private key, as only private keys need // this field. // // For most cases, the default KeyProbe implementation should be sufficient. // However, there may be cases in the future where there are new key types // that require further information. Perhaps you are embedding another hint // in your JWK to further specify what kind of key it is. In that case, you // would need to probe more. // // Normally you can only change how an object is unmarshaled by specifying // JSON tags when defining a struct, but we use `reflect` package capabilities // to create an object dynamically, which is shared among all parsing operations. // // To add a new field to be probed, you need to register a new `reflect.StructField` // object that has all of the information. For example, the code below would // register a field named "MyHint" that is of type string, and has a JSON tag // of "my_hint". // // jwk.RegisterProbeField(reflect.StructField{Name: "MyHint", Type: reflect.TypeOf(""), Tag: `json:"my_hint"`}) // // The value of this field can be retrieved by calling `Get()` method on the // KeyProbe object (from the `KeyParser`'s `ParseKey()` method discussed later) // // var myhint string // _ = probe.Get("MyHint", &myhint) // // var kty string // _ = probe.Get("Kty", &kty) // // This mechanism allows you to be flexible when trying to determine the key type // to instantiate. // // ## Parse via the KeyParser // // When `jwk.Parse` / `jwk.ParseKey` is called, the library will first probe // the payload as discussed above. // // Once the probe is done, the library will iterate over the registered parsers // and attempt to parse the key by calling their `ParseKey()` methods. // // The parsers will be called in reverse order that they were registered. // This means that it will try all parsers that were registered by third // parties, and once those are exhausted, the default parser will be used. // // Each parser's `ParseKey()“ method will receive three arguments: the probe object, a // KeyUnmarshaler, and the raw payload. The probe object can be used // as a hint to determine what kind of key to instantiate. An example // pseudocode may look like this: // // var kty string // _ = probe.Get("Kty", &kty) // switch kty { // case "RSA": // // create an RSA key // case "EC": // // create an EC key // ... // } // // The `KeyUnmarshaler` is a thin wrapper around `json.Unmarshal`. It works almost // identical to `json.Unmarshal`, but it allows us to add extra magic that is // specific to this library (which users do not need to be aware of) before calling // the actual `json.Unmarshal`. Please use the `KeyUnmarshaler` to unmarshal JWKs instead of `json.Unmarshal`. // // Putting it all together, the boiler plate for registering a new parser may look like this: // // func init() { // jwk.RegisterFieldProbe(reflect.StructField{Name: "MyHint", Type: reflect.TypeOf(""), Tag: `json:"my_hint"`}) // jwk.RegisterParser(&MyKeyParser{}) // } // // type MyKeyParser struct { ... } // func(*MyKeyParser) ParseKey(rawProbe *KeyProbe, unmarshaler KeyUnmarshaler, data []byte) (jwk.Key, error) { // // Create concrete type // var hint string // if err := probe.Get("MyHint", &hint); err != nil { // // if it doesn't have the `my_hint` field, it probably means // // it's not for us, so we return ContinueParseError so that // // the next parser can pick it up // return nil, jwk.ContinueParseError() // } // // // Use hint to determine concrete key type // var key jwk.Key // switch hint { // case ...: // key = = myNewAwesomeJWK() // ... // } // // return unmarshaler.Unmarshal(data, key) // } // // ## Registering KeyImporter/KeyExporter // // If you are going to do anything with the key that was parsed by your KeyParser, // you will need to tell the library how to convert back and forth between // raw keys and JWKs. Conversion from raw keys to jwk.Keys are done by KeyImporters, // and conversion from jwk.Keys to raw keys are done by KeyExporters. // // ## Using jwk.Import() using KeyImporter // // Each KeyImporter is hooked to run against a specific raw key type. // // When `jwk.Import()` is called, the library will iterate over all registered // KeyImporters for the specified raw key type, and attempt to convert the raw // key to a JWK by calling the `Import()` method on each KeyImporter. // // The KeyImporter's `Import()` method will receive the raw key to be converted, // and should return a JWK or an error if the conversion fails, or the return // `jwk.ContinueError()` if the specified raw key cannot be handled by ths/ KeyImporter. // // Once a KeyImporter is available, you will be able to pass the raw key to `jwk.Import()`. // The following example shows how you might register a KeyImporter for a hypotheical // mypkg.SuperSecretKey: // // jwk.RegisterKeyImporter(&mypkg.SuperSecretKey{}, jwk.KeyImportFunc(imnportSuperSecretKey)) // // func importSuperSecretKey(key any) (jwk.Key, error) { // mykey, ok := key.(*mypkg.SuperSecretKey) // if !ok { // // You must return jwk.ContinueError here, or otherwise // // processing will stop with an error // return nil, fmt.Errorf("invalid key type %T for importer: %w", key, jwk.ContinueError()) // } // // return mypkg.SuperSecretJWK{ .... }, nil // You could reuse existing JWK types if you can // } // // ## Registering a KeyExporter // // KeyExporters are the opposite of KeyImporters: they convert a JWK to a raw key when `key.Raw(...)` is // called. If you intend to use `key.Raw(...)` for a JWK created using one of your KeyImporters, // you will also // // KeyExporters are registered by key type. For example, if you want to register a KeyExporter for // RSA keys, you would do: // // jwk.RegisterKeyExporter(jwa.RSA, jwk.KeyExportFunc(exportRSAKey)) // // For a given JWK, it will be passed a "destination" object to store the exported raw key. For example, // an RSA-based private JWK can be exported to a `*rsa.PrivateKey` or to a `*any`, but not // to a `*ecdsa.PrivateKey`: // // var dst *rsa.PrivateKey // key.Raw(&dst) // OK // // var dst any // key.Raw(&dst) // OK // // var dst *ecdsa.PrivateKey // key.Raw(&dst) // Error, if key is an RSA key // // You will need to handle this distinction yourself in your KeyImporter. For example, certain // elliptic curve keys can be expressed in JWK in the same format, minus the "kty". In that case // you will need to check for the type of the destination object and return an error if it is // not compatible with your key. // // var raw mypkg.PrivateKey // assume a hypothetical private key type using a different curve than standard ones lie P-256 // key, _ := jwk.Import(raw) // // key could be jwk.ECDSAPrivateKey, with different curve than P-256 // // var dst *ecdsa.PrivateKey // key.Raw(&dst) // your KeyImporter will be called with *ecdsa.PrivateKey, which is not compatible with your key // // To implement this your code should look like the following: // // jwk.RegisterKeyExporter(jwk.EC, jwk.KeyExportFunc(exportMyKey)) // // func exportMyKey(key jwk.Key, hint any) (any, error) { // // check if the type of object in hint is compatible with your key // switch hint.(type) { // case *mypkg.PrivateKey, *any: // // OK, we can proceed // default: // // Not compatible, return jwk.ContinueError // return nil, jwk.ContinueError() // } // // // key is a jwk.ECDSAPrivateKey or jwk.ECDSAPublicKey // switch key := key.(type) { // case jwk.ECDSAPrivateKey: // // convert key to mypkg.PrivateKey // case jwk.ECDSAPublicKey: // // convert key to mypkg.PublicKey // default: // // Not compatible, return jwk.ContinueError // return nil, jwk.ContinueError() // } // return ..., nil // } package jwk golang-github-lestrrat-go-jwx-3.0.13/jwk/ecdsa.go000066400000000000000000000234651515060566400216170ustar00rootroot00000000000000package jwk import ( "crypto" "crypto/ecdh" "crypto/ecdsa" "crypto/elliptic" "fmt" "math/big" "reflect" "github.com/lestrrat-go/jwx/v3/internal/base64" "github.com/lestrrat-go/jwx/v3/internal/ecutil" "github.com/lestrrat-go/jwx/v3/jwa" ourecdsa "github.com/lestrrat-go/jwx/v3/jwk/ecdsa" ) func init() { ourecdsa.RegisterCurve(jwa.P256(), elliptic.P256()) ourecdsa.RegisterCurve(jwa.P384(), elliptic.P384()) ourecdsa.RegisterCurve(jwa.P521(), elliptic.P521()) RegisterKeyExporter(jwa.EC(), KeyExportFunc(ecdsaJWKToRaw)) } func (k *ecdsaPublicKey) Import(rawKey *ecdsa.PublicKey) error { k.mu.Lock() defer k.mu.Unlock() if rawKey.X == nil { return fmt.Errorf(`invalid ecdsa.PublicKey`) } if rawKey.Y == nil { return fmt.Errorf(`invalid ecdsa.PublicKey`) } xbuf := ecutil.AllocECPointBuffer(rawKey.X, rawKey.Curve) ybuf := ecutil.AllocECPointBuffer(rawKey.Y, rawKey.Curve) defer ecutil.ReleaseECPointBuffer(xbuf) defer ecutil.ReleaseECPointBuffer(ybuf) k.x = make([]byte, len(xbuf)) copy(k.x, xbuf) k.y = make([]byte, len(ybuf)) copy(k.y, ybuf) alg, err := ourecdsa.AlgorithmFromCurve(rawKey.Curve) if err != nil { return fmt.Errorf(`jwk: failed to get algorithm for converting ECDSA public key to JWK: %w`, err) } k.crv = &alg return nil } func (k *ecdsaPrivateKey) Import(rawKey *ecdsa.PrivateKey) error { k.mu.Lock() defer k.mu.Unlock() if rawKey.PublicKey.X == nil { return fmt.Errorf(`invalid ecdsa.PrivateKey`) } if rawKey.PublicKey.Y == nil { return fmt.Errorf(`invalid ecdsa.PrivateKey`) } if rawKey.D == nil { return fmt.Errorf(`invalid ecdsa.PrivateKey`) } xbuf := ecutil.AllocECPointBuffer(rawKey.PublicKey.X, rawKey.Curve) ybuf := ecutil.AllocECPointBuffer(rawKey.PublicKey.Y, rawKey.Curve) dbuf := ecutil.AllocECPointBuffer(rawKey.D, rawKey.Curve) defer ecutil.ReleaseECPointBuffer(xbuf) defer ecutil.ReleaseECPointBuffer(ybuf) defer ecutil.ReleaseECPointBuffer(dbuf) k.x = make([]byte, len(xbuf)) copy(k.x, xbuf) k.y = make([]byte, len(ybuf)) copy(k.y, ybuf) k.d = make([]byte, len(dbuf)) copy(k.d, dbuf) alg, err := ourecdsa.AlgorithmFromCurve(rawKey.Curve) if err != nil { return fmt.Errorf(`jwk: failed to get algorithm for converting ECDSA private key to JWK: %w`, err) } k.crv = &alg return nil } func buildECDSAPublicKey(alg jwa.EllipticCurveAlgorithm, xbuf, ybuf []byte) (*ecdsa.PublicKey, error) { crv, err := ourecdsa.CurveFromAlgorithm(alg) if err != nil { return nil, fmt.Errorf(`jwk: failed to get algorithm for ECDSA public key: %w`, err) } var x, y big.Int x.SetBytes(xbuf) y.SetBytes(ybuf) return &ecdsa.PublicKey{Curve: crv, X: &x, Y: &y}, nil } func buildECDHPublicKey(alg jwa.EllipticCurveAlgorithm, xbuf, ybuf []byte) (*ecdh.PublicKey, error) { var ecdhcrv ecdh.Curve switch alg { case jwa.X25519(): ecdhcrv = ecdh.X25519() case jwa.P256(): ecdhcrv = ecdh.P256() case jwa.P384(): ecdhcrv = ecdh.P384() case jwa.P521(): ecdhcrv = ecdh.P521() default: return nil, fmt.Errorf(`jwk: unsupported ECDH curve %s`, alg) } return ecdhcrv.NewPublicKey(append([]byte{0x04}, append(xbuf, ybuf...)...)) } func buildECDHPrivateKey(alg jwa.EllipticCurveAlgorithm, dbuf []byte) (*ecdh.PrivateKey, error) { var ecdhcrv ecdh.Curve switch alg { case jwa.X25519(): ecdhcrv = ecdh.X25519() case jwa.P256(): ecdhcrv = ecdh.P256() case jwa.P384(): ecdhcrv = ecdh.P384() case jwa.P521(): ecdhcrv = ecdh.P521() default: return nil, fmt.Errorf(`jwk: unsupported ECDH curve %s`, alg) } return ecdhcrv.NewPrivateKey(dbuf) } var ecdsaConvertibleTypes = []reflect.Type{ reflect.TypeFor[ECDSAPrivateKey](), reflect.TypeFor[ECDSAPublicKey](), } func ecdsaJWKToRaw(keyif Key, hint any) (any, error) { var isECDH bool extracted, err := extractEmbeddedKey(keyif, ecdsaConvertibleTypes) if err != nil { return nil, fmt.Errorf(`jwk: failed to extract embedded key: %w`, err) } switch k := extracted.(type) { case ECDSAPrivateKey: switch hint.(type) { case ecdsa.PrivateKey, *ecdsa.PrivateKey: case ecdh.PrivateKey, *ecdh.PrivateKey: isECDH = true default: rv := reflect.ValueOf(hint) //nolint:revive if rv.Kind() == reflect.Ptr && rv.Elem().Kind() == reflect.Interface { // pointer to an interface value, presumably they want us to dynamically // create an object of the right type } else { return nil, fmt.Errorf(`invalid destination object type %T: %w`, hint, ContinueError()) } } locker, ok := k.(rlocker) if ok { locker.rlock() defer locker.runlock() } crv, ok := k.Crv() if !ok { return nil, fmt.Errorf(`missing "crv" field`) } if isECDH { d, ok := k.D() if !ok { return nil, fmt.Errorf(`missing "d" field`) } return buildECDHPrivateKey(crv, d) } x, ok := k.X() if !ok { return nil, fmt.Errorf(`missing "x" field`) } y, ok := k.Y() if !ok { return nil, fmt.Errorf(`missing "y" field`) } pubk, err := buildECDSAPublicKey(crv, x, y) if err != nil { return nil, fmt.Errorf(`failed to build public key: %w`, err) } var key ecdsa.PrivateKey var d big.Int origD, ok := k.D() if !ok { return nil, fmt.Errorf(`missing "d" field`) } d.SetBytes(origD) key.D = &d key.PublicKey = *pubk return &key, nil case ECDSAPublicKey: switch hint.(type) { case ecdsa.PublicKey, *ecdsa.PublicKey: case ecdh.PublicKey, *ecdh.PublicKey: isECDH = true default: rv := reflect.ValueOf(hint) //nolint:revive if rv.Kind() == reflect.Ptr && rv.Elem().Kind() == reflect.Interface { // pointer to an interface value, presumably they want us to dynamically // create an object of the right type } else { return nil, fmt.Errorf(`invalid destination object type %T: %w`, hint, ContinueError()) } } locker, ok := k.(rlocker) if ok { locker.rlock() defer locker.runlock() } crv, ok := k.Crv() if !ok { return nil, fmt.Errorf(`missing "crv" field`) } x, ok := k.X() if !ok { return nil, fmt.Errorf(`missing "x" field`) } y, ok := k.Y() if !ok { return nil, fmt.Errorf(`missing "y" field`) } if isECDH { return buildECDHPublicKey(crv, x, y) } return buildECDSAPublicKey(crv, x, y) default: return nil, ContinueError() } } func makeECDSAPublicKey(src Key) (Key, error) { newKey := newECDSAPublicKey() // Iterate and copy everything except for the bits that should not be in the public key for _, k := range src.Keys() { switch k { case ECDSADKey: continue default: var v any if err := src.Get(k, &v); err != nil { return nil, fmt.Errorf(`ecdsa: makeECDSAPublicKey: failed to get field %q: %w`, k, err) } if err := newKey.Set(k, v); err != nil { return nil, fmt.Errorf(`ecdsa: makeECDSAPublicKey: failed to set field %q: %w`, k, err) } } } return newKey, nil } func (k *ecdsaPrivateKey) PublicKey() (Key, error) { return makeECDSAPublicKey(k) } func (k *ecdsaPublicKey) PublicKey() (Key, error) { return makeECDSAPublicKey(k) } func ecdsaThumbprint(hash crypto.Hash, crv, x, y string) []byte { h := hash.New() fmt.Fprint(h, `{"crv":"`) fmt.Fprint(h, crv) fmt.Fprint(h, `","kty":"EC","x":"`) fmt.Fprint(h, x) fmt.Fprint(h, `","y":"`) fmt.Fprint(h, y) fmt.Fprint(h, `"}`) return h.Sum(nil) } // Thumbprint returns the JWK thumbprint using the indicated // hashing algorithm, according to RFC 7638 func (k ecdsaPublicKey) Thumbprint(hash crypto.Hash) ([]byte, error) { k.mu.RLock() defer k.mu.RUnlock() var key ecdsa.PublicKey if err := Export(&k, &key); err != nil { return nil, fmt.Errorf(`failed to export ecdsa.PublicKey for thumbprint generation: %w`, err) } xbuf := ecutil.AllocECPointBuffer(key.X, key.Curve) ybuf := ecutil.AllocECPointBuffer(key.Y, key.Curve) defer ecutil.ReleaseECPointBuffer(xbuf) defer ecutil.ReleaseECPointBuffer(ybuf) return ecdsaThumbprint( hash, key.Curve.Params().Name, base64.EncodeToString(xbuf), base64.EncodeToString(ybuf), ), nil } // Thumbprint returns the JWK thumbprint using the indicated // hashing algorithm, according to RFC 7638 func (k ecdsaPrivateKey) Thumbprint(hash crypto.Hash) ([]byte, error) { k.mu.RLock() defer k.mu.RUnlock() var key ecdsa.PrivateKey if err := Export(&k, &key); err != nil { return nil, fmt.Errorf(`failed to export ecdsa.PrivateKey for thumbprint generation: %w`, err) } xbuf := ecutil.AllocECPointBuffer(key.X, key.Curve) ybuf := ecutil.AllocECPointBuffer(key.Y, key.Curve) defer ecutil.ReleaseECPointBuffer(xbuf) defer ecutil.ReleaseECPointBuffer(ybuf) return ecdsaThumbprint( hash, key.Curve.Params().Name, base64.EncodeToString(xbuf), base64.EncodeToString(ybuf), ), nil } func ecdsaValidateKey(k interface { Crv() (jwa.EllipticCurveAlgorithm, bool) X() ([]byte, bool) Y() ([]byte, bool) }, checkPrivate bool) error { crvtyp, ok := k.Crv() if !ok { return fmt.Errorf(`missing "crv" field`) } crv, err := ourecdsa.CurveFromAlgorithm(crvtyp) if err != nil { return fmt.Errorf(`invalid curve algorithm %q: %w`, crvtyp, err) } keySize := ecutil.CalculateKeySize(crv) if x, ok := k.X(); !ok || len(x) != keySize { return fmt.Errorf(`invalid "x" length (%d) for curve %q`, len(x), crv.Params().Name) } if y, ok := k.Y(); !ok || len(y) != keySize { return fmt.Errorf(`invalid "y" length (%d) for curve %q`, len(y), crv.Params().Name) } if checkPrivate { if priv, ok := k.(keyWithD); ok { if d, ok := priv.D(); !ok || len(d) != keySize { return fmt.Errorf(`invalid "d" length (%d) for curve %q`, len(d), crv.Params().Name) } } else { return fmt.Errorf(`missing "d" value`) } } return nil } func (k *ecdsaPrivateKey) Validate() error { if err := ecdsaValidateKey(k, true); err != nil { return NewKeyValidationError(fmt.Errorf(`jwk.ECDSAPrivateKey: %w`, err)) } return nil } func (k *ecdsaPublicKey) Validate() error { if err := ecdsaValidateKey(k, false); err != nil { return NewKeyValidationError(fmt.Errorf(`jwk.ECDSAPublicKey: %w`, err)) } return nil } golang-github-lestrrat-go-jwx-3.0.13/jwk/ecdsa/000077500000000000000000000000001515060566400212565ustar00rootroot00000000000000golang-github-lestrrat-go-jwx-3.0.13/jwk/ecdsa/BUILD.bazel000066400000000000000000000005201515060566400231310ustar00rootroot00000000000000load("@rules_go//go:def.bzl", "go_library") go_library( name = "ecdsa", srcs = ["ecdsa.go"], importpath = "github.com/lestrrat-go/jwx/v3/jwk/ecdsa", visibility = ["//visibility:public"], deps = ["//jwa"], ) alias( name = "go_default_library", actual = ":ecdsa", visibility = ["//visibility:public"], ) golang-github-lestrrat-go-jwx-3.0.13/jwk/ecdsa/ecdsa.go000066400000000000000000000035221515060566400226660ustar00rootroot00000000000000package ecdsa import ( "crypto/elliptic" "fmt" "sync" "github.com/lestrrat-go/jwx/v3/jwa" ) var muCurves sync.RWMutex var algToCurveMap map[jwa.EllipticCurveAlgorithm]elliptic.Curve var curveToAlgMap map[elliptic.Curve]jwa.EllipticCurveAlgorithm var algList []jwa.EllipticCurveAlgorithm func init() { muCurves.Lock() algToCurveMap = make(map[jwa.EllipticCurveAlgorithm]elliptic.Curve) curveToAlgMap = make(map[elliptic.Curve]jwa.EllipticCurveAlgorithm) muCurves.Unlock() } // RegisterCurve registers a jwa.EllipticCurveAlgorithm constant and its // corresponding elliptic.Curve object. Users do not need to call this unless // they are registering a new ECDSA key type func RegisterCurve(alg jwa.EllipticCurveAlgorithm, crv elliptic.Curve) { muCurves.Lock() defer muCurves.Unlock() algToCurveMap[alg] = crv curveToAlgMap[crv] = alg rebuildCurves() } func rebuildCurves() { l := len(algToCurveMap) if cap(algList) < l { algList = make([]jwa.EllipticCurveAlgorithm, 0, l) } else { algList = algList[:0] } for alg := range algToCurveMap { algList = append(algList, alg) } } // Algorithms returns the list of registered jwa.EllipticCurveAlgorithms // that ca be used for ECDSA keys. func Algorithms() []jwa.EllipticCurveAlgorithm { muCurves.RLock() defer muCurves.RUnlock() return algList } func AlgorithmFromCurve(crv elliptic.Curve) (jwa.EllipticCurveAlgorithm, error) { alg, ok := curveToAlgMap[crv] if !ok { return jwa.InvalidEllipticCurve(), fmt.Errorf(`unknown elliptic curve: %q`, crv) } return alg, nil } func CurveFromAlgorithm(alg jwa.EllipticCurveAlgorithm) (elliptic.Curve, error) { crv, ok := algToCurveMap[alg] if !ok { return nil, fmt.Errorf(`unknown elliptic curve algorithm: %q`, alg) } return crv, nil } func IsCurveAvailable(alg jwa.EllipticCurveAlgorithm) bool { _, ok := algToCurveMap[alg] return ok } golang-github-lestrrat-go-jwx-3.0.13/jwk/ecdsa_gen.go000066400000000000000000001133771515060566400224520ustar00rootroot00000000000000// Code generated by tools/cmd/genjwk/main.go. DO NOT EDIT. package jwk import ( "bytes" "fmt" "sort" "sync" "github.com/lestrrat-go/blackmagic" "github.com/lestrrat-go/jwx/v3/cert" "github.com/lestrrat-go/jwx/v3/internal/base64" "github.com/lestrrat-go/jwx/v3/internal/json" "github.com/lestrrat-go/jwx/v3/internal/pool" "github.com/lestrrat-go/jwx/v3/internal/tokens" "github.com/lestrrat-go/jwx/v3/jwa" ) const ( ECDSACrvKey = "crv" ECDSADKey = "d" ECDSAXKey = "x" ECDSAYKey = "y" ) type ECDSAPublicKey interface { Key Crv() (jwa.EllipticCurveAlgorithm, bool) X() ([]byte, bool) Y() ([]byte, bool) } type ecdsaPublicKey struct { algorithm *jwa.KeyAlgorithm // https://tools.ietf.org/html/rfc7517#section-4.4 crv *jwa.EllipticCurveAlgorithm keyID *string // https://tools.ietf.org/html/rfc7515#section-4.1.4 keyOps *KeyOperationList // https://tools.ietf.org/html/rfc7517#section-4.3 keyUsage *string // https://tools.ietf.org/html/rfc7517#section-4.2 x []byte x509CertChain *cert.Chain // https://tools.ietf.org/html/rfc7515#section-4.1.6 x509CertThumbprint *string // https://tools.ietf.org/html/rfc7515#section-4.1.7 x509CertThumbprintS256 *string // https://tools.ietf.org/html/rfc7515#section-4.1.8 x509URL *string // https://tools.ietf.org/html/rfc7515#section-4.1.5 y []byte privateParams map[string]any mu *sync.RWMutex dc json.DecodeCtx } var _ ECDSAPublicKey = &ecdsaPublicKey{} var _ Key = &ecdsaPublicKey{} func newECDSAPublicKey() *ecdsaPublicKey { return &ecdsaPublicKey{ mu: &sync.RWMutex{}, privateParams: make(map[string]any), } } func (h ecdsaPublicKey) KeyType() jwa.KeyType { return jwa.EC() } func (h ecdsaPublicKey) rlock() { h.mu.RLock() } func (h ecdsaPublicKey) runlock() { h.mu.RUnlock() } func (h ecdsaPublicKey) IsPrivate() bool { return false } func (h *ecdsaPublicKey) Algorithm() (jwa.KeyAlgorithm, bool) { if h.algorithm != nil { return *(h.algorithm), true } return nil, false } func (h *ecdsaPublicKey) Crv() (jwa.EllipticCurveAlgorithm, bool) { if h.crv != nil { return *(h.crv), true } return jwa.InvalidEllipticCurve(), false } func (h *ecdsaPublicKey) KeyID() (string, bool) { if h.keyID != nil { return *(h.keyID), true } return "", false } func (h *ecdsaPublicKey) KeyOps() (KeyOperationList, bool) { if h.keyOps != nil { return *(h.keyOps), true } return nil, false } func (h *ecdsaPublicKey) KeyUsage() (string, bool) { if h.keyUsage != nil { return *(h.keyUsage), true } return "", false } func (h *ecdsaPublicKey) X() ([]byte, bool) { if h.x != nil { return h.x, true } return nil, false } func (h *ecdsaPublicKey) X509CertChain() (*cert.Chain, bool) { return h.x509CertChain, true } func (h *ecdsaPublicKey) X509CertThumbprint() (string, bool) { if h.x509CertThumbprint != nil { return *(h.x509CertThumbprint), true } return "", false } func (h *ecdsaPublicKey) X509CertThumbprintS256() (string, bool) { if h.x509CertThumbprintS256 != nil { return *(h.x509CertThumbprintS256), true } return "", false } func (h *ecdsaPublicKey) X509URL() (string, bool) { if h.x509URL != nil { return *(h.x509URL), true } return "", false } func (h *ecdsaPublicKey) Y() ([]byte, bool) { if h.y != nil { return h.y, true } return nil, false } func (h *ecdsaPublicKey) Has(name string) bool { h.mu.RLock() defer h.mu.RUnlock() switch name { case KeyTypeKey: return true case AlgorithmKey: return h.algorithm != nil case ECDSACrvKey: return h.crv != nil case KeyIDKey: return h.keyID != nil case KeyOpsKey: return h.keyOps != nil case KeyUsageKey: return h.keyUsage != nil case ECDSAXKey: return h.x != nil case X509CertChainKey: return h.x509CertChain != nil case X509CertThumbprintKey: return h.x509CertThumbprint != nil case X509CertThumbprintS256Key: return h.x509CertThumbprintS256 != nil case X509URLKey: return h.x509URL != nil case ECDSAYKey: return h.y != nil default: _, ok := h.privateParams[name] return ok } } func (h *ecdsaPublicKey) Get(name string, dst any) error { h.mu.RLock() defer h.mu.RUnlock() switch name { case KeyTypeKey: if err := blackmagic.AssignIfCompatible(dst, h.KeyType()); err != nil { return fmt.Errorf(`ecdsaPublicKey.Get: failed to assign value for field %q to destination object: %w`, name, err) } case AlgorithmKey: if h.algorithm == nil { return fmt.Errorf(`field %q not found`, name) } if err := blackmagic.AssignIfCompatible(dst, *(h.algorithm)); err != nil { return fmt.Errorf(`failed to assign value for field %q: %w`, name, err) } return nil case ECDSACrvKey: if h.crv == nil { return fmt.Errorf(`field %q not found`, name) } if err := blackmagic.AssignIfCompatible(dst, *(h.crv)); err != nil { return fmt.Errorf(`failed to assign value for field %q: %w`, name, err) } return nil case KeyIDKey: if h.keyID == nil { return fmt.Errorf(`field %q not found`, name) } if err := blackmagic.AssignIfCompatible(dst, *(h.keyID)); err != nil { return fmt.Errorf(`failed to assign value for field %q: %w`, name, err) } return nil case KeyOpsKey: if h.keyOps == nil { return fmt.Errorf(`field %q not found`, name) } if err := blackmagic.AssignIfCompatible(dst, *(h.keyOps)); err != nil { return fmt.Errorf(`failed to assign value for field %q: %w`, name, err) } return nil case KeyUsageKey: if h.keyUsage == nil { return fmt.Errorf(`field %q not found`, name) } if err := blackmagic.AssignIfCompatible(dst, *(h.keyUsage)); err != nil { return fmt.Errorf(`failed to assign value for field %q: %w`, name, err) } return nil case ECDSAXKey: if h.x == nil { return fmt.Errorf(`field %q not found`, name) } if err := blackmagic.AssignIfCompatible(dst, h.x); err != nil { return fmt.Errorf(`failed to assign value for field %q: %w`, name, err) } return nil case X509CertChainKey: if h.x509CertChain == nil { return fmt.Errorf(`field %q not found`, name) } if err := blackmagic.AssignIfCompatible(dst, h.x509CertChain); err != nil { return fmt.Errorf(`failed to assign value for field %q: %w`, name, err) } return nil case X509CertThumbprintKey: if h.x509CertThumbprint == nil { return fmt.Errorf(`field %q not found`, name) } if err := blackmagic.AssignIfCompatible(dst, *(h.x509CertThumbprint)); err != nil { return fmt.Errorf(`failed to assign value for field %q: %w`, name, err) } return nil case X509CertThumbprintS256Key: if h.x509CertThumbprintS256 == nil { return fmt.Errorf(`field %q not found`, name) } if err := blackmagic.AssignIfCompatible(dst, *(h.x509CertThumbprintS256)); err != nil { return fmt.Errorf(`failed to assign value for field %q: %w`, name, err) } return nil case X509URLKey: if h.x509URL == nil { return fmt.Errorf(`field %q not found`, name) } if err := blackmagic.AssignIfCompatible(dst, *(h.x509URL)); err != nil { return fmt.Errorf(`failed to assign value for field %q: %w`, name, err) } return nil case ECDSAYKey: if h.y == nil { return fmt.Errorf(`field %q not found`, name) } if err := blackmagic.AssignIfCompatible(dst, h.y); err != nil { return fmt.Errorf(`failed to assign value for field %q: %w`, name, err) } return nil default: v, ok := h.privateParams[name] if !ok { return fmt.Errorf(`field %q not found`, name) } if err := blackmagic.AssignIfCompatible(dst, v); err != nil { return fmt.Errorf(`failed to assign value for field %q: %w`, name, err) } } return nil } func (h *ecdsaPublicKey) Set(name string, value any) error { h.mu.Lock() defer h.mu.Unlock() return h.setNoLock(name, value) } func (h *ecdsaPublicKey) setNoLock(name string, value any) error { switch name { case "kty": return nil case AlgorithmKey: switch v := value.(type) { case string, jwa.SignatureAlgorithm, jwa.KeyEncryptionAlgorithm, jwa.ContentEncryptionAlgorithm: tmp, err := jwa.KeyAlgorithmFrom(v) if err != nil { return fmt.Errorf(`invalid algorithm for %q key: %w`, AlgorithmKey, err) } h.algorithm = &tmp default: return fmt.Errorf(`invalid type for %q key: %T`, AlgorithmKey, value) } return nil case ECDSACrvKey: if v, ok := value.(jwa.EllipticCurveAlgorithm); ok { h.crv = &v return nil } return fmt.Errorf(`invalid value for %s key: %T`, ECDSACrvKey, value) case KeyIDKey: if v, ok := value.(string); ok { h.keyID = &v return nil } return fmt.Errorf(`invalid value for %s key: %T`, KeyIDKey, value) case KeyOpsKey: var acceptor KeyOperationList if err := acceptor.Accept(value); err != nil { return fmt.Errorf(`invalid value for %s key: %w`, KeyOpsKey, err) } h.keyOps = &acceptor return nil case KeyUsageKey: switch v := value.(type) { case KeyUsageType: switch v { case ForSignature, ForEncryption: tmp := v.String() h.keyUsage = &tmp default: return fmt.Errorf(`invalid key usage type %s`, v) } case string: h.keyUsage = &v default: return fmt.Errorf(`invalid key usage type %s`, v) } case ECDSAXKey: if v, ok := value.([]byte); ok { h.x = v return nil } return fmt.Errorf(`invalid value for %s key: %T`, ECDSAXKey, value) case X509CertChainKey: if v, ok := value.(*cert.Chain); ok { h.x509CertChain = v return nil } return fmt.Errorf(`invalid value for %s key: %T`, X509CertChainKey, value) case X509CertThumbprintKey: if v, ok := value.(string); ok { h.x509CertThumbprint = &v return nil } return fmt.Errorf(`invalid value for %s key: %T`, X509CertThumbprintKey, value) case X509CertThumbprintS256Key: if v, ok := value.(string); ok { h.x509CertThumbprintS256 = &v return nil } return fmt.Errorf(`invalid value for %s key: %T`, X509CertThumbprintS256Key, value) case X509URLKey: if v, ok := value.(string); ok { h.x509URL = &v return nil } return fmt.Errorf(`invalid value for %s key: %T`, X509URLKey, value) case ECDSAYKey: if v, ok := value.([]byte); ok { h.y = v return nil } return fmt.Errorf(`invalid value for %s key: %T`, ECDSAYKey, value) default: if h.privateParams == nil { h.privateParams = map[string]any{} } h.privateParams[name] = value } return nil } func (k *ecdsaPublicKey) Remove(key string) error { k.mu.Lock() defer k.mu.Unlock() switch key { case AlgorithmKey: k.algorithm = nil case ECDSACrvKey: k.crv = nil case KeyIDKey: k.keyID = nil case KeyOpsKey: k.keyOps = nil case KeyUsageKey: k.keyUsage = nil case ECDSAXKey: k.x = nil case X509CertChainKey: k.x509CertChain = nil case X509CertThumbprintKey: k.x509CertThumbprint = nil case X509CertThumbprintS256Key: k.x509CertThumbprintS256 = nil case X509URLKey: k.x509URL = nil case ECDSAYKey: k.y = nil default: delete(k.privateParams, key) } return nil } func (k *ecdsaPublicKey) Clone() (Key, error) { key, err := cloneKey(k) if err != nil { return nil, fmt.Errorf(`ecdsaPublicKey.Clone: %w`, err) } return key, nil } func (k *ecdsaPublicKey) DecodeCtx() json.DecodeCtx { k.mu.RLock() defer k.mu.RUnlock() return k.dc } func (k *ecdsaPublicKey) SetDecodeCtx(dc json.DecodeCtx) { k.mu.Lock() defer k.mu.Unlock() k.dc = dc } func (h *ecdsaPublicKey) UnmarshalJSON(buf []byte) error { h.mu.Lock() defer h.mu.Unlock() h.algorithm = nil h.crv = nil h.keyID = nil h.keyOps = nil h.keyUsage = nil h.x = nil h.x509CertChain = nil h.x509CertThumbprint = nil h.x509CertThumbprintS256 = nil h.x509URL = nil h.y = nil dec := json.NewDecoder(bytes.NewReader(buf)) LOOP: for { tok, err := dec.Token() if err != nil { return fmt.Errorf(`error reading token: %w`, err) } switch tok := tok.(type) { case json.Delim: // Assuming we're doing everything correctly, we should ONLY // get either tokens.OpenCurlyBracket or tokens.CloseCurlyBracket here. if tok == tokens.CloseCurlyBracket { // End of object break LOOP } else if tok != tokens.OpenCurlyBracket { return fmt.Errorf(`expected '%c' but got '%c'`, tokens.OpenCurlyBracket, tok) } case string: // Objects can only have string keys switch tok { case KeyTypeKey: val, err := json.ReadNextStringToken(dec) if err != nil { return fmt.Errorf(`error reading token: %w`, err) } if val != jwa.EC().String() { return fmt.Errorf(`invalid kty value for RSAPublicKey (%s)`, val) } case AlgorithmKey: var s string if err := dec.Decode(&s); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, AlgorithmKey, err) } alg, err := jwa.KeyAlgorithmFrom(s) if err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, AlgorithmKey, err) } h.algorithm = &alg case ECDSACrvKey: var decoded jwa.EllipticCurveAlgorithm if err := dec.Decode(&decoded); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, ECDSACrvKey, err) } h.crv = &decoded case KeyIDKey: if err := json.AssignNextStringToken(&h.keyID, dec); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, KeyIDKey, err) } case KeyOpsKey: var decoded KeyOperationList if err := dec.Decode(&decoded); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, KeyOpsKey, err) } h.keyOps = &decoded case KeyUsageKey: if err := json.AssignNextStringToken(&h.keyUsage, dec); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, KeyUsageKey, err) } case ECDSAXKey: if err := json.AssignNextBytesToken(&h.x, dec); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, ECDSAXKey, err) } case X509CertChainKey: var decoded cert.Chain if err := dec.Decode(&decoded); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, X509CertChainKey, err) } h.x509CertChain = &decoded case X509CertThumbprintKey: if err := json.AssignNextStringToken(&h.x509CertThumbprint, dec); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, X509CertThumbprintKey, err) } case X509CertThumbprintS256Key: if err := json.AssignNextStringToken(&h.x509CertThumbprintS256, dec); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, X509CertThumbprintS256Key, err) } case X509URLKey: if err := json.AssignNextStringToken(&h.x509URL, dec); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, X509URLKey, err) } case ECDSAYKey: if err := json.AssignNextBytesToken(&h.y, dec); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, ECDSAYKey, err) } default: if dc := h.dc; dc != nil { if localReg := dc.Registry(); localReg != nil { decoded, err := localReg.Decode(dec, tok) if err == nil { h.setNoLock(tok, decoded) continue } } } decoded, err := registry.Decode(dec, tok) if err == nil { h.setNoLock(tok, decoded) continue } return fmt.Errorf(`could not decode field %s: %w`, tok, err) } default: return fmt.Errorf(`invalid token %T`, tok) } } if h.crv == nil { return fmt.Errorf(`required field crv is missing`) } if h.x == nil { return fmt.Errorf(`required field x is missing`) } if h.y == nil { return fmt.Errorf(`required field y is missing`) } return nil } func (h ecdsaPublicKey) MarshalJSON() ([]byte, error) { data := make(map[string]any) fields := make([]string, 0, 11) data[KeyTypeKey] = jwa.EC() fields = append(fields, KeyTypeKey) if h.algorithm != nil { data[AlgorithmKey] = *(h.algorithm) fields = append(fields, AlgorithmKey) } if h.crv != nil { data[ECDSACrvKey] = *(h.crv) fields = append(fields, ECDSACrvKey) } if h.keyID != nil { data[KeyIDKey] = *(h.keyID) fields = append(fields, KeyIDKey) } if h.keyOps != nil { data[KeyOpsKey] = *(h.keyOps) fields = append(fields, KeyOpsKey) } if h.keyUsage != nil { data[KeyUsageKey] = *(h.keyUsage) fields = append(fields, KeyUsageKey) } if h.x != nil { data[ECDSAXKey] = h.x fields = append(fields, ECDSAXKey) } if h.x509CertChain != nil { data[X509CertChainKey] = h.x509CertChain fields = append(fields, X509CertChainKey) } if h.x509CertThumbprint != nil { data[X509CertThumbprintKey] = *(h.x509CertThumbprint) fields = append(fields, X509CertThumbprintKey) } if h.x509CertThumbprintS256 != nil { data[X509CertThumbprintS256Key] = *(h.x509CertThumbprintS256) fields = append(fields, X509CertThumbprintS256Key) } if h.x509URL != nil { data[X509URLKey] = *(h.x509URL) fields = append(fields, X509URLKey) } if h.y != nil { data[ECDSAYKey] = h.y fields = append(fields, ECDSAYKey) } for k, v := range h.privateParams { data[k] = v fields = append(fields, k) } sort.Strings(fields) buf := pool.BytesBuffer().Get() defer pool.BytesBuffer().Put(buf) buf.WriteByte(tokens.OpenCurlyBracket) enc := json.NewEncoder(buf) for i, f := range fields { if i > 0 { buf.WriteRune(tokens.Comma) } buf.WriteRune(tokens.DoubleQuote) buf.WriteString(f) buf.WriteString(`":`) v := data[f] switch v := v.(type) { case []byte: buf.WriteRune(tokens.DoubleQuote) buf.WriteString(base64.EncodeToString(v)) buf.WriteRune(tokens.DoubleQuote) default: if err := enc.Encode(v); err != nil { return nil, fmt.Errorf(`failed to encode value for field %s: %w`, f, err) } buf.Truncate(buf.Len() - 1) } } buf.WriteByte(tokens.CloseCurlyBracket) ret := make([]byte, buf.Len()) copy(ret, buf.Bytes()) return ret, nil } func (h *ecdsaPublicKey) Keys() []string { h.mu.RLock() defer h.mu.RUnlock() keys := make([]string, 0, 11+len(h.privateParams)) keys = append(keys, KeyTypeKey) if h.algorithm != nil { keys = append(keys, AlgorithmKey) } if h.crv != nil { keys = append(keys, ECDSACrvKey) } if h.keyID != nil { keys = append(keys, KeyIDKey) } if h.keyOps != nil { keys = append(keys, KeyOpsKey) } if h.keyUsage != nil { keys = append(keys, KeyUsageKey) } if h.x != nil { keys = append(keys, ECDSAXKey) } if h.x509CertChain != nil { keys = append(keys, X509CertChainKey) } if h.x509CertThumbprint != nil { keys = append(keys, X509CertThumbprintKey) } if h.x509CertThumbprintS256 != nil { keys = append(keys, X509CertThumbprintS256Key) } if h.x509URL != nil { keys = append(keys, X509URLKey) } if h.y != nil { keys = append(keys, ECDSAYKey) } for k := range h.privateParams { keys = append(keys, k) } return keys } type ECDSAPrivateKey interface { Key Crv() (jwa.EllipticCurveAlgorithm, bool) D() ([]byte, bool) X() ([]byte, bool) Y() ([]byte, bool) } type ecdsaPrivateKey struct { algorithm *jwa.KeyAlgorithm // https://tools.ietf.org/html/rfc7517#section-4.4 crv *jwa.EllipticCurveAlgorithm d []byte keyID *string // https://tools.ietf.org/html/rfc7515#section-4.1.4 keyOps *KeyOperationList // https://tools.ietf.org/html/rfc7517#section-4.3 keyUsage *string // https://tools.ietf.org/html/rfc7517#section-4.2 x []byte x509CertChain *cert.Chain // https://tools.ietf.org/html/rfc7515#section-4.1.6 x509CertThumbprint *string // https://tools.ietf.org/html/rfc7515#section-4.1.7 x509CertThumbprintS256 *string // https://tools.ietf.org/html/rfc7515#section-4.1.8 x509URL *string // https://tools.ietf.org/html/rfc7515#section-4.1.5 y []byte privateParams map[string]any mu *sync.RWMutex dc json.DecodeCtx } var _ ECDSAPrivateKey = &ecdsaPrivateKey{} var _ Key = &ecdsaPrivateKey{} func newECDSAPrivateKey() *ecdsaPrivateKey { return &ecdsaPrivateKey{ mu: &sync.RWMutex{}, privateParams: make(map[string]any), } } func (h ecdsaPrivateKey) KeyType() jwa.KeyType { return jwa.EC() } func (h ecdsaPrivateKey) rlock() { h.mu.RLock() } func (h ecdsaPrivateKey) runlock() { h.mu.RUnlock() } func (h ecdsaPrivateKey) IsPrivate() bool { return true } func (h *ecdsaPrivateKey) Algorithm() (jwa.KeyAlgorithm, bool) { if h.algorithm != nil { return *(h.algorithm), true } return nil, false } func (h *ecdsaPrivateKey) Crv() (jwa.EllipticCurveAlgorithm, bool) { if h.crv != nil { return *(h.crv), true } return jwa.InvalidEllipticCurve(), false } func (h *ecdsaPrivateKey) D() ([]byte, bool) { if h.d != nil { return h.d, true } return nil, false } func (h *ecdsaPrivateKey) KeyID() (string, bool) { if h.keyID != nil { return *(h.keyID), true } return "", false } func (h *ecdsaPrivateKey) KeyOps() (KeyOperationList, bool) { if h.keyOps != nil { return *(h.keyOps), true } return nil, false } func (h *ecdsaPrivateKey) KeyUsage() (string, bool) { if h.keyUsage != nil { return *(h.keyUsage), true } return "", false } func (h *ecdsaPrivateKey) X() ([]byte, bool) { if h.x != nil { return h.x, true } return nil, false } func (h *ecdsaPrivateKey) X509CertChain() (*cert.Chain, bool) { return h.x509CertChain, true } func (h *ecdsaPrivateKey) X509CertThumbprint() (string, bool) { if h.x509CertThumbprint != nil { return *(h.x509CertThumbprint), true } return "", false } func (h *ecdsaPrivateKey) X509CertThumbprintS256() (string, bool) { if h.x509CertThumbprintS256 != nil { return *(h.x509CertThumbprintS256), true } return "", false } func (h *ecdsaPrivateKey) X509URL() (string, bool) { if h.x509URL != nil { return *(h.x509URL), true } return "", false } func (h *ecdsaPrivateKey) Y() ([]byte, bool) { if h.y != nil { return h.y, true } return nil, false } func (h *ecdsaPrivateKey) Has(name string) bool { h.mu.RLock() defer h.mu.RUnlock() switch name { case KeyTypeKey: return true case AlgorithmKey: return h.algorithm != nil case ECDSACrvKey: return h.crv != nil case ECDSADKey: return h.d != nil case KeyIDKey: return h.keyID != nil case KeyOpsKey: return h.keyOps != nil case KeyUsageKey: return h.keyUsage != nil case ECDSAXKey: return h.x != nil case X509CertChainKey: return h.x509CertChain != nil case X509CertThumbprintKey: return h.x509CertThumbprint != nil case X509CertThumbprintS256Key: return h.x509CertThumbprintS256 != nil case X509URLKey: return h.x509URL != nil case ECDSAYKey: return h.y != nil default: _, ok := h.privateParams[name] return ok } } func (h *ecdsaPrivateKey) Get(name string, dst any) error { h.mu.RLock() defer h.mu.RUnlock() switch name { case KeyTypeKey: if err := blackmagic.AssignIfCompatible(dst, h.KeyType()); err != nil { return fmt.Errorf(`ecdsaPrivateKey.Get: failed to assign value for field %q to destination object: %w`, name, err) } case AlgorithmKey: if h.algorithm == nil { return fmt.Errorf(`field %q not found`, name) } if err := blackmagic.AssignIfCompatible(dst, *(h.algorithm)); err != nil { return fmt.Errorf(`failed to assign value for field %q: %w`, name, err) } return nil case ECDSACrvKey: if h.crv == nil { return fmt.Errorf(`field %q not found`, name) } if err := blackmagic.AssignIfCompatible(dst, *(h.crv)); err != nil { return fmt.Errorf(`failed to assign value for field %q: %w`, name, err) } return nil case ECDSADKey: if h.d == nil { return fmt.Errorf(`field %q not found`, name) } if err := blackmagic.AssignIfCompatible(dst, h.d); err != nil { return fmt.Errorf(`failed to assign value for field %q: %w`, name, err) } return nil case KeyIDKey: if h.keyID == nil { return fmt.Errorf(`field %q not found`, name) } if err := blackmagic.AssignIfCompatible(dst, *(h.keyID)); err != nil { return fmt.Errorf(`failed to assign value for field %q: %w`, name, err) } return nil case KeyOpsKey: if h.keyOps == nil { return fmt.Errorf(`field %q not found`, name) } if err := blackmagic.AssignIfCompatible(dst, *(h.keyOps)); err != nil { return fmt.Errorf(`failed to assign value for field %q: %w`, name, err) } return nil case KeyUsageKey: if h.keyUsage == nil { return fmt.Errorf(`field %q not found`, name) } if err := blackmagic.AssignIfCompatible(dst, *(h.keyUsage)); err != nil { return fmt.Errorf(`failed to assign value for field %q: %w`, name, err) } return nil case ECDSAXKey: if h.x == nil { return fmt.Errorf(`field %q not found`, name) } if err := blackmagic.AssignIfCompatible(dst, h.x); err != nil { return fmt.Errorf(`failed to assign value for field %q: %w`, name, err) } return nil case X509CertChainKey: if h.x509CertChain == nil { return fmt.Errorf(`field %q not found`, name) } if err := blackmagic.AssignIfCompatible(dst, h.x509CertChain); err != nil { return fmt.Errorf(`failed to assign value for field %q: %w`, name, err) } return nil case X509CertThumbprintKey: if h.x509CertThumbprint == nil { return fmt.Errorf(`field %q not found`, name) } if err := blackmagic.AssignIfCompatible(dst, *(h.x509CertThumbprint)); err != nil { return fmt.Errorf(`failed to assign value for field %q: %w`, name, err) } return nil case X509CertThumbprintS256Key: if h.x509CertThumbprintS256 == nil { return fmt.Errorf(`field %q not found`, name) } if err := blackmagic.AssignIfCompatible(dst, *(h.x509CertThumbprintS256)); err != nil { return fmt.Errorf(`failed to assign value for field %q: %w`, name, err) } return nil case X509URLKey: if h.x509URL == nil { return fmt.Errorf(`field %q not found`, name) } if err := blackmagic.AssignIfCompatible(dst, *(h.x509URL)); err != nil { return fmt.Errorf(`failed to assign value for field %q: %w`, name, err) } return nil case ECDSAYKey: if h.y == nil { return fmt.Errorf(`field %q not found`, name) } if err := blackmagic.AssignIfCompatible(dst, h.y); err != nil { return fmt.Errorf(`failed to assign value for field %q: %w`, name, err) } return nil default: v, ok := h.privateParams[name] if !ok { return fmt.Errorf(`field %q not found`, name) } if err := blackmagic.AssignIfCompatible(dst, v); err != nil { return fmt.Errorf(`failed to assign value for field %q: %w`, name, err) } } return nil } func (h *ecdsaPrivateKey) Set(name string, value any) error { h.mu.Lock() defer h.mu.Unlock() return h.setNoLock(name, value) } func (h *ecdsaPrivateKey) setNoLock(name string, value any) error { switch name { case "kty": return nil case AlgorithmKey: switch v := value.(type) { case string, jwa.SignatureAlgorithm, jwa.KeyEncryptionAlgorithm, jwa.ContentEncryptionAlgorithm: tmp, err := jwa.KeyAlgorithmFrom(v) if err != nil { return fmt.Errorf(`invalid algorithm for %q key: %w`, AlgorithmKey, err) } h.algorithm = &tmp default: return fmt.Errorf(`invalid type for %q key: %T`, AlgorithmKey, value) } return nil case ECDSACrvKey: if v, ok := value.(jwa.EllipticCurveAlgorithm); ok { h.crv = &v return nil } return fmt.Errorf(`invalid value for %s key: %T`, ECDSACrvKey, value) case ECDSADKey: if v, ok := value.([]byte); ok { h.d = v return nil } return fmt.Errorf(`invalid value for %s key: %T`, ECDSADKey, value) case KeyIDKey: if v, ok := value.(string); ok { h.keyID = &v return nil } return fmt.Errorf(`invalid value for %s key: %T`, KeyIDKey, value) case KeyOpsKey: var acceptor KeyOperationList if err := acceptor.Accept(value); err != nil { return fmt.Errorf(`invalid value for %s key: %w`, KeyOpsKey, err) } h.keyOps = &acceptor return nil case KeyUsageKey: switch v := value.(type) { case KeyUsageType: switch v { case ForSignature, ForEncryption: tmp := v.String() h.keyUsage = &tmp default: return fmt.Errorf(`invalid key usage type %s`, v) } case string: h.keyUsage = &v default: return fmt.Errorf(`invalid key usage type %s`, v) } case ECDSAXKey: if v, ok := value.([]byte); ok { h.x = v return nil } return fmt.Errorf(`invalid value for %s key: %T`, ECDSAXKey, value) case X509CertChainKey: if v, ok := value.(*cert.Chain); ok { h.x509CertChain = v return nil } return fmt.Errorf(`invalid value for %s key: %T`, X509CertChainKey, value) case X509CertThumbprintKey: if v, ok := value.(string); ok { h.x509CertThumbprint = &v return nil } return fmt.Errorf(`invalid value for %s key: %T`, X509CertThumbprintKey, value) case X509CertThumbprintS256Key: if v, ok := value.(string); ok { h.x509CertThumbprintS256 = &v return nil } return fmt.Errorf(`invalid value for %s key: %T`, X509CertThumbprintS256Key, value) case X509URLKey: if v, ok := value.(string); ok { h.x509URL = &v return nil } return fmt.Errorf(`invalid value for %s key: %T`, X509URLKey, value) case ECDSAYKey: if v, ok := value.([]byte); ok { h.y = v return nil } return fmt.Errorf(`invalid value for %s key: %T`, ECDSAYKey, value) default: if h.privateParams == nil { h.privateParams = map[string]any{} } h.privateParams[name] = value } return nil } func (k *ecdsaPrivateKey) Remove(key string) error { k.mu.Lock() defer k.mu.Unlock() switch key { case AlgorithmKey: k.algorithm = nil case ECDSACrvKey: k.crv = nil case ECDSADKey: k.d = nil case KeyIDKey: k.keyID = nil case KeyOpsKey: k.keyOps = nil case KeyUsageKey: k.keyUsage = nil case ECDSAXKey: k.x = nil case X509CertChainKey: k.x509CertChain = nil case X509CertThumbprintKey: k.x509CertThumbprint = nil case X509CertThumbprintS256Key: k.x509CertThumbprintS256 = nil case X509URLKey: k.x509URL = nil case ECDSAYKey: k.y = nil default: delete(k.privateParams, key) } return nil } func (k *ecdsaPrivateKey) Clone() (Key, error) { key, err := cloneKey(k) if err != nil { return nil, fmt.Errorf(`ecdsaPrivateKey.Clone: %w`, err) } return key, nil } func (k *ecdsaPrivateKey) DecodeCtx() json.DecodeCtx { k.mu.RLock() defer k.mu.RUnlock() return k.dc } func (k *ecdsaPrivateKey) SetDecodeCtx(dc json.DecodeCtx) { k.mu.Lock() defer k.mu.Unlock() k.dc = dc } func (h *ecdsaPrivateKey) UnmarshalJSON(buf []byte) error { h.mu.Lock() defer h.mu.Unlock() h.algorithm = nil h.crv = nil h.d = nil h.keyID = nil h.keyOps = nil h.keyUsage = nil h.x = nil h.x509CertChain = nil h.x509CertThumbprint = nil h.x509CertThumbprintS256 = nil h.x509URL = nil h.y = nil dec := json.NewDecoder(bytes.NewReader(buf)) LOOP: for { tok, err := dec.Token() if err != nil { return fmt.Errorf(`error reading token: %w`, err) } switch tok := tok.(type) { case json.Delim: // Assuming we're doing everything correctly, we should ONLY // get either tokens.OpenCurlyBracket or tokens.CloseCurlyBracket here. if tok == tokens.CloseCurlyBracket { // End of object break LOOP } else if tok != tokens.OpenCurlyBracket { return fmt.Errorf(`expected '%c' but got '%c'`, tokens.OpenCurlyBracket, tok) } case string: // Objects can only have string keys switch tok { case KeyTypeKey: val, err := json.ReadNextStringToken(dec) if err != nil { return fmt.Errorf(`error reading token: %w`, err) } if val != jwa.EC().String() { return fmt.Errorf(`invalid kty value for RSAPublicKey (%s)`, val) } case AlgorithmKey: var s string if err := dec.Decode(&s); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, AlgorithmKey, err) } alg, err := jwa.KeyAlgorithmFrom(s) if err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, AlgorithmKey, err) } h.algorithm = &alg case ECDSACrvKey: var decoded jwa.EllipticCurveAlgorithm if err := dec.Decode(&decoded); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, ECDSACrvKey, err) } h.crv = &decoded case ECDSADKey: if err := json.AssignNextBytesToken(&h.d, dec); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, ECDSADKey, err) } case KeyIDKey: if err := json.AssignNextStringToken(&h.keyID, dec); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, KeyIDKey, err) } case KeyOpsKey: var decoded KeyOperationList if err := dec.Decode(&decoded); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, KeyOpsKey, err) } h.keyOps = &decoded case KeyUsageKey: if err := json.AssignNextStringToken(&h.keyUsage, dec); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, KeyUsageKey, err) } case ECDSAXKey: if err := json.AssignNextBytesToken(&h.x, dec); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, ECDSAXKey, err) } case X509CertChainKey: var decoded cert.Chain if err := dec.Decode(&decoded); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, X509CertChainKey, err) } h.x509CertChain = &decoded case X509CertThumbprintKey: if err := json.AssignNextStringToken(&h.x509CertThumbprint, dec); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, X509CertThumbprintKey, err) } case X509CertThumbprintS256Key: if err := json.AssignNextStringToken(&h.x509CertThumbprintS256, dec); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, X509CertThumbprintS256Key, err) } case X509URLKey: if err := json.AssignNextStringToken(&h.x509URL, dec); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, X509URLKey, err) } case ECDSAYKey: if err := json.AssignNextBytesToken(&h.y, dec); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, ECDSAYKey, err) } default: if dc := h.dc; dc != nil { if localReg := dc.Registry(); localReg != nil { decoded, err := localReg.Decode(dec, tok) if err == nil { h.setNoLock(tok, decoded) continue } } } decoded, err := registry.Decode(dec, tok) if err == nil { h.setNoLock(tok, decoded) continue } return fmt.Errorf(`could not decode field %s: %w`, tok, err) } default: return fmt.Errorf(`invalid token %T`, tok) } } if h.crv == nil { return fmt.Errorf(`required field crv is missing`) } if h.d == nil { return fmt.Errorf(`required field d is missing`) } if h.x == nil { return fmt.Errorf(`required field x is missing`) } if h.y == nil { return fmt.Errorf(`required field y is missing`) } return nil } func (h ecdsaPrivateKey) MarshalJSON() ([]byte, error) { data := make(map[string]any) fields := make([]string, 0, 12) data[KeyTypeKey] = jwa.EC() fields = append(fields, KeyTypeKey) if h.algorithm != nil { data[AlgorithmKey] = *(h.algorithm) fields = append(fields, AlgorithmKey) } if h.crv != nil { data[ECDSACrvKey] = *(h.crv) fields = append(fields, ECDSACrvKey) } if h.d != nil { data[ECDSADKey] = h.d fields = append(fields, ECDSADKey) } if h.keyID != nil { data[KeyIDKey] = *(h.keyID) fields = append(fields, KeyIDKey) } if h.keyOps != nil { data[KeyOpsKey] = *(h.keyOps) fields = append(fields, KeyOpsKey) } if h.keyUsage != nil { data[KeyUsageKey] = *(h.keyUsage) fields = append(fields, KeyUsageKey) } if h.x != nil { data[ECDSAXKey] = h.x fields = append(fields, ECDSAXKey) } if h.x509CertChain != nil { data[X509CertChainKey] = h.x509CertChain fields = append(fields, X509CertChainKey) } if h.x509CertThumbprint != nil { data[X509CertThumbprintKey] = *(h.x509CertThumbprint) fields = append(fields, X509CertThumbprintKey) } if h.x509CertThumbprintS256 != nil { data[X509CertThumbprintS256Key] = *(h.x509CertThumbprintS256) fields = append(fields, X509CertThumbprintS256Key) } if h.x509URL != nil { data[X509URLKey] = *(h.x509URL) fields = append(fields, X509URLKey) } if h.y != nil { data[ECDSAYKey] = h.y fields = append(fields, ECDSAYKey) } for k, v := range h.privateParams { data[k] = v fields = append(fields, k) } sort.Strings(fields) buf := pool.BytesBuffer().Get() defer pool.BytesBuffer().Put(buf) buf.WriteByte(tokens.OpenCurlyBracket) enc := json.NewEncoder(buf) for i, f := range fields { if i > 0 { buf.WriteRune(tokens.Comma) } buf.WriteRune(tokens.DoubleQuote) buf.WriteString(f) buf.WriteString(`":`) v := data[f] switch v := v.(type) { case []byte: buf.WriteRune(tokens.DoubleQuote) buf.WriteString(base64.EncodeToString(v)) buf.WriteRune(tokens.DoubleQuote) default: if err := enc.Encode(v); err != nil { return nil, fmt.Errorf(`failed to encode value for field %s: %w`, f, err) } buf.Truncate(buf.Len() - 1) } } buf.WriteByte(tokens.CloseCurlyBracket) ret := make([]byte, buf.Len()) copy(ret, buf.Bytes()) return ret, nil } func (h *ecdsaPrivateKey) Keys() []string { h.mu.RLock() defer h.mu.RUnlock() keys := make([]string, 0, 12+len(h.privateParams)) keys = append(keys, KeyTypeKey) if h.algorithm != nil { keys = append(keys, AlgorithmKey) } if h.crv != nil { keys = append(keys, ECDSACrvKey) } if h.d != nil { keys = append(keys, ECDSADKey) } if h.keyID != nil { keys = append(keys, KeyIDKey) } if h.keyOps != nil { keys = append(keys, KeyOpsKey) } if h.keyUsage != nil { keys = append(keys, KeyUsageKey) } if h.x != nil { keys = append(keys, ECDSAXKey) } if h.x509CertChain != nil { keys = append(keys, X509CertChainKey) } if h.x509CertThumbprint != nil { keys = append(keys, X509CertThumbprintKey) } if h.x509CertThumbprintS256 != nil { keys = append(keys, X509CertThumbprintS256Key) } if h.x509URL != nil { keys = append(keys, X509URLKey) } if h.y != nil { keys = append(keys, ECDSAYKey) } for k := range h.privateParams { keys = append(keys, k) } return keys } var ecdsaStandardFields KeyFilter func init() { ecdsaStandardFields = NewFieldNameFilter(KeyTypeKey, KeyUsageKey, KeyOpsKey, AlgorithmKey, KeyIDKey, X509URLKey, X509CertChainKey, X509CertThumbprintKey, X509CertThumbprintS256Key, ECDSACrvKey, ECDSAXKey, ECDSAYKey, ECDSADKey) } // ECDSAStandardFieldsFilter returns a KeyFilter that filters out standard ECDSA fields. func ECDSAStandardFieldsFilter() KeyFilter { return ecdsaStandardFields } golang-github-lestrrat-go-jwx-3.0.13/jwk/errors.go000066400000000000000000000030051515060566400220400ustar00rootroot00000000000000package jwk import ( "errors" "fmt" ) var cpe = &continueError{} // ContinueError returns an opaque error that can be returned // when a `KeyParser`, `KeyImporter`, or `KeyExporter` cannot handle the given payload, // but would like the process to continue with the next handler. func ContinueError() error { return cpe } type continueError struct{} func (e *continueError) Error() string { return "continue parsing" } type importError struct { error } func (e importError) Unwrap() error { return e.error } func (importError) Is(err error) bool { _, ok := err.(importError) return ok } func importerr(f string, args ...any) error { return importError{fmt.Errorf(`jwk.Import: `+f, args...)} } var errDefaultImportError = importError{errors.New(`import error`)} func ImportError() error { return errDefaultImportError } type parseError struct { error } func (e parseError) Unwrap() error { return e.error } func (parseError) Is(err error) bool { _, ok := err.(parseError) return ok } func bparseerr(prefix string, f string, args ...any) error { return parseError{fmt.Errorf(prefix+`: `+f, args...)} } func parseerr(f string, args ...any) error { return bparseerr(`jwk.Parse`, f, args...) } func rparseerr(f string, args ...any) error { return bparseerr(`jwk.ParseReader`, f, args...) } func sparseerr(f string, args ...any) error { return bparseerr(`jwk.ParseString`, f, args...) } var errDefaultParseError = parseError{errors.New(`parse error`)} func ParseError() error { return errDefaultParseError } golang-github-lestrrat-go-jwx-3.0.13/jwk/es256k.go000066400000000000000000000004011515060566400215400ustar00rootroot00000000000000//go:build jwx_es256k package jwk import ( "github.com/decred/dcrd/dcrec/secp256k1/v4" "github.com/lestrrat-go/jwx/v3/jwa" ourecdsa "github.com/lestrrat-go/jwx/v3/jwk/ecdsa" ) func init() { ourecdsa.RegisterCurve(jwa.Secp256k1(), secp256k1.S256()) } golang-github-lestrrat-go-jwx-3.0.13/jwk/es256k_test.go000066400000000000000000000027711515060566400226130ustar00rootroot00000000000000//go:build jwx_es256k package jwk_test import ( "crypto/ecdsa" "encoding/base64" "encoding/json" "math/big" "testing" "github.com/decred/dcrd/dcrec/secp256k1/v4" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/lestrrat-go/jwx/v3/jwk" ourecdsa "github.com/lestrrat-go/jwx/v3/jwk/ecdsa" "github.com/stretchr/testify/require" ) func TestES256K(t *testing.T) { require.True(t, ourecdsa.IsCurveAvailable(jwa.Secp256k1()), `jwa.Secp256k1 should be available`) } func BenchmarkKeyInstantiation(b *testing.B) { const xb64 = "YAXIamcY9mIhcTp3BzxBKRzDq7_NA6pJVemytQ2_f5s" const yb64 = "ZnLa0NRq3mHjgveYiKc-p4mdlBm-zx1snsIIfBGI-hg" x, err := base64.RawURLEncoding.DecodeString(xb64) require.NoError(b, err, `DecodeBase64 should succeed`) y, err := base64.RawURLEncoding.DecodeString(yb64) require.NoError(b, err, `DecodeBase64 should succeed`) b.Run("Use json.Marshal/json.Unmarshal", func(b *testing.B) { for i := 0; i < b.N; i++ { serialized, err := json.Marshal(map[string]any{ "kty": "EC", "crv": "secp256k1", "x": xb64, "y": yb64, }) if err != nil { panic(err) } key, err := jwk.Parse(serialized) if err != nil { panic(err) } _ = key } }) b.Run("Use jwk.Import", func(b *testing.B) { for i := 0; i < b.N; i++ { var raw ecdsa.PublicKey raw.Curve = secp256k1.S256() raw.X = &big.Int{} raw.Y = &big.Int{} raw.X.SetBytes(x) raw.Y.SetBytes(y) key, err := jwk.Import(&raw) if err != nil { panic(err) } _ = key } }) } golang-github-lestrrat-go-jwx-3.0.13/jwk/fetch.go000066400000000000000000000101451515060566400216200ustar00rootroot00000000000000package jwk import ( "context" "fmt" "io" "net/http" ) // Fetcher is an interface that represents an object that fetches a JWKS. // Currently this is only used in the `jws.WithVerifyAuto` option. // // Particularly, do not confuse this as the backend to `jwk.Fetch()` function. // If you need to control how `jwk.Fetch()` implements HTTP requests look into // providing a custom `http.Client` object via `jwk.WithHTTPClient` option type Fetcher interface { Fetch(context.Context, string, ...FetchOption) (Set, error) } // FetchFunc describes a type of Fetcher that is represented as a function. // // You can use this to wrap functions (e.g. `jwk.Fetch“) as a Fetcher object. type FetchFunc func(context.Context, string, ...FetchOption) (Set, error) func (ff FetchFunc) Fetch(ctx context.Context, u string, options ...FetchOption) (Set, error) { return ff(ctx, u, options...) } // CachedFetcher wraps `jwk.Cache` so that it can be used as a `jwk.Fetcher`. // // One notable diffence from a general use fetcher is that `jwk.CachedFetcher` // can only be used with JWKS URLs that have been registered with the cache. // Please read the documentation fo `(jwk.CachedFetcher).Fetch` for more details. // // This object is intended to be used with `jws.WithVerifyAuto` option, specifically // for a scenario where there is a very small number of JWKS URLs that are trusted // and used to verify JWS messages. It is NOT meant to be used as a general purpose // caching fetcher object. type CachedFetcher struct { cache *Cache } // NewCachedFetcher creates a new `jwk.CachedFetcher` object. func NewCachedFetcher(cache *Cache) *CachedFetcher { return &CachedFetcher{cache} } // Fetch fetches a JWKS from the cache. If the JWKS URL has not been registered with // the cache, an error is returned. func (f *CachedFetcher) Fetch(ctx context.Context, u string, _ ...FetchOption) (Set, error) { if !f.cache.IsRegistered(ctx, u) { return nil, fmt.Errorf(`jwk.CachedFetcher: url %q has not been registered`, u) } return f.cache.Lookup(ctx, u) } // Fetch fetches a JWK resource specified by a URL. The url must be // pointing to a resource that is supported by `net/http`. // // This function is just a wrapper around `net/http` and `jwk.Parse`. // There is nothing special here, so you are safe to use your own // mechanism to fetch the JWKS. // // If you are using the same `jwk.Set` for long periods of time during // the lifecycle of your program, and would like to periodically refresh the // contents of the object with the data at the remote resource, // consider using `jwk.Cache`, which automatically refreshes // jwk.Set objects asynchronously. func Fetch(ctx context.Context, u string, options ...FetchOption) (Set, error) { var parseOptions []ParseOption //nolint:revive // I want to keep the type of `wl` as `Whitelist` instead of `InsecureWhitelist` var wl Whitelist = InsecureWhitelist{} var client HTTPClient = http.DefaultClient for _, option := range options { if parseOpt, ok := option.(ParseOption); ok { parseOptions = append(parseOptions, parseOpt) continue } switch option.Ident() { case identHTTPClient{}: if err := option.Value(&client); err != nil { return nil, fmt.Errorf(`failed to retrieve HTTPClient option value: %w`, err) } case identFetchWhitelist{}: if err := option.Value(&wl); err != nil { return nil, fmt.Errorf(`failed to retrieve fetch whitelist option value: %w`, err) } } } if !wl.IsAllowed(u) { return nil, fmt.Errorf(`jwk.Fetch: url %q has been rejected by whitelist`, u) } req, err := http.NewRequestWithContext(ctx, http.MethodGet, u, nil) if err != nil { return nil, fmt.Errorf(`jwk.Fetch: failed to create new request: %w`, err) } res, err := client.Do(req) if err != nil { return nil, fmt.Errorf(`jwk.Fetch: request failed: %w`, err) } defer res.Body.Close() if res.StatusCode != http.StatusOK { return nil, fmt.Errorf(`jwk.Fetch: request returned status %d, expected 200`, res.StatusCode) } buf, err := io.ReadAll(res.Body) if err != nil { return nil, fmt.Errorf(`jwk.Fetch: failed to read response body for %q: %w`, u, err) } return Parse(buf, parseOptions...) } golang-github-lestrrat-go-jwx-3.0.13/jwk/filter.go000066400000000000000000000021341515060566400220130ustar00rootroot00000000000000package jwk import ( "github.com/lestrrat-go/jwx/v3/transform" ) // KeyFilter is an interface that allows users to filter JWK key fields. // It provides two methods: Filter and Reject; Filter returns a new key with only // the fields that match the filter criteria, while Reject returns a new key with // only the fields that DO NOT match the filter. // // EXPERIMENTAL: This API is experimental and its interface and behavior is // subject to change in future releases. This API is not subject to semver // compatibility guarantees. type KeyFilter interface { Filter(key Key) (Key, error) Reject(key Key) (Key, error) } // NewFieldNameFilter creates a new FieldNameFilter with the specified field names. // // Note that because some JWK fields are associated with the type instead of // stored as data, this filter will not be able to remove them. An example would // be the `kty` field: it's associated with the underlying JWK key type, and will // always be present even if you attempt to remove it. func NewFieldNameFilter(names ...string) KeyFilter { return transform.NewNameBasedFilter[Key](names...) } golang-github-lestrrat-go-jwx-3.0.13/jwk/filter_test.go000066400000000000000000000266521515060566400230650ustar00rootroot00000000000000package jwk_test import ( "testing" "github.com/lestrrat-go/jwx/v3/jwk" "github.com/stretchr/testify/require" ) func TestFieldNameFilter(t *testing.T) { t.Run("NewFieldNameFilter", func(t *testing.T) { fn := jwk.NewFieldNameFilter("a", "b", "c") require.NotNil(t, fn, "NewFieldNameFilter should return a non-nil value") }) t.Run("Filter", func(t *testing.T) { // Create a FieldNameFilter with field names a, b, c fn := jwk.NewFieldNameFilter("a", "b", "c") // Create a key with fields kty, a, b, c, d, e const src = `{ "kty": "oct", "k": "AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow", "a": "value_a", "b": "value_b", "c": "value_c", "d": "value_d", "e": "value_e" }` rawKey, err := jwk.ParseKey([]byte(src)) require.NoError(t, err, "jwk.ParseKey should succeed") // Filter should return a key with only fields kty, a, b, c filtered, err := fn.Filter(rawKey) require.NoError(t, err, "fn.Filter should succeed") // Debug: print keys for inspection t.Logf("Original keys: %v", rawKey.Keys()) t.Logf("Filtered keys: %v", filtered.Keys()) // Check that filtered key contains kty, a, b, c by examining Keys() filteredKeys := filtered.Keys() require.Contains(t, filteredKeys, jwk.KeyTypeKey, "filtered key must have kty field") require.Contains(t, filteredKeys, "a", "filtered key should have field 'a'") require.Contains(t, filteredKeys, "b", "filtered key should have field 'b'") require.Contains(t, filteredKeys, "c", "filtered key should have field 'c'") require.NotContains(t, filteredKeys, "d", "filtered key should not have field 'd'") require.NotContains(t, filteredKeys, "e", "filtered key should not have field 'e'") require.True(t, filtered.Has("a"), "filtered key should have field 'a'") require.True(t, filtered.Has("b"), "filtered key should have field 'b'") require.True(t, filtered.Has("c"), "filtered key should have field 'c'") require.False(t, filtered.Has("d"), "filtered key should not have field 'd'") require.False(t, filtered.Has("e"), "filtered key should not have field 'e'") // Verify values are preserved var val string require.NoError(t, filtered.Get("a", &val), "filtered.Get should succeed") require.Equal(t, "value_a", val, "value for field 'a' should be preserved") }) t.Run("Reject", func(t *testing.T) { // Create a FieldNameFilter with field names a, b, c fn := jwk.NewFieldNameFilter("a", "b", "c") // Create a key with fields kty, a, b, c, d, e const src = `{ "kty": "oct", "k": "AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow", "a": "value_a", "b": "value_b", "c": "value_c", "d": "value_d", "e": "value_e" }` rawKey, err := jwk.ParseKey([]byte(src)) require.NoError(t, err, "jwk.ParseKey should succeed") // Reject should return a key with only fields kty, d, e rejected, err := fn.Reject(rawKey) require.NoError(t, err, "fn.Reject should succeed") // Debug: print keys for inspection t.Logf("Original keys: %v", rawKey.Keys()) t.Logf("Rejected keys: %v", rejected.Keys()) // Check that rejected key contains only kty, d, e by examining Keys() rejectedKeys := rejected.Keys() require.Contains(t, rejectedKeys, jwk.KeyTypeKey, "rejected key must have kty field") require.NotContains(t, rejectedKeys, "a", "rejected key should not have field 'a'") require.NotContains(t, rejectedKeys, "b", "rejected key should not have field 'b'") require.NotContains(t, rejectedKeys, "c", "rejected key should not have field 'c'") require.Contains(t, rejectedKeys, "d", "rejected key should have field 'd'") require.Contains(t, rejectedKeys, "e", "rejected key should have field 'e'") require.False(t, rejected.Has("a"), "rejected key should not have field 'a'") require.False(t, rejected.Has("b"), "rejected key should not have field 'b'") require.False(t, rejected.Has("c"), "rejected key should not have field 'c'") require.True(t, rejected.Has("d"), "rejected key should have field 'd'") require.True(t, rejected.Has("e"), "rejected key should have field 'e'") // Verify values are preserved var val string require.NoError(t, rejected.Get("d", &val), "rejected.Get should succeed") require.Equal(t, "value_d", val, "value for field 'd' should be preserved") }) t.Run("Error handling with Clone", func(t *testing.T) { // Since it's hard to create a failing Clone scenario, // we'll just verify Filter and Reject don't panic with a simple key const src = `{ "kty": "oct", "k": "AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ" }` rawKey, err := jwk.ParseKey([]byte(src)) require.NoError(t, err, "jwk.ParseKey should succeed") fn := jwk.NewFieldNameFilter("a", "b", "c") _, err = fn.Filter(rawKey) require.NoError(t, err, "fn.Filter should succeed even for a basic key") _, err = fn.Reject(rawKey) require.NoError(t, err, "fn.Reject should succeed even for a basic key") }) t.Run("Empty FieldNameFilter", func(t *testing.T) { // Create an empty FieldNameFilter fn := jwk.NewFieldNameFilter() // Create a key with fields kty, a, b, c const src = `{ "kty": "oct", "k": "AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow", "a": "value_a", "b": "value_b", "c": "value_c" }` rawKey, err := jwk.ParseKey([]byte(src)) require.NoError(t, err, "jwk.ParseKey should succeed") // Filter with empty FieldNameFilter should result in a key with only kty filtered, err := fn.Filter(rawKey) require.NoError(t, err, "fn.Filter should succeed") keys := filtered.Keys() require.Len(t, keys, 1, "filtered key should have only one field (kty)") require.Equal(t, jwk.KeyTypeKey, keys[0], "the only field should be kty") // Reject with empty FieldNameFilter should result in a copy of the original key rejected, err := fn.Reject(rawKey) require.NoError(t, err, "fn.Reject should succeed") // Check that rejected key has the same keys as original originalKeys := rawKey.Keys() rejectedKeys := rejected.Keys() require.ElementsMatch(t, originalKeys, rejectedKeys, "rejected key should have the same fields as the original") }) t.Run("Concurrency safety", func(t *testing.T) { // This is more of a logical test than an actual concurrency test // but it ensures the mutex is being used correctly in the Filter/Reject methods fn := jwk.NewFieldNameFilter("a", "b", "c") const src = `{ "kty": "oct", "k": "AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow", "a": "value_a", "d": "value_d" }` rawKey, err := jwk.ParseKey([]byte(src)) require.NoError(t, err, "jwk.ParseKey should succeed") // Should not deadlock or have race conditions filtered, err := fn.Filter(rawKey) require.NoError(t, err, "fn.Filter should succeed") require.True(t, filtered.Has("a"), "filtered key should have field 'a'") require.False(t, filtered.Has("d"), "filtered key should not have field 'd'") rejected, err := fn.Reject(rawKey) require.NoError(t, err, "fn.Reject should succeed") require.False(t, rejected.Has("a"), "rejected key should not have field 'a'") require.True(t, rejected.Has("d"), "rejected key should have field 'd'") }) } func TestStandardFieldsFilter(t *testing.T) { t.Run("Filter standard fields", func(t *testing.T) { // Create a key with standard and custom fields const src = `{ "kty": "EC", "crv": "P-256", "x": "MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4", "y": "4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM", "kid": "kid-value", "use": "sig", "key_ops": ["sign", "verify"], "alg": "ES256", "x5u": "https://example.com/x509", "x5c": ["cert1", "cert2"], "x5t": "thumbprint", "x5t#S256": "thumbprint-s256", "custom1": "value1", "custom2": "value2" }` rawKey, err := jwk.ParseKey([]byte(src)) require.NoError(t, err, "jwk.ParseKey should succeed") require.NotNil(t, rawKey, "key should not be nil") stdFilter := jwk.ECDSAStandardFieldsFilter() t.Run("Filter standard fields", func(t *testing.T) { // Filter should return a key with only standard fields filtered, err := stdFilter.Filter(rawKey) require.NoError(t, err, "filter.Filter should succeed") // Debug: print keys for inspection t.Logf("Original keys: %v", rawKey.Keys()) t.Logf("Filtered keys: %v", filtered.Keys()) // Verify standard fields are present by examining Keys() filteredKeys := filtered.Keys() require.Contains(t, filteredKeys, jwk.KeyTypeKey, "filtered key should have kty field") require.Contains(t, filteredKeys, jwk.KeyIDKey, "filtered key should have kid field") require.Contains(t, filteredKeys, jwk.KeyUsageKey, "filtered key should have use field") require.Contains(t, filteredKeys, jwk.KeyOpsKey, "filtered key should have key_ops field") require.Contains(t, filteredKeys, jwk.AlgorithmKey, "filtered key should have alg field") require.Contains(t, filteredKeys, jwk.X509URLKey, "filtered key should have x5u field") require.Contains(t, filteredKeys, jwk.X509CertChainKey, "filtered key should have x5c field") require.Contains(t, filteredKeys, jwk.X509CertThumbprintKey, "filtered key should have x5t field") require.Contains(t, filteredKeys, jwk.X509CertThumbprintS256Key, "filtered key should have x5t#S256 field") // Verify custom fields are not present require.NotContains(t, filteredKeys, "custom1", "filtered key should not have custom1 field") require.NotContains(t, filteredKeys, "custom2", "filtered key should not have custom2 field") // Verify values are preserved var kid string require.NoError(t, filtered.Get(jwk.KeyIDKey, &kid), "filtered.Get should succeed") require.Equal(t, "kid-value", kid, "value for kid field should be preserved") }) t.Run("Reject standard fields", func(t *testing.T) { // Reject should return a key with only custom fields rejected, err := stdFilter.Reject(rawKey) require.NoError(t, err, "filter.Reject should succeed") // Debug: print keys for inspection t.Logf("Original keys: %v", rawKey.Keys()) t.Logf("Rejected keys: %v", rejected.Keys()) // Verify standard fields are not present (except kty which cannot be removed) rejectedKeys := rejected.Keys() require.Contains(t, rejectedKeys, jwk.KeyTypeKey, "rejected key must have kty field") require.NotContains(t, rejectedKeys, jwk.KeyIDKey, "rejected key should not have kid field") require.NotContains(t, rejectedKeys, jwk.KeyUsageKey, "rejected key should not have use field") require.NotContains(t, rejectedKeys, jwk.KeyOpsKey, "rejected key should not have key_ops field") require.NotContains(t, rejectedKeys, jwk.AlgorithmKey, "rejected key should not have alg field") require.NotContains(t, rejectedKeys, jwk.X509URLKey, "rejected key should not have x5u field") require.NotContains(t, rejectedKeys, jwk.X509CertChainKey, "rejected key should not have x5c field") require.NotContains(t, rejectedKeys, jwk.X509CertThumbprintKey, "rejected key should not have x5t field") require.NotContains(t, rejectedKeys, jwk.X509CertThumbprintS256Key, "rejected key should not have x5t#S256 field") // Verify custom fields are present require.True(t, rejected.Has("custom1"), "rejected key should have custom1 field") require.True(t, rejected.Has("custom2"), "rejected key should have custom2 field") // Verify values are preserved var customValue string require.NoError(t, rejected.Get("custom1", &customValue), "rejected.Get should succeed") require.Equal(t, "value1", customValue, "value for custom1 field should be preserved") }) }) } golang-github-lestrrat-go-jwx-3.0.13/jwk/headers_test.go000066400000000000000000000057301515060566400232050ustar00rootroot00000000000000package jwk_test import ( "testing" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/lestrrat-go/jwx/v3/jwk" "github.com/stretchr/testify/require" ) func TestHeader(t *testing.T) { t.Parallel() t.Run("Roundtrip", func(t *testing.T) { t.Parallel() values := map[string]any{ jwk.KeyIDKey: "helloworld01", jwk.KeyOpsKey: jwk.KeyOperationList{jwk.KeyOpSign}, jwk.KeyUsageKey: "sig", jwk.X509CertThumbprintKey: "thumbprint", jwk.X509CertThumbprintS256Key: "thumbprint256", jwk.X509URLKey: "cert1", "private": "boofoo", } h, err := jwk.Import([]byte("dummy")) require.NoError(t, err, `jwk.New should succeed`) for k, v := range values { require.NoError(t, h.Set(k, v), "Set works for '%s'", k) var got any require.NoError(t, h.Get(k, &got), "Get works for '%s'", k) require.Equal(t, v, got, "values match '%s'", k) require.NoError(t, h.Set(k, v), "Set works for '%s'", k) } t.Run("Private params", func(t *testing.T) { t.Parallel() var v string require.NoError(t, h.Get(`private`, &v), `h.Get should succeed`) require.Equal(t, v, "boofoo", "value for 'private' should match") }) }) t.Run("RoundtripError", func(t *testing.T) { t.Parallel() type dummyStruct struct { dummy1 int dummy2 float64 } dummy := &dummyStruct{1, 3.4} values := map[string]any{ jwk.AlgorithmKey: dummy, jwk.KeyIDKey: dummy, jwk.KeyUsageKey: dummy, jwk.KeyOpsKey: dummy, jwk.X509CertChainKey: dummy, jwk.X509CertThumbprintKey: dummy, jwk.X509CertThumbprintS256Key: dummy, jwk.X509URLKey: dummy, } h, err := jwk.Import([]byte("dummy")) require.NoError(t, err, `jwk.New should succeed`) for k, v := range values { err := h.Set(k, v) if err == nil { t.Fatalf("Setting %s value should have failed", k) } } require.NoError(t, h.Set("Default", dummy), `Setting "Default" should succeed`) alg, ok := h.Algorithm() require.True(t, !ok, `Algorithm should not be set`) require.Nil(t, alg, "Algorithm should be nil") kid, ok := h.KeyID() require.False(t, ok, `KeyID should not be set`) require.Empty(t, kid, "KeyID should be empty") use, ok := h.KeyUsage() require.False(t, ok, `KeyUsage should not be set`) require.Empty(t, use, "KeyUsage should be empty") ops, ok := h.KeyOps() require.False(t, ok, `KeyOps should not be set`) require.Nil(t, ops, "KeyOps should be nil") }) t.Run("Algorithm", func(t *testing.T) { t.Parallel() h, err := jwk.Import([]byte("dummy")) require.NoError(t, err, `jwk.New should succeed`) for _, value := range []any{jwa.RS256(), jwa.RSA1_5()} { require.NoError(t, h.Set(jwk.AlgorithmKey, value), "Set for alg should succeed") var got jwa.KeyAlgorithm require.NoError(t, h.Get("alg", &got), "Get for alg should succeed") require.Equal(t, value, got, "values match") } }) } golang-github-lestrrat-go-jwx-3.0.13/jwk/interface.go000066400000000000000000000120731515060566400224710ustar00rootroot00000000000000package jwk import ( "sync" "github.com/lestrrat-go/jwx/v3/internal/json" ) // AsymmetricKey describes a Key that represents a key in an asymmetric key pair, // which in turn can be either a private or a public key. This interface // allows those keys to be queried if they are one or the other. type AsymmetricKey interface { IsPrivate() bool } // KeyUsageType is used to denote what this key should be used for type KeyUsageType string const ( // ForSignature is the value used in the headers to indicate that // this key should be used for signatures ForSignature KeyUsageType = "sig" // ForEncryption is the value used in the headers to indicate that // this key should be used for encrypting ForEncryption KeyUsageType = "enc" ) type KeyOperation string type KeyOperationList []KeyOperation const ( KeyOpSign KeyOperation = "sign" // (compute digital signature or MAC) KeyOpVerify KeyOperation = "verify" // (verify digital signature or MAC) KeyOpEncrypt KeyOperation = "encrypt" // (encrypt content) KeyOpDecrypt KeyOperation = "decrypt" // (decrypt content and validate decryption, if applicable) KeyOpWrapKey KeyOperation = "wrapKey" // (encrypt key) KeyOpUnwrapKey KeyOperation = "unwrapKey" // (decrypt key and validate decryption, if applicable) KeyOpDeriveKey KeyOperation = "deriveKey" // (derive key) KeyOpDeriveBits KeyOperation = "deriveBits" // (derive bits not to be used as a key) ) // Set represents JWKS object, a collection of jwk.Key objects. // // Sets can be safely converted to and from JSON using the standard // `"encoding/json".Marshal` and `"encoding/json".Unmarshal`. However, // if you do not know if the payload contains a single JWK or a JWK set, // consider using `jwk.Parse()` to always get a `jwk.Set` out of it. // // Since v1.2.12, JWK sets with private parameters can be parsed as well. // Such private parameters can be accessed via the `Field()` method. // If a resource contains a single JWK instead of a JWK set, private parameters // are stored in _both_ the resulting `jwk.Set` object and the `jwk.Key` object . // //nolint:interfacebloat type Set interface { // AddKey adds the specified key. If the key already exists in the set, // an error is returned. AddKey(Key) error // Clear resets the list of keys associated with this set, emptying the // internal list of `jwk.Key`s, as well as clearing any other non-key // fields Clear() error // Get returns the key at index `idx`. If the index is out of range, // then the second return value is false. Key(int) (Key, bool) // Get returns the value of a private field in the key set. // // For the purposes of a key set, any field other than the "keys" field is // considered to be a private field. In other words, you cannot use this // method to directly access the list of keys in the set Get(string, any) error // Set sets the value of a single field. // // This method, which takes an `any`, exists because // these objects can contain extra _arbitrary_ fields that users can // specify, and there is no way of knowing what type they could be. Set(string, any) error // Remove removes the specified non-key field from the set. // Keys may not be removed using this method. See RemoveKey for // removing keys. Remove(string) error // Index returns the index where the given key exists, -1 otherwise Index(Key) int // Len returns the number of keys in the set Len() int // LookupKeyID returns the first key matching the given key id. // // The second return value is false if there are no keys matching the key id. // The set *may* contain multiple keys with the same key id. If you // need all of them, Len() and Key(int) // // This method is meant to be used to lookup a key with a unique ID. // Bacauseof this, you cannot use this method to lookup keys with an empty key ID // (i.e. `kid` is not specified, or is an empty string). LookupKeyID(string) (Key, bool) // RemoveKey removes the key from the set. // RemoveKey returns an error when the specified key does not exist // in set. RemoveKey(Key) error // Keys returns the list of keys present in the Set, except for `keys`. // e.g. if you had `{"keys": ["a", "b"], "c": .., "d": ...}`, this method would // return `["c", "d"]`. Note that the order of the keys is not guaranteed. // // TODO: name is confusing between this and Key() Keys() []string // Clone create a new set with identical keys. Keys themselves are not cloned. Clone() (Set, error) } type set struct { keys []Key mu sync.RWMutex dc DecodeCtx privateParams map[string]any } type PublicKeyer interface { // PublicKey creates the corresponding PublicKey type for this object. // All fields are copied onto the new public key, except for those that are not allowed. // Returned value must not be the receiver itself. PublicKey() (Key, error) } type DecodeCtx interface { json.DecodeCtx IgnoreParseError() bool } type KeyWithDecodeCtx interface { SetDecodeCtx(DecodeCtx) DecodeCtx() DecodeCtx } // Used internally: It's used to lock a key type rlocker interface { rlock() runlock() } golang-github-lestrrat-go-jwx-3.0.13/jwk/interface_gen.go000066400000000000000000000103741515060566400233240ustar00rootroot00000000000000// Code generated by tools/cmd/genjwk/main.go. DO NOT EDIT. package jwk import ( "crypto" "github.com/lestrrat-go/jwx/v3/cert" "github.com/lestrrat-go/jwx/v3/jwa" ) const ( KeyTypeKey = "kty" KeyUsageKey = "use" KeyOpsKey = "key_ops" AlgorithmKey = "alg" KeyIDKey = "kid" X509URLKey = "x5u" X509CertChainKey = "x5c" X509CertThumbprintKey = "x5t" X509CertThumbprintS256Key = "x5t#S256" ) // Key defines the minimal interface for each of the // key types. Their use and implementation differ significantly // between each key type, so you should use type assertions // to perform more specific tasks with each key type Key interface { // Has returns true if the specified field has a value, even if // the value is empty-ish (e.g. 0, false, "") as long as it has been // explicitly set. Has(string) bool // Get is used to extract the value of any field, including non-standard fields, out of the key. // // The first argument is the name of the field. The second argument is a pointer // to a variable that will receive the value of the field. The method returns // an error if the field does not exist, or if the value cannot be assigned to // the destination variable. Note that a field is considered to "exist" even if // the value is empty-ish (e.g. 0, false, ""), as long as it is explicitly set. Get(string, any) error // Set sets the value of a single field. Note that certain fields, // notably "kty", cannot be altered, but will not return an error // // This method, which takes an `any`, exists because // these objects can contain extra _arbitrary_ fields that users can // specify, and there is no way of knowing what type they could be Set(string, any) error // Remove removes the field associated with the specified key. // There is no way to remove the `kty` (key type). You will ALWAYS be left with one field in a jwk.Key. Remove(string) error // Validate performs _minimal_ checks if the data stored in the key are valid. // By minimal, we mean that it does not check if the key is valid for use in // cryptographic operations. For example, it does not check if an RSA key's // `e` field is a valid exponent, or if the `n` field is a valid modulus. // Instead, it checks for things such as the _presence_ of some required fields, // or if certain keys' values are of particular length. // // Note that depending on th underlying key type, use of this method requires // that multiple fields in the key are properly populated. For example, an EC // key's "x", "y" fields cannot be validated unless the "crv" field is populated first. // // Validate is never called by `UnmarshalJSON()` or `Set`. It must explicitly be // called by the user Validate() error // Thumbprint returns the JWK thumbprint using the indicated // hashing algorithm, according to RFC 7638 Thumbprint(crypto.Hash) ([]byte, error) // Keys returns a list of the keys contained in this jwk.Key. Keys() []string // Clone creates a new instance of the same type Clone() (Key, error) // PublicKey creates the corresponding PublicKey type for this object. // All fields are copied onto the new public key, except for those that are not allowed. // // If the key is already a public key, it returns a new copy minus the disallowed fields as above. PublicKey() (Key, error) // KeyType returns the `kty` of a JWK KeyType() jwa.KeyType // KeyUsage returns `use` of a JWK KeyUsage() (string, bool) // KeyOps returns `key_ops` of a JWK KeyOps() (KeyOperationList, bool) // Algorithm returns `alg` of a JWK // Algorithm returns the value of the `alg` field. // // This field may contain either `jwk.SignatureAlgorithm`, `jwk.KeyEncryptionAlgorithm`, or `jwk.ContentEncryptionAlgorithm`. // This is why there exists a `jwa.KeyAlgorithm` type that encompasses both types. Algorithm() (jwa.KeyAlgorithm, bool) // KeyID returns `kid` of a JWK KeyID() (string, bool) // X509URL returns `x5u` of a JWK X509URL() (string, bool) // X509CertChain returns `x5c` of a JWK X509CertChain() (*cert.Chain, bool) // X509CertThumbprint returns `x5t` of a JWK X509CertThumbprint() (string, bool) // X509CertThumbprintS256 returns `x5t#S256` of a JWK X509CertThumbprintS256() (string, bool) } golang-github-lestrrat-go-jwx-3.0.13/jwk/io.go000066400000000000000000000014241515060566400211360ustar00rootroot00000000000000// Code generated by tools/cmd/genreadfile/main.go. DO NOT EDIT. package jwk import ( "fmt" "io/fs" "os" ) type sysFS struct{} func (sysFS) Open(path string) (fs.File, error) { return os.Open(path) } func ReadFile(path string, options ...ReadFileOption) (Set, error) { var parseOptions []ParseOption for _, option := range options { if po, ok := option.(ParseOption); ok { parseOptions = append(parseOptions, po) } } var srcFS fs.FS = sysFS{} for _, option := range options { switch option.Ident() { case identFS{}: if err := option.Value(&srcFS); err != nil { return nil, fmt.Errorf("failed to set fs.FS: %w", err) } } } f, err := srcFS.Open(path) if err != nil { return nil, err } defer f.Close() return ParseReader(f, parseOptions...) } golang-github-lestrrat-go-jwx-3.0.13/jwk/jwk.go000066400000000000000000000520561515060566400213310ustar00rootroot00000000000000//go:generate ../tools/cmd/genjwk.sh package jwk import ( "bytes" "crypto" "crypto/ecdsa" "crypto/x509" "encoding/pem" "errors" "fmt" "io" "math/big" "reflect" "slices" "github.com/lestrrat-go/jwx/v3/internal/base64" "github.com/lestrrat-go/jwx/v3/internal/json" ) var registry = json.NewRegistry() func bigIntToBytes(n *big.Int) ([]byte, error) { if n == nil { return nil, fmt.Errorf(`invalid *big.Int value`) } return n.Bytes(), nil } func init() { if err := RegisterProbeField(reflect.StructField{ Name: "Kty", Type: reflect.TypeFor[string](), Tag: `json:"kty"`, }); err != nil { panic(fmt.Errorf("failed to register mandatory probe for 'kty' field: %w", err)) } if err := RegisterProbeField(reflect.StructField{ Name: "D", Type: reflect.TypeFor[json.RawMessage](), Tag: `json:"d,omitempty"`, }); err != nil { panic(fmt.Errorf("failed to register mandatory probe for 'kty' field: %w", err)) } } // Import creates a jwk.Key from the given key (RSA/ECDSA/symmetric keys). // // The constructor auto-detects the type of key to be instantiated // based on the input type: // // - "crypto/rsa".PrivateKey and "crypto/rsa".PublicKey creates an RSA based key // - "crypto/ecdsa".PrivateKey and "crypto/ecdsa".PublicKey creates an EC based key // - "crypto/ed25519".PrivateKey and "crypto/ed25519".PublicKey creates an OKP based key // - "crypto/ecdh".PrivateKey and "crypto/ecdh".PublicKey creates an OKP based key // - []byte creates a symmetric key func Import(raw any) (Key, error) { if raw == nil { return nil, importerr(`a non-nil key is required`) } muKeyImporters.RLock() conv, ok := keyImporters[reflect.TypeOf(raw)] muKeyImporters.RUnlock() if !ok { return nil, importerr(`failed to convert %T to jwk.Key: no converters were able to convert`, raw) } return conv.Import(raw) } // PublicSetOf returns a new jwk.Set consisting of // public keys of the keys contained in the set. // // This is useful when you are generating a set of private keys, and // you want to generate the corresponding public versions for the // users to verify with. // // Be aware that all fields will be copied onto the new public key. It is the caller's // responsibility to remove any fields, if necessary. func PublicSetOf(v Set) (Set, error) { newSet := NewSet() n := v.Len() for i := range n { k, ok := v.Key(i) if !ok { return nil, fmt.Errorf(`key not found`) } pubKey, err := PublicKeyOf(k) if err != nil { return nil, fmt.Errorf(`failed to get public key of %T: %w`, k, err) } if err := newSet.AddKey(pubKey); err != nil { return nil, fmt.Errorf(`failed to add key to public key set: %w`, err) } } return newSet, nil } // PublicKeyOf returns the corresponding public version of the jwk.Key. // If `v` is a SymmetricKey, then the same value is returned. // If `v` is already a public key, the key itself is returned. // // If `v` is a private key type that has a `PublicKey()` method, be aware // that all fields will be copied onto the new public key. It is the caller's // responsibility to remove any fields, if necessary // // If `v` is a raw key, the key is first converted to a `jwk.Key` func PublicKeyOf(v any) (Key, error) { // This should catch all jwk.Key instances if pk, ok := v.(PublicKeyer); ok { return pk.PublicKey() } jk, err := Import(v) if err != nil { return nil, fmt.Errorf(`jwk.PublicKeyOf: failed to convert key into JWK: %w`, err) } return jk.PublicKey() } // PublicRawKeyOf returns the corresponding public key of the given // value `v` (e.g. given *rsa.PrivateKey, *rsa.PublicKey is returned) // If `v` is already a public key, the key itself is returned. // // The returned value will always be a pointer to the public key, // except when a []byte (e.g. symmetric key, ed25519 key) is passed to `v`. // In this case, the same []byte value is returned. // // This function must go through converting the object once to a jwk.Key, // then back to a raw key, so it's not exactly efficient. func PublicRawKeyOf(v any) (any, error) { pk, ok := v.(PublicKeyer) if !ok { k, err := Import(v) if err != nil { return nil, fmt.Errorf(`jwk.PublicRawKeyOf: failed to convert key to jwk.Key: %w`, err) } pk, ok = k.(PublicKeyer) if !ok { return nil, fmt.Errorf(`jwk.PublicRawKeyOf: failed to convert key to jwk.PublicKeyer: %w`, err) } } pubk, err := pk.PublicKey() if err != nil { return nil, fmt.Errorf(`jwk.PublicRawKeyOf: failed to obtain public key from %T: %w`, v, err) } var raw any if err := Export(pubk, &raw); err != nil { return nil, fmt.Errorf(`jwk.PublicRawKeyOf: failed to obtain raw key from %T: %w`, pubk, err) } return raw, nil } // ParseRawKey is a combination of ParseKey and Raw. It parses a single JWK key, // and assigns the "raw" key to the given parameter. The key must either be // a pointer to an empty interface, or a pointer to the actual raw key type // such as *rsa.PrivateKey, *ecdsa.PublicKey, *[]byte, etc. func ParseRawKey(data []byte, rawkey any) error { key, err := ParseKey(data) if err != nil { return fmt.Errorf(`failed to parse key: %w`, err) } if err := Export(key, rawkey); err != nil { return fmt.Errorf(`failed to assign to raw key variable: %w`, err) } return nil } type setDecodeCtx struct { json.DecodeCtx ignoreParseError bool } func (ctx *setDecodeCtx) IgnoreParseError() bool { return ctx.ignoreParseError } // ParseKey parses a single key JWK. Unlike `jwk.Parse` this method will // report failure if you attempt to pass a JWK set. Only use this function // when you know that the data is a single JWK. // // Given a WithPEM(true) option, this function assumes that the given input // is PEM encoded ASN.1 DER format key. // // Note that a successful parsing of any type of key does NOT necessarily // guarantee a valid key. For example, no checks against expiration dates // are performed for certificate expiration, no checks against missing // parameters are performed, etc. func ParseKey(data []byte, options ...ParseOption) (Key, error) { var parsePEM bool var localReg *json.Registry var pemDecoder PEMDecoder for _, option := range options { switch option.Ident() { case identPEM{}: if err := option.Value(&parsePEM); err != nil { return nil, fmt.Errorf(`failed to retrieve PEM option value: %w`, err) } case identPEMDecoder{}: if err := option.Value(&pemDecoder); err != nil { return nil, fmt.Errorf(`failed to retrieve PEMDecoder option value: %w`, err) } case identLocalRegistry{}: if err := option.Value(&localReg); err != nil { return nil, fmt.Errorf(`failed to retrieve local registry option value: %w`, err) } case identTypedField{}: var pair typedFieldPair // temporary var needed for typed field if err := option.Value(&pair); err != nil { return nil, fmt.Errorf(`failed to retrieve typed field option value: %w`, err) } if localReg == nil { localReg = json.NewRegistry() } localReg.Register(pair.Name, pair.Value) case identIgnoreParseError{}: return nil, fmt.Errorf(`jwk.WithIgnoreParseError() cannot be used for ParseKey()`) } } if parsePEM { var raw any // PEMDecoder should probably be deprecated, because of being a misnomer. if pemDecoder != nil { if err := decodeX509WithPEMDEcoder(&raw, data, pemDecoder); err != nil { return nil, fmt.Errorf(`failed to decode PEM encoded key: %w`, err) } } else { // This version takes into account the various X509 decoders that are // pre-registered. if err := decodeX509(&raw, data); err != nil { return nil, fmt.Errorf(`failed to decode X.509 encoded key: %w`, err) } } return Import(raw) } probe, err := keyProbe.Probe(data) if err != nil { return nil, fmt.Errorf(`jwk.Parse: failed to probe data: %w`, err) } unmarshaler := keyUnmarshaler{localReg: localReg} muKeyParser.RLock() parsers := make([]KeyParser, len(keyParsers)) copy(parsers, keyParsers) muKeyParser.RUnlock() for i := len(parsers) - 1; i >= 0; i-- { parser := parsers[i] key, err := parser.ParseKey(probe, &unmarshaler, data) if err == nil { return key, nil } if errors.Is(err, ContinueError()) { continue } return nil, err } return nil, fmt.Errorf(`jwk.Parse: no parser was able to parse the key`) } // Parse parses JWK from the incoming []byte. // // For JWK sets, this is a convenience function. You could just as well // call `json.Unmarshal` against an empty set created by `jwk.NewSet()` // to parse a JSON buffer into a `jwk.Set`. // // This function exists because many times the user does not know before hand // if a JWK(s) resource at a remote location contains a single JWK key or // a JWK set, and `jwk.Parse()` can handle either case, returning a JWK Set // even if the data only contains a single JWK key // // If you are looking for more information on how JWKs are parsed, or if // you know for sure that you have a single key, please see the documentation // for `jwk.ParseKey()`. func Parse(src []byte, options ...ParseOption) (Set, error) { var parsePEM bool var parseX509 bool var localReg *json.Registry var ignoreParseError bool var pemDecoder PEMDecoder for _, option := range options { switch option.Ident() { case identPEM{}: if err := option.Value(&parsePEM); err != nil { return nil, parseerr(`failed to retrieve PEM option value: %w`, err) } case identX509{}: if err := option.Value(&parseX509); err != nil { return nil, parseerr(`failed to retrieve X509 option value: %w`, err) } case identPEMDecoder{}: if err := option.Value(&pemDecoder); err != nil { return nil, parseerr(`failed to retrieve PEMDecoder option value: %w`, err) } case identIgnoreParseError{}: if err := option.Value(&ignoreParseError); err != nil { return nil, parseerr(`failed to retrieve IgnoreParseError option value: %w`, err) } case identTypedField{}: var pair typedFieldPair // temporary var needed for typed field if err := option.Value(&pair); err != nil { return nil, parseerr(`failed to retrieve typed field option value: %w`, err) } if localReg == nil { localReg = json.NewRegistry() } localReg.Register(pair.Name, pair.Value) } } s := NewSet() if parsePEM || parseX509 { if pemDecoder == nil { pemDecoder = NewPEMDecoder() } src = bytes.TrimSpace(src) for len(src) > 0 { raw, rest, err := pemDecoder.Decode(src) if err != nil { return nil, parseerr(`failed to parse PEM encoded key: %w`, err) } key, err := Import(raw) if err != nil { return nil, parseerr(`failed to create jwk.Key from %T: %w`, raw, err) } if err := s.AddKey(key); err != nil { return nil, parseerr(`failed to add jwk.Key to set: %w`, err) } src = bytes.TrimSpace(rest) } return s, nil } if localReg != nil || ignoreParseError { dcKs, ok := s.(KeyWithDecodeCtx) if !ok { return nil, parseerr(`typed field was requested, but the key set (%T) does not support DecodeCtx`, s) } dc := &setDecodeCtx{ DecodeCtx: json.NewDecodeCtx(localReg), ignoreParseError: ignoreParseError, } dcKs.SetDecodeCtx(dc) defer func() { dcKs.SetDecodeCtx(nil) }() } if err := json.Unmarshal(src, s); err != nil { return nil, parseerr(`failed to unmarshal JWK set: %w`, err) } return s, nil } // ParseReader parses a JWK set from the incoming byte buffer. func ParseReader(src io.Reader, options ...ParseOption) (Set, error) { // meh, there's no way to tell if a stream has "ended" a single // JWKs except when we encounter an EOF, so just... ReadAll buf, err := io.ReadAll(src) if err != nil { return nil, rparseerr(`failed to read from io.Reader: %w`, err) } set, err := Parse(buf, options...) if err != nil { return nil, rparseerr(`failed to parse reader: %w`, err) } return set, nil } // ParseString parses a JWK set from the incoming string. func ParseString(s string, options ...ParseOption) (Set, error) { set, err := Parse([]byte(s), options...) if err != nil { return nil, sparseerr(`failed to parse string: %w`, err) } return set, nil } // AssignKeyID is a convenience function to automatically assign the "kid" // section of the key, if it already doesn't have one. It uses Key.Thumbprint // method with crypto.SHA256 as the default hashing algorithm func AssignKeyID(key Key, options ...AssignKeyIDOption) error { if key.Has(KeyIDKey) { return nil } hash := crypto.SHA256 for _, option := range options { switch option.Ident() { case identThumbprintHash{}: if err := option.Value(&hash); err != nil { return fmt.Errorf(`failed to retrieve thumbprint hash option value: %w`, err) } } } h, err := key.Thumbprint(hash) if err != nil { return fmt.Errorf(`failed to generate thumbprint: %w`, err) } if err := key.Set(KeyIDKey, base64.EncodeToString(h)); err != nil { return fmt.Errorf(`failed to set "kid": %w`, err) } return nil } // NOTE: may need to remove this to allow pluggale key types func cloneKey(src Key) (Key, error) { var dst Key switch src.(type) { case RSAPrivateKey: dst = newRSAPrivateKey() case RSAPublicKey: dst = newRSAPublicKey() case ECDSAPrivateKey: dst = newECDSAPrivateKey() case ECDSAPublicKey: dst = newECDSAPublicKey() case OKPPrivateKey: dst = newOKPPrivateKey() case OKPPublicKey: dst = newOKPPublicKey() case SymmetricKey: dst = newSymmetricKey() default: return nil, fmt.Errorf(`jwk.cloneKey: unknown key type %T`, src) } for _, k := range src.Keys() { // It's absolutely var v any if err := src.Get(k, &v); err != nil { return nil, fmt.Errorf(`jwk.cloneKey: failed to get %q: %w`, k, err) } if err := dst.Set(k, v); err != nil { return nil, fmt.Errorf(`jwk.cloneKey: failed to set %q: %w`, k, err) } } return dst, nil } // Pem serializes the given jwk.Key in PEM encoded ASN.1 DER format, // using either PKCS8 for private keys and PKIX for public keys. // If you need to encode using PKCS1 or SEC1, you must do it yourself. // // # Argument must be of type jwk.Key or jwk.Set // // Currently only EC (including Ed25519) and RSA keys (and jwk.Set // comprised of these key types) are supported. func Pem(v any) ([]byte, error) { var set Set switch v := v.(type) { case Key: set = NewSet() if err := set.AddKey(v); err != nil { return nil, fmt.Errorf(`failed to add key to set: %w`, err) } case Set: set = v default: return nil, fmt.Errorf(`argument to Pem must be either jwk.Key or jwk.Set: %T`, v) } var ret []byte for i := range set.Len() { key, _ := set.Key(i) typ, buf, err := asnEncode(key) if err != nil { return nil, fmt.Errorf(`failed to encode content for key #%d: %w`, i, err) } var block pem.Block block.Type = typ block.Bytes = buf ret = append(ret, pem.EncodeToMemory(&block)...) } return ret, nil } func asnEncode(key Key) (string, []byte, error) { switch key := key.(type) { case ECDSAPrivateKey: var rawkey ecdsa.PrivateKey if err := Export(key, &rawkey); err != nil { return "", nil, fmt.Errorf(`failed to get raw key from jwk.Key: %w`, err) } buf, err := x509.MarshalECPrivateKey(&rawkey) if err != nil { return "", nil, fmt.Errorf(`failed to marshal PKCS8: %w`, err) } return pmECPrivateKey, buf, nil case RSAPrivateKey, OKPPrivateKey: var rawkey any if err := Export(key, &rawkey); err != nil { return "", nil, fmt.Errorf(`failed to get raw key from jwk.Key: %w`, err) } buf, err := x509.MarshalPKCS8PrivateKey(rawkey) if err != nil { return "", nil, fmt.Errorf(`failed to marshal PKCS8: %w`, err) } return pmPrivateKey, buf, nil case RSAPublicKey, ECDSAPublicKey, OKPPublicKey: var rawkey any if err := Export(key, &rawkey); err != nil { return "", nil, fmt.Errorf(`failed to get raw key from jwk.Key: %w`, err) } buf, err := x509.MarshalPKIXPublicKey(rawkey) if err != nil { return "", nil, fmt.Errorf(`failed to marshal PKIX: %w`, err) } return pmPublicKey, buf, nil default: return "", nil, fmt.Errorf(`unsupported key type %T`, key) } } type CustomDecoder = json.CustomDecoder type CustomDecodeFunc = json.CustomDecodeFunc // RegisterCustomField allows users to specify that a private field // be decoded as an instance of the specified type. This option has // a global effect. // // For example, suppose you have a custom field `x-birthday`, which // you want to represent as a string formatted in RFC3339 in JSON, // but want it back as `time.Time`. // // In such case you would register a custom field as follows // // jwk.RegisterCustomField(`x-birthday`, time.Time{}) // // Then you can use a `time.Time` variable to extract the value // of `x-birthday` field, instead of having to use `any` // and later convert it to `time.Time` // // var bday time.Time // _ = key.Get(`x-birthday`, &bday) // // If you need a more fine-tuned control over the decoding process, // you can register a `CustomDecoder`. For example, below shows // how to register a decoder that can parse RFC1123 format string: // // jwk.RegisterCustomField(`x-birthday`, jwk.CustomDecodeFunc(func(data []byte) (any, error) { // return time.Parse(time.RFC1123, string(data)) // })) // // Please note that use of custom fields can be problematic if you // are using a library that does not implement MarshalJSON/UnmarshalJSON // and you try to roundtrip from an object to JSON, and then back to an object. // For example, in the above example, you can _parse_ time values formatted // in the format specified in RFC822, but when you convert an object into // JSON, it will be formatted in RFC3339, because that's what `time.Time` // likes to do. To avoid this, it's always better to use a custom type // that wraps your desired type (in this case `time.Time`) and implement // MarshalJSON and UnmashalJSON. func RegisterCustomField(name string, object any) { registry.Register(name, object) } // Equal compares two keys and returns true if they are equal. The comparison // is solely done against the thumbprints of k1 and k2. It is possible for keys // that have, for example, different key IDs, key usage, etc, to be considered equal. func Equal(k1, k2 Key) bool { h := crypto.SHA256 tp1, err := k1.Thumbprint(h) if err != nil { return false // can't report error } tp2, err := k2.Thumbprint(h) if err != nil { return false // can't report error } return bytes.Equal(tp1, tp2) } // IsPrivateKey returns true if the supplied key is a private key of an // asymmetric key pair. The argument `k` must implement the `AsymmetricKey` // interface. // // An error is returned if the supplied key is not an `AsymmetricKey`. func IsPrivateKey(k Key) (bool, error) { asymmetric, ok := k.(AsymmetricKey) if ok { return asymmetric.IsPrivate(), nil } return false, fmt.Errorf("jwk.IsPrivateKey: %T is not an asymmetric key", k) } type keyValidationError struct { err error } func (e *keyValidationError) Error() string { return fmt.Sprintf(`key validation failed: %s`, e.err) } func (e *keyValidationError) Unwrap() error { return e.err } func (e *keyValidationError) Is(target error) bool { _, ok := target.(*keyValidationError) return ok } // NewKeyValidationError wraps the given error with an error that denotes // `key.Validate()` has failed. This error type should ONLY be used as // return value from the `Validate()` method. func NewKeyValidationError(err error) error { return &keyValidationError{err: err} } func IsKeyValidationError(err error) bool { var kve keyValidationError return errors.Is(err, &kve) } // Configure is used to configure global behavior of the jwk package. func Configure(options ...GlobalOption) { var strictKeyUsagePtr *bool for _, option := range options { switch option.Ident() { case identStrictKeyUsage{}: var v bool if err := option.Value(&v); err != nil { continue } strictKeyUsagePtr = &v } } if strictKeyUsagePtr != nil { strictKeyUsage.Store(*strictKeyUsagePtr) } } // These are used when validating keys. type keyWithD interface { D() ([]byte, bool) } var _ keyWithD = &okpPrivateKey{} func extractEmbeddedKey(keyif Key, concretTypes []reflect.Type) (Key, error) { rv := reflect.ValueOf(keyif) // If the value can be converted to one of the concrete types, then we're done if slices.ContainsFunc(concretTypes, func(t reflect.Type) bool { return rv.Type().ConvertibleTo(t) }) { return keyif, nil } // When a struct implements the Key interface via embedding, you unfortunately // cannot use a type switch to determine the concrete type, because if rv.Kind() == reflect.Ptr { if rv.IsNil() { return nil, fmt.Errorf(`invalid key value (0): %w`, ContinueError()) } rv = rv.Elem() } if rv.Kind() != reflect.Struct { return nil, fmt.Errorf(`invalid key value type %T (1): %w`, keyif, ContinueError()) } if rv.NumField() == 0 { return nil, fmt.Errorf(`invalid key value type %T (2): %w`, keyif, ContinueError()) } // Iterate through the fields of the struct to find the first field that // implements the Key interface rt := rv.Type() for i := range rv.NumField() { field := rv.Field(i) ft := rt.Field(i) if !ft.Anonymous { // We can only salvage this object if the object implements jwk.Key // via embedding, so we skip fields that are not anonymous continue } if field.CanInterface() { if k, ok := field.Interface().(Key); ok { return extractEmbeddedKey(k, concretTypes) } } } return nil, fmt.Errorf(`invalid key value type %T (3): %w`, keyif, ContinueError()) } golang-github-lestrrat-go-jwx-3.0.13/jwk/jwk_internal_test.go000066400000000000000000000254051515060566400242620ustar00rootroot00000000000000//go:build none package jwk import ( "context" "fmt" "testing" "github.com/lestrrat-go/jwx/v3/cert" "github.com/lestrrat-go/jwx/v3/internal/base64" "github.com/lestrrat-go/jwx/v3/internal/json" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/stretchr/testify/require" ) func TestX509CertChain(t *testing.T) { certSrc := []string{ "MIIE3jCCA8agAwIBAgICAwEwDQYJKoZIhvcNAQEFBQAwYzELMAkGA1UEBhMCVVMxITAfBgNVBAoTGFRoZSBHbyBEYWRkeSBHcm91cCwgSW5jLjExMC8GA1UECxMoR28gRGFkZHkgQ2xhc3MgMiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjExMTYwMTU0MzdaFw0yNjExMTYwMTU0MzdaMIHKMQswCQYDVQQGEwJVUzEQMA4GA1UECBMHQXJpem9uYTETMBEGA1UEBxMKU2NvdHRzZGFsZTEaMBgGA1UEChMRR29EYWRkeS5jb20sIEluYy4xMzAxBgNVBAsTKmh0dHA6Ly9jZXJ0aWZpY2F0ZXMuZ29kYWRkeS5jb20vcmVwb3NpdG9yeTEwMC4GA1UEAxMnR28gRGFkZHkgU2VjdXJlIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MREwDwYDVQQFEwgwNzk2OTI4NzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMQt1RWMnCZM7DI161+4WQFapmGBWTtwY6vj3D3HKrjJM9N55DrtPDAjhI6zMBS2sofDPZVUBJ7fmd0LJR4h3mUpfjWoqVTr9vcyOdQmVZWt7/v+WIbXnvQAjYwqDL1CBM6nPwT27oDyqu9SoWlm2r4arV3aLGbqGmu75RpRSgAvSMeYddi5Kcju+GZtCpyz8/x4fKL4o/K1w/O5epHBp+YlLpyo7RJlbmr2EkRTcDCVw5wrWCs9CHRK8r5RsL+H0EwnWGu1NcWdrxcx+AuP7q2BNgWJCJjPOq8lh8BJ6qf9Z/dFjpfMFDniNoW1fho3/Rb2cRGadDAW/hOUoz+EDU8CAwEAAaOCATIwggEuMB0GA1UdDgQWBBT9rGEyk2xF1uLuhV+auud2mWjM5zAfBgNVHSMEGDAWgBTSxLDSkdRMEXGzYcs9of7dqGrU4zASBgNVHRMBAf8ECDAGAQH/AgEAMDMGCCsGAQUFBwEBBCcwJTAjBggrBgEFBQcwAYYXaHR0cDovL29jc3AuZ29kYWRkeS5jb20wRgYDVR0fBD8wPTA7oDmgN4Y1aHR0cDovL2NlcnRpZmljYXRlcy5nb2RhZGR5LmNvbS9yZXBvc2l0b3J5L2dkcm9vdC5jcmwwSwYDVR0gBEQwQjBABgRVHSAAMDgwNgYIKwYBBQUHAgEWKmh0dHA6Ly9jZXJ0aWZpY2F0ZXMuZ29kYWRkeS5jb20vcmVwb3NpdG9yeTAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQEFBQADggEBANKGwOy9+aG2Z+5mC6IGOgRQjhVyrEp0lVPLN8tESe8HkGsz2ZbwlFalEzAFPIUyIXvJxwqoJKSQ3kbTJSMUA2fCENZvD117esyfxVgqwcSeIaha86ykRvOe5GPLL5CkKSkB2XIsKd83ASe8T+5o0yGPwLPk9Qnt0hCqU7S+8MxZC9Y7lhyVJEnfzuz9p0iRFEUOOjZv2kWzRaJBydTXRE4+uXR21aITVSzGh6O1mawGhId/dQb8vxRMDsxuxN89txJx9OjxUUAiKEngHUuHqDTMBqLdElrRhjZkAzVvb3du6/KFUJheqwNTrZEjYx8WnM25sgVjOuH0aBsXBTWVU+4=", "MIIE+zCCBGSgAwIBAgICAQ0wDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENsYXNzIDIgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTA0MDYyOTE3MDYyMFoXDTI0MDYyOTE3MDYyMFowYzELMAkGA1UEBhMCVVMxITAfBgNVBAoTGFRoZSBHbyBEYWRkeSBHcm91cCwgSW5jLjExMC8GA1UECxMoR28gRGFkZHkgQ2xhc3MgMiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASAwDQYJKoZIhvcNAQEBBQADggENADCCAQgCggEBAN6d1+pXGEmhW+vXX0iG6r7d/+TvZxz0ZWizV3GgXne77ZtJ6XCAPVYYYwhv2vLM0D9/AlQiVBDYsoHUwHU9S3/Hd8M+eKsaA7Ugay9qK7HFiH7Eux6wwdhFJ2+qN1j3hybX2C32qRe3H3I2TqYXP2WYktsqbl2i/ojgC95/5Y0V4evLOtXiEqITLdiOr18SPaAIBQi2XKVlOARFmR6jYGB0xUGlcmIbYsUfb18aQr4CUWWoriMYavx4A6lNf4DD+qta/KFApMoZFv6yyO9ecw3ud72a9nmYvLEHZ6IVDd2gWMZEewo+YihfukEHU1jPEX44dMX4/7VpkI+EdOqXG68CAQOjggHhMIIB3TAdBgNVHQ4EFgQU0sSw0pHUTBFxs2HLPaH+3ahq1OMwgdIGA1UdIwSByjCBx6GBwaSBvjCBuzEkMCIGA1UEBxMbVmFsaUNlcnQgVmFsaWRhdGlvbiBOZXR3b3JrMRcwFQYDVQQKEw5WYWxpQ2VydCwgSW5jLjE1MDMGA1UECxMsVmFsaUNlcnQgQ2xhc3MgMiBQb2xpY3kgVmFsaWRhdGlvbiBBdXRob3JpdHkxITAfBgNVBAMTGGh0dHA6Ly93d3cudmFsaWNlcnQuY29tLzEgMB4GCSqGSIb3DQEJARYRaW5mb0B2YWxpY2VydC5jb22CAQEwDwYDVR0TAQH/BAUwAwEB/zAzBggrBgEFBQcBAQQnMCUwIwYIKwYBBQUHMAGGF2h0dHA6Ly9vY3NwLmdvZGFkZHkuY29tMEQGA1UdHwQ9MDswOaA3oDWGM2h0dHA6Ly9jZXJ0aWZpY2F0ZXMuZ29kYWRkeS5jb20vcmVwb3NpdG9yeS9yb290LmNybDBLBgNVHSAERDBCMEAGBFUdIAAwODA2BggrBgEFBQcCARYqaHR0cDovL2NlcnRpZmljYXRlcy5nb2RhZGR5LmNvbS9yZXBvc2l0b3J5MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOBgQC1QPmnHfbq/qQaQlpE9xXUhUaJwL6e4+PrxeNYiY+Sn1eocSxI0YGyeR+sBjUZsE4OWBsUs5iB0QQeyAfJg594RAoYC5jcdnplDQ1tgMQLARzLrUc+cb53S8wGd9D0VmsfSxOaFIqII6hR8INMqzW/Rn453HWkrugp++85j09VZw==", "MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENsYXNzIDIgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNjAwMTk1NFoXDTE5MDYyNjAwMTk1NFowgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENsYXNzIDIgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDOOnHK5avIWZJV16vYdA757tn2VUdZZUcOBVXc65g2PFxTXdMwzzjsvUGJ7SVCCSRrCl6zfN1SLUzm1NZ9WlmpZdRJEy0kTRxQb7XBhVQ7/nHk01xC+YDgkRoKWzk2Z/M/VXwbP7RfZHM047QSv4dk+NoS/zcnwbNDu+97bi5p9wIDAQABMA0GCSqGSIb3DQEBBQUAA4GBADt/UG9vUJSZSWI4OB9L+KXIPqeCgfYrx+jFzug6EILLGACOTb2oWH+heQC1u+mNr0HZDzTuIYEZoDJJKPTEjlbVUjP9UNV+mWwD5MlM/Mtsq2azSiGM5bUMMj4QssxsodyamEwCW/POuZ6lcg5Ktz885hZo+L7tdEy8W9ViH0Pd", } var c cert.Chain for _, src := range certSrc { _ = c.AddString(src) } for _, key := range []Key{ newRSAPrivateKey(), newRSAPublicKey(), newECDSAPrivateKey(), newECDSAPublicKey(), newSymmetricKey(), } { key := key t.Run("Set X509CertChainKey", func(t *testing.T) { require.NoError(t, key.Set(X509CertChainKey, &c), "Set for x5c should succeed") var gotcerts cert.Chain require.NoError(t, key.Get(X509CertChainKey, &gotcerts), "Get for x5c should succeed") require.Equal(t, gotcerts.Len(), 3, `should have 3 cert`) }) } } func TestIterator(t *testing.T) { commonValues := map[string]any{ AlgorithmKey: jwa.KeyAlgorithmFrom("dummy"), KeyIDKey: "dummy-kid", KeyUsageKey: "dummy-usage", KeyOpsKey: KeyOperationList{KeyOpSign, KeyOpVerify, KeyOpEncrypt, KeyOpDecrypt, KeyOpWrapKey, KeyOpUnwrapKey, KeyOpDeriveKey, KeyOpDeriveBits}, "private": "dummy-private", } verifyIterators := func(t *testing.T, v Key, expected map[string]any) { t.Helper() t.Run("Iterate", func(t *testing.T) { seen := make(map[string]any) for iter := v.Iterate(context.TODO()); iter.Next(context.TODO()); { pair := iter.Pair() seen[pair.Key.(string)] = pair.Value var getV any require.NoError(t, v.Get(pair.Key.(string), &getV), `v.Get should succeed for key %#v`, pair.Key) require.Equal(t, pair.Value, getV, `pair.Value should match value from v.Get()`) } require.Equal(t, expected, seen, `values should match`) }) t.Run("Walk", func(t *testing.T) { seen := make(map[string]any) v.Walk(context.TODO(), HeaderVisitorFunc(func(key string, value any) error { seen[key] = value return nil })) require.Equal(t, expected, seen, `values should match`) }) t.Run("AsMap", func(t *testing.T) { seen, err := v.AsMap(context.TODO()) require.NoError(t, err, `v.AsMap should succeed`) require.Equal(t, expected, seen, `values should match`) }) } type iterTestCase struct { Extras map[string]any Func func() Key } testcases := []iterTestCase{ { Extras: map[string]any{ RSANKey: []byte("0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw"), RSAEKey: []byte("AQAB"), RSADKey: []byte("X4cTteJY_gn4FYPsXB8rdXix5vwsg1FLN5E3EaG6RJoVH-HLLKD9M7dx5oo7GURknchnrRweUkC7hT5fJLM0WbFAKNLWY2vv7B6NqXSzUvxT0_YSfqijwp3RTzlBaCxWp4doFk5N2o8Gy_nHNKroADIkJ46pRUohsXywbReAdYaMwFs9tv8d_cPVY3i07a3t8MN6TNwm0dSawm9v47UiCl3Sk5ZiG7xojPLu4sbg1U2jx4IBTNBznbJSzFHK66jT8bgkuqsk0GjskDJk19Z4qwjwbsnn4j2WBii3RL-Us2lGVkY8fkFzme1z0HbIkfz0Y6mqnOYtqc0X4jfcKoAC8Q"), RSAPKey: []byte("83i-7IvMGXoMXCskv73TKr8637FiO7Z27zv8oj6pbWUQyLPQBQxtPVnwD20R-60eTDmD2ujnMt5PoqMrm8RfmNhVWDtjjMmCMjOpSXicFHj7XOuVIYQyqVWlWEh6dN36GVZYk93N8Bc9vY41xy8B9RzzOGVQzXvNEvn7O0nVbfs"), RSAQKey: []byte("3dfOR9cuYq-0S-mkFLzgItgMEfFzB2q3hWehMuG0oCuqnb3vobLyumqjVZQO1dIrdwgTnCdpYzBcOfW5r370AFXjiWft_NGEiovonizhKpo9VVS78TzFgxkIdrecRezsZ-1kYd_s1qDbxtkDEgfAITAG9LUnADun4vIcb6yelxk"), RSADPKey: []byte("G4sPXkc6Ya9y8oJW9_ILj4xuppu0lzi_H7VTkS8xj5SdX3coE0oimYwxIi2emTAue0UOa5dpgFGyBJ4c8tQ2VF402XRugKDTP8akYhFo5tAA77Qe_NmtuYZc3C3m3I24G2GvR5sSDxUyAN2zq8Lfn9EUms6rY3Ob8YeiKkTiBj0"), RSADQKey: []byte("s9lAH9fggBsoFR8Oac2R_E2gw282rT2kGOAhvIllETE1efrA6huUUvMfBcMpn8lqeW6vzznYY5SSQF7pMdC_agI3nG8Ibp1BUb0JUiraRNqUfLhcQb_d9GF4Dh7e74WbRsobRonujTYN1xCaP6TO61jvWrX-L18txXw494Q_cgk"), RSAQIKey: []byte("GyM_p6JrXySiz1toFgKbWV-JdI3jQ4ypu9rbMWx3rQJBfmt0FoYzgUIZEVFEcOqwemRN81zoDAaa-Bk0KWNGDjJHZDdDmFhW3AN7lI-puxk_mHZGJ11rxyR8O55XLSe3SPmRfKwZI6yU24ZxvQKFYItdldUKGzO6Ia6zTKhAVRU"), }, Func: func() Key { return newRSAPrivateKey() }, }, { Extras: map[string]any{ RSANKey: []byte("0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw"), RSAEKey: []byte("AQAB"), }, Func: func() Key { return newRSAPublicKey() }, }, { Extras: map[string]any{ ECDSACrvKey: jwa.P256, ECDSAXKey: (func() []byte { s, _ := base64.DecodeString("MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4") return s })(), ECDSAYKey: (func() []byte { s, _ := base64.DecodeString("4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM") return s })(), ECDSADKey: (func() []byte { s, _ := base64.DecodeString("870MB6gfuTJ4HtUnUvYMyJpr5eUZNP4Bk43bVdj3eAE") return s })(), }, Func: func() Key { return newECDSAPrivateKey() }, }, { Extras: map[string]any{ ECDSACrvKey: jwa.P256, ECDSAXKey: (func() []byte { s, _ := base64.DecodeString("MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4") return s })(), ECDSAYKey: (func() []byte { s, _ := base64.DecodeString("4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM") return s })(), }, Func: func() Key { return newECDSAPublicKey() }, }, { Extras: map[string]any{ SymmetricOctetsKey: []byte("abcd"), }, Func: func() Key { return newSymmetricKey() }, }, } for _, test := range testcases { key := test.Func() key2 := test.Func() expected := make(map[string]any) expected[KeyTypeKey] = key.KeyType() for k, v := range commonValues { require.NoError(t, key.Set(k, v), `key.Set %#v should succeed`, k) expected[k] = v } for k, v := range test.Extras { require.NoError(t, key.Set(k, v), `key.Set %#v should succeed`, k) expected[k] = v } t.Run(fmt.Sprintf("%T", key), func(t *testing.T) { verifyIterators(t, key, expected) }) t.Run(fmt.Sprintf("%T (after json roundtripping)", key), func(t *testing.T) { buf, err := json.Marshal(key) require.NoError(t, err, `json.Marshal should succeed`) require.NoError(t, json.Unmarshal(buf, key2), `json.Unmarshal should succeed`) verifyIterators(t, key2, expected) }) } } golang-github-lestrrat-go-jwx-3.0.13/jwk/jwk_test.go000066400000000000000000002407711515060566400223730ustar00rootroot00000000000000package jwk_test import ( "bytes" "context" "crypto" "crypto/ecdh" "crypto/ecdsa" "crypto/ed25519" "crypto/rand" "crypto/rsa" "encoding/pem" "fmt" "io" "log" "math/big" "net/http" "net/http/httptest" "reflect" "regexp" "strconv" "strings" "testing" "time" "github.com/lestrrat-go/blackmagic" "github.com/lestrrat-go/httprc/v3" "github.com/lestrrat-go/jwx/v3/cert" "github.com/lestrrat-go/jwx/v3/internal/base64" "github.com/lestrrat-go/jwx/v3/internal/jose" "github.com/lestrrat-go/jwx/v3/internal/json" "github.com/lestrrat-go/jwx/v3/internal/jwxtest" "github.com/lestrrat-go/jwx/v3/internal/tokens" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/lestrrat-go/jwx/v3/jwk" ourecdsa "github.com/lestrrat-go/jwx/v3/jwk/ecdsa" "github.com/stretchr/testify/require" ) var zeroval reflect.Value var certChain *cert.Chain var certChainSrc = []string{ "MIIE3jCCA8agAwIBAgICAwEwDQYJKoZIhvcNAQEFBQAwYzELMAkGA1UEBhMCVVMxITAfBgNVBAoTGFRoZSBHbyBEYWRkeSBHcm91cCwgSW5jLjExMC8GA1UECxMoR28gRGFkZHkgQ2xhc3MgMiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjExMTYwMTU0MzdaFw0yNjExMTYwMTU0MzdaMIHKMQswCQYDVQQGEwJVUzEQMA4GA1UECBMHQXJpem9uYTETMBEGA1UEBxMKU2NvdHRzZGFsZTEaMBgGA1UEChMRR29EYWRkeS5jb20sIEluYy4xMzAxBgNVBAsTKmh0dHA6Ly9jZXJ0aWZpY2F0ZXMuZ29kYWRkeS5jb20vcmVwb3NpdG9yeTEwMC4GA1UEAxMnR28gRGFkZHkgU2VjdXJlIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MREwDwYDVQQFEwgwNzk2OTI4NzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMQt1RWMnCZM7DI161+4WQFapmGBWTtwY6vj3D3HKrjJM9N55DrtPDAjhI6zMBS2sofDPZVUBJ7fmd0LJR4h3mUpfjWoqVTr9vcyOdQmVZWt7/v+WIbXnvQAjYwqDL1CBM6nPwT27oDyqu9SoWlm2r4arV3aLGbqGmu75RpRSgAvSMeYddi5Kcju+GZtCpyz8/x4fKL4o/K1w/O5epHBp+YlLpyo7RJlbmr2EkRTcDCVw5wrWCs9CHRK8r5RsL+H0EwnWGu1NcWdrxcx+AuP7q2BNgWJCJjPOq8lh8BJ6qf9Z/dFjpfMFDniNoW1fho3/Rb2cRGadDAW/hOUoz+EDU8CAwEAAaOCATIwggEuMB0GA1UdDgQWBBT9rGEyk2xF1uLuhV+auud2mWjM5zAfBgNVHSMEGDAWgBTSxLDSkdRMEXGzYcs9of7dqGrU4zASBgNVHRMBAf8ECDAGAQH/AgEAMDMGCCsGAQUFBwEBBCcwJTAjBggrBgEFBQcwAYYXaHR0cDovL29jc3AuZ29kYWRkeS5jb20wRgYDVR0fBD8wPTA7oDmgN4Y1aHR0cDovL2NlcnRpZmljYXRlcy5nb2RhZGR5LmNvbS9yZXBvc2l0b3J5L2dkcm9vdC5jcmwwSwYDVR0gBEQwQjBABgRVHSAAMDgwNgYIKwYBBQUHAgEWKmh0dHA6Ly9jZXJ0aWZpY2F0ZXMuZ29kYWRkeS5jb20vcmVwb3NpdG9yeTAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQEFBQADggEBANKGwOy9+aG2Z+5mC6IGOgRQjhVyrEp0lVPLN8tESe8HkGsz2ZbwlFalEzAFPIUyIXvJxwqoJKSQ3kbTJSMUA2fCENZvD117esyfxVgqwcSeIaha86ykRvOe5GPLL5CkKSkB2XIsKd83ASe8T+5o0yGPwLPk9Qnt0hCqU7S+8MxZC9Y7lhyVJEnfzuz9p0iRFEUOOjZv2kWzRaJBydTXRE4+uXR21aITVSzGh6O1mawGhId/dQb8vxRMDsxuxN89txJx9OjxUUAiKEngHUuHqDTMBqLdElrRhjZkAzVvb3du6/KFUJheqwNTrZEjYx8WnM25sgVjOuH0aBsXBTWVU+4=", "MIIE+zCCBGSgAwIBAgICAQ0wDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENsYXNzIDIgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTA0MDYyOTE3MDYyMFoXDTI0MDYyOTE3MDYyMFowYzELMAkGA1UEBhMCVVMxITAfBgNVBAoTGFRoZSBHbyBEYWRkeSBHcm91cCwgSW5jLjExMC8GA1UECxMoR28gRGFkZHkgQ2xhc3MgMiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASAwDQYJKoZIhvcNAQEBBQADggENADCCAQgCggEBAN6d1+pXGEmhW+vXX0iG6r7d/+TvZxz0ZWizV3GgXne77ZtJ6XCAPVYYYwhv2vLM0D9/AlQiVBDYsoHUwHU9S3/Hd8M+eKsaA7Ugay9qK7HFiH7Eux6wwdhFJ2+qN1j3hybX2C32qRe3H3I2TqYXP2WYktsqbl2i/ojgC95/5Y0V4evLOtXiEqITLdiOr18SPaAIBQi2XKVlOARFmR6jYGB0xUGlcmIbYsUfb18aQr4CUWWoriMYavx4A6lNf4DD+qta/KFApMoZFv6yyO9ecw3ud72a9nmYvLEHZ6IVDd2gWMZEewo+YihfukEHU1jPEX44dMX4/7VpkI+EdOqXG68CAQOjggHhMIIB3TAdBgNVHQ4EFgQU0sSw0pHUTBFxs2HLPaH+3ahq1OMwgdIGA1UdIwSByjCBx6GBwaSBvjCBuzEkMCIGA1UEBxMbVmFsaUNlcnQgVmFsaWRhdGlvbiBOZXR3b3JrMRcwFQYDVQQKEw5WYWxpQ2VydCwgSW5jLjE1MDMGA1UECxMsVmFsaUNlcnQgQ2xhc3MgMiBQb2xpY3kgVmFsaWRhdGlvbiBBdXRob3JpdHkxITAfBgNVBAMTGGh0dHA6Ly93d3cudmFsaWNlcnQuY29tLzEgMB4GCSqGSIb3DQEJARYRaW5mb0B2YWxpY2VydC5jb22CAQEwDwYDVR0TAQH/BAUwAwEB/zAzBggrBgEFBQcBAQQnMCUwIwYIKwYBBQUHMAGGF2h0dHA6Ly9vY3NwLmdvZGFkZHkuY29tMEQGA1UdHwQ9MDswOaA3oDWGM2h0dHA6Ly9jZXJ0aWZpY2F0ZXMuZ29kYWRkeS5jb20vcmVwb3NpdG9yeS9yb290LmNybDBLBgNVHSAERDBCMEAGBFUdIAAwODA2BggrBgEFBQcCARYqaHR0cDovL2NlcnRpZmljYXRlcy5nb2RhZGR5LmNvbS9yZXBvc2l0b3J5MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOBgQC1QPmnHfbq/qQaQlpE9xXUhUaJwL6e4+PrxeNYiY+Sn1eocSxI0YGyeR+sBjUZsE4OWBsUs5iB0QQeyAfJg594RAoYC5jcdnplDQ1tgMQLARzLrUc+cb53S8wGd9D0VmsfSxOaFIqII6hR8INMqzW/Rn453HWkrugp++85j09VZw==", "MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENsYXNzIDIgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNjAwMTk1NFoXDTE5MDYyNjAwMTk1NFowgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENsYXNzIDIgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDOOnHK5avIWZJV16vYdA757tn2VUdZZUcOBVXc65g2PFxTXdMwzzjsvUGJ7SVCCSRrCl6zfN1SLUzm1NZ9WlmpZdRJEy0kTRxQb7XBhVQ7/nHk01xC+YDgkRoKWzk2Z/M/VXwbP7RfZHM047QSv4dk+NoS/zcnwbNDu+97bi5p9wIDAQABMA0GCSqGSIb3DQEBBQUAA4GBADt/UG9vUJSZSWI4OB9L+KXIPqeCgfYrx+jFzug6EILLGACOTb2oWH+heQC1u+mNr0HZDzTuIYEZoDJJKPTEjlbVUjP9UNV+mWwD5MlM/Mtsq2azSiGM5bUMMj4QssxsodyamEwCW/POuZ6lcg5Ktz885hZo+L7tdEy8W9ViH0Pd", } type keyDef struct { Expected any Value any Method string } var commonDef map[string]keyDef func init() { jwa.RegisterSignatureAlgorithm(jwa.NewSignatureAlgorithm("ECMR")) certChain = &cert.Chain{} for _, src := range certChainSrc { _ = certChain.AddString(src) } alg, ok := jwa.LookupSignatureAlgorithm("RS256") if !ok { panic("failed to find RS256 algorithm") } commonDef = map[string]keyDef{ jwk.AlgorithmKey: { Method: "Algorithm", Value: alg, }, jwk.KeyIDKey: { Method: "KeyID", Value: "12312rdfsdfer2342342", }, jwk.KeyUsageKey: { Method: "KeyUsage", Value: jwk.ForSignature, Expected: string(jwk.ForSignature), }, jwk.KeyOpsKey: { Method: "KeyOps", Value: jwk.KeyOperationList{ jwk.KeyOpSign, jwk.KeyOpVerify, jwk.KeyOpEncrypt, jwk.KeyOpDecrypt, jwk.KeyOpWrapKey, jwk.KeyOpUnwrapKey, jwk.KeyOpDeriveKey, jwk.KeyOpDeriveBits, }, }, jwk.X509CertChainKey: { Method: "X509CertChain", Value: certChain, Expected: certChain, }, jwk.X509CertThumbprintKey: { Value: "x5t blah", Method: "X509CertThumbprint", }, jwk.X509CertThumbprintS256Key: { Value: "x5t#256 blah", Method: "X509CertThumbprintS256", }, jwk.X509URLKey: { Value: "http://github.com/lestrrat-go/jwx/v3", Method: "X509URL", }, "private": {Value: "boofoo"}, } } func complimentDef(def map[string]keyDef) map[string]keyDef { for k, v := range commonDef { if _, ok := def[k]; !ok { def[k] = v } } return def } func makeKeyJSON(def map[string]keyDef) []byte { data := map[string]any{} for k, v := range def { data[k] = v.Value } src, err := json.Marshal(data) if err != nil { panic(err) } return src } func expectBase64(kdef keyDef) keyDef { v, err := base64.DecodeString(kdef.Value.(string)) if err != nil { panic(err) } kdef.Expected = v return kdef } func expectedRawKeyType(key jwk.Key) any { switch key := key.(type) { case jwk.RSAPrivateKey: return &rsa.PrivateKey{} case jwk.RSAPublicKey: return &rsa.PublicKey{} case jwk.ECDSAPrivateKey: return &ecdsa.PrivateKey{} case jwk.ECDSAPublicKey: return &ecdsa.PublicKey{} case jwk.SymmetricKey: return []byte(nil) case jwk.OKPPrivateKey: crv, ok := key.Crv() if !ok { panic("missing crv") } switch crv { case jwa.Ed25519(): return ed25519.PrivateKey(nil) case jwa.X25519(): return &ecdh.PrivateKey{} default: panic("unknown curve type for OKPPrivateKey:" + crv.String()) } case jwk.OKPPublicKey: crv, ok := key.Crv() if !ok { panic("missing crv") } switch crv { case jwa.Ed25519(): return ed25519.PublicKey(nil) case jwa.X25519(): return &ecdh.PublicKey{} default: panic("unknown curve type for OKPPublicKey:" + crv.String()) } default: panic(fmt.Sprintf("unknown key type: %T", key)) } } func VerifyKey(t *testing.T, def map[string]keyDef) { t.Helper() def = complimentDef(def) key, err := jwk.ParseKey(makeKeyJSON(def)) require.NoError(t, err, `jwk.ParseKey should succeed`) t.Run("Fields", func(t *testing.T) { for k, kdef := range def { t.Run(k, func(t *testing.T) { var getval any require.NoError(t, key.Get(k, &getval), `key.Get(%s) should succeed`, k) expected := kdef.Expected if expected == nil { expected = kdef.Value } require.Equal(t, expected, getval) if mname := kdef.Method; mname != "" { method := reflect.ValueOf(key).MethodByName(mname) require.NotEqual(t, zeroval, method, `method should not be a zero value`) retvals := method.Call(nil) expectedReturnValues := 2 if mname == "KeyType" { expectedReturnValues = 1 } require.Len(t, retvals, expectedReturnValues, `there should be 1 return value`) require.Equal(t, expected, retvals[0].Interface()) } }) } }) t.Run("Roundtrip", func(t *testing.T) { var supportsPEM bool switch key.KeyType() { case jwa.OKP(), jwa.OctetSeq(): default: supportsPEM = true } for _, usePEM := range []bool{true, false} { if usePEM && !supportsPEM { continue } t.Run(fmt.Sprintf("WithPEM(%t)", usePEM), func(t *testing.T) { var buf []byte if usePEM { pem, err := jwk.EncodePEM(key) require.NoError(t, err, `jwk.EncodePEM should succeed`) buf = pem } else { jsonbuf, err := json.Marshal(key) require.NoError(t, err, `json.Marshal should succeed`) buf = jsonbuf t.Logf("%s", buf) } newkey, err := jwk.ParseKey(buf, jwk.WithPEM(usePEM)) require.NoError(t, err, `jwk.ParseKey should succeed`) LOOP: for _, k := range key.Keys() { if usePEM { switch k { case `private`, jwk.AlgorithmKey, jwk.KeyIDKey, jwk.KeyOpsKey, jwk.KeyUsageKey, jwk.X509CertChainKey, jwk.X509CertThumbprintKey, jwk.X509CertThumbprintS256Key, jwk.X509URLKey: continue LOOP } } var v any var v2 any require.NoError(t, key.Get(k, &v), `key.Get(%s) should succeed`, k) require.NoError(t, newkey.Get(k, &v2), `newkey.Get(%s) should succeed`, k) require.Equal(t, v, v2, `values should match`) } }) } }) t.Run("Raw", func(t *testing.T) { typ := expectedRawKeyType(key) var rawkey any require.NoError(t, jwk.Export(key, &rawkey), `Raw() should succeed`) require.IsType(t, rawkey, typ, `raw key should be of this type`) }) t.Run("PublicKey", func(t *testing.T) { _, err := jwk.PublicKeyOf(key) require.NoError(t, err, `jwk.PublicKeyOf should succeed`) }) t.Run("IsPrivate", func(t *testing.T) { _, err := jwk.IsPrivateKey(key) if _, ok := key.(jwk.SymmetricKey); ok { require.Error(t, err, `jwk.IsPrivateKey should fail`) } else { require.NoError(t, err, `jwk.IsPrivateKey should succeed`) } }) t.Run("Set/Remove", func(t *testing.T) { newkey, err := key.Clone() require.NoError(t, err, `key.Clone should succeed`) for _, k := range key.Keys() { require.NoError(t, newkey.Remove(k), `newkey.Remove should succeed`) } for _, k := range newkey.Keys() { require.Equal(t, k, jwk.KeyTypeKey, `key should be kty`) } for _, k := range key.Keys() { var v any require.NoError(t, key.Get(k, &v), `key.Get should succeed`) require.NoError(t, newkey.Set(k, v), `newkey.Set should succeed`) } }) } func TestNew(t *testing.T) { t.Parallel() k, err := jwk.Import(nil) require.Nil(t, k, "key should be nil") require.Error(t, err, "nil key should cause an error") } func TestParse(t *testing.T) { t.Parallel() verify := func(t *testing.T, src string, expected reflect.Type) { t.Helper() t.Run("json.Unmarshal", func(t *testing.T) { set := jwk.NewSet() err := json.Unmarshal([]byte(src), set) require.NoError(t, err, `json.Unmarshal should succeed`) require.True(t, set.Len() > 0, "set.Keys should be greater than 0") for i := range set.Len() { key, err := set.Key(i) require.True(t, err, `set.Key(%d) should succeed`, i) require.True(t, reflect.TypeOf(key).AssignableTo(expected), "key should be a %s", expected) } }) t.Run("jwk.Parse", func(t *testing.T) { t.Helper() set, err := jwk.Parse([]byte(`{"keys":[` + src + `]}`)) require.NoError(t, err, `jwk.Parse should succeed`) require.True(t, set.Len() > 0, "set.Len should be greater than 0") for i := range set.Len() { key, ok := set.Key(i) require.True(t, ok, `set.Key(%d) should succeed`, i) switch key := key.(type) { case jwk.RSAPrivateKey, jwk.ECDSAPrivateKey, jwk.OKPPrivateKey, jwk.RSAPublicKey, jwk.ECDSAPublicKey, jwk.OKPPublicKey, jwk.SymmetricKey: default: require.Fail(t, fmt.Sprintf("invalid type: %T", key)) } } }) t.Run("jwk.ParseKey", func(t *testing.T) { t.Helper() key, err := jwk.ParseKey([]byte(src)) require.NoError(t, err, `jwk.ParseKey should succeed`) t.Run("Raw", func(t *testing.T) { t.Helper() var irawkey any require.NoError(t, jwk.Export(key, &irawkey), `key.Raw(&interface) should ucceed`) isPrivate, err := jwk.IsPrivateKey(key) require.NoError(t, err, "jwk.IsPrivateKey(%T) should succeed", key) var crawkey any switch k := key.(type) { case jwk.RSAPrivateKey: require.True(t, isPrivate, `jwk.IsPrivateKey(&rsa.PrivateKey) should be true`) var rawkey rsa.PrivateKey require.NoError(t, jwk.Export(key, &rawkey), `key.Raw(&rsa.PrivateKey) should succeed`) crawkey = &rawkey case jwk.RSAPublicKey: require.False(t, isPrivate, `jwk.IsPrivateKey(&rsa.PublicKey) should be false`) var rawkey rsa.PublicKey require.NoError(t, jwk.Export(key, &rawkey), `key.Raw(&rsa.PublicKey) should succeed`) crawkey = &rawkey case jwk.ECDSAPrivateKey: require.True(t, isPrivate, `jwk.IsPrivateKey(&ecdsa.PrivateKey) should be true`) var rawkey ecdsa.PrivateKey require.NoError(t, jwk.Export(key, &rawkey), `key.Raw(&ecdsa.PrivateKey) should succeed`) crawkey = &rawkey case jwk.OKPPrivateKey: require.True(t, isPrivate, `jwk.IsPrivateKey(&ed25519.PrivateKey) should be true`) crv, ok := k.Crv() require.True(t, ok, `k.Crv() should succeed`) switch crv { case jwa.Ed25519(): var rawkey ed25519.PrivateKey require.NoError(t, jwk.Export(key, &rawkey), `key.Raw(&ed25519.PrivateKey) should succeed`) crawkey = rawkey case jwa.X25519(): var rawkey ecdh.PrivateKey require.NoError(t, jwk.Export(key, &rawkey), `key.Raw(&ecdh.PrivateKey) should succeed`) crawkey = &rawkey default: t.Errorf(`invalid curve %s`, crv) } // NOTE: Has to come after private // key, since it's a subset of the // private key variant. case jwk.OKPPublicKey: require.False(t, isPrivate, `jwk.IsPrivateKey(&ed25519.PublicKey) should be false`) crv, ok := k.Crv() require.True(t, ok, `k.Crv() should succeed`) switch crv { case jwa.Ed25519(): var rawkey ed25519.PublicKey require.NoError(t, jwk.Export(key, &rawkey), `key.Raw(&ed25519.PublicKey) should succeed`) crawkey = rawkey case jwa.X25519(): var rawkey ecdh.PublicKey require.NoError(t, jwk.Export(key, &rawkey), `key.Raw(&ecdh.PublicKey) should succeed`) crawkey = &rawkey default: t.Errorf(`invalid curve %s`, crv) } default: t.Errorf(`invalid key type %T`, key) return } require.IsType(t, crawkey, irawkey, `key types should match`) }) }) t.Run("ParseRawKey", func(t *testing.T) { var v any require.NoError(t, jwk.ParseRawKey([]byte(src), &v), `jwk.ParseRawKey should succeed`) }) } t.Run("RSA Public Key", func(t *testing.T) { t.Parallel() const src = `{ "e":"AQAB", "kty":"RSA", "n":"0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw" }` verify(t, src, reflect.TypeFor[jwk.RSAPublicKey]()) }) t.Run("RSA Private Key", func(t *testing.T) { t.Parallel() const src = `{ "kty":"RSA", "n":"0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw", "e":"AQAB", "d":"X4cTteJY_gn4FYPsXB8rdXix5vwsg1FLN5E3EaG6RJoVH-HLLKD9M7dx5oo7GURknchnrRweUkC7hT5fJLM0WbFAKNLWY2vv7B6NqXSzUvxT0_YSfqijwp3RTzlBaCxWp4doFk5N2o8Gy_nHNKroADIkJ46pRUohsXywbReAdYaMwFs9tv8d_cPVY3i07a3t8MN6TNwm0dSawm9v47UiCl3Sk5ZiG7xojPLu4sbg1U2jx4IBTNBznbJSzFHK66jT8bgkuqsk0GjskDJk19Z4qwjwbsnn4j2WBii3RL-Us2lGVkY8fkFzme1z0HbIkfz0Y6mqnOYtqc0X4jfcKoAC8Q", "p":"83i-7IvMGXoMXCskv73TKr8637FiO7Z27zv8oj6pbWUQyLPQBQxtPVnwD20R-60eTDmD2ujnMt5PoqMrm8RfmNhVWDtjjMmCMjOpSXicFHj7XOuVIYQyqVWlWEh6dN36GVZYk93N8Bc9vY41xy8B9RzzOGVQzXvNEvn7O0nVbfs", "q":"3dfOR9cuYq-0S-mkFLzgItgMEfFzB2q3hWehMuG0oCuqnb3vobLyumqjVZQO1dIrdwgTnCdpYzBcOfW5r370AFXjiWft_NGEiovonizhKpo9VVS78TzFgxkIdrecRezsZ-1kYd_s1qDbxtkDEgfAITAG9LUnADun4vIcb6yelxk", "dp":"G4sPXkc6Ya9y8oJW9_ILj4xuppu0lzi_H7VTkS8xj5SdX3coE0oimYwxIi2emTAue0UOa5dpgFGyBJ4c8tQ2VF402XRugKDTP8akYhFo5tAA77Qe_NmtuYZc3C3m3I24G2GvR5sSDxUyAN2zq8Lfn9EUms6rY3Ob8YeiKkTiBj0", "dq":"s9lAH9fggBsoFR8Oac2R_E2gw282rT2kGOAhvIllETE1efrA6huUUvMfBcMpn8lqeW6vzznYY5SSQF7pMdC_agI3nG8Ibp1BUb0JUiraRNqUfLhcQb_d9GF4Dh7e74WbRsobRonujTYN1xCaP6TO61jvWrX-L18txXw494Q_cgk", "qi":"GyM_p6JrXySiz1toFgKbWV-JdI3jQ4ypu9rbMWx3rQJBfmt0FoYzgUIZEVFEcOqwemRN81zoDAaa-Bk0KWNGDjJHZDdDmFhW3AN7lI-puxk_mHZGJ11rxyR8O55XLSe3SPmRfKwZI6yU24ZxvQKFYItdldUKGzO6Ia6zTKhAVRU", "alg":"RS256", "kid":"2011-04-29" }` verify(t, src, reflect.TypeFor[jwk.RSAPrivateKey]()) }) t.Run("ECDSA Private Key", func(t *testing.T) { t.Parallel() const src = `{ "kty" : "EC", "crv" : "P-256", "x" : "SVqB4JcUD6lsfvqMr-OKUNUphdNn64Eay60978ZlL74", "y" : "lf0u0pMj4lGAzZix5u4Cm5CMQIgMNpkwy163wtKYVKI", "d" : "0g5vAEKzugrXaRbgKG0Tj2qJ5lMP4Bezds1_sTybkfk" }` verify(t, src, reflect.TypeFor[jwk.ECDSAPrivateKey]()) }) t.Run("Invalid ECDSA Private Key", func(t *testing.T) { t.Parallel() const src = `{ "kty" : "EC", "crv" : "P-256", "y" : "lf0u0pMj4lGAzZix5u4Cm5CMQIgMNpkwy163wtKYVKI", "d" : "0g5vAEKzugrXaRbgKG0Tj2qJ5lMP4Bezds1_sTybkfk" }` _, err := jwk.ParseString(src) require.Error(t, err, `jwk.ParseString should fail`) }) t.Run("Ed25519 Public Key", func(t *testing.T) { t.Parallel() // Key taken from RFC 8037 const src = `{ "kty" : "OKP", "crv" : "Ed25519", "x" : "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo" }` verify(t, src, reflect.TypeFor[jwk.OKPPublicKey]()) }) t.Run("Ed25519 Private Key", func(t *testing.T) { t.Parallel() // Key taken from RFC 8037 const src = `{ "kty" : "OKP", "crv" : "Ed25519", "d" : "nWGxne_9WmC6hEr0kuwsxERJxWl7MmkZcDusAxyuf2A", "x" : "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo" }` verify(t, src, reflect.TypeFor[jwk.OKPPrivateKey]()) }) t.Run("X25519 Public Key", func(t *testing.T) { t.Parallel() // Key taken from RFC 8037 const src = `{ "kty" : "OKP", "crv" : "X25519", "x" : "3p7bfXt9wbTTW2HC7OQ1Nz-DQ8hbeGdNrfx-FG-IK08" }` verify(t, src, reflect.TypeFor[jwk.OKPPublicKey]()) }) t.Run("X25519 Private Key", func(t *testing.T) { t.Parallel() // Key taken from RFC 8037 const src = `{ "kty" : "OKP", "crv" : "X25519", "d" : "dwdtCnMYpX08FsFyUbJmRd9ML4frwJkqsXf7pR25LCo", "x" : "hSDwCYkwp1R0i33ctD73Wg2_Og0mOBr066SpjqqbTmo" }` verify(t, src, reflect.TypeFor[jwk.OKPPrivateKey]()) }) } func TestRoundtrip(t *testing.T) { t.Parallel() generateRSA := func(use string, keyID string) (jwk.Key, error) { k, err := jwxtest.GenerateRsaJwk() if err != nil { return nil, err } k.Set(jwk.KeyUsageKey, use) k.Set(jwk.KeyIDKey, keyID) return k, nil } generateECDSA := func(use, keyID string) (jwk.Key, error) { k, err := jwxtest.GenerateEcdsaJwk() if err != nil { return nil, err } k.Set(jwk.KeyUsageKey, use) k.Set(jwk.KeyIDKey, keyID) return k, nil } generateSymmetric := func(use, keyID string) (jwk.Key, error) { k, err := jwxtest.GenerateSymmetricJwk() if err != nil { return nil, err } k.Set(jwk.KeyUsageKey, use) k.Set(jwk.KeyIDKey, keyID) return k, nil } generateEd25519 := func(use, keyID string) (jwk.Key, error) { k, err := jwxtest.GenerateEd25519Jwk() if err != nil { return nil, err } k.Set(jwk.KeyUsageKey, use) k.Set(jwk.KeyIDKey, keyID) return k, nil } generateX25519 := func(use, keyID string) (jwk.Key, error) { k, err := jwxtest.GenerateX25519Jwk() if err != nil { return nil, err } k.Set(jwk.KeyUsageKey, use) k.Set(jwk.KeyIDKey, keyID) return k, nil } tests := []struct { generate func(string, string) (jwk.Key, error) use string keyID string }{ { use: "enc", keyID: "enc1", generate: generateRSA, }, { use: "enc", keyID: "enc2", generate: generateRSA, }, { use: "sig", keyID: "sig1", generate: generateRSA, }, { use: "sig", keyID: "sig2", generate: generateRSA, }, { use: "sig", keyID: "sig3", generate: generateSymmetric, }, { use: "enc", keyID: "enc4", generate: generateECDSA, }, { use: "enc", keyID: "enc5", generate: generateECDSA, }, { use: "sig", keyID: "sig4", generate: generateECDSA, }, { use: "sig", keyID: "sig5", generate: generateECDSA, }, { use: "sig", keyID: "sig6", generate: generateEd25519, }, { use: "enc", keyID: "enc6", generate: generateX25519, }, } ks1 := jwk.NewSet() for _, tc := range tests { key, err := tc.generate(tc.use, tc.keyID) require.NoError(t, err, `tc.generate should succeed`) require.NoError(t, ks1.AddKey(key), `ks1.Add should succeed`) } buf, err := json.MarshalIndent(ks1, "", " ") require.NoError(t, err, "JSON marshal succeeded") ks2, err := jwk.Parse(buf) require.NoError(t, err, "JSON unmarshal succeeded") for _, tc := range tests { key1, ok := ks2.LookupKeyID(tc.keyID) require.True(t, ok, "ks2.LookupKeyID should succeed") key2, ok := ks1.LookupKeyID(tc.keyID) require.True(t, ok, "ks1.LookupKeyID should succeed") pk1json, _ := json.Marshal(key1) pk2json, _ := json.Marshal(key2) require.Equal(t, pk1json, pk2json, "Keys should match (kid = %s)", tc.keyID) } } func TestAccept(t *testing.T) { t.Parallel() t.Run("KeyOperation", func(t *testing.T) { t.Parallel() testcases := []struct { Args any Error bool }{ { Args: "sign", }, { Args: []jwk.KeyOperation{jwk.KeyOpSign, jwk.KeyOpVerify, jwk.KeyOpEncrypt, jwk.KeyOpDecrypt, jwk.KeyOpWrapKey, jwk.KeyOpUnwrapKey}, }, { Args: jwk.KeyOperationList{jwk.KeyOpSign, jwk.KeyOpVerify, jwk.KeyOpEncrypt, jwk.KeyOpDecrypt, jwk.KeyOpWrapKey, jwk.KeyOpUnwrapKey}, }, { Args: []any{"sign", "verify", "encrypt", "decrypt", "wrapKey", "unwrapKey"}, }, { Args: []string{"sign", "verify", "encrypt", "decrypt", "wrapKey", "unwrapKey"}, }, { Args: []string{"sigh"}, Error: true, }, } for _, test := range testcases { var ops jwk.KeyOperationList if test.Error { require.Error(t, ops.Accept(test.Args), `KeyOperationList.Accept should fail`) } else { require.NoError(t, ops.Accept(test.Args), `KeyOperationList.Accept should succeed`) } } }) t.Run("KeyUsage", func(t *testing.T) { t.Parallel() testcases := []struct { Args any Error bool }{ {Args: jwk.ForSignature}, {Args: jwk.ForEncryption}, {Args: jwk.ForSignature.String()}, {Args: jwk.ForEncryption.String()}, {Args: jwk.KeyUsageType("bogus"), Error: true}, {Args: "bogus", Error: true}, } for _, test := range testcases { var usage jwk.KeyUsageType if test.Error { require.Error(t, usage.Accept(test.Args), `KeyUsage.Accept should fail`) } else { require.NoError(t, usage.Accept(test.Args), `KeyUsage.Accept should succeed`) } } }) } func TestAssignKeyID(t *testing.T) { t.Parallel() generators := []func() (jwk.Key, error){ jwxtest.GenerateRsaJwk, jwxtest.GenerateRsaPublicJwk, jwxtest.GenerateEcdsaJwk, jwxtest.GenerateEcdsaPublicJwk, jwxtest.GenerateSymmetricJwk, jwxtest.GenerateEd25519Jwk, } for _, generator := range generators { k, err := generator() require.NoError(t, err, `jwk generation should be successful`) kid, ok := k.KeyID() require.False(t, ok, `k.KeyID should be empty`) require.Empty(t, kid, `k.KeyID should be non-empty`) require.NoError(t, jwk.AssignKeyID(k), `AssignKeyID shuld be successful`) kid, ok = k.KeyID() require.True(t, ok, `k.KeyID should be non-empty`) require.NotEmpty(t, kid, `k.KeyID should be non-empty`) } } func TestPublicKeyOf(t *testing.T) { t.Parallel() rsakey, err := jwxtest.GenerateRsaKey() require.NoError(t, err, `generating raw RSA key should succeed`) ecdsakey, err := jwxtest.GenerateEcdsaKey(jwa.P521()) require.NoError(t, err, `generating raw ECDSA key should succeed`) octets := jwxtest.GenerateSymmetricKey() ed25519key, err := jwxtest.GenerateEd25519Key() require.NoError(t, err, `generating raw Ed25519 key should succeed`) x25519key, err := jwxtest.GenerateX25519Key() require.NoError(t, err, `generating raw X25519 key should succeed`) keys := []struct { Key any PublicKeyType reflect.Type }{ { Key: rsakey, PublicKeyType: reflect.PointerTo(reflect.TypeFor[rsa.PublicKey]()), }, { Key: *rsakey, PublicKeyType: reflect.PointerTo(reflect.TypeFor[rsa.PublicKey]()), }, { Key: rsakey.PublicKey, PublicKeyType: reflect.PointerTo(reflect.TypeFor[rsa.PublicKey]()), }, { Key: &rsakey.PublicKey, PublicKeyType: reflect.PointerTo(reflect.TypeFor[rsa.PublicKey]()), }, { Key: ecdsakey, PublicKeyType: reflect.PointerTo(reflect.TypeFor[ecdsa.PublicKey]()), }, { Key: *ecdsakey, PublicKeyType: reflect.PointerTo(reflect.TypeFor[ecdsa.PublicKey]()), }, { Key: ecdsakey.PublicKey, PublicKeyType: reflect.PointerTo(reflect.TypeFor[ecdsa.PublicKey]()), }, { Key: &ecdsakey.PublicKey, PublicKeyType: reflect.PointerTo(reflect.TypeFor[ecdsa.PublicKey]()), }, { Key: octets, PublicKeyType: reflect.TypeFor[[]byte](), }, { Key: ed25519key, PublicKeyType: reflect.TypeOf(ed25519key.Public()), }, { Key: ed25519key.Public(), PublicKeyType: reflect.TypeOf(ed25519key.Public()), }, { Key: x25519key, PublicKeyType: reflect.TypeFor[*ecdh.PublicKey](), }, { Key: x25519key.Public(), PublicKeyType: reflect.TypeFor[*ecdh.PublicKey](), }, } for _, key := range keys { t.Run(fmt.Sprintf("%T", key.Key), func(t *testing.T) { t.Parallel() pubkey, err := jwk.PublicRawKeyOf(key.Key) require.NoError(t, err, `jwk.PublicKeyOf(%T) should succeed`, key.Key) require.Equal(t, key.PublicKeyType, reflect.TypeOf(pubkey), `public key types should match (got %T)`, pubkey) // Go through jwk.Import jwkKey, err := jwk.Import(key.Key) require.NoError(t, err, `jwk.Import should succeed`) pubJwkKey, err := jwk.PublicKeyOf(jwkKey) require.NoError(t, err, `jwk.PublicKeyOf(%T) should succeed`, jwkKey) // Get the raw key to compare var rawKey any require.NoError(t, jwk.Export(pubJwkKey, &rawKey), `pubJwkKey.Raw should succeed`) require.Equal(t, key.PublicKeyType, reflect.TypeOf(rawKey), `public key types should match (got %T)`, rawKey) }) } t.Run("Set", func(t *testing.T) { var setKeys []struct { Key jwk.Key PublicKeyType reflect.Type } set := jwk.NewSet() count := 0 for _, key := range keys { if reflect.TypeOf(key.Key) == key.PublicKeyType { continue } jwkKey, err := jwk.Import(key.Key) require.NoError(t, err, `jwk.Import should succeed`) jwkKey.Set(jwk.KeyIDKey, fmt.Sprintf("key%d", count)) setKeys = append(setKeys, struct { Key jwk.Key PublicKeyType reflect.Type }{ Key: jwkKey, PublicKeyType: key.PublicKeyType, }) set.AddKey(jwkKey) count++ } newSet, err := jwk.PublicSetOf(set) require.NoError(t, err, `jwk.PublicKeyOf(jwk.Set) should succeed`) for i, key := range setKeys { setKey, ok := newSet.Key(i) require.True(t, ok, `element %d should be present`, i) kid, ok := setKey.KeyID() require.True(t, ok, `KeyID() should be present`) require.Equal(t, fmt.Sprintf("key%d", i), kid, `KeyID() should match for %T`, setKey) // Get the raw key to compare var rawKey any require.NoError(t, jwk.Export(setKey, &rawKey), `pubJwkKey.Raw should succeed`) require.Equal(t, key.PublicKeyType, reflect.TypeOf(rawKey), `public key types should match (got %T)`, rawKey) } }) } func TestIssue207(t *testing.T) { t.Parallel() const src = `{"kty":"EC","alg":"ECMR","crv":"P-521","key_ops":["deriveKey"],"x":"AJwCS845x9VljR-fcrN2WMzIJHDYuLmFShhyu8ci14rmi2DMFp8txIvaxG8n7ZcODeKIs1EO4E_Bldm_pxxs8cUn","y":"ASjz754cIQHPJObihPV8D7vVNfjp_nuwP76PtbLwUkqTk9J1mzCDKM3VADEk-Z1tP-DHiwib6If8jxnb_FjNkiLJ"}` // Using a loop here because we're using sync.Pool // just for sanity. for range 10 { k, err := jwk.ParseKey([]byte(src)) require.NoError(t, err, `jwk.ParseKey should succeed`) thumb, err := k.Thumbprint(crypto.SHA1) require.NoError(t, err, `k.Thumbprint should succeed`) require.Equal(t, `2Mc_43O_BOrOJTNrGX7uJ6JsIYE`, base64.EncodeToString(thumb), `thumbprints should match`) } } func TestIssue270(t *testing.T) { t.Parallel() const src = `{"kty":"EC","alg":"ECMR","crv":"P-521","key_ops":["deriveKey"],"x":"AJwCS845x9VljR-fcrN2WMzIJHDYuLmFShhyu8ci14rmi2DMFp8txIvaxG8n7ZcODeKIs1EO4E_Bldm_pxxs8cUn","y":"ASjz754cIQHPJObihPV8D7vVNfjp_nuwP76PtbLwUkqTk9J1mzCDKM3VADEk-Z1tP-DHiwib6If8jxnb_FjNkiLJ"}` k, err := jwk.ParseKey([]byte(src)) require.NoError(t, err, `jwk.ParseKey should succeed`) for _, usage := range []string{"sig", "enc"} { require.NoError(t, k.Set(jwk.KeyUsageKey, usage)) require.NoError(t, k.Set(jwk.KeyUsageKey, jwk.KeyUsageType(usage))) } } func TestReadFile(t *testing.T) { t.Parallel() if !jose.Available() { t.SkipNow() } ctx := t.Context() fn, clean, err := jose.GenerateJwk(ctx, t, `{"alg": "RS256"}`) require.NoError(t, err, `jose.GenerateJwk`) defer clean() _, err = jwk.ReadFile(fn) require.NoError(t, err, `jwk.ReadFile should succeed`) } func TestRSA(t *testing.T) { t.Parallel() t.Run("PublicKey", func(t *testing.T) { t.Parallel() VerifyKey(t, map[string]keyDef{ jwk.RSAEKey: expectBase64(keyDef{ Method: "E", Value: "AQAB", }), jwk.KeyTypeKey: { Method: "KeyType", Value: jwa.RSA(), }, jwk.RSANKey: expectBase64(keyDef{ Method: "N", Value: "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw", }), }) t.Run("New", func(t *testing.T) { for _, raw := range []rsa.PublicKey{ {}, } { _, err := jwk.Import(raw) require.Error(t, err, `jwk.Import should fail for invalid key`) } }) }) t.Run("Private Key", func(t *testing.T) { t.Parallel() VerifyKey(t, map[string]keyDef{ jwk.KeyTypeKey: { Method: "KeyType", Value: jwa.RSA(), }, jwk.RSANKey: expectBase64(keyDef{ Method: "N", Value: "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw", }), jwk.RSAEKey: expectBase64(keyDef{ Method: "E", Value: "AQAB", }), jwk.RSADKey: expectBase64(keyDef{ Method: "D", Value: "X4cTteJY_gn4FYPsXB8rdXix5vwsg1FLN5E3EaG6RJoVH-HLLKD9M7dx5oo7GURknchnrRweUkC7hT5fJLM0WbFAKNLWY2vv7B6NqXSzUvxT0_YSfqijwp3RTzlBaCxWp4doFk5N2o8Gy_nHNKroADIkJ46pRUohsXywbReAdYaMwFs9tv8d_cPVY3i07a3t8MN6TNwm0dSawm9v47UiCl3Sk5ZiG7xojPLu4sbg1U2jx4IBTNBznbJSzFHK66jT8bgkuqsk0GjskDJk19Z4qwjwbsnn4j2WBii3RL-Us2lGVkY8fkFzme1z0HbIkfz0Y6mqnOYtqc0X4jfcKoAC8Q", }), jwk.RSAPKey: expectBase64(keyDef{ Method: "P", Value: "83i-7IvMGXoMXCskv73TKr8637FiO7Z27zv8oj6pbWUQyLPQBQxtPVnwD20R-60eTDmD2ujnMt5PoqMrm8RfmNhVWDtjjMmCMjOpSXicFHj7XOuVIYQyqVWlWEh6dN36GVZYk93N8Bc9vY41xy8B9RzzOGVQzXvNEvn7O0nVbfs", }), jwk.RSAQKey: expectBase64(keyDef{ Method: "Q", Value: "3dfOR9cuYq-0S-mkFLzgItgMEfFzB2q3hWehMuG0oCuqnb3vobLyumqjVZQO1dIrdwgTnCdpYzBcOfW5r370AFXjiWft_NGEiovonizhKpo9VVS78TzFgxkIdrecRezsZ-1kYd_s1qDbxtkDEgfAITAG9LUnADun4vIcb6yelxk", }), jwk.RSADPKey: expectBase64(keyDef{ Method: "DP", Value: "G4sPXkc6Ya9y8oJW9_ILj4xuppu0lzi_H7VTkS8xj5SdX3coE0oimYwxIi2emTAue0UOa5dpgFGyBJ4c8tQ2VF402XRugKDTP8akYhFo5tAA77Qe_NmtuYZc3C3m3I24G2GvR5sSDxUyAN2zq8Lfn9EUms6rY3Ob8YeiKkTiBj0", }), jwk.RSADQKey: expectBase64(keyDef{ Method: "DQ", Value: "s9lAH9fggBsoFR8Oac2R_E2gw282rT2kGOAhvIllETE1efrA6huUUvMfBcMpn8lqeW6vzznYY5SSQF7pMdC_agI3nG8Ibp1BUb0JUiraRNqUfLhcQb_d9GF4Dh7e74WbRsobRonujTYN1xCaP6TO61jvWrX-L18txXw494Q_cgk", }), jwk.RSAQIKey: expectBase64(keyDef{ Method: "QI", Value: "GyM_p6JrXySiz1toFgKbWV-JdI3jQ4ypu9rbMWx3rQJBfmt0FoYzgUIZEVFEcOqwemRN81zoDAaa-Bk0KWNGDjJHZDdDmFhW3AN7lI-puxk_mHZGJ11rxyR8O55XLSe3SPmRfKwZI6yU24ZxvQKFYItdldUKGzO6Ia6zTKhAVRU", }), }) t.Run("New", func(t *testing.T) { for _, raw := range []rsa.PrivateKey{ {}, // Missing D { // Missing primes D: &big.Int{}, }, { // Missing Primes[0] D: &big.Int{}, Primes: []*big.Int{nil, {}}, }, { // Missing Primes[1] D: &big.Int{}, Primes: []*big.Int{{}, nil}, }, { // Missing PrivateKey.N D: &big.Int{}, Primes: []*big.Int{{}, {}}, }, } { _, err := jwk.Import(raw) require.Error(t, err, `jwk.Import should fail for empty key`) } }) }) t.Run("Thumbprint", func(t *testing.T) { expected := []byte{55, 54, 203, 177, 120, 124, 184, 48, 156, 119, 238, 140, 55, 5, 197, 225, 111, 251, 158, 133, 151, 21, 144, 31, 30, 76, 89, 177, 17, 130, 245, 123, } const src = `{ "kty":"RSA", "e": "AQAB", "n": "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw" }` key, err := jwk.ParseKey([]byte(src)) require.NoError(t, err, `jwk.ParseKey should succeed`) tp, err := key.Thumbprint(crypto.SHA256) require.NoError(t, err, "Thumbprint should succeed") require.Equal(t, expected, tp, "Thumbprint should match") }) } func TestECDSA(t *testing.T) { t.Run("PrivateKey", func(t *testing.T) { t.Run("New", func(t *testing.T) { for _, raw := range []ecdsa.PrivateKey{ {}, { // Missing PublicKey D: &big.Int{}, }, { // Missing PublicKey.X D: &big.Int{}, PublicKey: ecdsa.PublicKey{ Y: &big.Int{}, }, }, { // Missing PublicKey.Y D: &big.Int{}, PublicKey: ecdsa.PublicKey{ X: &big.Int{}, }, }, } { _, err := jwk.Import(raw) require.Error(t, err, `jwk.Import should fail for invalid key`) } }) VerifyKey(t, map[string]keyDef{ jwk.KeyTypeKey: { Method: "KeyType", Value: jwa.EC(), }, jwk.ECDSACrvKey: { Method: "Crv", Value: jwa.P256(), }, jwk.ECDSAXKey: expectBase64(keyDef{ Method: "X", Value: "MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4", }), jwk.ECDSAYKey: expectBase64(keyDef{ Method: "Y", Value: "4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM", }), jwk.ECDSADKey: expectBase64(keyDef{ Method: "D", Value: "870MB6gfuTJ4HtUnUvYMyJpr5eUZNP4Bk43bVdj3eAE", }), }) }) t.Run("PublicKey", func(t *testing.T) { t.Run("New", func(t *testing.T) { for _, raw := range []ecdsa.PublicKey{ {}, { // Missing X Y: &big.Int{}, }, { // Missing Y X: &big.Int{}, }, } { _, err := jwk.Import(raw) require.Error(t, err, `jwk.Import should fail for invalid key`) } }) VerifyKey(t, map[string]keyDef{ jwk.KeyTypeKey: { Method: "KeyType", Value: jwa.EC(), }, jwk.ECDSACrvKey: { Method: "Crv", Value: jwa.P256(), }, jwk.ECDSAXKey: expectBase64(keyDef{ Method: "X", Value: "MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4", }), jwk.ECDSAYKey: expectBase64(keyDef{ Method: "Y", Value: "4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM", }), }) }) t.Run("Curve types", func(t *testing.T) { algorithms := ourecdsa.Algorithms() require.True(t, len(algorithms) >= 3, `algorithm length should be greater than or equal to 3`) for _, alg := range algorithms { t.Run(alg.String(), func(t *testing.T) { key, err := jwxtest.GenerateEcdsaKey(alg) require.NoError(t, err, `jwxtest.GenerateEcdsaKey should succeed`) privkey, err := jwk.Import(key) require.NoError(t, err, `jwk.Import should succeed`) pubkey, err := jwk.Import(key) require.NoError(t, err, `jwk.Import should succeed`) privtp, err := privkey.Thumbprint(crypto.SHA512) require.NoError(t, err, `privkey.Thumbprint should succeed`) pubtp, err := pubkey.Thumbprint(crypto.SHA512) require.NoError(t, err, `pubkey.Thumbprint should succeed`) require.Equal(t, privtp, pubtp, `Thumbprints should match`) }) } }) } func TestSymmetric(t *testing.T) { t.Run("Key", func(t *testing.T) { VerifyKey(t, map[string]keyDef{ jwk.KeyTypeKey: { Method: "KeyType", Value: jwa.OctetSeq(), }, jwk.SymmetricOctetsKey: expectBase64(keyDef{ Method: "Octets", Value: "aGVsbG8K", }), }) }) } func TestOKP(t *testing.T) { t.Parallel() ecdhkey, err := ecdh.P256().GenerateKey(rand.Reader) require.NoError(t, err, `ecdh.P256().GenerateKey should succeed`) x, err := ecdhkey.ECDH(ecdhkey.PublicKey()) require.NoError(t, err, `ecdhkey.ECDH should succeed`) log.Printf("ecdhkey.PublicKey().Bytes(): %x", ecdhkey.PublicKey().Bytes()) _, ed25519privkey, err := ed25519.GenerateKey(rand.Reader) require.NoError(t, err, `ed25519.GenerateKey should succeed`) keys := map[string][]struct { Name string Data map[string]keyDef }{ "Ed25519": { { Name: "PrivateKey", Data: map[string]keyDef{ jwk.KeyTypeKey: { Method: "KeyType", Value: jwa.OKP(), }, jwk.OKPDKey: expectBase64(keyDef{ Method: "D", Value: base64.EncodeToString(ed25519privkey.Seed()), }), jwk.OKPXKey: expectBase64(keyDef{ Method: "X", Value: base64.EncodeToString(ed25519privkey.Public().(ed25519.PublicKey)), }), jwk.OKPCrvKey: { Method: "Crv", Value: jwa.Ed25519(), }, }, }, { Name: "PublicKey", Data: map[string]keyDef{ jwk.KeyTypeKey: { Method: "KeyType", Value: jwa.OKP(), }, jwk.OKPXKey: expectBase64(keyDef{ Method: "X", Value: base64.EncodeToString(ed25519privkey.Public().(ed25519.PublicKey)), }), jwk.OKPCrvKey: { Method: "Crv", Value: jwa.Ed25519(), }, }, }, }, "ECDH": { { Name: "PrivateKey", Data: map[string]keyDef{ jwk.KeyTypeKey: { Method: "KeyType", Value: jwa.OKP(), }, jwk.OKPDKey: expectBase64(keyDef{ Method: "D", Value: base64.EncodeToString(ecdhkey.Bytes()), }), jwk.OKPXKey: expectBase64(keyDef{ Method: "X", Value: base64.EncodeToString(x), }), jwk.OKPCrvKey: { Method: "Crv", Value: jwa.X25519(), }, }, }, { Name: "PublicKey", Data: map[string]keyDef{ jwk.KeyTypeKey: { Method: "KeyType", Value: jwa.OKP(), }, jwk.OKPXKey: expectBase64(keyDef{ Method: "X", Value: base64.EncodeToString(x), }), jwk.OKPCrvKey: { Method: "Crv", Value: jwa.X25519(), }, }, }, }, } for typ, keys := range keys { t.Run(typ, func(t *testing.T) { t.Parallel() for _, key := range keys { t.Run(key.Name, func(t *testing.T) { t.Parallel() VerifyKey(t, key.Data) }) } }) } } func TestCustomField(t *testing.T) { const rfc3339Key = `x-rfc3339-key` const rfc1123Key = `x-rfc1123-key` // XXX has global effect!!! jwk.RegisterCustomField(rfc3339Key, time.Time{}) jwk.RegisterCustomField(rfc1123Key, jwk.CustomDecodeFunc(func(data []byte) (any, error) { var s string if err := json.Unmarshal(data, &s); err != nil { return nil, err } return time.Parse(time.RFC1123, s) })) defer jwk.RegisterCustomField(rfc3339Key, nil) defer jwk.RegisterCustomField(rfc1123Key, nil) expected := time.Date(2015, 11, 4, 5, 12, 52, 0, time.UTC) rfc3339bytes, _ := expected.MarshalText() // RFC3339 rfc1123bytes := expected.Format(time.RFC1123) var b strings.Builder b.WriteString(`{"e":"AQAB", "kty":"RSA", "n":"0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw","`) b.WriteString(rfc3339Key) b.WriteString(`":"`) b.Write(rfc3339bytes) b.WriteString(`","`) b.WriteString(rfc1123Key) b.WriteString(`":"`) b.WriteString(rfc1123bytes) b.WriteString(`"}`) src := b.String() t.Run("jwk.ParseKey", func(t *testing.T) { key, err := jwk.ParseKey([]byte(src)) require.NoError(t, err, `jwk.ParseKey should succeed`) for _, name := range []string{rfc3339Key, rfc1123Key} { var v time.Time require.NoError(t, key.Get(name, &v), `key.Get(%q) should succeed`, name) require.Equal(t, expected, v, `values should match`) } }) } func TestCertificate(t *testing.T) { const src = `-----BEGIN CERTIFICATE----- MIIEljCCAn4CCQCTQBoGDvUbQTANBgkqhkiG9w0BAQsFADANMQswCQYDVQQGEwJK UDAeFw0yMTA0MDEwMDE4MjhaFw0yMjA0MDEwMDE4MjhaMA0xCzAJBgNVBAYTAkpQ MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAvws4H/OxVS3CW1zvUgjs H443df9zCAblLVPPdeRD11Jl1OZmGS7rtQNjQyT5xGpeuk77ZJcfDNLx+mSEtiYQ V37GD5MPz+RX3hP2azuLvxoBseaHE6kC8tkDed8buQLl1hgms15KmKnt7E8B+EK2 1YRj0w6ZzehIllTbbj6gDJ39kZ2VHdLf5+4W0Kyh9cM4aA0si2jQJQsohW2rpt89 b+IagFau+sxP3GFUjSEvyXIamXhS0NLWuAW9UvY/RwhnIo5BzmWZd/y2R305T+QT rHtb/8aGav8mP3uDx6AMDp/0UMKFUO4mpoOusMnrplUPS4Lz6RNpffmrrglOEuRZ /eSFzGL35OeL12aYSyrbFIVsc/aLs6MkoplsuSG6Zhx345h/dA2a8Ub5khr6bksP zGLer+bpBrQQsy21unvCIUz5y7uaYhV3Ql+aIZ+dwpEgZ3xxAvdKKeoCGQlhH/4J 0sSuutUtuTLfrBSgLHJEv2HIzeynChL2CYR8aku/nL68VTdmSt9UY2JGMOf9U8BI fGRpkWBvI8hddMxNm8wF+09WScaZ2JWu7qW/l2jOdgesPIWRg+Hm3NaRSHqAWCOq VUJk9WkCAye0FPALqSvH0ApDKxNtGZb5JZRCW19TqmhgXbAqIf5hsxDaGIXZcW9S CqapZPw7Ccs7BOKSFvmM9p0CAwEAATANBgkqhkiG9w0BAQsFAAOCAgEAVfLzKRdA 0vFpAAp3K+CDth7mag2WWFOXjlWZ+4pxfEBX3k7erJbj6+qYuCvCHXqIZnK1kZzD p4zwsu8t8RfSmPvxcm/jkvecG4DAIGTdhBVtAf/9PU3e4kZFQCqizicQABh+ZFKV dDtkRebUA5EAvP8E/OrvrjYU5xnOxOZU3arVXJfKFjVD619qLuF8XXW5700Gdqwn wBgasTCCg9+tniiscKaET1m9C4PdrlXuAIscV9tGcJ7yEAao1BXokyJ+mK6K2Zv1 z/vvUJA/rGMBJoUjnWrRHON1JMNou2KyRO6z37GpRnfPiNgFpGv2x3ZNeix7H4bP 6+x4KZWQir5047p9hV4YrqMXeULEj3uG2GnOgdR7+hiN39arFVr11DMgABmx19SM VQpTHrC8a605wwCBWnkiYdNojLa5WgeEHdBghKVpWnx9frYgZcz2UP861el5Lg9R j04wkGL4IORYiM7VHSHNU4u/dlgfQE1y0T+1CzXwquy4csvbBzBKnZ1o9ZBsOtWS ox0RaBsMD70mvTwKKmlCSD5HgZZTC0CfGWk4dQp/Mct5Z0x0HJMEJCJzpgTn3CRX z8CjezfckLs7UKJOlhu3OU9TFsiGDzSDBZdDWO1/uciJ/AAWeSmsBt8cKL0MirIr c4wOvhbalcX0FqTM3mXCgMFRbibquhwdxbU= -----END CERTIFICATE-----` key, err := jwk.ParseKey([]byte(src), jwk.WithPEM(true)) require.NoError(t, err, `jwk.ParseKey should succeed`) require.Equal(t, jwa.RSA(), key.KeyType(), `key type should be RSA`) var pubkey rsa.PublicKey require.NoError(t, jwk.Export(key, &pubkey), `key.Raw should succeed`) N := &big.Int{} N, _ = N.SetString(`779390807991489150242580488277564408218067197694419403671246387831173881192316375931050469298375090533614189460270485948672580508192398132571230359681952349714254730569052029178325305344289615160181016909374016900403698428293142159695593998453788610098596363011884623801134548926432366560975619087466760747503535615491182090094278093592303467050094984372887804234341012289019841973178427045121609424191835554013017436743418746919496835541323790719629313070434897002108079086472354410640690933161025543816362962891190753195691593288890628966181309776957070655619665306995097798188588453327627252794498823229009195585001242181503742627414517186199717150645163224325403559815442522031412813762764879089624715721999552786759649849125487587658121901233329199571710176245013452847516179837767710027433169340850618643815395642568876192931279303797384539146396956216244189819533317558165234451499206045369678277987397913889177569796721689284116762473340601498426367267765652880247655009239893325078809797979771964770948333084772104541394544131668212262901583064272659565503500144472388676955404823979083054620299811247635425415371418720649368570747531327436083928369741631909855731133100553629456091216238379430154237251461586878393695925917`, 10) require.Equal(t, N, pubkey.N, `value for N should match`) require.Equal(t, 65537, pubkey.E, `value for E should amtch`) } type typedField struct { Foo string Bar int64 } func TestTypedFields(t *testing.T) { expected := &typedField{Foo: "Foo", Bar: 0xdeadbeef} var keys []jwk.Key { k1, e1 := jwxtest.GenerateRsaJwk() require.NoError(t, e1, `jwxtest.GenerateRsaJwk should succeed`) k2, e2 := jwxtest.GenerateEcdsaJwk() require.NoError(t, e2, `jwxtest.GenerateEcdsaJwk should succeed`) k3, e3 := jwxtest.GenerateSymmetricJwk() require.NoError(t, e3, `jwxtest.GenerateSymmetricJwk should succeed`) k4, e4 := jwxtest.GenerateEd25519Jwk() require.NoError(t, e4, `jwxtest.GenerateEd25519Jwk should succeed`) keys = []jwk.Key{k1, k2, k3, k4} } for _, key := range keys { key.Set("typed-field", expected) } testcases := []struct { Name string Options []jwk.ParseOption PostProcess func(*testing.T, any) (*typedField, error) }{ { Name: "Basic", Options: []jwk.ParseOption{jwk.WithTypedField("typed-field", typedField{})}, PostProcess: func(t *testing.T, field any) (*typedField, error) { t.Helper() v, ok := field.(typedField) if !ok { return nil, fmt.Errorf(`field value should be of type "typedField", but got %T`, field) } return &v, nil }, }, { Name: "json.RawMessage", Options: []jwk.ParseOption{jwk.WithTypedField("typed-field", json.RawMessage{})}, PostProcess: func(t *testing.T, field any) (*typedField, error) { t.Helper() v, ok := field.(json.RawMessage) if !ok { return nil, fmt.Errorf(`field value should be of type "json.RawMessage", but got %T`, field) } var c typedField if err := json.Unmarshal(v, &c); err != nil { return nil, fmt.Errorf(`json.Unmarshal failed: %w`, err) } return &c, nil }, }, } for _, key := range keys { serialized, err := json.Marshal(key) require.NoError(t, err, `json.Marshal should succeed`) t.Run(fmt.Sprintf("%T", key), func(t *testing.T) { for _, tc := range testcases { t.Run(tc.Name, func(t *testing.T) { got, err := jwk.ParseKey(serialized, tc.Options...) require.NoError(t, err, `jwk.Parse should succeed`) var v any require.NoError(t, got.Get("typed-field", &v), `got.Get() should succeed`) field, err := tc.PostProcess(t, v) require.NoError(t, err, `tc.PostProcess should succeed`) require.Equal(t, field, expected, `field should match expected value`) }) } }) } t.Run("Set", func(t *testing.T) { s := jwk.NewSet() for _, key := range keys { s.AddKey(key) } serialized, err := json.Marshal(s) require.NoError(t, err, `json.Marshal should succeed`) for _, tc := range testcases { t.Run(tc.Name, func(t *testing.T) { got, err := jwk.Parse(serialized, tc.Options...) require.NoError(t, err, `jwk.Parse should succeed`) for i := range got.Len() { key, ok := got.Key(i) require.True(t, ok, `got.Key() should succeed`) var v any require.NoError(t, key.Get("typed-field", &v), `key.Get() should succeed`) field, err := tc.PostProcess(t, v) require.NoError(t, err, `tc.PostProcess should succeed`) require.Equal(t, field, expected, `field should match expected value`) } }) } }) } func TestGH412(t *testing.T) { base := jwk.NewSet() const iterations = 5 kids := make(map[string]struct{}) for i := range iterations { k, err := jwxtest.GenerateRsaJwk() require.NoError(t, err, `jwxttest.GenerateRsaJwk() should succeed`) kid := "key-" + strconv.Itoa(i) k.Set(jwk.KeyIDKey, kid) base.AddKey(k) kids[kid] = struct{}{} } for i := range iterations { idx := i currentKid := "key-" + strconv.Itoa(i) t.Run(fmt.Sprintf("Remove at position %d", i), func(t *testing.T) { set, err := base.Clone() require.NoError(t, err, `base.Clone() should succeed`) require.Equal(t, iterations, set.Len(), `set.Len should be %d`, iterations) k, ok := set.Key(idx) require.True(t, ok, `set.Get should succeed`) require.NoError(t, set.RemoveKey(k), `set.Remove should succeed`) kid, ok := k.KeyID() require.True(t, ok, `k.KeyID should succeed`) t.Logf("deleted key %s", kid) require.Equal(t, iterations-1, set.Len(), `set.Len should be %d`, iterations-1) expected := make(map[string]struct{}) for k := range kids { if k == currentKid { continue } expected[k] = struct{}{} } for i := range set.Len() { key, ok := set.Key(i) require.True(t, ok, `set.Key() should succeed`) gotkid, ok := key.KeyID() require.True(t, ok, `key.KeyID should succeed`) require.NotEqual(t, kid, gotkid, `key id should not match`) delete(expected, gotkid) } require.Len(t, expected, 0, `expected map should be empty`) }) } } func TestGH491(t *testing.T) { msg := `{"keys":[{"alg":"ECMR","crv":"P-521","key_ops":["deriveKey"],"kty":"EC","x":"AEFldixpd6xWI1rPigk_i_fW_9SLXh3q3h_CbmRIJ2vmnneWnfylvg37q9_BeSxhLpTQkq580tP-7QiOoNem4ubg","y":"AD8MroFIWQI4nm1rVKOb0ImO0Y7EzPt1HTQfZxagv2IoMez8H_vV7Ra9fU7lJhoe3v-Th6x3-4540FodeIxxiphn"},{"alg":"ES512","crv":"P-521","key_ops":["verify"],"kty":"EC","x":"AFZApUzXzvjVJCZQX1De3LUudI7fiWZcZS3t4F2yrxn0tItCYIZrfygPiCZfV1hVKa3WuH2YMrISZUPrSgi_RN2d","y":"ASEyw-_9xcwNBnvpT7thmAF5qHv9-UPYf38AC7y5QBVejQH_DO1xpKzlTbrHCz0jrMeEir8TyW5ywZIYnqGzPBpn"}]}` keys, err := jwk.Parse([]byte(msg)) require.NoError(t, err, `jwk.Parse should succeed`) // there should be 2 keys , get the first key k, _ := keys.Key(0) ops, ok := k.KeyOps() require.True(t, ok, `k.KeyOps should succeed`) require.Equal(t, jwk.KeyOperationList{jwk.KeyOpDeriveKey}, ops, `k.KeyOps should match`) } func TestSetWithPrivateParams(t *testing.T) { k1, err := jwxtest.GenerateRsaJwk() require.NoError(t, err, `jwxtest.GenerateRsaJwk should succeed`) k2, err := jwxtest.GenerateEcdsaJwk() require.NoError(t, err, `jwxtest.GenerateEcdsaJwk should succeed`) k3, err := jwxtest.GenerateSymmetricJwk() require.NoError(t, err, `jwxtest.GenerateSymmetricJwk should succeed`) t.Run("JWK instead of JWKS", func(t *testing.T) { var buf bytes.Buffer _ = k1.Set(`renewal_kid`, "foo") _ = json.NewEncoder(&buf).Encode(k1) var check = func(t *testing.T, buf []byte) { set, err := jwk.Parse(buf) require.NoError(t, err, `jwk.Parse should succeed`) require.Equal(t, 1, set.Len(), `set.Len() should be 1`) var kid string require.NoError(t, set.Get(`renewal_kid`, &kid), `set.Get("renewal_kid") should succeed`) require.Equal(t, `foo`, kid, `set.Get("renewal_kid") should return "foo"`) key, ok := set.Key(0) require.True(t, ok, `set.Key(0) should return ok = true`) kid = "" require.NoError(t, key.Get(`renewal_kid`, &kid), `key.Get("renewal_kid") should return ok = true`) require.Equal(t, `foo`, kid, `key.Get("renewal_kid") should return "foo"`) } t.Run("Check original buffer", func(t *testing.T) { check(t, buf.Bytes()) }) t.Run("Check serialized", func(t *testing.T) { set, err := jwk.Parse(buf.Bytes()) require.NoError(t, err, `jwk.Parse should succeed`) js, err := json.MarshalIndent(set, "", " ") require.NoError(t, err, `json.MarshalIndent should succeed`) check(t, js) }) }) t.Run("JWKS with multiple keys", func(t *testing.T) { var buf bytes.Buffer buf.WriteString(`{"renewal_kid":"foo","keys":[`) enc := json.NewEncoder(&buf) _ = enc.Encode(k1) buf.WriteByte(tokens.Comma) _ = enc.Encode(k2) buf.WriteByte(tokens.Comma) _ = enc.Encode(k3) buf.WriteString(`]}`) var check = func(t *testing.T, buf []byte) { set, err := jwk.Parse(buf) require.NoError(t, err, `jwk.Parse should succeed`) require.Equal(t, 3, set.Len(), `set.Len() should be 3`) var v any require.NoError(t, set.Get(`renewal_kid`, &v), `set.Get("renewal_kid") should return ok = true`) require.Equal(t, `foo`, v, `set.Get("renewal_kid") should return "foo"`) } t.Run("Check original buffer", func(t *testing.T) { check(t, buf.Bytes()) }) t.Run("Check serialized", func(t *testing.T) { set, err := jwk.Parse(buf.Bytes()) require.NoError(t, err, `jwk.Parse should succeed`) js, err := json.MarshalIndent(set, "", " ") require.NoError(t, err, `json.MarshalIndent should succeed`) check(t, js) }) }) t.Run("Set private parameters", func(t *testing.T) { set := jwk.NewSet() require.NoError(t, set.Set(`renewal_kid`, `foo`), `set.Set should succeed`) var v any require.NoError(t, set.Get(`renewal_kid`, &v), `set.Get("renewal_kid") should succeed`) require.Equal(t, `foo`, v, `set.Get("renewal_kid") should return "foo"`) require.Error(t, set.Set(`keys`, []string{"foo"}), `set.Set should fail`) k, err := jwk.Import([]byte("foobar")) require.NoError(t, err, `jwk.Import should succeed`) keys := []jwk.Key{k} require.NoError(t, set.Set(`keys`, keys), `set.Set should succeed`) require.Equal(t, set.Len(), 1, `set should have 1 key`) }) } type DummyRoundTripper struct{} func (t *DummyRoundTripper) RoundTrip(_ *http.Request) (*http.Response, error) { return &http.Response{ StatusCode: http.StatusTeapot, Body: io.NopCloser(bytes.NewReader([]byte(nil))), }, nil } func TestFetch(t *testing.T) { k1, err := jwxtest.GenerateRsaJwk() require.NoError(t, err, `jwxtest.GenerateRsaJwk should succeed`) k2, err := jwxtest.GenerateEcdsaJwk() require.NoError(t, err, `jwxtest.GenerateEcdsaJwk should succeed`) k3, err := jwxtest.GenerateSymmetricJwk() require.NoError(t, err, `jwxtest.GenerateSymmetricJwk should succeed`) set := jwk.NewSet() set.AddKey(k1) set.AddKey(k2) set.AddKey(k3) expected, err := json.MarshalIndent(set, "", " ") require.NoError(t, err, `json.MarshalIndent should succeed`) srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusOK) w.Write(expected) })) // 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. // // defer goleak.VerifyNone(t) defer srv.Close() t.Run("HTTPClient", func(t *testing.T) { ctx := t.Context() client := &http.Client{ Transport: &DummyRoundTripper{}, } _, err := jwk.Fetch(ctx, `https://www.googleapis.com/oauth2/v3/certs`, jwk.WithHTTPClient(client)) require.Error(t, err, `jwk.Fetch should fail`) require.Contains(t, err.Error(), `418`, `error should contain 418`) }) t.Run("Whitelist", func(t *testing.T) { testcases := []struct { Name string Whitelist func() jwk.Whitelist Error bool }{ { Name: `InsecureWhitelist`, Whitelist: func() jwk.Whitelist { return jwk.InsecureWhitelist{} }, }, { Name: `MapWhitelist`, Error: true, Whitelist: func() jwk.Whitelist { return jwk.NewMapWhitelist(). Add(`https://www.googleapis.com/oauth2/v3/certs`). Add(srv.URL) }, }, { Name: `RegexpWhitelist`, Error: true, Whitelist: func() jwk.Whitelist { return jwk.NewRegexpWhitelist(). Add(regexp.MustCompile(regexp.QuoteMeta(srv.URL))) }, }, { Name: `WhitelistFunc`, Error: true, Whitelist: func() jwk.Whitelist { return jwk.WhitelistFunc(func(s string) bool { return s == srv.URL }) }, }, } for _, tc := range testcases { t.Run(tc.Name, func(t *testing.T) { ctx := t.Context() wl := tc.Whitelist() _, err = jwk.Fetch(ctx, `https://github.com/lestrrat-go/jwx/`, jwk.WithFetchWhitelist(wl)) if tc.Error { require.Error(t, err, `jwk.Fetch should fail`) require.True(t, strings.Contains(err.Error(), `rejected by whitelist`), `error should be whitelist error`) } fetched, err := jwk.Fetch(ctx, srv.URL, jwk.WithFetchWhitelist(wl)) require.NoError(t, err, `jwk.Fetch should succeed`) got, err := json.MarshalIndent(fetched, "", " ") require.NoError(t, err, `json.MarshalIndent should succeed`) require.Equal(t, expected, got, `data should match`) }) } }) } func TestGH567(t *testing.T) { const src = `{ "keys": [ { "kty": "RSA", "use": "sig", "kid": "20595A4BE9F566771792BC3DBC7DF78FF9C36575", "x5t": "20595A4BE9F566771792BC3DBC7DF78FF9C36575", "e": "AQAB", "n": "tAN2xCfMuGpZukiGJl_-aQi_HGd4voyEwuOyL79wZphgtAmMAeOEO9QgxSX00ZczonlOm_I1Xpv2RVnNzSiHfB0bTqn4bLt15JVCBhE1vXaRf63QXn5oZ38fxm_aNctfnmkf65sF3lSzcZmfp1934L1KxJObq4BEOpIxvj00gIOpZQ4Mqw1khfsLhIVeXh8xtiEJwQZPdwIUQD03Yt5XQ_QU3NhxmyXiG8c6auOstdZybbGw10uJQEN4PrW0ESvp_GMnLssYrq6x9PhyvJhZhMFX3rBsYhOI7ILMaqo-QeDYUo0lQ1ENoQFyvtWrNQ_6A-CbmJvL9HdN6AuMkujtUmZzEfbT-k3FRyaZL0JE4-yQikdPGHVK6Q2Ho_Zggx2OTNmLbEORBHNe8cbb7t_5fmK6Fk4TpW3795PR8dG-v-AUGpQEgipg5j-3ONxefyBVZyWyjXaxrhQk6nCeRKcXJ0dKiZQX6ykYrtkgwE3mlcuw9-WzUqvHVEMZgAqBhBhL", "x5c": [ "MIIGpDCCBYygAwIBAgIEX5xBDDANBgkqhkiG9w0BAQsFADBJMQswCQYDVQQGEwJESzESMBAGA1UECgwJVFJVU1QyNDA4MSYwJAYDVQQDDB1UUlVTVDI0MDggU3lzdGVtdGVzdCBYWFhJViBDQTAeFw0yMTA0MjYxMjI1NDBaFw0yNDA0MjYxMTM5NDBaMIGNMQswCQYDVQQGEwJESzEsMCoGA1UECgwjU0lHTkFUVVJHUlVQUEVOIEEvUyAvLyBDVlI6Mjk5MTU5MzgxUDAgBgNVBAUTGUNWUjoyOTkxNTkzOC1VSUQ6NTk5MTEyMjcwLAYDVQQDDCVTSUdOQVRVUkdSVVBQRU4gQS9TIC0gTkVCIFRyYW5zYWN0IFBQMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAtAN2xCfMuGpZukiGJl/\u002BaQi/HGd4voyEwuOyL79wZphgtAmMAeOEO9QgxSX00ZczonlOm/I1Xpv2RVnNzSiHfB0bTqn4bLt15JVCBhE1vXaRf63QXn5oZ38fxm/aNctfnmkf65sF3lSzcZmfp1934L1KxJObq4BEOpIxvj00gIOpZQ4Mqw1khfsLhIVeXh8xtiEJwQZPdwIUQD03Yt5XQ/QU3NhxmyXiG8c6auOstdZybbGw10uJQEN4PrW0ESvp/GMnLssYrq6x9PhyvJhZhMFX3rBsYhOI7ILMaqo\u002BQeDYUo0lQ1ENoQFyvtWrNQ/6A\u002BCbmJvL9HdN6AuMkujtUmZzEfbT\u002Bk3FRyaZL0JE4\u002ByQikdPGHVK6Q2Ho/Zggx2OTNmLbEORBHNe8cbb7t/5fmK6Fk4TpW3795PR8dG\u002Bv\u002BAUGpQEgipg5j\u002B3ONxefyBVZyWyjXaxrhQk6nCeRKcXJ0dKiZQX6ykYrtkgwE3mlcuw9\u002BWzUqvHVEMZgAqBhBhLAgMBAAGjggLNMIICyTAOBgNVHQ8BAf8EBAMCA7gwgZcGCCsGAQUFBwEBBIGKMIGHMDwGCCsGAQUFBzABhjBodHRwOi8vb2NzcC5zeXN0ZW10ZXN0MzQudHJ1c3QyNDA4LmNvbS9yZXNwb25kZXIwRwYIKwYBBQUHMAKGO2h0dHA6Ly92LmFpYS5zeXN0ZW10ZXN0MzQudHJ1c3QyNDA4LmNvbS9zeXN0ZW10ZXN0MzQtY2EuY2VyMIIBIAYDVR0gBIIBFzCCARMwggEPBg0rBgEEAYH0UQIEBgMFMIH9MC8GCCsGAQUFBwIBFiNodHRwOi8vd3d3LnRydXN0MjQwOC5jb20vcmVwb3NpdG9yeTCByQYIKwYBBQUHAgIwgbwwDBYFRGFuSUQwAwIBARqBq0RhbklEIHRlc3QgY2VydGlmaWthdGVyIGZyYSBkZW5uZSBDQSB1ZHN0ZWRlcyB1bmRlciBPSUQgMS4zLjYuMS40LjEuMzEzMTMuMi40LjYuMy41LiBEYW5JRCB0ZXN0IGNlcnRpZmljYXRlcyBmcm9tIHRoaXMgQ0EgYXJlIGlzc3VlZCB1bmRlciBPSUQgMS4zLjYuMS40LjEuMzEzMTMuMi40LjYuMy41LjCBrQYDVR0fBIGlMIGiMDygOqA4hjZodHRwOi8vY3JsLnN5c3RlbXRlc3QzNC50cnVzdDI0MDguY29tL3N5c3RlbXRlc3QzNC5jcmwwYqBgoF6kXDBaMQswCQYDVQQGEwJESzESMBAGA1UECgwJVFJVU1QyNDA4MSYwJAYDVQQDDB1UUlVTVDI0MDggU3lzdGVtdGVzdCBYWFhJViBDQTEPMA0GA1UEAwwGQ1JMMTUwMB8GA1UdIwQYMBaAFM1saJc5chmkNatk6vQRo4GH\u002BGk7MB0GA1UdDgQWBBSJJABtTjZRzzFHsb1JwED0qCo49TAJBgNVHRMEAjAAMA0GCSqGSIb3DQEBCwUAA4IBAQA2yBdGVPbKoYUYRZj4bHLj5Xwqk/yh6sodxwAPYrwxkJBzUKFAwUCTCigpwq8NE00kp3xhFT7Hz/9Z7aZLV4N/D94tc0qDU0JIwlbkrn/i3wx8N8UXf242WVaunJLdKqGtV4ijqjZFGa68XBc3elGjnAMY8eoF56dGg35Ps8Cw2BZyG2aXyEQNs6JYUNzp57\u002B6lJWl0T9Zniaut7Aw2rCII8XRe9WY\u002BHIQX6GuBSw0Q4v9wAfyYftnoULsgfVklkxtQI4kAO5rG17Z5NJuvRkXJD8jp\u002B2jRzcaD8Ud\u002Bpe4keTvuJZ\u002BRj\u002BBDLn/3\u002ByPbs3arD3CIO\u002BlW3Nndr34Le/s" ], "alg": "RS256" }, { "kty": "RSA", "use": "sig", "kid": "048058BB59F4D3007045896FD488CE81F4EB4923", "x5t": "048058BB59F4D3007045896FD488CE81F4EB4923", "e": "AQAB", "n": "4bOwMSWoWqOSJoLvwFOCVrKmIO_XX5BCI8KYDIgWjII3a83vwia0a11UHm3B6oPlR3L5udworbH7axrmnPz4GEamQp57Yf0uhnGctlVpVVZHOvXPaMlZgTTGhWpJAGnLnyihZbARgyJefuxZ6ZIqeNyjgc_fC-0J7RWFMxNKS_n6ZKFqQlIlmJInJPWR-YZTuooIb4T4C0JAwFDEvXiAs_fX34Tj1FvD1nv01VPGF5Wx6cBV6fejbRCjY4uFfovhE-dtKX0IakZI8jks-uqMjIOB2x1pOuaqrmrINrTYzKTCKrnMpfaW4urhFmQRKNIgaoLnPgzIb3W9F-vpgrdTjw", "x5c": [ "MIID/DCCAeSgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBBMQswCQYDVQQGEwJESzEyMDAGA1UEAxMpTmV0cyBlSUQgQnJva2VyIFRva2VuIFNpZ25pbmcgUm9vdCBQUCBFbnYwHhcNMjEwMjI0MDAwMDAwWhcNNDEwMjI0MDAwMDAwWjA\u002BMQswCQYDVQQGEwJESzEvMC0GA1UEAxMmTmV0cyBlSUQgQnJva2VyIFRva2VuIFNpZ25pbmcgMSBQUCBFbnYwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDhs7AxJahao5Imgu/AU4JWsqYg79dfkEIjwpgMiBaMgjdrze/CJrRrXVQebcHqg\u002BVHcvm53CitsftrGuac/PgYRqZCnnth/S6GcZy2VWlVVkc69c9oyVmBNMaFakkAacufKKFlsBGDIl5\u002B7Fnpkip43KOBz98L7QntFYUzE0pL\u002BfpkoWpCUiWYkick9ZH5hlO6ighvhPgLQkDAUMS9eICz99ffhOPUW8PWe/TVU8YXlbHpwFXp96NtEKNji4V\u002Bi\u002BET520pfQhqRkjyOSz66oyMg4HbHWk65qquasg2tNjMpMIqucyl9pbi6uEWZBEo0iBqguc\u002BDMhvdb0X6\u002BmCt1OPAgMBAAGjAjAAMA0GCSqGSIb3DQEBCwUAA4ICAQAFKs2PbOIHLgLTNPIhqKCv7Uj\u002Buj0tpF6vO7CFtw1Q0NKo0UB4wD0T9AYu\u002B8sq3M\u002BZjR77eHP6IkvVBEaqBrZgjq6wU1pijPuIUliXF772\u002B/Hr8Wa23iILWevk\u002BleOXDI8kN7E1JPfr0ADsnCJxaXApDqE6Mysd68\u002BGN2adRuvGAcEcKauVfYLAGPQVctkmQSH6VhIJoDKni7gFF/oMZUZ362sOguhlYltcNUMIILJZkFkksRRriHOlz4I8HJiTzWI1Ufuw6iqFWMquOR4BsQZzBSsdPVGlQvjyqOiBia7rPTJE1Z3Kj0mujIbgKTri8YsnFsBynyHq8puYWvMwoGLWu0goxq9rFrINTe39/YpRE6lZUUZU50DddS\u002B0syBTs1H1gX00ofqt6FgWmACc20zJZm2GyhWDtqtiMurn5WKLoZBQrwN5/a6c6HNCStSVxn8o0g9xCgmWM855S8TqHFYXKJSMG00xZZEbsOAPqujkbKakhC/kJQU7XKGjFRskQZhHvpGipFTK4ZapHYYoo5KqZTytAvFENIcuibIk0u8zlCZuXU/PsowMN4G54FftVVyNHuj4TqiKIvB\u002BZNj/zcPopQHHUISVRApR6YO6fqwPxVaJmSTzZ/0uPTaAdnMz5j1wIYf\u002BZDk1ywTxOBRS7/FwNnWAyIuYGFzewY4H2QUNg==" ], "alg": "RS256" }, { "kty": "RSA", "use": "enc", "kid": "A2E10A6BAF4E43E86273F57F218A44B824203176", "x5t": "A2E10A6BAF4E43E86273F57F218A44B824203176", "x5c": [ "MIIDGjCCAgKgAwIBAgIIQFkx7rDLvyowDQYJKoZIhvcNAQELBQAwNDEyMDAGA1UEAxMpTmV0cyBlSUQgQnJva2VyIGNsaWVudCByZXF1ZXN0IGVuY3J5cHRpb24wIBcNMjIwMjA5MTQxMDQ1WhgPMjExMjAyMTAxNDEwNDVaMDQxMjAwBgNVBAMTKU5ldHMgZUlEIEJyb2tlciBjbGllbnQgcmVxdWVzdCBlbmNyeXB0aW9uMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAps2R1sxEGxyYfP\u002BK2jq4uDONxrEP4W4oAKJkTv71oOJiK3CBK4ybyk597WufUZfpy8isUKtuqx1x6DTK6vnecUv5KV77/ql6Ac8LJaj5zQBRGuPoPezbD2tPglxp4XEU\u002BqivoyMJiZoLl75MklxvmVVlJQgu1H4RmvdCcp0xYmV2CBX4GKvK6g26y3IZ1FW4Vj5G8eB\u002Bxw423EtsBO3iKPx24BbVZWXC56lhke\u002BQbpbagU/hpQnOdW5tdWdm7oHGUfGwsvg1f6b2Yllwv57ANJkk\u002BfBr\u002BoN0eD2DRNyHbExzJfOBPkDt2FEq1u30kfLP\u002B6ecSByaxSFTl\u002BgFeUUw0wIDAQABoy4wLDALBgNVHQ8EBAMCBBAwHQYDVR0OBBYEFPK08YkmSYK1xNIFEr1AdscfYKnZMA0GCSqGSIb3DQEBCwUAA4IBAQAYeP7IAv3ND\u002B6UMGr9X\u002BP1wURz7UQd66oRldhcdkdS\u002BBNMcU/gVeiU31Es5Y/GhdmKPiuQGdIQdPO88u9A7STWIYUj/lnrgtKif\u002BJ8V/PtfsvHbBYD5f7wFd7fqOpcDFQ2dobOathhrqJ1r3ShFaObpVBX3PL3\u002BSFK3ofHaMYWuIoD\u002BroiOJfIYlL02rrKiiw9r2L9nUCZfSAq3G9rMw\u002BzL38D9BQvrEe9yvwqM3im0m3seiNODiArcY/ee\u002B538\u002BYaToaMPHxUAizREeJFm4aOtwzGND48XHQzbNyfhoSsJesCJsugcNfHUe6o0nuPZ\u002BzvdLrboutrrtxEN8yOm489" ], "alg": "http://www.w3.org/2001/04/xmlenc#rsa-oaep" } ] }` srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.Header().Set(`Content-Type`, `application/json`) w.WriteHeader(http.StatusOK) io.WriteString(w, src) })) defer srv.Close() for _, ignoreParseError := range []bool{true, false} { t.Run(fmt.Sprintf(`Parse with ignoreParseError=%t`, ignoreParseError), func(t *testing.T) { ctx := t.Context() c, err := jwk.NewCache(ctx, httprc.NewClient()) require.NoError(t, err, `jwk.NewCache should succeed`) ctx2, cancel2 := context.WithTimeout(ctx, 1*time.Second) defer cancel2() err = c.Register(ctx2, srv.URL, jwk.WithIgnoreParseError(ignoreParseError)) if ignoreParseError { require.NoError(t, err, `c.Register should succeed`) } else { require.Error(t, err, `c.Register should fail`) } set, err := c.Lookup(ctx, srv.URL) if ignoreParseError { require.NoError(t, err, `c.Fetch should succeed`) require.NotNil(t, set, `c.Fetch should return a set`) require.Equal(t, set.Len(), 2, `JWKS should contain two keys`) } else { require.Error(t, err, `c.Fetch should fail`) } }) } // Test the case when WithIgnoreParseError is passed to ParseKey t.Run(`ParseKey + WithIgnoreParseError should be an error`, func(t *testing.T) { key, err := jwxtest.GenerateRsaJwk() require.NoError(t, err, `jwxtest.GenerateRsaJwk() should succeed`) buf, err := json.Marshal(key) require.NoError(t, err, `json.Marshal should succeed`) _, err = jwk.ParseKey(buf) require.NoError(t, err, `jwk.ParseKey (no WithIgnoreParseError) should succeed`) _, err = jwk.ParseKey(buf, jwk.WithIgnoreParseError(true)) require.Error(t, err, `jwk.ParseKey (no WithIgnoreParseError) should fail`) }) } // This test existed to test if we can handle it when the user nukes // the private keys' precomputed values. But as of go1.24 the values // are validated by the crypto/rsa package, so we just let crypto/rsa // Do The Right Thing, and not deal with it. This test is commented out // for the time being; we should remove it once we no longer support // any of the Go versions that _dont_ validate these values. /* func TestGH664(t *testing.T) { privkey, err := jwxtest.GenerateRsaKey() require.NoError(t, err, `jwxtext.GenerateRsaKey() should succeed`) // first, test a stupid case where Primes > 2 privkey.Primes = append(privkey.Primes, &big.Int{}) _, err = jwk.Import(privkey) require.Error(t, err, `jwk.Import should fail`) privkey.Primes = privkey.Primes[:2] // nuke p and q, dp, dq, qi for i := range 3 { t.Run(fmt.Sprintf("Check what happens when primes are reduced to %d", i), func(t *testing.T) { privkey.Primes = privkey.Primes[:i] privkey.Precomputed.Dp = nil privkey.Precomputed.Dq = nil privkey.Precomputed.Qinv = nil privkey.Precomputed.CRTValues = nil jwkPrivkey, err := jwk.Import(privkey) require.NoError(t, err, `jwk.Import should succeed`) buf, _ := json.MarshalIndent(jwkPrivkey, "", " ") parsed, err := jwk.ParseKey(buf) require.NoError(t, err, `jwk.ParseKey should succeed`) payload := []byte(`hello , world!`) signed, err := jws.Sign(payload, jws.WithKey(jwa.RS256(), parsed)) require.NoError(t, err, `jws.Sign should succeed`) verified, err := jws.Verify(signed, jws.WithKey(jwa.RS256(), privkey.PublicKey)) require.NoError(t, err, `jws.Verify should succeed`) require.Equal(t, payload, verified, `verified content should match`) }) } } */ func TestGH730(t *testing.T) { key, err := jwk.Import([]byte(`abracadabra`)) require.NoError(t, err, `jwk.Import should succeed`) set := jwk.NewSet() require.NoError(t, set.AddKey(key), `first AddKey should succeed`) require.Error(t, set.AddKey(key), `second AddKey should fail`) } // This test was lifted from #875. See tests under Roundtrip/WithPEM(true) for other key types func TestECDSAPEM(t *testing.T) { // go make an EC key at https://mkjwk.org/ key, err := jwk.ParseKey([]byte(`{ "kty": "EC", "d": "zqYPTs5gMEwtidOqjlFJSk6L4BQSfhCJX6FTgbuuiE0", "crv": "P-256", "x": "AYwhwiE1hXWdfwu-HlBSsY5Chxycu-LyE6WsZ_w2DO4", "y": "zumemGclMFkimMsKMXlLdKYWtLle58e4N9hDPcN7lig" }`)) if err != nil { t.Fatal(err) } pem, err := jwk.EncodePEM(key) if err != nil { t.Fatal(err) } _, err = jwk.ParseKey(pem, jwk.WithPEM(true)) if err != nil { t.Fatal(err) } } func TestGH947(t *testing.T) { // AS OP described it. Below case will panic if the problem exists, raw := []byte(`{"crv":"Ed25519","d":"","x":"","kty":"OKP"}`) k, err := jwk.ParseKey(raw) require.NoError(t, err, `jwk.ParseKey should succeed`) var exported []byte require.Error(t, jwk.Export(k, &exported), `(okpkey).Raw with 0-length OKP key should fail`) } func TestValidation(t *testing.T) { { key, err := jwxtest.GenerateRsaJwk() require.NoError(t, err, `jwx.GenerateRsaJwk should succeed`) require.NoError(t, key.Validate(), `key.Validate should succeed (vanilla key)`) require.NoError(t, key.Set(jwk.RSADKey, []byte(nil)), `key.Set should succeed`) require.Error(t, key.Validate(), `key.Validate should fail`) } { key, err := jwxtest.GenerateEcdsaJwk() require.NoError(t, err, `jwx.GenerateEcdsaJwk should succeed`) require.NoError(t, key.Validate(), `key.Validate should succeed`) x, ok := key.(jwk.ECDSAPrivateKey).X() require.True(t, ok, `key.(jwk.ECDSAPrivateKey).X should succeed`) require.NoError(t, key.Set(jwk.ECDSAXKey, x[:len(x)/2]), `key.Set should succeed`) require.Error(t, key.Validate(), `key.Validate should fail`) require.NoError(t, key.Set(jwk.ECDSAXKey, x), `key.Set should succeed`) require.NoError(t, key.Validate(), `key.Validate should succeed`) require.NoError(t, key.Set(jwk.ECDSADKey, []byte(nil)), `key.Set should succeed`) require.Error(t, key.Validate(), `key.Validate should fail`) } { key, err := jwxtest.GenerateEd25519Jwk() require.NoError(t, err, `jwx.GenerateEd25519Jwk should succeed`) require.NoError(t, key.Validate(), `key.Validate should succeed`) x, ok := key.(jwk.OKPPrivateKey).X() require.True(t, ok, `key.(jwk.OKPPrivateKey).X should succeed`) require.NoError(t, key.Set(jwk.OKPXKey, []byte(nil)), `key.Set should succeed`) require.Error(t, key.Validate(), `key.Validate should fail`) require.NoError(t, key.Set(jwk.OKPXKey, x), `key.Set should succeed`) require.NoError(t, key.Validate(), `key.Validate should succeed`) require.NoError(t, key.Set(jwk.OKPDKey, []byte(nil)), `key.Set should succeed`) require.Error(t, key.Validate(), `key.Validate should fail`) } { key, err := jwxtest.GenerateSymmetricJwk() require.NoError(t, err, `jwx.GenerateSymmetricJwk should succeed`) require.NoError(t, key.Validate(), `key.Validate should succeed`) require.NoError(t, key.Set(jwk.SymmetricOctetsKey, []byte(nil)), `key.Set should succeed`) require.Error(t, key.Validate(), `key.Validate should fail`) } } func TestParse_fail(t *testing.T) { t.Parallel() t.Run(`malformed json`, func(t *testing.T) { t.Parallel() const src = `{blah}` t.Run("string", func(t *testing.T) { t.Parallel() _, err := jwk.ParseString(src) require.Error(t, err, `jwk.ParseString should fail`) require.ErrorIs(t, err, jwk.ParseError(), `error should be ParseError`) require.True(t, strings.HasPrefix(err.Error(), `jwk.ParseString: `)) }) t.Run("[]byte", func(t *testing.T) { t.Parallel() _, err := jwk.Parse([]byte(src)) require.Error(t, err, `jwk.Parse should fail`) require.ErrorIs(t, err, jwk.ParseError(), `error should be ParseError`) require.True(t, strings.HasPrefix(err.Error(), `jwk.Parse: `)) }) t.Run("io.Reader", func(t *testing.T) { t.Parallel() _, err := jwk.ParseReader(strings.NewReader(src)) require.Error(t, err, `jwk.ParseReader should fail`) require.ErrorIs(t, err, jwk.ParseError(), `error should be ParseError`) require.True(t, strings.HasPrefix(err.Error(), `jwk.ParseReader: `)) }) }) } func TestGH1262(t *testing.T) { t.Run("Updated Example test", func(t *testing.T) { keyCli, err := ecdh.P384().GenerateKey(rand.Reader) require.NoError(t, err, `ecdh.P384().GenerateKey should succeed`) jwkCliPriv, err := jwk.Import(keyCli) require.NoError(t, err, `jwk.Import should succeed`) _ = jwkCliPriv var rawCliPriv ecdh.PrivateKey require.NoError(t, jwk.Export(jwkCliPriv, &rawCliPriv), `jwk.Export should succeed`) pubCli := keyCli.PublicKey() // server is able to retrieve the pub key part of client keySrv, err := ecdh.P384().GenerateKey(rand.Reader) require.NoError(t, err, `ecdh.P384().GenerateKey should succeed`) jwkSrv, err := jwk.Import(keySrv.PublicKey()) require.NoError(t, err, `jwk.Import should succeed`) jwkBuf, err := json.Marshal(jwkSrv) require.NoError(t, err, `json.Marshal should succeed`) secretSrv, err := keySrv.ECDH(pubCli) require.NoError(t, err, `keySrv.ECDH should succeed`) _ = secretSrv // doing some non-standard encryption & response with encrypted data // client pubSrv := &ecdh.PublicKey{} jwkCli, err := jwk.ParseKey(jwkBuf) // extract jwkBuf require.NoError(t, err, `jwk.ParseKey should succeed`) require.NoError(t, jwk.Export(jwkCli, pubSrv), `jwk.Export should succeed`) secretCli, err := keyCli.ECDH(pubSrv) require.NoError(t, err, `keyCli.ECDH should succeed`) _ = secretCli // doing some non-standard encryption }) } // DirectEmbed embeds jwk.Key directly type DirectEmbed struct { jwk.Key } // IndirectEmbed embeds DirectEmbed which embeds jwk.Key type IndirectEmbed struct { DirectEmbed } // DoubleIndirectEmbed embeds IndirectEmbed which embeds DirectEmbed which embeds jwk.Key type DoubleIndirectEmbed struct { IndirectEmbed } func TestExportEmbeddedKey(t *testing.T) { t.Run("Direct Embed", func(t *testing.T) { // Create a RSA key rsaKey, err := jwxtest.GenerateRsaJwk() require.NoError(t, err, "jwxtest.GenerateRsaJwk should succeed") // Create a direct embedding directEmbed := &DirectEmbed{Key: rsaKey} // Export the key from the direct embedding var rawKey rsa.PrivateKey err = jwk.Export(directEmbed, &rawKey) require.NoError(t, err, "jwk.Export should succeed with direct embed") }) t.Run("Indirect Embed", func(t *testing.T) { // Create a RSA key rsaKey, err := jwxtest.GenerateRsaJwk() require.NoError(t, err, "jwxtest.GenerateRsaJwk should succeed") // Create an indirect embedding directEmbed := DirectEmbed{Key: rsaKey} indirectEmbed := &IndirectEmbed{DirectEmbed: directEmbed} // Export the key from the indirect embedding var rawKey rsa.PrivateKey err = jwk.Export(indirectEmbed, &rawKey) if err != nil { t.Logf("Error: %s", err) } require.NoError(t, err, "jwk.Export should succeed with indirect embed") }) t.Run("Double Indirect Embed", func(t *testing.T) { // Create a RSA key rsaKey, err := jwxtest.GenerateRsaJwk() require.NoError(t, err, "jwxtest.GenerateRsaJwk should succeed") // Create a double indirect embedding directEmbed := DirectEmbed{Key: rsaKey} indirectEmbed := IndirectEmbed{DirectEmbed: directEmbed} doubleIndirectEmbed := &DoubleIndirectEmbed{IndirectEmbed: indirectEmbed} // Export the key from the double indirect embedding var rawKey rsa.PrivateKey err = jwk.Export(doubleIndirectEmbed, &rawKey) require.NoError(t, err, "jwk.Export should succeed with double indirect embed") }) } func TestWithPEMDecoder(t *testing.T) { // Generate a valid RSA private key privateKey, err := jwxtest.GenerateRsaKey() require.NoError(t, err) // Encode it to PEM format pemData, err := jwk.EncodePEM(privateKey) require.NoError(t, err) // Create a PEM decoder decoder := jwk.NewPEMDecoder() // Test that Parse with WithPEMDecoder works correctly parsedKey, err := jwk.Parse(pemData, jwk.WithPEM(true), jwk.WithPEMDecoder(decoder)) require.NoError(t, err, "Parse should succeed with valid PEM data and custom decoder") require.NotNil(t, parsedKey, "Parsed key should not be nil") } func TestRegisterX509Decoder(t *testing.T) { // Generate a real RSA key for testing testKey, err := jwxtest.GenerateRsaKey() require.NoError(t, err) // Create a custom decoder that handles a custom PEM type and returns the test key customIdent := "test-custom-decoder" customDecoder := jwk.X509DecodeFunc(func(dst any, block *pem.Block) error { if block.Type == "TEST CUSTOM KEY" { // Use blackmagic to assign the key to the destination return blackmagic.AssignIfCompatible(dst, testKey) } return fmt.Errorf("unsupported type or block") }) // Register the custom decoder jwk.RegisterX509Decoder(customIdent, customDecoder) // Create test PEM data with our custom type testPEMData := `-----BEGIN TEST CUSTOM KEY----- dGVzdCBkYXRh -----END TEST CUSTOM KEY-----` // Test that our custom decoder can handle this via ParseKey parsedKey, err := jwk.ParseKey([]byte(testPEMData), jwk.WithPEM(true)) require.NoError(t, err) require.NotNil(t, parsedKey) // Verify we get back a valid RSA key require.Equal(t, "RSA", parsedKey.KeyType().String()) // Clean up: unregister the decoder jwk.UnregisterX509Decoder(customIdent) } func TestUnregisterX509Decoder(t *testing.T) { // Generate a real RSA key for testing testKey, err := jwxtest.GenerateRsaKey() require.NoError(t, err) // Create a custom decoder customIdent := "test-unregister-decoder" customDecoder := jwk.X509DecodeFunc(func(dst any, block *pem.Block) error { if block.Type == "TEST UNREGISTER" { // Use blackmagic to assign the key to the destination return blackmagic.AssignIfCompatible(dst, testKey) } return fmt.Errorf("unsupported type or block") }) // Register the decoder jwk.RegisterX509Decoder(customIdent, customDecoder) // Create test PEM data testPEMData := `-----BEGIN TEST UNREGISTER----- dGVzdCBkYXRh -----END TEST UNREGISTER-----` // Verify it works when registered parsedKey1, err := jwk.ParseKey([]byte(testPEMData), jwk.WithPEM(true)) require.NoError(t, err) require.NotNil(t, parsedKey1) // Now unregister the decoder jwk.UnregisterX509Decoder(customIdent) // Verify it no longer works parsedKey2, err := jwk.ParseKey([]byte(testPEMData), jwk.WithPEM(true)) require.Error(t, err) require.Nil(t, parsedKey2) } func TestRegisterX509Decoder_NilPanic(t *testing.T) { // Test that registering nil decoder panics require.Panics(t, func() { jwk.RegisterX509Decoder("test", nil) }) } func TestRegisterX509Decoder_DuplicateRegistration(t *testing.T) { // Generate a test key for verification testKey, err := jwxtest.GenerateRsaKey() require.NoError(t, err) // Create a custom decoder that handles a specific PEM type customIdent := "test-duplicate-decoder" callCount := 0 decoder := jwk.X509DecodeFunc(func(dst any, block *pem.Block) error { callCount++ if block.Type == "TEST DUPLICATE" { return blackmagic.AssignIfCompatible(dst, testKey) } return fmt.Errorf("unsupported type") }) // Register the decoder jwk.RegisterX509Decoder(customIdent, decoder) // Create test PEM data testPEMData := `-----BEGIN TEST DUPLICATE----- dGVzdCBkYXRh -----END TEST DUPLICATE-----` // Verify it works after first registration parsedKey1, err := jwk.ParseKey([]byte(testPEMData), jwk.WithPEM(true)) require.NoError(t, err) require.NotNil(t, parsedKey1) require.Equal(t, 1, callCount, "Decoder should be called once") // Register it again (duplicate) - should be idempotent jwk.RegisterX509Decoder(customIdent, decoder) // Verify it still works after duplicate registration and decoder wasn't added twice parsedKey2, err := jwk.ParseKey([]byte(testPEMData), jwk.WithPEM(true)) require.NoError(t, err) require.NotNil(t, parsedKey2) require.Equal(t, 2, callCount, "Decoder should be called once more, not duplicated") // Clean up jwk.UnregisterX509Decoder(customIdent) } func TestUnregisterX509Decoder_NotRegistered(t *testing.T) { // Unregistering a non-existent decoder should be safe (no-op) require.NotPanics(t, func() { jwk.UnregisterX509Decoder("non-existent-decoder") }) } func TestGH1529(t *testing.T) { t.Run("Clone set with custom fields and modify", func(t *testing.T) { set1 := jwk.NewSet() require.NoError(t, set1.Set("foo", "bar"), `set1.Set should succeed`) set2, err := set1.Clone() require.NoError(t, err, `set1.Clone should succeed`) // Verify the custom field was copied during clone var v2Initial any require.NoError(t, set2.Get("foo", &v2Initial), `set2.Get should succeed after clone`) require.Equal(t, "bar", v2Initial, `set2 should have copied "foo" from set1`) // This should not error - cloned set should allow modifying the same field require.NoError(t, set2.Set("foo", "baz"), `set2.Set should succeed`) // Verify the sets are independent (modifying set2 doesn't affect set1) var v1, v2 any require.NoError(t, set1.Get("foo", &v1), `set1.Get should succeed`) require.NoError(t, set2.Get("foo", &v2), `set2.Get should succeed`) require.Equal(t, "bar", v1, `set1.Get("foo") should still return "bar"`) require.Equal(t, "baz", v2, `set2.Get("foo") should return "baz"`) }) } golang-github-lestrrat-go-jwx-3.0.13/jwk/jwkbb/000077500000000000000000000000001515060566400212765ustar00rootroot00000000000000golang-github-lestrrat-go-jwx-3.0.13/jwk/jwkbb/BUILD.bazel000066400000000000000000000006071515060566400231570ustar00rootroot00000000000000load("@rules_go//go:def.bzl", "go_library") go_library( name = "jwkbb", srcs = ["x509.go"], importpath = "github.com/lestrrat-go/jwx/v3/jwk/jwkbb", visibility = ["//visibility:public"], deps = [ "@com_github_lestrrat_go_blackmagic//:blackmagic", ], ) alias( name = "go_default_library", actual = ":jwkbb", visibility = ["//visibility:public"], )golang-github-lestrrat-go-jwx-3.0.13/jwk/jwkbb/x509.go000066400000000000000000000064501515060566400223370ustar00rootroot00000000000000package jwkbb import ( "crypto/ecdsa" "crypto/ed25519" "crypto/rsa" "crypto/x509" "encoding/pem" "fmt" "github.com/lestrrat-go/blackmagic" ) const ( PrivateKeyBlockType = `PRIVATE KEY` PublicKeyBlockType = `PUBLIC KEY` ECPrivateKeyBlockType = `EC PRIVATE KEY` RSAPublicKeyBlockType = `RSA PUBLIC KEY` RSAPrivateKeyBlockType = `RSA PRIVATE KEY` CertificateBlockType = `CERTIFICATE` ) // EncodeX509 encodes the given value into ASN.1 DER format, and returns // the encoded bytes. The value must be one of the following types: // *rsa.PrivateKey, *ecdsa.PrivateKey, ed25519.PrivateKey, // *rsa.PublicKey, *ecdsa.PublicKey, ed25519.PublicKey. // // Users can pass a pre-allocated byte slice (but make sure its length is // changed so that the encoded buffer is appended to the correct location) // as `dst` to avoid allocations. func EncodeX509(dst []byte, v any) ([]byte, error) { var block pem.Block // Try to convert it into a certificate switch v := v.(type) { case *rsa.PrivateKey: block.Type = RSAPrivateKeyBlockType block.Bytes = x509.MarshalPKCS1PrivateKey(v) case *ecdsa.PrivateKey: marshaled, err := x509.MarshalECPrivateKey(v) if err != nil { return nil, err } block.Type = ECPrivateKeyBlockType block.Bytes = marshaled case ed25519.PrivateKey: marshaled, err := x509.MarshalPKCS8PrivateKey(v) if err != nil { return nil, err } block.Type = PrivateKeyBlockType block.Bytes = marshaled case *rsa.PublicKey, *ecdsa.PublicKey, ed25519.PublicKey: marshaled, err := x509.MarshalPKIXPublicKey(v) if err != nil { return nil, err } block.Type = PublicKeyBlockType block.Bytes = marshaled default: return nil, fmt.Errorf(`unsupported type %T for ASN.1 DER encoding`, v) } encoded := pem.EncodeToMemory(&block) dst = append(dst, encoded...) return dst, nil } func DecodeX509(dst any, block *pem.Block) error { switch block.Type { // Handle the semi-obvious cases case RSAPrivateKeyBlockType: key, err := x509.ParsePKCS1PrivateKey(block.Bytes) if err != nil { return fmt.Errorf(`failed to parse PKCS1 private key: %w`, err) } return blackmagic.AssignIfCompatible(dst, key) case RSAPublicKeyBlockType: key, err := x509.ParsePKCS1PublicKey(block.Bytes) if err != nil { return fmt.Errorf(`failed to parse PKCS1 public key: %w`, err) } return blackmagic.AssignIfCompatible(dst, key) case ECPrivateKeyBlockType: key, err := x509.ParseECPrivateKey(block.Bytes) if err != nil { return fmt.Errorf(`failed to parse EC private key: %w`, err) } return blackmagic.AssignIfCompatible(dst, key) case PublicKeyBlockType: // XXX *could* return dsa.PublicKey key, err := x509.ParsePKIXPublicKey(block.Bytes) if err != nil { return fmt.Errorf(`failed to parse PKIX public key: %w`, err) } return blackmagic.AssignIfCompatible(dst, key) case PrivateKeyBlockType: key, err := x509.ParsePKCS8PrivateKey(block.Bytes) if err != nil { return fmt.Errorf(`failed to parse PKCS8 private key: %w`, err) } return blackmagic.AssignIfCompatible(dst, key) case CertificateBlockType: cert, err := x509.ParseCertificate(block.Bytes) if err != nil { return fmt.Errorf(`failed to parse certificate: %w`, err) } return blackmagic.AssignIfCompatible(dst, cert.PublicKey) default: return fmt.Errorf(`invalid PEM block type %s`, block.Type) } } golang-github-lestrrat-go-jwx-3.0.13/jwk/key_ops.go000066400000000000000000000024151515060566400222010ustar00rootroot00000000000000package jwk import "fmt" func (ops *KeyOperationList) Get() KeyOperationList { if ops == nil { return nil } return *ops } func (ops *KeyOperationList) Accept(v any) error { switch x := v.(type) { case string: return ops.Accept([]string{x}) case []any: l := make([]string, len(x)) for i, e := range x { if es, ok := e.(string); ok { l[i] = es } else { return fmt.Errorf(`invalid list element type: expected string, got %T`, v) } } return ops.Accept(l) case []string: list := make(KeyOperationList, len(x)) for i, e := range x { switch e := KeyOperation(e); e { case KeyOpSign, KeyOpVerify, KeyOpEncrypt, KeyOpDecrypt, KeyOpWrapKey, KeyOpUnwrapKey, KeyOpDeriveKey, KeyOpDeriveBits: list[i] = e default: return fmt.Errorf(`invalid keyoperation %v`, e) } } *ops = list return nil case []KeyOperation: list := make(KeyOperationList, len(x)) for i, e := range x { switch e { case KeyOpSign, KeyOpVerify, KeyOpEncrypt, KeyOpDecrypt, KeyOpWrapKey, KeyOpUnwrapKey, KeyOpDeriveKey, KeyOpDeriveBits: list[i] = e default: return fmt.Errorf(`invalid keyoperation %v`, e) } } *ops = list return nil case KeyOperationList: *ops = x return nil default: return fmt.Errorf(`invalid value %T`, v) } } golang-github-lestrrat-go-jwx-3.0.13/jwk/okp.go000066400000000000000000000172351515060566400213270ustar00rootroot00000000000000package jwk import ( "bytes" "crypto" "crypto/ecdh" "crypto/ed25519" "fmt" "reflect" "github.com/lestrrat-go/blackmagic" "github.com/lestrrat-go/jwx/v3/internal/base64" "github.com/lestrrat-go/jwx/v3/jwa" ) func init() { RegisterKeyExporter(jwa.OKP(), KeyExportFunc(okpJWKToRaw)) } // Mental note: // // Curve25519 refers to a particular curve, and is represented in its Montgomery form. // // Ed25519 refers to the biratinally equivalent curve of Curve25519, except it's in Edwards form. // Ed25519 is the name of the curve and the also the signature scheme using that curve. // The full name of the scheme is Edwards Curve Digital Signature Algorithm, and thus it is // also referred to as EdDSA. // // X25519 refers to the Diffie-Hellman key exchange protocol that uses Cruve25519. // Because this is an elliptic curve based Diffie Hellman protocol, it is also referred to // as ECDH. // // OKP keys are used to represent private/public pairs of thse elliptic curve // keys. But note that the name just means Octet Key Pair. func (k *okpPublicKey) Import(rawKeyIf any) error { k.mu.Lock() defer k.mu.Unlock() var crv jwa.EllipticCurveAlgorithm switch rawKey := rawKeyIf.(type) { case ed25519.PublicKey: k.x = rawKey crv = jwa.Ed25519() k.crv = &crv case *ecdh.PublicKey: k.x = rawKey.Bytes() crv = jwa.X25519() k.crv = &crv default: return fmt.Errorf(`unknown key type %T`, rawKeyIf) } return nil } func (k *okpPrivateKey) Import(rawKeyIf any) error { k.mu.Lock() defer k.mu.Unlock() var crv jwa.EllipticCurveAlgorithm switch rawKey := rawKeyIf.(type) { case ed25519.PrivateKey: k.d = rawKey.Seed() k.x = rawKey.Public().(ed25519.PublicKey) //nolint:forcetypeassert crv = jwa.Ed25519() k.crv = &crv case *ecdh.PrivateKey: // k.d = rawKey.Seed() k.d = rawKey.Bytes() k.x = rawKey.PublicKey().Bytes() crv = jwa.X25519() k.crv = &crv default: return fmt.Errorf(`unknown key type %T`, rawKeyIf) } return nil } func buildOKPPublicKey(alg jwa.EllipticCurveAlgorithm, xbuf []byte) (any, error) { switch alg { case jwa.Ed25519(): return ed25519.PublicKey(xbuf), nil case jwa.X25519(): ret, err := ecdh.X25519().NewPublicKey(xbuf) if err != nil { return nil, fmt.Errorf(`failed to parse x25519 public key %x (size %d): %w`, xbuf, len(xbuf), err) } return ret, nil default: return nil, fmt.Errorf(`invalid curve algorithm %s`, alg) } } // Raw returns the EC-DSA public key represented by this JWK func (k *okpPublicKey) Raw(v any) error { k.mu.RLock() defer k.mu.RUnlock() crv, ok := k.Crv() if !ok { return fmt.Errorf(`missing "crv" field`) } pubk, err := buildOKPPublicKey(crv, k.x) if err != nil { return fmt.Errorf(`jwk.OKPPublicKey: failed to build public key: %w`, err) } if err := blackmagic.AssignIfCompatible(v, pubk); err != nil { return fmt.Errorf(`jwk.OKPPublicKey: failed to assign to destination variable: %w`, err) } return nil } func buildOKPPrivateKey(alg jwa.EllipticCurveAlgorithm, xbuf []byte, dbuf []byte) (any, error) { if len(dbuf) == 0 { return nil, fmt.Errorf(`cannot use empty seed`) } switch alg { case jwa.Ed25519(): if len(dbuf) != ed25519.SeedSize { return nil, fmt.Errorf(`ed25519: wrong private key size`) } ret := ed25519.NewKeyFromSeed(dbuf) //nolint:forcetypeassert if !bytes.Equal(xbuf, ret.Public().(ed25519.PublicKey)) { return nil, fmt.Errorf(`ed25519: invalid x value given d value`) } return ret, nil case jwa.X25519(): ret, err := ecdh.X25519().NewPrivateKey(dbuf) if err != nil { return nil, fmt.Errorf(`x25519: unable to construct x25519 private key from seed: %w`, err) } return ret, nil default: return nil, fmt.Errorf(`invalid curve algorithm %s`, alg) } } var okpConvertibleKeys = []reflect.Type{ reflect.TypeFor[OKPPrivateKey](), reflect.TypeFor[OKPPublicKey](), } // This is half baked. I think it will blow up if we used ecdh.* keys and/or x25519 keys func okpJWKToRaw(key Key, _ any /* this is unused because this is half baked */) (any, error) { extracted, err := extractEmbeddedKey(key, okpConvertibleKeys) if err != nil { return nil, fmt.Errorf(`jwk.OKP: failed to extract embedded key: %w`, err) } switch key := extracted.(type) { case OKPPrivateKey: locker, ok := key.(rlocker) if ok { locker.rlock() defer locker.runlock() } crv, ok := key.Crv() if !ok { return nil, fmt.Errorf(`missing "crv" field`) } x, ok := key.X() if !ok { return nil, fmt.Errorf(`missing "x" field`) } d, ok := key.D() if !ok { return nil, fmt.Errorf(`missing "d" field`) } privk, err := buildOKPPrivateKey(crv, x, d) if err != nil { return nil, fmt.Errorf(`jwk.OKPPrivateKey: failed to build private key: %w`, err) } return privk, nil case OKPPublicKey: locker, ok := key.(rlocker) if ok { locker.rlock() defer locker.runlock() } crv, ok := key.Crv() if !ok { return nil, fmt.Errorf(`missing "crv" field`) } x, ok := key.X() if !ok { return nil, fmt.Errorf(`missing "x" field`) } pubk, err := buildOKPPublicKey(crv, x) if err != nil { return nil, fmt.Errorf(`jwk.OKPPublicKey: failed to build public key: %w`, err) } return pubk, nil default: return nil, ContinueError() } } func makeOKPPublicKey(src Key) (Key, error) { newKey := newOKPPublicKey() // Iterate and copy everything except for the bits that should not be in the public key for _, k := range src.Keys() { switch k { case OKPDKey: continue default: var v any if err := src.Get(k, &v); err != nil { return nil, fmt.Errorf(`failed to get field %q: %w`, k, err) } if err := newKey.Set(k, v); err != nil { return nil, fmt.Errorf(`failed to set field %q: %w`, k, err) } } } return newKey, nil } func (k *okpPrivateKey) PublicKey() (Key, error) { return makeOKPPublicKey(k) } func (k *okpPublicKey) PublicKey() (Key, error) { return makeOKPPublicKey(k) } func okpThumbprint(hash crypto.Hash, crv, x string) []byte { h := hash.New() fmt.Fprint(h, `{"crv":"`) fmt.Fprint(h, crv) fmt.Fprint(h, `","kty":"OKP","x":"`) fmt.Fprint(h, x) fmt.Fprint(h, `"}`) return h.Sum(nil) } // Thumbprint returns the JWK thumbprint using the indicated // hashing algorithm, according to RFC 7638 / 8037 func (k okpPublicKey) Thumbprint(hash crypto.Hash) ([]byte, error) { k.mu.RLock() defer k.mu.RUnlock() crv, ok := k.Crv() if !ok { return nil, fmt.Errorf(`missing "crv" field`) } return okpThumbprint( hash, crv.String(), base64.EncodeToString(k.x), ), nil } // Thumbprint returns the JWK thumbprint using the indicated // hashing algorithm, according to RFC 7638 / 8037 func (k okpPrivateKey) Thumbprint(hash crypto.Hash) ([]byte, error) { k.mu.RLock() defer k.mu.RUnlock() crv, ok := k.Crv() if !ok { return nil, fmt.Errorf(`missing "crv" field`) } return okpThumbprint( hash, crv.String(), base64.EncodeToString(k.x), ), nil } func validateOKPKey(key interface { Crv() (jwa.EllipticCurveAlgorithm, bool) X() ([]byte, bool) }) error { if v, ok := key.Crv(); !ok || v == jwa.InvalidEllipticCurve() { return fmt.Errorf(`invalid curve algorithm`) } if v, ok := key.X(); !ok || len(v) == 0 { return fmt.Errorf(`missing "x" field`) } if priv, ok := key.(keyWithD); ok { if d, ok := priv.D(); !ok || len(d) == 0 { return fmt.Errorf(`missing "d" field`) } } return nil } func (k *okpPublicKey) Validate() error { k.mu.RLock() defer k.mu.RUnlock() if err := validateOKPKey(k); err != nil { return NewKeyValidationError(fmt.Errorf(`jwk.OKPPublicKey: %w`, err)) } return nil } func (k *okpPrivateKey) Validate() error { k.mu.RLock() defer k.mu.RUnlock() if err := validateOKPKey(k); err != nil { return NewKeyValidationError(fmt.Errorf(`jwk.OKPPrivateKey: %w`, err)) } return nil } golang-github-lestrrat-go-jwx-3.0.13/jwk/okp_gen.go000066400000000000000000001067761515060566400221710ustar00rootroot00000000000000// Code generated by tools/cmd/genjwk/main.go. DO NOT EDIT. package jwk import ( "bytes" "fmt" "sort" "sync" "github.com/lestrrat-go/blackmagic" "github.com/lestrrat-go/jwx/v3/cert" "github.com/lestrrat-go/jwx/v3/internal/base64" "github.com/lestrrat-go/jwx/v3/internal/json" "github.com/lestrrat-go/jwx/v3/internal/pool" "github.com/lestrrat-go/jwx/v3/internal/tokens" "github.com/lestrrat-go/jwx/v3/jwa" ) const ( OKPCrvKey = "crv" OKPDKey = "d" OKPXKey = "x" ) type OKPPublicKey interface { Key Crv() (jwa.EllipticCurveAlgorithm, bool) X() ([]byte, bool) } type okpPublicKey struct { algorithm *jwa.KeyAlgorithm // https://tools.ietf.org/html/rfc7517#section-4.4 crv *jwa.EllipticCurveAlgorithm keyID *string // https://tools.ietf.org/html/rfc7515#section-4.1.4 keyOps *KeyOperationList // https://tools.ietf.org/html/rfc7517#section-4.3 keyUsage *string // https://tools.ietf.org/html/rfc7517#section-4.2 x []byte x509CertChain *cert.Chain // https://tools.ietf.org/html/rfc7515#section-4.1.6 x509CertThumbprint *string // https://tools.ietf.org/html/rfc7515#section-4.1.7 x509CertThumbprintS256 *string // https://tools.ietf.org/html/rfc7515#section-4.1.8 x509URL *string // https://tools.ietf.org/html/rfc7515#section-4.1.5 privateParams map[string]any mu *sync.RWMutex dc json.DecodeCtx } var _ OKPPublicKey = &okpPublicKey{} var _ Key = &okpPublicKey{} func newOKPPublicKey() *okpPublicKey { return &okpPublicKey{ mu: &sync.RWMutex{}, privateParams: make(map[string]any), } } func (h okpPublicKey) KeyType() jwa.KeyType { return jwa.OKP() } func (h okpPublicKey) rlock() { h.mu.RLock() } func (h okpPublicKey) runlock() { h.mu.RUnlock() } func (h okpPublicKey) IsPrivate() bool { return false } func (h *okpPublicKey) Algorithm() (jwa.KeyAlgorithm, bool) { if h.algorithm != nil { return *(h.algorithm), true } return nil, false } func (h *okpPublicKey) Crv() (jwa.EllipticCurveAlgorithm, bool) { if h.crv != nil { return *(h.crv), true } return jwa.InvalidEllipticCurve(), false } func (h *okpPublicKey) KeyID() (string, bool) { if h.keyID != nil { return *(h.keyID), true } return "", false } func (h *okpPublicKey) KeyOps() (KeyOperationList, bool) { if h.keyOps != nil { return *(h.keyOps), true } return nil, false } func (h *okpPublicKey) KeyUsage() (string, bool) { if h.keyUsage != nil { return *(h.keyUsage), true } return "", false } func (h *okpPublicKey) X() ([]byte, bool) { if h.x != nil { return h.x, true } return nil, false } func (h *okpPublicKey) X509CertChain() (*cert.Chain, bool) { return h.x509CertChain, true } func (h *okpPublicKey) X509CertThumbprint() (string, bool) { if h.x509CertThumbprint != nil { return *(h.x509CertThumbprint), true } return "", false } func (h *okpPublicKey) X509CertThumbprintS256() (string, bool) { if h.x509CertThumbprintS256 != nil { return *(h.x509CertThumbprintS256), true } return "", false } func (h *okpPublicKey) X509URL() (string, bool) { if h.x509URL != nil { return *(h.x509URL), true } return "", false } func (h *okpPublicKey) Has(name string) bool { h.mu.RLock() defer h.mu.RUnlock() switch name { case KeyTypeKey: return true case AlgorithmKey: return h.algorithm != nil case OKPCrvKey: return h.crv != nil case KeyIDKey: return h.keyID != nil case KeyOpsKey: return h.keyOps != nil case KeyUsageKey: return h.keyUsage != nil case OKPXKey: return h.x != nil case X509CertChainKey: return h.x509CertChain != nil case X509CertThumbprintKey: return h.x509CertThumbprint != nil case X509CertThumbprintS256Key: return h.x509CertThumbprintS256 != nil case X509URLKey: return h.x509URL != nil default: _, ok := h.privateParams[name] return ok } } func (h *okpPublicKey) Get(name string, dst any) error { h.mu.RLock() defer h.mu.RUnlock() switch name { case KeyTypeKey: if err := blackmagic.AssignIfCompatible(dst, h.KeyType()); err != nil { return fmt.Errorf(`okpPublicKey.Get: failed to assign value for field %q to destination object: %w`, name, err) } case AlgorithmKey: if h.algorithm == nil { return fmt.Errorf(`field %q not found`, name) } if err := blackmagic.AssignIfCompatible(dst, *(h.algorithm)); err != nil { return fmt.Errorf(`failed to assign value for field %q: %w`, name, err) } return nil case OKPCrvKey: if h.crv == nil { return fmt.Errorf(`field %q not found`, name) } if err := blackmagic.AssignIfCompatible(dst, *(h.crv)); err != nil { return fmt.Errorf(`failed to assign value for field %q: %w`, name, err) } return nil case KeyIDKey: if h.keyID == nil { return fmt.Errorf(`field %q not found`, name) } if err := blackmagic.AssignIfCompatible(dst, *(h.keyID)); err != nil { return fmt.Errorf(`failed to assign value for field %q: %w`, name, err) } return nil case KeyOpsKey: if h.keyOps == nil { return fmt.Errorf(`field %q not found`, name) } if err := blackmagic.AssignIfCompatible(dst, *(h.keyOps)); err != nil { return fmt.Errorf(`failed to assign value for field %q: %w`, name, err) } return nil case KeyUsageKey: if h.keyUsage == nil { return fmt.Errorf(`field %q not found`, name) } if err := blackmagic.AssignIfCompatible(dst, *(h.keyUsage)); err != nil { return fmt.Errorf(`failed to assign value for field %q: %w`, name, err) } return nil case OKPXKey: if h.x == nil { return fmt.Errorf(`field %q not found`, name) } if err := blackmagic.AssignIfCompatible(dst, h.x); err != nil { return fmt.Errorf(`failed to assign value for field %q: %w`, name, err) } return nil case X509CertChainKey: if h.x509CertChain == nil { return fmt.Errorf(`field %q not found`, name) } if err := blackmagic.AssignIfCompatible(dst, h.x509CertChain); err != nil { return fmt.Errorf(`failed to assign value for field %q: %w`, name, err) } return nil case X509CertThumbprintKey: if h.x509CertThumbprint == nil { return fmt.Errorf(`field %q not found`, name) } if err := blackmagic.AssignIfCompatible(dst, *(h.x509CertThumbprint)); err != nil { return fmt.Errorf(`failed to assign value for field %q: %w`, name, err) } return nil case X509CertThumbprintS256Key: if h.x509CertThumbprintS256 == nil { return fmt.Errorf(`field %q not found`, name) } if err := blackmagic.AssignIfCompatible(dst, *(h.x509CertThumbprintS256)); err != nil { return fmt.Errorf(`failed to assign value for field %q: %w`, name, err) } return nil case X509URLKey: if h.x509URL == nil { return fmt.Errorf(`field %q not found`, name) } if err := blackmagic.AssignIfCompatible(dst, *(h.x509URL)); err != nil { return fmt.Errorf(`failed to assign value for field %q: %w`, name, err) } return nil default: v, ok := h.privateParams[name] if !ok { return fmt.Errorf(`field %q not found`, name) } if err := blackmagic.AssignIfCompatible(dst, v); err != nil { return fmt.Errorf(`failed to assign value for field %q: %w`, name, err) } } return nil } func (h *okpPublicKey) Set(name string, value any) error { h.mu.Lock() defer h.mu.Unlock() return h.setNoLock(name, value) } func (h *okpPublicKey) setNoLock(name string, value any) error { switch name { case "kty": return nil case AlgorithmKey: switch v := value.(type) { case string, jwa.SignatureAlgorithm, jwa.KeyEncryptionAlgorithm, jwa.ContentEncryptionAlgorithm: tmp, err := jwa.KeyAlgorithmFrom(v) if err != nil { return fmt.Errorf(`invalid algorithm for %q key: %w`, AlgorithmKey, err) } h.algorithm = &tmp default: return fmt.Errorf(`invalid type for %q key: %T`, AlgorithmKey, value) } return nil case OKPCrvKey: if v, ok := value.(jwa.EllipticCurveAlgorithm); ok { h.crv = &v return nil } return fmt.Errorf(`invalid value for %s key: %T`, OKPCrvKey, value) case KeyIDKey: if v, ok := value.(string); ok { h.keyID = &v return nil } return fmt.Errorf(`invalid value for %s key: %T`, KeyIDKey, value) case KeyOpsKey: var acceptor KeyOperationList if err := acceptor.Accept(value); err != nil { return fmt.Errorf(`invalid value for %s key: %w`, KeyOpsKey, err) } h.keyOps = &acceptor return nil case KeyUsageKey: switch v := value.(type) { case KeyUsageType: switch v { case ForSignature, ForEncryption: tmp := v.String() h.keyUsage = &tmp default: return fmt.Errorf(`invalid key usage type %s`, v) } case string: h.keyUsage = &v default: return fmt.Errorf(`invalid key usage type %s`, v) } case OKPXKey: if v, ok := value.([]byte); ok { h.x = v return nil } return fmt.Errorf(`invalid value for %s key: %T`, OKPXKey, value) case X509CertChainKey: if v, ok := value.(*cert.Chain); ok { h.x509CertChain = v return nil } return fmt.Errorf(`invalid value for %s key: %T`, X509CertChainKey, value) case X509CertThumbprintKey: if v, ok := value.(string); ok { h.x509CertThumbprint = &v return nil } return fmt.Errorf(`invalid value for %s key: %T`, X509CertThumbprintKey, value) case X509CertThumbprintS256Key: if v, ok := value.(string); ok { h.x509CertThumbprintS256 = &v return nil } return fmt.Errorf(`invalid value for %s key: %T`, X509CertThumbprintS256Key, value) case X509URLKey: if v, ok := value.(string); ok { h.x509URL = &v return nil } return fmt.Errorf(`invalid value for %s key: %T`, X509URLKey, value) default: if h.privateParams == nil { h.privateParams = map[string]any{} } h.privateParams[name] = value } return nil } func (k *okpPublicKey) Remove(key string) error { k.mu.Lock() defer k.mu.Unlock() switch key { case AlgorithmKey: k.algorithm = nil case OKPCrvKey: k.crv = nil case KeyIDKey: k.keyID = nil case KeyOpsKey: k.keyOps = nil case KeyUsageKey: k.keyUsage = nil case OKPXKey: k.x = nil case X509CertChainKey: k.x509CertChain = nil case X509CertThumbprintKey: k.x509CertThumbprint = nil case X509CertThumbprintS256Key: k.x509CertThumbprintS256 = nil case X509URLKey: k.x509URL = nil default: delete(k.privateParams, key) } return nil } func (k *okpPublicKey) Clone() (Key, error) { key, err := cloneKey(k) if err != nil { return nil, fmt.Errorf(`okpPublicKey.Clone: %w`, err) } return key, nil } func (k *okpPublicKey) DecodeCtx() json.DecodeCtx { k.mu.RLock() defer k.mu.RUnlock() return k.dc } func (k *okpPublicKey) SetDecodeCtx(dc json.DecodeCtx) { k.mu.Lock() defer k.mu.Unlock() k.dc = dc } func (h *okpPublicKey) UnmarshalJSON(buf []byte) error { h.mu.Lock() defer h.mu.Unlock() h.algorithm = nil h.crv = nil h.keyID = nil h.keyOps = nil h.keyUsage = nil h.x = nil h.x509CertChain = nil h.x509CertThumbprint = nil h.x509CertThumbprintS256 = nil h.x509URL = nil dec := json.NewDecoder(bytes.NewReader(buf)) LOOP: for { tok, err := dec.Token() if err != nil { return fmt.Errorf(`error reading token: %w`, err) } switch tok := tok.(type) { case json.Delim: // Assuming we're doing everything correctly, we should ONLY // get either tokens.OpenCurlyBracket or tokens.CloseCurlyBracket here. if tok == tokens.CloseCurlyBracket { // End of object break LOOP } else if tok != tokens.OpenCurlyBracket { return fmt.Errorf(`expected '%c' but got '%c'`, tokens.OpenCurlyBracket, tok) } case string: // Objects can only have string keys switch tok { case KeyTypeKey: val, err := json.ReadNextStringToken(dec) if err != nil { return fmt.Errorf(`error reading token: %w`, err) } if val != jwa.OKP().String() { return fmt.Errorf(`invalid kty value for RSAPublicKey (%s)`, val) } case AlgorithmKey: var s string if err := dec.Decode(&s); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, AlgorithmKey, err) } alg, err := jwa.KeyAlgorithmFrom(s) if err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, AlgorithmKey, err) } h.algorithm = &alg case OKPCrvKey: var decoded jwa.EllipticCurveAlgorithm if err := dec.Decode(&decoded); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, OKPCrvKey, err) } h.crv = &decoded case KeyIDKey: if err := json.AssignNextStringToken(&h.keyID, dec); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, KeyIDKey, err) } case KeyOpsKey: var decoded KeyOperationList if err := dec.Decode(&decoded); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, KeyOpsKey, err) } h.keyOps = &decoded case KeyUsageKey: if err := json.AssignNextStringToken(&h.keyUsage, dec); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, KeyUsageKey, err) } case OKPXKey: if err := json.AssignNextBytesToken(&h.x, dec); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, OKPXKey, err) } case X509CertChainKey: var decoded cert.Chain if err := dec.Decode(&decoded); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, X509CertChainKey, err) } h.x509CertChain = &decoded case X509CertThumbprintKey: if err := json.AssignNextStringToken(&h.x509CertThumbprint, dec); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, X509CertThumbprintKey, err) } case X509CertThumbprintS256Key: if err := json.AssignNextStringToken(&h.x509CertThumbprintS256, dec); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, X509CertThumbprintS256Key, err) } case X509URLKey: if err := json.AssignNextStringToken(&h.x509URL, dec); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, X509URLKey, err) } default: if dc := h.dc; dc != nil { if localReg := dc.Registry(); localReg != nil { decoded, err := localReg.Decode(dec, tok) if err == nil { h.setNoLock(tok, decoded) continue } } } decoded, err := registry.Decode(dec, tok) if err == nil { h.setNoLock(tok, decoded) continue } return fmt.Errorf(`could not decode field %s: %w`, tok, err) } default: return fmt.Errorf(`invalid token %T`, tok) } } if h.crv == nil { return fmt.Errorf(`required field crv is missing`) } if h.x == nil { return fmt.Errorf(`required field x is missing`) } return nil } func (h okpPublicKey) MarshalJSON() ([]byte, error) { data := make(map[string]any) fields := make([]string, 0, 10) data[KeyTypeKey] = jwa.OKP() fields = append(fields, KeyTypeKey) if h.algorithm != nil { data[AlgorithmKey] = *(h.algorithm) fields = append(fields, AlgorithmKey) } if h.crv != nil { data[OKPCrvKey] = *(h.crv) fields = append(fields, OKPCrvKey) } if h.keyID != nil { data[KeyIDKey] = *(h.keyID) fields = append(fields, KeyIDKey) } if h.keyOps != nil { data[KeyOpsKey] = *(h.keyOps) fields = append(fields, KeyOpsKey) } if h.keyUsage != nil { data[KeyUsageKey] = *(h.keyUsage) fields = append(fields, KeyUsageKey) } if h.x != nil { data[OKPXKey] = h.x fields = append(fields, OKPXKey) } if h.x509CertChain != nil { data[X509CertChainKey] = h.x509CertChain fields = append(fields, X509CertChainKey) } if h.x509CertThumbprint != nil { data[X509CertThumbprintKey] = *(h.x509CertThumbprint) fields = append(fields, X509CertThumbprintKey) } if h.x509CertThumbprintS256 != nil { data[X509CertThumbprintS256Key] = *(h.x509CertThumbprintS256) fields = append(fields, X509CertThumbprintS256Key) } if h.x509URL != nil { data[X509URLKey] = *(h.x509URL) fields = append(fields, X509URLKey) } for k, v := range h.privateParams { data[k] = v fields = append(fields, k) } sort.Strings(fields) buf := pool.BytesBuffer().Get() defer pool.BytesBuffer().Put(buf) buf.WriteByte(tokens.OpenCurlyBracket) enc := json.NewEncoder(buf) for i, f := range fields { if i > 0 { buf.WriteRune(tokens.Comma) } buf.WriteRune(tokens.DoubleQuote) buf.WriteString(f) buf.WriteString(`":`) v := data[f] switch v := v.(type) { case []byte: buf.WriteRune(tokens.DoubleQuote) buf.WriteString(base64.EncodeToString(v)) buf.WriteRune(tokens.DoubleQuote) default: if err := enc.Encode(v); err != nil { return nil, fmt.Errorf(`failed to encode value for field %s: %w`, f, err) } buf.Truncate(buf.Len() - 1) } } buf.WriteByte(tokens.CloseCurlyBracket) ret := make([]byte, buf.Len()) copy(ret, buf.Bytes()) return ret, nil } func (h *okpPublicKey) Keys() []string { h.mu.RLock() defer h.mu.RUnlock() keys := make([]string, 0, 10+len(h.privateParams)) keys = append(keys, KeyTypeKey) if h.algorithm != nil { keys = append(keys, AlgorithmKey) } if h.crv != nil { keys = append(keys, OKPCrvKey) } if h.keyID != nil { keys = append(keys, KeyIDKey) } if h.keyOps != nil { keys = append(keys, KeyOpsKey) } if h.keyUsage != nil { keys = append(keys, KeyUsageKey) } if h.x != nil { keys = append(keys, OKPXKey) } if h.x509CertChain != nil { keys = append(keys, X509CertChainKey) } if h.x509CertThumbprint != nil { keys = append(keys, X509CertThumbprintKey) } if h.x509CertThumbprintS256 != nil { keys = append(keys, X509CertThumbprintS256Key) } if h.x509URL != nil { keys = append(keys, X509URLKey) } for k := range h.privateParams { keys = append(keys, k) } return keys } type OKPPrivateKey interface { Key Crv() (jwa.EllipticCurveAlgorithm, bool) D() ([]byte, bool) X() ([]byte, bool) } type okpPrivateKey struct { algorithm *jwa.KeyAlgorithm // https://tools.ietf.org/html/rfc7517#section-4.4 crv *jwa.EllipticCurveAlgorithm d []byte keyID *string // https://tools.ietf.org/html/rfc7515#section-4.1.4 keyOps *KeyOperationList // https://tools.ietf.org/html/rfc7517#section-4.3 keyUsage *string // https://tools.ietf.org/html/rfc7517#section-4.2 x []byte x509CertChain *cert.Chain // https://tools.ietf.org/html/rfc7515#section-4.1.6 x509CertThumbprint *string // https://tools.ietf.org/html/rfc7515#section-4.1.7 x509CertThumbprintS256 *string // https://tools.ietf.org/html/rfc7515#section-4.1.8 x509URL *string // https://tools.ietf.org/html/rfc7515#section-4.1.5 privateParams map[string]any mu *sync.RWMutex dc json.DecodeCtx } var _ OKPPrivateKey = &okpPrivateKey{} var _ Key = &okpPrivateKey{} func newOKPPrivateKey() *okpPrivateKey { return &okpPrivateKey{ mu: &sync.RWMutex{}, privateParams: make(map[string]any), } } func (h okpPrivateKey) KeyType() jwa.KeyType { return jwa.OKP() } func (h okpPrivateKey) rlock() { h.mu.RLock() } func (h okpPrivateKey) runlock() { h.mu.RUnlock() } func (h okpPrivateKey) IsPrivate() bool { return true } func (h *okpPrivateKey) Algorithm() (jwa.KeyAlgorithm, bool) { if h.algorithm != nil { return *(h.algorithm), true } return nil, false } func (h *okpPrivateKey) Crv() (jwa.EllipticCurveAlgorithm, bool) { if h.crv != nil { return *(h.crv), true } return jwa.InvalidEllipticCurve(), false } func (h *okpPrivateKey) D() ([]byte, bool) { if h.d != nil { return h.d, true } return nil, false } func (h *okpPrivateKey) KeyID() (string, bool) { if h.keyID != nil { return *(h.keyID), true } return "", false } func (h *okpPrivateKey) KeyOps() (KeyOperationList, bool) { if h.keyOps != nil { return *(h.keyOps), true } return nil, false } func (h *okpPrivateKey) KeyUsage() (string, bool) { if h.keyUsage != nil { return *(h.keyUsage), true } return "", false } func (h *okpPrivateKey) X() ([]byte, bool) { if h.x != nil { return h.x, true } return nil, false } func (h *okpPrivateKey) X509CertChain() (*cert.Chain, bool) { return h.x509CertChain, true } func (h *okpPrivateKey) X509CertThumbprint() (string, bool) { if h.x509CertThumbprint != nil { return *(h.x509CertThumbprint), true } return "", false } func (h *okpPrivateKey) X509CertThumbprintS256() (string, bool) { if h.x509CertThumbprintS256 != nil { return *(h.x509CertThumbprintS256), true } return "", false } func (h *okpPrivateKey) X509URL() (string, bool) { if h.x509URL != nil { return *(h.x509URL), true } return "", false } func (h *okpPrivateKey) Has(name string) bool { h.mu.RLock() defer h.mu.RUnlock() switch name { case KeyTypeKey: return true case AlgorithmKey: return h.algorithm != nil case OKPCrvKey: return h.crv != nil case OKPDKey: return h.d != nil case KeyIDKey: return h.keyID != nil case KeyOpsKey: return h.keyOps != nil case KeyUsageKey: return h.keyUsage != nil case OKPXKey: return h.x != nil case X509CertChainKey: return h.x509CertChain != nil case X509CertThumbprintKey: return h.x509CertThumbprint != nil case X509CertThumbprintS256Key: return h.x509CertThumbprintS256 != nil case X509URLKey: return h.x509URL != nil default: _, ok := h.privateParams[name] return ok } } func (h *okpPrivateKey) Get(name string, dst any) error { h.mu.RLock() defer h.mu.RUnlock() switch name { case KeyTypeKey: if err := blackmagic.AssignIfCompatible(dst, h.KeyType()); err != nil { return fmt.Errorf(`okpPrivateKey.Get: failed to assign value for field %q to destination object: %w`, name, err) } case AlgorithmKey: if h.algorithm == nil { return fmt.Errorf(`field %q not found`, name) } if err := blackmagic.AssignIfCompatible(dst, *(h.algorithm)); err != nil { return fmt.Errorf(`failed to assign value for field %q: %w`, name, err) } return nil case OKPCrvKey: if h.crv == nil { return fmt.Errorf(`field %q not found`, name) } if err := blackmagic.AssignIfCompatible(dst, *(h.crv)); err != nil { return fmt.Errorf(`failed to assign value for field %q: %w`, name, err) } return nil case OKPDKey: if h.d == nil { return fmt.Errorf(`field %q not found`, name) } if err := blackmagic.AssignIfCompatible(dst, h.d); err != nil { return fmt.Errorf(`failed to assign value for field %q: %w`, name, err) } return nil case KeyIDKey: if h.keyID == nil { return fmt.Errorf(`field %q not found`, name) } if err := blackmagic.AssignIfCompatible(dst, *(h.keyID)); err != nil { return fmt.Errorf(`failed to assign value for field %q: %w`, name, err) } return nil case KeyOpsKey: if h.keyOps == nil { return fmt.Errorf(`field %q not found`, name) } if err := blackmagic.AssignIfCompatible(dst, *(h.keyOps)); err != nil { return fmt.Errorf(`failed to assign value for field %q: %w`, name, err) } return nil case KeyUsageKey: if h.keyUsage == nil { return fmt.Errorf(`field %q not found`, name) } if err := blackmagic.AssignIfCompatible(dst, *(h.keyUsage)); err != nil { return fmt.Errorf(`failed to assign value for field %q: %w`, name, err) } return nil case OKPXKey: if h.x == nil { return fmt.Errorf(`field %q not found`, name) } if err := blackmagic.AssignIfCompatible(dst, h.x); err != nil { return fmt.Errorf(`failed to assign value for field %q: %w`, name, err) } return nil case X509CertChainKey: if h.x509CertChain == nil { return fmt.Errorf(`field %q not found`, name) } if err := blackmagic.AssignIfCompatible(dst, h.x509CertChain); err != nil { return fmt.Errorf(`failed to assign value for field %q: %w`, name, err) } return nil case X509CertThumbprintKey: if h.x509CertThumbprint == nil { return fmt.Errorf(`field %q not found`, name) } if err := blackmagic.AssignIfCompatible(dst, *(h.x509CertThumbprint)); err != nil { return fmt.Errorf(`failed to assign value for field %q: %w`, name, err) } return nil case X509CertThumbprintS256Key: if h.x509CertThumbprintS256 == nil { return fmt.Errorf(`field %q not found`, name) } if err := blackmagic.AssignIfCompatible(dst, *(h.x509CertThumbprintS256)); err != nil { return fmt.Errorf(`failed to assign value for field %q: %w`, name, err) } return nil case X509URLKey: if h.x509URL == nil { return fmt.Errorf(`field %q not found`, name) } if err := blackmagic.AssignIfCompatible(dst, *(h.x509URL)); err != nil { return fmt.Errorf(`failed to assign value for field %q: %w`, name, err) } return nil default: v, ok := h.privateParams[name] if !ok { return fmt.Errorf(`field %q not found`, name) } if err := blackmagic.AssignIfCompatible(dst, v); err != nil { return fmt.Errorf(`failed to assign value for field %q: %w`, name, err) } } return nil } func (h *okpPrivateKey) Set(name string, value any) error { h.mu.Lock() defer h.mu.Unlock() return h.setNoLock(name, value) } func (h *okpPrivateKey) setNoLock(name string, value any) error { switch name { case "kty": return nil case AlgorithmKey: switch v := value.(type) { case string, jwa.SignatureAlgorithm, jwa.KeyEncryptionAlgorithm, jwa.ContentEncryptionAlgorithm: tmp, err := jwa.KeyAlgorithmFrom(v) if err != nil { return fmt.Errorf(`invalid algorithm for %q key: %w`, AlgorithmKey, err) } h.algorithm = &tmp default: return fmt.Errorf(`invalid type for %q key: %T`, AlgorithmKey, value) } return nil case OKPCrvKey: if v, ok := value.(jwa.EllipticCurveAlgorithm); ok { h.crv = &v return nil } return fmt.Errorf(`invalid value for %s key: %T`, OKPCrvKey, value) case OKPDKey: if v, ok := value.([]byte); ok { h.d = v return nil } return fmt.Errorf(`invalid value for %s key: %T`, OKPDKey, value) case KeyIDKey: if v, ok := value.(string); ok { h.keyID = &v return nil } return fmt.Errorf(`invalid value for %s key: %T`, KeyIDKey, value) case KeyOpsKey: var acceptor KeyOperationList if err := acceptor.Accept(value); err != nil { return fmt.Errorf(`invalid value for %s key: %w`, KeyOpsKey, err) } h.keyOps = &acceptor return nil case KeyUsageKey: switch v := value.(type) { case KeyUsageType: switch v { case ForSignature, ForEncryption: tmp := v.String() h.keyUsage = &tmp default: return fmt.Errorf(`invalid key usage type %s`, v) } case string: h.keyUsage = &v default: return fmt.Errorf(`invalid key usage type %s`, v) } case OKPXKey: if v, ok := value.([]byte); ok { h.x = v return nil } return fmt.Errorf(`invalid value for %s key: %T`, OKPXKey, value) case X509CertChainKey: if v, ok := value.(*cert.Chain); ok { h.x509CertChain = v return nil } return fmt.Errorf(`invalid value for %s key: %T`, X509CertChainKey, value) case X509CertThumbprintKey: if v, ok := value.(string); ok { h.x509CertThumbprint = &v return nil } return fmt.Errorf(`invalid value for %s key: %T`, X509CertThumbprintKey, value) case X509CertThumbprintS256Key: if v, ok := value.(string); ok { h.x509CertThumbprintS256 = &v return nil } return fmt.Errorf(`invalid value for %s key: %T`, X509CertThumbprintS256Key, value) case X509URLKey: if v, ok := value.(string); ok { h.x509URL = &v return nil } return fmt.Errorf(`invalid value for %s key: %T`, X509URLKey, value) default: if h.privateParams == nil { h.privateParams = map[string]any{} } h.privateParams[name] = value } return nil } func (k *okpPrivateKey) Remove(key string) error { k.mu.Lock() defer k.mu.Unlock() switch key { case AlgorithmKey: k.algorithm = nil case OKPCrvKey: k.crv = nil case OKPDKey: k.d = nil case KeyIDKey: k.keyID = nil case KeyOpsKey: k.keyOps = nil case KeyUsageKey: k.keyUsage = nil case OKPXKey: k.x = nil case X509CertChainKey: k.x509CertChain = nil case X509CertThumbprintKey: k.x509CertThumbprint = nil case X509CertThumbprintS256Key: k.x509CertThumbprintS256 = nil case X509URLKey: k.x509URL = nil default: delete(k.privateParams, key) } return nil } func (k *okpPrivateKey) Clone() (Key, error) { key, err := cloneKey(k) if err != nil { return nil, fmt.Errorf(`okpPrivateKey.Clone: %w`, err) } return key, nil } func (k *okpPrivateKey) DecodeCtx() json.DecodeCtx { k.mu.RLock() defer k.mu.RUnlock() return k.dc } func (k *okpPrivateKey) SetDecodeCtx(dc json.DecodeCtx) { k.mu.Lock() defer k.mu.Unlock() k.dc = dc } func (h *okpPrivateKey) UnmarshalJSON(buf []byte) error { h.mu.Lock() defer h.mu.Unlock() h.algorithm = nil h.crv = nil h.d = nil h.keyID = nil h.keyOps = nil h.keyUsage = nil h.x = nil h.x509CertChain = nil h.x509CertThumbprint = nil h.x509CertThumbprintS256 = nil h.x509URL = nil dec := json.NewDecoder(bytes.NewReader(buf)) LOOP: for { tok, err := dec.Token() if err != nil { return fmt.Errorf(`error reading token: %w`, err) } switch tok := tok.(type) { case json.Delim: // Assuming we're doing everything correctly, we should ONLY // get either tokens.OpenCurlyBracket or tokens.CloseCurlyBracket here. if tok == tokens.CloseCurlyBracket { // End of object break LOOP } else if tok != tokens.OpenCurlyBracket { return fmt.Errorf(`expected '%c' but got '%c'`, tokens.OpenCurlyBracket, tok) } case string: // Objects can only have string keys switch tok { case KeyTypeKey: val, err := json.ReadNextStringToken(dec) if err != nil { return fmt.Errorf(`error reading token: %w`, err) } if val != jwa.OKP().String() { return fmt.Errorf(`invalid kty value for RSAPublicKey (%s)`, val) } case AlgorithmKey: var s string if err := dec.Decode(&s); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, AlgorithmKey, err) } alg, err := jwa.KeyAlgorithmFrom(s) if err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, AlgorithmKey, err) } h.algorithm = &alg case OKPCrvKey: var decoded jwa.EllipticCurveAlgorithm if err := dec.Decode(&decoded); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, OKPCrvKey, err) } h.crv = &decoded case OKPDKey: if err := json.AssignNextBytesToken(&h.d, dec); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, OKPDKey, err) } case KeyIDKey: if err := json.AssignNextStringToken(&h.keyID, dec); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, KeyIDKey, err) } case KeyOpsKey: var decoded KeyOperationList if err := dec.Decode(&decoded); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, KeyOpsKey, err) } h.keyOps = &decoded case KeyUsageKey: if err := json.AssignNextStringToken(&h.keyUsage, dec); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, KeyUsageKey, err) } case OKPXKey: if err := json.AssignNextBytesToken(&h.x, dec); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, OKPXKey, err) } case X509CertChainKey: var decoded cert.Chain if err := dec.Decode(&decoded); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, X509CertChainKey, err) } h.x509CertChain = &decoded case X509CertThumbprintKey: if err := json.AssignNextStringToken(&h.x509CertThumbprint, dec); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, X509CertThumbprintKey, err) } case X509CertThumbprintS256Key: if err := json.AssignNextStringToken(&h.x509CertThumbprintS256, dec); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, X509CertThumbprintS256Key, err) } case X509URLKey: if err := json.AssignNextStringToken(&h.x509URL, dec); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, X509URLKey, err) } default: if dc := h.dc; dc != nil { if localReg := dc.Registry(); localReg != nil { decoded, err := localReg.Decode(dec, tok) if err == nil { h.setNoLock(tok, decoded) continue } } } decoded, err := registry.Decode(dec, tok) if err == nil { h.setNoLock(tok, decoded) continue } return fmt.Errorf(`could not decode field %s: %w`, tok, err) } default: return fmt.Errorf(`invalid token %T`, tok) } } if h.crv == nil { return fmt.Errorf(`required field crv is missing`) } if h.d == nil { return fmt.Errorf(`required field d is missing`) } if h.x == nil { return fmt.Errorf(`required field x is missing`) } return nil } func (h okpPrivateKey) MarshalJSON() ([]byte, error) { data := make(map[string]any) fields := make([]string, 0, 11) data[KeyTypeKey] = jwa.OKP() fields = append(fields, KeyTypeKey) if h.algorithm != nil { data[AlgorithmKey] = *(h.algorithm) fields = append(fields, AlgorithmKey) } if h.crv != nil { data[OKPCrvKey] = *(h.crv) fields = append(fields, OKPCrvKey) } if h.d != nil { data[OKPDKey] = h.d fields = append(fields, OKPDKey) } if h.keyID != nil { data[KeyIDKey] = *(h.keyID) fields = append(fields, KeyIDKey) } if h.keyOps != nil { data[KeyOpsKey] = *(h.keyOps) fields = append(fields, KeyOpsKey) } if h.keyUsage != nil { data[KeyUsageKey] = *(h.keyUsage) fields = append(fields, KeyUsageKey) } if h.x != nil { data[OKPXKey] = h.x fields = append(fields, OKPXKey) } if h.x509CertChain != nil { data[X509CertChainKey] = h.x509CertChain fields = append(fields, X509CertChainKey) } if h.x509CertThumbprint != nil { data[X509CertThumbprintKey] = *(h.x509CertThumbprint) fields = append(fields, X509CertThumbprintKey) } if h.x509CertThumbprintS256 != nil { data[X509CertThumbprintS256Key] = *(h.x509CertThumbprintS256) fields = append(fields, X509CertThumbprintS256Key) } if h.x509URL != nil { data[X509URLKey] = *(h.x509URL) fields = append(fields, X509URLKey) } for k, v := range h.privateParams { data[k] = v fields = append(fields, k) } sort.Strings(fields) buf := pool.BytesBuffer().Get() defer pool.BytesBuffer().Put(buf) buf.WriteByte(tokens.OpenCurlyBracket) enc := json.NewEncoder(buf) for i, f := range fields { if i > 0 { buf.WriteRune(tokens.Comma) } buf.WriteRune(tokens.DoubleQuote) buf.WriteString(f) buf.WriteString(`":`) v := data[f] switch v := v.(type) { case []byte: buf.WriteRune(tokens.DoubleQuote) buf.WriteString(base64.EncodeToString(v)) buf.WriteRune(tokens.DoubleQuote) default: if err := enc.Encode(v); err != nil { return nil, fmt.Errorf(`failed to encode value for field %s: %w`, f, err) } buf.Truncate(buf.Len() - 1) } } buf.WriteByte(tokens.CloseCurlyBracket) ret := make([]byte, buf.Len()) copy(ret, buf.Bytes()) return ret, nil } func (h *okpPrivateKey) Keys() []string { h.mu.RLock() defer h.mu.RUnlock() keys := make([]string, 0, 11+len(h.privateParams)) keys = append(keys, KeyTypeKey) if h.algorithm != nil { keys = append(keys, AlgorithmKey) } if h.crv != nil { keys = append(keys, OKPCrvKey) } if h.d != nil { keys = append(keys, OKPDKey) } if h.keyID != nil { keys = append(keys, KeyIDKey) } if h.keyOps != nil { keys = append(keys, KeyOpsKey) } if h.keyUsage != nil { keys = append(keys, KeyUsageKey) } if h.x != nil { keys = append(keys, OKPXKey) } if h.x509CertChain != nil { keys = append(keys, X509CertChainKey) } if h.x509CertThumbprint != nil { keys = append(keys, X509CertThumbprintKey) } if h.x509CertThumbprintS256 != nil { keys = append(keys, X509CertThumbprintS256Key) } if h.x509URL != nil { keys = append(keys, X509URLKey) } for k := range h.privateParams { keys = append(keys, k) } return keys } var okpStandardFields KeyFilter func init() { okpStandardFields = NewFieldNameFilter(KeyTypeKey, KeyUsageKey, KeyOpsKey, AlgorithmKey, KeyIDKey, X509URLKey, X509CertChainKey, X509CertThumbprintKey, X509CertThumbprintS256Key, OKPCrvKey, OKPXKey, OKPDKey) } // OKPStandardFieldsFilter returns a KeyFilter that filters out standard OKP fields. func OKPStandardFieldsFilter() KeyFilter { return okpStandardFields } golang-github-lestrrat-go-jwx-3.0.13/jwk/options.go000066400000000000000000000053501515060566400222240ustar00rootroot00000000000000package jwk import ( "time" "github.com/lestrrat-go/httprc/v3" "github.com/lestrrat-go/option/v2" ) type identTypedField struct{} type typedFieldPair struct { Name string Value any } // WithTypedField allows a private field to be parsed into the object type of // your choice. It works much like the RegisterCustomField, but the effect // is only applicable to the jwt.Parse function call which receives this option. // // While this can be extremely useful, this option should be used with caution: // There are many caveats that your entire team/user-base needs to be aware of, // and therefore in general its use is discouraged. Only use it when you know // what you are doing, and you document its use clearly for others. // // First and foremost, this is a "per-object" option. Meaning that given the same // serialized format, it is possible to generate two objects whose internal // representations may differ. That is, if you parse one _WITH_ the option, // and the other _WITHOUT_, their internal representation may completely differ. // This could potentially lead to problems. // // Second, specifying this option will slightly slow down the decoding process // as it needs to consult multiple definitions sources (global and local), so // be careful if you are decoding a large number of tokens, as the effects will stack up. func WithTypedField(name string, object any) ParseOption { return &parseOption{ option.New(identTypedField{}, typedFieldPair{Name: name, Value: object}, ), } } type registerResourceOption struct { option.Interface } func (registerResourceOption) registerOption() {} func (registerResourceOption) resourceOption() {} type identNewResourceOption struct{} // WithHttprcResourceOption can be used to pass arbitrary `httprc.NewResourceOption` // to `(httprc.Client).Add` by way of `(jwk.Cache).Register`. func WithHttprcResourceOption(o httprc.NewResourceOption) RegisterOption { return ®isterResourceOption{ option.New(identNewResourceOption{}, o), } } // WithConstantInterval can be used to pass `httprc.WithConstantInterval` option to // `(httprc.Client).Add` by way of `(jwk.Cache).Register`. func WithConstantInterval(d time.Duration) RegisterOption { return WithHttprcResourceOption(httprc.WithConstantInterval(d)) } // WithMinInterval can be used to pass `httprc.WithMinInterval` option to // `(httprc.Client).Add` by way of `(jwk.Cache).Register`. func WithMinInterval(d time.Duration) RegisterOption { return WithHttprcResourceOption(httprc.WithMinInterval(d)) } // WithMaxInterval can be used to pass `httprc.WithMaxInterval` option to // `(httprc.Client).Add` by way of `(jwk.Cache).Register`. func WithMaxInterval(d time.Duration) RegisterOption { return WithHttprcResourceOption(httprc.WithMaxInterval(d)) } golang-github-lestrrat-go-jwx-3.0.13/jwk/options.yaml000066400000000000000000000130571515060566400225640ustar00rootroot00000000000000package_name: jwk output: jwk/options_gen.go interfaces: - name: CacheOption comment: | CacheOption is a type of Option that can be passed to the the `jwk.NewCache()` function. - name: ResourceOption comment: | ResourceOption is a type of Option that can be passed to the `httprc.NewResource` function by way of RegisterOption. - name: AssignKeyIDOption - name: FetchOption methods: - fetchOption - parseOption - registerOption comment: | FetchOption is a type of Option that can be passed to `jwk.Fetch()` FetchOption also implements the `RegisterOption`, and thus can safely be passed to `(*jwk.Cache).Register()` - name: ParseOption methods: - fetchOption - registerOption - readFileOption comment: | ParseOption is a type of Option that can be passed to `jwk.Parse()` ParseOption also implements the `ReadFileOption` and `NewCacheOption`, and thus safely be passed to `jwk.ReadFile` and `(*jwk.Cache).Configure()` - name: ReadFileOption comment: | ReadFileOption is a type of `Option` that can be passed to `jwk.ReadFile` - name: RegisterOption comment: | RegisterOption describes options that can be passed to `(jwk.Cache).Register()` - name: RegisterFetchOption methods: - fetchOption - registerOption - parseOption comment: | RegisterFetchOption describes options that can be passed to `(jwk.Cache).Register()` and `jwk.Fetch()` - name: GlobalOption comment: | GlobalOption is a type of Option that can be passed to the `jwk.Configure()` to change the global configuration of the jwk package. options: - ident: HTTPClient interface: RegisterFetchOption argument_type: HTTPClient comment: | WithHTTPClient allows users to specify the "net/http".Client object that is used when fetching jwk.Set objects. - ident: ThumbprintHash interface: AssignKeyIDOption argument_type: crypto.Hash - ident: LocalRegistry option_name: withLocalRegistry interface: ParseOption argument_type: '*json.Registry' comment: This option is only available for internal code. Users don't get to play with it - ident: PEM interface: ParseOption argument_type: bool comment: | WithPEM specifies that the input to `Parse()` is a PEM encoded key. This option is planned to be deprecated in the future. The plan is to replace it with `jwk.WithX509(true)` - ident: X509 interface: ParseOption argument_type: bool comment: | WithX509 specifies that the input to `Parse()` is an X.509 encoded key - ident: PEMDecoder interface: ParseOption argument_type: PEMDecoder comment: | WithPEMDecoder specifies the PEMDecoder object to use when decoding PEM encoded keys. This option can be passed to `jwk.Parse()` This option is planned to be deprecated in the future. The plan is to use `jwk.RegisterX509Decoder()` to register a custom X.509 decoder globally. - ident: FetchWhitelist interface: FetchOption argument_type: Whitelist comment: | WithFetchWhitelist specifies the Whitelist object to use when fetching JWKs from a remote source. This option can be passed to both `jwk.Fetch()` - ident: IgnoreParseError interface: ParseOption argument_type: bool comment: | WithIgnoreParseError is only applicable when used with `jwk.Parse()` (i.e. to parse JWK sets). If passed to `jwk.ParseKey()`, the function will return an error no matter what the input is. DO NOT USE WITHOUT EXHAUSTING ALL OTHER ROUTES FIRST. The option specifies that errors found during parsing of individual keys are ignored. For example, if you had keys A, B, C where B is invalid (e.g. it does not contain the required fields), then the resulting JWKS will contain keys A and C only. This options exists as an escape hatch for those times when a key in a JWKS that is irrelevant for your use case is causing your JWKS parsing to fail, and you want to get to the rest of the keys in the JWKS. Again, DO NOT USE unless you have exhausted all other routes. When you use this option, you will not be able to tell if you are using a faulty JWKS, except for when there are JSON syntax errors. - ident: FS interface: ReadFileOption argument_type: fs.FS comment: | WithFS specifies the source `fs.FS` object to read the file from. - ident: WaitReady interface: RegisterOption argument_type: bool comment: | WithWaitReady specifies that the `jwk.Cache` should wait until the first fetch is done before returning from the `Register()` call. This option is by default true. Specify a false value if you would like to return immediately from the `Register()` call. This options is exactly the same as `httprc.WithWaitReady()` - ident: StrictKeyUsage interface: GlobalOption argument_type: bool comment: | WithStrictKeyUsage specifies if during JWK parsing, the "use" field should be confined to the values that have been registered via `jwk.RegisterKeyType()`. By default this option is true, and the initial allowed values are "use" and "enc" only. If this option is set to false, then the "use" field can be any value. If this options is set to true, then the "use" field must be one of the registered values, and otherwise an error will be reported during parsing / assignment to `jwk.KeyUsageType`golang-github-lestrrat-go-jwx-3.0.13/jwk/options_gen.go000066400000000000000000000174001515060566400230540ustar00rootroot00000000000000// Code generated by tools/cmd/genoptions/main.go. DO NOT EDIT. package jwk import ( "crypto" "io/fs" "github.com/lestrrat-go/jwx/v3/internal/json" "github.com/lestrrat-go/option/v2" ) type Option = option.Interface type AssignKeyIDOption interface { Option assignKeyIDOption() } type assignKeyIDOption struct { Option } func (*assignKeyIDOption) assignKeyIDOption() {} // CacheOption is a type of Option that can be passed to the // the `jwk.NewCache()` function. type CacheOption interface { Option cacheOption() } type cacheOption struct { Option } func (*cacheOption) cacheOption() {} // FetchOption is a type of Option that can be passed to `jwk.Fetch()` // FetchOption also implements the `RegisterOption`, and thus can // safely be passed to `(*jwk.Cache).Register()` type FetchOption interface { Option fetchOption() parseOption() registerOption() } type fetchOption struct { Option } func (*fetchOption) fetchOption() {} func (*fetchOption) parseOption() {} func (*fetchOption) registerOption() {} // GlobalOption is a type of Option that can be passed to the `jwk.Configure()` to // change the global configuration of the jwk package. type GlobalOption interface { Option globalOption() } type globalOption struct { Option } func (*globalOption) globalOption() {} // ParseOption is a type of Option that can be passed to `jwk.Parse()` // ParseOption also implements the `ReadFileOption` and `NewCacheOption`, // and thus safely be passed to `jwk.ReadFile` and `(*jwk.Cache).Configure()` type ParseOption interface { Option fetchOption() registerOption() readFileOption() } type parseOption struct { Option } func (*parseOption) fetchOption() {} func (*parseOption) registerOption() {} func (*parseOption) readFileOption() {} // ReadFileOption is a type of `Option` that can be passed to `jwk.ReadFile` type ReadFileOption interface { Option readFileOption() } type readFileOption struct { Option } func (*readFileOption) readFileOption() {} // RegisterFetchOption describes options that can be passed to `(jwk.Cache).Register()` and `jwk.Fetch()` type RegisterFetchOption interface { Option fetchOption() registerOption() parseOption() } type registerFetchOption struct { Option } func (*registerFetchOption) fetchOption() {} func (*registerFetchOption) registerOption() {} func (*registerFetchOption) parseOption() {} // RegisterOption describes options that can be passed to `(jwk.Cache).Register()` type RegisterOption interface { Option registerOption() } type registerOption struct { Option } func (*registerOption) registerOption() {} // ResourceOption is a type of Option that can be passed to the `httprc.NewResource` function // by way of RegisterOption. type ResourceOption interface { Option resourceOption() } type resourceOption struct { Option } func (*resourceOption) resourceOption() {} type identFS struct{} type identFetchWhitelist struct{} type identHTTPClient struct{} type identIgnoreParseError struct{} type identLocalRegistry struct{} type identPEM struct{} type identPEMDecoder struct{} type identStrictKeyUsage struct{} type identThumbprintHash struct{} type identWaitReady struct{} type identX509 struct{} func (identFS) String() string { return "WithFS" } func (identFetchWhitelist) String() string { return "WithFetchWhitelist" } func (identHTTPClient) String() string { return "WithHTTPClient" } func (identIgnoreParseError) String() string { return "WithIgnoreParseError" } func (identLocalRegistry) String() string { return "withLocalRegistry" } func (identPEM) String() string { return "WithPEM" } func (identPEMDecoder) String() string { return "WithPEMDecoder" } func (identStrictKeyUsage) String() string { return "WithStrictKeyUsage" } func (identThumbprintHash) String() string { return "WithThumbprintHash" } func (identWaitReady) String() string { return "WithWaitReady" } func (identX509) String() string { return "WithX509" } // WithFS specifies the source `fs.FS` object to read the file from. func WithFS(v fs.FS) ReadFileOption { return &readFileOption{option.New(identFS{}, v)} } // WithFetchWhitelist specifies the Whitelist object to use when // fetching JWKs from a remote source. This option can be passed // to both `jwk.Fetch()` func WithFetchWhitelist(v Whitelist) FetchOption { return &fetchOption{option.New(identFetchWhitelist{}, v)} } // WithHTTPClient allows users to specify the "net/http".Client object that // is used when fetching jwk.Set objects. func WithHTTPClient(v HTTPClient) RegisterFetchOption { return ®isterFetchOption{option.New(identHTTPClient{}, v)} } // WithIgnoreParseError is only applicable when used with `jwk.Parse()` // (i.e. to parse JWK sets). If passed to `jwk.ParseKey()`, the function // will return an error no matter what the input is. // // DO NOT USE WITHOUT EXHAUSTING ALL OTHER ROUTES FIRST. // // The option specifies that errors found during parsing of individual // keys are ignored. For example, if you had keys A, B, C where B is // invalid (e.g. it does not contain the required fields), then the // resulting JWKS will contain keys A and C only. // // This options exists as an escape hatch for those times when a // key in a JWKS that is irrelevant for your use case is causing // your JWKS parsing to fail, and you want to get to the rest of the // keys in the JWKS. // // Again, DO NOT USE unless you have exhausted all other routes. // When you use this option, you will not be able to tell if you are // using a faulty JWKS, except for when there are JSON syntax errors. func WithIgnoreParseError(v bool) ParseOption { return &parseOption{option.New(identIgnoreParseError{}, v)} } // This option is only available for internal code. Users don't get to play with it func withLocalRegistry(v *json.Registry) ParseOption { return &parseOption{option.New(identLocalRegistry{}, v)} } // WithPEM specifies that the input to `Parse()` is a PEM encoded key. // // This option is planned to be deprecated in the future. The plan is to // replace it with `jwk.WithX509(true)` func WithPEM(v bool) ParseOption { return &parseOption{option.New(identPEM{}, v)} } // WithPEMDecoder specifies the PEMDecoder object to use when decoding // PEM encoded keys. This option can be passed to `jwk.Parse()` // // This option is planned to be deprecated in the future. The plan is to // use `jwk.RegisterX509Decoder()` to register a custom X.509 decoder globally. func WithPEMDecoder(v PEMDecoder) ParseOption { return &parseOption{option.New(identPEMDecoder{}, v)} } // WithStrictKeyUsage specifies if during JWK parsing, the "use" field // should be confined to the values that have been registered via // `jwk.RegisterKeyType()`. By default this option is true, and the // initial allowed values are "use" and "enc" only. // // If this option is set to false, then the "use" field can be any // value. If this options is set to true, then the "use" field must // be one of the registered values, and otherwise an error will be // reported during parsing / assignment to `jwk.KeyUsageType` func WithStrictKeyUsage(v bool) GlobalOption { return &globalOption{option.New(identStrictKeyUsage{}, v)} } func WithThumbprintHash(v crypto.Hash) AssignKeyIDOption { return &assignKeyIDOption{option.New(identThumbprintHash{}, v)} } // WithWaitReady specifies that the `jwk.Cache` should wait until the // first fetch is done before returning from the `Register()` call. // // This option is by default true. Specify a false value if you would // like to return immediately from the `Register()` call. // // This options is exactly the same as `httprc.WithWaitReady()` func WithWaitReady(v bool) RegisterOption { return ®isterOption{option.New(identWaitReady{}, v)} } // WithX509 specifies that the input to `Parse()` is an X.509 encoded key func WithX509(v bool) ParseOption { return &parseOption{option.New(identX509{}, v)} } golang-github-lestrrat-go-jwx-3.0.13/jwk/options_gen_test.go000066400000000000000000000015621515060566400241150ustar00rootroot00000000000000// Code generated by tools/cmd/genoptions/main.go. DO NOT EDIT. package jwk import ( "testing" "github.com/stretchr/testify/require" ) func TestOptionIdent(t *testing.T) { require.Equal(t, "WithFS", identFS{}.String()) require.Equal(t, "WithFetchWhitelist", identFetchWhitelist{}.String()) require.Equal(t, "WithHTTPClient", identHTTPClient{}.String()) require.Equal(t, "WithIgnoreParseError", identIgnoreParseError{}.String()) require.Equal(t, "withLocalRegistry", identLocalRegistry{}.String()) require.Equal(t, "WithPEM", identPEM{}.String()) require.Equal(t, "WithPEMDecoder", identPEMDecoder{}.String()) require.Equal(t, "WithStrictKeyUsage", identStrictKeyUsage{}.String()) require.Equal(t, "WithThumbprintHash", identThumbprintHash{}.String()) require.Equal(t, "WithWaitReady", identWaitReady{}.String()) require.Equal(t, "WithX509", identX509{}.String()) } golang-github-lestrrat-go-jwx-3.0.13/jwk/parser.go000066400000000000000000000173161515060566400220320ustar00rootroot00000000000000package jwk import ( "fmt" "reflect" "sync" "github.com/lestrrat-go/blackmagic" "github.com/lestrrat-go/jwx/v3/internal/json" "github.com/lestrrat-go/jwx/v3/jwa" ) // KeyParser represents a type that can parse a JSON representation of a JWK into // a jwk.Key. // See KeyConvertor for a type that can convert a raw key into a jwk.Key type KeyParser interface { // ParseKey parses a JSON payload to a `jwk.Key` object. The first // argument is an object that contains some hints as to what kind of // key the JSON payload contains. // // If your KeyParser decides that the payload is not something // you can parse, and you would like to continue parsing with // the remaining KeyParser instances that are registered, // return a `jwk.ContinueParseError`. Any other errors will immediately // halt the parsing process. // // When unmarshaling JSON, use the unmarshaler object supplied as // the second argument. This will ensure that the JSON is unmarshaled // in a way that is compatible with the rest of the library. ParseKey(probe *KeyProbe, unmarshaler KeyUnmarshaler, payload []byte) (Key, error) } // KeyParseFunc is a type of KeyParser that is based on a function/closure type KeyParseFunc func(probe *KeyProbe, unmarshaler KeyUnmarshaler, payload []byte) (Key, error) func (f KeyParseFunc) ParseKey(probe *KeyProbe, unmarshaler KeyUnmarshaler, payload []byte) (Key, error) { return f(probe, unmarshaler, payload) } // protects keyParsers var muKeyParser sync.RWMutex // list of parsers var keyParsers = []KeyParser{KeyParseFunc(defaultParseKey)} // RegisterKeyParser adds a new KeyParser. Parsers are called in FILO order. // That is, the last parser to be registered is called first. There is no // check for duplicate entries. func RegisterKeyParser(kp KeyParser) { muKeyParser.Lock() defer muKeyParser.Unlock() keyParsers = append(keyParsers, kp) } func defaultParseKey(probe *KeyProbe, unmarshaler KeyUnmarshaler, data []byte) (Key, error) { var key Key var kty string var d json.RawMessage if err := probe.Get("Kty", &kty); err != nil { return nil, fmt.Errorf(`jwk.Parse: failed to get "kty" hint: %w`, err) } // We ignore errors from this field, as it's optional _ = probe.Get("D", &d) switch v, _ := jwa.LookupKeyType(kty); v { case jwa.RSA(): if d != nil { key = newRSAPrivateKey() } else { key = newRSAPublicKey() } case jwa.EC(): if d != nil { key = newECDSAPrivateKey() } else { key = newECDSAPublicKey() } case jwa.OctetSeq(): key = newSymmetricKey() case jwa.OKP(): if d != nil { key = newOKPPrivateKey() } else { key = newOKPPublicKey() } default: return nil, fmt.Errorf(`invalid key type from JSON (%s)`, kty) } if err := unmarshaler.UnmarshalKey(data, key); err != nil { return nil, fmt.Errorf(`failed to unmarshal JSON into key (%T): %w`, key, err) } return key, nil } type keyUnmarshaler struct { localReg *json.Registry } func (ku *keyUnmarshaler) UnmarshalKey(data []byte, key any) error { if ku.localReg != nil { dcKey, ok := key.(json.DecodeCtxContainer) if !ok { return fmt.Errorf(`typed field was requested, but the key (%T) does not support DecodeCtx`, key) } dc := json.NewDecodeCtx(ku.localReg) dcKey.SetDecodeCtx(dc) defer func() { dcKey.SetDecodeCtx(nil) }() } if err := json.Unmarshal(data, key); err != nil { return fmt.Errorf(`failed to unmarshal JSON into key (%T): %w`, key, err) } return nil } // keyProber is the object that starts the probing. When Probe() is called, // it creates (possibly from a cached value) an object that is used to // hold hint values. type keyProber struct { mu sync.RWMutex pool *sync.Pool fields map[string]reflect.StructField typ reflect.Type } func (kp *keyProber) AddField(field reflect.StructField) error { kp.mu.Lock() defer kp.mu.Unlock() if _, ok := kp.fields[field.Name]; ok { return fmt.Errorf(`field name %s is already registered`, field.Name) } kp.fields[field.Name] = field kp.makeStructType() // Update pool (note: the logic is the same, but we need to recreate it // so that we don't accidentally use old stored values) kp.pool = &sync.Pool{ New: kp.makeStruct, } return nil } func (kp *keyProber) makeStructType() { // DOES NOT LOCK fields := make([]reflect.StructField, 0, len(kp.fields)) for _, f := range kp.fields { fields = append(fields, f) } kp.typ = reflect.StructOf(fields) } func (kp *keyProber) makeStruct() any { return reflect.New(kp.typ) } func (kp *keyProber) Probe(data []byte) (*KeyProbe, error) { kp.mu.RLock() defer kp.mu.RUnlock() // if the field list unchanged, so is the pool object, so effectively // we should be using the cached version v := kp.pool.Get() if v == nil { return nil, fmt.Errorf(`probe: failed to get object from pool`) } rv, ok := v.(reflect.Value) if !ok { return nil, fmt.Errorf(`probe: value returned from pool as of type %T, expected reflect.Value`, v) } if err := json.Unmarshal(data, rv.Interface()); err != nil { return nil, fmt.Errorf(`probe: failed to unmarshal data: %w`, err) } return &KeyProbe{data: rv}, nil } // KeyProbe is the object that carries the hints when parsing a key. // The exact list of fields can vary depending on the types of key // that are registered. // // Use `Get()` to access the value of a field. // // The underlying data stored in a KeyProbe is recycled each // time a value is parsed, therefore you are not allowed to hold // onto this object after ParseKey() is done. type KeyProbe struct { data reflect.Value } // Get returns the value of the field with the given `name“. // `dst` must be a pointer to a value that can hold the type of // the value of the field, which is determined by the // field type registered through `jwk.RegisterProbeField()` func (kp *KeyProbe) Get(name string, dst any) error { f := kp.data.Elem().FieldByName(name) if !f.IsValid() { return fmt.Errorf(`field %s not found`, name) } if err := blackmagic.AssignIfCompatible(dst, f.Addr().Interface()); err != nil { return fmt.Errorf(`failed to assign value of field %q to %T: %w`, name, dst, err) } return nil } // We don't really need the object, we need to know its type var keyProbe = &keyProber{ fields: make(map[string]reflect.StructField), } // RegisterProbeField adds a new field to be probed during the initial // phase of parsing. This is done by partially parsing the JSON payload, // and we do this by calling `json.Unmarshal` using a dynamic type that // can possibly be modified during runtime. This function is used to // add a new field to this dynamic type. // // Note that the `Name` field for the given `reflect.StructField` must start // with an upper case alphabet, such that it is treated as an exported field. // So for example, if you want to probe the "my_hint" field, you should specify // the field name as "MyHint" or similar. // // Also the field name must be unique. If you believe that your field name may // collide with other packages that may want to add their own probes, // it is the responsibility of the caller // to ensure that the field name is unique (possibly by prefixing the field // name with a unique string). It is important to note that the field name // need not be the same as the JSON field name. For example, your field name // could be "MyPkg_MyHint", while the actual JSON field name could be "my_hint". // // If the field name is not unique, an error is returned. func RegisterProbeField(p reflect.StructField) error { // locking is done inside keyProbe return keyProbe.AddField(p) } // KeyUnmarshaler is a thin wrapper around json.Unmarshal. It behaves almost // exactly like json.Unmarshal, but it allows us to add extra magic that // is specific to this library before calling the actual json.Unmarshal. type KeyUnmarshaler interface { UnmarshalKey(data []byte, key any) error } golang-github-lestrrat-go-jwx-3.0.13/jwk/refresh_test.go000066400000000000000000000225631515060566400232330ustar00rootroot00000000000000package jwk_test import ( "bytes" "context" "fmt" "log/slog" "net/http" "net/http/httptest" "os" "sync" "testing" "time" "github.com/lestrrat-go/httprc/v3" "github.com/lestrrat-go/httprc/v3/tracesink" "github.com/lestrrat-go/jwx/v3/internal/json" "github.com/lestrrat-go/jwx/v3/internal/jwxtest" "github.com/lestrrat-go/jwx/v3/jwk" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func checkAccessCount(t *testing.T, src jwk.Set, expected ...int) { t.Helper() key, ok := src.Key(0) require.True(t, ok, `src.Key(0) should succeed`) var v float64 require.NoError(t, key.Get(`accessCount`, &v), `key.Get("accessCount") should succeed`) for _, e := range expected { if v == float64(e) { // We _know_ this is going to pass assert.Equal(t, float64(e), v, `key.Get("accessCount") should be %d`, e) return } } var buf bytes.Buffer fmt.Fprint(&buf, "[") for i, e := range expected { fmt.Fprintf(&buf, "%d", e) if i < len(expected)-1 { fmt.Fprint(&buf, ", ") } } fmt.Fprintf(&buf, "]") require.Failf(t, `checking access count failed`, `key.Get("accessCount") should be one of %s (got %f)`, buf.String(), v) } func TestCachedSet(t *testing.T) { t.Parallel() const numKeys = 3 ctx, cancel := context.WithTimeout(context.Background(), time.Minute) defer cancel() set := jwk.NewSet() for range numKeys { key, err := jwxtest.GenerateRsaJwk() require.NoError(t, err, `jwxtest.GenerateRsaJwk should succeed`) require.NoError(t, set.AddKey(key), `set.AddKey should succeed`) } srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { hdrs := w.Header() hdrs.Set(`Content-Type`, `application/json`) hdrs.Set(`Cache-Control`, `max-age=5`) json.NewEncoder(w).Encode(set) })) defer srv.Close() c, err := jwk.NewCache(ctx, httprc.NewClient()) require.NoError(t, err, `jwk.NewCache should succeed`) require.NoError(t, c.Register(ctx, srv.URL), `af.Register should succeed`) cs, err := c.CachedSet(srv.URL) require.NoError(t, err, `c.CachedSet should succeed`) require.Error(t, cs.Set("bogus", nil), `cs.Set should be an error`) require.Error(t, cs.Remove("bogus"), `cs.Remove should be an error`) require.Error(t, cs.AddKey(nil), `cs.AddKey should be an error`) require.Error(t, cs.RemoveKey(nil), `cs.RemoveKey should be an error`) require.Equal(t, set.Len(), cs.Len(), `value of Len() should be the same`) for i := range set.Len() { k, err := set.Key(i) ck, cerr := cs.Key(i) require.Equal(t, k, ck, `key %d should match`, i) require.Equal(t, err, cerr, `error %d should match`, i) } } func TestCache_explicit_refresh_interval(t *testing.T) { t.Parallel() ctx, cancel := context.WithTimeout(context.Background(), time.Minute) defer cancel() var accessCount int srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { accessCount++ key := map[string]any{ "kty": "EC", "crv": "P-256", "x": "SVqB4JcUD6lsfvqMr-OKUNUphdNn64Eay60978ZlL74", "y": "lf0u0pMj4lGAzZix5u4Cm5CMQIgMNpkwy163wtKYVKI", "accessCount": accessCount, } hdrs := w.Header() hdrs.Set(`Content-Type`, `application/json`) hdrs.Set(`Cache-Control`, `max-age=7200`) // Make sure this is ignored json.NewEncoder(w).Encode(key) })) defer srv.Close() c, err := jwk.NewCache(ctx, httprc.NewClient( // httprc.WithTraceSink(tracesink.NewSlog(slog.New(slog.NewJSONHandler(os.Stdout, nil)))), )) require.NoError(t, err, `jwk.NewCache should succeed`) require.NoError(t, c.Register(ctx, srv.URL, jwk.WithConstantInterval(2*time.Second+500*time.Millisecond)), `c.Register should succeed`) retries := 5 var wg sync.WaitGroup wg.Add(retries) for range retries { // Run these in separate goroutines to emulate a possible thundering herd go func() { defer wg.Done() ks, err := c.Lookup(ctx, srv.URL) require.NoError(t, err, `c.Lookup should succeed`) require.NotNil(t, ks, `c.Lookup should return a non-nil key set`) checkAccessCount(t, ks, 1) }() } t.Logf("Waiting for fetching goroutines...") wg.Wait() t.Logf("Waiting for the refresh ...") time.Sleep(6 * time.Second) ks, err := c.Lookup(ctx, srv.URL) require.NoError(t, err, `c.Lookup should succeed`) checkAccessCount(t, ks, 2, 3) } func TestCache_calculate_interval_from_cache_control(t *testing.T) { t.Parallel() ctx, cancel := context.WithTimeout(context.Background(), time.Minute) defer cancel() var accessCount int srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { accessCount++ key := map[string]any{ "kty": "EC", "crv": "P-256", "x": "SVqB4JcUD6lsfvqMr-OKUNUphdNn64Eay60978ZlL74", "y": "lf0u0pMj4lGAzZix5u4Cm5CMQIgMNpkwy163wtKYVKI", "accessCount": accessCount, } hdrs := w.Header() hdrs.Set(`Content-Type`, `application/json`) hdrs.Set(`Cache-Control`, `max-age=3`) json.NewEncoder(w).Encode(key) })) defer srv.Close() c, err := jwk.NewCache(ctx, httprc.NewClient( httprc.WithTraceSink(tracesink.NewSlog( slog.New(slog.NewJSONHandler(os.Stdout, nil)).With("test", "Cache_calculate_interval_from_cache_control"), )), )) require.NoError(t, err, `jwk.NewCache should succeed`) require.NoError(t, c.Register(ctx, srv.URL, jwk.WithMinInterval(3*time.Second), ), `c.Register should succeed`) require.True(t, c.IsRegistered(ctx, srv.URL), `c.IsRegistered should be true`) retries := 5 var wg sync.WaitGroup wg.Add(retries) for range retries { // Run these in separate goroutines to emulate a possible thundering herd go func() { defer wg.Done() ks, err := c.Lookup(ctx, srv.URL) require.NoError(t, err, `c.Lookup should succeed`) require.NotNil(t, ks, `c.Lookup should return a non-nil key set`) checkAccessCount(t, ks, 1) }() } t.Logf("Waiting for fetching goroutines...") wg.Wait() t.Logf("Waiting for the refresh ...") time.Sleep(4 * time.Second) ks, err := c.Lookup(ctx, srv.URL) require.NoError(t, err, `c.Lookup should succeed`) checkAccessCount(t, ks, 2) } func TestCache_backoff(t *testing.T) { t.Parallel() ctx, cancel := context.WithTimeout(context.Background(), time.Minute) defer cancel() var accessCount int srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { hdrs := w.Header() hdrs.Set(`Cache-Control`, `max-age=1`) accessCount++ if accessCount > 1 && accessCount < 4 { http.Error(w, "wait for it....", http.StatusForbidden) return } key := map[string]any{ "kty": "EC", "crv": "P-256", "x": "SVqB4JcUD6lsfvqMr-OKUNUphdNn64Eay60978ZlL74", "y": "lf0u0pMj4lGAzZix5u4Cm5CMQIgMNpkwy163wtKYVKI", "accessCount": accessCount, } hdrs.Set(`Content-Type`, `application/json`) json.NewEncoder(w).Encode(key) })) defer srv.Close() c, err := jwk.NewCache(ctx, httprc.NewClient( httprc.WithTraceSink(tracesink.NewSlog( slog.New(slog.NewJSONHandler(os.Stdout, nil)).With("test", "Cache_bacckoff"), )), )) require.NoError(t, err, `jwk.NewCache should succeed`) require.NoError(t, c.Register(ctx, srv.URL, jwk.WithMinInterval(time.Second)), `c.Register should succeed`) // First fetch should succeed ks, err := c.Lookup(ctx, srv.URL) require.NoError(t, err, `c.Lookup (#1) should succeed`) require.NotNil(t, ks, `c.Lookup (#1) should return a non-nil key set`) checkAccessCount(t, ks, 1) // enough time for 1 refresh to have occurred time.Sleep(1500 * time.Millisecond) ks, err = c.Lookup(ctx, srv.URL) require.NoError(t, err, `c.Lookup (#2) should succeed`) require.NotNil(t, ks, `c.Lookup (#2) should return a non-nil key set`) // Should be using the cached version checkAccessCount(t, ks, 1) // enough time for 2 refreshes to have occurred time.Sleep(3000 * time.Millisecond) ks, err = c.Lookup(ctx, srv.URL) require.NoError(t, err, `c.Lookup (#3) should succeed`) require.NotNil(t, ks, `c.Lookup (#3) should return a non-nil key set`) // should be new checkAccessCount(t, ks, 4, 5) } type accumulateErrs struct { mu sync.RWMutex errs []error } func (e *accumulateErrs) Put(_ context.Context, err error) { e.mu.Lock() e.errs = append(e.errs, err) e.mu.Unlock() } func (e *accumulateErrs) Len() int { e.mu.RLock() l := len(e.errs) e.mu.RUnlock() return l } func TestErrorSink(t *testing.T) { t.Parallel() k, err := jwxtest.GenerateRsaJwk() require.NoError(t, err, `jwxtest.GenerateRsaJwk should succeed`) set := jwk.NewSet() _ = set.AddKey(k) testcases := []struct { Name string Options func() []httprc.NewClientOption Handler http.Handler }{ { Name: `rejected by whitelist`, Options: func() []httprc.NewClientOption { return []httprc.NewClientOption{ httprc.WithWhitelist(httprc.NewBlockAllWhitelist()), } }, Handler: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(k) }), }, } for _, tc := range testcases { t.Run(tc.Name, func(t *testing.T) { t.Parallel() ctx, cancel := context.WithTimeout(context.Background(), time.Minute) defer cancel() srv := httptest.NewServer(tc.Handler) defer srv.Close() var errSink accumulateErrs options := append(tc.Options(), httprc.WithErrorSink(&errSink)) c, err := jwk.NewCache(ctx, httprc.NewClient(options...)) require.NoError(t, err, `jwk.NewCache should succeed`) require.Error(t, c.Register(ctx, srv.URL), `c.Register should fail`) }) } } golang-github-lestrrat-go-jwx-3.0.13/jwk/rsa.go000066400000000000000000000175761515060566400213330ustar00rootroot00000000000000package jwk import ( "crypto" "crypto/rsa" "encoding/binary" "fmt" "math/big" "reflect" "github.com/lestrrat-go/jwx/v3/internal/base64" "github.com/lestrrat-go/jwx/v3/internal/pool" "github.com/lestrrat-go/jwx/v3/jwa" ) func init() { RegisterKeyExporter(jwa.RSA(), KeyExportFunc(rsaJWKToRaw)) } func (k *rsaPrivateKey) Import(rawKey *rsa.PrivateKey) error { k.mu.Lock() defer k.mu.Unlock() d, err := bigIntToBytes(rawKey.D) if err != nil { return fmt.Errorf(`invalid rsa.PrivateKey: %w`, err) } k.d = d l := len(rawKey.Primes) if l < 0 /* I know, I'm being paranoid */ || l > 2 { return fmt.Errorf(`invalid number of primes in rsa.PrivateKey: need 0 to 2, but got %d`, len(rawKey.Primes)) } if l > 0 { p, err := bigIntToBytes(rawKey.Primes[0]) if err != nil { return fmt.Errorf(`invalid rsa.PrivateKey: %w`, err) } k.p = p } if l > 1 { q, err := bigIntToBytes(rawKey.Primes[1]) if err != nil { return fmt.Errorf(`invalid rsa.PrivateKey: %w`, err) } k.q = q } // dp, dq, qi are optional values if v, err := bigIntToBytes(rawKey.Precomputed.Dp); err == nil { k.dp = v } if v, err := bigIntToBytes(rawKey.Precomputed.Dq); err == nil { k.dq = v } if v, err := bigIntToBytes(rawKey.Precomputed.Qinv); err == nil { k.qi = v } // public key part n, e, err := importRsaPublicKeyByteValues(&rawKey.PublicKey) if err != nil { return fmt.Errorf(`invalid rsa.PrivateKey: %w`, err) } k.n = n k.e = e return nil } func importRsaPublicKeyByteValues(rawKey *rsa.PublicKey) ([]byte, []byte, error) { n, err := bigIntToBytes(rawKey.N) if err != nil { return nil, nil, fmt.Errorf(`invalid rsa.PublicKey: %w`, err) } data := make([]byte, 8) binary.BigEndian.PutUint64(data, uint64(rawKey.E)) i := 0 for ; i < len(data); i++ { if data[i] != 0x0 { break } } return n, data[i:], nil } func (k *rsaPublicKey) Import(rawKey *rsa.PublicKey) error { k.mu.Lock() defer k.mu.Unlock() n, e, err := importRsaPublicKeyByteValues(rawKey) if err != nil { return fmt.Errorf(`invalid rsa.PrivateKey: %w`, err) } k.n = n k.e = e return nil } func buildRSAPublicKey(key *rsa.PublicKey, n, e []byte) { bin := pool.BigInt().Get() bie := pool.BigInt().Get() defer pool.BigInt().Put(bie) bin.SetBytes(n) bie.SetBytes(e) key.N = bin key.E = int(bie.Int64()) } var rsaConvertibleKeys = []reflect.Type{ reflect.TypeFor[RSAPrivateKey](), reflect.TypeFor[RSAPublicKey](), } func rsaJWKToRaw(key Key, hint any) (any, error) { extracted, err := extractEmbeddedKey(key, rsaConvertibleKeys) if err != nil { return nil, fmt.Errorf(`failed to extract embedded key: %w`, err) } switch key := extracted.(type) { case RSAPrivateKey: switch hint.(type) { case *rsa.PrivateKey, *any: default: return nil, fmt.Errorf(`invalid destination object type %T for private RSA JWK: %w`, hint, ContinueError()) } locker, ok := key.(rlocker) if !ok { locker.rlock() defer locker.runlock() } od, ok := key.D() if !ok { return nil, fmt.Errorf(`missing "d" value`) } oq, ok := key.Q() if !ok { return nil, fmt.Errorf(`missing "q" value`) } op, ok := key.P() if !ok { return nil, fmt.Errorf(`missing "p" value`) } var d, q, p big.Int // note: do not use from sync.Pool d.SetBytes(od) q.SetBytes(oq) p.SetBytes(op) // optional fields var dp, dq, qi *big.Int if odp, ok := key.DP(); ok { dp = &big.Int{} // note: do not use from sync.Pool dp.SetBytes(odp) } if odq, ok := key.DQ(); ok { dq = &big.Int{} // note: do not use from sync.Pool dq.SetBytes(odq) } if oqi, ok := key.QI(); ok { qi = &big.Int{} // note: do not use from sync.Pool qi.SetBytes(oqi) } n, ok := key.N() if !ok { return nil, fmt.Errorf(`missing "n" value`) } e, ok := key.E() if !ok { return nil, fmt.Errorf(`missing "e" value`) } var privkey rsa.PrivateKey buildRSAPublicKey(&privkey.PublicKey, n, e) privkey.D = &d privkey.Primes = []*big.Int{&p, &q} if dp != nil { privkey.Precomputed.Dp = dp } if dq != nil { privkey.Precomputed.Dq = dq } if qi != nil { privkey.Precomputed.Qinv = qi } // This may look like a no-op, but it's required if we want to // compare it against a key generated by rsa.GenerateKey privkey.Precomputed.CRTValues = []rsa.CRTValue{} return &privkey, nil case RSAPublicKey: switch hint.(type) { case *rsa.PublicKey, *any: default: return nil, fmt.Errorf(`invalid destination object type %T for public RSA JWK: %w`, hint, ContinueError()) } locker, ok := key.(rlocker) if !ok { locker.rlock() defer locker.runlock() } n, ok := key.N() if !ok { return nil, fmt.Errorf(`missing "n" value`) } e, ok := key.E() if !ok { return nil, fmt.Errorf(`missing "e" value`) } var pubkey rsa.PublicKey buildRSAPublicKey(&pubkey, n, e) return &pubkey, nil default: return nil, ContinueError() } } func makeRSAPublicKey(src Key) (Key, error) { newKey := newRSAPublicKey() // Iterate and copy everything except for the bits that should not be in the public key for _, k := range src.Keys() { switch k { case RSADKey, RSADPKey, RSADQKey, RSAPKey, RSAQKey, RSAQIKey: continue default: var v any if err := src.Get(k, &v); err != nil { return nil, fmt.Errorf(`rsa: makeRSAPublicKey: failed to get field %q: %w`, k, err) } if err := newKey.Set(k, v); err != nil { return nil, fmt.Errorf(`rsa: makeRSAPublicKey: failed to set field %q: %w`, k, err) } } } return newKey, nil } func (k *rsaPrivateKey) PublicKey() (Key, error) { return makeRSAPublicKey(k) } func (k *rsaPublicKey) PublicKey() (Key, error) { return makeRSAPublicKey(k) } // Thumbprint returns the JWK thumbprint using the indicated // hashing algorithm, according to RFC 7638 func (k rsaPrivateKey) Thumbprint(hash crypto.Hash) ([]byte, error) { k.mu.RLock() defer k.mu.RUnlock() var key rsa.PrivateKey if err := Export(&k, &key); err != nil { return nil, fmt.Errorf(`failed to export RSA private key: %w`, err) } return rsaThumbprint(hash, &key.PublicKey) } func (k rsaPublicKey) Thumbprint(hash crypto.Hash) ([]byte, error) { k.mu.RLock() defer k.mu.RUnlock() var key rsa.PublicKey if err := Export(&k, &key); err != nil { return nil, fmt.Errorf(`failed to export RSA public key: %w`, err) } return rsaThumbprint(hash, &key) } func rsaThumbprint(hash crypto.Hash, key *rsa.PublicKey) ([]byte, error) { buf := pool.BytesBuffer().Get() defer pool.BytesBuffer().Put(buf) buf.WriteString(`{"e":"`) buf.WriteString(base64.EncodeUint64ToString(uint64(key.E))) buf.WriteString(`","kty":"RSA","n":"`) buf.WriteString(base64.EncodeToString(key.N.Bytes())) buf.WriteString(`"}`) h := hash.New() if _, err := buf.WriteTo(h); err != nil { return nil, fmt.Errorf(`failed to write rsaThumbprint: %w`, err) } return h.Sum(nil), nil } func validateRSAKey(key interface { N() ([]byte, bool) E() ([]byte, bool) }, checkPrivate bool) error { n, ok := key.N() if !ok { return fmt.Errorf(`missing "n" value`) } e, ok := key.E() if !ok { return fmt.Errorf(`missing "e" value`) } if len(n) == 0 { // Ideally we would like to check for the actual length, but unlike // EC keys, we have nothing in the key itself that will tell us // how many bits this key should have. return fmt.Errorf(`missing "n" value`) } if len(e) == 0 { return fmt.Errorf(`missing "e" value`) } if checkPrivate { if priv, ok := key.(keyWithD); ok { if d, ok := priv.D(); !ok || len(d) == 0 { return fmt.Errorf(`missing "d" value`) } } else { return fmt.Errorf(`missing "d" value`) } } return nil } func (k *rsaPrivateKey) Validate() error { if err := validateRSAKey(k, true); err != nil { return NewKeyValidationError(fmt.Errorf(`jwk.RSAPrivateKey: %w`, err)) } return nil } func (k *rsaPublicKey) Validate() error { if err := validateRSAKey(k, false); err != nil { return NewKeyValidationError(fmt.Errorf(`jwk.RSAPublicKey: %w`, err)) } return nil } golang-github-lestrrat-go-jwx-3.0.13/jwk/rsa_gen.go000066400000000000000000001173711515060566400221560ustar00rootroot00000000000000// Code generated by tools/cmd/genjwk/main.go. DO NOT EDIT. package jwk import ( "bytes" "fmt" "sort" "sync" "github.com/lestrrat-go/blackmagic" "github.com/lestrrat-go/jwx/v3/cert" "github.com/lestrrat-go/jwx/v3/internal/base64" "github.com/lestrrat-go/jwx/v3/internal/json" "github.com/lestrrat-go/jwx/v3/internal/pool" "github.com/lestrrat-go/jwx/v3/internal/tokens" "github.com/lestrrat-go/jwx/v3/jwa" ) const ( RSADKey = "d" RSADPKey = "dp" RSADQKey = "dq" RSAEKey = "e" RSANKey = "n" RSAPKey = "p" RSAQIKey = "qi" RSAQKey = "q" ) type RSAPublicKey interface { Key E() ([]byte, bool) N() ([]byte, bool) } type rsaPublicKey struct { algorithm *jwa.KeyAlgorithm // https://tools.ietf.org/html/rfc7517#section-4.4 e []byte keyID *string // https://tools.ietf.org/html/rfc7515#section-4.1.4 keyOps *KeyOperationList // https://tools.ietf.org/html/rfc7517#section-4.3 keyUsage *string // https://tools.ietf.org/html/rfc7517#section-4.2 n []byte x509CertChain *cert.Chain // https://tools.ietf.org/html/rfc7515#section-4.1.6 x509CertThumbprint *string // https://tools.ietf.org/html/rfc7515#section-4.1.7 x509CertThumbprintS256 *string // https://tools.ietf.org/html/rfc7515#section-4.1.8 x509URL *string // https://tools.ietf.org/html/rfc7515#section-4.1.5 privateParams map[string]any mu *sync.RWMutex dc json.DecodeCtx } var _ RSAPublicKey = &rsaPublicKey{} var _ Key = &rsaPublicKey{} func newRSAPublicKey() *rsaPublicKey { return &rsaPublicKey{ mu: &sync.RWMutex{}, privateParams: make(map[string]any), } } func (h rsaPublicKey) KeyType() jwa.KeyType { return jwa.RSA() } func (h rsaPublicKey) rlock() { h.mu.RLock() } func (h rsaPublicKey) runlock() { h.mu.RUnlock() } func (h rsaPublicKey) IsPrivate() bool { return false } func (h *rsaPublicKey) Algorithm() (jwa.KeyAlgorithm, bool) { if h.algorithm != nil { return *(h.algorithm), true } return nil, false } func (h *rsaPublicKey) E() ([]byte, bool) { if h.e != nil { return h.e, true } return nil, false } func (h *rsaPublicKey) KeyID() (string, bool) { if h.keyID != nil { return *(h.keyID), true } return "", false } func (h *rsaPublicKey) KeyOps() (KeyOperationList, bool) { if h.keyOps != nil { return *(h.keyOps), true } return nil, false } func (h *rsaPublicKey) KeyUsage() (string, bool) { if h.keyUsage != nil { return *(h.keyUsage), true } return "", false } func (h *rsaPublicKey) N() ([]byte, bool) { if h.n != nil { return h.n, true } return nil, false } func (h *rsaPublicKey) X509CertChain() (*cert.Chain, bool) { return h.x509CertChain, true } func (h *rsaPublicKey) X509CertThumbprint() (string, bool) { if h.x509CertThumbprint != nil { return *(h.x509CertThumbprint), true } return "", false } func (h *rsaPublicKey) X509CertThumbprintS256() (string, bool) { if h.x509CertThumbprintS256 != nil { return *(h.x509CertThumbprintS256), true } return "", false } func (h *rsaPublicKey) X509URL() (string, bool) { if h.x509URL != nil { return *(h.x509URL), true } return "", false } func (h *rsaPublicKey) Has(name string) bool { h.mu.RLock() defer h.mu.RUnlock() switch name { case KeyTypeKey: return true case AlgorithmKey: return h.algorithm != nil case RSAEKey: return h.e != nil case KeyIDKey: return h.keyID != nil case KeyOpsKey: return h.keyOps != nil case KeyUsageKey: return h.keyUsage != nil case RSANKey: return h.n != nil case X509CertChainKey: return h.x509CertChain != nil case X509CertThumbprintKey: return h.x509CertThumbprint != nil case X509CertThumbprintS256Key: return h.x509CertThumbprintS256 != nil case X509URLKey: return h.x509URL != nil default: _, ok := h.privateParams[name] return ok } } func (h *rsaPublicKey) Get(name string, dst any) error { h.mu.RLock() defer h.mu.RUnlock() switch name { case KeyTypeKey: if err := blackmagic.AssignIfCompatible(dst, h.KeyType()); err != nil { return fmt.Errorf(`rsaPublicKey.Get: failed to assign value for field %q to destination object: %w`, name, err) } case AlgorithmKey: if h.algorithm == nil { return fmt.Errorf(`field %q not found`, name) } if err := blackmagic.AssignIfCompatible(dst, *(h.algorithm)); err != nil { return fmt.Errorf(`failed to assign value for field %q: %w`, name, err) } return nil case RSAEKey: if h.e == nil { return fmt.Errorf(`field %q not found`, name) } if err := blackmagic.AssignIfCompatible(dst, h.e); err != nil { return fmt.Errorf(`failed to assign value for field %q: %w`, name, err) } return nil case KeyIDKey: if h.keyID == nil { return fmt.Errorf(`field %q not found`, name) } if err := blackmagic.AssignIfCompatible(dst, *(h.keyID)); err != nil { return fmt.Errorf(`failed to assign value for field %q: %w`, name, err) } return nil case KeyOpsKey: if h.keyOps == nil { return fmt.Errorf(`field %q not found`, name) } if err := blackmagic.AssignIfCompatible(dst, *(h.keyOps)); err != nil { return fmt.Errorf(`failed to assign value for field %q: %w`, name, err) } return nil case KeyUsageKey: if h.keyUsage == nil { return fmt.Errorf(`field %q not found`, name) } if err := blackmagic.AssignIfCompatible(dst, *(h.keyUsage)); err != nil { return fmt.Errorf(`failed to assign value for field %q: %w`, name, err) } return nil case RSANKey: if h.n == nil { return fmt.Errorf(`field %q not found`, name) } if err := blackmagic.AssignIfCompatible(dst, h.n); err != nil { return fmt.Errorf(`failed to assign value for field %q: %w`, name, err) } return nil case X509CertChainKey: if h.x509CertChain == nil { return fmt.Errorf(`field %q not found`, name) } if err := blackmagic.AssignIfCompatible(dst, h.x509CertChain); err != nil { return fmt.Errorf(`failed to assign value for field %q: %w`, name, err) } return nil case X509CertThumbprintKey: if h.x509CertThumbprint == nil { return fmt.Errorf(`field %q not found`, name) } if err := blackmagic.AssignIfCompatible(dst, *(h.x509CertThumbprint)); err != nil { return fmt.Errorf(`failed to assign value for field %q: %w`, name, err) } return nil case X509CertThumbprintS256Key: if h.x509CertThumbprintS256 == nil { return fmt.Errorf(`field %q not found`, name) } if err := blackmagic.AssignIfCompatible(dst, *(h.x509CertThumbprintS256)); err != nil { return fmt.Errorf(`failed to assign value for field %q: %w`, name, err) } return nil case X509URLKey: if h.x509URL == nil { return fmt.Errorf(`field %q not found`, name) } if err := blackmagic.AssignIfCompatible(dst, *(h.x509URL)); err != nil { return fmt.Errorf(`failed to assign value for field %q: %w`, name, err) } return nil default: v, ok := h.privateParams[name] if !ok { return fmt.Errorf(`field %q not found`, name) } if err := blackmagic.AssignIfCompatible(dst, v); err != nil { return fmt.Errorf(`failed to assign value for field %q: %w`, name, err) } } return nil } func (h *rsaPublicKey) Set(name string, value any) error { h.mu.Lock() defer h.mu.Unlock() return h.setNoLock(name, value) } func (h *rsaPublicKey) setNoLock(name string, value any) error { switch name { case "kty": return nil case AlgorithmKey: switch v := value.(type) { case string, jwa.SignatureAlgorithm, jwa.KeyEncryptionAlgorithm, jwa.ContentEncryptionAlgorithm: tmp, err := jwa.KeyAlgorithmFrom(v) if err != nil { return fmt.Errorf(`invalid algorithm for %q key: %w`, AlgorithmKey, err) } h.algorithm = &tmp default: return fmt.Errorf(`invalid type for %q key: %T`, AlgorithmKey, value) } return nil case RSAEKey: if v, ok := value.([]byte); ok { h.e = v return nil } return fmt.Errorf(`invalid value for %s key: %T`, RSAEKey, value) case KeyIDKey: if v, ok := value.(string); ok { h.keyID = &v return nil } return fmt.Errorf(`invalid value for %s key: %T`, KeyIDKey, value) case KeyOpsKey: var acceptor KeyOperationList if err := acceptor.Accept(value); err != nil { return fmt.Errorf(`invalid value for %s key: %w`, KeyOpsKey, err) } h.keyOps = &acceptor return nil case KeyUsageKey: switch v := value.(type) { case KeyUsageType: switch v { case ForSignature, ForEncryption: tmp := v.String() h.keyUsage = &tmp default: return fmt.Errorf(`invalid key usage type %s`, v) } case string: h.keyUsage = &v default: return fmt.Errorf(`invalid key usage type %s`, v) } case RSANKey: if v, ok := value.([]byte); ok { h.n = v return nil } return fmt.Errorf(`invalid value for %s key: %T`, RSANKey, value) case X509CertChainKey: if v, ok := value.(*cert.Chain); ok { h.x509CertChain = v return nil } return fmt.Errorf(`invalid value for %s key: %T`, X509CertChainKey, value) case X509CertThumbprintKey: if v, ok := value.(string); ok { h.x509CertThumbprint = &v return nil } return fmt.Errorf(`invalid value for %s key: %T`, X509CertThumbprintKey, value) case X509CertThumbprintS256Key: if v, ok := value.(string); ok { h.x509CertThumbprintS256 = &v return nil } return fmt.Errorf(`invalid value for %s key: %T`, X509CertThumbprintS256Key, value) case X509URLKey: if v, ok := value.(string); ok { h.x509URL = &v return nil } return fmt.Errorf(`invalid value for %s key: %T`, X509URLKey, value) default: if h.privateParams == nil { h.privateParams = map[string]any{} } h.privateParams[name] = value } return nil } func (k *rsaPublicKey) Remove(key string) error { k.mu.Lock() defer k.mu.Unlock() switch key { case AlgorithmKey: k.algorithm = nil case RSAEKey: k.e = nil case KeyIDKey: k.keyID = nil case KeyOpsKey: k.keyOps = nil case KeyUsageKey: k.keyUsage = nil case RSANKey: k.n = nil case X509CertChainKey: k.x509CertChain = nil case X509CertThumbprintKey: k.x509CertThumbprint = nil case X509CertThumbprintS256Key: k.x509CertThumbprintS256 = nil case X509URLKey: k.x509URL = nil default: delete(k.privateParams, key) } return nil } func (k *rsaPublicKey) Clone() (Key, error) { key, err := cloneKey(k) if err != nil { return nil, fmt.Errorf(`rsaPublicKey.Clone: %w`, err) } return key, nil } func (k *rsaPublicKey) DecodeCtx() json.DecodeCtx { k.mu.RLock() defer k.mu.RUnlock() return k.dc } func (k *rsaPublicKey) SetDecodeCtx(dc json.DecodeCtx) { k.mu.Lock() defer k.mu.Unlock() k.dc = dc } func (h *rsaPublicKey) UnmarshalJSON(buf []byte) error { h.mu.Lock() defer h.mu.Unlock() h.algorithm = nil h.e = nil h.keyID = nil h.keyOps = nil h.keyUsage = nil h.n = nil h.x509CertChain = nil h.x509CertThumbprint = nil h.x509CertThumbprintS256 = nil h.x509URL = nil dec := json.NewDecoder(bytes.NewReader(buf)) LOOP: for { tok, err := dec.Token() if err != nil { return fmt.Errorf(`error reading token: %w`, err) } switch tok := tok.(type) { case json.Delim: // Assuming we're doing everything correctly, we should ONLY // get either tokens.OpenCurlyBracket or tokens.CloseCurlyBracket here. if tok == tokens.CloseCurlyBracket { // End of object break LOOP } else if tok != tokens.OpenCurlyBracket { return fmt.Errorf(`expected '%c' but got '%c'`, tokens.OpenCurlyBracket, tok) } case string: // Objects can only have string keys switch tok { case KeyTypeKey: val, err := json.ReadNextStringToken(dec) if err != nil { return fmt.Errorf(`error reading token: %w`, err) } if val != jwa.RSA().String() { return fmt.Errorf(`invalid kty value for RSAPublicKey (%s)`, val) } case AlgorithmKey: var s string if err := dec.Decode(&s); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, AlgorithmKey, err) } alg, err := jwa.KeyAlgorithmFrom(s) if err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, AlgorithmKey, err) } h.algorithm = &alg case RSAEKey: if err := json.AssignNextBytesToken(&h.e, dec); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, RSAEKey, err) } case KeyIDKey: if err := json.AssignNextStringToken(&h.keyID, dec); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, KeyIDKey, err) } case KeyOpsKey: var decoded KeyOperationList if err := dec.Decode(&decoded); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, KeyOpsKey, err) } h.keyOps = &decoded case KeyUsageKey: if err := json.AssignNextStringToken(&h.keyUsage, dec); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, KeyUsageKey, err) } case RSANKey: if err := json.AssignNextBytesToken(&h.n, dec); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, RSANKey, err) } case X509CertChainKey: var decoded cert.Chain if err := dec.Decode(&decoded); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, X509CertChainKey, err) } h.x509CertChain = &decoded case X509CertThumbprintKey: if err := json.AssignNextStringToken(&h.x509CertThumbprint, dec); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, X509CertThumbprintKey, err) } case X509CertThumbprintS256Key: if err := json.AssignNextStringToken(&h.x509CertThumbprintS256, dec); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, X509CertThumbprintS256Key, err) } case X509URLKey: if err := json.AssignNextStringToken(&h.x509URL, dec); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, X509URLKey, err) } default: if dc := h.dc; dc != nil { if localReg := dc.Registry(); localReg != nil { decoded, err := localReg.Decode(dec, tok) if err == nil { h.setNoLock(tok, decoded) continue } } } decoded, err := registry.Decode(dec, tok) if err == nil { h.setNoLock(tok, decoded) continue } return fmt.Errorf(`could not decode field %s: %w`, tok, err) } default: return fmt.Errorf(`invalid token %T`, tok) } } if h.e == nil { return fmt.Errorf(`required field e is missing`) } if h.n == nil { return fmt.Errorf(`required field n is missing`) } return nil } func (h rsaPublicKey) MarshalJSON() ([]byte, error) { data := make(map[string]any) fields := make([]string, 0, 10) data[KeyTypeKey] = jwa.RSA() fields = append(fields, KeyTypeKey) if h.algorithm != nil { data[AlgorithmKey] = *(h.algorithm) fields = append(fields, AlgorithmKey) } if h.e != nil { data[RSAEKey] = h.e fields = append(fields, RSAEKey) } if h.keyID != nil { data[KeyIDKey] = *(h.keyID) fields = append(fields, KeyIDKey) } if h.keyOps != nil { data[KeyOpsKey] = *(h.keyOps) fields = append(fields, KeyOpsKey) } if h.keyUsage != nil { data[KeyUsageKey] = *(h.keyUsage) fields = append(fields, KeyUsageKey) } if h.n != nil { data[RSANKey] = h.n fields = append(fields, RSANKey) } if h.x509CertChain != nil { data[X509CertChainKey] = h.x509CertChain fields = append(fields, X509CertChainKey) } if h.x509CertThumbprint != nil { data[X509CertThumbprintKey] = *(h.x509CertThumbprint) fields = append(fields, X509CertThumbprintKey) } if h.x509CertThumbprintS256 != nil { data[X509CertThumbprintS256Key] = *(h.x509CertThumbprintS256) fields = append(fields, X509CertThumbprintS256Key) } if h.x509URL != nil { data[X509URLKey] = *(h.x509URL) fields = append(fields, X509URLKey) } for k, v := range h.privateParams { data[k] = v fields = append(fields, k) } sort.Strings(fields) buf := pool.BytesBuffer().Get() defer pool.BytesBuffer().Put(buf) buf.WriteByte(tokens.OpenCurlyBracket) enc := json.NewEncoder(buf) for i, f := range fields { if i > 0 { buf.WriteRune(tokens.Comma) } buf.WriteRune(tokens.DoubleQuote) buf.WriteString(f) buf.WriteString(`":`) v := data[f] switch v := v.(type) { case []byte: buf.WriteRune(tokens.DoubleQuote) buf.WriteString(base64.EncodeToString(v)) buf.WriteRune(tokens.DoubleQuote) default: if err := enc.Encode(v); err != nil { return nil, fmt.Errorf(`failed to encode value for field %s: %w`, f, err) } buf.Truncate(buf.Len() - 1) } } buf.WriteByte(tokens.CloseCurlyBracket) ret := make([]byte, buf.Len()) copy(ret, buf.Bytes()) return ret, nil } func (h *rsaPublicKey) Keys() []string { h.mu.RLock() defer h.mu.RUnlock() keys := make([]string, 0, 10+len(h.privateParams)) keys = append(keys, KeyTypeKey) if h.algorithm != nil { keys = append(keys, AlgorithmKey) } if h.e != nil { keys = append(keys, RSAEKey) } if h.keyID != nil { keys = append(keys, KeyIDKey) } if h.keyOps != nil { keys = append(keys, KeyOpsKey) } if h.keyUsage != nil { keys = append(keys, KeyUsageKey) } if h.n != nil { keys = append(keys, RSANKey) } if h.x509CertChain != nil { keys = append(keys, X509CertChainKey) } if h.x509CertThumbprint != nil { keys = append(keys, X509CertThumbprintKey) } if h.x509CertThumbprintS256 != nil { keys = append(keys, X509CertThumbprintS256Key) } if h.x509URL != nil { keys = append(keys, X509URLKey) } for k := range h.privateParams { keys = append(keys, k) } return keys } type RSAPrivateKey interface { Key D() ([]byte, bool) DP() ([]byte, bool) DQ() ([]byte, bool) E() ([]byte, bool) N() ([]byte, bool) P() ([]byte, bool) Q() ([]byte, bool) QI() ([]byte, bool) } type rsaPrivateKey struct { algorithm *jwa.KeyAlgorithm // https://tools.ietf.org/html/rfc7517#section-4.4 d []byte dp []byte dq []byte e []byte keyID *string // https://tools.ietf.org/html/rfc7515#section-4.1.4 keyOps *KeyOperationList // https://tools.ietf.org/html/rfc7517#section-4.3 keyUsage *string // https://tools.ietf.org/html/rfc7517#section-4.2 n []byte p []byte q []byte qi []byte x509CertChain *cert.Chain // https://tools.ietf.org/html/rfc7515#section-4.1.6 x509CertThumbprint *string // https://tools.ietf.org/html/rfc7515#section-4.1.7 x509CertThumbprintS256 *string // https://tools.ietf.org/html/rfc7515#section-4.1.8 x509URL *string // https://tools.ietf.org/html/rfc7515#section-4.1.5 privateParams map[string]any mu *sync.RWMutex dc json.DecodeCtx } var _ RSAPrivateKey = &rsaPrivateKey{} var _ Key = &rsaPrivateKey{} func newRSAPrivateKey() *rsaPrivateKey { return &rsaPrivateKey{ mu: &sync.RWMutex{}, privateParams: make(map[string]any), } } func (h rsaPrivateKey) KeyType() jwa.KeyType { return jwa.RSA() } func (h rsaPrivateKey) rlock() { h.mu.RLock() } func (h rsaPrivateKey) runlock() { h.mu.RUnlock() } func (h rsaPrivateKey) IsPrivate() bool { return true } func (h *rsaPrivateKey) Algorithm() (jwa.KeyAlgorithm, bool) { if h.algorithm != nil { return *(h.algorithm), true } return nil, false } func (h *rsaPrivateKey) D() ([]byte, bool) { if h.d != nil { return h.d, true } return nil, false } func (h *rsaPrivateKey) DP() ([]byte, bool) { if h.dp != nil { return h.dp, true } return nil, false } func (h *rsaPrivateKey) DQ() ([]byte, bool) { if h.dq != nil { return h.dq, true } return nil, false } func (h *rsaPrivateKey) E() ([]byte, bool) { if h.e != nil { return h.e, true } return nil, false } func (h *rsaPrivateKey) KeyID() (string, bool) { if h.keyID != nil { return *(h.keyID), true } return "", false } func (h *rsaPrivateKey) KeyOps() (KeyOperationList, bool) { if h.keyOps != nil { return *(h.keyOps), true } return nil, false } func (h *rsaPrivateKey) KeyUsage() (string, bool) { if h.keyUsage != nil { return *(h.keyUsage), true } return "", false } func (h *rsaPrivateKey) N() ([]byte, bool) { if h.n != nil { return h.n, true } return nil, false } func (h *rsaPrivateKey) P() ([]byte, bool) { if h.p != nil { return h.p, true } return nil, false } func (h *rsaPrivateKey) Q() ([]byte, bool) { if h.q != nil { return h.q, true } return nil, false } func (h *rsaPrivateKey) QI() ([]byte, bool) { if h.qi != nil { return h.qi, true } return nil, false } func (h *rsaPrivateKey) X509CertChain() (*cert.Chain, bool) { return h.x509CertChain, true } func (h *rsaPrivateKey) X509CertThumbprint() (string, bool) { if h.x509CertThumbprint != nil { return *(h.x509CertThumbprint), true } return "", false } func (h *rsaPrivateKey) X509CertThumbprintS256() (string, bool) { if h.x509CertThumbprintS256 != nil { return *(h.x509CertThumbprintS256), true } return "", false } func (h *rsaPrivateKey) X509URL() (string, bool) { if h.x509URL != nil { return *(h.x509URL), true } return "", false } func (h *rsaPrivateKey) Has(name string) bool { h.mu.RLock() defer h.mu.RUnlock() switch name { case KeyTypeKey: return true case AlgorithmKey: return h.algorithm != nil case RSADKey: return h.d != nil case RSADPKey: return h.dp != nil case RSADQKey: return h.dq != nil case RSAEKey: return h.e != nil case KeyIDKey: return h.keyID != nil case KeyOpsKey: return h.keyOps != nil case KeyUsageKey: return h.keyUsage != nil case RSANKey: return h.n != nil case RSAPKey: return h.p != nil case RSAQKey: return h.q != nil case RSAQIKey: return h.qi != nil case X509CertChainKey: return h.x509CertChain != nil case X509CertThumbprintKey: return h.x509CertThumbprint != nil case X509CertThumbprintS256Key: return h.x509CertThumbprintS256 != nil case X509URLKey: return h.x509URL != nil default: _, ok := h.privateParams[name] return ok } } func (h *rsaPrivateKey) Get(name string, dst any) error { h.mu.RLock() defer h.mu.RUnlock() switch name { case KeyTypeKey: if err := blackmagic.AssignIfCompatible(dst, h.KeyType()); err != nil { return fmt.Errorf(`rsaPrivateKey.Get: failed to assign value for field %q to destination object: %w`, name, err) } case AlgorithmKey: if h.algorithm == nil { return fmt.Errorf(`field %q not found`, name) } if err := blackmagic.AssignIfCompatible(dst, *(h.algorithm)); err != nil { return fmt.Errorf(`failed to assign value for field %q: %w`, name, err) } return nil case RSADKey: if h.d == nil { return fmt.Errorf(`field %q not found`, name) } if err := blackmagic.AssignIfCompatible(dst, h.d); err != nil { return fmt.Errorf(`failed to assign value for field %q: %w`, name, err) } return nil case RSADPKey: if h.dp == nil { return fmt.Errorf(`field %q not found`, name) } if err := blackmagic.AssignIfCompatible(dst, h.dp); err != nil { return fmt.Errorf(`failed to assign value for field %q: %w`, name, err) } return nil case RSADQKey: if h.dq == nil { return fmt.Errorf(`field %q not found`, name) } if err := blackmagic.AssignIfCompatible(dst, h.dq); err != nil { return fmt.Errorf(`failed to assign value for field %q: %w`, name, err) } return nil case RSAEKey: if h.e == nil { return fmt.Errorf(`field %q not found`, name) } if err := blackmagic.AssignIfCompatible(dst, h.e); err != nil { return fmt.Errorf(`failed to assign value for field %q: %w`, name, err) } return nil case KeyIDKey: if h.keyID == nil { return fmt.Errorf(`field %q not found`, name) } if err := blackmagic.AssignIfCompatible(dst, *(h.keyID)); err != nil { return fmt.Errorf(`failed to assign value for field %q: %w`, name, err) } return nil case KeyOpsKey: if h.keyOps == nil { return fmt.Errorf(`field %q not found`, name) } if err := blackmagic.AssignIfCompatible(dst, *(h.keyOps)); err != nil { return fmt.Errorf(`failed to assign value for field %q: %w`, name, err) } return nil case KeyUsageKey: if h.keyUsage == nil { return fmt.Errorf(`field %q not found`, name) } if err := blackmagic.AssignIfCompatible(dst, *(h.keyUsage)); err != nil { return fmt.Errorf(`failed to assign value for field %q: %w`, name, err) } return nil case RSANKey: if h.n == nil { return fmt.Errorf(`field %q not found`, name) } if err := blackmagic.AssignIfCompatible(dst, h.n); err != nil { return fmt.Errorf(`failed to assign value for field %q: %w`, name, err) } return nil case RSAPKey: if h.p == nil { return fmt.Errorf(`field %q not found`, name) } if err := blackmagic.AssignIfCompatible(dst, h.p); err != nil { return fmt.Errorf(`failed to assign value for field %q: %w`, name, err) } return nil case RSAQKey: if h.q == nil { return fmt.Errorf(`field %q not found`, name) } if err := blackmagic.AssignIfCompatible(dst, h.q); err != nil { return fmt.Errorf(`failed to assign value for field %q: %w`, name, err) } return nil case RSAQIKey: if h.qi == nil { return fmt.Errorf(`field %q not found`, name) } if err := blackmagic.AssignIfCompatible(dst, h.qi); err != nil { return fmt.Errorf(`failed to assign value for field %q: %w`, name, err) } return nil case X509CertChainKey: if h.x509CertChain == nil { return fmt.Errorf(`field %q not found`, name) } if err := blackmagic.AssignIfCompatible(dst, h.x509CertChain); err != nil { return fmt.Errorf(`failed to assign value for field %q: %w`, name, err) } return nil case X509CertThumbprintKey: if h.x509CertThumbprint == nil { return fmt.Errorf(`field %q not found`, name) } if err := blackmagic.AssignIfCompatible(dst, *(h.x509CertThumbprint)); err != nil { return fmt.Errorf(`failed to assign value for field %q: %w`, name, err) } return nil case X509CertThumbprintS256Key: if h.x509CertThumbprintS256 == nil { return fmt.Errorf(`field %q not found`, name) } if err := blackmagic.AssignIfCompatible(dst, *(h.x509CertThumbprintS256)); err != nil { return fmt.Errorf(`failed to assign value for field %q: %w`, name, err) } return nil case X509URLKey: if h.x509URL == nil { return fmt.Errorf(`field %q not found`, name) } if err := blackmagic.AssignIfCompatible(dst, *(h.x509URL)); err != nil { return fmt.Errorf(`failed to assign value for field %q: %w`, name, err) } return nil default: v, ok := h.privateParams[name] if !ok { return fmt.Errorf(`field %q not found`, name) } if err := blackmagic.AssignIfCompatible(dst, v); err != nil { return fmt.Errorf(`failed to assign value for field %q: %w`, name, err) } } return nil } func (h *rsaPrivateKey) Set(name string, value any) error { h.mu.Lock() defer h.mu.Unlock() return h.setNoLock(name, value) } func (h *rsaPrivateKey) setNoLock(name string, value any) error { switch name { case "kty": return nil case AlgorithmKey: switch v := value.(type) { case string, jwa.SignatureAlgorithm, jwa.KeyEncryptionAlgorithm, jwa.ContentEncryptionAlgorithm: tmp, err := jwa.KeyAlgorithmFrom(v) if err != nil { return fmt.Errorf(`invalid algorithm for %q key: %w`, AlgorithmKey, err) } h.algorithm = &tmp default: return fmt.Errorf(`invalid type for %q key: %T`, AlgorithmKey, value) } return nil case RSADKey: if v, ok := value.([]byte); ok { h.d = v return nil } return fmt.Errorf(`invalid value for %s key: %T`, RSADKey, value) case RSADPKey: if v, ok := value.([]byte); ok { h.dp = v return nil } return fmt.Errorf(`invalid value for %s key: %T`, RSADPKey, value) case RSADQKey: if v, ok := value.([]byte); ok { h.dq = v return nil } return fmt.Errorf(`invalid value for %s key: %T`, RSADQKey, value) case RSAEKey: if v, ok := value.([]byte); ok { h.e = v return nil } return fmt.Errorf(`invalid value for %s key: %T`, RSAEKey, value) case KeyIDKey: if v, ok := value.(string); ok { h.keyID = &v return nil } return fmt.Errorf(`invalid value for %s key: %T`, KeyIDKey, value) case KeyOpsKey: var acceptor KeyOperationList if err := acceptor.Accept(value); err != nil { return fmt.Errorf(`invalid value for %s key: %w`, KeyOpsKey, err) } h.keyOps = &acceptor return nil case KeyUsageKey: switch v := value.(type) { case KeyUsageType: switch v { case ForSignature, ForEncryption: tmp := v.String() h.keyUsage = &tmp default: return fmt.Errorf(`invalid key usage type %s`, v) } case string: h.keyUsage = &v default: return fmt.Errorf(`invalid key usage type %s`, v) } case RSANKey: if v, ok := value.([]byte); ok { h.n = v return nil } return fmt.Errorf(`invalid value for %s key: %T`, RSANKey, value) case RSAPKey: if v, ok := value.([]byte); ok { h.p = v return nil } return fmt.Errorf(`invalid value for %s key: %T`, RSAPKey, value) case RSAQKey: if v, ok := value.([]byte); ok { h.q = v return nil } return fmt.Errorf(`invalid value for %s key: %T`, RSAQKey, value) case RSAQIKey: if v, ok := value.([]byte); ok { h.qi = v return nil } return fmt.Errorf(`invalid value for %s key: %T`, RSAQIKey, value) case X509CertChainKey: if v, ok := value.(*cert.Chain); ok { h.x509CertChain = v return nil } return fmt.Errorf(`invalid value for %s key: %T`, X509CertChainKey, value) case X509CertThumbprintKey: if v, ok := value.(string); ok { h.x509CertThumbprint = &v return nil } return fmt.Errorf(`invalid value for %s key: %T`, X509CertThumbprintKey, value) case X509CertThumbprintS256Key: if v, ok := value.(string); ok { h.x509CertThumbprintS256 = &v return nil } return fmt.Errorf(`invalid value for %s key: %T`, X509CertThumbprintS256Key, value) case X509URLKey: if v, ok := value.(string); ok { h.x509URL = &v return nil } return fmt.Errorf(`invalid value for %s key: %T`, X509URLKey, value) default: if h.privateParams == nil { h.privateParams = map[string]any{} } h.privateParams[name] = value } return nil } func (k *rsaPrivateKey) Remove(key string) error { k.mu.Lock() defer k.mu.Unlock() switch key { case AlgorithmKey: k.algorithm = nil case RSADKey: k.d = nil case RSADPKey: k.dp = nil case RSADQKey: k.dq = nil case RSAEKey: k.e = nil case KeyIDKey: k.keyID = nil case KeyOpsKey: k.keyOps = nil case KeyUsageKey: k.keyUsage = nil case RSANKey: k.n = nil case RSAPKey: k.p = nil case RSAQKey: k.q = nil case RSAQIKey: k.qi = nil case X509CertChainKey: k.x509CertChain = nil case X509CertThumbprintKey: k.x509CertThumbprint = nil case X509CertThumbprintS256Key: k.x509CertThumbprintS256 = nil case X509URLKey: k.x509URL = nil default: delete(k.privateParams, key) } return nil } func (k *rsaPrivateKey) Clone() (Key, error) { key, err := cloneKey(k) if err != nil { return nil, fmt.Errorf(`rsaPrivateKey.Clone: %w`, err) } return key, nil } func (k *rsaPrivateKey) DecodeCtx() json.DecodeCtx { k.mu.RLock() defer k.mu.RUnlock() return k.dc } func (k *rsaPrivateKey) SetDecodeCtx(dc json.DecodeCtx) { k.mu.Lock() defer k.mu.Unlock() k.dc = dc } func (h *rsaPrivateKey) UnmarshalJSON(buf []byte) error { h.mu.Lock() defer h.mu.Unlock() h.algorithm = nil h.d = nil h.dp = nil h.dq = nil h.e = nil h.keyID = nil h.keyOps = nil h.keyUsage = nil h.n = nil h.p = nil h.q = nil h.qi = nil h.x509CertChain = nil h.x509CertThumbprint = nil h.x509CertThumbprintS256 = nil h.x509URL = nil dec := json.NewDecoder(bytes.NewReader(buf)) LOOP: for { tok, err := dec.Token() if err != nil { return fmt.Errorf(`error reading token: %w`, err) } switch tok := tok.(type) { case json.Delim: // Assuming we're doing everything correctly, we should ONLY // get either tokens.OpenCurlyBracket or tokens.CloseCurlyBracket here. if tok == tokens.CloseCurlyBracket { // End of object break LOOP } else if tok != tokens.OpenCurlyBracket { return fmt.Errorf(`expected '%c' but got '%c'`, tokens.OpenCurlyBracket, tok) } case string: // Objects can only have string keys switch tok { case KeyTypeKey: val, err := json.ReadNextStringToken(dec) if err != nil { return fmt.Errorf(`error reading token: %w`, err) } if val != jwa.RSA().String() { return fmt.Errorf(`invalid kty value for RSAPublicKey (%s)`, val) } case AlgorithmKey: var s string if err := dec.Decode(&s); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, AlgorithmKey, err) } alg, err := jwa.KeyAlgorithmFrom(s) if err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, AlgorithmKey, err) } h.algorithm = &alg case RSADKey: if err := json.AssignNextBytesToken(&h.d, dec); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, RSADKey, err) } case RSADPKey: if err := json.AssignNextBytesToken(&h.dp, dec); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, RSADPKey, err) } case RSADQKey: if err := json.AssignNextBytesToken(&h.dq, dec); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, RSADQKey, err) } case RSAEKey: if err := json.AssignNextBytesToken(&h.e, dec); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, RSAEKey, err) } case KeyIDKey: if err := json.AssignNextStringToken(&h.keyID, dec); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, KeyIDKey, err) } case KeyOpsKey: var decoded KeyOperationList if err := dec.Decode(&decoded); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, KeyOpsKey, err) } h.keyOps = &decoded case KeyUsageKey: if err := json.AssignNextStringToken(&h.keyUsage, dec); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, KeyUsageKey, err) } case RSANKey: if err := json.AssignNextBytesToken(&h.n, dec); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, RSANKey, err) } case RSAPKey: if err := json.AssignNextBytesToken(&h.p, dec); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, RSAPKey, err) } case RSAQKey: if err := json.AssignNextBytesToken(&h.q, dec); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, RSAQKey, err) } case RSAQIKey: if err := json.AssignNextBytesToken(&h.qi, dec); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, RSAQIKey, err) } case X509CertChainKey: var decoded cert.Chain if err := dec.Decode(&decoded); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, X509CertChainKey, err) } h.x509CertChain = &decoded case X509CertThumbprintKey: if err := json.AssignNextStringToken(&h.x509CertThumbprint, dec); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, X509CertThumbprintKey, err) } case X509CertThumbprintS256Key: if err := json.AssignNextStringToken(&h.x509CertThumbprintS256, dec); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, X509CertThumbprintS256Key, err) } case X509URLKey: if err := json.AssignNextStringToken(&h.x509URL, dec); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, X509URLKey, err) } default: if dc := h.dc; dc != nil { if localReg := dc.Registry(); localReg != nil { decoded, err := localReg.Decode(dec, tok) if err == nil { h.setNoLock(tok, decoded) continue } } } decoded, err := registry.Decode(dec, tok) if err == nil { h.setNoLock(tok, decoded) continue } return fmt.Errorf(`could not decode field %s: %w`, tok, err) } default: return fmt.Errorf(`invalid token %T`, tok) } } if h.d == nil { return fmt.Errorf(`required field d is missing`) } if h.e == nil { return fmt.Errorf(`required field e is missing`) } if h.n == nil { return fmt.Errorf(`required field n is missing`) } return nil } func (h rsaPrivateKey) MarshalJSON() ([]byte, error) { data := make(map[string]any) fields := make([]string, 0, 16) data[KeyTypeKey] = jwa.RSA() fields = append(fields, KeyTypeKey) if h.algorithm != nil { data[AlgorithmKey] = *(h.algorithm) fields = append(fields, AlgorithmKey) } if h.d != nil { data[RSADKey] = h.d fields = append(fields, RSADKey) } if h.dp != nil { data[RSADPKey] = h.dp fields = append(fields, RSADPKey) } if h.dq != nil { data[RSADQKey] = h.dq fields = append(fields, RSADQKey) } if h.e != nil { data[RSAEKey] = h.e fields = append(fields, RSAEKey) } if h.keyID != nil { data[KeyIDKey] = *(h.keyID) fields = append(fields, KeyIDKey) } if h.keyOps != nil { data[KeyOpsKey] = *(h.keyOps) fields = append(fields, KeyOpsKey) } if h.keyUsage != nil { data[KeyUsageKey] = *(h.keyUsage) fields = append(fields, KeyUsageKey) } if h.n != nil { data[RSANKey] = h.n fields = append(fields, RSANKey) } if h.p != nil { data[RSAPKey] = h.p fields = append(fields, RSAPKey) } if h.q != nil { data[RSAQKey] = h.q fields = append(fields, RSAQKey) } if h.qi != nil { data[RSAQIKey] = h.qi fields = append(fields, RSAQIKey) } if h.x509CertChain != nil { data[X509CertChainKey] = h.x509CertChain fields = append(fields, X509CertChainKey) } if h.x509CertThumbprint != nil { data[X509CertThumbprintKey] = *(h.x509CertThumbprint) fields = append(fields, X509CertThumbprintKey) } if h.x509CertThumbprintS256 != nil { data[X509CertThumbprintS256Key] = *(h.x509CertThumbprintS256) fields = append(fields, X509CertThumbprintS256Key) } if h.x509URL != nil { data[X509URLKey] = *(h.x509URL) fields = append(fields, X509URLKey) } for k, v := range h.privateParams { data[k] = v fields = append(fields, k) } sort.Strings(fields) buf := pool.BytesBuffer().Get() defer pool.BytesBuffer().Put(buf) buf.WriteByte(tokens.OpenCurlyBracket) enc := json.NewEncoder(buf) for i, f := range fields { if i > 0 { buf.WriteRune(tokens.Comma) } buf.WriteRune(tokens.DoubleQuote) buf.WriteString(f) buf.WriteString(`":`) v := data[f] switch v := v.(type) { case []byte: buf.WriteRune(tokens.DoubleQuote) buf.WriteString(base64.EncodeToString(v)) buf.WriteRune(tokens.DoubleQuote) default: if err := enc.Encode(v); err != nil { return nil, fmt.Errorf(`failed to encode value for field %s: %w`, f, err) } buf.Truncate(buf.Len() - 1) } } buf.WriteByte(tokens.CloseCurlyBracket) ret := make([]byte, buf.Len()) copy(ret, buf.Bytes()) return ret, nil } func (h *rsaPrivateKey) Keys() []string { h.mu.RLock() defer h.mu.RUnlock() keys := make([]string, 0, 16+len(h.privateParams)) keys = append(keys, KeyTypeKey) if h.algorithm != nil { keys = append(keys, AlgorithmKey) } if h.d != nil { keys = append(keys, RSADKey) } if h.dp != nil { keys = append(keys, RSADPKey) } if h.dq != nil { keys = append(keys, RSADQKey) } if h.e != nil { keys = append(keys, RSAEKey) } if h.keyID != nil { keys = append(keys, KeyIDKey) } if h.keyOps != nil { keys = append(keys, KeyOpsKey) } if h.keyUsage != nil { keys = append(keys, KeyUsageKey) } if h.n != nil { keys = append(keys, RSANKey) } if h.p != nil { keys = append(keys, RSAPKey) } if h.q != nil { keys = append(keys, RSAQKey) } if h.qi != nil { keys = append(keys, RSAQIKey) } if h.x509CertChain != nil { keys = append(keys, X509CertChainKey) } if h.x509CertThumbprint != nil { keys = append(keys, X509CertThumbprintKey) } if h.x509CertThumbprintS256 != nil { keys = append(keys, X509CertThumbprintS256Key) } if h.x509URL != nil { keys = append(keys, X509URLKey) } for k := range h.privateParams { keys = append(keys, k) } return keys } var rsaStandardFields KeyFilter func init() { rsaStandardFields = NewFieldNameFilter(KeyTypeKey, KeyUsageKey, KeyOpsKey, AlgorithmKey, KeyIDKey, X509URLKey, X509CertChainKey, X509CertThumbprintKey, X509CertThumbprintS256Key, RSAEKey, RSANKey, RSADKey, RSADPKey, RSADQKey, RSAPKey, RSAQKey, RSAQIKey) } // RSAStandardFieldsFilter returns a KeyFilter that filters out standard RSA fields. func RSAStandardFieldsFilter() KeyFilter { return rsaStandardFields } golang-github-lestrrat-go-jwx-3.0.13/jwk/set.go000066400000000000000000000143701515060566400213260ustar00rootroot00000000000000package jwk import ( "bytes" "fmt" "maps" "reflect" "sort" "github.com/lestrrat-go/blackmagic" "github.com/lestrrat-go/jwx/v3/internal/json" "github.com/lestrrat-go/jwx/v3/internal/pool" "github.com/lestrrat-go/jwx/v3/internal/tokens" ) const keysKey = `keys` // appease linter func newSet() *set { return &set{ privateParams: make(map[string]any), } } // NewSet creates and empty `jwk.Set` object func NewSet() Set { return newSet() } func (s *set) Set(n string, v any) error { s.mu.RLock() defer s.mu.RUnlock() if n == keysKey { vl, ok := v.([]Key) if !ok { return fmt.Errorf(`value for field "keys" must be []jwk.Key`) } s.keys = vl return nil } s.privateParams[n] = v return nil } func (s *set) Get(name string, dst any) error { s.mu.RLock() defer s.mu.RUnlock() v, ok := s.privateParams[name] if !ok { return fmt.Errorf(`field %q not found`, name) } if err := blackmagic.AssignIfCompatible(dst, v); err != nil { return fmt.Errorf(`failed to assign value to dst: %w`, err) } return nil } func (s *set) Key(idx int) (Key, bool) { s.mu.RLock() defer s.mu.RUnlock() if idx >= 0 && idx < len(s.keys) { return s.keys[idx], true } return nil, false } func (s *set) Len() int { s.mu.RLock() defer s.mu.RUnlock() return len(s.keys) } // indexNL is Index(), but without the locking func (s *set) indexNL(key Key) int { for i, k := range s.keys { if k == key { return i } } return -1 } func (s *set) Index(key Key) int { s.mu.RLock() defer s.mu.RUnlock() return s.indexNL(key) } func (s *set) AddKey(key Key) error { s.mu.Lock() defer s.mu.Unlock() if reflect.ValueOf(key).IsNil() { panic("nil key") } if i := s.indexNL(key); i > -1 { return fmt.Errorf(`(jwk.Set).AddKey: key already exists`) } s.keys = append(s.keys, key) return nil } func (s *set) Remove(name string) error { s.mu.Lock() defer s.mu.Unlock() delete(s.privateParams, name) return nil } func (s *set) RemoveKey(key Key) error { s.mu.Lock() defer s.mu.Unlock() for i, k := range s.keys { if k == key { switch i { case 0: s.keys = s.keys[1:] case len(s.keys) - 1: s.keys = s.keys[:i] default: s.keys = append(s.keys[:i], s.keys[i+1:]...) } return nil } } return fmt.Errorf(`(jwk.Set).RemoveKey: specified key does not exist in set`) } func (s *set) Clear() error { s.mu.Lock() defer s.mu.Unlock() s.keys = nil s.privateParams = make(map[string]any) return nil } func (s *set) Keys() []string { ret := make([]string, len(s.privateParams)) var i int for k := range s.privateParams { ret[i] = k i++ } return ret } func (s *set) MarshalJSON() ([]byte, error) { s.mu.RLock() defer s.mu.RUnlock() buf := pool.BytesBuffer().Get() defer pool.BytesBuffer().Put(buf) enc := json.NewEncoder(buf) fields := []string{keysKey} for k := range s.privateParams { fields = append(fields, k) } sort.Strings(fields) buf.WriteByte(tokens.OpenCurlyBracket) for i, field := range fields { if i > 0 { buf.WriteByte(tokens.Comma) } fmt.Fprintf(buf, `%q:`, field) if field != keysKey { if err := enc.Encode(s.privateParams[field]); err != nil { return nil, fmt.Errorf(`failed to marshal field %q: %w`, field, err) } } else { buf.WriteByte(tokens.OpenSquareBracket) for j, k := range s.keys { if j > 0 { buf.WriteByte(tokens.Comma) } if err := enc.Encode(k); err != nil { return nil, fmt.Errorf(`failed to marshal key #%d: %w`, i, err) } } buf.WriteByte(tokens.CloseSquareBracket) } } buf.WriteByte(tokens.CloseCurlyBracket) ret := make([]byte, buf.Len()) copy(ret, buf.Bytes()) return ret, nil } func (s *set) UnmarshalJSON(data []byte) error { s.mu.Lock() defer s.mu.Unlock() s.privateParams = make(map[string]any) s.keys = nil var options []ParseOption var ignoreParseError bool if dc := s.dc; dc != nil { if localReg := dc.Registry(); localReg != nil { options = append(options, withLocalRegistry(localReg)) } ignoreParseError = dc.IgnoreParseError() } var sawKeysField bool dec := json.NewDecoder(bytes.NewReader(data)) LOOP: for { tok, err := dec.Token() if err != nil { return fmt.Errorf(`error reading token: %w`, err) } switch tok := tok.(type) { case json.Delim: // Assuming we're doing everything correctly, we should ONLY // get either tokens.OpenCurlyBracket or tokens.CloseCurlyBracket here. if tok == tokens.CloseCurlyBracket { // End of object break LOOP } else if tok != tokens.OpenCurlyBracket { return fmt.Errorf(`expected '%c' but got '%c'`, tokens.OpenCurlyBracket, tok) } case string: switch tok { case "keys": sawKeysField = true var list []json.RawMessage if err := dec.Decode(&list); err != nil { return fmt.Errorf(`failed to decode "keys": %w`, err) } for i, keysrc := range list { key, err := ParseKey(keysrc, options...) if err != nil { if !ignoreParseError { return fmt.Errorf(`failed to decode key #%d in "keys": %w`, i, err) } continue } s.keys = append(s.keys, key) } default: var v any if err := dec.Decode(&v); err != nil { return fmt.Errorf(`failed to decode value for key %q: %w`, tok, err) } s.privateParams[tok] = v } } } // This is really silly, but we can only detect the // lack of the "keys" field after going through the // entire object once // Not checking for len(s.keys) == 0, because it could be // an empty key set if !sawKeysField { key, err := ParseKey(data, options...) if err != nil { return fmt.Errorf(`failed to parse sole key in key set`) } s.keys = append(s.keys, key) } return nil } func (s *set) LookupKeyID(kid string) (Key, bool) { s.mu.RLock() defer s.mu.RUnlock() for i := range s.Len() { key, ok := s.Key(i) if !ok { return nil, false } gotkid, ok := key.KeyID() if ok && gotkid == kid { return key, true } } return nil, false } func (s *set) DecodeCtx() DecodeCtx { s.mu.RLock() defer s.mu.RUnlock() return s.dc } func (s *set) SetDecodeCtx(dc DecodeCtx) { s.mu.Lock() defer s.mu.Unlock() s.dc = dc } func (s *set) Clone() (Set, error) { s2 := newSet() s.mu.RLock() defer s.mu.RUnlock() s2.keys = make([]Key, len(s.keys)) copy(s2.keys, s.keys) maps.Copy(s2.privateParams, s.privateParams) return s2, nil } golang-github-lestrrat-go-jwx-3.0.13/jwk/set_test.go000066400000000000000000000031161515060566400223610ustar00rootroot00000000000000package jwk_test import ( "sort" "testing" "github.com/lestrrat-go/jwx/v3/internal/jwxtest" "github.com/lestrrat-go/jwx/v3/jwk" "github.com/stretchr/testify/require" ) func TestSet(t *testing.T) { set := jwk.NewSet() keygens := []func() (jwk.Key, error){ jwxtest.GenerateRsaJwk, jwxtest.GenerateEcdsaJwk, jwxtest.GenerateSymmetricJwk, } //nolint:prealloc var keys []jwk.Key for _, gen := range keygens { k, err := gen() require.NoError(t, err, `key generation should succeed`) require.NoError(t, set.AddKey(k), `set.AddKey should succeed`) keys = append(keys, k) } require.Equal(t, set.Len(), 3, `set.Len should be 3`) for i, k := range keys { require.Equal(t, i, set.Index(k), `set.Index should return %d`, i) } for _, k := range keys { require.NoError(t, set.RemoveKey(k), `set.RemoveKey should succeed`) } require.Equal(t, set.Len(), 0, `set.Len should be 0`) for _, gen := range keygens { k, err := gen() require.NoError(t, err, `key generation should succeed`) require.NoError(t, set.AddKey(k), `set.Add should succeed`) } require.Equal(t, set.Len(), 3, `set.Len should be 3`) set.Clear() require.Equal(t, set.Len(), 0, `set.Len should be 0`) } func TestSetKeys(t *testing.T) { set := jwk.NewSet() require.NoError(t, set.Set("a", "foo"), `Set should succeed`) require.NoError(t, set.Set("b", "bar"), `Set should succeed`) keys := set.Keys() sort.Strings(keys) // sorting is necessary because the order of keys obtained from a regular map is not guaranteed require.EqualValues(t, []string{"a", "b"}, keys, `Keys should return "a" and "b"`) } golang-github-lestrrat-go-jwx-3.0.13/jwk/symmetric.go000066400000000000000000000044501515060566400225450ustar00rootroot00000000000000package jwk import ( "crypto" "fmt" "reflect" "github.com/lestrrat-go/jwx/v3/internal/base64" "github.com/lestrrat-go/jwx/v3/jwa" ) func init() { RegisterKeyExporter(jwa.OctetSeq(), KeyExportFunc(octetSeqToRaw)) } func (k *symmetricKey) Import(rawKey []byte) error { k.mu.Lock() defer k.mu.Unlock() if len(rawKey) == 0 { return fmt.Errorf(`non-empty []byte key required`) } k.octets = rawKey return nil } var symmetricConvertibleKeys = []reflect.Type{ reflect.TypeFor[SymmetricKey](), } func octetSeqToRaw(key Key, hint any) (any, error) { extracted, err := extractEmbeddedKey(key, symmetricConvertibleKeys) if err != nil { return nil, fmt.Errorf(`failed to extract embedded key: %w`, err) } switch key := extracted.(type) { case SymmetricKey: switch hint.(type) { case *[]byte, *any: default: return nil, fmt.Errorf(`invalid destination object type %T for symmetric key: %w`, hint, ContinueError()) } locker, ok := key.(rlocker) if ok { locker.rlock() defer locker.runlock() } ooctets, ok := key.Octets() if !ok { return nil, fmt.Errorf(`jwk.SymmetricKey: missing "k" field`) } octets := make([]byte, len(ooctets)) copy(octets, ooctets) return octets, nil default: return nil, ContinueError() } } // Thumbprint returns the JWK thumbprint using the indicated // hashing algorithm, according to RFC 7638 func (k *symmetricKey) Thumbprint(hash crypto.Hash) ([]byte, error) { k.mu.RLock() defer k.mu.RUnlock() var octets []byte if err := Export(k, &octets); err != nil { return nil, fmt.Errorf(`failed to export symmetric key: %w`, err) } h := hash.New() fmt.Fprint(h, `{"k":"`) fmt.Fprint(h, base64.EncodeToString(octets)) fmt.Fprint(h, `","kty":"oct"}`) return h.Sum(nil), nil } func (k *symmetricKey) PublicKey() (Key, error) { newKey := newSymmetricKey() for _, key := range k.Keys() { var v any if err := k.Get(key, &v); err != nil { return nil, fmt.Errorf(`failed to get field %q: %w`, key, err) } if err := newKey.Set(key, v); err != nil { return nil, fmt.Errorf(`failed to set field %q: %w`, key, err) } } return newKey, nil } func (k *symmetricKey) Validate() error { octets, ok := k.Octets() if !ok || len(octets) == 0 { return NewKeyValidationError(fmt.Errorf(`jwk.SymmetricKey: missing "k" field`)) } return nil } golang-github-lestrrat-go-jwx-3.0.13/jwk/symmetric_gen.go000066400000000000000000000412431515060566400233770ustar00rootroot00000000000000// Code generated by tools/cmd/genjwk/main.go. DO NOT EDIT. package jwk import ( "bytes" "fmt" "sort" "sync" "github.com/lestrrat-go/blackmagic" "github.com/lestrrat-go/jwx/v3/cert" "github.com/lestrrat-go/jwx/v3/internal/base64" "github.com/lestrrat-go/jwx/v3/internal/json" "github.com/lestrrat-go/jwx/v3/internal/pool" "github.com/lestrrat-go/jwx/v3/internal/tokens" "github.com/lestrrat-go/jwx/v3/jwa" ) const ( SymmetricOctetsKey = "k" ) type SymmetricKey interface { Key Octets() ([]byte, bool) } type symmetricKey struct { algorithm *jwa.KeyAlgorithm // https://tools.ietf.org/html/rfc7517#section-4.4 keyID *string // https://tools.ietf.org/html/rfc7515#section-4.1.4 keyOps *KeyOperationList // https://tools.ietf.org/html/rfc7517#section-4.3 keyUsage *string // https://tools.ietf.org/html/rfc7517#section-4.2 octets []byte x509CertChain *cert.Chain // https://tools.ietf.org/html/rfc7515#section-4.1.6 x509CertThumbprint *string // https://tools.ietf.org/html/rfc7515#section-4.1.7 x509CertThumbprintS256 *string // https://tools.ietf.org/html/rfc7515#section-4.1.8 x509URL *string // https://tools.ietf.org/html/rfc7515#section-4.1.5 privateParams map[string]any mu *sync.RWMutex dc json.DecodeCtx } var _ SymmetricKey = &symmetricKey{} var _ Key = &symmetricKey{} func newSymmetricKey() *symmetricKey { return &symmetricKey{ mu: &sync.RWMutex{}, privateParams: make(map[string]any), } } func (h symmetricKey) KeyType() jwa.KeyType { return jwa.OctetSeq() } func (h symmetricKey) rlock() { h.mu.RLock() } func (h symmetricKey) runlock() { h.mu.RUnlock() } func (h *symmetricKey) Algorithm() (jwa.KeyAlgorithm, bool) { if h.algorithm != nil { return *(h.algorithm), true } return nil, false } func (h *symmetricKey) KeyID() (string, bool) { if h.keyID != nil { return *(h.keyID), true } return "", false } func (h *symmetricKey) KeyOps() (KeyOperationList, bool) { if h.keyOps != nil { return *(h.keyOps), true } return nil, false } func (h *symmetricKey) KeyUsage() (string, bool) { if h.keyUsage != nil { return *(h.keyUsage), true } return "", false } func (h *symmetricKey) Octets() ([]byte, bool) { if h.octets != nil { return h.octets, true } return nil, false } func (h *symmetricKey) X509CertChain() (*cert.Chain, bool) { return h.x509CertChain, true } func (h *symmetricKey) X509CertThumbprint() (string, bool) { if h.x509CertThumbprint != nil { return *(h.x509CertThumbprint), true } return "", false } func (h *symmetricKey) X509CertThumbprintS256() (string, bool) { if h.x509CertThumbprintS256 != nil { return *(h.x509CertThumbprintS256), true } return "", false } func (h *symmetricKey) X509URL() (string, bool) { if h.x509URL != nil { return *(h.x509URL), true } return "", false } func (h *symmetricKey) Has(name string) bool { h.mu.RLock() defer h.mu.RUnlock() switch name { case KeyTypeKey: return true case AlgorithmKey: return h.algorithm != nil case KeyIDKey: return h.keyID != nil case KeyOpsKey: return h.keyOps != nil case KeyUsageKey: return h.keyUsage != nil case SymmetricOctetsKey: return h.octets != nil case X509CertChainKey: return h.x509CertChain != nil case X509CertThumbprintKey: return h.x509CertThumbprint != nil case X509CertThumbprintS256Key: return h.x509CertThumbprintS256 != nil case X509URLKey: return h.x509URL != nil default: _, ok := h.privateParams[name] return ok } } func (h *symmetricKey) Get(name string, dst any) error { h.mu.RLock() defer h.mu.RUnlock() switch name { case KeyTypeKey: if err := blackmagic.AssignIfCompatible(dst, h.KeyType()); err != nil { return fmt.Errorf(`symmetricKey.Get: failed to assign value for field %q to destination object: %w`, name, err) } case AlgorithmKey: if h.algorithm == nil { return fmt.Errorf(`field %q not found`, name) } if err := blackmagic.AssignIfCompatible(dst, *(h.algorithm)); err != nil { return fmt.Errorf(`failed to assign value for field %q: %w`, name, err) } return nil case KeyIDKey: if h.keyID == nil { return fmt.Errorf(`field %q not found`, name) } if err := blackmagic.AssignIfCompatible(dst, *(h.keyID)); err != nil { return fmt.Errorf(`failed to assign value for field %q: %w`, name, err) } return nil case KeyOpsKey: if h.keyOps == nil { return fmt.Errorf(`field %q not found`, name) } if err := blackmagic.AssignIfCompatible(dst, *(h.keyOps)); err != nil { return fmt.Errorf(`failed to assign value for field %q: %w`, name, err) } return nil case KeyUsageKey: if h.keyUsage == nil { return fmt.Errorf(`field %q not found`, name) } if err := blackmagic.AssignIfCompatible(dst, *(h.keyUsage)); err != nil { return fmt.Errorf(`failed to assign value for field %q: %w`, name, err) } return nil case SymmetricOctetsKey: if h.octets == nil { return fmt.Errorf(`field %q not found`, name) } if err := blackmagic.AssignIfCompatible(dst, h.octets); err != nil { return fmt.Errorf(`failed to assign value for field %q: %w`, name, err) } return nil case X509CertChainKey: if h.x509CertChain == nil { return fmt.Errorf(`field %q not found`, name) } if err := blackmagic.AssignIfCompatible(dst, h.x509CertChain); err != nil { return fmt.Errorf(`failed to assign value for field %q: %w`, name, err) } return nil case X509CertThumbprintKey: if h.x509CertThumbprint == nil { return fmt.Errorf(`field %q not found`, name) } if err := blackmagic.AssignIfCompatible(dst, *(h.x509CertThumbprint)); err != nil { return fmt.Errorf(`failed to assign value for field %q: %w`, name, err) } return nil case X509CertThumbprintS256Key: if h.x509CertThumbprintS256 == nil { return fmt.Errorf(`field %q not found`, name) } if err := blackmagic.AssignIfCompatible(dst, *(h.x509CertThumbprintS256)); err != nil { return fmt.Errorf(`failed to assign value for field %q: %w`, name, err) } return nil case X509URLKey: if h.x509URL == nil { return fmt.Errorf(`field %q not found`, name) } if err := blackmagic.AssignIfCompatible(dst, *(h.x509URL)); err != nil { return fmt.Errorf(`failed to assign value for field %q: %w`, name, err) } return nil default: v, ok := h.privateParams[name] if !ok { return fmt.Errorf(`field %q not found`, name) } if err := blackmagic.AssignIfCompatible(dst, v); err != nil { return fmt.Errorf(`failed to assign value for field %q: %w`, name, err) } } return nil } func (h *symmetricKey) Set(name string, value any) error { h.mu.Lock() defer h.mu.Unlock() return h.setNoLock(name, value) } func (h *symmetricKey) setNoLock(name string, value any) error { switch name { case "kty": return nil case AlgorithmKey: switch v := value.(type) { case string, jwa.SignatureAlgorithm, jwa.KeyEncryptionAlgorithm, jwa.ContentEncryptionAlgorithm: tmp, err := jwa.KeyAlgorithmFrom(v) if err != nil { return fmt.Errorf(`invalid algorithm for %q key: %w`, AlgorithmKey, err) } h.algorithm = &tmp default: return fmt.Errorf(`invalid type for %q key: %T`, AlgorithmKey, value) } return nil case KeyIDKey: if v, ok := value.(string); ok { h.keyID = &v return nil } return fmt.Errorf(`invalid value for %s key: %T`, KeyIDKey, value) case KeyOpsKey: var acceptor KeyOperationList if err := acceptor.Accept(value); err != nil { return fmt.Errorf(`invalid value for %s key: %w`, KeyOpsKey, err) } h.keyOps = &acceptor return nil case KeyUsageKey: switch v := value.(type) { case KeyUsageType: switch v { case ForSignature, ForEncryption: tmp := v.String() h.keyUsage = &tmp default: return fmt.Errorf(`invalid key usage type %s`, v) } case string: h.keyUsage = &v default: return fmt.Errorf(`invalid key usage type %s`, v) } case SymmetricOctetsKey: if v, ok := value.([]byte); ok { h.octets = v return nil } return fmt.Errorf(`invalid value for %s key: %T`, SymmetricOctetsKey, value) case X509CertChainKey: if v, ok := value.(*cert.Chain); ok { h.x509CertChain = v return nil } return fmt.Errorf(`invalid value for %s key: %T`, X509CertChainKey, value) case X509CertThumbprintKey: if v, ok := value.(string); ok { h.x509CertThumbprint = &v return nil } return fmt.Errorf(`invalid value for %s key: %T`, X509CertThumbprintKey, value) case X509CertThumbprintS256Key: if v, ok := value.(string); ok { h.x509CertThumbprintS256 = &v return nil } return fmt.Errorf(`invalid value for %s key: %T`, X509CertThumbprintS256Key, value) case X509URLKey: if v, ok := value.(string); ok { h.x509URL = &v return nil } return fmt.Errorf(`invalid value for %s key: %T`, X509URLKey, value) default: if h.privateParams == nil { h.privateParams = map[string]any{} } h.privateParams[name] = value } return nil } func (k *symmetricKey) Remove(key string) error { k.mu.Lock() defer k.mu.Unlock() switch key { case AlgorithmKey: k.algorithm = nil case KeyIDKey: k.keyID = nil case KeyOpsKey: k.keyOps = nil case KeyUsageKey: k.keyUsage = nil case SymmetricOctetsKey: k.octets = nil case X509CertChainKey: k.x509CertChain = nil case X509CertThumbprintKey: k.x509CertThumbprint = nil case X509CertThumbprintS256Key: k.x509CertThumbprintS256 = nil case X509URLKey: k.x509URL = nil default: delete(k.privateParams, key) } return nil } func (k *symmetricKey) Clone() (Key, error) { key, err := cloneKey(k) if err != nil { return nil, fmt.Errorf(`symmetricKey.Clone: %w`, err) } return key, nil } func (k *symmetricKey) DecodeCtx() json.DecodeCtx { k.mu.RLock() defer k.mu.RUnlock() return k.dc } func (k *symmetricKey) SetDecodeCtx(dc json.DecodeCtx) { k.mu.Lock() defer k.mu.Unlock() k.dc = dc } func (h *symmetricKey) UnmarshalJSON(buf []byte) error { h.mu.Lock() defer h.mu.Unlock() h.algorithm = nil h.keyID = nil h.keyOps = nil h.keyUsage = nil h.octets = nil h.x509CertChain = nil h.x509CertThumbprint = nil h.x509CertThumbprintS256 = nil h.x509URL = nil dec := json.NewDecoder(bytes.NewReader(buf)) LOOP: for { tok, err := dec.Token() if err != nil { return fmt.Errorf(`error reading token: %w`, err) } switch tok := tok.(type) { case json.Delim: // Assuming we're doing everything correctly, we should ONLY // get either tokens.OpenCurlyBracket or tokens.CloseCurlyBracket here. if tok == tokens.CloseCurlyBracket { // End of object break LOOP } else if tok != tokens.OpenCurlyBracket { return fmt.Errorf(`expected '%c' but got '%c'`, tokens.OpenCurlyBracket, tok) } case string: // Objects can only have string keys switch tok { case KeyTypeKey: val, err := json.ReadNextStringToken(dec) if err != nil { return fmt.Errorf(`error reading token: %w`, err) } if val != jwa.OctetSeq().String() { return fmt.Errorf(`invalid kty value for RSAPublicKey (%s)`, val) } case AlgorithmKey: var s string if err := dec.Decode(&s); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, AlgorithmKey, err) } alg, err := jwa.KeyAlgorithmFrom(s) if err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, AlgorithmKey, err) } h.algorithm = &alg case KeyIDKey: if err := json.AssignNextStringToken(&h.keyID, dec); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, KeyIDKey, err) } case KeyOpsKey: var decoded KeyOperationList if err := dec.Decode(&decoded); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, KeyOpsKey, err) } h.keyOps = &decoded case KeyUsageKey: if err := json.AssignNextStringToken(&h.keyUsage, dec); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, KeyUsageKey, err) } case SymmetricOctetsKey: if err := json.AssignNextBytesToken(&h.octets, dec); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, SymmetricOctetsKey, err) } case X509CertChainKey: var decoded cert.Chain if err := dec.Decode(&decoded); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, X509CertChainKey, err) } h.x509CertChain = &decoded case X509CertThumbprintKey: if err := json.AssignNextStringToken(&h.x509CertThumbprint, dec); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, X509CertThumbprintKey, err) } case X509CertThumbprintS256Key: if err := json.AssignNextStringToken(&h.x509CertThumbprintS256, dec); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, X509CertThumbprintS256Key, err) } case X509URLKey: if err := json.AssignNextStringToken(&h.x509URL, dec); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, X509URLKey, err) } default: if dc := h.dc; dc != nil { if localReg := dc.Registry(); localReg != nil { decoded, err := localReg.Decode(dec, tok) if err == nil { h.setNoLock(tok, decoded) continue } } } decoded, err := registry.Decode(dec, tok) if err == nil { h.setNoLock(tok, decoded) continue } return fmt.Errorf(`could not decode field %s: %w`, tok, err) } default: return fmt.Errorf(`invalid token %T`, tok) } } if h.octets == nil { return fmt.Errorf(`required field k is missing`) } return nil } func (h symmetricKey) MarshalJSON() ([]byte, error) { data := make(map[string]any) fields := make([]string, 0, 9) data[KeyTypeKey] = jwa.OctetSeq() fields = append(fields, KeyTypeKey) if h.algorithm != nil { data[AlgorithmKey] = *(h.algorithm) fields = append(fields, AlgorithmKey) } if h.keyID != nil { data[KeyIDKey] = *(h.keyID) fields = append(fields, KeyIDKey) } if h.keyOps != nil { data[KeyOpsKey] = *(h.keyOps) fields = append(fields, KeyOpsKey) } if h.keyUsage != nil { data[KeyUsageKey] = *(h.keyUsage) fields = append(fields, KeyUsageKey) } if h.octets != nil { data[SymmetricOctetsKey] = h.octets fields = append(fields, SymmetricOctetsKey) } if h.x509CertChain != nil { data[X509CertChainKey] = h.x509CertChain fields = append(fields, X509CertChainKey) } if h.x509CertThumbprint != nil { data[X509CertThumbprintKey] = *(h.x509CertThumbprint) fields = append(fields, X509CertThumbprintKey) } if h.x509CertThumbprintS256 != nil { data[X509CertThumbprintS256Key] = *(h.x509CertThumbprintS256) fields = append(fields, X509CertThumbprintS256Key) } if h.x509URL != nil { data[X509URLKey] = *(h.x509URL) fields = append(fields, X509URLKey) } for k, v := range h.privateParams { data[k] = v fields = append(fields, k) } sort.Strings(fields) buf := pool.BytesBuffer().Get() defer pool.BytesBuffer().Put(buf) buf.WriteByte(tokens.OpenCurlyBracket) enc := json.NewEncoder(buf) for i, f := range fields { if i > 0 { buf.WriteRune(tokens.Comma) } buf.WriteRune(tokens.DoubleQuote) buf.WriteString(f) buf.WriteString(`":`) v := data[f] switch v := v.(type) { case []byte: buf.WriteRune(tokens.DoubleQuote) buf.WriteString(base64.EncodeToString(v)) buf.WriteRune(tokens.DoubleQuote) default: if err := enc.Encode(v); err != nil { return nil, fmt.Errorf(`failed to encode value for field %s: %w`, f, err) } buf.Truncate(buf.Len() - 1) } } buf.WriteByte(tokens.CloseCurlyBracket) ret := make([]byte, buf.Len()) copy(ret, buf.Bytes()) return ret, nil } func (h *symmetricKey) Keys() []string { h.mu.RLock() defer h.mu.RUnlock() keys := make([]string, 0, 9+len(h.privateParams)) keys = append(keys, KeyTypeKey) if h.algorithm != nil { keys = append(keys, AlgorithmKey) } if h.keyID != nil { keys = append(keys, KeyIDKey) } if h.keyOps != nil { keys = append(keys, KeyOpsKey) } if h.keyUsage != nil { keys = append(keys, KeyUsageKey) } if h.octets != nil { keys = append(keys, SymmetricOctetsKey) } if h.x509CertChain != nil { keys = append(keys, X509CertChainKey) } if h.x509CertThumbprint != nil { keys = append(keys, X509CertThumbprintKey) } if h.x509CertThumbprintS256 != nil { keys = append(keys, X509CertThumbprintS256Key) } if h.x509URL != nil { keys = append(keys, X509URLKey) } for k := range h.privateParams { keys = append(keys, k) } return keys } var symmetricStandardFields KeyFilter func init() { symmetricStandardFields = NewFieldNameFilter(KeyTypeKey, KeyUsageKey, KeyOpsKey, AlgorithmKey, KeyIDKey, X509URLKey, X509CertChainKey, X509CertThumbprintKey, X509CertThumbprintS256Key, SymmetricOctetsKey) } // SymmetricStandardFieldsFilter returns a KeyFilter that filters out standard Symmetric fields. func SymmetricStandardFieldsFilter() KeyFilter { return symmetricStandardFields } golang-github-lestrrat-go-jwx-3.0.13/jwk/testdata/000077500000000000000000000000001515060566400220105ustar00rootroot00000000000000golang-github-lestrrat-go-jwx-3.0.13/jwk/testdata/rs256.jwk000066400000000000000000000031711515060566400234100ustar00rootroot00000000000000{"alg":"RS256","d":"fZQDgdgQUTu9BjMRvNwt0-2Pl5cJ2-9m0cW3xZl5ap_JUF0nyOG5gmR30tdst2xUTichbNvJZvM5Sq38TZk7Q5k3khZvm2TMDzimPI4zg8X0mRf4keDis-Npsj0liuPtv7l4Zdni8lRVl4nBWnO91-e2HDbGkO_qBpcaL8t64B9LIxZbIDHUBXu25PurOrLOeucfs5uHO6oXTuWwMMjR64w7497x8nyco16YxP_1rr5Ku-hko2rzOkCyIR2Z5-JVDAlZhgSvUie3VMy_ax1ADH9P5LZdbj-fXwAikB80r9EYfp0stGThOstQrX06Gh_A5m1s-aprG5r_ctSpq4LIQQ","dp":"Kg2YsdKtsgDR-MHSftSbGrnRVKnduldPW4ufruyUiZn-cEwApAPYq5FEJOw-bUJ-QmwPzhv8M-AXYUo98lP-hlVdqSrpiZb5g7OmsjQD1vbBDKjh291-gtDTOdVLlvRWVTcw4TGV3kWLeSLPjKNXVORPkVuEEf0n-XV2wZ5bqh0","dq":"wFxWLTFH3dozNqt96S3LHEBP7bA0QbUx_8T4AxnifSknp-INzS-V0-7oeF5HenXJ9Nk8QemcUvQ2wHpfbIX_Fu2UdLlsbE2xLlvjcQbA36NLWZc0Bo3oupG660_CPB-bXUFVsI7rZJt5tCpw_BWnANeeX_l3i4yQwqRXPDqTEHc","e":"AQAB","key_ops":["sign","verify"],"kty":"RSA","n":"2ju-i9zuyy_-tbWneu90bviHlWMsVYp_9CNBWl_KJ-xeKw31LgH4pG0CATCL_m6ltJeDA5C27BnZ-Knq5jARI6fGE9zSLYnhSjbi7VlHsp-b2knCX_gaNUL_Yv4ZCRRCtoSr0hKGHGW3F8gf9e8BDnuyQo7z_xEEmxtEXTplvX8nSYN6sdKm9KKp38l1QzRVYQc9aaI6JdHm30SJ8m0Xoq7gSM9GWn3Qpc0qJJOqsIyJdek4ezPltce_0vHsKFvFYXJHsDIt9Gz-wzAkK-9yJ7x0QZh7HYBpxEN1WkckSnfbVbdo6DEdz3gpKLXRm7IA9RLpD5N0G0VVCu2oGBvKuQ","p":"7l685f3zywEx8R3JMS2y2B8WgaSQdKoVlbLA3VUOQ12_fHHqp-6RUuV31X817PqK5Ek0b1KjlAtolS5Y7tEfiWhf-RrDNedb26AMLLKBdfupGpF7KuVZl2OMkZ0xHYj-7WP-PLjnr3sDNBVFYRaKFFy9IBW686wVOaTez8ztA1c","q":"6l-9y52790WNoyOgXAtPP3j4DBH_4qa8uzo4TaSx5kTymQ8UsdqAUaeaG9EvubN7Oh83jTuS4W7vGla5Y9V3_fdYNzx969cJ54hW_GrKHxRLM6VtkET86djzs7rm2xmgaHYVaqBXgy-NUTpyBGaRo1kiR5H2OqoDt9DL2spkaG8","qi":"awGOEDph5Jds0dNNC-ezhBI6-vWX_ZfYEcZxUBkzQQup9KHTfuFtL4r_bZuffw0-A7pLtTszWwCfOb1uWP7pBZIVIOcAR-3hMqchoHYUOjvF99czv9a-8mT9N4BaRFFGtlxWfrIEVSvjrifgGFGkVsc_5bDE7uoCQ6trHfils3o"}golang-github-lestrrat-go-jwx-3.0.13/jwk/usage.go000066400000000000000000000033721515060566400216370ustar00rootroot00000000000000package jwk import ( "fmt" "sync" "sync/atomic" ) var strictKeyUsage = atomic.Bool{} var keyUsageNames = map[string]struct{}{} var muKeyUsageName sync.RWMutex // RegisterKeyUsage registers a possible value that can be used for KeyUsageType. // Normally, key usage (or the "use" field in a JWK) is either "sig" or "enc", // but other values may be used. // // While this module only works with "sig" and "enc", it is possible that // systems choose to use other values. This function allows users to register // new values to be accepted as valid key usage types. Values are case sensitive. // // Furthermore, the check against registered values can be completely turned off // by setting the global option `jwk.WithStrictKeyUsage(false)`. func RegisterKeyUsage(v string) { muKeyUsageName.Lock() defer muKeyUsageName.Unlock() keyUsageNames[v] = struct{}{} } func UnregisterKeyUsage(v string) { muKeyUsageName.Lock() defer muKeyUsageName.Unlock() delete(keyUsageNames, v) } func init() { strictKeyUsage.Store(true) RegisterKeyUsage("sig") RegisterKeyUsage("enc") } func isValidUsage(v string) bool { // This function can return true if strictKeyUsage is false if !strictKeyUsage.Load() { return true } muKeyUsageName.RLock() defer muKeyUsageName.RUnlock() _, ok := keyUsageNames[v] return ok } func (k KeyUsageType) String() string { return string(k) } func (k *KeyUsageType) Accept(v any) error { switch v := v.(type) { case KeyUsageType: if !isValidUsage(v.String()) { return fmt.Errorf("invalid key usage type: %q", v) } *k = v return nil case string: if !isValidUsage(v) { return fmt.Errorf("invalid key usage type: %q", v) } *k = KeyUsageType(v) return nil } return fmt.Errorf("invalid Go type for key usage type: %T", v) } golang-github-lestrrat-go-jwx-3.0.13/jwk/whitelist.go000066400000000000000000000022201515060566400225360ustar00rootroot00000000000000package jwk import "github.com/lestrrat-go/httprc/v3" type Whitelist = httprc.Whitelist type WhitelistFunc = httprc.WhitelistFunc // InsecureWhitelist is an alias to httprc.InsecureWhitelist. Use // functions in the `httprc` package to interact with this type. type InsecureWhitelist = httprc.InsecureWhitelist func NewInsecureWhitelist() InsecureWhitelist { return httprc.NewInsecureWhitelist() } // BlockAllWhitelist is an alias to httprc.BlockAllWhitelist. Use // functions in the `httprc` package to interact with this type. type BlockAllWhitelist = httprc.BlockAllWhitelist func NewBlockAllWhitelist() BlockAllWhitelist { return httprc.NewBlockAllWhitelist() } // RegexpWhitelist is an alias to httprc.RegexpWhitelist. Use // functions in the `httprc` package to interact with this type. type RegexpWhitelist = httprc.RegexpWhitelist func NewRegexpWhitelist() *RegexpWhitelist { return httprc.NewRegexpWhitelist() } // MapWhitelist is an alias to httprc.MapWhitelist. Use // functions in the `httprc` package to interact with this type. type MapWhitelist = httprc.MapWhitelist func NewMapWhitelist() MapWhitelist { return httprc.NewMapWhitelist() } golang-github-lestrrat-go-jwx-3.0.13/jwk/x509.go000066400000000000000000000165521515060566400212440ustar00rootroot00000000000000package jwk import ( "crypto/ecdsa" "crypto/ed25519" "crypto/rsa" "crypto/x509" "encoding/pem" "errors" "fmt" "sync" "github.com/lestrrat-go/blackmagic" "github.com/lestrrat-go/jwx/v3/jwk/jwkbb" ) // PEMDecoder is an interface to describe an object that can decode // a key from PEM encoded ASN.1 DER format. // // A PEMDecoder can be specified as an option to `jwk.Parse()` or `jwk.ParseKey()` // along with the `jwk.WithPEM()` option. type PEMDecoder interface { Decode([]byte) (any, []byte, error) } // PEMEncoder is an interface to describe an object that can encode // a key into PEM encoded ASN.1 DER format. // // `jwk.Key` instances do not implement a way to encode themselves into // PEM format. Normally you can just use `jwk.EncodePEM()` to do this, but // this interface allows you to generalize the encoding process by // abstracting the `jwk.EncodePEM()` function using `jwk.PEMEncodeFunc` // along with alternate implementations, should you need them. type PEMEncoder interface { Encode(any) (string, []byte, error) } type PEMEncodeFunc func(any) (string, []byte, error) func (f PEMEncodeFunc) Encode(v any) (string, []byte, error) { return f(v) } func encodeX509(v any) (string, []byte, error) { // we can't import jwk, so just use the interface if key, ok := v.(Key); ok { var raw any if err := Export(key, &raw); err != nil { return "", nil, fmt.Errorf(`failed to get raw key out of %T: %w`, key, err) } v = raw } // Try to convert it into a certificate switch v := v.(type) { case *rsa.PrivateKey: return pmRSAPrivateKey, x509.MarshalPKCS1PrivateKey(v), nil case *ecdsa.PrivateKey: marshaled, err := x509.MarshalECPrivateKey(v) if err != nil { return "", nil, err } return pmECPrivateKey, marshaled, nil case ed25519.PrivateKey: marshaled, err := x509.MarshalPKCS8PrivateKey(v) if err != nil { return "", nil, err } return pmPrivateKey, marshaled, nil case *rsa.PublicKey, *ecdsa.PublicKey, ed25519.PublicKey: marshaled, err := x509.MarshalPKIXPublicKey(v) if err != nil { return "", nil, err } return pmPublicKey, marshaled, nil default: return "", nil, fmt.Errorf(`unsupported type %T for ASN.1 DER encoding`, v) } } // EncodePEM encodes the key into a PEM encoded ASN.1 DER format. // The key can be a jwk.Key or a raw key instance, but it must be one of // the types supported by `x509` package. // // Internally, it uses the same routine as `jwk.EncodeX509()`, and therefore // the same caveats apply func EncodePEM(v any) ([]byte, error) { typ, marshaled, err := encodeX509(v) if err != nil { return nil, fmt.Errorf(`failed to encode key in x509: %w`, err) } block := &pem.Block{ Type: typ, Bytes: marshaled, } return pem.EncodeToMemory(block), nil } const ( pmPrivateKey = `PRIVATE KEY` pmPublicKey = `PUBLIC KEY` pmECPrivateKey = `EC PRIVATE KEY` pmRSAPublicKey = `RSA PUBLIC KEY` pmRSAPrivateKey = `RSA PRIVATE KEY` ) // NewPEMDecoder returns a PEMDecoder that decodes keys in PEM encoded ASN.1 DER format. // You can use it as argument to `jwk.WithPEMDecoder()` option. // // The use of this function is planned to be deprecated. The plan is to replace the // `jwk.WithPEMDecoder()` option with globally available custom X509 decoders which // can be registered via `jwk.RegisterX509Decoder()` function. func NewPEMDecoder() PEMDecoder { return pemDecoder{} } type pemDecoder struct{} // Decode decodes a key in PEM encoded ASN.1 DER format. // and returns a raw key. func (pemDecoder) Decode(src []byte) (any, []byte, error) { block, rest := pem.Decode(src) if block == nil { return nil, rest, fmt.Errorf(`failed to decode PEM data`) } var ret any if err := jwkbb.DecodeX509(&ret, block); err != nil { return nil, rest, err } return ret, rest, nil } // X509Decoder is an interface that describes an object that can decode // a PEM encoded ASN.1 DER format into a specific type of key. // // This interface is experimental, and may change in the future. type X509Decoder interface { // DecodeX509 decodes the given PEM block into the destination object. // The destination object must be a pointer to a type that can hold the // decoded key, such as *rsa.PrivateKey, *ecdsa.PrivateKey, etc. DecodeX509(dst any, block *pem.Block) error } // X509DecodeFunc is a function type that implements the X509Decoder interface. // It allows you to create a custom X509Decoder by providing a function // that takes a destination and a PEM block, and returns an error if the decoding fails. // // This interface is experimental, and may change in the future. type X509DecodeFunc func(dst any, block *pem.Block) error func (f X509DecodeFunc) DecodeX509(dst any, block *pem.Block) error { return f(dst, block) } var muX509Decoders sync.Mutex var x509Decoders = map[any]int{} var x509DecoderList = []X509Decoder{} type identDefaultX509Decoder struct{} func init() { RegisterX509Decoder(identDefaultX509Decoder{}, X509DecodeFunc(jwkbb.DecodeX509)) } // RegisterX509Decoder registers a new X509Decoder that can decode PEM encoded ASN.1 DER format. // Because the decoder could be non-comparable, you must provide an identifier that can be used // as a map key to identify the decoder. // // This function is experimental, and may change in the future. func RegisterX509Decoder(ident any, decoder X509Decoder) { if decoder == nil { panic(`jwk.RegisterX509Decoder: decoder cannot be nil`) } muX509Decoders.Lock() defer muX509Decoders.Unlock() if _, ok := x509Decoders[ident]; ok { return // already registered } x509Decoders[ident] = len(x509DecoderList) x509DecoderList = append(x509DecoderList, decoder) } // UnregisterX509Decoder unregisters the X509Decoder identified by the given identifier. // If the identifier is not registered, it does nothing. // // This function is experimental, and may change in the future. func UnregisterX509Decoder(ident any) { muX509Decoders.Lock() defer muX509Decoders.Unlock() idx, ok := x509Decoders[ident] if !ok { return // not registered } delete(x509Decoders, ident) l := len(x509DecoderList) switch idx { case l - 1: // if the last element, just truncate the slice x509DecoderList = x509DecoderList[:l-1] case 0: // if the first element, just shift the slice x509DecoderList = x509DecoderList[1:] default: // if the element is in the middle, remove it by slicing // and appending the two slices together x509DecoderList = append(x509DecoderList[:idx], x509DecoderList[idx+1:]...) } } // decodeX509 decodes a PEM encoded ASN.1 DER format into the given destination. // It tries all registered X509 decoders until one of them succeeds. // If no decoder can handle the PEM block, it returns an error. func decodeX509(dst any, src []byte) error { block, _ := pem.Decode(src) if block == nil { return fmt.Errorf(`failed to decode PEM data`) } var errs []error for _, d := range x509DecoderList { if err := d.DecodeX509(dst, block); err != nil { errs = append(errs, err) continue } // successfully decoded return nil } return fmt.Errorf(`failed to decode X509 data using any of the decoders: %w`, errors.Join(errs...)) } func decodeX509WithPEMDEcoder(dst any, src []byte, decoder PEMDecoder) error { ret, _, err := decoder.Decode(src) if err != nil { return fmt.Errorf(`failed to decode PEM data: %w`, err) } if err := blackmagic.AssignIfCompatible(dst, ret); err != nil { return fmt.Errorf(`failed to assign decoded key to destination: %w`, err) } return nil } golang-github-lestrrat-go-jwx-3.0.13/jwk/x5c_test.go000066400000000000000000000121511515060566400222640ustar00rootroot00000000000000package jwk_test import ( "testing" "github.com/lestrrat-go/jwx/v3/cert" "github.com/lestrrat-go/jwx/v3/internal/json" "github.com/stretchr/testify/require" ) func Test_X5CHeader(t *testing.T) { certs := []string{ "MIIE3jCCA8agAwIBAgICAwEwDQYJKoZIhvcNAQEFBQAwYzELMAkGA1UEBhMCVVMxITAfBgNVBAoTGFRoZSBHbyBEYWRkeSBHcm91cCwgSW5jLjExMC8GA1UECxMoR28gRGFkZHkgQ2xhc3MgMiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjExMTYwMTU0MzdaFw0yNjExMTYwMTU0MzdaMIHKMQswCQYDVQQGEwJVUzEQMA4GA1UECBMHQXJpem9uYTETMBEGA1UEBxMKU2NvdHRzZGFsZTEaMBgGA1UEChMRR29EYWRkeS5jb20sIEluYy4xMzAxBgNVBAsTKmh0dHA6Ly9jZXJ0aWZpY2F0ZXMuZ29kYWRkeS5jb20vcmVwb3NpdG9yeTEwMC4GA1UEAxMnR28gRGFkZHkgU2VjdXJlIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MREwDwYDVQQFEwgwNzk2OTI4NzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMQt1RWMnCZM7DI161+4WQFapmGBWTtwY6vj3D3HKrjJM9N55DrtPDAjhI6zMBS2sofDPZVUBJ7fmd0LJR4h3mUpfjWoqVTr9vcyOdQmVZWt7/v+WIbXnvQAjYwqDL1CBM6nPwT27oDyqu9SoWlm2r4arV3aLGbqGmu75RpRSgAvSMeYddi5Kcju+GZtCpyz8/x4fKL4o/K1w/O5epHBp+YlLpyo7RJlbmr2EkRTcDCVw5wrWCs9CHRK8r5RsL+H0EwnWGu1NcWdrxcx+AuP7q2BNgWJCJjPOq8lh8BJ6qf9Z/dFjpfMFDniNoW1fho3/Rb2cRGadDAW/hOUoz+EDU8CAwEAAaOCATIwggEuMB0GA1UdDgQWBBT9rGEyk2xF1uLuhV+auud2mWjM5zAfBgNVHSMEGDAWgBTSxLDSkdRMEXGzYcs9of7dqGrU4zASBgNVHRMBAf8ECDAGAQH/AgEAMDMGCCsGAQUFBwEBBCcwJTAjBggrBgEFBQcwAYYXaHR0cDovL29jc3AuZ29kYWRkeS5jb20wRgYDVR0fBD8wPTA7oDmgN4Y1aHR0cDovL2NlcnRpZmljYXRlcy5nb2RhZGR5LmNvbS9yZXBvc2l0b3J5L2dkcm9vdC5jcmwwSwYDVR0gBEQwQjBABgRVHSAAMDgwNgYIKwYBBQUHAgEWKmh0dHA6Ly9jZXJ0aWZpY2F0ZXMuZ29kYWRkeS5jb20vcmVwb3NpdG9yeTAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQEFBQADggEBANKGwOy9+aG2Z+5mC6IGOgRQjhVyrEp0lVPLN8tESe8HkGsz2ZbwlFalEzAFPIUyIXvJxwqoJKSQ3kbTJSMUA2fCENZvD117esyfxVgqwcSeIaha86ykRvOe5GPLL5CkKSkB2XIsKd83ASe8T+5o0yGPwLPk9Qnt0hCqU7S+8MxZC9Y7lhyVJEnfzuz9p0iRFEUOOjZv2kWzRaJBydTXRE4+uXR21aITVSzGh6O1mawGhId/dQb8vxRMDsxuxN89txJx9OjxUUAiKEngHUuHqDTMBqLdElrRhjZkAzVvb3du6/KFUJheqwNTrZEjYx8WnM25sgVjOuH0aBsXBTWVU+4=", "MIIE+zCCBGSgAwIBAgICAQ0wDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENsYXNzIDIgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTA0MDYyOTE3MDYyMFoXDTI0MDYyOTE3MDYyMFowYzELMAkGA1UEBhMCVVMxITAfBgNVBAoTGFRoZSBHbyBEYWRkeSBHcm91cCwgSW5jLjExMC8GA1UECxMoR28gRGFkZHkgQ2xhc3MgMiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASAwDQYJKoZIhvcNAQEBBQADggENADCCAQgCggEBAN6d1+pXGEmhW+vXX0iG6r7d/+TvZxz0ZWizV3GgXne77ZtJ6XCAPVYYYwhv2vLM0D9/AlQiVBDYsoHUwHU9S3/Hd8M+eKsaA7Ugay9qK7HFiH7Eux6wwdhFJ2+qN1j3hybX2C32qRe3H3I2TqYXP2WYktsqbl2i/ojgC95/5Y0V4evLOtXiEqITLdiOr18SPaAIBQi2XKVlOARFmR6jYGB0xUGlcmIbYsUfb18aQr4CUWWoriMYavx4A6lNf4DD+qta/KFApMoZFv6yyO9ecw3ud72a9nmYvLEHZ6IVDd2gWMZEewo+YihfukEHU1jPEX44dMX4/7VpkI+EdOqXG68CAQOjggHhMIIB3TAdBgNVHQ4EFgQU0sSw0pHUTBFxs2HLPaH+3ahq1OMwgdIGA1UdIwSByjCBx6GBwaSBvjCBuzEkMCIGA1UEBxMbVmFsaUNlcnQgVmFsaWRhdGlvbiBOZXR3b3JrMRcwFQYDVQQKEw5WYWxpQ2VydCwgSW5jLjE1MDMGA1UECxMsVmFsaUNlcnQgQ2xhc3MgMiBQb2xpY3kgVmFsaWRhdGlvbiBBdXRob3JpdHkxITAfBgNVBAMTGGh0dHA6Ly93d3cudmFsaWNlcnQuY29tLzEgMB4GCSqGSIb3DQEJARYRaW5mb0B2YWxpY2VydC5jb22CAQEwDwYDVR0TAQH/BAUwAwEB/zAzBggrBgEFBQcBAQQnMCUwIwYIKwYBBQUHMAGGF2h0dHA6Ly9vY3NwLmdvZGFkZHkuY29tMEQGA1UdHwQ9MDswOaA3oDWGM2h0dHA6Ly9jZXJ0aWZpY2F0ZXMuZ29kYWRkeS5jb20vcmVwb3NpdG9yeS9yb290LmNybDBLBgNVHSAERDBCMEAGBFUdIAAwODA2BggrBgEFBQcCARYqaHR0cDovL2NlcnRpZmljYXRlcy5nb2RhZGR5LmNvbS9yZXBvc2l0b3J5MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOBgQC1QPmnHfbq/qQaQlpE9xXUhUaJwL6e4+PrxeNYiY+Sn1eocSxI0YGyeR+sBjUZsE4OWBsUs5iB0QQeyAfJg594RAoYC5jcdnplDQ1tgMQLARzLrUc+cb53S8wGd9D0VmsfSxOaFIqII6hR8INMqzW/Rn453HWkrugp++85j09VZw==", "MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENsYXNzIDIgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNjAwMTk1NFoXDTE5MDYyNjAwMTk1NFowgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENsYXNzIDIgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDOOnHK5avIWZJV16vYdA757tn2VUdZZUcOBVXc65g2PFxTXdMwzzjsvUGJ7SVCCSRrCl6zfN1SLUzm1NZ9WlmpZdRJEy0kTRxQb7XBhVQ7/nHk01xC+YDgkRoKWzk2Z/M/VXwbP7RfZHM047QSv4dk+NoS/zcnwbNDu+97bi5p9wIDAQABMA0GCSqGSIb3DQEBBQUAA4GBADt/UG9vUJSZSWI4OB9L+KXIPqeCgfYrx+jFzug6EILLGACOTb2oWH+heQC1u+mNr0HZDzTuIYEZoDJJKPTEjlbVUjP9UNV+mWwD5MlM/Mtsq2azSiGM5bUMMj4QssxsodyamEwCW/POuZ6lcg5Ktz885hZo+L7tdEy8W9ViH0Pd", } t.Run("Marshal/Unmarshal", func(t *testing.T) { expected, err := json.Marshal(certs) require.NoError(t, err, `json.Marshal should succeed`) // Take the input, and create a json jsonbuf, err := json.Marshal(certs) require.NoError(t, err, `json.Marshal should succeed (for input)`) var c cert.Chain require.NoError(t, json.Unmarshal(jsonbuf, &c), `json.Unmarshal should succeed`) require.Equal(t, c.Len(), 3, `should have three certs`) buf, err := json.Marshal(c) require.NoError(t, err, `json.Marshal should succeed`) require.Equal(t, expected, buf, `json output should match`) }) } golang-github-lestrrat-go-jwx-3.0.13/jws/000077500000000000000000000000001515060566400202075ustar00rootroot00000000000000golang-github-lestrrat-go-jwx-3.0.13/jws/BUILD.bazel000066400000000000000000000033471515060566400220740ustar00rootroot00000000000000load("@rules_go//go:def.bzl", "go_library", "go_test") go_library( name = "jws", srcs = [ "errors.go", "filter.go", "headers.go", "headers_gen.go", "interface.go", "io.go", "jws.go", "key_provider.go", "legacy.go", "message.go", "options.go", "options_gen.go", "signer.go", "sign_context.go", "signature_builder.go", "verifier.go", "verify_context.go", ], importpath = "github.com/lestrrat-go/jwx/v3/jws", visibility = ["//visibility:public"], deps = [ "//cert", "//internal/base64", "//internal/ecutil", "//internal/json", "//internal/jwxio", "//internal/tokens", "//internal/keyconv", "//internal/pool", "//jwa", "//jwk", "//jws/internal/keytype", "//jws/jwsbb", "//jws/legacy", "//transform", "@com_github_lestrrat_go_blackmagic//:blackmagic", "@com_github_lestrrat_go_option_v2//:option", ], ) go_test( name = "jws_test", srcs = [ "es256k_test.go", "filter_test.go", "headers_test.go", "jws_test.go", "message_test.go", "options_gen_test.go", "signer_test.go", ], embed = [":jws"], deps = [ "//cert", "//internal/base64", "//internal/ecutil", "//internal/json", "//internal/jwxtest", "//jwa", "//jwk", "//jwt", "@com_github_lestrrat_go_httprc_v3//:httprc", "@com_github_stretchr_testify//require", ], ) alias( name = "go_default_library", actual = ":jws", visibility = ["//visibility:public"], ) golang-github-lestrrat-go-jwx-3.0.13/jws/README.md000066400000000000000000000076261515060566400215010ustar00rootroot00000000000000# JWS [![Go Reference](https://pkg.go.dev/badge/github.com/lestrrat-go/jwx/v3/jws.svg)](https://pkg.go.dev/github.com/lestrrat-go/jwx/v3/jws) Package jws implements JWS as described in [RFC7515](https://tools.ietf.org/html/rfc7515) and [RFC7797](https://tools.ietf.org/html/rfc7797) * Parse and generate compact or JSON serializations * Sign and verify arbitrary payload * Use any of the keys supported in [github.com/lestrrat-go/jwx/v3/jwk](../jwk) * Add arbitrary fields in the JWS object * Ability to add/replace existing signature methods * Respect "b64" settings for RFC7797 How-to style documentation can be found in the [docs directory](../docs). Examples are located in the examples directory ([jws_example_test.go](../examples/jws_example_test.go)) Supported signature algorithms: | Algorithm | Supported? | Constant in [jwa](../jwa) | |:----------------------------------------|:-----------|:-------------------------| | HMAC using SHA-256 | YES | jwa.HS256 | | HMAC using SHA-384 | YES | jwa.HS384 | | HMAC using SHA-512 | YES | jwa.HS512 | | RSASSA-PKCS-v1.5 using SHA-256 | YES | jwa.RS256 | | RSASSA-PKCS-v1.5 using SHA-384 | YES | jwa.RS384 | | RSASSA-PKCS-v1.5 using SHA-512 | YES | jwa.RS512 | | ECDSA using P-256 and SHA-256 | YES | jwa.ES256 | | ECDSA using P-384 and SHA-384 | YES | jwa.ES384 | | ECDSA using P-521 and SHA-512 | YES | jwa.ES512 | | ECDSA using secp256k1 and SHA-256 (2) | YES | jwa.ES256K | | RSASSA-PSS using SHA256 and MGF1-SHA256 | YES | jwa.PS256 | | RSASSA-PSS using SHA384 and MGF1-SHA384 | YES | jwa.PS384 | | RSASSA-PSS using SHA512 and MGF1-SHA512 | YES | jwa.PS512 | | EdDSA (1) | YES | jwa.EdDSA | * Note 1: Experimental * Note 2: Experimental, and must be toggled using `-tags jwx_es256k` build tag # SYNOPSIS ## Sign and verify arbitrary data ```go import( "crypto/rand" "crypto/rsa" "log" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/lestrrat-go/jwx/v3/jws" ) func main() { privkey, err := rsa.GenerateKey(rand.Reader, 2048) if err != nil { log.Printf("failed to generate private key: %s", err) return } buf, err := jws.Sign([]byte("Lorem ipsum"), jws.WithKey(jwa.RS256, privkey)) if err != nil { log.Printf("failed to created JWS message: %s", err) return } // When you receive a JWS message, you can verify the signature // and grab the payload sent in the message in one go: verified, err := jws.Verify(buf, jws.WithKey(jwa.RS256, &privkey.PublicKey)) if err != nil { log.Printf("failed to verify message: %s", err) return } log.Printf("signed message verified! -> %s", verified) } ``` ## Programmatically manipulate `jws.Message` ```go func ExampleMessage() { // initialization for the following variables have been omitted. // please see jws_example_test.go for details var decodedPayload, decodedSig1, decodedSig2 []byte var public1, protected1, public2, protected2 jws.Header // Construct a message. DO NOT use values that are base64 encoded m := jws.NewMessage(). SetPayload(decodedPayload). AppendSignature( jws.NewSignature(). SetSignature(decodedSig1). SetProtectedHeaders(public1). SetPublicHeaders(protected1), ). AppendSignature( jws.NewSignature(). SetSignature(decodedSig2). SetProtectedHeaders(public2). SetPublicHeaders(protected2), ) buf, err := json.MarshalIndent(m, "", " ") if err != nil { fmt.Printf("%s\n", err) return } _ = buf } ``` golang-github-lestrrat-go-jwx-3.0.13/jws/errors.go000066400000000000000000000047641515060566400220650ustar00rootroot00000000000000package jws import ( "fmt" ) type signError struct { error } var errDefaultSignError = signerr(`unknown error`) // SignError returns an error that can be passed to `errors.Is` to check if the error is a sign error. func SignError() error { return errDefaultSignError } func (e signError) Unwrap() error { return e.error } func (signError) Is(err error) bool { _, ok := err.(signError) return ok } func signerr(f string, args ...any) error { return signError{fmt.Errorf(`jws.Sign: `+f, args...)} } // This error is returned when jws.Verify fails, but note that there's another type of // message that can be returned by jws.Verify, which is `errVerification`. type verifyError struct { error } var errDefaultVerifyError = verifyerr(`unknown error`) // VerifyError returns an error that can be passed to `errors.Is` to check if the error is a verify error. func VerifyError() error { return errDefaultVerifyError } func (e verifyError) Unwrap() error { return e.error } func (verifyError) Is(err error) bool { _, ok := err.(verifyError) return ok } func verifyerr(f string, args ...any) error { return verifyError{fmt.Errorf(`jws.Verify: `+f, args...)} } // verificationError is returned when the actual _verification_ of the key/payload fails. type verificationError struct { error } var errDefaultVerificationError = verificationError{fmt.Errorf(`unknown verification error`)} // VerificationError returns an error that can be passed to `errors.Is` to check if the error is a verification error. func VerificationError() error { return errDefaultVerificationError } func (e verificationError) Unwrap() error { return e.error } func (verificationError) Is(err error) bool { _, ok := err.(verificationError) return ok } type parseError struct { error } var errDefaultParseError = parseerr(`unknown error`) // ParseError returns an error that can be passed to `errors.Is` to check if the error is a parse error. func ParseError() error { return errDefaultParseError } func (e parseError) Unwrap() error { return e.error } func (parseError) Is(err error) bool { _, ok := err.(parseError) return ok } func bparseerr(prefix string, f string, args ...any) error { return parseError{fmt.Errorf(prefix+": "+f, args...)} } func parseerr(f string, args ...any) error { return bparseerr(`jws.Parse`, f, args...) } func sparseerr(f string, args ...any) error { return bparseerr(`jws.ParseString`, f, args...) } func rparseerr(f string, args ...any) error { return bparseerr(`jws.ParseReader`, f, args...) } golang-github-lestrrat-go-jwx-3.0.13/jws/es256k.go000066400000000000000000000003001515060566400215460ustar00rootroot00000000000000//go:build jwx_es256k package jws import ( "github.com/lestrrat-go/jwx/v3/jwa" ) func init() { // Register ES256K to EC algorithm family addAlgorithmForKeyType(jwa.EC(), jwa.ES256K()) } golang-github-lestrrat-go-jwx-3.0.13/jws/es256k_test.go000066400000000000000000000012741515060566400226200ustar00rootroot00000000000000//go:build jwx_es256k package jws_test import ( "testing" "github.com/lestrrat-go/jwx/v3/internal/jwxtest" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/lestrrat-go/jwx/v3/jwk" "github.com/stretchr/testify/require" ) func init() { hasES256K = true } func TestES256K(t *testing.T) { payload := []byte("Hello, World!") t.Parallel() key, err := jwxtest.GenerateEcdsaKey(jwa.Secp256k1()) require.NoError(t, err, "ECDSA key generated") jwkKey, _ := jwk.Import(key.PublicKey) keys := map[string]any{ "Verify(ecdsa.PublicKey)": key.PublicKey, "Verify(*ecdsa.PublicKey)": &key.PublicKey, "Verify(jwk.Key)": jwkKey, } testRoundtrip(t, payload, jwa.ES256K(), key, keys) } golang-github-lestrrat-go-jwx-3.0.13/jws/filter.go000066400000000000000000000024401515060566400220230ustar00rootroot00000000000000package jws import ( "github.com/lestrrat-go/jwx/v3/transform" ) // HeaderFilter is an interface that allows users to filter JWS header fields. // It provides two methods: Filter and Reject; Filter returns a new header with only // the fields that match the filter criteria, while Reject returns a new header with // only the fields that DO NOT match the filter. // // EXPERIMENTAL: This API is experimental and its interface and behavior is // subject to change in future releases. This API is not subject to semver // compatibility guarantees. type HeaderFilter interface { Filter(header Headers) (Headers, error) Reject(header Headers) (Headers, error) } // StandardHeadersFilter returns a HeaderFilter that filters out standard JWS header fields. // // You can use this filter to create headers that either only have standard fields // or only custom fields. // // If you need to configure the filter more precisely, consider // using the HeaderNameFilter directly. func StandardHeadersFilter() HeaderFilter { return stdHeadersFilter } var stdHeadersFilter = NewHeaderNameFilter(stdHeaderNames...) // NewHeaderNameFilter creates a new HeaderNameFilter with the specified field names. func NewHeaderNameFilter(names ...string) HeaderFilter { return transform.NewNameBasedFilter[Headers](names...) } golang-github-lestrrat-go-jwx-3.0.13/jws/filter_test.go000066400000000000000000000160031515060566400230620ustar00rootroot00000000000000package jws_test import ( "errors" "sync" "testing" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/lestrrat-go/jwx/v3/jws" "github.com/stretchr/testify/require" ) func TestHeaderNameFilter(t *testing.T) { t.Run("Basic functionality", func(t *testing.T) { // Create a filter that includes specific fields fn := jws.NewHeaderNameFilter("custom1", "custom3") // Create a header with standard and custom fields headers := jws.NewHeaders() // Headers in JWS might need special handling to be recognized by Keys() and Has() // so we'll primarily test with custom fields that are stored in privateParams headers.Set("custom1", "value1") headers.Set("custom2", "value2") headers.Set("custom3", "value3") t.Run("Filter specific fields", func(t *testing.T) { // Filter should return a header with only specified fields filtered, err := fn.Filter(headers) require.NoError(t, err, "fn.Filter should succeed") // Debug info t.Logf("Original headers keys: %v", headers.Keys()) t.Logf("Filtered headers keys: %v", filtered.Keys()) // Verify included fields are present require.True(t, filtered.Has("custom1"), "filtered header should have custom1 field") require.True(t, filtered.Has("custom3"), "filtered header should have custom3 field") // Verify excluded fields are not present require.False(t, filtered.Has("custom2"), "filtered header should not have custom2 field") }) t.Run("Reject specific fields", func(t *testing.T) { // Reject should return a header without specified fields rejected, err := fn.Reject(headers) require.NoError(t, err, "fn.Reject should succeed") // Verify included fields are not present require.False(t, rejected.Has("custom1"), "rejected header should not have custom1 field") require.False(t, rejected.Has("custom3"), "rejected header should not have custom3 field") // Verify excluded fields are present require.True(t, rejected.Has("custom2"), "rejected header should have custom2 field") }) }) t.Run("Empty filter", func(t *testing.T) { // Create an empty filter (no fields) fn := jws.NewHeaderNameFilter() // Create a header with some fields headers := jws.NewHeaders() headers.Set(jws.AlgorithmKey, jwa.ES256()) headers.Set("custom", "value") // Filter with empty HeaderNameFilter should result in an empty header filtered, err := fn.Filter(headers) require.NoError(t, err, "fn.Filter should succeed") require.Empty(t, filtered.Keys(), "filtered header should have no fields") // Reject with empty HeaderNameFilter should result in a copy of the original header rejected, err := fn.Reject(headers) require.NoError(t, err, "fn.Reject should succeed") // Check that rejected header has the same keys as original originalKeys := headers.Keys() rejectedKeys := rejected.Keys() require.ElementsMatch(t, originalKeys, rejectedKeys, "rejected header should have the same fields as the original") }) t.Run("Concurrency safety", func(t *testing.T) { // This is more of a logical test than an actual concurrency test // but it ensures the filter is being used correctly fn := jws.NewHeaderNameFilter("custom1") // Create a header headers := jws.NewHeaders() headers.Set("custom1", "value1") headers.Set("custom2", "value2") // Run filter and reject operations concurrently var wg sync.WaitGroup const iterations = 10 const numGoroutines = 2 wg.Add(iterations * numGoroutines) for range iterations { go func() { defer wg.Done() filtered, err := fn.Filter(headers) require.NoError(t, err, "fn.Filter should succeed") require.True(t, filtered.Has("custom1"), "filtered header should have custom1 field") require.False(t, filtered.Has("custom2"), "filtered header should not have custom2 field") }() go func() { defer wg.Done() rejected, err := fn.Reject(headers) require.NoError(t, err, "fn.Reject should succeed") require.False(t, rejected.Has("custom1"), "rejected header should not have custom1 field") require.True(t, rejected.Has("custom2"), "rejected header should have custom2 field") }() } wg.Wait() }) } func TestStandardHeadersFilter(t *testing.T) { t.Run("Filter standard headers", func(t *testing.T) { // Create a header with standard and custom fields headers := jws.NewHeaders() // Standard headers may not be properly reflected in Keys() // so we focus on testing custom fields which are stored in privateParams headers.Set("custom1", "value1") headers.Set("custom2", "value2") stdFilter := jws.StandardHeadersFilter() t.Run("Filter standard headers", func(t *testing.T) { // Filter should return a header with only standard fields filtered, err := stdFilter.Filter(headers) require.NoError(t, err, "filter.Filter should succeed") // Since our test only uses custom fields (non-standard), // the filtered result should be empty require.Empty(t, filtered.Keys(), "filtered header should have no fields") // Verify custom fields are not present require.False(t, filtered.Has("custom1"), "filtered header should not have custom1 field") require.False(t, filtered.Has("custom2"), "filtered header should not have custom2 field") }) t.Run("Reject standard headers", func(t *testing.T) { // Reject should return a header with only custom fields rejected, err := stdFilter.Reject(headers) require.NoError(t, err, "filter.Reject should succeed") // Verify custom fields are present (all original fields should be present) require.True(t, rejected.Has("custom1"), "rejected header should have custom1 field") require.True(t, rejected.Has("custom2"), "rejected header should have custom2 field") // Check that all original keys are present originalKeys := headers.Keys() rejectedKeys := rejected.Keys() require.ElementsMatch(t, originalKeys, rejectedKeys, "rejected should have all original fields") // Verify values are preserved var customValue string require.NoError(t, rejected.Get("custom1", &customValue), "rejected.Get should succeed") require.Equal(t, "value1", customValue, "value for custom1 field should be preserved") }) }) } // This test ensures proper error handling when Clone fails type errorHeaders struct { jws.Headers } func (h *errorHeaders) Clone() (jws.Headers, error) { return nil, errors.New("forced Clone error") } func (h *errorHeaders) Keys() []string { return []string{} } func TestHeaderFilterErrors(t *testing.T) { // Create a header that will cause errors badHeaders := &errorHeaders{jws.NewHeaders()} filter := jws.NewHeaderNameFilter("test") t.Run("Filter error handling", func(t *testing.T) { _, err := filter.Filter(badHeaders) require.Error(t, err, "filter.Filter should fail with errorHeaders") require.Contains(t, err.Error(), "forced Clone error", "error message should include Clone error") }) t.Run("Reject error handling", func(t *testing.T) { _, err := filter.Reject(badHeaders) require.Error(t, err, "filter.Reject should fail with errorHeaders") require.Contains(t, err.Error(), "forced Clone error", "error message should include Clone error") }) } golang-github-lestrrat-go-jwx-3.0.13/jws/headers.go000066400000000000000000000023471515060566400221570ustar00rootroot00000000000000package jws import ( "fmt" ) func (h *stdHeaders) Copy(dst Headers) error { for _, k := range h.Keys() { var v any if err := h.Get(k, &v); err != nil { return fmt.Errorf(`failed to get header %q: %w`, k, err) } if err := dst.Set(k, v); err != nil { return fmt.Errorf(`failed to set header %q: %w`, k, err) } } return nil } // mergeHeaders merges two headers, and works even if the first Header // object is nil. This is not exported because ATM it felt like this // function is not frequently used, and MergeHeaders seemed a clunky name func mergeHeaders(h1, h2 Headers) (Headers, error) { h3 := NewHeaders() if h1 != nil { if err := h1.Copy(h3); err != nil { return nil, fmt.Errorf(`failed to copy headers from first Header: %w`, err) } } if h2 != nil { if err := h2.Copy(h3); err != nil { return nil, fmt.Errorf(`failed to copy headers from second Header: %w`, err) } } return h3, nil } func (h *stdHeaders) Merge(h2 Headers) (Headers, error) { return mergeHeaders(h, h2) } // Clone creates a deep copy of the header func (h *stdHeaders) Clone() (Headers, error) { dst := NewHeaders() if err := h.Copy(dst); err != nil { return nil, fmt.Errorf(`failed to copy header: %w`, err) } return dst, nil } golang-github-lestrrat-go-jwx-3.0.13/jws/headers_gen.go000066400000000000000000000471231515060566400230110ustar00rootroot00000000000000// Code generated by tools/cmd/genjws/main.go. DO NOT EDIT. package jws import ( "bytes" "fmt" "sort" "sync" "github.com/lestrrat-go/blackmagic" "github.com/lestrrat-go/jwx/v3/cert" "github.com/lestrrat-go/jwx/v3/internal/base64" "github.com/lestrrat-go/jwx/v3/internal/json" "github.com/lestrrat-go/jwx/v3/internal/pool" "github.com/lestrrat-go/jwx/v3/internal/tokens" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/lestrrat-go/jwx/v3/jwk" ) const ( AlgorithmKey = "alg" ContentTypeKey = "cty" CriticalKey = "crit" JWKKey = "jwk" JWKSetURLKey = "jku" KeyIDKey = "kid" TypeKey = "typ" X509CertChainKey = "x5c" X509CertThumbprintKey = "x5t" X509CertThumbprintS256Key = "x5t#S256" X509URLKey = "x5u" ) // Headers describe a standard JWS Header set. It is part of the JWS message // and is used to represet both Public or Protected headers, which in turn // can be found in each Signature object. If you are not sure how this works, // it is strongly recommended that you read RFC7515, especially the section // that describes the full JSON serialization format of JWS messages. // // In most cases, you likely want to use the protected headers, as this is part of the signed content. type Headers interface { Algorithm() (jwa.SignatureAlgorithm, bool) ContentType() (string, bool) Critical() ([]string, bool) JWK() (jwk.Key, bool) JWKSetURL() (string, bool) KeyID() (string, bool) Type() (string, bool) X509CertChain() (*cert.Chain, bool) X509CertThumbprint() (string, bool) X509CertThumbprintS256() (string, bool) X509URL() (string, bool) Copy(Headers) error Merge(Headers) (Headers, error) Clone() (Headers, error) // Get is used to extract the value of any field, including non-standard fields, out of the header. // // The first argument is the name of the field. The second argument is a pointer // to a variable that will receive the value of the field. The method returns // an error if the field does not exist, or if the value cannot be assigned to // the destination variable. Note that a field is considered to "exist" even if // the value is empty-ish (e.g. 0, false, ""), as long as it is explicitly set. Get(string, any) error Set(string, any) error Remove(string) error // Has returns true if the specified header has a value, even if // the value is empty-ish (e.g. 0, false, "") as long as it has been // explicitly set. Has(string) bool Keys() []string } // stdHeaderNames is a list of all standard header names defined in the JWS specification. var stdHeaderNames = []string{AlgorithmKey, ContentTypeKey, CriticalKey, JWKKey, JWKSetURLKey, KeyIDKey, TypeKey, X509CertChainKey, X509CertThumbprintKey, X509CertThumbprintS256Key, X509URLKey} type stdHeaders struct { algorithm *jwa.SignatureAlgorithm // https://tools.ietf.org/html/rfc7515#section-4.1.1 contentType *string // https://tools.ietf.org/html/rfc7515#section-4.1.10 critical []string // https://tools.ietf.org/html/rfc7515#section-4.1.11 jwk jwk.Key // https://tools.ietf.org/html/rfc7515#section-4.1.3 jwkSetURL *string // https://tools.ietf.org/html/rfc7515#section-4.1.2 keyID *string // https://tools.ietf.org/html/rfc7515#section-4.1.4 typ *string // https://tools.ietf.org/html/rfc7515#section-4.1.9 x509CertChain *cert.Chain // https://tools.ietf.org/html/rfc7515#section-4.1.6 x509CertThumbprint *string // https://tools.ietf.org/html/rfc7515#section-4.1.7 x509CertThumbprintS256 *string // https://tools.ietf.org/html/rfc7515#section-4.1.8 x509URL *string // https://tools.ietf.org/html/rfc7515#section-4.1.5 privateParams map[string]any mu *sync.RWMutex dc DecodeCtx raw []byte // stores the raw version of the header so it can be used later } func NewHeaders() Headers { return &stdHeaders{ mu: &sync.RWMutex{}, } } func (h *stdHeaders) Algorithm() (jwa.SignatureAlgorithm, bool) { h.mu.RLock() defer h.mu.RUnlock() if h.algorithm == nil { return jwa.EmptySignatureAlgorithm(), false } return *(h.algorithm), true } func (h *stdHeaders) ContentType() (string, bool) { h.mu.RLock() defer h.mu.RUnlock() if h.contentType == nil { return "", false } return *(h.contentType), true } func (h *stdHeaders) Critical() ([]string, bool) { h.mu.RLock() defer h.mu.RUnlock() return h.critical, true } func (h *stdHeaders) JWK() (jwk.Key, bool) { h.mu.RLock() defer h.mu.RUnlock() return h.jwk, true } func (h *stdHeaders) JWKSetURL() (string, bool) { h.mu.RLock() defer h.mu.RUnlock() if h.jwkSetURL == nil { return "", false } return *(h.jwkSetURL), true } func (h *stdHeaders) KeyID() (string, bool) { h.mu.RLock() defer h.mu.RUnlock() if h.keyID == nil { return "", false } return *(h.keyID), true } func (h *stdHeaders) Type() (string, bool) { h.mu.RLock() defer h.mu.RUnlock() if h.typ == nil { return "", false } return *(h.typ), true } func (h *stdHeaders) X509CertChain() (*cert.Chain, bool) { h.mu.RLock() defer h.mu.RUnlock() return h.x509CertChain, true } func (h *stdHeaders) X509CertThumbprint() (string, bool) { h.mu.RLock() defer h.mu.RUnlock() if h.x509CertThumbprint == nil { return "", false } return *(h.x509CertThumbprint), true } func (h *stdHeaders) X509CertThumbprintS256() (string, bool) { h.mu.RLock() defer h.mu.RUnlock() if h.x509CertThumbprintS256 == nil { return "", false } return *(h.x509CertThumbprintS256), true } func (h *stdHeaders) X509URL() (string, bool) { h.mu.RLock() defer h.mu.RUnlock() if h.x509URL == nil { return "", false } return *(h.x509URL), true } func (h *stdHeaders) clear() { h.algorithm = nil h.contentType = nil h.critical = nil h.jwk = nil h.jwkSetURL = nil h.keyID = nil h.typ = nil h.x509CertChain = nil h.x509CertThumbprint = nil h.x509CertThumbprintS256 = nil h.x509URL = nil h.privateParams = nil h.raw = nil } func (h *stdHeaders) DecodeCtx() DecodeCtx { h.mu.RLock() defer h.mu.RUnlock() return h.dc } func (h *stdHeaders) SetDecodeCtx(dc DecodeCtx) { h.mu.Lock() defer h.mu.Unlock() h.dc = dc } func (h *stdHeaders) rawBuffer() []byte { return h.raw } func (h *stdHeaders) PrivateParams() map[string]any { h.mu.RLock() defer h.mu.RUnlock() return h.privateParams } func (h *stdHeaders) Has(name string) bool { h.mu.RLock() defer h.mu.RUnlock() switch name { case AlgorithmKey: return h.algorithm != nil case ContentTypeKey: return h.contentType != nil case CriticalKey: return h.critical != nil case JWKKey: return h.jwk != nil case JWKSetURLKey: return h.jwkSetURL != nil case KeyIDKey: return h.keyID != nil case TypeKey: return h.typ != nil case X509CertChainKey: return h.x509CertChain != nil case X509CertThumbprintKey: return h.x509CertThumbprint != nil case X509CertThumbprintS256Key: return h.x509CertThumbprintS256 != nil case X509URLKey: return h.x509URL != nil default: _, ok := h.privateParams[name] return ok } } func (h *stdHeaders) Get(name string, dst any) error { h.mu.RLock() defer h.mu.RUnlock() switch name { case AlgorithmKey: if h.algorithm == nil { return fmt.Errorf(`field %q not found`, name) } if err := blackmagic.AssignIfCompatible(dst, *(h.algorithm)); err != nil { return fmt.Errorf(`failed to assign value for field %q: %w`, name, err) } return nil case ContentTypeKey: if h.contentType == nil { return fmt.Errorf(`field %q not found`, name) } if err := blackmagic.AssignIfCompatible(dst, *(h.contentType)); err != nil { return fmt.Errorf(`failed to assign value for field %q: %w`, name, err) } return nil case CriticalKey: if h.critical == nil { return fmt.Errorf(`field %q not found`, name) } if err := blackmagic.AssignIfCompatible(dst, h.critical); err != nil { return fmt.Errorf(`failed to assign value for field %q: %w`, name, err) } return nil case JWKKey: if h.jwk == nil { return fmt.Errorf(`field %q not found`, name) } if err := blackmagic.AssignIfCompatible(dst, h.jwk); err != nil { return fmt.Errorf(`failed to assign value for field %q: %w`, name, err) } return nil case JWKSetURLKey: if h.jwkSetURL == nil { return fmt.Errorf(`field %q not found`, name) } if err := blackmagic.AssignIfCompatible(dst, *(h.jwkSetURL)); err != nil { return fmt.Errorf(`failed to assign value for field %q: %w`, name, err) } return nil case KeyIDKey: if h.keyID == nil { return fmt.Errorf(`field %q not found`, name) } if err := blackmagic.AssignIfCompatible(dst, *(h.keyID)); err != nil { return fmt.Errorf(`failed to assign value for field %q: %w`, name, err) } return nil case TypeKey: if h.typ == nil { return fmt.Errorf(`field %q not found`, name) } if err := blackmagic.AssignIfCompatible(dst, *(h.typ)); err != nil { return fmt.Errorf(`failed to assign value for field %q: %w`, name, err) } return nil case X509CertChainKey: if h.x509CertChain == nil { return fmt.Errorf(`field %q not found`, name) } if err := blackmagic.AssignIfCompatible(dst, h.x509CertChain); err != nil { return fmt.Errorf(`failed to assign value for field %q: %w`, name, err) } return nil case X509CertThumbprintKey: if h.x509CertThumbprint == nil { return fmt.Errorf(`field %q not found`, name) } if err := blackmagic.AssignIfCompatible(dst, *(h.x509CertThumbprint)); err != nil { return fmt.Errorf(`failed to assign value for field %q: %w`, name, err) } return nil case X509CertThumbprintS256Key: if h.x509CertThumbprintS256 == nil { return fmt.Errorf(`field %q not found`, name) } if err := blackmagic.AssignIfCompatible(dst, *(h.x509CertThumbprintS256)); err != nil { return fmt.Errorf(`failed to assign value for field %q: %w`, name, err) } return nil case X509URLKey: if h.x509URL == nil { return fmt.Errorf(`field %q not found`, name) } if err := blackmagic.AssignIfCompatible(dst, *(h.x509URL)); err != nil { return fmt.Errorf(`failed to assign value for field %q: %w`, name, err) } return nil default: v, ok := h.privateParams[name] if !ok { return fmt.Errorf(`field %q not found`, name) } if err := blackmagic.AssignIfCompatible(dst, v); err != nil { return fmt.Errorf(`failed to assign value for field %q: %w`, name, err) } } return nil } func (h *stdHeaders) Set(name string, value any) error { h.mu.Lock() defer h.mu.Unlock() return h.setNoLock(name, value) } func (h *stdHeaders) setNoLock(name string, value any) error { switch name { case AlgorithmKey: alg, err := jwa.KeyAlgorithmFrom(value) if err != nil { return fmt.Errorf("invalid value for %s key: %w", AlgorithmKey, err) } if salg, ok := alg.(jwa.SignatureAlgorithm); ok { h.algorithm = &salg return nil } return fmt.Errorf("expecte jwa.SignatureAlgorithm, received %T", alg) case ContentTypeKey: if v, ok := value.(string); ok { h.contentType = &v return nil } return fmt.Errorf(`invalid value for %s key: %T`, ContentTypeKey, value) case CriticalKey: if v, ok := value.([]string); ok { h.critical = v return nil } return fmt.Errorf(`invalid value for %s key: %T`, CriticalKey, value) case JWKKey: if v, ok := value.(jwk.Key); ok { h.jwk = v return nil } return fmt.Errorf(`invalid value for %s key: %T`, JWKKey, value) case JWKSetURLKey: if v, ok := value.(string); ok { h.jwkSetURL = &v return nil } return fmt.Errorf(`invalid value for %s key: %T`, JWKSetURLKey, value) case KeyIDKey: if v, ok := value.(string); ok { h.keyID = &v return nil } return fmt.Errorf(`invalid value for %s key: %T`, KeyIDKey, value) case TypeKey: if v, ok := value.(string); ok { h.typ = &v return nil } return fmt.Errorf(`invalid value for %s key: %T`, TypeKey, value) case X509CertChainKey: if v, ok := value.(*cert.Chain); ok { h.x509CertChain = v return nil } return fmt.Errorf(`invalid value for %s key: %T`, X509CertChainKey, value) case X509CertThumbprintKey: if v, ok := value.(string); ok { h.x509CertThumbprint = &v return nil } return fmt.Errorf(`invalid value for %s key: %T`, X509CertThumbprintKey, value) case X509CertThumbprintS256Key: if v, ok := value.(string); ok { h.x509CertThumbprintS256 = &v return nil } return fmt.Errorf(`invalid value for %s key: %T`, X509CertThumbprintS256Key, value) case X509URLKey: if v, ok := value.(string); ok { h.x509URL = &v return nil } return fmt.Errorf(`invalid value for %s key: %T`, X509URLKey, value) default: if h.privateParams == nil { h.privateParams = map[string]any{} } h.privateParams[name] = value } return nil } func (h *stdHeaders) Remove(key string) error { h.mu.Lock() defer h.mu.Unlock() switch key { case AlgorithmKey: h.algorithm = nil case ContentTypeKey: h.contentType = nil case CriticalKey: h.critical = nil case JWKKey: h.jwk = nil case JWKSetURLKey: h.jwkSetURL = nil case KeyIDKey: h.keyID = nil case TypeKey: h.typ = nil case X509CertChainKey: h.x509CertChain = nil case X509CertThumbprintKey: h.x509CertThumbprint = nil case X509CertThumbprintS256Key: h.x509CertThumbprintS256 = nil case X509URLKey: h.x509URL = nil default: delete(h.privateParams, key) } return nil } func (h *stdHeaders) UnmarshalJSON(buf []byte) error { h.mu.Lock() defer h.mu.Unlock() h.clear() dec := json.NewDecoder(bytes.NewReader(buf)) LOOP: for { tok, err := dec.Token() if err != nil { return fmt.Errorf(`error reading token: %w`, err) } switch tok := tok.(type) { case json.Delim: // Assuming we're doing everything correctly, we should ONLY // get either tokens.OpenCurlyBracket or tokens.CloseCurlyBracket here. if tok == tokens.CloseCurlyBracket { // End of object break LOOP } else if tok != tokens.OpenCurlyBracket { return fmt.Errorf(`expected '%c' but got '%c'`, tokens.OpenCurlyBracket, tok) } case string: // Objects can only have string keys switch tok { case AlgorithmKey: var decoded jwa.SignatureAlgorithm if err := dec.Decode(&decoded); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, AlgorithmKey, err) } h.algorithm = &decoded case ContentTypeKey: if err := json.AssignNextStringToken(&h.contentType, dec); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, ContentTypeKey, err) } case CriticalKey: var decoded []string if err := dec.Decode(&decoded); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, CriticalKey, err) } h.critical = decoded case JWKKey: var buf json.RawMessage if err := dec.Decode(&buf); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, JWKKey, err) } key, err := jwk.ParseKey(buf) if err != nil { return fmt.Errorf(`failed to parse JWK for key %s: %w`, JWKKey, err) } h.jwk = key case JWKSetURLKey: if err := json.AssignNextStringToken(&h.jwkSetURL, dec); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, JWKSetURLKey, err) } case KeyIDKey: if err := json.AssignNextStringToken(&h.keyID, dec); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, KeyIDKey, err) } case TypeKey: if err := json.AssignNextStringToken(&h.typ, dec); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, TypeKey, err) } case X509CertChainKey: var decoded cert.Chain if err := dec.Decode(&decoded); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, X509CertChainKey, err) } h.x509CertChain = &decoded case X509CertThumbprintKey: if err := json.AssignNextStringToken(&h.x509CertThumbprint, dec); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, X509CertThumbprintKey, err) } case X509CertThumbprintS256Key: if err := json.AssignNextStringToken(&h.x509CertThumbprintS256, dec); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, X509CertThumbprintS256Key, err) } case X509URLKey: if err := json.AssignNextStringToken(&h.x509URL, dec); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, X509URLKey, err) } default: decoded, err := registry.Decode(dec, tok) if err != nil { return err } h.setNoLock(tok, decoded) } default: return fmt.Errorf(`invalid token %T`, tok) } } h.raw = buf return nil } func (h *stdHeaders) Keys() []string { h.mu.RLock() defer h.mu.RUnlock() keys := make([]string, 0, 11+len(h.privateParams)) if h.algorithm != nil { keys = append(keys, AlgorithmKey) } if h.contentType != nil { keys = append(keys, ContentTypeKey) } if h.critical != nil { keys = append(keys, CriticalKey) } if h.jwk != nil { keys = append(keys, JWKKey) } if h.jwkSetURL != nil { keys = append(keys, JWKSetURLKey) } if h.keyID != nil { keys = append(keys, KeyIDKey) } if h.typ != nil { keys = append(keys, TypeKey) } if h.x509CertChain != nil { keys = append(keys, X509CertChainKey) } if h.x509CertThumbprint != nil { keys = append(keys, X509CertThumbprintKey) } if h.x509CertThumbprintS256 != nil { keys = append(keys, X509CertThumbprintS256Key) } if h.x509URL != nil { keys = append(keys, X509URLKey) } for k := range h.privateParams { keys = append(keys, k) } return keys } func (h stdHeaders) MarshalJSON() ([]byte, error) { h.mu.RLock() data := make(map[string]any) keys := make([]string, 0, 11+len(h.privateParams)) if h.algorithm != nil { data[AlgorithmKey] = *(h.algorithm) keys = append(keys, AlgorithmKey) } if h.contentType != nil { data[ContentTypeKey] = *(h.contentType) keys = append(keys, ContentTypeKey) } if h.critical != nil { data[CriticalKey] = h.critical keys = append(keys, CriticalKey) } if h.jwk != nil { data[JWKKey] = h.jwk keys = append(keys, JWKKey) } if h.jwkSetURL != nil { data[JWKSetURLKey] = *(h.jwkSetURL) keys = append(keys, JWKSetURLKey) } if h.keyID != nil { data[KeyIDKey] = *(h.keyID) keys = append(keys, KeyIDKey) } if h.typ != nil { data[TypeKey] = *(h.typ) keys = append(keys, TypeKey) } if h.x509CertChain != nil { data[X509CertChainKey] = h.x509CertChain keys = append(keys, X509CertChainKey) } if h.x509CertThumbprint != nil { data[X509CertThumbprintKey] = *(h.x509CertThumbprint) keys = append(keys, X509CertThumbprintKey) } if h.x509CertThumbprintS256 != nil { data[X509CertThumbprintS256Key] = *(h.x509CertThumbprintS256) keys = append(keys, X509CertThumbprintS256Key) } if h.x509URL != nil { data[X509URLKey] = *(h.x509URL) keys = append(keys, X509URLKey) } for k, v := range h.privateParams { data[k] = v keys = append(keys, k) } h.mu.RUnlock() sort.Strings(keys) buf := pool.BytesBuffer().Get() defer pool.BytesBuffer().Put(buf) enc := json.NewEncoder(buf) buf.WriteByte(tokens.OpenCurlyBracket) for i, k := range keys { if i > 0 { buf.WriteRune(tokens.Comma) } buf.WriteRune(tokens.DoubleQuote) buf.WriteString(k) buf.WriteString(`":`) switch v := data[k].(type) { case []byte: buf.WriteRune(tokens.DoubleQuote) buf.WriteString(base64.EncodeToString(v)) buf.WriteRune(tokens.DoubleQuote) default: if err := enc.Encode(v); err != nil { return nil, fmt.Errorf(`failed to encode value for field %s: %w`, k, err) } buf.Truncate(buf.Len() - 1) } } buf.WriteByte(tokens.CloseCurlyBracket) ret := make([]byte, buf.Len()) copy(ret, buf.Bytes()) return ret, nil } golang-github-lestrrat-go-jwx-3.0.13/jws/headers_test.go000066400000000000000000000211451515060566400232130ustar00rootroot00000000000000package jws_test import ( "reflect" "testing" "github.com/lestrrat-go/jwx/v3/cert" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/lestrrat-go/jwx/v3/jwk" "github.com/lestrrat-go/jwx/v3/jws" "github.com/stretchr/testify/require" ) var zeroval reflect.Value func TestHeader(t *testing.T) { publicKey := `{"kty":"RSA", "n": "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw", "e":"AQAB", "alg":"RS256", "kid":"2011-04-29"}` jwkPublicKey, err := jwk.ParseKey([]byte(publicKey)) require.NoError(t, err, `jwk.ParseKey should succeed`) var chain cert.Chain _ = chain.AddString("MIIE3jCCA8agAwIBAgICAwEwDQYJKoZIhvcNAQEFBQAwYzELMAkGA1UEBhMCVVMxITAfBgNVBAoTGFRoZSBHbyBEYWRkeSBHcm91cCwgSW5jLjExMC8GA1UECxMoR28gRGFkZHkgQ2xhc3MgMiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjExMTYwMTU0MzdaFw0yNjExMTYwMTU0MzdaMIHKMQswCQYDVQQGEwJVUzEQMA4GA1UECBMHQXJpem9uYTETMBEGA1UEBxMKU2NvdHRzZGFsZTEaMBgGA1UEChMRR29EYWRkeS5jb20sIEluYy4xMzAxBgNVBAsTKmh0dHA6Ly9jZXJ0aWZpY2F0ZXMuZ29kYWRkeS5jb20vcmVwb3NpdG9yeTEwMC4GA1UEAxMnR28gRGFkZHkgU2VjdXJlIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MREwDwYDVQQFEwgwNzk2OTI4NzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMQt1RWMnCZM7DI161+4WQFapmGBWTtwY6vj3D3HKrjJM9N55DrtPDAjhI6zMBS2sofDPZVUBJ7fmd0LJR4h3mUpfjWoqVTr9vcyOdQmVZWt7/v+WIbXnvQAjYwqDL1CBM6nPwT27oDyqu9SoWlm2r4arV3aLGbqGmu75RpRSgAvSMeYddi5Kcju+GZtCpyz8/x4fKL4o/K1w/O5epHBp+YlLpyo7RJlbmr2EkRTcDCVw5wrWCs9CHRK8r5RsL+H0EwnWGu1NcWdrxcx+AuP7q2BNgWJCJjPOq8lh8BJ6qf9Z/dFjpfMFDniNoW1fho3/Rb2cRGadDAW/hOUoz+EDU8CAwEAAaOCATIwggEuMB0GA1UdDgQWBBT9rGEyk2xF1uLuhV+auud2mWjM5zAfBgNVHSMEGDAWgBTSxLDSkdRMEXGzYcs9of7dqGrU4zASBgNVHRMBAf8ECDAGAQH/AgEAMDMGCCsGAQUFBwEBBCcwJTAjBggrBgEFBQcwAYYXaHR0cDovL29jc3AuZ29kYWRkeS5jb20wRgYDVR0fBD8wPTA7oDmgN4Y1aHR0cDovL2NlcnRpZmljYXRlcy5nb2RhZGR5LmNvbS9yZXBvc2l0b3J5L2dkcm9vdC5jcmwwSwYDVR0gBEQwQjBABgRVHSAAMDgwNgYIKwYBBQUHAgEWKmh0dHA6Ly9jZXJ0aWZpY2F0ZXMuZ29kYWRkeS5jb20vcmVwb3NpdG9yeTAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQEFBQADggEBANKGwOy9+aG2Z+5mC6IGOgRQjhVyrEp0lVPLN8tESe8HkGsz2ZbwlFalEzAFPIUyIXvJxwqoJKSQ3kbTJSMUA2fCENZvD117esyfxVgqwcSeIaha86ykRvOe5GPLL5CkKSkB2XIsKd83ASe8T+5o0yGPwLPk9Qnt0hCqU7S+8MxZC9Y7lhyVJEnfzuz9p0iRFEUOOjZv2kWzRaJBydTXRE4+uXR21aITVSzGh6O1mawGhId/dQb8vxRMDsxuxN89txJx9OjxUUAiKEngHUuHqDTMBqLdElrRhjZkAzVvb3du6/KFUJheqwNTrZEjYx8WnM25sgVjOuH0aBsXBTWVU+4=") _ = chain.AddString("MIIE+zCCBGSgAwIBAgICAQ0wDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENsYXNzIDIgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTA0MDYyOTE3MDYyMFoXDTI0MDYyOTE3MDYyMFowYzELMAkGA1UEBhMCVVMxITAfBgNVBAoTGFRoZSBHbyBEYWRkeSBHcm91cCwgSW5jLjExMC8GA1UECxMoR28gRGFkZHkgQ2xhc3MgMiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASAwDQYJKoZIhvcNAQEBBQADggENADCCAQgCggEBAN6d1+pXGEmhW+vXX0iG6r7d/+TvZxz0ZWizV3GgXne77ZtJ6XCAPVYYYwhv2vLM0D9/AlQiVBDYsoHUwHU9S3/Hd8M+eKsaA7Ugay9qK7HFiH7Eux6wwdhFJ2+qN1j3hybX2C32qRe3H3I2TqYXP2WYktsqbl2i/ojgC95/5Y0V4evLOtXiEqITLdiOr18SPaAIBQi2XKVlOARFmR6jYGB0xUGlcmIbYsUfb18aQr4CUWWoriMYavx4A6lNf4DD+qta/KFApMoZFv6yyO9ecw3ud72a9nmYvLEHZ6IVDd2gWMZEewo+YihfukEHU1jPEX44dMX4/7VpkI+EdOqXG68CAQOjggHhMIIB3TAdBgNVHQ4EFgQU0sSw0pHUTBFxs2HLPaH+3ahq1OMwgdIGA1UdIwSByjCBx6GBwaSBvjCBuzEkMCIGA1UEBxMbVmFsaUNlcnQgVmFsaWRhdGlvbiBOZXR3b3JrMRcwFQYDVQQKEw5WYWxpQ2VydCwgSW5jLjE1MDMGA1UECxMsVmFsaUNlcnQgQ2xhc3MgMiBQb2xpY3kgVmFsaWRhdGlvbiBBdXRob3JpdHkxITAfBgNVBAMTGGh0dHA6Ly93d3cudmFsaWNlcnQuY29tLzEgMB4GCSqGSIb3DQEJARYRaW5mb0B2YWxpY2VydC5jb22CAQEwDwYDVR0TAQH/BAUwAwEB/zAzBggrBgEFBQcBAQQnMCUwIwYIKwYBBQUHMAGGF2h0dHA6Ly9vY3NwLmdvZGFkZHkuY29tMEQGA1UdHwQ9MDswOaA3oDWGM2h0dHA6Ly9jZXJ0aWZpY2F0ZXMuZ29kYWRkeS5jb20vcmVwb3NpdG9yeS9yb290LmNybDBLBgNVHSAERDBCMEAGBFUdIAAwODA2BggrBgEFBQcCARYqaHR0cDovL2NlcnRpZmljYXRlcy5nb2RhZGR5LmNvbS9yZXBvc2l0b3J5MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOBgQC1QPmnHfbq/qQaQlpE9xXUhUaJwL6e4+PrxeNYiY+Sn1eocSxI0YGyeR+sBjUZsE4OWBsUs5iB0QQeyAfJg594RAoYC5jcdnplDQ1tgMQLARzLrUc+cb53S8wGd9D0VmsfSxOaFIqII6hR8INMqzW/Rn453HWkrugp++85j09VZw==") _ = chain.AddString("MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENsYXNzIDIgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNjAwMTk1NFoXDTE5MDYyNjAwMTk1NFowgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENsYXNzIDIgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDOOnHK5avIWZJV16vYdA757tn2VUdZZUcOBVXc65g2PFxTXdMwzzjsvUGJ7SVCCSRrCl6zfN1SLUzm1NZ9WlmpZdRJEy0kTRxQb7XBhVQ7/nHk01xC+YDgkRoKWzk2Z/M/VXwbP7RfZHM047QSv4dk+NoS/zcnwbNDu+97bi5p9wIDAQABMA0GCSqGSIb3DQEBBQUAA4GBADt/UG9vUJSZSWI4OB9L+KXIPqeCgfYrx+jFzug6EILLGACOTb2oWH+heQC1u+mNr0HZDzTuIYEZoDJJKPTEjlbVUjP9UNV+mWwD5MlM/Mtsq2azSiGM5bUMMj4QssxsodyamEwCW/POuZ6lcg5Ktz885hZo+L7tdEy8W9ViH0Pd") data := []struct { Key string Value any Expected any Method string }{ { Key: jws.AlgorithmKey, Value: jwa.ES256(), Method: "Algorithm", }, { Key: jws.ContentTypeKey, Value: "example", Method: "ContentType", }, { Key: jws.CriticalKey, Value: []string{"exp"}, Method: "Critical", }, { Key: jws.JWKKey, Value: jwkPublicKey, Method: "JWK", }, { Key: jws.JWKSetURLKey, Value: "https://www.jwk.com/key.json", Method: "JWKSetURL", }, { Key: jws.TypeKey, Value: "JWT", Method: "Type", }, { Key: jws.KeyIDKey, Value: "e9bc097a-ce51-4036-9562-d2ade882db0d", Method: "KeyID", }, { Key: jws.X509CertChainKey, Value: &chain, Method: "X509CertChain", }, { Key: jws.X509CertThumbprintKey, Value: "QzY0NjREMjkyQTI4RTU2RkE4MUJBRDExNzY1MUY1N0I4QjFCODlBOQ", Method: "X509CertThumbprint", }, { Key: jws.X509URLKey, Value: "https://www.x509.com/key.pem", Method: "X509URL", }, {Key: "private", Value: "boofoo"}, } base := jws.NewHeaders() t.Run("Set values", func(t *testing.T) { // DO NOT RUN THIS IN PARALLEL. THIS IS AN INITIALIZER for _, tc := range data { require.NoError(t, base.Set(tc.Key, tc.Value), "Headers.Set should succeed") } }) t.Run("Set/Get", func(t *testing.T) { h := jws.NewHeaders() for _, k := range base.Keys() { var v any require.NoError(t, base.Get(k, &v), `base.Get should succeed for key %#v`, k) require.NoError(t, h.Set(k, v), `h.Set should succeed for key %#v`, k) } for _, tc := range data { var values []any var viaGet any require.NoError(t, h.Get(tc.Key, &viaGet), `h.Get should succeed`) values = append(values, viaGet) if method := tc.Method; method != "" { m := reflect.ValueOf(h).MethodByName(method) require.NotEqual(t, m, zeroval, "method %s should be available", method) ret := m.Call(nil) require.Len(t, ret, 2, `should get exactly 2 value as return value`) require.True(t, ret[1].Bool(), `method %s should return true`, method) values = append(values, ret[0].Interface()) } expected := tc.Expected if expected == nil { expected = tc.Value } for i, got := range values { require.Equal(t, expected, got, "value %d should match", i) } } }) t.Run("PrivateParams", func(t *testing.T) { h := base var v any require.NoError(t, h.Get(`private`, &v), `h.Get should succeed`) require.Equal(t, v, "boofoo", "value for 'private' should match") }) t.Run("Iterator", func(t *testing.T) { expected := map[string]any{} for _, tc := range data { v := tc.Value if expected := tc.Expected; expected != nil { v = expected } expected[tc.Key] = v } h := base t.Run("Iterate", func(t *testing.T) { seen := make(map[string]any) for _, k := range h.Keys() { var v any var getV any require.NoError(t, h.Get(k, &v), `h.Get should succeed`) require.NoError(t, h.Get(k, &getV), `v.Get should succeed`) require.Equal(t, v, getV, `pair.Value should match value from v.Get()`) seen[k] = getV } require.Equal(t, expected, seen, `values should match`) }) t.Run("Remove", func(t *testing.T) { h := base for _, k := range h.Keys() { require.NoError(t, h.Remove(k), `h.Remove should succeed`) } require.Len(t, h.Keys(), 0, `h.Keys should return zero`) }) }) } golang-github-lestrrat-go-jwx-3.0.13/jws/interface.go000066400000000000000000000056411515060566400225040ustar00rootroot00000000000000package jws import ( "github.com/lestrrat-go/jwx/v3/internal/base64" "github.com/lestrrat-go/jwx/v3/jws/legacy" ) type Signer = legacy.Signer type Verifier = legacy.Verifier type HMACSigner = legacy.HMACSigner type HMACVerifier = legacy.HMACVerifier // Base64Encoder is an interface that can be used when encoding JWS message // components to base64. This is useful when you want to use a non-standard // base64 encoder while generating or verifying signatures. By default JWS // uses raw url base64 encoding (without padding), but there are apparently // some cases where you may want to use a base64 encoders that uses padding. // // For example, apparently AWS ALB User Claims is provided in JWT format, // but it uses a base64 encoding with padding. type Base64Encoder = base64.Encoder type DecodeCtx interface { CollectRaw() bool } // Message represents a full JWS encoded message. Flattened serialization // is not supported as a struct, but rather it's represented as a // Message struct with only one `signature` element. // // Do not expect to use the Message object to verify or construct a // signed payload with. You should only use this when you want to actually // programmatically view the contents of the full JWS payload. // // As of this version, there is one big incompatibility when using Message // objects to convert between compact and JSON representations. // The protected header is sometimes encoded differently from the original // message and the JSON serialization that we use in Go. // // For example, the protected header `eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9` // decodes to // // {"typ":"JWT", // "alg":"HS256"} // // However, when we parse this into a message, we create a jws.Header object, // which, when we marshal into a JSON object again, becomes // // {"typ":"JWT","alg":"HS256"} // // Notice that serialization lacks a line break and a space between `"JWT",` // and `"alg"`. This causes a problem when verifying the signatures AFTER // a compact JWS message has been unmarshaled into a jws.Message. // // jws.Verify() doesn't go through this step, and therefore this does not // manifest itself. However, you may see this discrepancy when you manually // go through these conversions, and/or use the `jwx` tool like so: // // jwx jws parse message.jws | jwx jws verify --key somekey.jwk --stdin // // In this scenario, the first `jwx jws parse` outputs a parsed jws.Message // which is marshaled into JSON. At this point the message's protected // headers and the signatures don't match. // // To sign and verify, use the appropriate `Sign()` and `Verify()` functions. type Message struct { dc DecodeCtx payload []byte signatures []*Signature b64 bool // true if payload should be base64 encoded } type Signature struct { encoder Base64Encoder dc DecodeCtx headers Headers // Unprotected Headers protected Headers // Protected Headers signature []byte // Signature detached bool } golang-github-lestrrat-go-jwx-3.0.13/jws/internal/000077500000000000000000000000001515060566400220235ustar00rootroot00000000000000golang-github-lestrrat-go-jwx-3.0.13/jws/internal/keytype/000077500000000000000000000000001515060566400235155ustar00rootroot00000000000000golang-github-lestrrat-go-jwx-3.0.13/jws/internal/keytype/BUILD.bazel000066400000000000000000000004041515060566400253710ustar00rootroot00000000000000load("@rules_go//go:def.bzl", "go_library") go_library( name = "keytype", srcs = ["keytype.go"], importpath = "github.com/lestrrat-go/jwx/v3/jws/internal/keytype", visibility = ["//jws:__subpackages__"], deps = [ "//jwk", ], ) golang-github-lestrrat-go-jwx-3.0.13/jws/internal/keytype/keytype.go000066400000000000000000000027051515060566400255420ustar00rootroot00000000000000package keytype import ( "crypto/ecdsa" "crypto/ed25519" "crypto/rsa" "github.com/lestrrat-go/jwx/v3/jwk" ) // Because the keys defined in github.com/lestrrat-go/jwx/jwk may also implement // crypto.Signer, it would be possible for to mix up key types when signing/verifying // for example, when we specify jws.WithKey(jwa.RSA256, cryptoSigner), the cryptoSigner // can be for RSA, or any other type that implements crypto.Signer... even if it's for the // wrong algorithm. // // These functions are there to differentiate between the valid KNOWN key types. // For any other key type that is outside of the Go std library and our own code, // we must rely on the user to be vigilant. // // Notes: symmetric keys are obviously not part of this. for v2 OKP keys, // x25519 does not implement Sign() func IsValidRSAKey(key any) bool { switch key.(type) { case ecdsa.PrivateKey, *ecdsa.PrivateKey, ed25519.PrivateKey, jwk.ECDSAPrivateKey, jwk.OKPPrivateKey: // these are NOT ok return false } return true } func IsValidECDSAKey(key any) bool { switch key.(type) { case ed25519.PrivateKey, rsa.PrivateKey, *rsa.PrivateKey, jwk.RSAPrivateKey, jwk.OKPPrivateKey: // these are NOT ok return false } return true } func IsValidEDDSAKey(key any) bool { switch key.(type) { case ecdsa.PrivateKey, *ecdsa.PrivateKey, rsa.PrivateKey, *rsa.PrivateKey, jwk.RSAPrivateKey, jwk.ECDSAPrivateKey: // these are NOT ok return false } return true } golang-github-lestrrat-go-jwx-3.0.13/jws/io.go000066400000000000000000000011521515060566400211440ustar00rootroot00000000000000// Code generated by tools/cmd/genreadfile/main.go. DO NOT EDIT. package jws import ( "fmt" "io/fs" "os" ) type sysFS struct{} func (sysFS) Open(path string) (fs.File, error) { return os.Open(path) } func ReadFile(path string, options ...ReadFileOption) (*Message, error) { var srcFS fs.FS = sysFS{} for _, option := range options { switch option.Ident() { case identFS{}: if err := option.Value(&srcFS); err != nil { return nil, fmt.Errorf("failed to set fs.FS: %w", err) } } } f, err := srcFS.Open(path) if err != nil { return nil, err } defer f.Close() return ParseReader(f) } golang-github-lestrrat-go-jwx-3.0.13/jws/jws.go000066400000000000000000000536511515060566400213530ustar00rootroot00000000000000//go:generate ../tools/cmd/genjws.sh // Package jws implements the digital signature on JSON based data // structures as described in https://tools.ietf.org/html/rfc7515 // // If you do not care about the details, the only things that you // would need to use are the following functions: // // jws.Sign(payload, jws.WithKey(algorithm, key)) // jws.Verify(serialized, jws.WithKey(algorithm, key)) // // To sign, simply use `jws.Sign`. `payload` is a []byte buffer that // contains whatever data you want to sign. `alg` is one of the // jwa.SignatureAlgorithm constants from package jwa. For RSA and // ECDSA family of algorithms, you will need to prepare a private key. // For HMAC family, you just need a []byte value. The `jws.Sign` // function will return the encoded JWS message on success. // // To verify, use `jws.Verify`. It will parse the `encodedjws` buffer // and verify the result using `algorithm` and `key`. Upon successful // verification, the original payload is returned, so you can work on it. // // As a sidenote, consider using github.com/lestrrat-go/htmsig if you // looking for HTTP Message Signatures (RFC9421) -- it uses the same // underlying signing/verification mechanisms as this module. package jws import ( "bufio" "crypto/ecdh" "crypto/ecdsa" "crypto/ed25519" "crypto/rsa" "errors" "fmt" "io" "reflect" "sync" "unicode" "unicode/utf8" "github.com/lestrrat-go/jwx/v3/internal/base64" "github.com/lestrrat-go/jwx/v3/internal/json" "github.com/lestrrat-go/jwx/v3/internal/jwxio" "github.com/lestrrat-go/jwx/v3/internal/pool" "github.com/lestrrat-go/jwx/v3/internal/tokens" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/lestrrat-go/jwx/v3/jwk" "github.com/lestrrat-go/jwx/v3/jws/jwsbb" ) var registry = json.NewRegistry() var signers = make(map[jwa.SignatureAlgorithm]Signer) var muSigner = &sync.Mutex{} func removeSigner(alg jwa.SignatureAlgorithm) { muSigner.Lock() defer muSigner.Unlock() delete(signers, alg) } type defaultSigner struct { alg jwa.SignatureAlgorithm } func (s defaultSigner) Algorithm() jwa.SignatureAlgorithm { return s.alg } func (s defaultSigner) Sign(key any, payload []byte) ([]byte, error) { return jwsbb.Sign(key, s.alg.String(), payload, nil) } type signerAdapter struct { signer Signer } func (s signerAdapter) Algorithm() jwa.SignatureAlgorithm { return s.signer.Algorithm() } func (s signerAdapter) Sign(key any, payload []byte) ([]byte, error) { return s.signer.Sign(payload, key) } const ( fmtInvalid = 1 << iota fmtCompact fmtJSON fmtJSONPretty fmtMax ) // silence linters var _ = fmtInvalid var _ = fmtMax func validateKeyBeforeUse(key any) error { jwkKey, ok := key.(jwk.Key) if !ok { converted, err := jwk.Import(key) if err != nil { return fmt.Errorf(`could not convert key of type %T to jwk.Key for validation: %w`, key, err) } jwkKey = converted } return jwkKey.Validate() } // Sign generates a JWS message for the given payload and returns // it in serialized form, which can be in either compact or // JSON format. Default is compact. // // You must pass at least one key to `jws.Sign()` by using `jws.WithKey()` // option. // // jws.Sign(payload, jws.WithKey(alg, key)) // jws.Sign(payload, jws.WithJSON(), jws.WithKey(alg1, key1), jws.WithKey(alg2, key2)) // // Note that in the second example the `jws.WithJSON()` option is // specified as well. This is because the compact serialization // format does not support multiple signatures, and users must // specifically ask for the JSON serialization format. // // Read the documentation for `jws.WithKey()` to learn more about the // possible values that can be used for `alg` and `key`. // // You may create JWS messages with the "none" (jwa.NoSignature) algorithm // if you use the `jws.WithInsecureNoSignature()` option. This option // can be combined with one or more signature keys, as well as the // `jws.WithJSON()` option to generate multiple signatures (though // the usefulness of such constructs is highly debatable) // // Note that this library does not allow you to successfully call `jws.Verify()` on // signatures with the "none" algorithm. To parse these, use `jws.Parse()` instead. // // If you want to use a detached payload, use `jws.WithDetachedPayload()` as // one of the options. When you use this option, you must always set the // first parameter (`payload`) to `nil`, or the function will return an error // // You may also want to look at how to pass protected headers to the // signing process, as you will likely be required to set the `b64` field // when using detached payload. // // Look for options that return `jws.SignOption` or `jws.SignVerifyOption` // for a complete list of options that can be passed to this function. // // You can use `errors.Is` with `jws.SignError()` to check if an error is from this function. func Sign(payload []byte, options ...SignOption) ([]byte, error) { sc := signContextPool.Get() defer signContextPool.Put(sc) sc.payload = payload if err := sc.ProcessOptions(options); err != nil { return nil, signerr(`failed to process options: %w`, err) } lsigner := len(sc.sigbuilders) if lsigner == 0 { return nil, signerr(`no signers available. Specify an algorithm and a key using jws.WithKey()`) } // Design note: while we could have easily set format = fmtJSON when // lsigner > 1, I believe the decision to change serialization formats // must be explicitly stated by the caller. Otherwise, I'm pretty sure // there would be people filing issues saying "I get JSON when I expected // compact serialization". // // Therefore, instead of making implicit format conversions, we force the // user to spell it out as `jws.Sign(..., jws.WithJSON(), jws.WithKey(...), jws.WithKey(...))` if sc.format == fmtCompact && lsigner != 1 { return nil, signerr(`cannot have multiple signers (keys) specified for compact serialization. Use only one jws.WithKey()`) } // Create a Message object with all the bits and bobs, and we'll // serialize it in the end var result Message if err := sc.PopulateMessage(&result); err != nil { return nil, signerr(`failed to populate message: %w`, err) } switch sc.format { case fmtJSON: return json.Marshal(result) case fmtJSONPretty: return json.MarshalIndent(result, "", " ") case fmtCompact: // Take the only signature object, and convert it into a Compact // serialization format var compactOpts []CompactOption if sc.detached { compactOpts = append(compactOpts, WithDetached(true)) } for _, option := range options { if copt, ok := option.(CompactOption); ok { compactOpts = append(compactOpts, copt) } } return Compact(&result, compactOpts...) default: return nil, signerr(`invalid serialization format`) } } var allowNoneWhitelist = jwk.WhitelistFunc(func(string) bool { return false }) // Verify checks if the given JWS message is verifiable using `alg` and `key`. // `key` may be a "raw" key (e.g. rsa.PublicKey) or a jwk.Key // // If the verification is successful, `err` is nil, and the content of the // payload that was signed is returned. If you need more fine-grained // control of the verification process, manually generate a // `Verifier` in `verify` subpackage, and call `Verify` method on it. // If you need to access signatures and JOSE headers in a JWS message, // use `Parse` function to get `Message` object. // // Because the use of "none" (jwa.NoSignature) algorithm is strongly discouraged, // this function DOES NOT consider it a success when `{"alg":"none"}` is // encountered in the message (it would also be counterintuitive when the code says // it _verified_ something when in fact it did no such thing). If you want to // accept messages with "none" signature algorithm, use `jws.Parse` to get the // raw JWS message. // // The error returned by this function is of type can be checked against // `jws.VerifyError()` and `jws.VerificationError()`. The latter is returned // when the verification process itself fails (e.g. invalid signature, wrong key), // while the former is returned when any other part of the `jws.Verify()` // function fails. func Verify(buf []byte, options ...VerifyOption) ([]byte, error) { vc := verifyContextPool.Get() defer verifyContextPool.Put(vc) if err := vc.ProcessOptions(options); err != nil { return nil, verifyerr(`failed to process options: %w`, err) } return vc.VerifyMessage(buf) } // get the value of b64 header field. // If the field does not exist, returns true (default) // Otherwise return the value specified by the header field. func getB64Value(hdr Headers) bool { var b64 bool if err := hdr.Get("b64", &b64); err != nil { return true // default } return b64 } // Parse parses contents from the given source and creates a jws.Message // struct. By default the input can be in either compact or full JSON serialization. // // You may pass `jws.WithJSON()` and/or `jws.WithCompact()` to specify // explicitly which format to use. If neither or both is specified, the function // will attempt to autodetect the format. If one or the other is specified, // only the specified format will be attempted. // // On error, returns a jws.ParseError. func Parse(src []byte, options ...ParseOption) (*Message, error) { var formats int for _, option := range options { switch option.Ident() { case identSerialization{}: var v int if err := option.Value(&v); err != nil { return nil, parseerr(`failed to retrieve serialization option value: %w`, err) } switch v { case fmtJSON: formats |= fmtJSON case fmtCompact: formats |= fmtCompact } } } // if format is 0 or both JSON/Compact, auto detect if v := formats & (fmtJSON | fmtCompact); v == 0 || v == fmtJSON|fmtCompact { CHECKLOOP: for i := range src { r := rune(src[i]) if r >= utf8.RuneSelf { r, _ = utf8.DecodeRune(src) } if !unicode.IsSpace(r) { if r == tokens.OpenCurlyBracket { formats = fmtJSON } else { formats = fmtCompact } break CHECKLOOP } } } if formats&fmtCompact == fmtCompact { msg, err := parseCompact(src) if err != nil { return nil, parseerr(`failed to parse compact format: %w`, err) } return msg, nil } else if formats&fmtJSON == fmtJSON { msg, err := parseJSON(src) if err != nil { return nil, parseerr(`failed to parse JSON format: %w`, err) } return msg, nil } return nil, parseerr(`invalid byte sequence`) } // ParseString parses contents from the given source and creates a jws.Message // struct. The input can be in either compact or full JSON serialization. // // On error, returns a jws.ParseError. func ParseString(src string) (*Message, error) { msg, err := Parse([]byte(src)) if err != nil { return nil, sparseerr(`failed to parse string: %w`, err) } return msg, nil } // ParseReader parses contents from the given source and creates a jws.Message // struct. The input can be in either compact or full JSON serialization. // // On error, returns a jws.ParseError. func ParseReader(src io.Reader) (*Message, error) { data, err := jwxio.ReadAllFromFiniteSource(src) if err == nil { return Parse(data) } if !errors.Is(err, jwxio.NonFiniteSourceError()) { return nil, rparseerr(`failed to read from finite source: %w`, err) } rdr := bufio.NewReader(src) var first rune for { r, _, err := rdr.ReadRune() if err != nil { return nil, rparseerr(`failed to read rune: %w`, err) } if !unicode.IsSpace(r) { first = r if err := rdr.UnreadRune(); err != nil { return nil, rparseerr(`failed to unread rune: %w`, err) } break } } var parser func(io.Reader) (*Message, error) if first == tokens.OpenCurlyBracket { parser = parseJSONReader } else { parser = parseCompactReader } m, err := parser(rdr) if err != nil { return nil, rparseerr(`failed to parse reader: %w`, err) } return m, nil } func parseJSONReader(src io.Reader) (result *Message, err error) { var m Message if err := json.NewDecoder(src).Decode(&m); err != nil { return nil, fmt.Errorf(`failed to unmarshal jws message: %w`, err) } return &m, nil } func parseJSON(data []byte) (result *Message, err error) { var m Message if err := json.Unmarshal(data, &m); err != nil { return nil, fmt.Errorf(`failed to unmarshal jws message: %w`, err) } return &m, nil } // SplitCompact splits a JWS in compact format and returns its three parts // separately: protected headers, payload and signature. // On error, returns a jws.ParseError. // // This function will be deprecated in v4. It is a low-level API, and // thus will be available in the `jwsbb` package. func SplitCompact(src []byte) ([]byte, []byte, []byte, error) { hdr, payload, signature, err := jwsbb.SplitCompact(src) if err != nil { return nil, nil, nil, parseerr(`%w`, err) } return hdr, payload, signature, nil } // SplitCompactString splits a JWT and returns its three parts // separately: protected headers, payload and signature. // On error, returns a jws.ParseError. // // This function will be deprecated in v4. It is a low-level API, and // thus will be available in the `jwsbb` package. func SplitCompactString(src string) ([]byte, []byte, []byte, error) { hdr, payload, signature, err := jwsbb.SplitCompactString(src) if err != nil { return nil, nil, nil, parseerr(`%w`, err) } return hdr, payload, signature, nil } // SplitCompactReader splits a JWT and returns its three parts // separately: protected headers, payload and signature. // On error, returns a jws.ParseError. // // This function will be deprecated in v4. It is a low-level API, and // thus will be available in the `jwsbb` package. func SplitCompactReader(rdr io.Reader) ([]byte, []byte, []byte, error) { hdr, payload, signature, err := jwsbb.SplitCompactReader(rdr) if err != nil { return nil, nil, nil, parseerr(`%w`, err) } return hdr, payload, signature, nil } // parseCompactReader parses a JWS value serialized via compact serialization. func parseCompactReader(rdr io.Reader) (m *Message, err error) { protected, payload, signature, err := SplitCompactReader(rdr) if err != nil { return nil, fmt.Errorf(`invalid compact serialization format: %w`, err) } return parse(protected, payload, signature) } func parseCompact(data []byte) (m *Message, err error) { protected, payload, signature, err := SplitCompact(data) if err != nil { return nil, fmt.Errorf(`invalid compact serialization format: %w`, err) } return parse(protected, payload, signature) } func parse(protected, payload, signature []byte) (*Message, error) { decodedHeader, err := base64.Decode(protected) if err != nil { return nil, fmt.Errorf(`failed to decode protected headers: %w`, err) } hdr := NewHeaders() if err := json.Unmarshal(decodedHeader, hdr); err != nil { return nil, fmt.Errorf(`failed to parse JOSE headers: %w`, err) } var decodedPayload []byte b64 := getB64Value(hdr) if !b64 { decodedPayload = payload } else { v, err := base64.Decode(payload) if err != nil { return nil, fmt.Errorf(`failed to decode payload: %w`, err) } decodedPayload = v } decodedSignature, err := base64.Decode(signature) if err != nil { return nil, fmt.Errorf(`failed to decode signature: %w`, err) } var msg Message msg.payload = decodedPayload msg.signatures = append(msg.signatures, &Signature{ protected: hdr, signature: decodedSignature, }) msg.b64 = b64 return &msg, nil } type CustomDecoder = json.CustomDecoder type CustomDecodeFunc = json.CustomDecodeFunc // RegisterCustomField allows users to specify that a private field // be decoded as an instance of the specified type. This option has // a global effect. // // For example, suppose you have a custom field `x-birthday`, which // you want to represent as a string formatted in RFC3339 in JSON, // but want it back as `time.Time`. // // In such case you would register a custom field as follows // // jws.RegisterCustomField(`x-birthday`, time.Time{}) // // Then you can use a `time.Time` variable to extract the value // of `x-birthday` field, instead of having to use `any` // and later convert it to `time.Time` // // var bday time.Time // _ = hdr.Get(`x-birthday`, &bday) // // If you need a more fine-tuned control over the decoding process, // you can register a `CustomDecoder`. For example, below shows // how to register a decoder that can parse RFC1123 format string: // // jws.RegisterCustomField(`x-birthday`, jws.CustomDecodeFunc(func(data []byte) (any, error) { // return time.Parse(time.RFC1123, string(data)) // })) // // Please note that use of custom fields can be problematic if you // are using a library that does not implement MarshalJSON/UnmarshalJSON // and you try to roundtrip from an object to JSON, and then back to an object. // For example, in the above example, you can _parse_ time values formatted // in the format specified in RFC822, but when you convert an object into // JSON, it will be formatted in RFC3339, because that's what `time.Time` // likes to do. To avoid this, it's always better to use a custom type // that wraps your desired type (in this case `time.Time`) and implement // MarshalJSON and UnmashalJSON. func RegisterCustomField(name string, object any) { registry.Register(name, object) } // Helpers for signature verification var rawKeyToKeyType = make(map[reflect.Type]jwa.KeyType) var keyTypeToAlgorithms = make(map[jwa.KeyType][]jwa.SignatureAlgorithm) func init() { rawKeyToKeyType[reflect.TypeFor[[]byte]()] = jwa.OctetSeq() rawKeyToKeyType[reflect.TypeFor[ed25519.PublicKey]()] = jwa.OKP() rawKeyToKeyType[reflect.TypeFor[rsa.PublicKey]()] = jwa.RSA() rawKeyToKeyType[reflect.TypeFor[*rsa.PublicKey]()] = jwa.RSA() rawKeyToKeyType[reflect.TypeFor[ecdsa.PublicKey]()] = jwa.EC() rawKeyToKeyType[reflect.TypeFor[*ecdsa.PublicKey]()] = jwa.EC() addAlgorithmForKeyType(jwa.OKP(), jwa.EdDSA()) for _, alg := range []jwa.SignatureAlgorithm{jwa.HS256(), jwa.HS384(), jwa.HS512()} { addAlgorithmForKeyType(jwa.OctetSeq(), alg) } for _, alg := range []jwa.SignatureAlgorithm{jwa.RS256(), jwa.RS384(), jwa.RS512(), jwa.PS256(), jwa.PS384(), jwa.PS512()} { addAlgorithmForKeyType(jwa.RSA(), alg) } for _, alg := range []jwa.SignatureAlgorithm{jwa.ES256(), jwa.ES384(), jwa.ES512()} { addAlgorithmForKeyType(jwa.EC(), alg) } } func addAlgorithmForKeyType(kty jwa.KeyType, alg jwa.SignatureAlgorithm) { keyTypeToAlgorithms[kty] = append(keyTypeToAlgorithms[kty], alg) } // AlgorithmsForKey returns the possible signature algorithms that can // be used for a given key. It only takes in consideration keys/algorithms // for verification purposes, as this is the only usage where one may need // dynamically figure out which method to use. func AlgorithmsForKey(key any) ([]jwa.SignatureAlgorithm, error) { var kty jwa.KeyType switch key := key.(type) { case jwk.Key: kty = key.KeyType() case rsa.PublicKey, *rsa.PublicKey, rsa.PrivateKey, *rsa.PrivateKey: kty = jwa.RSA() case ecdsa.PublicKey, *ecdsa.PublicKey, ecdsa.PrivateKey, *ecdsa.PrivateKey: kty = jwa.EC() case ed25519.PublicKey, ed25519.PrivateKey, *ecdh.PublicKey, ecdh.PublicKey, *ecdh.PrivateKey, ecdh.PrivateKey: kty = jwa.OKP() case []byte: kty = jwa.OctetSeq() default: return nil, fmt.Errorf(`unknown key type %T`, key) } algs, ok := keyTypeToAlgorithms[kty] if !ok { return nil, fmt.Errorf(`unregistered key type %q`, kty) } return algs, nil } // Settings allows you to set global settings for this JWS operations. // // Currently, the only setting available is `jws.WithLegacySigners()`, // which for various reason is now a no-op. func Settings(options ...GlobalOption) { for _, option := range options { switch option.Ident() { case identLegacySigners{}: } } } // VerifyCompactFast is a fast path verification function for JWS messages // in compact serialization format. // // This function is considered experimental, and may change or be removed // in the future. // // VerifyCompactFast performs signature verification on a JWS compact // serialization without fully parsing the message into a jws.Message object. // This makes it more efficient for cases where you only need to verify // the signature and extract the payload, without needing access to headers // or other JWS metadata. // // Returns the original payload that was signed if verification succeeds. // // Unlike jws.Verify(), this function requires you to specify the // algorithm explicitly rather than extracting it from the JWS headers. // This can be useful for performance-critical applications where the // algorithm is known in advance. // // Since this function avoids doing many checks that jws.Verify would perform, // you must ensure to perform the necessary checks including ensuring that algorithm is safe to use for your payload yourself. func VerifyCompactFast(key any, compact []byte, alg jwa.SignatureAlgorithm) ([]byte, error) { algstr := alg.String() // Split the serialized JWT into its components hdr, payload, encodedSig, err := jwsbb.SplitCompact(compact) if err != nil { return nil, fmt.Errorf("jwt.verifyFast: failed to split compact: %w", err) } signature, err := base64.Decode(encodedSig) if err != nil { return nil, fmt.Errorf("jwt.verifyFast: failed to decode signature: %w", err) } // Instead of appending, copy the data from hdr/payload lvb := len(hdr) + 1 + len(payload) verifyBuf := pool.ByteSlice().GetCapacity(lvb) verifyBuf = verifyBuf[:lvb] copy(verifyBuf, hdr) verifyBuf[len(hdr)] = tokens.Period copy(verifyBuf[len(hdr)+1:], payload) defer pool.ByteSlice().Put(verifyBuf) // Verify the signature if verifier2, err := VerifierFor(alg); err == nil { if err := verifier2.Verify(key, verifyBuf, signature); err != nil { return nil, verifyError{verificationError{fmt.Errorf("jwt.VerifyCompact: signature verification failed for %s: %w", algstr, err)}} } } else { legacyVerifier, err := NewVerifier(alg) if err != nil { return nil, verifyerr("jwt.VerifyCompact: failed to create verifier for %s: %w", algstr, err) } if err := legacyVerifier.Verify(verifyBuf, signature, key); err != nil { return nil, verifyError{verificationError{fmt.Errorf("jwt.VerifyCompact: signature verification failed for %s: %w", algstr, err)}} } } decoded, err := base64.Decode(payload) if err != nil { return nil, verifyerr("jwt.VerifyCompact: failed to decode payload: %w", err) } return decoded, nil } golang-github-lestrrat-go-jwx-3.0.13/jws/jws_internal_test.go000066400000000000000000000060151515060566400242760ustar00rootroot00000000000000package jws import ( "sync" "testing" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/lestrrat-go/jwx/v3/jws/legacy" "github.com/stretchr/testify/require" ) func wipeoutLegacySignatureRegistry() { enableLegacySignersOnce = &sync.Once{} signerDB = make(map[jwa.SignatureAlgorithm]SignerFactory) } func TestLegacySignatureSign(t *testing.T) { // This tests using the legacy NewSigner API, WHICH SHOULD NOT BE USED ANYMORE. // This test just ensures that the legacy API still works for those who, for some // reason, decided to use this API. t.Run("Test GH#1459", func(t *testing.T) { // For this test, we need to do something naughty, which is to wipe out the // legacy signature algorithm registry to make sure NewSigner works out // of the box. This is why the test is in `jws` package, not `jws_test`. wipeoutLegacySignatureRegistry() require.Len(t, signerDB, 0, "signerDB should be empty") // Test all supported signature algorithms using NewSigner. Note that NewSigner // is a deprecated API. When the right time comes, we should remove this test. for _, sig := range jwa.SignatureAlgorithms() { if sig == jwa.NoSignature() { // Skip alg=none continue } signer, err := NewSigner(sig) require.NoError(t, err, "NewSigner should succeed") require.NotNil(t, signer, "NewSigner should return a valid signer") } require.NotEqual(t, len(signerDB), 0, "signerDB should not be empty anymore") }) t.Run("use legacy signature algorithm registraiton API", func(t *testing.T) { // Create a custom algorithm "HS256-legacy" for testing hs256LegacyAlg := jwa.NewSignatureAlgorithm("HS256-legacy") // Register the legacy HS256 signer factory err := RegisterSigner(hs256LegacyAlg, SignerFactoryFn(func() (Signer, error) { return legacy.NewHMACSigner(jwa.HS256()), nil })) require.NoError(t, err, "RegisterSigner should succeed") // Clean up after test t.Cleanup(func() { UnregisterSigner(hs256LegacyAlg) jwa.UnregisterSignatureAlgorithm(hs256LegacyAlg) }) // Test data key := []byte("test-secret-key-for-legacy-comparison") payload := []byte("test payload for legacy signature comparison") // Create a signed JWS using Sign with HS256 algorithm signed, err := Sign(payload, WithKey(jwa.HS256(), key)) require.NoError(t, err, "Sign should succeed") // Parse the JWS to extract the signature msg, err := Parse(signed) require.NoError(t, err, "Parse should succeed") require.Len(t, msg.Signatures(), 1, "should have one signature") jwsSignature := msg.Signatures()[0].Signature() // Create a signature using Signature.Sign() with HS256-legacy algorithm sig := NewSignature() legacySigner, err := NewSigner(hs256LegacyAlg) require.NoError(t, err, "NewSigner should succeed for legacy algorithm") rawSig, _, err := sig.Sign(payload, legacySigner, key) require.NoError(t, err, "Signature.Sign should succeed") // Compare the signatures - they should be identical require.Equal(t, jwsSignature, rawSig, "signatures from Sign and Signature.Sign should be identical") }) } golang-github-lestrrat-go-jwx-3.0.13/jws/jws_test.go000066400000000000000000001644241515060566400224130ustar00rootroot00000000000000package jws_test import ( "bufio" "bytes" "crypto" "crypto/ecdh" "crypto/ecdsa" "crypto/ed25519" "crypto/rsa" "crypto/sha256" "encoding/asn1" "errors" "fmt" "io" "maps" "math/big" "net/http" "net/http/httptest" "os" "sort" "strings" "testing" "time" "github.com/lestrrat-go/httprc/v3" "github.com/lestrrat-go/jwx/v3/internal/base64" "github.com/lestrrat-go/jwx/v3/internal/json" "github.com/lestrrat-go/jwx/v3/internal/jwxtest" "github.com/lestrrat-go/jwx/v3/internal/tokens" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/lestrrat-go/jwx/v3/jwk" "github.com/lestrrat-go/jwx/v3/jws" "github.com/lestrrat-go/jwx/v3/jwt" "github.com/stretchr/testify/require" ) const examplePayload = `{"iss":"joe",` + "\r\n" + ` "exp":1300819380,` + "\r\n" + ` "http://example.com/is_root":true}` const exampleCompactSerialization = `eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk` const badValue = "%badvalue%" var hasES256K bool func TestSanity(t *testing.T) { t.Run("sanity: Verify with single key", func(t *testing.T) { key, err := jwk.ParseKey([]byte(`{ "kty": "oct", "k": "AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow" }`)) require.NoError(t, err, `jwk.ParseKey should succeed`) payload, err := jws.Verify([]byte(exampleCompactSerialization), jws.WithKey(jwa.HS256(), key)) require.NoError(t, err, `jws.Verify should succeed`) require.Equal(t, []byte(examplePayload), payload, `payloads should match`) }) t.Run("sanity: Verification failure", func(t *testing.T) { key1, err := jwxtest.GenerateSymmetricJwk() require.NoError(t, err, `jwxtest.GenerateSymmetricJwk should succeed`) require.NoError(t, key1.Set(jwk.KeyIDKey, "key1"), `key1.Set should succeed`) key2, err := jwxtest.GenerateRsaJwk() require.NoError(t, err, `jwxtest.GenerateRsaJwk should succeed`) require.NoError(t, key2.Set(jwk.KeyIDKey, "key2"), `key2.Set should succeed`) key3, err := jwxtest.GenerateEcdsaJwk() require.NoError(t, err, `jwxtest.GenerateEcdsaJwk should succeed`) require.NoError(t, key3.Set(jwk.KeyIDKey, "key3"), `key3.Set should succeed`) payload := []byte(`Lorem Ipsum Dolor Sit Amet`) signed, err := jws.Sign( payload, jws.WithJSON(), jws.WithKey(jwa.HS256(), key1), jws.WithKey(jwa.RS256(), key2), jws.WithKey(jwa.ES256(), key3), ) require.NoError(t, err, `jws.Sign should succeed`) t.Run("error type when parse fails", func(t *testing.T) { // try to verify a malformed jws message _, err = jws.Verify( []byte(`this.is.not.a.ws.message`), jws.WithKey(jwa.HS256(), key1), jws.WithKey(jwa.RS256(), key2), jws.WithKey(jwa.ES256(), key3), ) require.Error(t, err, `jws.Verify should fail`) // this should return true because it's an error returned from jws.Verify require.True(t, errors.Is(err, jws.VerifyError()), `errors.Is(jws.VerifyError()) should return true`) // this should return false because it's a parse error, not something from the verification process require.False(t, errors.Is(err, jws.VerificationError()), `errors.Is(jws.VerificationError()) should return false`) // this actually should be a parse error require.True(t, errors.Is(err, jws.ParseError()), `errors.Is(jws.ParseError()) should return true`) }) t.Run("error type when verification fails", func(t *testing.T) { // Create new keys so that verification fails key1, err := jwxtest.GenerateSymmetricJwk() require.NoError(t, err, `jwxtest.GenerateSymmetricJwk should succeed`) require.NoError(t, key1.Set(jwk.KeyIDKey, "key1"), `key1.Set should succeed`) key2, err := jwxtest.GenerateRsaJwk() require.NoError(t, err, `jwxtest.GenerateRsaJwk should succeed`) require.NoError(t, key2.Set(jwk.KeyIDKey, "key2"), `key2.Set should succeed`) key3, err := jwxtest.GenerateEcdsaJwk() require.NoError(t, err, `jwxtest.GenerateEcdsaJwk should succeed`) require.NoError(t, key3.Set(jwk.KeyIDKey, "key3"), `key3.Set should succeed`) verified, err := jws.Verify( signed, jws.WithKey(jwa.HS256(), key1), jws.WithKey(jwa.RS256(), key2), jws.WithKey(jwa.ES256(), key3), ) require.Error(t, err, `jws.Verify should fail`) require.Nil(t, verified, `verified should be nil`) // this should return true because it's an error returned from jws.Verify require.True(t, errors.Is(err, jws.VerifyError()), `errors.Is(jws.VerifyError()) should return true`) // this should also return true because it's an error returned from the verification process require.ErrorIs(t, err, jws.VerificationError(), `errors.Is(jws.VerificationError()) should return true (was %T)`, err) }) }) } func TestParseReader(t *testing.T) { t.Parallel() t.Run("Empty []byte", func(t *testing.T) { t.Parallel() _, err := jws.Parse(nil) require.Error(t, err, "Parsing an empty byte slice should result in an error") }) t.Run("Empty bytes.Buffer", func(t *testing.T) { t.Parallel() _, err := jws.ParseReader(&bytes.Buffer{}) require.Error(t, err, "Parsing an empty buffer should result in an error") }) t.Run("Compact detached payload", func(t *testing.T) { t.Parallel() split := strings.Split(exampleCompactSerialization, ".") incoming := strings.Join([]string{split[0], "", split[2]}, ".") _, err := jws.ParseString(incoming) require.NoError(t, err, `jws.ParseString should succeed`) }) t.Run("Compact missing header", func(t *testing.T) { t.Parallel() incoming := strings.Join( (strings.Split( exampleCompactSerialization, ".", ))[:2], ".", ) for _, useReader := range []bool{true, false} { var err error if useReader { // Force ParseReader() to choose un-optimized path by using bufio.NewReader _, err = jws.ParseReader(bufio.NewReader(strings.NewReader(incoming))) } else { _, err = jws.ParseString(incoming) } require.Error(t, err, "Parsing compact serialization with less than 3 parts should be an error") } }) t.Run("Compact bad header", func(t *testing.T) { t.Parallel() parts := strings.Split(exampleCompactSerialization, ".") parts[0] = badValue incoming := strings.Join(parts, ".") for _, useReader := range []bool{true, false} { var err error if useReader { _, err = jws.ParseReader(bufio.NewReader(strings.NewReader(incoming))) } else { _, err = jws.ParseString(incoming) } require.Error(t, err, "Parsing compact serialization with bad header should be an error") } }) t.Run("Compact bad payload", func(t *testing.T) { t.Parallel() parts := strings.Split(exampleCompactSerialization, ".") parts[1] = badValue incoming := strings.Join(parts, ".") for _, useReader := range []bool{true, false} { var err error if useReader { _, err = jws.ParseReader(bufio.NewReader(strings.NewReader(incoming))) } else { _, err = jws.ParseString(incoming) } require.Error(t, err, "Parsing compact serialization with bad payload should be an error") } }) t.Run("Compact bad signature", func(t *testing.T) { t.Parallel() parts := strings.Split(exampleCompactSerialization, ".") parts[2] = badValue incoming := strings.Join(parts, ".") for _, useReader := range []bool{true, false} { var err error if useReader { _, err = jws.ParseReader(bufio.NewReader(strings.NewReader(incoming))) } else { _, err = jws.ParseString(incoming) } require.Error(t, err, "Parsing compact serialization with bad signature should be an error") } }) } type dummyCryptoSigner struct { raw crypto.Signer } func (s *dummyCryptoSigner) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error) { return s.raw.Sign(rand, digest, opts) } func (s *dummyCryptoSigner) Public() crypto.PublicKey { return s.raw.Public() } var _ crypto.Signer = &dummyCryptoSigner{} type dummyECDSACryptoSigner struct { raw *ecdsa.PrivateKey } func (es *dummyECDSACryptoSigner) Public() crypto.PublicKey { return es.raw.Public() } func (es *dummyECDSACryptoSigner) Sign(rand io.Reader, digest []byte, _ crypto.SignerOpts) ([]byte, error) { // The implementation is the same as ecdsaCryptoSigner. // This is just here to test the interface conversion r, s, err := ecdsa.Sign(rand, es.raw, digest) if err != nil { return nil, fmt.Errorf(`failed to sign payload using ecdsa: %w`, err) } return asn1.Marshal(struct { R *big.Int S *big.Int }{R: r, S: s}) } var _ crypto.Signer = &dummyECDSACryptoSigner{} func testRoundtrip(t *testing.T, payload []byte, alg jwa.SignatureAlgorithm, signKey any, keys map[string]any) { jwkKey, err := jwk.Import(signKey) require.NoError(t, err, `jwk.New should succeed`) signKeys := []struct { Name string Key any }{ { Name: "Raw Key", Key: signKey, }, { Name: "JWK Key", Key: jwkKey, }, } verifyKeys := make(map[string]any) maps.Copy(verifyKeys, keys) if es, ok := signKey.(*ecdsa.PrivateKey); ok { k := &dummyECDSACryptoSigner{raw: es} signKeys = append(signKeys, struct { Name string Key any }{ Name: "crypto.Signer", Key: k, }) verifyKeys["Verify(crypto.Signer)"] = k } else if cs, ok := signKey.(crypto.Signer); ok { k := &dummyCryptoSigner{raw: cs} signKeys = append(signKeys, struct { Name string Key any }{ Name: "crypto.Signer", Key: k, }) verifyKeys["Verify(crypto.Signer)"] = k } for _, key := range signKeys { t.Run(key.Name, func(t *testing.T) { signed, err := jws.Sign(payload, jws.WithKey(alg, key.Key)) require.NoError(t, err, "jws.Sign should succeed") parsers := map[string]func([]byte) (*jws.Message, error){ "ParseReader(io.Reader)": func(b []byte) (*jws.Message, error) { return jws.ParseReader(bufio.NewReader(bytes.NewReader(b))) }, "Parse([]byte)": func(b []byte) (*jws.Message, error) { return jws.Parse(b) }, "ParseString(string)": func(b []byte) (*jws.Message, error) { return jws.ParseString(string(b)) }, } for name, f := range parsers { t.Run(name, func(t *testing.T) { t.Parallel() m, err := f(signed) require.NoError(t, err, "(%s) %s is successful", alg, name) require.Equal(t, payload, m.Payload(), "(%s) %s: Payload is decoded", alg, name) }) } for name, testKey := range verifyKeys { t.Run(name, func(t *testing.T) { verified, err := jws.Verify(signed, jws.WithKey(alg, testKey)) require.NoError(t, err, "(%s) Verify is successful", alg) require.Equal(t, payload, verified, "(%s) Verified payload is the same", alg) }) } }) } } func TestRoundtrip(t *testing.T) { t.Parallel() payload := []byte("Lorem ipsum") t.Run("HMAC", func(t *testing.T) { t.Parallel() sharedkey := []byte("Avracadabra") jwkKey, _ := jwk.Import(sharedkey) keys := map[string]any{ "[]byte": sharedkey, "jwk.Key": jwkKey, } hmacAlgorithms := []jwa.SignatureAlgorithm{jwa.HS256(), jwa.HS384(), jwa.HS512()} for _, alg := range hmacAlgorithms { t.Run(alg.String(), func(t *testing.T) { t.Parallel() testRoundtrip(t, payload, alg, sharedkey, keys) }) } }) t.Run("ECDSA", func(t *testing.T) { t.Parallel() key, err := jwxtest.GenerateEcdsaKey(jwa.P521()) require.NoError(t, err, "ECDSA key generated") jwkKey, _ := jwk.Import(key.PublicKey) keys := map[string]any{ "Verify(ecdsa.PublicKey)": key.PublicKey, "Verify(*ecdsa.PublicKey)": &key.PublicKey, "Verify(jwk.Key)": jwkKey, } for _, alg := range []jwa.SignatureAlgorithm{jwa.ES256(), jwa.ES384(), jwa.ES512()} { t.Run(alg.String(), func(t *testing.T) { t.Parallel() testRoundtrip(t, payload, alg, key, keys) }) } }) t.Run("RSA", func(t *testing.T) { t.Parallel() key, err := jwxtest.GenerateRsaKey() require.NoError(t, err, "RSA key generated") jwkKey, _ := jwk.Import(key.PublicKey) keys := map[string]any{ "Verify(rsa.PublicKey)": key.PublicKey, "Verify(*rsa.PublicKey)": &key.PublicKey, "Verify(jwk.Key)": jwkKey, } for _, alg := range []jwa.SignatureAlgorithm{jwa.RS256(), jwa.RS384(), jwa.RS512(), jwa.PS256(), jwa.PS384(), jwa.PS512()} { t.Run(alg.String(), func(t *testing.T) { t.Parallel() testRoundtrip(t, payload, alg, key, keys) }) } }) t.Run("EdDSA", func(t *testing.T) { t.Parallel() key, err := jwxtest.GenerateEd25519Key() require.NoError(t, err, "ed25519 key generated") pubkey := key.Public() jwkKey, _ := jwk.Import(pubkey) keys := map[string]any{ "Verify(ed25519.Public())": pubkey, // Meh, this doesn't work // "Verify(*ed25519.Public())": &pubkey, "Verify(jwk.Key)": jwkKey, } for _, alg := range []jwa.SignatureAlgorithm{jwa.EdDSA()} { t.Run(alg.String(), func(t *testing.T) { t.Parallel() testRoundtrip(t, payload, alg, key, keys) }) } }) } func TestSignMulti2(t *testing.T) { sharedkey := []byte("Avracadabra") payload := []byte("Lorem ipsum") hmacAlgorithms := []jwa.SignatureAlgorithm{jwa.HS256(), jwa.HS384(), jwa.HS512()} var options = []jws.SignOption{jws.WithJSON()} for _, alg := range hmacAlgorithms { options = append(options, jws.WithKey(alg, sharedkey)) // (signer, sharedkey, nil, nil)) } signed, err := jws.Sign(payload, options...) require.NoError(t, err, `jws.Sign with multiple keys should succeed`) for _, alg := range hmacAlgorithms { m := jws.NewMessage() verified, err := jws.Verify(signed, jws.WithKey(alg, sharedkey), jws.WithMessage(m)) require.NoError(t, err, "Verify succeeded") require.Equal(t, payload, verified, "verified payload matches") // XXX This actually doesn't really test much, but if there was anything // wrong, the process should have failed well before reaching here require.Equal(t, payload, m.Payload(), "message payload matches") } } func TestEncode(t *testing.T) { t.Parallel() t.Run("UnsecuredCompact", func(t *testing.T) { t.Parallel() s := `eyJhbGciOiJub25lIn0.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.` m, err := jws.ParseReader(strings.NewReader(s)) require.NoError(t, err, "Parsing compact serialization") { v := map[string]any{} require.NoError(t, json.Unmarshal(m.Payload(), &v), "Unmarshal payload") require.Equal(t, v["iss"], "joe", "iss matches") require.Equal(t, int(v["exp"].(float64)), 1300819380, "exp matches") require.Equal(t, v["http://example.com/is_root"], true, "'http://example.com/is_root' matches") } require.Len(t, m.Signatures(), 1, "There should be 1 signature") signatures := m.Signatures() algorithm, ok := signatures[0].ProtectedHeaders().Algorithm() if !ok || algorithm != jwa.NoSignature() { t.Fatal("Algorithm in header does not match") } require.Empty(t, signatures[0].Signature(), "Signature should be empty") }) t.Run("CompleteJSON", func(t *testing.T) { t.Parallel() s := `{ "payload": "eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ", "signatures":[ { "header": {"kid":"2010-12-29"}, "protected":"eyJhbGciOiJSUzI1NiJ9", "signature": "cC4hiUPoj9Eetdgtv3hF80EGrhuB__dzERat0XF9g2VtQgr9PJbu3XOiZj5RZmh7AAuHIm4Bh-0Qc_lF5YKt_O8W2Fp5jujGbds9uJdbF9CUAr7t1dnZcAcQjbKBYNX4BAynRFdiuB--f_nZLgrnbyTyWzO75vRK5h6xBArLIARNPvkSjtQBMHlb1L07Qe7K0GarZRmB_eSN9383LcOLn6_dO--xi12jzDwusC-eOkHWEsqtFZESc6BfI7noOPqvhJ1phCnvWh6IeYI2w9QOYEUipUTI8np6LbgGY9Fs98rqVt5AXLIhWkWywlVmtVrBp0igcN_IoypGlUPQGe77Rw" }, { "header": {"kid":"e9bc097a-ce51-4036-9562-d2ade882db0d"}, "protected":"eyJhbGciOiJFUzI1NiJ9", "signature": "DtEhU3ljbEg8L38VWAfUAqOyKAM6-Xx-F4GawxaepmXFCgfTjDxw5djxLa8ISlSApmWQxfKTUJqPP3-Kg6NU1Q" } ] }` m, err := jws.ParseReader(strings.NewReader(s)) require.NoError(t, err, "Unmarshal complete json serialization") require.Len(t, m.Signatures(), 2, "There should be 2 signatures") sigs := m.LookupSignature("2010-12-29") require.Len(t, sigs, 1, "There should be 1 signature with kid = '2010-12-29'") }) t.Run("Protected Header lookup", func(t *testing.T) { t.Parallel() s := `{ "payload": "eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ", "signatures":[ { "header": {"cty":"example"}, "protected":"eyJhbGciOiJFUzI1NiIsImtpZCI6ImU5YmMwOTdhLWNlNTEtNDAzNi05NTYyLWQyYWRlODgyZGIwZCJ9", "signature": "JcLb1udPAV72TayGv6eawZKlIQQ3K1NzB0fU7wwYoFypGxEczdCQU-V9jp4WwY2ueJKYeE4fF6jigB0PdSKR0Q" } ] }` // Protected Header is {"alg":"ES256","kid":"e9bc097a-ce51-4036-9562-d2ade882db0d"} // This protected header combination forces the parser/unmarshal to go trough the code path to populate and look for protected header fields. // The signature is valid. m, err := jws.ParseReader(strings.NewReader(s)) require.NoError(t, err, "Unmarshal complete json serialization") require.Len(t, m.Signatures(), 1, "There should be 1 signature") sigs := m.LookupSignature("e9bc097a-ce51-4036-9562-d2ade882db0d") require.Len(t, sigs, 1, "There should be 1 signature with kid = '2010-12-29'") }) t.Run("FlattenedJSON", func(t *testing.T) { t.Parallel() s := `{ "payload": "eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ", "protected":"eyJhbGciOiJFUzI1NiJ9", "header": { "kid":"e9bc097a-ce51-4036-9562-d2ade882db0d" }, "signature": "DtEhU3ljbEg8L38VWAfUAqOyKAM6-Xx-F4GawxaepmXFCgfTjDxw5djxLa8ISlSApmWQxfKTUJqPP3-Kg6NU1Q" }` m, err := jws.ParseReader(strings.NewReader(s)) require.NoError(t, err, "Parsing flattened json serialization") require.Len(t, m.Signatures(), 1, "There should be 1 signature") jsonbuf, _ := json.MarshalIndent(m, "", " ") t.Logf("%s", jsonbuf) }) t.Run("SplitCompact", func(t *testing.T) { testcases := []struct { Name string Size int }{ {Name: "Short", Size: 100}, {Name: "Long", Size: 8000}, } for _, tc := range testcases { size := tc.Size t.Run(tc.Name, func(t *testing.T) { t.Parallel() // Create payload with X.Y.Z var payload []byte for range size { payload = append(payload, 'X') } payload = append(payload, tokens.Period) for range size { payload = append(payload, 'Y') } payload = append(payload, tokens.Period) for range size { payload = append(payload, 'Y') } // Test using bytes, reader optimized and non-optimized path for _, method := range []int{0, 1, 2} { var x, y, z []byte var err error switch method { case 0: // bytes x, y, z, err = jws.SplitCompact(payload) case 1: // un-optimized io.Reader x, y, z, err = jws.SplitCompactReader(bytes.NewReader(payload)) default: // optimized io.Reader x, y, z, err = jws.SplitCompactReader(bufio.NewReader(bytes.NewReader(payload))) } require.NoError(t, err, "SplitCompact should succeed") require.Len(t, x, size, "Length of header") require.Len(t, y, size, "Length of payload") require.Len(t, z, size, "Length of signature") } }) } }) } func TestReadFile(t *testing.T) { t.Parallel() f, err := os.CreateTemp(t.TempDir(), "test-read-file-*.jws") require.NoError(t, err, `io.CreateTemp should succeed`) defer f.Close() fmt.Fprintf(f, "%s", exampleCompactSerialization) _, err = jws.ReadFile(f.Name()) require.NoError(t, err, `jws.ReadFile should succeed`) } func TestVerifyNonUniqueKid(t *testing.T) { const payload = "Lorem ipsum" const kid = "notUniqueKid" privateKey, err := jwxtest.GenerateRsaJwk() require.NoError(t, err, "jwxtest.GenerateJwk should succeed") _ = privateKey.Set(jwk.KeyIDKey, kid) signed, err := jws.Sign([]byte(payload), jws.WithKey(jwa.RS256(), privateKey)) require.NoError(t, err, `jws.Sign should succeed`) correctKey, _ := jwk.PublicKeyOf(privateKey) _ = correctKey.Set(jwk.AlgorithmKey, jwa.RS256()) makeSet := func(keys ...jwk.Key) jwk.Set { set := jwk.NewSet() for _, key := range keys { _ = set.AddKey(key) } return set } testcases := []struct { Name string Key func() jwk.Key // Generates the "wrong" key }{ { Name: `match 2 keys via same "kid"`, Key: func() jwk.Key { privateKey, _ := jwxtest.GenerateRsaJwk() wrongKey, _ := jwk.PublicKeyOf(privateKey) _ = wrongKey.Set(jwk.KeyIDKey, kid) _ = wrongKey.Set(jwk.AlgorithmKey, jwa.RS256()) return wrongKey }, }, { Name: `match 2 keys via same "kid", same key value but different alg`, Key: func() jwk.Key { wrongKey, _ := correctKey.Clone() _ = wrongKey.Set(jwk.KeyIDKey, kid) _ = wrongKey.Set(jwk.AlgorithmKey, jwa.RS512()) return wrongKey }, }, { Name: `match 2 keys via same "kid", same key type but different alg`, Key: func() jwk.Key { privateKey, _ := jwxtest.GenerateRsaJwk() wrongKey, _ := jwk.PublicKeyOf(privateKey) _ = wrongKey.Set(jwk.KeyIDKey, kid) _ = wrongKey.Set(jwk.AlgorithmKey, jwa.RS512()) return wrongKey }, }, { Name: `match 2 keys via same "kid" and different key type / alg`, Key: func() jwk.Key { privateKey, _ := jwxtest.GenerateEcdsaKey(jwa.P256()) wrongKey, err := jwk.PublicKeyOf(privateKey) require.NoError(t, err, `jwk.PublicKeyOf should succeed`) _ = wrongKey.Set(jwk.KeyIDKey, kid) _ = wrongKey.Set(jwk.AlgorithmKey, jwa.ES256K()) return wrongKey }, }, } for _, tc := range testcases { wrongKey, err := tc.Key().Clone() require.NoError(t, err, `cloning wrong key should succeed`) for _, set := range []jwk.Set{makeSet(wrongKey, correctKey), makeSet(correctKey, wrongKey)} { t.Run(tc.Name, func(t *testing.T) { // Try matching in different orders var usedKey jwk.Key _, err = jws.Verify(signed, jws.WithKeySet(set, jws.WithMultipleKeysPerKeyID(true)), jws.WithKeyUsed(&usedKey)) require.NoError(t, err, `jws.Verify should succeed`) require.Equal(t, usedKey, correctKey) }) } } } func TestVerifySet(t *testing.T) { t.Parallel() const payload = "Lorem ipsum" makeSet := func(privkey jwk.Key) jwk.Set { set := jwk.NewSet() k1, err := jwk.Import([]byte("abracadabra")) require.NoError(t, err, `jwk.Import should succeed`) set.AddKey(k1) k2, err := jwk.Import([]byte("opensesame")) require.NoError(t, err, `jwk.Import should succeed`) set.AddKey(k2) pubkey, err := jwk.PublicKeyOf(privkey) require.NoError(t, err, `jwk.PublicKeyOf should succeed`) require.NoError(t, pubkey.Set(jwk.AlgorithmKey, jwa.RS256()), `setting algorithm should succeed`) set.AddKey(pubkey) return set } for _, useJSON := range []bool{true, false} { t.Run(fmt.Sprintf("useJSON=%t", useJSON), func(t *testing.T) { t.Parallel() t.Run(`match via "alg"`, func(t *testing.T) { t.Parallel() key, err := jwxtest.GenerateRsaJwk() require.NoError(t, err, "jwxtest.GenerateJwk should succeed") set := makeSet(key) signed, err := jws.Sign([]byte(payload), jws.WithKey(jwa.RS256(), key)) require.NoError(t, err, `jws.Sign should succeed`) if useJSON { m, err := jws.Parse(signed) require.NoError(t, err, `jws.Parse should succeed`) signed, err = json.Marshal(m) require.NoError(t, err, `json.Marshal should succeed`) } var used jwk.Key verified, err := jws.Verify(signed, jws.WithKeySet(set, jws.WithRequireKid(false)), jws.WithKeyUsed(&used)) require.NoError(t, err, `jws.Verify should succeed`) require.Equal(t, []byte(payload), verified, `payload should match`) expected, _ := jwk.PublicKeyOf(key) thumb1, _ := expected.Thumbprint(crypto.SHA1) thumb2, _ := used.Thumbprint(crypto.SHA1) require.Equal(t, thumb1, thumb2, `keys should match`) }) t.Run(`match via "kid"`, func(t *testing.T) { t.Parallel() key, err := jwxtest.GenerateRsaJwk() require.NoError(t, err, "jwxtest.GenerateJwk should succeed") key.Set(jwk.KeyIDKey, `mykey`) set := makeSet(key) signed, err := jws.Sign([]byte(payload), jws.WithKey(jwa.RS256(), key)) require.NoError(t, err, `jws.Sign should succeed`) if useJSON { m, err := jws.Parse(signed) require.NoError(t, err, `jws.Parse should succeed`) signed, err = json.Marshal(m) require.NoError(t, err, `json.Marshal should succeed`) } var used jwk.Key verified, err := jws.Verify(signed, jws.WithKeySet(set), jws.WithKeyUsed(&used)) require.NoError(t, err, `jws.Verify should succeed`) require.Equal(t, []byte(payload), verified, `payload should match`) expected, _ := jwk.PublicKeyOf(key) thumb1, _ := expected.Thumbprint(crypto.SHA1) thumb2, _ := used.Thumbprint(crypto.SHA1) require.Equal(t, thumb1, thumb2, `keys should match`) }) }) } } func TestCustomField(t *testing.T) { // XXX has global effect!!! const rfc3339Key = `x-test-rfc3339` const rfc1123Key = `x-test-rfc1123` jws.RegisterCustomField(rfc3339Key, time.Time{}) jws.RegisterCustomField(rfc1123Key, jws.CustomDecodeFunc(func(data []byte) (any, error) { var s string if err := json.Unmarshal(data, &s); err != nil { return nil, err } return time.Parse(time.RFC1123, s) })) defer jws.RegisterCustomField(rfc3339Key, nil) defer jws.RegisterCustomField(rfc1123Key, nil) expected := time.Date(2015, 11, 4, 5, 12, 52, 0, time.UTC) rfc3339bytes, _ := expected.MarshalText() // RFC3339 rfc1123bytes := expected.Format(time.RFC1123) plaintext := []byte("Hello, World!") rsakey, err := jwxtest.GenerateRsaJwk() require.NoError(t, err, `jwxtest.GenerateRsaJwk() should succeed`) t.Run("jws.Parse", func(t *testing.T) { protected := jws.NewHeaders() protected.Set(rfc3339Key, string(rfc3339bytes)) protected.Set(rfc1123Key, rfc1123bytes) encrypted, err := jws.Sign(plaintext, jws.WithKey(jwa.RS256(), rsakey, jws.WithProtectedHeaders(protected))) require.NoError(t, err, `jws.Sign should succeed`) msg, err := jws.Parse(encrypted) require.NoError(t, err, `jws.Parse should succeed`) for _, key := range []string{rfc3339Key, rfc1123Key} { var v time.Time require.NoError(t, msg.Signatures()[0].ProtectedHeaders().Get(key, &v), `msg.Get(%q) should succeed`, key) require.Equal(t, expected, v, `values should match`) } }) t.Run("json.Unmarshal", func(t *testing.T) { protected := jws.NewHeaders() protected.Set(rfc3339Key, string(rfc3339bytes)) protected.Set(rfc1123Key, rfc1123bytes) encrypted, err := jws.Sign(plaintext, jws.WithKey(jwa.RS256(), rsakey, jws.WithProtectedHeaders(protected)), jws.WithJSON()) require.NoError(t, err, `jws.Sign should succeed`) msg := jws.NewMessage() require.NoError(t, json.Unmarshal(encrypted, msg), `json.Unmarshal should succeed`) for _, key := range []string{rfc3339Key, rfc1123Key} { var v time.Time require.NoError(t, msg.Signatures()[0].ProtectedHeaders().Get(key, &v), `msg.Get(%q) should succeed`, key) require.Equal(t, expected, v, `values should match`) } }) /* // XXX has global effect!!! jws.RegisterCustomField(`x-birthday`, time.Time{}) defer jws.RegisterCustomField(`x-birthday`, nil) expected := time.Date(2015, 11, 4, 5, 12, 52, 0, time.UTC) bdaybytes, _ := expected.MarshalText() // RFC3339 payload := "Hello, World!" privkey, err := jwxtest.GenerateRsaJwk() require.NoError(t, err, `jwxtest.GenerateRsaJwk() should succeed`) hdrs := jws.NewHeaders() hdrs.Set(`x-birthday`, string(bdaybytes)) signed, err := jws.Sign([]byte(payload), jws.WithKey(jwa.RS256(), privkey, jws.WithProtectedHeaders(hdrs))) require.NoError(t, err, `jws.Sign should succeed`) t.Run("jws.Parse + json.Unmarshal", func(t *testing.T) { msg, err := jws.Parse(signed) require.NoError(t, err, `jws.Parse should succeed`) var v any require.NoError(t, msg.Signatures()[0].ProtectedHeaders().Get(`x-birthday`, &v), `msg.Signatures()[0].ProtectedHeaders().Get("x-birthday") should succeed`) require.Equal(t, expected, v, `values should match`) // Create JSON from jws.Message buf, err := json.Marshal(msg) require.NoError(t, err, `json.Marshal should succeed`) var msg2 jws.Message require.NoError(t, json.Unmarshal(buf, &msg2), `json.Unmarshal should succeed`) v = nil require.NoError(t, msg2.Signatures()[0].ProtectedHeaders().Get(`x-birthday`, &v), `msg2.Signatures()[0].ProtectedHeaders().Get("x-birthday") should succeed`) require.Equal(t, expected, v, `values should match`) }) */ } func TestWithMessage(t *testing.T) { key, err := jwxtest.GenerateRsaKey() require.NoError(t, err, "jwxtest.Generate should succeed") const text = "hello, world" signed, err := jws.Sign([]byte(text), jws.WithKey(jwa.RS256(), key)) require.NoError(t, err, `jws.Sign should succeed`) m := jws.NewMessage() payload, err := jws.Verify(signed, jws.WithKey(jwa.RS256(), key.PublicKey), jws.WithMessage(m)) require.NoError(t, err, `jws.Verify should succeed`) require.Equal(t, payload, []byte(text), `jws.Verify should produce the correct payload`) parsed, err := jws.Parse(signed) require.NoError(t, err, `jws.Parse should succeed`) // The result of using jws.WithMessage should match the result of jws.Parse buf1, _ := json.Marshal(m) buf2, _ := json.Marshal(parsed) require.Equal(t, buf1, buf2, `result of jws.PArse and jws.Verify(..., jws.WithMessage()) should match`) } func TestRFC7797(t *testing.T) { const keysrc = `{"kty":"oct", "k":"AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow" }` key, err := jwk.ParseKey([]byte(keysrc)) require.NoError(t, err, `jwk.Parse should succeed`) t.Run("Invalid payload when b64 = false and NOT detached", func(t *testing.T) { const payload = `$.02` hdrs := jws.NewHeaders() hdrs.Set("b64", false) hdrs.Set("crit", "b64") _, err := jws.Sign([]byte(payload), jws.WithKey(jwa.HS256(), key, jws.WithProtectedHeaders(hdrs))) require.Error(t, err, `jws.Sign should fail`) }) t.Run("Invalid usage when b64 = false and NOT detached", func(t *testing.T) { const payload = `$.02` hdrs := jws.NewHeaders() hdrs.Set("b64", false) hdrs.Set("crit", "b64") _, err := jws.Sign([]byte(payload), jws.WithKey(jwa.HS256(), key, jws.WithProtectedHeaders(hdrs)), jws.WithDetachedPayload([]byte(payload))) require.Error(t, err, `jws.Sign should fail`) }) t.Run("Valid payload when b64 = false", func(t *testing.T) { testcases := []struct { Name string Payload []byte Detached bool }{ { Name: `(Detached) payload contains a period`, Payload: []byte(`$.02`), Detached: true, }, { Name: `(NOT detached) payload does not contain a period`, Payload: []byte(`hell0w0rld`), }, } for _, tc := range testcases { t.Run(tc.Name, func(t *testing.T) { hdrs := jws.NewHeaders() hdrs.Set("b64", false) hdrs.Set("crit", "b64") payload := tc.Payload signOptions := []jws.SignOption{jws.WithKey(jwa.HS256(), key, jws.WithProtectedHeaders(hdrs))} var verifyOptions []jws.VerifyOption verifyOptions = append(verifyOptions, jws.WithKey(jwa.HS256(), key)) if tc.Detached { signOptions = append(signOptions, jws.WithDetachedPayload(payload)) verifyOptions = append(verifyOptions, jws.WithDetachedPayload(payload)) payload = nil } signed, err := jws.Sign(payload, signOptions...) require.NoError(t, err, `jws.Sign should succeed`) verified, err := jws.Verify(signed, verifyOptions...) require.NoError(t, err, `jws.Verify should succeed`) require.Equal(t, tc.Payload, verified, `payload should match`) }) } }) t.Run("Verify", func(t *testing.T) { detached := []byte(`$.02`) testcases := []struct { Name string Input []byte VerifyOptions []jws.VerifyOption Error bool }{ { Name: "JSON format", Input: []byte(`{ "protected": "eyJhbGciOiJIUzI1NiIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19", "payload": "$.02", "signature": "A5dxf2s96_n5FLueVuW1Z_vh161FwXZC4YLPff6dmDY" }`), }, { Name: "JSON format (detached payload)", VerifyOptions: []jws.VerifyOption{ jws.WithDetachedPayload(detached), }, Input: []byte(`{ "protected": "eyJhbGciOiJIUzI1NiIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19", "signature": "A5dxf2s96_n5FLueVuW1Z_vh161FwXZC4YLPff6dmDY" }`), }, { Name: "JSON Format (b64 does not match)", Error: true, Input: []byte(`{ "signatures": [ { "protected": "eyJhbGciOiJIUzI1NiIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19", "signature": "A5dxf2s96_n5FLueVuW1Z_vh161FwXZC4YLPff6dmDY" }, { "protected": "eyJhbGciOiJIUzI1NiIsImI2NCI6dHJ1ZSwiY3JpdCI6WyJiNjQiXX0", "signature": "6BjugbC8MfrT_yy5WxWVFZrEHVPDtpdsV9u-wbzQDV8" } ], "payload":"$.02" }`), }, { Name: "Compact (detached payload)", Input: []byte(`eyJhbGciOiJIUzI1NiIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19..A5dxf2s96_n5FLueVuW1Z_vh161FwXZC4YLPff6dmDY`), VerifyOptions: []jws.VerifyOption{ jws.WithDetachedPayload(detached), }, }, } for _, tc := range testcases { t.Run(tc.Name, func(t *testing.T) { options := tc.VerifyOptions options = append(options, jws.WithKey(jwa.HS256(), key)) payload, err := jws.Verify(tc.Input, options...) if tc.Error { require.Error(t, err, `jws.Verify should fail`) require.True(t, errors.Is(err, jws.VerifyError()), `jws.IsVerifyError should return true`) require.False(t, errors.Is(err, jws.VerificationError()), `jws.IsVerifyError should return false`) } else { require.NoError(t, err, `jws.Verify should succeed`) require.Equal(t, detached, payload, `payload should match`) } }) } }) } func TestGH485(t *testing.T) { const payload = `eyJhIjoiYiJ9` const protected = `eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiIsImNyaXQiOlsiZXhwIl0sImV4cCI6MCwiaXNzIjoiZm9vIiwibmJmIjowLCJpYXQiOjB9` const signature = `qM0CdRcyR4hw03J2ThJDat3Af40U87wVCF3Tp3xsyOg` const expected = `{"a":"b"}` signed := fmt.Sprintf(`{ "payload": %q, "signatures": [{"protected": %q, "signature": %q}] }`, payload, protected, signature) verified, err := jws.Verify([]byte(signed), jws.WithKey(jwa.HS256(), []byte("secret"))) require.NoError(t, err, `jws.Verify should succeed`) require.Equal(t, expected, string(verified), `verified payload should match`) compact := strings.Join([]string{protected, payload, signature}, ".") verified, err = jws.Verify([]byte(compact), jws.WithKey(jwa.HS256(), []byte("secret"))) require.NoError(t, err, `jws.Verify should succeed`) require.Equal(t, expected, string(verified), `verified payload should match`) } func TestJKU(t *testing.T) { ctx := t.Context() key, err := jwxtest.GenerateRsaJwk() require.NoError(t, err, `jwxtest.GenerateRsaJwk should succeed`) key.Set(jwk.KeyIDKey, `my-awesome-key`) pubkey, err := jwk.PublicKeyOf(key) require.NoError(t, err, `jwk.PublicKeyOf should succeed`) set := jwk.NewSet() set.AddKey(pubkey) srv := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(set) })) defer srv.Close() payload := []byte("Lorem Ipsum") t.Run("Compact", func(t *testing.T) { testcases := []struct { Name string Error bool Query string Fetcher func() jwk.Fetcher FetchOptions func() []jwk.FetchOption }{ { Name: "Fail without whitelist", Error: true, FetchOptions: func() []jwk.FetchOption { return []jwk.FetchOption{jwk.WithHTTPClient(srv.Client())} }, }, { Name: "Success", FetchOptions: func() []jwk.FetchOption { return []jwk.FetchOption{ jwk.WithFetchWhitelist(jwk.InsecureWhitelist{}), jwk.WithHTTPClient(srv.Client()), } }, }, { Name: "Rejected by whitelist", Error: true, FetchOptions: func() []jwk.FetchOption { wl := jwk.NewMapWhitelist().Add(`https://github.com/lestrrat-go/jwx/v3`) return []jwk.FetchOption{ jwk.WithFetchWhitelist(wl), jwk.WithHTTPClient(srv.Client()), } }, }, { Name: "Cache", Fetcher: func() jwk.Fetcher { c, err := jwk.NewCache(ctx, httprc.NewClient()) require.NoError(t, err, `jwk.NewCache should succeed`) require.NoError(t, c.Register(ctx, srv.URL, jwk.WithHTTPClient(srv.Client())), `c.Register should succeed`) return jwk.NewCachedFetcher(c) }, }, } for _, tc := range testcases { t.Run(tc.Name, func(t *testing.T) { hdr := jws.NewHeaders() u := srv.URL if tc.Query != "" { u += "?" + tc.Query } hdr.Set(jws.JWKSetURLKey, u) signed, err := jws.Sign(payload, jws.WithKey(jwa.RS256(), key, jws.WithProtectedHeaders(hdr))) require.NoError(t, err, `jws.Sign should succeed`) var options []jwk.FetchOption if f := tc.FetchOptions; f != nil { options = append(options, f()...) } var fetcher jwk.Fetcher if f := tc.Fetcher; f != nil { fetcher = f() } decoded, err := jws.Verify(signed, jws.WithVerifyAuto(fetcher, options...)) if tc.Error { require.Error(t, err, `jws.Verify should fail`) } else { require.NoError(t, err, `jws.Verify should succeed`) require.Equal(t, payload, decoded, `decoded payload should match`) } }) } }) t.Run("JSON", func(t *testing.T) { // scenario: create a JSON message, which contains 3 signature entries. // 1st and 3rd signatures are valid, but signed using keys that are not // present in the JWKS. // Only the second signature uses a key found in the JWKS var keys []jwk.Key for i := range 3 { key, err := jwxtest.GenerateRsaJwk() require.NoError(t, err, `jwxtest.GenerateRsaJwk should succeed`) key.Set(jwk.KeyIDKey, fmt.Sprintf(`used-%d`, i)) keys = append(keys, key) } var unusedKeys []jwk.Key for i := range 2 { key, err := jwxtest.GenerateRsaJwk() require.NoError(t, err, `jwxtest.GenerateRsaJwk should succeed`) key.Set(jwk.KeyIDKey, fmt.Sprintf(`unused-%d`, i)) unusedKeys = append(unusedKeys, key) } // The set should contain unused key, used key, and unused key. // ...but they need to be public keys set := jwk.NewSet() for _, key := range []jwk.Key{unusedKeys[0], keys[1], unusedKeys[1]} { pubkey, err := jwk.PublicKeyOf(key) require.NoError(t, err, `jwk.PublicKeyOf should succeed`) kid, ok := key.KeyID() require.True(t, ok, `key ID should be populated`) pubkid, ok := pubkey.KeyID() require.True(t, ok, `key ID should be populated`) require.Equal(t, kid, pubkid, `key ID should be populated`) set.AddKey(pubkey) } srv := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(set) })) defer srv.Close() // Sign the payload using the three keys var signOptions = []jws.SignOption{jws.WithJSON()} for _, key := range keys { hdr := jws.NewHeaders() hdr.Set(jws.JWKSetURLKey, srv.URL) signOptions = append(signOptions, jws.WithKey(jwa.RS256(), key, jws.WithProtectedHeaders(hdr))) } signed, err := jws.Sign(payload, signOptions...) require.NoError(t, err, `jws.SignMulti should succeed`) testcases := []struct { Name string FetchOptions func() []jwk.FetchOption Error bool }{ { Name: "Fail without whitelist", Error: true, }, { Name: "Success", FetchOptions: func() []jwk.FetchOption { return []jwk.FetchOption{ jwk.WithFetchWhitelist(jwk.InsecureWhitelist{}), } }, }, { Name: "Rejected by whitelist", Error: true, FetchOptions: func() []jwk.FetchOption { wl := jwk.NewMapWhitelist().Add(`https://github.com/lestrrat-go/jwx/v3`) return []jwk.FetchOption{ jwk.WithFetchWhitelist(wl), } }, }, } for _, tc := range testcases { t.Run(tc.Name, func(t *testing.T) { m := jws.NewMessage() var options []jwk.FetchOption if fn := tc.FetchOptions; fn != nil { options = fn() } options = append(options, jwk.WithHTTPClient(srv.Client())) decoded, err := jws.Verify(signed, jws.WithVerifyAuto(nil, options...), jws.WithMessage(m)) if tc.Error { require.Error(t, err, `jws.Verify should fail`) } else { require.NoError(t, err, `jws.Verify should succeed`) require.Equal(t, payload, decoded, `decoded payload should match`) // XXX This actually doesn't really test much, but if there was anything // wrong, the process should have failed well before reaching here require.Equal(t, payload, m.Payload(), "message payload matches") } }) } }) } func TestAlgorithmsForKey(t *testing.T) { rsaprivkey, err := jwxtest.GenerateRsaJwk() require.NoError(t, err, `jwxtest.GenerateRsaPrivateKey should succeed`) rsapubkey, err := rsaprivkey.PublicKey() require.NoError(t, err, `jwk (RSA) PublicKey() should succeed`) ecdsaprivkey, err := jwxtest.GenerateEcdsaJwk() require.NoError(t, err, `jwxtest.GenerateEcdsaPrivateKey should succeed`) ecdsapubkey, err := ecdsaprivkey.PublicKey() require.NoError(t, err, `jwk (ECDSA) PublicKey() should succeed`) testcases := []struct { Name string Key any Expected []jwa.SignatureAlgorithm }{ { Name: "Octet sequence", Key: []byte("hello"), Expected: []jwa.SignatureAlgorithm{jwa.HS256(), jwa.HS384(), jwa.HS512()}, }, { Name: "rsa.PublicKey", Key: rsa.PublicKey{}, Expected: []jwa.SignatureAlgorithm{jwa.RS256(), jwa.RS384(), jwa.RS512(), jwa.PS256(), jwa.PS384(), jwa.PS512()}, }, { Name: "*rsa.PublicKey", Key: &rsa.PublicKey{}, Expected: []jwa.SignatureAlgorithm{jwa.RS256(), jwa.RS384(), jwa.RS512(), jwa.PS256(), jwa.PS384(), jwa.PS512()}, }, { Name: "jwk.RSAPublicKey", Key: rsapubkey, Expected: []jwa.SignatureAlgorithm{jwa.RS256(), jwa.RS384(), jwa.RS512(), jwa.PS256(), jwa.PS384(), jwa.PS512()}, }, { Name: "ecdsa.PublicKey", Key: ecdsa.PublicKey{}, Expected: []jwa.SignatureAlgorithm{jwa.ES256(), jwa.ES384(), jwa.ES512()}, }, { Name: "*ecdsa.PublicKey", Key: &ecdsa.PublicKey{}, Expected: []jwa.SignatureAlgorithm{jwa.ES256(), jwa.ES384(), jwa.ES512()}, }, { Name: "jwk.ECDSAPublicKey", Key: ecdsapubkey, Expected: []jwa.SignatureAlgorithm{jwa.ES256(), jwa.ES384(), jwa.ES512()}, }, { Name: "rsa.PrivateKey", Key: rsa.PrivateKey{}, Expected: []jwa.SignatureAlgorithm{jwa.RS256(), jwa.RS384(), jwa.RS512(), jwa.PS256(), jwa.PS384(), jwa.PS512()}, }, { Name: "*rsa.PrivateKey", Key: &rsa.PrivateKey{}, Expected: []jwa.SignatureAlgorithm{jwa.RS256(), jwa.RS384(), jwa.RS512(), jwa.PS256(), jwa.PS384(), jwa.PS512()}, }, { Name: "jwk.RSAPrivateKey", Key: rsapubkey, Expected: []jwa.SignatureAlgorithm{jwa.RS256(), jwa.RS384(), jwa.RS512(), jwa.PS256(), jwa.PS384(), jwa.PS512()}, }, { Name: "ecdsa.PrivateKey", Key: ecdsa.PrivateKey{}, Expected: []jwa.SignatureAlgorithm{jwa.ES256(), jwa.ES384(), jwa.ES512()}, }, { Name: "*ecdsa.PrivateKey", Key: &ecdsa.PrivateKey{}, Expected: []jwa.SignatureAlgorithm{jwa.ES256(), jwa.ES384(), jwa.ES512()}, }, { Name: "jwk.ECDSAPrivateKey", Key: ecdsaprivkey, Expected: []jwa.SignatureAlgorithm{jwa.ES256(), jwa.ES384(), jwa.ES512()}, }, { Name: "ed25519.PublicKey", Key: ed25519.PublicKey(nil), Expected: []jwa.SignatureAlgorithm{jwa.EdDSA()}, }, { Name: "x25519.PublicKey", Key: &ecdh.PublicKey{}, Expected: []jwa.SignatureAlgorithm{jwa.EdDSA()}, }, } for _, tc := range testcases { if hasES256K { if strings.Contains(strings.ToLower(tc.Name), `ecdsa`) { tc.Expected = append(tc.Expected, jwa.ES256K()) } } sort.Slice(tc.Expected, func(i, j int) bool { return tc.Expected[i].String() < tc.Expected[j].String() }) t.Run(tc.Name, func(t *testing.T) { algs, err := jws.AlgorithmsForKey(tc.Key) require.NoError(t, err, `jws.AlgorithmsForKey should succeed`) sort.Slice(algs, func(i, j int) bool { return algs[i].String() < algs[j].String() }) require.Equal(t, tc.Expected, algs, `results should match`) }) } } func TestGH681(t *testing.T) { privkey, err := jwxtest.GenerateRsaKey() require.NoError(t, err, "failed to create private key") buf, err := jws.Sign(nil, jws.WithKey(jwa.RS256(), privkey), jws.WithDetachedPayload([]byte("Lorem ipsum"))) require.NoError(t, err, "failed to sign payload") t.Logf("%s", buf) _, err = jws.Verify(buf, jws.WithKey(jwa.RS256(), &privkey.PublicKey), jws.WithDetachedPayload([]byte("Lorem ipsum"))) require.NoError(t, err, "failed to verify JWS message") } func TestGH840(t *testing.T) { // Go 1.19+ panics if elliptic curve operations are called against // a point that's _NOT_ on the curve untrustedJWK := []byte(`{ "kty": "EC", "crv": "P-256", "x": "MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqx7D4", "y": "4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM", "d": "870MB6gfuTJ4HtUnUvYMyJpr5eUZNP4Bk43bVdj3eAE" }`) // Parse, serialize, slice and dice JWKs! privkey, err := jwk.ParseKey(untrustedJWK) require.NoError(t, err, `jwk.ParseKey should succeed`) pubkey, err := jwk.PublicKeyOf(privkey) require.NoError(t, err, `jwk.PublicKeyOf should succeed`) tok, err := jwt.NewBuilder(). Issuer(`github.com/lestrrat-go/jwx`). IssuedAt(time.Now()). Build() require.NoError(t, err, `jwt.NewBuilder should succeed`) // As of go1.24.0, generating a signature with a private key that has // X/Y that's not on the curve will fail, but all go < 1.24 will succeed. // Instead of checking the version, we'll just check if the operation fails, // and if it does we won't run the check for jwt.Parse signed, err := jwt.Sign(tok, jwt.WithKey(jwa.ES256(), privkey)) if err != nil { require.Error(t, err, `jwt.Sign should fail`) return } require.NoError(t, err, `jwt.Sign should succeed`) _, err = jwt.Parse(signed, jwt.WithKey(jwa.ES256(), pubkey)) require.Error(t, err, `jwt.Parse should FAIL`) // pubkey's X/Y is not on the curve } func TestGH888(t *testing.T) { // This should fail because we're passing multiple keys (i.e. multiple signatures) // and yet we haven't specified JSON serialization _, err := jws.Sign([]byte(`foo`), jws.WithInsecureNoSignature(), jws.WithKey(jwa.HS256(), []byte(`bar`))) require.Error(t, err, `jws.Sign with multiple keys (including alg=none) should fail`) // This should pass because we can now have multiple signatures with JSON serialization signed, err := jws.Sign([]byte(`foo`), jws.WithInsecureNoSignature(), jws.WithKey(jwa.HS256(), []byte(`bar`)), jws.WithJSON()) require.NoError(t, err, `jws.Sign should succeed`) message, err := jws.Parse(signed) require.NoError(t, err, `jws.Parse should succeed`) // Look for alg=none signature var foundNoSignature bool for _, sig := range message.Signatures() { if v, ok := sig.ProtectedHeaders().Algorithm(); !ok || v != jwa.NoSignature() { continue } require.Nil(t, sig.Signature(), `signature must be nil for alg=none`) foundNoSignature = true } require.True(t, foundNoSignature, `signature with no signature was found`) _, err = jws.Verify(signed) require.Error(t, err, `jws.Verify should fail`) _, err = jws.Verify(signed, jws.WithKey(jwa.NoSignature(), nil)) require.Error(t, err, `jws.Verify should fail`) // Note: you can't do jws.Verify(..., jws.WithInsecureNoSignature()) verified, err := jws.Verify(signed, jws.WithKey(jwa.HS256(), []byte(`bar`))) 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{} var sha256Algo = jwa.NewSignatureAlgorithm("SillyTest256") func (s256SignerVerifier) Algorithm() jwa.SignatureAlgorithm { return sha256Algo } func (s256SignerVerifier) Sign(payload []byte, _ any) ([]byte, error) { h := sha256.Sum256(payload) return h[:], nil } func (s256SignerVerifier) Verify(payload, signature []byte, _ any) error { h := sha256.Sum256(payload) if !bytes.Equal(h[:], signature) { return fmt.Errorf("invalid signature: expected %q, got %q", base64.EncodeToString(h[:]), base64.EncodeToString(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 })) t.Cleanup(func() { jws.UnregisterSigner(sha256Algo) }) jws.RegisterVerifier(sha256Algo, jws.VerifierFactoryFn(func() (jws.Verifier, error) { return s256SignerVerifier{}, nil })) t.Cleanup(func() { jws.UnregisterVerifier(sha256Algo) jwa.UnregisterSignatureAlgorithm(sha256Algo) }) // 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`) jws.UnregisterSigner(sha256Algo) // Now try after unregistering the signer for the algorithm _, err = jws.Sign([]byte(src), jws.WithKey(sha256Algo, nil)) require.Error(t, err, `jws.Sign should succeed`) jws.RegisterSigner(sha256Algo, jws.SignerFactoryFn(func() (jws.Signer, error) { return s256SignerVerifier{}, nil })) _, err = jws.Sign([]byte(src), jws.WithKey(sha256Algo, nil)) require.NoError(t, err, `jws.Sign should succeed`) } func TestUnpaddedSignatureR(t *testing.T) { // I brute-forced generating a key and signature where the R portion // of the signature was not padded by using the following code in the // first run, then copied the result to the test /* for i := 0; i < 10000; i++ { rawKey, err := jwxtest.GenerateEcdsaKey(jwa.P256) require.NoError(t, err, `jwxtest.GenerateEcdsaJwk should succeed`) key, err := jwk.Import(rawKey) require.NoError(t, err, `jwk.Import should succeed`) pubkey, _ := key.PublicKey() signed, err := jws.Sign([]byte("Lorem Ipsum"), jws.WithKey(jwa.ES256(), key)) require.NoError(t, err, `jws.Sign should succeed`) message, err := jws.Parse(signed) require.NoError(t, err, `jws.Parse should succeed`) asJson, _ := json.Marshal(message) t.Logf("%s", asJson) for _, sig := range message.Signatures() { sigBytes := sig.Signature() if sigBytes[0] == 0x00 { // Found it! t.Logf("Found signature that can be unpadded.") t.Logf("Original signature: %q", base64.EncodeToString(sigBytes)) // unpaddedSig := append(sigBytes[1:31], sigBytes[32:]...) unpaddedSig := sigBytes[1:] t.Logf("Signature with first byte of R removed: %q", base64.EncodeToString(unpaddedSig)) t.Logf("Original JWS payload: %q", signed) require.Len(t, unpaddedSig, 63) i := bytes.LastIndexByte(signed, tokens.Period) modified := append(signed[:i+1], base64.Encode(unpaddedSig)...) t.Logf("JWS payload with unpadded signature: %q", modified) // jws.Verify for sanity verified, err := jws.Verify(modified, jws.WithKey(jwa.ES256(), pubkey)) require.NoError(t, err, `jws.Verify should succeed`) t.Logf("verified payload: %q", verified) buf, _ := json.Marshal(key) t.Logf("Private JWK: %s", buf) return } } } */ // Padded has R with a leading 0 (as it should) padded := "eyJhbGciOiJFUzI1NiJ9.TG9yZW0gSXBzdW0.ALFru4CRZDiAlVKyyHtlLGtXIAWxC3lXIlZuYO8G8a5ePzCwyw6c2FzWBZwrLaoLFZb_TcYs3TcZ8mhONPaavQ" // Unpadded has R with a leading 0 removed (31 bytes, WRONG) unpadded := "eyJhbGciOiJFUzI1NiJ9.TG9yZW0gSXBzdW0.sWu7gJFkOICVUrLIe2Usa1cgBbELeVciVm5g7wbxrl4_MLDLDpzYXNYFnCstqgsVlv9NxizdNxnyaE409pq9" // This is the private key used to sign the payload keySrc := `{"crv":"P-256","d":"MqGwMl-dlJFrMnu7rFyslPV8EdsVC7I4V19N-ADVqaU","kty":"EC","x":"Anf1p2lRrcXgZKpVRRC1xLxPiw_45PbOlygfbxvD8Es","y":"d0HiZq-aurVVLLtK-xqXPpzpWloZJNwKNve7akBDuvg"}` privKey, err := jwk.ParseKey([]byte(keySrc)) require.NoError(t, err, `jwk.ParseKey should succeed`) pubKey, err := jwk.PublicKeyOf(privKey) require.NoError(t, err, `jwk.PublicKeyOf should succeed`) // Should always succeed payload, err := jws.Verify([]byte(padded), jws.WithKey(jwa.ES256(), pubKey)) require.NoError(t, err, `jws.Verify should succeed`) require.Equal(t, "Lorem Ipsum", string(payload)) // Should fail _, err = jws.Verify([]byte(unpadded), jws.WithKey(jwa.ES256(), pubKey)) require.Error(t, err, `jws.Verify should fail`) } func TestValidateKey(t *testing.T) { privKey, err := jwxtest.GenerateRsaJwk() require.NoError(t, err, `jwxtest.GenerateRsaJwk should succeed`) signed, err := jws.Sign([]byte("Lorem Ipsum"), jws.WithKey(jwa.RS256(), privKey), jws.WithValidateKey(true)) require.NoError(t, err, `jws.Sign should succeed`) // This should fail because D is empty require.NoError(t, privKey.Set(jwk.RSADKey, []byte(nil)), `jwk.Set should succeed`) _, err = jws.Sign([]byte("Lorem Ipsum"), jws.WithKey(jwa.RS256(), privKey), jws.WithValidateKey(true)) require.Error(t, err, `jws.Sign should fail`) pubKey, err := jwk.PublicKeyOf(privKey) require.NoError(t, err, `jwk.PublicKeyOf should succeed`) n, ok := pubKey.(jwk.RSAPublicKey).N() require.True(t, ok, `N should be present`) // Set N to an empty value require.NoError(t, pubKey.Set(jwk.RSANKey, []byte(nil)), `jwk.Set should succeed`) // This is going to fail regardless, because the public key is now // invalid (empty N), but we want to make sure that it fails because // of the validation failing _, err = jws.Verify(signed, jws.WithKey(jwa.RS256(), pubKey), jws.WithValidateKey(true)) require.Error(t, err, `jws.Verify should fail`) require.True(t, jwk.IsKeyValidationError(err), `jwk.IsKeyValidationError should return true`) // The following should now succeed, because N has been reinstated require.NoError(t, pubKey.Set(jwk.RSANKey, n), `jwk.Set should succeed`) _, err = jws.Verify(signed, jws.WithKey(jwa.RS256(), pubKey), jws.WithValidateKey(true)) require.NoError(t, err, `jws.Verify should succeed`) } func TestEmptyProtectedField(t *testing.T) { // MEMO: this was the only test case from the original report // This passes. It should produce an invalid JWS message, but // that's not `jws.Parse`'s problem. _, err := jws.Parse([]byte(`{"signature": ""}`)) require.NoError(t, err, `jws.Parse should fail`) // Also test that non-flattened serialization passes. _, err = jws.Parse([]byte(`{"signatures": [{}]}`)) require.NoError(t, err, `jws.Parse should fail`) // MEMO: rest of the cases are present to be extra pedantic about it privKey, err := jwxtest.GenerateRsaJwk() require.NoError(t, err, `jwxtest.GenerateRsaJwk should succeed`) // This fails. `jws.Parse` works, but the subsequent verification // workflow fails to verify anything without the presence of a signature or // a protected header. _, err = jws.Verify([]byte(`{"signature": ""}`), jws.WithKey(jwa.RS256(), privKey)) require.Error(t, err, `jws.Parse should fail`) // Create a valid signatre. signed, err := jws.Sign([]byte("Lorem Ipsum"), jws.WithKey(jwa.RS256(), privKey)) require.NoError(t, err, `jws.Sign should succeed`) _, payload, signature, err := jws.SplitCompact(signed) require.NoError(t, err, `jws.SplitCompact should succeed`) // This fails as well. we have a valid signature and a valid // key to verify it, but no protected headers _, err = jws.Verify( fmt.Appendf(nil, `{"signature": "%s"}`, signature), jws.WithKey(jwa.RS256(), privKey), ) require.Error(t, err, `jws.Verify should fail`) // Test for cases when we have an incomplete compact form JWS var buf bytes.Buffer buf.WriteRune(tokens.Period) buf.Write(payload) buf.WriteRune(tokens.Period) buf.Write(signature) invalidMessage := buf.Bytes() // This is an error because the format is simply wrong. // Whereas in the other JSON-based JWS's case the lack of protected field // is not a SYNTAX error, this one is, and therefore we barf. _, err = jws.Parse(invalidMessage) require.Error(t, err, `jws.Parse should fail`) } func TestParseFormat(t *testing.T) { privKey, err := jwxtest.GenerateRsaJwk() require.NoError(t, err, `jwxtest.GenerateRsaJwk should succeed`) signedCompact, err := jws.Sign([]byte("Lorem Ipsum"), jws.WithKey(jwa.RS256(), privKey), jws.WithValidateKey(true)) require.NoError(t, err, `jws.Sign should succeed`) signedJSON, err := jws.Sign([]byte("Lorem Ipsum"), jws.WithKey(jwa.RS256(), privKey), jws.WithValidateKey(true), jws.WithJSON()) require.NoError(t, err, `jws.Sign should succeed`) // Only compact formats should succeed _, err = jws.Verify(signedCompact, jws.WithKey(jwa.RS256(), privKey), jws.WithCompact()) require.NoError(t, err, `jws.Verify should succeed`) _, err = jws.Verify(signedJSON, jws.WithKey(jwa.RS256(), privKey), jws.WithCompact()) require.Error(t, err, `jws.Verify should fail`) _, err = jws.Parse(signedCompact, jws.WithCompact()) require.NoError(t, err, `jws.Parse should succeed`) _, err = jws.Parse(signedJSON, jws.WithCompact()) require.Error(t, err, `jws.Parse should fail`) // Only JSON formats should succeed _, err = jws.Verify(signedCompact, jws.WithKey(jwa.RS256(), privKey), jws.WithJSON()) require.Error(t, err, `jws.Verify should fail`) _, err = jws.Verify(signedJSON, jws.WithKey(jwa.RS256(), privKey), jws.WithJSON()) require.NoError(t, err, `jws.Verify should succeed`) _, err = jws.Parse(signedJSON, jws.WithJSON()) require.NoError(t, err, `jws.Parse should succeed`) _, err = jws.Parse(signedCompact, jws.WithJSON()) require.Error(t, err, `jws.Parse should fail`) // Either format should succeed _, err = jws.Verify(signedCompact, jws.WithKey(jwa.RS256(), privKey)) require.NoError(t, err, `jws.Verify should succeed`) _, err = jws.Verify(signedCompact, jws.WithKey(jwa.RS256(), privKey), jws.WithJSON(), jws.WithCompact()) require.NoError(t, err, `jws.Verify should succeed`) _, err = jws.Parse(signedCompact) require.NoError(t, err, `jws.Parse should succeed`) _, err = jws.Parse(signedCompact, jws.WithJSON(), jws.WithCompact()) require.NoError(t, err, `jws.Parse should succeed`) _, err = jws.Verify(signedJSON, jws.WithKey(jwa.RS256(), privKey)) require.NoError(t, err, `jws.Verify should succeed`) _, err = jws.Verify(signedJSON, jws.WithKey(jwa.RS256(), privKey), jws.WithJSON(), jws.WithCompact()) require.NoError(t, err, `jws.Verify should succeed`) _, err = jws.Parse(signedJSON) require.NoError(t, err, `jws.Parse should succeed`) _, err = jws.Parse(signedJSON, jws.WithJSON(), jws.WithCompact()) require.NoError(t, err, `jws.Parse should succeed`) } func BenchmarkSplitCompat(b *testing.B) { for b.Loop() { _, _, _, err := jws.SplitCompact([]byte(exampleCompactSerialization)) if err != nil { panic(err) } } } func BenchmarkSplitCompatString(b *testing.B) { for b.Loop() { _, _, _, err := jws.SplitCompactString(exampleCompactSerialization) if err != nil { panic(err) } } } golang-github-lestrrat-go-jwx-3.0.13/jws/jwsbb/000077500000000000000000000000001515060566400213165ustar00rootroot00000000000000golang-github-lestrrat-go-jwx-3.0.13/jws/jwsbb/BUILD.bazel000066400000000000000000000015341515060566400231770ustar00rootroot00000000000000load("@rules_go//go:def.bzl", "go_library", "go_test") go_library( name = "jwsbb", srcs = [ "crypto_signer.go", "ecdsa.go", "eddsa.go", "format.go", "hmac.go", "jwsbb.go", "rsa.go", "sign.go", "verify.go", ], importpath = "github.com/lestrrat-go/jwx/v3/jws/jwsbb", visibility = ["//visibility:public"], deps = [ "//internal/base64", "//internal/ecutil", "//internal/jwxio", "//internal/keyconv", "//internal/pool", "//internal/tokens", "//jws/internal/keytype", "@com_github_lestrrat_go_dsig//:dsig", ], ) go_test( name = "jwsbb_test", srcs = ["jwsbb_test.go"], embed = [":jwsbb"], deps = [ "//internal/base64", "@com_github_stretchr_testify//require", ], ) golang-github-lestrrat-go-jwx-3.0.13/jws/jwsbb/crypto_signer.go000066400000000000000000000027441515060566400245430ustar00rootroot00000000000000package jwsbb import ( "crypto" "crypto/rand" "fmt" "io" ) // cryptosign is a low-level function that signs a payload using a crypto.Signer. // If hash is crypto.Hash(0), the payload is signed directly without hashing. // Otherwise, the payload is hashed using the specified hash function before signing. // // rr is an io.Reader that provides randomness for signing. If rr is nil, it defaults to rand.Reader. func cryptosign(signer crypto.Signer, payload []byte, hash crypto.Hash, opts crypto.SignerOpts, rr io.Reader) ([]byte, error) { if rr == nil { rr = rand.Reader } var digest []byte if hash == crypto.Hash(0) { digest = payload } else { h := hash.New() if _, err := h.Write(payload); err != nil { return nil, fmt.Errorf(`failed to write payload to hash: %w`, err) } digest = h.Sum(nil) } return signer.Sign(rr, digest, opts) } // SignCryptoSigner generates a signature using a crypto.Signer interface. // This function can be used for hardware security modules, smart cards, // and other implementations of the crypto.Signer interface. // // rr is an io.Reader that provides randomness for signing. If rr is nil, it defaults to rand.Reader. // // Returns the signature bytes or an error if signing fails. func SignCryptoSigner(signer crypto.Signer, raw []byte, h crypto.Hash, opts crypto.SignerOpts, rr io.Reader) ([]byte, error) { if signer == nil { return nil, fmt.Errorf("jwsbb.SignCryptoSignerRaw: signer is nil") } return cryptosign(signer, raw, h, opts, rr) } golang-github-lestrrat-go-jwx-3.0.13/jws/jwsbb/ecdsa.go000066400000000000000000000144401515060566400227270ustar00rootroot00000000000000package jwsbb import ( "crypto" "crypto/ecdsa" "encoding/asn1" "fmt" "io" "math/big" "github.com/lestrrat-go/dsig" "github.com/lestrrat-go/jwx/v3/internal/ecutil" ) // ecdsaHashToDsigAlgorithm maps ECDSA hash functions to dsig algorithm constants func ecdsaHashToDsigAlgorithm(h crypto.Hash) (string, error) { switch h { case crypto.SHA256: return dsig.ECDSAWithP256AndSHA256, nil case crypto.SHA384: return dsig.ECDSAWithP384AndSHA384, nil case crypto.SHA512: return dsig.ECDSAWithP521AndSHA512, nil default: return "", fmt.Errorf("unsupported ECDSA hash function: %v", h) } } // UnpackASN1ECDSASignature unpacks an ASN.1 encoded ECDSA signature into r and s values. // This is typically used when working with crypto.Signer interfaces that return ASN.1 encoded signatures. func UnpackASN1ECDSASignature(signed []byte, r, s *big.Int) error { // Okay, this is silly, but hear me out. When we use the // crypto.Signer interface, the PrivateKey is hidden. // But we need some information about the key (its bit size). // // So while silly, we're going to have to make another call // here and fetch the Public key. // (This probably means that this information should be cached somewhere) var p struct { R *big.Int // TODO: get this from a pool? S *big.Int } if _, err := asn1.Unmarshal(signed, &p); err != nil { return fmt.Errorf(`failed to unmarshal ASN1 encoded signature: %w`, err) } r.Set(p.R) s.Set(p.S) return nil } // UnpackECDSASignature unpacks a JWS-format ECDSA signature into r and s values. // The signature should be in the format specified by RFC 7515 (r||s as fixed-length byte arrays). func UnpackECDSASignature(signature []byte, pubkey *ecdsa.PublicKey, r, s *big.Int) error { keySize := ecutil.CalculateKeySize(pubkey.Curve) if len(signature) != keySize*2 { return fmt.Errorf(`invalid signature length for curve %q`, pubkey.Curve.Params().Name) } r.SetBytes(signature[:keySize]) s.SetBytes(signature[keySize:]) return nil } // PackECDSASignature packs the r and s values from an ECDSA signature into a JWS-format byte slice. // The output format follows RFC 7515: r||s as fixed-length byte arrays. func PackECDSASignature(r *big.Int, sbig *big.Int, curveBits int) ([]byte, error) { keyBytes := curveBits / 8 if curveBits%8 > 0 { keyBytes++ } // Serialize r and s into fixed-length bytes rBytes := r.Bytes() rBytesPadded := make([]byte, keyBytes) copy(rBytesPadded[keyBytes-len(rBytes):], rBytes) sBytes := sbig.Bytes() sBytesPadded := make([]byte, keyBytes) copy(sBytesPadded[keyBytes-len(sBytes):], sBytes) // Output as r||s return append(rBytesPadded, sBytesPadded...), nil } // SignECDSA generates an ECDSA signature for the given payload using the specified private key and hash. // The raw parameter should be the pre-computed signing input (typically header.payload). // // rr is an io.Reader that provides randomness for signing. if rr is nil, it defaults to rand.Reader. // // This function is now a thin wrapper around dsig.SignECDSA. For new projects, you should // consider using dsig instead of this function. func SignECDSA(key *ecdsa.PrivateKey, payload []byte, h crypto.Hash, rr io.Reader) ([]byte, error) { dsigAlg, err := ecdsaHashToDsigAlgorithm(h) if err != nil { return nil, fmt.Errorf("jwsbb.SignECDSA: %w", err) } return dsig.Sign(key, dsigAlg, payload, rr) } // SignECDSACryptoSigner generates an ECDSA signature using a crypto.Signer interface. // This function works with hardware security modules and other crypto.Signer implementations. // The signature is converted from ASN.1 format to JWS format (r||s). // // rr is an io.Reader that provides randomness for signing. If rr is nil, it defaults to rand.Reader. func SignECDSACryptoSigner(signer crypto.Signer, raw []byte, h crypto.Hash, rr io.Reader) ([]byte, error) { signed, err := SignCryptoSigner(signer, raw, h, h, rr) if err != nil { return nil, fmt.Errorf(`failed to sign payload using crypto.Signer: %w`, err) } return signECDSACryptoSigner(signer, signed) } func signECDSACryptoSigner(signer crypto.Signer, signed []byte) ([]byte, error) { cpub := signer.Public() pubkey, ok := cpub.(*ecdsa.PublicKey) if !ok { return nil, fmt.Errorf(`expected *ecdsa.PublicKey, got %T`, pubkey) } curveBits := pubkey.Curve.Params().BitSize var r, s big.Int if err := UnpackASN1ECDSASignature(signed, &r, &s); err != nil { return nil, fmt.Errorf(`failed to unpack ASN1 encoded signature: %w`, err) } return PackECDSASignature(&r, &s, curveBits) } func ecdsaVerify(key *ecdsa.PublicKey, buf []byte, h crypto.Hash, r, s *big.Int) error { hasher := h.New() hasher.Write(buf) digest := hasher.Sum(nil) if !ecdsa.Verify(key, digest, r, s) { return fmt.Errorf("jwsbb.ECDSAVerifier: invalid ECDSA signature") } return nil } // VerifyECDSA verifies an ECDSA signature for the given payload. // This function verifies the signature using the specified public key and hash algorithm. // The payload parameter should be the pre-computed signing input (typically header.payload). // // This function is now a thin wrapper around dsig.VerifyECDSA. For new projects, you should // consider using dsig instead of this function. func VerifyECDSA(key *ecdsa.PublicKey, payload, signature []byte, h crypto.Hash) error { dsigAlg, err := ecdsaHashToDsigAlgorithm(h) if err != nil { return fmt.Errorf("jwsbb.VerifyECDSA: %w", err) } return dsig.Verify(key, dsigAlg, payload, signature) } // VerifyECDSACryptoSigner verifies an ECDSA signature for crypto.Signer implementations. // This function is useful for verifying signatures created by hardware security modules // or other implementations of the crypto.Signer interface. // The payload parameter should be the pre-computed signing input (typically header.payload). func VerifyECDSACryptoSigner(signer crypto.Signer, payload, signature []byte, h crypto.Hash) error { var pubkey *ecdsa.PublicKey switch cpub := signer.Public(); cpub := cpub.(type) { case ecdsa.PublicKey: pubkey = &cpub case *ecdsa.PublicKey: pubkey = cpub default: return fmt.Errorf(`jwsbb.VerifyECDSACryptoSigner: expected *ecdsa.PublicKey, got %T`, cpub) } var r, s big.Int if err := UnpackECDSASignature(signature, pubkey, &r, &s); err != nil { return fmt.Errorf("jwsbb.ECDSAVerifier: failed to unpack ASN.1 encoded ECDSA signature: %w", err) } return ecdsaVerify(pubkey, payload, h, &r, &s) } golang-github-lestrrat-go-jwx-3.0.13/jws/jwsbb/eddsa.go000066400000000000000000000024531515060566400227310ustar00rootroot00000000000000package jwsbb import ( "crypto/ed25519" "github.com/lestrrat-go/dsig" ) // SignEdDSA generates an EdDSA (Ed25519) signature for the given payload. // The raw parameter should be the pre-computed signing input (typically header.payload). // EdDSA is deterministic and doesn't require additional hashing of the input. // // This function is now a thin wrapper around dsig.SignEdDSA. For new projects, you should // consider using dsig instead of this function. func SignEdDSA(key ed25519.PrivateKey, payload []byte) ([]byte, error) { // Use dsig.Sign with EdDSA algorithm constant return dsig.Sign(key, dsig.EdDSA, payload, nil) } // VerifyEdDSA verifies an EdDSA (Ed25519) signature for the given payload. // This function verifies the signature using Ed25519 verification algorithm. // The payload parameter should be the pre-computed signing input (typically header.payload). // EdDSA is deterministic and provides strong security guarantees without requiring hash function selection. // // This function is now a thin wrapper around dsig.VerifyEdDSA. For new projects, you should // consider using dsig instead of this function. func VerifyEdDSA(key ed25519.PublicKey, payload, signature []byte) error { // Use dsig.Verify with EdDSA algorithm constant return dsig.Verify(key, dsig.EdDSA, payload, signature) } golang-github-lestrrat-go-jwx-3.0.13/jws/jwsbb/es256k.go000066400000000000000000000004201515060566400226600ustar00rootroot00000000000000//go:build jwx_es256k package jwsbb import ( dsigsecp256k1 "github.com/lestrrat-go/dsig-secp256k1" ) const es256k = "ES256K" func init() { // Add ES256K mapping when this build tag is enabled jwsToDsigAlgorithm[es256k] = dsigsecp256k1.ECDSAWithSecp256k1AndSHA256 } golang-github-lestrrat-go-jwx-3.0.13/jws/jwsbb/es256k_test.go000066400000000000000000000023441515060566400237260ustar00rootroot00000000000000//go:build jwx_es256k package jwsbb_test import ( "crypto/ecdsa" "crypto/rand" "testing" dsigsecp256k1 "github.com/lestrrat-go/dsig-secp256k1" "github.com/lestrrat-go/jwx/v3/jws/jwsbb" "github.com/stretchr/testify/require" ) func TestES256KWithSecp256k1(t *testing.T) { payload := []byte("hello world") // Generate a secp256k1 key (the actual curve ES256K should use) secp256k1Key, err := ecdsa.GenerateKey(dsigsecp256k1.Curve(), rand.Reader) require.NoError(t, err, "secp256k1 key generation should succeed") // Test Sign with ES256K algorithm using secp256k1 key signature, err := jwsbb.Sign(secp256k1Key, "ES256K", payload, nil) require.NoError(t, err, "ES256K signing should not error") // Test Verify with ES256K algorithm using secp256k1 key err = jwsbb.Verify(&secp256k1Key.PublicKey, "ES256K", payload, signature) require.NoError(t, err, "ES256K verification should succeed") // Verify that a tampered signature fails tamperedSig := make([]byte, len(signature)) copy(tamperedSig, signature) if len(tamperedSig) > 0 { tamperedSig[0] ^= 1 // flip a bit } err = jwsbb.Verify(&secp256k1Key.PublicKey, "ES256K", payload, tamperedSig) require.Error(t, err, "ES256K verification should fail for tampered signature") } golang-github-lestrrat-go-jwx-3.0.13/jws/jwsbb/format.go000066400000000000000000000174071515060566400231460ustar00rootroot00000000000000package jwsbb import ( "bytes" "errors" "io" "github.com/lestrrat-go/jwx/v3/internal/base64" "github.com/lestrrat-go/jwx/v3/internal/jwxio" "github.com/lestrrat-go/jwx/v3/internal/tokens" ) // SignBuffer combines the base64-encoded header and payload into a single byte slice // for signing purposes. This creates the signing input according to JWS specification (RFC 7515). // The result should be passed to signature generation functions. // // Parameters: // - buf: Reusable buffer (can be nil for automatic allocation) // - hdr: Raw header bytes (will be base64-encoded) // - payload: Raw payload bytes (encoded based on encodePayload flag) // - encoder: Base64 encoder to use for encoding components // - encodePayload: If true, payload is base64-encoded; if false, payload is used as-is // // Returns the constructed signing input in the format: base64(header).base64(payload) or base64(header).payload func SignBuffer(buf, hdr, payload []byte, encoder base64.Encoder, encodePayload bool) []byte { l := encoder.EncodedLen(len(hdr)+len(payload)) + 1 if cap(buf) < l { buf = make([]byte, 0, l) } buf = buf[:0] buf = encoder.AppendEncode(buf, hdr) buf = append(buf, tokens.Period) if encodePayload { buf = encoder.AppendEncode(buf, payload) } else { buf = append(buf, payload...) } return buf } // AppendSignature appends a base64-encoded signature to a JWS signing input buffer. // This completes the compact JWS serialization by adding the final signature component. // The input buffer should contain the signing input (header.payload), and this function // adds the period separator and base64-encoded signature. // // Parameters: // - buf: Buffer containing the signing input (typically from SignBuffer) // - signature: Raw signature bytes (will be base64-encoded) // - encoder: Base64 encoder to use for encoding the signature // // Returns the complete compact JWS in the format: base64(header).base64(payload).base64(signature) func AppendSignature(buf, signature []byte, encoder base64.Encoder) []byte { l := len(buf) + len(signature) + 1 if cap(buf) < l { buf = make([]byte, 0, l) } buf = append(buf, tokens.Period) buf = encoder.AppendEncode(buf, signature) return buf } // JoinCompact creates a complete compact JWS serialization from individual components. // This is a one-step function that combines header, payload, and signature into the final JWS format. // It includes safety checks to prevent excessive memory allocation. // // Parameters: // - buf: Reusable buffer (can be nil for automatic allocation) // - hdr: Raw header bytes (will be base64-encoded) // - payload: Raw payload bytes (encoded based on encodePayload flag) // - signature: Raw signature bytes (will be base64-encoded) // - encoder: Base64 encoder to use for encoding all components // - encodePayload: If true, payload is base64-encoded; if false, payload is used as-is // // Returns the complete compact JWS or an error if the total size exceeds safety limits (1GB). func JoinCompact(buf, hdr, payload, signature []byte, encoder base64.Encoder, encodePayload bool) ([]byte, error) { const MaxBufferSize = 1 << 30 // 1 GB totalSize := len(hdr) + len(payload) + len(signature) + 2 if totalSize > MaxBufferSize { return nil, errors.New("input sizes exceed maximum allowable buffer size") } if cap(buf) < totalSize { buf = make([]byte, 0, totalSize) } buf = buf[:0] buf = encoder.AppendEncode(buf, hdr) buf = append(buf, tokens.Period) if encodePayload { buf = encoder.AppendEncode(buf, payload) } else { buf = append(buf, payload...) } buf = append(buf, tokens.Period) buf = encoder.AppendEncode(buf, signature) return buf, nil } var compactDelim = []byte{tokens.Period} var errInvalidNumberOfSegments = errors.New(`jwsbb: invalid number of segments`) // InvalidNumberOfSegmentsError returns the standard error for invalid JWS segment count. // A valid compact JWS must have exactly 3 segments separated by periods: header.payload.signature func InvalidNumberOfSegmentsError() error { return errInvalidNumberOfSegments } // SplitCompact parses a compact JWS serialization into its three components. // This function validates that the input has exactly 3 segments separated by periods // and returns the base64-encoded components without decoding them. // // Parameters: // - src: Complete compact JWS string as bytes // // Returns: // - protected: Base64-encoded protected header // - payload: Base64-encoded payload (or raw payload if b64=false was used) // - signature: Base64-encoded signature // - err: Error if the format is invalid or segment count is wrong func SplitCompact(src []byte) (protected, payload, signature []byte, err error) { var s []byte var ok bool protected, s, ok = bytes.Cut(src, compactDelim) if !ok { // no period found return nil, nil, nil, InvalidNumberOfSegmentsError() } payload, s, ok = bytes.Cut(s, compactDelim) if !ok { // only one period found return nil, nil, nil, InvalidNumberOfSegmentsError() } signature, _, ok = bytes.Cut(s, compactDelim) if ok { // three periods found return nil, nil, nil, InvalidNumberOfSegmentsError() } return protected, payload, signature, nil } // SplitCompactString is a convenience wrapper around SplitCompact for string inputs. // It converts the string to bytes and parses the compact JWS serialization. // // Parameters: // - src: Complete compact JWS as a string // // Returns the same components as SplitCompact: protected header, payload, signature, and error. func SplitCompactString(src string) (protected, payload, signature []byte, err error) { return SplitCompact([]byte(src)) } // SplitCompactReader parses a compact JWS serialization from an io.Reader. // This function handles both finite and streaming sources efficiently. // For finite sources, it reads all data at once. For streaming sources, // it uses a buffer-based approach to find segment boundaries. // // Parameters: // - rdr: Reader containing the compact JWS data // // Returns: // - protected: Base64-encoded protected header // - payload: Base64-encoded payload (or raw payload if b64=false was used) // - signature: Base64-encoded signature // - err: Error if reading fails or the format is invalid // // The function validates that exactly 3 segments are present, separated by periods. func SplitCompactReader(rdr io.Reader) (protected, payload, signature []byte, err error) { data, err := jwxio.ReadAllFromFiniteSource(rdr) if err == nil { return SplitCompact(data) } if !errors.Is(err, jwxio.NonFiniteSourceError()) { return nil, nil, nil, err } var periods int var state int buf := make([]byte, 4096) var sofar []byte for { // read next bytes n, err := rdr.Read(buf) // return on unexpected read error if err != nil && err != io.EOF { return nil, nil, nil, io.ErrUnexpectedEOF } // append to current buffer sofar = append(sofar, buf[:n]...) // loop to capture multiple tokens.Period in current buffer for loop := true; loop; { var i = bytes.IndexByte(sofar, tokens.Period) if i == -1 && err != io.EOF { // no tokens.Period found -> exit and read next bytes (outer loop) loop = false continue } else if i == -1 && err == io.EOF { // no tokens.Period found -> process rest and exit i = len(sofar) loop = false } else { // tokens.Period found periods++ } // Reaching this point means we have found a tokens.Period or EOF and process the rest of the buffer switch state { case 0: protected = sofar[:i] state++ case 1: payload = sofar[:i] state++ case 2: signature = sofar[:i] } // Shorten current buffer if len(sofar) > i { sofar = sofar[i+1:] } } // Exit on EOF if err == io.EOF { break } } if periods != 2 { return nil, nil, nil, InvalidNumberOfSegmentsError() } return protected, payload, signature, nil } golang-github-lestrrat-go-jwx-3.0.13/jws/jwsbb/header.go000066400000000000000000000153011515060566400230750ustar00rootroot00000000000000package jwsbb import ( "fmt" "github.com/lestrrat-go/jwx/v3/internal/base64" "github.com/valyala/fastjson" ) type headerNotFoundError struct { key string } func (e headerNotFoundError) Error() string { return fmt.Sprintf(`jwsbb: header "%s" not found`, e.key) } func (e headerNotFoundError) Is(target error) bool { switch target.(type) { case headerNotFoundError, *headerNotFoundError: // If the target is a headerNotFoundError or a pointer to it, we // consider it a match return true default: return false } } // ErrHeaderNotFound returns an error that can be passed to `errors.Is` to check if the error is // the result of the field not being found func ErrHeaderNotFound() error { return headerNotFoundError{} } // ErrFieldNotFound is an alias for ErrHeaderNotFound, and is deprecated. It was a misnomer. // It will be removed in a future release. func ErrFieldNotFound() error { return ErrHeaderNotFound() } // Header is an object that allows you to access the JWS header in a quick and // dirty way. It does not verify anything, it does not know anything about what // each header field means, and it does not care about the JWS specification. // But when you need to access the JWS header for that one field that you // need, this is the object you want to use. // // As of this writing, HeaderParser cannot be used from concurrent goroutines. // You will need to create a new instance for each goroutine that needs to parse a JWS header. // Also, in general values obtained from this object should only be used // while the Header object is still in scope. // // This type is experimental and may change or be removed in the future. type Header interface { // I'm hiding this behind an interface so that users won't accidentally // rely on the underlying json handler implementation, nor the concrete // type name that jwsbb provides, as we may choose a different one in the future. jwsbbHeader() } type header struct { v *fastjson.Value err error } func (h *header) jwsbbHeader() {} // HeaderParseCompact parses a JWS header from a compact serialization format. // You will need to call HeaderGet* functions to extract the values from the header. // // This function is experimental and may change or be removed in the future. func HeaderParseCompact(buf []byte) Header { decoded, err := base64.Decode(buf) if err != nil { return &header{err: err} } return HeaderParse(decoded) } // HeaderParse parses a JWS header from a byte slice containing the decoded JSON. // You will need to call HeaderGet* functions to extract the values from the header. // // Unlike HeaderParseCompact, this function does not perform any base64 decoding. // This function is experimental and may change or be removed in the future. func HeaderParse(decoded []byte) Header { var p fastjson.Parser v, err := p.ParseBytes(decoded) if err != nil { return &header{err: err} } return &header{ v: v, } } func headerGet(h Header, key string) (*fastjson.Value, error) { //nolint:forcetypeassert hh := h.(*header) // we _know_ this can't be another type if hh.err != nil { return nil, hh.err } v := hh.v.Get(key) if v == nil { return nil, headerNotFoundError{key: key} } return v, nil } // HeaderGetString returns the string value for the given key from the JWS header. // An error is returned if the JSON was not valid, if the key does not exist, // or if the value is not a string. // // This function is experimental and may change or be removed in the future. func HeaderGetString(h Header, key string) (string, error) { v, err := headerGet(h, key) if err != nil { return "", err } sb, err := v.StringBytes() if err != nil { return "", err } return string(sb), nil } // HeaderGetBool returns the boolean value for the given key from the JWS header. // An error is returned if the JSON was not valid, if the key does not exist, // or if the value is not a boolean. // // This function is experimental and may change or be removed in the future. func HeaderGetBool(h Header, key string) (bool, error) { v, err := headerGet(h, key) if err != nil { return false, err } return v.Bool() } // HeaderGetFloat64 returns the float64 value for the given key from the JWS header. // An error is returned if the JSON was not valid, if the key does not exist, // or if the value is not a float64. // // This function is experimental and may change or be removed in the future. func HeaderGetFloat64(h Header, key string) (float64, error) { v, err := headerGet(h, key) if err != nil { return 0, err } return v.Float64() } // HeaderGetInt returns the int value for the given key from the JWS header. // An error is returned if the JSON was not valid, if the key does not exist, // or if the value is not an int. // // This function is experimental and may change or be removed in the future. func HeaderGetInt(h Header, key string) (int, error) { v, err := headerGet(h, key) if err != nil { return 0, err } return v.Int() } // HeaderGetInt64 returns the int64 value for the given key from the JWS header. // An error is returned if the JSON was not valid, if the key does not exist, // or if the value is not an int64. // // This function is experimental and may change or be removed in the future. func HeaderGetInt64(h Header, key string) (int64, error) { v, err := headerGet(h, key) if err != nil { return 0, err } return v.Int64() } // HeaderGetStringBytes returns the byte slice value for the given key from the JWS header. // An error is returned if the JSON was not valid, if the key does not exist, // or if the value is not a byte slice. // // Because of limitations of the underlying library, you cannot use the return value // of this function after the parser is garbage collected. // // This function is experimental and may change or be removed in the future. func HeaderGetStringBytes(h Header, key string) ([]byte, error) { v, err := headerGet(h, key) if err != nil { return nil, err } return v.StringBytes() } // HeaderGetUint returns the uint value for the given key from the JWS header. // An error is returned if the JSON was not valid, if the key does not exist, // or if the value is not a uint. // // This function is experimental and may change or be removed in the future. func HeaderGetUint(h Header, key string) (uint, error) { v, err := headerGet(h, key) if err != nil { return 0, err } return v.Uint() } // HeaderGetUint64 returns the uint64 value for the given key from the JWS header. // An error is returned if the JSON was not valid, if the key does not exist, // or if the value is not a uint64. // // This function is experimental and may change or be removed in the future. func HeaderGetUint64(h Header, key string) (uint64, error) { v, err := headerGet(h, key) if err != nil { return 0, err } return v.Uint64() } golang-github-lestrrat-go-jwx-3.0.13/jws/jwsbb/header_test.go000066400000000000000000000166261515060566400241470ustar00rootroot00000000000000package jwsbb_test import ( "crypto" "crypto/rand" "crypto/rsa" "testing" "github.com/lestrrat-go/jwx/v3/internal/base64" "github.com/lestrrat-go/jwx/v3/jws/jwsbb" "github.com/stretchr/testify/require" ) func TestHeader(t *testing.T) { t.Parallel() // Test basic header parsing t.Run("HeaderParseCompact", func(t *testing.T) { t.Parallel() // Base64URL encoded {"alg":"HS256","typ":"JWT","kid":"test-key"} headerJSON := `{"alg":"HS256","typ":"JWT","kid":"test-key"}` headerB64 := base64.DefaultEncoder().EncodeToString([]byte(headerJSON)) header := jwsbb.HeaderParseCompact([]byte(headerB64)) require.NotNil(t, header, "HeaderParseCompact should return a valid header") // Test HeaderGetString alg, err := jwsbb.HeaderGetString(header, "alg") require.NoError(t, err, "HeaderGetString should not return error") require.Equal(t, "HS256", alg, "alg should be HS256") typ, err := jwsbb.HeaderGetString(header, "typ") require.NoError(t, err, "HeaderGetString should not return error") require.Equal(t, "JWT", typ, "typ should be JWT") kid, err := jwsbb.HeaderGetString(header, "kid") require.NoError(t, err, "HeaderGetString should not return error") require.Equal(t, "test-key", kid, "kid should be test-key") // Test non-existent field nonExistent, err := jwsbb.HeaderGetString(header, "nonexistent") require.Error(t, err, "HeaderGetString should not return error for non-existent field") require.Equal(t, "", nonExistent, "non-existent field should return empty string") }) t.Run("HeaderGetBool", func(t *testing.T) { t.Parallel() headerJSON := `{"debug":true,"enabled":false}` headerB64 := base64.DefaultEncoder().EncodeToString([]byte(headerJSON)) header := jwsbb.HeaderParseCompact([]byte(headerB64)) debug, err := jwsbb.HeaderGetBool(header, "debug") require.NoError(t, err, "HeaderGetBool should not return error") require.True(t, debug, "debug should be true") enabled, err := jwsbb.HeaderGetBool(header, "enabled") require.NoError(t, err, "HeaderGetBool should not return error") require.False(t, enabled, "enabled should be false") }) t.Run("HeaderGetInt", func(t *testing.T) { t.Parallel() headerJSON := `{"count":42,"negative":-10}` headerB64 := base64.DefaultEncoder().EncodeToString([]byte(headerJSON)) header := jwsbb.HeaderParseCompact([]byte(headerB64)) count, err := jwsbb.HeaderGetInt(header, "count") require.NoError(t, err, "HeaderGetInt should not return error") require.Equal(t, 42, count, "count should be 42") negative, err := jwsbb.HeaderGetInt(header, "negative") require.NoError(t, err, "HeaderGetInt should not return error") require.Equal(t, -10, negative, "negative should be -10") }) t.Run("HeaderGetFloat64", func(t *testing.T) { t.Parallel() headerJSON := `{"pi":3.14159,"ratio":2.5}` headerB64 := base64.DefaultEncoder().EncodeToString([]byte(headerJSON)) header := jwsbb.HeaderParseCompact([]byte(headerB64)) pi, err := jwsbb.HeaderGetFloat64(header, "pi") require.NoError(t, err, "HeaderGetFloat64 should not return error") require.Equal(t, 3.14159, pi, "pi should be 3.14159") ratio, err := jwsbb.HeaderGetFloat64(header, "ratio") require.NoError(t, err, "HeaderGetFloat64 should not return error") require.Equal(t, 2.5, ratio, "ratio should be 2.5") }) t.Run("HeaderWithKidLookupAndVerification", func(t *testing.T) { t.Parallel() // Generate RSA key pairs for signing rsaKey1, err := rsa.GenerateKey(rand.Reader, 2048) require.NoError(t, err, "RSA key generation should not error") rsaKey2, err := rsa.GenerateKey(rand.Reader, 2048) require.NoError(t, err, "RSA key generation should not error") // Create a simple key map for lookup with different keys keyMap := map[string]crypto.PublicKey{ "key-1": &rsaKey1.PublicKey, "key-2": &rsaKey2.PublicKey, } var signed []byte { // First part: sign the payload // Create JWS header with kid headerJSON := `{"alg":"RS256","typ":"JWT","kid":"key-1"}` // Create payload payload := []byte("test payload") // Create signature input using SignBuffer encoder := base64.DefaultEncoder() signInput := jwsbb.SignBuffer(nil, []byte(headerJSON), payload, encoder, true) // Sign with RSA using jwsbb.SignRSA signature, err := jwsbb.SignRSA(rsaKey1, signInput, crypto.SHA256, false, nil) require.NoError(t, err, "RSA signing should not error") // Create full JWS compact format using JoinCompact v, err := jwsbb.JoinCompact(nil, []byte(headerJSON), payload, signature, encoder, true) require.NoError(t, err, "JoinCompact should not error") signed = v } { // Work with the signed compact JWS // Parse header and extract kid headerB64, payloadB64, signatureB64, err := jwsbb.SplitCompact(signed) require.NoError(t, err, "SplitCompact should not return error") header := jwsbb.HeaderParseCompact(headerB64) kid, err := jwsbb.HeaderGetString(header, "kid") require.NoError(t, err, "HeaderGetString should not return error for kid") require.Equal(t, "key-1", kid, "kid should be key-1") // Look up key using kid pubKey, exists := keyMap[kid] require.True(t, exists, "key should exist in keyMap") // Verify signature using the looked up key rsaPubKey, ok := pubKey.(*rsa.PublicKey) require.True(t, ok, "key should be RSA public key") // since the header/payload are already base64-encoded, we're just going // to craft them by hand signBuffer := append(append(headerB64, '.'), payloadB64...) signature, err := base64.Decode(signatureB64) require.NoError(t, err, "Base64 decoding of signature should not error") err = jwsbb.VerifyRSA(rsaPubKey, signBuffer, signature, crypto.SHA256, false) require.NoError(t, err, "RSA signature verification should succeed") } }) t.Run("ErrorHandling", func(t *testing.T) { t.Parallel() t.Run("non-existent field", func(t *testing.T) { t.Parallel() headerJSON := `{"alg":"HS256","typ":"JWT"}` h := jwsbb.HeaderParse([]byte(headerJSON)) _, err := jwsbb.HeaderGetString(h, "nonexistent") require.Error(t, err, "HeaderGetString should return error for non-existent field") require.ErrorIs(t, err, jwsbb.ErrHeaderNotFound(), "Error should be ErrHeaderNotFound") }) t.Run("invalid JSON", func(t *testing.T) { t.Parallel() // Test invalid JSON invalidHeader := jwsbb.HeaderParseCompact([]byte("invalid-json")) _, err := jwsbb.HeaderGetString(invalidHeader, "alg") require.Error(t, err, "HeaderGetString should return error for invalid header") _, err = jwsbb.HeaderGetBool(invalidHeader, "debug") require.Error(t, err, "HeaderGetBool should return error for invalid header") _, err = jwsbb.HeaderGetInt(invalidHeader, "count") require.Error(t, err, "HeaderGetInt should return error for invalid header") _, err = jwsbb.HeaderGetFloat64(invalidHeader, "pi") require.Error(t, err, "HeaderGetFloat64 should return error for invalid header") _, err = jwsbb.HeaderGetStringBytes(invalidHeader, "data") require.Error(t, err, "HeaderGetStringBytes should return error for invalid header") _, err = jwsbb.HeaderGetUint(invalidHeader, "count") require.Error(t, err, "HeaderGetUint should return error for invalid header") _, err = jwsbb.HeaderGetInt64(invalidHeader, "timestamp") require.Error(t, err, "HeaderGetInt64 should return error for invalid header") _, err = jwsbb.HeaderGetUint64(invalidHeader, "timestamp") require.Error(t, err, "HeaderGetUint64 should return error for invalid header") }) }) } golang-github-lestrrat-go-jwx-3.0.13/jws/jwsbb/hmac.go000066400000000000000000000033151515060566400225570ustar00rootroot00000000000000package jwsbb import ( "fmt" "hash" "github.com/lestrrat-go/dsig" ) // hmacHashToDsigAlgorithm maps HMAC hash function sizes to dsig algorithm constants func hmacHashToDsigAlgorithm(hfunc func() hash.Hash) (string, error) { h := hfunc() switch h.Size() { case 32: // SHA256 return dsig.HMACWithSHA256, nil case 48: // SHA384 return dsig.HMACWithSHA384, nil case 64: // SHA512 return dsig.HMACWithSHA512, nil default: return "", fmt.Errorf("unsupported HMAC hash function: size=%d", h.Size()) } } // SignHMAC generates an HMAC signature for the given payload using the specified hash function and key. // The raw parameter should be the pre-computed signing input (typically header.payload). // // This function is now a thin wrapper around dsig.SignHMAC. For new projects, you should // consider using dsig instead of this function. func SignHMAC(key, payload []byte, hfunc func() hash.Hash) ([]byte, error) { dsigAlg, err := hmacHashToDsigAlgorithm(hfunc) if err != nil { return nil, fmt.Errorf("jwsbb.SignHMAC: %w", err) } return dsig.Sign(key, dsigAlg, payload, nil) } // VerifyHMAC verifies an HMAC signature for the given payload. // This function verifies the signature using the specified key and hash function. // The payload parameter should be the pre-computed signing input (typically header.payload). // // This function is now a thin wrapper around dsig.VerifyHMAC. For new projects, you should // consider using dsig instead of this function. func VerifyHMAC(key, payload, signature []byte, hfunc func() hash.Hash) error { dsigAlg, err := hmacHashToDsigAlgorithm(hfunc) if err != nil { return fmt.Errorf("jwsbb.VerifyHMAC: %w", err) } return dsig.Verify(key, dsigAlg, payload, signature) } golang-github-lestrrat-go-jwx-3.0.13/jws/jwsbb/jwsbb.go000066400000000000000000000057641515060566400227700ustar00rootroot00000000000000// Package jwsbb provides the building blocks (hence the name "bb") for JWS operations. // It should be thought of as a low-level API, almost akin to internal packages // that should not be used directly by users of the jwx package. However, these exist // to provide a more efficient way to perform JWS operations without the overhead of // the higher-level jws package to power-users who know what they are doing. // // This package is currently considered EXPERIMENTAL, and the API may change // without notice. It is not recommended to use this package unless you are // fully aware of the implications of using it. // // All bb packages in jwx follow the same design principles: // 1. Does minimal checking of input parameters (for performance); callers need to ensure that the parameters are valid. // 2. All exported functions are strongly typed (i.e. they do not take `any` types unless they absolutely have to). // 3. Does not rely on other public jwx packages (they are standalone, except for internal packages). // // This implementation uses github.com/lestrrat-go/dsig as the underlying signature provider. package jwsbb import ( "github.com/lestrrat-go/dsig" ) // JWS algorithm name constants const ( // HMAC algorithms hs256 = "HS256" hs384 = "HS384" hs512 = "HS512" // RSA PKCS#1 v1.5 algorithms rs256 = "RS256" rs384 = "RS384" rs512 = "RS512" // RSA PSS algorithms ps256 = "PS256" ps384 = "PS384" ps512 = "PS512" // ECDSA algorithms es256 = "ES256" es384 = "ES384" es512 = "ES512" // EdDSA algorithm edDSA = "EdDSA" ) // Signer is a generic interface that defines the method for signing payloads. // The type parameter K represents the key type (e.g., []byte for HMAC keys, // *rsa.PrivateKey for RSA keys, *ecdsa.PrivateKey for ECDSA keys). type Signer[K any] interface { Sign(key K, payload []byte) ([]byte, error) } // Verifier is a generic interface that defines the method for verifying signatures. // The type parameter K represents the key type (e.g., []byte for HMAC keys, // *rsa.PublicKey for RSA keys, *ecdsa.PublicKey for ECDSA keys). type Verifier[K any] interface { Verify(key K, buf []byte, signature []byte) error } // JWS to dsig algorithm mapping var jwsToDsigAlgorithm = map[string]string{ // HMAC algorithms hs256: dsig.HMACWithSHA256, hs384: dsig.HMACWithSHA384, hs512: dsig.HMACWithSHA512, // RSA PKCS#1 v1.5 algorithms rs256: dsig.RSAPKCS1v15WithSHA256, rs384: dsig.RSAPKCS1v15WithSHA384, rs512: dsig.RSAPKCS1v15WithSHA512, // RSA PSS algorithms ps256: dsig.RSAPSSWithSHA256, ps384: dsig.RSAPSSWithSHA384, ps512: dsig.RSAPSSWithSHA512, // ECDSA algorithms es256: dsig.ECDSAWithP256AndSHA256, es384: dsig.ECDSAWithP384AndSHA384, es512: dsig.ECDSAWithP521AndSHA512, // Note: ES256K requires external dependency and is handled separately // EdDSA algorithm edDSA: dsig.EdDSA, } // getDsigAlgorithm returns the dsig algorithm name for a JWS algorithm func getDsigAlgorithm(jwsAlg string) (string, bool) { dsigAlg, ok := jwsToDsigAlgorithm[jwsAlg] return dsigAlg, ok } golang-github-lestrrat-go-jwx-3.0.13/jws/jwsbb/jwsbb_test.go000066400000000000000000000145061515060566400240210ustar00rootroot00000000000000package jwsbb_test import ( "crypto" "crypto/ecdsa" "crypto/ed25519" "crypto/elliptic" "crypto/rand" "crypto/rsa" "crypto/sha256" "crypto/sha512" "hash" "testing" "github.com/lestrrat-go/jwx/v3/internal/base64" "github.com/lestrrat-go/jwx/v3/internal/pool" "github.com/lestrrat-go/jwx/v3/jws/jwsbb" "github.com/stretchr/testify/require" ) const sampleHeader = "eyJmb28iOiJiYXIifQ" // Base64URL of {"foo":"bar"} func TestHMAC(t *testing.T) { t.Parallel() tests := []struct { alg string hfunc func() hash.Hash encodePayload bool }{ {"HS256", sha256.New, true}, {"HS384", sha512.New384, true}, {"HS512", sha512.New, true}, {"HS256", sha256.New, false}, {"HS384", sha512.New384, false}, {"HS512", sha512.New, false}, } encoder := base64.DefaultEncoder() for _, tc := range tests { t.Run(tc.alg, func(t *testing.T) { payload := []byte("hello") key := []byte("secretkey") header := []byte(sampleHeader) signBuffer := jwsbb.SignBuffer(nil, header, payload, encoder, tc.encodePayload) sig, err := jwsbb.SignHMAC(key, signBuffer, tc.hfunc) require.NoError(t, err, "SignHMAC should not return error") require.NoError(t, jwsbb.VerifyHMAC(key, signBuffer, sig, tc.hfunc), "VerifyHMAC should succeed for a valid signature") require.Error(t, jwsbb.VerifyHMAC(key, signBuffer, sig[:len(sig)-1], tc.hfunc), "VerifyHMAC should fail for an invalid signature") }) } const examplePayload = `{"iss":"joe",` + "\r\n" + ` "exp":1300819380,` + "\r\n" + ` "http://example.com/is_root":true}` t.Run("RFC Example", func(t *testing.T) { t.Parallel() const hdr = `{"typ":"JWT",` + "\r\n" + ` "alg":"HS256"}` const hmacKey = `AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow` const expected = `eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk` hmacKeyDecoded, err := base64.DecodeString(hmacKey) require.NoError(t, err, "decoding key should succeed") signBuffer := jwsbb.SignBuffer(nil, []byte(hdr), []byte(examplePayload), base64.DefaultEncoder(), true) signature, err := jwsbb.SignHMAC(hmacKeyDecoded, signBuffer, sha256.New) require.NoError(t, err, "SignHMAC should succeed") buf := pool.ByteSlice().Get() buf, err = jwsbb.JoinCompact(buf, []byte(hdr), []byte(examplePayload), signature, base64.DefaultEncoder(), true) require.NoError(t, err, "JoinCompact should succeed") defer pool.ByteSlice().Put(buf) require.Equal(t, expected, string(buf), "serialized output should match expected value") }) } func TestRSA(t *testing.T) { priv, err := rsa.GenerateKey(rand.Reader, 2048) require.NoError(t, err, "RSA key generation should not error") testcases := []struct { name string h crypto.Hash pss bool encodePayload bool }{ {"RS256", crypto.SHA256, false, true}, {"RS384", crypto.SHA384, false, true}, {"RS512", crypto.SHA512, false, true}, {"PS256", crypto.SHA256, true, true}, {"PS384", crypto.SHA384, true, true}, {"PS512", crypto.SHA512, true, true}, {"RS256_no_encode", crypto.SHA256, false, false}, {"RS384_no_encode", crypto.SHA384, false, false}, {"RS512_no_encode", crypto.SHA512, false, false}, {"PS256_no_encode", crypto.SHA256, true, false}, {"PS384_no_encode", crypto.SHA384, true, false}, {"PS512_no_encode", crypto.SHA512, true, false}, } encoding := base64.DefaultEncoder() for _, tc := range testcases { t.Run(tc.name, func(t *testing.T) { payload := []byte("hello") header := []byte(sampleHeader) signBuffer := jwsbb.SignBuffer(nil, header, payload, encoding, tc.encodePayload) sig, err := jwsbb.SignRSA(priv, signBuffer, tc.h, tc.pss, nil) require.NoError(t, err, "SignRSA should not return error") require.NoError(t, jwsbb.VerifyRSA(&priv.PublicKey, signBuffer, sig, tc.h, tc.pss), "VerifyRSA should succeed for a valid signature") require.Error(t, jwsbb.VerifyRSA(&priv.PublicKey, signBuffer, sig[:len(sig)-1], tc.h, tc.pss), "VerifyRSA should fail for an invalid signature") }) } } func TestECDSA(t *testing.T) { table := []struct { name string curve elliptic.Curve h crypto.Hash encodePayload bool }{ {"P256_SHA256_b64=true", elliptic.P256(), crypto.SHA256, true}, {"P384_SHA384_b64=true", elliptic.P384(), crypto.SHA384, true}, {"P521_SHA512_b64=true", elliptic.P521(), crypto.SHA512, true}, {"P256_SHA256", elliptic.P256(), crypto.SHA256, false}, {"P384_SHA384", elliptic.P384(), crypto.SHA384, false}, {"P521_SHA512", elliptic.P521(), crypto.SHA512, false}, } encoder := base64.DefaultEncoder() for _, tc := range table { t.Run(tc.name, func(t *testing.T) { payload := []byte("hello") priv, err := ecdsa.GenerateKey(tc.curve, rand.Reader) require.NoError(t, err, "ECDSA key generation should not error") // prepare placeholder header header := []byte(sampleHeader) signBuffer := jwsbb.SignBuffer(nil, header, payload, encoder, tc.encodePayload) sig, err := jwsbb.SignECDSA(priv, signBuffer, tc.h, nil) require.NoError(t, err, "SignECDSA should not return error") require.NoError(t, jwsbb.VerifyECDSA(&priv.PublicKey, signBuffer, sig, tc.h), "VerifyECDSA should succeed for a valid signature") require.Error(t, jwsbb.VerifyECDSA(&priv.PublicKey, signBuffer, sig[:len(sig)-1], tc.h), "VerifyECDSA should fail for an invalid signature") }) } } func TestEdDSA(t *testing.T) { testcases := []struct { name string encodePayload bool }{ {"Ed25519_b64=true", true}, {"Ed25519_b64=false", false}, } encoding := base64.DefaultEncoder() for _, tc := range testcases { t.Run(tc.name, func(t *testing.T) { payload := []byte("hello") pub, priv, err := ed25519.GenerateKey(rand.Reader) require.NoError(t, err, "EdDSA key generation should not error") // prepare placeholder header header := []byte(sampleHeader) signBuffer := jwsbb.SignBuffer(nil, header, payload, encoding, tc.encodePayload) sig, err := jwsbb.SignEdDSA(priv, signBuffer) require.NoError(t, err, "SignEdDSA should not return error") require.NoError(t, jwsbb.VerifyEdDSA(pub, signBuffer, sig), "VerifyEdDSA should succeed for a valid signature") require.Error(t, jwsbb.VerifyEdDSA(pub, signBuffer, sig[:len(sig)-1]), "VerifyEdDSA should fail for an invalid signature") }) } } golang-github-lestrrat-go-jwx-3.0.13/jws/jwsbb/rsa.go000066400000000000000000000046211515060566400224350ustar00rootroot00000000000000package jwsbb import ( "crypto" "crypto/rsa" "fmt" "io" "github.com/lestrrat-go/dsig" ) // rsaHashToDsigAlgorithm maps RSA hash functions to dsig algorithm constants func rsaHashToDsigAlgorithm(h crypto.Hash, pss bool) (string, error) { if pss { switch h { case crypto.SHA256: return dsig.RSAPSSWithSHA256, nil case crypto.SHA384: return dsig.RSAPSSWithSHA384, nil case crypto.SHA512: return dsig.RSAPSSWithSHA512, nil default: return "", fmt.Errorf("unsupported hash algorithm for RSA-PSS: %v", h) } } else { switch h { case crypto.SHA256: return dsig.RSAPKCS1v15WithSHA256, nil case crypto.SHA384: return dsig.RSAPKCS1v15WithSHA384, nil case crypto.SHA512: return dsig.RSAPKCS1v15WithSHA512, nil default: return "", fmt.Errorf("unsupported hash algorithm for RSA PKCS#1 v1.5: %v", h) } } } // SignRSA generates an RSA signature for the given payload using the specified private key and options. // The raw parameter should be the pre-computed signing input (typically header.payload). // If pss is true, RSA-PSS is used; otherwise, PKCS#1 v1.5 is used. // // The rr parameter is an optional io.Reader that can be used to provide randomness for signing. // If rr is nil, it defaults to rand.Reader. // // This function is now a thin wrapper around dsig.SignRSA. For new projects, you should // consider using dsig instead of this function. func SignRSA(key *rsa.PrivateKey, payload []byte, h crypto.Hash, pss bool, rr io.Reader) ([]byte, error) { dsigAlg, err := rsaHashToDsigAlgorithm(h, pss) if err != nil { return nil, fmt.Errorf("jwsbb.SignRSA: %w", err) } return dsig.Sign(key, dsigAlg, payload, rr) } // VerifyRSA verifies an RSA signature for the given payload and header. // This function constructs the signing input by encoding the header and payload according to JWS specification, // then verifies the signature using the specified public key and hash algorithm. // If pss is true, RSA-PSS verification is used; otherwise, PKCS#1 v1.5 verification is used. // // This function is now a thin wrapper around dsig.VerifyRSA. For new projects, you should // consider using dsig instead of this function. func VerifyRSA(key *rsa.PublicKey, payload, signature []byte, h crypto.Hash, pss bool) error { dsigAlg, err := rsaHashToDsigAlgorithm(h, pss) if err != nil { return fmt.Errorf("jwsbb.VerifyRSA: %w", err) } return dsig.Verify(key, dsigAlg, payload, signature) } golang-github-lestrrat-go-jwx-3.0.13/jws/jwsbb/sign.go000066400000000000000000000071701515060566400226120ustar00rootroot00000000000000package jwsbb import ( "crypto" "crypto/ecdsa" "crypto/ed25519" "crypto/rsa" "fmt" "io" "github.com/lestrrat-go/dsig" "github.com/lestrrat-go/jwx/v3/internal/keyconv" ) // Sign generates a JWS signature using the specified key and algorithm. // // This function loads the signer registered in the jwsbb package _ONLY_. // It does not support custom signers that the user might have registered. // // rr is an io.Reader that provides randomness for signing. If rr is nil, it defaults to rand.Reader. // Not all algorithms require this parameter, but it is included for consistency. // 99% of the time, you can pass nil for rr, and it will work fine. func Sign(key any, alg string, payload []byte, rr io.Reader) ([]byte, error) { dsigAlg, ok := getDsigAlgorithm(alg) if !ok { return nil, fmt.Errorf(`jwsbb.Sign: unsupported signature algorithm %q`, alg) } // Get dsig algorithm info to determine key conversion strategy dsigInfo, ok := dsig.GetAlgorithmInfo(dsigAlg) if !ok { return nil, fmt.Errorf(`jwsbb.Sign: dsig algorithm %q not registered`, dsigAlg) } switch dsigInfo.Family { case dsig.HMAC: return dispatchHMACSign(key, dsigAlg, payload) case dsig.RSA: return dispatchRSASign(key, dsigAlg, payload, rr) case dsig.ECDSA: return dispatchECDSASign(key, dsigAlg, payload, rr) case dsig.EdDSAFamily: return dispatchEdDSASign(key, dsigAlg, payload, rr) default: return nil, fmt.Errorf(`jwsbb.Sign: unsupported dsig algorithm family %q`, dsigInfo.Family) } } func dispatchHMACSign(key any, dsigAlg string, payload []byte) ([]byte, error) { var hmackey []byte if err := keyconv.ByteSliceKey(&hmackey, key); err != nil { return nil, fmt.Errorf(`jwsbb.Sign: invalid key type %T. []byte is required: %w`, key, err) } return dsig.Sign(hmackey, dsigAlg, payload, nil) } func dispatchRSASign(key any, dsigAlg string, payload []byte, rr io.Reader) ([]byte, error) { // Try crypto.Signer first (dsig can handle it directly) if signer, ok := key.(crypto.Signer); ok { // Verify it's an RSA key if _, ok := signer.Public().(*rsa.PublicKey); ok { return dsig.Sign(signer, dsigAlg, payload, rr) } } // Fall back to concrete key types var privkey *rsa.PrivateKey if err := keyconv.RSAPrivateKey(&privkey, key); err != nil { return nil, fmt.Errorf(`jwsbb.Sign: invalid key type %T. *rsa.PrivateKey is required: %w`, key, err) } return dsig.Sign(privkey, dsigAlg, payload, rr) } func dispatchECDSASign(key any, dsigAlg string, payload []byte, rr io.Reader) ([]byte, error) { // Try crypto.Signer first (dsig can handle it directly) if signer, ok := key.(crypto.Signer); ok { // Verify it's an ECDSA key if _, ok := signer.Public().(*ecdsa.PublicKey); ok { return dsig.Sign(signer, dsigAlg, payload, rr) } } // Fall back to concrete key types var privkey *ecdsa.PrivateKey if err := keyconv.ECDSAPrivateKey(&privkey, key); err != nil { return nil, fmt.Errorf(`jwsbb.Sign: invalid key type %T. *ecdsa.PrivateKey is required: %w`, key, err) } return dsig.Sign(privkey, dsigAlg, payload, rr) } func dispatchEdDSASign(key any, dsigAlg string, payload []byte, rr io.Reader) ([]byte, error) { // Try crypto.Signer first (dsig can handle it directly) if signer, ok := key.(crypto.Signer); ok { // Verify it's an EdDSA key if _, ok := signer.Public().(ed25519.PublicKey); ok { return dsig.Sign(signer, dsigAlg, payload, rr) } } // Fall back to concrete key types var privkey ed25519.PrivateKey if err := keyconv.Ed25519PrivateKey(&privkey, key); err != nil { return nil, fmt.Errorf(`jwsbb.Sign: invalid key type %T. ed25519.PrivateKey is required: %w`, key, err) } return dsig.Sign(privkey, dsigAlg, payload, rr) } golang-github-lestrrat-go-jwx-3.0.13/jws/jwsbb/verify.go000066400000000000000000000066161515060566400231620ustar00rootroot00000000000000package jwsbb import ( "crypto" "crypto/ecdsa" "crypto/ed25519" "crypto/rsa" "fmt" "github.com/lestrrat-go/dsig" "github.com/lestrrat-go/jwx/v3/internal/keyconv" ) // Verify verifies a JWS signature using the specified key and algorithm. // // This function loads the verifier registered in the jwsbb package _ONLY_. // It does not support custom verifiers that the user might have registered. func Verify(key any, alg string, payload, signature []byte) error { dsigAlg, ok := getDsigAlgorithm(alg) if !ok { return fmt.Errorf(`jwsbb.Verify: unsupported signature algorithm %q`, alg) } // Get dsig algorithm info to determine key conversion strategy dsigInfo, ok := dsig.GetAlgorithmInfo(dsigAlg) if !ok { return fmt.Errorf(`jwsbb.Verify: dsig algorithm %q not registered`, dsigAlg) } switch dsigInfo.Family { case dsig.HMAC: return dispatchHMACVerify(key, dsigAlg, payload, signature) case dsig.RSA: return dispatchRSAVerify(key, dsigAlg, payload, signature) case dsig.ECDSA: return dispatchECDSAVerify(key, dsigAlg, payload, signature) case dsig.EdDSAFamily: return dispatchEdDSAVerify(key, dsigAlg, payload, signature) default: return fmt.Errorf(`jwsbb.Verify: unsupported dsig algorithm family %q`, dsigInfo.Family) } } func dispatchHMACVerify(key any, dsigAlg string, payload, signature []byte) error { var hmackey []byte if err := keyconv.ByteSliceKey(&hmackey, key); err != nil { return fmt.Errorf(`jwsbb.Verify: invalid key type %T. []byte is required: %w`, key, err) } return dsig.Verify(hmackey, dsigAlg, payload, signature) } func dispatchRSAVerify(key any, dsigAlg string, payload, signature []byte) error { // Try crypto.Signer first (dsig can handle it directly) if signer, ok := key.(crypto.Signer); ok { // Verify it's an RSA key if _, ok := signer.Public().(*rsa.PublicKey); ok { return dsig.Verify(signer, dsigAlg, payload, signature) } } // Fall back to concrete key types var pubkey *rsa.PublicKey if err := keyconv.RSAPublicKey(&pubkey, key); err != nil { return fmt.Errorf(`jwsbb.Verify: invalid key type %T. *rsa.PublicKey is required: %w`, key, err) } return dsig.Verify(pubkey, dsigAlg, payload, signature) } func dispatchECDSAVerify(key any, dsigAlg string, payload, signature []byte) error { // Try crypto.Signer first (dsig can handle it directly) if signer, ok := key.(crypto.Signer); ok { // Verify it's an ECDSA key if _, ok := signer.Public().(*ecdsa.PublicKey); ok { return dsig.Verify(signer, dsigAlg, payload, signature) } } // Fall back to concrete key types var pubkey *ecdsa.PublicKey if err := keyconv.ECDSAPublicKey(&pubkey, key); err != nil { return fmt.Errorf(`jwsbb.Verify: invalid key type %T. *ecdsa.PublicKey is required: %w`, key, err) } return dsig.Verify(pubkey, dsigAlg, payload, signature) } func dispatchEdDSAVerify(key any, dsigAlg string, payload, signature []byte) error { // Try crypto.Signer first (dsig can handle it directly) if signer, ok := key.(crypto.Signer); ok { // Verify it's an EdDSA key if _, ok := signer.Public().(ed25519.PublicKey); ok { return dsig.Verify(signer, dsigAlg, payload, signature) } } // Fall back to concrete key types var pubkey ed25519.PublicKey if err := keyconv.Ed25519PublicKey(&pubkey, key); err != nil { return fmt.Errorf(`jwsbb.Verify: invalid key type %T. ed25519.PublicKey is required: %w`, key, err) } return dsig.Verify(pubkey, dsigAlg, payload, signature) } golang-github-lestrrat-go-jwx-3.0.13/jws/key_provider.go000066400000000000000000000206121515060566400232410ustar00rootroot00000000000000package jws import ( "context" "fmt" "net/url" "sync" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/lestrrat-go/jwx/v3/jwk" ) // KeyProvider is responsible for providing key(s) to sign or verify a payload. // Multiple `jws.KeyProvider`s can be passed to `jws.Verify()` or `jws.Sign()` // // `jws.Sign()` can only accept static key providers via `jws.WithKey()`, // while `jws.Verify()` can accept `jws.WithKey()`, `jws.WithKeySet()`, // `jws.WithVerifyAuto()`, and `jws.WithKeyProvider()`. // // Understanding how this works is crucial to learn how this package works. // // `jws.Sign()` is straightforward: signatures are created for each // provided key. // // `jws.Verify()` is a bit more involved, because there are cases you // will want to compute/deduce/guess the keys that you would like to // use for verification. // // The first thing that `jws.Verify()` does is to collect the // KeyProviders from the option list that the user provided (presented in pseudocode): // // keyProviders := filterKeyProviders(options) // // Then, remember that a JWS message may contain multiple signatures in the // message. For each signature, we call on the KeyProviders to give us // the key(s) to use on this signature: // // for sig in msg.Signatures { // for kp in keyProviders { // kp.FetchKeys(ctx, sink, sig, msg) // ... // } // } // // The `sink` argument passed to the KeyProvider is a temporary storage // for the keys (either a jwk.Key or a "raw" key). The `KeyProvider` // is responsible for sending keys into the `sink`. // // When called, the `KeyProvider` created by `jws.WithKey()` sends the same key, // `jws.WithKeySet()` sends keys that matches a particular `kid` and `alg`, // `jws.WithVerifyAuto()` fetches a JWK from the `jku` URL, // and finally `jws.WithKeyProvider()` allows you to execute arbitrary // logic to provide keys. If you are providing a custom `KeyProvider`, // you should execute the necessary checks or retrieval of keys, and // then send the key(s) to the sink: // // sink.Key(alg, key) // // These keys are then retrieved and tried for each signature, until // a match is found: // // keys := sink.Keys() // for key in keys { // if givenSignature == makeSignature(key, payload, ...)) { // return OK // } // } type KeyProvider interface { FetchKeys(context.Context, KeySink, *Signature, *Message) error } // KeySink is a data storage where `jws.KeyProvider` objects should // send their keys to. type KeySink interface { Key(jwa.SignatureAlgorithm, any) } type algKeyPair struct { alg jwa.KeyAlgorithm key any } type algKeySink struct { mu sync.Mutex list []algKeyPair } func (s *algKeySink) Key(alg jwa.SignatureAlgorithm, key any) { s.mu.Lock() s.list = append(s.list, algKeyPair{alg, key}) s.mu.Unlock() } type staticKeyProvider struct { alg jwa.SignatureAlgorithm key any } func (kp *staticKeyProvider) FetchKeys(_ context.Context, sink KeySink, _ *Signature, _ *Message) error { sink.Key(kp.alg, kp.key) return nil } type keySetProvider struct { set jwk.Set requireKid bool // true if `kid` must be specified useDefault bool // true if the first key should be used iff there's exactly one key in set inferAlgorithm bool // true if the algorithm should be inferred from key type multipleKeysPerKeyID bool // true if we should attempt to match multiple keys per key ID. if false we assume that only one key exists for a given key ID } func (kp *keySetProvider) selectKey(sink KeySink, key jwk.Key, sig *Signature, _ *Message) error { if usage, ok := key.KeyUsage(); ok { // it's okay if use: "". we'll assume it's "sig" if usage != "" && usage != jwk.ForSignature.String() { return nil } } if v, ok := key.Algorithm(); ok { salg, ok := jwa.LookupSignatureAlgorithm(v.String()) if !ok { return fmt.Errorf(`invalid signature algorithm %q`, v) } sink.Key(salg, key) return nil } if kp.inferAlgorithm { algs, err := AlgorithmsForKey(key) if err != nil { return fmt.Errorf(`failed to get a list of signature methods for key type %s: %w`, key.KeyType(), err) } // bail out if the JWT has a `alg` field, and it doesn't match if tokAlg, ok := sig.ProtectedHeaders().Algorithm(); ok { for _, alg := range algs { if tokAlg == alg { sink.Key(alg, key) return nil } } return fmt.Errorf(`algorithm in the message does not match any of the inferred algorithms`) } // Yes, you get to try them all!!!!!!! for _, alg := range algs { sink.Key(alg, key) } return nil } return nil } func (kp *keySetProvider) FetchKeys(_ context.Context, sink KeySink, sig *Signature, msg *Message) error { if kp.requireKid { wantedKid, ok := sig.ProtectedHeaders().KeyID() if !ok { // If the kid is NOT specified... kp.useDefault needs to be true, and the // JWKs must have exactly one key in it if !kp.useDefault { return fmt.Errorf(`failed to find matching key: no key ID ("kid") specified in token`) } else if kp.useDefault && kp.set.Len() > 1 { return fmt.Errorf(`failed to find matching key: no key ID ("kid") specified in token but multiple keys available in key set`) } // if we got here, then useDefault == true AND there is exactly // one key in the set. key, ok := kp.set.Key(0) if !ok { return fmt.Errorf(`failed to get key at index 0 (empty JWKS?)`) } return kp.selectKey(sink, key, sig, msg) } // Otherwise we better be able to look up the key. // <= v2.0.3 backwards compatible case: only match a single key // whose key ID matches `wantedKid` if !kp.multipleKeysPerKeyID { key, ok := kp.set.LookupKeyID(wantedKid) if !ok { return fmt.Errorf(`failed to find key with key ID %q in key set`, wantedKid) } return kp.selectKey(sink, key, sig, msg) } // if multipleKeysPerKeyID is true, we attempt all keys whose key ID matches // the wantedKey ok = false for i := range kp.set.Len() { key, _ := kp.set.Key(i) if kid, ok := key.KeyID(); !ok || kid != wantedKid { continue } if err := kp.selectKey(sink, key, sig, msg); err != nil { continue } ok = true // continue processing so that we try all keys with the same key ID } if !ok { return fmt.Errorf(`failed to find key with key ID %q in key set`, wantedKid) } return nil } // Otherwise just try all keys for i := range kp.set.Len() { key, ok := kp.set.Key(i) if !ok { return fmt.Errorf(`failed to get key at index %d`, i) } if err := kp.selectKey(sink, key, sig, msg); err != nil { continue } } return nil } type jkuProvider struct { fetcher jwk.Fetcher options []jwk.FetchOption } func (kp jkuProvider) FetchKeys(ctx context.Context, sink KeySink, sig *Signature, _ *Message) error { if kp.fetcher == nil { kp.fetcher = jwk.FetchFunc(jwk.Fetch) } kid, ok := sig.ProtectedHeaders().KeyID() if !ok { return fmt.Errorf(`use of "jku" requires that the payload contain a "kid" field in the protected header`) } // errors here can't be reliably passed to the consumers. // it's unfortunate, but if you need this control, you are // going to have to write your own fetcher u, ok := sig.ProtectedHeaders().JWKSetURL() if !ok || u == "" { return fmt.Errorf(`use of "jku" field specified, but the field is empty`) } uo, err := url.Parse(u) if err != nil { return fmt.Errorf(`failed to parse "jku": %w`, err) } if uo.Scheme != "https" { return fmt.Errorf(`url in "jku" must be HTTPS`) } set, err := kp.fetcher.Fetch(ctx, u, kp.options...) if err != nil { return fmt.Errorf(`failed to fetch %q: %w`, u, err) } key, ok := set.LookupKeyID(kid) if !ok { // It is not an error if the key with the kid doesn't exist return nil } algs, err := AlgorithmsForKey(key) if err != nil { return fmt.Errorf(`failed to get a list of signature methods for key type %s: %w`, key.KeyType(), err) } hdrAlg, ok := sig.ProtectedHeaders().Algorithm() if ok { for _, alg := range algs { // if we have an "alg" field in the JWS, we can only proceed if // the inferred algorithm matches if hdrAlg != alg { continue } sink.Key(alg, key) break } } return nil } // KeyProviderFunc is a type of KeyProvider that is implemented by // a single function. You can use this to create ad-hoc `KeyProvider` // instances. type KeyProviderFunc func(context.Context, KeySink, *Signature, *Message) error func (kp KeyProviderFunc) FetchKeys(ctx context.Context, sink KeySink, sig *Signature, msg *Message) error { return kp(ctx, sink, sig, msg) } golang-github-lestrrat-go-jwx-3.0.13/jws/legacy.go000066400000000000000000000054711515060566400220110ustar00rootroot00000000000000package jws import ( "fmt" "sync" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/lestrrat-go/jwx/v3/jws/legacy" ) var enableLegacySignersOnce = &sync.Once{} func enableLegacySigners() { for _, alg := range []jwa.SignatureAlgorithm{jwa.HS256(), jwa.HS384(), jwa.HS512()} { if err := RegisterSigner(alg, func(alg jwa.SignatureAlgorithm) SignerFactory { return SignerFactoryFn(func() (Signer, error) { return legacy.NewHMACSigner(alg), nil }) }(alg)); err != nil { panic(fmt.Sprintf("RegisterSigner failed: %v", err)) } if err := RegisterVerifier(alg, func(alg jwa.SignatureAlgorithm) VerifierFactory { return VerifierFactoryFn(func() (Verifier, error) { return legacy.NewHMACVerifier(alg), nil }) }(alg)); err != nil { panic(fmt.Sprintf("RegisterVerifier failed: %v", err)) } } for _, alg := range []jwa.SignatureAlgorithm{jwa.RS256(), jwa.RS384(), jwa.RS512(), jwa.PS256(), jwa.PS384(), jwa.PS512()} { if err := RegisterSigner(alg, func(alg jwa.SignatureAlgorithm) SignerFactory { return SignerFactoryFn(func() (Signer, error) { return legacy.NewRSASigner(alg), nil }) }(alg)); err != nil { panic(fmt.Sprintf("RegisterSigner failed: %v", err)) } if err := RegisterVerifier(alg, func(alg jwa.SignatureAlgorithm) VerifierFactory { return VerifierFactoryFn(func() (Verifier, error) { return legacy.NewRSAVerifier(alg), nil }) }(alg)); err != nil { panic(fmt.Sprintf("RegisterVerifier failed: %v", err)) } } for _, alg := range []jwa.SignatureAlgorithm{jwa.ES256(), jwa.ES384(), jwa.ES512(), jwa.ES256K()} { if err := RegisterSigner(alg, func(alg jwa.SignatureAlgorithm) SignerFactory { return SignerFactoryFn(func() (Signer, error) { return legacy.NewECDSASigner(alg), nil }) }(alg)); err != nil { panic(fmt.Sprintf("RegisterSigner failed: %v", err)) } if err := RegisterVerifier(alg, func(alg jwa.SignatureAlgorithm) VerifierFactory { return VerifierFactoryFn(func() (Verifier, error) { return legacy.NewECDSAVerifier(alg), nil }) }(alg)); err != nil { panic(fmt.Sprintf("RegisterVerifier failed: %v", err)) } } if err := RegisterSigner(jwa.EdDSA(), SignerFactoryFn(func() (Signer, error) { return legacy.NewEdDSASigner(), nil })); err != nil { panic(fmt.Sprintf("RegisterSigner failed: %v", err)) } if err := RegisterVerifier(jwa.EdDSA(), VerifierFactoryFn(func() (Verifier, error) { return legacy.NewEdDSAVerifier(), nil })); err != nil { panic(fmt.Sprintf("RegisterVerifier failed: %v", err)) } } func legacySignerFor(alg jwa.SignatureAlgorithm) (Signer, error) { muSigner.Lock() s, ok := signers[alg] if !ok { v, err := newLegacySigner(alg) if err != nil { muSigner.Unlock() return nil, fmt.Errorf(`failed to create payload signer: %w`, err) } signers[alg] = v s = v } muSigner.Unlock() return s, nil } golang-github-lestrrat-go-jwx-3.0.13/jws/legacy/000077500000000000000000000000001515060566400214535ustar00rootroot00000000000000golang-github-lestrrat-go-jwx-3.0.13/jws/legacy/BUILD.bazel000066400000000000000000000007121515060566400233310ustar00rootroot00000000000000load("@rules_go//go:def.bzl", "go_library") go_library( name = "legacy", srcs = [ "ecdsa.go", "eddsa.go", "hmac.go", "legacy.go", "rsa.go", ], importpath = "github.com/lestrrat-go/jwx/v3/jws/legacy", visibility = ["//visibility:public"], deps = [ "//internal/ecutil", "//internal/keyconv", "//internal/pool", "//jwa", "//jws/internal/keytype", ], ) golang-github-lestrrat-go-jwx-3.0.13/jws/legacy/ecdsa.go000066400000000000000000000121541515060566400230640ustar00rootroot00000000000000package legacy import ( "crypto" "crypto/ecdsa" "crypto/rand" "encoding/asn1" "fmt" "math/big" "github.com/lestrrat-go/jwx/v3/internal/ecutil" "github.com/lestrrat-go/jwx/v3/internal/keyconv" "github.com/lestrrat-go/jwx/v3/internal/pool" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/lestrrat-go/jwx/v3/jws/internal/keytype" ) var ecdsaSigners = make(map[jwa.SignatureAlgorithm]*ecdsaSigner) var ecdsaVerifiers = make(map[jwa.SignatureAlgorithm]*ecdsaVerifier) func init() { algs := map[jwa.SignatureAlgorithm]crypto.Hash{ jwa.ES256(): crypto.SHA256, jwa.ES384(): crypto.SHA384, jwa.ES512(): crypto.SHA512, jwa.ES256K(): crypto.SHA256, } for alg, hash := range algs { ecdsaSigners[alg] = &ecdsaSigner{ alg: alg, hash: hash, } ecdsaVerifiers[alg] = &ecdsaVerifier{ alg: alg, hash: hash, } } } func NewECDSASigner(alg jwa.SignatureAlgorithm) Signer { return ecdsaSigners[alg] } // ecdsaSigners are immutable. type ecdsaSigner struct { alg jwa.SignatureAlgorithm hash crypto.Hash } func (es ecdsaSigner) Algorithm() jwa.SignatureAlgorithm { return es.alg } func (es *ecdsaSigner) Sign(payload []byte, key any) ([]byte, error) { if key == nil { return nil, fmt.Errorf(`missing private key while signing payload`) } h := es.hash.New() if _, err := h.Write(payload); err != nil { return nil, fmt.Errorf(`failed to write payload using ecdsa: %w`, err) } signer, ok := key.(crypto.Signer) if ok { if !keytype.IsValidECDSAKey(key) { return nil, fmt.Errorf(`cannot use key of type %T to generate ECDSA based signatures`, key) } switch key.(type) { case ecdsa.PrivateKey, *ecdsa.PrivateKey: // if it's a ecdsa.PrivateKey, it's more efficient to // go through the non-crypto.Signer route. Set ok to false ok = false } } var r, s *big.Int var curveBits int if ok { signed, err := signer.Sign(rand.Reader, h.Sum(nil), es.hash) if err != nil { return nil, err } var p struct { R *big.Int S *big.Int } if _, err := asn1.Unmarshal(signed, &p); err != nil { return nil, fmt.Errorf(`failed to unmarshal ASN1 encoded signature: %w`, err) } // Okay, this is silly, but hear me out. When we use the // crypto.Signer interface, the PrivateKey is hidden. // But we need some information about the key (its bit size). // // So while silly, we're going to have to make another call // here and fetch the Public key. // This probably means that this should be cached some where. cpub := signer.Public() pubkey, ok := cpub.(*ecdsa.PublicKey) if !ok { return nil, fmt.Errorf(`expected *ecdsa.PublicKey, got %T`, pubkey) } curveBits = pubkey.Curve.Params().BitSize r = p.R s = p.S } else { var privkey ecdsa.PrivateKey if err := keyconv.ECDSAPrivateKey(&privkey, key); err != nil { return nil, fmt.Errorf(`failed to retrieve ecdsa.PrivateKey out of %T: %w`, key, err) } curveBits = privkey.Curve.Params().BitSize rtmp, stmp, err := ecdsa.Sign(rand.Reader, &privkey, h.Sum(nil)) if err != nil { return nil, fmt.Errorf(`failed to sign payload using ecdsa: %w`, err) } r = rtmp s = stmp } keyBytes := curveBits / 8 // Curve bits do not need to be a multiple of 8. if curveBits%8 > 0 { keyBytes++ } rBytes := r.Bytes() rBytesPadded := make([]byte, keyBytes) copy(rBytesPadded[keyBytes-len(rBytes):], rBytes) sBytes := s.Bytes() sBytesPadded := make([]byte, keyBytes) copy(sBytesPadded[keyBytes-len(sBytes):], sBytes) out := append(rBytesPadded, sBytesPadded...) return out, nil } // ecdsaVerifiers are immutable. type ecdsaVerifier struct { alg jwa.SignatureAlgorithm hash crypto.Hash } func NewECDSAVerifier(alg jwa.SignatureAlgorithm) Verifier { return ecdsaVerifiers[alg] } func (v ecdsaVerifier) Algorithm() jwa.SignatureAlgorithm { return v.alg } func (v *ecdsaVerifier) Verify(payload []byte, signature []byte, key any) error { if key == nil { return fmt.Errorf(`missing public key while verifying payload`) } var pubkey ecdsa.PublicKey if cs, ok := key.(crypto.Signer); ok { cpub := cs.Public() switch cpub := cpub.(type) { case ecdsa.PublicKey: pubkey = cpub case *ecdsa.PublicKey: pubkey = *cpub default: return fmt.Errorf(`failed to retrieve ecdsa.PublicKey out of crypto.Signer %T`, key) } } else { if err := keyconv.ECDSAPublicKey(&pubkey, key); err != nil { return fmt.Errorf(`failed to retrieve ecdsa.PublicKey out of %T: %w`, key, err) } } if !pubkey.Curve.IsOnCurve(pubkey.X, pubkey.Y) { return fmt.Errorf(`public key used does not contain a point (X,Y) on the curve`) } r := pool.BigInt().Get() s := pool.BigInt().Get() defer pool.BigInt().Put(r) defer pool.BigInt().Put(s) keySize := ecutil.CalculateKeySize(pubkey.Curve) if len(signature) != keySize*2 { return fmt.Errorf(`invalid signature length for curve %q`, pubkey.Curve.Params().Name) } r.SetBytes(signature[:keySize]) s.SetBytes(signature[keySize:]) h := v.hash.New() if _, err := h.Write(payload); err != nil { return fmt.Errorf(`failed to write payload using ecdsa: %w`, err) } if !ecdsa.Verify(&pubkey, h.Sum(nil), r, s) { return fmt.Errorf(`failed to verify signature using ecdsa`) } return nil } golang-github-lestrrat-go-jwx-3.0.13/jws/legacy/eddsa.go000066400000000000000000000037771515060566400231000ustar00rootroot00000000000000package legacy import ( "crypto" "crypto/ed25519" "crypto/rand" "fmt" "github.com/lestrrat-go/jwx/v3/internal/keyconv" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/lestrrat-go/jwx/v3/jws/internal/keytype" ) type eddsaSigner struct{} func NewEdDSASigner() Signer { return &eddsaSigner{} } func (s eddsaSigner) Algorithm() jwa.SignatureAlgorithm { return jwa.EdDSA() } func (s eddsaSigner) Sign(payload []byte, key any) ([]byte, error) { if key == nil { return nil, fmt.Errorf(`missing private key while signing payload`) } // The ed25519.PrivateKey object implements crypto.Signer, so we should // simply accept a crypto.Signer here. signer, ok := key.(crypto.Signer) if ok { if !keytype.IsValidEDDSAKey(key) { return nil, fmt.Errorf(`cannot use key of type %T to generate EdDSA based signatures`, key) } } else { // This fallback exists for cases when jwk.Key was passed, or // users gave us a pointer instead of non-pointer, etc. var privkey ed25519.PrivateKey if err := keyconv.Ed25519PrivateKey(&privkey, key); err != nil { return nil, fmt.Errorf(`failed to retrieve ed25519.PrivateKey out of %T: %w`, key, err) } signer = privkey } return signer.Sign(rand.Reader, payload, crypto.Hash(0)) } type eddsaVerifier struct{} func NewEdDSAVerifier() Verifier { return &eddsaVerifier{} } func (v eddsaVerifier) Verify(payload, signature []byte, key any) (err error) { if key == nil { return fmt.Errorf(`missing public key while verifying payload`) } var pubkey ed25519.PublicKey signer, ok := key.(crypto.Signer) if ok { v := signer.Public() pubkey, ok = v.(ed25519.PublicKey) if !ok { return fmt.Errorf(`expected crypto.Signer.Public() to return ed25519.PublicKey, but got %T`, v) } } else { if err := keyconv.Ed25519PublicKey(&pubkey, key); err != nil { return fmt.Errorf(`failed to retrieve ed25519.PublicKey out of %T: %w`, key, err) } } if !ed25519.Verify(pubkey, payload, signature) { return fmt.Errorf(`failed to match EdDSA signature`) } return nil } golang-github-lestrrat-go-jwx-3.0.13/jws/legacy/hmac.go000066400000000000000000000040471515060566400227170ustar00rootroot00000000000000package legacy import ( "crypto/hmac" "crypto/sha256" "crypto/sha512" "fmt" "hash" "github.com/lestrrat-go/jwx/v3/internal/keyconv" "github.com/lestrrat-go/jwx/v3/jwa" ) func init() { algs := map[jwa.SignatureAlgorithm]func() hash.Hash{ jwa.HS256(): sha256.New, jwa.HS384(): sha512.New384, jwa.HS512(): sha512.New, } for alg, h := range algs { hmacSignFuncs[alg] = makeHMACSignFunc(h) } } // HMACSigner uses crypto/hmac to sign the payloads. // This is for legacy support only. type HMACSigner struct { alg jwa.SignatureAlgorithm sign hmacSignFunc } type HMACVerifier struct { signer Signer } type hmacSignFunc func(payload []byte, key []byte) ([]byte, error) var hmacSignFuncs = make(map[jwa.SignatureAlgorithm]hmacSignFunc) func NewHMACSigner(alg jwa.SignatureAlgorithm) Signer { return &HMACSigner{ alg: alg, sign: hmacSignFuncs[alg], // we know this will succeed } } func makeHMACSignFunc(hfunc func() hash.Hash) hmacSignFunc { return func(payload []byte, key []byte) ([]byte, error) { h := hmac.New(hfunc, key) if _, err := h.Write(payload); err != nil { return nil, fmt.Errorf(`failed to write payload using hmac: %w`, err) } return h.Sum(nil), nil } } func (s HMACSigner) Algorithm() jwa.SignatureAlgorithm { return s.alg } func (s HMACSigner) Sign(payload []byte, key any) ([]byte, error) { var hmackey []byte if err := keyconv.ByteSliceKey(&hmackey, key); err != nil { return nil, fmt.Errorf(`invalid key type %T. []byte is required: %w`, key, err) } if len(hmackey) == 0 { return nil, fmt.Errorf(`missing key while signing payload`) } return s.sign(payload, hmackey) } func NewHMACVerifier(alg jwa.SignatureAlgorithm) Verifier { s := NewHMACSigner(alg) return &HMACVerifier{signer: s} } func (v HMACVerifier) Verify(payload, signature []byte, key any) (err error) { expected, err := v.signer.Sign(payload, key) if err != nil { return fmt.Errorf(`failed to generated signature: %w`, err) } if !hmac.Equal(signature, expected) { return fmt.Errorf(`failed to match hmac signature`) } return nil } golang-github-lestrrat-go-jwx-3.0.13/jws/legacy/legacy.go000066400000000000000000000026421515060566400232520ustar00rootroot00000000000000// Package legacy provides support for legacy implementation of JWS signing and verification. // Types, functions, and variables in this package are exported only for legacy support, // and should not be relied upon for new code. // // This package will be available until v3 is sunset, but it will be removed in v4 package legacy import ( "github.com/lestrrat-go/jwx/v3/jwa" ) // Signer generates the signature for a given payload. // This is for legacy support only. type Signer interface { // Sign creates a signature for the given payload. // The second argument is the key used for signing the payload, and is usually // the private key type associated with the signature method. For example, // for `jwa.RSXXX` and `jwa.PSXXX` types, you need to pass the // `*"crypto/rsa".PrivateKey` type. // Check the documentation for each signer for details Sign([]byte, any) ([]byte, error) Algorithm() jwa.SignatureAlgorithm } // Verifier is for legacy support only. type Verifier interface { // Verify checks whether the payload and signature are valid for // the given key. // `key` is the key used for verifying the payload, and is usually // the public key associated with the signature method. For example, // for `jwa.RSXXX` and `jwa.PSXXX` types, you need to pass the // `*"crypto/rsa".PublicKey` type. // Check the documentation for each verifier for details Verify(payload []byte, signature []byte, key any) error } golang-github-lestrrat-go-jwx-3.0.13/jws/legacy/rsa.go000066400000000000000000000062071515060566400225740ustar00rootroot00000000000000package legacy import ( "crypto" "crypto/rand" "crypto/rsa" "fmt" "github.com/lestrrat-go/jwx/v3/internal/keyconv" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/lestrrat-go/jwx/v3/jws/internal/keytype" ) var rsaSigners = make(map[jwa.SignatureAlgorithm]*rsaSigner) var rsaVerifiers = make(map[jwa.SignatureAlgorithm]*rsaVerifier) func init() { data := map[jwa.SignatureAlgorithm]struct { Hash crypto.Hash PSS bool }{ jwa.RS256(): { Hash: crypto.SHA256, }, jwa.RS384(): { Hash: crypto.SHA384, }, jwa.RS512(): { Hash: crypto.SHA512, }, jwa.PS256(): { Hash: crypto.SHA256, PSS: true, }, jwa.PS384(): { Hash: crypto.SHA384, PSS: true, }, jwa.PS512(): { Hash: crypto.SHA512, PSS: true, }, } for alg, item := range data { rsaSigners[alg] = &rsaSigner{ alg: alg, hash: item.Hash, pss: item.PSS, } rsaVerifiers[alg] = &rsaVerifier{ alg: alg, hash: item.Hash, pss: item.PSS, } } } type rsaSigner struct { alg jwa.SignatureAlgorithm hash crypto.Hash pss bool } func NewRSASigner(alg jwa.SignatureAlgorithm) Signer { return rsaSigners[alg] } func (rs *rsaSigner) Algorithm() jwa.SignatureAlgorithm { return rs.alg } func (rs *rsaSigner) Sign(payload []byte, key any) ([]byte, error) { if key == nil { return nil, fmt.Errorf(`missing private key while signing payload`) } signer, ok := key.(crypto.Signer) if ok { if !keytype.IsValidRSAKey(key) { return nil, fmt.Errorf(`cannot use key of type %T to generate RSA based signatures`, key) } } else { var privkey rsa.PrivateKey if err := keyconv.RSAPrivateKey(&privkey, key); err != nil { return nil, fmt.Errorf(`failed to retrieve rsa.PrivateKey out of %T: %w`, key, err) } signer = &privkey } h := rs.hash.New() if _, err := h.Write(payload); err != nil { return nil, fmt.Errorf(`failed to write payload to hash: %w`, err) } if rs.pss { return signer.Sign(rand.Reader, h.Sum(nil), &rsa.PSSOptions{ Hash: rs.hash, SaltLength: rsa.PSSSaltLengthEqualsHash, }) } return signer.Sign(rand.Reader, h.Sum(nil), rs.hash) } type rsaVerifier struct { alg jwa.SignatureAlgorithm hash crypto.Hash pss bool } func NewRSAVerifier(alg jwa.SignatureAlgorithm) Verifier { return rsaVerifiers[alg] } func (rv *rsaVerifier) Verify(payload, signature []byte, key any) error { if key == nil { return fmt.Errorf(`missing public key while verifying payload`) } var pubkey rsa.PublicKey if cs, ok := key.(crypto.Signer); ok { cpub := cs.Public() switch cpub := cpub.(type) { case rsa.PublicKey: pubkey = cpub case *rsa.PublicKey: pubkey = *cpub default: return fmt.Errorf(`failed to retrieve rsa.PublicKey out of crypto.Signer %T`, key) } } else { if err := keyconv.RSAPublicKey(&pubkey, key); err != nil { return fmt.Errorf(`failed to retrieve rsa.PublicKey out of %T: %w`, key, err) } } h := rv.hash.New() if _, err := h.Write(payload); err != nil { return fmt.Errorf(`failed to write payload to hash: %w`, err) } if rv.pss { return rsa.VerifyPSS(&pubkey, rv.hash, h.Sum(nil), signature, nil) } return rsa.VerifyPKCS1v15(&pubkey, rv.hash, h.Sum(nil), signature) } golang-github-lestrrat-go-jwx-3.0.13/jws/message.go000066400000000000000000000337671515060566400222020ustar00rootroot00000000000000package jws import ( "bytes" "fmt" "github.com/lestrrat-go/jwx/v3/internal/base64" "github.com/lestrrat-go/jwx/v3/internal/json" "github.com/lestrrat-go/jwx/v3/internal/pool" "github.com/lestrrat-go/jwx/v3/internal/tokens" "github.com/lestrrat-go/jwx/v3/jwa" ) func NewSignature() *Signature { return &Signature{} } func (s *Signature) DecodeCtx() DecodeCtx { return s.dc } func (s *Signature) SetDecodeCtx(dc DecodeCtx) { s.dc = dc } func (s Signature) PublicHeaders() Headers { return s.headers } func (s *Signature) SetPublicHeaders(v Headers) *Signature { s.headers = v return s } func (s Signature) ProtectedHeaders() Headers { return s.protected } func (s *Signature) SetProtectedHeaders(v Headers) *Signature { s.protected = v return s } func (s Signature) Signature() []byte { return s.signature } func (s *Signature) SetSignature(v []byte) *Signature { s.signature = v return s } type signatureUnmarshalProbe struct { Header Headers `json:"header,omitempty"` Protected *string `json:"protected,omitempty"` Signature *string `json:"signature,omitempty"` } func (s *Signature) UnmarshalJSON(data []byte) error { var sup signatureUnmarshalProbe sup.Header = NewHeaders() if err := json.Unmarshal(data, &sup); err != nil { return fmt.Errorf(`failed to unmarshal signature into temporary struct: %w`, err) } s.headers = sup.Header if buf := sup.Protected; buf != nil { src := []byte(*buf) if !bytes.HasPrefix(src, []byte{tokens.OpenCurlyBracket}) { decoded, err := base64.Decode(src) if err != nil { return fmt.Errorf(`failed to base64 decode protected headers: %w`, err) } src = decoded } prt := NewHeaders() //nolint:forcetypeassert prt.(*stdHeaders).SetDecodeCtx(s.DecodeCtx()) if err := json.Unmarshal(src, prt); err != nil { return fmt.Errorf(`failed to unmarshal protected headers: %w`, err) } //nolint:forcetypeassert prt.(*stdHeaders).SetDecodeCtx(nil) s.protected = prt } if sup.Signature != nil { decoded, err := base64.DecodeString(*sup.Signature) if err != nil { return fmt.Errorf(`failed to base decode signature: %w`, err) } s.signature = decoded } return nil } // Sign populates the signature field, with a signature generated by // given the signer object and payload. // // The first return value is the raw signature in binary format. // The second return value s the full three-segment signature // (e.g. "eyXXXX.XXXXX.XXXX") // // This method is deprecated, and will be remove in a future release. // Signature objects in the future will only be used as containers, // and signing will be done using the `jws.Sign` function, or alternatively // you could use jwsbb package to craft the signature manually. func (s *Signature) Sign(payload []byte, signer Signer, key any) ([]byte, []byte, error) { return s.sign2(payload, signer, key) } func (s *Signature) sign2(payload []byte, signer interface{ Algorithm() jwa.SignatureAlgorithm }, key any) ([]byte, []byte, error) { // Create a signatureBuilder to use the shared signing logic sb := signatureBuilderPool.Get() defer signatureBuilderPool.Put(sb) sb.alg = signer.Algorithm() sb.key = key sb.protected = s.protected sb.public = s.headers // Set up the appropriate signer interface switch typedSigner := signer.(type) { case Signer2: sb.signer2 = typedSigner case Signer: sb.signer = typedSigner default: return nil, nil, fmt.Errorf(`invalid signer type: %T`, signer) } // Create a minimal sign context sc := signContextPool.Get() defer signContextPool.Put(sc) sc.detached = s.detached encoder := s.encoder if encoder == nil { encoder = base64.DefaultEncoder() } sc.encoder = encoder // Build the signature using signatureBuilder sig, err := sb.Build(sc, payload) if err != nil { return nil, nil, fmt.Errorf(`failed to build signature: %w`, err) } // Copy the signature result back to this signature instance s.signature = sig.signature s.protected = sig.protected s.headers = sig.headers // Build the complete JWS token for the return value buf := pool.BytesBuffer().Get() defer pool.BytesBuffer().Put(buf) // Marshal the merged headers for the final output hdrs, err := mergeHeaders(s.headers, s.protected) if err != nil { return nil, nil, fmt.Errorf(`failed to merge headers: %w`, err) } hdrbuf, err := json.Marshal(hdrs) if err != nil { return nil, nil, fmt.Errorf(`failed to marshal headers: %w`, err) } buf.WriteString(encoder.EncodeToString(hdrbuf)) buf.WriteByte(tokens.Period) var plen int b64 := getB64Value(hdrs) if b64 { encoded := encoder.EncodeToString(payload) plen = len(encoded) buf.WriteString(encoded) } else { if !s.detached { if bytes.Contains(payload, []byte{tokens.Period}) { return nil, nil, fmt.Errorf(`payload must not contain a "."`) } } plen = len(payload) buf.Write(payload) } // Handle detached payload if s.detached { buf.Truncate(buf.Len() - plen) } buf.WriteByte(tokens.Period) buf.WriteString(encoder.EncodeToString(s.signature)) ret := make([]byte, buf.Len()) copy(ret, buf.Bytes()) return s.signature, ret, nil } func NewMessage() *Message { return &Message{} } // Clears the internal raw buffer that was accumulated during // the verify phase func (m *Message) clearRaw() { for _, sig := range m.signatures { if protected := sig.protected; protected != nil { if cr, ok := protected.(*stdHeaders); ok { cr.raw = nil } } } } func (m *Message) SetDecodeCtx(dc DecodeCtx) { m.dc = dc } func (m *Message) DecodeCtx() DecodeCtx { return m.dc } // Payload returns the decoded payload func (m Message) Payload() []byte { return m.payload } func (m *Message) SetPayload(v []byte) *Message { m.payload = v return m } func (m Message) Signatures() []*Signature { return m.signatures } func (m *Message) AppendSignature(v *Signature) *Message { m.signatures = append(m.signatures, v) return m } func (m *Message) ClearSignatures() *Message { m.signatures = nil return m } // LookupSignature looks up a particular signature entry using // the `kid` value func (m Message) LookupSignature(kid string) []*Signature { var sigs []*Signature for _, sig := range m.signatures { if hdr := sig.PublicHeaders(); hdr != nil { hdrKeyID, ok := hdr.KeyID() if ok && hdrKeyID == kid { sigs = append(sigs, sig) continue } } if hdr := sig.ProtectedHeaders(); hdr != nil { hdrKeyID, ok := hdr.KeyID() if ok && hdrKeyID == kid { sigs = append(sigs, sig) continue } } } return sigs } // This struct is used to first probe for the structure of the // incoming JSON object. We then decide how to parse it // from the fields that are populated. type messageUnmarshalProbe struct { Payload *string `json:"payload"` Signatures []json.RawMessage `json:"signatures,omitempty"` Header Headers `json:"header,omitempty"` Protected *string `json:"protected,omitempty"` Signature *string `json:"signature,omitempty"` } func (m *Message) UnmarshalJSON(buf []byte) error { m.payload = nil m.signatures = nil m.b64 = true var mup messageUnmarshalProbe mup.Header = NewHeaders() if err := json.Unmarshal(buf, &mup); err != nil { return fmt.Errorf(`failed to unmarshal into temporary structure: %w`, err) } b64 := true if mup.Signature == nil { // flattened signature is NOT present if len(mup.Signatures) == 0 { return fmt.Errorf(`required field "signatures" not present`) } m.signatures = make([]*Signature, 0, len(mup.Signatures)) for i, rawsig := range mup.Signatures { var sig Signature sig.SetDecodeCtx(m.DecodeCtx()) if err := json.Unmarshal(rawsig, &sig); err != nil { return fmt.Errorf(`failed to unmarshal signature #%d: %w`, i+1, err) } sig.SetDecodeCtx(nil) if sig.protected == nil { // Instead of barfing on a nil protected header, use an empty header sig.protected = NewHeaders() } if i == 0 { if !getB64Value(sig.protected) { b64 = false } } else { if b64 != getB64Value(sig.protected) { return fmt.Errorf(`b64 value must be the same for all signatures`) } } m.signatures = append(m.signatures, &sig) } } else { // .signature is present, it's a flattened structure if len(mup.Signatures) != 0 { return fmt.Errorf(`invalid format ("signatures" and "signature" keys cannot both be present)`) } var sig Signature sig.headers = mup.Header if src := mup.Protected; src != nil { decoded, err := base64.DecodeString(*src) if err != nil { return fmt.Errorf(`failed to base64 decode flattened protected headers: %w`, err) } prt := NewHeaders() //nolint:forcetypeassert prt.(*stdHeaders).SetDecodeCtx(m.DecodeCtx()) if err := json.Unmarshal(decoded, prt); err != nil { return fmt.Errorf(`failed to unmarshal flattened protected headers: %w`, err) } //nolint:forcetypeassert prt.(*stdHeaders).SetDecodeCtx(nil) sig.protected = prt } if sig.protected == nil { // Instead of barfing on a nil protected header, use an empty header sig.protected = NewHeaders() } decoded, err := base64.DecodeString(*mup.Signature) if err != nil { return fmt.Errorf(`failed to base64 decode flattened signature: %w`, err) } sig.signature = decoded m.signatures = []*Signature{&sig} b64 = getB64Value(sig.protected) } if mup.Payload != nil { if !b64 { // NOT base64 encoded m.payload = []byte(*mup.Payload) } else { decoded, err := base64.DecodeString(*mup.Payload) if err != nil { return fmt.Errorf(`failed to base64 decode payload: %w`, err) } m.payload = decoded } } m.b64 = b64 return nil } func (m Message) MarshalJSON() ([]byte, error) { if len(m.signatures) == 1 { return m.marshalFlattened() } return m.marshalFull() } func (m Message) marshalFlattened() ([]byte, error) { buf := pool.BytesBuffer().Get() defer pool.BytesBuffer().Put(buf) sig := m.signatures[0] buf.WriteRune(tokens.OpenCurlyBracket) var wrote bool if hdr := sig.headers; hdr != nil { hdrjs, err := json.Marshal(hdr) if err != nil { return nil, fmt.Errorf(`failed to marshal "header" (flattened format): %w`, err) } buf.WriteString(`"header":`) buf.Write(hdrjs) wrote = true } if wrote { buf.WriteRune(tokens.Comma) } buf.WriteString(`"payload":"`) buf.WriteString(base64.EncodeToString(m.payload)) buf.WriteRune('"') if protected := sig.protected; protected != nil { protectedbuf, err := json.Marshal(protected) if err != nil { return nil, fmt.Errorf(`failed to marshal "protected" (flattened format): %w`, err) } buf.WriteString(`,"protected":"`) buf.WriteString(base64.EncodeToString(protectedbuf)) buf.WriteRune('"') } buf.WriteString(`,"signature":"`) buf.WriteString(base64.EncodeToString(sig.signature)) buf.WriteRune('"') buf.WriteRune(tokens.CloseCurlyBracket) ret := make([]byte, buf.Len()) copy(ret, buf.Bytes()) return ret, nil } func (m Message) marshalFull() ([]byte, error) { buf := pool.BytesBuffer().Get() defer pool.BytesBuffer().Put(buf) buf.WriteString(`{"payload":"`) buf.WriteString(base64.EncodeToString(m.payload)) buf.WriteString(`","signatures":[`) for i, sig := range m.signatures { if i > 0 { buf.WriteRune(tokens.Comma) } buf.WriteRune(tokens.OpenCurlyBracket) var wrote bool if hdr := sig.headers; hdr != nil { hdrbuf, err := json.Marshal(hdr) if err != nil { return nil, fmt.Errorf(`failed to marshal "header" for signature #%d: %w`, i+1, err) } buf.WriteString(`"header":`) buf.Write(hdrbuf) wrote = true } if protected := sig.protected; protected != nil { protectedbuf, err := json.Marshal(protected) if err != nil { return nil, fmt.Errorf(`failed to marshal "protected" for signature #%d: %w`, i+1, err) } if wrote { buf.WriteRune(tokens.Comma) } buf.WriteString(`"protected":"`) buf.WriteString(base64.EncodeToString(protectedbuf)) buf.WriteRune('"') wrote = true } if len(sig.signature) > 0 { // If InsecureNoSignature is enabled, signature may not exist if wrote { buf.WriteRune(tokens.Comma) } buf.WriteString(`"signature":"`) buf.WriteString(base64.EncodeToString(sig.signature)) buf.WriteString(`"`) } buf.WriteString(`}`) } buf.WriteString(`]}`) ret := make([]byte, buf.Len()) copy(ret, buf.Bytes()) return ret, nil } // Compact generates a JWS message in compact serialization format from // `*jws.Message` object. The object contain exactly one signature, or // an error is returned. // // If using a detached payload, the payload must already be stored in // the `*jws.Message` object, and the `jws.WithDetached()` option // must be passed to the function. func Compact(msg *Message, options ...CompactOption) ([]byte, error) { if l := len(msg.signatures); l != 1 { return nil, fmt.Errorf(`jws.Compact: cannot serialize message with %d signatures (must be one)`, l) } var detached bool var encoder Base64Encoder = base64.DefaultEncoder() for _, option := range options { switch option.Ident() { case identDetached{}: if err := option.Value(&detached); err != nil { return nil, fmt.Errorf(`jws.Compact: failed to retrieve detached option value: %w`, err) } case identBase64Encoder{}: if err := option.Value(&encoder); err != nil { return nil, fmt.Errorf(`jws.Compact: failed to retrieve base64 encoder option value: %w`, err) } } } s := msg.signatures[0] // XXX check if this is correct hdrs := s.ProtectedHeaders() hdrbuf, err := json.Marshal(hdrs) if err != nil { return nil, fmt.Errorf(`jws.Compress: failed to marshal headers: %w`, err) } buf := pool.BytesBuffer().Get() defer pool.BytesBuffer().Put(buf) buf.WriteString(encoder.EncodeToString(hdrbuf)) buf.WriteByte(tokens.Period) if !detached { if getB64Value(hdrs) { encoded := encoder.EncodeToString(msg.payload) buf.WriteString(encoded) } else { if bytes.Contains(msg.payload, []byte{tokens.Period}) { return nil, fmt.Errorf(`jws.Compress: payload must not contain a "."`) } buf.Write(msg.payload) } } buf.WriteByte(tokens.Period) buf.WriteString(encoder.EncodeToString(s.signature)) ret := make([]byte, buf.Len()) copy(ret, buf.Bytes()) return ret, nil } golang-github-lestrrat-go-jwx-3.0.13/jws/message_test.go000066400000000000000000000105561515060566400232300ustar00rootroot00000000000000package jws_test import ( "testing" "github.com/lestrrat-go/jwx/v3/internal/base64" "github.com/lestrrat-go/jwx/v3/internal/json" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/lestrrat-go/jwx/v3/jws" "github.com/stretchr/testify/require" ) func TestMessage(t *testing.T) { t.Run("JSON", func(t *testing.T) { const src = `{ "payload": "eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ", "signatures": [ { "header": { "kid": "2010-12-29" }, "protected": "eyJhbGciOiJSUzI1NiJ9", "signature": "cC4hiUPoj9Eetdgtv3hF80EGrhuB__dzERat0XF9g2VtQgr9PJbu3XOiZj5RZmh7AAuHIm4Bh-0Qc_lF5YKt_O8W2Fp5jujGbds9uJdbF9CUAr7t1dnZcAcQjbKBYNX4BAynRFdiuB--f_nZLgrnbyTyWzO75vRK5h6xBArLIARNPvkSjtQBMHlb1L07Qe7K0GarZRmB_eSN9383LcOLn6_dO--xi12jzDwusC-eOkHWEsqtFZESc6BfI7noOPqvhJ1phCnvWh6IeYI2w9QOYEUipUTI8np6LbgGY9Fs98rqVt5AXLIhWkWywlVmtVrBp0igcN_IoypGlUPQGe77Rw" }, { "header": { "kid": "e9bc097a-ce51-4036-9562-d2ade882db0d" }, "protected": "eyJhbGciOiJFUzI1NiJ9", "signature": "DtEhU3ljbEg8L38VWAfUAqOyKAM6-Xx-F4GawxaepmXFCgfTjDxw5djxLa8ISlSApmWQxfKTUJqPP3-Kg6NU1Q" } ] }` var m jws.Message require.NoError(t, json.Unmarshal([]byte(src), &m), `json.Unmarshal should succeed`) buf, err := json.MarshalIndent(m, "", " ") require.NoError(t, err, `json.Marshal should succeed`) require.Equal(t, src, string(buf), `roundtrip should match`) }) t.Run("Construction/Manipulation", func(t *testing.T) { const payload = `eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ` const encodedSig1 = `cC4hiUPoj9Eetdgtv3hF80EGrhuB__dzERat0XF9g2VtQgr9PJbu3XOiZj5RZmh7AAuHIm4Bh-0Qc_lF5YKt_O8W2Fp5jujGbds9uJdbF9CUAr7t1dnZcAcQjbKBYNX4BAynRFdiuB--f_nZLgrnbyTyWzO75vRK5h6xBArLIARNPvkSjtQBMHlb1L07Qe7K0GarZRmB_eSN9383LcOLn6_dO--xi12jzDwusC-eOkHWEsqtFZESc6BfI7noOPqvhJ1phCnvWh6IeYI2w9QOYEUipUTI8np6LbgGY9Fs98rqVt5AXLIhWkWywlVmtVrBp0igcN_IoypGlUPQGe77Rw` const encodedSig2 = "DtEhU3ljbEg8L38VWAfUAqOyKAM6-Xx-F4GawxaepmXFCgfTjDxw5djxLa8ISlSApmWQxfKTUJqPP3-Kg6NU1Q" decodedPayload, err := base64.DecodeString(payload) require.NoError(t, err, `base64.DecodeString should succeed (payload)`) decodedSig1, err := base64.DecodeString(encodedSig1) require.NoError(t, err, `base64.DecodeString should succeed (sig1)`) decodedSig2, err := base64.DecodeString(encodedSig2) require.NoError(t, err, `base64.DecodeString should succeed (sig2)`) public1 := jws.NewHeaders() require.NoError(t, public1.Set(jws.AlgorithmKey, jwa.RS256()), `public1.Set should succeed`) protected1 := jws.NewHeaders() require.NoError(t, protected1.Set(jws.KeyIDKey, "2010-12-29"), `protected1.Set should succeed`) public2 := jws.NewHeaders() require.NoError(t, public2.Set(jws.AlgorithmKey, jwa.ES256()), `public2.Set should succeed`) protected2 := jws.NewHeaders() require.NoError(t, protected2.Set(jws.KeyIDKey, "e9bc097a-ce51-4036-9562-d2ade882db0d"), `protected2.Set should succeed`) m := jws.NewMessage(). SetPayload(decodedPayload). AppendSignature( jws.NewSignature(). SetSignature(decodedSig1). SetProtectedHeaders(public1). SetPublicHeaders(protected1), ). AppendSignature( jws.NewSignature(). SetSignature(decodedSig2). SetProtectedHeaders(public2). SetPublicHeaders(protected2), ) const expected = `{ "payload": "eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ", "signatures": [ { "header": { "kid": "2010-12-29" }, "protected": "eyJhbGciOiJSUzI1NiJ9", "signature": "cC4hiUPoj9Eetdgtv3hF80EGrhuB__dzERat0XF9g2VtQgr9PJbu3XOiZj5RZmh7AAuHIm4Bh-0Qc_lF5YKt_O8W2Fp5jujGbds9uJdbF9CUAr7t1dnZcAcQjbKBYNX4BAynRFdiuB--f_nZLgrnbyTyWzO75vRK5h6xBArLIARNPvkSjtQBMHlb1L07Qe7K0GarZRmB_eSN9383LcOLn6_dO--xi12jzDwusC-eOkHWEsqtFZESc6BfI7noOPqvhJ1phCnvWh6IeYI2w9QOYEUipUTI8np6LbgGY9Fs98rqVt5AXLIhWkWywlVmtVrBp0igcN_IoypGlUPQGe77Rw" }, { "header": { "kid": "e9bc097a-ce51-4036-9562-d2ade882db0d" }, "protected": "eyJhbGciOiJFUzI1NiJ9", "signature": "DtEhU3ljbEg8L38VWAfUAqOyKAM6-Xx-F4GawxaepmXFCgfTjDxw5djxLa8ISlSApmWQxfKTUJqPP3-Kg6NU1Q" } ] }` buf, err := json.MarshalIndent(m, "", " ") require.NoError(t, err, `json.MarshalIndent should succeed`) require.Equal(t, expected, string(buf), `output should match`) }) } golang-github-lestrrat-go-jwx-3.0.13/jws/options.go000066400000000000000000000233421515060566400222350ustar00rootroot00000000000000package jws import ( "github.com/lestrrat-go/jwx/v3/jwa" "github.com/lestrrat-go/jwx/v3/jwk" "github.com/lestrrat-go/option/v2" ) type identInsecureNoSignature struct{} // WithJSON specifies that the result of `jws.Sign()` is serialized in // JSON format. // // If you pass multiple keys to `jws.Sign()`, it will fail unless // you also pass this option. func WithJSON(options ...WithJSONSuboption) SignVerifyParseOption { var pretty bool for _, option := range options { switch option.Ident() { case identPretty{}: if err := option.Value(&pretty); err != nil { panic(`jws.WithJSON() option must be of type bool`) } } } format := fmtJSON if pretty { format = fmtJSONPretty } return &signVerifyParseOption{option.New(identSerialization{}, format)} } type withKey struct { alg jwa.KeyAlgorithm key any protected Headers public Headers } // Protected exists as an escape hatch to modify the header values after the fact func (w *withKey) Protected(v Headers) Headers { if w.protected == nil && v != nil { w.protected = v } return w.protected } // WithKey is used to pass a static algorithm/key pair to either `jws.Sign()` or `jws.Verify()`. // // The `alg` parameter is the identifier for the signature algorithm that should be used. // It is of type `jwa.KeyAlgorithm` but in reality you can only pass `jwa.SignatureAlgorithm` // types. It is this way so that the value in `(jwk.Key).Algorithm()` can be directly // passed to the option. If you specify other algorithm types such as `jwa.KeyEncryptionAlgorithm`, // then you will get an error when `jws.Sign()` or `jws.Verify()` is executed. // // The `alg` parameter cannot be "none" (jwa.NoSignature) for security reasons. // You will have to use a separate, more explicit option to allow the use of "none" // algorithm (WithInsecureNoSignature). // // The algorithm specified in the `alg` parameter MUST be able to support // the type of key you provided, otherwise an error is returned. // // Any of the following is accepted for the `key` parameter: // * A "raw" key (e.g. rsa.PrivateKey, ecdsa.PrivateKey, etc) // * A crypto.Signer // * A jwk.Key // // Note that due to technical reasons, this library is NOT able to differentiate // between a valid/invalid key for given algorithm if the key implements crypto.Signer // and the key is from an external library. For example, while we can tell that it is // invalid to use `jwk.WithKey(jwa.RSA256, ecdsaPrivateKey)` because the key is // presumably from `crypto/ecdsa` or this library, if you use a KMS wrapper // that implements crypto.Signer that is outside of the go standard library or this // library, we will not be able to properly catch the misuse of such keys -- // the output will happily generate an ECDSA signature even in the presence of // `jwa.RSA256` // // A `crypto.Signer` is used when the private part of a key is // kept in an inaccessible location, such as hardware. // `crypto.Signer` is currently supported for RSA, ECDSA, and EdDSA // family of algorithms. You may consider using `github.com/jwx-go/crypto-signer` // if you would like to use keys stored in GCP/AWS KMS services. // // If the key is a jwk.Key and the key contains a key ID (`kid` field), // then it is added to the protected header generated by the signature. // // `jws.WithKey()` can further accept suboptions to change signing behavior // when used with `jws.Sign()`. `jws.WithProtected()` and `jws.WithPublic()` // can be passed to specify JWS headers that should be used whe signing. // // If the protected headers contain "b64" field, then the boolean value for the field // is respected when serializing. That is, if you specify a header with // `{"b64": false}`, then the payload is not base64 encoded. // // These suboptions are ignored when the `jws.WithKey()` option is used with `jws.Verify()`. func WithKey(alg jwa.KeyAlgorithm, key any, options ...WithKeySuboption) SignVerifyOption { // Implementation note: this option is shared between Sign() and // Verify(). As such we don't create a KeyProvider here because // if used in Sign() we would be doing something else. var protected, public Headers for _, option := range options { switch option.Ident() { case identProtectedHeaders{}: if err := option.Value(&protected); err != nil { panic(`jws.WithKey() option must be of type Headers`) } case identPublicHeaders{}: if err := option.Value(&public); err != nil { panic(`jws.WithKey() option must be of type Headers`) } } } return &signVerifyOption{ option.New(identKey{}, &withKey{ alg: alg, key: key, protected: protected, public: public, }), } } // WithKeySet specifies a JWKS (jwk.Set) to use for verification. // // Because a JWKS can contain multiple keys and this library cannot tell // which one of the keys should be used for verification, we by default // require that both `alg` and `kid` fields in the JWS _and_ the // key match before a key is considered to be used. // // There are ways to override this behavior, but they must be explicitly // specified by the caller. // // To work with keys/JWS messages not having a `kid` field, you may specify // the suboption `WithKeySetRequired` via `jws.WithKey(key, jws.WithRequireKid(false))`. // This will allow the library to proceed without having to match the `kid` field. // // However, it will still check if the `alg` fields in the JWS message and the key(s) // match. If you must work with JWS messages that do not have an `alg` field, // you will need to use `jws.WithKeySet(key, jws.WithInferAlgorithm(true))`. // // See the documentation for `WithInferAlgorithm()` for more details. func WithKeySet(set jwk.Set, options ...WithKeySetSuboption) VerifyOption { requireKid := true var useDefault, inferAlgorithm, multipleKeysPerKeyID bool for _, option := range options { switch option.Ident() { case identRequireKid{}: if err := option.Value(&requireKid); err != nil { panic(`jws.WithKeySet() option must be of type bool`) } case identUseDefault{}: if err := option.Value(&useDefault); err != nil { panic(`jws.WithKeySet() option must be of type bool`) } case identMultipleKeysPerKeyID{}: if err := option.Value(&multipleKeysPerKeyID); err != nil { panic(`jws.WithKeySet() option must be of type bool`) } case identInferAlgorithmFromKey{}: if err := option.Value(&inferAlgorithm); err != nil { panic(`jws.WithKeySet() option must be of type bool`) } } } return WithKeyProvider(&keySetProvider{ set: set, requireKid: requireKid, useDefault: useDefault, multipleKeysPerKeyID: multipleKeysPerKeyID, inferAlgorithm: inferAlgorithm, }) } // WithVerifyAuto enables automatic verification of the signature using the JWKS specified in // the `jku` header. Note that by default this option will _reject_ any jku // provided by the JWS message. Read on for details. // // The JWKS is retrieved by the `jwk.Fetcher` specified in the first argument. // If the fetcher object is nil, the default fetcher, which is the `jwk.Fetch()` // function (wrapped in the `jwk.FetchFunc` type) is used. // // The remaining arguments are passed to the `(jwk.Fetcher).Fetch` method // when the JWKS is retrieved. // // jws.WithVerifyAuto(nil) // uses jwk.Fetch // jws.WithVerifyAuto(jwk.NewCachedFetcher(...)) // uses cached fetcher // jws.WithVerifyAuto(myFetcher) // use your custom fetcher // // By default a whitelist that disallows all URLs is added to the options // passed to the fetcher. You must explicitly specify a whitelist that allows // the URLs you trust. This default behavior is provided because by design // of the JWS specification it is the/ caller's responsibility to verify if // the URL specified in the `jku` header can be trusted -- thus by default // we trust nothing. // // Users are free to specify an open whitelist if they so choose, but this must // be explicitly done: // // jws.WithVerifyAuto(nil, jwk.WithFetchWhitelist(jwk.InsecureWhitelist())) // // You can also use `jwk.CachedFetcher` to use cached JWKS objects, but do note // that this object is not really designed to accommodate a large set of // arbitrary URLs. Use `jwk.CachedFetcher` as the first argument if you only // have a small set of URLs that you trust. For anything more complex, you should // implement your own `jwk.Fetcher` object. func WithVerifyAuto(f jwk.Fetcher, options ...jwk.FetchOption) VerifyOption { // the option MUST start with a "disallow no whitelist" to force // users provide a whitelist options = append(append([]jwk.FetchOption(nil), jwk.WithFetchWhitelist(allowNoneWhitelist)), options...) return WithKeyProvider(jkuProvider{ fetcher: f, options: options, }) } type withInsecureNoSignature struct { protected Headers } // Protected exists as an escape hatch to modify the header values after the fact func (w *withInsecureNoSignature) Protected(v Headers) Headers { if w.protected == nil && v != nil { w.protected = v } return w.protected } // WithInsecureNoSignature creates an option that allows the user to use the // "none" signature algorithm. // // Please note that this is insecure, and should never be used in production // (this is exactly why specifying "none"/jwa.NoSignature to `jws.WithKey()` // results in an error when `jws.Sign()` is called -- we do not allow using // "none" by accident) // // TODO: create specific suboption set for this option func WithInsecureNoSignature(options ...WithKeySuboption) SignOption { var protected Headers for _, option := range options { switch option.Ident() { case identProtectedHeaders{}: if err := option.Value(&protected); err != nil { panic(`jws.WithInsecureNoSignature() option must be of type Headers`) } } } return &signOption{ option.New(identInsecureNoSignature{}, &withInsecureNoSignature{ protected: protected, }, ), } } golang-github-lestrrat-go-jwx-3.0.13/jws/options.yaml000066400000000000000000000213431515060566400225710ustar00rootroot00000000000000package_name: jws output: jws/options_gen.go interfaces: - name: CompactOption comment: | CompactOption describes options that can be passed to `jws.Compact` - name: VerifyOption comment: | VerifyOption describes options that can be passed to `jws.Verify` methods: - verifyOption - parseOption - name: SignOption comment: | SignOption describes options that can be passed to `jws.Sign` - name: SignVerifyOption methods: - signOption - verifyOption - parseOption comment: | SignVerifyOption describes options that can be passed to either `jws.Verify` or `jws.Sign` - name: SignVerifyCompactOption methods: - signOption - verifyOption - compactOption - parseOption comment: | SignVerifyCompactOption describes options that can be passed to either `jws.Verify`, `jws.Sign`, or `jws.Compact` - name: WithJSONSuboption concrete_type: withJSONSuboption comment: | JSONSuboption describes suboptions that can be passed to the `jws.WithJSON()` option. - name: WithKeySuboption comment: | WithKeySuboption describes option types that can be passed to the `jws.WithKey()` option. - name: WithKeySetSuboption comment: | WithKeySetSuboption is a suboption passed to the `jws.WithKeySet()` option - name: ParseOption methods: - readFileOption comment: | ReadFileOption is a type of `Option` that can be passed to `jwe.Parse` - name: ReadFileOption comment: | ReadFileOption is a type of `Option` that can be passed to `jws.ReadFile` - name: SignVerifyParseOption methods: - signOption - verifyOption - parseOption - readFileOption - name: GlobalOption comment: | GlobalOption can be passed to `jws.Settings()` to set global options for the JWS package. options: - ident: Key skip_option: true - ident: Serialization skip_option: true - ident: Serialization option_name: WithCompact interface: SignVerifyParseOption constant_value: fmtCompact comment: | WithCompact specifies that the result of `jws.Sign()` is serialized in compact format. By default `jws.Sign()` will opt to use compact format, so you usually do not need to specify this option other than to be explicit about it - ident: Detached interface: CompactOption argument_type: bool comment: | WithDetached specifies that the `jws.Message` should be serialized in JWS compact serialization with detached payload. The resulting octet sequence will not contain the payload section. - ident: DetachedPayload interface: SignVerifyOption argument_type: '[]byte' comment: | WithDetachedPayload can be used to both sign or verify a JWS message with a detached payload. Note that this option does NOT populate the `b64` header, which is sometimes required by other JWS implementations. When this option is used for `jws.Sign()`, the first parameter (normally the payload) must be set to `nil`. If you have to verify using this option, you should know exactly how and why this works. - ident: Base64Encoder interface: SignVerifyCompactOption argument_type: Base64Encoder comment: | WithBase64Encoder specifies the base64 encoder to be used while signing or verifying the JWS message. By default, the raw URL base64 encoding (no padding) is used. - ident: Message interface: VerifyOption argument_type: '*Message' comment: | WithMessage can be passed to Verify() to obtain the jws.Message upon a successful verification. - ident: KeyUsed interface: VerifyOption argument_type: 'any' comment: | WithKeyUsed allows you to specify the `jws.Verify()` function to return the key used for verification. This may be useful when you specify multiple key sources or if you pass a `jwk.Set` and you want to know which key was successful at verifying the signature. `v` must be a pointer to an empty `any`. Do not use `jwk.Key` here unless you are 100% sure that all keys that you have provided are instances of `jwk.Key` (remember that the jwx API allows users to specify a raw key such as *rsa.PublicKey) - ident: ValidateKey interface: SignVerifyOption argument_type: bool comment: | WithValidateKey specifies whether the key used for signing or verification should be validated before using. Note that this means calling `key.Validate()` on the key, which in turn means that your key must be a `jwk.Key` instance, or a key that can be converted to a `jwk.Key` by calling `jwk.Import()`. This means that your custom hardware-backed keys will probably not work. You can directly call `key.Validate()` yourself if you need to mix keys that cannot be converted to `jwk.Key`. Please also note that use of this option will also result in one extra conversion of raw keys to a `jwk.Key` instance. If you care about shaving off as much as possible, consider using a pre-validated key instead of using this option to validate the key on-demand each time. By default, the key is not validated. - ident: InferAlgorithmFromKey interface: WithKeySetSuboption argument_type: bool comment: | WithInferAlgorithmFromKey specifies whether the JWS signing algorithm name should be inferred by looking at the provided key, in case the JWS message or the key does not have a proper `alg` header. When this option is set to true, a list of algorithm(s) that is compatible with the key type will be enumerated, and _ALL_ of them will be tried against the key/message pair. If any of them succeeds, the verification will be considered successful. Compared to providing explicit `alg` from the key this is slower, and verification may fail to verify if somehow our heuristics are wrong or outdated. Also, automatic detection of signature verification methods are always more vulnerable for potential attack vectors. It is highly recommended that you fix your key to contain a proper `alg` header field instead of resorting to using this option, but sometimes it just needs to happen. - ident: UseDefault interface: WithKeySetSuboption argument_type: bool comment: | WithUseDefault specifies that if and only if a jwk.Key contains exactly one jwk.Key, that key should be used. - ident: RequireKid interface: WithKeySetSuboption argument_type: bool comment: | WithRequiredKid specifies whether the keys in the jwk.Set should only be matched if the target JWS message's Key ID and the Key ID in the given key matches. - ident: MultipleKeysPerKeyID interface: WithKeySetSuboption argument_type: bool comment: | WithMultipleKeysPerKeyID specifies if we should expect multiple keys to match against a key ID. By default it is assumed that key IDs are unique, i.e. for a given key ID, the key set only contains a single key that has the matching ID. When this option is set to true, multiple keys that match the same key ID in the set can be tried. - ident: Pretty interface: WithJSONSuboption argument_type: bool comment: | WithPretty specifies whether the JSON output should be formatted and indented - ident: KeyProvider interface: VerifyOption argument_type: KeyProvider - ident: Context interface: VerifyOption argument_type: context.Context - ident: ProtectedHeaders interface: WithKeySuboption argument_type: Headers comment: | WithProtected is used with `jws.WithKey()` option when used with `jws.Sign()` to specify a protected header to be attached to the JWS signature. It has no effect if used when `jws.WithKey()` is passed to `jws.Verify()` - ident: PublicHeaders interface: WithKeySuboption argument_type: Headers comment: | WithPublic is used with `jws.WithKey()` option when used with `jws.Sign()` to specify a public header to be attached to the JWS signature. It has no effect if used when `jws.WithKey()` is passed to `jws.Verify()` `jws.Sign()` will result in an error if `jws.WithPublic()` is used and the serialization format is compact serialization. - ident: FS interface: ReadFileOption argument_type: fs.FS comment: | WithFS specifies the source `fs.FS` object to read the file from. - ident: LegacySigners interface: GlobalOption constant_value: true comment: | WithLegacySigners is a no-op option that exists only for backwards compatibility. golang-github-lestrrat-go-jwx-3.0.13/jws/options_gen.go000066400000000000000000000301041515060566400230600ustar00rootroot00000000000000// Code generated by tools/cmd/genoptions/main.go. DO NOT EDIT. package jws import ( "context" "io/fs" "github.com/lestrrat-go/option/v2" ) type Option = option.Interface // CompactOption describes options that can be passed to `jws.Compact` type CompactOption interface { Option compactOption() } type compactOption struct { Option } func (*compactOption) compactOption() {} // GlobalOption can be passed to `jws.Settings()` to set global options for the JWS package. type GlobalOption interface { Option globalOption() } type globalOption struct { Option } func (*globalOption) globalOption() {} // ReadFileOption is a type of `Option` that can be passed to `jwe.Parse` type ParseOption interface { Option readFileOption() } type parseOption struct { Option } func (*parseOption) readFileOption() {} // ReadFileOption is a type of `Option` that can be passed to `jws.ReadFile` type ReadFileOption interface { Option readFileOption() } type readFileOption struct { Option } func (*readFileOption) readFileOption() {} // SignOption describes options that can be passed to `jws.Sign` type SignOption interface { Option signOption() } type signOption struct { Option } func (*signOption) signOption() {} // SignVerifyCompactOption describes options that can be passed to either `jws.Verify`, // `jws.Sign`, or `jws.Compact` type SignVerifyCompactOption interface { Option signOption() verifyOption() compactOption() parseOption() } type signVerifyCompactOption struct { Option } func (*signVerifyCompactOption) signOption() {} func (*signVerifyCompactOption) verifyOption() {} func (*signVerifyCompactOption) compactOption() {} func (*signVerifyCompactOption) parseOption() {} // SignVerifyOption describes options that can be passed to either `jws.Verify` or `jws.Sign` type SignVerifyOption interface { Option signOption() verifyOption() parseOption() } type signVerifyOption struct { Option } func (*signVerifyOption) signOption() {} func (*signVerifyOption) verifyOption() {} func (*signVerifyOption) parseOption() {} type SignVerifyParseOption interface { Option signOption() verifyOption() parseOption() readFileOption() } type signVerifyParseOption struct { Option } func (*signVerifyParseOption) signOption() {} func (*signVerifyParseOption) verifyOption() {} func (*signVerifyParseOption) parseOption() {} func (*signVerifyParseOption) readFileOption() {} // VerifyOption describes options that can be passed to `jws.Verify` type VerifyOption interface { Option verifyOption() parseOption() } type verifyOption struct { Option } func (*verifyOption) verifyOption() {} func (*verifyOption) parseOption() {} // JSONSuboption describes suboptions that can be passed to the `jws.WithJSON()` option. type WithJSONSuboption interface { Option withJSONSuboption() } type withJSONSuboption struct { Option } func (*withJSONSuboption) withJSONSuboption() {} // WithKeySetSuboption is a suboption passed to the `jws.WithKeySet()` option type WithKeySetSuboption interface { Option withKeySetSuboption() } type withKeySetSuboption struct { Option } func (*withKeySetSuboption) withKeySetSuboption() {} // WithKeySuboption describes option types that can be passed to the `jws.WithKey()` // option. type WithKeySuboption interface { Option withKeySuboption() } type withKeySuboption struct { Option } func (*withKeySuboption) withKeySuboption() {} type identBase64Encoder struct{} type identContext struct{} type identDetached struct{} type identDetachedPayload struct{} type identFS struct{} type identInferAlgorithmFromKey struct{} type identKey struct{} type identKeyProvider struct{} type identKeyUsed struct{} type identLegacySigners struct{} type identMessage struct{} type identMultipleKeysPerKeyID struct{} type identPretty struct{} type identProtectedHeaders struct{} type identPublicHeaders struct{} type identRequireKid struct{} type identSerialization struct{} type identUseDefault struct{} type identValidateKey struct{} func (identBase64Encoder) String() string { return "WithBase64Encoder" } func (identContext) String() string { return "WithContext" } func (identDetached) String() string { return "WithDetached" } func (identDetachedPayload) String() string { return "WithDetachedPayload" } func (identFS) String() string { return "WithFS" } func (identInferAlgorithmFromKey) String() string { return "WithInferAlgorithmFromKey" } func (identKey) String() string { return "WithKey" } func (identKeyProvider) String() string { return "WithKeyProvider" } func (identKeyUsed) String() string { return "WithKeyUsed" } func (identLegacySigners) String() string { return "WithLegacySigners" } func (identMessage) String() string { return "WithMessage" } func (identMultipleKeysPerKeyID) String() string { return "WithMultipleKeysPerKeyID" } func (identPretty) String() string { return "WithPretty" } func (identProtectedHeaders) String() string { return "WithProtectedHeaders" } func (identPublicHeaders) String() string { return "WithPublicHeaders" } func (identRequireKid) String() string { return "WithRequireKid" } func (identSerialization) String() string { return "WithSerialization" } func (identUseDefault) String() string { return "WithUseDefault" } func (identValidateKey) String() string { return "WithValidateKey" } // WithBase64Encoder specifies the base64 encoder to be used while signing or // verifying the JWS message. By default, the raw URL base64 encoding (no padding) // is used. func WithBase64Encoder(v Base64Encoder) SignVerifyCompactOption { return &signVerifyCompactOption{option.New(identBase64Encoder{}, v)} } func WithContext(v context.Context) VerifyOption { return &verifyOption{option.New(identContext{}, v)} } // WithDetached specifies that the `jws.Message` should be serialized in // JWS compact serialization with detached payload. The resulting octet // sequence will not contain the payload section. func WithDetached(v bool) CompactOption { return &compactOption{option.New(identDetached{}, v)} } // WithDetachedPayload can be used to both sign or verify a JWS message with a // detached payload. // Note that this option does NOT populate the `b64` header, which is sometimes // required by other JWS implementations. // // When this option is used for `jws.Sign()`, the first parameter (normally the payload) // must be set to `nil`. // // If you have to verify using this option, you should know exactly how and why this works. func WithDetachedPayload(v []byte) SignVerifyOption { return &signVerifyOption{option.New(identDetachedPayload{}, v)} } // WithFS specifies the source `fs.FS` object to read the file from. func WithFS(v fs.FS) ReadFileOption { return &readFileOption{option.New(identFS{}, v)} } // WithInferAlgorithmFromKey specifies whether the JWS signing algorithm name // should be inferred by looking at the provided key, in case the JWS // message or the key does not have a proper `alg` header. // // When this option is set to true, a list of algorithm(s) that is compatible // with the key type will be enumerated, and _ALL_ of them will be tried // against the key/message pair. If any of them succeeds, the verification // will be considered successful. // // Compared to providing explicit `alg` from the key this is slower, and // verification may fail to verify if somehow our heuristics are wrong // or outdated. // // Also, automatic detection of signature verification methods are always // more vulnerable for potential attack vectors. // // It is highly recommended that you fix your key to contain a proper `alg` // header field instead of resorting to using this option, but sometimes // it just needs to happen. func WithInferAlgorithmFromKey(v bool) WithKeySetSuboption { return &withKeySetSuboption{option.New(identInferAlgorithmFromKey{}, v)} } func WithKeyProvider(v KeyProvider) VerifyOption { return &verifyOption{option.New(identKeyProvider{}, v)} } // WithKeyUsed allows you to specify the `jws.Verify()` function to // return the key used for verification. This may be useful when // you specify multiple key sources or if you pass a `jwk.Set` // and you want to know which key was successful at verifying the // signature. // // `v` must be a pointer to an empty `any`. Do not use // `jwk.Key` here unless you are 100% sure that all keys that you // have provided are instances of `jwk.Key` (remember that the // jwx API allows users to specify a raw key such as *rsa.PublicKey) func WithKeyUsed(v any) VerifyOption { return &verifyOption{option.New(identKeyUsed{}, v)} } // WithLegacySigners is a no-op option that exists only for backwards compatibility. func WithLegacySigners() GlobalOption { return &globalOption{option.New(identLegacySigners{}, true)} } // WithMessage can be passed to Verify() to obtain the jws.Message upon // a successful verification. func WithMessage(v *Message) VerifyOption { return &verifyOption{option.New(identMessage{}, v)} } // WithMultipleKeysPerKeyID specifies if we should expect multiple keys // to match against a key ID. By default it is assumed that key IDs are // unique, i.e. for a given key ID, the key set only contains a single // key that has the matching ID. When this option is set to true, // multiple keys that match the same key ID in the set can be tried. func WithMultipleKeysPerKeyID(v bool) WithKeySetSuboption { return &withKeySetSuboption{option.New(identMultipleKeysPerKeyID{}, v)} } // WithPretty specifies whether the JSON output should be formatted and // indented func WithPretty(v bool) WithJSONSuboption { return &withJSONSuboption{option.New(identPretty{}, v)} } // WithProtected is used with `jws.WithKey()` option when used with `jws.Sign()` // to specify a protected header to be attached to the JWS signature. // // It has no effect if used when `jws.WithKey()` is passed to `jws.Verify()` func WithProtectedHeaders(v Headers) WithKeySuboption { return &withKeySuboption{option.New(identProtectedHeaders{}, v)} } // WithPublic is used with `jws.WithKey()` option when used with `jws.Sign()` // to specify a public header to be attached to the JWS signature. // // It has no effect if used when `jws.WithKey()` is passed to `jws.Verify()` // // `jws.Sign()` will result in an error if `jws.WithPublic()` is used // and the serialization format is compact serialization. func WithPublicHeaders(v Headers) WithKeySuboption { return &withKeySuboption{option.New(identPublicHeaders{}, v)} } // WithRequiredKid specifies whether the keys in the jwk.Set should // only be matched if the target JWS message's Key ID and the Key ID // in the given key matches. func WithRequireKid(v bool) WithKeySetSuboption { return &withKeySetSuboption{option.New(identRequireKid{}, v)} } // WithCompact specifies that the result of `jws.Sign()` is serialized in // compact format. // // By default `jws.Sign()` will opt to use compact format, so you usually // do not need to specify this option other than to be explicit about it func WithCompact() SignVerifyParseOption { return &signVerifyParseOption{option.New(identSerialization{}, fmtCompact)} } // WithUseDefault specifies that if and only if a jwk.Key contains // exactly one jwk.Key, that key should be used. func WithUseDefault(v bool) WithKeySetSuboption { return &withKeySetSuboption{option.New(identUseDefault{}, v)} } // WithValidateKey specifies whether the key used for signing or verification // should be validated before using. Note that this means calling // `key.Validate()` on the key, which in turn means that your key // must be a `jwk.Key` instance, or a key that can be converted to // a `jwk.Key` by calling `jwk.Import()`. This means that your // custom hardware-backed keys will probably not work. // // You can directly call `key.Validate()` yourself if you need to // mix keys that cannot be converted to `jwk.Key`. // // Please also note that use of this option will also result in // one extra conversion of raw keys to a `jwk.Key` instance. If you // care about shaving off as much as possible, consider using a // pre-validated key instead of using this option to validate // the key on-demand each time. // // By default, the key is not validated. func WithValidateKey(v bool) SignVerifyOption { return &signVerifyOption{option.New(identValidateKey{}, v)} } golang-github-lestrrat-go-jwx-3.0.13/jws/options_gen_test.go000066400000000000000000000026241515060566400241250ustar00rootroot00000000000000// Code generated by tools/cmd/genoptions/main.go. DO NOT EDIT. package jws import ( "testing" "github.com/stretchr/testify/require" ) func TestOptionIdent(t *testing.T) { require.Equal(t, "WithBase64Encoder", identBase64Encoder{}.String()) require.Equal(t, "WithContext", identContext{}.String()) require.Equal(t, "WithDetached", identDetached{}.String()) require.Equal(t, "WithDetachedPayload", identDetachedPayload{}.String()) require.Equal(t, "WithFS", identFS{}.String()) require.Equal(t, "WithInferAlgorithmFromKey", identInferAlgorithmFromKey{}.String()) require.Equal(t, "WithKey", identKey{}.String()) require.Equal(t, "WithKeyProvider", identKeyProvider{}.String()) require.Equal(t, "WithKeyUsed", identKeyUsed{}.String()) require.Equal(t, "WithLegacySigners", identLegacySigners{}.String()) require.Equal(t, "WithMessage", identMessage{}.String()) require.Equal(t, "WithMultipleKeysPerKeyID", identMultipleKeysPerKeyID{}.String()) require.Equal(t, "WithPretty", identPretty{}.String()) require.Equal(t, "WithProtectedHeaders", identProtectedHeaders{}.String()) require.Equal(t, "WithPublicHeaders", identPublicHeaders{}.String()) require.Equal(t, "WithRequireKid", identRequireKid{}.String()) require.Equal(t, "WithSerialization", identSerialization{}.String()) require.Equal(t, "WithUseDefault", identUseDefault{}.String()) require.Equal(t, "WithValidateKey", identValidateKey{}.String()) } golang-github-lestrrat-go-jwx-3.0.13/jws/sign_context.go000066400000000000000000000073341515060566400232510ustar00rootroot00000000000000package jws import ( "fmt" "github.com/lestrrat-go/jwx/v3/internal/base64" "github.com/lestrrat-go/jwx/v3/internal/pool" "github.com/lestrrat-go/jwx/v3/jwa" ) type signContext struct { format int detached bool validateKey bool payload []byte encoder Base64Encoder none *signatureBuilder // special signature builder sigbuilders []*signatureBuilder } var signContextPool = pool.New[*signContext](allocSignContext, freeSignContext) func allocSignContext() *signContext { return &signContext{ format: fmtCompact, sigbuilders: make([]*signatureBuilder, 0, 1), encoder: base64.DefaultEncoder(), } } func freeSignContext(ctx *signContext) *signContext { ctx.format = fmtCompact for _, sb := range ctx.sigbuilders { signatureBuilderPool.Put(sb) } ctx.sigbuilders = ctx.sigbuilders[:0] ctx.detached = false ctx.validateKey = false ctx.encoder = base64.DefaultEncoder() ctx.none = nil ctx.payload = nil return ctx } func (sc *signContext) ProcessOptions(options []SignOption) error { for _, option := range options { switch option.Ident() { case identSerialization{}: if err := option.Value(&sc.format); err != nil { return signerr(`failed to retrieve serialization option value: %w`, err) } case identInsecureNoSignature{}: var data withInsecureNoSignature if err := option.Value(&data); err != nil { return signerr(`failed to retrieve insecure-no-signature option value: %w`, err) } sb := signatureBuilderPool.Get() sb.alg = jwa.NoSignature() sb.protected = data.protected sb.signer = noneSigner{} sc.none = sb sc.sigbuilders = append(sc.sigbuilders, sb) case identKey{}: var data *withKey if err := option.Value(&data); err != nil { return signerr(`jws.Sign: invalid value for WithKey option: %w`, err) } alg, ok := data.alg.(jwa.SignatureAlgorithm) if !ok { return signerr(`expected algorithm to be of type jwa.SignatureAlgorithm but got (%[1]q, %[1]T)`, data.alg) } // No, we don't accept "none" here. if alg == jwa.NoSignature() { return signerr(`"none" (jwa.NoSignature) cannot be used with jws.WithKey`) } sb := signatureBuilderPool.Get() sb.alg = alg sb.protected = data.protected sb.key = data.key sb.public = data.public s2, err := SignerFor(alg) if err == nil { sb.signer2 = s2 } else { s1, err := legacySignerFor(alg) if err != nil { sb.signer2 = defaultSigner{alg: alg} } else { sb.signer = s1 } } sc.sigbuilders = append(sc.sigbuilders, sb) case identDetachedPayload{}: if sc.payload != nil { return signerr(`payload must be nil when jws.WithDetachedPayload() is specified`) } if err := option.Value(&sc.payload); err != nil { return signerr(`failed to retrieve detached payload option value: %w`, err) } sc.detached = true case identValidateKey{}: if err := option.Value(&sc.validateKey); err != nil { return signerr(`failed to retrieve validate-key option value: %w`, err) } case identBase64Encoder{}: if err := option.Value(&sc.encoder); err != nil { return signerr(`failed to retrieve base64-encoder option value: %w`, err) } } } return nil } func (sc *signContext) PopulateMessage(m *Message) error { m.payload = sc.payload m.signatures = make([]*Signature, 0, len(sc.sigbuilders)) for i, sb := range sc.sigbuilders { // Create signature for each builders if sc.validateKey { if err := validateKeyBeforeUse(sb.key); err != nil { return fmt.Errorf(`failed to validate key for signature %d: %w`, i, err) } } sig, err := sb.Build(sc, m.payload) if err != nil { return fmt.Errorf(`failed to build signature %d: %w`, i, err) } m.signatures = append(m.signatures, sig) } return nil } golang-github-lestrrat-go-jwx-3.0.13/jws/signature_builder.go000066400000000000000000000060311515060566400242450ustar00rootroot00000000000000package jws import ( "bytes" "fmt" "github.com/lestrrat-go/jwx/v3/internal/json" "github.com/lestrrat-go/jwx/v3/internal/pool" "github.com/lestrrat-go/jwx/v3/internal/tokens" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/lestrrat-go/jwx/v3/jwk" "github.com/lestrrat-go/jwx/v3/jws/jwsbb" ) var signatureBuilderPool = pool.New[*signatureBuilder](allocSignatureBuilder, freeSignatureBuilder) // signatureBuilder is a transient object that is used to build // a single JWS signature. // // In a multi-signature JWS message, each message is paired with // the following: // - a signer (the object that takes a buffer and key and generates a signature) // - a key (the key that is used to sign the payload) // - protected headers (the headers that are protected by the signature) // - public headers (the headers that are not protected by the signature) // // This object stores all of this information in one place. // // This object does NOT take care of any synchronization, because it is // meant to be used in a single-threaded context. type signatureBuilder struct { alg jwa.SignatureAlgorithm signer Signer signer2 Signer2 key any protected Headers public Headers } func allocSignatureBuilder() *signatureBuilder { return &signatureBuilder{} } func freeSignatureBuilder(sb *signatureBuilder) *signatureBuilder { sb.alg = jwa.EmptySignatureAlgorithm() sb.signer = nil sb.signer2 = nil sb.key = nil sb.protected = nil sb.public = nil return sb } func (sb *signatureBuilder) Build(sc *signContext, payload []byte) (*Signature, error) { protected := sb.protected if protected == nil { protected = NewHeaders() } if err := protected.Set(AlgorithmKey, sb.alg); err != nil { return nil, signerr(`failed to set "alg" header: %w`, err) } if key, ok := sb.key.(jwk.Key); ok { if kid, ok := key.KeyID(); ok && kid != "" { if err := protected.Set(KeyIDKey, kid); err != nil { return nil, signerr(`failed to set "kid" header: %w`, err) } } } hdrs, err := mergeHeaders(sb.public, protected) if err != nil { return nil, signerr(`failed to merge headers: %w`, err) } // raw, json format headers hdrbuf, err := json.Marshal(hdrs) if err != nil { return nil, fmt.Errorf(`failed to marshal headers: %w`, err) } // check if we need to base64 encode the payload b64 := getB64Value(hdrs) if !b64 && !sc.detached { if bytes.IndexByte(payload, tokens.Period) != -1 { return nil, fmt.Errorf(`payload must not contain a "."`) } } combined := jwsbb.SignBuffer(nil, hdrbuf, payload, sc.encoder, b64) var sig Signature sig.protected = protected sig.headers = sb.public if sb.signer2 != nil { signature, err := sb.signer2.Sign(sb.key, combined) if err != nil { return nil, fmt.Errorf(`failed to sign payload: %w`, err) } sig.signature = signature return &sig, nil } if sb.signer == nil { panic("can't get here") } signature, err := sb.signer.Sign(combined, sb.key) if err != nil { return nil, fmt.Errorf(`failed to sign payload: %w`, err) } sig.signature = signature return &sig, nil } golang-github-lestrrat-go-jwx-3.0.13/jws/signer.go000066400000000000000000000131011515060566400220210ustar00rootroot00000000000000package jws import ( "fmt" "strings" "sync" "github.com/lestrrat-go/jwx/v3/jwa" ) // Signer2 is an interface that represents a per-signature algorithm signing // operation. type Signer2 interface { Algorithm() jwa.SignatureAlgorithm // Sign takes a key and a payload, and returns the signature for the payload. // The key type is restricted by the signature algorithm that this // signer is associated with. // // (Note to users of legacy Signer interface: the method signature // is different from the legacy Signer interface) Sign(key any, payload []byte) ([]byte, error) } var muSigner2DB sync.RWMutex var signer2DB = make(map[jwa.SignatureAlgorithm]Signer2) type SignerFactory interface { Create() (Signer, error) } type SignerFactoryFn func() (Signer, error) func (fn SignerFactoryFn) Create() (Signer, error) { return fn() } func init() { // register the signers using jwsbb. These will be used by default. for _, alg := range jwa.SignatureAlgorithms() { if alg == jwa.NoSignature() { continue } if err := RegisterSigner(alg, defaultSigner{alg: alg}); err != nil { panic(fmt.Sprintf("RegisterSigner failed: %v", err)) } } } // SignerFor returns a Signer2 for the given signature algorithm. // // Currently, this function will never fail. It will always return a // valid Signer2 object. The heuristic is as follows: // 1. If a Signer2 is registered for the given algorithm, it will return that. // 2. If a legacy Signer(Factory) is registered for the given algorithm, it will // return a Signer2 that wraps the legacy Signer. // 3. If no Signer2 or legacy Signer(Factory) is registered, it will return a // default signer that uses jwsbb.Sign. // // 1 and 2 will take care of 99% of the cases. The only time 3 will happen is // when you are using a custom algorithm that is not supported out of the box. // // jwsbb.Sign knows how to handle a static set of algorithms, so if the // algorithm is not supported, it will return an error when you call // `Sign` on the default signer. func SignerFor(alg jwa.SignatureAlgorithm) (Signer2, error) { muSigner2DB.RLock() defer muSigner2DB.RUnlock() signer, ok := signer2DB[alg] if ok { return signer, nil } s1, err := legacySignerFor(alg) if err == nil { return signerAdapter{signer: s1}, nil } return defaultSigner{alg: alg}, nil } var muSignerDB sync.RWMutex var signerDB = make(map[jwa.SignatureAlgorithm]SignerFactory) // RegisterSigner is used to register a signer for the given // algorithm. // // Please note that this function is intended to be passed a // signer object as its second argument, but due to historical // reasons the function signature is defined as taking `any` type. // // You should create a signer object that implements the `Signer2` // interface to register a signer, unless you have legacy code that // plugged into the `SignerFactory` interface. // // Unlike the `UnregisterSigner` function, this function automatically // calls `jwa.RegisterSignatureAlgorithm` to register the algorithm // in this module's algorithm database. // // For backwards compatibility, this function also accepts // `SignerFactory` implementations, but this usage is deprecated. // You should use `Signer2` implementations instead. // // If you want to completely remove an algorithm, you must call // `jwa.UnregisterSignatureAlgorithm` yourself after calling // `UnregisterSigner`. func RegisterSigner(alg jwa.SignatureAlgorithm, f any) error { jwa.RegisterSignatureAlgorithm(alg) switch s := f.(type) { case Signer2: muSigner2DB.Lock() signer2DB[alg] = s muSigner2DB.Unlock() case SignerFactory: muSignerDB.Lock() signerDB[alg] = s muSignerDB.Unlock() default: return fmt.Errorf(`jws.RegisterSigner: unsupported type %T for algorithm %q`, f, alg) } return nil } // UnregisterSigner removes the signer factory associated with // the given algorithm, as well as the signer instance created // by the factory. // // Note that when you call this function, the algorithm itself is // not automatically unregistered from this module's algorithm 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) { muSigner2DB.Lock() delete(signer2DB, alg) muSigner2DB.Unlock() muSignerDB.Lock() delete(signerDB, alg) muSignerDB.Unlock() // Remove previous signer removeSigner(alg) } // NewSigner creates a signer that signs payloads using the given signature algorithm. // This function is deprecated, and will either be removed to re-purposed using // a different signature. // // When you want to load a Signer object, you should use `SignerFor()` instead. func NewSigner(alg jwa.SignatureAlgorithm) (Signer, error) { s, err := newLegacySigner(alg) if err == nil { return s, nil } if strings.HasPrefix(err.Error(), `jws.NewSigner: unsupported signature algorithm`) { // When newLegacySigner fails, automatically trigger to enable signers enableLegacySignersOnce.Do(enableLegacySigners) return newLegacySigner(alg) } return nil, err } func newLegacySigner(alg jwa.SignatureAlgorithm) (Signer, error) { muSignerDB.RLock() f, ok := signerDB[alg] muSignerDB.RUnlock() if ok { return f.Create() } return nil, fmt.Errorf(`jws.NewSigner: unsupported signature algorithm "%s"`, alg) } type noneSigner struct{} func (noneSigner) Algorithm() jwa.SignatureAlgorithm { return jwa.NoSignature() } func (noneSigner) Sign([]byte, any) ([]byte, error) { return nil, nil } golang-github-lestrrat-go-jwx-3.0.13/jws/signer_test.go000066400000000000000000000032121515060566400230620ustar00rootroot00000000000000package jws_test import ( "strings" "testing" "github.com/lestrrat-go/jwx/v3/internal/jwxtest" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/lestrrat-go/jwx/v3/jws" "github.com/stretchr/testify/require" ) func TestSign(t *testing.T) { t.Parallel() t.Run("Bad algorithm", func(t *testing.T) { t.Parallel() _, err := jws.Sign([]byte(nil), jws.WithKey(jwa.NewSignatureAlgorithm("FooBar"), nil)) require.Error(t, err, "Unknown algorithm should return error") }) t.Run("No private key", func(t *testing.T) { t.Parallel() _, err := jws.Sign([]byte{'a', 'b', 'c'}, jws.WithKey(jwa.RS256(), nil)) require.Error(t, err, "Sign with no private key should return error") }) t.Run("RSA verify with no public key", func(t *testing.T) { t.Parallel() _, err := jws.Verify([]byte(nil), jws.WithKey(jwa.RS256(), nil)) require.Error(t, err, "Verify with no private key should return error") }) } func TestSignMulti(t *testing.T) { rsakey, err := jwxtest.GenerateRsaKey() require.NoError(t, err, "RSA key generated") dsakey, err := jwxtest.GenerateEcdsaKey(jwa.P521()) require.NoError(t, err, "ECDSA key generated") s1hdr := jws.NewHeaders() s1hdr.Set(jws.KeyIDKey, "2010-12-29") s2hdr := jws.NewHeaders() s2hdr.Set(jws.KeyIDKey, "e9bc097a-ce51-4036-9562-d2ade882db0d") v := strings.Join([]string{`{"iss":"joe",`, ` "exp":1300819380,`, ` "http://example.com/is_root":true}`}, "\r\n") m, err := jws.Sign([]byte(v), jws.WithJSON(), jws.WithKey(jwa.RS256(), rsakey, jws.WithPublicHeaders(s1hdr)), jws.WithKey(jwa.ES256(), dsakey, jws.WithPublicHeaders(s2hdr)), ) require.NoError(t, err, "jws.SignMulti should succeed") t.Logf("%s", m) } golang-github-lestrrat-go-jwx-3.0.13/jws/verifier.go000066400000000000000000000105751515060566400223610ustar00rootroot00000000000000package jws import ( "fmt" "sync" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/lestrrat-go/jwx/v3/jws/jwsbb" ) type defaultVerifier struct { alg jwa.SignatureAlgorithm } func (v defaultVerifier) Algorithm() jwa.SignatureAlgorithm { return v.alg } func (v defaultVerifier) Verify(key any, payload, signature []byte) error { if err := jwsbb.Verify(key, v.alg.String(), payload, signature); err != nil { return verifyError{verificationError{err}} } return nil } type Verifier2 interface { Verify(key any, payload, signature []byte) error } var muVerifier2DB sync.RWMutex var verifier2DB = make(map[jwa.SignatureAlgorithm]Verifier2) type verifierAdapter struct { v Verifier } func (v verifierAdapter) Verify(key any, payload, signature []byte) error { if err := v.v.Verify(payload, signature, key); err != nil { return verifyError{verificationError{err}} } return nil } // VerifierFor returns a Verifier2 for the given signature algorithm. // // Currently, this function will never fail. It will always return a // valid Verifier2 object. The heuristic is as follows: // 1. If a Verifier2 is registered for the given algorithm, it will return that. // 2. If a legacy Verifier(Factory) is registered for the given algorithm, it will // return a Verifier2 that wraps the legacy Verifier. // 3. If no Verifier2 or legacy Verifier(Factory) is registered, it will return a // default verifier that uses jwsbb.Verify. // // jwsbb.Verify knows how to handle a static set of algorithms, so if the // algorithm is not supported, it will return an error when you call // `Verify` on the default verifier. func VerifierFor(alg jwa.SignatureAlgorithm) (Verifier2, error) { muVerifier2DB.RLock() defer muVerifier2DB.RUnlock() v2, ok := verifier2DB[alg] if ok { return v2, nil } v1, err := NewVerifier(alg) if err == nil { return verifierAdapter{v: v1}, nil } return defaultVerifier{alg: alg}, nil } type VerifierFactory interface { Create() (Verifier, error) } type VerifierFactoryFn func() (Verifier, error) func (fn VerifierFactoryFn) Create() (Verifier, error) { return fn() } var muVerifierDB sync.RWMutex var verifierDB = make(map[jwa.SignatureAlgorithm]VerifierFactory) // RegisterVerifier is used to register a verifier for the given // algorithm. // // Please note that this function is intended to be passed a // verifier object as its second argument, but due to historical // reasons the function signature is defined as taking `any` type. // // You should create a signer object that implements the `Verifier2` // interface to register a signer, unless you have legacy code that // plugged into the `SignerFactory` interface. // // Unlike the `UnregisterVerifier` function, this function automatically // calls `jwa.RegisterSignatureAlgorithm` to register the algorithm // in this module's algorithm database. func RegisterVerifier(alg jwa.SignatureAlgorithm, f any) error { jwa.RegisterSignatureAlgorithm(alg) switch v := f.(type) { case Verifier2: muVerifier2DB.Lock() verifier2DB[alg] = v muVerifier2DB.Unlock() muVerifierDB.Lock() delete(verifierDB, alg) muVerifierDB.Unlock() case VerifierFactory: muVerifierDB.Lock() verifierDB[alg] = v muVerifierDB.Unlock() muVerifier2DB.Lock() delete(verifier2DB, alg) muVerifier2DB.Unlock() default: return fmt.Errorf(`jws.RegisterVerifier: unsupported type %T for algorithm %q`, f, alg) } return nil } // 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 this module's algorithm 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) { muVerifier2DB.Lock() delete(verifier2DB, alg) muVerifier2DB.Unlock() muVerifierDB.Lock() delete(verifierDB, alg) muVerifierDB.Unlock() } // 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() } return nil, fmt.Errorf(`jws.NewVerifier: unsupported signature algorithm "%s"`, alg) } golang-github-lestrrat-go-jwx-3.0.13/jws/verify_context.go000066400000000000000000000145431515060566400236150ustar00rootroot00000000000000package jws import ( "context" "errors" "fmt" "strings" "github.com/lestrrat-go/blackmagic" "github.com/lestrrat-go/jwx/v3/internal/base64" "github.com/lestrrat-go/jwx/v3/internal/json" "github.com/lestrrat-go/jwx/v3/internal/pool" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/lestrrat-go/jwx/v3/jws/jwsbb" ) // verifyContext holds the state during JWS verification type verifyContext struct { parseOptions []ParseOption dst *Message detachedPayload []byte keyProviders []KeyProvider keyUsed any validateKey bool encoder Base64Encoder //nolint:containedctx ctx context.Context } var verifyContextPool = pool.New[*verifyContext](allocVerifyContext, freeVerifyContext) func allocVerifyContext() *verifyContext { return &verifyContext{ encoder: base64.DefaultEncoder(), ctx: context.Background(), } } func freeVerifyContext(vc *verifyContext) *verifyContext { vc.parseOptions = vc.parseOptions[:0] vc.dst = nil vc.detachedPayload = nil vc.keyProviders = vc.keyProviders[:0] vc.keyUsed = nil vc.validateKey = false vc.encoder = base64.DefaultEncoder() vc.ctx = context.Background() return vc } func (vc *verifyContext) ProcessOptions(options []VerifyOption) error { //nolint:forcetypeassert for _, option := range options { switch option.Ident() { case identMessage{}: if err := option.Value(&vc.dst); err != nil { return verifyerr(`invalid value for option WithMessage: %w`, err) } case identDetachedPayload{}: if err := option.Value(&vc.detachedPayload); err != nil { return verifyerr(`invalid value for option WithDetachedPayload: %w`, err) } case identKey{}: var pair *withKey if err := option.Value(&pair); err != nil { return verifyerr(`invalid value for option WithKey: %w`, err) } vc.keyProviders = append(vc.keyProviders, &staticKeyProvider{ alg: pair.alg.(jwa.SignatureAlgorithm), key: pair.key, }) case identKeyProvider{}: var kp KeyProvider if err := option.Value(&kp); err != nil { return verifyerr(`failed to retrieve key-provider option value: %w`, err) } vc.keyProviders = append(vc.keyProviders, kp) case identKeyUsed{}: if err := option.Value(&vc.keyUsed); err != nil { return verifyerr(`failed to retrieve key-used option value: %w`, err) } case identContext{}: if err := option.Value(&vc.ctx); err != nil { return verifyerr(`failed to retrieve context option value: %w`, err) } case identValidateKey{}: if err := option.Value(&vc.validateKey); err != nil { return verifyerr(`failed to retrieve validate-key option value: %w`, err) } case identSerialization{}: vc.parseOptions = append(vc.parseOptions, option.(ParseOption)) case identBase64Encoder{}: if err := option.Value(&vc.encoder); err != nil { return verifyerr(`failed to retrieve base64-encoder option value: %w`, err) } default: return verifyerr(`invalid jws.VerifyOption %q passed`, `With`+strings.TrimPrefix(fmt.Sprintf(`%T`, option.Ident()), `jws.ident`)) } } if len(vc.keyProviders) < 1 { return verifyerr(`no key providers have been provided (see jws.WithKey(), jws.WithKeySet(), jws.WithVerifyAuto(), and jws.WithKeyProvider()`) } return nil } func (vc *verifyContext) VerifyMessage(buf []byte) ([]byte, error) { msg, err := Parse(buf, vc.parseOptions...) if err != nil { return nil, verifyerr(`failed to parse jws: %w`, err) } defer msg.clearRaw() if vc.detachedPayload != nil { if len(msg.payload) != 0 { return nil, verifyerr(`can't specify detached payload for JWS with payload`) } msg.payload = vc.detachedPayload } verifyBuf := pool.ByteSlice().Get() // Because deferred functions bind to the current value of the variable, // we can't just use `defer pool.ByteSlice().Put(verifyBuf)` here. // Instead, we use a closure to reference the _variable_. // it would be better if we could call it directly, but there are // too many place we may return from this function defer func() { pool.ByteSlice().Put(verifyBuf) }() errs := pool.ErrorSlice().Get() defer func() { pool.ErrorSlice().Put(errs) }() for idx, sig := range msg.signatures { var rawHeaders []byte if rbp, ok := sig.protected.(interface{ rawBuffer() []byte }); ok { if raw := rbp.rawBuffer(); raw != nil { rawHeaders = raw } } if rawHeaders == nil { protected, err := json.Marshal(sig.protected) if err != nil { return nil, verifyerr(`failed to marshal "protected" for signature #%d: %w`, idx+1, err) } rawHeaders = protected } verifyBuf = verifyBuf[:0] verifyBuf = jwsbb.SignBuffer(verifyBuf, rawHeaders, msg.payload, vc.encoder, msg.b64) for i, kp := range vc.keyProviders { var sink algKeySink if err := kp.FetchKeys(vc.ctx, &sink, sig, msg); err != nil { return nil, verifyerr(`key provider %d failed: %w`, i, err) } for _, pair := range sink.list { // alg is converted here because pair.alg is of type jwa.KeyAlgorithm. // this may seem ugly, but we're trying to avoid declaring separate // structs for `alg jwa.KeyEncryptionAlgorithm` and `alg jwa.SignatureAlgorithm` //nolint:forcetypeassert alg := pair.alg.(jwa.SignatureAlgorithm) key := pair.key if err := vc.tryKey(verifyBuf, alg, key, msg, sig); err != nil { errs = append(errs, verifyerr(`failed to verify signature #%d with key %T: %w`, idx+1, key, err)) continue } return msg.payload, nil } } errs = append(errs, verifyerr(`signature #%d could not be verified with any of the keys`, idx+1)) } return nil, verifyerr(`could not verify message using any of the signatures or keys: %w`, errors.Join(errs...)) } func (vc *verifyContext) tryKey(verifyBuf []byte, alg jwa.SignatureAlgorithm, key any, msg *Message, sig *Signature) error { if vc.validateKey { if err := validateKeyBeforeUse(key); err != nil { return fmt.Errorf(`failed to validate key before signing: %w`, err) } } verifier, err := VerifierFor(alg) if err != nil { return fmt.Errorf(`failed to get verifier for algorithm %q: %w`, alg, err) } if err := verifier.Verify(key, verifyBuf, sig.signature); err != nil { return verificationError{err} } // Verification succeeded if vc.keyUsed != nil { if err := blackmagic.AssignIfCompatible(vc.keyUsed, key); err != nil { return fmt.Errorf(`failed to assign used key (%T) to %T: %w`, key, vc.keyUsed, err) } } if vc.dst != nil { *(vc.dst) = *msg } return nil } golang-github-lestrrat-go-jwx-3.0.13/jwt/000077500000000000000000000000001515060566400202105ustar00rootroot00000000000000golang-github-lestrrat-go-jwx-3.0.13/jwt/BUILD.bazel000066400000000000000000000031471515060566400220730ustar00rootroot00000000000000load("@rules_go//go:def.bzl", "go_library", "go_test") go_library( name = "jwt", srcs = [ "builder_gen.go", "errors.go", "filter.go", "fastpath.go", "http.go", "interface.go", "io.go", "jwt.go", "options.go", "options_gen.go", "serialize.go", "token_gen.go", "token_options.go", "token_options_gen.go", "validate.go", ], importpath = "github.com/lestrrat-go/jwx/v3/jwt", visibility = ["//visibility:public"], deps = [ "//:jwx", "//internal/base64", "//transform", "//internal/json", "//internal/tokens", "//internal/pool", "//jwa", "//jwe", "//jwk", "//jws", "//jws/jwsbb", "//jwt/internal/types", "//jwt/internal/errors", "@com_github_lestrrat_go_blackmagic//:blackmagic", "@com_github_lestrrat_go_option_v2//:option", ], ) go_test( name = "jwt_test", srcs = [ "jwt_test.go", "options_gen_test.go", "token_options_test.go", "token_test.go", "validate_test.go", "verify_test.go", ], embed = [":jwt"], deps = [ "//internal/json", "//internal/jwxtest", "//jwa", "//jwe", "//jwk", "//jwk/ecdsa", "//jws", "//jwt/internal/types", "@com_github_lestrrat_go_httprc_v3//:httprc", "@com_github_stretchr_testify//require", ], ) alias( name = "go_default_library", actual = ":jwt", visibility = ["//visibility:public"], ) golang-github-lestrrat-go-jwx-3.0.13/jwt/README.md000066400000000000000000000167301515060566400214760ustar00rootroot00000000000000# JWT [![Go Reference](https://pkg.go.dev/badge/github.com/lestrrat-go/jwx/v3/jwt.svg)](https://pkg.go.dev/github.com/lestrrat-go/jwx/v3/jwt) Package jwt implements JSON Web Tokens as described in [RFC7519](https://tools.ietf.org/html/rfc7519). * Convenience methods for oft-used keys ("aud", "sub", "iss", etc) * Convenience functions to extract/parse from http.Request, http.Header, url.Values * Ability to Get/Set arbitrary keys * Conversion to and from JSON * Generate signed tokens * Verify signed tokens * Extra support for OpenID tokens via [github.com/lestrrat-go/jwx/v3/jwt/openid](./jwt/openid) How-to style documentation can be found in the [docs directory](../docs). More examples are located in the examples directory ([jwt_example_test.go](../examples/jwt_example_test.go)) # SYNOPSIS ## Verify a signed JWT ```go token, err := jwt.Parse(payload, jwt.WithKey(alg, key)) if err != nil { fmt.Printf("failed to parse payload: %s\n", err) } ``` ## Token Usage ```go func ExampleJWT() { const aLongLongTimeAgo = 233431200 t := jwt.New() t.Set(jwt.SubjectKey, `https://github.com/lestrrat-go/jwx/v3/jwt`) t.Set(jwt.AudienceKey, `Golang Users`) t.Set(jwt.IssuedAtKey, time.Unix(aLongLongTimeAgo, 0)) t.Set(`privateClaimKey`, `Hello, World!`) buf, err := json.MarshalIndent(t, "", " ") if err != nil { fmt.Printf("failed to generate JSON: %s\n", err) return } fmt.Printf("%s\n", buf) fmt.Printf("aud -> '%s'\n", t.Audience()) fmt.Printf("iat -> '%s'\n", t.IssuedAt().Format(time.RFC3339)) if v, ok := t.Get(`privateClaimKey`); ok { fmt.Printf("privateClaimKey -> '%s'\n", v) } fmt.Printf("sub -> '%s'\n", t.Subject()) key, err := rsa.GenerateKey(rand.Reader, 2048) if err != nil { log.Printf("failed to generate private key: %s", err) return } { // Signing a token (using raw rsa.PrivateKey) signed, err := jwt.Sign(t, jwt.WithKey(jwa.RS256, key)) if err != nil { log.Printf("failed to sign token: %s", err) return } _ = signed } { // Signing a token (using JWK) jwkKey, err := jwk.New(key) if err != nil { log.Printf("failed to create JWK key: %s", err) return } signed, err := jwt.Sign(t, jwt.WithKey(jwa.RS256, jwkKey)) if err != nil { log.Printf("failed to sign token: %s", err) return } _ = signed } } ``` ## OpenID Claims `jwt` package can work with token types other than the default one. For OpenID claims, use the token created by `openid.New()`, or use the `jwt.WithToken(openid.New())`. If you need to use other specialized claims, use `jwt.WithToken()` to specify the exact token type ```go func Example_openid() { const aLongLongTimeAgo = 233431200 t := openid.New() t.Set(jwt.SubjectKey, `https://github.com/lestrrat-go/jwx/v3/jwt`) t.Set(jwt.AudienceKey, `Golang Users`) t.Set(jwt.IssuedAtKey, time.Unix(aLongLongTimeAgo, 0)) t.Set(`privateClaimKey`, `Hello, World!`) addr := openid.NewAddress() addr.Set(openid.AddressPostalCodeKey, `105-0011`) addr.Set(openid.AddressCountryKey, `æ—ĨæœŦ`) addr.Set(openid.AddressRegionKey, `æąäēŦéƒŊ`) addr.Set(openid.AddressLocalityKey, `港åŒē`) addr.Set(openid.AddressStreetAddressKey, `芝å…Ŧ園 4-2-8`) t.Set(openid.AddressKey, addr) buf, err := json.MarshalIndent(t, "", " ") if err != nil { fmt.Printf("failed to generate JSON: %s\n", err) return } fmt.Printf("%s\n", buf) t2, err := jwt.Parse(buf, jwt.WithToken(openid.New())) if err != nil { fmt.Printf("failed to parse JSON: %s\n", err) return } if _, ok := t2.(openid.Token); !ok { fmt.Printf("using jwt.WithToken(openid.New()) creates an openid.Token instance") return } } ``` # FAQ ## Why is `jwt.Token` an interface? In this package, `jwt.Token` is an interface. This is not an arbitrary choice: there are actual reason for the type being an interface. We understand that if you are migrating from another library this may be a deal breaker, but we hope you can at least appreciate the fact that this was not done arbitrarily, and that there were real technical trade offs that were evaluated. ### No uninitialized tokens First and foremost, by making it an interface, you cannot use an uninitialized token: ```go var token1 jwt.Token // this is nil, you can't just start using this if err := json.Unmarshal(data, &token1); err != nil { // so you can't do this ... } // But you _can_ do this, and we _want_ you to do this so the object is properly initialized token2 = jwt.New() if err := json.Unmarshal(data, &token2); err != nil { // actually, in practice you should use jwt.Parse() .... } ``` ### But why does it need to be initialized? There are several reasons, but one of the reasons is that I'm using a sync.Mutex to avoid races. We want this to be properly initialized. The other reason is that we support custom claims out of the box. The `map[string]interface{}` container is initialized during new. This is important when checking for equality using reflect-y methods (akin to `reflect.DeepEqual`), because if you allowed zero values, you could end up with "empty" tokens, that actually differ. Consider the following: ```go // assume jwt.Token was s struct, not an interface token1 := jwt.Token{ privateClaims: make(map[string]interface{}) } token2 := jwt.Token{ privateClaims: nil } ``` These are semantically equivalent, but users would need to be aware of this difference when comparing values. By forcing the user to use a constructor, we can force a uniform empty state. ### Standard way to store values Unlike some other libraries, this library allows you to store standard claims and non-standard claims in the same token. You _want_ to store standard claims in a properly typed field, which we do for fields like "iss", "nbf", etc. But for non-standard claims, there is just no way of doing this, so we _have_ to use a container like `map[string]interface{}` This means that if you allow direct access to these fields via a struct, you will have two different ways to access the claims, which is confusing: ```go tok.Issuer = ... tok.PrivateClaims["foo"] = ... ``` So we want to hide where this data is stored, and use a standard method like `Set()` and `Get()` to store all the values. At this point you are effectively going to hide the implementation detail from the user, so you end up with a struct like below, which is fundamentally not so different from providing just an interface{}: ```go type Token struct { // unexported fields } func (tok *Token) Set(...) { ... } ``` ### Use of pointers to store values We wanted to differentiate the state between a claim being uninitialized, and a claim being initialized to empty. So we use pointers to store values: ```go type stdToken struct { .... issuer *string // if nil, uninitialized. if &(""), initialized to empty } ``` This is fine for us, but we doubt that this would be something users would want to do. This is a subtle difference, but cluttering up the API with slight variations of the same type (i.e. pointers vs non-pointers) seemed like a bad idea to us. ```go token.Issuer = &issuer // want to avoid this token.Set(jwt.IssuerKey, "foobar") // so this is what we picked ``` This way users no longer need to care how the data is internally stored. ### Allow more than one type of token through the same interface `dgrijalva/jwt-go` does this in a different way, but we felt that it would be more intuitive for all tokens to follow a single interface so there is fewer type conversions required. See the `openid` token for an example. golang-github-lestrrat-go-jwx-3.0.13/jwt/builder_gen.go000066400000000000000000000040371515060566400230220ustar00rootroot00000000000000// Code generated by tools/cmd/genjwt/main.go. DO NOT EDIT. package jwt import ( "fmt" "sync" "time" ) // Builder is a convenience wrapper around the New() constructor // and the Set() methods to assign values to Token claims. // Users can successively call Claim() on the Builder, and have it // construct the Token when Build() is called. This alleviates the // need for the user to check for the return value of every single // Set() method call. // Note that each call to Claim() overwrites the value set from the // previous call. type Builder struct { mu sync.Mutex claims map[string]any } func NewBuilder() *Builder { return &Builder{} } func (b *Builder) init() { if b.claims == nil { b.claims = make(map[string]any) } } func (b *Builder) Claim(name string, value any) *Builder { b.mu.Lock() defer b.mu.Unlock() b.init() b.claims[name] = value return b } func (b *Builder) Audience(v []string) *Builder { return b.Claim(AudienceKey, v) } func (b *Builder) Expiration(v time.Time) *Builder { return b.Claim(ExpirationKey, v) } func (b *Builder) IssuedAt(v time.Time) *Builder { return b.Claim(IssuedAtKey, v) } func (b *Builder) Issuer(v string) *Builder { return b.Claim(IssuerKey, v) } func (b *Builder) JwtID(v string) *Builder { return b.Claim(JwtIDKey, v) } func (b *Builder) NotBefore(v time.Time) *Builder { return b.Claim(NotBeforeKey, v) } func (b *Builder) Subject(v string) *Builder { return b.Claim(SubjectKey, v) } // Build creates a new token based on the claims that the builder has received // so far. If a claim cannot be set, then the method returns a nil Token with // a en error as a second return value // // Once `Build()` is called, all claims are cleared from the Builder, and the // Builder can be reused to build another token func (b *Builder) Build() (Token, error) { b.mu.Lock() claims := b.claims b.claims = nil b.mu.Unlock() tok := New() for k, v := range claims { if err := tok.Set(k, v); err != nil { return nil, fmt.Errorf(`failed to set claim %q: %w`, k, err) } } return tok, nil } golang-github-lestrrat-go-jwx-3.0.13/jwt/errors.go000066400000000000000000000057521515060566400220640ustar00rootroot00000000000000package jwt import ( jwterrs "github.com/lestrrat-go/jwx/v3/jwt/internal/errors" ) // ClaimNotFoundError returns the opaque error value that is returned when // `jwt.Get` fails to find the requested claim. // // This value should only be used for comparison using `errors.Is()`. func ClaimNotFoundError() error { return jwterrs.ErrClaimNotFound } // ClaimAssignmentFailedError returns the opaque error value that is returned // when `jwt.Get` fails to assign the value to the destination. For example, // this can happen when the value is a string, but you passed a &int as the // destination. // // This value should only be used for comparison using `errors.Is()`. func ClaimAssignmentFailedError() error { return jwterrs.ErrClaimAssignmentFailed } // UnknownPayloadTypeError returns the opaque error value that is returned when // `jwt.Parse` fails due to not being able to deduce the format of // the incoming buffer. // // This value should only be used for comparison using `errors.Is()`. func UnknownPayloadTypeError() error { return jwterrs.ErrUnknownPayloadType } // ParseError returns the opaque error that is returned from jwt.Parse when // the input is not a valid JWT. // // This value should only be used for comparison using `errors.Is()`. func ParseError() error { return jwterrs.ErrParse } // ValidateError returns the immutable error used for validation errors // // This value should only be used for comparison using `errors.Is()`. func ValidateError() error { return jwterrs.ErrValidateDefault } // InvalidIssuerError returns the immutable error used when `iss` claim // is not satisfied // // This value should only be used for comparison using `errors.Is()`. func InvalidIssuerError() error { return jwterrs.ErrInvalidIssuerDefault } // TokenExpiredError returns the immutable error used when `exp` claim // is not satisfied. // // This value should only be used for comparison using `errors.Is()`. func TokenExpiredError() error { return jwterrs.ErrTokenExpiredDefault } // InvalidIssuedAtError returns the immutable error used when `iat` claim // is not satisfied // // This value should only be used for comparison using `errors.Is()`. func InvalidIssuedAtError() error { return jwterrs.ErrInvalidIssuedAtDefault } // TokenNotYetValidError returns the immutable error used when `nbf` claim // is not satisfied // // This value should only be used for comparison using `errors.Is()`. func TokenNotYetValidError() error { return jwterrs.ErrTokenNotYetValidDefault } // InvalidAudienceError returns the immutable error used when `aud` claim // is not satisfied // // This value should only be used for comparison using `errors.Is()`. func InvalidAudienceError() error { return jwterrs.ErrInvalidAudienceDefault } // MissingRequiredClaimError returns the immutable error used when the claim // specified by `jwt.IsRequired()` is not present. // // This value should only be used for comparison using `errors.Is()`. func MissingRequiredClaimError() error { return jwterrs.ErrMissingRequiredClaimDefault } golang-github-lestrrat-go-jwx-3.0.13/jwt/fastpath.go000066400000000000000000000040071515060566400223520ustar00rootroot00000000000000package jwt import ( "fmt" "github.com/lestrrat-go/jwx/v3/internal/base64" "github.com/lestrrat-go/jwx/v3/internal/json" "github.com/lestrrat-go/jwx/v3/internal/pool" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/lestrrat-go/jwx/v3/jwk" "github.com/lestrrat-go/jwx/v3/jws" "github.com/lestrrat-go/jwx/v3/jws/jwsbb" ) // signFast reinvents the wheel a bit to avoid the overhead of // going through the entire jws.Sign() machinery. func signFast(t Token, alg jwa.SignatureAlgorithm, key any) ([]byte, error) { algstr := alg.String() var kid string if jwkKey, ok := key.(jwk.Key); ok { if v, ok := jwkKey.KeyID(); ok && v != "" { kid = v } } // Setup headers // {"alg":"","typ":"JWT"} // 1234567890123456789012 want := len(algstr) + 22 // also, if kid != "", we need to add "kid":"$kid" if kid != "" { // "kid":"" // 12345689 want += len(kid) + 9 } hdr := pool.ByteSlice().GetCapacity(want) hdr = append(hdr, '{', '"', 'a', 'l', 'g', '"', ':', '"') hdr = append(hdr, algstr...) hdr = append(hdr, '"') if kid != "" { hdr = append(hdr, ',', '"', 'k', 'i', 'd', '"', ':', '"') hdr = append(hdr, kid...) hdr = append(hdr, '"') } hdr = append(hdr, ',', '"', 't', 'y', 'p', '"', ':', '"', 'J', 'W', 'T', '"', '}') defer pool.ByteSlice().Put(hdr) // setup the buffer to sign with payload, err := json.Marshal(t) if err != nil { return nil, fmt.Errorf(`jwt.signFast: failed to marshal token payload: %w`, err) } combined := jwsbb.SignBuffer(nil, hdr, payload, base64.DefaultEncoder(), true) signer, err := jws.SignerFor(alg) if err != nil { return nil, fmt.Errorf(`jwt.signFast: failed to get signer for %s: %w`, alg, err) } signature, err := signer.Sign(key, combined) if err != nil { return nil, fmt.Errorf(`jwt.signFast: failed to sign payload with %s: %w`, alg, err) } serialized, err := jwsbb.JoinCompact(nil, hdr, payload, signature, base64.DefaultEncoder(), true) if err != nil { return nil, fmt.Errorf("jwt.signFast: failed to join compact: %w", err) } return serialized, nil } golang-github-lestrrat-go-jwx-3.0.13/jwt/filter.go000066400000000000000000000023551515060566400220310ustar00rootroot00000000000000package jwt import ( "github.com/lestrrat-go/jwx/v3/transform" ) // TokenFilter is an interface that allows users to filter JWT claims. // It provides two methods: Filter and Reject; Filter returns a new token with only // the claims that match the filter criteria, while Reject returns a new token with // only the claims that DO NOT match the filter. // // EXPERIMENTAL: This API is experimental and its interface and behavior is // subject to change in future releases. This API is not subject to semver // compatibility guarantees. type TokenFilter interface { Filter(token Token) (Token, error) Reject(token Token) (Token, error) } // StandardClaimsFilter returns a TokenFilter that filters out standard JWT claims. // // You can use this filter to create tokens that either only has standard claims // or only custom claims. If you need to configure the filter more precisely, consider // using the ClaimNameFilter directly. func StandardClaimsFilter() TokenFilter { return stdClaimsFilter } var stdClaimsFilter = NewClaimNameFilter(stdClaimNames...) // NewClaimNameFilter creates a new ClaimNameFilter with the specified claim names. func NewClaimNameFilter(names ...string) TokenFilter { return transform.NewNameBasedFilter[Token](names...) } golang-github-lestrrat-go-jwx-3.0.13/jwt/filter_test.go000066400000000000000000000221161515060566400230650ustar00rootroot00000000000000package jwt_test import ( "testing" "time" "github.com/lestrrat-go/jwx/v3/jwt" "github.com/stretchr/testify/require" ) func TestClaimNames(t *testing.T) { t.Run("NewClaimNames", func(t *testing.T) { cn := jwt.NewClaimNameFilter("a", "b", "c") require.NotNil(t, cn, "NewClaimNames should return a non-nil value") }) t.Run("Filter", func(t *testing.T) { // Create a ClaimNames with names a, b, c cn := jwt.NewClaimNameFilter("a", "b", "c") // Create a token with claims a, b, c, d, e token := jwt.New() require.NoError(t, token.Set("a", "value_a"), "token.Set should succeed") require.NoError(t, token.Set("b", "value_b"), "token.Set should succeed") require.NoError(t, token.Set("c", "value_c"), "token.Set should succeed") require.NoError(t, token.Set("d", "value_d"), "token.Set should succeed") require.NoError(t, token.Set("e", "value_e"), "token.Set should succeed") // Filter should return a token with only claims a, b, c filtered, err := cn.Filter(token) require.NoError(t, err, "cn.Filter should succeed") // Check that filtered token contains only a, b, c require.True(t, filtered.Has("a"), "filtered token should have claim 'a'") require.True(t, filtered.Has("b"), "filtered token should have claim 'b'") require.True(t, filtered.Has("c"), "filtered token should have claim 'c'") require.False(t, filtered.Has("d"), "filtered token should not have claim 'd'") require.False(t, filtered.Has("e"), "filtered token should not have claim 'e'") // Verify values are preserved var val string require.NoError(t, filtered.Get("a", &val), "filtered.Get should succeed") require.Equal(t, "value_a", val, "value for claim 'a' should be preserved") }) t.Run("Reject", func(t *testing.T) { // Create a ClaimNames with names a, b, c cn := jwt.NewClaimNameFilter("a", "b", "c") // Create a token with claims a, b, c, d, e token := jwt.New() require.NoError(t, token.Set("a", "value_a"), "token.Set should succeed") require.NoError(t, token.Set("b", "value_b"), "token.Set should succeed") require.NoError(t, token.Set("c", "value_c"), "token.Set should succeed") require.NoError(t, token.Set("d", "value_d"), "token.Set should succeed") require.NoError(t, token.Set("e", "value_e"), "token.Set should succeed") // Reject should return a token with only claims d, e rejected, err := cn.Reject(token) require.NoError(t, err, "cn.Reject should succeed") // Check that rejected token contains only d, e require.False(t, rejected.Has("a"), "rejected token should not have claim 'a'") require.False(t, rejected.Has("b"), "rejected token should not have claim 'b'") require.False(t, rejected.Has("c"), "rejected token should not have claim 'c'") require.True(t, rejected.Has("d"), "rejected token should have claim 'd'") require.True(t, rejected.Has("e"), "rejected token should have claim 'e'") // Verify values are preserved var val string require.NoError(t, rejected.Get("d", &val), "rejected.Get should succeed") require.Equal(t, "value_d", val, "value for claim 'd' should be preserved") }) t.Run("Error handling with Clone", func(t *testing.T) { // Mock a token that will fail Clone token := jwt.New() // Setting a value that cannot be retrieved to simulate a Clone error // We can't actually create a token that fails to clone in the test code, // so this test is a bit artificial. In real code, we'd use a mock. // But let's at least verify Filter and Reject don't panic cn := jwt.NewClaimNameFilter("a", "b", "c") _, err := cn.Filter(token) require.NoError(t, err, "cn.Filter should succeed even for an empty token") _, err = cn.Reject(token) require.NoError(t, err, "cn.Reject should succeed even for an empty token") }) t.Run("Empty ClaimNames", func(t *testing.T) { // Create an empty ClaimNames cn := jwt.NewClaimNameFilter() // Create a token with claims a, b, c token := jwt.New() require.NoError(t, token.Set("a", "value_a"), "token.Set should succeed") require.NoError(t, token.Set("b", "value_b"), "token.Set should succeed") require.NoError(t, token.Set("c", "value_c"), "token.Set should succeed") // Filter with empty ClaimNames should result in an empty token filtered, err := cn.Filter(token) require.NoError(t, err, "cn.Filter should succeed") require.Empty(t, filtered.Keys(), "filtered token should have no claims") // Reject with empty ClaimNames should result in a copy of the original token rejected, err := cn.Reject(token) require.NoError(t, err, "cn.Reject should succeed") // Check that rejected token has the same keys as original, ignoring order originalKeys := token.Keys() rejectedKeys := rejected.Keys() require.ElementsMatch(t, originalKeys, rejectedKeys, "rejected token should have the same claims as the original") }) t.Run("Concurrency safety", func(t *testing.T) { // This is more of a logical test than an actual concurrency test // but it ensures the mutex is being used correctly in the Filter/Reject methods cn := jwt.NewClaimNameFilter("a", "b", "c") token := jwt.New() require.NoError(t, token.Set("a", "value_a"), "token.Set should succeed") require.NoError(t, token.Set("d", "value_d"), "token.Set should succeed") // Should not deadlock or have race conditions filtered, err := cn.Filter(token) require.NoError(t, err, "cn.Filter should succeed") require.True(t, filtered.Has("a"), "filtered token should have claim 'a'") require.False(t, filtered.Has("d"), "filtered token should not have claim 'd'") rejected, err := cn.Reject(token) require.NoError(t, err, "cn.Reject should succeed") require.False(t, rejected.Has("a"), "rejected token should not have claim 'a'") require.True(t, rejected.Has("d"), "rejected token should have claim 'd'") }) } func TestStandardClaimsFilter(t *testing.T) { t.Run("Filter standard claims", func(t *testing.T) { // Create a token with standard and custom claims token, err := jwt.NewBuilder(). Audience([]string{"aud1", "aud2"}). Expiration(time.Unix(aLongLongTimeAgo, 0)). IssuedAt(time.Unix(aLongLongTimeAgo, 0)). Issuer("issuer"). JwtID("jwt-id"). NotBefore(time.Unix(aLongLongTimeAgo, 0)). Subject("subject"). Claim("custom1", "value1"). Claim("custom2", "value2"). Build() require.NoError(t, err, "token should be created successfully") require.NotNil(t, token, "token should not be nil") stdFilter := jwt.StandardClaimsFilter() t.Run("Filter standard claims", func(t *testing.T) { // Filter should return a token with only standard claims filtered, err := stdFilter.Filter(token) require.NoError(t, err, "filter.Filter should succeed") // Verify standard claims are present require.True(t, filtered.Has(jwt.AudienceKey), "filtered token should have audience claim") require.True(t, filtered.Has(jwt.ExpirationKey), "filtered token should have expiration claim") require.True(t, filtered.Has(jwt.IssuedAtKey), "filtered token should have issued-at claim") require.True(t, filtered.Has(jwt.IssuerKey), "filtered token should have issuer claim") require.True(t, filtered.Has(jwt.JwtIDKey), "filtered token should have jwt-id claim") require.True(t, filtered.Has(jwt.NotBeforeKey), "filtered token should have not-before claim") require.True(t, filtered.Has(jwt.SubjectKey), "filtered token should have subject claim") // Verify custom claims are not present require.False(t, filtered.Has("custom1"), "filtered token should not have custom1 claim") require.False(t, filtered.Has("custom2"), "filtered token should not have custom2 claim") // Verify values are preserved var issuer string require.NoError(t, filtered.Get(jwt.IssuerKey, &issuer), "filtered.Get should succeed") require.Equal(t, "issuer", issuer, "value for issuer claim should be preserved") }) t.Run("Reject standard claims", func(t *testing.T) { // Reject should return a token with only custom claims rejected, err := stdFilter.Reject(token) require.NoError(t, err, "filter.Reject should succeed") // Verify standard claims are not present require.False(t, rejected.Has(jwt.AudienceKey), "rejected token should not have audience claim") require.False(t, rejected.Has(jwt.ExpirationKey), "rejected token should not have expiration claim") require.False(t, rejected.Has(jwt.IssuedAtKey), "rejected token should not have issued-at claim") require.False(t, rejected.Has(jwt.IssuerKey), "rejected token should not have issuer claim") require.False(t, rejected.Has(jwt.JwtIDKey), "rejected token should not have jwt-id claim") require.False(t, rejected.Has(jwt.NotBeforeKey), "rejected token should not have not-before claim") require.False(t, rejected.Has(jwt.SubjectKey), "rejected token should not have subject claim") // Verify custom claims are present require.True(t, rejected.Has("custom1"), "rejected token should have custom1 claim") require.True(t, rejected.Has("custom2"), "rejected token should have custom2 claim") // Verify values are preserved var customValue string require.NoError(t, rejected.Get("custom1", &customValue), "rejected.Get should succeed") require.Equal(t, "value1", customValue, "value for custom1 claim should be preserved") }) }) } golang-github-lestrrat-go-jwx-3.0.13/jwt/http.go000066400000000000000000000174371515060566400215320ustar00rootroot00000000000000package jwt import ( "fmt" "net/http" "net/url" "strconv" "strings" "github.com/lestrrat-go/jwx/v3/internal/pool" "github.com/lestrrat-go/jwx/v3/internal/tokens" ) // ParseCookie parses a JWT stored in a http.Cookie with the given name. // If the specified cookie is not found, http.ErrNoCookie is returned. func ParseCookie(req *http.Request, name string, options ...ParseOption) (Token, error) { var dst **http.Cookie for _, option := range options { switch option.Ident() { case identCookie{}: if err := option.Value(&dst); err != nil { return nil, fmt.Errorf(`jws.ParseCookie: value to option WithCookie must be **http.Cookie: %w`, err) } } } cookie, err := req.Cookie(name) if err != nil { return nil, err } tok, err := ParseString(cookie.Value, options...) if err != nil { return nil, fmt.Errorf(`jws.ParseCookie: failed to parse token stored in cookie: %w`, err) } if dst != nil { *dst = cookie } return tok, nil } // ParseHeader parses a JWT stored in a http.Header. // // For the header "Authorization", it will strip the prefix "Bearer " and will // treat the remaining value as a JWT. func ParseHeader(hdr http.Header, name string, options ...ParseOption) (Token, error) { key := http.CanonicalHeaderKey(name) v := strings.TrimSpace(hdr.Get(key)) if v == "" { return nil, fmt.Errorf(`empty header (%s)`, key) } if key == "Authorization" { // Authorization header is an exception. We strip the "Bearer " from // the prefix v = strings.TrimSpace(strings.TrimPrefix(v, "Bearer")) } tok, err := ParseString(v, options...) if err != nil { return nil, fmt.Errorf(`failed to parse token stored in header (%s): %w`, key, err) } return tok, nil } // ParseForm parses a JWT stored in a url.Value. func ParseForm(values url.Values, name string, options ...ParseOption) (Token, error) { v := strings.TrimSpace(values.Get(name)) if v == "" { return nil, fmt.Errorf(`empty value (%s)`, name) } return ParseString(v, options...) } // ParseRequest searches a http.Request object for a JWT token. // // Specifying WithHeaderKey() will tell it to search under a specific // header key. Specifying WithFormKey() will tell it to search under // a specific form field. // // If none of jwt.WithHeaderKey()/jwt.WithCookieKey()/jwt.WithFormKey() is // used, "Authorization" header will be searched. If any of these options // are specified, you must explicitly re-enable searching for "Authorization" header // if you also want to search for it. // // # searches for "Authorization" // jwt.ParseRequest(req) // // # searches for "x-my-token" ONLY. // jwt.ParseRequest(req, jwt.WithHeaderKey("x-my-token")) // // # searches for "Authorization" AND "x-my-token" // jwt.ParseRequest(req, jwt.WithHeaderKey("Authorization"), jwt.WithHeaderKey("x-my-token")) // // Cookies are searched using (http.Request).Cookie(). If you have multiple // cookies with the same name, and you want to search for a specific one that // (http.Request).Cookie() would not return, you will need to implement your // own logic to extract the cookie and use jwt.ParseString(). func ParseRequest(req *http.Request, options ...ParseOption) (Token, error) { var hdrkeys []string var formkeys []string var cookiekeys []string var parseOptions []ParseOption for _, option := range options { switch option.Ident() { case identHeaderKey{}: var v string if err := option.Value(&v); err != nil { return nil, fmt.Errorf(`jws.ParseRequest: value to option WithHeaderKey must be string: %w`, err) } hdrkeys = append(hdrkeys, v) case identFormKey{}: var v string if err := option.Value(&v); err != nil { return nil, fmt.Errorf(`jws.ParseRequest: value to option WithFormKey must be string: %w`, err) } formkeys = append(formkeys, v) case identCookieKey{}: var v string if err := option.Value(&v); err != nil { return nil, fmt.Errorf(`jws.ParseRequest: value to option WithCookieKey must be string: %w`, err) } cookiekeys = append(cookiekeys, v) default: parseOptions = append(parseOptions, option) } } if len(hdrkeys) == 0 && len(formkeys) == 0 && len(cookiekeys) == 0 { hdrkeys = append(hdrkeys, "Authorization") } mhdrs := pool.KeyToErrorMap().Get() defer pool.KeyToErrorMap().Put(mhdrs) mfrms := pool.KeyToErrorMap().Get() defer pool.KeyToErrorMap().Put(mfrms) mcookies := pool.KeyToErrorMap().Get() defer pool.KeyToErrorMap().Put(mcookies) for _, hdrkey := range hdrkeys { // Check presence via a direct map lookup if _, ok := req.Header[http.CanonicalHeaderKey(hdrkey)]; !ok { // if non-existent, not error continue } tok, err := ParseHeader(req.Header, hdrkey, parseOptions...) if err != nil { mhdrs[hdrkey] = err continue } return tok, nil } for _, name := range cookiekeys { tok, err := ParseCookie(req, name, parseOptions...) if err != nil { if err == http.ErrNoCookie { // not fatal mcookies[name] = err } continue } return tok, nil } if cl := req.ContentLength; cl > 0 { if err := req.ParseForm(); err != nil { return nil, fmt.Errorf(`failed to parse form: %w`, err) } } for _, formkey := range formkeys { // Check presence via a direct map lookup if _, ok := req.Form[formkey]; !ok { // if non-existent, not error continue } tok, err := ParseForm(req.Form, formkey, parseOptions...) if err != nil { mfrms[formkey] = err continue } return tok, nil } // Everything below is a prelude to error reporting. var triedHdrs strings.Builder for i, hdrkey := range hdrkeys { if i > 0 { triedHdrs.WriteString(", ") } triedHdrs.WriteString(strconv.Quote(hdrkey)) } var triedForms strings.Builder for i, formkey := range formkeys { if i > 0 { triedForms.WriteString(", ") } triedForms.WriteString(strconv.Quote(formkey)) } var triedCookies strings.Builder for i, cookiekey := range cookiekeys { if i > 0 { triedCookies.WriteString(", ") } triedCookies.WriteString(strconv.Quote(cookiekey)) } var b strings.Builder b.WriteString(`failed to find a valid token in any location of the request (tried: `) olen := b.Len() if triedHdrs.Len() > 0 { b.WriteString(`header keys: [`) b.WriteString(triedHdrs.String()) b.WriteByte(tokens.CloseSquareBracket) } if triedForms.Len() > 0 { if b.Len() > olen { b.WriteString(", ") } b.WriteString("form keys: [") b.WriteString(triedForms.String()) b.WriteByte(tokens.CloseSquareBracket) } if triedCookies.Len() > 0 { if b.Len() > olen { b.WriteString(", ") } b.WriteString("cookie keys: [") b.WriteString(triedCookies.String()) b.WriteByte(tokens.CloseSquareBracket) } b.WriteByte(')') lmhdrs := len(mhdrs) lmfrms := len(mfrms) lmcookies := len(mcookies) var errors []any if lmhdrs > 0 || lmfrms > 0 || lmcookies > 0 { b.WriteString(". Additionally, errors were encountered during attempts to verify using:") if lmhdrs > 0 { b.WriteString(" headers: (") count := 0 for hdrkey, err := range mhdrs { if count > 0 { b.WriteString(", ") } b.WriteString("[header key: ") b.WriteString(strconv.Quote(hdrkey)) b.WriteString(", error: %w]") errors = append(errors, err) count++ } b.WriteString(")") } if lmcookies > 0 { count := 0 b.WriteString(" cookies: (") for cookiekey, err := range mcookies { if count > 0 { b.WriteString(", ") } b.WriteString("[cookie key: ") b.WriteString(strconv.Quote(cookiekey)) b.WriteString(", error: %w]") errors = append(errors, err) count++ } } if lmfrms > 0 { count := 0 b.WriteString(" forms: (") for formkey, err := range mfrms { if count > 0 { b.WriteString(", ") } b.WriteString("[form key: ") b.WriteString(strconv.Quote(formkey)) b.WriteString(", error: %w]") errors = append(errors, err) count++ } } } return nil, fmt.Errorf(b.String(), errors...) } golang-github-lestrrat-go-jwx-3.0.13/jwt/interface.go000066400000000000000000000002321515060566400224740ustar00rootroot00000000000000package jwt import ( "github.com/lestrrat-go/jwx/v3/internal/json" ) type DecodeCtx = json.DecodeCtx type TokenWithDecodeCtx = json.DecodeCtxContainer golang-github-lestrrat-go-jwx-3.0.13/jwt/internal/000077500000000000000000000000001515060566400220245ustar00rootroot00000000000000golang-github-lestrrat-go-jwx-3.0.13/jwt/internal/errors/000077500000000000000000000000001515060566400233405ustar00rootroot00000000000000golang-github-lestrrat-go-jwx-3.0.13/jwt/internal/errors/BUILD.bazel000066400000000000000000000005311515060566400252150ustar00rootroot00000000000000load("@rules_go//go:def.bzl", "go_library") go_library( name = "errors", srcs = [ "errors.go", ], importpath = "github.com/lestrrat-go/jwx/v3/jwt/internal/errors", visibility = ["//jwt:__subpackages__"], ) alias( name = "go_default_library", actual = ":errors", visibility = ["//jwt:__subpackages__"], )golang-github-lestrrat-go-jwx-3.0.13/jwt/internal/errors/errors.go000066400000000000000000000110051515060566400252000ustar00rootroot00000000000000// Package errors exist to store errors for jwt and openid packages. // // It's internal because we don't want to expose _anything_ about these errors // so users absolutely cannot do anything other than use them as opaque errors. // //nolint:revive package errors import ( "errors" "fmt" ) var ( ErrClaimNotFound = ClaimNotFoundError{} ErrClaimAssignmentFailed = ClaimAssignmentFailedError{Err: errors.New(`claim assignment failed`)} ErrUnknownPayloadType = errors.New(`unknown payload type (payload is not JWT?)`) ErrParse = ParseError{error: errors.New(`jwt.Parse: unknown error`)} ErrValidateDefault = ValidationError{errors.New(`unknown error`)} ErrInvalidIssuerDefault = InvalidIssuerError{errors.New(`"iss" not satisfied`)} ErrTokenExpiredDefault = TokenExpiredError{errors.New(`"exp" not satisfied: token is expired`)} ErrInvalidIssuedAtDefault = InvalidIssuedAtError{errors.New(`"iat" not satisfied`)} ErrTokenNotYetValidDefault = TokenNotYetValidError{errors.New(`"nbf" not satisfied: token is not yet valid`)} ErrInvalidAudienceDefault = InvalidAudienceError{errors.New(`"aud" not satisfied`)} ErrMissingRequiredClaimDefault = &MissingRequiredClaimError{error: errors.New(`required claim is missing`)} ) type ClaimNotFoundError struct { Name string } func (e ClaimNotFoundError) Error() string { // This error message uses "field" instead of "claim" for backwards compatibility, // but it shuold really be "claim" since it refers to a JWT claim. return fmt.Sprintf(`field "%s" not found`, e.Name) } func (e ClaimNotFoundError) Is(target error) bool { _, ok := target.(ClaimNotFoundError) return ok } type ClaimAssignmentFailedError struct { Err error } func (e ClaimAssignmentFailedError) Error() string { // This error message probably should be tweaked, but it is this way // for backwards compatibility. return fmt.Sprintf(`failed to assign value to dst: %s`, e.Err.Error()) } func (e ClaimAssignmentFailedError) Unwrap() error { return e.Err } func (e ClaimAssignmentFailedError) Is(target error) bool { _, ok := target.(ClaimAssignmentFailedError) return ok } type ParseError struct { error } func (e ParseError) Unwrap() error { return e.error } func (ParseError) Is(err error) bool { _, ok := err.(ParseError) return ok } func ParseErrorf(prefix, f string, args ...any) error { return ParseError{fmt.Errorf(prefix+": "+f, args...)} } type ValidationError struct { error } func (ValidationError) Is(err error) bool { _, ok := err.(ValidationError) return ok } func (err ValidationError) Unwrap() error { return err.error } func ValidateErrorf(f string, args ...any) error { return ValidationError{fmt.Errorf(`jwt.Validate: `+f, args...)} } type InvalidIssuerError struct { error } func (err InvalidIssuerError) Is(target error) bool { _, ok := target.(InvalidIssuerError) return ok } func (err InvalidIssuerError) Unwrap() error { return err.error } func IssuerErrorf(f string, args ...any) error { return InvalidIssuerError{fmt.Errorf(`"iss" not satisfied: `+f, args...)} } type TokenExpiredError struct { error } func (err TokenExpiredError) Is(target error) bool { _, ok := target.(TokenExpiredError) return ok } func (err TokenExpiredError) Unwrap() error { return err.error } type InvalidIssuedAtError struct { error } func (err InvalidIssuedAtError) Is(target error) bool { _, ok := target.(InvalidIssuedAtError) return ok } func (err InvalidIssuedAtError) Unwrap() error { return err.error } type TokenNotYetValidError struct { error } func (err TokenNotYetValidError) Is(target error) bool { _, ok := target.(TokenNotYetValidError) return ok } func (err TokenNotYetValidError) Unwrap() error { return err.error } type InvalidAudienceError struct { error } func (err InvalidAudienceError) Is(target error) bool { _, ok := target.(InvalidAudienceError) return ok } func (err InvalidAudienceError) Unwrap() error { return err.error } func AudienceErrorf(f string, args ...any) error { return InvalidAudienceError{fmt.Errorf(`"aud" not satisfied: `+f, args...)} } type MissingRequiredClaimError struct { error claim string } func (err *MissingRequiredClaimError) Is(target error) bool { err1, ok := target.(*MissingRequiredClaimError) if !ok { return false } return err1 == ErrMissingRequiredClaimDefault || err1.claim == err.claim } func MissingRequiredClaimErrorf(name string) error { return &MissingRequiredClaimError{claim: name, error: fmt.Errorf(`required claim "%s" is missing`, name)} } golang-github-lestrrat-go-jwx-3.0.13/jwt/internal/types/000077500000000000000000000000001515060566400231705ustar00rootroot00000000000000golang-github-lestrrat-go-jwx-3.0.13/jwt/internal/types/BUILD.bazel000066400000000000000000000012571515060566400250530ustar00rootroot00000000000000load("@rules_go//go:def.bzl", "go_library", "go_test") go_library( name = "types", srcs = [ "date.go", "string.go", ], importpath = "github.com/lestrrat-go/jwx/v3/jwt/internal/types", visibility = ["//jwt:__subpackages__"], deps = [ "//internal/json", "//internal/tokens", ], ) go_test( name = "types_test", srcs = [ "date_test.go", "string_test.go", ], deps = [ ":types", "//internal/json", "//jwt", "@com_github_stretchr_testify//require", ], ) alias( name = "go_default_library", actual = ":types", visibility = ["//jwt:__subpackages__"], ) golang-github-lestrrat-go-jwx-3.0.13/jwt/internal/types/date.go000066400000000000000000000105741515060566400244430ustar00rootroot00000000000000package types import ( "fmt" "strconv" "strings" "time" "github.com/lestrrat-go/jwx/v3/internal/json" "github.com/lestrrat-go/jwx/v3/internal/tokens" ) const ( DefaultPrecision uint32 = 0 // second level MaxPrecision uint32 = 9 // nanosecond level ) var Pedantic uint32 var ParsePrecision = DefaultPrecision var FormatPrecision = DefaultPrecision // NumericDate represents the date format used in the 'nbf' claim type NumericDate struct { time.Time } func (n *NumericDate) Get() time.Time { if n == nil { return (time.Time{}).UTC() } return n.Time } func intToTime(v any, t *time.Time) bool { var n int64 switch x := v.(type) { case int64: n = x case int32: n = int64(x) case int16: n = int64(x) case int8: n = int64(x) case int: n = int64(x) default: return false } *t = time.Unix(n, 0) return true } func parseNumericString(x string) (time.Time, error) { var t time.Time // empty time for empty return value // Only check for the escape hatch if it's the pedantic // flag is off if Pedantic != 1 { // This is an escape hatch for non-conformant providers // that gives us RFC3339 instead of epoch time for _, r := range x { // 0x30 = '0', 0x39 = '9', 0x2E = tokens.Period if (r >= 0x30 && r <= 0x39) || r == 0x2E { continue } // if it got here, then it probably isn't epoch time tv, err := time.Parse(time.RFC3339, x) if err != nil { return t, fmt.Errorf(`value is not number of seconds since the epoch, and attempt to parse it as RFC3339 timestamp failed: %w`, err) } return tv, nil } } var fractional string whole := x if i := strings.IndexRune(x, tokens.Period); i > 0 { if ParsePrecision > 0 && len(x) > i+1 { fractional = x[i+1:] // everything after the tokens.Period if int(ParsePrecision) < len(fractional) { // Remove insignificant digits fractional = fractional[:int(ParsePrecision)] } // Replace missing fractional diits with zeros for len(fractional) < int(MaxPrecision) { fractional = fractional + "0" } } whole = x[:i] } n, err := strconv.ParseInt(whole, 10, 64) if err != nil { return t, fmt.Errorf(`failed to parse whole value %q: %w`, whole, err) } var nsecs int64 if fractional != "" { v, err := strconv.ParseInt(fractional, 10, 64) if err != nil { return t, fmt.Errorf(`failed to parse fractional value %q: %w`, fractional, err) } nsecs = v } return time.Unix(n, nsecs).UTC(), nil } func (n *NumericDate) Accept(v any) error { var t time.Time switch x := v.(type) { case float32: tv, err := parseNumericString(fmt.Sprintf(`%.9f`, x)) if err != nil { return fmt.Errorf(`failed to accept float32 %.9f: %w`, x, err) } t = tv case float64: tv, err := parseNumericString(fmt.Sprintf(`%.9f`, x)) if err != nil { return fmt.Errorf(`failed to accept float32 %.9f: %w`, x, err) } t = tv case json.Number: tv, err := parseNumericString(x.String()) if err != nil { return fmt.Errorf(`failed to accept json.Number %q: %w`, x.String(), err) } t = tv case string: tv, err := parseNumericString(x) if err != nil { return fmt.Errorf(`failed to accept string %q: %w`, x, err) } t = tv case time.Time: t = x default: if !intToTime(v, &t) { return fmt.Errorf(`invalid type %T`, v) } } n.Time = t.UTC() return nil } func (n NumericDate) String() string { if FormatPrecision == 0 { return strconv.FormatInt(n.Unix(), 10) } // This is cheating, but it's better (easier) than doing floating point math // We basically munge with strings after formatting an integer value // for nanoseconds since epoch s := strconv.FormatInt(n.UnixNano(), 10) for len(s) < int(MaxPrecision) { s = "0" + s } slwhole := len(s) - int(MaxPrecision) s = s[:slwhole] + "." + s[slwhole:slwhole+int(FormatPrecision)] if s[0] == tokens.Period { s = "0" + s } return s } // MarshalJSON translates from internal representation to JSON NumericDate // See https://tools.ietf.org/html/rfc7519#page-6 func (n *NumericDate) MarshalJSON() ([]byte, error) { if n.IsZero() { return json.Marshal(nil) } return json.Marshal(n.String()) } func (n *NumericDate) UnmarshalJSON(data []byte) error { var v any if err := json.Unmarshal(data, &v); err != nil { return fmt.Errorf(`failed to unmarshal date: %w`, err) } var n2 NumericDate if err := n2.Accept(v); err != nil { return fmt.Errorf(`invalid value for NumericDate: %w`, err) } *n = n2 return nil } golang-github-lestrrat-go-jwx-3.0.13/jwt/internal/types/date_test.go000066400000000000000000000046471515060566400255060ustar00rootroot00000000000000package types_test import ( "fmt" "testing" "time" "github.com/lestrrat-go/jwx/v3/internal/json" "github.com/lestrrat-go/jwx/v3/jwt" "github.com/lestrrat-go/jwx/v3/jwt/internal/types" "github.com/stretchr/testify/require" ) func TestDate(t *testing.T) { t.Run("Get from a nil NumericDate", func(t *testing.T) { var n *types.NumericDate require.Equal(t, time.Time{}, n.Get()) }) t.Run("MarshalJSON with a zero value", func(t *testing.T) { var n *types.NumericDate buf, err := json.Marshal(n) require.NoError(t, err, `json.Marshal against a zero value should succeed`) require.Equal(t, []byte(`null`), buf, `result should be null`) }) // This test alters global behavior, and can't be ran in parallel t.Run("Accept values", func(t *testing.T) { // NumericDate allows assignment from various different Go types, // so that it's easier for the devs, and conversion to/from JSON testcases := []struct { Input any Expected time.Time Precision int }{ { Input: int64(127), Expected: time.Unix(127, 0).UTC(), }, { Input: int32(127), Expected: time.Unix(127, 0).UTC(), }, { Input: int16(127), Expected: time.Unix(127, 0).UTC(), }, { Input: int8(127), Expected: time.Unix(127, 0).UTC(), }, { Input: float32(127.11), Expected: time.Unix(127, 0).UTC(), }, { Input: float32(127.11), Expected: time.Unix(127, 0).UTC(), }, { Input: json.Number("127"), Expected: time.Unix(127, 0).UTC(), }, { Input: json.Number("127.11"), Expected: time.Unix(127, 0).UTC(), }, { Input: json.Number("127.11"), Expected: time.Unix(127, 110000000).UTC(), Precision: 4, }, { Input: json.Number("127.110000011"), Expected: time.Unix(127, 110000011).UTC(), Precision: 9, }, { Input: json.Number("127.110000011111"), Expected: time.Unix(127, 110000011).UTC(), Precision: 9, }, } for _, tc := range testcases { precision := tc.Precision t.Run(fmt.Sprintf("%v(type=%T, precision=%d)", tc.Input, tc.Input, precision), func(t *testing.T) { jwt.Settings(jwt.WithNumericDateParsePrecision(precision)) t1 := jwt.New() err := t1.Set(jwt.IssuedAtKey, tc.Input) require.NoError(t, err) var v time.Time require.NoError(t, t1.Get(jwt.IssuedAtKey, &v), `t1.Get should succeed`) require.Equal(t, tc.Expected, v) }) } }) } golang-github-lestrrat-go-jwx-3.0.13/jwt/internal/types/string.go000066400000000000000000000014351515060566400250300ustar00rootroot00000000000000package types import ( "fmt" "github.com/lestrrat-go/jwx/v3/internal/json" ) type StringList []string func (l StringList) Get() []string { return []string(l) } func (l *StringList) Accept(v any) error { switch x := v.(type) { case string: *l = StringList([]string{x}) case []string: *l = StringList(x) case []any: list := make(StringList, len(x)) for i, e := range x { if s, ok := e.(string); ok { list[i] = s continue } return fmt.Errorf(`invalid list element type %T`, e) } *l = list default: return fmt.Errorf(`invalid type: %T`, v) } return nil } func (l *StringList) UnmarshalJSON(data []byte) error { var v any if err := json.Unmarshal(data, &v); err != nil { return fmt.Errorf(`failed to unmarshal data: %w`, err) } return l.Accept(v) } golang-github-lestrrat-go-jwx-3.0.13/jwt/internal/types/string_test.go000066400000000000000000000006651515060566400260730ustar00rootroot00000000000000package types_test import ( "testing" "github.com/lestrrat-go/jwx/v3/jwt/internal/types" "github.com/stretchr/testify/require" ) func TestStringList_Accept(t *testing.T) { t.Parallel() var x types.StringList interfaceList := make([]any, 0) interfaceList = append(interfaceList, "first") interfaceList = append(interfaceList, "second") require.NoError(t, x.Accept(interfaceList), "failed to convert []any into StringList") } golang-github-lestrrat-go-jwx-3.0.13/jwt/io.go000066400000000000000000000014261515060566400211510ustar00rootroot00000000000000// Code generated by tools/cmd/genreadfile/main.go. DO NOT EDIT. package jwt import ( "fmt" "io/fs" "os" ) type sysFS struct{} func (sysFS) Open(path string) (fs.File, error) { return os.Open(path) } func ReadFile(path string, options ...ReadFileOption) (Token, error) { var parseOptions []ParseOption for _, option := range options { if po, ok := option.(ParseOption); ok { parseOptions = append(parseOptions, po) } } var srcFS fs.FS = sysFS{} for _, option := range options { switch option.Ident() { case identFS{}: if err := option.Value(&srcFS); err != nil { return nil, fmt.Errorf("failed to set fs.FS: %w", err) } } } f, err := srcFS.Open(path) if err != nil { return nil, err } defer f.Close() return ParseReader(f, parseOptions...) } golang-github-lestrrat-go-jwx-3.0.13/jwt/jwt.go000066400000000000000000000454461515060566400213600ustar00rootroot00000000000000//go:generate ../tools/cmd/genjwt.sh //go:generate stringer -type=TokenOption -output=token_options_gen.go // Package jwt implements JSON Web Tokens as described in https://tools.ietf.org/html/rfc7519 package jwt import ( "bytes" "fmt" "io" "sync/atomic" "time" "github.com/lestrrat-go/jwx/v3" "github.com/lestrrat-go/jwx/v3/internal/json" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/lestrrat-go/jwx/v3/jws" jwterrs "github.com/lestrrat-go/jwx/v3/jwt/internal/errors" "github.com/lestrrat-go/jwx/v3/jwt/internal/types" ) var defaultTruncation atomic.Int64 // Settings controls global settings that are specific to JWTs. func Settings(options ...GlobalOption) { var flattenAudience bool var parsePedantic bool var parsePrecision = types.MaxPrecision + 1 // illegal value, so we can detect nothing was set var formatPrecision = types.MaxPrecision + 1 // illegal value, so we can detect nothing was set truncation := time.Duration(-1) for _, option := range options { switch option.Ident() { case identTruncation{}: if err := option.Value(&truncation); err != nil { panic(fmt.Sprintf("jwt.Settings: value for WithTruncation must be time.Duration: %s", err)) } case identFlattenAudience{}: if err := option.Value(&flattenAudience); err != nil { panic(fmt.Sprintf("jwt.Settings: value for WithFlattenAudience must be bool: %s", err)) } case identNumericDateParsePedantic{}: if err := option.Value(&parsePedantic); err != nil { panic(fmt.Sprintf("jwt.Settings: value for WithNumericDateParsePedantic must be bool: %s", err)) } case identNumericDateParsePrecision{}: var v int if err := option.Value(&v); err != nil { panic(fmt.Sprintf("jwt.Settings: value for WithNumericDateParsePrecision must be int: %s", err)) } // only accept this value if it's in our desired range if v >= 0 && v <= int(types.MaxPrecision) { parsePrecision = uint32(v) } case identNumericDateFormatPrecision{}: var v int if err := option.Value(&v); err != nil { panic(fmt.Sprintf("jwt.Settings: value for WithNumericDateFormatPrecision must be int: %s", err)) } // only accept this value if it's in our desired range if v >= 0 && v <= int(types.MaxPrecision) { formatPrecision = uint32(v) } } } if parsePrecision <= types.MaxPrecision { // remember we set default to max + 1 v := atomic.LoadUint32(&types.ParsePrecision) if v != parsePrecision { atomic.CompareAndSwapUint32(&types.ParsePrecision, v, parsePrecision) } } if formatPrecision <= types.MaxPrecision { // remember we set default to max + 1 v := atomic.LoadUint32(&types.FormatPrecision) if v != formatPrecision { atomic.CompareAndSwapUint32(&types.FormatPrecision, v, formatPrecision) } } { v := atomic.LoadUint32(&types.Pedantic) if (v == 1) != parsePedantic { var newVal uint32 if parsePedantic { newVal = 1 } atomic.CompareAndSwapUint32(&types.Pedantic, v, newVal) } } { defaultOptionsMu.Lock() if flattenAudience { defaultOptions.Enable(FlattenAudience) } else { defaultOptions.Disable(FlattenAudience) } defaultOptionsMu.Unlock() } if truncation >= 0 { defaultTruncation.Store(int64(truncation)) } } var registry = json.NewRegistry() // ParseString calls Parse against a string func ParseString(s string, options ...ParseOption) (Token, error) { tok, err := parseBytes([]byte(s), options...) if err != nil { return nil, jwterrs.ParseErrorf(`jwt.ParseString`, `failed to parse string: %w`, err) } return tok, nil } // Parse parses the JWT token payload and creates a new `jwt.Token` object. // The token must be encoded in JWS compact format, or a raw JSON form of JWT // without any signatures. // // If you need JWE support on top of JWS, you will need to rollout your // own workaround. // // If the token is signed, and you want to verify the payload matches the signature, // you must pass the jwt.WithKey(alg, key) or jwt.WithKeySet(jwk.Set) option. // If you do not specify these parameters, no verification will be performed. // // During verification, if the JWS headers specify a key ID (`kid`), the // key used for verification must match the specified ID. If you are somehow // using a key without a `kid` (which is highly unlikely if you are working // with a JWT from a well-know provider), you can work around this by modifying // the `jwk.Key` and setting the `kid` header. // // If you also want to assert the validity of the JWT itself (i.e. expiration // and such), use the `Validate()` function on the returned token, or pass the // `WithValidate(true)` option. Validate options can also be passed to // `Parse` // // This function takes both ParseOption and ValidateOption types: // ParseOptions control the parsing behavior, and ValidateOptions are // passed to `Validate()` when `jwt.WithValidate` is specified. func Parse(s []byte, options ...ParseOption) (Token, error) { tok, err := parseBytes(s, options...) if err != nil { return nil, jwterrs.ParseErrorf(`jwt.Parse`, `failed to parse token: %w`, err) } return tok, nil } // ParseInsecure is exactly the same as Parse(), but it disables // signature verification and token validation. // // You cannot override `jwt.WithVerify()` or `jwt.WithValidate()` // using this function. Providing these options would result in // an error func ParseInsecure(s []byte, options ...ParseOption) (Token, error) { for _, option := range options { switch option.Ident() { case identVerify{}, identValidate{}: return nil, jwterrs.ParseErrorf(`jwt.ParseInsecure`, `jwt.WithVerify() and jwt.WithValidate() may not be specified`) } } options = append(options, WithVerify(false), WithValidate(false)) tok, err := Parse(s, options...) if err != nil { return nil, jwterrs.ParseErrorf(`jwt.ParseInsecure`, `failed to parse token: %w`, err) } return tok, nil } // ParseReader calls Parse against an io.Reader func ParseReader(src io.Reader, options ...ParseOption) (Token, error) { // We're going to need the raw bytes regardless. Read it. data, err := io.ReadAll(src) if err != nil { return nil, jwterrs.ParseErrorf(`jwt.ParseReader`, `failed to read from token data source: %w`, err) } tok, err := parseBytes(data, options...) if err != nil { return nil, jwterrs.ParseErrorf(`jwt.ParseReader`, `failed to parse token: %w`, err) } return tok, nil } type parseCtx struct { token Token validateOpts []ValidateOption verifyOpts []jws.VerifyOption localReg *json.Registry pedantic bool skipVerification bool validate bool withKeyCount int withKey *withKey // this is used to detect if we have a WithKey option } func parseBytes(data []byte, options ...ParseOption) (Token, error) { var ctx parseCtx // Validation is turned on by default. You need to specify // jwt.WithValidate(false) if you want to disable it ctx.validate = true // Verification is required (i.e., it is assumed that the incoming // data is in JWS format) unless the user explicitly asks for // it to be skipped. verification := true var verifyOpts []Option for _, o := range options { if v, ok := o.(ValidateOption); ok { ctx.validateOpts = append(ctx.validateOpts, v) // context is used for both verification and validation, so we can't just continue switch o.Ident() { case identContext{}: default: continue } } switch o.Ident() { case identKey{}: // it would be nice to be able to detect if ctx.verifyOpts[0] // is a WithKey option, but unfortunately at that point we have // already converted the options to a jws option, which means // we can no longer compare its Ident() to jwt.identKey{}. // So let's just count this here ctx.withKeyCount++ if ctx.withKeyCount == 1 { if err := o.Value(&ctx.withKey); err != nil { return nil, fmt.Errorf("jws.parseBytes: value for WithKey option must be a *jwt.withKey: %w", err) } } verifyOpts = append(verifyOpts, o) case identKeySet{}, identVerifyAuto{}, identKeyProvider{}, identBase64Encoder{}, identContext{}: verifyOpts = append(verifyOpts, o) case identToken{}: var token Token if err := o.Value(&token); err != nil { return nil, fmt.Errorf("jws.parseBytes: value for WithToken option must be a jwt.Token: %w", err) } ctx.token = token case identPedantic{}: if err := o.Value(&ctx.pedantic); err != nil { return nil, fmt.Errorf("jws.parseBytes: value for WithPedantic option must be a bool: %w", err) } case identValidate{}: if err := o.Value(&ctx.validate); err != nil { return nil, fmt.Errorf("jws.parseBytes: value for WithValidate option must be a bool: %w", err) } case identVerify{}: if err := o.Value(&verification); err != nil { return nil, fmt.Errorf("jws.parseBytes: value for WithVerify option must be a bool: %w", err) } case identTypedClaim{}: var pair claimPair if err := o.Value(&pair); err != nil { return nil, fmt.Errorf("jws.parseBytes: value for WithTypedClaim option must be claimPair: %w", err) } if ctx.localReg == nil { ctx.localReg = json.NewRegistry() } ctx.localReg.Register(pair.Name, pair.Value) } } if !verification { ctx.skipVerification = true } lvo := len(verifyOpts) if lvo == 0 && verification { return nil, fmt.Errorf(`jwt.Parse: no keys for verification are provided (use jwt.WithVerify(false) to explicitly skip)`) } if lvo > 0 { converted, err := toVerifyOptions(verifyOpts...) if err != nil { return nil, fmt.Errorf(`jwt.Parse: failed to convert options into jws.VerifyOption: %w`, err) } ctx.verifyOpts = converted } data = bytes.TrimSpace(data) return parse(&ctx, data) } const ( _JwsVerifyInvalid = iota _JwsVerifyDone _JwsVerifyExpectNested _JwsVerifySkipped ) var _ = _JwsVerifyInvalid func verifyJWS(ctx *parseCtx, payload []byte) ([]byte, int, error) { lvo := len(ctx.verifyOpts) if lvo == 0 { return nil, _JwsVerifySkipped, nil } if lvo == 1 && ctx.withKeyCount == 1 { wk := ctx.withKey alg, ok := wk.alg.(jwa.SignatureAlgorithm) if ok && len(wk.options) == 0 { verified, err := jws.VerifyCompactFast(wk.key, payload, alg) if err != nil { return nil, _JwsVerifyDone, err } return verified, _JwsVerifyDone, nil } } verifyOpts := append(ctx.verifyOpts, jws.WithCompact()) verified, err := jws.Verify(payload, verifyOpts...) return verified, _JwsVerifyDone, err } // verify parameter exists to make sure that we don't accidentally skip // over verification just because alg == "" or key == nil or something. func parse(ctx *parseCtx, data []byte) (Token, error) { payload := data const maxDecodeLevels = 2 // If cty = `JWT`, we expect this to be a nested structure var expectNested bool OUTER: for i := range maxDecodeLevels { switch kind := jwx.GuessFormat(payload); kind { case jwx.JWT: if ctx.pedantic { if expectNested { return nil, fmt.Errorf(`expected nested encrypted/signed payload, got raw JWT`) } } if i == 0 { // We were NOT enveloped in other formats if !ctx.skipVerification { if _, _, err := verifyJWS(ctx, payload); err != nil { return nil, err } } } break OUTER case jwx.InvalidFormat: return nil, UnknownPayloadTypeError() case jwx.UnknownFormat: // "Unknown" may include invalid JWTs, for example, those who lack "aud" // claim. We could be pedantic and reject these if ctx.pedantic { return nil, fmt.Errorf(`unknown JWT format (pedantic)`) } if i == 0 { // We were NOT enveloped in other formats if !ctx.skipVerification { if _, _, err := verifyJWS(ctx, payload); err != nil { return nil, err } } } break OUTER case jwx.JWS: // Food for thought: This is going to break if you have multiple layers of // JWS enveloping using different keys. It is highly unlikely use case, // but it might happen. // skipVerification should only be set to true by us. It's used // when we just want to parse the JWT out of a payload if !ctx.skipVerification { // nested return value means: // false (next envelope _may_ need to be processed) // true (next envelope MUST be processed) v, state, err := verifyJWS(ctx, payload) if err != nil { return nil, err } if state != _JwsVerifySkipped { payload = v // We only check for cty and typ if the pedantic flag is enabled if !ctx.pedantic { continue } if state == _JwsVerifyExpectNested { expectNested = true continue OUTER } // if we're not nested, we found our target. bail out of this loop break OUTER } } // No verification. m, err := jws.Parse(data, jws.WithCompact()) if err != nil { return nil, fmt.Errorf(`invalid jws message: %w`, err) } payload = m.Payload() default: return nil, fmt.Errorf(`unsupported format (layer: #%d)`, i+1) } expectNested = false } if ctx.token == nil { ctx.token = New() } if ctx.localReg != nil { dcToken, ok := ctx.token.(TokenWithDecodeCtx) if !ok { return nil, fmt.Errorf(`typed claim was requested, but the token (%T) does not support DecodeCtx`, ctx.token) } dc := json.NewDecodeCtx(ctx.localReg) dcToken.SetDecodeCtx(dc) defer func() { dcToken.SetDecodeCtx(nil) }() } if err := json.Unmarshal(payload, ctx.token); err != nil { return nil, fmt.Errorf(`failed to parse token: %w`, err) } if ctx.validate { if err := Validate(ctx.token, ctx.validateOpts...); err != nil { return nil, err } } return ctx.token, nil } // Sign is a convenience function to create a signed JWT token serialized in // compact form. // // It accepts either a raw key (e.g. rsa.PrivateKey, ecdsa.PrivateKey, etc) // or a jwk.Key, and the name of the algorithm that should be used to sign // the token. // // For well-known algorithms with no special considerations (e.g. detached // payloads, extra protected heders, etc), this function will automatically // take the fast path and bypass the jws.Sign() machinery, which improves // performance significantly. // // If the key is a jwk.Key and the key contains a key ID (`kid` field), // then it is added to the protected header generated by the signature // // The algorithm specified in the `alg` parameter must be able to support // the type of key you provided, otherwise an error is returned. // For convenience `alg` is of type jwa.KeyAlgorithm so you can pass // the return value of `(jwk.Key).Algorithm()` directly, but in practice // it must be an instance of jwa.SignatureAlgorithm, otherwise an error // is returned. // // The protected header will also automatically have the `typ` field set // to the literal value `JWT`, unless you provide a custom value for it // by jws.WithProtectedHeaders option, that can be passed to `jwt.WithKey“. func Sign(t Token, options ...SignOption) ([]byte, error) { // fast path; can only happen if there is exactly one option if len(options) == 1 && (options[0].Ident() == identKey{}) { // The option must be a withKey option. var wk *withKey if err := options[0].Value(&wk); err == nil { alg, ok := wk.alg.(jwa.SignatureAlgorithm) if !ok { return nil, fmt.Errorf(`jwt.Sign: invalid algorithm type %T. jwa.SignatureAlgorithm is required`, wk.alg) } // Check if option contains anything other than alg/key if len(wk.options) == 0 { // yay, we have something we can put in the FAST PATH! return signFast(t, alg, wk.key) } // fallthrough } // fallthrough } var soptions []jws.SignOption if l := len(options); l > 0 { // we need to from SignOption to Option because ... reasons // (todo: when go1.18 prevails, use type parameters rawoptions := make([]Option, l) for i, option := range options { rawoptions[i] = option } converted, err := toSignOptions(rawoptions...) if err != nil { return nil, fmt.Errorf(`jwt.Sign: failed to convert options into jws.SignOption: %w`, err) } soptions = converted } return NewSerializer().sign(soptions...).Serialize(t) } // Equal compares two JWT tokens. Do not use `reflect.Equal` or the like // to compare tokens as they will also compare extra detail such as // sync.Mutex objects used to control concurrent access. // // The comparison for values is currently done using a simple equality ("=="), // except for time.Time, which uses time.Equal after dropping the monotonic // clock and truncating the values to 1 second accuracy. // // if both t1 and t2 are nil, returns true func Equal(t1, t2 Token) bool { if t1 == nil && t2 == nil { return true } // we already checked for t1 == t2 == nil, so safe to do this if t1 == nil || t2 == nil { return false } j1, err := json.Marshal(t1) if err != nil { return false } j2, err := json.Marshal(t2) if err != nil { return false } return bytes.Equal(j1, j2) } func (t *stdToken) Clone() (Token, error) { dst := New() dst.Options().Set(*(t.Options())) for _, k := range t.Keys() { var v any if err := t.Get(k, &v); err != nil { return nil, fmt.Errorf(`jwt.Clone: failed to get %s: %w`, k, err) } if err := dst.Set(k, v); err != nil { return nil, fmt.Errorf(`jwt.Clone failed to set %s: %w`, k, err) } } return dst, nil } type CustomDecoder = json.CustomDecoder type CustomDecodeFunc = json.CustomDecodeFunc // RegisterCustomField allows users to specify that a private field // be decoded as an instance of the specified type. This option has // a global effect. // // For example, suppose you have a custom field `x-birthday`, which // you want to represent as a string formatted in RFC3339 in JSON, // but want it back as `time.Time`. // // In such case you would register a custom field as follows // // jwt.RegisterCustomField(`x-birthday`, time.Time{}) // // Then you can use a `time.Time` variable to extract the value // of `x-birthday` field, instead of having to use `any` // and later convert it to `time.Time` // // var bday time.Time // _ = token.Get(`x-birthday`, &bday) // // If you need a more fine-tuned control over the decoding process, // you can register a `CustomDecoder`. For example, below shows // how to register a decoder that can parse RFC822 format string: // // jwt.RegisterCustomField(`x-birthday`, jwt.CustomDecodeFunc(func(data []byte) (any, error) { // return time.Parse(time.RFC822, string(data)) // })) // // Please note that use of custom fields can be problematic if you // are using a library that does not implement MarshalJSON/UnmarshalJSON // and you try to roundtrip from an object to JSON, and then back to an object. // For example, in the above example, you can _parse_ time values formatted // in the format specified in RFC822, but when you convert an object into // JSON, it will be formatted in RFC3339, because that's what `time.Time` // likes to do. To avoid this, it's always better to use a custom type // that wraps your desired type (in this case `time.Time`) and implement // MarshalJSON and UnmashalJSON. func RegisterCustomField(name string, object any) { registry.Register(name, object) } func getDefaultTruncation() time.Duration { return time.Duration(defaultTruncation.Load()) } golang-github-lestrrat-go-jwx-3.0.13/jwt/jwt_test.go000066400000000000000000001506141515060566400224110ustar00rootroot00000000000000package jwt_test import ( "bytes" "context" "crypto/ecdsa" "crypto/ed25519" "crypto/rand" "crypto/rsa" "encoding/base64" "errors" "fmt" "net/http" "net/http/httptest" "net/url" "os" "strconv" "strings" "sync" "testing" "time" "github.com/lestrrat-go/httprc/v3" "github.com/lestrrat-go/jwx/v3/internal/json" "github.com/lestrrat-go/jwx/v3/internal/jwxtest" "github.com/lestrrat-go/jwx/v3/internal/tokens" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/lestrrat-go/jwx/v3/jwe" "github.com/lestrrat-go/jwx/v3/jwk" ourecdsa "github.com/lestrrat-go/jwx/v3/jwk/ecdsa" "github.com/lestrrat-go/jwx/v3/jws" "github.com/lestrrat-go/jwx/v3/jwt" "github.com/lestrrat-go/jwx/v3/jwt/internal/types" "github.com/stretchr/testify/require" ) /* This is commented out, because it is intended to cause compilation errors */ /* func TestOption(t *testing.T) { var p jwt.ParseOption var v jwt.ValidateOption var o jwt.Option p = o // should be error v = o // should be error _ = p _ = v } */ func TestToken_Get(t *testing.T) { tok, _ := jwt.NewBuilder(). Issuer("github.com/lestrrat-go/jwx"). IssuedAt(time.Now().Round(0)). Expiration(time.Now().Add(time.Hour * 24)). Build() for _, name := range []string{`aud`, `unknown`} { var v any err := tok.Get(name, v) require.Error(t, err, `tok.Get should fail if value is not set`) require.ErrorIs(t, err, jwt.ClaimNotFoundError(), `tok.Get should return ClaimNotFoundError if value is not set`) } for _, name := range []string{`iss`, `exp`} { var i byte // a type that can't be assigned to from the fields err := tok.Get(name, &i) require.Error(t, err, `tok.Get should fail if value is not assignable`) require.ErrorIs(t, err, jwt.ClaimAssignmentFailedError(), `tok.Get should return ClaimAssignmentFailedError if value is not assignable`) } } func TestToken_Parse(t *testing.T) { t.Parallel() alg := jwa.RS256() key, err := jwxtest.GenerateRsaKey() require.NoError(t, err, `jwxtest.GenerateRsaKey should succeed`) t1 := jwt.New() signed, err := jwt.Sign(t1, jwt.WithKey(alg, key)) require.NoError(t, err, `jwt.Sign should succeed`) t.Logf("%s", signed) t.Run("Parse (no signature verification)", func(t *testing.T) { t.Parallel() t2, err := jwt.ParseInsecure(signed) require.NoError(t, err, `jwt.Parse should succeed`) require.True(t, jwt.Equal(t1, t2), `t1 == t2`) }) t.Run("ParseString (no signature verification)", func(t *testing.T) { t.Parallel() t2, err := jwt.ParseString(string(signed), jwt.WithVerify(false), jwt.WithValidate(false)) require.NoError(t, err, `jwt.ParseString should succeed`) require.True(t, jwt.Equal(t1, t2), `t1 == t2`) }) t.Run("ParseReader (no signature verification)", func(t *testing.T) { t.Parallel() t2, err := jwt.ParseReader(bytes.NewReader(signed), jwt.WithVerify(false), jwt.WithValidate(false)) require.NoError(t, err, `jwt.ParseReader should succeed`) require.True(t, jwt.Equal(t1, t2), `t1 == t2`) }) t.Run("Parse (correct signature key)", func(t *testing.T) { t.Parallel() t2, err := jwt.Parse(signed, jwt.WithKey(alg, &key.PublicKey)) require.NoError(t, err, `jwt.Parse should succeed`) require.True(t, jwt.Equal(t1, t2), `t1 == t2`) }) t.Run("parse (wrong signature algorithm)", func(t *testing.T) { t.Parallel() _, err := jwt.Parse(signed, jwt.WithKey(jwa.RS512(), &key.PublicKey)) require.Error(t, err, `jwt.Parse should fail`) require.True(t, errors.Is(err, jwt.ParseError()), `err should be a parse error`) require.True(t, errors.Is(err, jws.VerifyError()), `err should be a verify error`) require.True(t, errors.Is(err, jws.VerificationError()), `err should be a verification error`) }) t.Run("parse (wrong signature key)", func(t *testing.T) { t.Parallel() pubkey := key.PublicKey pubkey.E = 0 // bogus value _, err := jwt.Parse(signed, jwt.WithKey(alg, &pubkey)) require.Error(t, err, `jwt.Parse should fail`) require.True(t, errors.Is(err, jwt.ParseError()), `err should be a parse error`) require.True(t, errors.Is(err, jws.VerifyError()), `err should be a verify error`) require.True(t, errors.Is(err, jws.VerificationError()), `err should be a verification error`) }) } func TestJWTParseVerify(t *testing.T) { t.Parallel() keys := make([]any, 0, 6) keys = append(keys, []byte("abracadabra")) rsaPrivKey, err := jwxtest.GenerateRsaKey() require.NoError(t, err, "RSA key generated") keys = append(keys, rsaPrivKey) for _, alg := range []jwa.EllipticCurveAlgorithm{jwa.P256(), jwa.P384(), jwa.P521()} { ecdsaPrivKey, err := jwxtest.GenerateEcdsaKey(alg) require.NoError(t, err, "jwxtest.GenerateEcdsaKey should succeed for %s", alg) keys = append(keys, ecdsaPrivKey) } ed25519PrivKey, err := jwxtest.GenerateEd25519Key() require.NoError(t, err, `jwxtest.GenerateEd25519Key should succeed`) keys = append(keys, ed25519PrivKey) for _, key := range keys { t.Run(fmt.Sprintf("Key=%T", key), func(t *testing.T) { t.Parallel() algs, err := jws.AlgorithmsForKey(key) require.NoError(t, err, `jwas.AlgorithmsForKey should succeed`) var dummyRawKey any switch pk := key.(type) { case *rsa.PrivateKey: dummyRawKey, err = jwxtest.GenerateRsaKey() require.NoError(t, err, `jwxtest.GenerateRsaKey should succeed`) case *ecdsa.PrivateKey: alg, err := ourecdsa.AlgorithmFromCurve(pk.Curve) if err != nil { require.Fail(t, `unsupported elliptic.Curve: %w`, alg) } dummyRawKey, err = jwxtest.GenerateEcdsaKey(alg) require.NoError(t, err, `jwxtest.GenerateEcdsaKey should succeed`) case ed25519.PrivateKey: dummyRawKey, err = jwxtest.GenerateEd25519Key() require.NoError(t, err, `jwxtest.GenerateEd25519Key should succeed`) case []byte: dummyRawKey = jwxtest.GenerateSymmetricKey() default: require.Fail(t, fmt.Sprintf("Unhandled key type %T", key)) } testcases := []struct { SetAlgorithm bool SetKid bool InferAlgorithm bool Error bool }{ { SetAlgorithm: true, SetKid: true, InferAlgorithm: true, }, { SetAlgorithm: true, SetKid: true, InferAlgorithm: false, }, { SetAlgorithm: true, SetKid: false, InferAlgorithm: true, Error: true, }, { SetAlgorithm: false, SetKid: true, InferAlgorithm: true, }, { SetAlgorithm: false, SetKid: true, InferAlgorithm: false, Error: true, }, { SetAlgorithm: false, SetKid: false, InferAlgorithm: true, Error: true, }, { SetAlgorithm: true, SetKid: false, InferAlgorithm: false, Error: true, }, { SetAlgorithm: false, SetKid: false, InferAlgorithm: false, Error: true, }, } for _, alg := range algs { for _, tc := range testcases { t.Run(fmt.Sprintf("Algorithm=%s, SetAlgorithm=%t, SetKid=%t, InferAlgorithm=%t, Expect Error=%t", alg, tc.SetAlgorithm, tc.SetKid, tc.InferAlgorithm, tc.Error), func(t *testing.T) { t.Parallel() const kid = "test-jwt-parse-verify-kid" const dummyKid = "test-jwt-parse-verify-dummy-kid" hdrs := jws.NewHeaders() hdrs.Set(jws.KeyIDKey, kid) t1 := jwt.New() signed, err := jwt.Sign(t1, jwt.WithKey(alg, key, jws.WithProtectedHeaders(hdrs))) require.NoError(t, err, "token.Sign should succeed") pubkey, err := jwk.PublicKeyOf(key) require.NoError(t, err, `jwk.PublicKeyOf should succeed`) if tc.SetAlgorithm { pubkey.Set(jwk.AlgorithmKey, alg) } dummyKey, err := jwk.PublicKeyOf(dummyRawKey) require.NoError(t, err, `jwk.PublicKeyOf should succeed`) if tc.SetKid { pubkey.Set(jwk.KeyIDKey, kid) dummyKey.Set(jwk.KeyIDKey, dummyKid) } // Permute on the location of the correct key, to check for possible // cases where we loop too little or too much. for i := range 6 { var name string set := jwk.NewSet() switch i { case 0: name = "Lone key" set.AddKey(pubkey) case 1: name = "Two keys, correct one at the end" set.AddKey(dummyKey) set.AddKey(pubkey) case 2: name = "Two keys, correct one at the beginning" set.AddKey(pubkey) set.AddKey(dummyKey) case 3: name = "Three keys, correct one at the end" set.AddKey(dummyKey) set.AddKey(dummyKey) set.AddKey(pubkey) case 4: name = "Three keys, correct one at the middle" set.AddKey(dummyKey) set.AddKey(pubkey) set.AddKey(dummyKey) case 5: name = "Three keys, correct one at the beginning" set.AddKey(pubkey) set.AddKey(dummyKey) set.AddKey(dummyKey) } t.Run(name, func(t *testing.T) { options := []jwt.ParseOption{ jwt.WithKeySet(set, jws.WithInferAlgorithmFromKey(tc.InferAlgorithm)), } t2, err := jwt.Parse(signed, options...) if tc.Error { require.Error(t, err, `jwt.Parse should fail`) return } require.NoError(t, err, `jwt.Parse should succeed`) require.True(t, jwt.Equal(t1, t2), `t1 == t2`) }) } }) } } }) } t.Run("Miscellaneous", func(t *testing.T) { key, err := jwxtest.GenerateRsaKey() require.NoError(t, err, "RSA key generated") var alg = jwa.RS256() const kid = "my-very-special-key" hdrs := jws.NewHeaders() hdrs.Set(jws.KeyIDKey, kid) t1 := jwt.New() signed, err := jwt.Sign(t1, jwt.WithKey(alg, key, jws.WithProtectedHeaders(hdrs))) require.NoError(t, err, "token.Sign should succeed") t.Run("Alg does not match", func(t *testing.T) { t.Parallel() pubkey, err := jwk.PublicKeyOf(key) require.NoError(t, err) require.NoError(t, pubkey.Set(jwk.AlgorithmKey, jwa.HS256()), `pubkey.Set should succeed`) require.NoError(t, pubkey.Set(jwk.KeyIDKey, kid), `pubkey.Set should succeed`) set := jwk.NewSet() set.AddKey(pubkey) _, err = jwt.Parse(signed, jwt.WithKeySet(set, jws.WithInferAlgorithmFromKey(true), jws.WithUseDefault(true))) require.Error(t, err, `jwt.Parse should fail`) }) t.Run("UseDefault with a key set with 1 key", func(t *testing.T) { t.Parallel() pubkey, err := jwk.PublicKeyOf(key) require.NoError(t, err) pubkey.Set(jwk.AlgorithmKey, alg) pubkey.Set(jwk.KeyIDKey, kid) signedNoKid, err := jwt.Sign(t1, jwt.WithKey(alg, key)) if err != nil { t.Fatal("Failed to sign JWT") } set := jwk.NewSet() set.AddKey(pubkey) t2, err := jwt.Parse(signedNoKid, jwt.WithKeySet(set, jws.WithUseDefault(true))) require.NoError(t, err, `jwt.Parse with key set should succeed`) require.True(t, jwt.Equal(t1, t2), `t1 == t2`) }) t.Run("UseDefault with multiple keys should fail", func(t *testing.T) { t.Parallel() pubkey1, err := jwk.Import(&key.PublicKey) require.NoError(t, err) pubkey2, err := jwk.Import(&key.PublicKey) require.NoError(t, err) pubkey1.Set(jwk.KeyIDKey, kid) pubkey2.Set(jwk.KeyIDKey, "test-jwt-parse-verify-kid-2") signedNoKid, err := jwt.Sign(t1, jwt.WithKey(alg, key)) if err != nil { t.Fatal("Failed to sign JWT") } set := jwk.NewSet() set.AddKey(pubkey1) set.AddKey(pubkey2) _, err = jwt.Parse(signedNoKid, jwt.WithKeySet(set, jws.WithUseDefault(true))) require.Error(t, err, `jwt.Parse should fail`) }) // This is a test to check if we allow alg: none in the protected header section. // But in truth, since we delegate everything to jws.Verify anyways, it's really // a test to see if jws.Verify returns an error if alg: none is specified in the // header section. Move this test to jws if need be. t.Run("Check alg=none", func(t *testing.T) { t.Parallel() // Create a signed payload, but use alg=none _, payload, signature, err := jws.SplitCompact(signed) require.NoError(t, err, `jws.SplitCompact should succeed`) dummyHeader := jws.NewHeaders() for _, k := range hdrs.Keys() { var v any require.NoError(t, hdrs.Get(k, &v), `hdrs.Get should succeed`) require.NoError(t, dummyHeader.Set(k, v), `dummyHeader.Set should succeed`) } dummyHeader.Set(jws.AlgorithmKey, jwa.NoSignature) dummyMarshaled, err := json.Marshal(dummyHeader) require.NoError(t, err, `json.Marshal should succeed`) dummyEncoded := make([]byte, base64.RawURLEncoding.EncodedLen(len(dummyMarshaled))) base64.RawURLEncoding.Encode(dummyEncoded, dummyMarshaled) signedButNot := bytes.Join([][]byte{dummyEncoded, payload, signature}, []byte{tokens.Period}) pubkey, err := jwk.Import(&key.PublicKey) require.NoError(t, err) pubkey.Set(jwk.KeyIDKey, kid) set := jwk.NewSet() set.AddKey(pubkey) _, err = jwt.Parse(signedButNot, jwt.WithKeySet(set)) // This should fail require.Error(t, err, `jwt.Parse with key set + alg=none should fail`) }) }) } func TestValidateClaims(t *testing.T) { t.Parallel() // GitHub issue #37: tokens are invalid in the second they are created (because Now() is not after IssuedAt()) t.Run("Empty fields", func(t *testing.T) { t.Parallel() token := jwt.New() require.Error(t, jwt.Validate(token, jwt.WithIssuer("foo")), `token.Validate should fail`) require.Error(t, jwt.Validate(token, jwt.WithJwtID("foo")), `token.Validate should fail`) require.Error(t, jwt.Validate(token, jwt.WithSubject("foo")), `token.Validate should fail`) }) t.Run("Reset Validator, No validator", func(t *testing.T) { t.Parallel() token := jwt.New() now := time.Now().UTC() token.Set(jwt.IssuedAtKey, now) err := jwt.Validate(token, jwt.WithResetValidators(true)) require.Error(t, err, `token.Validate should fail`) require.Contains(t, err.Error(), "no validators specified", `error message should contain "no validators specified"`) }) t.Run("Reset Validator, Check iss only", func(t *testing.T) { t.Parallel() token := jwt.New() iat := time.Now().UTC().Add(time.Hour * 24) token.Set(jwt.IssuedAtKey, iat) token.Set(jwt.IssuerKey, "github.com/lestrrat-go") err := jwt.Validate(token, jwt.WithResetValidators(true), jwt.WithIssuer("github.com/lestrrat-go")) require.NoError(t, err, `token.Validate should succeed`) }) t.Run(jwt.IssuedAtKey+"+skew", func(t *testing.T) { t.Parallel() token := jwt.New() now := time.Now().UTC() token.Set(jwt.IssuedAtKey, now) const DefaultSkew = 0 args := []jwt.ValidateOption{ jwt.WithClock(jwt.ClockFunc(func() time.Time { return now })), jwt.WithAcceptableSkew(DefaultSkew), } require.NoError(t, jwt.Validate(token, args...), "token.Validate should validate tokens in the same second they are created") }) } const aLongLongTimeAgo = 233431200 const aLongLongTimeAgoString = "233431200" func TestUnmarshal(t *testing.T) { t.Parallel() testcases := []struct { Title string Source string Expected func() jwt.Token ExpectedJSON string }{ { Title: "single aud", Source: `{"aud":"foo"}`, Expected: func() jwt.Token { t := jwt.New() t.Set("aud", "foo") return t }, ExpectedJSON: `{"aud":["foo"]}`, }, { Title: "multiple aud's", Source: `{"aud":["foo","bar"]}`, Expected: func() jwt.Token { t := jwt.New() t.Set("aud", []string{"foo", "bar"}) return t }, ExpectedJSON: `{"aud":["foo","bar"]}`, }, { Title: "issuedAt", Source: `{"` + jwt.IssuedAtKey + `":` + aLongLongTimeAgoString + `}`, Expected: func() jwt.Token { t := jwt.New() t.Set(jwt.IssuedAtKey, aLongLongTimeAgo) return t }, ExpectedJSON: `{"` + jwt.IssuedAtKey + `":` + aLongLongTimeAgoString + `}`, }, } for _, tc := range testcases { t.Run(tc.Title, func(t *testing.T) { t.Parallel() token := jwt.New() require.NoError(t, json.Unmarshal([]byte(tc.Source), &token), `json.Unmarshal should succeed`) require.Equal(t, tc.Expected(), token, `token should match expected value`) var buf bytes.Buffer require.NoError(t, json.NewEncoder(&buf).Encode(token), `json.Marshal should succeed`) require.Equal(t, tc.ExpectedJSON, strings.TrimSpace(buf.String()), `json should match`) }) } } func TestGH52(t *testing.T) { if testing.Short() { t.SkipNow() } t.Parallel() priv, err := jwxtest.GenerateEcdsaKey(jwa.P521()) require.NoError(t, err) pub := &priv.PublicKey require.NoError(t, err) const iterations = 100 var wg sync.WaitGroup wg.Add(iterations) for i := range iterations { // Do not use t.Run here as it will clutter up the outpuA go func(t *testing.T, priv *ecdsa.PrivateKey, i int) { defer wg.Done() tok := jwt.New() s, err := jwt.Sign(tok, jwt.WithKey(jwa.ES256(), priv)) require.NoError(t, err) _, err = jws.Verify(s, jws.WithKey(jwa.ES256(), pub)) require.NoError(t, err, `test should pass (run %d)`, i) }(t, priv, i) } wg.Wait() } func TestUnmarshalJSON(t *testing.T) { t.Parallel() t.Run("Unmarshal audience with multiple values", func(t *testing.T) { t.Parallel() t1 := jwt.New() require.NoError(t, json.Unmarshal([]byte(`{"aud":["foo", "bar", "baz"]}`), &t1), `jwt.Parse should succeed`) var aud []string require.NoError(t, t1.Get(jwt.AudienceKey, &aud), `jwt.Get(jwt.AudienceKey) should succeed`) require.Equal(t, aud, []string{"foo", "bar", "baz"}, "audience should match. got %v", aud) }) } func TestSignErrors(t *testing.T) { t.Parallel() priv, err := jwxtest.GenerateEcdsaKey(jwa.P521()) require.NoError(t, err, `jwxtest.GenerateEcdsaKey should succeed`) tok := jwt.New() _, err = jwt.Sign(tok, jwt.WithKey(jwa.NewSignatureAlgorithm("BOGUS"), priv)) require.Error(t, err) require.Contains(t, err.Error(), `unsupported signature algorithm "BOGUS"`) _, err = jwt.Sign(tok, jwt.WithKey(jwa.ES256(), nil)) require.Error(t, err) } func TestSignJWK(t *testing.T) { t.Parallel() priv, err := jwxtest.GenerateRsaKey() require.Nil(t, err) key, err := jwk.Import(priv) require.Nil(t, err) require.NoError(t, key.Set(jwk.KeyIDKey, "test"), `key.Set should succeed`) require.NoError(t, key.Set(jwk.AlgorithmKey, jwa.RS256()), `key.Set should succeed`) tok := jwt.New() alg, ok := key.Algorithm() require.True(t, ok, `key.Algorithm should succeed`) signed, err := jwt.Sign(tok, jwt.WithKey(alg, key)) require.Nil(t, err) header, err := jws.ParseString(string(signed)) require.Nil(t, err) signatures := header.LookupSignature("test") require.Len(t, signatures, 1) } func getJWTHeaders(jwt []byte) (jws.Headers, error) { msg, err := jws.Parse(jwt) if err != nil { return nil, err } return msg.Signatures()[0].ProtectedHeaders(), nil } func TestSignTyp(t *testing.T) { t.Parallel() key, err := jwxtest.GenerateRsaKey() require.NoError(t, err) t.Run(`"typ" header parameter should be set to JWT by default`, func(t *testing.T) { t.Parallel() t1 := jwt.New() signed, err := jwt.Sign(t1, jwt.WithKey(jwa.RS256(), key)) require.NoError(t, err) got, err := getJWTHeaders(signed) require.NoError(t, err) v, ok := got.Type() require.True(t, ok, `"typ" header parameter should be set`) require.Equal(t, `JWT`, v, `"typ" header parameter should be set to JWT`) }) t.Run(`"typ" header parameter should be customizable by WithHeaders`, func(t *testing.T) { t.Parallel() t1 := jwt.New() hdrs := jws.NewHeaders() hdrs.Set(`typ`, `custom-typ`) signed, err := jwt.Sign(t1, jwt.WithKey(jwa.RS256(), key, jws.WithProtectedHeaders(hdrs))) require.NoError(t, err) got, err := getJWTHeaders(signed) require.NoError(t, err) v, ok := got.Type() require.True(t, ok, `"typ" header parameter should be set`) require.Equal(t, `custom-typ`, v, `"typ" header parameter should be set to the custom value`) }) } func TestReadFile(t *testing.T) { t.Parallel() f, err := os.CreateTemp(t.TempDir(), "test-read-file-*.jwt") require.NoError(t, err, `os.CreateTemp should succeed`) defer f.Close() token := jwt.New() token.Set(jwt.IssuerKey, `lestrrat`) require.NoError(t, json.NewEncoder(f).Encode(token), `json.NewEncoder.Encode should succeed`) _, err = jwt.ReadFile(f.Name(), jwt.WithVerify(false), jwt.WithValidate(true), jwt.WithIssuer("lestrrat")) require.NoError(t, err, `jwt.ReadFile should succeed`) _, err = jwt.ReadFile(f.Name(), jwt.WithVerify(false), jwt.WithValidate(true), jwt.WithIssuer("lestrrrrrat")) require.Error(t, err, `jwt.ReadFile should fail`) } func TestCustomField(t *testing.T) { // XXX has global effect!!! const rfc3339Key = `x-test-rfc3339` const rfc1123Key = `x-test-rfc1123` jwt.RegisterCustomField(rfc3339Key, time.Time{}) jwt.RegisterCustomField(rfc1123Key, jwt.CustomDecodeFunc(func(data []byte) (any, error) { var s string if err := json.Unmarshal(data, &s); err != nil { return nil, err } return time.Parse(time.RFC1123, s) })) defer jwt.RegisterCustomField(rfc3339Key, nil) defer jwt.RegisterCustomField(rfc1123Key, nil) expected := time.Date(2015, 11, 4, 5, 12, 52, 0, time.UTC) rfc3339bytes, _ := expected.MarshalText() // RFC3339 rfc1123bytes := expected.Format(time.RFC1123) var b strings.Builder b.WriteString(`{"iss": "github.com/lesstrrat-go/jwx", "`) b.WriteString(rfc3339Key) b.WriteString(`": "`) b.Write(rfc3339bytes) b.WriteString(`", "`) b.WriteString(rfc1123Key) b.WriteString(`": "`) b.WriteString(rfc1123bytes) b.WriteString(`"}`) src := b.String() t.Run("jwt.Parse", func(t *testing.T) { token, err := jwt.ParseInsecure([]byte(src)) require.NoError(t, err, `jwt.Parse should succeed`) for _, key := range []string{rfc3339Key, rfc1123Key} { var v time.Time require.NoError(t, token.Get(key, &v), `token.Get(%q) should succeed`, key) require.Equal(t, expected, v, `values should match`) } }) t.Run("json.Unmarshal", func(t *testing.T) { token := jwt.New() require.NoError(t, json.Unmarshal([]byte(src), token), `json.Unmarshal should succeed`) for _, key := range []string{rfc3339Key, rfc1123Key} { var v time.Time require.NoError(t, token.Get(key, &v), `token.Get(%q) should succeed`, key) require.Equal(t, expected, v, `values should match`) } }) } func TestParseRequest(t *testing.T) { const u = "https://github.com/lestrrat-gow/jwx/jwt" const xauth = "X-Authorization" privkey, _ := jwxtest.GenerateEcdsaJwk() require.NoError(t, privkey.Set(jwk.AlgorithmKey, jwa.ES256()), `privkey.Set should succeed`) require.NoError(t, privkey.Set(jwk.KeyIDKey, `my-awesome-key`), `privkey.Set should succeed`) pubkey, err := jwk.PublicKeyOf(privkey) require.NoError(t, err, `jwk.PublicKeyOf should succeed`) require.NoError(t, pubkey.Set(jwk.AlgorithmKey, jwa.ES256()), `pubkey.Set should succeed`) tok := jwt.New() tok.Set(jwt.IssuerKey, u) tok.Set(jwt.IssuedAtKey, time.Now().Round(0)) signed, _ := jwt.Sign(tok, jwt.WithKey(jwa.ES256(), privkey)) testcases := []struct { Request func() *http.Request Parse func(*http.Request) (jwt.Token, error) Name string Error bool }{ { Name: "Token not present (w/ multiple options)", Request: func() *http.Request { return httptest.NewRequest(http.MethodGet, u, nil) }, Parse: func(req *http.Request) (jwt.Token, error) { return jwt.ParseRequest(req, jwt.WithHeaderKey("Authorization"), jwt.WithHeaderKey(xauth), jwt.WithFormKey("access_token"), jwt.WithFormKey("token"), jwt.WithCookieKey("cookie"), jwt.WithKey(jwa.ES256(), pubkey)) }, Error: true, }, { Name: "Token not present (w/o options)", Request: func() *http.Request { return httptest.NewRequest(http.MethodGet, u, nil) }, Parse: func(req *http.Request) (jwt.Token, error) { return jwt.ParseRequest(req, jwt.WithKey(jwa.ES256(), pubkey)) }, Error: true, }, { Name: "Token in Authorization header (w/o extra options)", Request: func() *http.Request { req := httptest.NewRequest(http.MethodGet, u, nil) req.Header.Add("Authorization", "Bearer "+string(signed)) return req }, Parse: func(req *http.Request) (jwt.Token, error) { return jwt.ParseRequest(req, jwt.WithKey(jwa.ES256(), pubkey)) }, }, { Name: "Token in Authorization header (w/o extra options, using jwk.Set)", Request: func() *http.Request { req := httptest.NewRequest(http.MethodGet, u, nil) req.Header.Add("Authorization", "Bearer "+string(signed)) return req }, Parse: func(req *http.Request) (jwt.Token, error) { set := jwk.NewSet() require.NoError(t, set.AddKey(pubkey), `set.AddKey should succeed`) return jwt.ParseRequest(req, jwt.WithKeySet(set)) }, }, { Name: "Token in Authorization header but we specified another header key", Request: func() *http.Request { req := httptest.NewRequest(http.MethodGet, u, nil) req.Header.Add("Authorization", "Bearer "+string(signed)) return req }, Parse: func(req *http.Request) (jwt.Token, error) { return jwt.ParseRequest(req, jwt.WithHeaderKey(xauth), jwt.WithKey(jwa.ES256(), pubkey)) }, Error: true, }, { Name: fmt.Sprintf("Token in %s header (w/ option)", xauth), Request: func() *http.Request { req := httptest.NewRequest(http.MethodGet, u, nil) req.Header.Add(xauth, string(signed)) return req }, Parse: func(req *http.Request) (jwt.Token, error) { return jwt.ParseRequest(req, jwt.WithHeaderKey(xauth), jwt.WithKey(jwa.ES256(), pubkey)) }, }, { Name: fmt.Sprintf("Invalid token in %s header", xauth), Request: func() *http.Request { req := httptest.NewRequest(http.MethodGet, u, nil) req.Header.Add(xauth, string(signed)+"foobarbaz") return req }, Parse: func(req *http.Request) (jwt.Token, error) { return jwt.ParseRequest(req, jwt.WithHeaderKey(xauth), jwt.WithKey(jwa.ES256(), pubkey)) }, Error: true, }, { Name: "Token in access_token form field (w/ option)", Request: func() *http.Request { req := httptest.NewRequest(http.MethodPost, u, nil) // for whatever reason, I can't populate req.Body and get this to work // so populating req.Form directly instead req.Form = url.Values{} req.Form.Add("access_token", string(signed)) return req }, Parse: func(req *http.Request) (jwt.Token, error) { return jwt.ParseRequest(req, jwt.WithFormKey("access_token"), jwt.WithKey(jwa.ES256(), pubkey)) }, }, { Name: "Token in cookie (w/ option)", Request: func() *http.Request { req := httptest.NewRequest(http.MethodGet, u, nil) req.AddCookie(&http.Cookie{Name: "cookie", Value: string(signed)}) return req }, Parse: func(req *http.Request) (jwt.Token, error) { return jwt.ParseRequest(req, jwt.WithCookieKey("cookie"), jwt.WithKey(jwa.ES256(), pubkey)) }, }, { Name: "Invalid token in cookie", Request: func() *http.Request { req := httptest.NewRequest(http.MethodGet, u, nil) req.AddCookie(&http.Cookie{Name: "cookie", Value: string(signed) + "foobarbaz"}) return req }, Parse: func(req *http.Request) (jwt.Token, error) { return jwt.ParseRequest(req, jwt.WithCookieKey("cookie"), jwt.WithKey(jwa.ES256(), pubkey)) }, Error: true, }, { Name: "Token in access_token form field (w/o option)", Request: func() *http.Request { req := httptest.NewRequest(http.MethodPost, u, nil) // for whatever reason, I can't populate req.Body and get this to work // so populating req.Form directly instead req.Form = url.Values{} req.Form.Add("access_token", string(signed)) return req }, Parse: func(req *http.Request) (jwt.Token, error) { return jwt.ParseRequest(req, jwt.WithKey(jwa.ES256(), pubkey)) }, Error: true, }, { Name: "Invalid token in access_token form field", Request: func() *http.Request { req := httptest.NewRequest(http.MethodPost, u, nil) // for whatever reason, I can't populate req.Body and get this to work // so populating req.Form directly instead req.Form = url.Values{} req.Form.Add("access_token", string(signed)+"foobarbarz") return req }, Parse: func(req *http.Request) (jwt.Token, error) { return jwt.ParseRequest(req, jwt.WithKey(jwa.ES256(), pubkey), jwt.WithFormKey("access_token")) }, Error: true, }, } for _, tc := range testcases { t.Run(tc.Name, func(t *testing.T) { got, err := tc.Parse(tc.Request()) if tc.Error { t.Logf("%s", err) require.Error(t, err, `tc.Parse should fail`) return } require.NoError(t, err, `tc.Parse should succeed`) require.True(t, jwt.Equal(tok, got), `tokens should match`) }) } // One extra test. Make sure we can extract the cookie object that we used // when parsing from cookies t.Run("jwt.WithCookie", func(t *testing.T) { req := httptest.NewRequest(http.MethodGet, u, nil) req.AddCookie(&http.Cookie{Name: "cookie", Value: string(signed)}) var dst *http.Cookie _, err := jwt.ParseRequest(req, jwt.WithCookieKey("cookie"), jwt.WithCookie(&dst), jwt.WithKey(jwa.ES256(), pubkey)) require.NoError(t, err, `jwt.ParseRequest should succeed`) require.NotNil(t, dst, `cookie should be extracted`) }) } func TestGHIssue368(t *testing.T) { // DO NOT RUN THIS IN PARALLEL t.Run("Per-object control of flatten audience", func(t *testing.T) { for _, globalFlatten := range []bool{true, false} { for _, perObjectFlatten := range []bool{true, false} { // per-object settings always wins t.Run(fmt.Sprintf("Global=%t, Per-Object=%t", globalFlatten, perObjectFlatten), func(t *testing.T) { defer jwt.Settings(jwt.WithFlattenAudience(false)) jwt.Settings(jwt.WithFlattenAudience(globalFlatten)) tok, _ := jwt.NewBuilder(). Audience([]string{"hello"}). Build() if perObjectFlatten { tok.Options().Enable(jwt.FlattenAudience) } else { tok.Options().Disable(jwt.FlattenAudience) } buf, err := json.MarshalIndent(tok, "", " ") require.NoError(t, err, `json.MarshalIndent should succeed`) var expected string if perObjectFlatten { expected = `{ "aud": "hello" }` } else { expected = `{ "aud": [ "hello" ] }` } require.Equal(t, expected, string(buf), `output should match`) }) } } }) for _, flatten := range []bool{true, false} { t.Run(fmt.Sprintf("Test serialization (WithFlattenAudience(%t))", flatten), func(t *testing.T) { jwt.Settings(jwt.WithFlattenAudience(flatten)) t.Run("Single Key", func(t *testing.T) { tok := jwt.New() _ = tok.Set(jwt.AudienceKey, "hello") buf, err := json.MarshalIndent(tok, "", " ") require.NoError(t, err, `json.MarshalIndent should succeed`) var expected string if flatten { expected = `{ "aud": "hello" }` } else { expected = `{ "aud": [ "hello" ] }` } require.Equal(t, expected, string(buf), `output should match`) }) t.Run("Multiple Keys", func(t *testing.T) { tok, err := jwt.NewBuilder(). Audience([]string{"hello", "world"}). Build() require.NoError(t, err, `jwt.Builder should succeed`) buf, err := json.MarshalIndent(tok, "", " ") require.NoError(t, err, `json.MarshalIndent should succeed`) const expected = `{ "aud": [ "hello", "world" ] }` require.Equal(t, expected, string(buf), `output should match`) }) }) } } func TestGH375(t *testing.T) { key, err := jwxtest.GenerateRsaJwk() require.NoError(t, err, `jwxtest.GenerateRsaJwk should succeed`) key.Set(jwk.KeyIDKey, `test`) token, err := jwt.NewBuilder(). Issuer(`foobar`). Build() require.NoError(t, err, `jwt.Builder should succeed`) signAlg := jwa.RS512() signed, err := jwt.Sign(token, jwt.WithKey(signAlg, key)) require.NoError(t, err, `jwt.Sign should succeed`) verifyKey, err := jwk.PublicKeyOf(key) require.NoError(t, err, `jwk.PublicKeyOf should succeed`) verifyKey.Set(jwk.KeyIDKey, `test`) verifyKey.Set(jwk.AlgorithmKey, jwa.RS256) // != jwa.RS512 ks := jwk.NewSet() ks.AddKey(verifyKey) _, err = jwt.Parse(signed, jwt.WithKeySet(ks)) require.Error(t, err, `jwt.Parse should fail`) } type Claim struct { Foo string Bar int64 } func TestJWTParseWithTypedClaim(t *testing.T) { testcases := []struct { Name string Options []jwt.ParseOption PostProcess func(*testing.T, any) (*Claim, error) }{ { Name: "Basic", Options: []jwt.ParseOption{jwt.WithTypedClaim("typed-claim", Claim{})}, PostProcess: func(t *testing.T, claim any) (*Claim, error) { t.Helper() v, ok := claim.(Claim) if !ok { return nil, fmt.Errorf(`claim value should be of type "Claim", but got %T`, claim) } return &v, nil }, }, { Name: "json.RawMessage", Options: []jwt.ParseOption{jwt.WithTypedClaim("typed-claim", json.RawMessage{})}, PostProcess: func(t *testing.T, claim any) (*Claim, error) { t.Helper() v, ok := claim.(json.RawMessage) if !ok { return nil, fmt.Errorf(`claim value should be of type "json.RawMessage", but got %T`, claim) } var c Claim if err := json.Unmarshal(v, &c); err != nil { return nil, fmt.Errorf(`json.Unmarshal failed: %w`, err) } return &c, nil }, }, } expected := &Claim{Foo: "Foo", Bar: 0xdeadbeef} key, err := jwxtest.GenerateRsaKey() require.NoError(t, err, `jwxtest.GenerateRsaKey should succeed`) var signed []byte { token := jwt.New() require.NoError(t, token.Set("typed-claim", expected), `expected.Set should succeed`) v, err := jwt.Sign(token, jwt.WithKey(jwa.RS256(), key)) require.NoError(t, err, `jwt.Sign should succeed`) signed = v } for _, tc := range testcases { t.Run(tc.Name, func(t *testing.T) { options := append(tc.Options, jwt.WithVerify(false)) got, err := jwt.Parse(signed, options...) require.NoError(t, err, `jwt.Parse should succeed`) var v any require.NoError(t, got.Get("typed-claim", &v), `got.Get() should succeed`) claim, err := tc.PostProcess(t, v) require.NoError(t, err, `tc.PostProcess should succeed`) require.Equal(t, claim, expected, `claim should match expected value`) }) } } func TestGH393(t *testing.T) { t.Run("Non-existent required claims", func(t *testing.T) { tok := jwt.New() require.Error(t, jwt.Validate(tok, jwt.WithRequiredClaim(jwt.IssuedAtKey)), `jwt.Validate should fail`) }) t.Run("exp - iat < WithMaxDelta(10 secs)", func(t *testing.T) { now := time.Now() tok, err := jwt.NewBuilder(). IssuedAt(now). Expiration(now.Add(5 * time.Second)). Build() require.NoError(t, err, `jwt.Builder should succeed`) require.Error(t, jwt.Validate(tok, jwt.WithMaxDelta(2*time.Second, jwt.ExpirationKey, jwt.IssuedAtKey)), `jwt.Validate should fail`) require.NoError(t, jwt.Validate(tok, jwt.WithMaxDelta(10*time.Second, jwt.ExpirationKey, jwt.IssuedAtKey)), `jwt.Validate should succeed`) }) t.Run("iat - exp (5 secs) < WithMinDelta(10 secs)", func(t *testing.T) { now := time.Now() tok, err := jwt.NewBuilder(). IssuedAt(now). Expiration(now.Add(5 * time.Second)). Build() require.NoError(t, err, `jwt.Builder should succeed`) require.Error(t, jwt.Validate(tok, jwt.WithMinDelta(10*time.Second, jwt.ExpirationKey, jwt.IssuedAtKey)), `jwt.Validate should fail`) }) t.Run("iat - exp (5 secs) > WithMinDelta(10 secs)", func(t *testing.T) { now := time.Now() tok, err := jwt.NewBuilder(). IssuedAt(now). Expiration(now.Add(5 * time.Second)). Build() require.NoError(t, err, `jwt.Builder should succeed`) require.NoError(t, jwt.Validate(tok, jwt.WithMinDelta(10*time.Second, jwt.ExpirationKey, jwt.IssuedAtKey), jwt.WithAcceptableSkew(5*time.Second)), `jwt.Validate should succeed`) }) t.Run("now - iat < WithMaxDelta(10 secs)", func(t *testing.T) { now := time.Now() tok, err := jwt.NewBuilder(). IssuedAt(now). Build() require.NoError(t, err, `jwt.Builder should succeed`) require.NoError(t, jwt.Validate(tok, jwt.WithMaxDelta(10*time.Second, "", jwt.IssuedAtKey), jwt.WithClock(jwt.ClockFunc(func() time.Time { return now.Add(5 * time.Second) }))), `jwt.Validate should succeed`) }) t.Run("invalid claim name (c1)", func(t *testing.T) { now := time.Now() tok, err := jwt.NewBuilder(). Claim("foo", now). Expiration(now.Add(5 * time.Second)). Build() require.NoError(t, err, `jwt.Builder should succeed`) require.Error(t, jwt.Validate(tok, jwt.WithMinDelta(10*time.Second, jwt.ExpirationKey, "foo"), jwt.WithAcceptableSkew(5*time.Second)), `jwt.Validate should fail`) }) t.Run("invalid claim name (c2)", func(t *testing.T) { now := time.Now() tok, err := jwt.NewBuilder(). Claim("foo", now.Add(5*time.Second)). IssuedAt(now). Build() require.NoError(t, err, `jwt.Builder should succeed`) require.Error(t, jwt.Validate(tok, jwt.WithMinDelta(10*time.Second, "foo", jwt.IssuedAtKey), jwt.WithAcceptableSkew(5*time.Second)), `jwt.Validate should fail`) }) // Following tests deviate a little from the original issue, but // since they were added for the same issue, we just bundle the // tests together t.Run(`WithRequiredClaim fails for non-existent claim`, func(t *testing.T) { tok := jwt.New() require.Error(t, jwt.Validate(tok, jwt.WithRequiredClaim("foo")), `jwt.Validate should fail`) }) t.Run(`WithRequiredClaim succeeds for existing claim`, func(t *testing.T) { tok, err := jwt.NewBuilder(). Claim(`foo`, 1). Build() require.NoError(t, err, `jwt.Builder should succeed`) require.NoError(t, jwt.Validate(tok, jwt.WithRequiredClaim("foo")), `jwt.Validate should fail`) }) } func TestGH430(t *testing.T) { t1 := jwt.New() err := t1.Set("payload", map[string]any{ "name": "someone", }) require.NoError(t, err, `t1.Set should succeed`) key := []byte("secret") signed, err := jwt.Sign(t1, jwt.WithKey(jwa.HS256(), key)) require.NoError(t, err, `jwt.Sign should succeed`) _, err = jwt.Parse(signed, jwt.WithKey(jwa.HS256(), key)) require.NoError(t, err, `jwt.Parse should succeed`) } func TestGH706(t *testing.T) { err := jwt.Validate(jwt.New(), jwt.WithRequiredClaim("foo")) require.ErrorIs(t, err, jwt.ValidateError(), `error should be a validation error`) require.ErrorIs(t, err, jwt.MissingRequiredClaimError(), `err should be jwt.ErrRequiredClaim`) } func TestBenHigginsByPassRegression(t *testing.T) { key, err := rsa.GenerateKey(rand.Reader, 2048) if err != nil { panic(err) } // Test if an access token JSON payload parses when provided directly // // The JSON below is slightly modified example payload from: // https://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-using-the-access-token.html // Case 1: add "aud", and adjust exp to be valid // Case 2: do not add "aud", adjust exp exp := strconv.Itoa(int(time.Now().Unix()) + 1000) const tmpl = `{%s "sub": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee", "device_key": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee", "cognito:groups": ["admin"], "token_use": "access", "scope": "aws.cognito.signin.user.admin", "auth_time": 1562190524, "iss": "https://cognito-idp.us-west-2.amazonaws.com/us-west-2_example", "exp": %s, "iat": 1562190524, "origin_jti": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee", "jti": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee", "client_id": "57cbishk4j24pabc1234567890", "username": "janedoe@example.com" }` testcases := [][]byte{ fmt.Appendf(nil, tmpl, `"aud": ["test"],`, exp), fmt.Appendf(nil, tmpl, ``, exp), } for _, tc := range testcases { for _, pedantic := range []bool{true, false} { _, err = jwt.Parse( tc, jwt.WithValidate(true), jwt.WithPedantic(pedantic), jwt.WithKey(jwa.RS256(), &key.PublicKey), ) t.Logf("%s", err) require.Error(t, err, `jwt.Parse should fail`) } } } func TestVerifyAuto(t *testing.T) { ctx := t.Context() key, err := jwxtest.GenerateRsaJwk() require.NoError(t, err, `jwxtest.GenerateRsaJwk should succeed`) key.Set(jwk.KeyIDKey, `my-awesome-key`) pubkey, err := jwk.PublicKeyOf(key) require.NoError(t, err, `jwk.PublicKeyOf should succeed`) set := jwk.NewSet() set.AddKey(pubkey) backoffCount := 0 srv := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.URL.Query().Get(`type`) { case "backoff": backoffCount++ if backoffCount == 1 { w.WriteHeader(http.StatusInternalServerError) return } } w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(set) })) defer srv.Close() tok, err := jwt.NewBuilder(). Claim(jwt.IssuerKey, `https://github.com/lestrrat-go/jwx/v3`). Claim(jwt.SubjectKey, `jku-test`). Build() require.NoError(t, err, `jwt.NewBuilder.Build() should succeed`) hdrs := jws.NewHeaders() hdrs.Set(jws.JWKSetURLKey, srv.URL) signed, err := jwt.Sign(tok, jwt.WithKey(jwa.RS256(), key, jws.WithProtectedHeaders(hdrs))) require.NoError(t, err, `jwt.Sign() should succeed`) wl := jwk.NewMapWhitelist(). Add(srv.URL) parsed, err := jwt.Parse(signed, jwt.WithVerifyAuto(nil, jwk.WithFetchWhitelist(wl), jwk.WithHTTPClient(srv.Client()))) require.NoError(t, err, `jwt.Parse should succeed`) require.True(t, jwt.Equal(tok, parsed), `tokens should be equal`) _, err = jwt.Parse(signed, jwt.WithVerifyAuto(nil)) require.Error(t, err, `jwt.Parse should fail`) wl = jwk.NewMapWhitelist(). Add(`https://github.com/lestrrat-go/jwx/v3`) _, err = jwt.Parse(signed, jwt.WithVerifyAuto(nil, jwk.WithFetchWhitelist(wl))) require.Error(t, err, `jwt.Parse should fail`) // now with Cache c, err := jwk.NewCache(ctx, httprc.NewClient()) require.NoError(t, err, `jwk.NewCache should succeed`) parsed, err = jwt.Parse(signed, jwt.WithVerifyAuto( jwk.FetchFunc(func(ctx context.Context, u string, options ...jwk.FetchOption) (jwk.Set, error) { var registeropts []jwk.RegisterOption // jwk.FetchOption is also an CacheOption, but the container // doesn't match the signature... so... we need to convert them... for _, option := range options { registeropts = append(registeropts, option) } c.Register(ctx, u, registeropts...) return c.Lookup(ctx, u) }), jwk.WithHTTPClient(srv.Client()), jwk.WithFetchWhitelist(jwk.InsecureWhitelist{}), ), ) require.NoError(t, err, `jwt.Parse should succeed`) require.True(t, jwt.Equal(tok, parsed), `tokens should be equal`) } func TestSerializer(t *testing.T) { t.Run(`Invalid sign suboption`, func(t *testing.T) { _, err := jwt.NewSerializer(). Sign(jwt.WithKey(jwa.HS256(), []byte("abracadabra"), jwe.WithCompress(jwa.Deflate()))). Serialize(jwt.New()) require.Error(t, err, `Serialize() should fail`) }) t.Run(`Invalid SignatureAglrotihm`, func(t *testing.T) { _, err := jwt.NewSerializer(). Encrypt(jwt.WithKey(jwa.A256KW(), []byte("abracadabra"))). Serialize(jwt.New()) require.Error(t, err, `Serialize() should succeedl`) }) t.Run(`Invalid encrypt suboption`, func(t *testing.T) { _, err := jwt.NewSerializer(). Encrypt(jwt.WithKey(jwa.A256KW(), []byte("abracadabra"), jws.WithPretty(true))). Serialize(jwt.New()) require.Error(t, err, `Serialize() should fail`) }) t.Run(`Invalid KeyEncryptionAglrotihm`, func(t *testing.T) { _, err := jwt.NewSerializer(). Encrypt(jwt.WithKey(jwa.HS256(), []byte("abracadabra"))). Serialize(jwt.New()) require.Error(t, err, `Serialize() should succeedl`) }) } func TestFractional(t *testing.T) { t.Run("FormatPrecision", func(t *testing.T) { var nd types.NumericDate jwt.Settings(jwt.WithNumericDateParsePrecision(int(types.MaxPrecision))) s := fmt.Sprintf("%d.100000001", aLongLongTimeAgo) _ = nd.Accept(s) jwt.Settings(jwt.WithNumericDateParsePrecision(0)) testcases := []struct { Input types.NumericDate Expected string Precision int }{ { Input: nd, Expected: fmt.Sprintf(`%d`, aLongLongTimeAgo), }, { Input: types.NumericDate{Time: time.Unix(0, 1).UTC()}, Expected: "0", }, { Input: types.NumericDate{Time: time.Unix(0, 1).UTC()}, Precision: 9, Expected: "0.000000001", }, { Input: types.NumericDate{Time: time.Unix(0, 100000000).UTC()}, Precision: 9, Expected: "0.100000000", }, } for i := 1; i <= int(types.MaxPrecision); i++ { fractional := (fmt.Sprintf(`%d`, 100000001))[:i] testcases = append(testcases, struct { Input types.NumericDate Expected string Precision int }{ Input: nd, Precision: i, Expected: fmt.Sprintf(`%d.%s`, aLongLongTimeAgo, fractional), }) } for _, tc := range testcases { t.Run(fmt.Sprintf("%s (precision=%d)", tc.Input, tc.Precision), func(t *testing.T) { jwt.Settings(jwt.WithNumericDateFormatPrecision(tc.Precision)) require.Equal(t, tc.Expected, tc.Input.String()) }) } jwt.Settings(jwt.WithNumericDateFormatPrecision(0)) }) t.Run("ParsePrecision", func(t *testing.T) { const template = `{"iat":"%s"}` testcases := []struct { Input string Expected time.Time Precision int }{ { Input: "0", Expected: time.Unix(0, 0).UTC(), }, { Input: "0.000000001", Expected: time.Unix(0, 0).UTC(), }, { Input: fmt.Sprintf("%d.111111111", aLongLongTimeAgo), Expected: time.Unix(aLongLongTimeAgo, 0).UTC(), }, { // Max precision Input: fmt.Sprintf("%d.100000001", aLongLongTimeAgo), Precision: int(types.MaxPrecision), Expected: time.Unix(aLongLongTimeAgo, 100000001).UTC(), }, } for i := 1; i < int(types.MaxPrecision); i++ { testcases = append(testcases, struct { Input string Expected time.Time Precision int }{ Input: fmt.Sprintf("%d.100000001", aLongLongTimeAgo), Precision: i, Expected: time.Unix(aLongLongTimeAgo, 100000000).UTC(), }) } for _, tc := range testcases { t.Run(fmt.Sprintf("%s (precision=%d)", tc.Input, tc.Precision), func(t *testing.T) { jwt.Settings(jwt.WithNumericDateParsePrecision(tc.Precision)) tok, err := jwt.Parse( fmt.Appendf(nil, template, tc.Input), jwt.WithVerify(false), jwt.WithValidate(false), ) require.NoError(t, err, `jwt.Parse should succeed`) v, ok := tok.IssuedAt() require.True(t, ok, `iat should be present`) require.Equal(t, tc.Expected, v, `iat should match`) }) } jwt.Settings(jwt.WithNumericDateParsePrecision(0)) }) } func TestGH836(t *testing.T) { // tests on TokenOptionSet are found elsewhere. t1 := jwt.New() t1.Options().Enable(jwt.FlattenAudience) require.True(t, t1.Options().IsEnabled(jwt.FlattenAudience), `flag should be enabled`) t2, err := t1.Clone() require.NoError(t, err, `t1.Clone should succeed`) require.True(t, t2.Options().IsEnabled(jwt.FlattenAudience), `cloned token should have same settings`) t2.Options().Disable(jwt.FlattenAudience) require.True(t, t1.Options().IsEnabled(jwt.FlattenAudience), `flag should be enabled (t2.Options should have no effect on t1.Options)`) } func TestGH850(t *testing.T) { var testToken = `eyJhbGciOiJFUzI1NiJ9.eyJzdWIiOiJ0ZXN0IiwiaWF0IjoxNjY2MDkxMzczLCJmb28iOiJiYXIifQ.3GWevx1z2_uCBB9Vj-D0rsT_CMsMeP9GP2rEqGDWpesoG8nHEjAXJOEQV1jOVkkCtTnS18JhcQdb7dW4i-zmqg.trailing-rubbish` _, err := jwt.Parse([]byte(testToken), jwt.WithVerify(false)) require.True(t, errors.Is(err, jwt.UnknownPayloadTypeError())) } func TestGH888(t *testing.T) { // Use of "none" is insecure, and we just don't allow it by default. // In order to allow none, we must tell jwx that we actually want it. token, err := jwt.NewBuilder(). Subject("foo"). Issuer("bar"). Build() require.NoError(t, err, `jwt.Builder should succeed`) // 1) "none" must be triggered by its own option. Can't use jwt.WithKey(jwa.NoSignature, ...) t.Run("jwt.Sign(token, jwt.WithKey(jwa.NoSignature)) should fail", func(t *testing.T) { _, err := jwt.Sign(token, jwt.WithKey(jwa.NoSignature(), nil)) require.Error(t, err, `jwt.Sign with jwt.WithKey should fail`) }) t.Run("jwt.Sign(token, jwt.WithInsecureNoSignature())", func(t *testing.T) { signed, err := jwt.Sign(token, jwt.WithInsecureNoSignature()) require.NoError(t, err, `jwt.Sign should succeed`) require.Equal(t, `eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJpc3MiOiJiYXIiLCJzdWIiOiJmb28ifQ.`, string(signed)) _, err = jwt.Parse(signed) require.Error(t, err, `jwt.Parse with alg=none should fail`) }) } func TestGH951(t *testing.T) { signKey, err := jwxtest.GenerateRsaKey() require.NoError(t, err, `jwxtest.GenerateRsaKey should succeed`) sharedKey := []byte{ 25, 172, 32, 130, 225, 114, 26, 181, 138, 106, 254, 192, 95, 133, 74, 82, } token, err := jwt.NewBuilder(). Subject(`test-951`). Issuer(`jwt.Test951`). Build() require.NoError(t, err, `jwt.NewBuilder should succeed`) // this whole workflow actually works even if the bug in #951 is present. // so we shall compare the results with and without the encryption // options to see if there is a difference in the length of the // cipher text, which is the second from last component in the message serialized, err := jwt.NewSerializer(). Sign(jwt.WithKey(jwa.RS256(), signKey)). Encrypt( jwt.WithKey(jwa.A128KW(), sharedKey), jwt.WithEncryptOption(jwe.WithContentEncryption(jwa.A128GCM())), jwt.WithEncryptOption(jwe.WithCompress(jwa.Deflate())), ). Serialize(token) require.NoError(t, err, `jwt.NewSerializer()....Serizlie() should succeed`) serialized2, err := jwt.NewSerializer(). Sign(jwt.WithKey(jwa.RS256(), signKey)). Encrypt( jwt.WithKey(jwa.A128KW(), sharedKey), ). Serialize(token) require.NoError(t, err, `jwt.NewSerializer()....Serizlie() should succeed`) require.NotEqual(t, len(bytes.Split(serialized, []byte{tokens.Period})[3]), len(bytes.Split(serialized2, []byte{tokens.Period})[3]), ) decrypted, err := jwe.Decrypt(serialized, jwe.WithKey(jwa.A128KW(), sharedKey)) require.NoError(t, err, `jwe.Decrypt should succeed`) verified, err := jwt.Parse(decrypted, jwt.WithKey(jwa.RS256(), signKey.PublicKey)) require.NoError(t, err, `jwt.Parse should succeed`) require.True(t, jwt.Equal(verified, token), `tokens should be equal`) } func TestGH1007(t *testing.T) { key, err := jwxtest.GenerateRsaJwk() require.NoError(t, err, `jwxtest.GenerateRsaJwk should succeed`) tok, err := jwt.NewBuilder(). Claim(`claim1`, `value1`). Claim(`claim2`, `value2`). Issuer(`github.com/lestrrat-go/jwx`). Audience([]string{`users`}). Build() require.NoError(t, err, `jwt.NewBuilder should succeed`) signed, err := jwt.Sign(tok, jwt.WithKey(jwa.RS256(), key)) require.NoError(t, err, `jwt.Sign should succeed`) // This was the intended usage (no WithKey). This worked from the beginning _, err = jwt.ParseInsecure(signed) require.NoError(t, err, `jwt.ParseInsecure should succeed`) // This is the problematic behavior reporded in #1007. // The fact that we're specifying a wrong key caused Parse() to check for // verification and yet fail :/ wrongPubKey, err := jwxtest.GenerateRsaPublicJwk() require.NoError(t, err, `jwxtest.GenerateRsaPublicJwk should succeed`) require.NoError(t, err, `jwk.PublicKeyOf should succeed`) _, err = jwt.ParseInsecure(signed, jwt.WithKey(jwa.RS256(), wrongPubKey)) require.NoError(t, err, `jwt.ParseInsecure with jwt.WithKey() should succeed`) } func TestParseJSON(t *testing.T) { // NOTE: Unlike in v2, there is no setting for CompactOnly privKey, err := jwxtest.GenerateRsaJwk() require.NoError(t, err, `jwxtest.GenerateRsaJwk should succeed`) signedJSON, err := jws.Sign([]byte(`{}`), jws.WithKey(jwa.RS256(), privKey), jws.WithValidateKey(true), jws.WithJSON()) require.NoError(t, err, `jws.Sign should succeed`) // jws.Verify should succeed _, err = jws.Verify(signedJSON, jws.WithKey(jwa.RS256(), privKey)) require.NoError(t, err, `jws.Verify should succeed`) // jwt.Parse should fail _, err = jwt.Parse(signedJSON, jwt.WithKey(jwa.RS256(), privKey)) require.Error(t, err, `jwt.Parse should fail`) } func TestGH1175(t *testing.T) { token, err := jwt.NewBuilder(). Expiration(time.Now().Add(-1 * time.Hour)). Build() require.NoError(t, err, `jwt.NewBuilder should succeed`) secret := []byte("secret") signed, err := jwt.Sign(token, jwt.WithKey(jwa.HS256(), secret)) require.NoError(t, err, `jwt.Sign should succeed`) req := httptest.NewRequest(http.MethodGet, `http://example.com`, nil) req.Header.Set("Authorization", "Bearer "+string(signed)) _, err = jwt.ParseRequest(req, jwt.WithKey(jwa.HS256(), secret)) require.Error(t, err, `jwt.ParseRequest should fail`) require.ErrorIs(t, err, jwt.TokenExpiredError(), `jwt.ParseRequest should fail with jwt.ErrTokenExpired`) } func TestGH1482(t *testing.T) { tok, _ := jwt.NewBuilder().Issuer("github.com/lestrrat-go/jwx").Build() signed, err := jwt.Sign(tok, jwt.WithKey(jwa.HS256(), []byte("secret"))) require.NoError(t, err, `jwt.Sign should succeed`) var markerValue any kp := jws.KeyProviderFunc(func(ctx context.Context, sink jws.KeySink, _ *jws.Signature, _ *jws.Message) error { markerValue = ctx.Value("marker") key, err := jwk.Import([]byte("secret")) if err != nil { return err } sink.Key(jwa.HS256(), key) return nil }) //nolint:revive ctx := context.WithValue(context.Background(), "marker", "value") _, err = jwt.Parse(signed, jwt.WithKeyProvider(kp), jwt.WithContext(ctx)) require.NoError(t, err, `jwt.Parse should succeed`) require.NotEmpty(t, markerValue, "context value 'marker' should be present") } golang-github-lestrrat-go-jwx-3.0.13/jwt/openid/000077500000000000000000000000001515060566400214665ustar00rootroot00000000000000golang-github-lestrrat-go-jwx-3.0.13/jwt/openid/BUILD.bazel000066400000000000000000000021331515060566400233430ustar00rootroot00000000000000load("@rules_go//go:def.bzl", "go_library", "go_test") go_library( name = "openid", srcs = [ "address.go", "birthdate.go", "builder_gen.go", "filter.go", "interface.go", "openid.go", "token_gen.go", ], importpath = "github.com/lestrrat-go/jwx/v3/jwt/openid", visibility = ["//visibility:public"], deps = [ "//internal/base64", "//internal/json", "//internal/pool", "//internal/tokens", "//jwt", "//jwt/internal/types", "//jwt/internal/errors", "@com_github_lestrrat_go_blackmagic//:blackmagic", ], ) go_test( name = "openid_test", srcs = ["openid_test.go"], deps = [ ":openid", "//internal/json", "//internal/jwxtest", "//internal/tokens", "//jwa", "//jwt", "//jwt/internal/types", "@com_github_stretchr_testify//assert", "@com_github_stretchr_testify//require", ], ) alias( name = "go_default_library", actual = ":openid", visibility = ["//visibility:public"], ) golang-github-lestrrat-go-jwx-3.0.13/jwt/openid/address.go000066400000000000000000000173421515060566400234510ustar00rootroot00000000000000package openid import ( "fmt" "strconv" "github.com/lestrrat-go/jwx/v3/internal/json" "github.com/lestrrat-go/jwx/v3/internal/pool" "github.com/lestrrat-go/jwx/v3/internal/tokens" ) const ( AddressFormattedKey = "formatted" AddressStreetAddressKey = "street_address" AddressLocalityKey = "locality" AddressRegionKey = "region" AddressPostalCodeKey = "postal_code" AddressCountryKey = "country" ) // AddressClaim is the address claim as described in https://openid.net/specs/openid-connect-core-1_0.html#AddressClaim type AddressClaim struct { formatted *string // https://openid.net/specs/openid-connect-core-1_0.html#AddressClaim streetAddress *string // https://openid.net/specs/openid-connect-core-1_0.html#AddressClaim locality *string // https://openid.net/specs/openid-connect-core-1_0.html#AddressClaim region *string // https://openid.net/specs/openid-connect-core-1_0.html#AddressClaim postalCode *string // https://openid.net/specs/openid-connect-core-1_0.html#AddressClaim country *string // https://openid.net/specs/openid-connect-core-1_0.html#AddressClaim } type addressClaimMarshalProxy struct { Xformatted *string `json:"formatted,omitempty"` XstreetAddress *string `json:"street_address,omitempty"` Xlocality *string `json:"locality,omitempty"` Xregion *string `json:"region,omitempty"` XpostalCode *string `json:"postal_code,omitempty"` Xcountry *string `json:"country,omitempty"` } func NewAddress() *AddressClaim { return &AddressClaim{} } // Formatted is a convenience function to retrieve the corresponding value store in the token // if there is a problem retrieving the value, the zero value is returned. If you need to differentiate between existing/non-existing values, use `Get` instead func (t AddressClaim) Formatted() string { if t.formatted == nil { return "" } return *(t.formatted) } // StreetAddress is a convenience function to retrieve the corresponding value store in the token // if there is a problem retrieving the value, the zero value is returned. If you need to differentiate between existing/non-existing values, use `Get` instead func (t AddressClaim) StreetAddress() string { if t.streetAddress == nil { return "" } return *(t.streetAddress) } // Locality is a convenience function to retrieve the corresponding value store in the token // if there is a problem retrieving the value, the zero value is returned. If you need to differentiate between existing/non-existing values, use `Get` instead func (t AddressClaim) Locality() string { if t.locality == nil { return "" } return *(t.locality) } // Region is a convenience function to retrieve the corresponding value store in the token // if there is a problem retrieving the value, the zero value is returned. If you need to differentiate between existing/non-existing values, use `Get` instead func (t AddressClaim) Region() string { if t.region == nil { return "" } return *(t.region) } // PostalCode is a convenience function to retrieve the corresponding value store in the token // if there is a problem retrieving the value, the zero value is returned. If you need to differentiate between existing/non-existing values, use `Get` instead func (t AddressClaim) PostalCode() string { if t.postalCode == nil { return "" } return *(t.postalCode) } // Country is a convenience function to retrieve the corresponding value store in the token // if there is a problem retrieving the value, the zero value is returned. If you need to differentiate between existing/non-existing values, use `Get` instead func (t AddressClaim) Country() string { if t.country == nil { return "" } return *(t.country) } func (t *AddressClaim) Get(s string) (any, bool) { switch s { case AddressFormattedKey: if t.formatted == nil { return nil, false } return *(t.formatted), true case AddressStreetAddressKey: if t.streetAddress == nil { return nil, false } return *(t.streetAddress), true case AddressLocalityKey: if t.locality == nil { return nil, false } return *(t.locality), true case AddressRegionKey: if t.region == nil { return nil, false } return *(t.region), true case AddressPostalCodeKey: if t.postalCode == nil { return nil, false } return *(t.postalCode), true case AddressCountryKey: if t.country == nil { return nil, false } return *(t.country), true } return nil, false } func (t *AddressClaim) Set(key string, value any) error { switch key { case AddressFormattedKey: v, ok := value.(string) if ok { t.formatted = &v return nil } return fmt.Errorf(`invalid type for key 'formatted': %T`, value) case AddressStreetAddressKey: v, ok := value.(string) if ok { t.streetAddress = &v return nil } return fmt.Errorf(`invalid type for key 'streetAddress': %T`, value) case AddressLocalityKey: v, ok := value.(string) if ok { t.locality = &v return nil } return fmt.Errorf(`invalid type for key 'locality': %T`, value) case AddressRegionKey: v, ok := value.(string) if ok { t.region = &v return nil } return fmt.Errorf(`invalid type for key 'region': %T`, value) case AddressPostalCodeKey: v, ok := value.(string) if ok { t.postalCode = &v return nil } return fmt.Errorf(`invalid type for key 'postalCode': %T`, value) case AddressCountryKey: v, ok := value.(string) if ok { t.country = &v return nil } return fmt.Errorf(`invalid type for key 'country': %T`, value) default: return fmt.Errorf(`invalid key for address claim: %s`, key) } } func (t *AddressClaim) Accept(v any) error { switch v := v.(type) { case AddressClaim: *t = v return nil case *AddressClaim: *t = *v return nil case map[string]any: for key, value := range v { if err := t.Set(key, value); err != nil { return fmt.Errorf(`failed to set header: %w`, err) } } return nil default: return fmt.Errorf(`invalid type for AddressClaim: %T`, v) } } // MarshalJSON serializes the token in JSON format. func (t AddressClaim) MarshalJSON() ([]byte, error) { buf := pool.BytesBuffer().Get() defer pool.BytesBuffer().Put(buf) buf.WriteByte(tokens.OpenCurlyBracket) prev := buf.Len() if v := t.country; v != nil { buf.WriteString(`"country":`) buf.WriteString(strconv.Quote(*v)) } if v := t.formatted; v != nil { if buf.Len() > prev { buf.WriteByte(tokens.Comma) } prev = buf.Len() buf.WriteString(`"formatted":`) buf.WriteString(strconv.Quote(*v)) } if v := t.locality; v != nil { if buf.Len() > prev { buf.WriteByte(tokens.Comma) } prev = buf.Len() buf.WriteString(`"locality":`) buf.WriteString(strconv.Quote(*v)) } if v := t.postalCode; v != nil { if buf.Len() > prev { buf.WriteByte(tokens.Comma) } prev = buf.Len() buf.WriteString(`"postal_code":`) buf.WriteString(strconv.Quote(*v)) } if v := t.region; v != nil { if buf.Len() > prev { buf.WriteByte(tokens.Comma) } prev = buf.Len() buf.WriteString(`"region":`) buf.WriteString(strconv.Quote(*v)) } if v := t.streetAddress; v != nil { if buf.Len() > prev { buf.WriteByte(tokens.Comma) } buf.WriteString(`"street_address":`) buf.WriteString(strconv.Quote(*v)) } buf.WriteByte(tokens.CloseCurlyBracket) ret := make([]byte, buf.Len()) copy(ret, buf.Bytes()) return ret, nil } // UnmarshalJSON deserializes data from a JSON data buffer into a AddressClaim func (t *AddressClaim) UnmarshalJSON(data []byte) error { var proxy addressClaimMarshalProxy if err := json.Unmarshal(data, &proxy); err != nil { return fmt.Errorf(`failed to unmarshasl address claim: %w`, err) } t.formatted = proxy.Xformatted t.streetAddress = proxy.XstreetAddress t.locality = proxy.Xlocality t.region = proxy.Xregion t.postalCode = proxy.XpostalCode t.country = proxy.Xcountry return nil } golang-github-lestrrat-go-jwx-3.0.13/jwt/openid/birthdate.go000066400000000000000000000071021515060566400237630ustar00rootroot00000000000000package openid import ( "bytes" "fmt" "io" "math" "regexp" "strconv" "github.com/lestrrat-go/jwx/v3/internal/json" ) // https://openid.net/specs/openid-connect-core-1_0.html // // End-User's birthday, represented as an ISO 8601:2004 [ISO8601‑2004] YYYY-MM-DD format. // The year MAY be 0000, indicating that it is omitted. To represent only the year, YYYY // format is allowed. Note that depending on the underlying platform's date related function, // providing just year can result in varying month and day, so the implementers need to // take this factor into account to correctly process the dates. type BirthdateClaim struct { year *int month *int day *int } func (b BirthdateClaim) Year() int { if b.year == nil { return 0 } return *(b.year) } func (b BirthdateClaim) Month() int { if b.month == nil { return 0 } return *(b.month) } func (b BirthdateClaim) Day() int { if b.day == nil { return 0 } return *(b.day) } func (b *BirthdateClaim) UnmarshalJSON(data []byte) error { var s string if err := json.Unmarshal(data, &s); err != nil { return fmt.Errorf(`failed to unmarshal JSON string for birthdate claim: %w`, err) } if err := b.Accept(s); err != nil { return fmt.Errorf(`failed to accept JSON value for birthdate claim: %w`, err) } return nil } var intSize int func init() { intSize = 64 if math.MaxInt == math.MaxInt32 { intSize = 32 } } func parseBirthdayInt(s string) int { i, err := strconv.ParseInt(s, 10, intSize) if err != nil { return 0 } return int(i) } var birthdateRx = regexp.MustCompile(`^(\d{4})-(\d{2})-(\d{2})$`) // Accept accepts a value read from JSON, and converts it to a BirthdateClaim. // This method DOES NOT verify the correctness of a date. // Consumers should check for validity of dates such as Apr 31 et al func (b *BirthdateClaim) Accept(v any) error { b.year = nil b.month = nil b.day = nil switch v := v.(type) { case *BirthdateClaim: if ptr := v.year; ptr != nil { year := *ptr b.year = &year } if ptr := v.month; ptr != nil { month := *ptr b.month = &month } if ptr := v.day; ptr != nil { day := *ptr b.day = &day } return nil case string: // yeah, regexp is slow. PR's welcome indices := birthdateRx.FindStringSubmatchIndex(v) if indices == nil { return fmt.Errorf(`invalid pattern for birthdate`) } var tmp BirthdateClaim // Okay, this really isn't kosher, but we're doing this for // the coverage game... Because birthdateRx already checked that // the string contains 3 strings with consecutive decimal values // we can assume that strconv.ParseInt always succeeds. // strconv.ParseInt (and strconv.ParseUint that it uses internally) // only returns range errors, so we should be safe. year := parseBirthdayInt(v[indices[2]:indices[3]]) if year <= 0 { return fmt.Errorf(`failed to parse birthdate year`) } tmp.year = &year month := parseBirthdayInt(v[indices[4]:indices[5]]) if month <= 0 { return fmt.Errorf(`failed to parse birthdate month`) } tmp.month = &month day := parseBirthdayInt(v[indices[6]:indices[7]]) if day <= 0 { return fmt.Errorf(`failed to parse birthdate day`) } tmp.day = &day *b = tmp return nil default: return fmt.Errorf(`invalid type for birthdate: %T`, v) } } func (b BirthdateClaim) encode(dst io.Writer) { fmt.Fprintf(dst, "%04d-%02d-%02d", b.Year(), b.Month(), b.Day()) } func (b BirthdateClaim) String() string { var buf bytes.Buffer b.encode(&buf) return buf.String() } func (b BirthdateClaim) MarshalText() ([]byte, error) { var buf bytes.Buffer b.encode(&buf) return buf.Bytes(), nil } golang-github-lestrrat-go-jwx-3.0.13/jwt/openid/builder_gen.go000066400000000000000000000072021515060566400242750ustar00rootroot00000000000000// Code generated by tools/cmd/genjwt/main.go. DO NOT EDIT. package openid import ( "fmt" "sync" "time" ) // Builder is a convenience wrapper around the New() constructor // and the Set() methods to assign values to Token claims. // Users can successively call Claim() on the Builder, and have it // construct the Token when Build() is called. This alleviates the // need for the user to check for the return value of every single // Set() method call. // Note that each call to Claim() overwrites the value set from the // previous call. type Builder struct { mu sync.Mutex claims map[string]any } func NewBuilder() *Builder { return &Builder{} } func (b *Builder) init() { if b.claims == nil { b.claims = make(map[string]any) } } func (b *Builder) Claim(name string, value any) *Builder { b.mu.Lock() defer b.mu.Unlock() b.init() b.claims[name] = value return b } func (b *Builder) Address(v *AddressClaim) *Builder { return b.Claim(AddressKey, v) } func (b *Builder) Audience(v []string) *Builder { return b.Claim(AudienceKey, v) } func (b *Builder) Birthdate(v *BirthdateClaim) *Builder { return b.Claim(BirthdateKey, v) } func (b *Builder) Email(v string) *Builder { return b.Claim(EmailKey, v) } func (b *Builder) EmailVerified(v bool) *Builder { return b.Claim(EmailVerifiedKey, v) } func (b *Builder) Expiration(v time.Time) *Builder { return b.Claim(ExpirationKey, v) } func (b *Builder) FamilyName(v string) *Builder { return b.Claim(FamilyNameKey, v) } func (b *Builder) Gender(v string) *Builder { return b.Claim(GenderKey, v) } func (b *Builder) GivenName(v string) *Builder { return b.Claim(GivenNameKey, v) } func (b *Builder) IssuedAt(v time.Time) *Builder { return b.Claim(IssuedAtKey, v) } func (b *Builder) Issuer(v string) *Builder { return b.Claim(IssuerKey, v) } func (b *Builder) JwtID(v string) *Builder { return b.Claim(JwtIDKey, v) } func (b *Builder) Locale(v string) *Builder { return b.Claim(LocaleKey, v) } func (b *Builder) MiddleName(v string) *Builder { return b.Claim(MiddleNameKey, v) } func (b *Builder) Name(v string) *Builder { return b.Claim(NameKey, v) } func (b *Builder) Nickname(v string) *Builder { return b.Claim(NicknameKey, v) } func (b *Builder) NotBefore(v time.Time) *Builder { return b.Claim(NotBeforeKey, v) } func (b *Builder) PhoneNumber(v string) *Builder { return b.Claim(PhoneNumberKey, v) } func (b *Builder) PhoneNumberVerified(v bool) *Builder { return b.Claim(PhoneNumberVerifiedKey, v) } func (b *Builder) Picture(v string) *Builder { return b.Claim(PictureKey, v) } func (b *Builder) PreferredUsername(v string) *Builder { return b.Claim(PreferredUsernameKey, v) } func (b *Builder) Profile(v string) *Builder { return b.Claim(ProfileKey, v) } func (b *Builder) Subject(v string) *Builder { return b.Claim(SubjectKey, v) } func (b *Builder) UpdatedAt(v time.Time) *Builder { return b.Claim(UpdatedAtKey, v) } func (b *Builder) Website(v string) *Builder { return b.Claim(WebsiteKey, v) } func (b *Builder) Zoneinfo(v string) *Builder { return b.Claim(ZoneinfoKey, v) } // Build creates a new token based on the claims that the builder has received // so far. If a claim cannot be set, then the method returns a nil Token with // a en error as a second return value // // Once `Build()` is called, all claims are cleared from the Builder, and the // Builder can be reused to build another token func (b *Builder) Build() (Token, error) { b.mu.Lock() claims := b.claims b.claims = nil b.mu.Unlock() tok := New() for k, v := range claims { if err := tok.Set(k, v); err != nil { return nil, fmt.Errorf(`failed to set claim %q: %w`, k, err) } } return tok, nil } golang-github-lestrrat-go-jwx-3.0.13/jwt/openid/filter.go000066400000000000000000000007751515060566400233130ustar00rootroot00000000000000package openid import ( "github.com/lestrrat-go/jwx/v3/jwt" ) // StandardClaimsFilter returns a TokenFilter that filters out standard OpenID claims. // // You can use this filter to create tokens that either only has standard OpenID claims // or only custom claims. If you need to configure the filter more precisely, consider // using the jwt.ClaimNameFilter directly. func StandardClaimsFilter() jwt.TokenFilter { return stdClaimsFilter } var stdClaimsFilter = jwt.NewClaimNameFilter(stdClaimNames...) golang-github-lestrrat-go-jwx-3.0.13/jwt/openid/interface.go000066400000000000000000000002351515060566400237550ustar00rootroot00000000000000package openid import ( "github.com/lestrrat-go/jwx/v3/internal/json" ) type DecodeCtx = json.DecodeCtx type TokenWithDecodeCtx = json.DecodeCtxContainer golang-github-lestrrat-go-jwx-3.0.13/jwt/openid/openid.go000066400000000000000000000027251515060566400233010ustar00rootroot00000000000000// Package openid provides a specialized token that provides utilities // to work with OpenID JWT tokens. // // In order to use OpenID claims, you specify the token to use in the // jwt.Parse method // // jwt.Parse(data, jwt.WithToken(openid.New()) package openid import ( "fmt" "github.com/lestrrat-go/jwx/v3/internal/json" "github.com/lestrrat-go/jwx/v3/jwt" ) var registry = json.NewRegistry() func (t *stdToken) Clone() (jwt.Token, error) { var dst jwt.Token = New() for _, k := range t.Keys() { var v any if err := t.Get(k, &v); err != nil { return nil, fmt.Errorf(`openid.Clone: failed to get %s: %w`, k, err) } if err := dst.Set(k, v); err != nil { return nil, fmt.Errorf(`openid.Clone: failed to set %s: %w`, k, err) } } return dst, nil } // RegisterCustomField allows users to specify that a private field // be decoded as an instance of the specified type. This option has // a global effect. // // For example, suppose you have a custom field `x-birthday`, which // you want to represent as a string formatted in RFC3339 in JSON, // but want it back as `time.Time`. // // In that case you would register a custom field as follows // // jwt.RegisterCustomField(`x-birthday`, timeT) // // Then `token.Get("x-birthday")` will still return an `any`, // but you can convert its type to `time.Time` // // bdayif, _ := token.Get(`x-birthday`) // bday := bdayif.(time.Time) func RegisterCustomField(name string, object any) { registry.Register(name, object) } golang-github-lestrrat-go-jwx-3.0.13/jwt/openid/openid_test.go000066400000000000000000000537001515060566400243370ustar00rootroot00000000000000package openid_test import ( "bytes" "crypto/ecdsa" "crypto/rand" "crypto/sha256" "encoding/base64" "fmt" "strconv" "testing" "time" "github.com/lestrrat-go/jwx/v3/internal/json" "github.com/lestrrat-go/jwx/v3/internal/jwxtest" "github.com/lestrrat-go/jwx/v3/internal/tokens" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/lestrrat-go/jwx/v3/jwt" "github.com/lestrrat-go/jwx/v3/jwt/internal/types" "github.com/lestrrat-go/jwx/v3/jwt/openid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) const aLongLongTimeAgo = 233431200 const aLongLongTimeAgoString = "233431200" const ( tokenTime = 233431200 ) var expectedTokenTime = time.Unix(tokenTime, 0).UTC() func testStockAddressClaim(t *testing.T, x *openid.AddressClaim) { t.Helper() require.NotNil(t, x) tests := []struct { Accessor func() string KeyName string Value string }{ { Accessor: x.Formatted, KeyName: openid.AddressFormattedKey, Value: "〒105-0011 æąäēŦéƒŊ港åŒē芝å…Ŧ園īŧ”ä¸į›Žīŧ’−īŧ˜", }, { Accessor: x.Country, KeyName: openid.AddressCountryKey, Value: "æ—ĨæœŦ", }, { Accessor: x.Region, KeyName: openid.AddressRegionKey, Value: "æąäēŦéƒŊ", }, { Accessor: x.Locality, KeyName: openid.AddressLocalityKey, Value: "港åŒē", }, { Accessor: x.StreetAddress, KeyName: openid.AddressStreetAddressKey, Value: "芝å…Ŧ園īŧ”ä¸į›Žīŧ’−īŧ˜", }, { Accessor: x.PostalCode, KeyName: openid.AddressPostalCodeKey, Value: "105-0011", }, } for _, tc := range tests { t.Run(tc.KeyName, func(t *testing.T) { t.Run("Accessor", func(t *testing.T) { require.Equal(t, tc.Value, tc.Accessor(), "values should match") }) t.Run("Get", func(t *testing.T) { v, ok := x.Get(tc.KeyName) require.True(t, ok, `x.Get should succeed`) require.Equal(t, tc.Value, v, `values should match`) }) }) } } func TestAdressClaim(t *testing.T) { const src = `{ "formatted": "〒105-0011 æąäēŦéƒŊ港åŒē芝å…Ŧ園īŧ”ä¸į›Žīŧ’−īŧ˜", "street_address": "芝å…Ŧ園īŧ”ä¸į›Žīŧ’−īŧ˜", "locality": "港åŒē", "region": "æąäēŦéƒŊ", "postal_code": "105-0011", "country": "æ—ĨæœŦ" }` var address openid.AddressClaim require.NoError(t, json.Unmarshal([]byte(src), &address), "json.Unmarshal should succeed") var roundtrip openid.AddressClaim buf, err := json.Marshal(address) require.NoError(t, err, `json.Marshal(address) should succeed`) require.NoError(t, json.Unmarshal(buf, &roundtrip), "json.Unmarshal should succeed") for _, x := range []*openid.AddressClaim{&address, &roundtrip} { testStockAddressClaim(t, x) } } func TestOpenIDClaims(t *testing.T) { getVerify := func(token openid.Token, key string, expected any) bool { var v any if assert.NoError(t, token.Get(key, &v), `token.Get %#v should succeed`, key) { return false } return assert.Equal(t, v, expected) } var base = []struct { Value any Expected func(any) any Check func(openid.Token) Key string }{ { Key: openid.AudienceKey, Value: []string{"developers", "secops", "tac"}, Check: func(token openid.Token) { v, ok := token.Audience() require.True(t, ok, `token.Audience should succeed`) require.Equal(t, v, []string{"developers", "secops", "tac"}) }, }, { Key: openid.ExpirationKey, Value: tokenTime, Expected: func(v any) any { var n types.NumericDate if err := n.Accept(v); err != nil { panic(err) } return n.Get() }, Check: func(token openid.Token) { v, ok := token.Expiration() require.True(t, ok, `token.Expiration should succeed`) require.Equal(t, v, expectedTokenTime) }, }, { Key: openid.IssuedAtKey, Value: tokenTime, Expected: func(v any) any { var n types.NumericDate if err := n.Accept(v); err != nil { panic(err) } return n.Get() }, Check: func(token openid.Token) { v, ok := token.Expiration() require.True(t, ok, `token.Expiration should succeed`) require.Equal(t, v, expectedTokenTime) }, }, { Key: openid.IssuerKey, Value: "http://www.example.com", Check: func(token openid.Token) { v, ok := token.Issuer() require.True(t, ok, `token.Issuer should succeed`) require.Equal(t, v, "http://www.example.com") }, }, { Key: openid.JwtIDKey, Value: "e9bc097a-ce51-4036-9562-d2ade882db0d", Check: func(token openid.Token) { v, ok := token.JwtID() require.True(t, ok, `token.JwtID should succeed`) require.Equal(t, v, "e9bc097a-ce51-4036-9562-d2ade882db0d") }, }, { Key: openid.NotBeforeKey, Value: tokenTime, Expected: func(v any) any { var n types.NumericDate if err := n.Accept(v); err != nil { panic(err) } return n.Get() }, Check: func(token openid.Token) { v, ok := token.NotBefore() require.True(t, ok, `token.NotBefore should succeed`) require.Equal(t, v, expectedTokenTime) }, }, { Key: openid.SubjectKey, Value: "unit test", Check: func(token openid.Token) { v, ok := token.Subject() require.True(t, ok, `token.Subject should succeed`) require.Equal(t, v, "unit test") }, }, { Value: "jwx", Key: openid.NameKey, Check: func(token openid.Token) { v, ok := token.Name() require.True(t, ok, `token.Name should succeed`) require.Equal(t, v, "jwx") }, }, { Value: "jay", Key: openid.GivenNameKey, Check: func(token openid.Token) { v, ok := token.GivenName() require.True(t, ok, `token.GivenName should succeed`) require.Equal(t, v, "jay") }, }, { Value: "weee", Key: openid.MiddleNameKey, Check: func(token openid.Token) { v, ok := token.MiddleName() require.True(t, ok, `token.MiddleName should succeed`) require.Equal(t, v, "weee") }, }, { Value: "xi", Key: openid.FamilyNameKey, Check: func(token openid.Token) { v, ok := token.FamilyName() require.True(t, ok, `token.FamilyName should succeed`) require.Equal(t, v, "xi") }, }, { Value: "jayweexi", Key: openid.NicknameKey, Check: func(token openid.Token) { v, ok := token.Nickname() require.True(t, ok, `token.Nickname should succeed`) require.Equal(t, v, "jayweexi") }, }, { Value: "jwx", Key: openid.PreferredUsernameKey, Check: func(token openid.Token) { v, ok := token.PreferredUsername() require.True(t, ok, `token.PreferredUsername should succeed`) require.Equal(t, v, "jwx") }, }, { Value: "https://github.com/lestrrat-go/jwx/v3", Key: openid.ProfileKey, Check: func(token openid.Token) { v, ok := token.Profile() require.True(t, ok, `token.Profile should succeed`) require.Equal(t, v, "https://github.com/lestrrat-go/jwx/v3") }, }, { Value: "https://avatars1.githubusercontent.com/u/36653903?s=400&v=4", Key: openid.PictureKey, Check: func(token openid.Token) { v, ok := token.Picture() require.True(t, ok, `token.Picture should succeed`) require.Equal(t, v, "https://avatars1.githubusercontent.com/u/36653903?s=400&v=4") }, }, { Value: "https://github.com/lestrrat-go/jwx/v3", Key: openid.WebsiteKey, Check: func(token openid.Token) { v, ok := token.Website() require.True(t, ok, `token.Website should succeed`) require.Equal(t, v, "https://github.com/lestrrat-go/jwx/v3") }, }, { Value: "lestrrat+github@gmail.com", Key: openid.EmailKey, Check: func(token openid.Token) { v, ok := token.Email() require.True(t, ok, `token.Email should succeed`) require.Equal(t, v, "lestrrat+github@gmail.com") }, }, { Value: true, Key: openid.EmailVerifiedKey, Check: func(token openid.Token) { v, ok := token.EmailVerified() require.True(t, ok, `token.EmailVerified should succeed`) require.True(t, v, `values should match`) }, }, { Value: "n/a", Key: openid.GenderKey, Check: func(token openid.Token) { v, ok := token.Gender() require.True(t, ok, `token.Gender should succeed`) require.Equal(t, v, "n/a", `values should match`) }, }, { Value: "2015-11-04", Key: openid.BirthdateKey, Expected: func(v any) any { var b openid.BirthdateClaim if err := b.Accept(v); err != nil { panic(err) } return &b }, Check: func(token openid.Token) { var b openid.BirthdateClaim b.Accept("2015-11-04") v, ok := token.Birthdate() require.True(t, ok, `token.Birthdate should succeed`) require.Equal(t, v, &b) }, }, { Value: "Asia/Tokyo", Key: openid.ZoneinfoKey, Check: func(token openid.Token) { v, ok := token.Zoneinfo() require.True(t, ok, `token.Zoneinfo should succeed`) require.Equal(t, v, "Asia/Tokyo") }, }, { Value: "ja_JP", Key: openid.LocaleKey, Check: func(token openid.Token) { v, ok := token.Locale() require.True(t, ok, `token.Locale should succeed`) require.Equal(t, v, "ja_JP") }, }, { Value: "819012345678", Key: openid.PhoneNumberKey, Check: func(token openid.Token) { v, ok := token.PhoneNumber() require.True(t, ok, `token.PhoneNumber should succeed`) require.Equal(t, v, "819012345678") }, }, { Value: true, Key: openid.PhoneNumberVerifiedKey, Check: func(token openid.Token) { v, ok := token.PhoneNumberVerified() require.True(t, ok, `token.PhoneNumberVerified should succeed`) require.True(t, v) }, }, { Value: map[string]any{ "formatted": "〒105-0011 æąäēŦéƒŊ港åŒē芝å…Ŧ園īŧ”ä¸į›Žīŧ’−īŧ˜", "street_address": "芝å…Ŧ園īŧ”ä¸į›Žīŧ’−īŧ˜", "locality": "港åŒē", "region": "æąäēŦéƒŊ", "country": "æ—ĨæœŦ", "postal_code": "105-0011", }, Key: openid.AddressKey, Expected: func(v any) any { address := openid.NewAddress() m, ok := v.(map[string]any) if !ok { panic(fmt.Sprintf("expected map[string]any, got %T", v)) } for name, val := range m { require.NoError(t, address.Set(name, val), `address.Set should succeed`) } return address }, Check: func(token openid.Token) { v, ok := token.Address() require.True(t, ok, `token.Address should succeed`) testStockAddressClaim(t, v) }, }, { Value: aLongLongTimeAgoString, Key: openid.UpdatedAtKey, Expected: func(v any) any { var n types.NumericDate if err := n.Accept(v); err != nil { panic(err) } return n.Get() }, Check: func(token openid.Token) { v, ok := token.UpdatedAt() require.True(t, ok, `token.UpdatedAt should succeed`) require.Equal(t, time.Unix(aLongLongTimeAgo, 0).UTC(), v) }, }, { Value: `dummy`, Key: `dummy`, Check: func(token openid.Token) { var v any require.NoError(t, token.Get(`dummy`, &v), `token.Get should return valid value`) require.Equal(t, `dummy`, v, `values should match`) }, }, } var data = map[string]any{} var expected = map[string]any{} for _, value := range base { data[value.Key] = value.Value if expf := value.Expected; expf != nil { expected[value.Key] = expf(value.Value) } else { expected[value.Key] = value.Value } } type openidTokTestCase struct { Token openid.Token Name string } var tokens []openidTokTestCase { // one with Set() b := openid.NewBuilder() for name, value := range data { b.Claim(name, value) } token, err := b.Build() require.NoError(t, err, `b.Build() should succeed`) tokens = append(tokens, openidTokTestCase{Name: `token constructed by calling Set()`, Token: token}) } { // two with json.Marshal / json.Unmarshal src, err := json.MarshalIndent(data, "", " ") require.NoError(t, err, `failed to marshal base map`) t.Logf("Using source JSON: %s", src) token := openid.New() require.NoError(t, json.Unmarshal(src, &token), `json.Unmarshal should succeed`) tokens = append(tokens, openidTokTestCase{Name: `token constructed by Marshal(map)+Unmashal`, Token: token}) // One more... Marshal the token, _and_ re-unmarshal buf, err := json.Marshal(token) require.NoError(t, err, `json.Marshal should succeed`) token2 := openid.New() require.NoError(t, json.Unmarshal(buf, &token2), `json.Unmarshal should succeed`) tokens = append(tokens, openidTokTestCase{Name: `token constructed by Marshal(openid.Token)+Unmashal`, Token: token2}) // Sign it, and use jwt.Parse var token3 openid.Token { alg := jwa.RS256() key, err := jwxtest.GenerateRsaKey() require.NoError(t, err, `rsa.GeneraKey should succeed`) signed, err := jwt.Sign(token, jwt.WithKey(alg, key)) require.NoError(t, err, `jwt.Sign should succeed`) tokenTmp, err := jwt.Parse(signed, jwt.WithToken(openid.New()), jwt.WithKey(alg, &key.PublicKey), jwt.WithValidate(false)) require.NoError(t, err, `parsing the token via jwt.Parse should succeed`) // Check if token is an OpenID token if _, ok := tokenTmp.(openid.Token); !assert.True(t, ok, `token should be a openid.Token (%T)`, tokenTmp) { return } token3 = tokenTmp.(openid.Token) } tokens = append(tokens, openidTokTestCase{Name: `token constructed by jwt.Parse`, Token: token3}) } for _, token := range tokens { t.Run(token.Name, func(t *testing.T) { for _, value := range base { t.Run(value.Key, func(_ *testing.T) { value.Check(token.Token) }) t.Run(value.Key+" via Get()", func(_ *testing.T) { expected := value.Value if expf := value.Expected; expf != nil { expected = expf(value.Value) } getVerify(token.Token, value.Key, expected) }) } }) } t.Run("Iterator", func(t *testing.T) { tok := tokens[0].Token t.Run("Iterate", func(t *testing.T) { seen := make(map[string]any) for _, k := range tok.Keys() { var v any require.NoError(t, tok.Get(k, &v), `tok.Get should succeed`) seen[k] = v } require.Equal(t, expected, seen, `values should match`) }) t.Run("Clone", func(t *testing.T) { cloned, err := tok.Clone() require.NoError(t, err, `tok.Clone should succeed`) require.True(t, jwt.Equal(tok, cloned), `values should match`) }) }) } func TestBirthdateClaim(t *testing.T) { t.Parallel() t.Run("regular date", func(t *testing.T) { t.Parallel() testcases := []struct { Source string Year int Month int Day int Error bool }{ { Source: `"2015-11-04"`, Year: 2015, Month: 11, Day: 4, }, { Source: `"0009-09-09"`, Year: 9, Month: 9, Day: 9, }, { Source: `{}`, Error: true, }, { Source: `"202X-01-01"`, Error: true, }, { Source: `"0000-01-01"`, Error: true, }, { Source: `"0001-00-01"`, Error: true, }, { Source: `"0001-01-00"`, Error: true, }, } for _, tc := range testcases { t.Run(tc.Source, func(t *testing.T) { var b openid.BirthdateClaim if tc.Error { assert.Error(t, json.Unmarshal([]byte(tc.Source), &b), `json.Unmarshal should fail`) return } require.NoError(t, json.Unmarshal([]byte(tc.Source), &b), `json.Unmarshal should succeed`) require.Equal(t, b.Year(), tc.Year, "year should match") require.Equal(t, b.Month(), tc.Month, "month should match") require.Equal(t, b.Day(), tc.Day, "day should match") serialized, err := json.Marshal(b) require.NoError(t, err, `json.Marshal should succeed`) require.Equal(t, string(serialized), tc.Source, `serialized format should be the same`) stringified := b.String() expectedString, _ := strconv.Unquote(tc.Source) require.Equal(t, stringified, expectedString, `stringified format should be the same`) }) } }) t.Run("empty date", func(t *testing.T) { t.Parallel() var b openid.BirthdateClaim require.Equal(t, b.Year(), 0, "year should match") require.Equal(t, b.Month(), 0, "month should match") require.Equal(t, b.Day(), 0, "day should match") }) t.Run("invalid accept", func(t *testing.T) { t.Parallel() var b openid.BirthdateClaim require.Error(t, b.Accept(nil)) }) } func TestKeys(t *testing.T) { at := assert.New(t) at.Equal(`address`, openid.AddressKey) at.Equal(`aud`, openid.AudienceKey) at.Equal(`birthdate`, openid.BirthdateKey) at.Equal(`email`, openid.EmailKey) at.Equal(`email_verified`, openid.EmailVerifiedKey) at.Equal(`exp`, openid.ExpirationKey) at.Equal(`family_name`, openid.FamilyNameKey) at.Equal(`gender`, openid.GenderKey) at.Equal(`given_name`, openid.GivenNameKey) at.Equal(`iat`, openid.IssuedAtKey) at.Equal(`iss`, openid.IssuerKey) at.Equal(`jti`, openid.JwtIDKey) at.Equal(`locale`, openid.LocaleKey) at.Equal(`middle_name`, openid.MiddleNameKey) at.Equal(`name`, openid.NameKey) at.Equal(`nickname`, openid.NicknameKey) at.Equal(`nbf`, openid.NotBeforeKey) at.Equal(`phone_number`, openid.PhoneNumberKey) at.Equal(`phone_number_verified`, openid.PhoneNumberVerifiedKey) at.Equal(`picture`, openid.PictureKey) at.Equal(`preferred_username`, openid.PreferredUsernameKey) at.Equal(`profile`, openid.ProfileKey) at.Equal(`sub`, openid.SubjectKey) at.Equal(`updated_at`, openid.UpdatedAtKey) at.Equal(`website`, openid.WebsiteKey) at.Equal(`zoneinfo`, openid.ZoneinfoKey) } func TestGH734(t *testing.T) { const src = `{ "nickname": "miniscruff", "updated_at": "2022-05-06T04:57:24.367Z", "email_verified": true }` expected, _ := time.Parse(time.RFC3339, "2022-05-06T04:57:24.367Z") for _, pedantic := range []bool{true, false} { t.Run(fmt.Sprintf("pedantic=%t", pedantic), func(t *testing.T) { jwt.Settings(jwt.WithNumericDateParsePedantic(pedantic)) tok := openid.New() _, err := jwt.Parse( []byte(src), jwt.WithToken(tok), jwt.WithVerify(false), jwt.WithValidate(false), ) if pedantic { require.Error(t, err, `jwt.Parse should fail for pedantic parser`) } else { require.NoError(t, err, `jwt.Parse should succeed`) v, ok := tok.UpdatedAt() require.True(t, ok, `updated_at should be set`) require.Equal(t, expected, v, `updated_at should match`) } }) } jwt.Settings(jwt.WithNumericDateParsePedantic(false)) } func TestWithBase64Encoder(t *testing.T) { // see #1324 t.Run("Roundtrip", func(t *testing.T) { key, err := jwxtest.GenerateEcdsaKey(jwa.P256()) require.NoError(t, err) tok, err := openid.NewBuilder(). Subject("subject"). Name("John Smith"). Locale("CA"). Email("john@example.org"). PreferredUsername("john@example.org"). GivenName("John"). FamilyName("Smith"). EmailVerified(false). Issuer("https://example.org/oauth2/default"). Build() require.NoError(t, err, `openid.NewBuilder should succeed`) signed, err := jwt.Sign(tok, jwt.WithKey(jwa.ES256(), key), jwt.WithBase64Encoder(base64.URLEncoding)) require.NoError(t, err, `jwt.Sign should succeed`) parsed := openid.New() _, err = jwt.Parse(signed, jwt.WithToken(parsed), jwt.WithKey(jwa.ES256(), key.PublicKey), jwt.WithBase64Encoder(base64.URLEncoding)) require.NoError(t, err, `jwt.Parse should succeed`) require.Equal(t, tok, parsed, `parsed token should match original`) }) t.Run("Contributed Test Case", func(t *testing.T) { key, err := jwxtest.GenerateEcdsaKey(jwa.P256()) require.NoError(t, err) // ALB JWTs usually expire very quickly, but this one isn't real and isn't // signed by AWS so we'll make it an hour to be less tedious. expiry := time.Now().Add(time.Hour).Unix() // Generate header + payload header := map[string]any{ "typ": "JWT", "kid": "2563ee81-616e-4a38-b2f8-48a2cccba80f", // Not a real AWS Key ID "alg": "ES256", "iss": "https://example.org/oauth2/default", "client": "a-client-id", "signer": "arn:aws:elasticloadbalancing:us-east-1:123456789012:loadbalancer/app/an-alb-name/abcdef0123456789", // Not real "exp": expiry, } payload := map[string]any{ "sub": "some-subject-id", "name": "John Smith", "locale": "CA", "email": "john@example.org", "preferred_username": "john@example.org", "given_name": "John", "family_name": "Smith", "zone_info": "America/Winnipeg", "updated_at": time.Date(2025, time.March, 0, 0, 0, 0, 0, time.UTC).Unix(), "email_verified": false, "exp": expiry, "iss": "https://example.org/oauth2/default", } // Marshal header+bytes headerBytes, _ := json.Marshal(header) payloadBytes, _ := json.Marshal(payload) // Ok here's where it gets weird. AWS pads all base64-encoded components, // including the header, payload, and signature (they do however use the URL // encoding format). The following is _not_ specs compliant and should not be // used as an example of how to create + sign JWT. // // We want some test cases involving these JWTs though, so we have to generate // them the "wrong" way to be consistent with what we see from ALBs. headerEncoded := base64.URLEncoding.EncodeToString(headerBytes) payloadEncoded := base64.URLEncoding.EncodeToString(payloadBytes) // Assemble `
.` var buf bytes.Buffer buf.WriteString(headerEncoded) buf.WriteRune(tokens.Period) buf.WriteString(payloadEncoded) // Sign h := sha256.New() _, _ = h.Write(buf.Bytes()) curveBits := key.Curve.Params().BitSize r, s, err := ecdsa.Sign(rand.Reader, key, h.Sum(nil)) require.NoError(t, err, `ecdsa.Sign should succeed`) keyBytes := curveBits / 8 // Curve bits do not need to be a multiple of 8. if curveBits%8 > 0 { keyBytes++ } rBytes := r.Bytes() rBytesPadded := make([]byte, keyBytes) copy(rBytesPadded[keyBytes-len(rBytes):], rBytes) sBytes := s.Bytes() sBytesPadded := make([]byte, keyBytes) copy(sBytesPadded[keyBytes-len(sBytes):], sBytes) signature := append(rBytesPadded, sBytesPadded...) // Encode the signature, also URL-encoded + padding signatureEncoded := base64.URLEncoding.EncodeToString(signature) // Tack on signature to get `
..` buf.WriteRune(tokens.Period) buf.WriteString(signatureEncoded) _, err = jwt.Parse(buf.Bytes(), jwt.WithBase64Encoder(base64.URLEncoding), jwt.WithKey(jwa.ES256(), key)) require.NoError(t, err, `jwt.Parse should succeed`) }) } golang-github-lestrrat-go-jwx-3.0.13/jwt/openid/token_gen.go000066400000000000000000001256211515060566400237750ustar00rootroot00000000000000// Code generated by tools/cmd/genjwt/main.go. DO NOT EDIT. package openid import ( "bytes" "fmt" "sort" "sync" "time" "github.com/lestrrat-go/blackmagic" "github.com/lestrrat-go/jwx/v3/internal/json" "github.com/lestrrat-go/jwx/v3/internal/pool" "github.com/lestrrat-go/jwx/v3/internal/tokens" "github.com/lestrrat-go/jwx/v3/jwt" jwterrs "github.com/lestrrat-go/jwx/v3/jwt/internal/errors" "github.com/lestrrat-go/jwx/v3/jwt/internal/types" ) const ( AddressKey = "address" AudienceKey = "aud" BirthdateKey = "birthdate" EmailKey = "email" EmailVerifiedKey = "email_verified" ExpirationKey = "exp" FamilyNameKey = "family_name" GenderKey = "gender" GivenNameKey = "given_name" IssuedAtKey = "iat" IssuerKey = "iss" JwtIDKey = "jti" LocaleKey = "locale" MiddleNameKey = "middle_name" NameKey = "name" NicknameKey = "nickname" NotBeforeKey = "nbf" PhoneNumberKey = "phone_number" PhoneNumberVerifiedKey = "phone_number_verified" PictureKey = "picture" PreferredUsernameKey = "preferred_username" ProfileKey = "profile" SubjectKey = "sub" UpdatedAtKey = "updated_at" WebsiteKey = "website" ZoneinfoKey = "zoneinfo" ) // stdClaimNames is a list of all standard claim names defined in the JWT specification. var stdClaimNames = []string{AddressKey, AudienceKey, BirthdateKey, EmailKey, EmailVerifiedKey, ExpirationKey, FamilyNameKey, GenderKey, GivenNameKey, IssuedAtKey, IssuerKey, JwtIDKey, LocaleKey, MiddleNameKey, NameKey, NicknameKey, NotBeforeKey, PhoneNumberKey, PhoneNumberVerifiedKey, PictureKey, PreferredUsernameKey, ProfileKey, SubjectKey, UpdatedAtKey, WebsiteKey, ZoneinfoKey} type Token interface { // Address returns the value for "address" field of the token Address() (*AddressClaim, bool) // Audience returns the value for "aud" field of the token Audience() ([]string, bool) // Birthdate returns the value for "birthdate" field of the token Birthdate() (*BirthdateClaim, bool) // Email returns the value for "email" field of the token Email() (string, bool) // EmailVerified returns the value for "email_verified" field of the token EmailVerified() (bool, bool) // Expiration returns the value for "exp" field of the token Expiration() (time.Time, bool) // FamilyName returns the value for "family_name" field of the token FamilyName() (string, bool) // Gender returns the value for "gender" field of the token Gender() (string, bool) // GivenName returns the value for "given_name" field of the token GivenName() (string, bool) // IssuedAt returns the value for "iat" field of the token IssuedAt() (time.Time, bool) // Issuer returns the value for "iss" field of the token Issuer() (string, bool) // JwtID returns the value for "jti" field of the token JwtID() (string, bool) // Locale returns the value for "locale" field of the token Locale() (string, bool) // MiddleName returns the value for "middle_name" field of the token MiddleName() (string, bool) // Name returns the value for "name" field of the token Name() (string, bool) // Nickname returns the value for "nickname" field of the token Nickname() (string, bool) // NotBefore returns the value for "nbf" field of the token NotBefore() (time.Time, bool) // PhoneNumber returns the value for "phone_number" field of the token PhoneNumber() (string, bool) // PhoneNumberVerified returns the value for "phone_number_verified" field of the token PhoneNumberVerified() (bool, bool) // Picture returns the value for "picture" field of the token Picture() (string, bool) // PreferredUsername returns the value for "preferred_username" field of the token PreferredUsername() (string, bool) // Profile returns the value for "profile" field of the token Profile() (string, bool) // Subject returns the value for "sub" field of the token Subject() (string, bool) // UpdatedAt returns the value for "updated_at" field of the token UpdatedAt() (time.Time, bool) // Website returns the value for "website" field of the token Website() (string, bool) // Zoneinfo returns the value for "zoneinfo" field of the token Zoneinfo() (string, bool) // Get is used to extract the value of any claim, including non-standard claims, out of the token. // // The first argument is the name of the claim. The second argument is a pointer // to a variable that will receive the value of the claim. The method returns // an error if the claim does not exist, or if the value cannot be assigned to // the destination variable. Note that a field is considered to "exist" even if // the value is empty-ish (e.g. 0, false, ""), as long as it is explicitly set. // // For standard claims, you can use the corresponding getter method, such as // `Issuer()`, `Subject()`, `Audience()`, `IssuedAt()`, `NotBefore()`, `ExpiresAt()` // // Note that fields of JWS/JWE are NOT accessible through this method. You need // to use `jws.Parse` and `jwe.Parse` to obtain the JWS/JWE message (and NOT // the payload, which presumably is the JWT), and then use their `Get` methods in their respective packages Get(string, any) error // Set assigns a value to the corresponding field in the token. Some // pre-defined fields such as `nbf`, `iat`, `iss` need their values to // be of a specific type. See the other getter methods in this interface // for the types of each of these fields Set(string, any) error // Has returns true if the specified claim has a value, even if // the value is empty-ish (e.g. 0, false, "") as long as it has been // explicitly set. Has(string) bool Remove(string) error // Options returns the per-token options associated with this token. // The options set value will be copied when the token is cloned via `Clone()` // but it will not survive when the token goes through marshaling/unmarshaling // such as `json.Marshal` and `json.Unmarshal` Options() *jwt.TokenOptionSet Clone() (jwt.Token, error) Keys() []string } type stdToken struct { mu *sync.RWMutex dc DecodeCtx // per-object context for decoding options jwt.TokenOptionSet // per-object option address *AddressClaim audience types.StringList // https://tools.ietf.org/html/rfc7519#section-4.1.3 birthdate *BirthdateClaim email *string emailVerified *bool expiration *types.NumericDate // https://tools.ietf.org/html/rfc7519#section-4.1.4 familyName *string gender *string givenName *string issuedAt *types.NumericDate // https://tools.ietf.org/html/rfc7519#section-4.1.6 issuer *string // https://tools.ietf.org/html/rfc7519#section-4.1.1 jwtID *string // https://tools.ietf.org/html/rfc7519#section-4.1.7 locale *string middleName *string name *string nickname *string notBefore *types.NumericDate // https://tools.ietf.org/html/rfc7519#section-4.1.5 phoneNumber *string phoneNumberVerified *bool picture *string preferredUsername *string profile *string subject *string // https://tools.ietf.org/html/rfc7519#section-4.1.2 updatedAt *types.NumericDate website *string zoneinfo *string privateClaims map[string]any } // New creates a standard token, with minimal knowledge of // possible claims. Standard claims include"address", "aud", "birthdate", "email", "email_verified", "exp", "family_name", "gender", "given_name", "iat", "iss", "jti", "locale", "middle_name", "name", "nickname", "nbf", "phone_number", "phone_number_verified", "picture", "preferred_username", "profile", "sub", "updated_at", "website" and "zoneinfo". // Convenience accessors are provided for these standard claims func New() Token { return &stdToken{ mu: &sync.RWMutex{}, privateClaims: make(map[string]any), options: jwt.DefaultOptionSet(), } } func (t *stdToken) Options() *jwt.TokenOptionSet { return &t.options } func (t *stdToken) Has(name string) bool { t.mu.RLock() defer t.mu.RUnlock() switch name { case AddressKey: return t.address != nil case AudienceKey: return t.audience != nil case BirthdateKey: return t.birthdate != nil case EmailKey: return t.email != nil case EmailVerifiedKey: return t.emailVerified != nil case ExpirationKey: return t.expiration != nil case FamilyNameKey: return t.familyName != nil case GenderKey: return t.gender != nil case GivenNameKey: return t.givenName != nil case IssuedAtKey: return t.issuedAt != nil case IssuerKey: return t.issuer != nil case JwtIDKey: return t.jwtID != nil case LocaleKey: return t.locale != nil case MiddleNameKey: return t.middleName != nil case NameKey: return t.name != nil case NicknameKey: return t.nickname != nil case NotBeforeKey: return t.notBefore != nil case PhoneNumberKey: return t.phoneNumber != nil case PhoneNumberVerifiedKey: return t.phoneNumberVerified != nil case PictureKey: return t.picture != nil case PreferredUsernameKey: return t.preferredUsername != nil case ProfileKey: return t.profile != nil case SubjectKey: return t.subject != nil case UpdatedAtKey: return t.updatedAt != nil case WebsiteKey: return t.website != nil case ZoneinfoKey: return t.zoneinfo != nil default: _, ok := t.privateClaims[name] return ok } } func (t *stdToken) Get(name string, dst any) error { t.mu.RLock() defer t.mu.RUnlock() switch name { case AddressKey: if t.address == nil { return jwterrs.ClaimNotFoundError{Name: name} } if err := blackmagic.AssignIfCompatible(dst, t.address); err != nil { return jwterrs.ClaimAssignmentFailedError{Err: err} } return nil case AudienceKey: if t.audience == nil { return jwterrs.ClaimNotFoundError{Name: name} } if err := blackmagic.AssignIfCompatible(dst, t.audience.Get()); err != nil { return jwterrs.ClaimAssignmentFailedError{Err: err} } return nil case BirthdateKey: if t.birthdate == nil { return jwterrs.ClaimNotFoundError{Name: name} } if err := blackmagic.AssignIfCompatible(dst, t.birthdate); err != nil { return jwterrs.ClaimAssignmentFailedError{Err: err} } return nil case EmailKey: if t.email == nil { return jwterrs.ClaimNotFoundError{Name: name} } if err := blackmagic.AssignIfCompatible(dst, *(t.email)); err != nil { return jwterrs.ClaimAssignmentFailedError{Err: err} } return nil case EmailVerifiedKey: if t.emailVerified == nil { return jwterrs.ClaimNotFoundError{Name: name} } if err := blackmagic.AssignIfCompatible(dst, *(t.emailVerified)); err != nil { return jwterrs.ClaimAssignmentFailedError{Err: err} } return nil case ExpirationKey: if t.expiration == nil { return jwterrs.ClaimNotFoundError{Name: name} } if err := blackmagic.AssignIfCompatible(dst, t.expiration.Get()); err != nil { return jwterrs.ClaimAssignmentFailedError{Err: err} } return nil case FamilyNameKey: if t.familyName == nil { return jwterrs.ClaimNotFoundError{Name: name} } if err := blackmagic.AssignIfCompatible(dst, *(t.familyName)); err != nil { return jwterrs.ClaimAssignmentFailedError{Err: err} } return nil case GenderKey: if t.gender == nil { return jwterrs.ClaimNotFoundError{Name: name} } if err := blackmagic.AssignIfCompatible(dst, *(t.gender)); err != nil { return jwterrs.ClaimAssignmentFailedError{Err: err} } return nil case GivenNameKey: if t.givenName == nil { return jwterrs.ClaimNotFoundError{Name: name} } if err := blackmagic.AssignIfCompatible(dst, *(t.givenName)); err != nil { return jwterrs.ClaimAssignmentFailedError{Err: err} } return nil case IssuedAtKey: if t.issuedAt == nil { return jwterrs.ClaimNotFoundError{Name: name} } if err := blackmagic.AssignIfCompatible(dst, t.issuedAt.Get()); err != nil { return jwterrs.ClaimAssignmentFailedError{Err: err} } return nil case IssuerKey: if t.issuer == nil { return jwterrs.ClaimNotFoundError{Name: name} } if err := blackmagic.AssignIfCompatible(dst, *(t.issuer)); err != nil { return jwterrs.ClaimAssignmentFailedError{Err: err} } return nil case JwtIDKey: if t.jwtID == nil { return jwterrs.ClaimNotFoundError{Name: name} } if err := blackmagic.AssignIfCompatible(dst, *(t.jwtID)); err != nil { return jwterrs.ClaimAssignmentFailedError{Err: err} } return nil case LocaleKey: if t.locale == nil { return jwterrs.ClaimNotFoundError{Name: name} } if err := blackmagic.AssignIfCompatible(dst, *(t.locale)); err != nil { return jwterrs.ClaimAssignmentFailedError{Err: err} } return nil case MiddleNameKey: if t.middleName == nil { return jwterrs.ClaimNotFoundError{Name: name} } if err := blackmagic.AssignIfCompatible(dst, *(t.middleName)); err != nil { return jwterrs.ClaimAssignmentFailedError{Err: err} } return nil case NameKey: if t.name == nil { return jwterrs.ClaimNotFoundError{Name: name} } if err := blackmagic.AssignIfCompatible(dst, *(t.name)); err != nil { return jwterrs.ClaimAssignmentFailedError{Err: err} } return nil case NicknameKey: if t.nickname == nil { return jwterrs.ClaimNotFoundError{Name: name} } if err := blackmagic.AssignIfCompatible(dst, *(t.nickname)); err != nil { return jwterrs.ClaimAssignmentFailedError{Err: err} } return nil case NotBeforeKey: if t.notBefore == nil { return jwterrs.ClaimNotFoundError{Name: name} } if err := blackmagic.AssignIfCompatible(dst, t.notBefore.Get()); err != nil { return jwterrs.ClaimAssignmentFailedError{Err: err} } return nil case PhoneNumberKey: if t.phoneNumber == nil { return jwterrs.ClaimNotFoundError{Name: name} } if err := blackmagic.AssignIfCompatible(dst, *(t.phoneNumber)); err != nil { return jwterrs.ClaimAssignmentFailedError{Err: err} } return nil case PhoneNumberVerifiedKey: if t.phoneNumberVerified == nil { return jwterrs.ClaimNotFoundError{Name: name} } if err := blackmagic.AssignIfCompatible(dst, *(t.phoneNumberVerified)); err != nil { return jwterrs.ClaimAssignmentFailedError{Err: err} } return nil case PictureKey: if t.picture == nil { return jwterrs.ClaimNotFoundError{Name: name} } if err := blackmagic.AssignIfCompatible(dst, *(t.picture)); err != nil { return jwterrs.ClaimAssignmentFailedError{Err: err} } return nil case PreferredUsernameKey: if t.preferredUsername == nil { return jwterrs.ClaimNotFoundError{Name: name} } if err := blackmagic.AssignIfCompatible(dst, *(t.preferredUsername)); err != nil { return jwterrs.ClaimAssignmentFailedError{Err: err} } return nil case ProfileKey: if t.profile == nil { return jwterrs.ClaimNotFoundError{Name: name} } if err := blackmagic.AssignIfCompatible(dst, *(t.profile)); err != nil { return jwterrs.ClaimAssignmentFailedError{Err: err} } return nil case SubjectKey: if t.subject == nil { return jwterrs.ClaimNotFoundError{Name: name} } if err := blackmagic.AssignIfCompatible(dst, *(t.subject)); err != nil { return jwterrs.ClaimAssignmentFailedError{Err: err} } return nil case UpdatedAtKey: if t.updatedAt == nil { return jwterrs.ClaimNotFoundError{Name: name} } if err := blackmagic.AssignIfCompatible(dst, t.updatedAt.Get()); err != nil { return jwterrs.ClaimAssignmentFailedError{Err: err} } return nil case WebsiteKey: if t.website == nil { return jwterrs.ClaimNotFoundError{Name: name} } if err := blackmagic.AssignIfCompatible(dst, *(t.website)); err != nil { return jwterrs.ClaimAssignmentFailedError{Err: err} } return nil case ZoneinfoKey: if t.zoneinfo == nil { return jwterrs.ClaimNotFoundError{Name: name} } if err := blackmagic.AssignIfCompatible(dst, *(t.zoneinfo)); err != nil { return jwterrs.ClaimAssignmentFailedError{Err: err} } return nil default: v, ok := t.privateClaims[name] if !ok { return jwterrs.ClaimNotFoundError{Name: name} } if err := blackmagic.AssignIfCompatible(dst, v); err != nil { return jwterrs.ClaimAssignmentFailedError{Err: err} } return nil } } func (t *stdToken) Remove(key string) error { t.mu.Lock() defer t.mu.Unlock() switch key { case AddressKey: t.address = nil case AudienceKey: t.audience = nil case BirthdateKey: t.birthdate = nil case EmailKey: t.email = nil case EmailVerifiedKey: t.emailVerified = nil case ExpirationKey: t.expiration = nil case FamilyNameKey: t.familyName = nil case GenderKey: t.gender = nil case GivenNameKey: t.givenName = nil case IssuedAtKey: t.issuedAt = nil case IssuerKey: t.issuer = nil case JwtIDKey: t.jwtID = nil case LocaleKey: t.locale = nil case MiddleNameKey: t.middleName = nil case NameKey: t.name = nil case NicknameKey: t.nickname = nil case NotBeforeKey: t.notBefore = nil case PhoneNumberKey: t.phoneNumber = nil case PhoneNumberVerifiedKey: t.phoneNumberVerified = nil case PictureKey: t.picture = nil case PreferredUsernameKey: t.preferredUsername = nil case ProfileKey: t.profile = nil case SubjectKey: t.subject = nil case UpdatedAtKey: t.updatedAt = nil case WebsiteKey: t.website = nil case ZoneinfoKey: t.zoneinfo = nil default: delete(t.privateClaims, key) } return nil } func (t *stdToken) Set(name string, value any) error { t.mu.Lock() defer t.mu.Unlock() return t.setNoLock(name, value) } func (t *stdToken) DecodeCtx() DecodeCtx { t.mu.RLock() defer t.mu.RUnlock() return t.dc } func (t *stdToken) SetDecodeCtx(v DecodeCtx) { t.mu.Lock() defer t.mu.Unlock() t.dc = v } func (t *stdToken) setNoLock(name string, value any) error { switch name { case AddressKey: var acceptor AddressClaim if err := acceptor.Accept(value); err != nil { return fmt.Errorf(`invalid value for %s key: %w`, AddressKey, err) } t.address = &acceptor return nil case AudienceKey: var acceptor types.StringList if err := acceptor.Accept(value); err != nil { return fmt.Errorf(`invalid value for %s key: %w`, AudienceKey, err) } t.audience = acceptor return nil case BirthdateKey: var acceptor BirthdateClaim if err := acceptor.Accept(value); err != nil { return fmt.Errorf(`invalid value for %s key: %w`, BirthdateKey, err) } t.birthdate = &acceptor return nil case EmailKey: if v, ok := value.(string); ok { t.email = &v return nil } return fmt.Errorf(`invalid value for %s key: %T`, EmailKey, value) case EmailVerifiedKey: if v, ok := value.(bool); ok { t.emailVerified = &v return nil } return fmt.Errorf(`invalid value for %s key: %T`, EmailVerifiedKey, value) case ExpirationKey: var acceptor types.NumericDate if err := acceptor.Accept(value); err != nil { return fmt.Errorf(`invalid value for %s key: %w`, ExpirationKey, err) } t.expiration = &acceptor return nil case FamilyNameKey: if v, ok := value.(string); ok { t.familyName = &v return nil } return fmt.Errorf(`invalid value for %s key: %T`, FamilyNameKey, value) case GenderKey: if v, ok := value.(string); ok { t.gender = &v return nil } return fmt.Errorf(`invalid value for %s key: %T`, GenderKey, value) case GivenNameKey: if v, ok := value.(string); ok { t.givenName = &v return nil } return fmt.Errorf(`invalid value for %s key: %T`, GivenNameKey, value) case IssuedAtKey: var acceptor types.NumericDate if err := acceptor.Accept(value); err != nil { return fmt.Errorf(`invalid value for %s key: %w`, IssuedAtKey, err) } t.issuedAt = &acceptor return nil case IssuerKey: if v, ok := value.(string); ok { t.issuer = &v return nil } return fmt.Errorf(`invalid value for %s key: %T`, IssuerKey, value) case JwtIDKey: if v, ok := value.(string); ok { t.jwtID = &v return nil } return fmt.Errorf(`invalid value for %s key: %T`, JwtIDKey, value) case LocaleKey: if v, ok := value.(string); ok { t.locale = &v return nil } return fmt.Errorf(`invalid value for %s key: %T`, LocaleKey, value) case MiddleNameKey: if v, ok := value.(string); ok { t.middleName = &v return nil } return fmt.Errorf(`invalid value for %s key: %T`, MiddleNameKey, value) case NameKey: if v, ok := value.(string); ok { t.name = &v return nil } return fmt.Errorf(`invalid value for %s key: %T`, NameKey, value) case NicknameKey: if v, ok := value.(string); ok { t.nickname = &v return nil } return fmt.Errorf(`invalid value for %s key: %T`, NicknameKey, value) case NotBeforeKey: var acceptor types.NumericDate if err := acceptor.Accept(value); err != nil { return fmt.Errorf(`invalid value for %s key: %w`, NotBeforeKey, err) } t.notBefore = &acceptor return nil case PhoneNumberKey: if v, ok := value.(string); ok { t.phoneNumber = &v return nil } return fmt.Errorf(`invalid value for %s key: %T`, PhoneNumberKey, value) case PhoneNumberVerifiedKey: if v, ok := value.(bool); ok { t.phoneNumberVerified = &v return nil } return fmt.Errorf(`invalid value for %s key: %T`, PhoneNumberVerifiedKey, value) case PictureKey: if v, ok := value.(string); ok { t.picture = &v return nil } return fmt.Errorf(`invalid value for %s key: %T`, PictureKey, value) case PreferredUsernameKey: if v, ok := value.(string); ok { t.preferredUsername = &v return nil } return fmt.Errorf(`invalid value for %s key: %T`, PreferredUsernameKey, value) case ProfileKey: if v, ok := value.(string); ok { t.profile = &v return nil } return fmt.Errorf(`invalid value for %s key: %T`, ProfileKey, value) case SubjectKey: if v, ok := value.(string); ok { t.subject = &v return nil } return fmt.Errorf(`invalid value for %s key: %T`, SubjectKey, value) case UpdatedAtKey: var acceptor types.NumericDate if err := acceptor.Accept(value); err != nil { return fmt.Errorf(`invalid value for %s key: %w`, UpdatedAtKey, err) } t.updatedAt = &acceptor return nil case WebsiteKey: if v, ok := value.(string); ok { t.website = &v return nil } return fmt.Errorf(`invalid value for %s key: %T`, WebsiteKey, value) case ZoneinfoKey: if v, ok := value.(string); ok { t.zoneinfo = &v return nil } return fmt.Errorf(`invalid value for %s key: %T`, ZoneinfoKey, value) default: if t.privateClaims == nil { t.privateClaims = map[string]any{} } t.privateClaims[name] = value } return nil } func (t *stdToken) Address() (*AddressClaim, bool) { t.mu.RLock() defer t.mu.RUnlock() if t.address != nil { return t.address, true } return nil, false } func (t *stdToken) Audience() ([]string, bool) { t.mu.RLock() defer t.mu.RUnlock() if t.audience != nil { return t.audience.Get(), true } return nil, false } func (t *stdToken) Birthdate() (*BirthdateClaim, bool) { t.mu.RLock() defer t.mu.RUnlock() if t.birthdate != nil { return t.birthdate, true } return nil, false } func (t *stdToken) Email() (string, bool) { t.mu.RLock() defer t.mu.RUnlock() if t.email != nil { return *(t.email), true } return "", false } func (t *stdToken) EmailVerified() (bool, bool) { t.mu.RLock() defer t.mu.RUnlock() if t.emailVerified != nil { return *(t.emailVerified), true } return false, false } func (t *stdToken) Expiration() (time.Time, bool) { t.mu.RLock() defer t.mu.RUnlock() if t.expiration != nil { return t.expiration.Get(), true } return time.Time{}, false } func (t *stdToken) FamilyName() (string, bool) { t.mu.RLock() defer t.mu.RUnlock() if t.familyName != nil { return *(t.familyName), true } return "", false } func (t *stdToken) Gender() (string, bool) { t.mu.RLock() defer t.mu.RUnlock() if t.gender != nil { return *(t.gender), true } return "", false } func (t *stdToken) GivenName() (string, bool) { t.mu.RLock() defer t.mu.RUnlock() if t.givenName != nil { return *(t.givenName), true } return "", false } func (t *stdToken) IssuedAt() (time.Time, bool) { t.mu.RLock() defer t.mu.RUnlock() if t.issuedAt != nil { return t.issuedAt.Get(), true } return time.Time{}, false } func (t *stdToken) Issuer() (string, bool) { t.mu.RLock() defer t.mu.RUnlock() if t.issuer != nil { return *(t.issuer), true } return "", false } func (t *stdToken) JwtID() (string, bool) { t.mu.RLock() defer t.mu.RUnlock() if t.jwtID != nil { return *(t.jwtID), true } return "", false } func (t *stdToken) Locale() (string, bool) { t.mu.RLock() defer t.mu.RUnlock() if t.locale != nil { return *(t.locale), true } return "", false } func (t *stdToken) MiddleName() (string, bool) { t.mu.RLock() defer t.mu.RUnlock() if t.middleName != nil { return *(t.middleName), true } return "", false } func (t *stdToken) Name() (string, bool) { t.mu.RLock() defer t.mu.RUnlock() if t.name != nil { return *(t.name), true } return "", false } func (t *stdToken) Nickname() (string, bool) { t.mu.RLock() defer t.mu.RUnlock() if t.nickname != nil { return *(t.nickname), true } return "", false } func (t *stdToken) NotBefore() (time.Time, bool) { t.mu.RLock() defer t.mu.RUnlock() if t.notBefore != nil { return t.notBefore.Get(), true } return time.Time{}, false } func (t *stdToken) PhoneNumber() (string, bool) { t.mu.RLock() defer t.mu.RUnlock() if t.phoneNumber != nil { return *(t.phoneNumber), true } return "", false } func (t *stdToken) PhoneNumberVerified() (bool, bool) { t.mu.RLock() defer t.mu.RUnlock() if t.phoneNumberVerified != nil { return *(t.phoneNumberVerified), true } return false, false } func (t *stdToken) Picture() (string, bool) { t.mu.RLock() defer t.mu.RUnlock() if t.picture != nil { return *(t.picture), true } return "", false } func (t *stdToken) PreferredUsername() (string, bool) { t.mu.RLock() defer t.mu.RUnlock() if t.preferredUsername != nil { return *(t.preferredUsername), true } return "", false } func (t *stdToken) Profile() (string, bool) { t.mu.RLock() defer t.mu.RUnlock() if t.profile != nil { return *(t.profile), true } return "", false } func (t *stdToken) Subject() (string, bool) { t.mu.RLock() defer t.mu.RUnlock() if t.subject != nil { return *(t.subject), true } return "", false } func (t *stdToken) UpdatedAt() (time.Time, bool) { t.mu.RLock() defer t.mu.RUnlock() if t.updatedAt != nil { return t.updatedAt.Get(), true } return time.Time{}, false } func (t *stdToken) Website() (string, bool) { t.mu.RLock() defer t.mu.RUnlock() if t.website != nil { return *(t.website), true } return "", false } func (t *stdToken) Zoneinfo() (string, bool) { t.mu.RLock() defer t.mu.RUnlock() if t.zoneinfo != nil { return *(t.zoneinfo), true } return "", false } func (t *stdToken) PrivateClaims() map[string]any { t.mu.RLock() defer t.mu.RUnlock() return t.privateClaims } func (t *stdToken) UnmarshalJSON(buf []byte) error { t.mu.Lock() defer t.mu.Unlock() t.address = nil t.audience = nil t.birthdate = nil t.email = nil t.emailVerified = nil t.expiration = nil t.familyName = nil t.gender = nil t.givenName = nil t.issuedAt = nil t.issuer = nil t.jwtID = nil t.locale = nil t.middleName = nil t.name = nil t.nickname = nil t.notBefore = nil t.phoneNumber = nil t.phoneNumberVerified = nil t.picture = nil t.preferredUsername = nil t.profile = nil t.subject = nil t.updatedAt = nil t.website = nil t.zoneinfo = nil dec := json.NewDecoder(bytes.NewReader(buf)) LOOP: for { tok, err := dec.Token() if err != nil { return fmt.Errorf(`error reading token: %w`, err) } switch tok := tok.(type) { case json.Delim: // Assuming we're doing everything correctly, we should ONLY // get either tokens.OpenCurlyBracket or tokens.CloseCurlyBracket here. if tok == tokens.CloseCurlyBracket { // End of object break LOOP } else if tok != tokens.OpenCurlyBracket { return fmt.Errorf(`expected '%c', but got '%c'`, tokens.OpenCurlyBracket, tok) } case string: // Objects can only have string keys switch tok { case AddressKey: var decoded AddressClaim if err := dec.Decode(&decoded); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, AddressKey, err) } t.address = &decoded case AudienceKey: var decoded types.StringList if err := dec.Decode(&decoded); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, AudienceKey, err) } t.audience = decoded case BirthdateKey: var decoded BirthdateClaim if err := dec.Decode(&decoded); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, BirthdateKey, err) } t.birthdate = &decoded case EmailKey: if err := json.AssignNextStringToken(&t.email, dec); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, EmailKey, err) } case EmailVerifiedKey: var decoded bool if err := dec.Decode(&decoded); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, EmailVerifiedKey, err) } t.emailVerified = &decoded case ExpirationKey: var decoded types.NumericDate if err := dec.Decode(&decoded); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, ExpirationKey, err) } t.expiration = &decoded case FamilyNameKey: if err := json.AssignNextStringToken(&t.familyName, dec); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, FamilyNameKey, err) } case GenderKey: if err := json.AssignNextStringToken(&t.gender, dec); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, GenderKey, err) } case GivenNameKey: if err := json.AssignNextStringToken(&t.givenName, dec); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, GivenNameKey, err) } case IssuedAtKey: var decoded types.NumericDate if err := dec.Decode(&decoded); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, IssuedAtKey, err) } t.issuedAt = &decoded case IssuerKey: if err := json.AssignNextStringToken(&t.issuer, dec); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, IssuerKey, err) } case JwtIDKey: if err := json.AssignNextStringToken(&t.jwtID, dec); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, JwtIDKey, err) } case LocaleKey: if err := json.AssignNextStringToken(&t.locale, dec); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, LocaleKey, err) } case MiddleNameKey: if err := json.AssignNextStringToken(&t.middleName, dec); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, MiddleNameKey, err) } case NameKey: if err := json.AssignNextStringToken(&t.name, dec); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, NameKey, err) } case NicknameKey: if err := json.AssignNextStringToken(&t.nickname, dec); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, NicknameKey, err) } case NotBeforeKey: var decoded types.NumericDate if err := dec.Decode(&decoded); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, NotBeforeKey, err) } t.notBefore = &decoded case PhoneNumberKey: if err := json.AssignNextStringToken(&t.phoneNumber, dec); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, PhoneNumberKey, err) } case PhoneNumberVerifiedKey: var decoded bool if err := dec.Decode(&decoded); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, PhoneNumberVerifiedKey, err) } t.phoneNumberVerified = &decoded case PictureKey: if err := json.AssignNextStringToken(&t.picture, dec); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, PictureKey, err) } case PreferredUsernameKey: if err := json.AssignNextStringToken(&t.preferredUsername, dec); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, PreferredUsernameKey, err) } case ProfileKey: if err := json.AssignNextStringToken(&t.profile, dec); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, ProfileKey, err) } case SubjectKey: if err := json.AssignNextStringToken(&t.subject, dec); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, SubjectKey, err) } case UpdatedAtKey: var decoded types.NumericDate if err := dec.Decode(&decoded); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, UpdatedAtKey, err) } t.updatedAt = &decoded case WebsiteKey: if err := json.AssignNextStringToken(&t.website, dec); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, WebsiteKey, err) } case ZoneinfoKey: if err := json.AssignNextStringToken(&t.zoneinfo, dec); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, ZoneinfoKey, err) } default: if dc := t.dc; dc != nil { if localReg := dc.Registry(); localReg != nil { decoded, err := localReg.Decode(dec, tok) if err == nil { t.setNoLock(tok, decoded) continue } } } decoded, err := registry.Decode(dec, tok) if err == nil { t.setNoLock(tok, decoded) continue } return fmt.Errorf(`could not decode field %s: %w`, tok, err) } default: return fmt.Errorf(`invalid token %T`, tok) } } return nil } func (t *stdToken) Keys() []string { t.mu.RLock() defer t.mu.RUnlock() keys := make([]string, 0, 26+len(t.privateClaims)) if t.address != nil { keys = append(keys, AddressKey) } if t.audience != nil { keys = append(keys, AudienceKey) } if t.birthdate != nil { keys = append(keys, BirthdateKey) } if t.email != nil { keys = append(keys, EmailKey) } if t.emailVerified != nil { keys = append(keys, EmailVerifiedKey) } if t.expiration != nil { keys = append(keys, ExpirationKey) } if t.familyName != nil { keys = append(keys, FamilyNameKey) } if t.gender != nil { keys = append(keys, GenderKey) } if t.givenName != nil { keys = append(keys, GivenNameKey) } if t.issuedAt != nil { keys = append(keys, IssuedAtKey) } if t.issuer != nil { keys = append(keys, IssuerKey) } if t.jwtID != nil { keys = append(keys, JwtIDKey) } if t.locale != nil { keys = append(keys, LocaleKey) } if t.middleName != nil { keys = append(keys, MiddleNameKey) } if t.name != nil { keys = append(keys, NameKey) } if t.nickname != nil { keys = append(keys, NicknameKey) } if t.notBefore != nil { keys = append(keys, NotBeforeKey) } if t.phoneNumber != nil { keys = append(keys, PhoneNumberKey) } if t.phoneNumberVerified != nil { keys = append(keys, PhoneNumberVerifiedKey) } if t.picture != nil { keys = append(keys, PictureKey) } if t.preferredUsername != nil { keys = append(keys, PreferredUsernameKey) } if t.profile != nil { keys = append(keys, ProfileKey) } if t.subject != nil { keys = append(keys, SubjectKey) } if t.updatedAt != nil { keys = append(keys, UpdatedAtKey) } if t.website != nil { keys = append(keys, WebsiteKey) } if t.zoneinfo != nil { keys = append(keys, ZoneinfoKey) } for k := range t.privateClaims { keys = append(keys, k) } return keys } type claimPair struct { Name string Value any } var claimPairPool = sync.Pool{ New: func() any { return make([]claimPair, 0, 26) }, } func getClaimPairList() []claimPair { return claimPairPool.Get().([]claimPair) } func putClaimPairList(list []claimPair) { list = list[:0] claimPairPool.Put(list) } // makePairs creates a list of claimPair objects that are sorted by // their key names. The key names are always their JSON names, and // the values are already JSON encoded. // Because makePairs needs to allocate a slice, it _slows_ down // marshaling of the token to JSON. The upside is that it allows us to // marshal the token keys in a deterministic order. // Do we really need it...? Well, technically we don't, but it's so // much nicer to have this to make the example tests actually work // deterministically. Also if for whatever reason this becomes a // performance issue, we can always/ add a flag to use a more _optimized_ code path. // // The caller is responsible to call putClaimPairList() to return the // allocated slice back to the pool. func (t *stdToken) makePairs() ([]claimPair, error) { pairs := getClaimPairList() if t.address != nil { buf, err := json.Marshal(*(t.address)) if err != nil { return nil, fmt.Errorf(`failed to encode field "address": %w`, err) } pairs = append(pairs, claimPair{Name: AddressKey, Value: buf}) } if t.audience != nil { buf, err := json.MarshalAudience(t.audience, t.options.IsEnabled(jwt.FlattenAudience)) if err != nil { return nil, fmt.Errorf(`failed to encode "aud": %w`, err) } pairs = append(pairs, claimPair{Name: AudienceKey, Value: buf}) } if t.birthdate != nil { buf, err := json.Marshal(*(t.birthdate)) if err != nil { return nil, fmt.Errorf(`failed to encode field "birthdate": %w`, err) } pairs = append(pairs, claimPair{Name: BirthdateKey, Value: buf}) } if t.email != nil { buf, err := json.Marshal(*(t.email)) if err != nil { return nil, fmt.Errorf(`failed to encode field "email": %w`, err) } pairs = append(pairs, claimPair{Name: EmailKey, Value: buf}) } if t.emailVerified != nil { buf, err := json.Marshal(*(t.emailVerified)) if err != nil { return nil, fmt.Errorf(`failed to encode field "email_verified": %w`, err) } pairs = append(pairs, claimPair{Name: EmailVerifiedKey, Value: buf}) } if t.expiration != nil { buf, err := json.Marshal(t.expiration.Unix()) if err != nil { return nil, fmt.Errorf(`failed to encode "exp": %w`, err) } pairs = append(pairs, claimPair{Name: ExpirationKey, Value: buf}) } if t.familyName != nil { buf, err := json.Marshal(*(t.familyName)) if err != nil { return nil, fmt.Errorf(`failed to encode field "family_name": %w`, err) } pairs = append(pairs, claimPair{Name: FamilyNameKey, Value: buf}) } if t.gender != nil { buf, err := json.Marshal(*(t.gender)) if err != nil { return nil, fmt.Errorf(`failed to encode field "gender": %w`, err) } pairs = append(pairs, claimPair{Name: GenderKey, Value: buf}) } if t.givenName != nil { buf, err := json.Marshal(*(t.givenName)) if err != nil { return nil, fmt.Errorf(`failed to encode field "given_name": %w`, err) } pairs = append(pairs, claimPair{Name: GivenNameKey, Value: buf}) } if t.issuedAt != nil { buf, err := json.Marshal(t.issuedAt.Unix()) if err != nil { return nil, fmt.Errorf(`failed to encode "iat": %w`, err) } pairs = append(pairs, claimPair{Name: IssuedAtKey, Value: buf}) } if t.issuer != nil { buf, err := json.Marshal(*(t.issuer)) if err != nil { return nil, fmt.Errorf(`failed to encode field "iss": %w`, err) } pairs = append(pairs, claimPair{Name: IssuerKey, Value: buf}) } if t.jwtID != nil { buf, err := json.Marshal(*(t.jwtID)) if err != nil { return nil, fmt.Errorf(`failed to encode field "jti": %w`, err) } pairs = append(pairs, claimPair{Name: JwtIDKey, Value: buf}) } if t.locale != nil { buf, err := json.Marshal(*(t.locale)) if err != nil { return nil, fmt.Errorf(`failed to encode field "locale": %w`, err) } pairs = append(pairs, claimPair{Name: LocaleKey, Value: buf}) } if t.middleName != nil { buf, err := json.Marshal(*(t.middleName)) if err != nil { return nil, fmt.Errorf(`failed to encode field "middle_name": %w`, err) } pairs = append(pairs, claimPair{Name: MiddleNameKey, Value: buf}) } if t.name != nil { buf, err := json.Marshal(*(t.name)) if err != nil { return nil, fmt.Errorf(`failed to encode field "name": %w`, err) } pairs = append(pairs, claimPair{Name: NameKey, Value: buf}) } if t.nickname != nil { buf, err := json.Marshal(*(t.nickname)) if err != nil { return nil, fmt.Errorf(`failed to encode field "nickname": %w`, err) } pairs = append(pairs, claimPair{Name: NicknameKey, Value: buf}) } if t.notBefore != nil { buf, err := json.Marshal(t.notBefore.Unix()) if err != nil { return nil, fmt.Errorf(`failed to encode "nbf": %w`, err) } pairs = append(pairs, claimPair{Name: NotBeforeKey, Value: buf}) } if t.phoneNumber != nil { buf, err := json.Marshal(*(t.phoneNumber)) if err != nil { return nil, fmt.Errorf(`failed to encode field "phone_number": %w`, err) } pairs = append(pairs, claimPair{Name: PhoneNumberKey, Value: buf}) } if t.phoneNumberVerified != nil { buf, err := json.Marshal(*(t.phoneNumberVerified)) if err != nil { return nil, fmt.Errorf(`failed to encode field "phone_number_verified": %w`, err) } pairs = append(pairs, claimPair{Name: PhoneNumberVerifiedKey, Value: buf}) } if t.picture != nil { buf, err := json.Marshal(*(t.picture)) if err != nil { return nil, fmt.Errorf(`failed to encode field "picture": %w`, err) } pairs = append(pairs, claimPair{Name: PictureKey, Value: buf}) } if t.preferredUsername != nil { buf, err := json.Marshal(*(t.preferredUsername)) if err != nil { return nil, fmt.Errorf(`failed to encode field "preferred_username": %w`, err) } pairs = append(pairs, claimPair{Name: PreferredUsernameKey, Value: buf}) } if t.profile != nil { buf, err := json.Marshal(*(t.profile)) if err != nil { return nil, fmt.Errorf(`failed to encode field "profile": %w`, err) } pairs = append(pairs, claimPair{Name: ProfileKey, Value: buf}) } if t.subject != nil { buf, err := json.Marshal(*(t.subject)) if err != nil { return nil, fmt.Errorf(`failed to encode field "sub": %w`, err) } pairs = append(pairs, claimPair{Name: SubjectKey, Value: buf}) } if t.updatedAt != nil { buf, err := json.Marshal(t.updatedAt.Unix()) if err != nil { return nil, fmt.Errorf(`failed to encode "updated_at": %w`, err) } pairs = append(pairs, claimPair{Name: UpdatedAtKey, Value: buf}) } if t.website != nil { buf, err := json.Marshal(*(t.website)) if err != nil { return nil, fmt.Errorf(`failed to encode field "website": %w`, err) } pairs = append(pairs, claimPair{Name: WebsiteKey, Value: buf}) } if t.zoneinfo != nil { buf, err := json.Marshal(*(t.zoneinfo)) if err != nil { return nil, fmt.Errorf(`failed to encode field "zoneinfo": %w`, err) } pairs = append(pairs, claimPair{Name: ZoneinfoKey, Value: buf}) } for k, v := range t.privateClaims { buf, err := json.Marshal(v) if err != nil { return nil, fmt.Errorf(`failed to encode field %q: %w`, k, err) } pairs = append(pairs, claimPair{Name: k, Value: buf}) } sort.Slice(pairs, func(i, j int) bool { return pairs[i].Name < pairs[j].Name }) return pairs, nil } func (t stdToken) MarshalJSON() ([]byte, error) { buf := pool.BytesBuffer().Get() defer pool.BytesBuffer().Put(buf) pairs, err := t.makePairs() if err != nil { return nil, fmt.Errorf(`failed to make pairs: %w`, err) } buf.WriteByte(tokens.OpenCurlyBracket) for i, pair := range pairs { if i > 0 { buf.WriteByte(tokens.Comma) } fmt.Fprintf(buf, "%q: %s", pair.Name, pair.Value) } buf.WriteByte(tokens.CloseCurlyBracket) ret := make([]byte, buf.Len()) copy(ret, buf.Bytes()) putClaimPairList(pairs) return ret, nil } golang-github-lestrrat-go-jwx-3.0.13/jwt/options.go000066400000000000000000000315541515060566400222420ustar00rootroot00000000000000package jwt import ( "context" "fmt" "strings" "time" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/lestrrat-go/jwx/v3/jwe" "github.com/lestrrat-go/jwx/v3/jwk" "github.com/lestrrat-go/jwx/v3/jws" "github.com/lestrrat-go/option/v2" ) type identInsecureNoSignature struct{} type identKey struct{} type identKeySet struct{} type identTypedClaim struct{} type identVerifyAuto struct{} func toSignOptions(options ...Option) ([]jws.SignOption, error) { soptions := make([]jws.SignOption, 0, len(options)) for _, option := range options { switch option.Ident() { case identInsecureNoSignature{}: soptions = append(soptions, jws.WithInsecureNoSignature()) case identKey{}: var wk withKey if err := option.Value(&wk); err != nil { return nil, fmt.Errorf(`toSignOtpions: failed to convert option value to withKey: %w`, err) } var wksoptions []jws.WithKeySuboption for _, subopt := range wk.options { wksopt, ok := subopt.(jws.WithKeySuboption) if !ok { return nil, fmt.Errorf(`expected optional arguments in jwt.WithKey to be jws.WithKeySuboption, but got %T`, subopt) } wksoptions = append(wksoptions, wksopt) } soptions = append(soptions, jws.WithKey(wk.alg, wk.key, wksoptions...)) case identSignOption{}: var sigOpt jws.SignOption if err := option.Value(&sigOpt); err != nil { return nil, fmt.Errorf(`failed to decode SignOption: %w`, err) } soptions = append(soptions, sigOpt) case identBase64Encoder{}: var enc jws.Base64Encoder if err := option.Value(&enc); err != nil { return nil, fmt.Errorf(`failed to decode Base64Encoder: %w`, err) } soptions = append(soptions, jws.WithBase64Encoder(enc)) } } return soptions, nil } func toEncryptOptions(options ...Option) ([]jwe.EncryptOption, error) { soptions := make([]jwe.EncryptOption, 0, len(options)) for _, option := range options { switch option.Ident() { case identKey{}: var wk withKey if err := option.Value(&wk); err != nil { return nil, fmt.Errorf(`toEncryptOptions: failed to convert option value to withKey: %w`, err) } var wksoptions []jwe.WithKeySuboption for _, subopt := range wk.options { wksopt, ok := subopt.(jwe.WithKeySuboption) if !ok { return nil, fmt.Errorf(`expected optional arguments in jwt.WithKey to be jwe.WithKeySuboption, but got %T`, subopt) } wksoptions = append(wksoptions, wksopt) } soptions = append(soptions, jwe.WithKey(wk.alg, wk.key, wksoptions...)) case identEncryptOption{}: var encOpt jwe.EncryptOption if err := option.Value(&encOpt); err != nil { return nil, fmt.Errorf(`failed to decode EncryptOption: %w`, err) } soptions = append(soptions, encOpt) } } return soptions, nil } func toVerifyOptions(options ...Option) ([]jws.VerifyOption, error) { voptions := make([]jws.VerifyOption, 0, len(options)) for _, option := range options { switch option.Ident() { case identKey{}: var wk withKey if err := option.Value(&wk); err != nil { return nil, fmt.Errorf(`toVerifyOptions: failed to convert option value to withKey: %w`, err) } var wksoptions []jws.WithKeySuboption for _, subopt := range wk.options { wksopt, ok := subopt.(jws.WithKeySuboption) if !ok { return nil, fmt.Errorf(`expected optional arguments in jwt.WithKey to be jws.WithKeySuboption, but got %T`, subopt) } wksoptions = append(wksoptions, wksopt) } voptions = append(voptions, jws.WithKey(wk.alg, wk.key, wksoptions...)) case identKeySet{}: var wks withKeySet if err := option.Value(&wks); err != nil { return nil, fmt.Errorf(`failed to convert option value to withKeySet: %w`, err) } var wkssoptions []jws.WithKeySetSuboption for _, subopt := range wks.options { wkssopt, ok := subopt.(jws.WithKeySetSuboption) if !ok { return nil, fmt.Errorf(`expected optional arguments in jwt.WithKey to be jws.WithKeySetSuboption, but got %T`, subopt) } wkssoptions = append(wkssoptions, wkssopt) } voptions = append(voptions, jws.WithKeySet(wks.set, wkssoptions...)) case identVerifyAuto{}: var vo jws.VerifyOption if err := option.Value(&vo); err != nil { return nil, fmt.Errorf(`failed to decode VerifyOption: %w`, err) } voptions = append(voptions, vo) case identKeyProvider{}: var kp jws.KeyProvider if err := option.Value(&kp); err != nil { return nil, fmt.Errorf(`failed to decode KeyProvider: %w`, err) } voptions = append(voptions, jws.WithKeyProvider(kp)) case identBase64Encoder{}: var enc jws.Base64Encoder if err := option.Value(&enc); err != nil { return nil, fmt.Errorf(`failed to decode Base64Encoder: %w`, err) } voptions = append(voptions, jws.WithBase64Encoder(enc)) case identContext{}: var ctx context.Context if err := option.Value(&ctx); err != nil { return nil, fmt.Errorf(`failed to decode Context: %w`, err) } voptions = append(voptions, jws.WithContext(ctx)) default: return nil, fmt.Errorf(`invalid jws.VerifyOption %q passed`, `With`+strings.TrimPrefix(fmt.Sprintf(`%T`, option.Ident()), `jws.ident`)) } } return voptions, nil } type withKey struct { alg jwa.KeyAlgorithm key any options []Option } // WithKey is a multipurpose option. It can be used for either jwt.Sign, jwt.Parse (and // its siblings), and jwt.Serializer methods. For signatures, please see the documentation // for `jws.WithKey` for more details. For encryption, please see the documentation // for `jwe.WithKey`. // // It is the caller's responsibility to match the suboptions to the operation that they // are performing. For example, you are not allowed to do this, because the operation // is to generate a signature, and yet you are passing options for jwe: // // jwt.Sign(token, jwt.WithKey(alg, key, jweOptions...)) // // In the above example, the creation of the option via `jwt.WithKey()` will work, but // when `jwt.Sign()` is called, the fact that you passed JWE suboptions will be // detected, and an error will occur. func WithKey(alg jwa.KeyAlgorithm, key any, suboptions ...Option) SignEncryptParseOption { return &signEncryptParseOption{option.New(identKey{}, &withKey{ alg: alg, key: key, options: suboptions, })} } type withKeySet struct { set jwk.Set options []any } // WithKeySet forces the Parse method to verify the JWT message // using one of the keys in the given key set. // // Key IDs (`kid`) in the JWS message and the JWK in the given `jwk.Set` // must match in order for the key to be a candidate to be used for // verification. // // This is for security reasons. If you must disable it, you can do so by // specifying `jws.WithRequireKid(false)` in the suboptions. But we don't // recommend it unless you know exactly what the security implications are // // When using this option, keys MUST have a proper 'alg' field // set. This is because we need to know the exact algorithm that // you (the user) wants to use to verify the token. We do NOT // trust the token's headers, because they can easily be tampered with. // // However, there _is_ a workaround if you do understand the risks // of allowing a library to automatically choose a signature verification strategy, // and you do not mind the verification process having to possibly // attempt using multiple times before succeeding to verify. See // `jws.InferAlgorithmFromKey` option // // If you have only one key in the set, and are sure you want to // use that key, you can use the `jwt.WithDefaultKey` option. func WithKeySet(set jwk.Set, options ...any) ParseOption { return &parseOption{option.New(identKeySet{}, &withKeySet{ set: set, options: options, })} } // WithIssuer specifies that expected issuer value. If not specified, // the value of issuer is not verified at all. func WithIssuer(s string) ValidateOption { return WithValidator(issuerClaimValueIs(s)) } // WithSubject specifies that expected subject value. If not specified, // the value of subject is not verified at all. func WithSubject(s string) ValidateOption { return WithValidator(ClaimValueIs(SubjectKey, s)) } // WithJwtID specifies that expected jti value. If not specified, // the value of jti is not verified at all. func WithJwtID(s string) ValidateOption { return WithValidator(ClaimValueIs(JwtIDKey, s)) } // 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 `aud` is not // verified at all. func WithAudience(s string) ValidateOption { return WithValidator(audienceClaimContainsString(s)) } // WithClaimValue specifies the expected value for a given claim func WithClaimValue(name string, v any) ValidateOption { return WithValidator(ClaimValueIs(name, v)) } // WithTypedClaim allows a private claim to be parsed into the object type of // your choice. It works much like the RegisterCustomField, but the effect // is only applicable to the jwt.Parse function call which receives this option. // // While this can be extremely useful, this option should be used with caution: // There are many caveats that your entire team/user-base needs to be aware of, // and therefore in general its use is discouraged. Only use it when you know // what you are doing, and you document its use clearly for others. // // First and foremost, this is a "per-object" option. Meaning that given the same // serialized format, it is possible to generate two objects whose internal // representations may differ. That is, if you parse one _WITH_ the option, // and the other _WITHOUT_, their internal representation may completely differ. // This could potentially lead to problems. // // Second, specifying this option will slightly slow down the decoding process // as it needs to consult multiple definitions sources (global and local), so // be careful if you are decoding a large number of tokens, as the effects will stack up. // // Finally, this option will also NOT work unless the tokens themselves support such // parsing mechanism. For example, while tokens obtained from `jwt.New()` and // `openid.New()` will respect this option, if you provide your own custom // token type, it will need to implement the TokenWithDecodeCtx interface. func WithTypedClaim(name string, object any) ParseOption { return &parseOption{option.New(identTypedClaim{}, claimPair{Name: name, Value: object})} } // WithRequiredClaim specifies that the claim identified the given name // must exist in the token. Only the existence of the claim is checked: // the actual value associated with that field is not checked. func WithRequiredClaim(name string) ValidateOption { return WithValidator(IsRequired(name)) } // WithMaxDelta specifies that given two claims `c1` and `c2` that represent time, the difference in // time.Duration must be less than equal to the value specified by `d`. If `c1` or `c2` is the // empty string, the current time (as computed by `time.Now` or the object passed via // `WithClock()`) is used for the comparison. // // `c1` and `c2` are also assumed to be required, therefore not providing either claim in the // token will result in an error. // // Because there is no way of reliably knowing how to parse private claims, we currently only // support `iat`, `exp`, and `nbf` claims. // // If the empty string is passed to c1 or c2, then the current time (as calculated by time.Now() or // the clock object provided via WithClock()) is used. // // For example, in order to specify that `exp` - `iat` should be less than 10*time.Second, you would write // // jwt.Validate(token, jwt.WithMaxDelta(10*time.Second, jwt.ExpirationKey, jwt.IssuedAtKey)) // // If AcceptableSkew of 2 second is specified, the above will return valid for any value of // `exp` - `iat` between 8 (10-2) and 12 (10+2). func WithMaxDelta(dur time.Duration, c1, c2 string) ValidateOption { return WithValidator(MaxDeltaIs(c1, c2, dur)) } // WithMinDelta is almost exactly the same as WithMaxDelta, but force validation to fail if // the difference between time claims are less than dur. // // For example, in order to specify that `exp` - `iat` should be greater than 10*time.Second, you would write // // jwt.Validate(token, jwt.WithMinDelta(10*time.Second, jwt.ExpirationKey, jwt.IssuedAtKey)) // // The validation would fail if the difference is less than 10 seconds. func WithMinDelta(dur time.Duration, c1, c2 string) ValidateOption { return WithValidator(MinDeltaIs(c1, c2, dur)) } // WithVerifyAuto specifies that the JWS verification should be attempted // by using the data available in the JWS message. Currently only verification // method available is to use the keys available in the JWKS URL pointed // in the `jku` field. // // Please read the documentation for `jws.VerifyAuto` for more details. func WithVerifyAuto(f jwk.Fetcher, options ...jwk.FetchOption) ParseOption { return &parseOption{option.New(identVerifyAuto{}, jws.WithVerifyAuto(f, options...))} } func WithInsecureNoSignature() SignOption { return &signEncryptParseOption{option.New(identInsecureNoSignature{}, (any)(nil))} } golang-github-lestrrat-go-jwx-3.0.13/jwt/options.yaml000066400000000000000000000255231515060566400225760ustar00rootroot00000000000000package_name: jwt output: jwt/options_gen.go interfaces: - name: GlobalOption comment: | GlobalOption describes an Option that can be passed to `Settings()`. - name: EncryptOption comment: | EncryptOption describes an Option that can be passed to (jwt.Serializer).Encrypt - name: ParseOption methods: - parseOption - readFileOption comment: | ParseOption describes an Option that can be passed to `jwt.Parse()`. ParseOption also implements ReadFileOption, therefore it may be safely pass them to `jwt.ReadFile()` - name: SignOption comment: | SignOption describes an Option that can be passed to `jwt.Sign()` or (jwt.Serializer).Sign - name: SignParseOption methods: - signOption - parseOption - readFileOption comment: | SignParseOption describes an Option that can be passed to both `jwt.Sign()` or `jwt.Parse()` - name: SignEncryptParseOption methods: - parseOption - encryptOption - readFileOption - signOption comment: | SignEncryptParseOption describes an Option that can be passed to both `jwt.Sign()` or `jwt.Parse()` - name: ValidateOption methods: - parseOption - readFileOption - validateOption comment: | ValidateOption describes an Option that can be passed to Validate(). ValidateOption also implements ParseOption, therefore it may be safely passed to `Parse()` (and thus `jwt.ReadFile()`) - name: ReadFileOption comment: | ReadFileOption is a type of `Option` that can be passed to `jws.ReadFile` - name: GlobalValidateOption methods: - globalOption - parseOption - readFileOption - validateOption comment: | GlobalValidateOption describes an Option that can be passed to `jwt.Settings()` and `jwt.Validate()` options: - ident: AcceptableSkew interface: ValidateOption argument_type: time.Duration comment: | WithAcceptableSkew specifies the duration in which exp, iat and nbf claims may differ by. This value should be positive - ident: Truncation interface: GlobalValidateOption argument_type: time.Duration comment: | WithTruncation specifies the amount that should be used when truncating time values used during time-based validation routines, and by default this is disabled. In v2 of this library, time values were truncated down to second accuracy, i.e. 1.0000001 seconds is truncated to 1 second. To restore this behavior, set this value to `time.Second` Since v3, this option can be passed to `jwt.Settings()` to set the truncation value globally, as well as per invocation of `jwt.Validate()` - ident: Clock interface: ValidateOption argument_type: Clock comment: | WithClock specifies the `Clock` to be used when verifying exp, iat and nbf claims. - ident: Context interface: ValidateOption argument_type: context.Context comment: | WithContext allows you to specify a context.Context object to be used with `jwt.Validate()` option. Please be aware that in the next major release of this library, `jwt.Validate()`'s signature will change to include an explicit `context.Context` object. - ident: ResetValidators interface: ValidateOption argument_type: bool comment: | WithResetValidators specifies that the default validators should be reset before applying the custom validators. By default `jwt.Validate()` checks for the validity of JWT by checking `exp`, `nbf`, and `iat`, even when you specify more validators through other options. You SHOULD NOT use this option unless you know exactly what you are doing, as this will pose significant security issues when used incorrectly. Using this option with the value `true` will remove all default checks, and will expect you to specify validators as options. This is useful when you want to skip the default validators and only use specific validators, such as for https://openid.net/specs/openid-connect-rpinitiated-1_0.html, where the token could be accepted even if the token is expired. If you set this option to true and you do not specify any validators, `jwt.Validate()` will return an error. The default value is `false` (`iat`, `exp`, and `nbf` are automatically checked). - ident: FlattenAudience interface: GlobalOption argument_type: bool comment: | WithFlattenAudience specifies the the `jwt.FlattenAudience` option on every token defaults to enabled. You can still disable this on a per-object basis using the `jwt.Options().Disable(jwt.FlattenAudience)` method call. See the documentation for `jwt.TokenOptionSet`, `(jwt.Token).Options`, and `jwt.FlattenAudience` for more details - ident: FormKey interface: ParseOption argument_type: string comment: | WithFormKey is used to specify header keys to search for tokens. While the type system allows this option to be passed to jwt.Parse() directly, doing so will have no effect. Only use it for HTTP request parsing functions - ident: HeaderKey interface: ParseOption argument_type: string comment: | WithHeaderKey is used to specify header keys to search for tokens. While the type system allows this option to be passed to `jwt.Parse()` directly, doing so will have no effect. Only use it for HTTP request parsing functions - ident: Cookie interface: ParseOption argument_type: '**http.Cookie' comment: | WithCookie is used to specify a variable to store the cookie used when `jwt.ParseCookie()` is called. This allows you to inspect the cookie for additional information after a successful parsing of the JWT token stored in the cookie. While the type system allows this option to be passed to `jwt.Parse()` directly, doing so will have no effect. Only use it for HTTP request parsing functions - ident: CookieKey interface: ParseOption argument_type: string comment: | WithCookieKey is used to specify cookie keys to search for tokens. While the type system allows this option to be passed to `jwt.Parse()` directly, doing so will have no effect. Only use it for HTTP request parsing functions - ident: Token interface: ParseOption argument_type: Token comment: | WithToken specifies the token instance in which the resulting JWT is stored when parsing JWT tokens - ident: Validate interface: ParseOption argument_type: bool comment: | WithValidate is passed to `Parse()` method to denote that the validation of the JWT token should be performed (or not) after a successful parsing of the incoming payload. This option is enabled by default. If you would like disable validation, you must use `jwt.WithValidate(false)` or use `jwt.ParseInsecure()` - ident: Verify interface: ParseOption argument_type: bool comment: | WithVerify is passed to `Parse()` method to denote that the signature verification should be performed after a successful deserialization of the incoming payload. This option is enabled by default. If you do not provide any verification key sources, `jwt.Parse()` would return an error. If you would like to only parse the JWT payload and not verify it, you must use `jwt.WithVerify(false)` or use `jwt.ParseInsecure()` - ident: KeyProvider interface: ParseOption argument_type: jws.KeyProvider comment: | WithKeyProvider allows users to specify an object to provide keys to sign/verify tokens using arbitrary code. Please read the documentation for `jws.KeyProvider` in the `jws` package for details on how this works. - ident: Pedantic interface: ParseOption argument_type: bool comment: | WithPedantic enables pedantic mode for parsing JWTs. Currently this only applies to checking for the correct `typ` and/or `cty` when necessary. - ident: EncryptOption interface: EncryptOption argument_type: jwe.EncryptOption comment: | WithEncryptOption provides an escape hatch for cases where extra options to `(jws.Serializer).Encrypt()` must be specified when using `jwt.Sign()`. Normally you do not need to use this. - ident: SignOption interface: SignOption argument_type: jws.SignOption comment: | WithSignOption provides an escape hatch for cases where extra options to `jws.Sign()` must be specified when using `jwt.Sign()`. Normally you do not need to use this. - ident: Validator interface: ValidateOption argument_type: Validator comment: | WithValidator validates the token with the given Validator. For example, in order to validate tokens that are only valid during August, you would write validator := jwt.ValidatorFunc(func(_ context.Context, t jwt.Token) error { if time.Now().Month() != 8 { return fmt.Errorf(`tokens are only valid during August!`) } return nil }) err := jwt.Validate(token, jwt.WithValidator(validator)) - ident: FS interface: ReadFileOption argument_type: fs.FS comment: | WithFS specifies the source `fs.FS` object to read the file from. - ident: NumericDateParsePrecision interface: GlobalOption argument_type: int comment: | WithNumericDateParsePrecision sets the precision up to which the library uses to parse fractional dates found in the numeric date fields. Default is 0 (second, no fractions), max is 9 (nanosecond) - ident: NumericDateFormatPrecision interface: GlobalOption argument_type: int comment: | WithNumericDateFormatPrecision sets the precision up to which the library uses to format fractional dates found in the numeric date fields. Default is 0 (second, no fractions), max is 9 (nanosecond) - ident: NumericDateParsePedantic interface: GlobalOption argument_type: bool comment: | WithNumericDateParsePedantic specifies if the parser should behave in a pedantic manner when parsing numeric dates. Normally this library attempts to interpret timestamps as a numeric value representing number of seconds (with an optional fractional part), but if that fails it tries to parse using a RFC3339 parser. This allows us to parse payloads from non-conforming servers. However, when you set WithNumericDateParePedantic to `true`, the RFC3339 parser is not tried, and we expect a numeric value strictly - ident: Base64Encoder interface: SignParseOption argument_type: jws.Base64Encoder comment: | WithBase64Encoder specifies the base64 encoder to use for signing tokens and verifying JWS signatures.golang-github-lestrrat-go-jwx-3.0.13/jwt/options_gen.go000066400000000000000000000347531515060566400230770ustar00rootroot00000000000000// Code generated by tools/cmd/genoptions/main.go. DO NOT EDIT. package jwt import ( "context" "io/fs" "net/http" "time" "github.com/lestrrat-go/jwx/v3/jwe" "github.com/lestrrat-go/jwx/v3/jws" "github.com/lestrrat-go/option/v2" ) type Option = option.Interface // EncryptOption describes an Option that can be passed to (jwt.Serializer).Encrypt type EncryptOption interface { Option encryptOption() } type encryptOption struct { Option } func (*encryptOption) encryptOption() {} // GlobalOption describes an Option that can be passed to `Settings()`. type GlobalOption interface { Option globalOption() } type globalOption struct { Option } func (*globalOption) globalOption() {} // GlobalValidateOption describes an Option that can be passed to `jwt.Settings()` and `jwt.Validate()` type GlobalValidateOption interface { Option globalOption() parseOption() readFileOption() validateOption() } type globalValidateOption struct { Option } func (*globalValidateOption) globalOption() {} func (*globalValidateOption) parseOption() {} func (*globalValidateOption) readFileOption() {} func (*globalValidateOption) validateOption() {} // ParseOption describes an Option that can be passed to `jwt.Parse()`. // ParseOption also implements ReadFileOption, therefore it may be // safely pass them to `jwt.ReadFile()` type ParseOption interface { Option parseOption() readFileOption() } type parseOption struct { Option } func (*parseOption) parseOption() {} func (*parseOption) readFileOption() {} // ReadFileOption is a type of `Option` that can be passed to `jws.ReadFile` type ReadFileOption interface { Option readFileOption() } type readFileOption struct { Option } func (*readFileOption) readFileOption() {} // SignEncryptParseOption describes an Option that can be passed to both `jwt.Sign()` or // `jwt.Parse()` type SignEncryptParseOption interface { Option parseOption() encryptOption() readFileOption() signOption() } type signEncryptParseOption struct { Option } func (*signEncryptParseOption) parseOption() {} func (*signEncryptParseOption) encryptOption() {} func (*signEncryptParseOption) readFileOption() {} func (*signEncryptParseOption) signOption() {} // SignOption describes an Option that can be passed to `jwt.Sign()` or // (jwt.Serializer).Sign type SignOption interface { Option signOption() } type signOption struct { Option } func (*signOption) signOption() {} // SignParseOption describes an Option that can be passed to both `jwt.Sign()` or // `jwt.Parse()` type SignParseOption interface { Option signOption() parseOption() readFileOption() } type signParseOption struct { Option } func (*signParseOption) signOption() {} func (*signParseOption) parseOption() {} func (*signParseOption) readFileOption() {} // ValidateOption describes an Option that can be passed to Validate(). // ValidateOption also implements ParseOption, therefore it may be // safely passed to `Parse()` (and thus `jwt.ReadFile()`) type ValidateOption interface { Option parseOption() readFileOption() validateOption() } type validateOption struct { Option } func (*validateOption) parseOption() {} func (*validateOption) readFileOption() {} func (*validateOption) validateOption() {} type identAcceptableSkew struct{} type identBase64Encoder struct{} type identClock struct{} type identContext struct{} type identCookie struct{} type identCookieKey struct{} type identEncryptOption struct{} type identFS struct{} type identFlattenAudience struct{} type identFormKey struct{} type identHeaderKey struct{} type identKeyProvider struct{} type identNumericDateFormatPrecision struct{} type identNumericDateParsePedantic struct{} type identNumericDateParsePrecision struct{} type identPedantic struct{} type identResetValidators struct{} type identSignOption struct{} type identToken struct{} type identTruncation struct{} type identValidate struct{} type identValidator struct{} type identVerify struct{} func (identAcceptableSkew) String() string { return "WithAcceptableSkew" } func (identBase64Encoder) String() string { return "WithBase64Encoder" } func (identClock) String() string { return "WithClock" } func (identContext) String() string { return "WithContext" } func (identCookie) String() string { return "WithCookie" } func (identCookieKey) String() string { return "WithCookieKey" } func (identEncryptOption) String() string { return "WithEncryptOption" } func (identFS) String() string { return "WithFS" } func (identFlattenAudience) String() string { return "WithFlattenAudience" } func (identFormKey) String() string { return "WithFormKey" } func (identHeaderKey) String() string { return "WithHeaderKey" } func (identKeyProvider) String() string { return "WithKeyProvider" } func (identNumericDateFormatPrecision) String() string { return "WithNumericDateFormatPrecision" } func (identNumericDateParsePedantic) String() string { return "WithNumericDateParsePedantic" } func (identNumericDateParsePrecision) String() string { return "WithNumericDateParsePrecision" } func (identPedantic) String() string { return "WithPedantic" } func (identResetValidators) String() string { return "WithResetValidators" } func (identSignOption) String() string { return "WithSignOption" } func (identToken) String() string { return "WithToken" } func (identTruncation) String() string { return "WithTruncation" } func (identValidate) String() string { return "WithValidate" } func (identValidator) String() string { return "WithValidator" } func (identVerify) String() string { return "WithVerify" } // WithAcceptableSkew specifies the duration in which exp, iat and nbf // claims may differ by. This value should be positive func WithAcceptableSkew(v time.Duration) ValidateOption { return &validateOption{option.New(identAcceptableSkew{}, v)} } // WithBase64Encoder specifies the base64 encoder to use for signing // tokens and verifying JWS signatures. func WithBase64Encoder(v jws.Base64Encoder) SignParseOption { return &signParseOption{option.New(identBase64Encoder{}, v)} } // WithClock specifies the `Clock` to be used when verifying // exp, iat and nbf claims. func WithClock(v Clock) ValidateOption { return &validateOption{option.New(identClock{}, v)} } // WithContext allows you to specify a context.Context object to be used // with `jwt.Validate()` option. // // Please be aware that in the next major release of this library, // `jwt.Validate()`'s signature will change to include an explicit // `context.Context` object. func WithContext(v context.Context) ValidateOption { return &validateOption{option.New(identContext{}, v)} } // WithCookie is used to specify a variable to store the cookie used when `jwt.ParseCookie()` // is called. This allows you to inspect the cookie for additional information after a successful // parsing of the JWT token stored in the cookie. // // While the type system allows this option to be passed to `jwt.Parse()` directly, // doing so will have no effect. Only use it for HTTP request parsing functions func WithCookie(v **http.Cookie) ParseOption { return &parseOption{option.New(identCookie{}, v)} } // WithCookieKey is used to specify cookie keys to search for tokens. // // While the type system allows this option to be passed to `jwt.Parse()` directly, // doing so will have no effect. Only use it for HTTP request parsing functions func WithCookieKey(v string) ParseOption { return &parseOption{option.New(identCookieKey{}, v)} } // WithEncryptOption provides an escape hatch for cases where extra options to // `(jws.Serializer).Encrypt()` must be specified when using `jwt.Sign()`. Normally you do not // need to use this. func WithEncryptOption(v jwe.EncryptOption) EncryptOption { return &encryptOption{option.New(identEncryptOption{}, v)} } // WithFS specifies the source `fs.FS` object to read the file from. func WithFS(v fs.FS) ReadFileOption { return &readFileOption{option.New(identFS{}, v)} } // WithFlattenAudience specifies the the `jwt.FlattenAudience` option on // every token defaults to enabled. You can still disable this on a per-object // basis using the `jwt.Options().Disable(jwt.FlattenAudience)` method call. // // See the documentation for `jwt.TokenOptionSet`, `(jwt.Token).Options`, and // `jwt.FlattenAudience` for more details func WithFlattenAudience(v bool) GlobalOption { return &globalOption{option.New(identFlattenAudience{}, v)} } // WithFormKey is used to specify header keys to search for tokens. // // While the type system allows this option to be passed to jwt.Parse() directly, // doing so will have no effect. Only use it for HTTP request parsing functions func WithFormKey(v string) ParseOption { return &parseOption{option.New(identFormKey{}, v)} } // WithHeaderKey is used to specify header keys to search for tokens. // // While the type system allows this option to be passed to `jwt.Parse()` directly, // doing so will have no effect. Only use it for HTTP request parsing functions func WithHeaderKey(v string) ParseOption { return &parseOption{option.New(identHeaderKey{}, v)} } // WithKeyProvider allows users to specify an object to provide keys to // sign/verify tokens using arbitrary code. Please read the documentation // for `jws.KeyProvider` in the `jws` package for details on how this works. func WithKeyProvider(v jws.KeyProvider) ParseOption { return &parseOption{option.New(identKeyProvider{}, v)} } // WithNumericDateFormatPrecision sets the precision up to which the // library uses to format fractional dates found in the numeric date // fields. Default is 0 (second, no fractions), max is 9 (nanosecond) func WithNumericDateFormatPrecision(v int) GlobalOption { return &globalOption{option.New(identNumericDateFormatPrecision{}, v)} } // WithNumericDateParsePedantic specifies if the parser should behave // in a pedantic manner when parsing numeric dates. Normally this library // attempts to interpret timestamps as a numeric value representing // number of seconds (with an optional fractional part), but if that fails // it tries to parse using a RFC3339 parser. This allows us to parse // payloads from non-conforming servers. // // However, when you set WithNumericDateParePedantic to `true`, the // RFC3339 parser is not tried, and we expect a numeric value strictly func WithNumericDateParsePedantic(v bool) GlobalOption { return &globalOption{option.New(identNumericDateParsePedantic{}, v)} } // WithNumericDateParsePrecision sets the precision up to which the // library uses to parse fractional dates found in the numeric date // fields. Default is 0 (second, no fractions), max is 9 (nanosecond) func WithNumericDateParsePrecision(v int) GlobalOption { return &globalOption{option.New(identNumericDateParsePrecision{}, v)} } // WithPedantic enables pedantic mode for parsing JWTs. Currently this only // applies to checking for the correct `typ` and/or `cty` when necessary. func WithPedantic(v bool) ParseOption { return &parseOption{option.New(identPedantic{}, v)} } // WithResetValidators specifies that the default validators should be // reset before applying the custom validators. By default `jwt.Validate()` // checks for the validity of JWT by checking `exp`, `nbf`, and `iat`, even // when you specify more validators through other options. // // You SHOULD NOT use this option unless you know exactly what you are doing, // as this will pose significant security issues when used incorrectly. // // Using this option with the value `true` will remove all default checks, // and will expect you to specify validators as options. This is useful when you // want to skip the default validators and only use specific validators, such as // for https://openid.net/specs/openid-connect-rpinitiated-1_0.html, where // the token could be accepted even if the token is expired. // // If you set this option to true and you do not specify any validators, // `jwt.Validate()` will return an error. // // The default value is `false` (`iat`, `exp`, and `nbf` are automatically checked). func WithResetValidators(v bool) ValidateOption { return &validateOption{option.New(identResetValidators{}, v)} } // WithSignOption provides an escape hatch for cases where extra options to // `jws.Sign()` must be specified when using `jwt.Sign()`. Normally you do not // need to use this. func WithSignOption(v jws.SignOption) SignOption { return &signOption{option.New(identSignOption{}, v)} } // WithToken specifies the token instance in which the resulting JWT is stored // when parsing JWT tokens func WithToken(v Token) ParseOption { return &parseOption{option.New(identToken{}, v)} } // WithTruncation specifies the amount that should be used when // truncating time values used during time-based validation routines, // and by default this is disabled. // // In v2 of this library, time values were truncated down to second accuracy, i.e. // 1.0000001 seconds is truncated to 1 second. To restore this behavior, set // this value to `time.Second` // // Since v3, this option can be passed to `jwt.Settings()` to set the truncation // value globally, as well as per invocation of `jwt.Validate()` func WithTruncation(v time.Duration) GlobalValidateOption { return &globalValidateOption{option.New(identTruncation{}, v)} } // WithValidate is passed to `Parse()` method to denote that the // validation of the JWT token should be performed (or not) after // a successful parsing of the incoming payload. // // This option is enabled by default. // // If you would like disable validation, // you must use `jwt.WithValidate(false)` or use `jwt.ParseInsecure()` func WithValidate(v bool) ParseOption { return &parseOption{option.New(identValidate{}, v)} } // WithValidator validates the token with the given Validator. // // For example, in order to validate tokens that are only valid during August, you would write // // validator := jwt.ValidatorFunc(func(_ context.Context, t jwt.Token) error { // if time.Now().Month() != 8 { // return fmt.Errorf(`tokens are only valid during August!`) // } // return nil // }) // err := jwt.Validate(token, jwt.WithValidator(validator)) func WithValidator(v Validator) ValidateOption { return &validateOption{option.New(identValidator{}, v)} } // WithVerify is passed to `Parse()` method to denote that the // signature verification should be performed after a successful // deserialization of the incoming payload. // // This option is enabled by default. // // If you do not provide any verification key sources, `jwt.Parse()` // would return an error. // // If you would like to only parse the JWT payload and not verify it, // you must use `jwt.WithVerify(false)` or use `jwt.ParseInsecure()` func WithVerify(v bool) ParseOption { return &parseOption{option.New(identVerify{}, v)} } golang-github-lestrrat-go-jwx-3.0.13/jwt/options_gen_test.go000066400000000000000000000032521515060566400241240ustar00rootroot00000000000000// Code generated by tools/cmd/genoptions/main.go. DO NOT EDIT. package jwt import ( "testing" "github.com/stretchr/testify/require" ) func TestOptionIdent(t *testing.T) { require.Equal(t, "WithAcceptableSkew", identAcceptableSkew{}.String()) require.Equal(t, "WithBase64Encoder", identBase64Encoder{}.String()) require.Equal(t, "WithClock", identClock{}.String()) require.Equal(t, "WithContext", identContext{}.String()) require.Equal(t, "WithCookie", identCookie{}.String()) require.Equal(t, "WithCookieKey", identCookieKey{}.String()) require.Equal(t, "WithEncryptOption", identEncryptOption{}.String()) require.Equal(t, "WithFS", identFS{}.String()) require.Equal(t, "WithFlattenAudience", identFlattenAudience{}.String()) require.Equal(t, "WithFormKey", identFormKey{}.String()) require.Equal(t, "WithHeaderKey", identHeaderKey{}.String()) require.Equal(t, "WithKeyProvider", identKeyProvider{}.String()) require.Equal(t, "WithNumericDateFormatPrecision", identNumericDateFormatPrecision{}.String()) require.Equal(t, "WithNumericDateParsePedantic", identNumericDateParsePedantic{}.String()) require.Equal(t, "WithNumericDateParsePrecision", identNumericDateParsePrecision{}.String()) require.Equal(t, "WithPedantic", identPedantic{}.String()) require.Equal(t, "WithResetValidators", identResetValidators{}.String()) require.Equal(t, "WithSignOption", identSignOption{}.String()) require.Equal(t, "WithToken", identToken{}.String()) require.Equal(t, "WithTruncation", identTruncation{}.String()) require.Equal(t, "WithValidate", identValidate{}.String()) require.Equal(t, "WithValidator", identValidator{}.String()) require.Equal(t, "WithVerify", identVerify{}.String()) } golang-github-lestrrat-go-jwx-3.0.13/jwt/serialize.go000066400000000000000000000155051515060566400225340ustar00rootroot00000000000000package jwt import ( "fmt" "github.com/lestrrat-go/jwx/v3/internal/json" "github.com/lestrrat-go/jwx/v3/jwe" "github.com/lestrrat-go/jwx/v3/jws" ) type SerializeCtx interface { Step() int Nested() bool } type serializeCtx struct { step int nested bool } func (ctx *serializeCtx) Step() int { return ctx.step } func (ctx *serializeCtx) Nested() bool { return ctx.nested } type SerializeStep interface { Serialize(SerializeCtx, any) (any, error) } // errStep is always an error. used to indicate that a method like // serializer.Sign or Encrypt already errored out on configuration type errStep struct { err error } func (e errStep) Serialize(_ SerializeCtx, _ any) (any, error) { return nil, e.err } // Serializer is a generic serializer for JWTs. Whereas other convenience // functions can only do one thing (such as generate a JWS signed JWT), // Using this construct you can serialize the token however you want. // // By default, the serializer only marshals the token into a JSON payload. // You must set up the rest of the steps that should be taken by the // serializer. // // For example, to marshal the token into JSON, then apply JWS and JWE // in that order, you would do: // // serialized, err := jwt.NewSerializer(). // Sign(jwa.RS256, key). // Encrypt(jwe.WithEncryptOption(jwe.WithKey(jwa.RSA_OAEP(), publicKey))). // Serialize(token) // // The `jwt.Sign()` function is equivalent to // // serialized, err := jwt.NewSerializer(). // Sign(...args...). // Serialize(token) type Serializer struct { steps []SerializeStep } // NewSerializer creates a new empty serializer. func NewSerializer() *Serializer { return &Serializer{} } // Reset clears all of the registered steps. func (s *Serializer) Reset() *Serializer { s.steps = nil return s } // Step adds a new Step to the serialization process func (s *Serializer) Step(step SerializeStep) *Serializer { s.steps = append(s.steps, step) return s } type jsonSerializer struct{} func (jsonSerializer) Serialize(_ SerializeCtx, v any) (any, error) { token, ok := v.(Token) if !ok { return nil, fmt.Errorf(`invalid input: expected jwt.Token`) } buf, err := json.Marshal(token) if err != nil { return nil, fmt.Errorf(`failed to serialize as JSON: %w`, err) } return buf, nil } type genericHeader interface { Get(string, any) error Set(string, any) error Has(string) bool } func setTypeOrCty(ctx SerializeCtx, hdrs genericHeader) error { // cty and typ are common between JWE/JWS, so we don't use // the constants in jws/jwe package here const typKey = `typ` const ctyKey = `cty` if ctx.Step() == 1 { // We are executed immediately after json marshaling if !hdrs.Has(typKey) { if err := hdrs.Set(typKey, `JWT`); err != nil { return fmt.Errorf(`failed to set %s key to "JWT": %w`, typKey, err) } } } else { if ctx.Nested() { // If this is part of a nested sequence, we should set cty = 'JWT' // https://datatracker.ietf.org/doc/html/rfc7519#section-5.2 if err := hdrs.Set(ctyKey, `JWT`); err != nil { return fmt.Errorf(`failed to set %s key to "JWT": %w`, ctyKey, err) } } } return nil } type jwsSerializer struct { options []jws.SignOption } func (s *jwsSerializer) Serialize(ctx SerializeCtx, v any) (any, error) { payload, ok := v.([]byte) if !ok { return nil, fmt.Errorf(`expected []byte as input`) } for _, option := range s.options { var pc interface{ Protected(jws.Headers) jws.Headers } if err := option.Value(&pc); err != nil { continue } hdrs := pc.Protected(jws.NewHeaders()) if err := setTypeOrCty(ctx, hdrs); err != nil { return nil, err // this is already wrapped } // JWTs MUST NOT use b64 = false // https://datatracker.ietf.org/doc/html/rfc7797#section-7 var b64 bool if err := hdrs.Get("b64", &b64); err == nil { if !b64 { // b64 = false return nil, fmt.Errorf(`b64 cannot be false for JWTs`) } } } return jws.Sign(payload, s.options...) } func (s *Serializer) Sign(options ...SignOption) *Serializer { var soptions []jws.SignOption if l := len(options); l > 0 { // we need to from SignOption to Option because ... reasons // (todo: when go1.18 prevails, use type parameters rawoptions := make([]Option, l) for i, option := range options { rawoptions[i] = option } converted, err := toSignOptions(rawoptions...) if err != nil { return s.Step(errStep{fmt.Errorf(`(jwt.Serializer).Sign: failed to convert options into jws.SignOption: %w`, err)}) } soptions = converted } return s.sign(soptions...) } func (s *Serializer) sign(options ...jws.SignOption) *Serializer { return s.Step(&jwsSerializer{ options: options, }) } type jweSerializer struct { options []jwe.EncryptOption } func (s *jweSerializer) Serialize(ctx SerializeCtx, v any) (any, error) { payload, ok := v.([]byte) if !ok { return nil, fmt.Errorf(`expected []byte as input`) } hdrs := jwe.NewHeaders() if err := setTypeOrCty(ctx, hdrs); err != nil { return nil, err // this is already wrapped } options := append([]jwe.EncryptOption{jwe.WithMergeProtectedHeaders(true), jwe.WithProtectedHeaders(hdrs)}, s.options...) return jwe.Encrypt(payload, options...) } // Encrypt specifies the JWT to be serialized as an encrypted payload. // // One notable difference between this method and `jwe.Encrypt()` is that // while `jwe.Encrypt()` OVERWRITES the previous headers when `jwe.WithProtectedHeaders()` // is provided, this method MERGES them. This is due to the fact that we // MUST add some extra headers to construct a proper JWE message. // Be careful when you pass multiple `jwe.EncryptOption`s. func (s *Serializer) Encrypt(options ...EncryptOption) *Serializer { var eoptions []jwe.EncryptOption if l := len(options); l > 0 { // we need to from SignOption to Option because ... reasons // (todo: when go1.18 prevails, use type parameters rawoptions := make([]Option, l) for i, option := range options { rawoptions[i] = option } converted, err := toEncryptOptions(rawoptions...) if err != nil { return s.Step(errStep{fmt.Errorf(`(jwt.Serializer).Encrypt: failed to convert options into jwe.EncryptOption: %w`, err)}) } eoptions = converted } return s.encrypt(eoptions...) } func (s *Serializer) encrypt(options ...jwe.EncryptOption) *Serializer { return s.Step(&jweSerializer{ options: options, }) } func (s *Serializer) Serialize(t Token) ([]byte, error) { steps := make([]SerializeStep, len(s.steps)+1) steps[0] = jsonSerializer{} for i, step := range s.steps { steps[i+1] = step } var ctx serializeCtx ctx.nested = len(s.steps) > 1 var payload any = t for i, step := range steps { ctx.step = i v, err := step.Serialize(&ctx, payload) if err != nil { return nil, fmt.Errorf(`failed to serialize token at step #%d: %w`, i+1, err) } payload = v } res, ok := payload.([]byte) if !ok { return nil, fmt.Errorf(`invalid serialization produced`) } return res, nil } golang-github-lestrrat-go-jwx-3.0.13/jwt/token_gen.go000066400000000000000000000434021515060566400225130ustar00rootroot00000000000000// Code generated by tools/cmd/genjwt/main.go. DO NOT EDIT. package jwt import ( "bytes" "fmt" "sort" "sync" "time" "github.com/lestrrat-go/blackmagic" "github.com/lestrrat-go/jwx/v3/internal/json" "github.com/lestrrat-go/jwx/v3/internal/pool" "github.com/lestrrat-go/jwx/v3/internal/tokens" jwterrs "github.com/lestrrat-go/jwx/v3/jwt/internal/errors" "github.com/lestrrat-go/jwx/v3/jwt/internal/types" ) const ( AudienceKey = "aud" ExpirationKey = "exp" IssuedAtKey = "iat" IssuerKey = "iss" JwtIDKey = "jti" NotBeforeKey = "nbf" SubjectKey = "sub" ) // stdClaimNames is a list of all standard claim names defined in the JWT specification. var stdClaimNames = []string{AudienceKey, ExpirationKey, IssuedAtKey, IssuerKey, JwtIDKey, NotBeforeKey, SubjectKey} // Token represents a generic JWT token. // which are type-aware (to an extent). Other claims may be accessed via the `Get`/`Set` // methods but their types are not taken into consideration at all. If you have non-standard // claims that you must frequently access, consider creating accessors functions // like the following // // func SetFoo(tok jwt.Token) error // func GetFoo(tok jwt.Token) (*Customtyp, error) // // Embedding jwt.Token into another struct is not recommended, because // jwt.Token needs to handle private claims, and this really does not // work well when it is embedded in other structure type Token interface { // Audience returns the value for "aud" field of the token Audience() ([]string, bool) // Expiration returns the value for "exp" field of the token Expiration() (time.Time, bool) // IssuedAt returns the value for "iat" field of the token IssuedAt() (time.Time, bool) // Issuer returns the value for "iss" field of the token Issuer() (string, bool) // JwtID returns the value for "jti" field of the token JwtID() (string, bool) // NotBefore returns the value for "nbf" field of the token NotBefore() (time.Time, bool) // Subject returns the value for "sub" field of the token Subject() (string, bool) // Get is used to extract the value of any claim, including non-standard claims, out of the token. // // The first argument is the name of the claim. The second argument is a pointer // to a variable that will receive the value of the claim. The method returns // an error if the claim does not exist, or if the value cannot be assigned to // the destination variable. Note that a field is considered to "exist" even if // the value is empty-ish (e.g. 0, false, ""), as long as it is explicitly set. // // For standard claims, you can use the corresponding getter method, such as // `Issuer()`, `Subject()`, `Audience()`, `IssuedAt()`, `NotBefore()`, `ExpiresAt()` // // Note that fields of JWS/JWE are NOT accessible through this method. You need // to use `jws.Parse` and `jwe.Parse` to obtain the JWS/JWE message (and NOT // the payload, which presumably is the JWT), and then use their `Get` methods in their respective packages Get(string, any) error // Set assigns a value to the corresponding field in the token. Some // pre-defined fields such as `nbf`, `iat`, `iss` need their values to // be of a specific type. See the other getter methods in this interface // for the types of each of these fields Set(string, any) error // Has returns true if the specified claim has a value, even if // the value is empty-ish (e.g. 0, false, "") as long as it has been // explicitly set. Has(string) bool Remove(string) error // Options returns the per-token options associated with this token. // The options set value will be copied when the token is cloned via `Clone()` // but it will not survive when the token goes through marshaling/unmarshaling // such as `json.Marshal` and `json.Unmarshal` Options() *TokenOptionSet Clone() (Token, error) Keys() []string } type stdToken struct { mu *sync.RWMutex dc DecodeCtx // per-object context for decoding options TokenOptionSet // per-object option audience types.StringList // https://tools.ietf.org/html/rfc7519#section-4.1.3 expiration *types.NumericDate // https://tools.ietf.org/html/rfc7519#section-4.1.4 issuedAt *types.NumericDate // https://tools.ietf.org/html/rfc7519#section-4.1.6 issuer *string // https://tools.ietf.org/html/rfc7519#section-4.1.1 jwtID *string // https://tools.ietf.org/html/rfc7519#section-4.1.7 notBefore *types.NumericDate // https://tools.ietf.org/html/rfc7519#section-4.1.5 subject *string // https://tools.ietf.org/html/rfc7519#section-4.1.2 privateClaims map[string]any } // New creates a standard token, with minimal knowledge of // possible claims. Standard claims include"aud", "exp", "iat", "iss", "jti", "nbf" and "sub". // Convenience accessors are provided for these standard claims func New() Token { return &stdToken{ mu: &sync.RWMutex{}, privateClaims: make(map[string]any), options: DefaultOptionSet(), } } func (t *stdToken) Options() *TokenOptionSet { return &t.options } func (t *stdToken) Has(name string) bool { t.mu.RLock() defer t.mu.RUnlock() switch name { case AudienceKey: return t.audience != nil case ExpirationKey: return t.expiration != nil case IssuedAtKey: return t.issuedAt != nil case IssuerKey: return t.issuer != nil case JwtIDKey: return t.jwtID != nil case NotBeforeKey: return t.notBefore != nil case SubjectKey: return t.subject != nil default: _, ok := t.privateClaims[name] return ok } } func (t *stdToken) Get(name string, dst any) error { t.mu.RLock() defer t.mu.RUnlock() switch name { case AudienceKey: if t.audience == nil { return jwterrs.ClaimNotFoundError{Name: name} } if err := blackmagic.AssignIfCompatible(dst, t.audience.Get()); err != nil { return jwterrs.ClaimAssignmentFailedError{Err: err} } return nil case ExpirationKey: if t.expiration == nil { return jwterrs.ClaimNotFoundError{Name: name} } if err := blackmagic.AssignIfCompatible(dst, t.expiration.Get()); err != nil { return jwterrs.ClaimAssignmentFailedError{Err: err} } return nil case IssuedAtKey: if t.issuedAt == nil { return jwterrs.ClaimNotFoundError{Name: name} } if err := blackmagic.AssignIfCompatible(dst, t.issuedAt.Get()); err != nil { return jwterrs.ClaimAssignmentFailedError{Err: err} } return nil case IssuerKey: if t.issuer == nil { return jwterrs.ClaimNotFoundError{Name: name} } if err := blackmagic.AssignIfCompatible(dst, *(t.issuer)); err != nil { return jwterrs.ClaimAssignmentFailedError{Err: err} } return nil case JwtIDKey: if t.jwtID == nil { return jwterrs.ClaimNotFoundError{Name: name} } if err := blackmagic.AssignIfCompatible(dst, *(t.jwtID)); err != nil { return jwterrs.ClaimAssignmentFailedError{Err: err} } return nil case NotBeforeKey: if t.notBefore == nil { return jwterrs.ClaimNotFoundError{Name: name} } if err := blackmagic.AssignIfCompatible(dst, t.notBefore.Get()); err != nil { return jwterrs.ClaimAssignmentFailedError{Err: err} } return nil case SubjectKey: if t.subject == nil { return jwterrs.ClaimNotFoundError{Name: name} } if err := blackmagic.AssignIfCompatible(dst, *(t.subject)); err != nil { return jwterrs.ClaimAssignmentFailedError{Err: err} } return nil default: v, ok := t.privateClaims[name] if !ok { return jwterrs.ClaimNotFoundError{Name: name} } if err := blackmagic.AssignIfCompatible(dst, v); err != nil { return jwterrs.ClaimAssignmentFailedError{Err: err} } return nil } } func (t *stdToken) Remove(key string) error { t.mu.Lock() defer t.mu.Unlock() switch key { case AudienceKey: t.audience = nil case ExpirationKey: t.expiration = nil case IssuedAtKey: t.issuedAt = nil case IssuerKey: t.issuer = nil case JwtIDKey: t.jwtID = nil case NotBeforeKey: t.notBefore = nil case SubjectKey: t.subject = nil default: delete(t.privateClaims, key) } return nil } func (t *stdToken) Set(name string, value any) error { t.mu.Lock() defer t.mu.Unlock() return t.setNoLock(name, value) } func (t *stdToken) DecodeCtx() DecodeCtx { t.mu.RLock() defer t.mu.RUnlock() return t.dc } func (t *stdToken) SetDecodeCtx(v DecodeCtx) { t.mu.Lock() defer t.mu.Unlock() t.dc = v } func (t *stdToken) setNoLock(name string, value any) error { switch name { case AudienceKey: var acceptor types.StringList if err := acceptor.Accept(value); err != nil { return fmt.Errorf(`invalid value for %s key: %w`, AudienceKey, err) } t.audience = acceptor return nil case ExpirationKey: var acceptor types.NumericDate if err := acceptor.Accept(value); err != nil { return fmt.Errorf(`invalid value for %s key: %w`, ExpirationKey, err) } t.expiration = &acceptor return nil case IssuedAtKey: var acceptor types.NumericDate if err := acceptor.Accept(value); err != nil { return fmt.Errorf(`invalid value for %s key: %w`, IssuedAtKey, err) } t.issuedAt = &acceptor return nil case IssuerKey: if v, ok := value.(string); ok { t.issuer = &v return nil } return fmt.Errorf(`invalid value for %s key: %T`, IssuerKey, value) case JwtIDKey: if v, ok := value.(string); ok { t.jwtID = &v return nil } return fmt.Errorf(`invalid value for %s key: %T`, JwtIDKey, value) case NotBeforeKey: var acceptor types.NumericDate if err := acceptor.Accept(value); err != nil { return fmt.Errorf(`invalid value for %s key: %w`, NotBeforeKey, err) } t.notBefore = &acceptor return nil case SubjectKey: if v, ok := value.(string); ok { t.subject = &v return nil } return fmt.Errorf(`invalid value for %s key: %T`, SubjectKey, value) default: if t.privateClaims == nil { t.privateClaims = map[string]any{} } t.privateClaims[name] = value } return nil } func (t *stdToken) Audience() ([]string, bool) { t.mu.RLock() defer t.mu.RUnlock() if t.audience != nil { return t.audience.Get(), true } return nil, false } func (t *stdToken) Expiration() (time.Time, bool) { t.mu.RLock() defer t.mu.RUnlock() if t.expiration != nil { return t.expiration.Get(), true } return time.Time{}, false } func (t *stdToken) IssuedAt() (time.Time, bool) { t.mu.RLock() defer t.mu.RUnlock() if t.issuedAt != nil { return t.issuedAt.Get(), true } return time.Time{}, false } func (t *stdToken) Issuer() (string, bool) { t.mu.RLock() defer t.mu.RUnlock() if t.issuer != nil { return *(t.issuer), true } return "", false } func (t *stdToken) JwtID() (string, bool) { t.mu.RLock() defer t.mu.RUnlock() if t.jwtID != nil { return *(t.jwtID), true } return "", false } func (t *stdToken) NotBefore() (time.Time, bool) { t.mu.RLock() defer t.mu.RUnlock() if t.notBefore != nil { return t.notBefore.Get(), true } return time.Time{}, false } func (t *stdToken) Subject() (string, bool) { t.mu.RLock() defer t.mu.RUnlock() if t.subject != nil { return *(t.subject), true } return "", false } func (t *stdToken) PrivateClaims() map[string]any { t.mu.RLock() defer t.mu.RUnlock() return t.privateClaims } func (t *stdToken) UnmarshalJSON(buf []byte) error { t.mu.Lock() defer t.mu.Unlock() t.audience = nil t.expiration = nil t.issuedAt = nil t.issuer = nil t.jwtID = nil t.notBefore = nil t.subject = nil dec := json.NewDecoder(bytes.NewReader(buf)) LOOP: for { tok, err := dec.Token() if err != nil { return fmt.Errorf(`error reading token: %w`, err) } switch tok := tok.(type) { case json.Delim: // Assuming we're doing everything correctly, we should ONLY // get either tokens.OpenCurlyBracket or tokens.CloseCurlyBracket here. if tok == tokens.CloseCurlyBracket { // End of object break LOOP } else if tok != tokens.OpenCurlyBracket { return fmt.Errorf(`expected '%c', but got '%c'`, tokens.OpenCurlyBracket, tok) } case string: // Objects can only have string keys switch tok { case AudienceKey: var decoded types.StringList if err := dec.Decode(&decoded); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, AudienceKey, err) } t.audience = decoded case ExpirationKey: var decoded types.NumericDate if err := dec.Decode(&decoded); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, ExpirationKey, err) } t.expiration = &decoded case IssuedAtKey: var decoded types.NumericDate if err := dec.Decode(&decoded); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, IssuedAtKey, err) } t.issuedAt = &decoded case IssuerKey: if err := json.AssignNextStringToken(&t.issuer, dec); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, IssuerKey, err) } case JwtIDKey: if err := json.AssignNextStringToken(&t.jwtID, dec); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, JwtIDKey, err) } case NotBeforeKey: var decoded types.NumericDate if err := dec.Decode(&decoded); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, NotBeforeKey, err) } t.notBefore = &decoded case SubjectKey: if err := json.AssignNextStringToken(&t.subject, dec); err != nil { return fmt.Errorf(`failed to decode value for key %s: %w`, SubjectKey, err) } default: if dc := t.dc; dc != nil { if localReg := dc.Registry(); localReg != nil { decoded, err := localReg.Decode(dec, tok) if err == nil { t.setNoLock(tok, decoded) continue } } } decoded, err := registry.Decode(dec, tok) if err == nil { t.setNoLock(tok, decoded) continue } return fmt.Errorf(`could not decode field %s: %w`, tok, err) } default: return fmt.Errorf(`invalid token %T`, tok) } } return nil } func (t *stdToken) Keys() []string { t.mu.RLock() defer t.mu.RUnlock() keys := make([]string, 0, 7+len(t.privateClaims)) if t.audience != nil { keys = append(keys, AudienceKey) } if t.expiration != nil { keys = append(keys, ExpirationKey) } if t.issuedAt != nil { keys = append(keys, IssuedAtKey) } if t.issuer != nil { keys = append(keys, IssuerKey) } if t.jwtID != nil { keys = append(keys, JwtIDKey) } if t.notBefore != nil { keys = append(keys, NotBeforeKey) } if t.subject != nil { keys = append(keys, SubjectKey) } for k := range t.privateClaims { keys = append(keys, k) } return keys } type claimPair struct { Name string Value any } var claimPairPool = sync.Pool{ New: func() any { return make([]claimPair, 0, 7) }, } func getClaimPairList() []claimPair { return claimPairPool.Get().([]claimPair) } func putClaimPairList(list []claimPair) { list = list[:0] claimPairPool.Put(list) } // makePairs creates a list of claimPair objects that are sorted by // their key names. The key names are always their JSON names, and // the values are already JSON encoded. // Because makePairs needs to allocate a slice, it _slows_ down // marshaling of the token to JSON. The upside is that it allows us to // marshal the token keys in a deterministic order. // Do we really need it...? Well, technically we don't, but it's so // much nicer to have this to make the example tests actually work // deterministically. Also if for whatever reason this becomes a // performance issue, we can always/ add a flag to use a more _optimized_ code path. // // The caller is responsible to call putClaimPairList() to return the // allocated slice back to the pool. func (t *stdToken) makePairs() ([]claimPair, error) { pairs := getClaimPairList() if t.audience != nil { buf, err := json.MarshalAudience(t.audience, t.options.IsEnabled(FlattenAudience)) if err != nil { return nil, fmt.Errorf(`failed to encode "aud": %w`, err) } pairs = append(pairs, claimPair{Name: AudienceKey, Value: buf}) } if t.expiration != nil { buf, err := json.Marshal(t.expiration.Unix()) if err != nil { return nil, fmt.Errorf(`failed to encode "exp": %w`, err) } pairs = append(pairs, claimPair{Name: ExpirationKey, Value: buf}) } if t.issuedAt != nil { buf, err := json.Marshal(t.issuedAt.Unix()) if err != nil { return nil, fmt.Errorf(`failed to encode "iat": %w`, err) } pairs = append(pairs, claimPair{Name: IssuedAtKey, Value: buf}) } if t.issuer != nil { buf, err := json.Marshal(*(t.issuer)) if err != nil { return nil, fmt.Errorf(`failed to encode field "iss": %w`, err) } pairs = append(pairs, claimPair{Name: IssuerKey, Value: buf}) } if t.jwtID != nil { buf, err := json.Marshal(*(t.jwtID)) if err != nil { return nil, fmt.Errorf(`failed to encode field "jti": %w`, err) } pairs = append(pairs, claimPair{Name: JwtIDKey, Value: buf}) } if t.notBefore != nil { buf, err := json.Marshal(t.notBefore.Unix()) if err != nil { return nil, fmt.Errorf(`failed to encode "nbf": %w`, err) } pairs = append(pairs, claimPair{Name: NotBeforeKey, Value: buf}) } if t.subject != nil { buf, err := json.Marshal(*(t.subject)) if err != nil { return nil, fmt.Errorf(`failed to encode field "sub": %w`, err) } pairs = append(pairs, claimPair{Name: SubjectKey, Value: buf}) } for k, v := range t.privateClaims { buf, err := json.Marshal(v) if err != nil { return nil, fmt.Errorf(`failed to encode field %q: %w`, k, err) } pairs = append(pairs, claimPair{Name: k, Value: buf}) } sort.Slice(pairs, func(i, j int) bool { return pairs[i].Name < pairs[j].Name }) return pairs, nil } func (t stdToken) MarshalJSON() ([]byte, error) { buf := pool.BytesBuffer().Get() defer pool.BytesBuffer().Put(buf) pairs, err := t.makePairs() if err != nil { return nil, fmt.Errorf(`failed to make pairs: %w`, err) } buf.WriteByte(tokens.OpenCurlyBracket) for i, pair := range pairs { if i > 0 { buf.WriteByte(tokens.Comma) } fmt.Fprintf(buf, "%q: %s", pair.Name, pair.Value) } buf.WriteByte(tokens.CloseCurlyBracket) ret := make([]byte, buf.Len()) copy(ret, buf.Bytes()) putClaimPairList(pairs) return ret, nil } golang-github-lestrrat-go-jwx-3.0.13/jwt/token_options.go000066400000000000000000000050111515060566400234270ustar00rootroot00000000000000package jwt import "sync" // TokenOptionSet is a bit flag containing per-token options. type TokenOptionSet uint64 var defaultOptions TokenOptionSet var defaultOptionsMu sync.RWMutex // TokenOption describes a single token option that can be set on // the per-token option set (TokenOptionSet) type TokenOption uint64 const ( // FlattenAudience option controls whether the "aud" claim should be flattened // to a single string upon the token being serialized to JSON. // // This is sometimes important when a JWT consumer does not understand that // the "aud" claim can actually take the form of an array of strings. // (We have been notified by users that AWS Cognito has manifested this behavior // at some point) // // Unless the global option is set using `jwt.Settings()`, the default value is // `disabled`, which means that "aud" claims are always rendered as a arrays of // strings when serialized to JSON. FlattenAudience TokenOption = 1 << iota // MaxPerTokenOption is a marker to denote the last value that an option can take. // This value has no meaning other than to be used as a marker. MaxPerTokenOption ) // Value returns the uint64 value of a single option func (o TokenOption) Value() uint64 { return uint64(o) } // Value returns the uint64 bit flag value of an option set func (o TokenOptionSet) Value() uint64 { return uint64(o) } // DefaultOptionSet creates a new TokenOptionSet using the default // option set. This may differ depending on if/when functions that // change the global state has been called, such as `jwt.Settings` func DefaultOptionSet() TokenOptionSet { return TokenOptionSet(defaultOptions.Value()) } // Clear sets all bits to zero, effectively disabling all options func (o *TokenOptionSet) Clear() { *o = TokenOptionSet(uint64(0)) } // Set sets the value of this option set, effectively *replacing* // the entire option set with the new value. This is NOT the same // as Enable/Disable. func (o *TokenOptionSet) Set(s TokenOptionSet) { *o = s } // Enable sets the appropriate value to enable the option in the // option set func (o *TokenOptionSet) Enable(flag TokenOption) { *o = TokenOptionSet(o.Value() | uint64(flag)) } // Disable sets the appropriate value to disable the option in the // option set func (o *TokenOptionSet) Disable(flag TokenOption) { *o = TokenOptionSet(o.Value() & ^uint64(flag)) } // IsEnabled returns true if the given bit on the option set is enabled. func (o TokenOptionSet) IsEnabled(flag TokenOption) bool { return (uint64(o)&uint64(flag) == uint64(flag)) } golang-github-lestrrat-go-jwx-3.0.13/jwt/token_options_gen.go000066400000000000000000000013231515060566400242620ustar00rootroot00000000000000// Code generated by "stringer -type=TokenOption -output=token_options_gen.go"; DO NOT EDIT. package jwt import "strconv" func _() { // An "invalid array index" compiler error signifies that the constant values have changed. // Re-run the stringer command to generate them again. var x [1]struct{} _ = x[FlattenAudience-1] _ = x[MaxPerTokenOption-2] } const _TokenOption_name = "FlattenAudienceMaxPerTokenOption" var _TokenOption_index = [...]uint8{0, 15, 32} func (i TokenOption) String() string { idx := int(i) - 1 if i < 1 || idx >= len(_TokenOption_index)-1 { return "TokenOption(" + strconv.FormatInt(int64(i), 10) + ")" } return _TokenOption_name[_TokenOption_index[idx]:_TokenOption_index[idx+1]] } golang-github-lestrrat-go-jwx-3.0.13/jwt/token_options_test.go000066400000000000000000000025661515060566400245020ustar00rootroot00000000000000package jwt_test import ( "testing" "github.com/lestrrat-go/jwx/v3/jwt" "github.com/stretchr/testify/require" ) func TestTokenOptions(t *testing.T) { t.Run("Option names", func(t *testing.T) { for i := uint64(1); i < jwt.MaxPerTokenOption.Value(); i <<= 1 { t.Logf("%s", jwt.TokenOption(i)) } }) t.Run("Sanity", func(t *testing.T) { // Vanilla set var opt jwt.TokenOptionSet // Initially, the option should be false require.False(t, opt.IsEnabled(jwt.FlattenAudience), `option FlattenAudience should be false`) // Flip this bit on opt.Enable(jwt.FlattenAudience) require.True(t, opt.IsEnabled(jwt.FlattenAudience), `option FlattenAudience should be true`) // Test copying var opt2 jwt.TokenOptionSet opt2.Set(opt) require.True(t, opt.IsEnabled(jwt.FlattenAudience), `option FlattenAudience should be true`) require.True(t, opt2.IsEnabled(jwt.FlattenAudience), `option FlattenAudience should be true`) // Flip this bit off opt.Disable(jwt.FlattenAudience) require.False(t, opt.IsEnabled(jwt.FlattenAudience), `option FlattenAudience should be false`) // The above should have not action at a distance effect on opt2 require.True(t, opt2.IsEnabled(jwt.FlattenAudience), `option FlattenAudience should be true`) // Clear it opt2.Clear() require.False(t, opt2.IsEnabled(jwt.FlattenAudience), `option FlattenAudience should be false`) }) } golang-github-lestrrat-go-jwx-3.0.13/jwt/token_test.go000066400000000000000000000135721515060566400227260ustar00rootroot00000000000000package jwt_test import ( "reflect" "testing" "time" "github.com/lestrrat-go/jwx/v3/internal/json" "github.com/lestrrat-go/jwx/v3/jwt" "github.com/stretchr/testify/require" ) const ( tokenTime = 233431200 ) var zeroval reflect.Value var expectedTokenTime = time.Unix(tokenTime, 0).UTC() func TestHeader(t *testing.T) { t.Parallel() values := map[string]any{ jwt.AudienceKey: []string{"developers", "secops", "tac"}, jwt.ExpirationKey: expectedTokenTime, jwt.IssuedAtKey: expectedTokenTime, jwt.IssuerKey: "http://www.example.com", jwt.JwtIDKey: "e9bc097a-ce51-4036-9562-d2ade882db0d", jwt.NotBeforeKey: expectedTokenTime, jwt.SubjectKey: "unit test", } t.Run("Roundtrip", func(t *testing.T) { t.Parallel() h := jwt.New() for k, v := range values { require.NoError(t, h.Set(k, v), `h.Set should succeed for key %#v`, k) var got any require.NoError(t, h.Get(k, &got), `h.Get should succeed for key %#v`, k) if !reflect.DeepEqual(v, got) { t.Fatalf("Values do not match: (%v, %v)", v, got) } } }) t.Run("RoundtripError", func(t *testing.T) { t.Parallel() type dummyStruct struct { dummy1 int dummy2 float64 } dummy := &dummyStruct{1, 3.4} values := map[string]any{ jwt.AudienceKey: dummy, jwt.ExpirationKey: dummy, jwt.IssuedAtKey: dummy, jwt.IssuerKey: dummy, jwt.JwtIDKey: dummy, jwt.NotBeforeKey: dummy, jwt.SubjectKey: dummy, } h := jwt.New() for k, v := range values { err := h.Set(k, v) if err == nil { t.Fatalf("Setting %s value should have failed", k) } } err := h.Set("default", dummy) // private params if err != nil { t.Fatalf("Setting %s value failed", "default") } var tmp any for k := range values { require.Error(t, h.Get(k, &tmp), `Getting %s value should have failed`) } require.NoError(t, h.Get("default", &tmp), `Getting %s value should have succeeded`) }) t.Run("GetError", func(t *testing.T) { t.Parallel() h := jwt.New() issuer, ok := h.Issuer() require.False(t, ok, `Issuer should not be set`) require.Empty(t, issuer, `Issuer should be empty`) jwtID, ok := h.JwtID() require.False(t, ok, `JwtID should not be set`) require.Empty(t, jwtID, `JwtID should be empty`) }) } func TestTokenMarshal(t *testing.T) { t.Parallel() t1 := jwt.New() err := t1.Set(jwt.JwtIDKey, "AbCdEfG") if err != nil { t.Fatalf("Failed to set JWT ID: %s", err.Error()) } err = t1.Set(jwt.SubjectKey, "foobar@example.com") if err != nil { t.Fatalf("Failed to set Subject: %s", err.Error()) } // Silly fix to remove monotonic element from time.Time obtained // from time.Now(). Without this, the equality comparison goes // ga-ga for golang tip (1.9) now := time.Unix(time.Now().Unix(), 0) err = t1.Set(jwt.IssuedAtKey, now.Unix()) if err != nil { t.Fatalf("Failed to set IssuedAt: %s", err.Error()) } err = t1.Set(jwt.NotBeforeKey, now.Add(5*time.Second)) if err != nil { t.Fatalf("Failed to set NotBefore: %s", err.Error()) } err = t1.Set(jwt.ExpirationKey, now.Add(10*time.Second).Unix()) if err != nil { t.Fatalf("Failed to set Expiration: %s", err.Error()) } err = t1.Set(jwt.AudienceKey, []string{"devops", "secops", "tac"}) if err != nil { t.Fatalf("Failed to set audience: %s", err.Error()) } err = t1.Set("custom", "MyValue") if err != nil { t.Fatalf(`Failed to set private claim "custom": %s`, err.Error()) } jsonbuf1, err := json.MarshalIndent(t1, "", " ") if err != nil { t.Fatalf("JSON Marshal failed: %s", err.Error()) } t2 := jwt.New() require.NoError(t, json.Unmarshal(jsonbuf1, t2), `json.Unmarshal should succeed`) require.Equal(t, t1, t2, "tokens should match") _, err = json.MarshalIndent(t2, "", " ") require.NoError(t, err, `json.MarshalIndent should succeed`) } func TestToken(t *testing.T) { tok := jwt.New() def := map[string]struct { Value any Method string }{ jwt.AudienceKey: { Method: "Audience", Value: []string{"developers", "secops", "tac"}, }, jwt.ExpirationKey: { Method: "Expiration", Value: expectedTokenTime, }, jwt.IssuedAtKey: { Method: "IssuedAt", Value: expectedTokenTime, }, jwt.IssuerKey: { Method: "Issuer", Value: "http://www.example.com", }, jwt.JwtIDKey: { Method: "JwtID", Value: "e9bc097a-ce51-4036-9562-d2ade882db0d", }, jwt.NotBeforeKey: { Method: "NotBefore", Value: expectedTokenTime, }, jwt.SubjectKey: { Method: "Subject", Value: "unit test", }, "myClaim": { Value: "hello, world", }, } t.Run("Set", func(t *testing.T) { for k, kdef := range def { require.NoError(t, tok.Set(k, kdef.Value), `tok.Set(%s) should succeed`, k) } }) t.Run("Get", func(t *testing.T) { rv := reflect.ValueOf(tok) for k, kdef := range def { var getval any require.NoError(t, tok.Get(k, &getval), `tok.Get(%s) should succeed`, k) if mname := kdef.Method; mname != "" { method := rv.MethodByName(mname) require.NotEqual(t, zeroval, method, `method %s should not be zero value`, mname) retvals := method.Call(nil) require.Len(t, retvals, 2, `should have exactly one return value`) require.Equal(t, getval, retvals[0].Interface(), `values should match`) } } }) t.Run("Roundtrip", func(t *testing.T) { buf, err := json.Marshal(tok) require.NoError(t, err, `json.Marshal should succeed`) newtok, err := jwt.ParseInsecure(buf) require.NoError(t, err, `jwt.Parse should succeed`) require.True(t, jwt.Equal(tok, newtok), `tokens should match`) }) t.Run("Set/Remove", func(t *testing.T) { newtok, err := tok.Clone() require.NoError(t, err, `tok.Clone should succeed`) for _, k := range tok.Keys() { newtok.Remove(k) } require.Len(t, newtok.Keys(), 0, `toks should have 0 tok`) for _, k := range tok.Keys() { var v any require.NoError(t, tok.Get(k, &v), `tok.Get(%s) should succeed`, k) require.NoError(t, newtok.Set(k, v), `newtok.Set should succeed`) } }) } golang-github-lestrrat-go-jwx-3.0.13/jwt/validate.go000066400000000000000000000273361515060566400223430ustar00rootroot00000000000000package jwt import ( "context" "fmt" "slices" "strconv" "time" jwterrs "github.com/lestrrat-go/jwx/v3/jwt/internal/errors" ) type Clock interface { Now() time.Time } type ClockFunc func() time.Time func (f ClockFunc) Now() time.Time { return f() } func isSupportedTimeClaim(c string) error { switch c { case ExpirationKey, IssuedAtKey, NotBeforeKey: return nil } return fmt.Errorf(`unsupported time claim %s`, strconv.Quote(c)) } func timeClaim(t Token, clock Clock, c string) time.Time { // We don't check if the claims already exist. It should have been done // by piggybacking on `required` check. switch c { case ExpirationKey: tv, _ := t.Expiration() return tv case IssuedAtKey: tv, _ := t.IssuedAt() return tv case NotBeforeKey: tv, _ := t.NotBefore() return tv case "": return clock.Now() } return time.Time{} // should *NEVER* reach here, but... } // Validate makes sure that the essential claims stand. // // See the various `WithXXX` functions for optional parameters // that can control the behavior of this method. func Validate(t Token, options ...ValidateOption) error { ctx := context.Background() trunc := getDefaultTruncation() var clock Clock = ClockFunc(time.Now) var skew time.Duration var baseValidators = []Validator{ IsIssuedAtValid(), IsExpirationValid(), IsNbfValid(), } var extraValidators []Validator var resetValidators bool for _, o := range options { switch o.Ident() { case identClock{}: if err := o.Value(&clock); err != nil { return fmt.Errorf(`jwt.Validate: value for WithClock() option must be jwt.Clock: %w`, err) } case identAcceptableSkew{}: if err := o.Value(&skew); err != nil { return fmt.Errorf(`jwt.Validate: value for WithAcceptableSkew() option must be time.Duration: %w`, err) } case identTruncation{}: if err := o.Value(&trunc); err != nil { return fmt.Errorf(`jwt.Validate: value for WithTruncation() option must be time.Duration: %w`, err) } case identContext{}: if err := o.Value(&ctx); err != nil { return fmt.Errorf(`jwt.Validate: value for WithContext() option must be context.Context: %w`, err) } case identResetValidators{}: if err := o.Value(&resetValidators); err != nil { return fmt.Errorf(`jwt.Validate: value for WithResetValidators() option must be bool: %w`, err) } case identValidator{}: var v Validator if err := o.Value(&v); err != nil { return fmt.Errorf(`jwt.Validate: value for WithValidator() option must be jwt.Validator: %w`, err) } switch v := v.(type) { case *isInTimeRange: if v.c1 != "" { if err := isSupportedTimeClaim(v.c1); err != nil { return err } extraValidators = append(extraValidators, IsRequired(v.c1)) } if v.c2 != "" { if err := isSupportedTimeClaim(v.c2); err != nil { return err } extraValidators = append(extraValidators, IsRequired(v.c2)) } } extraValidators = append(extraValidators, v) } } ctx = SetValidationCtxSkew(ctx, skew) ctx = SetValidationCtxClock(ctx, clock) ctx = SetValidationCtxTruncation(ctx, trunc) var validators []Validator if !resetValidators { validators = append(baseValidators, extraValidators...) } else { if len(extraValidators) == 0 { return jwterrs.ValidateErrorf(`no validators specified: jwt.WithResetValidators(true) and no jwt.WithValidator() specified`) } validators = extraValidators } for _, v := range validators { if err := v.Validate(ctx, t); err != nil { return jwterrs.ValidateErrorf(`validation failed: %w`, err) } } return nil } type isInTimeRange struct { c1 string c2 string dur time.Duration less bool // if true, d =< c1 - c2. otherwise d >= c1 - c2 } // MaxDeltaIs implements the logic behind `WithMaxDelta()` option func MaxDeltaIs(c1, c2 string, dur time.Duration) Validator { return &isInTimeRange{ c1: c1, c2: c2, dur: dur, less: true, } } // MinDeltaIs implements the logic behind `WithMinDelta()` option func MinDeltaIs(c1, c2 string, dur time.Duration) Validator { return &isInTimeRange{ c1: c1, c2: c2, dur: dur, less: false, } } func (iitr *isInTimeRange) Validate(ctx context.Context, t Token) error { clock := ValidationCtxClock(ctx) // MUST be populated skew := ValidationCtxSkew(ctx) // MUST be populated // We don't check if the claims already exist, because we already did that // by piggybacking on `required` check. t1 := timeClaim(t, clock, iitr.c1) t2 := timeClaim(t, clock, iitr.c2) if iitr.less { // t1 - t2 <= iitr.dur // t1 - t2 < iitr.dur + skew if t1.Sub(t2) > iitr.dur+skew { return fmt.Errorf(`iitr between %s and %s exceeds %s (skew %s)`, iitr.c1, iitr.c2, iitr.dur, skew) } } else { if t1.Sub(t2) < iitr.dur-skew { return fmt.Errorf(`iitr between %s and %s is less than %s (skew %s)`, iitr.c1, iitr.c2, iitr.dur, skew) } } return nil } // Validator describes interface to validate a Token. type Validator interface { // Validate should return an error if a required conditions is not met. Validate(context.Context, Token) error } // ValidatorFunc is a type of Validator that does not have any // state, that is implemented as a function type ValidatorFunc func(context.Context, Token) error func (vf ValidatorFunc) Validate(ctx context.Context, tok Token) error { return vf(ctx, tok) } type identValidationCtxClock struct{} type identValidationCtxSkew struct{} type identValidationCtxTruncation struct{} func SetValidationCtxClock(ctx context.Context, cl Clock) context.Context { return context.WithValue(ctx, identValidationCtxClock{}, cl) } func SetValidationCtxTruncation(ctx context.Context, dur time.Duration) context.Context { return context.WithValue(ctx, identValidationCtxTruncation{}, dur) } func SetValidationCtxSkew(ctx context.Context, dur time.Duration) context.Context { return context.WithValue(ctx, identValidationCtxSkew{}, dur) } // ValidationCtxClock returns the Clock object associated with // the current validation context. This value will always be available // during validation of tokens. func ValidationCtxClock(ctx context.Context) Clock { //nolint:forcetypeassert return ctx.Value(identValidationCtxClock{}).(Clock) } func ValidationCtxSkew(ctx context.Context) time.Duration { //nolint:forcetypeassert return ctx.Value(identValidationCtxSkew{}).(time.Duration) } func ValidationCtxTruncation(ctx context.Context) time.Duration { //nolint:forcetypeassert return ctx.Value(identValidationCtxTruncation{}).(time.Duration) } // IsExpirationValid is one of the default validators that will be executed. // It does not need to be specified by users, but it exists as an // exported field so that you can check what it does. // // The supplied context.Context object must have the "clock" and "skew" // populated with appropriate values using SetValidationCtxClock() and // SetValidationCtxSkew() func IsExpirationValid() Validator { return ValidatorFunc(isExpirationValid) } func isExpirationValid(ctx context.Context, t Token) error { tv, ok := t.Expiration() if !ok { return nil } clock := ValidationCtxClock(ctx) // MUST be populated skew := ValidationCtxSkew(ctx) // MUST be populated trunc := ValidationCtxTruncation(ctx) // MUST be populated now := clock.Now().Truncate(trunc) ttv := tv.Truncate(trunc) // expiration date must be after NOW if !now.Before(ttv.Add(skew)) { return TokenExpiredError() } return nil } // IsIssuedAtValid is one of the default validators that will be executed. // It does not need to be specified by users, but it exists as an // exported field so that you can check what it does. // // The supplied context.Context object must have the "clock" and "skew" // populated with appropriate values using SetValidationCtxClock() and // SetValidationCtxSkew() func IsIssuedAtValid() Validator { return ValidatorFunc(isIssuedAtValid) } func isIssuedAtValid(ctx context.Context, t Token) error { tv, ok := t.IssuedAt() if !ok { return nil } clock := ValidationCtxClock(ctx) // MUST be populated skew := ValidationCtxSkew(ctx) // MUST be populated trunc := ValidationCtxTruncation(ctx) // MUST be populated now := clock.Now().Truncate(trunc) ttv := tv.Truncate(trunc) if now.Before(ttv.Add(-1 * skew)) { return InvalidIssuedAtError() } return nil } // IsNbfValid is one of the default validators that will be executed. // It does not need to be specified by users, but it exists as an // exported field so that you can check what it does. // // The supplied context.Context object must have the "clock" and "skew" // populated with appropriate values using SetValidationCtxClock() and // SetValidationCtxSkew() func IsNbfValid() Validator { return ValidatorFunc(isNbfValid) } func isNbfValid(ctx context.Context, t Token) error { tv, ok := t.NotBefore() if !ok { return nil } clock := ValidationCtxClock(ctx) // MUST be populated skew := ValidationCtxSkew(ctx) // MUST be populated trunc := ValidationCtxTruncation(ctx) // MUST be populated // Truncation always happens even for trunc = 0 because // we also use this to strip monotonic clocks now := clock.Now().Truncate(trunc) ttv := tv.Truncate(trunc) // "now" cannot be before t - skew, so we check for now > t - skew ttv = ttv.Add(-1 * skew) if now.Before(ttv) { return TokenNotYetValidError() } return nil } type claimContainsString struct { name string value string makeErr func(string, ...any) error } // ClaimContainsString can be used to check if the claim called `name`, which is // expected to be a list of strings, contains `value`. Currently, because of the // implementation, this will probably only work for `aud` fields. func ClaimContainsString(name, value string) Validator { return claimContainsString{ name: name, value: value, makeErr: fmt.Errorf, } } func (ccs claimContainsString) Validate(_ context.Context, t Token) error { var list []string if err := t.Get(ccs.name, &list); err != nil { return ccs.makeErr(`claim %q does not exist or is not a []string: %w`, ccs.name, err) } if !slices.Contains(list, ccs.value) { return ccs.makeErr(`%q not satisfied`, ccs.name) } return nil } // audienceClaimContainsString can be used to check if the audience claim, which is // expected to be a list of strings, contains `value`. func audienceClaimContainsString(value string) Validator { return claimContainsString{ name: AudienceKey, value: value, makeErr: jwterrs.AudienceErrorf, } } type claimValueIs struct { name string value any makeErr func(string, ...any) error } // ClaimValueIs creates a Validator that checks if the value of claim `name` // matches `value`. The comparison is done using a simple `==` comparison, // and therefore complex comparisons may fail using this code. If you // need to do more, use a custom Validator. func ClaimValueIs(name string, value any) Validator { return &claimValueIs{ name: name, value: value, makeErr: fmt.Errorf, } } func (cv *claimValueIs) Validate(_ context.Context, t Token) error { var v any if err := t.Get(cv.name, &v); err != nil { return cv.makeErr(`claim %[1]q does not exist or is not a []string: %[2]w`, cv.name, err) } if v != cv.value { return cv.makeErr(`claim %[1]q does not have the expected value`, cv.name) } return nil } // issuerClaimValueIs creates a Validator that checks if the issuer claim // matches `value`. func issuerClaimValueIs(value string) Validator { return &claimValueIs{ name: IssuerKey, value: value, makeErr: jwterrs.IssuerErrorf, } } // IsRequired creates a Validator that checks if the required claim `name` // exists in the token func IsRequired(name string) Validator { return isRequired(name) } type isRequired string func (ir isRequired) Validate(_ context.Context, t Token) error { name := string(ir) if !t.Has(name) { return jwterrs.MissingRequiredClaimErrorf(name) } return nil } golang-github-lestrrat-go-jwx-3.0.13/jwt/validate_test.go000066400000000000000000000717141515060566400234010ustar00rootroot00000000000000package jwt_test import ( "context" "errors" "testing" "time" "github.com/lestrrat-go/jwx/v3/internal/json" "github.com/lestrrat-go/jwx/v3/jwt" "github.com/stretchr/testify/require" ) func TestTimeValidation(t *testing.T) { t.Parallel() // This test is _almost_ identical to TestGH010, but we are now // testing with the default time truncation settings. testcases := []struct { ClaimName string ClaimValue string OptionFunc func(string) jwt.ValidateOption BuildFunc func(v string) (jwt.Token, error) }{ { ClaimName: jwt.JwtIDKey, ClaimValue: `my-sepcial-key`, OptionFunc: jwt.WithJwtID, BuildFunc: func(v string) (jwt.Token, error) { return jwt.NewBuilder(). JwtID(v). Build() }, }, { ClaimName: jwt.SubjectKey, ClaimValue: `very important subject`, OptionFunc: jwt.WithSubject, BuildFunc: func(v string) (jwt.Token, error) { return jwt.NewBuilder(). Subject(v). Build() }, }, } for _, tc := range testcases { t.Run(tc.ClaimName, func(t *testing.T) { t.Parallel() t1, err := tc.BuildFunc(tc.ClaimValue) require.NoError(t, err, `jwt.NewBuilder should succeed`) // This should succeed, because validation option (tc.OptionFunc) // is not provided in the optional parameters require.NoError(t, jwt.Validate(t1), "t1.Validate should succeed") // This should succeed, because the option is provided with same value require.NoError(t, jwt.Validate(t1, tc.OptionFunc(tc.ClaimValue)), "t1.Validate should succeed") require.Error(t, jwt.Validate(t1, jwt.WithIssuer("poop")), "t1.Validate should fail") }) } t.Run(jwt.IssuerKey, func(t *testing.T) { t.Parallel() t1, err := jwt.NewBuilder(). Issuer("github.com/lestrrat-go/jwx/v3"). Build() require.NoError(t, err, `jwt.NewBuilder should succeed`) // This should succeed, because WithIssuer is not provided in the // optional parameters require.NoError(t, jwt.Validate(t1), "jwt.Validate should succeed") // This should succeed, because WithIssuer is provided with same value iss, ok := t1.Issuer() require.True(t, ok, `t1.Issuer should succeed`) require.NoError(t, jwt.Validate(t1, jwt.WithIssuer(iss)), "jwt.Validate should succeed") err = jwt.Validate(t1, jwt.WithIssuer("poop")) require.Error(t, err, "jwt.Validate should fail") require.ErrorIs(t, err, jwt.InvalidIssuerError(), "error should be jwt.InvalidIssuerError") require.ErrorIs(t, err, jwt.ValidateError(), "error should be a validation error") }) t.Run(jwt.IssuedAtKey, func(t *testing.T) { t.Parallel() tm := time.Now() t1, err := jwt.NewBuilder(). Claim(jwt.IssuedAtKey, tm). Build() require.NoError(t, err, `jwt.NewBuilder should succeed`) testcases := []struct { Name string Options []jwt.ValidateOption Error bool }{ { Name: `clock is set to before iat`, Error: true, Options: []jwt.ValidateOption{ jwt.WithClock(jwt.ClockFunc(func() time.Time { return tm.Add(-1 * time.Hour) })), }, }, { // This works because the sub-second difference is rounded Name: `clock is set to some sub-seconds before iat`, Error: true, Options: []jwt.ValidateOption{ jwt.WithClock(jwt.ClockFunc(func() time.Time { return tm.Add(-1 * time.Millisecond) })), }, }, { Name: `clock is set to some sub-seconds before iat (trunc = 0)`, Error: true, Options: []jwt.ValidateOption{ jwt.WithClock(jwt.ClockFunc(func() time.Time { return tm.Add(-1 * time.Millisecond) })), }, }, } for _, tc := range testcases { t.Run(tc.Name, func(t *testing.T) { err := jwt.Validate(t1, tc.Options...) if !tc.Error { require.NoError(t, err, `jwt.Validate should succeed`) return } require.Error(t, err, `jwt.Validate should fail`) require.ErrorIs(t, err, jwt.InvalidIssuedAtError(), `error should be jwt.ErrInvalidIssuedAt`) require.NotErrorIs(t, err, jwt.TokenNotYetValidError(), `error should be not ErrNotYetValid`) require.ErrorIs(t, err, jwt.ValidateError(), `error should be a validation error`) }) } }) t.Run(jwt.AudienceKey, func(t *testing.T) { t.Parallel() t1, err := jwt.NewBuilder(). Claim(jwt.AudienceKey, []string{"foo", "bar", "baz"}). Build() require.NoError(t, err, `jwt.NewBuilder should succeed`) // This should succeed, because WithAudience is not provided in the // optional parameters t.Run("`aud` check disabled", func(t *testing.T) { t.Parallel() require.NoError(t, jwt.Validate(t1), `jwt.Validate should succeed`) }) // This should succeed, because WithAudience is provided, and its // value matches one of the audience values t.Run("`aud` contains `baz`", func(t *testing.T) { t.Parallel() require.NoError(t, jwt.Validate(t1, jwt.WithAudience("baz")), "jwt.Validate should succeed") }) t.Run("check `aud` contains `poop`", func(t *testing.T) { t.Parallel() err := jwt.Validate(t1, jwt.WithAudience("poop")) require.Error(t, err, "token.Validate should fail") require.ErrorIs(t, err, jwt.InvalidAudienceError(), `error should be ErrInvalidAudience`) require.True(t, errors.Is(err, jwt.ValidateError()), `error should be a validation error`) }) }) t.Run(jwt.SubjectKey, func(t *testing.T) { t.Parallel() t1, err := jwt.NewBuilder(). Claim(jwt.SubjectKey, "github.com/lestrrat-go/jwx/v3"). Build() require.NoError(t, err, `jwt.NewBuilder should succeed`) // This should succeed, because WithSubject is not provided in the // optional parameters require.NoError(t, jwt.Validate(t1), "token.Validate should succeed") // This should succeed, because WithSubject is provided with same value sub, ok := t1.Subject() require.True(t, ok, `t1.Subject should succeed`) require.NoError(t, jwt.Validate(t1, jwt.WithSubject(sub)), "token.Validate should succeed") require.Error(t, jwt.Validate(t1, jwt.WithSubject("poop")), "token.Validate should fail") }) t.Run(jwt.NotBeforeKey, func(t *testing.T) { t.Parallel() // NotBefore is set to future date tm := time.Now().Add(72 * time.Hour) t1, err := jwt.NewBuilder(). Claim(jwt.NotBeforeKey, tm). Build() require.NoError(t, err, `jwt.NewBuilder should succeed`) testcases := []struct { Name string Options []jwt.ValidateOption Error bool }{ { // This should fail, because nbf is the future Name: `'nbf' is less than current time`, Error: true, }, { // This should succeed, because we have given reaaaaaaly big skew Name: `skew is large enough`, Options: []jwt.ValidateOption{ jwt.WithAcceptableSkew(73 * time.Hour), }, }, { // This should succeed, because we have given a time // that is well enough into the future Name: `clock is set to some time after in nbf`, Options: []jwt.ValidateOption{ jwt.WithClock(jwt.ClockFunc(func() time.Time { return tm.Add(time.Hour) })), }, }, { // This should succeed, the time == NotBefore time // Note, this could fail if you are returning a monotonic clock // and we didn't do something about it Name: `clock is set to the same time as nbf`, Options: []jwt.ValidateOption{ jwt.WithClock(jwt.ClockFunc(func() time.Time { return tm })), }, }, { Name: `clock is set to some sub-seconds before nbf`, Error: true, Options: []jwt.ValidateOption{ jwt.WithClock(jwt.ClockFunc(func() time.Time { return tm.Add(-1 * time.Millisecond) })), }, }, { Name: `clock is set to some sub-seconds before nbf (but truncation = default)`, Error: true, Options: []jwt.ValidateOption{ jwt.WithClock(jwt.ClockFunc(func() time.Time { return tm.Add(-1 * time.Millisecond) })), }, }, { Name: `clock is set to some sub-seconds after nbf`, Options: []jwt.ValidateOption{ jwt.WithClock(jwt.ClockFunc(func() time.Time { return tm.Add(time.Second - time.Millisecond) })), }, }, } for _, tc := range testcases { t.Run(tc.Name, func(t *testing.T) { err := jwt.Validate(t1, tc.Options...) if !tc.Error { require.NoError(t, err, "token.Validate should succeed") return } require.Error(t, err, "token.Validate should fail") require.ErrorIs(t, err, jwt.TokenNotYetValidError(), `error should be ErrTokenNotYetValid`) require.NotErrorIs(t, err, jwt.TokenExpiredError(), `error should not be ErrTokenExpired`) require.ErrorIs(t, err, jwt.ValidateError(), `error should be a validation error`) }) } }) t.Run(jwt.ExpirationKey, func(t *testing.T) { t.Parallel() tm := time.Now() t1, err := jwt.NewBuilder(). // issuedAt = 1 Hr before current time Claim(jwt.IssuedAtKey, tm.Add(-1*time.Hour)). // valid for 2 minutes only from IssuedAt Claim(jwt.ExpirationKey, tm). Build() require.NoError(t, err, `jwt.NewBuilder should succeed`) testcases := []struct { Name string Options []jwt.ValidateOption Error bool }{ { Name: `clock is not modified (exp < now)`, Error: true, }, { Name: `clock is set to some time before exp`, Options: []jwt.ValidateOption{ jwt.WithClock(jwt.ClockFunc(func() time.Time { return tm.Add(-1 * time.Hour) })), }, }, { // This should fail, the time == Expiration. // Note, this could fail if you are returning a monotonic clock // and we didn't do something about it Name: `clock is set to same time as exp`, Error: true, Options: []jwt.ValidateOption{ jwt.WithClock(jwt.ClockFunc(func() time.Time { return tm })), }, }, { Name: `clock is set to some sub-seconds after exp`, Error: true, Options: []jwt.ValidateOption{ jwt.WithClock(jwt.ClockFunc(func() time.Time { return tm.Add(time.Millisecond) })), jwt.WithTruncation(0), }, }, { Name: `clock is set to some sub-seconds after exp (but truncation = default)`, Error: true, Options: []jwt.ValidateOption{ jwt.WithClock(jwt.ClockFunc(func() time.Time { return tm.Add(time.Millisecond) })), }, }, { Name: `clock is set to some sub-seconds before exp`, Options: []jwt.ValidateOption{ jwt.WithClock(jwt.ClockFunc(func() time.Time { return tm.Add(-1 * time.Millisecond) })), jwt.WithTruncation(0), }, }, } for _, tc := range testcases { t.Run(tc.Name, func(t *testing.T) { err := jwt.Validate(t1, tc.Options...) if !tc.Error { require.NoError(t, err, `jwt.Validate should succeed`) return } require.Error(t, err, `jwt.Validate should fail`) require.NotErrorIs(t, err, jwt.TokenNotYetValidError(), `error should not be ErrTokenNotYetValid`) require.ErrorIs(t, err, jwt.TokenExpiredError(), `error should be ErrTokenExpired`) require.ErrorIs(t, err, jwt.ValidateError(), `error should be a validation error`) }) } }) t.Run("Unix zero times", func(t *testing.T) { // See comments at ref: handling iat, nbf, and exp in v3 t.Parallel() // tm := time.Unix(0, 0) t1, err := jwt.NewBuilder(). //Claim(jwt.NotBeforeKey, tm). //Claim(jwt.IssuedAtKey, tm). //Claim(jwt.ExpirationKey, tm). Build() require.NoError(t, err, `jwt.NewBuilder should succeed`) // This should pass because the unix zero times should be ignored require.NoError(t, jwt.Validate(t1), "token.Validate should pass") }) t.Run("Go zero times", func(t *testing.T) { // ref: handling iat, nbf, and exp in v3 // Previously (v2) we used to treat the zero value as the same as // the field not existing, but this is no longer true. // // This test/ used to pass in v2 even when we set exp to time.Time{}, // but it is no longer the case in v3. To emulate the previous // behavior, we need to _NOT_ set the exp field at all t.Parallel() tm := time.Time{} t1, err := jwt.NewBuilder(). Claim(jwt.NotBeforeKey, tm). Claim(jwt.IssuedAtKey, tm). // Claim(jwt.ExpirationKey, tm). // Omit this Build() require.NoError(t, err, `jwt.NewBuilder should succeed`) // This should pass because the go zero times should be ignored require.NoError(t, jwt.Validate(t1), "token.Validate should pass") }) t.Run("Parse and validate", func(t *testing.T) { t.Parallel() tm := time.Now() t1, err := jwt.NewBuilder(). // issuedAt = 1 Hr before current time Claim(jwt.IssuedAtKey, tm.Add(-1*time.Hour)). // valid for 2 minutes only from IssuedAt Claim(jwt.ExpirationKey, tm.Add(-58*time.Minute)). Build() require.NoError(t, err, `jwt.NewBuilder should succeed`) buf, err := json.Marshal(t1) require.NoError(t, err, `json.Marshal should succeed`) _, err = jwt.Parse(buf, jwt.WithVerify(false), jwt.WithValidate(true)) // This should fail, because exp is set in the past require.Error(t, err, "jwt.Parse should fail") _, err = jwt.Parse(buf, jwt.WithVerify(false), jwt.WithValidate(true), jwt.WithAcceptableSkew(time.Hour)) // This should succeed, because we have given big skew // that is well enough to get us accepted require.NoError(t, err, "jwt.Parse should succeed (1)") // This should succeed, because we have given a time // that is well enough into the past clock := jwt.ClockFunc(func() time.Time { return tm.Add(-59 * time.Minute) }) _, err = jwt.Parse(buf, jwt.WithVerify(false), jwt.WithValidate(true), jwt.WithClock(clock)) require.NoError(t, err, "jwt.Parse should succeed (2)") }) t.Run("any claim value", func(t *testing.T) { t.Parallel() t1, err := jwt.NewBuilder(). Claim("email", "email@example.com"). Build() require.NoError(t, err, `jwt.NewBuilder should succeed`) // This should succeed, because WithClaimValue("email", "xxx") is not provided in the // optional parameters require.NoError(t, jwt.Validate(t1), "t1.Validate should succeed") // This should succeed, because WithClaimValue is provided with same value require.NoError(t, jwt.Validate(t1, jwt.WithClaimValue("email", "email@example.com")), "t1.Validate should succeed") require.Error(t, jwt.Validate(t1, jwt.WithClaimValue("email", "poop")), "t1.Validate should fail") require.Error(t, jwt.Validate(t1, jwt.WithClaimValue("xxxx", "email@example.com")), "t1.Validate should fail") require.Error(t, jwt.Validate(t1, jwt.WithClaimValue("xxxx", "")), "t1.Validate should fail") }) } //nolint:tparallel func TestGH010(t *testing.T) { // This test relies on behavior that was present in v2, but is no longer // present in v3. This requires setting a global value, so we cannot // run this in parallel jwt.Settings(jwt.WithTruncation(time.Second)) t.Cleanup(func() { jwt.Settings(jwt.WithTruncation(0)) }) // Simple string claims testcases := []struct { ClaimName string ClaimValue string OptionFunc func(string) jwt.ValidateOption BuildFunc func(v string) (jwt.Token, error) }{ { ClaimName: jwt.JwtIDKey, ClaimValue: `my-sepcial-key`, OptionFunc: jwt.WithJwtID, BuildFunc: func(v string) (jwt.Token, error) { return jwt.NewBuilder(). JwtID(v). Build() }, }, { ClaimName: jwt.SubjectKey, ClaimValue: `very important subject`, OptionFunc: jwt.WithSubject, BuildFunc: func(v string) (jwt.Token, error) { return jwt.NewBuilder(). Subject(v). Build() }, }, } for _, tc := range testcases { t.Run(tc.ClaimName, func(t *testing.T) { t.Parallel() t1, err := tc.BuildFunc(tc.ClaimValue) require.NoError(t, err, `jwt.NewBuilder should succeed`) // This should succeed, because validation option (tc.OptionFunc) // is not provided in the optional parameters require.NoError(t, jwt.Validate(t1), "t1.Validate should succeed") // This should succeed, because the option is provided with same value require.NoError(t, jwt.Validate(t1, tc.OptionFunc(tc.ClaimValue)), "t1.Validate should succeed") require.Error(t, jwt.Validate(t1, jwt.WithIssuer("poop")), "t1.Validate should fail") }) } t.Run(jwt.IssuerKey, func(t *testing.T) { t.Parallel() t1, err := jwt.NewBuilder(). Issuer("github.com/lestrrat-go/jwx/v3"). Build() require.NoError(t, err, `jwt.NewBuilder should succeed`) // This should succeed, because WithIssuer is not provided in the // optional parameters require.NoError(t, jwt.Validate(t1), "jwt.Validate should succeed") // This should succeed, because WithIssuer is provided with same value iss, ok := t1.Issuer() require.True(t, ok, `t1.Issuer should succeed`) require.NoError(t, jwt.Validate(t1, jwt.WithIssuer(iss)), "jwt.Validate should succeed") err = jwt.Validate(t1, jwt.WithIssuer("poop")) require.Error(t, err, "jwt.Validate should fail") require.ErrorIs(t, err, jwt.InvalidIssuerError(), "error should be jwt.InvalidIssuerError") require.ErrorIs(t, err, jwt.ValidateError(), "error should be a validation error") }) t.Run(jwt.IssuedAtKey, func(t *testing.T) { t.Parallel() tm := time.Now() t1, err := jwt.NewBuilder(). Claim(jwt.IssuedAtKey, tm). Build() require.NoError(t, err, `jwt.NewBuilder should succeed`) testcases := []struct { Name string Options []jwt.ValidateOption Error bool }{ { Name: `clock is set to before iat`, Error: true, Options: []jwt.ValidateOption{ jwt.WithClock(jwt.ClockFunc(func() time.Time { return tm.Add(-1 * time.Hour) })), }, }, { // This works because the sub-second difference is rounded Name: `clock is set to some sub-seconds before iat`, Options: []jwt.ValidateOption{ jwt.WithClock(jwt.ClockFunc(func() time.Time { return tm.Add(-1 * time.Millisecond) })), }, }, { Name: `clock is set to some sub-seconds before iat (trunc = 0)`, Error: true, Options: []jwt.ValidateOption{ jwt.WithClock(jwt.ClockFunc(func() time.Time { return tm.Add(-1 * time.Millisecond) })), jwt.WithTruncation(0), }, }, } for _, tc := range testcases { t.Run(tc.Name, func(t *testing.T) { err := jwt.Validate(t1, tc.Options...) if !tc.Error { require.NoError(t, err, `jwt.Validate should succeed`) return } require.Error(t, err, `jwt.Validate should fail`) require.ErrorIs(t, err, jwt.InvalidIssuedAtError(), `error should be jwt.ErrInvalidIssuedAt`) require.NotErrorIs(t, err, jwt.TokenNotYetValidError(), `error should be not ErrNotYetValid`) require.ErrorIs(t, err, jwt.ValidateError(), `error should be a validation error`) }) } }) t.Run(jwt.AudienceKey, func(t *testing.T) { t.Parallel() t1, err := jwt.NewBuilder(). Claim(jwt.AudienceKey, []string{"foo", "bar", "baz"}). Build() require.NoError(t, err, `jwt.NewBuilder should succeed`) // This should succeed, because WithAudience is not provided in the // optional parameters t.Run("`aud` check disabled", func(t *testing.T) { t.Parallel() require.NoError(t, jwt.Validate(t1), `jwt.Validate should succeed`) }) // This should succeed, because WithAudience is provided, and its // value matches one of the audience values t.Run("`aud` contains `baz`", func(t *testing.T) { t.Parallel() require.NoError(t, jwt.Validate(t1, jwt.WithAudience("baz")), "jwt.Validate should succeed") }) t.Run("check `aud` contains `poop`", func(t *testing.T) { t.Parallel() err := jwt.Validate(t1, jwt.WithAudience("poop")) require.Error(t, err, "token.Validate should fail") require.ErrorIs(t, err, jwt.InvalidAudienceError(), `error should be ErrInvalidAudience`) require.True(t, errors.Is(err, jwt.ValidateError()), `error should be a validation error`) }) }) t.Run(jwt.SubjectKey, func(t *testing.T) { t.Parallel() t1, err := jwt.NewBuilder(). Claim(jwt.SubjectKey, "github.com/lestrrat-go/jwx/v3"). Build() require.NoError(t, err, `jwt.NewBuilder should succeed`) // This should succeed, because WithSubject is not provided in the // optional parameters require.NoError(t, jwt.Validate(t1), "token.Validate should succeed") // This should succeed, because WithSubject is provided with same value sub, ok := t1.Subject() require.True(t, ok, `t1.Subject should succeed`) require.NoError(t, jwt.Validate(t1, jwt.WithSubject(sub)), "token.Validate should succeed") require.Error(t, jwt.Validate(t1, jwt.WithSubject("poop")), "token.Validate should fail") }) t.Run(jwt.NotBeforeKey, func(t *testing.T) { t.Parallel() // NotBefore is set to future date tm := time.Now().Add(72 * time.Hour) t1, err := jwt.NewBuilder(). Claim(jwt.NotBeforeKey, tm). Build() require.NoError(t, err, `jwt.NewBuilder should succeed`) testcases := []struct { Name string Options []jwt.ValidateOption Error bool }{ { // This should fail, because nbf is the future Name: `'nbf' is less than current time`, Error: true, }, { // This should succeed, because we have given reaaaaaaly big skew Name: `skew is large enough`, Options: []jwt.ValidateOption{ jwt.WithAcceptableSkew(73 * time.Hour), }, }, { // This should succeed, because we have given a time // that is well enough into the future Name: `clock is set to some time after in nbf`, Options: []jwt.ValidateOption{ jwt.WithClock(jwt.ClockFunc(func() time.Time { return tm.Add(time.Hour) })), }, }, { // This should succeed, the time == NotBefore time // Note, this could fail if you are returning a monotonic clock // and we didn't do something about it Name: `clock is set to the same time as nbf`, Options: []jwt.ValidateOption{ jwt.WithClock(jwt.ClockFunc(func() time.Time { return tm })), }, }, { Name: `clock is set to some sub-seconds before nbf`, Error: true, Options: []jwt.ValidateOption{ jwt.WithClock(jwt.ClockFunc(func() time.Time { return tm.Add(-1 * time.Millisecond) })), jwt.WithTruncation(0), }, }, { Name: `clock is set to some sub-seconds before nbf (but truncation = default)`, Options: []jwt.ValidateOption{ jwt.WithClock(jwt.ClockFunc(func() time.Time { return tm.Add(-1 * time.Millisecond) })), }, }, { Name: `clock is set to some sub-seconds after nbf`, Options: []jwt.ValidateOption{ jwt.WithClock(jwt.ClockFunc(func() time.Time { return tm.Add(time.Millisecond) })), jwt.WithTruncation(0), }, }, } for _, tc := range testcases { t.Run(tc.Name, func(t *testing.T) { err := jwt.Validate(t1, tc.Options...) if !tc.Error { require.NoError(t, err, "token.Validate should succeed") return } require.Error(t, err, "token.Validate should fail") require.ErrorIs(t, err, jwt.TokenNotYetValidError(), `error should be ErrTokenNotYetValid`) require.NotErrorIs(t, err, jwt.TokenExpiredError(), `error should not be ErrTokenExpired`) require.ErrorIs(t, err, jwt.ValidateError(), `error should be a validation error`) }) } }) t.Run(jwt.ExpirationKey, func(t *testing.T) { t.Parallel() tm := time.Now() t1, err := jwt.NewBuilder(). // issuedAt = 1 Hr before current time Claim(jwt.IssuedAtKey, tm.Add(-1*time.Hour)). // valid for 2 minutes only from IssuedAt Claim(jwt.ExpirationKey, tm). Build() require.NoError(t, err, `jwt.NewBuilder should succeed`) testcases := []struct { Name string Options []jwt.ValidateOption Error bool }{ { Name: `clock is not modified (exp < now)`, Error: true, }, { Name: `clock is set to some time before exp`, Options: []jwt.ValidateOption{ jwt.WithClock(jwt.ClockFunc(func() time.Time { return tm.Add(-1 * time.Hour) })), }, }, { // This should fail, the time == Expiration. // Note, this could fail if you are returning a monotonic clock // and we didn't do something about it Name: `clock is set to same time as exp`, Error: true, Options: []jwt.ValidateOption{ jwt.WithClock(jwt.ClockFunc(func() time.Time { return tm })), }, }, { Name: `clock is set to some sub-seconds after exp`, Error: true, Options: []jwt.ValidateOption{ jwt.WithClock(jwt.ClockFunc(func() time.Time { return tm.Add(time.Millisecond) })), jwt.WithTruncation(0), }, }, { Name: `clock is set to some sub-seconds after exp (but truncation = default)`, Error: true, Options: []jwt.ValidateOption{ jwt.WithClock(jwt.ClockFunc(func() time.Time { return tm.Add(time.Millisecond) })), }, }, { Name: `clock is set to some sub-seconds before exp`, Options: []jwt.ValidateOption{ jwt.WithClock(jwt.ClockFunc(func() time.Time { return tm.Add(-1 * time.Millisecond) })), jwt.WithTruncation(0), }, }, } for _, tc := range testcases { t.Run(tc.Name, func(t *testing.T) { err := jwt.Validate(t1, tc.Options...) if !tc.Error { require.NoError(t, err, `jwt.Validate should succeed`) return } require.Error(t, err, `jwt.Validate should fail`) require.NotErrorIs(t, err, jwt.TokenNotYetValidError(), `error should not be ErrTokenNotYetValid`) require.ErrorIs(t, err, jwt.TokenExpiredError(), `error should be ErrTokenExpired`) require.ErrorIs(t, err, jwt.ValidateError(), `error should be a validation error`) }) } }) t.Run("Unix zero times", func(t *testing.T) { // See comments at ref: handling iat, nbf, and exp in v3 t.Parallel() // tm := time.Unix(0, 0) t1, err := jwt.NewBuilder(). //Claim(jwt.NotBeforeKey, tm). //Claim(jwt.IssuedAtKey, tm). //Claim(jwt.ExpirationKey, tm). Build() require.NoError(t, err, `jwt.NewBuilder should succeed`) // This should pass because the unix zero times should be ignored require.NoError(t, jwt.Validate(t1), "token.Validate should pass") }) t.Run("Go zero times", func(t *testing.T) { // ref: handling iat, nbf, and exp in v3 // Previously (v2) we used to treat the zero value as the same as // the field not existing, but this is no longer true. // // This test/ used to pass in v2 even when we set exp to time.Time{}, // but it is no longer the case in v3. To emulate the previous // behavior, we need to _NOT_ set the exp field at all t.Parallel() tm := time.Time{} t1, err := jwt.NewBuilder(). Claim(jwt.NotBeforeKey, tm). Claim(jwt.IssuedAtKey, tm). // Claim(jwt.ExpirationKey, tm). // Omit this Build() require.NoError(t, err, `jwt.NewBuilder should succeed`) // This should pass because the go zero times should be ignored require.NoError(t, jwt.Validate(t1), "token.Validate should pass") }) t.Run("Parse and validate", func(t *testing.T) { t.Parallel() tm := time.Now() t1, err := jwt.NewBuilder(). // issuedAt = 1 Hr before current time Claim(jwt.IssuedAtKey, tm.Add(-1*time.Hour)). // valid for 2 minutes only from IssuedAt Claim(jwt.ExpirationKey, tm.Add(-58*time.Minute)). Build() require.NoError(t, err, `jwt.NewBuilder should succeed`) buf, err := json.Marshal(t1) require.NoError(t, err, `json.Marshal should succeed`) _, err = jwt.Parse(buf, jwt.WithVerify(false), jwt.WithValidate(true)) // This should fail, because exp is set in the past require.Error(t, err, "jwt.Parse should fail") _, err = jwt.Parse(buf, jwt.WithVerify(false), jwt.WithValidate(true), jwt.WithAcceptableSkew(time.Hour)) // This should succeed, because we have given big skew // that is well enough to get us accepted require.NoError(t, err, "jwt.Parse should succeed (1)") // This should succeed, because we have given a time // that is well enough into the past clock := jwt.ClockFunc(func() time.Time { return tm.Add(-59 * time.Minute) }) _, err = jwt.Parse(buf, jwt.WithVerify(false), jwt.WithValidate(true), jwt.WithClock(clock)) require.NoError(t, err, "jwt.Parse should succeed (2)") }) t.Run("any claim value", func(t *testing.T) { t.Parallel() t1, err := jwt.NewBuilder(). Claim("email", "email@example.com"). Build() require.NoError(t, err, `jwt.NewBuilder should succeed`) // This should succeed, because WithClaimValue("email", "xxx") is not provided in the // optional parameters require.NoError(t, jwt.Validate(t1), "t1.Validate should succeed") // This should succeed, because WithClaimValue is provided with same value require.NoError(t, jwt.Validate(t1, jwt.WithClaimValue("email", "email@example.com")), "t1.Validate should succeed") require.Error(t, jwt.Validate(t1, jwt.WithClaimValue("email", "poop")), "t1.Validate should fail") require.Error(t, jwt.Validate(t1, jwt.WithClaimValue("xxxx", "email@example.com")), "t1.Validate should fail") require.Error(t, jwt.Validate(t1, jwt.WithClaimValue("xxxx", "")), "t1.Validate should fail") }) } func TestClaimValidator(t *testing.T) { t.Parallel() const myClaim = "my-claim" err0 := errors.New(myClaim + " does not exist") v := jwt.ValidatorFunc(func(_ context.Context, tok jwt.Token) error { if !tok.Has(myClaim) { return err0 } return nil }) testcases := []struct { Name string MakeToken func() jwt.Token Error error }{ { Name: "Successful validation", MakeToken: func() jwt.Token { t1 := jwt.New() _ = t1.Set(myClaim, map[string]any{"k": "v"}) return t1 }, }, { Name: "Target claim does not exist", MakeToken: func() jwt.Token { t1 := jwt.New() _ = t1.Set("other-claim", map[string]any{"k": "v"}) return t1 }, Error: err0, }, } for _, tc := range testcases { t.Run(tc.Name, func(t *testing.T) { t.Parallel() t1 := tc.MakeToken() if err := tc.Error; err != nil { require.ErrorIs(t, jwt.Validate(t1, jwt.WithValidator(v)), err) return } require.NoError(t, jwt.Validate(t1, jwt.WithValidator(v))) }) } } golang-github-lestrrat-go-jwx-3.0.13/jwt/verify_test.go000066400000000000000000000263341515060566400231120ustar00rootroot00000000000000package jwt_test import ( "encoding/base64" "fmt" "strings" "testing" "time" "github.com/lestrrat-go/jwx/v3/internal/json" "github.com/lestrrat-go/jwx/v3/internal/jwxtest" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/lestrrat-go/jwx/v3/jwk" "github.com/lestrrat-go/jwx/v3/jws" "github.com/lestrrat-go/jwx/v3/jwt" "github.com/stretchr/testify/require" ) // TestVerifyCompactFastSecurityBypass tests potential security vulnerabilities // when the fast path bypasses certain security checks that are normally // performed by jws.Verify. func TestVerifyCompactFastSecurityBypass(t *testing.T) { t.Run("Algorithm confusion with single WithKey option", func(t *testing.T) { // Create a JWT signed with HS256 (symmetric key) secret := []byte("secret-key-for-hmac") token := jwt.New() require.NoError(t, token.Set(jwt.IssuerKey, "test"), `token.Set should succeed`) require.NoError(t, token.Set(jwt.SubjectKey, "user123"), `token.Set should succeed`) signedHS256, err := jwt.Sign(token, jwt.WithKey(jwa.HS256(), secret)) require.NoError(t, err, `jwt.Sign should succeed`) // Generate an RSA key pair rsaKey, err := jwxtest.GenerateRsaKey() require.NoError(t, err, `jwxtest.GenerateRsaKey should succeed`) // Test 1: Try to verify HS256 JWT using RS256 key (should fail) // This should fail because algorithm confusion should be prevented _, err = jwt.Parse(signedHS256, jwt.WithKey(jwa.RS256(), rsaKey.PublicKey)) require.Error(t, err, `jwt.Parse should fail when trying to use RS256 key for HS256 JWT`) // Test 2: Ensure the fast path doesn't bypass algorithm validation // The fast path should still validate that the algorithm in the header // matches the algorithm specified in WithKey _, err = jwt.Parse(signedHS256, jwt.WithKey(jwa.RS256(), rsaKey.PublicKey)) require.Error(t, err, `fast path should not allow algorithm confusion`) }) t.Run("Header tampering detection", func(t *testing.T) { secret := []byte("test-secret") token := jwt.New() require.NoError(t, token.Set(jwt.IssuerKey, "test"), `token.Set should succeed`) signedJWT, err := jwt.Sign(token, jwt.WithKey(jwa.HS256(), secret)) require.NoError(t, err, `jwt.Sign should succeed`) // Parse the JWT to get its components parts := strings.Split(string(signedJWT), ".") require.Len(t, parts, 3, `JWT should have 3 parts`) // Decode the header headerBytes, err := base64.RawURLEncoding.DecodeString(parts[0]) require.NoError(t, err, `header decode should succeed`) // Parse header JSON var header map[string]any require.NoError(t, json.Unmarshal(headerBytes, &header), `header unmarshal should succeed`) // Tamper with the algorithm in the header (change to "none") header["alg"] = "none" tamperedHeaderBytes, err := json.Marshal(header) require.NoError(t, err, `header marshal should succeed`) // Encode the tampered header tamperedHeader := base64.RawURLEncoding.EncodeToString(tamperedHeaderBytes) // Create JWT with tampered header tamperedJWT := tamperedHeader + "." + parts[1] + "." + parts[2] // Test: The parser should detect header tampering _, err = jwt.Parse([]byte(tamperedJWT), jwt.WithKey(jwa.HS256(), secret)) require.Error(t, err, `jwt.Parse should fail with tampered header`) }) t.Run("Critical header bypass", func(t *testing.T) { secret := []byte("test-secret") // Create a JWT with critical header that requires special handling token := jwt.New() require.NoError(t, token.Set(jwt.IssuerKey, "test"), `token.Set should succeed`) // Create JWS with critical header headers := jws.NewHeaders() require.NoError(t, headers.Set("crit", []string{"exp"}), `headers.Set should succeed`) require.NoError(t, headers.Set("exp", time.Now().Add(time.Hour).Unix()), `headers.Set should succeed`) signed, err := jws.Sign(json.RawMessage(`{"iss":"test"}`), jws.WithKey(jwa.HS256(), secret, jws.WithProtectedHeaders(headers))) require.NoError(t, err, `jws.Sign should succeed`) // The fast path should not bypass critical header validation // Note: This tests whether VerifyCompactFast properly handles critical headers _, err = jwt.Parse(signed, jwt.WithKey(jwa.HS256(), secret)) // This should either succeed with proper critical header handling or fail gracefully // The key point is that it shouldn't silently bypass the critical header check if err != nil { t.Logf("Critical header validation failed as expected: %v", err) } else { t.Logf("Critical header was properly handled") } }) t.Run("Key validation bypass", func(t *testing.T) { // Test that the fast path doesn't bypass key validation when WithValidateKey is used // Create an RSA key and import it as JWK rsaKey, err := jwxtest.GenerateRsaKey() require.NoError(t, err, `jwxtest.GenerateRsaKey should succeed`) jwkKey, err := jwk.Import(rsaKey) require.NoError(t, err, `jwk.Import should succeed`) // Corrupt the key by setting invalid D value (private exponent) require.NoError(t, jwkKey.Set(jwk.RSADKey, []byte{1, 2, 3}), `jwkKey.Set should succeed`) token := jwt.New() require.NoError(t, token.Set(jwt.IssuerKey, "test"), `token.Set should succeed`) // Serialize the token to JSON for JWS signing tokenBytes, err := json.Marshal(token) require.NoError(t, err, `json.Marshal should succeed`) // Try to sign with the corrupted key and key validation enabled // This should fail even in the fast path _, err = jws.Sign(tokenBytes, jws.WithKey(jwa.RS256(), jwkKey), jws.WithValidateKey(true)) require.Error(t, err, `jws.Sign should fail with invalid key when validation is enabled`) // Also test that JWT parsing with validation fails for invalid keys // Create a valid key first to sign a token validKey, err := jwxtest.GenerateRsaKey() require.NoError(t, err, `jwxtest.GenerateRsaKey should succeed`) signedJWT, err := jwt.Sign(token, jwt.WithKey(jwa.RS256(), validKey)) require.NoError(t, err, `jwt.Sign should succeed`) // Try to verify with corrupted public key and validation enabled corruptedPubKey, err := jwk.Import(&validKey.PublicKey) require.NoError(t, err, `jwk.Import should succeed`) // Corrupt the N value (modulus) require.NoError(t, corruptedPubKey.Set(jwk.RSANKey, []byte{1, 2, 3}), `jwk.Set should succeed`) // This should fail due to key validation _, err = jwt.Parse(signedJWT, jwt.WithKey(jwa.RS256(), corruptedPubKey)) require.Error(t, err, `jwt.Parse should fail with corrupted public key`) }) t.Run("Fast path vs slow path consistency", func(t *testing.T) { // Ensure that both fast path and slow path give the same results for edge cases secret := []byte("test-secret") token := jwt.New() require.NoError(t, token.Set(jwt.IssuerKey, "test"), `token.Set should succeed`) require.NoError(t, token.Set(jwt.ExpirationKey, time.Now().Add(-time.Hour)), `token.Set should succeed`) signedJWT, err := jwt.Sign(token, jwt.WithKey(jwa.HS256(), secret)) require.NoError(t, err, `jwt.Sign should succeed`) // Force slow path by using multiple verify options _, err1 := jwt.Parse(signedJWT, jwt.WithKey(jwa.HS256(), secret), jwt.WithValidate(false)) // Force fast path by using single WithKey option _, err2 := jwt.Parse(signedJWT, jwt.WithKey(jwa.HS256(), secret), jwt.WithValidate(false)) // Both should give consistent results if err1 != nil && err2 != nil { // Both failed - this is consistent t.Logf("Both paths failed consistently") } else if err1 == nil && err2 == nil { // Both succeeded - this is consistent t.Logf("Both paths succeeded consistently") } else { // Inconsistent results - this could indicate a security issue require.Fail(t, "Fast path and slow path gave inconsistent results", "slow path error: %v, fast path error: %v", err1, err2) } }) t.Run("Malformed JWT handling", func(t *testing.T) { secret := []byte("test-secret") // Test various malformed JWTs to ensure fast path doesn't bypass format validation malformedJWTs := []string{ "invalid.jwt.format.extra", // Too many parts "invalid.jwt", // Too few parts "invalid..signature", // Empty payload ".payload.signature", // Empty header "header.payload.", // Empty signature } for _, malformedJWT := range malformedJWTs { t.Run(fmt.Sprintf("malformed_%s", malformedJWT), func(t *testing.T) { _, err := jwt.Parse([]byte(malformedJWT), jwt.WithKey(jwa.HS256(), secret)) require.Error(t, err, `jwt.Parse should fail for malformed JWT: %s`, malformedJWT) }) } }) t.Run("None algorithm bypass", func(t *testing.T) { // Test that "alg": "none" cannot be exploited through the fast path secret := []byte("test-secret") // Create a legitimate JWT first token := jwt.New() require.NoError(t, token.Set(jwt.IssuerKey, "test"), `token.Set should succeed`) signedJWT, err := jwt.Sign(token, jwt.WithKey(jwa.HS256(), secret)) require.NoError(t, err, `jwt.Sign should succeed`) // Parse the JWT components parts := strings.Split(string(signedJWT), ".") require.Len(t, parts, 3, `JWT should have 3 parts`) // Create a new header with "alg": "none" noneHeader := map[string]any{ "alg": "none", "typ": "JWT", } noneHeaderBytes, err := json.Marshal(noneHeader) require.NoError(t, err, `json.Marshal should succeed`) noneHeaderEncoded := base64.RawURLEncoding.EncodeToString(noneHeaderBytes) // Create a malicious JWT with "none" algorithm but keeping the original payload and signature maliciousJWT := noneHeaderEncoded + "." + parts[1] + "." + parts[2] // This should fail even if using the fast path _, err = jwt.Parse([]byte(maliciousJWT), jwt.WithKey(jwa.HS256(), secret)) require.Error(t, err, `jwt.Parse should reject JWT with none algorithm when expecting HS256`) // Also test with empty signature as "none" algorithm typically uses maliciousJWTNoSig := noneHeaderEncoded + "." + parts[1] + "." _, err = jwt.Parse([]byte(maliciousJWTNoSig), jwt.WithKey(jwa.HS256(), secret)) require.Error(t, err, `jwt.Parse should reject JWT with none algorithm and no signature`) }) t.Run("Fast path detection", func(t *testing.T) { // This test attempts to verify that the fast path is actually being used // under the specific conditions we're testing secret := []byte("test-secret") token := jwt.New() require.NoError(t, token.Set(jwt.IssuerKey, "test"), `token.Set should succeed`) signedJWT, err := jwt.Sign(token, jwt.WithKey(jwa.HS256(), secret)) require.NoError(t, err, `jwt.Sign should succeed`) // Conditions that should trigger fast path: // 1. Single WithKey option // 2. Valid SignatureAlgorithm // 3. No other options that would force slow path // Test case 1: This should use fast path (single WithKey with valid algorithm) parsed1, err := jwt.Parse(signedJWT, jwt.WithKey(jwa.HS256(), secret)) require.NoError(t, err, `jwt.Parse with single WithKey should succeed`) require.NotNil(t, parsed1, `parsed token should not be nil`) // Test case 2: This should use slow path (multiple options) parsed2, err := jwt.Parse(signedJWT, jwt.WithKey(jwa.HS256(), secret), jwt.WithValidate(false)) require.NoError(t, err, `jwt.Parse with multiple options should succeed`) require.NotNil(t, parsed2, `parsed token should not be nil`) // Both should produce equivalent results require.True(t, jwt.Equal(parsed1, parsed2), `fast path and slow path should produce equivalent results`) }) } golang-github-lestrrat-go-jwx-3.0.13/jwx.go000066400000000000000000000032161515060566400205450ustar00rootroot00000000000000//go:generate ./tools/cmd/genreadfile.sh //go:generate ./tools/cmd/genoptions.sh //go:generate stringer -type=FormatKind //go:generate mv formatkind_string.go formatkind_string_gen.go // Package jwx contains tools that deal with the various JWx (JOSE) // technologies such as JWT, JWS, JWE, etc in Go. // // JWS (https://tools.ietf.org/html/rfc7515) // JWE (https://tools.ietf.org/html/rfc7516) // JWK (https://tools.ietf.org/html/rfc7517) // JWA (https://tools.ietf.org/html/rfc7518) // JWT (https://tools.ietf.org/html/rfc7519) // // Examples are stored in a separate Go module (to avoid adding // dependencies to this module), and thus does not appear in the // online documentation for this module. // You can find the examples in Github at https://github.com/lestrrat-go/jwx/tree/v3/examples // // You can find more high level documentation at Github (https://github.com/lestrrat-go/jwx/tree/v2) // // FAQ style documentation can be found in the repository (https://github.com/lestrrat-go/jwx/tree/develop/v3/docs) package jwx import ( "github.com/lestrrat-go/jwx/v3/internal/json" ) // DecoderSettings gives you a access to configure the "encoding/json".Decoder // used to decode JSON objects within the jwx framework. func DecoderSettings(options ...JSONOption) { // XXX We're using this format instead of just passing a single boolean // in case a new option is to be added some time later var useNumber bool for _, option := range options { switch option.Ident() { case identUseNumber{}: if err := option.Value(&useNumber); err != nil { panic("jwx.DecoderSettings: useNumber option must be a boolean") } } } json.DecoderSettings(useNumber) } golang-github-lestrrat-go-jwx-3.0.13/jwx_test.go000066400000000000000000000553341515060566400216140ustar00rootroot00000000000000package jwx_test import ( "bytes" "context" "crypto/ecdh" "crypto/ecdsa" "crypto/rand" "crypto/rsa" "fmt" "strings" "testing" "github.com/lestrrat-go/jwx/v3" "github.com/lestrrat-go/jwx/v3/internal/jose" "github.com/lestrrat-go/jwx/v3/internal/json" "github.com/lestrrat-go/jwx/v3/internal/jwxtest" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/lestrrat-go/jwx/v3/jwe" "github.com/lestrrat-go/jwx/v3/jwk" ourecdsa "github.com/lestrrat-go/jwx/v3/jwk/ecdsa" "github.com/lestrrat-go/jwx/v3/jws" "github.com/stretchr/testify/require" ) func TestShowBuildInfo(t *testing.T) { t.Logf("Running tests using JSON backend => %s\n", json.Engine()) t.Logf("Available elliptic curves:") for _, alg := range ourecdsa.Algorithms() { t.Logf(" %s", alg) } } type jsonUnmarshalWrapper struct { buf []byte } func (w jsonUnmarshalWrapper) Decode(v any) error { return json.Unmarshal(w.buf, v) } func TestDecoderSetting(t *testing.T) { // DO NOT MAKE THIS TEST PARALLEL. This test uses features with global side effects const src = `{"foo": 1}` for _, useNumber := range []bool{true, false} { t.Run(fmt.Sprintf("jwx.WithUseNumber(%t)", useNumber), func(t *testing.T) { if useNumber { jwx.DecoderSettings(jwx.WithUseNumber(useNumber)) t.Cleanup(func() { jwx.DecoderSettings(jwx.WithUseNumber(false)) }) } // json.NewDecoder must be called AFTER the above jwx.DecoderSettings call decoders := []struct { Name string Decoder interface{ Decode(any) error } }{ {Name: "Decoder", Decoder: json.NewDecoder(strings.NewReader(src))}, {Name: "Unmarshal", Decoder: jsonUnmarshalWrapper{buf: []byte(src)}}, } for _, tc := range decoders { t.Run(tc.Name, func(t *testing.T) { var m map[string]any require.NoError(t, tc.Decoder.Decode(&m), `Decode should succeed`) v, ok := m["foo"] require.True(t, ok, `m["foo"] should exist`) if useNumber { require.Equal(t, json.Number("1"), v, `v should be a json.Number object`) } else { require.Equal(t, float64(1), v, `v should be a float64`) } }) } }) } } // Test compatibility against `jose` tool func TestJoseCompatibility(t *testing.T) { if testing.Short() { t.Logf("Skipped during short tests") return } if !jose.Available() { t.Logf("`jose` binary not available, skipping tests") return } jwe.Settings(jwe.WithMaxPBES2Count(32768)) t.Cleanup(func() { jwe.WithMaxPBES2Count(10000) }) t.Run("jwk", func(t *testing.T) { testcases := []struct { Name string Raw any Template string VerifyKey func(context.Context, *testing.T, jwk.Key) }{ { Name: "RSA Private Key (256)", Raw: rsa.PrivateKey{}, Template: `{"alg": "RS256"}`, }, { Name: "RSA Private Key (384)", Raw: rsa.PrivateKey{}, Template: `{"alg": "RS384"}`, }, { Name: "RSA Private Key (512)", Raw: rsa.PrivateKey{}, Template: `{"alg": "RS512"}`, }, { Name: "RSA Private Key with Private Parameters", Raw: rsa.PrivateKey{}, Template: `{"alg": "RS256", "x-jwx": 1234}`, VerifyKey: func(_ context.Context, t *testing.T, key jwk.Key) { var v float64 require.NoError(t, key.Get(`x-jwx`, &v), `key.Get should succeed`) require.Equal(t, float64(1234), v, `private parameters should match`) }, }, } for _, tc := range testcases { t.Run(tc.Name, func(t *testing.T) { ctx := t.Context() keyfile, cleanup, err := jose.GenerateJwk(ctx, t, tc.Template) require.NoError(t, err, `jose.GenerateJwk should succeed`) defer cleanup() webkey, err := jwxtest.ParseJwkFile(ctx, keyfile) require.NoError(t, err, `ParseJwkFile should succeed`) if vk := tc.VerifyKey; vk != nil { vk(ctx, t, webkey) } require.NoError(t, jwk.Export(webkey, &tc.Raw), `jwk.Export should succeed`) }) } }) t.Run("jwe", func(t *testing.T) { // For some reason "jose" does not come with RSA-OAEP on some platforms. // In order to avoid doing this in an ad-hoc way, we're just going to // ask our jose package for the algorithms that it supports, and generate // the list dynamically ctx := t.Context() set, err := jose.Algorithms(ctx, t) require.NoError(t, err) var tests []interopTest for _, keyenc := range []jwa.KeyEncryptionAlgorithm{jwa.RSA1_5(), jwa.RSA_OAEP(), jwa.RSA_OAEP_256(), jwa.RSA_OAEP_384(), jwa.RSA_OAEP_512()} { if !set.Has(keyenc.String()) { t.Logf("jose does not support key encryption algorithm %q: skipping", keyenc) continue } for _, contentenc := range []jwa.ContentEncryptionAlgorithm{jwa.A128GCM(), jwa.A128CBC_HS256(), jwa.A256CBC_HS512()} { tests = append(tests, interopTest{keyenc, contentenc}) } } for _, keyenc := range []jwa.KeyEncryptionAlgorithm{jwa.ECDH_ES(), jwa.ECDH_ES_A128KW(), jwa.A128KW(), jwa.A128GCMKW(), jwa.A256KW(), jwa.A256GCMKW(), jwa.PBES2_HS256_A128KW(), jwa.DIRECT()} { if !set.Has(keyenc.String()) { t.Logf("jose does not support key encryption algorithm %q: skipping", keyenc) continue } for _, contentenc := range []jwa.ContentEncryptionAlgorithm{jwa.A128GCM(), jwa.A128CBC_HS256()} { tests = append(tests, interopTest{keyenc, contentenc}) } } for _, keyenc := range []jwa.KeyEncryptionAlgorithm{jwa.ECDH_ES(), jwa.ECDH_ES_A256KW(), jwa.A256KW(), jwa.A256GCMKW(), jwa.PBES2_HS512_A256KW(), jwa.DIRECT()} { if !set.Has(keyenc.String()) { t.Logf("jose does not support key encryption algorithm %q: skipping", keyenc) continue } for _, contentenc := range []jwa.ContentEncryptionAlgorithm{jwa.A256GCM(), jwa.A256CBC_HS512()} { tests = append(tests, interopTest{keyenc, contentenc}) } } for _, keyenc := range []jwa.KeyEncryptionAlgorithm{jwa.PBES2_HS384_A192KW()} { if !set.Has(keyenc.String()) { t.Logf("jose does not support key encryption algorithm %q: skipping", keyenc) continue } for _, contentenc := range []jwa.ContentEncryptionAlgorithm{jwa.A192GCM(), jwa.A192CBC_HS384()} { tests = append(tests, interopTest{keyenc, contentenc}) } } for _, test := range tests { t.Run(fmt.Sprintf("%s-%s", test.alg, test.enc), func(t *testing.T) { ctx := t.Context() joseInteropTest(ctx, test, t) }) } }) t.Run("jws", func(t *testing.T) { tests := []jwa.SignatureAlgorithm{ jwa.ES256(), //jwa.ES256K, jwa.ES384(), jwa.ES512(), //jwa.EdDSA, jwa.HS256(), jwa.HS384(), jwa.HS512(), jwa.PS256(), jwa.PS384(), jwa.PS512(), jwa.RS256(), jwa.RS384(), jwa.RS512(), } for _, test := range tests { t.Run(test.String(), func(t *testing.T) { t.Parallel() ctx := t.Context() joseJwsInteropTest(ctx, test, t) }) } }) } type interopTest struct { alg jwa.KeyEncryptionAlgorithm enc jwa.ContentEncryptionAlgorithm } func joseInteropTest(ctx context.Context, spec interopTest, t *testing.T) { t.Helper() expected := []byte("Lorem ipsum") // let jose generate a key file alg := spec.alg.String() if spec.alg == jwa.DIRECT() { alg = spec.enc.String() } joseJwkFile, joseJwkCleanup, err := jose.GenerateJwk(ctx, t, fmt.Sprintf(`{"alg": "%s"}`, alg)) require.NoError(t, err, `jose.GenerateJwk should succeed`) defer joseJwkCleanup() // Load the JWK generated by jose jwxJwk, err := jwxtest.ParseJwkFile(ctx, joseJwkFile) require.NoError(t, err, `jwxtest.ParseJwkFile should succeed`) t.Run("Parse JWK via jwx", func(t *testing.T) { switch spec.alg { case jwa.RSA1_5(), jwa.RSA_OAEP(), jwa.RSA_OAEP_256(), jwa.RSA_OAEP_384(), jwa.RSA_OAEP_512(): var rawkey rsa.PrivateKey require.NoError(t, jwk.Export(jwxJwk, &rawkey), `jwk.Export should succeed`) case jwa.ECDH_ES(), jwa.ECDH_ES_A128KW(), jwa.ECDH_ES_A192KW(), jwa.ECDH_ES_A256KW(): var rawkey ecdsa.PrivateKey require.NoError(t, jwk.Export(jwxJwk, &rawkey), `jwk.Export should succeed`) default: var rawkey []byte require.NoError(t, jwk.Export(jwxJwk, &rawkey), `jwk.Export should succeed`) } }) t.Run("Encrypt with jose, Decrypt with jwx", func(t *testing.T) { // let jose encrypt payload using the key file joseCryptFile, joseCryptCleanup, err := jose.EncryptJwe(ctx, t, expected, spec.alg.String(), joseJwkFile, spec.enc.String(), true) require.NoError(t, err, `jose.EncryptJwe should succeed`) defer joseCryptCleanup() jwxtest.DumpFile(t, joseCryptFile) // let jwx decrypt the jose crypted file payload, err := jwxtest.DecryptJweFile(ctx, joseCryptFile, spec.alg, joseJwkFile) require.NoError(t, err, `decryptFile.DecryptJwe should succeed`) require.Equal(t, expected, payload, `decrypted payloads should match`) }) t.Run("Encrypt with jwx, Decrypt with jose", func(t *testing.T) { jwxCryptFile, jwxCryptCleanup, err := jwxtest.EncryptJweFile(ctx, t.TempDir(), expected, spec.alg, joseJwkFile, spec.enc, jwa.NoCompress()) require.NoError(t, err, `jwxtest.EncryptJweFile should succeed`) defer jwxCryptCleanup() payload, err := jose.DecryptJwe(ctx, t, jwxCryptFile, joseJwkFile) require.NoError(t, err, `jose.DecryptJwe should succeed`) require.Equal(t, expected, payload, `decrypted payloads should match`) }) } func joseJwsInteropTest(ctx context.Context, alg jwa.SignatureAlgorithm, t *testing.T) { t.Helper() expected := []byte(`{"foo":"bar"}`) joseJwkFile, joseJwkCleanup, err := jose.GenerateJwk(ctx, t, fmt.Sprintf(`{"alg": "%s"}`, alg)) require.NoError(t, err, `jose.GenerateJwk should succeed`) defer joseJwkCleanup() // Load the JWK generated by jose _, err = jwxtest.ParseJwkFile(ctx, joseJwkFile) require.NoError(t, err, `jwxtest.ParseJwkFile should succeed`) t.Run("Sign with jose, Verify with jwx", func(t *testing.T) { // let jose encrypt payload using the key file joseCryptFile, joseCryptCleanup, err := jose.SignJws(ctx, t, expected, joseJwkFile, true) require.NoError(t, err, `jose.SignJws should succeed`) defer joseCryptCleanup() jwxtest.DumpFile(t, joseCryptFile) // let jwx decrypt the jose crypted file payload, err := jwxtest.VerifyJwsFile(ctx, joseCryptFile, alg, joseJwkFile) require.NoError(t, err, `jwxtest.VerifyJwsFile should succeed`) require.Equal(t, expected, payload, `decrypted payloads should match`) }) t.Run("Sign with jwx, Verify with jose", func(t *testing.T) { jwxCryptFile, jwxCryptCleanup, err := jwxtest.SignJwsFile(ctx, t.TempDir(), expected, alg, joseJwkFile) require.NoError(t, err, `jwxtest.SignJwsFile should succeed`) defer jwxCryptCleanup() payload, err := jose.VerifyJws(ctx, t, jwxCryptFile, joseJwkFile) require.NoError(t, err, `jose.VerifyJws should succeed`) require.Equal(t, expected, payload, `decrypted payloads should match`) }) } func TestGHIssue230(t *testing.T) { t.Parallel() if !jose.Available() { t.SkipNow() } data := "eyJhbGciOiJFQ0RILUVTIiwiY2xldmlzIjp7InBpbiI6InRhbmciLCJ0YW5nIjp7ImFkdiI6eyJrZXlzIjpbeyJhbGciOiJFQ01SIiwiY3J2IjoiUC01MjEiLCJrZXlfb3BzIjpbImRlcml2ZUtleSJdLCJrdHkiOiJFQyIsIngiOiJBZm5tR2xHRTFHRUZ5NEpUT2tGWmo5ZEhEUmdpVE5IeFBST3hpZDZLdm0xVGRFQkZ3bElsSVB6TG5lTjlnb3h6OUVGYmJLM3BoN0tWZS05aVF4MmxhOVNFIiwieSI6IkFmZGFaTVYzVzk1NE14elQxeXF3MWVaRU9xTFFZZnBXSGczMlJvekhyQjBEYmoxWWV3OVFvTDg1M2Y2aUw2REIyRC1nbEcxSFFsb3czdGRNdFhjN1pSY0IifSx7ImFsZyI6IkVTNTEyIiwiY3J2IjoiUC01MjEiLCJrZXlfb3BzIjpbInZlcmlmeSJdLCJrdHkiOiJFQyIsIngiOiJBR0drcXRPZzZqel9pZnhmVnVWQ01CalVySFhCTGtfS2hIb3lKRkU5NmJucTZKZVVHNFNMZnRrZ2FIYk5WT0U4Q3Mwd0JqR0ZkSWxDbnBmak94RGJfbFBoIiwieSI6IkFLU0laT0JYY1Jfa3RkWjZ6T3F3TGI5SEJzai0yYmRMUmw5dFZVbnVlV2N3aXg5X3NiekliSWx0SE9YUGhBTW9yaUlYMWVyNzc4Unh6Vkg5d0FtaUhGa1kifV19LCJ1cmwiOiJodHRwOi8vbG9jYWxob3N0OjM5NDIxIn19LCJlbmMiOiJBMjU2R0NNIiwiZXBrIjp7ImNydiI6IlAtNTIxIiwia3R5IjoiRUMiLCJ4IjoiQUJMUm9sQWotZFdVdzZLSjg2T3J6d1F6RjlGT09URFZBZnNWNkh0OU0zREhyQ045Q0N6dVJ1b3cwbWp6M3BjZnVCaFpYREpfN0dkdzE0LXdneV9fTFNrYyIsInkiOiJBT3NRMzlKZmFQVGhjc2FZTjhSMVBHXzIwYXZxRU1NRl9fM2RHQmI3c1BqNmktNEJORDVMdkZ3cVpJT1l4SS1kVWlvNzkyOWY1YnE0eEdJY0lGWWtlbllxIn0sImtpZCI6ImhlZmVpNzVqMkp4Sko3REZnSDAxUWlOVmlGayJ9..GH3-8v7wfxEsRnki.wns--EIYTRjM3Tb0HyA.EGn2Gq7PnSVvPaMN0oRi5A" compactMsg, err := jwe.ParseString(data) require.NoError(t, err, `jwe.ParseString should succeed`) formatted, err := jose.FmtJwe(context.TODO(), t, []byte(data)) require.NoError(t, err, `jose.FmtJwe should succeed`) jsonMsg, err := jwe.Parse(formatted) require.NoError(t, err, `jwe.Parse should succeed`) require.Equal(t, compactMsg, jsonMsg, `messages should match`) } func TestGuessFormat(t *testing.T) { testcases := []struct { Name string Expected jwx.FormatKind Source []byte }{ { Name: "Raw String", Expected: jwx.InvalidFormat, Source: []byte(`Hello, World`), }, { Name: "Random JSON Object", Expected: jwx.UnknownFormat, Source: []byte(`{"random": "JSON"}`), }, { Name: "Random JSON Array", Expected: jwx.InvalidFormat, Source: []byte(`["random", "JSON"]`), }, { Name: "Random Broken JSON", Expected: jwx.UnknownFormat, Source: []byte(`{"aud": "foo", "x-customg": "extra semicolon after this string", }`), }, { Name: "JWS", Expected: jwx.JWS, // from https://tools.ietf.org/html/rfc7515#appendix-A.1 Source: []byte(`eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk`), }, { Name: "JWE", Expected: jwx.JWE, Source: []byte(`eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkEyNTZHQ00ifQ.OKOawDo13gRp2ojaHV7LFpZcgV7T6DVZKTyKOMTYUmKoTCVJRgckCL9kiMT03JGeipsEdY3mx_etLbbWSrFr05kLzcSr4qKAq7YN7e9jwQRb23nfa6c9d-StnImGyFDbSv04uVuxIp5Zms1gNxKKK2Da14B8S4rzVRltdYwam_lDp5XnZAYpQdb76FdIKLaVmqgfwX7XWRxv2322i-vDxRfqNzo_tETKzpVLzfiwQyeyPGLBIO56YJ7eObdv0je81860ppamavo35UgoRdbYaBcoh9QcfylQr66oc6vFWXRcZ_ZT2LawVCWTIy3brGPi6UklfCpIMfIjf7iGdXKHzg.48V1_ALb6US04U3b.5eym8TW_c8SuK0ltJ3rpYIzOeDQz7TALvtu6UG9oMo4vpzs9tX_EFShS8iB7j6jiSdiwkIr3ajwQzaBtQD_A.XFBoMYUZodetZdvTiFvSkQ`), }, { Name: "JWK", Expected: jwx.JWK, Source: []byte(`{"kty":"OKP","crv":"X25519","x":"3p7bfXt9wbTTW2HC7OQ1Nz-DQ8hbeGdNrfx-FG-IK08"}`), }, { Name: "JWKS", Expected: jwx.JWKS, Source: []byte(`{"keys":[{"kty":"OKP","crv":"X25519","x":"3p7bfXt9wbTTW2HC7OQ1Nz-DQ8hbeGdNrfx-FG-IK08"}]}`), }, { Name: "JWS (JSON)", Expected: jwx.JWS, Source: []byte(`{"signatures": [], "payload": ""}`), }, { Name: "JWT", Expected: jwx.JWT, Source: []byte(`{"aud":"github.com/lestrrat-go/jwx/v3"}`), }, } for _, tc := range testcases { t.Run(tc.Name, func(t *testing.T) { got := jwx.GuessFormat(tc.Source) require.Equal(t, got, tc.Expected, `value of jwx.GuessFormat should match (%s != %s)`, got, tc.Expected) }) } } func TestFormat(t *testing.T) { testcases := []struct { Value jwx.FormatKind Expected string Error bool }{ { Value: jwx.UnknownFormat, Expected: "UnknownFormat", }, { Value: jwx.JWE, Expected: "JWE", }, { Value: jwx.JWS, Expected: "JWS", }, { Value: jwx.JWK, Expected: "JWK", }, { Value: jwx.JWKS, Expected: "JWKS", }, { Value: jwx.JWT, Expected: "JWT", }, { Value: jwx.FormatKind(9999999), Expected: "FormatKind(9999999)", }, } for _, tc := range testcases { t.Run(tc.Expected, func(t *testing.T) { require.Equal(t, tc.Expected, tc.Value.String(), `stringification should match`) }) } } func TestGH996(t *testing.T) { ecdsaKey, err := jwxtest.GenerateEcdsaKey(jwa.P256()) require.NoError(t, err, `jwxtest.GenerateEcdsaKey should succeed`) rsaKey, err := jwxtest.GenerateRsaKey() require.NoError(t, err, `jwxtest.GenerateRsaKey should succeed`) okpKey, err := jwxtest.GenerateEd25519Key() require.NoError(t, err, `jwxtest.GenerateEd25519Key should succeed`) symmetricKey := []byte(`abracadabra`) testcases := []struct { Name string Algorithm jwa.SignatureAlgorithm ValidSigningKeys []any InvalidSigningKeys []any ValidVerificationKeys []any InvalidVerificationKeys []any }{ { Name: `ECDSA`, Algorithm: jwa.ES256(), ValidSigningKeys: []any{ecdsaKey}, InvalidSigningKeys: []any{rsaKey, okpKey, symmetricKey}, ValidVerificationKeys: []any{ecdsaKey.PublicKey}, InvalidVerificationKeys: []any{rsaKey.PublicKey, okpKey.Public(), symmetricKey}, }, { Name: `RSA`, Algorithm: jwa.RS256(), ValidSigningKeys: []any{rsaKey}, InvalidSigningKeys: []any{ecdsaKey, okpKey, symmetricKey}, ValidVerificationKeys: []any{rsaKey.PublicKey}, InvalidVerificationKeys: []any{ecdsaKey.PublicKey, okpKey.Public(), symmetricKey}, }, { Name: `OKP`, Algorithm: jwa.EdDSA(), ValidSigningKeys: []any{okpKey}, InvalidSigningKeys: []any{ecdsaKey, rsaKey, symmetricKey}, ValidVerificationKeys: []any{okpKey.Public()}, InvalidVerificationKeys: []any{ecdsaKey.PublicKey, rsaKey.PublicKey, symmetricKey}, }, } for _, tc := range testcases { t.Run(tc.Name, func(t *testing.T) { for _, valid := range tc.ValidSigningKeys { t.Run(fmt.Sprintf("Sign Valid(%T)", valid), func(t *testing.T) { _, err := jws.Sign([]byte("Lorem Ipsum"), jws.WithKey(tc.Algorithm, valid)) require.NoError(t, err, `signing with %T should succeed`, valid) }) } for _, invalid := range tc.InvalidSigningKeys { t.Run(fmt.Sprintf("Sign Invalid(%T)", invalid), func(t *testing.T) { _, err := jws.Sign([]byte("Lorem Ipsum"), jws.WithKey(tc.Algorithm, invalid)) require.Error(t, err, `signing with %T should fail`, invalid) }) } signed, err := jws.Sign([]byte("Lorem Ipsum"), jws.WithKey(tc.Algorithm, tc.ValidSigningKeys[0])) require.NoError(t, err, `jws.Sign with valid key should succeed`) for _, valid := range tc.ValidVerificationKeys { t.Run(fmt.Sprintf("Verify Valid(%T)", valid), func(t *testing.T) { _, err := jws.Verify(signed, jws.WithKey(tc.Algorithm, valid)) require.NoError(t, err, `verifying with %T should succeed`, valid) }) } for _, invalid := range tc.InvalidVerificationKeys { t.Run(fmt.Sprintf("Verify Invalid(%T)", invalid), func(t *testing.T) { _, err := jws.Verify(signed, jws.WithKey(tc.Algorithm, invalid)) require.Error(t, err, `verifying with %T should fail`, invalid) }) } }) } } func TestGH1140(t *testing.T) { // Using WithUseNumber changes the type of value obtained from the // source JSON, which may cause issues jwx.DecoderSettings(jwx.WithUseNumber(true)) t.Cleanup(func() { jwx.DecoderSettings(jwx.WithUseNumber(false)) }) key, err := jwk.Import([]byte("secure-key")) require.NoError(t, err, `jwk.Import should succeed`) var encrypted []byte encrypted, err = jwe.Encrypt( []byte("test-encryption-payload"), jwe.WithKey(jwa.PBES2_HS256_A128KW(), key), ) require.NoError(t, err, `jwe.Encrypt should succeed`) _, err = jwe.Decrypt(encrypted, jwe.WithKey(jwa.PBES2_HS256_A128KW(), key)) require.NoError(t, err, `jwe.Decrypt should succeed`) } func TestGH1434(t *testing.T) { if testing.Short() { t.Logf("Skipped during short tests") return } if !jose.Available() { t.Logf("`jose` binary not available, skipping tests") return } ctx := t.Context() // Check if jose supports ECDH-ES algorithm set, err := jose.Algorithms(ctx, t) require.NoError(t, err) if !set.Has("ECDH-ES") { t.Logf("jose does not support ECDH-ES algorithm: skipping") return } expected := []byte("Hello, World! This tests ECDH-ES interoperability.") // Test with different elliptic curves and their corresponding ECDH-ES+KW algorithms curves := []struct { name string crv string ecdhCurve ecdh.Curve keyAlg jwa.KeyEncryptionAlgorithm }{ {"P256", "P-256", ecdh.P256(), jwa.ECDH_ES_A128KW()}, {"P384", "P-384", ecdh.P384(), jwa.ECDH_ES_A192KW()}, {"P521", "P-521", ecdh.P521(), jwa.ECDH_ES_A256KW()}, } for _, curve := range curves { t.Run(curve.name, func(t *testing.T) { // Check if jose supports this key encryption algorithm if !set.Has(curve.keyAlg.String()) { t.Logf("jose does not support %s algorithm: skipping", curve.keyAlg.String()) return } // Generate an ECDH private key directly ecdhPrivKey, err := curve.ecdhCurve.GenerateKey(rand.Reader) require.NoError(t, err, `ECDH key generation should succeed`) // Create a JWK from the ECDH private key jwxJwk, err := jwk.Import(ecdhPrivKey) require.NoError(t, err, `jwk.Import should succeed`) // Write the JWK to a temporary file for jose to use jwkBytes, err := json.Marshal(jwxJwk) require.NoError(t, err, `jwk JSON marshaling should succeed`) joseJwkFile, joseJwkCleanup, err := jwxtest.WriteFile(t.TempDir(), "ecdh-key-*.jwk", bytes.NewReader(jwkBytes)) require.NoError(t, err, `writing JWK file should succeed`) defer joseJwkCleanup() t.Run("Parse ECDH JWK via jwx", func(t *testing.T) { // Test exporting as ECDH key (should work directly) var ecdhKey ecdh.PrivateKey require.NoError(t, jwk.Export(jwxJwk, &ecdhKey), `jwk.Export to ECDH should succeed`) // Test exporting as ECDSA key (should use ECDHToECDSA conversion) var ecdsaKey ecdsa.PrivateKey require.NoError(t, jwk.Export(jwxJwk, &ecdsaKey), `jwk.Export to ECDSA should succeed via ECDHToECDSA conversion`) }) t.Run("Encrypt with jose using ECDH key, Decrypt with jwx", func(t *testing.T) { // let jose encrypt payload using ECDH-ES with key wrapping joseCryptFile, joseCryptCleanup, err := jose.EncryptJwe(ctx, t, expected, curve.keyAlg.String(), joseJwkFile, jwa.A256GCM().String(), true) require.NoError(t, err, `jose.EncryptJwe should succeed`) defer joseCryptCleanup() jwxtest.DumpFile(t, joseCryptFile) // let jwx decrypt using the ECDH key (which internally should convert to ECDSA if needed) encryptedData, err := jwxtest.ReadFile(joseCryptFile) require.NoError(t, err, `reading encrypted file should succeed`) payload, err := jwe.Decrypt(encryptedData, jwe.WithKey(curve.keyAlg, ecdhPrivKey)) require.NoError(t, err, `jwe.Decrypt with ECDH key should succeed`) require.Equal(t, expected, payload, `decrypted payloads should match`) }) t.Run("Encrypt with jwx using ECDH key, Decrypt with jose", func(t *testing.T) { // Encrypt using jwx with the ECDH key directly encrypted, err := jwe.Encrypt(expected, jwe.WithKey(curve.keyAlg, ecdhPrivKey.PublicKey()), jwe.WithContentEncryption(jwa.A256GCM())) require.NoError(t, err, `jwe.Encrypt with ECDH key should succeed`) // Write encrypted data to file for jose jwxCryptFile, jwxCryptCleanup, err := jwxtest.WriteFile(t.TempDir(), "jwx-encrypted-*.jwe", bytes.NewReader(encrypted)) require.NoError(t, err, `writing encrypted file should succeed`) defer jwxCryptCleanup() payload, err := jose.DecryptJwe(ctx, t, jwxCryptFile, joseJwkFile) require.NoError(t, err, `jose.DecryptJwe should succeed`) require.Equal(t, expected, payload, `decrypted payloads should match`) }) }) } } golang-github-lestrrat-go-jwx-3.0.13/options.go000066400000000000000000000011071515060566400214250ustar00rootroot00000000000000package jwx import "github.com/lestrrat-go/option/v2" type identUseNumber struct{} type Option = option.Interface type JSONOption interface { Option isJSONOption() } type jsonOption struct { Option } func (o *jsonOption) isJSONOption() {} func newJSONOption(n any, v any) JSONOption { return &jsonOption{option.New(n, v)} } // WithUseNumber controls whether the jwx package should unmarshal // JSON objects with the "encoding/json".Decoder.UseNumber feature on. // // Default is false. func WithUseNumber(b bool) JSONOption { return newJSONOption(identUseNumber{}, b) } golang-github-lestrrat-go-jwx-3.0.13/scripts/000077500000000000000000000000001515060566400210735ustar00rootroot00000000000000golang-github-lestrrat-go-jwx-3.0.13/scripts/benchcmp.sh000077500000000000000000000022061515060566400232110ustar00rootroot00000000000000#!/bin/bash # ./benchcmp.sh branch1 branch2 [count] function curbranch { git rev-parse --abbrev-ref HEAD 2>/dev/null } function curcommit { git log -n 1 --format=%H | cut -c 1-9 } count=$3 if [[ -z "$count" ]]; then count=5 fi if [[ ! "$count" != ^[1-9][0-9]*$ ]]; then echo "third argument must be a positive integer" exit 1 fi set -e tmpdir=$(mktemp -d 2>/dev/null || mktemp -d -t 'jwxbench') origbranch=$(git rev-parse --abbrev-ref HEAD) outputfiles=() commits=() echo "# Going to run ${count} iterations of benchmarks against $1 and $2 branches" for branch in $1 $2 do cbranch=$(curbranch) if [[ "$branch" != "$cbranch" ]]; then git switch $branch fi echo "# Running benchmark against $branch..." output="${tmpdir}/${branch/\//-}.bench.txt" outputfiles+=($output) commits+=($(curcommit)) pushd bench set -x go test -count=$count -bench . -benchmem | tee "$output" set +x popd done cbranch=$(curbranch) if [[ "$cbranch" != "$origbranch" ]]; then git switch "$origbranch" fi echo "Benchmark comparison for:" echo " $1 (${commits[0]})" echo " $2 (${commits[1]})" echo "" benchstat "${outputfiles[0]}" "${outputfiles[1]}" golang-github-lestrrat-go-jwx-3.0.13/scripts/check-diff.sh000077500000000000000000000010741515060566400234170ustar00rootroot00000000000000#!/bin/bash UNTRACKED=$(git ls-files --others --exclude-standard) DIFF=$(git diff) st=0 if [ ! -z "$DIFF" ]; then echo "==== START OF DIFF FOUND ===" echo "" echo "$DIFF" echo "" echo "Above diff was found." echo "" echo "==== END OF DIFF FOUND ===" echo "" st=1 fi if [ ! -z "$UNTRACKED" ]; then echo "==== START OF UNTRACKED FILES FOUND ===" echo "" echo "$UNTRACKED" echo "" echo "Above untracked files were found." echo "" echo "==== END OF UNTRACKED FILES FOUND ===" echo "" st=1 fi exit $stgolang-github-lestrrat-go-jwx-3.0.13/scripts/tidy.sh000077500000000000000000000001661515060566400224060ustar00rootroot00000000000000#!/bin/bash for dir in $(find . -name 'go.mod' | perl -pe 's{/go.mod$}{}'); do pushd "$dir" go mod tidy popd done golang-github-lestrrat-go-jwx-3.0.13/scripts/update-mods.sh000077500000000000000000000013531515060566400236560ustar00rootroot00000000000000#!/bin/bash set -e TAG="$1" if [[ -z "$TAG" ]]; then echo "tag name must be provided" fi # Make sure Changes file contains an entry for this release relentry=$(grep "$TAG" Changes | head -n 1) if [[ "$?" -ne 0 ]]; then echo "$TAG does not exist in Changes file"; exit 1; fi reldate=${relentry#$TAG - } reldate=${reldate//['$\t\n\r']} parseddate=$(date --date="$reldate" "+%d %b %Y") if [[ "$reldate" != "$parseddate" ]]; then echo "$TAG does not seem to exist in Changes file (wrong entry format?)"; exit 1; fi # Update dependency in ./cmd/jwx ./examples for dir in ./cmd/jwx ./examples ./bench/performance; do echo "👉 $dir" pushd $dir > /dev/null go get github.com/lestrrat-go/jwx/v3@"$TAG" go mod tidy popd > /dev/null done golang-github-lestrrat-go-jwx-3.0.13/tools/000077500000000000000000000000001515060566400205445ustar00rootroot00000000000000golang-github-lestrrat-go-jwx-3.0.13/tools/autodoc.pl000066400000000000000000000053611515060566400225440ustar00rootroot00000000000000#!perl use strict; use File::Temp; # Accept a list of filenames, and process them # if any of them has a diff, commit it # Use GITHUB_REF, but if the ref is develop/v\d, then use v\d my $link_ref = $ENV{GITHUB_REF}; if ($link_ref =~ /^(?:refs\/heads\/)?develop\/(v\d+)$/) { $link_ref = $1; } my @files = @ARGV; my @has_diff; for my $filename (@files) { open(my $src, '<', $filename) or die $!; my $output = File::Temp->new(SUFFIX => '.md'); my $skip_until_end; for my $line (<$src>) { if ($line =~ /^$/) { $skip_until_end = 0; } elsif ($skip_until_end) { next; } if ($line !~ /(^)$/) { $output->print($line); next; } $output->print("$1\n"); my $include_filename = $2; my $options = $3; $output->print("```go\n"); my $content = do { open(my $file, '<', $include_filename) or die "failed to include file $include_filename from source file $filename: $!"; local $/; <$file>; }; $content =~ s{^(\t+)}{" " x length($1)}gsme; $output->print($content); $output->print("```\n"); $output->print("source: [$include_filename](https://github.com/lestrrat-go/jwx/blob/$link_ref/$include_filename)\n"); # now we need to skip copying until the end of INCLUDE $skip_until_end = 1; } $output->close(); close($src); if (!$ENV{AUTODOC_DRYRUN}) { rename $output->filename, $filename or die $!; my $diff = `git diff $filename`; if ($diff) { push @has_diff, $filename; } } } if (!$ENV{AUTODOC_DRYRUN}) { if (@has_diff) { # Write multi-line commit message in a file my $commit_message_file = File::Temp->new(SUFFIX => '.txt'); print $commit_message_file "autodoc updates\n\n"; print " - $_\n" for @has_diff; $commit_message_file->close(); system("git", "remote", "set-url", "origin", "https://github-actions:$ENV{GITHUB_TOKEN}\@github.com/$ENV{GITHUB_REPOSITORY}") == 0 or die $!; system("git", "config", "--global", "user.name", "$ENV{GITHUB_ACTOR}") == 0 or die $!; system("git", "config", "--global", "user.email", "$ENV{GITHUB_ACTOR}\@users.noreply.github.com") == 0 or die $!; system("git", "switch", "-c", "autodoc-pr-$ENV{GITHUB_HEAD_REF}") == 0 or die $!; system("git", "commit", "-F", $commit_message_file->filename, @files) == 0 or die $!; system("git", "push", "origin", "HEAD:autodoc-pr-$ENV{GITHUB_HEAD_REF}") == 0 or die $!; system("gh", "pr", "create", "--base", "develop/$link_ref", "--fill") == 0 or die $!; } } golang-github-lestrrat-go-jwx-3.0.13/tools/cmd/000077500000000000000000000000001515060566400213075ustar00rootroot00000000000000golang-github-lestrrat-go-jwx-3.0.13/tools/cmd/genjwa.sh000077500000000000000000000006631515060566400231260ustar00rootroot00000000000000#!/bin/bash # Script to perform code generation. This exists to overcome # the fact that go:generate doesn't really allow you to change directories # This file is expected to be executed from jwa directory set -e echo "👉 Generating JWA files..." DIR=../tools/cmd/genjwa pushd "$DIR" > /dev/null GOWORK=off go build -o .genjwa main.go popd > /dev/null EXE="${DIR}/.genjwa" "$EXE" "$DIR/objects.yml" echo "✔ done!" rm "$EXE" golang-github-lestrrat-go-jwx-3.0.13/tools/cmd/genjwa/000077500000000000000000000000001515060566400225625ustar00rootroot00000000000000golang-github-lestrrat-go-jwx-3.0.13/tools/cmd/genjwa/go.mod000066400000000000000000000006711515060566400236740ustar00rootroot00000000000000module github.com/lestrrat-go/jwx/v3/tools/cmd/genalgs go 1.21.0 toolchain go1.24.2 require ( github.com/goccy/go-yaml v1.17.1 github.com/lestrrat-go/codegen v1.0.4 ) require ( github.com/lestrrat-go/option v1.0.1 // indirect github.com/lestrrat-go/xstrings v0.0.0-20220901080742-cacb16b8ddb7 // indirect github.com/stretchr/testify v1.8.2 // indirect golang.org/x/mod v0.14.0 // indirect golang.org/x/tools v0.16.1 // indirect ) golang-github-lestrrat-go-jwx-3.0.13/tools/cmd/genjwa/go.sum000066400000000000000000000115231515060566400237170ustar00rootroot00000000000000github.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/goccy/go-yaml v1.17.1 h1:LI34wktB2xEE3ONG/2Ar54+/HJVBriAGJ55PHls4YuY= github.com/goccy/go-yaml v1.17.1/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= github.com/lestrrat-go/codegen v1.0.4 h1:xWRqMkHzfpN/nfl4EeAwmbTvS7uotxfUPl8RhpjB3Go= github.com/lestrrat-go/codegen v1.0.4/go.mod h1:JQPYOh/5hA2lipdHWj3YZHoKEGUfLmGQoWcWs4I92qk= github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU= github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= github.com/lestrrat-go/xstrings v0.0.0-20210804220435-4dd8b234342b/go.mod h1:mPFmD3Wuy0ddyPFvllLq4sUpGfE40T3VE8kWWS8fxGA= github.com/lestrrat-go/xstrings v0.0.0-20220901080742-cacb16b8ddb7 h1:8YrnMMQZquDwIgfQvZZ+JGMrRIn9UdzremIkMGQ/RoU= github.com/lestrrat-go/xstrings v0.0.0-20220901080742-cacb16b8ddb7/go.mod h1:mPFmD3Wuy0ddyPFvllLq4sUpGfE40T3VE8kWWS8fxGA= 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/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.0/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/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/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-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200918232735-d647fc253266/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= golang.org/x/tools v0.16.1 h1:TLyB3WofjdOEepBHAU20JdNC1Zbg87elYofWYAY5oZA= golang.org/x/tools v0.16.1/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= golang-github-lestrrat-go-jwx-3.0.13/tools/cmd/genjwa/main.go000066400000000000000000000357721515060566400240530ustar00rootroot00000000000000package main import ( "bytes" "fmt" "log" "os" "sort" "strconv" "strings" "unicode" "github.com/goccy/go-yaml" "github.com/lestrrat-go/codegen" ) // Define the structs with exported fields for proper YAML unmarshaling type AlgYAML struct { Algorithms []Algorithm `yaml:"algorithms"` } type Algorithm struct { Name string `yaml:"name"` Comment string `yaml:"comment"` Filename string `yaml:"filename"` Elements []Element `yaml:"elements"` Symmetric bool `yaml:"symmetric"` } type Element struct { Name string `yaml:"name"` Value string `yaml:"value"` TokenReference string `yaml:"token_reference"` ReturnvalComment string `yaml:"returnval_comment"` Comment string `yaml:"comment"` Invalid bool `yaml:"invalid"` Sym bool `yaml:"sym"` Deprecated bool `yaml:"deprecated"` } func main() { if err := _main(); err != nil { log.Printf("%s", err) os.Exit(1) } } func _main() error { // Default to "objects.yml" if no argument is provided filename := "objects.yml" // If command line arguments are provided, use the first one as the file path if len(os.Args) > 1 { filename = os.Args[1] } // Read the algorithm definitions from the specified file yamlFile, err := os.ReadFile(filename) if err != nil { return fmt.Errorf("failed to read %s: %w", filename, err) } var algs AlgYAML if err := yaml.Unmarshal(yamlFile, &algs); err != nil { return fmt.Errorf("failed to unmarshal %s: %w", filename, err) } algorithms := algs.Algorithms sort.Slice(algorithms, func(i, j int) bool { return algorithms[i].Name < algorithms[j].Name }) for _, t := range algorithms { t := t // Make a copy for the closure sort.Slice(t.Elements, func(i, j int) bool { return t.Elements[i].Name < t.Elements[j].Name }) if err := Generate(t); err != nil { return fmt.Errorf(`failed to generate file: %w`, err) } if err := GenerateTest(t); err != nil { return fmt.Errorf(`failed to generate test file: %w`, err) } } return nil } func Generate(t Algorithm) error { var buf bytes.Buffer if t.Filename == "" { return fmt.Errorf("filename is empty for type %q", t.Name) } o := codegen.NewOutput(&buf) o.R("// Code generated by tools/cmd/genjwa/main.go. DO NOT EDIT.") o.LL("package jwa") o.LL("import (") pkgs := []string{ "encoding/json", "fmt", "sort", "sync", } // Check if we need to import tokens package needsTokens := false for _, e := range t.Elements { if e.TokenReference != "" { needsTokens = true break } } if needsTokens { pkgs = append(pkgs, "github.com/lestrrat-go/jwx/v3/internal/tokens") } for _, pkg := range pkgs { o.L("%s", strconv.Quote(pkg)) } o.L(")") o.LL("var muAll%s sync.RWMutex", t.Name) o.L("var all%[1]s = map[string]%[1]s{}", t.Name) o.L("var muList%s sync.RWMutex", t.Name) o.L("var list%s []%s", t.Name, t.Name) o.L("var builtin%s = map[string]struct{}{}", t.Name) o.LL("func init() {") o.L("// builtin values for %s", t.Name) // check if we have invalid elements, so we allocate just enough // space for the builtin algorithms invalids := 0 for _, e := range t.Elements { if e.Invalid { invalids++ } } o.L("algorithms := make([]%s, %d)", t.Name, len(t.Elements)-invalids) ecount := 0 for _, e := range t.Elements { if e.Invalid { continue } valueRef := fmt.Sprintf("%q", e.Value) if e.TokenReference != "" { valueRef = e.TokenReference } o.L("algorithms[%d] = New%s(%s", ecount, t.Name, valueRef) ecount++ if e.Deprecated { o.R(", WithDeprecated(true)") } if e.Sym { o.R(", WithIsSymmetric(true)") } o.R(")") } o.LL("Register%s(algorithms...)", t.Name) o.L("}") // end init // Accessors for builtin algorithms for _, e := range t.Elements { if e.Invalid { o.L("var %s = New%s(%q)", fmt.Sprintf("%c%s", unicode.ToLower(rune(e.Name[0])), e.Name[1:]), t.Name, e.Value) } if e.Value == "" || e.ReturnvalComment != "" { if e.ReturnvalComment == "" { return fmt.Errorf("missing value for %s (required if e.Value is empty)", e.Name) } o.LL("// %s returns an object representing %s.", e.Name, e.ReturnvalComment) } else { o.LL("// %s returns an object representing %s.", e.Name, e.Value) } if e.Comment != "" { o.R(" %s", e.Comment) } o.L("func %s() %s {", e.Name, t.Name) if e.Invalid { o.L("return %s", fmt.Sprintf("%c%s", unicode.ToLower(rune(e.Name[0])), e.Name[1:])) } else { valueRef := fmt.Sprintf("%q", e.Value) if e.TokenReference != "" { valueRef = e.TokenReference } o.L("return lookupBuiltin%s(%s)", t.Name, valueRef) } o.L("}") } o.LL("func lookupBuiltin%s(name string) %s {", t.Name, t.Name) o.L("muAll%s.RLock()", t.Name) o.L("v, ok := all%s[name]", t.Name) o.L("muAll%s.RUnlock()", t.Name) o.L("if !ok {") o.L("panic(fmt.Sprintf(`jwa: %s %%q not registered`, name))", t.Name) o.L("}") o.L("return v") o.L("}") o.LL("// %s", t.Comment) o.L("type %s struct {", t.Name) o.L("name string") o.L("deprecated bool") if t.Symmetric { o.L("isSymmetric bool") } o.L("}") o.LL("func (s %s) String() string {", t.Name) o.L("return s.name") o.L("}") o.LL("// IsDeprecated returns true if the %s object is deprecated.", t.Name) o.L("func (s %s) IsDeprecated() bool {", t.Name) o.L("return s.deprecated") o.L("}") if t.Symmetric { o.LL("// IsSymmetric returns true if the %s object is symmetric. Symmetric algorithms use the same key for both encryption and decryption.", t.Name) o.L("func (s %s) IsSymmetric() bool {", t.Name) o.L("return s.isSymmetric") o.L("}") } o.LL("// Empty%[1]s returns an empty %[1]s object, used as a zero value.", t.Name) o.L("func Empty%s() %s {", t.Name, t.Name) o.L("return %s{}", t.Name) o.L("}") o.LL("// New%[1]s creates a new %[1]s object with the given name.", t.Name) o.L("func New%[1]s(name string", t.Name) if t.Symmetric { o.R(", options ...New%[1]sOption", t.Name) } else { o.R(", options ...NewAlgorithmOption") } o.R(") %[1]s {", t.Name) o.L("var deprecated bool") if t.Symmetric { o.L("var isSymmetric bool") } o.L("for _, option := range options {") o.L("switch option.Ident() {") if t.Symmetric { o.L("case identIsSymmetric{}:") o.L("if err := option.Value(&isSymmetric); err != nil {") o.L("panic(\"jwa.New%s: WithIsSymmetric option must be a boolean\")", t.Name) o.L("}") } o.L("case identDeprecated{}:") o.L("if err := option.Value(&deprecated); err != nil {") o.L("panic(\"jwa.New%s: WithDeprecated option must be a boolean\")", t.Name) o.L("}") o.L("}") o.L("}") o.L("return %s{name: name, deprecated: deprecated", t.Name) if t.Symmetric { o.R(", isSymmetric: isSymmetric") } o.R("}") o.L("}") o.LL("// Lookup%[1]s returns the %[1]s object for the given name.", t.Name) o.L("func Lookup%[1]s(name string) (%[1]s, bool) {", t.Name) o.L("muAll%[1]s.RLock()", t.Name) o.L("v, ok := all%[1]s[name]", t.Name) o.L("muAll%[1]s.RUnlock()", t.Name) o.L("return v, ok") o.L("}") o.LL("// Register%[1]s registers a new %[1]s. The signature value must be immutable", t.Name) o.L("// and safe to be used by multiple goroutines, as it is going to be shared with all other users of this library.") o.L("func Register%[1]s(algorithms ...%[1]s) {", t.Name) o.L("muAll%[1]s.Lock()", t.Name) o.L("for _, alg := range algorithms {") o.L("all%[1]s[alg.String()] = alg", t.Name) o.L("}") o.L("muAll%[1]s.Unlock()", t.Name) o.L("rebuild%[1]s()", t.Name) o.L("}") o.LL("// Unregister%[1]s unregisters a %[1]s from its known database.", t.Name) o.L("// Non-existent entries, as well as built-in algorithms will silently be ignored.") o.L("func Unregister%[1]s(algorithms ...%[1]s) {", t.Name) o.L("muAll%[1]s.Lock()", t.Name) o.L("for _, alg := range algorithms {") o.L("if _, ok := builtin%[1]s[alg.String()]; ok {", t.Name) o.L("continue") o.L("}") o.L("delete(all%[1]s, alg.String())", t.Name) o.L("}") o.L("muAll%[1]s.Unlock()", t.Name) o.L("rebuild%[1]s()", t.Name) o.L("}") o.LL("func rebuild%[1]s() {", t.Name) o.L("list := make([]%[1]s, 0, len(all%[1]s))", t.Name) o.L("muAll%[1]s.RLock()", t.Name) o.L("for _, v := range all%[1]s {", t.Name) o.L("list = append(list, v)") o.L("}") o.L("muAll%[1]s.RUnlock()", t.Name) o.L("sort.Slice(list, func(i, j int) bool {") o.L("return list[i].String() < list[j].String()") o.L("})") o.L("muList%[1]s.Lock()", t.Name) o.L("list%[1]s = list", t.Name) o.L("muList%[1]s.Unlock()", t.Name) 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("muList%[1]s.RLock()", t.Name) o.L("defer muList%[1]s.RUnlock()", t.Name) o.L("return list%[1]s", t.Name) o.L("}") o.LL("// MarshalJSON serializes the %[1]s object to a JSON string.", t.Name) o.L("func (s %[1]s) MarshalJSON() ([]byte, error) {", t.Name) o.L("return json.Marshal(s.String())") o.L("}") o.LL("// UnmarshalJSON deserializes the JSON string to a %[1]s object.", t.Name) o.L("func (s *%[1]s) UnmarshalJSON(data []byte) error {", t.Name) o.L("var name string") o.L("if err := json.Unmarshal(data, &name); err != nil {") o.L("return fmt.Errorf(`failed to unmarshal %[1]s: %%w`, err)", t.Name) o.L("}") o.L("v, ok := Lookup%[1]s(name)", t.Name) o.L("if !ok {") o.L("return fmt.Errorf(`unknown %[1]s: %%q`, name)", t.Name) o.L("}") o.L("*s = v") o.L("return nil") o.L("}") if err := o.WriteFile(t.Filename, codegen.WithFormatCode(true)); err != nil { if cfe, ok := err.(codegen.CodeFormatError); ok { fmt.Fprint(os.Stderr, cfe.Source()) } return fmt.Errorf(`failed to write to %s: %w`, t.Filename, err) } return nil } func GenerateTest(t Algorithm) error { var buf bytes.Buffer valids := make([]Element, 0, len(t.Elements)) invalids := make([]Element, 0, len(t.Elements)) for _, e := range t.Elements { if e.Invalid { invalids = append(invalids, e) continue } valids = append(valids, e) } o := codegen.NewOutput(&buf) o.R("// Code generated by tools/cmd/genjwa/main.go. DO NOT EDIT") o.LL("package jwa_test") o.L("import (") pkgs := []string{ "strconv", "testing", "github.com/lestrrat-go/jwx/v3/jwa", "github.com/stretchr/testify/require", } for _, pkg := range pkgs { o.L("%s", strconv.Quote(pkg)) } o.L(")") o.LL("func Test%s(t *testing.T) {", t.Name) o.L("t.Parallel()") for _, e := range valids { o.L("t.Run(`Lookup the object`, func(t *testing.T) {") o.L("t.Parallel()") o.L("v, ok := jwa.Lookup%s(%q)", t.Name, e.Value) o.L("require.True(t, ok, `Lookup should succeed`)") o.L("require.Equal(t, jwa.%s(), v, `Lookup value should be equal to constant`)", e.Name) o.L("})") o.L("t.Run(`Unmarshal the string %s`, func(t *testing.T) {", e.Value) o.L("t.Parallel()") o.L("var dst jwa.%s", t.Name) o.L("require.NoError(t, json.Unmarshal([]byte(strconv.Quote(%q)), &dst), `UnmarshalJSON is successful`)", e.Value) o.L("require.Equal(t, jwa.%s(), dst, `unmarshaled value should be equal to constant`)", e.Name) o.L("})") o.L("t.Run(`stringification for %s`, func(t *testing.T) {", e.Value) o.L("t.Parallel()") o.L("require.Equal(t, %#v, jwa.%s().String(), `stringified value matches`)", e.Value, e.Name) o.L("})") } o.L("t.Run(`Unmarshal should fail for invalid value (totally made up) string value`, func(t *testing.T) {") o.L("t.Parallel()") o.L("var dst jwa.%s", t.Name) o.L("require.Error(t, json.Unmarshal([]byte(`totallyInvalidValue`), &dst), `Unmarshal should fail`)") o.L("})") if t.Symmetric { o.L("t.Run(`check symmetric values`, func(t *testing.T) {") o.L("t.Parallel()") for _, e := range t.Elements { o.L("t.Run(`%s`, func(t *testing.T) {", e.Name) if e.Sym { o.L("require.True") } else { o.L("require.False") } o.R("(t, jwa.%[1]s().IsSymmetric(), `jwa.%[1]s returns expected value`)", e.Name) o.L("})") } o.L("})") } o.L("t.Run(`check list of elements`, func(t *testing.T) {") o.L("t.Parallel()") o.L("var expected = map[jwa.%s]struct{} {", t.Name) for _, e := range t.Elements { if !e.Invalid { o.L("jwa.%s(): {},", e.Name) } } o.L("}") o.L("for _, v := range jwa.%ss() {", t.Name) if t.Name == "EllipticCurveAlgorithm" { o.L("// There is no good way to detect from a test if es256k (secp256k1)") o.L("// is supported, so just allow it") o.L("if v.String() == `secp256k1` {") o.L("continue") o.L("}") } o.L("_, ok := expected[v]") o.L("require.True(t, ok, `%%q should be in the list for %s`, v)", t.Name) o.L("delete(expected, v)") o.L("}") o.L("require.Len(t, expected, 0)") o.L("})") o.L("}") o.LL("// Note: this test can NOT be run in parallel as it uses options with global effect.") o.L("func Test%sCustomAlgorithm(t *testing.T) {", t.Name) o.L("// These subtests can NOT be run in parallel as options with global effect change.") o.L("const customAlgorithmValue = `custom-algorithm`") if t.Symmetric { o.L("for _, symmetric := range []bool{true, false} {") } o.L(`customAlgorithm := jwa.New%[1]s(customAlgorithmValue`, t.Name) if t.Symmetric { o.R(`, jwa.WithIsSymmetric(symmetric)`) } o.R(`)`) o.L("// Unregister the custom algorithm, in case tests fail.") o.L("t.Cleanup(func() {") o.L("jwa.Unregister%[1]s(customAlgorithm)", t.Name) o.L("})") o.L("t.Run(`with custom algorithm registered`, func(t *testing.T) {") o.L("jwa.Register%[1]s(customAlgorithm)", t.Name) o.L("t.Run(`Lookup the object`, func(t *testing.T) {") o.L("t.Parallel()") o.L("v, ok := jwa.Lookup%[1]s(customAlgorithmValue)", t.Name) o.L("require.True(t, ok, `Lookup should succeed`)") o.L("require.Equal(t, customAlgorithm, v, `Lookup value should be equal to constant`)") o.L("})") o.L("t.Run(`Unmarshal custom algorithm`, func(t *testing.T) {") o.L("t.Parallel()") o.L("var dst jwa.%[1]s", t.Name) o.L("require.NoError(t, json.Unmarshal([]byte(strconv.Quote(customAlgorithmValue)), &dst), `Unmarshal is successful`)") o.L("require.Equal(t, customAlgorithm, dst, `accepted value should be equal to variable`)") o.L("})") if t.Symmetric { o.L("t.Run(`check symmetric`, func(t *testing.T) {") o.L("t.Parallel()") o.L("require.Equal(t, symmetric, customAlgorithm.IsSymmetric(), `custom algorithm's symmetric attribute should match`)") o.L("})") } o.L("})") o.L("t.Run(`with custom algorithm deregistered`, func(t *testing.T) {") o.L("jwa.Unregister%[1]s(customAlgorithm)", t.Name) o.L("t.Run(`Lookup the object`, func(t *testing.T) {") o.L("t.Parallel()") o.L("_, ok := jwa.Lookup%[1]s(customAlgorithmValue)", t.Name) o.L("require.False(t, ok, `Lookup should fail`)") o.L("})") o.L("t.Run(`Unmarshal custom algorithm`, func(t *testing.T) {") o.L("t.Parallel()") o.L("var dst jwa.%[1]s", t.Name) o.L("require.Error(t, json.Unmarshal([]byte(customAlgorithmValue), &dst), `Unmarshal should fail`)") o.L("})") o.L("})") if t.Symmetric { o.L("}") // ending the for _, symmetric := range loop } o.L("}") filename := strings.Replace(t.Filename, "_gen.go", "_gen_test.go", 1) if err := o.WriteFile(filename, codegen.WithFormatCode(true)); err != nil { if cfe, ok := err.(codegen.CodeFormatError); ok { fmt.Fprint(os.Stderr, cfe.Source()) } return fmt.Errorf(`failed to write to %s: %w`, filename, err) } return nil } golang-github-lestrrat-go-jwx-3.0.13/tools/cmd/genjwa/objects.yml000066400000000000000000000231411515060566400247370ustar00rootroot00000000000000# Algorithm type definitions for JWA (JSON Web Algorithms) # Each algorithm type has a name, comment, filename, and a list of elements # Some types are marked as symmetric, which means they can use the same key for encryption and decryption algorithms: - name: CompressionAlgorithm comment: CompressionAlgorithm represents the compression algorithms as described in https://tools.ietf.org/html/rfc7518#section-7.3 filename: compression_gen.go elements: - name: NoCompress value: "" returnval_comment: an empty compression algorithm value comment: Using this value specifies that the content should not be compressed. - name: Deflate returnval_comment: the "DEF" content compression algorithm value value: DEF comment: Using this value specifies that the content should be compressed using DEFLATE (RFC 1951). - name: ContentEncryptionAlgorithm comment: ContentEncryptionAlgorithm represents the various encryption algorithms as described in https://tools.ietf.org/html/rfc7518#section-5 filename: content_encryption_gen.go elements: - name: A128CBC_HS256 value: A128CBC-HS256 token_reference: tokens.A128CBC_HS256 comment: Using this value specifies that the content should be encrypted using AES-CBC + HMAC-SHA256 (128). - name: A192CBC_HS384 value: A192CBC-HS384 token_reference: tokens.A192CBC_HS384 comment: Using this value specifies that the content should be encrypted using AES-CBC + HMAC-SHA384 (192). - name: A256CBC_HS512 value: A256CBC-HS512 token_reference: tokens.A256CBC_HS512 comment: Using this value specifies that the content should be encrypted using AES-CBC + HMAC-SHA512 (256). - name: A128GCM value: A128GCM token_reference: tokens.A128GCM comment: Using this value specifies that the content should be encrypted using AES-GCM (128). - name: A192GCM value: A192GCM token_reference: tokens.A192GCM comment: Using this value specifies that the content should be encrypted using AES-GCM (192). - name: A256GCM value: A256GCM token_reference: tokens.A256GCM comment: Using this value specifies that the content should be encrypted using AES-GCM (256). - name: KeyType comment: KeyType represents the key type ("kty") that are supported filename: key_type_gen.go elements: - name: InvalidKeyType value: "" returnval_comment: invalid key type comment: Invalid KeyType invalid: true - name: EC value: EC comment: Elliptic Curve - name: RSA value: RSA comment: RSA - name: OctetSeq value: oct comment: Octet sequence (used to represent symmetric keys) - name: OKP value: OKP comment: Octet string key pairs - name: EllipticCurveAlgorithm comment: EllipticCurveAlgorithm represents the algorithms used for EC keys filename: elliptic_gen.go elements: - name: InvalidEllipticCurve value: P-invalid returnval_comment: an invalid elliptic curve invalid: true - name: P256 value: P-256 returnval_comment: P-256 algorithm for ECDSA operations - name: P384 value: P-384 returnval_comment: P-384 algorithm for ECDSA operations - name: P521 value: P-521 returnval_comment: P-521 algorithm for ECDSA operations - name: Ed25519 value: Ed25519 returnval_comment: Ed25519 algorithm for EdDSA operations - name: Ed448 value: Ed448 returnval_comment: Ed448 algorithm for EdDSA operations - name: X25519 value: X25519 returnval_comment: X25519 algorithm for ECDH operations - name: X448 value: X448 returnval_comment: X448 algorithm for ECDH operations - name: SignatureAlgorithm comment: SignatureAlgorithm represents the various signature algorithms as described in https://tools.ietf.org/html/rfc7518#section-3.1 filename: signature_gen.go symmetric: true elements: - name: NoSignature value: none returnval_comment: the lack of a signature algorithm comment: Using this value specifies that the content should not be signed, which you should avoid doing. - name: HS256 value: HS256 returnval_comment: HMAC signature algorithm using SHA-256 sym: true - name: HS384 value: HS384 returnval_comment: HMAC signature algorithm using SHA-384 sym: true - name: HS512 value: HS512 returnval_comment: HMAC signature algorithm using SHA-512 sym: true - name: RS256 value: RS256 returnval_comment: RSASSA-PKCS-v1.5 signature algorithm using SHA-256 - name: RS384 value: RS384 returnval_comment: RSASSA-PKCS-v1.5 signature algorithm using SHA-384 - name: RS512 value: RS512 returnval_comment: RSASSA-PKCS-v1.5 signature algorithm using SHA-512 - name: ES256 value: ES256 returnval_comment: ECDSA signature algorithm using P-256 curve and SHA-256 - name: ES384 value: ES384 returnval_comment: ECDSA signature algorithm using P-384 curve and SHA-384 - name: ES512 value: ES512 returnval_comment: ECDSA signature algorithm using P-521 curve and SHA-512 - name: ES256K value: ES256K returnval_comment: ECDSA signature algorithm using secp256k1 curve and SHA-256 - name: EdDSA value: EdDSA returnval_comment: EdDSA signature algorithms - name: PS256 value: PS256 returnval_comment: RSASSA-PSS signature algorithm using SHA-256 and MGF1-SHA256 - name: PS384 value: PS384 returnval_comment: RSASSA-PSS signature algorithm using SHA-384 and MGF1-SHA384 - name: PS512 value: PS512 returnval_comment: RSASSA-PSS signature algorithm using SHA-512 and MGF1-SHA512 - name: KeyEncryptionAlgorithm comment: KeyEncryptionAlgorithm represents the various encryption algorithms as described in https://tools.ietf.org/html/rfc7518#section-4.1 filename: key_encryption_gen.go symmetric: true elements: - name: RSA1_5 value: RSA1_5 token_reference: tokens.RSA1_5 deprecated: true returnval_comment: RSA-PKCS1v1.5 key encryption algorithm - name: RSA_OAEP value: RSA-OAEP token_reference: tokens.RSA_OAEP returnval_comment: RSA-OAEP-SHA1 key encryption algorithm - name: RSA_OAEP_256 value: RSA-OAEP-256 token_reference: tokens.RSA_OAEP_256 returnval_comment: RSA-OAEP-SHA256 key encryption algorithm - name: RSA_OAEP_384 value: RSA-OAEP-384 token_reference: tokens.RSA_OAEP_384 returnval_comment: RSA-OAEP-SHA384 key encryption algorithm - name: RSA_OAEP_512 value: RSA-OAEP-512 token_reference: tokens.RSA_OAEP_512 returnval_comment: RSA-OAEP-SHA512 key encryption algorithm - name: A128KW value: A128KW token_reference: tokens.A128KW returnval_comment: AES key wrap (128) key encryption algorithm sym: true - name: A192KW value: A192KW token_reference: tokens.A192KW returnval_comment: AES key wrap (192) key encryption algorithm sym: true - name: A256KW value: A256KW token_reference: tokens.A256KW returnval_comment: AES key wrap (256) key encryption algorithm sym: true - name: DIRECT value: dir token_reference: tokens.DIRECT returnval_comment: Direct key encryption algorithm sym: true - name: ECDH_ES value: ECDH-ES token_reference: tokens.ECDH_ES returnval_comment: ECDH-ES key encryption algorithm - name: ECDH_ES_A128KW value: ECDH-ES+A128KW token_reference: tokens.ECDH_ES_A128KW returnval_comment: ECDH-ES + AES key wrap (128) key encryption algorithm - name: ECDH_ES_A192KW value: ECDH-ES+A192KW token_reference: tokens.ECDH_ES_A192KW returnval_comment: ECDH-ES + AES key wrap (192) key encryption algorithm - name: ECDH_ES_A256KW value: ECDH-ES+A256KW token_reference: tokens.ECDH_ES_A256KW returnval_comment: ECDH-ES + AES key wrap (256) key encryption algorithm - name: A128GCMKW value: A128GCMKW token_reference: tokens.A128GCMKW returnval_comment: AES-GCM key wrap (128) key encryption algorithm sym: true - name: A192GCMKW value: A192GCMKW token_reference: tokens.A192GCMKW returnval_comment: AES-GCM key wrap (192) key encryption algorithm sym: true - name: A256GCMKW value: A256GCMKW token_reference: tokens.A256GCMKW returnval_comment: AES-GCM key wrap (256) key encryption algorithm sym: true - name: PBES2_HS256_A128KW value: PBES2-HS256+A128KW token_reference: tokens.PBES2_HS256_A128KW returnval_comment: PBES2 + HMAC-SHA256 + AES key wrap (128) key encryption algorithm sym: true - name: PBES2_HS384_A192KW value: PBES2-HS384+A192KW token_reference: tokens.PBES2_HS384_A192KW returnval_comment: PBES2 + HMAC-SHA384 + AES key wrap (192) key encryption algorithm sym: true - name: PBES2_HS512_A256KW value: PBES2-HS512+A256KW token_reference: tokens.PBES2_HS512_A256KW returnval_comment: PBES2 + HMAC-SHA512 + AES key wrap (256) key encryption algorithm sym: truegolang-github-lestrrat-go-jwx-3.0.13/tools/cmd/genjwe.sh000077500000000000000000000006731515060566400231330ustar00rootroot00000000000000#!/bin/bash # Script to perform code generation. This exists to overcome # the fact that go:generate doesn't really allow you to change directories # This file is expected to be executed from jwe directory set -e echo "👉 Generating JWE files..." DIR=../tools/cmd/genjwe pushd "$DIR" > /dev/null GOWORK=off go build -o .genjwe main.go popd > /dev/null EXE="${DIR}/.genjwe" "$EXE" -objects="$DIR/objects.yml" echo "✔ done!" rm "$EXE" golang-github-lestrrat-go-jwx-3.0.13/tools/cmd/genjwe/000077500000000000000000000000001515060566400225665ustar00rootroot00000000000000golang-github-lestrrat-go-jwx-3.0.13/tools/cmd/genjwe/go.mod000066400000000000000000000013001515060566400236660ustar00rootroot00000000000000module github.com/lestrrat-go/jwx/v3/jwe/internal/cmd/genheader go 1.20 require ( github.com/goccy/go-yaml v1.11.2 github.com/lestrrat-go/codegen v1.0.4 ) require ( github.com/fatih/color v1.16.0 // indirect github.com/lestrrat-go/option v1.0.1 // indirect github.com/lestrrat-go/xstrings v0.0.0-20220901080742-cacb16b8ddb7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/stretchr/testify v1.8.2 // indirect golang.org/x/mod v0.3.0 // indirect golang.org/x/sys v0.14.0 // indirect golang.org/x/tools v0.0.0-20200918232735-d647fc253266 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect ) golang-github-lestrrat-go-jwx-3.0.13/tools/cmd/genjwe/go.sum000066400000000000000000000145361515060566400237320ustar00rootroot00000000000000github.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/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE= github.com/goccy/go-yaml v1.11.2 h1:joq77SxuyIs9zzxEjgyLBugMQ9NEgTWxXfz2wVqwAaQ= github.com/goccy/go-yaml v1.11.2/go.mod h1:wKnAMd44+9JAAnGQpWVEgBzGt3YuTaQ4uXoHvE4m7WU= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= github.com/lestrrat-go/codegen v1.0.4 h1:xWRqMkHzfpN/nfl4EeAwmbTvS7uotxfUPl8RhpjB3Go= github.com/lestrrat-go/codegen v1.0.4/go.mod h1:JQPYOh/5hA2lipdHWj3YZHoKEGUfLmGQoWcWs4I92qk= github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU= github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= github.com/lestrrat-go/xstrings v0.0.0-20210804220435-4dd8b234342b/go.mod h1:mPFmD3Wuy0ddyPFvllLq4sUpGfE40T3VE8kWWS8fxGA= github.com/lestrrat-go/xstrings v0.0.0-20220901080742-cacb16b8ddb7 h1:8YrnMMQZquDwIgfQvZZ+JGMrRIn9UdzremIkMGQ/RoU= github.com/lestrrat-go/xstrings v0.0.0-20220901080742-cacb16b8ddb7/go.mod h1:mPFmD3Wuy0ddyPFvllLq4sUpGfE40T3VE8kWWS8fxGA= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 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/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.0/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/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A= golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/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-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q= golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200918232735-d647fc253266 h1:k7tVuG0g1JwmD3Jh8oAl1vQ1C3jb4Hi/dUl1wWDBJpQ= golang.org/x/tools v0.0.0-20200918232735-d647fc253266/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= golang-github-lestrrat-go-jwx-3.0.13/tools/cmd/genjwe/main.go000066400000000000000000000342001515060566400240400ustar00rootroot00000000000000package main import ( "bytes" "encoding/json" "flag" "fmt" "log" "os" "strings" "github.com/goccy/go-yaml" "github.com/lestrrat-go/codegen" ) func main() { if err := _main(); err != nil { log.Printf("%s", err) os.Exit(1) } } func _main() error { codegen.RegisterZeroVal(`jwa.KeyEncryptionAlgorithm`, `jwa.EmptyKeyEncryptionAlgorithm()`) codegen.RegisterZeroVal(`jwa.CompressionAlgorithm`, `jwa.NoCompress()`) codegen.RegisterZeroVal(`jwa.ContentEncryptionAlgorithm`, `jwa.EmptyContentEncryptionAlgorithm()`) var objectsFile = flag.String("objects", "objects.yml", "") flag.Parse() jsonSrc, err := yaml2json(*objectsFile) if err != nil { return err } var object codegen.Object if err := json.NewDecoder(bytes.NewReader(jsonSrc)).Decode(&object); err != nil { return fmt.Errorf(`failed to decode %q: %w`, *objectsFile, err) } object.Organize() return generateHeaders(&object) } func yaml2json(fn string) ([]byte, error) { in, err := os.Open(fn) if err != nil { return nil, fmt.Errorf(`failed to open %q: %w`, fn, err) } defer in.Close() var v any if err := yaml.NewDecoder(in).Decode(&v); err != nil { return nil, fmt.Errorf(`failed to decode %q: %w`, fn, err) } return json.Marshal(v) } func IsPointer(f codegen.Field) bool { return strings.HasPrefix(f.Type(), `*`) } func PointerElem(f codegen.Field) string { return strings.TrimPrefix(f.Type(), `*`) } func fieldStorageType(s string) string { if fieldStorageTypeIsIndirect(s) { return `*` + s } return s } func fieldStorageTypeIsIndirect(s string) bool { return !(s == "jwk.Key" || s == "jwk.ECDSAPublicKey" || strings.HasPrefix(s, `*`) || strings.HasPrefix(s, `[]`)) } func generateHeaders(obj *codegen.Object) error { var buf bytes.Buffer o := codegen.NewOutput(&buf) o.L("// Code generated by tools/cmd/genjwe/main.go. DO NOT EDIT.") o.LL("package jwe") o.WriteImports( "github.com/lestrrat-go/blackmagic", "github.com/lestrrat-go/jwx/v3/cert", "github.com/lestrrat-go/jwx/v3/internal/base64", "github.com/lestrrat-go/jwx/v3/internal/json", "github.com/lestrrat-go/jwx/v3/internal/pool", "github.com/lestrrat-go/jwx/v3/internal/tokens", "github.com/lestrrat-go/jwx/v3/jwa", "github.com/lestrrat-go/jwx/v3/jwk", ) o.LL("const (") for _, f := range obj.Fields() { o.L("%sKey = %q", f.Name(true), f.JSON()) } o.L(")") // end const o.LL("// Headers describe a standard JWE Header set. It is part of the JWE message") o.L("// and is used to represent both Protected and Unprotected headers,") o.L("// which in turn can be found in each Recipient object.") o.L("// If you are not sure how this works, it is strongly recommended that") o.L("// you read RFC7516, especially the section") o.L("// that describes the full JSON serialization format of JWE messages.") o.L("//") o.L("// In most cases, you likely want to use the protected headers, as this is the part of the encrypted content") o.L("type Headers interface {") // These are the basic values that most jws have for _, f := range obj.Fields() { o.L("%s() (%s, bool)", f.GetterMethod(true), f.Type()) //PointerElem()) } // These are used to access a single element by key name o.LL("// Get is used to extract the value of any field, including non-standard fields, out of the header.") o.L("//") o.L("// The first argument is the name of the field. The second argument is a pointer") o.L("// to a variable that will receive the value of the field. The method returns") o.L("// an error if the field does not exist, or if the value cannot be assigned to") o.L("// the destination variable. Note that a field is considered to \"exist\" even if") o.L("// the value is empty-ish (e.g. 0, false, \"\"), as long as it is explicitly set.") o.L("Get(string, any) error") o.L("Set(string, any) error") o.L("Remove(string) error") o.L("// Has returns true if the specified header has a value, even if") o.L("// the value is empty-ish (e.g. 0, false, \"\") as long as it has been") o.L("// explicitly set.") o.L("Has(string) bool") // These are used to deal with encoded headers o.L("Encode() ([]byte, error)") o.L("Decode([]byte) error") o.L("Clone() (Headers, error)") o.L("Copy(Headers) error") o.L("Merge(Headers) (Headers, error)") o.LL("// Keys returns a list of the keys contained in this header.") o.L("Keys() []string") o.L("}") o.LL("// stdHeaderNames is a list of all standard header names defined in the JWE specification.") o.L("var stdHeaderNames = []string{") for i, f := range obj.Fields() { if i > 0 { o.R(",") } o.R("%sKey", f.Name(true)) } o.R("}") o.LL("type stdHeaders struct {") for _, f := range obj.Fields() { if c := f.Comment(); c != "" { o.L("%s %s // %s", f.Name(false), fieldStorageType(f.Type()), c) } else { o.L("%s %s", f.Name(false), fieldStorageType(f.Type())) } } o.L("privateParams map[string]any") o.L("mu *sync.RWMutex") o.L("}") // end type StandardHeaders o.LL("func NewHeaders() Headers {") o.L("return &stdHeaders{") o.L("mu: &sync.RWMutex{},") o.L("privateParams: map[string]any{},") o.L("}") o.L("}") for _, f := range obj.Fields() { o.LL("func (h *stdHeaders) %s() (%s, bool) {", f.GetterMethod(true), f.Type()) o.L("h.mu.RLock()") o.L("defer h.mu.RUnlock()") if !fieldStorageTypeIsIndirect(f.Type()) { o.L("return h.%[1]s, h.%[1]s!=nil", f.Name(false)) } else { o.L("if h.%s == nil {", f.Name(false)) o.L("return %s, false", codegen.ZeroVal(f.Type())) o.L("}") o.L("return *(h.%s), true", f.Name(false)) } o.L("}") // func (h *stdHeaders) %s() %s } o.LL("func (h *stdHeaders) PrivateParams() map[string]any {") o.L("h.mu.RLock()") o.L("defer h.mu.RUnlock()") o.L("return h.privateParams") o.L("}") o.LL("func (h *stdHeaders) Has(name string) bool {") o.L("h.mu.RLock()") o.L("defer h.mu.RUnlock()") o.L("switch name {") for _, f := range obj.Fields() { o.L("case %sKey:", f.Name(true)) o.L("return h.%s != nil", f.Name(false)) } o.L("default:") o.L("_, ok := h.privateParams[name]") o.L("return ok") o.L("}") o.L("}") o.LL("func (h *stdHeaders) Get(name string, dst any) error {") o.L("h.mu.RLock()") o.L("defer h.mu.RUnlock()") o.L("switch name {") for _, f := range obj.Fields() { o.L("case %sKey:", f.Name(true)) o.L("if h.%s == nil {", f.Name(false)) o.L("return fmt.Errorf(`field %%q not found`, name)") o.L("}") o.L("if err := blackmagic.AssignIfCompatible(dst, ") if fieldStorageTypeIsIndirect(f.Type()) { o.R("*(h.%s)", f.Name(false)) } else { o.R("h.%s", f.Name(false)) } o.R("); err != nil {") o.L("return fmt.Errorf(`failed to assign value for field %%q: %%w`, name, err)") o.L("}") } o.L("default:") o.L("v, ok := h.privateParams[name]") o.L("if !ok {") o.L("return fmt.Errorf(`field %%q not found`, name)") o.L("}") o.L("if err := blackmagic.AssignIfCompatible(dst, v); err != nil {") o.L("return fmt.Errorf(`failed to assign value for field %%q: %%w`, name, err)") o.L("}") o.L("}") // end switch name o.L("return nil") o.L("}") // func (h *stdHeaders) Get(name string) (any, bool) o.LL("func (h *stdHeaders) Set(name string, value any) error {") o.L("h.mu.Lock()") o.L("defer h.mu.Unlock()") o.L("return h.setNoLock(name, value)") o.L("}") o.LL("func (h *stdHeaders) setNoLock(name string, value any) error {") o.L("switch name {") for _, f := range obj.Fields() { o.L("case %sKey:", f.Name(true)) if f.Bool(`hasAccept`) { o.L("var acceptor %s", PointerElem(f)) o.L("if err := acceptor.Accept(value); err != nil {") o.L("return fmt.Errorf(`invalid value for %%s key: %%w`, %sKey, err)", f.Name(true)) o.L("}") // end if err := h.%s.Accept(value) o.L("h.%s = &acceptor", f.Name(false)) o.L("return nil") } else { o.L("if v, ok := value.(%s); ok {", f.Type()) if f.Name(false) == "contentEncryption" { // check for non-empty string, because empty content encryption is just baaaaaad o.L("if v == jwa.EmptyContentEncryptionAlgorithm() {") o.L("return fmt.Errorf(`%#v field cannot be an empty string`)", f.JSON()) o.L("}") } if fieldStorageTypeIsIndirect(f.Type()) { o.L("h.%s = &v", f.Name(false)) } else { o.L("h.%s = v", f.Name(false)) } o.L("return nil") o.L("}") // end if v, ok := value.(%s) o.L("return fmt.Errorf(`invalid value for %%s key: %%T`, %sKey, value)", f.Name(true)) } } o.L("default:") o.L("if h.privateParams == nil {") o.L("h.privateParams = map[string]any{}") o.L("}") // end if h.privateParams == nil o.L("h.privateParams[name] = value") o.L("}") // end switch name o.L("return nil") o.L("}") // end func (h *stdHeaders) Set(name string, value any) o.LL("func (h *stdHeaders) Remove(key string) error {") o.L("h.mu.Lock()") o.L("defer h.mu.Unlock()") o.L("switch key {") for _, f := range obj.Fields() { o.L("case %sKey:", f.Name(true)) o.L("h.%s = nil", f.Name(false)) } o.L("default:") o.L("delete(h.privateParams, key)") o.L("}") o.L("return nil") // currently unused, but who knows o.L("}") o.LL("func (h *stdHeaders) UnmarshalJSON(buf []byte) error {") for _, f := range obj.Fields() { o.L("h.%s = nil", f.Name(false)) } o.L("dec := json.NewDecoder(bytes.NewReader(buf))") o.L("LOOP:") o.L("for {") o.L("tok, err := dec.Token()") o.L("if err != nil {") o.L("return fmt.Errorf(`error reading token: %%w`, err)") o.L("}") o.L("switch tok := tok.(type) {") o.L("case json.Delim:") o.L("// Assuming we're doing everything correctly, we should ONLY") o.L("// get either tokens.OpenCurlyBracket or tokens.CloseCurlyBracket here.") o.L("if tok == tokens.CloseCurlyBracket { // End of object") o.L("break LOOP") o.L("} else if tok != tokens.OpenCurlyBracket {") o.L("return fmt.Errorf(`expected '%%c' but got '%%c'`, tokens.OpenCurlyBracket, tok)") o.L("}") o.L("case string: // Objects can only have string keys") o.L("switch tok {") for _, f := range obj.Fields() { if f.Type() == "string" { o.L("case %sKey:", f.Name(true)) o.L("if err := json.AssignNextStringToken(&h.%s, dec); err != nil {", f.Name(false)) o.L("return fmt.Errorf(`failed to decode value for key %%s: %%w`, %sKey, err)", f.Name(true)) o.L("}") } else if f.Type() == "[]byte" { o.L("case %sKey:", f.Name(true)) o.L("if err := json.AssignNextBytesToken(&h.%s, dec); err != nil {", f.Name(false)) o.L("return fmt.Errorf(`failed to decode value for key %%s: %%w`, %sKey, err)", f.Name(true)) o.L("}") } else if f.Type() == "jwk.Key" { o.L("case %sKey:", f.Name(true)) o.L("var buf json.RawMessage") o.L("if err := dec.Decode(&buf); err != nil {") o.L("return fmt.Errorf(`failed to decode value for key %%s:%%w`, %sKey, err)", f.Name(true)) o.L("}") o.L("key, err := jwk.ParseKey(buf)") o.L("if err != nil {") o.L("return fmt.Errorf(`failed to parse JWK for key %%s: %%w`, %sKey, err)", f.Name(true)) o.L("}") o.L("h.%s = key", f.Name(false)) } else if strings.HasPrefix(f.Type(), "[]") { o.L("case %sKey:", f.Name(true)) o.L("var decoded %s", f.Type()) o.L("if err := dec.Decode(&decoded); err != nil {") o.L("return fmt.Errorf(`failed to decode value for key %%s: %%w`, %sKey, err)", f.Name(true)) o.L("}") o.L("h.%s = decoded", f.Name(false)) } else { o.L("case %sKey:", f.Name(true)) if IsPointer(f) { o.L("var decoded %s", PointerElem(f)) } else { o.L("var decoded %s", f.Type()) } o.L("if err := dec.Decode(&decoded); err != nil {") o.L("return fmt.Errorf(`failed to decode value for key %%s: %%w`, %sKey, err)", f.Name(true)) o.L("}") o.L("h.%s = &decoded", f.Name(false)) } } o.L("default:") o.L("decoded, err := registry.Decode(dec, tok)") o.L("if err != nil {") o.L("return err") o.L("}") o.L("h.setNoLock(tok, decoded)") o.L("}") o.L("default:") o.L("return fmt.Errorf(`invalid token %%T`, tok)") o.L("}") o.L("}") o.L("return nil") o.L("}") o.LL("func (h *stdHeaders) Keys() []string {") o.L("h.mu.RLock()") o.L("defer h.mu.RUnlock()") o.L("keys := make([]string, 0, %d+len(h.privateParams))", len(obj.Fields())) for _, f := range obj.Fields() { keyName := f.Name(true) + "Key" o.L("if h.%s != nil {", f.Name(false)) o.L("keys = append(keys, %s)", keyName) o.L("}") } o.L("for k := range h.privateParams {") o.L("keys = append(keys, k)") o.L("}") o.L("return keys") o.L("}") o.LL("func (h stdHeaders) MarshalJSON() ([]byte, error) {") o.L("data := make(map[string]any)") o.L("keys := make([]string, 0, %d+len(h.privateParams))", len(obj.Fields())) o.L("h.mu.RLock()") for _, f := range obj.Fields() { o.L("if h.%s != nil {", f.Name(false)) if fieldStorageTypeIsIndirect(f.Type()) { o.L("data[%sKey] = *(h.%s)", f.Name(true), f.Name(false)) } else { o.L("data[%sKey] = h.%s", f.Name(true), f.Name(false)) } o.L("keys = append(keys, %sKey)", f.Name(true)) o.L("}") } o.L("for k, v := range h.privateParams {") o.L("data[k] = v") o.L("keys = append(keys, k)") o.L("}") o.L("h.mu.RUnlock()") o.LL("sort.Strings(keys)") o.L("buf := pool.BytesBuffer().Get()") o.L("defer pool.BytesBuffer().Put(buf)") o.L("enc := json.NewEncoder(buf)") o.L("buf.WriteByte(tokens.OpenCurlyBracket)") o.L("for i, k := range keys {") o.L("if i > 0 {") o.L("buf.WriteRune(tokens.Comma)") o.L("}") o.L("buf.WriteRune(tokens.DoubleQuote)") o.L("buf.WriteString(k)") o.L("buf.WriteString(`\":`)") o.L("v := data[k]") o.L("switch v := v.(type) {") o.L("case []byte:") o.L("buf.WriteRune(tokens.DoubleQuote)") o.L("buf.WriteString(base64.EncodeToString(v))") o.L("buf.WriteRune(tokens.DoubleQuote)") o.L("default:") o.L("if err := enc.Encode(v); err != nil {") o.L("return nil, fmt.Errorf(`failed to encode value for field %%s`, k)") o.L("}") o.L("buf.Truncate(buf.Len()-1)") o.L("}") o.L("}") o.L("buf.WriteByte(tokens.CloseCurlyBracket)") o.L("ret := make([]byte, buf.Len())") o.L("copy(ret, buf.Bytes())") o.L("return ret, nil") o.L("}") o.LL("func (h *stdHeaders) clear() {") o.L("h.mu.Lock()") for _, f := range obj.Fields() { o.L("h.%s = nil", f.Name(false)) } o.L("h.privateParams = map[string]any{}") o.L("h.mu.Unlock()") o.L("}") if err := o.WriteFile(`headers_gen.go`, codegen.WithFormatCode(true)); err != nil { if cfe, ok := err.(codegen.CodeFormatError); ok { fmt.Fprint(os.Stderr, cfe.Source()) } return fmt.Errorf(`failed to write to headers_gen.go: %w`, err) } return nil } golang-github-lestrrat-go-jwx-3.0.13/tools/cmd/genjwe/objects.yml000066400000000000000000000024711515060566400247460ustar00rootroot00000000000000fields: - name: agreementPartyUInfo type: "[]byte" json: apu - name: agreementPartyVInfo type: "[]byte" json: apv - name: algorithm type: jwa.KeyEncryptionAlgorithm json: alg - name: compression type: jwa.CompressionAlgorithm json: zip - name: contentEncryption type: jwa.ContentEncryptionAlgorithm json: enc - name: contentType json: cty - name: critical type: "[]string" json: crit - name: ephemeralPublicKey type: jwk.Key json: epk - name: jwk exported_name: JWK getter: JWK type: jwk.Key - name: jwkSetURL unexported_name: jwkSetURL exported_name: JWKSetURL getter: JWKSetURL json: jku - name: keyID json: kid - name: typ exported_name: Type getter: Type - name: x509URL unexported_name: x509URL exported_name: X509URL getter: X509URL json: x5u - name: x509CertChain unexported_name: x509CertChain exported_name: X509CertChain getter: X509CertChain type: "*cert.Chain" json: x5c - name: x509CertThumbprint unexported_name: x509CertThumbprint getter: X509CertThumbprint json: x5t - name: x509CertThumbprintS256 unexported_name: x509CertThumbprintS256 exported_name: X509CertThumbprintS256 getter: X509CertThumbprintS256 json: "x5t#S256" golang-github-lestrrat-go-jwx-3.0.13/tools/cmd/genjwk.sh000077500000000000000000000006731515060566400231410ustar00rootroot00000000000000#!/bin/bash # Script to perform code generation. This exists to overcome # the fact that go:generate doesn't really allow you to change directories # This file is expected to be executed from jwk directory set -e echo "👉 Generating JWK files..." DIR=../tools/cmd/genjwk pushd "$DIR" > /dev/null GOWORK=off go build -o .genjwk main.go popd > /dev/null EXE="${DIR}/.genjwk" "$EXE" -objects="$DIR/objects.yml" echo "✔ done!" rm "$EXE" golang-github-lestrrat-go-jwx-3.0.13/tools/cmd/genjwk/000077500000000000000000000000001515060566400225745ustar00rootroot00000000000000golang-github-lestrrat-go-jwx-3.0.13/tools/cmd/genjwk/go.mod000066400000000000000000000012571515060566400237070ustar00rootroot00000000000000module gitub.com/lestrrat-go/jwx/jwk/internal/cmd/genheader go 1.20 require ( github.com/goccy/go-yaml v1.11.2 github.com/lestrrat-go/codegen v1.0.4 ) require ( golang.org/x/mod v0.14.0 // indirect golang.org/x/sys v0.14.0 // indirect ) require ( github.com/fatih/color v1.16.0 // indirect github.com/lestrrat-go/option v1.0.1 // indirect github.com/lestrrat-go/xstrings v0.0.0-20220901080742-cacb16b8ddb7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/stretchr/testify v1.8.2 // indirect golang.org/x/tools v0.16.1 // indirect golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect ) golang-github-lestrrat-go-jwx-3.0.13/tools/cmd/genjwk/go.sum000066400000000000000000000147461515060566400237430ustar00rootroot00000000000000github.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/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE= github.com/goccy/go-yaml v1.11.2 h1:joq77SxuyIs9zzxEjgyLBugMQ9NEgTWxXfz2wVqwAaQ= github.com/goccy/go-yaml v1.11.2/go.mod h1:wKnAMd44+9JAAnGQpWVEgBzGt3YuTaQ4uXoHvE4m7WU= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= github.com/lestrrat-go/codegen v1.0.4 h1:xWRqMkHzfpN/nfl4EeAwmbTvS7uotxfUPl8RhpjB3Go= github.com/lestrrat-go/codegen v1.0.4/go.mod h1:JQPYOh/5hA2lipdHWj3YZHoKEGUfLmGQoWcWs4I92qk= github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU= github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= github.com/lestrrat-go/xstrings v0.0.0-20210804220435-4dd8b234342b/go.mod h1:mPFmD3Wuy0ddyPFvllLq4sUpGfE40T3VE8kWWS8fxGA= github.com/lestrrat-go/xstrings v0.0.0-20220901080742-cacb16b8ddb7 h1:8YrnMMQZquDwIgfQvZZ+JGMrRIn9UdzremIkMGQ/RoU= github.com/lestrrat-go/xstrings v0.0.0-20220901080742-cacb16b8ddb7/go.mod h1:mPFmD3Wuy0ddyPFvllLq4sUpGfE40T3VE8kWWS8fxGA= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 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/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.0/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/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/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-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q= golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200918232735-d647fc253266/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= golang.org/x/tools v0.16.1 h1:TLyB3WofjdOEepBHAU20JdNC1Zbg87elYofWYAY5oZA= golang.org/x/tools v0.16.1/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU= golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= golang-github-lestrrat-go-jwx-3.0.13/tools/cmd/genjwk/main.go000066400000000000000000000610071515060566400240530ustar00rootroot00000000000000package main // This program generates all of the possible key types that we use // RSA public/private keys, ECDSA private/public keys, and symmetric keys // // Each share the same standard header section, but have their own // header fields import ( "bytes" "encoding/json" "flag" "fmt" "log" "os" "sort" "strconv" "strings" "github.com/goccy/go-yaml" "github.com/lestrrat-go/codegen" ) func main() { if err := _main(); err != nil { log.Printf("%s", err) os.Exit(1) } } func yaml2json(fn string) ([]byte, error) { in, err := os.Open(fn) if err != nil { return nil, fmt.Errorf(`failed to open %q: %w`, fn, err) } defer in.Close() var v any if err := yaml.NewDecoder(in).Decode(&v); err != nil { return nil, fmt.Errorf(`failed to decode %q: %w`, fn, err) } return json.Marshal(v) } type KeyType struct { Filename string `json:"filename"` Prefix string `json:"prefix"` KeyType string `json:"key_type"` Objects []*codegen.Object `json:"objects"` } func _main() error { codegen.RegisterZeroVal(`jwa.EllipticCurveAlgorithm`, `jwa.InvalidEllipticCurve()`) codegen.RegisterZeroVal(`jwa.KeyType`, `jwa.InvalidKeyType()`) codegen.RegisterZeroVal(`jwa.KeyAlgorithm`, `nil`) var objectsFile = flag.String("objects", "objects.yml", "") flag.Parse() jsonSrc, err := yaml2json(*objectsFile) if err != nil { return err } var def struct { StdFields codegen.FieldList `json:"std_fields"` KeyTypes []*KeyType `json:"key_types"` } if err := json.NewDecoder(bytes.NewReader(jsonSrc)).Decode(&def); err != nil { return fmt.Errorf(`failed to decode %q: %w`, *objectsFile, err) } for _, kt := range def.KeyTypes { for _, object := range kt.Objects { for _, f := range def.StdFields { object.AddField(f) } object.Organize() } } if err := generateGenericHeaders(def.StdFields, def.KeyTypes); err != nil { return err } for _, kt := range def.KeyTypes { if err := generateKeyType(kt, def.StdFields); err != nil { return fmt.Errorf(`failed to generate key type %s: %w`, kt.Prefix, err) } } return nil } func IsPointer(f codegen.Field) bool { return strings.HasPrefix(f.Type(), `*`) } func PointerElem(f codegen.Field) string { return strings.TrimPrefix(f.Type(), `*`) } func fieldStorageType(s string) string { if fieldStorageTypeIsIndirect(s) { return `*` + s } return s } func fieldStorageTypeIsIndirect(s string) bool { return s == "KeyOperationList" || !(strings.HasPrefix(s, `*`) || strings.HasPrefix(s, `[]`) || strings.HasSuffix(s, `List`)) } type Constant struct { Name string Value string } func generateKeyType(kt *KeyType, stdFields codegen.FieldList) error { var buf bytes.Buffer o := codegen.NewOutput(&buf) o.L("// Code generated by tools/cmd/genjwk/main.go. DO NOT EDIT.") o.LL("package jwk") // Find unique field key names to create constants var constants []Constant seen := make(map[string]struct{}) for _, obj := range kt.Objects { for _, f := range obj.Fields() { if f.Bool(`is_std`) { continue } n := f.Name(true) if _, ok := seen[n]; ok { continue } seen[n] = struct{}{} constants = append(constants, Constant{Name: kt.Prefix + n + "Key", Value: f.JSON()}) } } sort.Slice(constants, func(i, j int) bool { return constants[i].Name < constants[j].Name }) o.LL("const (") for _, c := range constants { o.L("%s = %q", c.Name, c.Value) } o.L(")") for _, obj := range kt.Objects { if err := generateObject(o, kt, obj); err != nil { return fmt.Errorf(`failed to generate object %s: %w`, obj.Name(true), err) } } // Generate StandardFieldsFilter function for this key type if err := generateStandardFieldsFilterWithFields(o, kt, stdFields); err != nil { return fmt.Errorf(`failed to generate StandardFieldsFilter for %s: %w`, kt.Prefix, err) } if err := o.WriteFile(kt.Filename, codegen.WithFormatCode(true)); err != nil { if cfe, ok := err.(codegen.CodeFormatError); ok { fmt.Fprint(os.Stderr, cfe.Source()) } return fmt.Errorf(`failed to write to %s: %w`, kt.Filename, err) } return nil } // generateStandardFieldsFilterWithFields generates a StandardFieldsFilter function with explicit field list func generateStandardFieldsFilterWithFields(o *codegen.Output, kt *KeyType, fields codegen.FieldList) error { // Always include KeyTypeKey and standard fields fieldNames := []string{"KeyTypeKey"} // Add standard fields if provided for _, f := range fields { fieldNames = append(fieldNames, f.Name(true)+"Key") } // Add key-type specific fields from all objects, but avoid duplicates seenFields := make(map[string]bool) for _, obj := range kt.Objects { for _, f := range obj.Fields() { // Skip fields that are already in standard fields to avoid duplicates isStandardField := false for _, stdField := range fields { if f.Name(true) == stdField.Name(true) { isStandardField = true break } } if !isStandardField { keyName := kt.Prefix + f.Name(true) + "Key" if !seenFields[keyName] { fieldNames = append(fieldNames, keyName) seenFields[keyName] = true } } } } // Generate the standard fields variable o.LL("var %sStandardFields KeyFilter", strings.ToLower(kt.Prefix)) // Generate the init function o.LL("func init() {") o.L("%sStandardFields = NewFieldNameFilter(", strings.ToLower(kt.Prefix)) for i, name := range fieldNames { if i > 0 { o.R(", ") } o.R("%s", name) } o.R(")") o.L("}") // Generate the convenience function o.LL("// %sStandardFieldsFilter returns a KeyFilter that filters out standard %s fields.", kt.Prefix, kt.Prefix) o.L("func %sStandardFieldsFilter() KeyFilter {", kt.Prefix) o.L("return %sStandardFields", strings.ToLower(kt.Prefix)) o.L("}") return nil } func generateObject(o *codegen.Output, kt *KeyType, obj *codegen.Object) error { ifName := kt.Prefix + obj.Name(true) if v := obj.String(`interface`); v != "" { ifName = v } objName := obj.Name(true) structName := strings.ToLower(kt.Prefix) + objName if v := obj.String(`struct_name`); v != "" { structName = v } o.LL("type %s interface {", ifName) o.L("Key") for _, f := range obj.Fields() { if f.Bool(`is_std`) { continue } o.L("%s() (%s, bool)", f.GetterMethod(true), f.Type()) } o.L("}") o.LL("type %s struct {", structName) for _, f := range obj.Fields() { o.L("%s %s", f.Name(false), fieldStorageType(f.Type())) if c := f.Comment(); len(c) > 0 { o.R(" // %s", c) } } o.L("privateParams map[string]any") o.L("mu *sync.RWMutex") o.L("dc json.DecodeCtx") o.L("}") o.LL(`var _ %s = &%s{}`, ifName, structName) o.L(`var _ Key = &%s{}`, structName) o.LL("func new%s() *%s {", ifName, structName) o.L("return &%s{", structName) o.L("mu: &sync.RWMutex{},") o.L("privateParams: make(map[string]any),") o.L("}") o.L("}") o.LL("func (h %s) KeyType() jwa.KeyType {", structName) o.L("return %s", kt.KeyType) o.L("}") o.LL("func (h %s) rlock() {", structName) o.L("h.mu.RLock()") o.L("}") o.LL("func (h %s) runlock() {", structName) o.L("h.mu.RUnlock()") o.L("}") if objName == "PublicKey" || objName == "PrivateKey" { o.LL("func (h %s) IsPrivate() bool {", structName) o.L("return %s", fmt.Sprint(objName == "PrivateKey")) o.L("}") } for _, f := range obj.Fields() { o.LL("func (h *%s) %s() (", structName, f.GetterMethod(true)) if v := f.String(`getter_return_value`); v != "" { o.R("%s", v) } else if IsPointer(f) && f.Bool(`noDeref`) { o.R("%s", f.Type()) } else { o.R("%s", PointerElem(f)) } o.R(", bool) {") if f.Bool(`hasGet`) { o.L("if h.%s != nil {", f.Name(false)) o.L("return h.%s.Get(), true", f.Name(false)) o.L("}") o.L("return %s, false", codegen.ZeroVal(PointerElem(f))) } else if !IsPointer(f) { if fieldStorageTypeIsIndirect(f.Type()) { o.L("if h.%s != nil {", f.Name(false)) o.L("return *(h.%s), true", f.Name(false)) o.L("}") o.L("return %s, false", codegen.ZeroVal(PointerElem(f))) } else if strings.HasPrefix(f.Type(), `[]`) { o.L("if h.%s != nil {", f.Name(false)) o.L("return h.%s, true", f.Name(false)) o.L("}") o.L("return nil, false") } else { o.L("return h.%s, true", f.Name(false)) } } else { o.L(`return h.%s, true`, f.Name(false)) } o.L("}") // func (h *stdHeaders) %s() %s } o.LL("func (h *%s) Has(name string) bool {", structName) o.L("h.mu.RLock()") o.L("defer h.mu.RUnlock()") o.L("switch name {") o.L("case KeyTypeKey:") o.L("return true") // kty is always present for _, f := range obj.Fields() { if f.Bool(`is_std`) { if f.Name(true) != "KeyType" { // Skip KeyType since we handled it specially above o.L("case %sKey:", f.Name(true)) o.L("return h.%s != nil", f.Name(false)) } } else { o.L("case %s%sKey:", kt.Prefix, f.Name(true)) o.L("return h.%s != nil", f.Name(false)) } } o.L("default:") o.L("_, ok := h.privateParams[name]") o.L("return ok") o.L("}") o.L("}") o.LL("func (h *%s) Get(name string, dst any) error {", structName) o.L("h.mu.RLock()") o.L("defer h.mu.RUnlock()") o.L("switch name {") o.L("case KeyTypeKey:") o.L("if err := blackmagic.AssignIfCompatible(dst, h.KeyType()); err != nil {") o.L("return fmt.Errorf(`%s.Get: failed to assign value for field %%q to destination object: %%w`, name, err)", structName) o.L("}") for _, f := range obj.Fields() { if f.Bool(`is_std`) { o.L("case %sKey:", f.Name(true)) } else { o.L("case %s%sKey:", kt.Prefix, f.Name(true)) } o.L("if h.%s == nil {", f.Name(false)) o.L("return fmt.Errorf(`field %%q not found`, name)") o.L("}") o.L("if err := blackmagic.AssignIfCompatible(dst, ") if f.Bool(`hasGet`) { o.R("h.%s.Get()", f.Name(false)) } else if fieldStorageTypeIsIndirect(f.Type()) { o.R("*(h.%s)", f.Name(false)) } else { o.R("h.%s", f.Name(false)) } o.R("); err != nil {") o.L("return fmt.Errorf(`failed to assign value for field %%q: %%w`, name, err)") o.L("}") o.L("return nil") } o.L("default:") o.L("v, ok := h.privateParams[name]") o.L("if !ok {") o.L("return fmt.Errorf(`field %%q not found`, name)") o.L("}") o.L("if err := blackmagic.AssignIfCompatible(dst, v); err != nil {") o.L("return fmt.Errorf(`failed to assign value for field %%q: %%w`, name, err)") o.L("}") o.L("}") // end switch name o.L("return nil") o.L("}") // func (h *%s) Get(name string) (any, bool) o.LL("func (h *%s) Set(name string, value any) error {", structName) o.L("h.mu.Lock()") o.L("defer h.mu.Unlock()") o.L("return h.setNoLock(name, value)") o.L(`}`) o.LL("func (h *%s) setNoLock(name string, value any) error {", structName) o.L("switch name {") o.L("case \"kty\":") o.L("return nil") // This is not great, but we just ignore it for _, f := range obj.Fields() { var keyName string if f.Bool(`is_std`) { keyName = f.Name(true) + "Key" } else { keyName = kt.Prefix + f.Name(true) + "Key" } o.L("case %s:", keyName) if f.Name(false) == `algorithm` { o.L("switch v := value.(type) {") o.L("case string, jwa.SignatureAlgorithm, jwa.KeyEncryptionAlgorithm, jwa.ContentEncryptionAlgorithm:") o.L("tmp, err := jwa.KeyAlgorithmFrom(v)") o.L("if err != nil {") o.L("return fmt.Errorf(`invalid algorithm for %%q key: %%w`, %s, err)", keyName) o.L("} ") o.L("h.algorithm = &tmp") o.L("default:") o.L("return fmt.Errorf(`invalid type for %%q key: %%T`, %s, value)", keyName) o.L("}") o.L("return nil") } else if f.Name(false) == `keyUsage` { o.L("switch v := value.(type) {") o.L("case KeyUsageType:") o.L("switch v {") o.L("case ForSignature, ForEncryption:") o.L("tmp := v.String()") o.L("h.keyUsage = &tmp") o.L("default:") o.L("return fmt.Errorf(`invalid key usage type %%s`, v)") o.L("}") o.L("case string:") o.L("h.keyUsage = &v") o.L("default:") o.L("return fmt.Errorf(`invalid key usage type %%s`, v)") o.L("}") } else if f.Bool(`hasAccept`) { o.L("var acceptor %s", f.Type()) o.L("if err := acceptor.Accept(value); err != nil {") o.L("return fmt.Errorf(`invalid value for %%s key: %%w`, %s, err)", keyName) o.L("}") // end if err := h.%s.Accept(value) if fieldStorageTypeIsIndirect(f.Type()) { o.L("h.%s = &acceptor", f.Name(false)) } else { o.L("h.%s = acceptor", f.Name(false)) } o.L("return nil") } else { o.L("if v, ok := value.(%s); ok {", f.Type()) if fieldStorageTypeIsIndirect(f.Type()) { o.L("h.%s = &v", f.Name(false)) } else { o.L("h.%s = v", f.Name(false)) } o.L("return nil") o.L("}") // end if v, ok := value.(%s) o.L("return fmt.Errorf(`invalid value for %%s key: %%T`, %s, value)", keyName) } } o.L("default:") o.L("if h.privateParams == nil {") o.L("h.privateParams = map[string]any{}") o.L("}") // end if h.privateParams == nil o.L("h.privateParams[name] = value") o.L("}") // end switch name o.L("return nil") o.L("}") // end func (h *%s) Set(name string, value any) o.LL("func (k *%s) Remove(key string) error {", structName) o.L("k.mu.Lock()") o.L("defer k.mu.Unlock()") o.L("switch key {") for _, f := range obj.Fields() { var keyName string if f.Bool(`is_std`) { keyName = f.Name(true) + "Key" } else { keyName = kt.Prefix + f.Name(true) + "Key" } o.L("case %s:", keyName) o.L("k.%s = nil", f.Name(false)) } o.L("default:") o.L("delete(k.privateParams, key)") o.L("}") o.L("return nil") // currently unused, but who knows o.L("}") o.LL("func (k *%s) Clone() (Key, error) {", structName) o.L("key, err := cloneKey(k)") o.L("if err != nil {") o.L("return nil, fmt.Errorf(`%s.Clone: %%w`, err)", structName) o.L("}") o.L("return key, nil") o.L("}") o.LL("func (k *%s) DecodeCtx() json.DecodeCtx {", structName) o.L("k.mu.RLock()") o.L("defer k.mu.RUnlock()") o.L("return k.dc") o.L("}") o.LL("func (k *%s) SetDecodeCtx(dc json.DecodeCtx) {", structName) o.L("k.mu.Lock()") o.L("defer k.mu.Unlock()") o.L("k.dc = dc") o.L("}") o.LL("func (h *%s) UnmarshalJSON(buf []byte) error {", structName) o.L(`h.mu.Lock()`) o.L(`defer h.mu.Unlock()`) for _, f := range obj.Fields() { o.L("h.%s = nil", f.Name(false)) } o.L("dec := json.NewDecoder(bytes.NewReader(buf))") o.L("LOOP:") o.L("for {") o.L("tok, err := dec.Token()") o.L("if err != nil {") o.L("return fmt.Errorf(`error reading token: %%w`, err)") o.L("}") o.L("switch tok := tok.(type) {") o.L("case json.Delim:") o.L("// Assuming we're doing everything correctly, we should ONLY") o.L("// get either tokens.OpenCurlyBracket or tokens.CloseCurlyBracket here.") o.L("if tok == tokens.CloseCurlyBracket { // End of object") o.L("break LOOP") o.L("} else if tok != tokens.OpenCurlyBracket {") o.L("return fmt.Errorf(`expected '%%c' but got '%%c'`, tokens.OpenCurlyBracket, tok)") o.L("}") o.L("case string: // Objects can only have string keys") o.L("switch tok {") // kty is special. Hardcode it. o.L("case KeyTypeKey:") o.L("val, err := json.ReadNextStringToken(dec)") o.L("if err != nil {") o.L("return fmt.Errorf(`error reading token: %%w`, err)") o.L("}") o.L("if val != %s.String() {", kt.KeyType) o.L("return fmt.Errorf(`invalid kty value for RSAPublicKey (%%s)`, val)") o.L("}") for _, f := range obj.Fields() { if f.Type() == "string" { o.L("case %sKey:", f.Name(true)) o.L("if err := json.AssignNextStringToken(&h.%s, dec); err != nil {", f.Name(false)) o.L("return fmt.Errorf(`failed to decode value for key %%s: %%w`, %sKey, err)", f.Name(true)) o.L("}") } else if f.Type() == "jwa.KeyAlgorithm" { o.L("case %sKey:", f.Name(true)) o.L("var s string") o.L("if err := dec.Decode(&s); err != nil {") o.L("return fmt.Errorf(`failed to decode value for key %%s: %%w`, %sKey, err)", f.Name(true)) o.L("}") o.L("alg, err := jwa.KeyAlgorithmFrom(s)") o.L("if err != nil {") o.L("return fmt.Errorf(`failed to decode value for key %%s: %%w`, %sKey, err)", f.Name(true)) o.L("}") o.L("h.%s = &alg", f.Name(false)) } else if f.Type() == "[]byte" { name := f.Name(true) switch f.Name(false) { case "n", "e", "d", "p", "dp", "dq", "x", "y", "q", "qi", "octets": name = kt.Prefix + f.Name(true) } o.L("case %sKey:", name) o.L("if err := json.AssignNextBytesToken(&h.%s, dec); err != nil {", f.Name(false)) o.L("return fmt.Errorf(`failed to decode value for key %%s: %%w`, %sKey, err)", name) o.L("}") } else { name := f.Name(true) if f.Name(false) == "crv" { name = kt.Prefix + f.Name(true) } o.L("case %sKey:", name) if IsPointer(f) { o.L("var decoded %s", PointerElem(f)) } else { o.L("var decoded %s", f.Type()) } o.L("if err := dec.Decode(&decoded); err != nil {") o.L("return fmt.Errorf(`failed to decode value for key %%s: %%w`, %sKey, err)", name) o.L("}") o.L("h.%s = &decoded", f.Name(false)) } } o.L("default:") // This looks like bad code, but we're unrolling things for maximum // runtime efficiency o.L("if dc := h.dc; dc != nil {") o.L("if localReg := dc.Registry(); localReg != nil {") o.L("decoded, err := localReg.Decode(dec, tok)") o.L("if err == nil {") o.L("h.setNoLock(tok, decoded)") o.L("continue") o.L("}") o.L("}") o.L("}") o.L("decoded, err := registry.Decode(dec, tok)") o.L("if err == nil {") o.L("h.setNoLock(tok, decoded)") o.L("continue") o.L("}") o.L("return fmt.Errorf(`could not decode field %%s: %%w`, tok, err)") o.L("}") o.L("default:") o.L("return fmt.Errorf(`invalid token %%T`, tok)") o.L("}") o.L("}") for _, f := range obj.Fields() { if f.IsRequired() { o.L("if h.%s == nil {", f.Name(false)) o.L("return fmt.Errorf(`required field %s is missing`)", f.JSON()) o.L("}") } } o.L("return nil") o.L("}") o.LL("func (h %s) MarshalJSON() ([]byte, error) {", structName) o.L("data := make(map[string]any)") o.L("fields := make([]string, 0, %d)", len(obj.Fields())) o.L("data[KeyTypeKey] = %s", kt.KeyType) o.L("fields = append(fields, KeyTypeKey)") for _, f := range obj.Fields() { var keyName string if f.Bool(`is_std`) { keyName = f.Name(true) + "Key" } else { keyName = kt.Prefix + f.Name(true) + "Key" } o.L("if h.%s != nil {", f.Name(false)) if fieldStorageTypeIsIndirect(f.Type()) { o.L("data[%s] = *(h.%s)", keyName, f.Name(false)) } else { o.L("data[%s] = h.%s", keyName, f.Name(false)) } o.L("fields = append(fields, %s)", keyName) o.L("}") } o.L("for k, v := range h.privateParams {") o.L("data[k] = v") o.L("fields = append(fields, k)") o.L("}") o.LL("sort.Strings(fields)") o.L("buf := pool.BytesBuffer().Get()") o.L("defer pool.BytesBuffer().Put(buf)") o.L("buf.WriteByte(tokens.OpenCurlyBracket)") o.L("enc := json.NewEncoder(buf)") o.L("for i, f := range fields {") o.L("if i > 0 {") o.L("buf.WriteRune(tokens.Comma)") o.L("}") o.L("buf.WriteRune(tokens.DoubleQuote)") o.L("buf.WriteString(f)") o.L("buf.WriteString(`\":`)") o.L("v := data[f]") o.L("switch v := v.(type) {") o.L("case []byte:") o.L("buf.WriteRune(tokens.DoubleQuote)") o.L("buf.WriteString(base64.EncodeToString(v))") o.L("buf.WriteRune(tokens.DoubleQuote)") o.L("default:") o.L("if err := enc.Encode(v); err != nil {") o.L("return nil, fmt.Errorf(`failed to encode value for field %%s: %%w`, f, err)") o.L("}") o.L("buf.Truncate(buf.Len()-1)") o.L("}") o.L("}") o.L("buf.WriteByte(tokens.CloseCurlyBracket)") o.L("ret := make([]byte, buf.Len())") o.L("copy(ret, buf.Bytes())") o.L("return ret, nil") o.L("}") o.LL("func (h *%s) Keys() []string {", structName) o.L("h.mu.RLock()") o.L("defer h.mu.RUnlock()") o.L("keys := make([]string, 0, %d+len(h.privateParams))", len(obj.Fields())) o.L("keys = append(keys, KeyTypeKey)") for _, f := range obj.Fields() { var keyName string if f.Bool(`is_std`) { keyName = f.Name(true) + "Key" } else { keyName = kt.Prefix + f.Name(true) + "Key" } o.L("if h.%s != nil {", f.Name(false)) o.L("keys = append(keys, %s)", keyName) o.L("}") } o.L("for k := range h.privateParams {") o.L("keys = append(keys, k)") o.L("}") o.L("return keys") o.L("}") return nil } func generateGenericHeaders(fields codegen.FieldList, keyTypes []*KeyType) error { var buf bytes.Buffer o := codegen.NewOutput(&buf) o.L("// Code generated by tools/cmd/genjwk/main.go. DO NOT EDIT.") o.LL("package jwk") o.LL("import (") pkgs := []string{ "crypto/x509", "fmt", "github.com/lestrrat-go/jwx/v3/jwa", } for _, pkg := range pkgs { o.L("%s", strconv.Quote(pkg)) } o.L(")") o.LL("const (") o.L("KeyTypeKey = \"kty\"") for _, f := range fields { o.L("%sKey = %s", f.Name(true), strconv.Quote(f.JSON())) } o.L(")") // end const o.LL("// Key defines the minimal interface for each of the") o.L("// key types. Their use and implementation differ significantly") o.L("// between each key type, so you should use type assertions") o.L("// to perform more specific tasks with each key") o.L("type Key interface {") o.LL("// Has returns true if the specified field has a value, even if") o.L("// the value is empty-ish (e.g. 0, false, \"\") as long as it has been") o.L("// explicitly set.") o.L("Has(string) bool") o.LL("// Get is used to extract the value of any field, including non-standard fields, out of the key.") o.L("//") o.L("// The first argument is the name of the field. The second argument is a pointer") o.L("// to a variable that will receive the value of the field. The method returns") o.L("// an error if the field does not exist, or if the value cannot be assigned to") o.L("// the destination variable. Note that a field is considered to \"exist\" even if") o.L("// the value is empty-ish (e.g. 0, false, \"\"), as long as it is explicitly set.") o.L("Get(string, any) error") o.LL("// Set sets the value of a single field. Note that certain fields,") o.L("// notably \"kty\", cannot be altered, but will not return an error") o.L("//\n// This method, which takes an `any`, exists because") o.L("// these objects can contain extra _arbitrary_ fields that users can") o.L("// specify, and there is no way of knowing what type they could be") o.L("Set(string, any) error") o.LL("// Remove removes the field associated with the specified key.") o.L("// There is no way to remove the `kty` (key type). You will ALWAYS be left with one field in a jwk.Key.") o.L("Remove(string) error") o.L("// Validate performs _minimal_ checks if the data stored in the key are valid.") o.L("// By minimal, we mean that it does not check if the key is valid for use in") o.L("// cryptographic operations. For example, it does not check if an RSA key's") o.L("// `e` field is a valid exponent, or if the `n` field is a valid modulus.") o.L("// Instead, it checks for things such as the _presence_ of some required fields,") o.L("// or if certain keys' values are of particular length.") o.L("//") o.L("// Note that depending on th underlying key type, use of this method requires") o.L("// that multiple fields in the key are properly populated. For example, an EC") o.L("// key's \"x\", \"y\" fields cannot be validated unless the \"crv\" field is populated first.") o.L("//") o.L("// Validate is never called by `UnmarshalJSON()` or `Set`. It must explicitly be") o.L("// called by the user") o.L("Validate() error") o.LL("// Thumbprint returns the JWK thumbprint using the indicated") o.L("// hashing algorithm, according to RFC 7638") o.L("Thumbprint(crypto.Hash) ([]byte, error)") o.LL("// Keys returns a list of the keys contained in this jwk.Key.") o.L("Keys() []string") o.LL("// Clone creates a new instance of the same type") o.L("Clone() (Key, error)") o.LL("// PublicKey creates the corresponding PublicKey type for this object.") o.L("// All fields are copied onto the new public key, except for those that are not allowed.") o.L("//\n// If the key is already a public key, it returns a new copy minus the disallowed fields as above.") o.L("PublicKey() (Key, error)") o.LL("// KeyType returns the `kty` of a JWK") o.L("KeyType() jwa.KeyType") for _, f := range fields { o.L("// %s returns `%s` of a JWK", f.GetterMethod(true), f.JSON()) if f.Name(false) == "algorithm" { o.LL("// Algorithm returns the value of the `alg` field.") o.L("//") o.L("// This field may contain either `jwk.SignatureAlgorithm`, `jwk.KeyEncryptionAlgorithm`, or `jwk.ContentEncryptionAlgorithm`.") o.L("// This is why there exists a `jwa.KeyAlgorithm` type that encompasses both types.") } o.L("%s() (", f.GetterMethod(true)) if v := f.String(`getter_return_value`); v != "" { o.R("%s", v) } else if IsPointer(f) && f.Bool(`noDeref`) { o.R("%s", f.Type()) } else { o.R("%s", PointerElem(f)) } o.R(", bool)") } o.L("}") if err := o.WriteFile("interface_gen.go", codegen.WithFormatCode(true)); err != nil { if cfe, ok := err.(codegen.CodeFormatError); ok { fmt.Fprint(os.Stderr, cfe.Source()) } return fmt.Errorf(`failed to write to interface_gen.go: %w`, err) } return nil } golang-github-lestrrat-go-jwx-3.0.13/tools/cmd/genjwk/objects.yml000066400000000000000000000101011515060566400247410ustar00rootroot00000000000000std_fields: - name: keyUsage json: use comment: https://tools.ietf.org/html/rfc7517#section-4.2 is_std: true - name: keyOps type: KeyOperationList json: key_ops hasAccept: true is_std: true comment: https://tools.ietf.org/html/rfc7517#section-4.3 - name: algorithm json: alg is_std: true comment: https://tools.ietf.org/html/rfc7517#section-4.4 type: jwa.KeyAlgorithm - name: keyID json: kid is_std: true comment: https://tools.ietf.org/html/rfc7515#section-4.1.4 - name: x509URL getter: X509URL unexported_name: x509URL exported_name: X509URL json: x5u is_std: true comment: https://tools.ietf.org/html/rfc7515#section-4.1.5 - name: x509CertChain getter: X509CertChain getter_return_value: "*cert.Chain" type: "*cert.Chain" json: x5c is_std: true noDeref: true comment: https://tools.ietf.org/html/rfc7515#section-4.1.6 - name: x509CertThumbprint getter: X509CertThumbprint json: x5t is_std: true comment: https://tools.ietf.org/html/rfc7515#section-4.1.7 - name: x509CertThumbprintS256 getter: X509CertThumbprintS256 json: "x5t#S256" is_std: true comment: https://tools.ietf.org/html/rfc7515#section-4.1.8 key_types: - filename: rsa_gen.go prefix: RSA key_type: jwa.RSA() objects: - name: publicKey raw_key_type: "*rsa.PublicKey" fields: - name: n type: "[]byte" required: true - name: e type: "[]byte" required: true - name: privateKey raw_key_type: "*rsa.PrivateKey" fields: - name: d type: "[]byte" required: true - name: p type: "[]byte" - name: q type: "[]byte" - name: dp getter: DP exported_name: DP type: "[]byte" - name: dq getter: DQ exported_name: DQ type: "[]byte" - name: qi getter: QI exported_name: QI type: "[]byte" - name: n type: "[]byte" required: true - name: e type: "[]byte" required: true - filename: ecdsa_gen.go prefix: ECDSA key_type: jwa.EC() objects: - name: publicKey raw_key_type: "*ecdsa.PublicKey" fields: - name: x type: "[]byte" required: true - name: y type: "[]byte" required: true - name: crv getter: Crv type: jwa.EllipticCurveAlgorithm required: true - name: privateKey raw_key_type: "*ecdsa.PrivateKey" fields: - name: d type: "[]byte" required: true - name: x type: "[]byte" required: true - name: y type: "[]byte" required: true - name: crv getter: Crv type: jwa.EllipticCurveAlgorithm required: true - filename: symmetric_gen.go prefix: Symmetric key_type: jwa.OctetSeq() objects: - name: symmetricKey interface: SymmetricKey struct_name: symmetricKey raw_key_type: "[]byte" fields: - name: octets type: "[]byte" json: k required: true - filename: okp_gen.go prefix: OKP key_type: jwa.OKP() objects: - name: publicKey raw_key_type: "interface{}" fields: - name: x type: "[]byte" required: true - name: crv getter: Crv type: jwa.EllipticCurveAlgorithm required: true - name: privateKey raw_key_type: "interface{}" fields: - name: x type: "[]byte" required: true - name: d type: "[]byte" required: true - name: crv getter: Crv type: jwa.EllipticCurveAlgorithm required: true golang-github-lestrrat-go-jwx-3.0.13/tools/cmd/genjws.sh000077500000000000000000000006731515060566400231510ustar00rootroot00000000000000#!/bin/bash # Script to perform code generation. This exists to overcome # the fact that go:generate doesn't really allow you to change directories # This file is expected to be executed from jws directory set -e echo "👉 Generating JWS files..." DIR=../tools/cmd/genjws pushd "$DIR" > /dev/null GOWORK=off go build -o .genjws main.go popd > /dev/null EXE="${DIR}/.genjws" "$EXE" -objects="$DIR/objects.yml" echo "✔ done!" rm "$EXE" golang-github-lestrrat-go-jwx-3.0.13/tools/cmd/genjws/000077500000000000000000000000001515060566400226045ustar00rootroot00000000000000golang-github-lestrrat-go-jwx-3.0.13/tools/cmd/genjws/go.mod000066400000000000000000000012631515060566400237140ustar00rootroot00000000000000module github.com/lestrrat-go/jwx/v3/jws/internal/cmd/genheader go 1.20 require ( github.com/goccy/go-yaml v1.11.2 github.com/lestrrat-go/codegen v1.0.4 ) require ( golang.org/x/mod v0.14.0 // indirect golang.org/x/sys v0.14.0 // indirect ) require ( github.com/fatih/color v1.16.0 // indirect github.com/lestrrat-go/option v1.0.1 // indirect github.com/lestrrat-go/xstrings v0.0.0-20220901080742-cacb16b8ddb7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/stretchr/testify v1.8.2 // indirect golang.org/x/tools v0.16.1 // indirect golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect ) golang-github-lestrrat-go-jwx-3.0.13/tools/cmd/genjws/go.sum000066400000000000000000000147461515060566400237530ustar00rootroot00000000000000github.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/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE= github.com/goccy/go-yaml v1.11.2 h1:joq77SxuyIs9zzxEjgyLBugMQ9NEgTWxXfz2wVqwAaQ= github.com/goccy/go-yaml v1.11.2/go.mod h1:wKnAMd44+9JAAnGQpWVEgBzGt3YuTaQ4uXoHvE4m7WU= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= github.com/lestrrat-go/codegen v1.0.4 h1:xWRqMkHzfpN/nfl4EeAwmbTvS7uotxfUPl8RhpjB3Go= github.com/lestrrat-go/codegen v1.0.4/go.mod h1:JQPYOh/5hA2lipdHWj3YZHoKEGUfLmGQoWcWs4I92qk= github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU= github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= github.com/lestrrat-go/xstrings v0.0.0-20210804220435-4dd8b234342b/go.mod h1:mPFmD3Wuy0ddyPFvllLq4sUpGfE40T3VE8kWWS8fxGA= github.com/lestrrat-go/xstrings v0.0.0-20220901080742-cacb16b8ddb7 h1:8YrnMMQZquDwIgfQvZZ+JGMrRIn9UdzremIkMGQ/RoU= github.com/lestrrat-go/xstrings v0.0.0-20220901080742-cacb16b8ddb7/go.mod h1:mPFmD3Wuy0ddyPFvllLq4sUpGfE40T3VE8kWWS8fxGA= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 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/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.0/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/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/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-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q= golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200918232735-d647fc253266/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= golang.org/x/tools v0.16.1 h1:TLyB3WofjdOEepBHAU20JdNC1Zbg87elYofWYAY5oZA= golang.org/x/tools v0.16.1/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU= golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= golang-github-lestrrat-go-jwx-3.0.13/tools/cmd/genjws/main.go000066400000000000000000000342751515060566400240720ustar00rootroot00000000000000package main import ( "bytes" "encoding/json" "flag" "fmt" "log" "os" "strings" "github.com/goccy/go-yaml" "github.com/lestrrat-go/codegen" ) func main() { if err := _main(); err != nil { log.Printf("%s", err) os.Exit(1) } } func _main() error { codegen.RegisterZeroVal(`jwa.SignatureAlgorithm`, `jwa.EmptySignatureAlgorithm()`) var objectsFile = flag.String("objects", "objects.yml", "") flag.Parse() jsonSrc, err := yaml2json(*objectsFile) if err != nil { return err } var object codegen.Object if err := json.NewDecoder(bytes.NewReader(jsonSrc)).Decode(&object); err != nil { return fmt.Errorf(`failed to decode %q: %w`, *objectsFile, err) } object.Organize() return generateHeaders(&object) } func yaml2json(fn string) ([]byte, error) { in, err := os.Open(fn) if err != nil { return nil, fmt.Errorf(`failed to open %q: %w`, fn, err) } defer in.Close() var v any if err := yaml.NewDecoder(in).Decode(&v); err != nil { return nil, fmt.Errorf(`failed to decode %q: %w`, fn, err) } return json.Marshal(v) } func IsPointer(f codegen.Field) bool { return strings.HasPrefix(f.Type(), `*`) } func PointerElem(f codegen.Field) string { return strings.TrimPrefix(f.Type(), `*`) } func fieldStorageType(s string) string { if fieldStorageTypeIsIndirect(s) { return `*` + s } return s } func fieldStorageTypeIsIndirect(s string) bool { return !(s == "jwk.Key" || strings.HasPrefix(s, `*`) || strings.HasPrefix(s, `[]`)) } func generateHeaders(obj *codegen.Object) error { var buf bytes.Buffer o := codegen.NewOutput(&buf) o.L("// Code generated by tools/cmd/genjws/main.go. DO NOT EDIT.") o.LL("package jws") o.LL("const (") for _, f := range obj.Fields() { o.L("%sKey = %q", f.Name(true), f.JSON()) } o.L(")") // end const o.LL("// Headers describe a standard JWS Header set. It is part of the JWS message") o.L("// and is used to represet both Public or Protected headers, which in turn") o.L("// can be found in each Signature object. If you are not sure how this works,") o.L("// it is strongly recommended that you read RFC7515, especially the section") o.L("// that describes the full JSON serialization format of JWS messages.") o.L("//") o.L("// In most cases, you likely want to use the protected headers, as this is part of the signed content.") o.L("type Headers interface {") // These are the basic values that most jws have for _, f := range obj.Fields() { if f.Bool(`noDeref`) { o.L("%s() (%s, bool)", f.GetterMethod(true), f.Type()) } else { o.L("%s() (%s, bool)", f.GetterMethod(true), PointerElem(f)) } } o.L("Copy(Headers) error") o.L("Merge(Headers) (Headers, error)") o.L("Clone() (Headers, error)") // These are used to access a single element by key name o.L("// Get is used to extract the value of any field, including non-standard fields, out of the header.") o.L("//") o.L("// The first argument is the name of the field. The second argument is a pointer") o.L("// to a variable that will receive the value of the field. The method returns") o.L("// an error if the field does not exist, or if the value cannot be assigned to") o.L("// the destination variable. Note that a field is considered to \"exist\" even if") o.L("// the value is empty-ish (e.g. 0, false, \"\"), as long as it is explicitly set.") o.L("Get(string, any) error") o.L("Set(string, any) error") o.L("Remove(string) error") o.L("// Has returns true if the specified header has a value, even if") o.L("// the value is empty-ish (e.g. 0, false, \"\") as long as it has been") o.L("// explicitly set.") o.L("Has(string) bool") o.L("Keys() []string") o.L("}") o.LL("// stdHeaderNames is a list of all standard header names defined in the JWS specification.") o.L("var stdHeaderNames = []string{") for i, f := range obj.Fields() { if i > 0 { o.R(",") } o.R("%sKey", f.Name(true)) } o.R("}") o.LL("type stdHeaders struct {") for _, f := range obj.Fields() { if c := f.Comment(); c != "" { o.L("%s %s // %s", f.Name(false), fieldStorageType(f.Type()), c) } else { o.L("%s %s", f.Name(false), fieldStorageType(f.Type())) } } o.L("privateParams map[string]any") o.L("mu *sync.RWMutex") o.L("dc DecodeCtx") o.L("raw []byte // stores the raw version of the header so it can be used later") o.L("}") // end type StandardHeaders o.LL("func NewHeaders() Headers {") o.L("return &stdHeaders{") o.L("mu: &sync.RWMutex{},") o.L("}") o.L("}") for _, f := range obj.Fields() { o.LL("func (h *stdHeaders) %s() (%s, bool) {", f.GetterMethod(true), f.Type()) o.L("h.mu.RLock()") o.L("defer h.mu.RUnlock()") if fieldStorageTypeIsIndirect(f.Type()) { o.L("if h.%s == nil {", f.Name(false)) o.L("return %s, false", codegen.ZeroVal(f.Type())) o.L("}") o.L("return *(h.%s), true", f.Name(false)) } else { o.L("return h.%s, true", f.Name(false)) } o.L("}") // func (h *stdHeaders) %s() %s } o.LL("func (h *stdHeaders) clear() {") for _, f := range obj.Fields() { o.L("h.%s = nil", f.Name(false)) } o.L("h.privateParams = nil") o.L("h.raw = nil") o.L("}") o.LL("func (h *stdHeaders) DecodeCtx() DecodeCtx{") o.L("h.mu.RLock()") o.L("defer h.mu.RUnlock()") o.L("return h.dc") o.L("}") o.LL("func (h *stdHeaders) SetDecodeCtx(dc DecodeCtx) {") o.L("h.mu.Lock()") o.L("defer h.mu.Unlock()") o.L("h.dc = dc") o.L("}") // This has no lock because nothing can assign to it o.LL("func (h *stdHeaders) rawBuffer() []byte {") o.L("return h.raw") o.L("}") o.LL("func (h *stdHeaders) PrivateParams() map[string]any {") o.L("h.mu.RLock()") o.L("defer h.mu.RUnlock()") o.L("return h.privateParams") o.L("}") o.LL("func (h *stdHeaders) Has(name string) bool {") o.L("h.mu.RLock()") o.L("defer h.mu.RUnlock()") o.L("switch name {") for _, f := range obj.Fields() { o.L("case %sKey:", f.Name(true)) o.L("return h.%s != nil", f.Name(false)) } o.L("default:") o.L("_, ok := h.privateParams[name]") o.L("return ok") o.L("}") o.L("}") o.LL("func (h *stdHeaders) Get(name string, dst any) error {") o.L("h.mu.RLock()") o.L("defer h.mu.RUnlock()") o.L("switch name {") for _, f := range obj.Fields() { o.L("case %sKey:", f.Name(true)) o.L("if h.%s == nil {", f.Name(false)) o.L("return fmt.Errorf(`field %%q not found`, name)") o.L("}") o.L("if err := blackmagic.AssignIfCompatible(dst, ") if fieldStorageTypeIsIndirect(f.Type()) { o.R("*(h.%s)", f.Name(false)) } else { o.L("h.%s", f.Name(false)) } o.R("); err != nil {") o.L("return fmt.Errorf(`failed to assign value for field %%q: %%w`, name, err)") o.L("}") o.L("return nil") } o.L("default:") o.L("v, ok := h.privateParams[name]") o.L("if !ok {") o.L("return fmt.Errorf(`field %%q not found`, name)") o.L("}") o.L("if err := blackmagic.AssignIfCompatible(dst, v); err != nil {") o.L("return fmt.Errorf(`failed to assign value for field %%q: %%w`, name, err)") o.L("}") o.L("}") // end switch name o.L("return nil") o.L("}") // func (h *stdHeaders) Get(name string) (any, bool) o.LL("func (h *stdHeaders) Set(name string, value any) error {") o.L("h.mu.Lock()") o.L("defer h.mu.Unlock()") o.L("return h.setNoLock(name, value)") o.L("}") o.LL("func (h *stdHeaders) setNoLock(name string, value any) error {") o.L("switch name {") for _, f := range obj.Fields() { o.L("case %sKey:", f.Name(true)) if f.Name(true) == `Algorithm` { o.L(`alg, err := jwa.KeyAlgorithmFrom(value)`) o.L(`if err != nil {`) o.L(`return fmt.Errorf("invalid value for %%s key: %%w", %sKey, err)`, f.Name(true)) o.L(`}`) o.L(`if salg, ok := alg.(jwa.SignatureAlgorithm); ok {`) o.L(`h.%s = &salg`, f.Name(false)) o.L(`return nil`) o.L(`}`) o.L(`return fmt.Errorf("expecte jwa.SignatureAlgorithm, received %%T", alg)`) } else if f.Bool(`hasAccept`) { o.L("var acceptor %s", PointerElem(f)) o.L("if err := acceptor.Accept(value); err != nil {") o.L("return fmt.Errorf(`invalid value for %%s key: %%w`, %sKey, err)", f.Name(true)) o.L("}") // end if err := h.%s.Accept(value) o.L("h.%s = &acceptor", f.Name(false)) o.L("return nil") } else { o.L("if v, ok := value.(%s); ok {", f.Type()) if fieldStorageTypeIsIndirect(f.Type()) { o.L("h.%s = &v", f.Name(false)) } else { o.L("h.%s = v", f.Name(false)) } o.L("return nil") o.L("}") // end if v, ok := value.(%s) o.L("return fmt.Errorf(`invalid value for %%s key: %%T`, %sKey, value)", f.Name(true)) } } o.L("default:") o.L("if h.privateParams == nil {") o.L("h.privateParams = map[string]any{}") o.L("}") // end if h.privateParams == nil o.L("h.privateParams[name] = value") o.L("}") // end switch name o.L("return nil") o.L("}") o.LL("func (h *stdHeaders) Remove(key string) error {") o.L("h.mu.Lock()") o.L("defer h.mu.Unlock()") o.L("switch key {") for _, f := range obj.Fields() { o.L("case %sKey:", f.Name(true)) o.L("h.%s = nil", f.Name(false)) } o.L("default:") o.L("delete(h.privateParams, key)") o.L("}") o.L("return nil") // currently unused, but who knows o.L("}") o.LL("func (h *stdHeaders) UnmarshalJSON(buf []byte) error {") o.L("h.mu.Lock()") o.L("defer h.mu.Unlock()") o.L("h.clear()") o.L("dec := json.NewDecoder(bytes.NewReader(buf))") o.L("LOOP:") o.L("for {") o.L("tok, err := dec.Token()") o.L("if err != nil {") o.L("return fmt.Errorf(`error reading token: %%w`, err)") o.L("}") o.L("switch tok := tok.(type) {") o.L("case json.Delim:") o.L("// Assuming we're doing everything correctly, we should ONLY") o.L("// get either tokens.OpenCurlyBracket or tokens.CloseCurlyBracket here.") o.L("if tok == tokens.CloseCurlyBracket { // End of object") o.L("break LOOP") o.L("} else if tok != tokens.OpenCurlyBracket {") o.L("return fmt.Errorf(`expected '%%c' but got '%%c'`, tokens.OpenCurlyBracket, tok)") o.L("}") o.L("case string: // Objects can only have string keys") o.L("switch tok {") for _, f := range obj.Fields() { if f.Type() == "string" { o.L("case %sKey:", f.Name(true)) o.L("if err := json.AssignNextStringToken(&h.%s, dec); err != nil {", f.Name(false)) o.L("return fmt.Errorf(`failed to decode value for key %%s: %%w`, %sKey, err)", f.Name(true)) o.L("}") } else if f.Type() == "[]byte" { o.L("case %sKey:", f.Name(true)) o.L("if err := json.AssignNextBytesToken(&h.%s, dec); err != nil {", f.Name(false)) o.L("return fmt.Errorf(`failed to decode value for key %%s`, %sKey, err)", f.Name(true)) o.L("}") } else if f.Type() == "jwk.Key" { o.L("case %sKey:", f.Name(true)) o.L("var buf json.RawMessage") o.L("if err := dec.Decode(&buf); err != nil {") o.L("return fmt.Errorf(`failed to decode value for key %%s: %%w`, %sKey, err)", f.Name(true)) o.L("}") o.L("key, err := jwk.ParseKey(buf)") o.L("if err != nil {") o.L("return fmt.Errorf(`failed to parse JWK for key %%s: %%w`, %sKey, err)", f.Name(true)) o.L("}") o.L("h.%s = key", f.Name(false)) } else if strings.HasPrefix(f.Type(), "[]") { o.L("case %sKey:", f.Name(true)) o.L("var decoded %s", f.Type()) o.L("if err := dec.Decode(&decoded); err != nil {") o.L("return fmt.Errorf(`failed to decode value for key %%s: %%w`, %sKey, err)", f.Name(true)) o.L("}") o.L("h.%s = decoded", f.Name(false)) } else if f.Bool(`noDeref`) { o.L("case %sKey:", f.Name(true)) o.L("var decoded %s", PointerElem(f)) o.L("if err := dec.Decode(&decoded); err != nil {") o.L("return fmt.Errorf(`failed to decode value for key %%s: %%w`, %sKey, err)", f.Name(true)) o.L("}") o.L("h.%s = &decoded", f.Name(false)) } else { o.L("case %sKey:", f.Name(true)) o.L("var decoded %s", f.Type()) o.L("if err := dec.Decode(&decoded); err != nil {") o.L("return fmt.Errorf(`failed to decode value for key %%s: %%w`, %sKey, err)", f.Name(true)) o.L("}") o.L("h.%s = &decoded", f.Name(false)) } } o.L("default:") o.L("decoded, err := registry.Decode(dec, tok)") o.L("if err != nil {") o.L("return err") o.L("}") o.L("h.setNoLock(tok, decoded)") o.L("}") o.L("default:") o.L("return fmt.Errorf(`invalid token %%T`, tok)") o.L("}") o.L("}") o.L("h.raw = buf") o.L("return nil") o.L("}") o.LL("func (h *stdHeaders) Keys() []string {") o.L("h.mu.RLock()") o.L("defer h.mu.RUnlock()") o.L("keys := make([]string, 0, %d+len(h.privateParams))", len(obj.Fields())) for _, f := range obj.Fields() { keyName := f.Name(true) + "Key" o.L("if h.%s != nil {", f.Name(false)) o.L("keys = append(keys, %s)", keyName) o.L("}") } o.L("for k := range h.privateParams {") o.L("keys = append(keys, k)") o.L("}") o.L("return keys") o.L("}") o.LL("func (h stdHeaders) MarshalJSON() ([]byte, error) {") o.L("h.mu.RLock()") o.L("data := make(map[string]any)") o.L("keys := make([]string, 0, %d+len(h.privateParams))", len(obj.Fields())) for _, f := range obj.Fields() { o.L("if h.%s != nil {", f.Name(false)) if fieldStorageTypeIsIndirect(f.Type()) { o.L("data[%sKey] = *(h.%s)", f.Name(true), f.Name(false)) } else { o.L("data[%sKey] = h.%s", f.Name(true), f.Name(false)) } o.L("keys = append(keys, %sKey)", f.Name(true)) o.L("}") } o.L("for k, v := range h.privateParams {") o.L("data[k] = v") o.L("keys = append(keys, k)") o.L("}") o.L("h.mu.RUnlock()") o.L("sort.Strings(keys)") o.L("buf := pool.BytesBuffer().Get()") o.L("defer pool.BytesBuffer().Put(buf)") o.L("enc := json.NewEncoder(buf)") o.L("buf.WriteByte(tokens.OpenCurlyBracket)") o.L("for i, k := range keys {") o.L("if i > 0 {") o.L("buf.WriteRune(tokens.Comma)") o.L("}") o.L("buf.WriteRune(tokens.DoubleQuote)") o.L("buf.WriteString(k)") o.L("buf.WriteString(`\":`)") o.L("switch v := data[k].(type) {") o.L("case []byte:") o.L("buf.WriteRune(tokens.DoubleQuote)") o.L("buf.WriteString(base64.EncodeToString(v))") o.L("buf.WriteRune(tokens.DoubleQuote)") o.L("default:") o.L("if err := enc.Encode(v); err != nil {") o.L("return nil, fmt.Errorf(`failed to encode value for field %%s: %%w`, k, err)") o.L("}") o.L("buf.Truncate(buf.Len()-1)") o.L("}") o.L("}") o.L("buf.WriteByte(tokens.CloseCurlyBracket)") o.L("ret := make([]byte, buf.Len())") o.L("copy(ret, buf.Bytes())") o.L("return ret, nil") o.L("}") if err := o.WriteFile(`headers_gen.go`, codegen.WithFormatCode(true)); err != nil { if cfe, ok := err.(codegen.CodeFormatError); ok { fmt.Fprint(os.Stderr, cfe.Source()) } return fmt.Errorf(`failed to write to headers_gen.go: %w`, err) } return nil } golang-github-lestrrat-go-jwx-3.0.13/tools/cmd/genjws/objects.yml000066400000000000000000000032751515060566400247670ustar00rootroot00000000000000fields: - name: algorithm type: jwa.SignatureAlgorithm json: alg hasAccept: true comment: https://tools.ietf.org/html/rfc7515#section-4.1.1 - name: jwkSetURL unexported_name: jwkSetURL exported_name: JWKSetURL getter: JWKSetURL json: jku comment: https://tools.ietf.org/html/rfc7515#section-4.1.2 - name: jwk exported_name: JWK getter: JWK type: jwk.Key comment: https://tools.ietf.org/html/rfc7515#section-4.1.3 - name: keyID json: kid comment: https://tools.ietf.org/html/rfc7515#section-4.1.4 - name: x509URL unexported_name: x509URL exported_name: X509URL getter: X509URL json: x5u comment: https://tools.ietf.org/html/rfc7515#section-4.1.5 - name: x509CertChain unexported_name: x509CertChain exported_name: X509CertChain getter: X509CertChain type: "*cert.Chain" noDeref: true json: x5c comment: https://tools.ietf.org/html/rfc7515#section-4.1.6 - name: x509CertThumbprint unexported_name: x509CertThumbprint getter: X509CertThumbprint json: x5t comment: https://tools.ietf.org/html/rfc7515#section-4.1.7 - name: x509CertThumbprintS256 unexported_name: x509CertThumbprintS256 exported_name: X509CertThumbprintS256 getter: X509CertThumbprintS256 json: "x5t#S256" comment: https://tools.ietf.org/html/rfc7515#section-4.1.8 - name: typ exported_name: Type getter: Type comment: https://tools.ietf.org/html/rfc7515#section-4.1.9 - name: contentType json: cty comment: https://tools.ietf.org/html/rfc7515#section-4.1.10 - name: critical type: "[]string" json: crit comment: https://tools.ietf.org/html/rfc7515#section-4.1.11 golang-github-lestrrat-go-jwx-3.0.13/tools/cmd/genjwt.sh000077500000000000000000000006731515060566400231520ustar00rootroot00000000000000#!/bin/bash # Script to perform code generation. This exists to overcome # the fact that go:generate doesn't really allow you to change directories # This file is expected to be executed from jwt directory set -e echo "👉 Generating JWT files..." DIR=../tools/cmd/genjwt pushd "$DIR" > /dev/null GOWORK=off go build -o .genjwt main.go popd > /dev/null EXE="${DIR}/.genjwt" "$EXE" -objects="$DIR/objects.yml" echo "✔ done!" rm "$EXE" golang-github-lestrrat-go-jwx-3.0.13/tools/cmd/genjwt/000077500000000000000000000000001515060566400226055ustar00rootroot00000000000000golang-github-lestrrat-go-jwx-3.0.13/tools/cmd/genjwt/go.mod000066400000000000000000000014071515060566400237150ustar00rootroot00000000000000module github.com/lestrrat-go/jwx/v3/jwt/internal/cmd/gentoken go 1.24.0 toolchain go1.24.4 require ( github.com/fatih/color v1.16.0 // indirect github.com/goccy/go-json v0.10.3 github.com/goccy/go-yaml v1.11.2 github.com/lestrrat-go/codegen v1.0.4 ) require ( github.com/lestrrat-go/option v1.0.1 // indirect github.com/lestrrat-go/xstrings v0.0.0-20220901080742-cacb16b8ddb7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/stretchr/testify v1.11.1 // indirect golang.org/x/crypto v0.45.0 // indirect golang.org/x/mod v0.14.0 // indirect golang.org/x/sys v0.38.0 // indirect golang.org/x/tools v0.16.1 // indirect golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect ) golang-github-lestrrat-go-jwx-3.0.13/tools/cmd/genjwt/go.sum000066400000000000000000000156771515060566400237600ustar00rootroot00000000000000github.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/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE= github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA= github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/goccy/go-yaml v1.11.2 h1:joq77SxuyIs9zzxEjgyLBugMQ9NEgTWxXfz2wVqwAaQ= github.com/goccy/go-yaml v1.11.2/go.mod h1:wKnAMd44+9JAAnGQpWVEgBzGt3YuTaQ4uXoHvE4m7WU= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/lestrrat-go/codegen v1.0.4 h1:xWRqMkHzfpN/nfl4EeAwmbTvS7uotxfUPl8RhpjB3Go= github.com/lestrrat-go/codegen v1.0.4/go.mod h1:JQPYOh/5hA2lipdHWj3YZHoKEGUfLmGQoWcWs4I92qk= github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU= github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= github.com/lestrrat-go/xstrings v0.0.0-20210804220435-4dd8b234342b/go.mod h1:mPFmD3Wuy0ddyPFvllLq4sUpGfE40T3VE8kWWS8fxGA= github.com/lestrrat-go/xstrings v0.0.0-20220901080742-cacb16b8ddb7 h1:8YrnMMQZquDwIgfQvZZ+JGMrRIn9UdzremIkMGQ/RoU= github.com/lestrrat-go/xstrings v0.0.0-20220901080742-cacb16b8ddb7/go.mod h1:mPFmD3Wuy0ddyPFvllLq4sUpGfE40T3VE8kWWS8fxGA= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/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-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200918232735-d647fc253266/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= golang.org/x/tools v0.16.1 h1:TLyB3WofjdOEepBHAU20JdNC1Zbg87elYofWYAY5oZA= golang.org/x/tools v0.16.1/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU= golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= golang-github-lestrrat-go-jwx-3.0.13/tools/cmd/genjwt/main.go000066400000000000000000000537041515060566400240710ustar00rootroot00000000000000package main import ( "bytes" "flag" "fmt" "log" "os" "path/filepath" "strconv" "strings" "github.com/goccy/go-json" "github.com/goccy/go-yaml" "github.com/lestrrat-go/codegen" ) const ( byteSliceType = "[]byte" ) func main() { if err := _main(); err != nil { log.Printf("%s", err) os.Exit(1) } } func _main() error { var objectsFile = flag.String("objects", "objects.yml", "") flag.Parse() jsonSrc, err := yaml2json(*objectsFile) if err != nil { return err } var def struct { CommonFields codegen.FieldList `json:"common_fields"` Objects []*codegen.Object `json:"objects"` } if err := json.NewDecoder(bytes.NewReader(jsonSrc)).Decode(&def); err != nil { return fmt.Errorf(`failed to decode %q: %w`, *objectsFile, err) } for _, object := range def.Objects { for _, f := range def.CommonFields { object.AddField(f) } object.Organize() } for _, object := range def.Objects { if err := generateToken(object); err != nil { return fmt.Errorf(`failed to generate token file %s: %w`, object.MustString(`filename`), err) } } for _, object := range def.Objects { if err := genBuilder(object); err != nil { return fmt.Errorf(`failed to generate builder for package %q: %w`, object.MustString(`package`), err) } } return nil } func yaml2json(fn string) ([]byte, error) { in, err := os.Open(fn) if err != nil { return nil, fmt.Errorf(`failed to open %q: %w`, fn, err) } defer in.Close() var v any if err := yaml.NewDecoder(in).Decode(&v); err != nil { return nil, fmt.Errorf(`failed to decode %q: %w`, fn, err) } return json.Marshal(v) } func IsPointer(f codegen.Field) bool { return strings.HasPrefix(f.Type(), `*`) } func PointerElem(f codegen.Field) string { return strings.TrimPrefix(f.Type(), `*`) } func fieldStorageType(s string) string { if fieldStorageTypeIsIndirect(s) { return `*` + s } return s } func fieldStorageTypeIsIndirect(s string) bool { return !(strings.HasPrefix(s, `*`) || strings.HasPrefix(s, `[]`) || strings.HasSuffix(s, `List`)) } func generateToken(obj *codegen.Object) error { var buf bytes.Buffer o := codegen.NewOutput(&buf) o.L("// Code generated by tools/cmd/genjwt/main.go. DO NOT EDIT.") o.LL("package %s", obj.String(`package`)) o.WriteImportPkgs( codegen.ImportPkg{ Alias: `jwterrs`, URL: `github.com/lestrrat-go/jwx/v3/jwt/internal/errors`, }, ) var fields = obj.Fields() o.LL("const (") for _, f := range fields { o.L("%sKey = %s", f.Name(true), strconv.Quote(f.JSON())) } o.L(")") // end const var pkgPrefix string if obj.String(`package`) != `jwt` { pkgPrefix = `jwt.` } // Create a stdClaimNames array o.LL("// stdClaimNames is a list of all standard claim names defined in the JWT specification.") o.L("var stdClaimNames = []string{") for i, f := range fields { if i > 0 { o.R(", ") } o.R("%sKey", f.Name(true)) } o.R("}") if obj.String(`package`) == "jwt" && obj.Name(false) == "stdToken" { o.LL("// Token represents a generic JWT token.") o.L("// which are type-aware (to an extent). Other claims may be accessed via the `Get`/`Set`") o.L("// methods but their types are not taken into consideration at all. If you have non-standard") o.L("// claims that you must frequently access, consider creating accessors functions") o.L("// like the following") o.L("//\n// func SetFoo(tok jwt.Token) error") o.L("// func GetFoo(tok jwt.Token) (*Customtyp, error)") o.L("//\n// Embedding jwt.Token into another struct is not recommended, because") o.L("// jwt.Token needs to handle private claims, and this really does not") o.L("// work well when it is embedded in other structure") } o.L("type %s interface {", obj.String(`interface`)) for i, field := range fields { if i > 0 { o.L("") } o.L("// %s returns the value for %q field of the token", field.GetterMethod(true), field.JSON()) rv := field.String(`getter_return_value`) if rv == "" { rv = field.Type() } o.L("%s() (%s, bool)", field.GetterMethod(true), rv) } o.LL("// Get is used to extract the value of any claim, including non-standard claims, out of the token.") o.L("//") o.L("// The first argument is the name of the claim. The second argument is a pointer") o.L("// to a variable that will receive the value of the claim. The method returns") o.L("// an error if the claim does not exist, or if the value cannot be assigned to") o.L("// the destination variable. Note that a field is considered to \"exist\" even if") o.L("// the value is empty-ish (e.g. 0, false, \"\"), as long as it is explicitly set.") o.L("//") o.L("// For standard claims, you can use the corresponding getter method, such as") o.L("// `Issuer()`, `Subject()`, `Audience()`, `IssuedAt()`, `NotBefore()`, `ExpiresAt()`") o.L("//") o.L("// Note that fields of JWS/JWE are NOT accessible through this method. You need") o.L("// to use `jws.Parse` and `jwe.Parse` to obtain the JWS/JWE message (and NOT") o.L("// the payload, which presumably is the JWT), and then use their `Get` methods in their respective packages") o.L("Get(string, any) error") o.LL("// Set assigns a value to the corresponding field in the token. Some") o.L("// pre-defined fields such as `nbf`, `iat`, `iss` need their values to") o.L("// be of a specific type. See the other getter methods in this interface") o.L("// for the types of each of these fields") o.L("Set(string, any) error") o.LL("// Has returns true if the specified claim has a value, even if") o.L("// the value is empty-ish (e.g. 0, false, \"\") as long as it has been") o.L("// explicitly set.") o.L("Has(string) bool") o.L("Remove(string) error") o.LL("// Options returns the per-token options associated with this token.") o.L("// The options set value will be copied when the token is cloned via `Clone()`") o.L("// but it will not survive when the token goes through marshaling/unmarshaling") o.L("// such as `json.Marshal` and `json.Unmarshal`") o.L("Options() *%sTokenOptionSet", pkgPrefix) o.L("Clone() (%sToken, error)", pkgPrefix) o.L("Keys() []string") o.L("}") o.L("type %s struct {", obj.Name(false)) o.L("mu *sync.RWMutex") o.L("dc DecodeCtx // per-object context for decoding") o.L("options %sTokenOptionSet // per-object option", pkgPrefix) for _, f := range fields { if c := f.Comment(); c != "" { o.L("%s %s // %s", f.Name(false), fieldStorageType(f.Type()), c) } else { o.L("%s %s", f.Name(false), fieldStorageType(f.Type())) } } o.L("privateClaims map[string]any") o.L("}") // end type Token o.LL("// New creates a standard token, with minimal knowledge of") o.L("// possible claims. Standard claims include") for i, field := range fields { o.R("%s", strconv.Quote(field.JSON())) switch { case i < len(fields)-2: o.R(", ") case i == len(fields)-2: o.R(" and ") } } o.R(".\n// Convenience accessors are provided for these standard claims") o.L("func New() %s {", obj.String(`interface`)) o.L("return &%s{", obj.Name(false)) o.L("mu: &sync.RWMutex{},") o.L("privateClaims: make(map[string]any),") o.L("options: %sDefaultOptionSet(),", pkgPrefix) o.L("}") o.L("}") o.LL("func (t *%s) Options() *%sTokenOptionSet {", obj.Name(false), pkgPrefix) o.L("return &t.options") o.L("}") o.LL("func (t *%s) Has(name string) bool {", obj.Name(false)) o.L("t.mu.RLock()") o.L("defer t.mu.RUnlock()") o.L("switch name {") for _, f := range obj.Fields() { o.L("case %sKey:", f.Name(true)) o.L("return t.%s != nil", f.Name(false)) } o.L("default:") o.L("_, ok := t.privateClaims[name]") o.L("return ok") o.L("}") o.L("}") o.LL("func (t *%s) Get(name string, dst any) error {", obj.Name(false)) o.L("t.mu.RLock()") o.L("defer t.mu.RUnlock()") o.L("switch name {") for _, f := range fields { o.L("case %sKey:", f.Name(true)) o.L("if t.%s == nil {", f.Name(false)) o.L("return jwterrs.ClaimNotFoundError{Name: name}") o.L("}") o.L("if err := blackmagic.AssignIfCompatible(dst, ") if f.Bool(`hasGet`) { o.R("t.%s.Get()", f.Name(false)) } else { if fieldStorageTypeIsIndirect(f.Type()) { o.R("*(t.%s)", f.Name(false)) } else { o.R("t.%s", f.Name(false)) } } o.R("); err != nil {") o.L("return jwterrs.ClaimAssignmentFailedError{Err: err}") o.L("}") o.L("return nil") } o.L("default:") o.L("v, ok := t.privateClaims[name]") o.L("if !ok {") o.L("return jwterrs.ClaimNotFoundError{Name: name}") o.L("}") o.L("if err := blackmagic.AssignIfCompatible(dst, v); err != nil {") o.L("return jwterrs.ClaimAssignmentFailedError{Err: err}") o.L("}") o.L("return nil") o.L("}") // end switch name o.L("}") // end of Get o.LL("func (t *stdToken) Remove(key string) error {") o.L("t.mu.Lock()") o.L("defer t.mu.Unlock()") o.L("switch key {") for _, f := range fields { o.L("case %sKey:", f.Name(true)) o.L("t.%s = nil", f.Name(false)) } o.L("default:") o.L("delete(t.privateClaims, key)") o.L("}") o.L("return nil") // currently unused, but who knows o.L("}") o.LL("func (t *%s) Set(name string, value any) error {", obj.Name(false)) o.L("t.mu.Lock()") o.L("defer t.mu.Unlock()") o.L("return t.setNoLock(name, value)") o.L("}") o.LL("func (t *%s) DecodeCtx() DecodeCtx {", obj.Name(false)) o.L("t.mu.RLock()") o.L("defer t.mu.RUnlock()") o.L("return t.dc") o.L("}") o.LL("func (t *%s) SetDecodeCtx(v DecodeCtx) {", obj.Name(false)) o.L("t.mu.Lock()") o.L("defer t.mu.Unlock()") o.L("t.dc = v") o.L("}") o.LL("func (t *%s) setNoLock(name string, value any) error {", obj.Name(false)) o.L("switch name {") for _, f := range fields { keyName := f.Name(true) + "Key" o.L("case %s:", keyName) if f.Name(false) == `algorithm` { o.L("switch v := value.(type) {") o.L("case string:") o.L("t.algorithm = &v") o.L("case fmt.Stringer:") o.L("tmp := v.String()") o.L("t.algorithm = &tmp") o.L("default:") o.L("return fmt.Errorf(`invalid type for %%s key: %%T`, %s, value)", keyName) o.L("}") o.L("return nil") } else if f.Bool(`hasAccept`) { if IsPointer(f) { o.L("var acceptor %s", strings.TrimPrefix(f.Type(), "*")) } else { o.L("var acceptor %s", f.Type()) } o.L("if err := acceptor.Accept(value); err != nil {") o.L("return fmt.Errorf(`invalid value for %%s key: %%w`, %s, err)", keyName) o.L("}") // end if err := t.%s.Accept(value) if fieldStorageTypeIsIndirect(f.Type()) || IsPointer(f) { o.L("t.%s = &acceptor", f.Name(false)) } else { o.L("t.%s = acceptor", f.Name(false)) } o.L("return nil") } else { o.L("if v, ok := value.(%s); ok {", f.Type()) if fieldStorageTypeIsIndirect(f.Type()) { o.L("t.%s = &v", f.Name(false)) } else { o.L("t.%s = v", f.Name(false)) } o.L("return nil") o.L("}") // end if v, ok := value.(%s) o.L("return fmt.Errorf(`invalid value for %%s key: %%T`, %s, value)", keyName) } } o.L("default:") o.L("if t.privateClaims == nil {") o.L("t.privateClaims = map[string]any{}") o.L("}") // end if t.privateClaims == nil o.L("t.privateClaims[name] = value") o.L("}") // end switch name o.L("return nil") o.L("}") // end func (t *%s) Set(name string, value any) for _, f := range fields { rv := f.String(`getter_return_value`) if rv == "" { rv = f.Type() } o.LL("func (t *%s) %s() (", obj.Name(false), f.GetterMethod(true)) if rv != "" { o.R("%s", rv) } else if IsPointer(f) && f.Bool(`noDeref`) { o.R("%s", f.Type()) } else { o.R("%s", PointerElem(f)) } o.R(", bool) {") o.L("t.mu.RLock()") o.L("defer t.mu.RUnlock()") if f.Bool(`hasGet`) { o.L("if t.%s != nil {", f.Name(false)) o.L("return t.%s.Get(), true", f.Name(false)) o.L("}") o.L("return %s, false", codegen.ZeroVal(rv)) } else if !IsPointer(f) { if fieldStorageTypeIsIndirect(f.Type()) { o.L("if t.%s != nil {", f.Name(false)) o.L("return *(t.%s), true", f.Name(false)) o.L("}") o.L("return %s, false", codegen.ZeroVal(rv)) } else { o.L("return t.%s, true", f.Name(false)) } } else { o.L("if t.%s != nil {", f.Name(false)) o.L("return t.%s, true", f.Name(false)) o.L("}") o.L("return nil, false") } o.L("}") // func (h *stdHeaders) %s() %s } o.LL("func (t *%s) PrivateClaims() map[string]any {", obj.Name(false)) o.L("t.mu.RLock()") o.L("defer t.mu.RUnlock()") o.L("return t.privateClaims") o.L("}") o.LL("func (t *stdToken) UnmarshalJSON(buf []byte) error {") o.L("t.mu.Lock()") o.L("defer t.mu.Unlock()") for _, f := range fields { o.L("t.%s = nil", f.Name(false)) } o.L("dec := json.NewDecoder(bytes.NewReader(buf))") o.L("LOOP:") o.L("for {") o.L("tok, err := dec.Token()") o.L("if err != nil {") o.L("return fmt.Errorf(`error reading token: %%w`, err)") o.L("}") o.L("switch tok := tok.(type) {") o.L("case json.Delim:") o.L("// Assuming we're doing everything correctly, we should ONLY") o.L("// get either tokens.OpenCurlyBracket or tokens.CloseCurlyBracket here.") o.L("if tok == tokens.CloseCurlyBracket { // End of object") o.L("break LOOP") o.L("} else if tok != tokens.OpenCurlyBracket {") o.L("return fmt.Errorf(`expected '%%c', but got '%%c'`, tokens.OpenCurlyBracket, tok)") o.L("}") o.L("case string: // Objects can only have string keys") o.L("switch tok {") for _, f := range fields { if f.Type() == "string" { o.L("case %sKey:", f.Name(true)) o.L("if err := json.AssignNextStringToken(&t.%s, dec); err != nil {", f.Name(false)) o.L("return fmt.Errorf(`failed to decode value for key %%s: %%w`, %sKey, err)", f.Name(true)) o.L("}") } else if f.Type() == byteSliceType { o.L("case %sKey:", f.Name(true)) o.L("if err := json.AssignNextBytesToken(&t.%s, dec); err != nil {", f.Name(false)) o.L("return fmt.Errorf(`failed to decode value for key %%s: %%w`, %sKey, err)", f.Name(true)) o.L("}") } else if f.Type() == "types.StringList" || strings.HasPrefix(f.Type(), "[]") { o.L("case %sKey:", f.Name(true)) o.L("var decoded %s", f.Type()) o.L("if err := dec.Decode(&decoded); err != nil {") o.L("return fmt.Errorf(`failed to decode value for key %%s: %%w`, %sKey, err)", f.Name(true)) o.L("}") o.L("t.%s = decoded", f.Name(false)) } else { o.L("case %sKey:", f.Name(true)) if IsPointer(f) { o.L("var decoded %s", PointerElem(f)) } else { o.L("var decoded %s", f.Type()) } o.L("if err := dec.Decode(&decoded); err != nil {") o.L("return fmt.Errorf(`failed to decode value for key %%s: %%w`, %sKey, err)", f.Name(true)) o.L("}") o.L("t.%s = &decoded", f.Name(false)) } } o.L("default:") // This looks like bad code, but we're unrolling things for maximum // runtime efficiency o.L("if dc := t.dc; dc != nil {") o.L("if localReg := dc.Registry(); localReg != nil {") o.L("decoded, err := localReg.Decode(dec, tok)") o.L("if err == nil {") o.L("t.setNoLock(tok, decoded)") o.L("continue") o.L("}") o.L("}") o.L("}") o.L("decoded, err := registry.Decode(dec, tok)") o.L("if err == nil {") o.L("t.setNoLock(tok, decoded)") o.L("continue") o.L("}") o.L("return fmt.Errorf(`could not decode field %%s: %%w`, tok, err)") o.L("}") o.L("default:") o.L("return fmt.Errorf(`invalid token %%T`, tok)") o.L("}") o.L("}") o.L("return nil") o.L("}") o.LL("func (t *%s) Keys() []string {", obj.Name(false)) o.L("t.mu.RLock()") o.L("defer t.mu.RUnlock()") o.L("keys := make([]string, 0, %d+len(t.privateClaims))", len(obj.Fields())) for _, f := range obj.Fields() { keyName := f.Name(true) + "Key" o.L("if t.%s != nil {", f.Name(false)) o.L("keys = append(keys, %s)", keyName) o.L("}") } o.L("for k := range t.privateClaims {") o.L("keys = append(keys, k)") o.L("}") o.L("return keys") o.L("}") var numericDateFields []codegen.Field for _, field := range fields { if field.Type() == "types.NumericDate" { numericDateFields = append(numericDateFields, field) } } o.LL("type claimPair struct { Name string; Value any }") o.LL("var claimPairPool = sync.Pool{") o.L("New: func() any {") o.L("return make([]claimPair, 0, %d)", len(fields)) o.L("},") o.L("}") o.LL("func getClaimPairList() []claimPair {") o.L("return claimPairPool.Get().([]claimPair)") o.L("}") o.LL("func putClaimPairList(list []claimPair) {") o.L("list = list[:0]") o.L("claimPairPool.Put(list)") o.L("}") o.LL("// makePairs creates a list of claimPair objects that are sorted by") o.L("// their key names. The key names are always their JSON names, and") o.L("// the values are already JSON encoded.") o.L("// Because makePairs needs to allocate a slice, it _slows_ down ") o.L("// marshaling of the token to JSON. The upside is that it allows us to") o.L("// marshal the token keys in a deterministic order.") o.L("// Do we really need it...? Well, technically we don't, but it's so") o.L("// much nicer to have this to make the example tests actually work") o.L("// deterministically. Also if for whatever reason this becomes a") o.L("// performance issue, we can always/ add a flag to use a more _optimized_ code path.") o.L("//") o.L("// The caller is responsible to call putClaimPairList() to return the") o.L("// allocated slice back to the pool.") o.LL("func (t *%s) makePairs() ([]claimPair, error) {", obj.Name(false)) o.L("pairs := getClaimPairList()") for _, f := range fields { o.L("if t.%s != nil {", f.Name(false)) if f.Name(false) == `audience` { o.L("buf, err := json.MarshalAudience(t.audience, t.options.IsEnabled(%sFlattenAudience))", pkgPrefix) o.L("if err != nil {") o.L("return nil, fmt.Errorf(`failed to encode \"aud\": %%w`, err)") o.L("}") o.L("pairs = append(pairs, claimPair{Name: %sKey, Value: buf})", f.Name(true)) } else if f.Type() == "types.NumericDate" { o.L("buf, err := json.Marshal(t.%s.Unix())", f.Name(false)) o.L("if err != nil {") o.L("return nil, fmt.Errorf(`failed to encode %q: %%w`, err)", f.JSON()) o.L("}") o.L("pairs = append(pairs, claimPair{Name: %sKey, Value: buf})", f.Name(true)) } else if f.Type() == "[]byte" { o.L("buf := base64.EncodeToString(t.%s))", f.Name(false)) o.L("pairs = append(pairs, claimPair{Name: %sKey, Value: buf})", f.Name(true)) } else { o.L("buf, err := json.Marshal(*(t.%s))", f.Name(false)) o.L("if err != nil {") o.L("return nil, fmt.Errorf(`failed to encode field %q: %%w`, err)", f.JSON()) o.L("}") o.L("pairs = append(pairs, claimPair{Name: %sKey, Value: buf})", f.Name(true)) } o.L("}") } o.L("for k, v := range t.privateClaims {") o.L("buf, err := json.Marshal(v)") o.L("if err != nil {") o.L("return nil, fmt.Errorf(`failed to encode field %%q: %%w`, k, err)") o.L("}") o.L("pairs = append(pairs, claimPair{Name: k, Value: buf})") o.L("}") o.LL("sort.Slice(pairs, func(i, j int) bool {") o.L("return pairs[i].Name < pairs[j].Name") o.L("})") o.LL("return pairs, nil") o.L("}") o.LL("func (t %s) MarshalJSON() ([]byte, error) {", obj.Name(false)) o.L("buf := pool.BytesBuffer().Get()") o.L("defer pool.BytesBuffer().Put(buf)") o.L("pairs, err := t.makePairs()") o.L("if err != nil {") o.L("return nil, fmt.Errorf(`failed to make pairs: %%w`, err)") o.L("}") o.L("buf.WriteByte(tokens.OpenCurlyBracket)") o.LL("for i, pair := range pairs {") o.L("if i > 0 {") o.L("buf.WriteByte(tokens.Comma)") o.L("}") o.L(`fmt.Fprintf(buf, "%%q: %%s", pair.Name, pair.Value)`) o.L("}") o.L("buf.WriteByte(tokens.CloseCurlyBracket)") o.L("ret := make([]byte, buf.Len())") o.L("copy(ret, buf.Bytes())") o.L("putClaimPairList(pairs)") o.L("return ret, nil") o.L("}") if err := o.WriteFile(obj.MustString(`filename`), codegen.WithFormatCode(true)); err != nil { if cfe, ok := err.(codegen.CodeFormatError); ok { fmt.Fprint(os.Stderr, cfe.Source()) } return fmt.Errorf(`failed to write to %s: %w`, obj.MustString(`filename`), err) } return nil } func genBuilder(obj *codegen.Object) error { var buf bytes.Buffer pkg := obj.MustString(`package`) o := codegen.NewOutput(&buf) o.L("// Code generated by tools/cmd/genjwt/main.go. DO NOT EDIT.") o.LL("package %s", pkg) o.LL("// Builder is a convenience wrapper around the New() constructor") o.L("// and the Set() methods to assign values to Token claims.") o.L("// Users can successively call Claim() on the Builder, and have it") o.L("// construct the Token when Build() is called. This alleviates the") o.L("// need for the user to check for the return value of every single") o.L("// Set() method call.") o.L("// Note that each call to Claim() overwrites the value set from the") o.L("// previous call.") o.L("type Builder struct {") o.L("mu sync.Mutex") o.L("claims map[string]any") o.L("}") o.LL("func NewBuilder() *Builder {") o.L("return &Builder{}") o.L("}") o.LL("func (b *Builder) init() {") o.L("if b.claims == nil {") o.L("b.claims = make(map[string]any)") o.L("}") o.L("}") o.LL("func (b *Builder) Claim(name string, value any) *Builder {") o.L("b.mu.Lock()") o.L("defer b.mu.Unlock()") o.L("b.init()") o.L("b.claims[name] = value") o.L("return b") o.L("}") for _, f := range obj.Fields() { ftyp := f.Type() if ftyp == "types.NumericDate" { ftyp = "time.Time" } else if ftyp == "types.StringList" { ftyp = "[]string" } o.LL("func (b *Builder) %s(v %s) *Builder {", f.Name(true), ftyp) o.L("return b.Claim(%sKey, v)", f.Name(true)) o.L("}") } o.LL("// Build creates a new token based on the claims that the builder has received") o.L("// so far. If a claim cannot be set, then the method returns a nil Token with") o.L("// a en error as a second return value") o.L("//") o.L("// Once `Build()` is called, all claims are cleared from the Builder, and the") o.L("// Builder can be reused to build another token") o.L("func (b *Builder) Build() (Token, error) {") o.L("b.mu.Lock()") o.L("claims := b.claims") o.L("b.claims = nil") o.L("b.mu.Unlock()") o.L("tok := New()") o.L("for k, v := range claims {") o.L("if err := tok.Set(k, v); err != nil {") o.L("return nil, fmt.Errorf(`failed to set claim %%q: %%w`, k, err)") o.L("}") o.L("}") o.L("return tok, nil") o.L("}") fn := "builder_gen.go" if pkg != "jwt" { fn = filepath.Join(pkg, fn) } if err := o.WriteFile(fn, codegen.WithFormatCode(true)); err != nil { if cfe, ok := err.(codegen.CodeFormatError); ok { fmt.Fprint(os.Stderr, cfe.Source()) } return fmt.Errorf(`failed to write to %s: %w`, fn, err) } return nil } golang-github-lestrrat-go-jwx-3.0.13/tools/cmd/genjwt/objects.yml000066400000000000000000000044051515060566400247640ustar00rootroot00000000000000common_fields: - name: issuer json: iss comment: https://tools.ietf.org/html/rfc7519#section-4.1.1 - name: subject json: sub comment: https://tools.ietf.org/html/rfc7519#section-4.1.2 - name: audience json: aud type: types.StringList getter_return_value: "[]string" hasGet: true hasAccept: true comment: https://tools.ietf.org/html/rfc7519#section-4.1.3 - name: expiration json: exp type: types.NumericDate getter_return_value: time.Time hasAccept: true hasGet: true noDeref: true comment: https://tools.ietf.org/html/rfc7519#section-4.1.4 - name: notBefore getter_return_value: time.Time json: nbf type: types.NumericDate hasAccept: true hasGet: true noDeref: true comment: https://tools.ietf.org/html/rfc7519#section-4.1.5 - name: issuedAt json: iat type: types.NumericDate getter_return_value: time.Time hasGet: true hasAccept: true comment: https://tools.ietf.org/html/rfc7519#section-4.1.6 - name: jwtID getter: JwtID json: jti comment: https://tools.ietf.org/html/rfc7519#section-4.1.7 objects: - name: stdToken filename: token_gen.go interface: Token package: jwt - name: stdToken filename: openid/token_gen.go interface: Token package: openid fields: - name: name - name: givenName json: given_name - name: middleName json: middle_name - name: familyName json: family_name - name: nickname json: nickname - name: preferredUsername json: preferred_username - name: profile - name: picture - name: website - name: email - name: emailVerified type: bool json: email_verified - name: gender - name: birthdate type: "*BirthdateClaim" hasAccept: true - name: zoneinfo - name: locale - name: phoneNumber json: phone_number - name: phoneNumberVerified type: bool json: phone_number_verified - name: address type: "*AddressClaim" hasAccept: true - name: updatedAt getter_return_value: time.Time type: types.NumericDate json: updated_at hasGet: true hasAccept: true golang-github-lestrrat-go-jwx-3.0.13/tools/cmd/genoptions.sh000077500000000000000000000010471515060566400240350ustar00rootroot00000000000000#!/bin/bash # Script to perform code generation. This exists to overcome # the fact that go:generate doesn't really allow you to change directories # This script is expected to be executed from the root directory of jwx set -e echo "👉 Generating options..." DIR=tools/cmd/genoptions pushd "$DIR" > /dev/null GOWORK=off go build -o .genoptions main.go popd > /dev/null EXE="$DIR/.genoptions" for dir in jwa jwe jwk jws jwt; do echo " ⌛ Processing $dir/options.yaml" "$EXE" -objects="$dir/options.yaml" done echo "✔ done!" rm "$EXE" golang-github-lestrrat-go-jwx-3.0.13/tools/cmd/genoptions/000077500000000000000000000000001515060566400234745ustar00rootroot00000000000000golang-github-lestrrat-go-jwx-3.0.13/tools/cmd/genoptions/.gitignore000066400000000000000000000000141515060566400254570ustar00rootroot00000000000000.genoptions golang-github-lestrrat-go-jwx-3.0.13/tools/cmd/genoptions/go.mod000066400000000000000000000013301515060566400245770ustar00rootroot00000000000000module github.com/lestrrat-go/jwx/v3/jwe/tools/cmd/genoptions go 1.24.0 toolchain go1.24.4 require ( github.com/goccy/go-yaml v1.11.2 github.com/lestrrat-go/codegen v1.0.4 github.com/lestrrat-go/xstrings v0.0.0-20220901080742-cacb16b8ddb7 ) require ( github.com/fatih/color v1.16.0 // indirect github.com/lestrrat-go/option v1.0.1 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/stretchr/testify v1.11.1 // indirect golang.org/x/crypto v0.45.0 // indirect golang.org/x/mod v0.14.0 // indirect golang.org/x/sys v0.38.0 // indirect golang.org/x/tools v0.16.1 // indirect golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect ) golang-github-lestrrat-go-jwx-3.0.13/tools/cmd/genoptions/go.sum000066400000000000000000000154261515060566400246370ustar00rootroot00000000000000github.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/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE= github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= github.com/goccy/go-yaml v1.11.2 h1:joq77SxuyIs9zzxEjgyLBugMQ9NEgTWxXfz2wVqwAaQ= github.com/goccy/go-yaml v1.11.2/go.mod h1:wKnAMd44+9JAAnGQpWVEgBzGt3YuTaQ4uXoHvE4m7WU= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/lestrrat-go/codegen v1.0.4 h1:xWRqMkHzfpN/nfl4EeAwmbTvS7uotxfUPl8RhpjB3Go= github.com/lestrrat-go/codegen v1.0.4/go.mod h1:JQPYOh/5hA2lipdHWj3YZHoKEGUfLmGQoWcWs4I92qk= github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU= github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= github.com/lestrrat-go/xstrings v0.0.0-20210804220435-4dd8b234342b/go.mod h1:mPFmD3Wuy0ddyPFvllLq4sUpGfE40T3VE8kWWS8fxGA= github.com/lestrrat-go/xstrings v0.0.0-20220901080742-cacb16b8ddb7 h1:8YrnMMQZquDwIgfQvZZ+JGMrRIn9UdzremIkMGQ/RoU= github.com/lestrrat-go/xstrings v0.0.0-20220901080742-cacb16b8ddb7/go.mod h1:mPFmD3Wuy0ddyPFvllLq4sUpGfE40T3VE8kWWS8fxGA= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/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-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200918232735-d647fc253266/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= golang.org/x/tools v0.16.1 h1:TLyB3WofjdOEepBHAU20JdNC1Zbg87elYofWYAY5oZA= golang.org/x/tools v0.16.1/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU= golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= golang-github-lestrrat-go-jwx-3.0.13/tools/cmd/genoptions/main.go000066400000000000000000000151301515060566400247470ustar00rootroot00000000000000package main import ( "bytes" "flag" "fmt" "os" "regexp" "sort" "strings" "unicode" "github.com/goccy/go-yaml" "github.com/lestrrat-go/codegen" "github.com/lestrrat-go/xstrings" ) var objectsFile = flag.String(`objects`, `objects.yaml`, `specify file containing object definitions`) func main() { flag.Parse() if err := _main(); err != nil { fmt.Fprintf(os.Stderr, "%s\n", err) os.Exit(1) } } var reLooksLikeCodeBlock = regexp.MustCompile(`^\s+`) func writeComment(o *codegen.Output, comment string) bool { comment = strings.TrimSpace(comment) if comment == "" { return false } for i, line := range strings.Split(comment, "\n") { if reLooksLikeCodeBlock.MatchString(line) { o.L("//") var nonSpace int for j, r := range line { if !unicode.IsSpace(r) { nonSpace = j break } o.R("\t") } o.R(line[nonSpace:]) continue } if i == 0 { o.LL(`// %s`, line) } else { o.L(`// %s`, line) } } return true } type Objects struct { Output string PackageName string `yaml:"package_name"` Imports []string `yaml:"imports"` Interfaces []*struct { Name string Comment string ConcreteType string `yaml:"concrete_type"` Methods []string Embeds []string } `yaml:"interfaces"` Options []*struct { Ident string OptionName string `yaml:"option_name"` // usually "With" + $Ident SkipOption bool `yaml:"skip_option"` Interface string ConcreteType string Comment string ArgumentType string `yaml:"argument_type"` ConstantValue string `yaml:"constant_value"` } `yaml:"options"` } func _main() error { var objects Objects { buf, err := os.ReadFile(*objectsFile) if err != nil { return err } if err := yaml.Unmarshal(buf, &objects); err != nil { return err } } for _, iface := range objects.Interfaces { if iface.ConcreteType == "" { iface.ConcreteType = xstrings.LcFirst(iface.Name) } if len(iface.Methods) == 0 { iface.Methods = append(iface.Methods, iface.ConcreteType) } } for _, option := range objects.Options { if option.OptionName == "" { option.OptionName = `With` + option.Ident } if option.ConcreteType == "" { option.ConcreteType = xstrings.LcFirst(option.Interface) } } sort.Slice(objects.Interfaces, func(i, j int) bool { return objects.Interfaces[i].Name < objects.Interfaces[j].Name }) sort.Slice(objects.Options, func(i, j int) bool { return objects.Options[i].Ident < objects.Options[j].Ident }) if err := genOptions(&objects); err != nil { return fmt.Errorf(`failed to generate %q: %w`, objects.Output, err) } if err := genOptionTests(&objects); err != nil { return fmt.Errorf(`failed to generate tests for %q`, objects.Output) } return nil } func genOptions(objects *Objects) error { var buf bytes.Buffer o := codegen.NewOutput(&buf) o.L("// Code generated by tools/cmd/genoptions/main.go. DO NOT EDIT.") o.LL(`package %s`, objects.PackageName) imports := append(objects.Imports, []string{ `io/fs`, // for some reason without this the goimports in my environment tries to import a differnet package `github.com/lestrrat-go/jwx/v3/jwa`, `github.com/lestrrat-go/jwx/v3/jwe`, `github.com/lestrrat-go/jwx/v3/jwk`, `github.com/lestrrat-go/jwx/v3/jws`, `github.com/lestrrat-go/jwx/v3/jwt`, `github.com/lestrrat-go/option/v2`, }...) // Write all imports -- they will be pruned by golang.org/x/tools/imports eventually, // so it's okay to be redundant o.WriteImports(imports...) o.LL(`type Option = option.Interface`) for _, iface := range objects.Interfaces { if writeComment(o, iface.Comment) { o.L(`type %s interface {`, iface.Name) } else { o.LL(`type %s interface {`, iface.Name) } if len(iface.Embeds) < 1 { o.L(`Option`) } else { for _, embed := range iface.Embeds { o.L(embed) } } for _, method := range iface.Methods { o.L(`%s()`, method) } o.L(`}`) o.LL(`type %s struct {`, iface.ConcreteType) o.L(`Option`) o.L(`}`) for _, method := range iface.Methods { o.LL(`func (*%s) %s() {}`, iface.ConcreteType, method) } } o.L(``) { seen := make(map[string]struct{}) for _, option := range objects.Options { _, ok := seen[option.Ident] if !ok { o.L(`type ident%s struct{}`, option.Ident) seen[option.Ident] = struct{}{} } } } { seen := make(map[string]struct{}) for _, option := range objects.Options { _, ok := seen[option.Ident] if ok { continue } // WithCompact is a weird case.... optionName := option.OptionName if option.OptionName == `WithCompact` { optionName = `WithSerialization` } o.LL(`func (ident%s) String() string {`, option.Ident) o.L(`return %q`, optionName) o.L(`}`) seen[option.Ident] = struct{}{} } } for _, option := range objects.Options { if option.SkipOption { continue } if writeComment(o, option.Comment) { o.L(`func %s(`, option.OptionName) } else { o.LL(`func %s(`, option.OptionName) } if argType := option.ArgumentType; argType != "" { o.R(`v %s`, argType) } o.R(`) %s {`, option.Interface) value := `v` if cv := option.ConstantValue; cv != "" { value = cv } o.L(`return &%s{option.New(ident%s{}, %s)}`, option.ConcreteType, option.Ident, value) o.L(`}`) } if err := o.WriteFile(objects.Output, codegen.WithFormatCode(true)); err != nil { if cfe, ok := err.(codegen.CodeFormatError); ok { fmt.Fprint(os.Stderr, cfe.Source()) } return fmt.Errorf(`failed to write to headers_gen.go: %w`, err) } return nil } func genOptionTests(objects *Objects) error { var buf bytes.Buffer o := codegen.NewOutput(&buf) o.L("// Code generated by tools/cmd/genoptions/main.go. DO NOT EDIT.") o.LL(`package %s`, objects.PackageName) imports := append(objects.Imports, []string{ `testing`, `github.com/stretchr/testify/require`, }...) o.WriteImports(imports...) o.LL(`func TestOptionIdent(t *testing.T) {`) seen := make(map[string]struct{}) for _, option := range objects.Options { _, ok := seen[option.Ident] if ok { continue } // WithCompact is a weird case.... optionName := option.OptionName if option.OptionName == `WithCompact` { optionName = `WithSerialization` } o.L(`require.Equal(t, %q, ident%s{}.String())`, optionName, option.Ident) seen[option.Ident] = struct{}{} } o.L(`}`) filename := strings.Replace(objects.Output, `.go`, `_test.go`, -1) if err := o.WriteFile(filename, codegen.WithFormatCode(true)); err != nil { if cfe, ok := err.(codegen.CodeFormatError); ok { fmt.Fprint(os.Stderr, cfe.Source()) } return fmt.Errorf(`failed to write to headers_gen.go: %w`, err) } return nil } golang-github-lestrrat-go-jwx-3.0.13/tools/cmd/genreadfile.sh000077500000000000000000000006111515060566400241110ustar00rootroot00000000000000#!/bin/bash # Script to perform code generation. This exists to overcome # the fact that go:generate doesn't really allow you to change directories set -e echo "👉 Generating ReadFile() for each package..." export GOWORK=off DIR="tools/cmd/genreadfile" pushd "$DIR" > /dev/null go build -o .genreadfile main.go popd > /dev/null EXE="$DIR/.genreadfile" "$EXE" echo "✔ done!" rm "$EXE" golang-github-lestrrat-go-jwx-3.0.13/tools/cmd/genreadfile/000077500000000000000000000000001515060566400235545ustar00rootroot00000000000000golang-github-lestrrat-go-jwx-3.0.13/tools/cmd/genreadfile/.gitignore000066400000000000000000000000151515060566400255400ustar00rootroot00000000000000.genreadfile golang-github-lestrrat-go-jwx-3.0.13/tools/cmd/genreadfile/go.mod000066400000000000000000000006001515060566400246560ustar00rootroot00000000000000module github.com/lestrrat-go/jwx/v3/tools/cmd/genreadfile go 1.20 require github.com/lestrrat-go/codegen v1.0.4 require ( github.com/lestrrat-go/option v1.0.1 // indirect github.com/lestrrat-go/xstrings v0.0.0-20220901080742-cacb16b8ddb7 // indirect github.com/stretchr/testify v1.8.2 // indirect golang.org/x/mod v0.14.0 // indirect golang.org/x/tools v0.16.1 // indirect ) golang-github-lestrrat-go-jwx-3.0.13/tools/cmd/genreadfile/go.sum000066400000000000000000000112521515060566400247100ustar00rootroot00000000000000github.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/lestrrat-go/codegen v1.0.4 h1:xWRqMkHzfpN/nfl4EeAwmbTvS7uotxfUPl8RhpjB3Go= github.com/lestrrat-go/codegen v1.0.4/go.mod h1:JQPYOh/5hA2lipdHWj3YZHoKEGUfLmGQoWcWs4I92qk= github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU= github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= github.com/lestrrat-go/xstrings v0.0.0-20210804220435-4dd8b234342b/go.mod h1:mPFmD3Wuy0ddyPFvllLq4sUpGfE40T3VE8kWWS8fxGA= github.com/lestrrat-go/xstrings v0.0.0-20220901080742-cacb16b8ddb7 h1:8YrnMMQZquDwIgfQvZZ+JGMrRIn9UdzremIkMGQ/RoU= github.com/lestrrat-go/xstrings v0.0.0-20220901080742-cacb16b8ddb7/go.mod h1:mPFmD3Wuy0ddyPFvllLq4sUpGfE40T3VE8kWWS8fxGA= 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/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.0/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/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/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-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200918232735-d647fc253266/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= golang.org/x/tools v0.16.1 h1:TLyB3WofjdOEepBHAU20JdNC1Zbg87elYofWYAY5oZA= golang.org/x/tools v0.16.1/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= golang-github-lestrrat-go-jwx-3.0.13/tools/cmd/genreadfile/main.go000066400000000000000000000044761515060566400250420ustar00rootroot00000000000000package main import ( "bytes" "fmt" "os" "github.com/lestrrat-go/codegen" ) type definition struct { Filename string Package string ReturnType string ParseOptions bool } func main() { if err := _main(); err != nil { fmt.Fprintf(os.Stderr, "%s\n", err) os.Exit(1) } } func _main() error { definitions := []definition{ { Package: "jwk", ReturnType: "Set", Filename: "jwk/io.go", ParseOptions: true, }, { Package: "jws", ReturnType: "*Message", Filename: "jws/io.go", }, { Package: "jwe", ReturnType: "*Message", Filename: "jwe/io.go", }, { Package: "jwt", ReturnType: "Token", Filename: "jwt/io.go", ParseOptions: true, }, } for _, def := range definitions { if err := generateFile(def); err != nil { return err } } return nil } func generateFile(def definition) error { var buf bytes.Buffer o := codegen.NewOutput(&buf) o.LL("// Code generated by tools/cmd/genreadfile/main.go. DO NOT EDIT.") o.LL("package %s", def.Package) o.WriteImports("io/fs", "os", "github.com/lestrrat-go/option/v2") o.LL(`type sysFS struct {}`) o.LL(`func (sysFS) Open(path string) (fs.File, error) {`) o.L(`return os.Open(path)`) o.L(`}`) o.LL("func ReadFile(path string, options ...ReadFileOption) (%s, error) {", def.ReturnType) if def.ParseOptions { o.L("var parseOptions []ParseOption") o.L(`for _, option := range options {`) o.L(`if po, ok := option.(ParseOption); ok {`) o.L(`parseOptions = append(parseOptions, po)`) o.L(`}`) o.L(`}`) } o.LL(`var srcFS fs.FS = sysFS{}`) o.L("for _, option := range options {") o.L(`switch option.Ident() {`) o.L(`case identFS{}:`) o.L(`if err := option.Value(&srcFS); err != nil {`) o.L(`return nil, fmt.Errorf("failed to set fs.FS: %%w", err)`) o.L(`}`) o.L("}") o.L("}") o.LL("f, err := srcFS.Open(path)") o.L("if err != nil {") o.L("return nil, err") o.L("}") o.LL("defer f.Close()") if def.ParseOptions { o.L("return ParseReader(f, parseOptions...)") } else { o.L("return ParseReader(f)") } o.L("}") if err := o.WriteFile(def.Filename, codegen.WithFormatCode(true)); err != nil { if cfe, ok := err.(codegen.CodeFormatError); ok { fmt.Fprint(os.Stderr, cfe.Source()) } return fmt.Errorf(`failed to write to %s: %w`, def.Filename, err) } return nil } golang-github-lestrrat-go-jwx-3.0.13/tools/cmd/gofmt.sh000077500000000000000000000000321515060566400227550ustar00rootroot00000000000000#!/bin/bash go fmt ./... golang-github-lestrrat-go-jwx-3.0.13/tools/cmd/install-jwx.sh000077500000000000000000000005331515060566400241230ustar00rootroot00000000000000#!/bin/bash set -e # find where to install. GOBIN or GOPATH/bin install_dir="$(go env GOBIN)" if [[ -z "$install_dir" ]]; then install_dir=$(go env GOPATH)/bin fi # make sure the directory exists mkdir -p "$install_dir" pushd cmd/jwx > /dev/null go build -o "$install_dir/jwx" . popd > /dev/null echo "Installed jwx in $install_dir/jwx" golang-github-lestrrat-go-jwx-3.0.13/tools/test.sh000077500000000000000000000015161515060566400220650ustar00rootroot00000000000000#!/bin/bash export PATH="$(go env GOPATH)/bin:$PATH" ROOT=$(cd $(dirname $0)/..; pwd -P) DST="$ROOT/coverage.out" if [[ -e "$DST" ]]; then rm "$DST" fi testopts=($TESTOPTS) tmpfile=coverage.out.tmp case "$MODE" in "cover") testopts+=("-coverpkg=./...") testopts+=("-coverprofile=$tmpfile") ;; "short") testopts+=("-short") ;; esac failures=0 echo "mode: atomic" > "$DST" for dir in . ./examples ./bench/performance ./cmd/jwx; do testout=$(mktemp /tmp/jwx-test.XXXXX) pushd "$dir" > /dev/null go test -race ${testopts[@]} ./... if [[ "$?" != "0" ]]; then failures=$((failures+1)) fi if [[ -e "$tmpfile" ]]; then cat "$tmpfile" | tail -n +2 | grep -v "internal/jose" | grep -v "internal/jwxtest" | grep -v "internal/cmd" >> "$DST" rm "$tmpfile" fi popd > /dev/null done if [[ "$failures" != "0" ]]; then exit 1 fi golang-github-lestrrat-go-jwx-3.0.13/transform/000077500000000000000000000000001515060566400214175ustar00rootroot00000000000000golang-github-lestrrat-go-jwx-3.0.13/transform/BUILD.bazel000066400000000000000000000011751515060566400233010ustar00rootroot00000000000000load("@rules_go//go:def.bzl", "go_library", "go_test") go_library( name = "transform", srcs = [ "filter.go", "map.go", ], importpath = "github.com/lestrrat-go/jwx/v3/transform", visibility = ["//visibility:public"], deps = [ "@com_github_lestrrat_go_blackmagic//:blackmagic", ], ) go_test( name = "transform_test", srcs = [ "map_test.go", ], deps = [ ":transform", "//jwt", "@com_github_stretchr_testify//require", ], ) alias( name = "go_default_library", actual = ":transform", visibility = ["//visibility:public"], )golang-github-lestrrat-go-jwx-3.0.13/transform/filter.go000066400000000000000000000063371515060566400232440ustar00rootroot00000000000000package transform import "sync" // FilterLogic is an interface that defines the logic for filtering objects. type FilterLogic interface { Apply(key string, object any) bool } // FilterLogicFunc is a function type that implements the FilterLogic interface. type FilterLogicFunc func(key string, object any) bool func (f FilterLogicFunc) Apply(key string, object any) bool { return f(key, object) } // Filterable is an interface that must be implemented by objects that can be filtered. type Filterable[T any] interface { // Keys returns the names of all fields in the object. Keys() []string // Clone returns a deep copy of the object. Clone() (T, error) // Remove removes a field from the object. Remove(string) error } // Apply is a standalone function that provides type-safe filtering based on // specified filter logic. // // It returns a new object with only the fields that match the result of `logic.Apply`. func Apply[T Filterable[T]](object T, logic FilterLogic) (T, error) { return filterWith(object, logic, true) } // Reject is a standalone function that provides type-safe filtering based on // specified filter logic. // // It returns a new object with only the fields that DO NOT match the result // of `logic.Apply`. func Reject[T Filterable[T]](object T, logic FilterLogic) (T, error) { return filterWith(object, logic, false) } // filterWith is an internal function used by both Apply and Reject functions // to apply the filtering logic to an object. If include is true, only fields // matching the logic are included. If include is false, fields matching // the logic are excluded. func filterWith[T Filterable[T]](object T, logic FilterLogic, include bool) (T, error) { var zero T result, err := object.Clone() if err != nil { return zero, err } for _, k := range result.Keys() { if ok := logic.Apply(k, object); (include && ok) || (!include && !ok) { continue } if err := result.Remove(k); err != nil { return zero, err } } return result, nil } // NameBasedFilter is a filter that filters fields based on their field names. type NameBasedFilter[T Filterable[T]] struct { names map[string]struct{} mu sync.RWMutex logic FilterLogic } // NewNameBasedFilter creates a new NameBasedFilter with the specified field names. // // NameBasedFilter is the underlying implementation of the // various filters in jwe, jwk, jws, and jwt packages. You normally do not // need to use this directly. func NewNameBasedFilter[T Filterable[T]](names ...string) *NameBasedFilter[T] { nameMap := make(map[string]struct{}, len(names)) for _, name := range names { nameMap[name] = struct{}{} } nf := &NameBasedFilter[T]{ names: nameMap, } nf.logic = FilterLogicFunc(nf.filter) return nf } func (nf *NameBasedFilter[T]) filter(k string, _ any) bool { _, ok := nf.names[k] return ok } // Filter returns a new object with only the fields that match the specified names. func (nf *NameBasedFilter[T]) Filter(object T) (T, error) { nf.mu.RLock() defer nf.mu.RUnlock() return Apply(object, nf.logic) } // Reject returns a new object with only the fields that DO NOT match the specified names. func (nf *NameBasedFilter[T]) Reject(object T) (T, error) { nf.mu.RLock() defer nf.mu.RUnlock() return Reject(object, nf.logic) } golang-github-lestrrat-go-jwx-3.0.13/transform/map.go000066400000000000000000000025031515060566400225230ustar00rootroot00000000000000package transform import ( "errors" "fmt" "github.com/lestrrat-go/blackmagic" ) // Mappable is an interface that defines methods required when converting // a jwx structure into a map[string]any. // // EXPERIMENTAL: This API is experimental and its interface and behavior is // subject to change in future releases. This API is not subject to semver // compatibility guarantees. type Mappable interface { Get(key string, dst any) error Keys() []string } // AsMap takes the specified Mappable object and populates the map // `dst` with the key-value pairs from the Mappable object. // Many objects in jwe, jwk, jws, and jwt packages including // `jwt.Token`, `jwk.Key`, `jws.Header`, etc. // // EXPERIMENTAL: This API is experimental and its interface and behavior is // subject to change in future releases. This API is not subject to semver // compatibility guarantees. func AsMap(m Mappable, dst map[string]any) error { if dst == nil { return fmt.Errorf("transform.AsMap: destination map cannot be nil") } for _, k := range m.Keys() { var val any if err := m.Get(k, &val); err != nil { // Allow invalid value errors. Assume they are just nil values. if !errors.Is(err, blackmagic.InvalidValueError()) { return fmt.Errorf(`transform.AsMap: failed to get key %q: %w`, k, err) } } dst[k] = val } return nil } golang-github-lestrrat-go-jwx-3.0.13/transform/map_test.go000066400000000000000000000140441515060566400235650ustar00rootroot00000000000000package transform_test import ( "testing" "time" "github.com/lestrrat-go/jwx/v3/jwt" "github.com/lestrrat-go/jwx/v3/transform" "github.com/stretchr/testify/require" ) func TestAsMapWithJWTToken(t *testing.T) { t.Run("Basic JWT Token with standard claims", func(t *testing.T) { // Create a JWT token with standard claims token := jwt.New() expectedTime := time.Unix(1234567890, 0).UTC() require.NoError(t, token.Set(jwt.IssuerKey, "https://example.com")) require.NoError(t, token.Set(jwt.SubjectKey, "user123")) require.NoError(t, token.Set(jwt.AudienceKey, []string{"api", "web"})) require.NoError(t, token.Set(jwt.IssuedAtKey, expectedTime)) require.NoError(t, token.Set(jwt.ExpirationKey, expectedTime.Add(time.Hour))) require.NoError(t, token.Set(jwt.NotBeforeKey, expectedTime)) require.NoError(t, token.Set(jwt.JwtIDKey, "jwt-id-123")) dst := make(map[string]any) err := transform.AsMap(token, dst) require.NoError(t, err, "AsMap should succeed") // Verify all standard claims are present require.Equal(t, "https://example.com", dst[jwt.IssuerKey]) require.Equal(t, "user123", dst[jwt.SubjectKey]) require.Equal(t, []string{"api", "web"}, dst[jwt.AudienceKey]) require.Equal(t, expectedTime, dst[jwt.IssuedAtKey]) require.Equal(t, expectedTime.Add(time.Hour), dst[jwt.ExpirationKey]) require.Equal(t, expectedTime, dst[jwt.NotBeforeKey]) require.Equal(t, "jwt-id-123", dst[jwt.JwtIDKey]) }) t.Run("JWT Token with private claims", func(t *testing.T) { token := jwt.New() // Set standard claims require.NoError(t, token.Set(jwt.IssuerKey, "test-issuer")) require.NoError(t, token.Set(jwt.SubjectKey, "test-subject")) // Set private claims require.NoError(t, token.Set("custom_claim", "custom_value")) require.NoError(t, token.Set("user_role", "admin")) require.NoError(t, token.Set("permissions", []string{"read", "write", "delete"})) require.NoError(t, token.Set("metadata", map[string]any{ "version": "1.0", "source": "test", })) dst := make(map[string]any) err := transform.AsMap(token, dst) require.NoError(t, err, "AsMap should succeed") // Verify standard claims require.Equal(t, "test-issuer", dst[jwt.IssuerKey]) require.Equal(t, "test-subject", dst[jwt.SubjectKey]) // Verify private claims require.Equal(t, "custom_value", dst["custom_claim"]) require.Equal(t, "admin", dst["user_role"]) require.Equal(t, []string{"read", "write", "delete"}, dst["permissions"]) metadata, ok := dst["metadata"].(map[string]any) require.True(t, ok, "metadata should be a map") require.Equal(t, "1.0", metadata["version"]) require.Equal(t, "test", metadata["source"]) }) t.Run("Empty JWT Token", func(t *testing.T) { token := jwt.New() dst := make(map[string]any) err := transform.AsMap(token, dst) require.NoError(t, err, "AsMap should succeed for empty token") // Should have no keys since no claims were set require.Len(t, dst, 0, "Empty token should result in empty map") }) t.Run("JWT Token with single claim", func(t *testing.T) { token := jwt.New() require.NoError(t, token.Set(jwt.IssuerKey, "single-issuer")) dst := make(map[string]any) err := transform.AsMap(token, dst) require.NoError(t, err, "AsMap should succeed") require.Len(t, dst, 1, "Should have exactly one key") require.Equal(t, "single-issuer", dst[jwt.IssuerKey]) }) t.Run("JWT Token with various data types", func(t *testing.T) { token := jwt.New() // Test different data types require.NoError(t, token.Set("string_claim", "string value")) require.NoError(t, token.Set("int_claim", 42)) require.NoError(t, token.Set("bool_claim", true)) require.NoError(t, token.Set("float_claim", 3.14)) require.NoError(t, token.Set("array_claim", []any{"a", "b", "c"})) require.NoError(t, token.Set("null_claim", nil)) dst := make(map[string]any) err := transform.AsMap(token, dst) require.NoError(t, err, "AsMap should succeed") require.Equal(t, "string value", dst["string_claim"]) require.Equal(t, 42, dst["int_claim"]) require.Equal(t, true, dst["bool_claim"]) require.Equal(t, 3.14, dst["float_claim"]) require.Equal(t, []any{"a", "b", "c"}, dst["array_claim"]) require.Nil(t, dst["null_claim"]) }) t.Run("Nil destination map should return error", func(t *testing.T) { token := jwt.New() require.NoError(t, token.Set(jwt.IssuerKey, "test")) err := transform.AsMap(token, nil) require.Error(t, err, "AsMap should fail with nil destination") require.Contains(t, err.Error(), "destination map cannot be nil") }) t.Run("Pre-populated destination map should be extended", func(t *testing.T) { token := jwt.New() require.NoError(t, token.Set(jwt.IssuerKey, "token-issuer")) require.NoError(t, token.Set("custom_claim", "token-value")) // Pre-populate destination map dst := map[string]any{ "existing_key": "existing_value", "another_key": 123, } err := transform.AsMap(token, dst) require.NoError(t, err, "AsMap should succeed") // Original keys should still be present require.Equal(t, "existing_value", dst["existing_key"]) require.Equal(t, 123, dst["another_key"]) // New keys from token should be added require.Equal(t, "token-issuer", dst[jwt.IssuerKey]) require.Equal(t, "token-value", dst["custom_claim"]) require.Len(t, dst, 4, "Should have 4 total keys") }) t.Run("Overlapping keys should be overwritten", func(t *testing.T) { token := jwt.New() require.NoError(t, token.Set(jwt.IssuerKey, "token-issuer")) require.NoError(t, token.Set("shared_key", "token-value")) // Pre-populate with overlapping keys dst := map[string]any{ jwt.IssuerKey: "original-issuer", "shared_key": "original-value", "unique_key": "unique-value", } err := transform.AsMap(token, dst) require.NoError(t, err, "AsMap should succeed") // Overlapping keys should be overwritten with token values require.Equal(t, "token-issuer", dst[jwt.IssuerKey]) require.Equal(t, "token-value", dst["shared_key"]) // Unique key should remain unchanged require.Equal(t, "unique-value", dst["unique_key"]) require.Len(t, dst, 3, "Should have 3 total keys") }) }