pax_global_header00006660000000000000000000000064147671164720014531gustar00rootroot0000000000000052 comment=9b2386141d7fd0df06e08658b61782a6097e0e09 golang-github-lestrrat-go-jwx-2.1.4/000077500000000000000000000000001476711647200173335ustar00rootroot00000000000000golang-github-lestrrat-go-jwx-2.1.4/.aspect/000077500000000000000000000000001476711647200206705ustar00rootroot00000000000000golang-github-lestrrat-go-jwx-2.1.4/.aspect/bazelrc/000077500000000000000000000000001476711647200223125ustar00rootroot00000000000000golang-github-lestrrat-go-jwx-2.1.4/.aspect/bazelrc/BUILD.bazel000066400000000000000000000002311476711647200241640ustar00rootroot00000000000000load("@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-2.1.4/.aspect/bazelrc/bazel5.bazelrc000066400000000000000000000004021476711647200250340ustar00rootroot00000000000000# 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-2.1.4/.aspect/bazelrc/bazel6.bazelrc000066400000000000000000000017271476711647200250500ustar00rootroot00000000000000# 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 golang-github-lestrrat-go-jwx-2.1.4/.aspect/bazelrc/ci.bazelrc000066400000000000000000000071431476711647200242560ustar00rootroot00000000000000# We recommend enforcing a policy that keeps your CI from being slowed down # by individual test targets that should be optimized # or split up into multiple test targets with sharding or manually. # Set this flag to exclude targets that have their timeout set to eternal (>15m) from running on CI. # Docs: https://bazel.build/docs/user-manual#test-timeout-filters build --test_timeout_filters=-eternal # 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 build --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 5 seconds on CI. # https://bazel.build/reference/command-line-reference#flag--show_progress_rate_limit build --show_progress_rate_limit=5 # 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-2.1.4/.aspect/bazelrc/convenience.bazelrc000066400000000000000000000031151476711647200261520ustar00rootroot00000000000000# Attempt to build & test every target whose prerequisites were successfully built. # Docs: https://bazel.build/docs/user-manual#keep-going build --keep_going test --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 build --heap_dump_on_oom golang-github-lestrrat-go-jwx-2.1.4/.aspect/bazelrc/correctness.bazelrc000066400000000000000000000100161476711647200262060ustar00rootroot00000000000000# 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 test --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/aspect-build/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 # 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 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 golang-github-lestrrat-go-jwx-2.1.4/.aspect/bazelrc/debug.bazelrc000066400000000000000000000013771476711647200247540ustar00rootroot00000000000000############################################################ # 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-2.1.4/.aspect/bazelrc/javascript.bazelrc000066400000000000000000000031511476711647200260240ustar00rootroot00000000000000# 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 # Enable runfiles on all platforms. Runfiles are on by default on Linux and MacOS but off on # Windows. # # In general, rules_js and derivate rule sets assume that runfiles are enabled and do not support no # runfiles case because it does not scale to teach all Node.js tools to use the runfiles manifest. # # If you are developing on Windows, you must either run bazel with administrator privileges or # enable developer mode. If you do not you may hit this error on Windows: # # Bazel needs to create symlinks to build the runfiles tree. # Creating symlinks on Windows requires one of the following: # 1. Bazel is run with administrator privileges. # 2. The system version is Windows 10 Creators Update (1703) or later # and developer mode is enabled. # # Docs: https://bazel.build/reference/command-line-reference#flag--enable_runfiles build --enable_runfiles golang-github-lestrrat-go-jwx-2.1.4/.aspect/bazelrc/performance.bazelrc000066400000000000000000000072041476711647200261620ustar00rootroot00000000000000# Merkle tree calculations will be memoized to improve the remote cache hit checking speed. The # memory foot print of the cache is controlled by `--experimental_remote_merkle_tree_cache_size`. # Docs: https://bazel.build/reference/command-line-reference#flag--experimental_remote_merkle_tree_cache build --experimental_remote_merkle_tree_cache query --experimental_remote_merkle_tree_cache # The number of Merkle trees to memoize to improve the remote cache hit checking speed. Even though # the cache is automatically pruned according to Java's handling of soft references, out-of-memory # errors can occur if set too high. If set to 0 the cache size is unlimited. Optimal value varies # depending on project's size. # Docs: https://bazel.build/reference/command-line-reference#flag--experimental_remote_merkle_tree_cache_size build --experimental_remote_merkle_tree_cache_size=1000 query --experimental_remote_merkle_tree_cache_size=1000 # 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 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 build --incompatible_remote_results_ignore_disk # 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 # 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 run --nolegacy_external_runfiles test --nolegacy_external_runfiles # Some actions are always IO-intensive but require little compute. It's wasteful to put the output # in the remote cache, it just saturates the network and fills the cache storage causing earlier # evictions. It's also not worth sending them for remote execution. # For actions like PackageTar it's usually faster to just re-run the work locally every time. # You'll have to look at an execution log to figure out what other action mnemonics you care about. # In some cases you may need to patch rulesets to add a mnemonic to actions that don't have one. # https://bazel.build/reference/command-line-reference#flag--modify_execution_info build --modify_execution_info=PackageTar=+no-remote golang-github-lestrrat-go-jwx-2.1.4/.bazelignore000066400000000000000000000000311476711647200216270ustar00rootroot00000000000000cmd bench examples tools golang-github-lestrrat-go-jwx-2.1.4/.bazelrc000066400000000000000000000000621476711647200207540ustar00rootroot00000000000000import %workspace%/.aspect/bazelrc/bazel6.bazelrc golang-github-lestrrat-go-jwx-2.1.4/.bazelversion000066400000000000000000000000061476711647200220330ustar00rootroot000000000000006.4.0 golang-github-lestrrat-go-jwx-2.1.4/.github/000077500000000000000000000000001476711647200206735ustar00rootroot00000000000000golang-github-lestrrat-go-jwx-2.1.4/.github/CONTRIBUTING.md000066400000000000000000000156421476711647200231340ustar00rootroot00000000000000# 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-2.1.4/.github/FUNDING.yml000066400000000000000000000012061476711647200225070ustar00rootroot00000000000000# 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-2.1.4/.github/ISSUE_TEMPLATE/000077500000000000000000000000001476711647200230565ustar00rootroot00000000000000golang-github-lestrrat-go-jwx-2.1.4/.github/ISSUE_TEMPLATE/bug_report.md000066400000000000000000000016661476711647200255610ustar00rootroot00000000000000--- 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-2.1.4/.github/ISSUE_TEMPLATE/config.yml000066400000000000000000000000341476711647200250430ustar00rootroot00000000000000blank_issues_enabled: false golang-github-lestrrat-go-jwx-2.1.4/.github/ISSUE_TEMPLATE/feature_request.md000066400000000000000000000022211476711647200266000ustar00rootroot00000000000000--- 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-2.1.4/.github/ISSUE_TEMPLATE/others.md000066400000000000000000000004461476711647200247100ustar00rootroot00000000000000--- 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-2.1.4/.github/auto-assign-pr.yml000066400000000000000000000000771476711647200242730ustar00rootroot00000000000000addReviewers: true addAssignees: false reviewers: - lestrrat golang-github-lestrrat-go-jwx-2.1.4/.github/dependabot.yml000066400000000000000000000017171476711647200235310ustar00rootroot00000000000000version: 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: "gomod" directory: "/" schedule: interval: "daily" target-branch: "develop/v1" 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" - package-ecosystem: "github-actions" directory: "/" schedule: interval: "daily" target-branch: "develop/v1" golang-github-lestrrat-go-jwx-2.1.4/.github/workflows/000077500000000000000000000000001476711647200227305ustar00rootroot00000000000000golang-github-lestrrat-go-jwx-2.1.4/.github/workflows/assign-issue.yml000066400000000000000000000004271476711647200260700ustar00rootroot00000000000000name: 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-2.1.4/.github/workflows/assign-pr.yml000066400000000000000000000004561476711647200253630ustar00rootroot00000000000000name: '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-2.1.4/.github/workflows/autodoc.yml000066400000000000000000000010401476711647200251040ustar00rootroot00000000000000name: Auto-Doc on: pull_request: branches: - develop/v2 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@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Process markdown files run: | find . -name '*.md' | xargs perl tools/autodoc.pl env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} golang-github-lestrrat-go-jwx-2.1.4/.github/workflows/benchmark.yml000066400000000000000000000021441476711647200254060ustar00rootroot00000000000000name: Benchmark on: schedule: - cron: '0 5 * * 1' workflow_dispatch: {} jobs: build: runs-on: ubuntu-latest strategy: matrix: go: [ '1.21', '1.20' ] name: "Test [ Go ${{ matrix.go }} / JSON Backend ${{ matrix.json_backend }} ]" steps: - name: Checkout repository uses: actions/checkout@v4 - name: Cache Go modules uses: actions/cache@v4 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@v5 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-2.1.4/.github/workflows/ci.yml000066400000000000000000000031441476711647200240500ustar00rootroot00000000000000name: 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.24', '1.23', '1.22' ] name: "Test [ Go ${{ matrix.go }} / Tags ${{ matrix.go_tags }} ]" steps: - name: Checkout repository uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Cache Go modules uses: actions/cache@0c907a75c2c80ebcb7f088228285e798b750cf8f # v4.2.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@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2 with: go-version: ${{ matrix.go }} check-latest: true - name: Install stringer run: go install golang.org/x/tools/cmd/stringer@latest - name: Install tparse run: go install github.com/mfridman/tparse@v0.12.2 - name: Install jose run: sudo apt-get install -y --no-install-recommends jose - run: make generate - name: make tidy run: make tidy - name: Test with coverage run: make cover-${{ matrix.go_tags }} - uses: bazelbuild/setup-bazelisk@v3 - run: bazel run //:gazelle-update-repos - name: Check difference between generation code and commit code run: make check_diffs golang-github-lestrrat-go-jwx-2.1.4/.github/workflows/codeql.yml000066400000000000000000000057101476711647200247250ustar00rootroot00000000000000# 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/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@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL uses: github/codeql-action/init@v3 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@v3 # â„šī¸ 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@v3 with: category: "/language:${{matrix.language}}" golang-github-lestrrat-go-jwx-2.1.4/.github/workflows/dependabot.yml000066400000000000000000000017631476711647200255670ustar00rootroot00000000000000name: merge dependabot on: pull_request: types: [labeled] jobs: merge: if: ${{github.event.label.name == 'dependabot'}} runs-on: ubuntu-latest steps: - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Install tparse run: go install github.com/mfridman/tparse@v0.12.2 - run: | make tidy - run: | make test - run: | bazel run //:gazelle-update-repos - 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-2.1.4/.github/workflows/lint.yml000066400000000000000000000007161476711647200244250ustar00rootroot00000000000000name: lint on: [push] jobs: golangci: name: lint runs-on: ubuntu-latest steps: - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2 with: go-version-file: "go.mod" - uses: golangci/golangci-lint-action@2226d7cb06a077cd73e56eedd38eecad18e5d837 # v6.5.0 - name: Run go vet run: | go vet ./... golang-github-lestrrat-go-jwx-2.1.4/.github/workflows/smoke.yml000066400000000000000000000037041476711647200245750ustar00rootroot00000000000000# 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.24', '1.23', '1.22' ] name: "Smoke [ Go ${{ matrix.go }} / Tags ${{ matrix.go_tags }} ]" steps: - name: Checkout repository uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Check documentation generator run: | find . -name '*.md' | xargs env AUTODOC_DRYRUN=1 perl tools/autodoc.pl - name: Cache Go modules uses: actions/cache@0c907a75c2c80ebcb7f088228285e798b750cf8f # v4.2.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@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2 with: go-version: ${{ matrix.go }} check-latest: true - name: Install stringer run: go install golang.org/x/tools/cmd/stringer@latest - name: Install tparse run: go install github.com/mfridman/tparse@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-2.1.4/.github/workflows/stale.yml000066400000000000000000000025461476711647200245720ustar00rootroot00000000000000name: 'Close stale issues and PRs' on: schedule: - cron: '30 1 * * *' jobs: stale: runs-on: ubuntu-latest steps: - uses: actions/stale@5bef64f19d7facfb25b37b414482c7164d639639 # v9.1.0 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-2.1.4/.gitignore000066400000000000000000000006241476711647200213250ustar00rootroot00000000000000# 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-2.1.4/.golangci.yml000066400000000000000000000040341476711647200217200ustar00rootroot00000000000000linters-settings: govet: enable-all: true disable: - shadow - fieldalignment linters: enable-all: true disable: - cyclop - depguard - dupl - exhaustive - errorlint - err113 - funlen - gci - gochecknoglobals - gochecknoinits - gocognit - gocritic - gocyclo - godot - godox - gofumpt - gomnd - gosec - govet - inamedparam # oh, sod off - ireturn # No, I _LIKE_ returning interfaces - lll - maintidx # Do this in code review - makezero - mnd - nakedret - nestif - nlreturn - nonamedreturns # visit this back later - paralleltest - perfsprint - recvcheck - tagliatelle - testifylint # TODO: revisit when we have the chance - testpackage - thelper # Tests are fine - varnamelen # Short names are ok - wrapcheck - wsl issues: exclude-rules: # not needed - path: /*.go text: "ST1003: should not use underscores in package names" linters: - stylecheck - path: /*.go text: "don't use an underscore in package name" linters: - revive - linters: - staticcheck text: 'SA1019' - path: /*.go linters: - contextcheck - exhaustruct - path: /main.go linters: - errcheck - path: internal/codegen/codegen.go linters: - errcheck - path: internal/jwxtest/jwxtest.go linters: - errcheck - errchkjson - forcetypeassert - path: /*_test.go linters: - errcheck - errchkjson - forcetypeassert - path: /*_example_test.go linters: - forbidigo - path: cmd/jwx/jwx.go linters: - forbidigo - path: /*_test.go text: "var-naming: " linters: - revive # Maximum issues count per one linter. Set to 0 to disable. Default is 50. max-issues-per-linter: 0 # Maximum count of issues with the same text. Set to 0 to disable. Default is 3. max-same-issues: 0 golang-github-lestrrat-go-jwx-2.1.4/BUILD000066400000000000000000000024111476711647200201130ustar00rootroot00000000000000load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") load("@bazel_gazelle//:def.bzl", "gazelle") # gazelle:prefix github.com/lestrrat-go/jwx/v2 # gazelle:go_naming_convention import_alias gazelle(name = "gazelle") gazelle( name = "gazelle-update-repos", args = [ "-from_file=go.mod", "-to_macro=deps.bzl%go_dependencies", "-prune", "-build_file_proto_mode=disable_global", ], command = "update-repos", ) go_library( name = "jwx", srcs = [ "format.go", "formatkind_string_gen.go", "jwx.go", "options.go", ], importpath = "github.com/lestrrat-go/jwx/v2", visibility = ["//visibility:public"], deps = [ "//internal/json", "@com_github_lestrrat_go_option//:option", ], ) go_test( name = "jwx_test", srcs = ["jwx_test.go"], deps = [ ":jwx", "//internal/ecutil", "//internal/jose", "//internal/json", "//internal/jwxtest", "//jwa", "//jwe", "//jwk", "//jws", "@com_github_stretchr_testify//assert", "@com_github_stretchr_testify//require", ], ) alias( name = "go_default_library", actual = ":jwx", visibility = ["//visibility:public"], ) golang-github-lestrrat-go-jwx-2.1.4/Changes000066400000000000000000000633531476711647200206400ustar00rootroot00000000000000Changes ======= v2 has many incompatibilities with v1. To see the full list of differences between v1 and v2, please read the Changes-v2.md file (https://github.com/lestrrat-go/jwx/blob/develop/v2/Changes-v2.md) v2.1.4 Feb 25 2025 * Update code to work with go1.24 * Update tests to work with recent latchset/jose * Fix build pipeline to work with latest golangci-lint v2.1.3 24 Nov 2024 * [jwe] Test has been fixed to work under 32-bit systems. v2.1.2 25 Oct 2024 * [jwt] `jwt.ParseRequest` now uses %w to embed errors returned from `jwt.ParseHeader`, `jwt.ParseCookie`, and `jwt.ParseForm`, allowing users to correctly call `errors.Is(err, jwt.ErrTokenExpired)` and the like. Previously the error returned from `jwt.ParseRequest` showed in human readable format what the problem was, but it was not programmatically possible to determine the error type using `errors.Is` (#1175) v2.1.1 Jul 28 2024 * Update minimum required go version to go 1.20 * Update tests to work on 32-bit systems. * [jwa] Add RSA_OAEP_384 and RSA_OAEP_512 * [jwa] `jwa.SignatureAlgorithm` now has a `IsSymmetric` method. * [jwa] Add `jwa.RegisterSignatureAlgorithmOptions()` to register new algorithms while specifying extra options. Currently only `jwa.WithSymmetricAlgorithm()` is supported. * [jws] Clearly mark `jws.WithHeaders()` as deprecated v2.1.0 18 Jun 2024 [New Features] * [jwt] Added `jwt.ParseCookie()` function * [jwt] `jwt.ParseRequest()` can now accept a new option, jwt.WithCookieKey() to specify a cookie name to extract the token from. * [jwt] `jwt.ParseRequest()` and `jwt.ParseCookie()` can accept the `jwt.WithCookie()` option, which will, upon successful token parsing, make the functions assign the *http.Cookie used to parse the token. This allows users to further inspect the cookie where the token came from, should the need arise. * [jwt] (BREAKING CHANGE) `jwt.ParseRequest()` no longer automatically looks for "Authorization" header when only `jwt.WithFormKey()` is used. This behavior is the same for `jwt.WithCookieKey()` and any similar options that may be implemented in the future. # previously jwt.ParseRequest(req) // looks under Authorization jwt.ParseRequest(req, jwt.WithFormKey("foo")) // looks under foo AND Authorization jwt.ParseRequest(req, jwt.WithHeaderKey("Authorization"), jwt.WithFormKey("foo")) // looks under foo AND Authorization # since this release jwt.ParseRequest(req) // same as before jwt.ParseRequest(req, jwt.WithFormKey("foo")) // looks under foo jwt.ParseRequest(req, jwt.WithHeaderKey("Authorization"), jwt.WithFormKey("foo")) // looks under foo AND Authorization * [jwt] Add `jwt.WithResetValidators()` option to `jwt.Validate()`. This option will allow you to tell `jwt.Validate()` to NOT automatically check the default validators (`iat`, `exp`, and `nbf`), so that you can completely customize the validation with the validators you specify using `jwt.WithValidator()`. This sort of behavior is useful for special cases such as https://openid.net/specs/openid-connect-rpinitiated-1_0.html. However, you SHOULD NOT use this option unless you know exactly what you are doing, as this will pose significant security issues when used incorrectly. * [jwk] Provide a _stop-gap_ measure to work with PEM format ASN.1 DER encoded secp256k1 keys. In order to enable this feature, you must compile jwx with TWO build tags: `jwx_es256k` to enable ES256K/secp256k1, and `jwx_secp256k1_pem` to enable PEM handling. Not one, but BOTH tags need to be present. With this change, by supplying the `WithPEM(true)` option, `jwk.Parse()` is now able to read sep256k1 keys. Also, `jwk.Pem()` should be able to handle `jwk.Key` objects that represent a secp256k1 key. Please do note that the implementation of this feature is dodgy at best. Currently Go's crypto/x509 does not allow handling additional EC curves, and thus in order to accommodate secp256k1 keys in PEM/ASN.1 DER format we need to "patch" the stdlib. We do this by copy-and-pasting relevant parts of go 1.22.2's crypto/x509 code and adding the minimum required code to make secp256k1 keys work. Because of the above, there are several important caveats for this feature: 1. This feature is provided solely as a stop-gap measure until such time Go's stdlib provides a way to handle non-standard EC curves, or another external module is able to solve this issue. 2. This feature should be considered unstable and not guaranteed by semantic versioning backward compatibility. At any given point we may drop or modify this feature. It may be because we can no longer maintain the code, or perhaps a security issue is found in the version of the code that we ship with, etc. 3. Please always remember that we are now bundling a static set of code for handling x509 formats. You are taking a possible security risk by code that could be outdated. Please always do your own research, and if possible, please notify us if the bundled code needs to be updated. Unless you know what you are doing, it is not recommended that you enable this feature. 4. Please note that because we imported the code from go 1.22's src/crypto/x509, it has some go1.20-isms in its code. Therefore you will not be able to use the `jwx_secp256k1_pem` tag to enable secp256k1 key PEM handling against codebases that are built using go 1.19 and below (the build will succeed, but the feature will be unavailable). 5. We have no plans to include more curves this way. One is already one too many. * [jwe] Fixed a bug when using encryption algorithms involving PBES2 along with the jwx.WithUseNumber() global option. Enabling this option would turn all values stored in the JSON content to be of type `json.Number`, but we did not account for it when checking for the value of `p2c` header, resulting in a conversion error. v2.0.21 07 Mar 2024 [Security] * [jwe] Added `jwe.Settings(jwe.WithMaxDecompressBufferSize(int64))` to specify the maximum size of a decompressed JWE payload. The default value is 10MB. If you are compressing payloads greater than this and want to decompress it during a call to `jwe.Decrypt`, you need to explicitly set a value large enough to hold that data. The same option can be passed to `jwe.Decrypt` to control this behavior on a per-message basis. * [jwe] Added documentation stating that `jwe.WithMaxBufferSize` option will be renamed in future versions, i.e. v3 v2.0.20 20 Feb 2024 [New Features] * [jwe] Added `jwe.Settings(WithMaxBufferSize(int64))` to set the maximum size of internal buffers. The default value is 256MB. Most users do not need to change this value. * [jws] Allow `jws.WithCompact()` and `jws.WithJSON()` to be passed to `jws.Parse()` and `jws.Verify()`. These options control the expected serialization format for the JWS message. * [jwt] Add `jwt.WithCompactOnly()` to specify that only compact serialization can be used for `jwt.Parse()`. Previously, by virtue of `jws.Parse()` allowing either JSON or Compact serialization format, `jwt.Parse()` also allowed JSON serialization where as RFC7519 explicitly states that only compact serialization should be used. For backward compatibility the default behavior is not changed, but you can set this global option for jwt: `jwt.Settings(jwt.WithCompactOnly(true))` [Miscellaneous] * Internal key conversions should now allow private keys to be used in place of public keys. This would allow you to pass private keys where public keys are expected. v2.0.19 09 Jan 2024 [New Features] * [jws] Added jws.IsVerificationError to check if the error returned by `jws.Verify` was caused by actual verification step or something else, for example, while fetching a key from datasource [Security Fixes] * [jws] JWS messages formated in full JSON format (i.e. not the compact format, which consists of three base64 strings concatenated with a '.') with missing "protected" headers could cause a panic, thereby introducing a possibility of a DoS. This has been fixed so that the `jws.Parse` function succeeds in parsing a JWS message lacking a protected header. Calling `jws.Verify` on this same JWS message will result in a failed verification attempt. Note that this behavior will differ slightly when parsing JWS messages in compact form, which result in an error. v2.0.18 03 Dec 2023 [Security Fixes] * [jwe] A large number in p2c parameter for PBKDF2 based encryptions could cause a DoS attack, similar to https://nvd.nist.gov/vuln/detail/CVE-2022-36083. All users who use JWE via this package should upgrade. While the JOSE spec allows for encryption using JWE on JWTs, users of the `jwt` package are not immediately susceptible unless they explicitly try to decrypt JWTs -- by default the `jwt` package verifies signatures, but does not decrypt messages. [GHSA-7f9x-gw85-8grf] v2.0.17 20 Nov 2023 [Bug Fixes] * [jws] Previously, `jws.UnregisterSigner` did not remove the previous signer instance when the signer was registered and unregistered multiple times (#1016). This has been fixed. [New Features] * [jwe] (EXPERIMENTAL) `jwe.WithCEK` has been added to extract the content encryption key (CEK) from the Decrypt operation. * [jwe] (EXPERIMENTAL) `jwe.EncryptStatic` has been added to encrypt content using a static CEK. Using static CEKs has serious security implications, and you should not use this unless you completely understand the risks involved. v2.0.16 31 Oct 2023 [Security] * [jws] ECDSA signature verification requires us to check if the signature is of the desired length of bytes, but this check that used to exist before had been removed in #65, resulting in certain malformed signatures to pass verification. One of the ways this could happen if R is a 31 byte integer and S is 32 byte integer, both containing the correct signature values, but R is not zero-padded. Correct = R: [ 0 , ... ] (32 bytes) S: [ ... ] (32 bytes) Wrong = R: [ ... ] (31 bytes) S: [ ... ] (32 bytes) In order for this check to pass, you would still need to have all 63 bytes populated with the correct signature. The only modification a bad actor may be able to do is to add one more byte at the end, in which case the first 32 bytes (including what would have been S's first byte) is used for R, and S would contain the rest. But this will only result in the verification to fail. Therefore this in itself should not pose any security risk, albeit allowing some illegally formated messages to be verified. * [jwk] `jwk.Key` objects now have a `Validate()` method to validate the data stored in the keys. However, this still does not necessarily mean that the key's are valid for use in cryptographic operations. If `Validate()` is successful, it only means that the keys are in the right _format_, including the presence of required fields and that certain fields have proper length, etc. [New Features] * [jws] Added `jws.WithValidateKey()` to force calling `key.Validate()` before signing or verification. * [jws] `jws.Sign()` now returns a special type of error that can hold the individual errors from the signers. The stringification is still the same as before to preserve backwards compatibility. * [jwk] Added `jwk.IsKeyValidationError` that checks if an error is an error from `key.Validate()`. [Bug Fixes] * [jwt] `jwt.ParseInsecure()` was running verification if you provided a key via `jwt.WithKey()` or `jwt.WithKeySet()` (#1007) v2.0.15 19 20 Oct 2023 [Bug fixes] * [jws] jws.Sign() now properly check for valid algorithm / key type pair when the key implements crypto.Signer. This was caused by the fact that when jws.WithKey() accepted keys that implemented crypto.Signer, there really is no way to robustly check what algorithm the crypto.Signer implements. The code has now been modified to check for KNOWN key types, i.e. those that are defined in Go standard library, and those that are defined in this library. For example, now calling jws.Sign() with jws.WithKey(jwa.RS256, ecdsaKey) where ecdsaKey is either an instance of *ecdsa.PrivateKey or jwk.ECDSAPrivateKey will produce an error. However, if you use a separate library that wraps some KMS library which implements crypto.Signer, this same check will not be performed due to the fact that it is an unknown library to us. And there's no way to query a crypto.Signer for its algorithm family. v2.0.14 17 Oct 2023 [New Features] * [jwk] jwk.IsPrivateKey(), as well as jwk.AsymmetricKey has been added. The function can be used to tell if a jwk.Key is a private key of an asymmetric key pair. [Security] * golang.org/x/crypto has been updated to 0.14.0. The update contains a fix for HTTP/2 rapid reset DoS vulnerability, which some security scanning software may flag. However, do note that this library is NOT affected by the issue, as it does not have the capability to serve as an HTTP/2 server. This is included in this release document so that users will be able to tell why this library may be flagged when/if their scanning software do so. v2.0.13 26 Sep 2023 [New Features] * [jwk] jwk.Equal has been added. Please note that this is equivalent to comparing the keys' thumbprints, therefore it does NOT take in consideration non-essential fields. [Miscellaneous] * Various documentation fixes and additions. v2.0.12 - 11 Aug 2023 [Bug fixes] * [jwt] jwt.Serializer was ignoring JWE flags (#951) [Miscellaneous] * [jwk] Check for seed length on OKP JWKs to avoid panics (#947) * [jws] Documentation for jws.WithKeySet() v2.0.11 - 14 Jun 2023 [Security] * Potential Padding Oracle Attack Vulnerability and Timing Attack Vulnerability for JWE AES-CBC encrypted payloads affecting all v2 releases up to v2.0.10, all v1 releases up to v1.2.25, and all v0 releases up to v0.9.2 have been reported by @shogo82148. Please note that v0 versions will NOT receive fixes. This release fixes these vulnerabilities for the v2 series. v2.0.10 - 12 Jun 2023 [New Features] * [jwe] (EXPERIMENTAL) Added `jwe.KeyEncrypter` and `jwe.KeyDecrypter` interfaces that works in similar ways as how `crypto.Signer` works for signature generation and verification. It can act as the interface for your encryption/decryption keys that are for example stored in an hardware device. This feature is labeled experimental because the API for the above interfaces have not been battle tested, and may need to changed yet. Please be aware that until the API is deemed stable, you may have to adapt your code to these possible changes, _even_ during minor version upgrades of this library. [Bug fixes] * Registering JWS signers/verifiers did not work since v2.0.0, because the way we handle algorithm names changed in 2aa98ce6884187180a7145b73da78c859dd46c84. (We previously thought that this would be checked by the example code, but it apparently failed to flag us properly) The logic behind managing the internal database has been fixed, and `jws.RegisterSigner` and `jws.RegisterVerifier` now properly hooks into the new `jwa.RegisterSignatureAlgorithm` to automatically register new algorithm names (#910, #911) [Miscellaneous] * Added limited support for github.com/segmentio/asm/base64. Compile your code with the `jwx_asmbase64` build tag. This feature is EXPERIMENTAL. Through limited testing, the use of a faster base64 library provide 1~5% increase in throughput on average. It might make more difference if the input/output is large. If you care about this performance improvement, you should probably enable `goccy` JSON parser as well, by specifying `jwx_goccy,jwx_asmbase64` in your build call. * Slightly changed the way global variables underneath `jwk.Fetch` are initialized and configured. `jwk.Fetch` creates an object that spawns workers to fetch JWKS when it's first called. You can now also use `jwk.SetGlobalFetcher()` to set a fetcher object which you can control. v2.0.9 - 21 Mar 2023 [Security Fixes] * Updated use of golang.org/x/crypto to v0.7.0 [Bug fixes] * Emitted PEM file for EC private key types used the wrong PEM armor (#875) [Miscellaneous] * Banners for generated files have been modified to allow tools to pick them up (#867) * Remove unused variables around ReadFileOption (#866) * Fix test failures * Support bazel out of the box * Now you can create JWS messages using `{"alg":"none"}`, by calling `jws.Sign()` with `jws.WithInsecureNoSignature()` option. (#888, #890). Note that there is no way to call `jws.Verify()` while allowing `{"alg":"none"}` as you wouldn't be _verifying_ the message if we allowed the "none" algorithm. `jws.Parse()` will parse such messages without verification. `jwt` also allows you to sign using alg="none", but there's no symmetrical way to verify such messages. v2.0.8 - 25 Nov 2022 [Security Fixes] * [jws][jwe] Starting from go 1.19, code related to elliptic algorithms panics (instead of returning an error) when certain methods such as `ScalarMult` are called using points that are not on the elliptic curve being used. Using inputs that cause this condition, and you accept unverified JWK from the outside it may be possible for a third-party to cause panics in your program. This has been fixed by verifying that the point being used is actually on the curve before such computations (#840) [Miscellaneous] * `jwx.GuessFormat` now returns `jwx.InvalidFormat` when the heuristics is sure that the buffer format is invalid. v2.0.7 - 15 Nov 2022 [New features] * [jwt] Each `jwt.Token` now has an `Options()` method * [jwt] `jwt.Settings(jwt.WithFlattenedAudience(true))` has a slightly different semantic than before. Instead of changing a global variable, it now specifies that the default value of each per-token option for `jwt.FlattenAudience` is true. Therefore, this is what happens: // No global settings tok := jwt.New() tok.Options.IsEnabled(jwt.FlattenAudience) // false // With global settings jwt.Settings(jwt.WithFlattenedAudience(true)) tok := jwt.New() tok.Options.IsEnabled(jwt.FlattenAudience) // true // But you can still turn FlattenAudience off for this // token alone tok.Options.Disable(jwt.FlattenAudience) Note that while unlikely to happen for users relying on the old behavior, this change DOES introduce timing issues: whereas old versions switched the JSON marshaling for ALL tokens immediately after calling `jwt.Settings`, the new behavior does NOT affect tokens that have been created before the call to `jwt.Settings` (but marshaled afterwards). So the following may happen: // < v2.0.7 tok := jwt.New() jwt.Settings(jwt.WithFlattenedAudience(true)) json.Marshal(tok) // flatten = on // >= v2.0.7 tok := jwt.New() // flatten = off jwt.Settings(jwt.WithFlattenedAudience(true)) json.Marshal(tok) // flatten is still off It is recommended that you only set the global setting once at the very beginning of your program to avoid problems. Also note that `Clone()` copies the settings as well. [Miscellaneous] * WithCompact's stringification should have been that of the internal identity struct ("WithSerialization"), but it was wrongly producing "WithCompact". This has been fixed. * Go Workspaces have been enabled within this module. - When developing, modules will refer to the main jwx module that they are part of. This allows us to explicitly specify the dependency version in, for example, ./cmd/jwx/go.mod but still develop against the local version. - If you are using `goimports` and other tools, you might want to upgrade binaries -- for example, when using vim-go's auto-format-on-save feature, my old binaries took well over 5~10 seconds to compute the import paths. This was fixed when I switched to using go1.19, and upgraded the binaries used by vim-go v2.0.6 - 25 Aug 2022 [Bug fixes][Security] * [jwe] Agreement Party UInfo and VInfo (apv/apu) were not properly being passed to the functions to compute the aad when encrypting using ECDH-ES family of algorithms. Therefore, when using apu/apv, messages encrypted via this module would have failed to be properly decrypted. Please note that bogus encrypted messages would not have succeed being decrypted (i.e. this problem does not allow spoofed messages to be decrypted). Therefore this would not have caused unwanted data to to creep in -- however it did pose problems for data to be sent and decrypted from this module when using ECDH-ES with apu/apv. While not extensively tested, we believe this regression was introduced with the v2 release. v2.0.5 - 11 Aug 2022 [Bug fixes] * [jwt] Remove stray debug log * [jwk] Fix x5u field name, caused by a typo * [misc] Update golangci-lint action to v3; v2 was causing weird problems v2.0.4 - 19 Jul 2022 [Bug Fixes] * [jwk] github.com/lestrrat-go/httprc, which jwk.Cache depends on, had a problem with inserting URLs to be refetched into its queue. As a result it could have been the case that some JWKS were not updated properly. Please upgrade if you use jwk.Cache. * [jwk] cert.Get could fail with an out of bounds index look up * [jwk] Fix doc buglet in `KeyType()` method [New Features] * [jws] Add `jws.WithMultipleKeysPerKeyID()` suboption to allow non-unique key IDs in a given JWK set. By default we assume that a key ID is unique within a key set, but enabling this option allows you to handle JWK sets that contain multiple keys that contain the same key ID. * [jwt] Before v2.0.1, sub-second accuracy for time based fields (i.e. `iat`, `exp`, `nbf`) were not respected. Because of this the code to evaluate this code had always truncated any sub-second portion of these fields, and therefore no sub-second comparisons worked. A new option for validation `jwt.WithTruncation()` has been added to workaround this. This option controls the value used to truncate the time fields. When set to 0, sub-second comparison would be possible. FIY, truncation will still happen because we do not want to use the monotonic clocks when making comparisons. It's just that truncating using `0` as its argument effectively only strips out the monotonic clock v2.0.3 - 13 Jun 2022 [Bug Fixes] * [jwk] Update dependency on github.com/lestrrat-go/httprc to v1.0.2 to avoid unintended blocking in the update goroutine for jwk.Cache v2.0.2 - 23 May 2022 [Bug Fixes][Security] * [jwe] An old bug from at least 7 years ago existed in handling AES-CBC unpadding, where the unpad operation might remove more bytes than necessary (#744) This affects all jwx code that is available before v2.0.2 and v1.2.25. [New Features] * [jwt] RFC3339 timestamps are also accepted for Numeric Date types in JWT tokens. This allows users to parse servers that erroneously use RFC3339 timestamps in some pre-defined fields. You can change this behavior by setting `jwt.WithNumericDateParsePedantic` to `false` * [jwt] `jwt.WithNumericDateParsePedantic` has been added. This is a global option that is set using `jwt.Settings` v2.0.1 - 06 May 2022 * [jwk] `jwk.Set` had erroneously been documented as not returning an error when the same key already exists in the set. This is a behavior change since v2, and it was missing in the docs (#730) * [jwt] `jwt.ErrMissingRequiredClaim` has been deprecated. Please use `jwt.ErrRequiredClaim` instead. * [jwt] `jwt.WithNumericDateParsePrecision` and `jwt.WithNumericDateFormatPrecision` have been added to parse and format fractional seconds. These options can be passed to `jwt.Settings`. The default precision is set to 0, and fractional portions are not parsed nor formatted. The precision may be set up to 9. * `golang.org/x/crypto` has been upgraded (#724) * `io/ioutil` has been removed from the source code. v2.0.0 - 24 Apr 2022 * This i the first v2 release, which represents a set of design changes that were learnt over the previous 2 years. As a result the v2 API should be much more consistent and uniform across packages, and should be much more flexible to accomodate real-world needs. For a complete list of changes, please see the Changes-v2.md file, or check the diff at https://github.com/lestrrat-go/jwx/compare/v1...v2 [Miscellaneous] * Minor house cleaning on code generation tools [jwt] * `jwt.ErrMissingRequiredClaim()` has been added v2.0.0-beta2 - 16 Apr 2022 [jwk] * Updated `jwk.Set` API and reflected pending changes from v1 which were left over. Please see Changes-v2.md file for details. * Added `jwk.CachedSet`, a shim over `jwk.Cache` that allows you to have to write wrappers around `jwk.Cache` that retrieves a particular `jwk.Set` out of it. You can use it to, for example, pass `jwk.CachedSet` to a `jws.Verify` cache := jwk.NewCache(ctx) cache.Register(ctx, jwksURL) cachedSet := jwk.NewCachedSet(cache, jwksURL) jws.Verify(signed, jws.WithKeySet(cachedSet)) v2.0.0-beta1 - 09 Apr 2022 [Miscellaneous] * Renamed Changes.v2 to Changes-v2.md * Housecleaning for lint action. * While v2 was not affected, ported over equivalent test for #681 to catch regressions in the future. * Please note that there is no stability guarantees on pre-releases. v2.0.0-alpha1 - 04 Apr 2022 * Initial pre-release of v2 line. Please note that there is no stability guarantees on pre-releases. golang-github-lestrrat-go-jwx-2.1.4/Changes-v2.md000066400000000000000000000350401476711647200215540ustar00rootroot00000000000000# 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-2.1.4/LICENSE000066400000000000000000000020641476711647200203420ustar00rootroot00000000000000The 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-2.1.4/Makefile000066400000000000000000000036131476711647200207760ustar00rootroot00000000000000.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-2.1.4/README.md000066400000000000000000000252341476711647200206200ustar00rootroot00000000000000# github.com/lestrrat-go/jwx/v2 ![](https://github.com/lestrrat-go/jwx/workflows/CI/badge.svg?branch=develop/v2) [![Go Reference](https://pkg.go.dev/badge/github.com/lestrrat-go/jwx/v2.svg)](https://pkg.go.dev/github.com/lestrrat-go/jwx/v2) [![codecov.io](https://codecov.io/github/lestrrat-go/jwx/coverage.svg?branch=v2)](https://codecov.io/github/lestrrat-go/jwx?branch=v2) 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 v2 (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/v2/jwa" "github.com/lestrrat-go/jwx/v2/jwe" "github.com/lestrrat-go/jwx/v2/jwk" "github.com/lestrrat-go/jwx/v2/jws" "github.com/lestrrat-go/jwx/v2/jwt" ) func Example_jwx_readme() { // 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/v2/examples/jwx_readme_example_test.go) # How-to Documentation * [API documentation](https://pkg.go.dev/github.com/lestrrat-go/jwx/v2) * [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/v2/jwt) | [RFC 7519](https://tools.ietf.org/html/rfc7519) | | [jwk](https://github.com/lestrrat-go/jwx/tree/v2/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/v2/jwa) | [RFC 7518](https://tools.ietf.org/html/rfc7518) | | [jws](https://github.com/lestrrat-go/jwx/tree/v2/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/v2/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/v2`. 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/v2/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/v2/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. golang-github-lestrrat-go-jwx-2.1.4/WORKSPACE000066400000000000000000000031501476711647200206130ustar00rootroot00000000000000workspace(name = "com_github_lestrrat_go_jwx_v2") load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") http_archive( name = "io_bazel_rules_go", sha256 = "dd926a88a564a9246713a9c00b35315f54cbd46b31a26d5d8fb264c07045f05d", urls = [ "https://mirror.bazel.build/github.com/bazelbuild/rules_go/releases/download/v0.38.1/rules_go-v0.38.1.zip", "https://github.com/bazelbuild/rules_go/releases/download/v0.38.1/rules_go-v0.38.1.zip", ], ) http_archive( name = "bazel_gazelle", sha256 = "ecba0f04f96b4960a5b250c8e8eeec42281035970aa8852dda73098274d14a1d", urls = [ "https://mirror.bazel.build/github.com/bazelbuild/bazel-gazelle/releases/download/v0.29.0/bazel-gazelle-v0.29.0.tar.gz", "https://github.com/bazelbuild/bazel-gazelle/releases/download/v0.29.0/bazel-gazelle-v0.29.0.tar.gz", ], ) load("@io_bazel_rules_go//go:deps.bzl", "go_register_toolchains", "go_rules_dependencies") load("@bazel_gazelle//:deps.bzl", "gazelle_dependencies") load("//:deps.bzl", "go_dependencies") # gazelle:repository_macro deps.bzl%go_dependencies go_dependencies() go_rules_dependencies() go_register_toolchains(version = "1.20.14") gazelle_dependencies() # aspect-build related stuff http_archive( name = "aspect_bazel_lib", sha256 = "b4cd1114874ab15f794134eefbc254eb89d3e1de640bf4a11f2f402e886ad29e", strip_prefix = "bazel-lib-1.27.2", url = "https://github.com/aspect-build/bazel-lib/releases/download/v1.27.2/bazel-lib-v1.27.2.tar.gz", ) load("@aspect_bazel_lib//lib:repositories.bzl", "aspect_bazel_lib_dependencies") aspect_bazel_lib_dependencies() golang-github-lestrrat-go-jwx-2.1.4/bench/000077500000000000000000000000001476711647200204125ustar00rootroot00000000000000golang-github-lestrrat-go-jwx-2.1.4/bench/README.md000066400000000000000000000003311476711647200216660ustar00rootroot00000000000000# Benchmarks # Performance Benchmarks [Measures the performance of github.com/lestrrat-go/jwx/v2](./performance) # Comparison Benchmarks [Compares github.com/lestrrat-go/jwx/v2 with other libraries](./comparison) golang-github-lestrrat-go-jwx-2.1.4/bench/comparison/000077500000000000000000000000001476711647200225645ustar00rootroot00000000000000golang-github-lestrrat-go-jwx-2.1.4/bench/comparison/Makefile000066400000000000000000000003121476711647200242200ustar00rootroot00000000000000stdlib: go test -bench . -benchmem | tee stdlib.txt goccy: go test -bench . -benchmem -tags jwx_goccy | tee goccy.txt asmbase64: go test -bench . -benchmem -tags jwx_asmbase64 | tee asmbase64.txt golang-github-lestrrat-go-jwx-2.1.4/bench/comparison/README.md000066400000000000000000000010601476711647200240400ustar00rootroot00000000000000# Comparison Benchmarks ## Parsing signed JWT * github.com/lestrrat-go/jwx/v2 * github.com/golang-jwt/jwt ``` go test -bench . -benchmem -tags jwx_goccy | tee goccy.txt goos: linux goarch: amd64 pkg: github.com/lestrrat-go/jwx/v2/bench/comparison cpu: AMD Ryzen 9 3900X 12-Core Processor BenchmarkJWT/github.com/lestrrat-go/jwx/v2-24 100 10606620 ns/op 6094200 B/op 39411 allocs/op BenchmarkJWT/github.com/golang-jwt/jwt-24 100 10577532 ns/op 6080878 B/op 39366 allocs/op ``` golang-github-lestrrat-go-jwx-2.1.4/bench/comparison/go.mod000066400000000000000000000001571476711647200236750ustar00rootroot00000000000000module github.com/lestrrat-go/jwx/v2/bench/comparison go 1.15 replace github.com/lestrrat-go/jwx/v2 => ../.. golang-github-lestrrat-go-jwx-2.1.4/bench/comparison/go.sum000066400000000000000000000000001476711647200237050ustar00rootroot00000000000000golang-github-lestrrat-go-jwx-2.1.4/bench/performance/000077500000000000000000000000001476711647200227135ustar00rootroot00000000000000golang-github-lestrrat-go-jwx-2.1.4/bench/performance/Makefile000066400000000000000000000006631476711647200243600ustar00rootroot00000000000000stdlib: 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-2.1.4/bench/performance/README.md000066400000000000000000000357051476711647200242040ustar00rootroot00000000000000# 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-2.1.4/bench/performance/go.mod000066400000000000000000000001571476711647200240240ustar00rootroot00000000000000module github.com/lestrrat-go/jwx/v2/bench/performance go 1.16 require github.com/lestrrat-go/jwx/v2 v2.0.21 golang-github-lestrrat-go-jwx-2.1.4/bench/performance/go.sum000066400000000000000000000170551476711647200240560ustar00rootroot00000000000000github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/lestrrat-go/blackmagic v1.0.2 h1:Cg2gVSc9h7sz9NOByczrbUvLopQmXrfFx//N+AkAr5k= github.com/lestrrat-go/blackmagic v1.0.2/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU= github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE= github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E= github.com/lestrrat-go/httprc v1.0.5 h1:bsTfiH8xaKOJPrg1R+E3iE/AWZr/x0Phj9PBTG/OLUk= github.com/lestrrat-go/httprc v1.0.5/go.mod h1:mwwz3JMTPBjHUkkDv/IGJ39aALInZLrhBp0X7KGUZlo= github.com/lestrrat-go/iter v1.0.2 h1:gMXo1q4c2pHmC3dn8LzRhJfP1ceCbgSiT9lUydIzltI= github.com/lestrrat-go/iter v1.0.2/go.mod h1:Momfcq3AnRlRjI5b5O8/G5/BvpzrhoFTZcn06fEOPt4= github.com/lestrrat-go/jwx/v2 v2.0.21 h1:jAPKupy4uHgrHFEdjVjNkUgoBKtVDgrQPB/h55FHrR0= github.com/lestrrat-go/jwx/v2 v2.0.21/go.mod h1:09mLW8zto6bWL9GbwnqAli+ArLf+5M33QLQPDggkUWM= 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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys= github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 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/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 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-2.1.4/bench/performance/jwe_benchmark_test.go000066400000000000000000000022241476711647200271000ustar00rootroot00000000000000package bench_test import ( "encoding/json" "testing" "github.com/lestrrat-go/jwx/v2/jwe" ) func BenchmarkJWE(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 interface{} b.Run("Serialization", func(b *testing.B) { b.Run("JSON", 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-2.1.4/bench/performance/jwk_benchmark_test.go000066400000000000000000000035171476711647200271140ustar00rootroot00000000000000package bench_test import ( "bytes" "encoding/json" "testing" "github.com/lestrrat-go/jwx/v2/internal/jwxtest" "github.com/lestrrat-go/jwx/v2/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-2.1.4/bench/performance/jws_benchmark_test.go000066400000000000000000000057021476711647200271220ustar00rootroot00000000000000package bench_test import ( "bytes" "encoding/json" "testing" "github.com/lestrrat-go/jwx/v2/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-2.1.4/bench/performance/jwt_benchmark_test.go000066400000000000000000000101461476711647200271210ustar00rootroot00000000000000package bench_test import ( "bytes" "encoding/json" "testing" "time" "github.com/lestrrat-go/jwx/v2/internal/jwxtest" "github.com/lestrrat-go/jwx/v2/jwa" "github.com/lestrrat-go/jwx/v2/jwk" "github.com/lestrrat-go/jwx/v2/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 interface{} 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-2.1.4/bench/performance/jwx_benchmark_test.go000066400000000000000000000013531476711647200271250ustar00rootroot00000000000000package bench_test import ( "testing" "github.com/lestrrat-go/jwx/v2/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-2.1.4/cert/000077500000000000000000000000001476711647200202705ustar00rootroot00000000000000golang-github-lestrrat-go-jwx-2.1.4/cert/BUILD.bazel000066400000000000000000000012301476711647200221420ustar00rootroot00000000000000load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") go_library( name = "cert", srcs = [ "cert.go", "chain.go", ], importpath = "github.com/lestrrat-go/jwx/v2/cert", visibility = ["//visibility:public"], deps = ["//internal/base64"], ) go_test( name = "cert_test", srcs = [ "cert_test.go", "chain_test.go", ], deps = [ ":cert", "//internal/jwxtest", "@com_github_stretchr_testify//assert", "@com_github_stretchr_testify//require", ], ) alias( name = "go_default_library", actual = ":cert", visibility = ["//visibility:public"], ) golang-github-lestrrat-go-jwx-2.1.4/cert/cert.go000066400000000000000000000030631476711647200215560ustar00rootroot00000000000000package cert import ( "crypto/x509" stdlibb64 "encoding/base64" "fmt" "io" "github.com/lestrrat-go/jwx/v2/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 interface{}) ([]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-2.1.4/cert/cert_test.go000066400000000000000000000065461476711647200226260ustar00rootroot00000000000000package cert_test import ( "crypto/rand" "crypto/x509" "crypto/x509/pkix" "encoding/asn1" "math/big" "net" "net/url" "testing" "time" "github.com/lestrrat-go/jwx/v2/cert" "github.com/lestrrat-go/jwx/v2/internal/jwxtest" "github.com/stretchr/testify/assert" ) 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() if !assert.NoError(t, err, `jwxtest.GenerateRsaKey`) { return } 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) if !assert.NoError(t, err, `cert.Certificate should succeed`) { return } _, err = cert.Parse(b64) if !assert.NoError(t, err, `cert.Parse should succeed`) { return } } golang-github-lestrrat-go-jwx-2.1.4/cert/chain.go000066400000000000000000000035121476711647200217020ustar00rootroot00000000000000package cert import ( "bytes" "encoding/json" "fmt" ) // 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('[') for i, cert := range cc.certificates { if i > 0 { buf.WriteByte(',') } buf.WriteByte('"') buf.Write(cert) buf.WriteByte('"') } buf.WriteByte(']') 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-2.1.4/cert/chain_test.go000066400000000000000000000043641476711647200227470ustar00rootroot00000000000000package cert_test import ( "testing" "github.com/lestrrat-go/jwx/v2/cert" "github.com/stretchr/testify/assert" "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) if !assert.NoError(t, err, `x509.ParseCertificate should succeed`) { return } 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 { tc := tc t.Run(tc.Name, func(t *testing.T) { if !assert.NoError(t, chain.Add(tc.Data), `chain.Add should succeed`) { return } }) } if !assert.Equal(t, len(testcases), chain.Len(), `certificates in chain should match`) { return } for i := 0; i < chain.Len(); i++ { der, ok := chain.Get(i) if !assert.True(t, ok, `chain.Get(%d) should succeed`, i) { return } c, err := cert.Parse(der) if !assert.NoError(t, err, `cert.Parse should match`) { return } if !assert.True(t, c.Equal(goldenCert), `certificates should match`) { return } } 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-2.1.4/cmd/000077500000000000000000000000001476711647200200765ustar00rootroot00000000000000golang-github-lestrrat-go-jwx-2.1.4/cmd/jwx/000077500000000000000000000000001476711647200207065ustar00rootroot00000000000000golang-github-lestrrat-go-jwx-2.1.4/cmd/jwx/README.md000066400000000000000000000330241476711647200221670ustar00rootroot00000000000000# 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-2.1.4/cmd/jwx/go.mod000066400000000000000000000014471476711647200220220ustar00rootroot00000000000000module github.com/lestrrat-go/jwx/v2/cmd/jwx go 1.17 require ( github.com/lestrrat-go/jwx/v2 v2.0.21 github.com/urfave/cli/v2 v2.26.0 golang.org/x/crypto v0.21.0 ) require ( github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect github.com/goccy/go-json v0.10.2 // indirect github.com/lestrrat-go/blackmagic v1.0.2 // indirect github.com/lestrrat-go/httpcc v1.0.1 // indirect github.com/lestrrat-go/httprc v1.0.5 // indirect github.com/lestrrat-go/iter v1.0.2 // indirect github.com/lestrrat-go/option v1.0.1 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/segmentio/asm v1.2.0 // indirect github.com/xrash/smetrics v0.0.0-20231213231151-1d8dd44e695e // indirect golang.org/x/sys v0.18.0 // indirect ) golang-github-lestrrat-go-jwx-2.1.4/cmd/jwx/go.sum000066400000000000000000000207601476711647200220460ustar00rootroot00000000000000github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.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/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/lestrrat-go/blackmagic v1.0.2 h1:Cg2gVSc9h7sz9NOByczrbUvLopQmXrfFx//N+AkAr5k= github.com/lestrrat-go/blackmagic v1.0.2/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU= github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE= github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E= github.com/lestrrat-go/httprc v1.0.5 h1:bsTfiH8xaKOJPrg1R+E3iE/AWZr/x0Phj9PBTG/OLUk= github.com/lestrrat-go/httprc v1.0.5/go.mod h1:mwwz3JMTPBjHUkkDv/IGJ39aALInZLrhBp0X7KGUZlo= github.com/lestrrat-go/iter v1.0.2 h1:gMXo1q4c2pHmC3dn8LzRhJfP1ceCbgSiT9lUydIzltI= github.com/lestrrat-go/iter v1.0.2/go.mod h1:Momfcq3AnRlRjI5b5O8/G5/BvpzrhoFTZcn06fEOPt4= github.com/lestrrat-go/jwx/v2 v2.0.21 h1:jAPKupy4uHgrHFEdjVjNkUgoBKtVDgrQPB/h55FHrR0= github.com/lestrrat-go/jwx/v2 v2.0.21/go.mod h1:09mLW8zto6bWL9GbwnqAli+ArLf+5M33QLQPDggkUWM= 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/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/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/urfave/cli/v2 v2.26.0 h1:3f3AMg3HpThFNT4I++TKOejZO8yU55t3JnnSr4S4QEI= github.com/urfave/cli/v2 v2.26.0/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= 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= 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.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 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/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 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/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-2.1.4/cmd/jwx/jwa.go000066400000000000000000000030751476711647200220230ustar00rootroot00000000000000package main import ( "fmt" "os" "github.com/lestrrat-go/jwx/v2/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-2.1.4/cmd/jwx/jwe.go000066400000000000000000000111621476711647200220230ustar00rootroot00000000000000package main import ( "context" "fmt" "io" "github.com/lestrrat-go/jwx/v2/jwa" "github.com/lestrrat-go/jwx/v2/jwe" "github.com/lestrrat-go/jwx/v2/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 if err := keyenc.Accept(c.String("key-encryption")); err != nil { return fmt.Errorf(`invalid key encryption algorithm: %w`, err) } var cntenc jwa.ContentEncryptionAlgorithm if err := cntenc.Accept(c.String("content-encryption")); err != nil { return fmt.Errorf(`invalid content encryption algorithm: %w`, err) } 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 if err := keyenc.Accept(c.String("key-encryption")); err != nil { return fmt.Errorf(`invalid key encryption algorithm: %w`, err) } // 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 { sink.Key(r.Headers().Algorithm(), 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-2.1.4/cmd/jwx/jwk.go000066400000000000000000000155201476711647200220330ustar00rootroot00000000000000package main import ( "bytes" "crypto/ecdsa" "crypto/elliptic" "crypto/rand" "crypto/rsa" "encoding/json" "fmt" "io" "github.com/lestrrat-go/jwx/v2/jwa" "github.com/lestrrat-go/jwx/v2/jwk" "github.com/lestrrat-go/jwx/v2/x25519" "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 jwk.AvailableCurves() { if i > 0 { crvnames.WriteByte('/') } crvnames.WriteString(crv.Params().Name) } 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 interface{} switch typ := jwa.KeyType(c.String("type")); 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 crv elliptic.Curve var crvalg jwa.EllipticCurveAlgorithm if err := crvalg.Accept(c.String("curve")); err != nil { return fmt.Errorf(`invalid elliptic curve name %s: %w`, c.String("curve"), err) } crv, ok := jwk.CurveForAlgorithm(crvalg) if !ok { return fmt.Errorf(`invalid elliptic curve for ECDSA: %s (expected %s)`, crvalg, crvnames.String()) } 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 if err := crvalg.Accept(c.String("curve")); err != nil { return fmt.Errorf(`invalid elliptic curve name: %w`, err) } 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 := 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]interface{} 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.FromRaw(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-2.1.4/cmd/jwx/jws.go000066400000000000000000000160061476711647200220430ustar00rootroot00000000000000package main import ( "context" "encoding/json" "fmt" "io" "os" "github.com/lestrrat-go/jwx/v2/jwa" "github.com/lestrrat-go/jwx/v2/jwk" "github.com/lestrrat-go/jwx/v2/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) if err != nil { return fmt.Errorf(`failed to read message: %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) if err != nil { return fmt.Errorf(`failed to verify message: %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 givenalg := c.String("alg") if givenalg == "" { return fmt.Errorf(`option --alg must be given`) } if err := alg.Accept(givenalg); err != nil { return fmt.Errorf(`invalid alg %s`, givenalg) } ctx := context.Background() for iter := keyset.Keys(ctx); iter.Next(ctx); { pair := iter.Pair() key := pair.Value.(jwk.Key) 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) if err != nil { return fmt.Errorf(`failed to sign message: %w`, err) } } var alg jwa.SignatureAlgorithm givenalg := c.String("alg") if givenalg == "" { return fmt.Errorf(`option --alg must be given`) } if err := alg.Accept(givenalg); err != nil { return fmt.Errorf(`invalid alg %s`, givenalg) } // 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-2.1.4/cmd/jwx/jwx.go000066400000000000000000000050531476711647200220500ustar00rootroot00000000000000package main import ( "encoding/json" "fmt" "io" "os" "sort" "strings" "github.com/lestrrat-go/jwx/v2/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 interface{}) 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-2.1.4/codecov.yml000066400000000000000000000000501476711647200214730ustar00rootroot00000000000000codecov: allow_coverage_offsets: true golang-github-lestrrat-go-jwx-2.1.4/deps.bzl000066400000000000000000000123521476711647200210020ustar00rootroot00000000000000load("@bazel_gazelle//:deps.bzl", "go_repository") def go_dependencies(): go_repository( name = "com_github_davecgh_go_spew", build_file_proto_mode = "disable_global", importpath = "github.com/davecgh/go-spew", sum = "h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=", version = "v1.1.1", ) go_repository( name = "com_github_decred_dcrd_crypto_blake256", build_file_proto_mode = "disable_global", importpath = "github.com/decred/dcrd/crypto/blake256", sum = "h1:zPMNGQCm0g4QTY27fOCorQW7EryeQ/U0x++OzVrdms8=", version = "v1.1.0", ) go_repository( name = "com_github_decred_dcrd_dcrec_secp256k1_v4", build_file_proto_mode = "disable_global", importpath = "github.com/decred/dcrd/dcrec/secp256k1/v4", sum = "h1:NMZiJj8QnKe1LgsbDayM4UoHwbvwDRwnI3hwNaAHRnc=", version = "v4.4.0", ) go_repository( name = "com_github_goccy_go_json", build_file_proto_mode = "disable_global", importpath = "github.com/goccy/go-json", sum = "h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA=", version = "v0.10.3", ) go_repository( name = "com_github_lestrrat_go_blackmagic", build_file_proto_mode = "disable_global", importpath = "github.com/lestrrat-go/blackmagic", sum = "h1:Cg2gVSc9h7sz9NOByczrbUvLopQmXrfFx//N+AkAr5k=", version = "v1.0.2", ) go_repository( name = "com_github_lestrrat_go_httpcc", build_file_proto_mode = "disable_global", importpath = "github.com/lestrrat-go/httpcc", sum = "h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE=", version = "v1.0.1", ) go_repository( name = "com_github_lestrrat_go_httprc", build_file_proto_mode = "disable_global", importpath = "github.com/lestrrat-go/httprc", sum = "h1:qgmgIRhpvBqexMJjA/PmwSvhNk679oqD1RbovdCGW8k=", version = "v1.0.6", ) go_repository( name = "com_github_lestrrat_go_iter", build_file_proto_mode = "disable_global", importpath = "github.com/lestrrat-go/iter", sum = "h1:gMXo1q4c2pHmC3dn8LzRhJfP1ceCbgSiT9lUydIzltI=", version = "v1.0.2", ) go_repository( name = "com_github_lestrrat_go_option", build_file_proto_mode = "disable_global", importpath = "github.com/lestrrat-go/option", sum = "h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU=", version = "v1.0.1", ) go_repository( name = "com_github_pmezard_go_difflib", build_file_proto_mode = "disable_global", importpath = "github.com/pmezard/go-difflib", sum = "h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=", version = "v1.0.0", ) go_repository( name = "com_github_segmentio_asm", build_file_proto_mode = "disable_global", importpath = "github.com/segmentio/asm", sum = "h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys=", version = "v1.2.0", ) go_repository( name = "com_github_stretchr_objx", build_file_proto_mode = "disable_global", importpath = "github.com/stretchr/objx", sum = "h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=", version = "v0.5.2", ) go_repository( name = "com_github_stretchr_testify", build_file_proto_mode = "disable_global", importpath = "github.com/stretchr/testify", sum = "h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=", version = "v1.10.0", ) go_repository( name = "in_gopkg_check_v1", build_file_proto_mode = "disable_global", importpath = "gopkg.in/check.v1", sum = "h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=", version = "v0.0.0-20161208181325-20d25e280405", ) go_repository( name = "in_gopkg_yaml_v3", build_file_proto_mode = "disable_global", importpath = "gopkg.in/yaml.v3", sum = "h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=", version = "v3.0.1", ) go_repository( name = "org_golang_x_crypto", build_file_proto_mode = "disable_global", importpath = "golang.org/x/crypto", sum = "h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=", version = "v0.32.0", ) go_repository( name = "org_golang_x_net", build_file_proto_mode = "disable_global", importpath = "golang.org/x/net", sum = "h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=", version = "v0.21.0", ) go_repository( name = "org_golang_x_sys", build_file_proto_mode = "disable_global", importpath = "golang.org/x/sys", sum = "h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=", version = "v0.29.0", ) go_repository( name = "org_golang_x_term", build_file_proto_mode = "disable_global", importpath = "golang.org/x/term", sum = "h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg=", version = "v0.28.0", ) go_repository( name = "org_golang_x_text", build_file_proto_mode = "disable_global", importpath = "golang.org/x/text", sum = "h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=", version = "v0.21.0", ) golang-github-lestrrat-go-jwx-2.1.4/docs/000077500000000000000000000000001476711647200202635ustar00rootroot00000000000000golang-github-lestrrat-go-jwx-2.1.4/docs/00-anatomy.md000066400000000000000000000132171476711647200224760ustar00rootroot00000000000000# 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/v2/jwt package. There is also a package to deal with OpenID extensions in github.com/lestrrat-go/jwx/v2/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/v2/jwt` * [FAQ-style HOW-TOs](./01-jwt.md) * [Documentation on pkg.go.dev](https://pkg.go.dev/github.com/lestrrat-go/jwx/v2/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/v2/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/v2/jws` * [FAQ-style HOW-TOs](./02-jws.md) * [Documentation on pkg.go.dev](https://pkg.go.dev/github.com/lestrrat-go/jwx/v2/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/v2/jwe` * [Documentation on pkg.go.dev](https://pkg.go.dev/github.com/lestrrat-go/jwx/v2/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/v2/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/v2/jwk` * [FAQ-style HOW-TOs](./04-jwk.md) * [Documentation on pkg.go.dev](https://pkg.go.dev/github.com/lestrrat-go/jwx/v2/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/v2/jwa. golang-github-lestrrat-go-jwx-2.1.4/docs/01-jwt.md000066400000000000000000001277351476711647200216460ustar00rootroot00000000000000# Working with JWT In this document we describe how to work with JWT using `github.com/lestrrat-go/jwx/v2/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) * [Serialization](#jwt-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) --- # 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/v2/jwt#Parse) ```go package examples_test import ( "fmt" "github.com/lestrrat-go/jwx/v2/jwa" "github.com/lestrrat-go/jwx/v2/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/v2/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 parse a JWT stored in a file, use [`jwt.ReadFile()`](https://pkg.go.dev/github.com/lestrrat-go/jwx/v2/jwt#ReadFile). [`jwt.ReadFile()`](https://pkg.go.dev/github.com/lestrrat-go/jwx/v2/jwt#ReadFile) accepts the same options as [`jwt.Parse()`](https://pkg.go.dev/github.com/lestrrat-go/jwx/v2/jwt#Parse). ```go package examples_test import ( "fmt" "os" "github.com/lestrrat-go/jwx/v2/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.Fprintf(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/v2/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/v2/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/v2/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/v2/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/v2/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/v2/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/v2/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/v2/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/v2/jwt#Parse) function. ```go package examples_test import ( "fmt" "github.com/lestrrat-go/jwx/v2/jwa" "github.com/lestrrat-go/jwx/v2/jwk" "github.com/lestrrat-go/jwx/v2/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/v2/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/v2/jwk#Key) (i.e. [`jwk.ECDSAPrivateKey`](https://pkg.go.dev/github.com/lestrrat-go/jwx/v2/jwk#ECDSAPrivateKey), [`jwk.ECDSAPublicKey`](https://pkg.go.dev/github.com/lestrrat-go/jwx/v2/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/v2/jwk#Set), you need to add some options when calling the [`jwt.Parse()`](https://pkg.go.dev/github.com/lestrrat-go/jwx/v2/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/v2/jwa" "github.com/lestrrat-go/jwx/v2/jwk" "github.com/lestrrat-go/jwx/v2/jwt" ) func Example_jwt_parse_with_keyset() { 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.FromRaw(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.FromRaw([]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/v2/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/v2/jwa" "github.com/lestrrat-go/jwx/v2/jws" "github.com/lestrrat-go/jwx/v2/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 tok, err := jwt.NewBuilder(). Issuer("me"). 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 { switch parsed.Issuer() { case "me": sink.Key(alg, symmetricKey) return nil default: return fmt.Errorf("unknown issuer %q", parsed.Issuer()) } }))) if err != nil { fmt.Printf("%s\n", err) return } if parsed.Issuer() != tok.Issuer() { 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]interface{}) 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 := sig.ProtectedHeaders().Algorithm() 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/v2/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/v2/jwa" "github.com/lestrrat-go/jwx/v2/jwk" "github.com/lestrrat-go/jwx/v2/jws" "github.com/lestrrat-go/jwx/v2/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.FromRaw(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/v2/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/v2/jwt#Validate) function. ```go package examples_test import ( "encoding/json" "fmt" "time" "github.com/lestrrat-go/jwx/v2/jwt" ) func Example_validate_jwt() { 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: // "exp" not satisfied // "exp" not satisfied } ``` source: [examples/jwt_validate_example_test.go](https://github.com/lestrrat-go/jwx/blob/v2/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/v2/jwt#Validate) to check for other fields, use one of the various [`jwt.ValidateOption`](https://pkg.go.dev/github.com/lestrrat-go/jwx/v2/jwt#ValidateOption) values, such as `jwt.WithClaimValue()`, `jwt.WithRequiredClaim()`, etc. ```go package examples_test import ( "fmt" "time" "github.com/lestrrat-go/jwx/v2/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: // "iss" not satisfied: values do not match } ``` source: [examples/jwt_validate_issuer_example_test.go](https://github.com/lestrrat-go/jwx/blob/v2/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/v2/jwt" ) func Example_jwt_validate_validator() { validator := jwt.ValidatorFunc(func(_ context.Context, t jwt.Token) jwt.ValidationError { if t.IssuedAt().Month() != 8 { return jwt.NewValidationError(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: // tokens are only valid if issued during August! } ``` source: [examples/jwt_validate_validator_example_test.go](https://github.com/lestrrat-go/jwx/blob/v2/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/v2/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 jwt.IsValidationError(err) { 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 !jwt.IsValidationError(err) { fmt.Printf("error should be validation error\n") return } if !errors.Is(err, jwt.ErrTokenExpired()) { fmt.Printf("error should be of token expired type\n") return } fmt.Printf("%s\n", err) } // OUTPUT: // "exp" not satisfied } ``` source: [examples/jwt_validate_detect_error_type_example_test.go](https://github.com/lestrrat-go/jwx/blob/v2/examples/jwt_validate_detect_error_type_example_test.go) # JWT 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/v2/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/v2/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/v2/jwa" "github.com/lestrrat-go/jwx/v2/jwk" "github.com/lestrrat-go/jwx/v2/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.FromRaw(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 []interface{}{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/v2/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/v2/jwa" "github.com/lestrrat-go/jwx/v2/jwk" "github.com/lestrrat-go/jwx/v2/jwt" ) func Example_jwt_serialize_jwe_and_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.FromRaw(privkey.PublicKey) if err != nil { fmt.Printf("failed to create symmetric key: %s\n", err) return } signkey, err := jwk.FromRaw([]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/v2/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/v2/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/v2/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/v2/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/v2/jwa" "github.com/lestrrat-go/jwx/v2/jws" "github.com/lestrrat-go/jwx/v2/jwt" ) func Example_jwt_plain_struct() { t1, err := jwt.NewBuilder(). Issuer("https://github.com/lestrrat-go/jwx/v2/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/v2/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 v, ok := token.Get(name) ``` 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. golang-github-lestrrat-go-jwx-2.1.4/docs/02-jws.md000066400000000000000000000512771476711647200216430ustar00rootroot00000000000000# Working with JWS In this document we describe how to work with JWS using [`github.com/lestrrat-go/jwx/v2/jws`](https://pkg.go.dev/github.com/lestrrat-go/jwx/v2/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) # 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/v2/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/v2/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/v2/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/v2/jws#ReadFile). [`jws.ReadFile()`](https://pkg.go.dev/github.com/lestrrat-go/jwx/v2/jws#ReadFile) accepts the same options as [`jws.Parse()`](https://pkg.go.dev/github.com/lestrrat-go/jwx/v2/jws#Parse). ```go package examples_test import ( "encoding/json" "fmt" "os" "github.com/lestrrat-go/jwx/v2/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.Fprintf(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/v2/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/v2/jwa" "github.com/lestrrat-go/jwx/v2/jwk" "github.com/lestrrat-go/jwx/v2/jws" "github.com/lestrrat-go/jwx/v2/jwt" ) func Example_jws_use_header() { key, err := jwk.FromRaw([]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 fmt.Printf("%q\n", msg.Signatures()[0].ProtectedHeaders().KeyID()) // OUTPUT: // "secret-key" } ``` source: [examples/jws_use_jws_header_test.go](https://github.com/lestrrat-go/jwx/blob/v2/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/v2/jwa" "github.com/lestrrat-go/jwx/v2/jwk" "github.com/lestrrat-go/jwx/v2/jws" ) func Example_jws_sign() { key, err := jwk.FromRaw([]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/v2/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/v2/jws#Sign) function with the `jws.WithJSON()` option and multiple `jws.WithKey()` options: ```go package examples_test import ( "fmt" "github.com/lestrrat-go/jwx/v2/jwa" "github.com/lestrrat-go/jwx/v2/jwk" "github.com/lestrrat-go/jwx/v2/jws" ) func Example_jws_sign_json() { var keys []jwk.Key for i := 0; i < 3; i++ { key, err := jwk.FromRaw([]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/v2/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/v2/jwa" "github.com/lestrrat-go/jwx/v2/jwk" "github.com/lestrrat-go/jwx/v2/jws" ) func Example_jws_sign_detached_payload() { payload := `$.02` key, err := jwk.FromRaw([]byte(`abracadabra`)) if err != nil { fmt.Printf("failed to create symmetric key: %s\n", err) return } serialized, err := jws.Sign(nil, jws.WithKey(jwa.HS256, key), jws.WithDetachedPayload([]byte(payload))) if err != nil { fmt.Printf("failed to sign payload: %s\n", err) return } fmt.Printf("%s\n", serialized) // OUTPUT: // eyJhbGciOiJIUzI1NiJ9..H14oXKwyvAsl0IbBLjw9tLxNIoYisuIyb_oDV4-30Vk } ``` source: [examples/jws_sign_detached_payload_example_test.go](https://github.com/lestrrat-go/jwx/blob/v2/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/v2/jwa" "github.com/lestrrat-go/jwx/v2/jwk" "github.com/lestrrat-go/jwx/v2/jws" ) func Example_jws_sign_with_headers() { key, err := jwk.FromRaw([]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/v2/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). 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/v2/jwa" "github.com/lestrrat-go/jwx/v2/jwk" "github.com/lestrrat-go/jwx/v2/jws" ) func Example_jws_verify_with_key() { const src = `eyJhbGciOiJIUzI1NiJ9.TG9yZW0gaXBzdW0.EjVtju0uXjSz6QevNgAqN1ESd9aNCP7-tJLifkQ0_C0` key, err := jwk.FromRaw([]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/v2/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. 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-)". 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 ( "crypto/rand" "crypto/rsa" "fmt" "github.com/lestrrat-go/jwx/v2/jwa" "github.com/lestrrat-go/jwx/v2/jwk" "github.com/lestrrat-go/jwx/v2/jws" ) func Example_jws_verify_with_jwk_set() { // Setup payload first... 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" signed, err := jws.Sign([]byte(payload), jws.WithKey(jwa.RS256, privkey)) 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.FromRaw([]byte("abracadabra")) set.AddKey(k1) k2, _ := jwk.FromRaw([]byte("opensesame")) set.AddKey(k2) // AddKey the real thing pubkey, _ := jwk.PublicRawKeyOf(privkey) k3, _ := jwk.FromRaw(pubkey) k3.Set(jwk.AlgorithmKey, jwa.RS256) set.AddKey(k3) // Up to this point, you probably will replace with a simple jwk.Fetch() // Now verify using the set. if _, err := jws.Verify(signed, jws.WithKeySet(set, jws.WithRequireKid(false))); err != nil { fmt.Printf("Failed to verify using jwk.Set: %s", err) } // OUTPUT: } ``` source: [examples/jws_verify_with_keyset_example_test.go](https://github.com/lestrrat-go/jwx/blob/v2/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/v2/jwa" "github.com/lestrrat-go/jwx/v2/jwk" "github.com/lestrrat-go/jwx/v2/jws" ) func Example_jws_verify_detached_payload() { serialized := `eyJhbGciOiJIUzI1NiJ9..H14oXKwyvAsl0IbBLjw9tLxNIoYisuIyb_oDV4-30Vk` payload := `$.02` key, err := jwk.FromRaw([]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/v2/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/v2/jws#RegisterSigner) and [`jws.RegisterVerifier()`](https://pkg.go.dev/github.com/lestrrat-go/jwx/v2/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/v2/jwa" "github.com/lestrrat-go/jwx/v2/jws" ) type CirclEdDSASignerVerifier struct{} func NewCirclEdDSASigner() (jws.Signer, error) { return &CirclEdDSASignerVerifier{}, nil } func NewCirclEdDSAVerifier() (jws.Verifier, error) { return &CirclEdDSASignerVerifier{}, nil } func (s CirclEdDSASignerVerifier) Algorithm() jwa.SignatureAlgorithm { return jwa.EdDSA } func (s CirclEdDSASignerVerifier) Sign(payload []byte, keyif interface{}) ([]byte, error) { 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 CirclEdDSASignerVerifier) Verify(payload []byte, signature []byte, keyif interface{}) error { 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) } } func Example_jws_custom_signer_verifier() { // This example shows how to register external jws.Signer / jws.Verifier for // a given algorithm. jws.RegisterSigner(jwa.EdDSA, jws.SignerFactoryFn(NewCirclEdDSASigner)) jws.RegisterVerifier(jwa.EdDSA, jws.VerifierFactoryFn(NewCirclEdDSAVerifier)) 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: } ``` source: [examples/jws_custom_signer_verifier_example_test.go](https://github.com/lestrrat-go/jwx/blob/v2/examples/jws_custom_signer_verifier_example_test.go) # Enabling ES256K See [Enabling Optional Signature Methods](./20-global-settings.md#enabling-optional-signature-methods) golang-github-lestrrat-go-jwx-2.1.4/docs/03-jwe.md000066400000000000000000000357731476711647200216310ustar00rootroot00000000000000# Working with JWE In this document we describe how to work with JWK using `github.com/lestrrat-go/jwx/v2/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) # 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/v2/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/v2/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/v2/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/v2/jwe#ReadFile). [`jwe.ReadFile()`](https://pkg.go.dev/github.com/lestrrat-go/jwx/v2/jwe#ReadFile) accepts the same options as [`jwe.Parse()`](https://pkg.go.dev/github.com/lestrrat-go/jwx/v2/jwe#Parse). ```go package examples_test import ( "encoding/json" "fmt" "os" "github.com/lestrrat-go/jwx/v2/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/v2/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/v2/jwa" "github.com/lestrrat-go/jwx/v2/jwe" "github.com/lestrrat-go/jwx/v2/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.FromRaw(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/v2/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/v2/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/v2/jwa" "github.com/lestrrat-go/jwx/v2/jwe" "github.com/lestrrat-go/jwx/v2/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.FromRaw(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(), 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 } 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.FromRaw(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/v2/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/v2/internal/json" "github.com/lestrrat-go/jwx/v2/jwa" "github.com/lestrrat-go/jwx/v2/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/v2/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/v2/jwa" "github.com/lestrrat-go/jwx/v2/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/v2/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/v2/jwa" "github.com/lestrrat-go/jwx/v2/jwe" "github.com/lestrrat-go/jwx/v2/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.FromRaw([]byte("abracadabra")) set.AddKey(k1) k2, _ := jwk.FromRaw([]byte("opensesame")) set.AddKey(k2) // Add the real thing k3, _ := jwk.FromRaw(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/v2/examples/jwe_decrypt_with_keyset_example_test.go) golang-github-lestrrat-go-jwx-2.1.4/docs/04-jwk.md000066400000000000000000001045241476711647200216270ustar00rootroot00000000000000# Working with JWK In this document we describe how to work with JWK using `github.com/lestrrat-go/jwx/v2/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.FromRaw()](#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) --- # 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/v2/jwk#Parse) and [`jwk.ReadFile()`](https://pkg.go.dev/github.com/lestrrat-go/jwx/v2/jwk#ReadFile) returns a [`jwk.Set`](https://pkg.go.dev/github.com/lestrrat-go/jwx/v2/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.FromRaw` 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/v2/jwk#Parse). ```go package examples_test import ( "encoding/json" "fmt" "os" "github.com/lestrrat-go/jwx/v2/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/v2/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/v2/jwk#ParseKey). ```go package examples_test import ( "encoding/json" "fmt" "os" "github.com/lestrrat-go/jwx/v2/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/v2/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/v2/jwk#WithPEM) option. ```go package examples_test import ( "fmt" "os" "github.com/lestrrat-go/jwx/v2/internal/json" "github.com/lestrrat-go/jwx/v2/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/v2/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/v2/jwk#ReadFile) can be used. ```go package examples_test import ( "encoding/json" "fmt" "os" "github.com/lestrrat-go/jwx/v2/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.Fprintf(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/v2/examples/jwk_readfile_example_test.go) `jwk.ReadFile()` accepts the same options as [`jwk.Parse()`](https://pkg.go.dev/github.com/lestrrat-go/jwx/v2/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/v2/internal/json" "github.com/lestrrat-go/jwx/v2/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.Fprintf(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/v2/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/v2/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/v2/examples/jwk_struct_field_example_test.go) # Construction ## Using jwk.FromRaw() Users can create a new key from scratch using [`jwk.FromRaw()`](https://pkg.go.dev/github.com/lestrrat-go/jwx/v2/jwk#FromRaw). [`jwk.FromRaw()`](https://pkg.go.dev/github.com/lestrrat-go/jwx/v2/jwk#FromRaw) 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.FromRaw()`](https://pkg.go.dev/github.com/lestrrat-go/jwx/v2/jwk#FromRaw) 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/v2/jwk" ) func Example_jwk_from_raw() { // First, THIS IS THE WRONG WAY TO USE jwk.FromRaw(). // // Assume that the file contains a JWK in JSON format // // buf, _ := os.ReadFile(file) // key, _ := jwk.FromRaw(buf) // // This is not right, because the jwk.FromRaw() 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.FromRaw(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.FromRaw(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.FromRaw(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_from_raw_example_test.go](https://github.com/lestrrat-go/jwx/blob/v2/examples/jwk_from_raw_example_test.go) # Fetching JWK Sets ## Parse a key from a remote resource To parse keys stored in a remote location pointed by an HTTP(s) URL, use [`jwk.Fetch()`](https://pkg.go.dev/github.com/lestrrat-go/jwx/v2/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/v2/jwk#Cache) or [`jwk.CachedSet`](https://pkg.go.dev/github.com/lestrrat-go/jwx/v2/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/v2/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/v2/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/v2/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/v2/jwk` provides the [`jwk.Cache`](https://pkg.go.dev/github.com/lestrrat-go/jwx/v2/jwk#Cache) and [`jwk.CachedSet`](https://pkg.go.dev/github.com/lestrrat-go/jwx/v2/jwk#CachedSet) to do this for you. ```go package examples_test import ( "context" "fmt" "time" "github.com/lestrrat-go/jwx/v2/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. // // Note that by default refreshes only happen very 15 minutes at the // earliest. If you need to control this, use `jwk.WithRefreshWindow()` c := jwk.NewCache(ctx) // Tell *jwk.Cache that we only want to refresh this JWKS // when it needs to (based on Cache-Control or Expires header from // the HTTP response). If the calculated minimum refresh interval is less // than 15 minutes, don't go refreshing any earlier than 15 minutes. c.Register(googleCerts, jwk.WithMinRefreshInterval(15*time.Minute)) // Refresh the JWKS once before getting into the main loop. // This allows you to check if the JWKS is available before we start // a long-running program _, err := c.Refresh(ctx, googleCerts) if err != nil { fmt.Printf("failed to refresh 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.Get(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 // fetch upon calling `(jwk.Cache).Fetch()`. // 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/v2/examples/jwk_cache_example_test.go) ```go package examples_test import ( "context" "fmt" "time" "github.com/lestrrat-go/jwx/v2/jwk" "github.com/lestrrat-go/jwx/v2/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 := jwk.NewCache(ctx) c.Register(googleCerts, jwk.WithMinRefreshInterval(15*time.Minute)) _, err := c.Refresh(ctx, googleCerts) if err != nil { fmt.Printf("failed to refresh google JWKS: %s\n", err) return } cached := jwk.NewCachedSet(c, googleCerts) // 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) } ``` source: [examples/jwk_cached_set_example_test.go](https://github.com/lestrrat-go/jwx/blob/v2/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/v2/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/v2/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/v2/jwk" ) func Example_jwk_key_specific_methods() { 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.FromRaw(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/v2/examples/jwk_key_specific_methods_example_test.go) ## Setting values to fields Using [`jwk.FromRaw()`](https://pkg.go.dev/github.com/lestrrat-go/jwx/v2/jwk#FromRaw) 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/v2/jwk#Set) method. The [`jwk.Set()`](https://pkg.go.dev/github.com/lestrrat-go/jwx/v2/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/v2/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/v2/jwk#Key) object, use the [`Raw()`](https://github.com/github.com/lestrrat-go/jwx/v2/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/v2/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/v2/jwk#ParseRawKey). ```go var raw interface{} if err := jwk.ParseRawKey(src, &raw); err != nil { ... } ``` golang-github-lestrrat-go-jwx-2.1.4/docs/20-global-settings.md000066400000000000000000000076421476711647200241330ustar00rootroot00000000000000# 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/v2` 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 v, _ := token.Get(`x-foo-bar`) foobar := v.(mypkg.FooBar) ``` 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-2.1.4/docs/21-frameworks.md000066400000000000000000000056321476711647200232130ustar00rootroot00000000000000## 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-2.1.4/docs/99-faq.md000066400000000000000000000233351476711647200216210ustar00rootroot00000000000000# 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/v2/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. ## 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). golang-github-lestrrat-go-jwx-2.1.4/docs/README.md000066400000000000000000000011641476711647200215440ustar00rootroot00000000000000# 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/v2`](https://github.com/lestrrat-go/jwx/tree/v2). 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-2.1.4/examples/000077500000000000000000000000001476711647200211515ustar00rootroot00000000000000golang-github-lestrrat-go-jwx-2.1.4/examples/README.md000066400000000000000000000005071476711647200224320ustar00rootroot00000000000000# Examples (and Benchmarks) * [github.com/lestrrat-go/jwx/v2](./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-2.1.4/examples/go.mod000066400000000000000000000004651476711647200222640ustar00rootroot00000000000000module github.com/lestrrat-go/jwx/v2/examples go 1.16 require ( github.com/cloudflare/circl v1.3.7 github.com/lestrrat-go/jwx/v2 v2.0.11 ) replace github.com/cloudflare/circl v1.0.0 => github.com/cloudflare/circl v1.0.1-0.20210104183656-96a0695de3c3 replace github.com/lestrrat-go/jwx/v2 v2.0.11 => ../ golang-github-lestrrat-go-jwx-2.1.4/examples/go.sum000066400000000000000000000232621476711647200223110ustar00rootroot00000000000000github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/decred/dcrd/crypto/blake256 v1.1.0/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo= 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/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/lestrrat-go/blackmagic v1.0.2 h1:Cg2gVSc9h7sz9NOByczrbUvLopQmXrfFx//N+AkAr5k= github.com/lestrrat-go/blackmagic v1.0.2/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU= github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE= github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E= github.com/lestrrat-go/httprc v1.0.6 h1:qgmgIRhpvBqexMJjA/PmwSvhNk679oqD1RbovdCGW8k= github.com/lestrrat-go/httprc v1.0.6/go.mod h1:mwwz3JMTPBjHUkkDv/IGJ39aALInZLrhBp0X7KGUZlo= github.com/lestrrat-go/iter v1.0.2 h1:gMXo1q4c2pHmC3dn8LzRhJfP1ceCbgSiT9lUydIzltI= github.com/lestrrat-go/iter v1.0.2/go.mod h1:Momfcq3AnRlRjI5b5O8/G5/BvpzrhoFTZcn06fEOPt4= github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= 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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys= github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 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/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.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= 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/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 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/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= 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/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= 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.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek= 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/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= 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/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= 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-2.1.4/examples/jwe_decrypt_with_key_example_test.go000066400000000000000000000011101476711647200304650ustar00rootroot00000000000000package examples_test import ( "fmt" "github.com/lestrrat-go/jwx/v2/jwa" "github.com/lestrrat-go/jwx/v2/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-2.1.4/examples/jwe_decrypt_with_keyset_example_test.go000066400000000000000000000021311476711647200312050ustar00rootroot00000000000000package examples_test import ( "crypto/rand" "crypto/rsa" "fmt" "github.com/lestrrat-go/jwx/v2/jwa" "github.com/lestrrat-go/jwx/v2/jwe" "github.com/lestrrat-go/jwx/v2/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.FromRaw([]byte("abracadabra")) set.AddKey(k1) k2, _ := jwk.FromRaw([]byte("opensesame")) set.AddKey(k2) // Add the real thing k3, _ := jwk.FromRaw(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-2.1.4/examples/jwe_encrypt_example_test.go000066400000000000000000000017751476711647200266150ustar00rootroot00000000000000package examples_test import ( "crypto/rand" "crypto/rsa" "fmt" "github.com/lestrrat-go/jwx/v2/jwa" "github.com/lestrrat-go/jwx/v2/jwe" "github.com/lestrrat-go/jwx/v2/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.FromRaw(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-2.1.4/examples/jwe_encrypt_json_example_test.go000066400000000000000000000043101476711647200276320ustar00rootroot00000000000000package examples_test import ( "crypto/rand" "crypto/rsa" "fmt" "github.com/lestrrat-go/jwx/v2/jwa" "github.com/lestrrat-go/jwx/v2/jwe" "github.com/lestrrat-go/jwx/v2/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.FromRaw(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(), 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 } 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.FromRaw(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-2.1.4/examples/jwe_encrypt_with_headers_example_test.go000066400000000000000000000027131476711647200313340ustar00rootroot00000000000000package examples_test import ( "crypto/rand" "crypto/rsa" "fmt" "os" "github.com/lestrrat-go/jwx/v2/internal/json" "github.com/lestrrat-go/jwx/v2/jwa" "github.com/lestrrat-go/jwx/v2/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-2.1.4/examples/jwe_example_test.go000066400000000000000000000066521476711647200250500ustar00rootroot00000000000000package examples_test import ( "context" "crypto/rand" "crypto/rsa" "fmt" "log" "github.com/lestrrat-go/jwx/v2/internal/jwxtest" "github.com/lestrrat-go/jwx/v2/jwa" "github.com/lestrrat-go/jwx/v2/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 { rawhint, _ := msg.ProtectedHeaders().Get(`jwx-hints`) //nolint:forcetypeassert hint, ok := rawhint.(string) if ok && 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: %s`, rawhint) } // 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-2.1.4/examples/jwe_parse_example_test.go000066400000000000000000000025011476711647200262270ustar00rootroot00000000000000package examples_test import ( "encoding/json" "fmt" "os" "github.com/lestrrat-go/jwx/v2/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-2.1.4/examples/jwe_readfile_example_test.go000066400000000000000000000030541476711647200266740ustar00rootroot00000000000000package examples_test import ( "encoding/json" "fmt" "os" "github.com/lestrrat-go/jwx/v2/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-2.1.4/examples/jwk_cache_example_test.go000066400000000000000000000043151476711647200261730ustar00rootroot00000000000000package examples_test import ( "context" "fmt" "time" "github.com/lestrrat-go/jwx/v2/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. // // Note that by default refreshes only happen very 15 minutes at the // earliest. If you need to control this, use `jwk.WithRefreshWindow()` c := jwk.NewCache(ctx) // Tell *jwk.Cache that we only want to refresh this JWKS // when it needs to (based on Cache-Control or Expires header from // the HTTP response). If the calculated minimum refresh interval is less // than 15 minutes, don't go refreshing any earlier than 15 minutes. c.Register(googleCerts, jwk.WithMinRefreshInterval(15*time.Minute)) // Refresh the JWKS once before getting into the main loop. // This allows you to check if the JWKS is available before we start // a long-running program _, err := c.Refresh(ctx, googleCerts) if err != nil { fmt.Printf("failed to refresh 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.Get(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 // fetch upon calling `(jwk.Cache).Fetch()`. // 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-2.1.4/examples/jwk_cached_set_example_test.go000066400000000000000000000017541476711647200272160ustar00rootroot00000000000000package examples_test import ( "context" "fmt" "time" "github.com/lestrrat-go/jwx/v2/jwk" "github.com/lestrrat-go/jwx/v2/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 := jwk.NewCache(ctx) c.Register(googleCerts, jwk.WithMinRefreshInterval(15*time.Minute)) _, err := c.Refresh(ctx, googleCerts) if err != nil { fmt.Printf("failed to refresh google JWKS: %s\n", err) return } cached := jwk.NewCachedSet(c, googleCerts) // 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) } golang-github-lestrrat-go-jwx-2.1.4/examples/jwk_comparison_example_test.go000066400000000000000000000022741476711647200273040ustar00rootroot00000000000000package examples import ( "crypto/rand" "crypto/rsa" "fmt" "github.com/lestrrat-go/jwx/v2/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.FromRaw(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-2.1.4/examples/jwk_example_test.go000066400000000000000000000050351476711647200250500ustar00rootroot00000000000000package examples_test import ( "context" "fmt" "log" "github.com/lestrrat-go/jwx/v2/internal/json" "github.com/lestrrat-go/jwx/v2/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 it := set.Keys(context.Background()); it.Next(context.Background()); { pair := it.Pair() key := pair.Value.(jwk.Key) var rawkey interface{} // This is the raw key, like *rsa.PrivateKey or *ecdsa.PrivateKey if err := key.Raw(&rawkey); err != nil { log.Printf("failed to create public key: %s", err) return } // Use rawkey for jws.Verify() or whatever. _ = rawkey // You can create jwk.Key from a raw key, too fromRawKey, err := jwk.FromRaw(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.FromRaw(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-2.1.4/examples/jwk_fetch_example_test.go000066400000000000000000000035741476711647200262270ustar00rootroot00000000000000package examples_test import ( "context" "encoding/json" "fmt" "net/http" "net/http/httptest" "os" "github.com/lestrrat-go/jwx/v2/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-2.1.4/examples/jwk_from_raw_example_test.go000066400000000000000000000042611476711647200267440ustar00rootroot00000000000000package examples_test import ( "crypto/ecdsa" "crypto/elliptic" "crypto/rand" "crypto/rsa" "fmt" "github.com/lestrrat-go/jwx/v2/jwk" ) func Example_jwk_from_raw() { // First, THIS IS THE WRONG WAY TO USE jwk.FromRaw(). // // Assume that the file contains a JWK in JSON format // // buf, _ := os.ReadFile(file) // key, _ := jwk.FromRaw(buf) // // This is not right, because the jwk.FromRaw() 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.FromRaw(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.FromRaw(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.FromRaw(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-2.1.4/examples/jwk_key_specific_methods_example_test.go000066400000000000000000000016711476711647200313120ustar00rootroot00000000000000package examples_test import ( "crypto/rand" "crypto/rsa" "fmt" "github.com/lestrrat-go/jwx/v2/jwk" ) func Example_jwk_key_specific_methods() { 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.FromRaw(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-2.1.4/examples/jwk_parse_jwks_example_test.go000066400000000000000000000030241476711647200272740ustar00rootroot00000000000000package examples_test import ( "encoding/json" "fmt" "os" "github.com/lestrrat-go/jwx/v2/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-2.1.4/examples/jwk_parse_key_example_test.go000066400000000000000000000012121476711647200271030ustar00rootroot00000000000000package examples_test import ( "encoding/json" "fmt" "os" "github.com/lestrrat-go/jwx/v2/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-2.1.4/examples/jwk_parse_with_pem_example_test.go000066400000000000000000000053061476711647200301370ustar00rootroot00000000000000package examples_test import ( "fmt" "os" "github.com/lestrrat-go/jwx/v2/internal/json" "github.com/lestrrat-go/jwx/v2/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-2.1.4/examples/jwk_readfile_example_test.go000066400000000000000000000033321476711647200267010ustar00rootroot00000000000000package examples_test import ( "encoding/json" "fmt" "os" "github.com/lestrrat-go/jwx/v2/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.Fprintf(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-2.1.4/examples/jwk_readfile_with_pem_example_test.go000066400000000000000000000056421476711647200306030ustar00rootroot00000000000000package examples_test import ( "fmt" "os" "github.com/lestrrat-go/jwx/v2/internal/json" "github.com/lestrrat-go/jwx/v2/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.Fprintf(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-2.1.4/examples/jwk_struct_field_example_test.go000066400000000000000000000025701476711647200276200ustar00rootroot00000000000000package examples_test import ( "encoding/json" "fmt" "os" "github.com/lestrrat-go/jwx/v2/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-2.1.4/examples/jwk_whitelist_example_test.go000066400000000000000000000051751476711647200271510ustar00rootroot00000000000000package examples_test import ( "context" "encoding/json" "fmt" "net/http" "net/http/httptest" "os" "regexp" "github.com/lestrrat-go/jwx/v2/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-2.1.4/examples/jws_custom_signer_verifier_example_test.go000066400000000000000000000037171476711647200317210ustar00rootroot00000000000000package examples_test import ( "crypto/rand" "fmt" "github.com/cloudflare/circl/sign/ed25519" "github.com/lestrrat-go/jwx/v2/jwa" "github.com/lestrrat-go/jwx/v2/jws" ) type CirclEdDSASignerVerifier struct{} func NewCirclEdDSASigner() (jws.Signer, error) { return &CirclEdDSASignerVerifier{}, nil } func NewCirclEdDSAVerifier() (jws.Verifier, error) { return &CirclEdDSASignerVerifier{}, nil } func (s CirclEdDSASignerVerifier) Algorithm() jwa.SignatureAlgorithm { return jwa.EdDSA } func (s CirclEdDSASignerVerifier) Sign(payload []byte, keyif interface{}) ([]byte, error) { 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 CirclEdDSASignerVerifier) Verify(payload []byte, signature []byte, keyif interface{}) error { 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) } } func Example_jws_custom_signer_verifier() { // This example shows how to register external jws.Signer / jws.Verifier for // a given algorithm. jws.RegisterSigner(jwa.EdDSA, jws.SignerFactoryFn(NewCirclEdDSASigner)) jws.RegisterVerifier(jwa.EdDSA, jws.VerifierFactoryFn(NewCirclEdDSAVerifier)) 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: } golang-github-lestrrat-go-jwx-2.1.4/examples/jws_example_test.go000066400000000000000000000056471476711647200250710ustar00rootroot00000000000000package examples_test import ( "fmt" "github.com/lestrrat-go/jwx/v2/internal/base64" "github.com/lestrrat-go/jwx/v2/internal/json" "github.com/lestrrat-go/jwx/v2/jwa" "github.com/lestrrat-go/jwx/v2/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-2.1.4/examples/jws_parse_example_test.go000066400000000000000000000010211476711647200262410ustar00rootroot00000000000000package examples_test import ( "encoding/json" "fmt" "os" "github.com/lestrrat-go/jwx/v2/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-2.1.4/examples/jws_readfile_example_test.go000066400000000000000000000013341476711647200267110ustar00rootroot00000000000000package examples_test import ( "encoding/json" "fmt" "os" "github.com/lestrrat-go/jwx/v2/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.Fprintf(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-2.1.4/examples/jws_sign_detached_payload_example_test.go000066400000000000000000000012161476711647200314270ustar00rootroot00000000000000package examples_test import ( "fmt" "github.com/lestrrat-go/jwx/v2/jwa" "github.com/lestrrat-go/jwx/v2/jwk" "github.com/lestrrat-go/jwx/v2/jws" ) func Example_jws_sign_detached_payload() { payload := `$.02` key, err := jwk.FromRaw([]byte(`abracadabra`)) if err != nil { fmt.Printf("failed to create symmetric key: %s\n", err) return } serialized, err := jws.Sign(nil, jws.WithKey(jwa.HS256, key), jws.WithDetachedPayload([]byte(payload))) if err != nil { fmt.Printf("failed to sign payload: %s\n", err) return } fmt.Printf("%s\n", serialized) // OUTPUT: // eyJhbGciOiJIUzI1NiJ9..H14oXKwyvAsl0IbBLjw9tLxNIoYisuIyb_oDV4-30Vk } golang-github-lestrrat-go-jwx-2.1.4/examples/jws_sign_example_test.go000066400000000000000000000011071476711647200260740ustar00rootroot00000000000000package examples_test import ( "fmt" "github.com/lestrrat-go/jwx/v2/jwa" "github.com/lestrrat-go/jwx/v2/jwk" "github.com/lestrrat-go/jwx/v2/jws" ) func Example_jws_sign() { key, err := jwk.FromRaw([]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-2.1.4/examples/jws_sign_json_example_test.go000066400000000000000000000020371476711647200271300ustar00rootroot00000000000000package examples_test import ( "fmt" "github.com/lestrrat-go/jwx/v2/jwa" "github.com/lestrrat-go/jwx/v2/jwk" "github.com/lestrrat-go/jwx/v2/jws" ) func Example_jws_sign_json() { var keys []jwk.Key for i := 0; i < 3; i++ { key, err := jwk.FromRaw([]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-2.1.4/examples/jws_sign_with_headers_example_test.go000066400000000000000000000013021476711647200306170ustar00rootroot00000000000000package examples_test import ( "fmt" "github.com/lestrrat-go/jwx/v2/jwa" "github.com/lestrrat-go/jwx/v2/jwk" "github.com/lestrrat-go/jwx/v2/jws" ) func Example_jws_sign_with_headers() { key, err := jwk.FromRaw([]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-2.1.4/examples/jws_use_jws_header_test.go000066400000000000000000000020771476711647200264170ustar00rootroot00000000000000package examples_test import ( "fmt" "github.com/lestrrat-go/jwx/v2/jwa" "github.com/lestrrat-go/jwx/v2/jwk" "github.com/lestrrat-go/jwx/v2/jws" "github.com/lestrrat-go/jwx/v2/jwt" ) func Example_jws_use_header() { key, err := jwk.FromRaw([]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 fmt.Printf("%q\n", msg.Signatures()[0].ProtectedHeaders().KeyID()) // OUTPUT: // "secret-key" } golang-github-lestrrat-go-jwx-2.1.4/examples/jws_verify_detached_payload_example_test.go000066400000000000000000000012651476711647200317770ustar00rootroot00000000000000package examples_test import ( "fmt" "github.com/lestrrat-go/jwx/v2/jwa" "github.com/lestrrat-go/jwx/v2/jwk" "github.com/lestrrat-go/jwx/v2/jws" ) func Example_jws_verify_detached_payload() { serialized := `eyJhbGciOiJIUzI1NiJ9..H14oXKwyvAsl0IbBLjw9tLxNIoYisuIyb_oDV4-30Vk` payload := `$.02` key, err := jwk.FromRaw([]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-2.1.4/examples/jws_verify_with_key_example_test.go000066400000000000000000000011501476711647200303410ustar00rootroot00000000000000package examples_test import ( "fmt" "github.com/lestrrat-go/jwx/v2/jwa" "github.com/lestrrat-go/jwx/v2/jwk" "github.com/lestrrat-go/jwx/v2/jws" ) func Example_jws_verify_with_key() { const src = `eyJhbGciOiJIUzI1NiJ9.TG9yZW0gaXBzdW0.EjVtju0uXjSz6QevNgAqN1ESd9aNCP7-tJLifkQ0_C0` key, err := jwk.FromRaw([]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-2.1.4/examples/jws_verify_with_keyset_example_test.go000066400000000000000000000022431476711647200310610ustar00rootroot00000000000000package examples_test import ( "crypto/rand" "crypto/rsa" "fmt" "github.com/lestrrat-go/jwx/v2/jwa" "github.com/lestrrat-go/jwx/v2/jwk" "github.com/lestrrat-go/jwx/v2/jws" ) func Example_jws_verify_with_jwk_set() { // Setup payload first... 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" signed, err := jws.Sign([]byte(payload), jws.WithKey(jwa.RS256, privkey)) 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.FromRaw([]byte("abracadabra")) set.AddKey(k1) k2, _ := jwk.FromRaw([]byte("opensesame")) set.AddKey(k2) // AddKey the real thing pubkey, _ := jwk.PublicRawKeyOf(privkey) k3, _ := jwk.FromRaw(pubkey) k3.Set(jwk.AlgorithmKey, jwa.RS256) set.AddKey(k3) // Up to this point, you probably will replace with a simple jwk.Fetch() // Now verify using the set. if _, err := jws.Verify(signed, jws.WithKeySet(set, jws.WithRequireKid(false))); err != nil { fmt.Printf("Failed to verify using jwk.Set: %s", err) } // OUTPUT: } golang-github-lestrrat-go-jwx-2.1.4/examples/jwt_builder_example_test.go000066400000000000000000000011471476711647200265670ustar00rootroot00000000000000package examples_test import ( "encoding/json" "fmt" "os" "github.com/lestrrat-go/jwx/v2/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-2.1.4/examples/jwt_construct_example_test.go000066400000000000000000000011371476711647200271640ustar00rootroot00000000000000package examples_test import ( "encoding/json" "fmt" "os" "github.com/lestrrat-go/jwx/v2/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-2.1.4/examples/jwt_example_test.go000066400000000000000000000221711476711647200250610ustar00rootroot00000000000000package examples_test import ( "crypto/rand" "crypto/rsa" "fmt" "log" "time" "github.com/lestrrat-go/jwx/v2/internal/json" "github.com/lestrrat-go/jwx/v2/jwk" "github.com/lestrrat-go/jwx/v2/jws" "github.com/lestrrat-go/jwx/v2/jwt/openid" "github.com/lestrrat-go/jwx/v2/jwa" "github.com/lestrrat-go/jwx/v2/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.FromRaw(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.FromRaw(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/v2/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) if v, ok := t.Get(`privateClaimKey`); ok { fmt.Printf("privateClaimKey -> '%s'\n", v) } //convert jwk in bytes and return a new key jwkey, err := jwk.ParseKey([]byte(jwkStr)) if err != nil { log.Fatal("erro") } // 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/v2/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()) // OUTPUT: // { // "aud": [ // "Golang Users" // ], // "iat": 233431200, // "privateClaimKey": "Hello, World!", // "sub": "https://github.com/lestrrat-go/jwx/v2/jwt" // } // aud -> '[Golang Users]' // iat -> '1977-05-25T18:00:00Z' // privateClaimKey -> 'Hello, World!' // sub -> 'https://github.com/lestrrat-go/jwx/v2/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.FromRaw(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/v2/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/v2/jwt" // } } golang-github-lestrrat-go-jwx-2.1.4/examples/jwt_flatten_audience_example_test.go000066400000000000000000000050171476711647200304330ustar00rootroot00000000000000package examples_test import ( "encoding/json" "fmt" "os" "github.com/lestrrat-go/jwx/v2/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-2.1.4/examples/jwt_get_claims_example_test.go000066400000000000000000000030431476711647200272450ustar00rootroot00000000000000package examples_test import ( "encoding/json" "fmt" "time" "github.com/lestrrat-go/jwx/v2/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`). Build() if err != nil { fmt.Printf("failed to build token: %s\n", err) return } // Pre-defined fields have typed accessors. var _ time.Time = tok.IssuedAt() var _ string = tok.Issuer() var _ string = tok.Subject() var v interface{} var ok bool // But you can also get them via the generic `.Get()` method. // However, v is of type interface{}, so you might need to // use a type switch to properly use its value. // // For the key name you could also use jwt.IssuedAtKey constant v, ok = tok.Get(`iat`) // Private claims v, ok = tok.Get(`claim1`) v, ok = tok.Get(`claim2`) // 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 } v, ok = tok.Get(`claim2`) if !ok { fmt.Printf(`failed to get private claim "claim2"`) return } if _, ok := v.(time.Time); !ok { fmt.Printf(`claim2 expected to be time.Time, but got %T`, v) return } _ = v _ = ok // OUTPUT: } golang-github-lestrrat-go-jwx-2.1.4/examples/jwt_parse_example_test.go000066400000000000000000000004621476711647200262520ustar00rootroot00000000000000package examples_test import ( "fmt" "github.com/lestrrat-go/jwx/v2/jwa" "github.com/lestrrat-go/jwx/v2/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-2.1.4/examples/jwt_parse_request_example_test.go000066400000000000000000000036021476711647200300210ustar00rootroot00000000000000package examples_test import ( "fmt" "net/http" "net/url" "github.com/lestrrat-go/jwx/v2/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-2.1.4/examples/jwt_parse_with_jku_example_test.go000066400000000000000000000040721476711647200301570ustar00rootroot00000000000000package examples_test import ( "crypto/rand" "crypto/rsa" "encoding/json" "fmt" "net/http" "net/http/httptest" "github.com/lestrrat-go/jwx/v2/jwa" "github.com/lestrrat-go/jwx/v2/jwk" "github.com/lestrrat-go/jwx/v2/jws" "github.com/lestrrat-go/jwx/v2/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.FromRaw(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-2.1.4/examples/jwt_parse_with_key_example_test.go000066400000000000000000000011761476711647200301600ustar00rootroot00000000000000package examples_test import ( "fmt" "github.com/lestrrat-go/jwx/v2/jwa" "github.com/lestrrat-go/jwx/v2/jwk" "github.com/lestrrat-go/jwx/v2/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-2.1.4/examples/jwt_parse_with_key_provider_example_test.go000066400000000000000000000067021476711647200320720ustar00rootroot00000000000000package examples_test import ( "context" "crypto/rand" "crypto/rsa" "encoding/base64" "fmt" "github.com/lestrrat-go/jwx/v2/jwa" "github.com/lestrrat-go/jwx/v2/jws" "github.com/lestrrat-go/jwx/v2/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 tok, err := jwt.NewBuilder(). Issuer("me"). 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 { switch parsed.Issuer() { case "me": sink.Key(alg, symmetricKey) return nil default: return fmt.Errorf("unknown issuer %q", parsed.Issuer()) } }))) if err != nil { fmt.Printf("%s\n", err) return } if parsed.Issuer() != tok.Issuer() { 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]interface{}) 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 := sig.ProtectedHeaders().Algorithm() 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-2.1.4/examples/jwt_parse_with_keyset_example_test.go000066400000000000000000000053001476711647200306650ustar00rootroot00000000000000package examples_test import ( "crypto/rand" "crypto/rsa" "fmt" "github.com/lestrrat-go/jwx/v2/jwa" "github.com/lestrrat-go/jwx/v2/jwk" "github.com/lestrrat-go/jwx/v2/jwt" ) func Example_jwt_parse_with_keyset() { 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.FromRaw(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.FromRaw([]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-2.1.4/examples/jwt_raw_struct_example_test.go000066400000000000000000000020021476711647200273250ustar00rootroot00000000000000package examples import ( "encoding/json" "fmt" "os" "github.com/lestrrat-go/jwx/v2/jwa" "github.com/lestrrat-go/jwx/v2/jws" "github.com/lestrrat-go/jwx/v2/jwt" ) func Example_jwt_plain_struct() { t1, err := jwt.NewBuilder(). Issuer("https://github.com/lestrrat-go/jwx/v2/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-2.1.4/examples/jwt_readfile_example_test.go000066400000000000000000000013451476711647200267140ustar00rootroot00000000000000package examples_test import ( "fmt" "os" "github.com/lestrrat-go/jwx/v2/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.Fprintf(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-2.1.4/examples/jwt_serialize_json_example_test.go000066400000000000000000000007161476711647200301620ustar00rootroot00000000000000package examples_test import ( "encoding/json" "fmt" "os" "time" "github.com/lestrrat-go/jwx/v2/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-2.1.4/examples/jwt_serialize_jwe_jws_example_test.go000066400000000000000000000023401476711647200306540ustar00rootroot00000000000000package examples_test import ( "crypto/rand" "crypto/rsa" "fmt" "time" "github.com/lestrrat-go/jwx/v2/jwa" "github.com/lestrrat-go/jwx/v2/jwk" "github.com/lestrrat-go/jwx/v2/jwt" ) func Example_jwt_serialize_jwe_and_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.FromRaw(privkey.PublicKey) if err != nil { fmt.Printf("failed to create symmetric key: %s\n", err) return } signkey, err := jwk.FromRaw([]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-2.1.4/examples/jwt_serialize_jws_example_test.go000066400000000000000000000026031476711647200300110ustar00rootroot00000000000000package examples_test import ( "fmt" "time" "github.com/lestrrat-go/jwx/v2/jwa" "github.com/lestrrat-go/jwx/v2/jwk" "github.com/lestrrat-go/jwx/v2/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.FromRaw(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 []interface{}{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-2.1.4/examples/jwt_validate_detect_error_type_example_test.go000066400000000000000000000030141476711647200325270ustar00rootroot00000000000000package examples_test import ( "encoding/json" "errors" "fmt" "time" "github.com/lestrrat-go/jwx/v2/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 jwt.IsValidationError(err) { 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 !jwt.IsValidationError(err) { fmt.Printf("error should be validation error\n") return } if !errors.Is(err, jwt.ErrTokenExpired()) { fmt.Printf("error should be of token expired type\n") return } fmt.Printf("%s\n", err) } // OUTPUT: // "exp" not satisfied } golang-github-lestrrat-go-jwx-2.1.4/examples/jwt_validate_example_test.go000066400000000000000000000020241476711647200267250ustar00rootroot00000000000000package examples_test import ( "encoding/json" "fmt" "time" "github.com/lestrrat-go/jwx/v2/jwt" ) func Example_validate_jwt() { 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: // "exp" not satisfied // "exp" not satisfied } golang-github-lestrrat-go-jwx-2.1.4/examples/jwt_validate_issuer_example_test.go000066400000000000000000000010311476711647200303140ustar00rootroot00000000000000package examples_test import ( "fmt" "time" "github.com/lestrrat-go/jwx/v2/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: // "iss" not satisfied: values do not match } golang-github-lestrrat-go-jwx-2.1.4/examples/jwt_validate_validator_example_test.go000066400000000000000000000014571476711647200310030ustar00rootroot00000000000000package examples_test import ( "context" "errors" "fmt" "time" "github.com/lestrrat-go/jwx/v2/jwt" ) func Example_jwt_validate_validator() { validator := jwt.ValidatorFunc(func(_ context.Context, t jwt.Token) jwt.ValidationError { if t.IssuedAt().Month() != 8 { return jwt.NewValidationError(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: // tokens are only valid if issued during August! } golang-github-lestrrat-go-jwx-2.1.4/examples/jwx_example_test.go000066400000000000000000000042451476711647200250670ustar00rootroot00000000000000package examples_test import ( "crypto/rand" "crypto/rsa" "fmt" "time" "github.com/lestrrat-go/jwx/v2/internal/json" "github.com/lestrrat-go/jwx/v2/jwa" "github.com/lestrrat-go/jwx/v2/jwk" "github.com/lestrrat-go/jwx/v2/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.FromRaw(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.FromRaw(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.FromRaw([]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-2.1.4/examples/jwx_readme_example_test.go000066400000000000000000000051141476711647200264000ustar00rootroot00000000000000package examples_test import ( "bytes" "fmt" "net/http" "time" "github.com/lestrrat-go/jwx/v2/jwa" "github.com/lestrrat-go/jwx/v2/jwe" "github.com/lestrrat-go/jwx/v2/jwk" "github.com/lestrrat-go/jwx/v2/jws" "github.com/lestrrat-go/jwx/v2/jwt" ) func Example_jwx_readme() { // 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-2.1.4/examples/jwx_with_number_example_test.go000066400000000000000000000010701476711647200274630ustar00rootroot00000000000000//go:build ignore // +build ignore package examples_test import ( "crypto/rand" "crypto/rsa" "log" "github.com/lestrrat-go/jwx/v2/jwa" "github.com/lestrrat-go/jwx/v2/jwe" "github.com/lestrrat-go/jwx/v2/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-2.1.4/format.go000066400000000000000000000055241476711647200211600ustar00rootroot00000000000000package jwx import ( "bytes" "encoding/json" ) 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] != '{' { // Compact format. It's probably a JWS or JWE sep := []byte{'.'} // 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 '.' == 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-2.1.4/formatkind_string_gen.go000066400000000000000000000013771476711647200242470ustar00rootroot00000000000000// 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 { if i < 0 || i >= FormatKind(len(_FormatKind_index)-1) { return "FormatKind(" + strconv.FormatInt(int64(i), 10) + ")" } return _FormatKind_name[_FormatKind_index[i]:_FormatKind_index[i+1]] } golang-github-lestrrat-go-jwx-2.1.4/go.mod000066400000000000000000000011671476711647200204460ustar00rootroot00000000000000module github.com/lestrrat-go/jwx/v2 go 1.20 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.2 github.com/lestrrat-go/httprc v1.0.6 github.com/lestrrat-go/iter v1.0.2 github.com/lestrrat-go/option v1.0.1 github.com/segmentio/asm v1.2.0 github.com/stretchr/testify v1.10.0 golang.org/x/crypto v0.32.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.29.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) golang-github-lestrrat-go-jwx-2.1.4/go.sum000066400000000000000000000060141476711647200204670ustar00rootroot00000000000000github.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.2 h1:Cg2gVSc9h7sz9NOByczrbUvLopQmXrfFx//N+AkAr5k= github.com/lestrrat-go/blackmagic v1.0.2/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU= github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE= github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E= github.com/lestrrat-go/httprc v1.0.6 h1:qgmgIRhpvBqexMJjA/PmwSvhNk679oqD1RbovdCGW8k= github.com/lestrrat-go/httprc v1.0.6/go.mod h1:mwwz3JMTPBjHUkkDv/IGJ39aALInZLrhBp0X7KGUZlo= github.com/lestrrat-go/iter v1.0.2 h1:gMXo1q4c2pHmC3dn8LzRhJfP1ceCbgSiT9lUydIzltI= github.com/lestrrat-go/iter v1.0.2/go.mod h1:Momfcq3AnRlRjI5b5O8/G5/BvpzrhoFTZcn06fEOPt4= github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU= github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys= github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/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= golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 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-2.1.4/internal/000077500000000000000000000000001476711647200211475ustar00rootroot00000000000000golang-github-lestrrat-go-jwx-2.1.4/internal/base64/000077500000000000000000000000001476711647200222335ustar00rootroot00000000000000golang-github-lestrrat-go-jwx-2.1.4/internal/base64/BUILD.bazel000066400000000000000000000007501476711647200241130ustar00rootroot00000000000000load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") go_library( name = "base64", srcs = ["base64.go"], importpath = "github.com/lestrrat-go/jwx/v2/internal/base64", visibility = ["//:__subpackages__"], ) go_test( name = "base64_test", srcs = ["base64_test.go"], embed = [":base64"], deps = ["@com_github_stretchr_testify//assert"], ) alias( name = "go_default_library", actual = ":base64", visibility = ["//:__subpackages__"], ) golang-github-lestrrat-go-jwx-2.1.4/internal/base64/asmbase64.go000066400000000000000000000013431476711647200243500ustar00rootroot00000000000000//go:build jwx_asmbase64 package base64 import ( "fmt" asmbase64 "github.com/segmentio/asm/base64" ) func init() { SetEncoder(asmbase64.RawURLEncoding) SetDecoder(asmDecoder{}) } type asmDecoder struct{} func (d asmDecoder) Decode(src []byte) ([]byte, error) { var enc *asmbase64.Encoding switch Guess(src) { case Std: enc = asmbase64.StdEncoding case RawStd: enc = asmbase64.RawStdEncoding case URL: enc = asmbase64.URLEncoding case RawURL: enc = asmbase64.RawURLEncoding default: return nil, fmt.Errorf(`invalid encoding`) } dst := make([]byte, enc.DecodedLen(len(src))) n, err := enc.Decode(dst, src) if err != nil { return nil, fmt.Errorf(`failed to decode source: %w`, err) } return dst[:n], nil } golang-github-lestrrat-go-jwx-2.1.4/internal/base64/base64.go000066400000000000000000000047231476711647200236540ustar00rootroot00000000000000package 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 } 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 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-2.1.4/internal/base64/base64_test.go000066400000000000000000000016661476711647200247160ustar00rootroot00000000000000package base64 import ( "encoding/base64" "testing" "github.com/stretchr/testify/assert" ) 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 { tc := tc 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) if !assert.NoError(t, err, `Decode should succeed`) { return } if !assert.Equal(t, payload, decoded, `decoded content should match`) { return } }) } } golang-github-lestrrat-go-jwx-2.1.4/internal/ecutil/000077500000000000000000000000001476711647200224345ustar00rootroot00000000000000golang-github-lestrrat-go-jwx-2.1.4/internal/ecutil/BUILD.bazel000066400000000000000000000005401476711647200243110ustar00rootroot00000000000000load("@io_bazel_rules_go//go:def.bzl", "go_library") go_library( name = "ecutil", srcs = ["ecutil.go"], importpath = "github.com/lestrrat-go/jwx/v2/internal/ecutil", visibility = ["//:__subpackages__"], deps = ["//jwa"], ) alias( name = "go_default_library", actual = ":ecutil", visibility = ["//:__subpackages__"], ) golang-github-lestrrat-go-jwx-2.1.4/internal/ecutil/ecutil.go000066400000000000000000000055021476711647200242520ustar00rootroot00000000000000// Package ecutil defines tools that help with elliptic curve related // computation package ecutil import ( "crypto/elliptic" "math/big" "sync" "github.com/lestrrat-go/jwx/v2/jwa" ) // data for available curves. Some algorithms may be compiled in/out var curveToAlg = map[elliptic.Curve]jwa.EllipticCurveAlgorithm{} var algToCurve = map[jwa.EllipticCurveAlgorithm]elliptic.Curve{} var availableAlgs []jwa.EllipticCurveAlgorithm var availableCrvs []elliptic.Curve func RegisterCurve(crv elliptic.Curve, alg jwa.EllipticCurveAlgorithm) { curveToAlg[crv] = alg algToCurve[alg] = crv availableAlgs = append(availableAlgs, alg) availableCrvs = append(availableCrvs, crv) } func IsAvailable(alg jwa.EllipticCurveAlgorithm) bool { _, ok := algToCurve[alg] return ok } func AvailableAlgorithms() []jwa.EllipticCurveAlgorithm { return availableAlgs } func AvailableCurves() []elliptic.Curve { return availableCrvs } func AlgorithmForCurve(crv elliptic.Curve) (jwa.EllipticCurveAlgorithm, bool) { v, ok := curveToAlg[crv] return v, ok } func CurveForAlgorithm(alg jwa.EllipticCurveAlgorithm) (elliptic.Curve, bool) { v, ok := algToCurve[alg] return v, ok } const ( // size of buffer that needs to be allocated for EC521 curve ec521BufferSize = 66 // (521 / 8) + 1 ) var ecpointBufferPool = sync.Pool{ New: func() interface{} { // 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-2.1.4/internal/iter/000077500000000000000000000000001476711647200221125ustar00rootroot00000000000000golang-github-lestrrat-go-jwx-2.1.4/internal/iter/BUILD.bazel000066400000000000000000000006161476711647200237730ustar00rootroot00000000000000load("@io_bazel_rules_go//go:def.bzl", "go_library") go_library( name = "iter", srcs = ["mapiter.go"], importpath = "github.com/lestrrat-go/jwx/v2/internal/iter", visibility = ["//:__subpackages__"], deps = ["@com_github_lestrrat_go_iter//mapiter:go_default_library"], ) alias( name = "go_default_library", actual = ":iter", visibility = ["//:__subpackages__"], ) golang-github-lestrrat-go-jwx-2.1.4/internal/iter/mapiter.go000066400000000000000000000016341476711647200241060ustar00rootroot00000000000000package iter import ( "context" "fmt" "github.com/lestrrat-go/iter/mapiter" ) // MapVisitor is a specialized visitor for our purposes. // Whereas mapiter.Visitor supports any type of key, this // visitor assumes the key is a string type MapVisitor interface { Visit(string, interface{}) error } type MapVisitorFunc func(string, interface{}) error func (fn MapVisitorFunc) Visit(s string, v interface{}) error { return fn(s, v) } func WalkMap(ctx context.Context, src mapiter.Source, visitor MapVisitor) error { return mapiter.Walk(ctx, src, mapiter.VisitorFunc(func(k, v interface{}) error { //nolint:forcetypeassert return visitor.Visit(k.(string), v) })) } func AsMap(ctx context.Context, src mapiter.Source) (map[string]interface{}, error) { var m map[string]interface{} if err := mapiter.AsMap(ctx, src, &m); err != nil { return nil, fmt.Errorf(`mapiter.AsMap failed: %w`, err) } return m, nil } golang-github-lestrrat-go-jwx-2.1.4/internal/jose/000077500000000000000000000000001476711647200221075ustar00rootroot00000000000000golang-github-lestrrat-go-jwx-2.1.4/internal/jose/BUILD.bazel000066400000000000000000000005451476711647200237710ustar00rootroot00000000000000load("@io_bazel_rules_go//go:def.bzl", "go_library") go_library( name = "jose", srcs = ["jose.go"], importpath = "github.com/lestrrat-go/jwx/v2/internal/jose", visibility = ["//:__subpackages__"], deps = ["//internal/jwxtest"], ) alias( name = "go_default_library", actual = ":jose", visibility = ["//:__subpackages__"], ) golang-github-lestrrat-go-jwx-2.1.4/internal/jose/jose.go000066400000000000000000000156421476711647200234060ustar00rootroot00000000000000package jose import ( "bufio" "bytes" "context" "fmt" "io" "os/exec" "strings" "sync" "testing" "github.com/lestrrat-go/jwx/v2/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-2.1.4/internal/json/000077500000000000000000000000001476711647200221205ustar00rootroot00000000000000golang-github-lestrrat-go-jwx-2.1.4/internal/json/BUILD.bazel000066400000000000000000000006371476711647200240040ustar00rootroot00000000000000load("@io_bazel_rules_go//go:def.bzl", "go_library") go_library( name = "json", srcs = [ "json.go", "registry.go", "stdlib.go", ], importpath = "github.com/lestrrat-go/jwx/v2/internal/json", visibility = ["//:__subpackages__"], deps = ["//internal/base64"], ) alias( name = "go_default_library", actual = ":json", visibility = ["//:__subpackages__"], ) golang-github-lestrrat-go-jwx-2.1.4/internal/json/goccy.go000066400000000000000000000020741476711647200235560ustar00rootroot00000000000000//go:build jwx_goccy // +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 interface{}) ([]byte, error) { return json.Marshal(v) } // MarshalIndent is just a proxy for "encoding/json".MarshalIndent func MarshalIndent(v interface{}, prefix, indent string) ([]byte, error) { return json.MarshalIndent(v, prefix, indent) } golang-github-lestrrat-go-jwx-2.1.4/internal/json/json.go000066400000000000000000000053461476711647200234300ustar00rootroot00000000000000package json import ( "bytes" "fmt" "os" "sync/atomic" "github.com/lestrrat-go/jwx/v2/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 interface{}) 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 EncodeAudience(enc *Encoder, aud []string, flatten bool) error { var val interface{} 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 interface{}) { enc := NewEncoder(os.Stdout) enc.SetIndent("", " ") //nolint:errchkjson _ = enc.Encode(v) } golang-github-lestrrat-go-jwx-2.1.4/internal/json/registry.go000066400000000000000000000017701476711647200243240ustar00rootroot00000000000000package json import ( "fmt" "reflect" "sync" ) type Registry struct { mu *sync.RWMutex data map[string]reflect.Type } func NewRegistry() *Registry { return &Registry{ mu: &sync.RWMutex{}, data: make(map[string]reflect.Type), } } func (r *Registry) Register(name string, object interface{}) { if object == nil { r.mu.Lock() defer r.mu.Unlock() delete(r.data, name) return } typ := reflect.TypeOf(object) r.mu.Lock() defer r.mu.Unlock() r.data[name] = typ } func (r *Registry) Decode(dec *Decoder, name string) (interface{}, error) { r.mu.RLock() defer r.mu.RUnlock() if typ, ok := r.data[name]; ok { ptr := reflect.New(typ).Interface() if err := dec.Decode(ptr); err != nil { return nil, fmt.Errorf(`failed to decode field %s: %w`, name, err) } return reflect.ValueOf(ptr).Elem().Interface(), nil } var decoded interface{} 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-2.1.4/internal/json/stdlib.go000066400000000000000000000017521476711647200237350ustar00rootroot00000000000000//go:build !jwx_goccy // +build !jwx_goccy 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 interface{}) ([]byte, error) { return json.Marshal(v) } // MarshalIndent is just a proxy for "encoding/json".MarshalIndent func MarshalIndent(v interface{}, prefix, indent string) ([]byte, error) { return json.MarshalIndent(v, prefix, indent) } golang-github-lestrrat-go-jwx-2.1.4/internal/jwxtest/000077500000000000000000000000001476711647200226575ustar00rootroot00000000000000golang-github-lestrrat-go-jwx-2.1.4/internal/jwxtest/BUILD.bazel000066400000000000000000000010071476711647200245330ustar00rootroot00000000000000load("@io_bazel_rules_go//go:def.bzl", "go_library") go_library( name = "jwxtest", srcs = ["jwxtest.go"], importpath = "github.com/lestrrat-go/jwx/v2/internal/jwxtest", visibility = ["//:__subpackages__"], deps = [ "//internal/ecutil", "//jwa", "//jwe", "//jwk", "//jws", "//x25519", "@com_github_stretchr_testify//assert", ], ) alias( name = "go_default_library", actual = ":jwxtest", visibility = ["//:__subpackages__"], ) golang-github-lestrrat-go-jwx-2.1.4/internal/jwxtest/jwxtest.go000066400000000000000000000223421476711647200247210ustar00rootroot00000000000000package jwxtest import ( "bytes" "context" "crypto/ecdsa" "crypto/ed25519" "crypto/elliptic" "crypto/rand" "crypto/rsa" "encoding/json" "fmt" "io" "os" "strings" "testing" "github.com/lestrrat-go/jwx/v2/internal/ecutil" "github.com/lestrrat-go/jwx/v2/jwa" "github.com/lestrrat-go/jwx/v2/jwe" "github.com/lestrrat-go/jwx/v2/jwk" "github.com/lestrrat-go/jwx/v2/jws" "github.com/lestrrat-go/jwx/v2/x25519" "github.com/stretchr/testify/assert" ) 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.FromRaw(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) { var crv elliptic.Curve if tmp, ok := ecutil.CurveForAlgorithm(alg); ok { crv = tmp } else { return nil, fmt.Errorf(`invalid curve algorithm %s`, alg) } 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.FromRaw(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.FromRaw(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.FromRaw(key) if err != nil { return nil, fmt.Errorf(`failed to generate jwk.OKPPrivateKey: %w`, err) } return k, nil } func GenerateX25519Key() (x25519.PrivateKey, error) { _, priv, err := 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.FromRaw(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 interface{}) (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) if !assert.NoError(t, err, `failed to read file %s for debugging`, file) { return } if isHash, isArray := bytes.ContainsRune(buf, '{'), bytes.ContainsRune(buf, '['); isHash || isArray { // Looks like a JSON-like thing. Dump that in a formatted manner, and // be done with it var v interface{} if isHash { v = map[string]interface{}{} } else { v = []interface{}{} } if !assert.NoError(t, json.Unmarshal(buf, &v), `failed to parse contents as JSON`) { return } 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) if !assert.NoError(t, err, `failed to parse JWE encrypted message`) { return } 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 interface{} if err := key.Raw(&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 interface{} 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 := key.Raw(&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 := key.Raw(&rawkey); err != nil { return "", nil, fmt.Errorf(`failed to obtain raw key: %w`, err) } keyif = rawkey.PublicKey default: var rawkey []byte if err := key.Raw(&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 interface{} if err := key.Raw(&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-2.1.4/internal/keyconv/000077500000000000000000000000001476711647200226255ustar00rootroot00000000000000golang-github-lestrrat-go-jwx-2.1.4/internal/keyconv/BUILD.bazel000066400000000000000000000013111476711647200244770ustar00rootroot00000000000000load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") go_library( name = "keyconv", srcs = ["keyconv.go"], importpath = "github.com/lestrrat-go/jwx/v2/internal/keyconv", visibility = ["//:__subpackages__"], deps = [ "//jwk", "@com_github_lestrrat_go_blackmagic//:go_default_library", "@org_golang_x_crypto//ed25519", ], ) go_test( name = "keyconv_test", srcs = ["keyconv_test.go"], deps = [ ":keyconv", "//internal/jwxtest", "//jwa", "//jwk", "@com_github_stretchr_testify//assert", ], ) alias( name = "go_default_library", actual = ":keyconv", visibility = ["//:__subpackages__"], ) golang-github-lestrrat-go-jwx-2.1.4/internal/keyconv/keyconv.go000066400000000000000000000116371476711647200246420ustar00rootroot00000000000000package keyconv import ( "crypto" "crypto/ecdsa" "crypto/rsa" "fmt" "github.com/lestrrat-go/blackmagic" "github.com/lestrrat-go/jwx/v2/jwk" "golang.org/x/crypto/ed25519" ) // 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 interface{}) error { if jwkKey, ok := src.(jwk.Key); ok { var raw rsa.PrivateKey if err := jwkKey.Raw(&raw); err != nil { return fmt.Errorf(`keyconv: 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 interface{}) 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 interface{}) error { if jwkKey, ok := src.(jwk.Key); ok { var raw ecdsa.PrivateKey if err := jwkKey.Raw(&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 interface{}) 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 interface{}) error { if jwkKey, ok := src.(jwk.Key); ok { var raw []byte if err := jwkKey.Raw(&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 interface{}) error { if jwkKey, ok := src.(jwk.Key); ok { var raw ed25519.PrivateKey if err := jwkKey.Raw(&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 interface{}) 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) } golang-github-lestrrat-go-jwx-2.1.4/internal/keyconv/keyconv_test.go000066400000000000000000000143231476711647200256740ustar00rootroot00000000000000package keyconv_test import ( "crypto/ecdsa" "crypto/rsa" "testing" "github.com/lestrrat-go/jwx/v2/internal/jwxtest" "github.com/lestrrat-go/jwx/v2/internal/keyconv" "github.com/lestrrat-go/jwx/v2/jwa" "github.com/lestrrat-go/jwx/v2/jwk" "github.com/stretchr/testify/assert" ) func TestKeyconv(t *testing.T) { t.Run("RSA", func(t *testing.T) { key, err := jwxtest.GenerateRsaKey() if !assert.NoError(t, err, `rsa.GenerateKey should succeed`) { return } t.Run("PrivateKey", func(t *testing.T) { jwkKey, _ := jwk.FromRaw(key) testcases := []struct { Src interface{} Error bool }{ {Src: key}, {Src: *key}, {Src: jwkKey}, {Src: struct{}{}, Error: true}, } for _, tc := range testcases { tc := tc t.Run("Assign to rsa.PrivateKey", func(t *testing.T) { var dst rsa.PrivateKey var checker func(assert.TestingT, error, ...interface{}) bool if tc.Error { checker = assert.Error } else { checker = assert.NoError } if !checker(t, keyconv.RSAPrivateKey(&dst, tc.Src), `keyconv.RSAPrivateKey should succeed`) { return } if !tc.Error { // From Go 1.20 on, for purposes of our test, we need the // precomputed values as well dst.Precompute() if !assert.Equal(t, key, &dst, `keyconv.RSAPrivateKey should produce same value`) { return } } }) t.Run("Assign to *rsa.PrivateKey", func(t *testing.T) { dst := &rsa.PrivateKey{} var checker func(assert.TestingT, error, ...interface{}) bool if tc.Error { checker = assert.Error } else { checker = assert.NoError } if !checker(t, keyconv.RSAPrivateKey(dst, tc.Src), `keyconv.RSAPrivateKey should succeed`) { return } if !tc.Error { // From Go 1.20 on, for purposes of our test, we need the // precomputed values as well dst.Precompute() if !assert.Equal(t, key, dst, `keyconv.RSAPrivateKey should produce same value`) { return } } }) } }) t.Run("PublicKey", func(t *testing.T) { pubkey := &key.PublicKey jwkKey, _ := jwk.FromRaw(pubkey) testcases := []struct { Src interface{} Error bool }{ {Src: pubkey}, {Src: *pubkey}, {Src: jwkKey}, {Src: struct{}{}, Error: true}, } for _, tc := range testcases { tc := tc t.Run("Assign to rsa.PublicKey", func(t *testing.T) { var dst rsa.PublicKey var checker func(assert.TestingT, error, ...interface{}) bool if tc.Error { checker = assert.Error } else { checker = assert.NoError } if !checker(t, keyconv.RSAPublicKey(&dst, tc.Src), `keyconv.RSAPublicKey should succeed`) { return } if !tc.Error { if !assert.Equal(t, pubkey, &dst, `keyconv.RSAPublicKey should produce same value`) { return } } }) t.Run("Assign to *rsa.PublicKey", func(t *testing.T) { dst := &rsa.PublicKey{} var checker func(assert.TestingT, error, ...interface{}) bool if tc.Error { checker = assert.Error } else { checker = assert.NoError } if !checker(t, keyconv.RSAPublicKey(dst, tc.Src), `keyconv.RSAPublicKey should succeed`) { return } if !tc.Error { if !assert.Equal(t, pubkey, dst, `keyconv.RSAPublicKey should produce same value`) { return } } }) } }) }) t.Run("ECDSA", func(t *testing.T) { key, err := jwxtest.GenerateEcdsaKey(jwa.P521) if !assert.NoError(t, err, `ecdsa.GenerateKey should succeed`) { return } t.Run("PrivateKey", func(t *testing.T) { jwkKey, _ := jwk.FromRaw(key) testcases := []struct { Src interface{} Error bool }{ {Src: key}, {Src: *key}, {Src: jwkKey}, {Src: struct{}{}, Error: true}, } for _, tc := range testcases { tc := tc t.Run("Assign to ecdsa.PrivateKey", func(t *testing.T) { var dst ecdsa.PrivateKey var checker func(assert.TestingT, error, ...interface{}) bool if tc.Error { checker = assert.Error } else { checker = assert.NoError } if !checker(t, keyconv.ECDSAPrivateKey(&dst, tc.Src), `keyconv.ECDSAPrivateKey should succeed`) { return } if !tc.Error { if !assert.Equal(t, key, &dst, `keyconv.ECDSAPrivateKey should produce same value`) { return } } }) t.Run("Assign to *ecdsa.PrivateKey", func(t *testing.T) { dst := &ecdsa.PrivateKey{} var checker func(assert.TestingT, error, ...interface{}) bool if tc.Error { checker = assert.Error } else { checker = assert.NoError } if !checker(t, keyconv.ECDSAPrivateKey(dst, tc.Src), `keyconv.ECDSAPrivateKey should succeed`) { return } if !tc.Error { if !assert.Equal(t, key, dst, `keyconv.ECDSAPrivateKey should produce same value`) { return } } }) } }) t.Run("PublicKey", func(t *testing.T) { pubkey := &key.PublicKey jwkKey, _ := jwk.FromRaw(pubkey) testcases := []struct { Src interface{} Error bool }{ {Src: pubkey}, {Src: *pubkey}, {Src: jwkKey}, {Src: struct{}{}, Error: true}, } for _, tc := range testcases { tc := tc t.Run("Assign to ecdsa.PublicKey", func(t *testing.T) { var dst ecdsa.PublicKey var checker func(assert.TestingT, error, ...interface{}) bool if tc.Error { checker = assert.Error } else { checker = assert.NoError } if !checker(t, keyconv.ECDSAPublicKey(&dst, tc.Src), `keyconv.ECDSAPublicKey should succeed`) { return } if !tc.Error { if !assert.Equal(t, pubkey, &dst, `keyconv.ECDSAPublicKey should produce same value`) { return } } }) t.Run("Assign to *ecdsa.PublicKey", func(t *testing.T) { dst := &ecdsa.PublicKey{} var checker func(assert.TestingT, error, ...interface{}) bool if tc.Error { checker = assert.Error } else { checker = assert.NoError } if !checker(t, keyconv.ECDSAPublicKey(dst, tc.Src), `keyconv.ECDSAPublicKey should succeed`) { return } if !tc.Error { if !assert.Equal(t, pubkey, dst, `keyconv.ECDSAPublicKey should produce same value`) { return } } }) } }) }) } golang-github-lestrrat-go-jwx-2.1.4/internal/pool/000077500000000000000000000000001476711647200221205ustar00rootroot00000000000000golang-github-lestrrat-go-jwx-2.1.4/internal/pool/BUILD.bazel000066400000000000000000000005021476711647200237730ustar00rootroot00000000000000load("@io_bazel_rules_go//go:def.bzl", "go_library") go_library( name = "pool", srcs = ["pool.go"], importpath = "github.com/lestrrat-go/jwx/v2/internal/pool", visibility = ["//:__subpackages__"], ) alias( name = "go_default_library", actual = ":pool", visibility = ["//:__subpackages__"], ) golang-github-lestrrat-go-jwx-2.1.4/internal/pool/pool.go000066400000000000000000000017401476711647200234220ustar00rootroot00000000000000package pool import ( "bytes" "math/big" "sync" ) var bytesBufferPool = sync.Pool{ New: allocBytesBuffer, } func allocBytesBuffer() interface{} { return &bytes.Buffer{} } func GetBytesBuffer() *bytes.Buffer { //nolint:forcetypeassert return bytesBufferPool.Get().(*bytes.Buffer) } func ReleaseBytesBuffer(b *bytes.Buffer) { b.Reset() bytesBufferPool.Put(b) } var bigIntPool = sync.Pool{ New: allocBigInt, } func allocBigInt() interface{} { return &big.Int{} } func GetBigInt() *big.Int { //nolint:forcetypeassert return bigIntPool.Get().(*big.Int) } func ReleaseBigInt(i *big.Int) { bigIntPool.Put(i.SetInt64(0)) } var keyToErrorMapPool = sync.Pool{ New: allocKeyToErrorMap, } func allocKeyToErrorMap() interface{} { return make(map[string]error) } func GetKeyToErrorMap() map[string]error { //nolint:forcetypeassert return keyToErrorMapPool.Get().(map[string]error) } func ReleaseKeyToErrorMap(m map[string]error) { for key := range m { delete(m, key) } } golang-github-lestrrat-go-jwx-2.1.4/jwa/000077500000000000000000000000001476711647200201145ustar00rootroot00000000000000golang-github-lestrrat-go-jwx-2.1.4/jwa/BUILD.bazel000066400000000000000000000020211476711647200217650ustar00rootroot00000000000000load("@io_bazel_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/v2/jwa", visibility = ["//visibility:public"], deps = [ "@com_github_lestrrat_go_option//: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//assert", "@com_github_lestrrat_go_option//:option", ], ) alias( name = "go_default_library", actual = ":jwa", visibility = ["//visibility:public"], ) golang-github-lestrrat-go-jwx-2.1.4/jwa/README.md000066400000000000000000000004341476711647200213740ustar00rootroot00000000000000# JWA [![Go Reference](https://pkg.go.dev/badge/github.com/lestrrat-go/jwx/v2/jwa.svg)](https://pkg.go.dev/github.com/lestrrat-go/jwx/v2/jwa) Package [github.com/lestrrat-go/jwx/v2/jwa](./jwa) defines the various algorithm described in [RFC7518](https://tools.ietf.org/html/rfc7518) golang-github-lestrrat-go-jwx-2.1.4/jwa/compression_gen.go000066400000000000000000000061031476711647200236350ustar00rootroot00000000000000// Code generated by tools/cmd/genjwa/main.go. DO NOT EDIT. package jwa import ( "fmt" "sort" "sync" ) // CompressionAlgorithm represents the compression algorithms as described in https://tools.ietf.org/html/rfc7518#section-7.3 type CompressionAlgorithm string // Supported values for CompressionAlgorithm const ( Deflate CompressionAlgorithm = "DEF" // DEFLATE (RFC 1951) NoCompress CompressionAlgorithm = "" // No compression ) var muCompressionAlgorithms sync.RWMutex var allCompressionAlgorithms map[CompressionAlgorithm]struct{} var listCompressionAlgorithm []CompressionAlgorithm func init() { muCompressionAlgorithms.Lock() defer muCompressionAlgorithms.Unlock() allCompressionAlgorithms = make(map[CompressionAlgorithm]struct{}) allCompressionAlgorithms[Deflate] = struct{}{} allCompressionAlgorithms[NoCompress] = struct{}{} rebuildCompressionAlgorithm() } // RegisterCompressionAlgorithm registers a new CompressionAlgorithm so that the jwx can properly handle the new value. // Duplicates will silently be ignored func RegisterCompressionAlgorithm(v CompressionAlgorithm) { muCompressionAlgorithms.Lock() defer muCompressionAlgorithms.Unlock() if _, ok := allCompressionAlgorithms[v]; !ok { allCompressionAlgorithms[v] = struct{}{} rebuildCompressionAlgorithm() } } // UnregisterCompressionAlgorithm unregisters a CompressionAlgorithm from its known database. // Non-existent entries will silently be ignored func UnregisterCompressionAlgorithm(v CompressionAlgorithm) { muCompressionAlgorithms.Lock() defer muCompressionAlgorithms.Unlock() if _, ok := allCompressionAlgorithms[v]; ok { delete(allCompressionAlgorithms, v) rebuildCompressionAlgorithm() } } func rebuildCompressionAlgorithm() { listCompressionAlgorithm = make([]CompressionAlgorithm, 0, len(allCompressionAlgorithms)) for v := range allCompressionAlgorithms { listCompressionAlgorithm = append(listCompressionAlgorithm, v) } sort.Slice(listCompressionAlgorithm, func(i, j int) bool { return string(listCompressionAlgorithm[i]) < string(listCompressionAlgorithm[j]) }) } // CompressionAlgorithms returns a list of all available values for CompressionAlgorithm func CompressionAlgorithms() []CompressionAlgorithm { muCompressionAlgorithms.RLock() defer muCompressionAlgorithms.RUnlock() return listCompressionAlgorithm } // Accept is used when conversion from values given by // outside sources (such as JSON payloads) is required func (v *CompressionAlgorithm) Accept(value interface{}) error { var tmp CompressionAlgorithm if x, ok := value.(CompressionAlgorithm); ok { tmp = x } else { var s string switch x := value.(type) { case fmt.Stringer: s = x.String() case string: s = x default: return fmt.Errorf(`invalid type for jwa.CompressionAlgorithm: %T`, value) } tmp = CompressionAlgorithm(s) } if _, ok := allCompressionAlgorithms[tmp]; !ok { return fmt.Errorf(`invalid jwa.CompressionAlgorithm value`) } *v = tmp return nil } // String returns the string representation of a CompressionAlgorithm func (v CompressionAlgorithm) String() string { return string(v) } golang-github-lestrrat-go-jwx-2.1.4/jwa/compression_gen_test.go000066400000000000000000000125531476711647200247020ustar00rootroot00000000000000// Code generated by tools/cmd/genjwa/main.go. DO NOT EDIT package jwa_test import ( "testing" "github.com/lestrrat-go/jwx/v2/jwa" "github.com/stretchr/testify/assert" ) func TestCompressionAlgorithm(t *testing.T) { t.Parallel() t.Run(`accept jwa constant Deflate`, func(t *testing.T) { t.Parallel() var dst jwa.CompressionAlgorithm if !assert.NoError(t, dst.Accept(jwa.Deflate), `accept is successful`) { return } if !assert.Equal(t, jwa.Deflate, dst, `accepted value should be equal to constant`) { return } }) t.Run(`accept the string DEF`, func(t *testing.T) { t.Parallel() var dst jwa.CompressionAlgorithm if !assert.NoError(t, dst.Accept("DEF"), `accept is successful`) { return } if !assert.Equal(t, jwa.Deflate, dst, `accepted value should be equal to constant`) { return } }) t.Run(`accept fmt.Stringer for DEF`, func(t *testing.T) { t.Parallel() var dst jwa.CompressionAlgorithm if !assert.NoError(t, dst.Accept(stringer{src: "DEF"}), `accept is successful`) { return } if !assert.Equal(t, jwa.Deflate, dst, `accepted value should be equal to constant`) { return } }) t.Run(`stringification for DEF`, func(t *testing.T) { t.Parallel() if !assert.Equal(t, "DEF", jwa.Deflate.String(), `stringified value matches`) { return } }) t.Run(`accept jwa constant NoCompress`, func(t *testing.T) { t.Parallel() var dst jwa.CompressionAlgorithm if !assert.NoError(t, dst.Accept(jwa.NoCompress), `accept is successful`) { return } if !assert.Equal(t, jwa.NoCompress, dst, `accepted value should be equal to constant`) { return } }) t.Run(`accept the string `, func(t *testing.T) { t.Parallel() var dst jwa.CompressionAlgorithm if !assert.NoError(t, dst.Accept(""), `accept is successful`) { return } if !assert.Equal(t, jwa.NoCompress, dst, `accepted value should be equal to constant`) { return } }) t.Run(`accept fmt.Stringer for `, func(t *testing.T) { t.Parallel() var dst jwa.CompressionAlgorithm if !assert.NoError(t, dst.Accept(stringer{src: ""}), `accept is successful`) { return } if !assert.Equal(t, jwa.NoCompress, dst, `accepted value should be equal to constant`) { return } }) t.Run(`stringification for `, func(t *testing.T) { t.Parallel() if !assert.Equal(t, "", jwa.NoCompress.String(), `stringified value matches`) { return } }) t.Run(`bail out on random integer value`, func(t *testing.T) { t.Parallel() var dst jwa.CompressionAlgorithm if !assert.Error(t, dst.Accept(1), `accept should fail`) { return } }) t.Run(`do not accept invalid (totally made up) string value`, func(t *testing.T) { t.Parallel() var dst jwa.CompressionAlgorithm if !assert.Error(t, dst.Accept(`totallyInvalidValue`), `accept should fail`) { return } }) 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() { if _, ok := expected[v]; !assert.True(t, ok, `%s should be in the expected list`, v) { return } delete(expected, v) } if !assert.Len(t, expected, 0) { return } }) } // 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. customAlgorithm := jwa.CompressionAlgorithm("custom-algorithm") // 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(`accept variable used to register custom algorithm`, func(t *testing.T) { t.Parallel() var dst jwa.CompressionAlgorithm if !assert.NoError(t, dst.Accept(customAlgorithm), `accept is successful`) { return } assert.Equal(t, customAlgorithm, dst, `accepted value should be equal to variable`) }) t.Run(`accept the string custom-algorithm`, func(t *testing.T) { t.Parallel() var dst jwa.CompressionAlgorithm if !assert.NoError(t, dst.Accept(`custom-algorithm`), `accept is successful`) { return } assert.Equal(t, customAlgorithm, dst, `accepted value should be equal to variable`) }) t.Run(`accept fmt.Stringer for custom-algorithm`, func(t *testing.T) { t.Parallel() var dst jwa.CompressionAlgorithm if !assert.NoError(t, dst.Accept(stringer{src: `custom-algorithm`}), `accept is successful`) { return } assert.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(`reject variable used to register custom algorithm`, func(t *testing.T) { t.Parallel() var dst jwa.CompressionAlgorithm assert.Error(t, dst.Accept(customAlgorithm), `accept failed`) }) t.Run(`reject the string custom-algorithm`, func(t *testing.T) { t.Parallel() var dst jwa.CompressionAlgorithm assert.Error(t, dst.Accept(`custom-algorithm`), `accept failed`) }) t.Run(`reject fmt.Stringer for custom-algorithm`, func(t *testing.T) { t.Parallel() var dst jwa.CompressionAlgorithm assert.Error(t, dst.Accept(stringer{src: `custom-algorithm`}), `accept failed`) }) }) } golang-github-lestrrat-go-jwx-2.1.4/jwa/content_encryption_gen.go000066400000000000000000000100171476711647200252170ustar00rootroot00000000000000// Code generated by tools/cmd/genjwa/main.go. DO NOT EDIT. package jwa import ( "fmt" "sort" "sync" ) // ContentEncryptionAlgorithm represents the various encryption algorithms as described in https://tools.ietf.org/html/rfc7518#section-5 type ContentEncryptionAlgorithm string // Supported values for ContentEncryptionAlgorithm const ( A128CBC_HS256 ContentEncryptionAlgorithm = "A128CBC-HS256" // AES-CBC + HMAC-SHA256 (128) A128GCM ContentEncryptionAlgorithm = "A128GCM" // AES-GCM (128) A192CBC_HS384 ContentEncryptionAlgorithm = "A192CBC-HS384" // AES-CBC + HMAC-SHA384 (192) A192GCM ContentEncryptionAlgorithm = "A192GCM" // AES-GCM (192) A256CBC_HS512 ContentEncryptionAlgorithm = "A256CBC-HS512" // AES-CBC + HMAC-SHA512 (256) A256GCM ContentEncryptionAlgorithm = "A256GCM" // AES-GCM (256) ) var muContentEncryptionAlgorithms sync.RWMutex var allContentEncryptionAlgorithms map[ContentEncryptionAlgorithm]struct{} var listContentEncryptionAlgorithm []ContentEncryptionAlgorithm func init() { muContentEncryptionAlgorithms.Lock() defer muContentEncryptionAlgorithms.Unlock() allContentEncryptionAlgorithms = make(map[ContentEncryptionAlgorithm]struct{}) allContentEncryptionAlgorithms[A128CBC_HS256] = struct{}{} allContentEncryptionAlgorithms[A128GCM] = struct{}{} allContentEncryptionAlgorithms[A192CBC_HS384] = struct{}{} allContentEncryptionAlgorithms[A192GCM] = struct{}{} allContentEncryptionAlgorithms[A256CBC_HS512] = struct{}{} allContentEncryptionAlgorithms[A256GCM] = struct{}{} rebuildContentEncryptionAlgorithm() } // RegisterContentEncryptionAlgorithm registers a new ContentEncryptionAlgorithm so that the jwx can properly handle the new value. // Duplicates will silently be ignored func RegisterContentEncryptionAlgorithm(v ContentEncryptionAlgorithm) { muContentEncryptionAlgorithms.Lock() defer muContentEncryptionAlgorithms.Unlock() if _, ok := allContentEncryptionAlgorithms[v]; !ok { allContentEncryptionAlgorithms[v] = struct{}{} rebuildContentEncryptionAlgorithm() } } // UnregisterContentEncryptionAlgorithm unregisters a ContentEncryptionAlgorithm from its known database. // Non-existent entries will silently be ignored func UnregisterContentEncryptionAlgorithm(v ContentEncryptionAlgorithm) { muContentEncryptionAlgorithms.Lock() defer muContentEncryptionAlgorithms.Unlock() if _, ok := allContentEncryptionAlgorithms[v]; ok { delete(allContentEncryptionAlgorithms, v) rebuildContentEncryptionAlgorithm() } } func rebuildContentEncryptionAlgorithm() { listContentEncryptionAlgorithm = make([]ContentEncryptionAlgorithm, 0, len(allContentEncryptionAlgorithms)) for v := range allContentEncryptionAlgorithms { listContentEncryptionAlgorithm = append(listContentEncryptionAlgorithm, v) } sort.Slice(listContentEncryptionAlgorithm, func(i, j int) bool { return string(listContentEncryptionAlgorithm[i]) < string(listContentEncryptionAlgorithm[j]) }) } // ContentEncryptionAlgorithms returns a list of all available values for ContentEncryptionAlgorithm func ContentEncryptionAlgorithms() []ContentEncryptionAlgorithm { muContentEncryptionAlgorithms.RLock() defer muContentEncryptionAlgorithms.RUnlock() return listContentEncryptionAlgorithm } // Accept is used when conversion from values given by // outside sources (such as JSON payloads) is required func (v *ContentEncryptionAlgorithm) Accept(value interface{}) error { var tmp ContentEncryptionAlgorithm if x, ok := value.(ContentEncryptionAlgorithm); ok { tmp = x } else { var s string switch x := value.(type) { case fmt.Stringer: s = x.String() case string: s = x default: return fmt.Errorf(`invalid type for jwa.ContentEncryptionAlgorithm: %T`, value) } tmp = ContentEncryptionAlgorithm(s) } if _, ok := allContentEncryptionAlgorithms[tmp]; !ok { return fmt.Errorf(`invalid jwa.ContentEncryptionAlgorithm value`) } *v = tmp return nil } // String returns the string representation of a ContentEncryptionAlgorithm func (v ContentEncryptionAlgorithm) String() string { return string(v) } golang-github-lestrrat-go-jwx-2.1.4/jwa/content_encryption_gen_test.go000066400000000000000000000243511476711647200262640ustar00rootroot00000000000000// Code generated by tools/cmd/genjwa/main.go. DO NOT EDIT package jwa_test import ( "testing" "github.com/lestrrat-go/jwx/v2/jwa" "github.com/stretchr/testify/assert" ) func TestContentEncryptionAlgorithm(t *testing.T) { t.Parallel() t.Run(`accept jwa constant A128CBC_HS256`, func(t *testing.T) { t.Parallel() var dst jwa.ContentEncryptionAlgorithm if !assert.NoError(t, dst.Accept(jwa.A128CBC_HS256), `accept is successful`) { return } if !assert.Equal(t, jwa.A128CBC_HS256, dst, `accepted value should be equal to constant`) { return } }) t.Run(`accept the string A128CBC-HS256`, func(t *testing.T) { t.Parallel() var dst jwa.ContentEncryptionAlgorithm if !assert.NoError(t, dst.Accept("A128CBC-HS256"), `accept is successful`) { return } if !assert.Equal(t, jwa.A128CBC_HS256, dst, `accepted value should be equal to constant`) { return } }) t.Run(`accept fmt.Stringer for A128CBC-HS256`, func(t *testing.T) { t.Parallel() var dst jwa.ContentEncryptionAlgorithm if !assert.NoError(t, dst.Accept(stringer{src: "A128CBC-HS256"}), `accept is successful`) { return } if !assert.Equal(t, jwa.A128CBC_HS256, dst, `accepted value should be equal to constant`) { return } }) t.Run(`stringification for A128CBC-HS256`, func(t *testing.T) { t.Parallel() if !assert.Equal(t, "A128CBC-HS256", jwa.A128CBC_HS256.String(), `stringified value matches`) { return } }) t.Run(`accept jwa constant A128GCM`, func(t *testing.T) { t.Parallel() var dst jwa.ContentEncryptionAlgorithm if !assert.NoError(t, dst.Accept(jwa.A128GCM), `accept is successful`) { return } if !assert.Equal(t, jwa.A128GCM, dst, `accepted value should be equal to constant`) { return } }) t.Run(`accept the string A128GCM`, func(t *testing.T) { t.Parallel() var dst jwa.ContentEncryptionAlgorithm if !assert.NoError(t, dst.Accept("A128GCM"), `accept is successful`) { return } if !assert.Equal(t, jwa.A128GCM, dst, `accepted value should be equal to constant`) { return } }) t.Run(`accept fmt.Stringer for A128GCM`, func(t *testing.T) { t.Parallel() var dst jwa.ContentEncryptionAlgorithm if !assert.NoError(t, dst.Accept(stringer{src: "A128GCM"}), `accept is successful`) { return } if !assert.Equal(t, jwa.A128GCM, dst, `accepted value should be equal to constant`) { return } }) t.Run(`stringification for A128GCM`, func(t *testing.T) { t.Parallel() if !assert.Equal(t, "A128GCM", jwa.A128GCM.String(), `stringified value matches`) { return } }) t.Run(`accept jwa constant A192CBC_HS384`, func(t *testing.T) { t.Parallel() var dst jwa.ContentEncryptionAlgorithm if !assert.NoError(t, dst.Accept(jwa.A192CBC_HS384), `accept is successful`) { return } if !assert.Equal(t, jwa.A192CBC_HS384, dst, `accepted value should be equal to constant`) { return } }) t.Run(`accept the string A192CBC-HS384`, func(t *testing.T) { t.Parallel() var dst jwa.ContentEncryptionAlgorithm if !assert.NoError(t, dst.Accept("A192CBC-HS384"), `accept is successful`) { return } if !assert.Equal(t, jwa.A192CBC_HS384, dst, `accepted value should be equal to constant`) { return } }) t.Run(`accept fmt.Stringer for A192CBC-HS384`, func(t *testing.T) { t.Parallel() var dst jwa.ContentEncryptionAlgorithm if !assert.NoError(t, dst.Accept(stringer{src: "A192CBC-HS384"}), `accept is successful`) { return } if !assert.Equal(t, jwa.A192CBC_HS384, dst, `accepted value should be equal to constant`) { return } }) t.Run(`stringification for A192CBC-HS384`, func(t *testing.T) { t.Parallel() if !assert.Equal(t, "A192CBC-HS384", jwa.A192CBC_HS384.String(), `stringified value matches`) { return } }) t.Run(`accept jwa constant A192GCM`, func(t *testing.T) { t.Parallel() var dst jwa.ContentEncryptionAlgorithm if !assert.NoError(t, dst.Accept(jwa.A192GCM), `accept is successful`) { return } if !assert.Equal(t, jwa.A192GCM, dst, `accepted value should be equal to constant`) { return } }) t.Run(`accept the string A192GCM`, func(t *testing.T) { t.Parallel() var dst jwa.ContentEncryptionAlgorithm if !assert.NoError(t, dst.Accept("A192GCM"), `accept is successful`) { return } if !assert.Equal(t, jwa.A192GCM, dst, `accepted value should be equal to constant`) { return } }) t.Run(`accept fmt.Stringer for A192GCM`, func(t *testing.T) { t.Parallel() var dst jwa.ContentEncryptionAlgorithm if !assert.NoError(t, dst.Accept(stringer{src: "A192GCM"}), `accept is successful`) { return } if !assert.Equal(t, jwa.A192GCM, dst, `accepted value should be equal to constant`) { return } }) t.Run(`stringification for A192GCM`, func(t *testing.T) { t.Parallel() if !assert.Equal(t, "A192GCM", jwa.A192GCM.String(), `stringified value matches`) { return } }) t.Run(`accept jwa constant A256CBC_HS512`, func(t *testing.T) { t.Parallel() var dst jwa.ContentEncryptionAlgorithm if !assert.NoError(t, dst.Accept(jwa.A256CBC_HS512), `accept is successful`) { return } if !assert.Equal(t, jwa.A256CBC_HS512, dst, `accepted value should be equal to constant`) { return } }) t.Run(`accept the string A256CBC-HS512`, func(t *testing.T) { t.Parallel() var dst jwa.ContentEncryptionAlgorithm if !assert.NoError(t, dst.Accept("A256CBC-HS512"), `accept is successful`) { return } if !assert.Equal(t, jwa.A256CBC_HS512, dst, `accepted value should be equal to constant`) { return } }) t.Run(`accept fmt.Stringer for A256CBC-HS512`, func(t *testing.T) { t.Parallel() var dst jwa.ContentEncryptionAlgorithm if !assert.NoError(t, dst.Accept(stringer{src: "A256CBC-HS512"}), `accept is successful`) { return } if !assert.Equal(t, jwa.A256CBC_HS512, dst, `accepted value should be equal to constant`) { return } }) t.Run(`stringification for A256CBC-HS512`, func(t *testing.T) { t.Parallel() if !assert.Equal(t, "A256CBC-HS512", jwa.A256CBC_HS512.String(), `stringified value matches`) { return } }) t.Run(`accept jwa constant A256GCM`, func(t *testing.T) { t.Parallel() var dst jwa.ContentEncryptionAlgorithm if !assert.NoError(t, dst.Accept(jwa.A256GCM), `accept is successful`) { return } if !assert.Equal(t, jwa.A256GCM, dst, `accepted value should be equal to constant`) { return } }) t.Run(`accept the string A256GCM`, func(t *testing.T) { t.Parallel() var dst jwa.ContentEncryptionAlgorithm if !assert.NoError(t, dst.Accept("A256GCM"), `accept is successful`) { return } if !assert.Equal(t, jwa.A256GCM, dst, `accepted value should be equal to constant`) { return } }) t.Run(`accept fmt.Stringer for A256GCM`, func(t *testing.T) { t.Parallel() var dst jwa.ContentEncryptionAlgorithm if !assert.NoError(t, dst.Accept(stringer{src: "A256GCM"}), `accept is successful`) { return } if !assert.Equal(t, jwa.A256GCM, dst, `accepted value should be equal to constant`) { return } }) t.Run(`stringification for A256GCM`, func(t *testing.T) { t.Parallel() if !assert.Equal(t, "A256GCM", jwa.A256GCM.String(), `stringified value matches`) { return } }) t.Run(`bail out on random integer value`, func(t *testing.T) { t.Parallel() var dst jwa.ContentEncryptionAlgorithm if !assert.Error(t, dst.Accept(1), `accept should fail`) { return } }) t.Run(`do not accept invalid (totally made up) string value`, func(t *testing.T) { t.Parallel() var dst jwa.ContentEncryptionAlgorithm if !assert.Error(t, dst.Accept(`totallyInvalidValue`), `accept should fail`) { return } }) 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() { if _, ok := expected[v]; !assert.True(t, ok, `%s should be in the expected list`, v) { return } delete(expected, v) } if !assert.Len(t, expected, 0) { return } }) } // 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. customAlgorithm := jwa.ContentEncryptionAlgorithm("custom-algorithm") // 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(`accept variable used to register custom algorithm`, func(t *testing.T) { t.Parallel() var dst jwa.ContentEncryptionAlgorithm if !assert.NoError(t, dst.Accept(customAlgorithm), `accept is successful`) { return } assert.Equal(t, customAlgorithm, dst, `accepted value should be equal to variable`) }) t.Run(`accept the string custom-algorithm`, func(t *testing.T) { t.Parallel() var dst jwa.ContentEncryptionAlgorithm if !assert.NoError(t, dst.Accept(`custom-algorithm`), `accept is successful`) { return } assert.Equal(t, customAlgorithm, dst, `accepted value should be equal to variable`) }) t.Run(`accept fmt.Stringer for custom-algorithm`, func(t *testing.T) { t.Parallel() var dst jwa.ContentEncryptionAlgorithm if !assert.NoError(t, dst.Accept(stringer{src: `custom-algorithm`}), `accept is successful`) { return } assert.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(`reject variable used to register custom algorithm`, func(t *testing.T) { t.Parallel() var dst jwa.ContentEncryptionAlgorithm assert.Error(t, dst.Accept(customAlgorithm), `accept failed`) }) t.Run(`reject the string custom-algorithm`, func(t *testing.T) { t.Parallel() var dst jwa.ContentEncryptionAlgorithm assert.Error(t, dst.Accept(`custom-algorithm`), `accept failed`) }) t.Run(`reject fmt.Stringer for custom-algorithm`, func(t *testing.T) { t.Parallel() var dst jwa.ContentEncryptionAlgorithm assert.Error(t, dst.Accept(stringer{src: `custom-algorithm`}), `accept failed`) }) }) } golang-github-lestrrat-go-jwx-2.1.4/jwa/elliptic_gen.go000066400000000000000000000072521476711647200231070ustar00rootroot00000000000000// Code generated by tools/cmd/genjwa/main.go. DO NOT EDIT. package jwa import ( "fmt" "sort" "sync" ) // EllipticCurveAlgorithm represents the algorithms used for EC keys type EllipticCurveAlgorithm string // Supported values for EllipticCurveAlgorithm const ( Ed25519 EllipticCurveAlgorithm = "Ed25519" Ed448 EllipticCurveAlgorithm = "Ed448" InvalidEllipticCurve EllipticCurveAlgorithm = "P-invalid" P256 EllipticCurveAlgorithm = "P-256" P384 EllipticCurveAlgorithm = "P-384" P521 EllipticCurveAlgorithm = "P-521" X25519 EllipticCurveAlgorithm = "X25519" X448 EllipticCurveAlgorithm = "X448" ) var muEllipticCurveAlgorithms sync.RWMutex var allEllipticCurveAlgorithms map[EllipticCurveAlgorithm]struct{} var listEllipticCurveAlgorithm []EllipticCurveAlgorithm func init() { muEllipticCurveAlgorithms.Lock() defer muEllipticCurveAlgorithms.Unlock() allEllipticCurveAlgorithms = make(map[EllipticCurveAlgorithm]struct{}) allEllipticCurveAlgorithms[Ed25519] = struct{}{} allEllipticCurveAlgorithms[Ed448] = struct{}{} allEllipticCurveAlgorithms[P256] = struct{}{} allEllipticCurveAlgorithms[P384] = struct{}{} allEllipticCurveAlgorithms[P521] = struct{}{} allEllipticCurveAlgorithms[X25519] = struct{}{} allEllipticCurveAlgorithms[X448] = struct{}{} rebuildEllipticCurveAlgorithm() } // RegisterEllipticCurveAlgorithm registers a new EllipticCurveAlgorithm so that the jwx can properly handle the new value. // Duplicates will silently be ignored func RegisterEllipticCurveAlgorithm(v EllipticCurveAlgorithm) { muEllipticCurveAlgorithms.Lock() defer muEllipticCurveAlgorithms.Unlock() if _, ok := allEllipticCurveAlgorithms[v]; !ok { allEllipticCurveAlgorithms[v] = struct{}{} rebuildEllipticCurveAlgorithm() } } // UnregisterEllipticCurveAlgorithm unregisters a EllipticCurveAlgorithm from its known database. // Non-existent entries will silently be ignored func UnregisterEllipticCurveAlgorithm(v EllipticCurveAlgorithm) { muEllipticCurveAlgorithms.Lock() defer muEllipticCurveAlgorithms.Unlock() if _, ok := allEllipticCurveAlgorithms[v]; ok { delete(allEllipticCurveAlgorithms, v) rebuildEllipticCurveAlgorithm() } } func rebuildEllipticCurveAlgorithm() { listEllipticCurveAlgorithm = make([]EllipticCurveAlgorithm, 0, len(allEllipticCurveAlgorithms)) for v := range allEllipticCurveAlgorithms { listEllipticCurveAlgorithm = append(listEllipticCurveAlgorithm, v) } sort.Slice(listEllipticCurveAlgorithm, func(i, j int) bool { return string(listEllipticCurveAlgorithm[i]) < string(listEllipticCurveAlgorithm[j]) }) } // EllipticCurveAlgorithms returns a list of all available values for EllipticCurveAlgorithm func EllipticCurveAlgorithms() []EllipticCurveAlgorithm { muEllipticCurveAlgorithms.RLock() defer muEllipticCurveAlgorithms.RUnlock() return listEllipticCurveAlgorithm } // Accept is used when conversion from values given by // outside sources (such as JSON payloads) is required func (v *EllipticCurveAlgorithm) Accept(value interface{}) error { var tmp EllipticCurveAlgorithm if x, ok := value.(EllipticCurveAlgorithm); ok { tmp = x } else { var s string switch x := value.(type) { case fmt.Stringer: s = x.String() case string: s = x default: return fmt.Errorf(`invalid type for jwa.EllipticCurveAlgorithm: %T`, value) } tmp = EllipticCurveAlgorithm(s) } if _, ok := allEllipticCurveAlgorithms[tmp]; !ok { return fmt.Errorf(`invalid jwa.EllipticCurveAlgorithm value`) } *v = tmp return nil } // String returns the string representation of a EllipticCurveAlgorithm func (v EllipticCurveAlgorithm) String() string { return string(v) } golang-github-lestrrat-go-jwx-2.1.4/jwa/elliptic_gen_test.go000066400000000000000000000262711476711647200241500ustar00rootroot00000000000000// Code generated by tools/cmd/genjwa/main.go. DO NOT EDIT package jwa_test import ( "testing" "github.com/lestrrat-go/jwx/v2/jwa" "github.com/stretchr/testify/assert" ) func TestEllipticCurveAlgorithm(t *testing.T) { t.Parallel() t.Run(`accept jwa constant Ed25519`, func(t *testing.T) { t.Parallel() var dst jwa.EllipticCurveAlgorithm if !assert.NoError(t, dst.Accept(jwa.Ed25519), `accept is successful`) { return } if !assert.Equal(t, jwa.Ed25519, dst, `accepted value should be equal to constant`) { return } }) t.Run(`accept the string Ed25519`, func(t *testing.T) { t.Parallel() var dst jwa.EllipticCurveAlgorithm if !assert.NoError(t, dst.Accept("Ed25519"), `accept is successful`) { return } if !assert.Equal(t, jwa.Ed25519, dst, `accepted value should be equal to constant`) { return } }) t.Run(`accept fmt.Stringer for Ed25519`, func(t *testing.T) { t.Parallel() var dst jwa.EllipticCurveAlgorithm if !assert.NoError(t, dst.Accept(stringer{src: "Ed25519"}), `accept is successful`) { return } if !assert.Equal(t, jwa.Ed25519, dst, `accepted value should be equal to constant`) { return } }) t.Run(`stringification for Ed25519`, func(t *testing.T) { t.Parallel() if !assert.Equal(t, "Ed25519", jwa.Ed25519.String(), `stringified value matches`) { return } }) t.Run(`accept jwa constant Ed448`, func(t *testing.T) { t.Parallel() var dst jwa.EllipticCurveAlgorithm if !assert.NoError(t, dst.Accept(jwa.Ed448), `accept is successful`) { return } if !assert.Equal(t, jwa.Ed448, dst, `accepted value should be equal to constant`) { return } }) t.Run(`accept the string Ed448`, func(t *testing.T) { t.Parallel() var dst jwa.EllipticCurveAlgorithm if !assert.NoError(t, dst.Accept("Ed448"), `accept is successful`) { return } if !assert.Equal(t, jwa.Ed448, dst, `accepted value should be equal to constant`) { return } }) t.Run(`accept fmt.Stringer for Ed448`, func(t *testing.T) { t.Parallel() var dst jwa.EllipticCurveAlgorithm if !assert.NoError(t, dst.Accept(stringer{src: "Ed448"}), `accept is successful`) { return } if !assert.Equal(t, jwa.Ed448, dst, `accepted value should be equal to constant`) { return } }) t.Run(`stringification for Ed448`, func(t *testing.T) { t.Parallel() if !assert.Equal(t, "Ed448", jwa.Ed448.String(), `stringified value matches`) { return } }) t.Run(`accept jwa constant P256`, func(t *testing.T) { t.Parallel() var dst jwa.EllipticCurveAlgorithm if !assert.NoError(t, dst.Accept(jwa.P256), `accept is successful`) { return } if !assert.Equal(t, jwa.P256, dst, `accepted value should be equal to constant`) { return } }) t.Run(`accept the string P-256`, func(t *testing.T) { t.Parallel() var dst jwa.EllipticCurveAlgorithm if !assert.NoError(t, dst.Accept("P-256"), `accept is successful`) { return } if !assert.Equal(t, jwa.P256, dst, `accepted value should be equal to constant`) { return } }) t.Run(`accept fmt.Stringer for P-256`, func(t *testing.T) { t.Parallel() var dst jwa.EllipticCurveAlgorithm if !assert.NoError(t, dst.Accept(stringer{src: "P-256"}), `accept is successful`) { return } if !assert.Equal(t, jwa.P256, dst, `accepted value should be equal to constant`) { return } }) t.Run(`stringification for P-256`, func(t *testing.T) { t.Parallel() if !assert.Equal(t, "P-256", jwa.P256.String(), `stringified value matches`) { return } }) t.Run(`accept jwa constant P384`, func(t *testing.T) { t.Parallel() var dst jwa.EllipticCurveAlgorithm if !assert.NoError(t, dst.Accept(jwa.P384), `accept is successful`) { return } if !assert.Equal(t, jwa.P384, dst, `accepted value should be equal to constant`) { return } }) t.Run(`accept the string P-384`, func(t *testing.T) { t.Parallel() var dst jwa.EllipticCurveAlgorithm if !assert.NoError(t, dst.Accept("P-384"), `accept is successful`) { return } if !assert.Equal(t, jwa.P384, dst, `accepted value should be equal to constant`) { return } }) t.Run(`accept fmt.Stringer for P-384`, func(t *testing.T) { t.Parallel() var dst jwa.EllipticCurveAlgorithm if !assert.NoError(t, dst.Accept(stringer{src: "P-384"}), `accept is successful`) { return } if !assert.Equal(t, jwa.P384, dst, `accepted value should be equal to constant`) { return } }) t.Run(`stringification for P-384`, func(t *testing.T) { t.Parallel() if !assert.Equal(t, "P-384", jwa.P384.String(), `stringified value matches`) { return } }) t.Run(`accept jwa constant P521`, func(t *testing.T) { t.Parallel() var dst jwa.EllipticCurveAlgorithm if !assert.NoError(t, dst.Accept(jwa.P521), `accept is successful`) { return } if !assert.Equal(t, jwa.P521, dst, `accepted value should be equal to constant`) { return } }) t.Run(`accept the string P-521`, func(t *testing.T) { t.Parallel() var dst jwa.EllipticCurveAlgorithm if !assert.NoError(t, dst.Accept("P-521"), `accept is successful`) { return } if !assert.Equal(t, jwa.P521, dst, `accepted value should be equal to constant`) { return } }) t.Run(`accept fmt.Stringer for P-521`, func(t *testing.T) { t.Parallel() var dst jwa.EllipticCurveAlgorithm if !assert.NoError(t, dst.Accept(stringer{src: "P-521"}), `accept is successful`) { return } if !assert.Equal(t, jwa.P521, dst, `accepted value should be equal to constant`) { return } }) t.Run(`stringification for P-521`, func(t *testing.T) { t.Parallel() if !assert.Equal(t, "P-521", jwa.P521.String(), `stringified value matches`) { return } }) t.Run(`accept jwa constant X25519`, func(t *testing.T) { t.Parallel() var dst jwa.EllipticCurveAlgorithm if !assert.NoError(t, dst.Accept(jwa.X25519), `accept is successful`) { return } if !assert.Equal(t, jwa.X25519, dst, `accepted value should be equal to constant`) { return } }) t.Run(`accept the string X25519`, func(t *testing.T) { t.Parallel() var dst jwa.EllipticCurveAlgorithm if !assert.NoError(t, dst.Accept("X25519"), `accept is successful`) { return } if !assert.Equal(t, jwa.X25519, dst, `accepted value should be equal to constant`) { return } }) t.Run(`accept fmt.Stringer for X25519`, func(t *testing.T) { t.Parallel() var dst jwa.EllipticCurveAlgorithm if !assert.NoError(t, dst.Accept(stringer{src: "X25519"}), `accept is successful`) { return } if !assert.Equal(t, jwa.X25519, dst, `accepted value should be equal to constant`) { return } }) t.Run(`stringification for X25519`, func(t *testing.T) { t.Parallel() if !assert.Equal(t, "X25519", jwa.X25519.String(), `stringified value matches`) { return } }) t.Run(`accept jwa constant X448`, func(t *testing.T) { t.Parallel() var dst jwa.EllipticCurveAlgorithm if !assert.NoError(t, dst.Accept(jwa.X448), `accept is successful`) { return } if !assert.Equal(t, jwa.X448, dst, `accepted value should be equal to constant`) { return } }) t.Run(`accept the string X448`, func(t *testing.T) { t.Parallel() var dst jwa.EllipticCurveAlgorithm if !assert.NoError(t, dst.Accept("X448"), `accept is successful`) { return } if !assert.Equal(t, jwa.X448, dst, `accepted value should be equal to constant`) { return } }) t.Run(`accept fmt.Stringer for X448`, func(t *testing.T) { t.Parallel() var dst jwa.EllipticCurveAlgorithm if !assert.NoError(t, dst.Accept(stringer{src: "X448"}), `accept is successful`) { return } if !assert.Equal(t, jwa.X448, dst, `accepted value should be equal to constant`) { return } }) t.Run(`stringification for X448`, func(t *testing.T) { t.Parallel() if !assert.Equal(t, "X448", jwa.X448.String(), `stringified value matches`) { return } }) t.Run(`do not accept invalid constant InvalidEllipticCurve`, func(t *testing.T) { t.Parallel() var dst jwa.EllipticCurveAlgorithm if !assert.Error(t, dst.Accept(jwa.InvalidEllipticCurve), `accept should fail`) { return } }) t.Run(`bail out on random integer value`, func(t *testing.T) { t.Parallel() var dst jwa.EllipticCurveAlgorithm if !assert.Error(t, dst.Accept(1), `accept should fail`) { return } }) t.Run(`do not accept invalid (totally made up) string value`, func(t *testing.T) { t.Parallel() var dst jwa.EllipticCurveAlgorithm if !assert.Error(t, dst.Accept(`totallyInvalidValue`), `accept should fail`) { return } }) 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 } if _, ok := expected[v]; !assert.True(t, ok, `%s should be in the expected list`, v) { return } delete(expected, v) } if !assert.Len(t, expected, 0) { return } }) } // 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. customAlgorithm := jwa.EllipticCurveAlgorithm("custom-algorithm") // 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(`accept variable used to register custom algorithm`, func(t *testing.T) { t.Parallel() var dst jwa.EllipticCurveAlgorithm if !assert.NoError(t, dst.Accept(customAlgorithm), `accept is successful`) { return } assert.Equal(t, customAlgorithm, dst, `accepted value should be equal to variable`) }) t.Run(`accept the string custom-algorithm`, func(t *testing.T) { t.Parallel() var dst jwa.EllipticCurveAlgorithm if !assert.NoError(t, dst.Accept(`custom-algorithm`), `accept is successful`) { return } assert.Equal(t, customAlgorithm, dst, `accepted value should be equal to variable`) }) t.Run(`accept fmt.Stringer for custom-algorithm`, func(t *testing.T) { t.Parallel() var dst jwa.EllipticCurveAlgorithm if !assert.NoError(t, dst.Accept(stringer{src: `custom-algorithm`}), `accept is successful`) { return } assert.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(`reject variable used to register custom algorithm`, func(t *testing.T) { t.Parallel() var dst jwa.EllipticCurveAlgorithm assert.Error(t, dst.Accept(customAlgorithm), `accept failed`) }) t.Run(`reject the string custom-algorithm`, func(t *testing.T) { t.Parallel() var dst jwa.EllipticCurveAlgorithm assert.Error(t, dst.Accept(`custom-algorithm`), `accept failed`) }) t.Run(`reject fmt.Stringer for custom-algorithm`, func(t *testing.T) { t.Parallel() var dst jwa.EllipticCurveAlgorithm assert.Error(t, dst.Accept(stringer{src: `custom-algorithm`}), `accept failed`) }) }) } golang-github-lestrrat-go-jwx-2.1.4/jwa/jwa.go000066400000000000000000000035431476711647200212310ustar00rootroot00000000000000//go:generate ../tools/cmd/genjwa.sh // Package jwa defines the various algorithm described in https://tools.ietf.org/html/rfc7518 package jwa import "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 } // InvalidKeyAlgorithm represents an algorithm that the library is not aware of. type InvalidKeyAlgorithm string func (s InvalidKeyAlgorithm) String() string { return string(s) } func (InvalidKeyAlgorithm) Accept(_ interface{}) error { return fmt.Errorf(`jwa.InvalidKeyAlgorithm does not support Accept() method calls`) } // KeyAlgorithmFrom takes either a string, `jwa.SignatureAlgorithm` or `jwa.KeyEncryptionAlgorithm` // 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 interface{}) KeyAlgorithm { switch v := v.(type) { case SignatureAlgorithm: return v case KeyEncryptionAlgorithm: return v case string: var salg SignatureAlgorithm if err := salg.Accept(v); err == nil { return salg } var kealg KeyEncryptionAlgorithm if err := kealg.Accept(v); err == nil { return kealg } return InvalidKeyAlgorithm(v) default: return InvalidKeyAlgorithm(fmt.Sprintf("%s", v)) } } golang-github-lestrrat-go-jwx-2.1.4/jwa/jwa_test.go000066400000000000000000000026741476711647200222740ustar00rootroot00000000000000package jwa_test import ( "fmt" "testing" "github.com/lestrrat-go/jwx/v2/jwa" "github.com/stretchr/testify/assert" ) type stringer struct { src string } func (s stringer) String() string { return s.src } func TestSanity(t *testing.T) { var k1 jwa.KeyAlgorithm = jwa.RS256 if _, ok := k1.(jwa.SignatureAlgorithm); !assert.True(t, ok, `converting k1 to jws.SignatureAlgorithm should succeed`) { return } if _, ok := k1.(jwa.KeyEncryptionAlgorithm); !assert.False(t, ok, `converting k1 to jws.KeyEncryptionAlgorithm should fail`) { return } var k2 jwa.KeyAlgorithm = jwa.DIRECT if _, ok := k2.(jwa.SignatureAlgorithm); !assert.False(t, ok, `converting k2 to jws.SignatureAlgorithm should fail`) { return } if _, ok := k2.(jwa.KeyEncryptionAlgorithm); !assert.True(t, ok, `converting k2 to jws.KeyEncryptionAlgorithm should succeed`) { return } } func TestKeyAlgorithmFrom(t *testing.T) { testcases := []struct { Input interface{} Error bool }{ { Input: jwa.RS256, }, { Input: jwa.DIRECT, }, { Input: jwa.A128CBC_HS256, Error: true, }, } for _, tc := range testcases { tc := tc t.Run(fmt.Sprintf("%T", tc.Input), func(t *testing.T) { alg := jwa.KeyAlgorithmFrom(tc.Input) if tc.Error { if !assert.IsType(t, alg, jwa.InvalidKeyAlgorithm(""), `key should be invalid`) { return } } else { if !assert.Equal(t, alg, tc.Input, `key should be valid`) { return } } }) } } golang-github-lestrrat-go-jwx-2.1.4/jwa/key_encryption_gen.go000066400000000000000000000173151476711647200243450ustar00rootroot00000000000000// Code generated by tools/cmd/genjwa/main.go. DO NOT EDIT. package jwa import ( "fmt" "sort" "sync" ) // KeyEncryptionAlgorithm represents the various encryption algorithms as described in https://tools.ietf.org/html/rfc7518#section-4.1 type KeyEncryptionAlgorithm string // Supported values for KeyEncryptionAlgorithm const ( A128GCMKW KeyEncryptionAlgorithm = "A128GCMKW" // AES-GCM key wrap (128) A128KW KeyEncryptionAlgorithm = "A128KW" // AES key wrap (128) A192GCMKW KeyEncryptionAlgorithm = "A192GCMKW" // AES-GCM key wrap (192) A192KW KeyEncryptionAlgorithm = "A192KW" // AES key wrap (192) A256GCMKW KeyEncryptionAlgorithm = "A256GCMKW" // AES-GCM key wrap (256) A256KW KeyEncryptionAlgorithm = "A256KW" // AES key wrap (256) DIRECT KeyEncryptionAlgorithm = "dir" // Direct encryption ECDH_ES KeyEncryptionAlgorithm = "ECDH-ES" // ECDH-ES ECDH_ES_A128KW KeyEncryptionAlgorithm = "ECDH-ES+A128KW" // ECDH-ES + AES key wrap (128) ECDH_ES_A192KW KeyEncryptionAlgorithm = "ECDH-ES+A192KW" // ECDH-ES + AES key wrap (192) ECDH_ES_A256KW KeyEncryptionAlgorithm = "ECDH-ES+A256KW" // ECDH-ES + AES key wrap (256) PBES2_HS256_A128KW KeyEncryptionAlgorithm = "PBES2-HS256+A128KW" // PBES2 + HMAC-SHA256 + AES key wrap (128) PBES2_HS384_A192KW KeyEncryptionAlgorithm = "PBES2-HS384+A192KW" // PBES2 + HMAC-SHA384 + AES key wrap (192) PBES2_HS512_A256KW KeyEncryptionAlgorithm = "PBES2-HS512+A256KW" // PBES2 + HMAC-SHA512 + AES key wrap (256) RSA1_5 KeyEncryptionAlgorithm = "RSA1_5" // RSA-PKCS1v1.5 RSA_OAEP KeyEncryptionAlgorithm = "RSA-OAEP" // RSA-OAEP-SHA1 RSA_OAEP_256 KeyEncryptionAlgorithm = "RSA-OAEP-256" // RSA-OAEP-SHA256 RSA_OAEP_384 KeyEncryptionAlgorithm = "RSA-OAEP-384" // RSA-OAEP-SHA384 RSA_OAEP_512 KeyEncryptionAlgorithm = "RSA-OAEP-512" // RSA-OAEP-SHA512 ) var muKeyEncryptionAlgorithms sync.RWMutex var allKeyEncryptionAlgorithms map[KeyEncryptionAlgorithm]struct{} var listKeyEncryptionAlgorithm []KeyEncryptionAlgorithm var symmetricKeyEncryptionAlgorithms map[KeyEncryptionAlgorithm]struct{} func init() { muKeyEncryptionAlgorithms.Lock() defer muKeyEncryptionAlgorithms.Unlock() allKeyEncryptionAlgorithms = make(map[KeyEncryptionAlgorithm]struct{}) allKeyEncryptionAlgorithms[A128GCMKW] = struct{}{} allKeyEncryptionAlgorithms[A128KW] = struct{}{} allKeyEncryptionAlgorithms[A192GCMKW] = struct{}{} allKeyEncryptionAlgorithms[A192KW] = struct{}{} allKeyEncryptionAlgorithms[A256GCMKW] = struct{}{} allKeyEncryptionAlgorithms[A256KW] = struct{}{} allKeyEncryptionAlgorithms[DIRECT] = struct{}{} allKeyEncryptionAlgorithms[ECDH_ES] = struct{}{} allKeyEncryptionAlgorithms[ECDH_ES_A128KW] = struct{}{} allKeyEncryptionAlgorithms[ECDH_ES_A192KW] = struct{}{} allKeyEncryptionAlgorithms[ECDH_ES_A256KW] = struct{}{} allKeyEncryptionAlgorithms[PBES2_HS256_A128KW] = struct{}{} allKeyEncryptionAlgorithms[PBES2_HS384_A192KW] = struct{}{} allKeyEncryptionAlgorithms[PBES2_HS512_A256KW] = struct{}{} allKeyEncryptionAlgorithms[RSA1_5] = struct{}{} allKeyEncryptionAlgorithms[RSA_OAEP] = struct{}{} allKeyEncryptionAlgorithms[RSA_OAEP_256] = struct{}{} allKeyEncryptionAlgorithms[RSA_OAEP_384] = struct{}{} allKeyEncryptionAlgorithms[RSA_OAEP_512] = struct{}{} symmetricKeyEncryptionAlgorithms = make(map[KeyEncryptionAlgorithm]struct{}) symmetricKeyEncryptionAlgorithms[A128GCMKW] = struct{}{} symmetricKeyEncryptionAlgorithms[A128KW] = struct{}{} symmetricKeyEncryptionAlgorithms[A192GCMKW] = struct{}{} symmetricKeyEncryptionAlgorithms[A192KW] = struct{}{} symmetricKeyEncryptionAlgorithms[A256GCMKW] = struct{}{} symmetricKeyEncryptionAlgorithms[A256KW] = struct{}{} symmetricKeyEncryptionAlgorithms[DIRECT] = struct{}{} symmetricKeyEncryptionAlgorithms[PBES2_HS256_A128KW] = struct{}{} symmetricKeyEncryptionAlgorithms[PBES2_HS384_A192KW] = struct{}{} symmetricKeyEncryptionAlgorithms[PBES2_HS512_A256KW] = struct{}{} rebuildKeyEncryptionAlgorithm() } // RegisterKeyEncryptionAlgorithm registers a new KeyEncryptionAlgorithm so that the jwx can properly handle the new value. // Duplicates will silently be ignored func RegisterKeyEncryptionAlgorithm(v KeyEncryptionAlgorithm) { RegisterKeyEncryptionAlgorithmWithOptions(v) } // RegisterKeyEncryptionAlgorithmWithOptions is the same as RegisterKeyEncryptionAlgorithm when used without options, // but allows its behavior to change based on the provided options. // This is an experimental AND stopgap function which will most likely be merged in RegisterKeyEncryptionAlgorithm, and subsequently removed in the future. As such it should not be considered part of the stable API -- it is still subject to change. // // You can pass `WithSymmetricAlgorithm(true)` to let the library know that it's a symmetric algorithm. This library makes no attempt to verify if the algorithm is indeed symmetric or not. func RegisterKeyEncryptionAlgorithmWithOptions(v KeyEncryptionAlgorithm, options ...RegisterAlgorithmOption) { var symmetric bool //nolint:forcetypeassert for _, option := range options { switch option.Ident() { case identSymmetricAlgorithm{}: symmetric = option.Value().(bool) } } muKeyEncryptionAlgorithms.Lock() defer muKeyEncryptionAlgorithms.Unlock() if _, ok := allKeyEncryptionAlgorithms[v]; !ok { allKeyEncryptionAlgorithms[v] = struct{}{} if symmetric { symmetricKeyEncryptionAlgorithms[v] = struct{}{} } rebuildKeyEncryptionAlgorithm() } } // UnregisterKeyEncryptionAlgorithm unregisters a KeyEncryptionAlgorithm from its known database. // Non-existent entries will silently be ignored func UnregisterKeyEncryptionAlgorithm(v KeyEncryptionAlgorithm) { muKeyEncryptionAlgorithms.Lock() defer muKeyEncryptionAlgorithms.Unlock() if _, ok := allKeyEncryptionAlgorithms[v]; ok { delete(allKeyEncryptionAlgorithms, v) if _, ok := symmetricKeyEncryptionAlgorithms[v]; ok { delete(symmetricKeyEncryptionAlgorithms, v) } rebuildKeyEncryptionAlgorithm() } } func rebuildKeyEncryptionAlgorithm() { listKeyEncryptionAlgorithm = make([]KeyEncryptionAlgorithm, 0, len(allKeyEncryptionAlgorithms)) for v := range allKeyEncryptionAlgorithms { listKeyEncryptionAlgorithm = append(listKeyEncryptionAlgorithm, v) } sort.Slice(listKeyEncryptionAlgorithm, func(i, j int) bool { return string(listKeyEncryptionAlgorithm[i]) < string(listKeyEncryptionAlgorithm[j]) }) } // KeyEncryptionAlgorithms returns a list of all available values for KeyEncryptionAlgorithm func KeyEncryptionAlgorithms() []KeyEncryptionAlgorithm { muKeyEncryptionAlgorithms.RLock() defer muKeyEncryptionAlgorithms.RUnlock() return listKeyEncryptionAlgorithm } // Accept is used when conversion from values given by // outside sources (such as JSON payloads) is required func (v *KeyEncryptionAlgorithm) Accept(value interface{}) error { var tmp KeyEncryptionAlgorithm if x, ok := value.(KeyEncryptionAlgorithm); ok { tmp = x } else { var s string switch x := value.(type) { case fmt.Stringer: s = x.String() case string: s = x default: return fmt.Errorf(`invalid type for jwa.KeyEncryptionAlgorithm: %T`, value) } tmp = KeyEncryptionAlgorithm(s) } if _, ok := allKeyEncryptionAlgorithms[tmp]; !ok { return fmt.Errorf(`invalid jwa.KeyEncryptionAlgorithm value`) } *v = tmp return nil } // String returns the string representation of a KeyEncryptionAlgorithm func (v KeyEncryptionAlgorithm) String() string { return string(v) } // IsSymmetric returns true if the algorithm is a symmetric type. func (v KeyEncryptionAlgorithm) IsSymmetric() bool { _, ok := symmetricKeyEncryptionAlgorithms[v] return ok } golang-github-lestrrat-go-jwx-2.1.4/jwa/key_encryption_gen_test.go000066400000000000000000001011451476711647200253770ustar00rootroot00000000000000// Code generated by tools/cmd/genjwa/main.go. DO NOT EDIT package jwa_test import ( "testing" "github.com/lestrrat-go/jwx/v2/jwa" "github.com/stretchr/testify/assert" ) func TestKeyEncryptionAlgorithm(t *testing.T) { t.Parallel() t.Run(`accept jwa constant A128GCMKW`, func(t *testing.T) { t.Parallel() var dst jwa.KeyEncryptionAlgorithm if !assert.NoError(t, dst.Accept(jwa.A128GCMKW), `accept is successful`) { return } if !assert.Equal(t, jwa.A128GCMKW, dst, `accepted value should be equal to constant`) { return } }) t.Run(`accept the string A128GCMKW`, func(t *testing.T) { t.Parallel() var dst jwa.KeyEncryptionAlgorithm if !assert.NoError(t, dst.Accept("A128GCMKW"), `accept is successful`) { return } if !assert.Equal(t, jwa.A128GCMKW, dst, `accepted value should be equal to constant`) { return } }) t.Run(`accept fmt.Stringer for A128GCMKW`, func(t *testing.T) { t.Parallel() var dst jwa.KeyEncryptionAlgorithm if !assert.NoError(t, dst.Accept(stringer{src: "A128GCMKW"}), `accept is successful`) { return } if !assert.Equal(t, jwa.A128GCMKW, dst, `accepted value should be equal to constant`) { return } }) t.Run(`stringification for A128GCMKW`, func(t *testing.T) { t.Parallel() if !assert.Equal(t, "A128GCMKW", jwa.A128GCMKW.String(), `stringified value matches`) { return } }) t.Run(`accept jwa constant A128KW`, func(t *testing.T) { t.Parallel() var dst jwa.KeyEncryptionAlgorithm if !assert.NoError(t, dst.Accept(jwa.A128KW), `accept is successful`) { return } if !assert.Equal(t, jwa.A128KW, dst, `accepted value should be equal to constant`) { return } }) t.Run(`accept the string A128KW`, func(t *testing.T) { t.Parallel() var dst jwa.KeyEncryptionAlgorithm if !assert.NoError(t, dst.Accept("A128KW"), `accept is successful`) { return } if !assert.Equal(t, jwa.A128KW, dst, `accepted value should be equal to constant`) { return } }) t.Run(`accept fmt.Stringer for A128KW`, func(t *testing.T) { t.Parallel() var dst jwa.KeyEncryptionAlgorithm if !assert.NoError(t, dst.Accept(stringer{src: "A128KW"}), `accept is successful`) { return } if !assert.Equal(t, jwa.A128KW, dst, `accepted value should be equal to constant`) { return } }) t.Run(`stringification for A128KW`, func(t *testing.T) { t.Parallel() if !assert.Equal(t, "A128KW", jwa.A128KW.String(), `stringified value matches`) { return } }) t.Run(`accept jwa constant A192GCMKW`, func(t *testing.T) { t.Parallel() var dst jwa.KeyEncryptionAlgorithm if !assert.NoError(t, dst.Accept(jwa.A192GCMKW), `accept is successful`) { return } if !assert.Equal(t, jwa.A192GCMKW, dst, `accepted value should be equal to constant`) { return } }) t.Run(`accept the string A192GCMKW`, func(t *testing.T) { t.Parallel() var dst jwa.KeyEncryptionAlgorithm if !assert.NoError(t, dst.Accept("A192GCMKW"), `accept is successful`) { return } if !assert.Equal(t, jwa.A192GCMKW, dst, `accepted value should be equal to constant`) { return } }) t.Run(`accept fmt.Stringer for A192GCMKW`, func(t *testing.T) { t.Parallel() var dst jwa.KeyEncryptionAlgorithm if !assert.NoError(t, dst.Accept(stringer{src: "A192GCMKW"}), `accept is successful`) { return } if !assert.Equal(t, jwa.A192GCMKW, dst, `accepted value should be equal to constant`) { return } }) t.Run(`stringification for A192GCMKW`, func(t *testing.T) { t.Parallel() if !assert.Equal(t, "A192GCMKW", jwa.A192GCMKW.String(), `stringified value matches`) { return } }) t.Run(`accept jwa constant A192KW`, func(t *testing.T) { t.Parallel() var dst jwa.KeyEncryptionAlgorithm if !assert.NoError(t, dst.Accept(jwa.A192KW), `accept is successful`) { return } if !assert.Equal(t, jwa.A192KW, dst, `accepted value should be equal to constant`) { return } }) t.Run(`accept the string A192KW`, func(t *testing.T) { t.Parallel() var dst jwa.KeyEncryptionAlgorithm if !assert.NoError(t, dst.Accept("A192KW"), `accept is successful`) { return } if !assert.Equal(t, jwa.A192KW, dst, `accepted value should be equal to constant`) { return } }) t.Run(`accept fmt.Stringer for A192KW`, func(t *testing.T) { t.Parallel() var dst jwa.KeyEncryptionAlgorithm if !assert.NoError(t, dst.Accept(stringer{src: "A192KW"}), `accept is successful`) { return } if !assert.Equal(t, jwa.A192KW, dst, `accepted value should be equal to constant`) { return } }) t.Run(`stringification for A192KW`, func(t *testing.T) { t.Parallel() if !assert.Equal(t, "A192KW", jwa.A192KW.String(), `stringified value matches`) { return } }) t.Run(`accept jwa constant A256GCMKW`, func(t *testing.T) { t.Parallel() var dst jwa.KeyEncryptionAlgorithm if !assert.NoError(t, dst.Accept(jwa.A256GCMKW), `accept is successful`) { return } if !assert.Equal(t, jwa.A256GCMKW, dst, `accepted value should be equal to constant`) { return } }) t.Run(`accept the string A256GCMKW`, func(t *testing.T) { t.Parallel() var dst jwa.KeyEncryptionAlgorithm if !assert.NoError(t, dst.Accept("A256GCMKW"), `accept is successful`) { return } if !assert.Equal(t, jwa.A256GCMKW, dst, `accepted value should be equal to constant`) { return } }) t.Run(`accept fmt.Stringer for A256GCMKW`, func(t *testing.T) { t.Parallel() var dst jwa.KeyEncryptionAlgorithm if !assert.NoError(t, dst.Accept(stringer{src: "A256GCMKW"}), `accept is successful`) { return } if !assert.Equal(t, jwa.A256GCMKW, dst, `accepted value should be equal to constant`) { return } }) t.Run(`stringification for A256GCMKW`, func(t *testing.T) { t.Parallel() if !assert.Equal(t, "A256GCMKW", jwa.A256GCMKW.String(), `stringified value matches`) { return } }) t.Run(`accept jwa constant A256KW`, func(t *testing.T) { t.Parallel() var dst jwa.KeyEncryptionAlgorithm if !assert.NoError(t, dst.Accept(jwa.A256KW), `accept is successful`) { return } if !assert.Equal(t, jwa.A256KW, dst, `accepted value should be equal to constant`) { return } }) t.Run(`accept the string A256KW`, func(t *testing.T) { t.Parallel() var dst jwa.KeyEncryptionAlgorithm if !assert.NoError(t, dst.Accept("A256KW"), `accept is successful`) { return } if !assert.Equal(t, jwa.A256KW, dst, `accepted value should be equal to constant`) { return } }) t.Run(`accept fmt.Stringer for A256KW`, func(t *testing.T) { t.Parallel() var dst jwa.KeyEncryptionAlgorithm if !assert.NoError(t, dst.Accept(stringer{src: "A256KW"}), `accept is successful`) { return } if !assert.Equal(t, jwa.A256KW, dst, `accepted value should be equal to constant`) { return } }) t.Run(`stringification for A256KW`, func(t *testing.T) { t.Parallel() if !assert.Equal(t, "A256KW", jwa.A256KW.String(), `stringified value matches`) { return } }) t.Run(`accept jwa constant DIRECT`, func(t *testing.T) { t.Parallel() var dst jwa.KeyEncryptionAlgorithm if !assert.NoError(t, dst.Accept(jwa.DIRECT), `accept is successful`) { return } if !assert.Equal(t, jwa.DIRECT, dst, `accepted value should be equal to constant`) { return } }) t.Run(`accept the string dir`, func(t *testing.T) { t.Parallel() var dst jwa.KeyEncryptionAlgorithm if !assert.NoError(t, dst.Accept("dir"), `accept is successful`) { return } if !assert.Equal(t, jwa.DIRECT, dst, `accepted value should be equal to constant`) { return } }) t.Run(`accept fmt.Stringer for dir`, func(t *testing.T) { t.Parallel() var dst jwa.KeyEncryptionAlgorithm if !assert.NoError(t, dst.Accept(stringer{src: "dir"}), `accept is successful`) { return } if !assert.Equal(t, jwa.DIRECT, dst, `accepted value should be equal to constant`) { return } }) t.Run(`stringification for dir`, func(t *testing.T) { t.Parallel() if !assert.Equal(t, "dir", jwa.DIRECT.String(), `stringified value matches`) { return } }) t.Run(`accept jwa constant ECDH_ES`, func(t *testing.T) { t.Parallel() var dst jwa.KeyEncryptionAlgorithm if !assert.NoError(t, dst.Accept(jwa.ECDH_ES), `accept is successful`) { return } if !assert.Equal(t, jwa.ECDH_ES, dst, `accepted value should be equal to constant`) { return } }) t.Run(`accept the string ECDH-ES`, func(t *testing.T) { t.Parallel() var dst jwa.KeyEncryptionAlgorithm if !assert.NoError(t, dst.Accept("ECDH-ES"), `accept is successful`) { return } if !assert.Equal(t, jwa.ECDH_ES, dst, `accepted value should be equal to constant`) { return } }) t.Run(`accept fmt.Stringer for ECDH-ES`, func(t *testing.T) { t.Parallel() var dst jwa.KeyEncryptionAlgorithm if !assert.NoError(t, dst.Accept(stringer{src: "ECDH-ES"}), `accept is successful`) { return } if !assert.Equal(t, jwa.ECDH_ES, dst, `accepted value should be equal to constant`) { return } }) t.Run(`stringification for ECDH-ES`, func(t *testing.T) { t.Parallel() if !assert.Equal(t, "ECDH-ES", jwa.ECDH_ES.String(), `stringified value matches`) { return } }) t.Run(`accept jwa constant ECDH_ES_A128KW`, func(t *testing.T) { t.Parallel() var dst jwa.KeyEncryptionAlgorithm if !assert.NoError(t, dst.Accept(jwa.ECDH_ES_A128KW), `accept is successful`) { return } if !assert.Equal(t, jwa.ECDH_ES_A128KW, dst, `accepted value should be equal to constant`) { return } }) t.Run(`accept the string ECDH-ES+A128KW`, func(t *testing.T) { t.Parallel() var dst jwa.KeyEncryptionAlgorithm if !assert.NoError(t, dst.Accept("ECDH-ES+A128KW"), `accept is successful`) { return } if !assert.Equal(t, jwa.ECDH_ES_A128KW, dst, `accepted value should be equal to constant`) { return } }) t.Run(`accept fmt.Stringer for ECDH-ES+A128KW`, func(t *testing.T) { t.Parallel() var dst jwa.KeyEncryptionAlgorithm if !assert.NoError(t, dst.Accept(stringer{src: "ECDH-ES+A128KW"}), `accept is successful`) { return } if !assert.Equal(t, jwa.ECDH_ES_A128KW, dst, `accepted value should be equal to constant`) { return } }) t.Run(`stringification for ECDH-ES+A128KW`, func(t *testing.T) { t.Parallel() if !assert.Equal(t, "ECDH-ES+A128KW", jwa.ECDH_ES_A128KW.String(), `stringified value matches`) { return } }) t.Run(`accept jwa constant ECDH_ES_A192KW`, func(t *testing.T) { t.Parallel() var dst jwa.KeyEncryptionAlgorithm if !assert.NoError(t, dst.Accept(jwa.ECDH_ES_A192KW), `accept is successful`) { return } if !assert.Equal(t, jwa.ECDH_ES_A192KW, dst, `accepted value should be equal to constant`) { return } }) t.Run(`accept the string ECDH-ES+A192KW`, func(t *testing.T) { t.Parallel() var dst jwa.KeyEncryptionAlgorithm if !assert.NoError(t, dst.Accept("ECDH-ES+A192KW"), `accept is successful`) { return } if !assert.Equal(t, jwa.ECDH_ES_A192KW, dst, `accepted value should be equal to constant`) { return } }) t.Run(`accept fmt.Stringer for ECDH-ES+A192KW`, func(t *testing.T) { t.Parallel() var dst jwa.KeyEncryptionAlgorithm if !assert.NoError(t, dst.Accept(stringer{src: "ECDH-ES+A192KW"}), `accept is successful`) { return } if !assert.Equal(t, jwa.ECDH_ES_A192KW, dst, `accepted value should be equal to constant`) { return } }) t.Run(`stringification for ECDH-ES+A192KW`, func(t *testing.T) { t.Parallel() if !assert.Equal(t, "ECDH-ES+A192KW", jwa.ECDH_ES_A192KW.String(), `stringified value matches`) { return } }) t.Run(`accept jwa constant ECDH_ES_A256KW`, func(t *testing.T) { t.Parallel() var dst jwa.KeyEncryptionAlgorithm if !assert.NoError(t, dst.Accept(jwa.ECDH_ES_A256KW), `accept is successful`) { return } if !assert.Equal(t, jwa.ECDH_ES_A256KW, dst, `accepted value should be equal to constant`) { return } }) t.Run(`accept the string ECDH-ES+A256KW`, func(t *testing.T) { t.Parallel() var dst jwa.KeyEncryptionAlgorithm if !assert.NoError(t, dst.Accept("ECDH-ES+A256KW"), `accept is successful`) { return } if !assert.Equal(t, jwa.ECDH_ES_A256KW, dst, `accepted value should be equal to constant`) { return } }) t.Run(`accept fmt.Stringer for ECDH-ES+A256KW`, func(t *testing.T) { t.Parallel() var dst jwa.KeyEncryptionAlgorithm if !assert.NoError(t, dst.Accept(stringer{src: "ECDH-ES+A256KW"}), `accept is successful`) { return } if !assert.Equal(t, jwa.ECDH_ES_A256KW, dst, `accepted value should be equal to constant`) { return } }) t.Run(`stringification for ECDH-ES+A256KW`, func(t *testing.T) { t.Parallel() if !assert.Equal(t, "ECDH-ES+A256KW", jwa.ECDH_ES_A256KW.String(), `stringified value matches`) { return } }) t.Run(`accept jwa constant PBES2_HS256_A128KW`, func(t *testing.T) { t.Parallel() var dst jwa.KeyEncryptionAlgorithm if !assert.NoError(t, dst.Accept(jwa.PBES2_HS256_A128KW), `accept is successful`) { return } if !assert.Equal(t, jwa.PBES2_HS256_A128KW, dst, `accepted value should be equal to constant`) { return } }) t.Run(`accept the string PBES2-HS256+A128KW`, func(t *testing.T) { t.Parallel() var dst jwa.KeyEncryptionAlgorithm if !assert.NoError(t, dst.Accept("PBES2-HS256+A128KW"), `accept is successful`) { return } if !assert.Equal(t, jwa.PBES2_HS256_A128KW, dst, `accepted value should be equal to constant`) { return } }) t.Run(`accept fmt.Stringer for PBES2-HS256+A128KW`, func(t *testing.T) { t.Parallel() var dst jwa.KeyEncryptionAlgorithm if !assert.NoError(t, dst.Accept(stringer{src: "PBES2-HS256+A128KW"}), `accept is successful`) { return } if !assert.Equal(t, jwa.PBES2_HS256_A128KW, dst, `accepted value should be equal to constant`) { return } }) t.Run(`stringification for PBES2-HS256+A128KW`, func(t *testing.T) { t.Parallel() if !assert.Equal(t, "PBES2-HS256+A128KW", jwa.PBES2_HS256_A128KW.String(), `stringified value matches`) { return } }) t.Run(`accept jwa constant PBES2_HS384_A192KW`, func(t *testing.T) { t.Parallel() var dst jwa.KeyEncryptionAlgorithm if !assert.NoError(t, dst.Accept(jwa.PBES2_HS384_A192KW), `accept is successful`) { return } if !assert.Equal(t, jwa.PBES2_HS384_A192KW, dst, `accepted value should be equal to constant`) { return } }) t.Run(`accept the string PBES2-HS384+A192KW`, func(t *testing.T) { t.Parallel() var dst jwa.KeyEncryptionAlgorithm if !assert.NoError(t, dst.Accept("PBES2-HS384+A192KW"), `accept is successful`) { return } if !assert.Equal(t, jwa.PBES2_HS384_A192KW, dst, `accepted value should be equal to constant`) { return } }) t.Run(`accept fmt.Stringer for PBES2-HS384+A192KW`, func(t *testing.T) { t.Parallel() var dst jwa.KeyEncryptionAlgorithm if !assert.NoError(t, dst.Accept(stringer{src: "PBES2-HS384+A192KW"}), `accept is successful`) { return } if !assert.Equal(t, jwa.PBES2_HS384_A192KW, dst, `accepted value should be equal to constant`) { return } }) t.Run(`stringification for PBES2-HS384+A192KW`, func(t *testing.T) { t.Parallel() if !assert.Equal(t, "PBES2-HS384+A192KW", jwa.PBES2_HS384_A192KW.String(), `stringified value matches`) { return } }) t.Run(`accept jwa constant PBES2_HS512_A256KW`, func(t *testing.T) { t.Parallel() var dst jwa.KeyEncryptionAlgorithm if !assert.NoError(t, dst.Accept(jwa.PBES2_HS512_A256KW), `accept is successful`) { return } if !assert.Equal(t, jwa.PBES2_HS512_A256KW, dst, `accepted value should be equal to constant`) { return } }) t.Run(`accept the string PBES2-HS512+A256KW`, func(t *testing.T) { t.Parallel() var dst jwa.KeyEncryptionAlgorithm if !assert.NoError(t, dst.Accept("PBES2-HS512+A256KW"), `accept is successful`) { return } if !assert.Equal(t, jwa.PBES2_HS512_A256KW, dst, `accepted value should be equal to constant`) { return } }) t.Run(`accept fmt.Stringer for PBES2-HS512+A256KW`, func(t *testing.T) { t.Parallel() var dst jwa.KeyEncryptionAlgorithm if !assert.NoError(t, dst.Accept(stringer{src: "PBES2-HS512+A256KW"}), `accept is successful`) { return } if !assert.Equal(t, jwa.PBES2_HS512_A256KW, dst, `accepted value should be equal to constant`) { return } }) t.Run(`stringification for PBES2-HS512+A256KW`, func(t *testing.T) { t.Parallel() if !assert.Equal(t, "PBES2-HS512+A256KW", jwa.PBES2_HS512_A256KW.String(), `stringified value matches`) { return } }) t.Run(`accept jwa constant RSA1_5`, func(t *testing.T) { t.Parallel() var dst jwa.KeyEncryptionAlgorithm if !assert.NoError(t, dst.Accept(jwa.RSA1_5), `accept is successful`) { return } if !assert.Equal(t, jwa.RSA1_5, dst, `accepted value should be equal to constant`) { return } }) t.Run(`accept the string RSA1_5`, func(t *testing.T) { t.Parallel() var dst jwa.KeyEncryptionAlgorithm if !assert.NoError(t, dst.Accept("RSA1_5"), `accept is successful`) { return } if !assert.Equal(t, jwa.RSA1_5, dst, `accepted value should be equal to constant`) { return } }) t.Run(`accept fmt.Stringer for RSA1_5`, func(t *testing.T) { t.Parallel() var dst jwa.KeyEncryptionAlgorithm if !assert.NoError(t, dst.Accept(stringer{src: "RSA1_5"}), `accept is successful`) { return } if !assert.Equal(t, jwa.RSA1_5, dst, `accepted value should be equal to constant`) { return } }) t.Run(`stringification for RSA1_5`, func(t *testing.T) { t.Parallel() if !assert.Equal(t, "RSA1_5", jwa.RSA1_5.String(), `stringified value matches`) { return } }) t.Run(`accept jwa constant RSA_OAEP`, func(t *testing.T) { t.Parallel() var dst jwa.KeyEncryptionAlgorithm if !assert.NoError(t, dst.Accept(jwa.RSA_OAEP), `accept is successful`) { return } if !assert.Equal(t, jwa.RSA_OAEP, dst, `accepted value should be equal to constant`) { return } }) t.Run(`accept the string RSA-OAEP`, func(t *testing.T) { t.Parallel() var dst jwa.KeyEncryptionAlgorithm if !assert.NoError(t, dst.Accept("RSA-OAEP"), `accept is successful`) { return } if !assert.Equal(t, jwa.RSA_OAEP, dst, `accepted value should be equal to constant`) { return } }) t.Run(`accept fmt.Stringer for RSA-OAEP`, func(t *testing.T) { t.Parallel() var dst jwa.KeyEncryptionAlgorithm if !assert.NoError(t, dst.Accept(stringer{src: "RSA-OAEP"}), `accept is successful`) { return } if !assert.Equal(t, jwa.RSA_OAEP, dst, `accepted value should be equal to constant`) { return } }) t.Run(`stringification for RSA-OAEP`, func(t *testing.T) { t.Parallel() if !assert.Equal(t, "RSA-OAEP", jwa.RSA_OAEP.String(), `stringified value matches`) { return } }) t.Run(`accept jwa constant RSA_OAEP_256`, func(t *testing.T) { t.Parallel() var dst jwa.KeyEncryptionAlgorithm if !assert.NoError(t, dst.Accept(jwa.RSA_OAEP_256), `accept is successful`) { return } if !assert.Equal(t, jwa.RSA_OAEP_256, dst, `accepted value should be equal to constant`) { return } }) t.Run(`accept the string RSA-OAEP-256`, func(t *testing.T) { t.Parallel() var dst jwa.KeyEncryptionAlgorithm if !assert.NoError(t, dst.Accept("RSA-OAEP-256"), `accept is successful`) { return } if !assert.Equal(t, jwa.RSA_OAEP_256, dst, `accepted value should be equal to constant`) { return } }) t.Run(`accept fmt.Stringer for RSA-OAEP-256`, func(t *testing.T) { t.Parallel() var dst jwa.KeyEncryptionAlgorithm if !assert.NoError(t, dst.Accept(stringer{src: "RSA-OAEP-256"}), `accept is successful`) { return } if !assert.Equal(t, jwa.RSA_OAEP_256, dst, `accepted value should be equal to constant`) { return } }) t.Run(`stringification for RSA-OAEP-256`, func(t *testing.T) { t.Parallel() if !assert.Equal(t, "RSA-OAEP-256", jwa.RSA_OAEP_256.String(), `stringified value matches`) { return } }) t.Run(`accept jwa constant RSA_OAEP_384`, func(t *testing.T) { t.Parallel() var dst jwa.KeyEncryptionAlgorithm if !assert.NoError(t, dst.Accept(jwa.RSA_OAEP_384), `accept is successful`) { return } if !assert.Equal(t, jwa.RSA_OAEP_384, dst, `accepted value should be equal to constant`) { return } }) t.Run(`accept the string RSA-OAEP-384`, func(t *testing.T) { t.Parallel() var dst jwa.KeyEncryptionAlgorithm if !assert.NoError(t, dst.Accept("RSA-OAEP-384"), `accept is successful`) { return } if !assert.Equal(t, jwa.RSA_OAEP_384, dst, `accepted value should be equal to constant`) { return } }) t.Run(`accept fmt.Stringer for RSA-OAEP-384`, func(t *testing.T) { t.Parallel() var dst jwa.KeyEncryptionAlgorithm if !assert.NoError(t, dst.Accept(stringer{src: "RSA-OAEP-384"}), `accept is successful`) { return } if !assert.Equal(t, jwa.RSA_OAEP_384, dst, `accepted value should be equal to constant`) { return } }) t.Run(`stringification for RSA-OAEP-384`, func(t *testing.T) { t.Parallel() if !assert.Equal(t, "RSA-OAEP-384", jwa.RSA_OAEP_384.String(), `stringified value matches`) { return } }) t.Run(`accept jwa constant RSA_OAEP_512`, func(t *testing.T) { t.Parallel() var dst jwa.KeyEncryptionAlgorithm if !assert.NoError(t, dst.Accept(jwa.RSA_OAEP_512), `accept is successful`) { return } if !assert.Equal(t, jwa.RSA_OAEP_512, dst, `accepted value should be equal to constant`) { return } }) t.Run(`accept the string RSA-OAEP-512`, func(t *testing.T) { t.Parallel() var dst jwa.KeyEncryptionAlgorithm if !assert.NoError(t, dst.Accept("RSA-OAEP-512"), `accept is successful`) { return } if !assert.Equal(t, jwa.RSA_OAEP_512, dst, `accepted value should be equal to constant`) { return } }) t.Run(`accept fmt.Stringer for RSA-OAEP-512`, func(t *testing.T) { t.Parallel() var dst jwa.KeyEncryptionAlgorithm if !assert.NoError(t, dst.Accept(stringer{src: "RSA-OAEP-512"}), `accept is successful`) { return } if !assert.Equal(t, jwa.RSA_OAEP_512, dst, `accepted value should be equal to constant`) { return } }) t.Run(`stringification for RSA-OAEP-512`, func(t *testing.T) { t.Parallel() if !assert.Equal(t, "RSA-OAEP-512", jwa.RSA_OAEP_512.String(), `stringified value matches`) { return } }) t.Run(`bail out on random integer value`, func(t *testing.T) { t.Parallel() var dst jwa.KeyEncryptionAlgorithm if !assert.Error(t, dst.Accept(1), `accept should fail`) { return } }) t.Run(`do not accept invalid (totally made up) string value`, func(t *testing.T) { t.Parallel() var dst jwa.KeyEncryptionAlgorithm if !assert.Error(t, dst.Accept(`totallyInvalidValue`), `accept should fail`) { return } }) t.Run(`check symmetric values`, func(t *testing.T) { t.Parallel() t.Run(`A128GCMKW`, func(t *testing.T) { assert.True(t, jwa.A128GCMKW.IsSymmetric(), `jwa.A128GCMKW should be symmetric`) }) t.Run(`A128KW`, func(t *testing.T) { assert.True(t, jwa.A128KW.IsSymmetric(), `jwa.A128KW should be symmetric`) }) t.Run(`A192GCMKW`, func(t *testing.T) { assert.True(t, jwa.A192GCMKW.IsSymmetric(), `jwa.A192GCMKW should be symmetric`) }) t.Run(`A192KW`, func(t *testing.T) { assert.True(t, jwa.A192KW.IsSymmetric(), `jwa.A192KW should be symmetric`) }) t.Run(`A256GCMKW`, func(t *testing.T) { assert.True(t, jwa.A256GCMKW.IsSymmetric(), `jwa.A256GCMKW should be symmetric`) }) t.Run(`A256KW`, func(t *testing.T) { assert.True(t, jwa.A256KW.IsSymmetric(), `jwa.A256KW should be symmetric`) }) t.Run(`DIRECT`, func(t *testing.T) { assert.True(t, jwa.DIRECT.IsSymmetric(), `jwa.DIRECT should be symmetric`) }) t.Run(`ECDH_ES`, func(t *testing.T) { assert.False(t, jwa.ECDH_ES.IsSymmetric(), `jwa.ECDH_ES should NOT be symmetric`) }) t.Run(`ECDH_ES_A128KW`, func(t *testing.T) { assert.False(t, jwa.ECDH_ES_A128KW.IsSymmetric(), `jwa.ECDH_ES_A128KW should NOT be symmetric`) }) t.Run(`ECDH_ES_A192KW`, func(t *testing.T) { assert.False(t, jwa.ECDH_ES_A192KW.IsSymmetric(), `jwa.ECDH_ES_A192KW should NOT be symmetric`) }) t.Run(`ECDH_ES_A256KW`, func(t *testing.T) { assert.False(t, jwa.ECDH_ES_A256KW.IsSymmetric(), `jwa.ECDH_ES_A256KW should NOT be symmetric`) }) t.Run(`PBES2_HS256_A128KW`, func(t *testing.T) { assert.True(t, jwa.PBES2_HS256_A128KW.IsSymmetric(), `jwa.PBES2_HS256_A128KW should be symmetric`) }) t.Run(`PBES2_HS384_A192KW`, func(t *testing.T) { assert.True(t, jwa.PBES2_HS384_A192KW.IsSymmetric(), `jwa.PBES2_HS384_A192KW should be symmetric`) }) t.Run(`PBES2_HS512_A256KW`, func(t *testing.T) { assert.True(t, jwa.PBES2_HS512_A256KW.IsSymmetric(), `jwa.PBES2_HS512_A256KW should be symmetric`) }) t.Run(`RSA1_5`, func(t *testing.T) { assert.False(t, jwa.RSA1_5.IsSymmetric(), `jwa.RSA1_5 should NOT be symmetric`) }) t.Run(`RSA_OAEP`, func(t *testing.T) { assert.False(t, jwa.RSA_OAEP.IsSymmetric(), `jwa.RSA_OAEP should NOT be symmetric`) }) t.Run(`RSA_OAEP_256`, func(t *testing.T) { assert.False(t, jwa.RSA_OAEP_256.IsSymmetric(), `jwa.RSA_OAEP_256 should NOT be symmetric`) }) t.Run(`RSA_OAEP_384`, func(t *testing.T) { assert.False(t, jwa.RSA_OAEP_384.IsSymmetric(), `jwa.RSA_OAEP_384 should NOT be symmetric`) }) t.Run(`RSA_OAEP_512`, func(t *testing.T) { assert.False(t, jwa.RSA_OAEP_512.IsSymmetric(), `jwa.RSA_OAEP_512 should NOT be symmetric`) }) }) 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() { if _, ok := expected[v]; !assert.True(t, ok, `%s should be in the expected list`, v) { return } delete(expected, v) } if !assert.Len(t, expected, 0) { return } }) } // 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. customAlgorithm := jwa.KeyEncryptionAlgorithm("custom-algorithm") // 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(`accept variable used to register custom algorithm`, func(t *testing.T) { t.Parallel() var dst jwa.KeyEncryptionAlgorithm if !assert.NoError(t, dst.Accept(customAlgorithm), `accept is successful`) { return } assert.Equal(t, customAlgorithm, dst, `accepted value should be equal to variable`) }) t.Run(`accept the string custom-algorithm`, func(t *testing.T) { t.Parallel() var dst jwa.KeyEncryptionAlgorithm if !assert.NoError(t, dst.Accept(`custom-algorithm`), `accept is successful`) { return } assert.Equal(t, customAlgorithm, dst, `accepted value should be equal to variable`) }) t.Run(`accept fmt.Stringer for custom-algorithm`, func(t *testing.T) { t.Parallel() var dst jwa.KeyEncryptionAlgorithm if !assert.NoError(t, dst.Accept(stringer{src: `custom-algorithm`}), `accept is successful`) { return } assert.Equal(t, customAlgorithm, dst, `accepted value should be equal to variable`) }) t.Run(`check symmetric`, func(t *testing.T) { t.Parallel() assert.False(t, customAlgorithm.IsSymmetric(), `custom algorithm should NOT be symmetric`) }) }) t.Run(`with custom algorithm deregistered`, func(t *testing.T) { jwa.UnregisterKeyEncryptionAlgorithm(customAlgorithm) t.Run(`reject variable used to register custom algorithm`, func(t *testing.T) { t.Parallel() var dst jwa.KeyEncryptionAlgorithm assert.Error(t, dst.Accept(customAlgorithm), `accept failed`) }) t.Run(`reject the string custom-algorithm`, func(t *testing.T) { t.Parallel() var dst jwa.KeyEncryptionAlgorithm assert.Error(t, dst.Accept(`custom-algorithm`), `accept failed`) }) t.Run(`reject fmt.Stringer for custom-algorithm`, func(t *testing.T) { t.Parallel() var dst jwa.KeyEncryptionAlgorithm assert.Error(t, dst.Accept(stringer{src: `custom-algorithm`}), `accept failed`) }) t.Run(`check symmetric`, func(t *testing.T) { t.Parallel() assert.False(t, customAlgorithm.IsSymmetric(), `custom algorithm should NOT be symmetric`) }) }) t.Run(`with custom algorithm registered with WithSymmetricAlgorithm(false)`, func(t *testing.T) { jwa.RegisterKeyEncryptionAlgorithmWithOptions(customAlgorithm, jwa.WithSymmetricAlgorithm(false)) t.Run(`accept variable used to register custom algorithm`, func(t *testing.T) { t.Parallel() var dst jwa.KeyEncryptionAlgorithm if !assert.NoError(t, dst.Accept(customAlgorithm), `accept is successful`) { return } assert.Equal(t, customAlgorithm, dst, `accepted value should be equal to variable`) }) t.Run(`accept the string custom-algorithm`, func(t *testing.T) { t.Parallel() var dst jwa.KeyEncryptionAlgorithm if !assert.NoError(t, dst.Accept(`custom-algorithm`), `accept is successful`) { return } assert.Equal(t, customAlgorithm, dst, `accepted value should be equal to variable`) }) t.Run(`accept fmt.Stringer for custom-algorithm`, func(t *testing.T) { t.Parallel() var dst jwa.KeyEncryptionAlgorithm if !assert.NoError(t, dst.Accept(stringer{src: `custom-algorithm`}), `accept is successful`) { return } assert.Equal(t, customAlgorithm, dst, `accepted value should be equal to variable`) }) t.Run(`check symmetric`, func(t *testing.T) { t.Parallel() assert.False(t, customAlgorithm.IsSymmetric(), `custom algorithm should NOT be symmetric`) }) }) t.Run(`with custom algorithm deregistered (was WithSymmetricAlgorithm(false))`, func(t *testing.T) { jwa.UnregisterKeyEncryptionAlgorithm(customAlgorithm) t.Run(`reject variable used to register custom algorithm`, func(t *testing.T) { t.Parallel() var dst jwa.KeyEncryptionAlgorithm assert.Error(t, dst.Accept(customAlgorithm), `accept failed`) }) t.Run(`reject the string custom-algorithm`, func(t *testing.T) { t.Parallel() var dst jwa.KeyEncryptionAlgorithm assert.Error(t, dst.Accept(`custom-algorithm`), `accept failed`) }) t.Run(`reject fmt.Stringer for custom-algorithm`, func(t *testing.T) { t.Parallel() var dst jwa.KeyEncryptionAlgorithm assert.Error(t, dst.Accept(stringer{src: `custom-algorithm`}), `accept failed`) }) t.Run(`check symmetric`, func(t *testing.T) { t.Parallel() assert.False(t, customAlgorithm.IsSymmetric(), `custom algorithm should NOT be symmetric`) }) }) t.Run(`with custom algorithm registered with WithSymmetricAlgorithm(true)`, func(t *testing.T) { jwa.RegisterKeyEncryptionAlgorithmWithOptions(customAlgorithm, jwa.WithSymmetricAlgorithm(true)) t.Run(`accept variable used to register custom algorithm`, func(t *testing.T) { t.Parallel() var dst jwa.KeyEncryptionAlgorithm if !assert.NoError(t, dst.Accept(customAlgorithm), `accept is successful`) { return } assert.Equal(t, customAlgorithm, dst, `accepted value should be equal to variable`) }) t.Run(`accept the string custom-algorithm`, func(t *testing.T) { t.Parallel() var dst jwa.KeyEncryptionAlgorithm if !assert.NoError(t, dst.Accept(`custom-algorithm`), `accept is successful`) { return } assert.Equal(t, customAlgorithm, dst, `accepted value should be equal to variable`) }) t.Run(`accept fmt.Stringer for custom-algorithm`, func(t *testing.T) { t.Parallel() var dst jwa.KeyEncryptionAlgorithm if !assert.NoError(t, dst.Accept(stringer{src: `custom-algorithm`}), `accept is successful`) { return } assert.Equal(t, customAlgorithm, dst, `accepted value should be equal to variable`) }) t.Run(`check symmetric`, func(t *testing.T) { t.Parallel() assert.True(t, customAlgorithm.IsSymmetric(), `custom algorithm should be symmetric`) }) }) t.Run(`with custom algorithm deregistered (was WithSymmetricAlgorithm(true))`, func(t *testing.T) { jwa.UnregisterKeyEncryptionAlgorithm(customAlgorithm) t.Run(`reject variable used to register custom algorithm`, func(t *testing.T) { t.Parallel() var dst jwa.KeyEncryptionAlgorithm assert.Error(t, dst.Accept(customAlgorithm), `accept failed`) }) t.Run(`reject the string custom-algorithm`, func(t *testing.T) { t.Parallel() var dst jwa.KeyEncryptionAlgorithm assert.Error(t, dst.Accept(`custom-algorithm`), `accept failed`) }) t.Run(`reject fmt.Stringer for custom-algorithm`, func(t *testing.T) { t.Parallel() var dst jwa.KeyEncryptionAlgorithm assert.Error(t, dst.Accept(stringer{src: `custom-algorithm`}), `accept failed`) }) t.Run(`check symmetric`, func(t *testing.T) { t.Parallel() assert.False(t, customAlgorithm.IsSymmetric(), `custom algorithm should NOT be symmetric`) }) }) } golang-github-lestrrat-go-jwx-2.1.4/jwa/key_type_gen.go000066400000000000000000000047511476711647200231340ustar00rootroot00000000000000// Code generated by tools/cmd/genjwa/main.go. DO NOT EDIT. package jwa import ( "fmt" "sort" "sync" ) // KeyType represents the key type ("kty") that are supported type KeyType string // Supported values for KeyType const ( EC KeyType = "EC" // Elliptic Curve InvalidKeyType KeyType = "" // Invalid KeyType OKP KeyType = "OKP" // Octet string key pairs OctetSeq KeyType = "oct" // Octet sequence (used to represent symmetric keys) RSA KeyType = "RSA" // RSA ) var muKeyTypes sync.RWMutex var allKeyTypes map[KeyType]struct{} var listKeyType []KeyType func init() { muKeyTypes.Lock() defer muKeyTypes.Unlock() allKeyTypes = make(map[KeyType]struct{}) allKeyTypes[EC] = struct{}{} allKeyTypes[OKP] = struct{}{} allKeyTypes[OctetSeq] = struct{}{} allKeyTypes[RSA] = struct{}{} rebuildKeyType() } // RegisterKeyType registers a new KeyType so that the jwx can properly handle the new value. // Duplicates will silently be ignored func RegisterKeyType(v KeyType) { muKeyTypes.Lock() defer muKeyTypes.Unlock() if _, ok := allKeyTypes[v]; !ok { allKeyTypes[v] = struct{}{} rebuildKeyType() } } // UnregisterKeyType unregisters a KeyType from its known database. // Non-existent entries will silently be ignored func UnregisterKeyType(v KeyType) { muKeyTypes.Lock() defer muKeyTypes.Unlock() if _, ok := allKeyTypes[v]; ok { delete(allKeyTypes, v) rebuildKeyType() } } func rebuildKeyType() { listKeyType = make([]KeyType, 0, len(allKeyTypes)) for v := range allKeyTypes { listKeyType = append(listKeyType, v) } sort.Slice(listKeyType, func(i, j int) bool { return string(listKeyType[i]) < string(listKeyType[j]) }) } // KeyTypes returns a list of all available values for KeyType func KeyTypes() []KeyType { muKeyTypes.RLock() defer muKeyTypes.RUnlock() return listKeyType } // Accept is used when conversion from values given by // outside sources (such as JSON payloads) is required func (v *KeyType) Accept(value interface{}) error { var tmp KeyType if x, ok := value.(KeyType); ok { tmp = x } else { var s string switch x := value.(type) { case fmt.Stringer: s = x.String() case string: s = x default: return fmt.Errorf(`invalid type for jwa.KeyType: %T`, value) } tmp = KeyType(s) } if _, ok := allKeyTypes[tmp]; !ok { return fmt.Errorf(`invalid jwa.KeyType value`) } *v = tmp return nil } // String returns the string representation of a KeyType func (v KeyType) String() string { return string(v) } golang-github-lestrrat-go-jwx-2.1.4/jwa/key_type_gen_test.go000066400000000000000000000164271476711647200241760ustar00rootroot00000000000000// Code generated by tools/cmd/genjwa/main.go. DO NOT EDIT package jwa_test import ( "testing" "github.com/lestrrat-go/jwx/v2/jwa" "github.com/stretchr/testify/assert" ) func TestKeyType(t *testing.T) { t.Parallel() t.Run(`accept jwa constant EC`, func(t *testing.T) { t.Parallel() var dst jwa.KeyType if !assert.NoError(t, dst.Accept(jwa.EC), `accept is successful`) { return } if !assert.Equal(t, jwa.EC, dst, `accepted value should be equal to constant`) { return } }) t.Run(`accept the string EC`, func(t *testing.T) { t.Parallel() var dst jwa.KeyType if !assert.NoError(t, dst.Accept("EC"), `accept is successful`) { return } if !assert.Equal(t, jwa.EC, dst, `accepted value should be equal to constant`) { return } }) t.Run(`accept fmt.Stringer for EC`, func(t *testing.T) { t.Parallel() var dst jwa.KeyType if !assert.NoError(t, dst.Accept(stringer{src: "EC"}), `accept is successful`) { return } if !assert.Equal(t, jwa.EC, dst, `accepted value should be equal to constant`) { return } }) t.Run(`stringification for EC`, func(t *testing.T) { t.Parallel() if !assert.Equal(t, "EC", jwa.EC.String(), `stringified value matches`) { return } }) t.Run(`accept jwa constant OKP`, func(t *testing.T) { t.Parallel() var dst jwa.KeyType if !assert.NoError(t, dst.Accept(jwa.OKP), `accept is successful`) { return } if !assert.Equal(t, jwa.OKP, dst, `accepted value should be equal to constant`) { return } }) t.Run(`accept the string OKP`, func(t *testing.T) { t.Parallel() var dst jwa.KeyType if !assert.NoError(t, dst.Accept("OKP"), `accept is successful`) { return } if !assert.Equal(t, jwa.OKP, dst, `accepted value should be equal to constant`) { return } }) t.Run(`accept fmt.Stringer for OKP`, func(t *testing.T) { t.Parallel() var dst jwa.KeyType if !assert.NoError(t, dst.Accept(stringer{src: "OKP"}), `accept is successful`) { return } if !assert.Equal(t, jwa.OKP, dst, `accepted value should be equal to constant`) { return } }) t.Run(`stringification for OKP`, func(t *testing.T) { t.Parallel() if !assert.Equal(t, "OKP", jwa.OKP.String(), `stringified value matches`) { return } }) t.Run(`accept jwa constant OctetSeq`, func(t *testing.T) { t.Parallel() var dst jwa.KeyType if !assert.NoError(t, dst.Accept(jwa.OctetSeq), `accept is successful`) { return } if !assert.Equal(t, jwa.OctetSeq, dst, `accepted value should be equal to constant`) { return } }) t.Run(`accept the string oct`, func(t *testing.T) { t.Parallel() var dst jwa.KeyType if !assert.NoError(t, dst.Accept("oct"), `accept is successful`) { return } if !assert.Equal(t, jwa.OctetSeq, dst, `accepted value should be equal to constant`) { return } }) t.Run(`accept fmt.Stringer for oct`, func(t *testing.T) { t.Parallel() var dst jwa.KeyType if !assert.NoError(t, dst.Accept(stringer{src: "oct"}), `accept is successful`) { return } if !assert.Equal(t, jwa.OctetSeq, dst, `accepted value should be equal to constant`) { return } }) t.Run(`stringification for oct`, func(t *testing.T) { t.Parallel() if !assert.Equal(t, "oct", jwa.OctetSeq.String(), `stringified value matches`) { return } }) t.Run(`accept jwa constant RSA`, func(t *testing.T) { t.Parallel() var dst jwa.KeyType if !assert.NoError(t, dst.Accept(jwa.RSA), `accept is successful`) { return } if !assert.Equal(t, jwa.RSA, dst, `accepted value should be equal to constant`) { return } }) t.Run(`accept the string RSA`, func(t *testing.T) { t.Parallel() var dst jwa.KeyType if !assert.NoError(t, dst.Accept("RSA"), `accept is successful`) { return } if !assert.Equal(t, jwa.RSA, dst, `accepted value should be equal to constant`) { return } }) t.Run(`accept fmt.Stringer for RSA`, func(t *testing.T) { t.Parallel() var dst jwa.KeyType if !assert.NoError(t, dst.Accept(stringer{src: "RSA"}), `accept is successful`) { return } if !assert.Equal(t, jwa.RSA, dst, `accepted value should be equal to constant`) { return } }) t.Run(`stringification for RSA`, func(t *testing.T) { t.Parallel() if !assert.Equal(t, "RSA", jwa.RSA.String(), `stringified value matches`) { return } }) t.Run(`do not accept invalid constant InvalidKeyType`, func(t *testing.T) { t.Parallel() var dst jwa.KeyType if !assert.Error(t, dst.Accept(jwa.InvalidKeyType), `accept should fail`) { return } }) t.Run(`bail out on random integer value`, func(t *testing.T) { t.Parallel() var dst jwa.KeyType if !assert.Error(t, dst.Accept(1), `accept should fail`) { return } }) t.Run(`do not accept invalid (totally made up) string value`, func(t *testing.T) { t.Parallel() var dst jwa.KeyType if !assert.Error(t, dst.Accept(`totallyInvalidValue`), `accept should fail`) { return } }) 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() { if _, ok := expected[v]; !assert.True(t, ok, `%s should be in the expected list`, v) { return } delete(expected, v) } if !assert.Len(t, expected, 0) { return } }) } // 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. customAlgorithm := jwa.KeyType("custom-algorithm") // 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(`accept variable used to register custom algorithm`, func(t *testing.T) { t.Parallel() var dst jwa.KeyType if !assert.NoError(t, dst.Accept(customAlgorithm), `accept is successful`) { return } assert.Equal(t, customAlgorithm, dst, `accepted value should be equal to variable`) }) t.Run(`accept the string custom-algorithm`, func(t *testing.T) { t.Parallel() var dst jwa.KeyType if !assert.NoError(t, dst.Accept(`custom-algorithm`), `accept is successful`) { return } assert.Equal(t, customAlgorithm, dst, `accepted value should be equal to variable`) }) t.Run(`accept fmt.Stringer for custom-algorithm`, func(t *testing.T) { t.Parallel() var dst jwa.KeyType if !assert.NoError(t, dst.Accept(stringer{src: `custom-algorithm`}), `accept is successful`) { return } assert.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(`reject variable used to register custom algorithm`, func(t *testing.T) { t.Parallel() var dst jwa.KeyType assert.Error(t, dst.Accept(customAlgorithm), `accept failed`) }) t.Run(`reject the string custom-algorithm`, func(t *testing.T) { t.Parallel() var dst jwa.KeyType assert.Error(t, dst.Accept(`custom-algorithm`), `accept failed`) }) t.Run(`reject fmt.Stringer for custom-algorithm`, func(t *testing.T) { t.Parallel() var dst jwa.KeyType assert.Error(t, dst.Accept(stringer{src: `custom-algorithm`}), `accept failed`) }) }) } golang-github-lestrrat-go-jwx-2.1.4/jwa/options.yaml000066400000000000000000000012351476711647200224740ustar00rootroot00000000000000package_name: jwa output: jwa/options_gen.go interfaces: - name: RegisterAlgorithmOption comment: | RegisterAlgorithmOption describes options that can be passed to the algorithm registering functions that support options such as RegisterKeyEncryptionAlgorithmWithOptions. options: - ident: SymmetricAlgorithm interface: RegisterAlgorithmOption argument_type: bool comment: | WithSymmetricAlgorithm lets the library know whether the algorithm is symmetric. This affects the response of the `IsSymmetric` method of the algorithm. If the algorithms does not support this method, using this option will result in an error. golang-github-lestrrat-go-jwx-2.1.4/jwa/options_gen.go000066400000000000000000000020121476711647200227620ustar00rootroot00000000000000// Code generated by tools/cmd/genoptions/main.go. DO NOT EDIT. package jwa import "github.com/lestrrat-go/option" type Option = option.Interface // RegisterAlgorithmOption describes options that can be passed to the algorithm registering // functions that support options such as RegisterKeyEncryptionAlgorithmWithOptions. type RegisterAlgorithmOption interface { Option registerAlgorithmOption() } type registerAlgorithmOption struct { Option } func (*registerAlgorithmOption) registerAlgorithmOption() {} type identSymmetricAlgorithm struct{} func (identSymmetricAlgorithm) String() string { return "WithSymmetricAlgorithm" } // WithSymmetricAlgorithm lets the library know whether the algorithm is symmetric. This affects // the response of the `IsSymmetric` method of the algorithm. If the algorithms does not support // this method, using this option will result in an error. func WithSymmetricAlgorithm(v bool) RegisterAlgorithmOption { return ®isterAlgorithmOption{option.New(identSymmetricAlgorithm{}, v)} } golang-github-lestrrat-go-jwx-2.1.4/jwa/options_gen_test.go000066400000000000000000000004041476711647200240240ustar00rootroot00000000000000// 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, "WithSymmetricAlgorithm", identSymmetricAlgorithm{}.String()) } golang-github-lestrrat-go-jwx-2.1.4/jwa/secp2561k.go000066400000000000000000000003741476711647200220720ustar00rootroot00000000000000//go:build jwx_es256k // +build jwx_es256k package jwa // This constant is only available if compiled with jwx_es256k build tag const Secp256k1 EllipticCurveAlgorithm = "secp256k1" func init() { allEllipticCurveAlgorithms[Secp256k1] = struct{}{} } golang-github-lestrrat-go-jwx-2.1.4/jwa/secp2561k_test.go000066400000000000000000000025011476711647200231230ustar00rootroot00000000000000//go:build jwx_es256k // +build jwx_es256k package jwa_test import ( "testing" "github.com/lestrrat-go/jwx/v2/jwa" "github.com/stretchr/testify/assert" ) func TestSecp256k1(t *testing.T) { t.Parallel() t.Run(`accept jwa constant Secp256k1`, func(t *testing.T) { t.Parallel() var dst jwa.EllipticCurveAlgorithm if !assert.NoError(t, dst.Accept(jwa.Secp256k1), `accept is successful`) { return } if !assert.Equal(t, jwa.Secp256k1, dst, `accepted value should be equal to constant`) { return } }) t.Run(`accept the string secp256k1`, func(t *testing.T) { t.Parallel() var dst jwa.EllipticCurveAlgorithm if !assert.NoError(t, dst.Accept("secp256k1"), `accept is successful`) { return } if !assert.Equal(t, jwa.Secp256k1, dst, `accepted value should be equal to constant`) { return } }) t.Run(`accept fmt.Stringer for secp256k1`, func(t *testing.T) { t.Parallel() var dst jwa.EllipticCurveAlgorithm if !assert.NoError(t, dst.Accept(stringer{src: "secp256k1"}), `accept is successful`) { return } if !assert.Equal(t, jwa.Secp256k1, dst, `accepted value should be equal to constant`) { return } }) t.Run(`stringification for secp256k1`, func(t *testing.T) { t.Parallel() if !assert.Equal(t, "secp256k1", jwa.Secp256k1.String(), `stringified value matches`) { return } }) } golang-github-lestrrat-go-jwx-2.1.4/jwa/signature_gen.go000066400000000000000000000141471476711647200233040ustar00rootroot00000000000000// Code generated by tools/cmd/genjwa/main.go. DO NOT EDIT. package jwa import ( "fmt" "sort" "sync" ) // SignatureAlgorithm represents the various signature algorithms as described in https://tools.ietf.org/html/rfc7518#section-3.1 type SignatureAlgorithm string // Supported values for SignatureAlgorithm const ( ES256 SignatureAlgorithm = "ES256" // ECDSA using P-256 and SHA-256 ES256K SignatureAlgorithm = "ES256K" // ECDSA using secp256k1 and SHA-256 ES384 SignatureAlgorithm = "ES384" // ECDSA using P-384 and SHA-384 ES512 SignatureAlgorithm = "ES512" // ECDSA using P-521 and SHA-512 EdDSA SignatureAlgorithm = "EdDSA" // EdDSA signature algorithms HS256 SignatureAlgorithm = "HS256" // HMAC using SHA-256 HS384 SignatureAlgorithm = "HS384" // HMAC using SHA-384 HS512 SignatureAlgorithm = "HS512" // HMAC using SHA-512 NoSignature SignatureAlgorithm = "none" PS256 SignatureAlgorithm = "PS256" // RSASSA-PSS using SHA256 and MGF1-SHA256 PS384 SignatureAlgorithm = "PS384" // RSASSA-PSS using SHA384 and MGF1-SHA384 PS512 SignatureAlgorithm = "PS512" // RSASSA-PSS using SHA512 and MGF1-SHA512 RS256 SignatureAlgorithm = "RS256" // RSASSA-PKCS-v1.5 using SHA-256 RS384 SignatureAlgorithm = "RS384" // RSASSA-PKCS-v1.5 using SHA-384 RS512 SignatureAlgorithm = "RS512" // RSASSA-PKCS-v1.5 using SHA-512 ) var muSignatureAlgorithms sync.RWMutex var allSignatureAlgorithms map[SignatureAlgorithm]struct{} var listSignatureAlgorithm []SignatureAlgorithm var symmetricSignatureAlgorithms map[SignatureAlgorithm]struct{} func init() { muSignatureAlgorithms.Lock() defer muSignatureAlgorithms.Unlock() allSignatureAlgorithms = make(map[SignatureAlgorithm]struct{}) allSignatureAlgorithms[ES256] = struct{}{} allSignatureAlgorithms[ES256K] = struct{}{} allSignatureAlgorithms[ES384] = struct{}{} allSignatureAlgorithms[ES512] = struct{}{} allSignatureAlgorithms[EdDSA] = struct{}{} allSignatureAlgorithms[HS256] = struct{}{} allSignatureAlgorithms[HS384] = struct{}{} allSignatureAlgorithms[HS512] = struct{}{} allSignatureAlgorithms[NoSignature] = struct{}{} allSignatureAlgorithms[PS256] = struct{}{} allSignatureAlgorithms[PS384] = struct{}{} allSignatureAlgorithms[PS512] = struct{}{} allSignatureAlgorithms[RS256] = struct{}{} allSignatureAlgorithms[RS384] = struct{}{} allSignatureAlgorithms[RS512] = struct{}{} symmetricSignatureAlgorithms = make(map[SignatureAlgorithm]struct{}) symmetricSignatureAlgorithms[HS256] = struct{}{} symmetricSignatureAlgorithms[HS384] = struct{}{} symmetricSignatureAlgorithms[HS512] = struct{}{} rebuildSignatureAlgorithm() } // RegisterSignatureAlgorithm registers a new SignatureAlgorithm so that the jwx can properly handle the new value. // Duplicates will silently be ignored func RegisterSignatureAlgorithm(v SignatureAlgorithm) { RegisterSignatureAlgorithmWithOptions(v) } // RegisterSignatureAlgorithmWithOptions is the same as RegisterSignatureAlgorithm when used without options, // but allows its behavior to change based on the provided options. // This is an experimental AND stopgap function which will most likely be merged in RegisterSignatureAlgorithm, and subsequently removed in the future. As such it should not be considered part of the stable API -- it is still subject to change. // // You can pass `WithSymmetricAlgorithm(true)` to let the library know that it's a symmetric algorithm. This library makes no attempt to verify if the algorithm is indeed symmetric or not. func RegisterSignatureAlgorithmWithOptions(v SignatureAlgorithm, options ...RegisterAlgorithmOption) { var symmetric bool //nolint:forcetypeassert for _, option := range options { switch option.Ident() { case identSymmetricAlgorithm{}: symmetric = option.Value().(bool) } } muSignatureAlgorithms.Lock() defer muSignatureAlgorithms.Unlock() if _, ok := allSignatureAlgorithms[v]; !ok { allSignatureAlgorithms[v] = struct{}{} if symmetric { symmetricSignatureAlgorithms[v] = struct{}{} } rebuildSignatureAlgorithm() } } // UnregisterSignatureAlgorithm unregisters a SignatureAlgorithm from its known database. // Non-existent entries will silently be ignored func UnregisterSignatureAlgorithm(v SignatureAlgorithm) { muSignatureAlgorithms.Lock() defer muSignatureAlgorithms.Unlock() if _, ok := allSignatureAlgorithms[v]; ok { delete(allSignatureAlgorithms, v) if _, ok := symmetricSignatureAlgorithms[v]; ok { delete(symmetricSignatureAlgorithms, v) } rebuildSignatureAlgorithm() } } func rebuildSignatureAlgorithm() { listSignatureAlgorithm = make([]SignatureAlgorithm, 0, len(allSignatureAlgorithms)) for v := range allSignatureAlgorithms { listSignatureAlgorithm = append(listSignatureAlgorithm, v) } sort.Slice(listSignatureAlgorithm, func(i, j int) bool { return string(listSignatureAlgorithm[i]) < string(listSignatureAlgorithm[j]) }) } // SignatureAlgorithms returns a list of all available values for SignatureAlgorithm func SignatureAlgorithms() []SignatureAlgorithm { muSignatureAlgorithms.RLock() defer muSignatureAlgorithms.RUnlock() return listSignatureAlgorithm } // Accept is used when conversion from values given by // outside sources (such as JSON payloads) is required func (v *SignatureAlgorithm) Accept(value interface{}) error { var tmp SignatureAlgorithm if x, ok := value.(SignatureAlgorithm); ok { tmp = x } else { var s string switch x := value.(type) { case fmt.Stringer: s = x.String() case string: s = x default: return fmt.Errorf(`invalid type for jwa.SignatureAlgorithm: %T`, value) } tmp = SignatureAlgorithm(s) } if _, ok := allSignatureAlgorithms[tmp]; !ok { return fmt.Errorf(`invalid jwa.SignatureAlgorithm value`) } *v = tmp return nil } // String returns the string representation of a SignatureAlgorithm func (v SignatureAlgorithm) String() string { return string(v) } // IsSymmetric returns true if the algorithm is a symmetric type. // Keep in mind that the NoSignature algorithm is neither a symmetric nor an asymmetric algorithm. func (v SignatureAlgorithm) IsSymmetric() bool { _, ok := symmetricSignatureAlgorithms[v] return ok } golang-github-lestrrat-go-jwx-2.1.4/jwa/signature_gen_test.go000066400000000000000000000635711476711647200243500ustar00rootroot00000000000000// Code generated by tools/cmd/genjwa/main.go. DO NOT EDIT package jwa_test import ( "testing" "github.com/lestrrat-go/jwx/v2/jwa" "github.com/stretchr/testify/assert" ) func TestSignatureAlgorithm(t *testing.T) { t.Parallel() t.Run(`accept jwa constant ES256`, func(t *testing.T) { t.Parallel() var dst jwa.SignatureAlgorithm if !assert.NoError(t, dst.Accept(jwa.ES256), `accept is successful`) { return } if !assert.Equal(t, jwa.ES256, dst, `accepted value should be equal to constant`) { return } }) t.Run(`accept the string ES256`, func(t *testing.T) { t.Parallel() var dst jwa.SignatureAlgorithm if !assert.NoError(t, dst.Accept("ES256"), `accept is successful`) { return } if !assert.Equal(t, jwa.ES256, dst, `accepted value should be equal to constant`) { return } }) t.Run(`accept fmt.Stringer for ES256`, func(t *testing.T) { t.Parallel() var dst jwa.SignatureAlgorithm if !assert.NoError(t, dst.Accept(stringer{src: "ES256"}), `accept is successful`) { return } if !assert.Equal(t, jwa.ES256, dst, `accepted value should be equal to constant`) { return } }) t.Run(`stringification for ES256`, func(t *testing.T) { t.Parallel() if !assert.Equal(t, "ES256", jwa.ES256.String(), `stringified value matches`) { return } }) t.Run(`accept jwa constant ES256K`, func(t *testing.T) { t.Parallel() var dst jwa.SignatureAlgorithm if !assert.NoError(t, dst.Accept(jwa.ES256K), `accept is successful`) { return } if !assert.Equal(t, jwa.ES256K, dst, `accepted value should be equal to constant`) { return } }) t.Run(`accept the string ES256K`, func(t *testing.T) { t.Parallel() var dst jwa.SignatureAlgorithm if !assert.NoError(t, dst.Accept("ES256K"), `accept is successful`) { return } if !assert.Equal(t, jwa.ES256K, dst, `accepted value should be equal to constant`) { return } }) t.Run(`accept fmt.Stringer for ES256K`, func(t *testing.T) { t.Parallel() var dst jwa.SignatureAlgorithm if !assert.NoError(t, dst.Accept(stringer{src: "ES256K"}), `accept is successful`) { return } if !assert.Equal(t, jwa.ES256K, dst, `accepted value should be equal to constant`) { return } }) t.Run(`stringification for ES256K`, func(t *testing.T) { t.Parallel() if !assert.Equal(t, "ES256K", jwa.ES256K.String(), `stringified value matches`) { return } }) t.Run(`accept jwa constant ES384`, func(t *testing.T) { t.Parallel() var dst jwa.SignatureAlgorithm if !assert.NoError(t, dst.Accept(jwa.ES384), `accept is successful`) { return } if !assert.Equal(t, jwa.ES384, dst, `accepted value should be equal to constant`) { return } }) t.Run(`accept the string ES384`, func(t *testing.T) { t.Parallel() var dst jwa.SignatureAlgorithm if !assert.NoError(t, dst.Accept("ES384"), `accept is successful`) { return } if !assert.Equal(t, jwa.ES384, dst, `accepted value should be equal to constant`) { return } }) t.Run(`accept fmt.Stringer for ES384`, func(t *testing.T) { t.Parallel() var dst jwa.SignatureAlgorithm if !assert.NoError(t, dst.Accept(stringer{src: "ES384"}), `accept is successful`) { return } if !assert.Equal(t, jwa.ES384, dst, `accepted value should be equal to constant`) { return } }) t.Run(`stringification for ES384`, func(t *testing.T) { t.Parallel() if !assert.Equal(t, "ES384", jwa.ES384.String(), `stringified value matches`) { return } }) t.Run(`accept jwa constant ES512`, func(t *testing.T) { t.Parallel() var dst jwa.SignatureAlgorithm if !assert.NoError(t, dst.Accept(jwa.ES512), `accept is successful`) { return } if !assert.Equal(t, jwa.ES512, dst, `accepted value should be equal to constant`) { return } }) t.Run(`accept the string ES512`, func(t *testing.T) { t.Parallel() var dst jwa.SignatureAlgorithm if !assert.NoError(t, dst.Accept("ES512"), `accept is successful`) { return } if !assert.Equal(t, jwa.ES512, dst, `accepted value should be equal to constant`) { return } }) t.Run(`accept fmt.Stringer for ES512`, func(t *testing.T) { t.Parallel() var dst jwa.SignatureAlgorithm if !assert.NoError(t, dst.Accept(stringer{src: "ES512"}), `accept is successful`) { return } if !assert.Equal(t, jwa.ES512, dst, `accepted value should be equal to constant`) { return } }) t.Run(`stringification for ES512`, func(t *testing.T) { t.Parallel() if !assert.Equal(t, "ES512", jwa.ES512.String(), `stringified value matches`) { return } }) t.Run(`accept jwa constant EdDSA`, func(t *testing.T) { t.Parallel() var dst jwa.SignatureAlgorithm if !assert.NoError(t, dst.Accept(jwa.EdDSA), `accept is successful`) { return } if !assert.Equal(t, jwa.EdDSA, dst, `accepted value should be equal to constant`) { return } }) t.Run(`accept the string EdDSA`, func(t *testing.T) { t.Parallel() var dst jwa.SignatureAlgorithm if !assert.NoError(t, dst.Accept("EdDSA"), `accept is successful`) { return } if !assert.Equal(t, jwa.EdDSA, dst, `accepted value should be equal to constant`) { return } }) t.Run(`accept fmt.Stringer for EdDSA`, func(t *testing.T) { t.Parallel() var dst jwa.SignatureAlgorithm if !assert.NoError(t, dst.Accept(stringer{src: "EdDSA"}), `accept is successful`) { return } if !assert.Equal(t, jwa.EdDSA, dst, `accepted value should be equal to constant`) { return } }) t.Run(`stringification for EdDSA`, func(t *testing.T) { t.Parallel() if !assert.Equal(t, "EdDSA", jwa.EdDSA.String(), `stringified value matches`) { return } }) t.Run(`accept jwa constant HS256`, func(t *testing.T) { t.Parallel() var dst jwa.SignatureAlgorithm if !assert.NoError(t, dst.Accept(jwa.HS256), `accept is successful`) { return } if !assert.Equal(t, jwa.HS256, dst, `accepted value should be equal to constant`) { return } }) t.Run(`accept the string HS256`, func(t *testing.T) { t.Parallel() var dst jwa.SignatureAlgorithm if !assert.NoError(t, dst.Accept("HS256"), `accept is successful`) { return } if !assert.Equal(t, jwa.HS256, dst, `accepted value should be equal to constant`) { return } }) t.Run(`accept fmt.Stringer for HS256`, func(t *testing.T) { t.Parallel() var dst jwa.SignatureAlgorithm if !assert.NoError(t, dst.Accept(stringer{src: "HS256"}), `accept is successful`) { return } if !assert.Equal(t, jwa.HS256, dst, `accepted value should be equal to constant`) { return } }) t.Run(`stringification for HS256`, func(t *testing.T) { t.Parallel() if !assert.Equal(t, "HS256", jwa.HS256.String(), `stringified value matches`) { return } }) t.Run(`accept jwa constant HS384`, func(t *testing.T) { t.Parallel() var dst jwa.SignatureAlgorithm if !assert.NoError(t, dst.Accept(jwa.HS384), `accept is successful`) { return } if !assert.Equal(t, jwa.HS384, dst, `accepted value should be equal to constant`) { return } }) t.Run(`accept the string HS384`, func(t *testing.T) { t.Parallel() var dst jwa.SignatureAlgorithm if !assert.NoError(t, dst.Accept("HS384"), `accept is successful`) { return } if !assert.Equal(t, jwa.HS384, dst, `accepted value should be equal to constant`) { return } }) t.Run(`accept fmt.Stringer for HS384`, func(t *testing.T) { t.Parallel() var dst jwa.SignatureAlgorithm if !assert.NoError(t, dst.Accept(stringer{src: "HS384"}), `accept is successful`) { return } if !assert.Equal(t, jwa.HS384, dst, `accepted value should be equal to constant`) { return } }) t.Run(`stringification for HS384`, func(t *testing.T) { t.Parallel() if !assert.Equal(t, "HS384", jwa.HS384.String(), `stringified value matches`) { return } }) t.Run(`accept jwa constant HS512`, func(t *testing.T) { t.Parallel() var dst jwa.SignatureAlgorithm if !assert.NoError(t, dst.Accept(jwa.HS512), `accept is successful`) { return } if !assert.Equal(t, jwa.HS512, dst, `accepted value should be equal to constant`) { return } }) t.Run(`accept the string HS512`, func(t *testing.T) { t.Parallel() var dst jwa.SignatureAlgorithm if !assert.NoError(t, dst.Accept("HS512"), `accept is successful`) { return } if !assert.Equal(t, jwa.HS512, dst, `accepted value should be equal to constant`) { return } }) t.Run(`accept fmt.Stringer for HS512`, func(t *testing.T) { t.Parallel() var dst jwa.SignatureAlgorithm if !assert.NoError(t, dst.Accept(stringer{src: "HS512"}), `accept is successful`) { return } if !assert.Equal(t, jwa.HS512, dst, `accepted value should be equal to constant`) { return } }) t.Run(`stringification for HS512`, func(t *testing.T) { t.Parallel() if !assert.Equal(t, "HS512", jwa.HS512.String(), `stringified value matches`) { return } }) t.Run(`accept jwa constant NoSignature`, func(t *testing.T) { t.Parallel() var dst jwa.SignatureAlgorithm if !assert.NoError(t, dst.Accept(jwa.NoSignature), `accept is successful`) { return } if !assert.Equal(t, jwa.NoSignature, dst, `accepted value should be equal to constant`) { return } }) t.Run(`accept the string none`, func(t *testing.T) { t.Parallel() var dst jwa.SignatureAlgorithm if !assert.NoError(t, dst.Accept("none"), `accept is successful`) { return } if !assert.Equal(t, jwa.NoSignature, dst, `accepted value should be equal to constant`) { return } }) t.Run(`accept fmt.Stringer for none`, func(t *testing.T) { t.Parallel() var dst jwa.SignatureAlgorithm if !assert.NoError(t, dst.Accept(stringer{src: "none"}), `accept is successful`) { return } if !assert.Equal(t, jwa.NoSignature, dst, `accepted value should be equal to constant`) { return } }) t.Run(`stringification for none`, func(t *testing.T) { t.Parallel() if !assert.Equal(t, "none", jwa.NoSignature.String(), `stringified value matches`) { return } }) t.Run(`accept jwa constant PS256`, func(t *testing.T) { t.Parallel() var dst jwa.SignatureAlgorithm if !assert.NoError(t, dst.Accept(jwa.PS256), `accept is successful`) { return } if !assert.Equal(t, jwa.PS256, dst, `accepted value should be equal to constant`) { return } }) t.Run(`accept the string PS256`, func(t *testing.T) { t.Parallel() var dst jwa.SignatureAlgorithm if !assert.NoError(t, dst.Accept("PS256"), `accept is successful`) { return } if !assert.Equal(t, jwa.PS256, dst, `accepted value should be equal to constant`) { return } }) t.Run(`accept fmt.Stringer for PS256`, func(t *testing.T) { t.Parallel() var dst jwa.SignatureAlgorithm if !assert.NoError(t, dst.Accept(stringer{src: "PS256"}), `accept is successful`) { return } if !assert.Equal(t, jwa.PS256, dst, `accepted value should be equal to constant`) { return } }) t.Run(`stringification for PS256`, func(t *testing.T) { t.Parallel() if !assert.Equal(t, "PS256", jwa.PS256.String(), `stringified value matches`) { return } }) t.Run(`accept jwa constant PS384`, func(t *testing.T) { t.Parallel() var dst jwa.SignatureAlgorithm if !assert.NoError(t, dst.Accept(jwa.PS384), `accept is successful`) { return } if !assert.Equal(t, jwa.PS384, dst, `accepted value should be equal to constant`) { return } }) t.Run(`accept the string PS384`, func(t *testing.T) { t.Parallel() var dst jwa.SignatureAlgorithm if !assert.NoError(t, dst.Accept("PS384"), `accept is successful`) { return } if !assert.Equal(t, jwa.PS384, dst, `accepted value should be equal to constant`) { return } }) t.Run(`accept fmt.Stringer for PS384`, func(t *testing.T) { t.Parallel() var dst jwa.SignatureAlgorithm if !assert.NoError(t, dst.Accept(stringer{src: "PS384"}), `accept is successful`) { return } if !assert.Equal(t, jwa.PS384, dst, `accepted value should be equal to constant`) { return } }) t.Run(`stringification for PS384`, func(t *testing.T) { t.Parallel() if !assert.Equal(t, "PS384", jwa.PS384.String(), `stringified value matches`) { return } }) t.Run(`accept jwa constant PS512`, func(t *testing.T) { t.Parallel() var dst jwa.SignatureAlgorithm if !assert.NoError(t, dst.Accept(jwa.PS512), `accept is successful`) { return } if !assert.Equal(t, jwa.PS512, dst, `accepted value should be equal to constant`) { return } }) t.Run(`accept the string PS512`, func(t *testing.T) { t.Parallel() var dst jwa.SignatureAlgorithm if !assert.NoError(t, dst.Accept("PS512"), `accept is successful`) { return } if !assert.Equal(t, jwa.PS512, dst, `accepted value should be equal to constant`) { return } }) t.Run(`accept fmt.Stringer for PS512`, func(t *testing.T) { t.Parallel() var dst jwa.SignatureAlgorithm if !assert.NoError(t, dst.Accept(stringer{src: "PS512"}), `accept is successful`) { return } if !assert.Equal(t, jwa.PS512, dst, `accepted value should be equal to constant`) { return } }) t.Run(`stringification for PS512`, func(t *testing.T) { t.Parallel() if !assert.Equal(t, "PS512", jwa.PS512.String(), `stringified value matches`) { return } }) t.Run(`accept jwa constant RS256`, func(t *testing.T) { t.Parallel() var dst jwa.SignatureAlgorithm if !assert.NoError(t, dst.Accept(jwa.RS256), `accept is successful`) { return } if !assert.Equal(t, jwa.RS256, dst, `accepted value should be equal to constant`) { return } }) t.Run(`accept the string RS256`, func(t *testing.T) { t.Parallel() var dst jwa.SignatureAlgorithm if !assert.NoError(t, dst.Accept("RS256"), `accept is successful`) { return } if !assert.Equal(t, jwa.RS256, dst, `accepted value should be equal to constant`) { return } }) t.Run(`accept fmt.Stringer for RS256`, func(t *testing.T) { t.Parallel() var dst jwa.SignatureAlgorithm if !assert.NoError(t, dst.Accept(stringer{src: "RS256"}), `accept is successful`) { return } if !assert.Equal(t, jwa.RS256, dst, `accepted value should be equal to constant`) { return } }) t.Run(`stringification for RS256`, func(t *testing.T) { t.Parallel() if !assert.Equal(t, "RS256", jwa.RS256.String(), `stringified value matches`) { return } }) t.Run(`accept jwa constant RS384`, func(t *testing.T) { t.Parallel() var dst jwa.SignatureAlgorithm if !assert.NoError(t, dst.Accept(jwa.RS384), `accept is successful`) { return } if !assert.Equal(t, jwa.RS384, dst, `accepted value should be equal to constant`) { return } }) t.Run(`accept the string RS384`, func(t *testing.T) { t.Parallel() var dst jwa.SignatureAlgorithm if !assert.NoError(t, dst.Accept("RS384"), `accept is successful`) { return } if !assert.Equal(t, jwa.RS384, dst, `accepted value should be equal to constant`) { return } }) t.Run(`accept fmt.Stringer for RS384`, func(t *testing.T) { t.Parallel() var dst jwa.SignatureAlgorithm if !assert.NoError(t, dst.Accept(stringer{src: "RS384"}), `accept is successful`) { return } if !assert.Equal(t, jwa.RS384, dst, `accepted value should be equal to constant`) { return } }) t.Run(`stringification for RS384`, func(t *testing.T) { t.Parallel() if !assert.Equal(t, "RS384", jwa.RS384.String(), `stringified value matches`) { return } }) t.Run(`accept jwa constant RS512`, func(t *testing.T) { t.Parallel() var dst jwa.SignatureAlgorithm if !assert.NoError(t, dst.Accept(jwa.RS512), `accept is successful`) { return } if !assert.Equal(t, jwa.RS512, dst, `accepted value should be equal to constant`) { return } }) t.Run(`accept the string RS512`, func(t *testing.T) { t.Parallel() var dst jwa.SignatureAlgorithm if !assert.NoError(t, dst.Accept("RS512"), `accept is successful`) { return } if !assert.Equal(t, jwa.RS512, dst, `accepted value should be equal to constant`) { return } }) t.Run(`accept fmt.Stringer for RS512`, func(t *testing.T) { t.Parallel() var dst jwa.SignatureAlgorithm if !assert.NoError(t, dst.Accept(stringer{src: "RS512"}), `accept is successful`) { return } if !assert.Equal(t, jwa.RS512, dst, `accepted value should be equal to constant`) { return } }) t.Run(`stringification for RS512`, func(t *testing.T) { t.Parallel() if !assert.Equal(t, "RS512", jwa.RS512.String(), `stringified value matches`) { return } }) t.Run(`bail out on random integer value`, func(t *testing.T) { t.Parallel() var dst jwa.SignatureAlgorithm if !assert.Error(t, dst.Accept(1), `accept should fail`) { return } }) t.Run(`do not accept invalid (totally made up) string value`, func(t *testing.T) { t.Parallel() var dst jwa.SignatureAlgorithm if !assert.Error(t, dst.Accept(`totallyInvalidValue`), `accept should fail`) { return } }) t.Run(`check symmetric values`, func(t *testing.T) { t.Parallel() t.Run(`ES256`, func(t *testing.T) { assert.False(t, jwa.ES256.IsSymmetric(), `jwa.ES256 should NOT be symmetric`) }) t.Run(`ES256K`, func(t *testing.T) { assert.False(t, jwa.ES256K.IsSymmetric(), `jwa.ES256K should NOT be symmetric`) }) t.Run(`ES384`, func(t *testing.T) { assert.False(t, jwa.ES384.IsSymmetric(), `jwa.ES384 should NOT be symmetric`) }) t.Run(`ES512`, func(t *testing.T) { assert.False(t, jwa.ES512.IsSymmetric(), `jwa.ES512 should NOT be symmetric`) }) t.Run(`EdDSA`, func(t *testing.T) { assert.False(t, jwa.EdDSA.IsSymmetric(), `jwa.EdDSA should NOT be symmetric`) }) t.Run(`HS256`, func(t *testing.T) { assert.True(t, jwa.HS256.IsSymmetric(), `jwa.HS256 should be symmetric`) }) t.Run(`HS384`, func(t *testing.T) { assert.True(t, jwa.HS384.IsSymmetric(), `jwa.HS384 should be symmetric`) }) t.Run(`HS512`, func(t *testing.T) { assert.True(t, jwa.HS512.IsSymmetric(), `jwa.HS512 should be symmetric`) }) t.Run(`NoSignature`, func(t *testing.T) { assert.False(t, jwa.NoSignature.IsSymmetric(), `jwa.NoSignature should NOT be symmetric`) }) t.Run(`PS256`, func(t *testing.T) { assert.False(t, jwa.PS256.IsSymmetric(), `jwa.PS256 should NOT be symmetric`) }) t.Run(`PS384`, func(t *testing.T) { assert.False(t, jwa.PS384.IsSymmetric(), `jwa.PS384 should NOT be symmetric`) }) t.Run(`PS512`, func(t *testing.T) { assert.False(t, jwa.PS512.IsSymmetric(), `jwa.PS512 should NOT be symmetric`) }) t.Run(`RS256`, func(t *testing.T) { assert.False(t, jwa.RS256.IsSymmetric(), `jwa.RS256 should NOT be symmetric`) }) t.Run(`RS384`, func(t *testing.T) { assert.False(t, jwa.RS384.IsSymmetric(), `jwa.RS384 should NOT be symmetric`) }) t.Run(`RS512`, func(t *testing.T) { assert.False(t, jwa.RS512.IsSymmetric(), `jwa.RS512 should NOT be symmetric`) }) }) 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() { if _, ok := expected[v]; !assert.True(t, ok, `%s should be in the expected list`, v) { return } delete(expected, v) } if !assert.Len(t, expected, 0) { return } }) } // 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. customAlgorithm := jwa.SignatureAlgorithm("custom-algorithm") // 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(`accept variable used to register custom algorithm`, func(t *testing.T) { t.Parallel() var dst jwa.SignatureAlgorithm if !assert.NoError(t, dst.Accept(customAlgorithm), `accept is successful`) { return } assert.Equal(t, customAlgorithm, dst, `accepted value should be equal to variable`) }) t.Run(`accept the string custom-algorithm`, func(t *testing.T) { t.Parallel() var dst jwa.SignatureAlgorithm if !assert.NoError(t, dst.Accept(`custom-algorithm`), `accept is successful`) { return } assert.Equal(t, customAlgorithm, dst, `accepted value should be equal to variable`) }) t.Run(`accept fmt.Stringer for custom-algorithm`, func(t *testing.T) { t.Parallel() var dst jwa.SignatureAlgorithm if !assert.NoError(t, dst.Accept(stringer{src: `custom-algorithm`}), `accept is successful`) { return } assert.Equal(t, customAlgorithm, dst, `accepted value should be equal to variable`) }) t.Run(`check symmetric`, func(t *testing.T) { t.Parallel() assert.False(t, customAlgorithm.IsSymmetric(), `custom algorithm should NOT be symmetric`) }) }) t.Run(`with custom algorithm deregistered`, func(t *testing.T) { jwa.UnregisterSignatureAlgorithm(customAlgorithm) t.Run(`reject variable used to register custom algorithm`, func(t *testing.T) { t.Parallel() var dst jwa.SignatureAlgorithm assert.Error(t, dst.Accept(customAlgorithm), `accept failed`) }) t.Run(`reject the string custom-algorithm`, func(t *testing.T) { t.Parallel() var dst jwa.SignatureAlgorithm assert.Error(t, dst.Accept(`custom-algorithm`), `accept failed`) }) t.Run(`reject fmt.Stringer for custom-algorithm`, func(t *testing.T) { t.Parallel() var dst jwa.SignatureAlgorithm assert.Error(t, dst.Accept(stringer{src: `custom-algorithm`}), `accept failed`) }) t.Run(`check symmetric`, func(t *testing.T) { t.Parallel() assert.False(t, customAlgorithm.IsSymmetric(), `custom algorithm should NOT be symmetric`) }) }) t.Run(`with custom algorithm registered with WithSymmetricAlgorithm(false)`, func(t *testing.T) { jwa.RegisterSignatureAlgorithmWithOptions(customAlgorithm, jwa.WithSymmetricAlgorithm(false)) t.Run(`accept variable used to register custom algorithm`, func(t *testing.T) { t.Parallel() var dst jwa.SignatureAlgorithm if !assert.NoError(t, dst.Accept(customAlgorithm), `accept is successful`) { return } assert.Equal(t, customAlgorithm, dst, `accepted value should be equal to variable`) }) t.Run(`accept the string custom-algorithm`, func(t *testing.T) { t.Parallel() var dst jwa.SignatureAlgorithm if !assert.NoError(t, dst.Accept(`custom-algorithm`), `accept is successful`) { return } assert.Equal(t, customAlgorithm, dst, `accepted value should be equal to variable`) }) t.Run(`accept fmt.Stringer for custom-algorithm`, func(t *testing.T) { t.Parallel() var dst jwa.SignatureAlgorithm if !assert.NoError(t, dst.Accept(stringer{src: `custom-algorithm`}), `accept is successful`) { return } assert.Equal(t, customAlgorithm, dst, `accepted value should be equal to variable`) }) t.Run(`check symmetric`, func(t *testing.T) { t.Parallel() assert.False(t, customAlgorithm.IsSymmetric(), `custom algorithm should NOT be symmetric`) }) }) t.Run(`with custom algorithm deregistered (was WithSymmetricAlgorithm(false))`, func(t *testing.T) { jwa.UnregisterSignatureAlgorithm(customAlgorithm) t.Run(`reject variable used to register custom algorithm`, func(t *testing.T) { t.Parallel() var dst jwa.SignatureAlgorithm assert.Error(t, dst.Accept(customAlgorithm), `accept failed`) }) t.Run(`reject the string custom-algorithm`, func(t *testing.T) { t.Parallel() var dst jwa.SignatureAlgorithm assert.Error(t, dst.Accept(`custom-algorithm`), `accept failed`) }) t.Run(`reject fmt.Stringer for custom-algorithm`, func(t *testing.T) { t.Parallel() var dst jwa.SignatureAlgorithm assert.Error(t, dst.Accept(stringer{src: `custom-algorithm`}), `accept failed`) }) t.Run(`check symmetric`, func(t *testing.T) { t.Parallel() assert.False(t, customAlgorithm.IsSymmetric(), `custom algorithm should NOT be symmetric`) }) }) t.Run(`with custom algorithm registered with WithSymmetricAlgorithm(true)`, func(t *testing.T) { jwa.RegisterSignatureAlgorithmWithOptions(customAlgorithm, jwa.WithSymmetricAlgorithm(true)) t.Run(`accept variable used to register custom algorithm`, func(t *testing.T) { t.Parallel() var dst jwa.SignatureAlgorithm if !assert.NoError(t, dst.Accept(customAlgorithm), `accept is successful`) { return } assert.Equal(t, customAlgorithm, dst, `accepted value should be equal to variable`) }) t.Run(`accept the string custom-algorithm`, func(t *testing.T) { t.Parallel() var dst jwa.SignatureAlgorithm if !assert.NoError(t, dst.Accept(`custom-algorithm`), `accept is successful`) { return } assert.Equal(t, customAlgorithm, dst, `accepted value should be equal to variable`) }) t.Run(`accept fmt.Stringer for custom-algorithm`, func(t *testing.T) { t.Parallel() var dst jwa.SignatureAlgorithm if !assert.NoError(t, dst.Accept(stringer{src: `custom-algorithm`}), `accept is successful`) { return } assert.Equal(t, customAlgorithm, dst, `accepted value should be equal to variable`) }) t.Run(`check symmetric`, func(t *testing.T) { t.Parallel() assert.True(t, customAlgorithm.IsSymmetric(), `custom algorithm should be symmetric`) }) }) t.Run(`with custom algorithm deregistered (was WithSymmetricAlgorithm(true))`, func(t *testing.T) { jwa.UnregisterSignatureAlgorithm(customAlgorithm) t.Run(`reject variable used to register custom algorithm`, func(t *testing.T) { t.Parallel() var dst jwa.SignatureAlgorithm assert.Error(t, dst.Accept(customAlgorithm), `accept failed`) }) t.Run(`reject the string custom-algorithm`, func(t *testing.T) { t.Parallel() var dst jwa.SignatureAlgorithm assert.Error(t, dst.Accept(`custom-algorithm`), `accept failed`) }) t.Run(`reject fmt.Stringer for custom-algorithm`, func(t *testing.T) { t.Parallel() var dst jwa.SignatureAlgorithm assert.Error(t, dst.Accept(stringer{src: `custom-algorithm`}), `accept failed`) }) t.Run(`check symmetric`, func(t *testing.T) { t.Parallel() assert.False(t, customAlgorithm.IsSymmetric(), `custom algorithm should NOT be symmetric`) }) }) } golang-github-lestrrat-go-jwx-2.1.4/jwe/000077500000000000000000000000001476711647200201205ustar00rootroot00000000000000golang-github-lestrrat-go-jwx-2.1.4/jwe/BUILD.bazel000066400000000000000000000032171476711647200220010ustar00rootroot00000000000000load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") go_library( name = "jwe", srcs = [ "compress.go", "decrypt.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/v2/jwe", visibility = ["//visibility:public"], deps = [ "//cert", "//internal/base64", "//internal/iter", "//internal/json", "//internal/keyconv", "//internal/pool", "//jwa", "//jwe/internal/aescbc", "//jwe/internal/cipher", "//jwe/internal/content_crypt", "//jwe/internal/keyenc", "//jwe/internal/keygen", "//jwk", "//x25519", "@com_github_lestrrat_go_blackmagic//:go_default_library", "@com_github_lestrrat_go_iter//mapiter:go_default_library", "@com_github_lestrrat_go_option//:option", "@org_golang_x_crypto//pbkdf2", ], ) go_test( name = "jwe_test", srcs = [ "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", "//x25519", "@com_github_stretchr_testify//assert", "@com_github_stretchr_testify//require", ], ) alias( name = "go_default_library", actual = ":jwe", visibility = ["//visibility:public"], ) golang-github-lestrrat-go-jwx-2.1.4/jwe/README.md000066400000000000000000000073261476711647200214070ustar00rootroot00000000000000# JWE [![Go Reference](https://pkg.go.dev/badge/github.com/lestrrat-go/jwx/v2/jwe.svg)](https://pkg.go.dev/github.com/lestrrat-go/jwx/v2/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-2.1.4/jwe/compress.go000066400000000000000000000026111476711647200223020ustar00rootroot00000000000000package jwe import ( "bytes" "compress/flate" "fmt" "io" "github.com/lestrrat-go/jwx/v2/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.GetBytesBuffer() defer pool.ReleaseBytesBuffer(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-2.1.4/jwe/decrypt.go000066400000000000000000000211341476711647200221220ustar00rootroot00000000000000package jwe import ( "crypto/aes" cryptocipher "crypto/cipher" "crypto/ecdsa" "crypto/rsa" "crypto/sha256" "crypto/sha512" "fmt" "hash" "golang.org/x/crypto/pbkdf2" "github.com/lestrrat-go/jwx/v2/internal/keyconv" "github.com/lestrrat-go/jwx/v2/jwa" "github.com/lestrrat-go/jwx/v2/jwe/internal/cipher" "github.com/lestrrat-go/jwx/v2/jwe/internal/content_crypt" "github.com/lestrrat-go/jwx/v2/jwe/internal/keyenc" "github.com/lestrrat-go/jwx/v2/x25519" ) // 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 interface{} pubkey interface{} 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 interface{}) *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 interface{}) *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 { switch d.ctalg { case jwa.A128GCM, jwa.A192GCM, jwa.A256GCM, jwa.A128CBC_HS256, jwa.A192CBC_HS384, jwa.A256CBC_HS512: cipher, err := cipher.NewAES(d.ctalg) if err != nil { return nil, fmt.Errorf(`failed to build content cipher for %s: %w`, d.ctalg, err) } d.cipher = cipher default: return nil, fmt.Errorf(`invalid content cipher algorithm (%s)`, d.ctalg) } } 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, '.'), 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) decryptSymmetricKey(recipientKey, cek []byte) ([]byte, error) { switch d.keyalg { case jwa.DIRECT: return cek, nil case jwa.PBES2_HS256_A128KW, jwa.PBES2_HS384_A192KW, jwa.PBES2_HS512_A256KW: var hashFunc func() hash.Hash var keylen int switch d.keyalg { case jwa.PBES2_HS256_A128KW: hashFunc = sha256.New keylen = 16 case jwa.PBES2_HS384_A192KW: hashFunc = sha512.New384 keylen = 24 case jwa.PBES2_HS512_A256KW: hashFunc = sha512.New keylen = 32 } salt := []byte(d.keyalg) salt = append(salt, byte(0)) salt = append(salt, d.keysalt...) cek = pbkdf2.Key(cek, salt, d.keycount, keylen, hashFunc) fallthrough case jwa.A128KW, jwa.A192KW, jwa.A256KW: block, err := aes.NewCipher(cek) if err != nil { return nil, fmt.Errorf(`failed to create new AES cipher: %w`, err) } jek, err := keyenc.Unwrap(block, recipientKey) if err != nil { return nil, fmt.Errorf(`failed to unwrap key: %w`, err) } return jek, nil case jwa.A128GCMKW, jwa.A192GCMKW, jwa.A256GCMKW: if len(d.keyiv) != 12 { return nil, fmt.Errorf("GCM requires 96-bit iv, got %d", len(d.keyiv)*8) } if len(d.keytag) != 16 { return nil, fmt.Errorf("GCM requires 128-bit tag, got %d", len(d.keytag)*8) } block, err := aes.NewCipher(cek) 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) } ciphertext := recipientKey[:] ciphertext = append(ciphertext, d.keytag...) jek, err := aesgcm.Open(nil, d.keyiv, ciphertext, nil) if err != nil { return nil, fmt.Errorf(`failed to decode key: %w`, err) } return jek, nil default: return nil, fmt.Errorf("decrypt key: unsupported algorithm %s", d.keyalg) } } func (d *decrypter) DecryptKey(recipient Recipient, msg *Message) (cek []byte, err error) { recipientKey := recipient.EncryptedKey() if kd, ok := d.privkey.(KeyDecrypter); ok { return kd.DecryptKey(d.keyalg, recipientKey, recipient, msg) } if d.keyalg.IsSymmetric() { var ok bool cek, ok = d.privkey.([]byte) if !ok { return nil, fmt.Errorf("decrypt key: []byte is required as the key to build %s key decrypter (got %T)", d.keyalg, d.privkey) } return d.decryptSymmetricKey(recipientKey, cek) } k, err := d.BuildKeyDecrypter() if err != nil { return nil, fmt.Errorf(`failed to build key decrypter: %w`, err) } cek, err = k.Decrypt(recipientKey) if err != nil { return nil, fmt.Errorf(`failed to decrypt key: %w`, err) } return cek, nil } func (d *decrypter) BuildKeyDecrypter() (keyenc.Decrypter, error) { cipher, err := d.ContentCipher() if err != nil { return nil, fmt.Errorf(`failed to fetch content crypt cipher: %w`, err) } switch alg := d.keyalg; alg { case jwa.RSA1_5: var privkey rsa.PrivateKey if err := keyconv.RSAPrivateKey(&privkey, d.privkey); err != nil { return nil, fmt.Errorf(`*rsa.PrivateKey is required as the key to build %s key decrypter: %w`, alg, err) } return keyenc.NewRSAPKCS15Decrypt(alg, &privkey, cipher.KeySize()/2), nil case jwa.RSA_OAEP, jwa.RSA_OAEP_256, jwa.RSA_OAEP_384, jwa.RSA_OAEP_512: var privkey rsa.PrivateKey if err := keyconv.RSAPrivateKey(&privkey, d.privkey); err != nil { return nil, fmt.Errorf(`*rsa.PrivateKey is required as the key to build %s key decrypter: %w`, alg, err) } return keyenc.NewRSAOAEPDecrypt(alg, &privkey) case jwa.A128KW, jwa.A192KW, jwa.A256KW: sharedkey, ok := d.privkey.([]byte) if !ok { return nil, fmt.Errorf("[]byte is required as the key to build %s key decrypter", alg) } return keyenc.NewAES(alg, sharedkey) case jwa.ECDH_ES, jwa.ECDH_ES_A128KW, jwa.ECDH_ES_A192KW, jwa.ECDH_ES_A256KW: switch d.pubkey.(type) { case x25519.PublicKey: return keyenc.NewECDHESDecrypt(alg, d.ctalg, d.pubkey, d.apu, d.apv, d.privkey), nil default: var pubkey ecdsa.PublicKey if err := keyconv.ECDSAPublicKey(&pubkey, d.pubkey); err != nil { return nil, fmt.Errorf(`*ecdsa.PublicKey is required as the key to build %s key decrypter: %w`, alg, err) } var privkey ecdsa.PrivateKey if err := keyconv.ECDSAPrivateKey(&privkey, d.privkey); err != nil { return nil, fmt.Errorf(`*ecdsa.PrivateKey is required as the key to build %s key decrypter: %w`, alg, err) } return keyenc.NewECDHESDecrypt(alg, d.ctalg, &pubkey, d.apu, d.apv, &privkey), nil } default: return nil, fmt.Errorf(`unsupported algorithm for key decryption (%s)`, alg) } } golang-github-lestrrat-go-jwx-2.1.4/jwe/gh402_test.go000066400000000000000000000273221476711647200223400ustar00rootroot00000000000000package jwe_test import ( "context" "testing" "github.com/lestrrat-go/jwx/v2/internal/json" "github.com/lestrrat-go/jwx/v2/jwa" "github.com/lestrrat-go/jwx/v2/jwe" "github.com/stretchr/testify/assert" ) // 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.KeyEncryptionAlgorithm("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 })), ) if !assert.NoError(t, err, `jwe.Decrypt should succeed`) { return } if string(plain) != "testing Shamir Secret Sharing" { t.Errorf("expected 'testing Shamir Secret Sharing', got %s", string(plain)) return } if customField { if !assert.NotNil(t, m.ProtectedHeaders(), `m.ProtectedHeaders should be non-nil`) { return } v, ok := m.ProtectedHeaders().Get("clevis") if !assert.True(t, ok, `m.Get("clevis") should be true`) { return } if !assert.IsType(t, Pin{}, v, `result of m.Get("clevis") should be an instance of Pin{}`) { return } } } 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-2.1.4/jwe/headers.go000066400000000000000000000056501476711647200220700ustar00rootroot00000000000000package jwe import ( "context" "fmt" "github.com/lestrrat-go/jwx/v2/internal/base64" "github.com/lestrrat-go/jwx/v2/internal/json" "github.com/lestrrat-go/iter/mapiter" "github.com/lestrrat-go/jwx/v2/internal/iter" ) 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 } // Iterate returns a channel that successively returns all the // header name and values. func (h *stdHeaders) Iterate(ctx context.Context) Iterator { pairs := h.makePairs() ch := make(chan *HeaderPair, len(pairs)) go func(ctx context.Context, ch chan *HeaderPair, pairs []*HeaderPair) { defer close(ch) for _, pair := range pairs { select { case <-ctx.Done(): return case ch <- pair: } } }(ctx, ch, pairs) return mapiter.New(ch) } func (h *stdHeaders) Walk(ctx context.Context, visitor Visitor) error { return iter.WalkMap(ctx, h, visitor) } func (h *stdHeaders) AsMap(ctx context.Context) (map[string]interface{}, error) { return iter.AsMap(ctx, h) } func (h *stdHeaders) Clone(ctx context.Context) (Headers, error) { dst := NewHeaders() if err := h.Copy(ctx, dst); err != nil { return nil, fmt.Errorf(`failed to copy header contents to new object: %w`, err) } return dst, nil } func (h *stdHeaders) Copy(_ context.Context, dst Headers) error { for _, pair := range h.makePairs() { //nolint:forcetypeassert key := pair.Key.(string) if err := dst.Set(key, pair.Value); err != nil { return fmt.Errorf(`failed to set header %q: %w`, key, err) } } return nil } func (h *stdHeaders) Merge(ctx context.Context, h2 Headers) (Headers, error) { h3 := NewHeaders() if h != nil { if err := h.Copy(ctx, h3); err != nil { return nil, fmt.Errorf(`failed to copy headers from receiver: %w`, err) } } if h2 != nil { if err := h2.Copy(ctx, 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-2.1.4/jwe/headers_gen.go000066400000000000000000000455011476711647200227200ustar00rootroot00000000000000// Code generated by tools/cmd/genjwe/main.go. DO NOT EDIT. package jwe import ( "bytes" "context" "fmt" "sort" "sync" "github.com/lestrrat-go/jwx/v2/cert" "github.com/lestrrat-go/jwx/v2/internal/base64" "github.com/lestrrat-go/jwx/v2/internal/json" "github.com/lestrrat-go/jwx/v2/internal/pool" "github.com/lestrrat-go/jwx/v2/jwa" "github.com/lestrrat-go/jwx/v2/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 Header set. type Headers interface { json.Marshaler json.Unmarshaler 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 Type() string X509CertChain() *cert.Chain X509CertThumbprint() string X509CertThumbprintS256() string X509URL() string Iterate(ctx context.Context) Iterator Walk(ctx context.Context, v Visitor) error AsMap(ctx context.Context) (map[string]interface{}, error) Get(string) (interface{}, bool) Set(string, interface{}) error Remove(string) error Encode() ([]byte, error) Decode([]byte) error // PrivateParams returns the map containing the non-standard ('private') parameters // in the associated header. WARNING: DO NOT USE PrivateParams() // IF YOU HAVE CONCURRENT CODE ACCESSING THEM. Use AsMap() to // get a copy of the entire header instead PrivateParams() map[string]interface{} Clone(context.Context) (Headers, error) Copy(context.Context, Headers) error Merge(context.Context, Headers) (Headers, error) } 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]interface{} mu *sync.RWMutex } func NewHeaders() Headers { return &stdHeaders{ mu: &sync.RWMutex{}, privateParams: map[string]interface{}{}, } } func (h *stdHeaders) AgreementPartyUInfo() []byte { h.mu.RLock() defer h.mu.RUnlock() return h.agreementPartyUInfo } func (h *stdHeaders) AgreementPartyVInfo() []byte { h.mu.RLock() defer h.mu.RUnlock() return h.agreementPartyVInfo } func (h *stdHeaders) Algorithm() jwa.KeyEncryptionAlgorithm { h.mu.RLock() defer h.mu.RUnlock() if h.algorithm == nil { return "" } return *(h.algorithm) } func (h *stdHeaders) Compression() jwa.CompressionAlgorithm { h.mu.RLock() defer h.mu.RUnlock() if h.compression == nil { return jwa.NoCompress } return *(h.compression) } func (h *stdHeaders) ContentEncryption() jwa.ContentEncryptionAlgorithm { h.mu.RLock() defer h.mu.RUnlock() if h.contentEncryption == nil { return "" } return *(h.contentEncryption) } func (h *stdHeaders) ContentType() string { h.mu.RLock() defer h.mu.RUnlock() if h.contentType == nil { return "" } return *(h.contentType) } func (h *stdHeaders) Critical() []string { h.mu.RLock() defer h.mu.RUnlock() return h.critical } func (h *stdHeaders) EphemeralPublicKey() jwk.Key { h.mu.RLock() defer h.mu.RUnlock() return h.ephemeralPublicKey } func (h *stdHeaders) JWK() jwk.Key { h.mu.RLock() defer h.mu.RUnlock() return h.jwk } func (h *stdHeaders) JWKSetURL() string { h.mu.RLock() defer h.mu.RUnlock() if h.jwkSetURL == nil { return "" } return *(h.jwkSetURL) } func (h *stdHeaders) KeyID() string { h.mu.RLock() defer h.mu.RUnlock() if h.keyID == nil { return "" } return *(h.keyID) } func (h *stdHeaders) Type() string { h.mu.RLock() defer h.mu.RUnlock() if h.typ == nil { return "" } return *(h.typ) } func (h *stdHeaders) X509CertChain() *cert.Chain { h.mu.RLock() defer h.mu.RUnlock() return h.x509CertChain } func (h *stdHeaders) X509CertThumbprint() string { h.mu.RLock() defer h.mu.RUnlock() if h.x509CertThumbprint == nil { return "" } return *(h.x509CertThumbprint) } func (h *stdHeaders) X509CertThumbprintS256() string { h.mu.RLock() defer h.mu.RUnlock() if h.x509CertThumbprintS256 == nil { return "" } return *(h.x509CertThumbprintS256) } func (h *stdHeaders) X509URL() string { h.mu.RLock() defer h.mu.RUnlock() if h.x509URL == nil { return "" } return *(h.x509URL) } func (h *stdHeaders) makePairs() []*HeaderPair { h.mu.RLock() defer h.mu.RUnlock() var pairs []*HeaderPair if h.agreementPartyUInfo != nil { pairs = append(pairs, &HeaderPair{Key: AgreementPartyUInfoKey, Value: h.agreementPartyUInfo}) } if h.agreementPartyVInfo != nil { pairs = append(pairs, &HeaderPair{Key: AgreementPartyVInfoKey, Value: h.agreementPartyVInfo}) } if h.algorithm != nil { pairs = append(pairs, &HeaderPair{Key: AlgorithmKey, Value: *(h.algorithm)}) } if h.compression != nil { pairs = append(pairs, &HeaderPair{Key: CompressionKey, Value: *(h.compression)}) } if h.contentEncryption != nil { pairs = append(pairs, &HeaderPair{Key: ContentEncryptionKey, Value: *(h.contentEncryption)}) } if h.contentType != nil { pairs = append(pairs, &HeaderPair{Key: ContentTypeKey, Value: *(h.contentType)}) } if h.critical != nil { pairs = append(pairs, &HeaderPair{Key: CriticalKey, Value: h.critical}) } if h.ephemeralPublicKey != nil { pairs = append(pairs, &HeaderPair{Key: EphemeralPublicKeyKey, Value: h.ephemeralPublicKey}) } if h.jwk != nil { pairs = append(pairs, &HeaderPair{Key: JWKKey, Value: h.jwk}) } if h.jwkSetURL != nil { pairs = append(pairs, &HeaderPair{Key: JWKSetURLKey, Value: *(h.jwkSetURL)}) } if h.keyID != nil { pairs = append(pairs, &HeaderPair{Key: KeyIDKey, Value: *(h.keyID)}) } if h.typ != nil { pairs = append(pairs, &HeaderPair{Key: TypeKey, Value: *(h.typ)}) } if h.x509CertChain != nil { pairs = append(pairs, &HeaderPair{Key: X509CertChainKey, Value: h.x509CertChain}) } if h.x509CertThumbprint != nil { pairs = append(pairs, &HeaderPair{Key: X509CertThumbprintKey, Value: *(h.x509CertThumbprint)}) } if h.x509CertThumbprintS256 != nil { pairs = append(pairs, &HeaderPair{Key: X509CertThumbprintS256Key, Value: *(h.x509CertThumbprintS256)}) } if h.x509URL != nil { pairs = append(pairs, &HeaderPair{Key: X509URLKey, Value: *(h.x509URL)}) } for k, v := range h.privateParams { pairs = append(pairs, &HeaderPair{Key: k, Value: v}) } return pairs } func (h *stdHeaders) PrivateParams() map[string]interface{} { h.mu.RLock() defer h.mu.RUnlock() return h.privateParams } func (h *stdHeaders) Get(name string) (interface{}, bool) { h.mu.RLock() defer h.mu.RUnlock() switch name { case AgreementPartyUInfoKey: if h.agreementPartyUInfo == nil { return nil, false } return h.agreementPartyUInfo, true case AgreementPartyVInfoKey: if h.agreementPartyVInfo == nil { return nil, false } return h.agreementPartyVInfo, true case AlgorithmKey: if h.algorithm == nil { return nil, false } return *(h.algorithm), true case CompressionKey: if h.compression == nil { return nil, false } return *(h.compression), true case ContentEncryptionKey: if h.contentEncryption == nil { return nil, false } return *(h.contentEncryption), true case ContentTypeKey: if h.contentType == nil { return nil, false } return *(h.contentType), true case CriticalKey: if h.critical == nil { return nil, false } return h.critical, true case EphemeralPublicKeyKey: if h.ephemeralPublicKey == nil { return nil, false } return h.ephemeralPublicKey, true case JWKKey: if h.jwk == nil { return nil, false } return h.jwk, true case JWKSetURLKey: if h.jwkSetURL == nil { return nil, false } return *(h.jwkSetURL), true case KeyIDKey: if h.keyID == nil { return nil, false } return *(h.keyID), true case TypeKey: if h.typ == nil { return nil, false } return *(h.typ), true case X509CertChainKey: if h.x509CertChain == nil { return nil, false } return h.x509CertChain, true case X509CertThumbprintKey: if h.x509CertThumbprint == nil { return nil, false } return *(h.x509CertThumbprint), true case X509CertThumbprintS256Key: if h.x509CertThumbprintS256 == nil { return nil, false } return *(h.x509CertThumbprintS256), true case X509URLKey: if h.x509URL == nil { return nil, false } return *(h.x509URL), true default: v, ok := h.privateParams[name] return v, ok } } func (h *stdHeaders) Set(name string, value interface{}) error { h.mu.Lock() defer h.mu.Unlock() return h.setNoLock(name, value) } func (h *stdHeaders) setNoLock(name string, value interface{}) 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 == "" { 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]interface{}{} } 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 '{' or '}' here. if tok == '}' { // End of object break LOOP } else if tok != '{' { return fmt.Errorf(`expected '{', but got '%c'`, 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) MarshalJSON() ([]byte, error) { data := make(map[string]interface{}) fields := make([]string, 0, 16) for _, pair := range h.makePairs() { fields = append(fields, pair.Key.(string)) data[pair.Key.(string)] = pair.Value } sort.Strings(fields) buf := pool.GetBytesBuffer() defer pool.ReleaseBytesBuffer(buf) buf.WriteByte('{') enc := json.NewEncoder(buf) for i, f := range fields { if i > 0 { buf.WriteRune(',') } buf.WriteRune('"') buf.WriteString(f) buf.WriteString(`":`) v := data[f] switch v := v.(type) { case []byte: buf.WriteRune('"') buf.WriteString(base64.EncodeToString(v)) buf.WriteRune('"') default: if err := enc.Encode(v); err != nil { return nil, fmt.Errorf(`failed to encode value for field %s`, f) } buf.Truncate(buf.Len() - 1) } } buf.WriteByte('}') ret := make([]byte, buf.Len()) copy(ret, buf.Bytes()) return ret, nil } golang-github-lestrrat-go-jwx-2.1.4/jwe/headers_test.go000066400000000000000000000243771476711647200231360ustar00rootroot00000000000000package jwe_test import ( "context" "reflect" "testing" "github.com/lestrrat-go/jwx/v2/cert" "github.com/lestrrat-go/jwx/v2/internal/jwxtest" "github.com/lestrrat-go/jwx/v2/jwa" "github.com/lestrrat-go/jwx/v2/jwe" "github.com/lestrrat-go/jwx/v2/jwk" "github.com/stretchr/testify/assert" ) 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) if !assert.NoError(t, err, `jwxtest.GenerateEcdsaKey should succeed`) { return } privKey, err := jwk.FromRaw(rawKey) if !assert.NoError(t, err, `jwk.FromRaw should succeed`) { return } pubKey, err := jwk.FromRaw(rawKey.PublicKey) if !assert.NoError(t, err, `jwk.FromRaw should succeed`) { return } data := []struct { Key string Value interface{} Expected interface{} 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/v2", 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/v2", 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 { if !assert.NoError(t, base.Set(tc.Key, tc.Value), "Headers.Set should succeed") { return } } }) t.Run("Set/Get", func(t *testing.T) { h := jwe.NewHeaders() ctx := context.Background() for iter := base.Iterate(ctx); iter.Next(ctx); { pair := iter.Pair() if !assert.NoError(t, h.Set(pair.Key.(string), pair.Value), `h.Set should be successful`) { return } } for _, tc := range data { var values []interface{} viaGet, ok := h.Get(tc.Key) if !assert.True(t, ok, "value for %s should exist", tc.Key) { return } values = append(values, viaGet) if method := tc.Method; method != "" { m := reflect.ValueOf(h).MethodByName(method) if !assert.NotEqual(t, m, zeroval, "method %s should be available", method) { return } ret := m.Call(nil) if !assert.Len(t, ret, 1, `should get exactly 1 value as return value`) { return } values = append(values, ret[0].Interface()) } expected := tc.Expected if expected == nil { expected = tc.Value } for i, got := range values { if !assert.Equal(t, expected, got, "value %d should match", i) { return } } } }) t.Run("PrivateParams", func(t *testing.T) { h := base pp, err := h.AsMap(context.Background()) if !assert.NoError(t, err, `h.AsMap should succeed`) { return } v, ok := pp["private"] if !assert.True(t, ok, "key 'private' should exists") { return } if !assert.Equal(t, v, "boofoo", "value for 'private' should match") { return } }) t.Run("Encode", func(t *testing.T) { h1 := jwe.NewHeaders() h1.Set(jwe.AlgorithmKey, jwa.A128GCMKW) h1.Set("foo", "bar") buf, err := h1.Encode() if !assert.NoError(t, err, `h1.Encode should succeed`) { return } h2 := jwe.NewHeaders() if !assert.NoError(t, h2.Decode(buf), `h2.Decode should succeed`) { return } if !assert.Equal(t, h1, h2, `objects should match`) { return } }) t.Run("Iterator", func(t *testing.T) { expected := map[string]interface{}{} for _, tc := range data { v := tc.Value if expected := tc.Expected; expected != nil { v = expected } expected[tc.Key] = v } v := base t.Run("Iterate", func(t *testing.T) { seen := make(map[string]interface{}) for iter := v.Iterate(context.TODO()); iter.Next(context.TODO()); { pair := iter.Pair() seen[pair.Key.(string)] = pair.Value getV, ok := v.Get(pair.Key.(string)) if !assert.True(t, ok, `v.Get should succeed for key %#v`, pair.Key) { return } if !assert.Equal(t, pair.Value, getV, `pair.Value should match value from v.Get()`) { return } } if !assert.Equal(t, expected, seen, `values should match`) { return } }) t.Run("Walk", func(t *testing.T) { seen := make(map[string]interface{}) v.Walk(context.TODO(), jwk.HeaderVisitorFunc(func(key string, value interface{}) error { seen[key] = value return nil })) if !assert.Equal(t, expected, seen, `values should match`) { return } }) t.Run("AsMap", func(t *testing.T) { m, err := v.AsMap(context.TODO()) if !assert.NoError(t, err, `v.AsMap should succeed`) { return } if !assert.Equal(t, expected, m, `values should match`) { return } }) t.Run("Remove", func(t *testing.T) { h := base for iter := h.Iterate(context.TODO()); iter.Next(context.TODO()); { pair := iter.Pair() h.Remove(pair.Key.(string)) } m, err := h.AsMap(context.TODO()) if !assert.NoError(t, err, `h.AsMap should succeed`) { return } if !assert.Len(t, m, 0, `len should be zero`) { return } }) }) } golang-github-lestrrat-go-jwx-2.1.4/jwe/interface.go000066400000000000000000000210751476711647200224140ustar00rootroot00000000000000package jwe import ( "github.com/lestrrat-go/iter/mapiter" "github.com/lestrrat-go/jwx/v2/internal/iter" "github.com/lestrrat-go/jwx/v2/jwa" "github.com/lestrrat-go/jwx/v2/jwe/internal/keygen" ) // KeyEncrypter is an interface for object that can encrypt a // content encryption key. // // You can use this in place of a regular key (i.e. in jwe.WithKey()) // to encrypt the content encryption key in a JWE message without // having to expose the secret key in memory, for example, when you // want to use hardware security modules (HSMs) to encrypt the key. // // This API is experimental and may change without notice, even // in minor releases. type KeyEncrypter interface { // Algorithm returns the algorithm used to encrypt the key. Algorithm() jwa.KeyEncryptionAlgorithm // EncryptKey encrypts the given content encryption key. EncryptKey([]byte) ([]byte, error) } // KeyIDer is an interface for things that can return a key ID. // // As of this writing, this is solely used to identify KeyEncrypter // objects that also carry a key ID on its own. type KeyIDer interface { KeyID() string } // KeyDecrypter is an interface for objects that can decrypt a content // encryption key. // // You can use this in place of a regular key (i.e. in jwe.WithKey()) // to decrypt the encrypted key in a JWE message without having to // expose the secret key in memory, for example, when you want to use // hardware security modules (HSMs) to decrypt the key. // // This API is experimental and may change without notice, even // in minor releases. type KeyDecrypter interface { // Decrypt decrypts the encrypted key of a JWE message. // // Make sure you understand how JWE messages are structured. // // For example, while in most circumstances a JWE message will only have one recipient, // a JWE message may contain multiple recipients, each with their own // encrypted key. This method will be called for each recipient, instead of // just once for a message. // // Also, header values could be found in either protected/unprotected headers // of a JWE message, as well as in protected/unprotected headers for each recipient. // When checking a header value, you can decide to use either one, or both, but you // must be aware that there are multiple places to look for. DecryptKey(alg jwa.KeyEncryptionAlgorithm, encryptedKey []byte, recipient Recipient, message *Message) ([]byte, error) } // Recipient holds the encrypted key and hints to decrypt the key type Recipient interface { Headers() Headers 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]interface{} // 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 } type Visitor = iter.MapVisitor type VisitorFunc = iter.MapVisitorFunc type HeaderPair = mapiter.Pair type Iterator = mapiter.Iterator golang-github-lestrrat-go-jwx-2.1.4/jwe/internal/000077500000000000000000000000001476711647200217345ustar00rootroot00000000000000golang-github-lestrrat-go-jwx-2.1.4/jwe/internal/aescbc/000077500000000000000000000000001476711647200231545ustar00rootroot00000000000000golang-github-lestrrat-go-jwx-2.1.4/jwe/internal/aescbc/BUILD.bazel000066400000000000000000000007571476711647200250430ustar00rootroot00000000000000load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") go_library( name = "aescbc", srcs = ["aescbc.go"], importpath = "github.com/lestrrat-go/jwx/v2/jwe/internal/aescbc", visibility = ["//:__subpackages__"], ) go_test( name = "aescbc_test", srcs = ["aescbc_test.go"], embed = [":aescbc"], deps = ["@com_github_stretchr_testify//assert"], ) alias( name = "go_default_library", actual = ":aescbc", visibility = ["//jwe:__subpackages__"], ) golang-github-lestrrat-go-jwx-2.1.4/jwe/internal/aescbc/aescbc.go000066400000000000000000000154661476711647200247370ustar00rootroot00000000000000package aescbc import ( "crypto/cipher" "crypto/hmac" "crypto/sha256" "crypto/sha512" "crypto/subtle" "encoding/binary" "errors" "fmt" "hash" "sync/atomic" ) 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 if toCheck > len(payload) { 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 := make([]byte, 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-2.1.4/jwe/internal/aescbc/aescbc_test.go000066400000000000000000000041551476711647200257670ustar00rootroot00000000000000package aescbc import ( "crypto/aes" "testing" "github.com/stretchr/testify/assert" ) 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) if !assert.NoError(t, err, "aescbc.New") { return } out := enc.Seal(nil, nonce, plaintext, aad) if !assert.NoError(t, err, "enc.Seal") { return } if !assert.Equal(t, ciphertext, out[:len(out)-enc.keysize], "Ciphertext tag should match") { return } if !assert.Equal(t, authtag, out[len(out)-enc.keysize:], "Auth tag should match") { return } out, err = enc.Open(nil, nonce, out, aad) if !assert.NoError(t, err, "Open should succeed") { return } if !assert.Equal(t, plaintext, out, "Open should get us original text") { return } } func TestPad(t *testing.T) { for i := 0; i < 256; i++ { buf := make([]byte, i) pb := pad(buf, 16) if !assert.Equal(t, len(pb)%16, 0, "pb should be multiple of 16") { return } toRemove, good := extractPadding(pb) if !assert.Equal(t, 1, int(good)&1, "padding should be good") { return } pb = pb[:len(pb)-toRemove] if !assert.Len(t, pb, i, "Unpad should result in len = %d", i) { return } } } golang-github-lestrrat-go-jwx-2.1.4/jwe/internal/cipher/000077500000000000000000000000001476711647200232065ustar00rootroot00000000000000golang-github-lestrrat-go-jwx-2.1.4/jwe/internal/cipher/BUILD.bazel000066400000000000000000000012271476711647200250660ustar00rootroot00000000000000load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") go_library( name = "cipher", srcs = [ "cipher.go", "interface.go", ], importpath = "github.com/lestrrat-go/jwx/v2/jwe/internal/cipher", visibility = ["//:__subpackages__"], deps = [ "//jwa", "//jwe/internal/aescbc", "//jwe/internal/keygen", ], ) go_test( name = "cipher_test", srcs = ["cipher_test.go"], deps = [ ":cipher", "//jwa", "@com_github_stretchr_testify//assert", ], ) alias( name = "go_default_library", actual = ":cipher", visibility = ["//jwe:__subpackages__"], ) golang-github-lestrrat-go-jwx-2.1.4/jwe/internal/cipher/cipher.go000066400000000000000000000071131476711647200250110ustar00rootroot00000000000000package cipher import ( "crypto/aes" "crypto/cipher" "fmt" "github.com/lestrrat-go/jwx/v2/jwa" "github.com/lestrrat-go/jwx/v2/jwe/internal/aescbc" "github.com/lestrrat-go/jwx/v2/jwe/internal/keygen" ) var gcm = &gcmFetcher{} var cbc = &cbcFetcher{} func (f gcmFetcher) Fetch(key []byte) (cipher.AEAD, error) { 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) (cipher.AEAD, error) { 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 jwa.ContentEncryptionAlgorithm) (*AesContentCipher, error) { var keysize int var tagsize int var fetcher Fetcher switch alg { case jwa.A128GCM: keysize = 16 tagsize = 16 fetcher = gcm case jwa.A192GCM: keysize = 24 tagsize = 16 fetcher = gcm case jwa.A256GCM: keysize = 32 tagsize = 16 fetcher = gcm case jwa.A128CBC_HS256: tagsize = 16 keysize = tagsize * 2 fetcher = cbc case jwa.A192CBC_HS384: tagsize = 24 keysize = tagsize * 2 fetcher = cbc case jwa.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) 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) } }() var bs keygen.ByteSource if c.NonceGenerator == nil { bs, err = keygen.NewRandom(aead.NonceSize()).Generate() } else { bs, err = c.NonceGenerator.Generate() } if err != nil { return nil, nil, nil, fmt.Errorf(`failed to generate 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) 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-2.1.4/jwe/internal/cipher/cipher_test.go000066400000000000000000000010331476711647200260430ustar00rootroot00000000000000package cipher_test import ( "testing" "github.com/lestrrat-go/jwx/v2/jwa" "github.com/lestrrat-go/jwx/v2/jwe/internal/cipher" "github.com/stretchr/testify/assert" ) func TestAES(t *testing.T) { algs := []jwa.ContentEncryptionAlgorithm{ jwa.A128GCM, jwa.A192GCM, jwa.A256GCM, jwa.A128CBC_HS256, jwa.A192CBC_HS384, jwa.A256CBC_HS512, } for _, alg := range algs { c, err := cipher.NewAES(alg) if !assert.NoError(t, err, "BuildCipher for %s succeeds", alg) { return } t.Logf("keysize = %d", c.KeySize()) } } golang-github-lestrrat-go-jwx-2.1.4/jwe/internal/cipher/interface.go000066400000000000000000000013061476711647200254750ustar00rootroot00000000000000package cipher import ( "crypto/cipher" "github.com/lestrrat-go/jwx/v2/jwe/internal/keygen" ) 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) (cipher.AEAD, error) } type gcmFetcher struct{} type cbcFetcher struct{} // AesContentCipher represents a cipher based on AES type AesContentCipher struct { NonceGenerator keygen.Generator fetch Fetcher keysize int tagsize int } golang-github-lestrrat-go-jwx-2.1.4/jwe/internal/concatkdf/000077500000000000000000000000001476711647200236705ustar00rootroot00000000000000golang-github-lestrrat-go-jwx-2.1.4/jwe/internal/concatkdf/BUILD.bazel000066400000000000000000000010441476711647200255450ustar00rootroot00000000000000load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") go_library( name = "concatkdf", srcs = ["concatkdf.go"], importpath = "github.com/lestrrat-go/jwx/v2/jwe/internal/concatkdf", visibility = ["//:__subpackages__"], ) go_test( name = "concatkdf_test", srcs = ["concatkdf_test.go"], embed = [":concatkdf"], deps = [ "//jwa", "@com_github_stretchr_testify//assert", ], ) alias( name = "go_default_library", actual = ":concatkdf", visibility = ["//jwe:__subpackages__"], ) golang-github-lestrrat-go-jwx-2.1.4/jwe/internal/concatkdf/concatkdf.go000066400000000000000000000026041476711647200261550ustar00rootroot00000000000000package 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-2.1.4/jwe/internal/concatkdf/concatkdf_test.go000066400000000000000000000021251476711647200272120ustar00rootroot00000000000000package concatkdf import ( "crypto" "testing" "github.com/lestrrat-go/jwx/v2/jwa" "github.com/stretchr/testify/assert" ) // 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]) if !assert.Equal(t, 5, n, "first read bytes matches") || !assert.NoError(t, err, "first read successful") { return } n, err = kdf.Read(out[5:]) if !assert.Equal(t, 11, n, "second read bytes matches") || !assert.NoError(t, err, "second read successful") { return } if !assert.Equal(t, expected, out, "generated value matches") { return } } golang-github-lestrrat-go-jwx-2.1.4/jwe/internal/content_crypt/000077500000000000000000000000001476711647200246275ustar00rootroot00000000000000golang-github-lestrrat-go-jwx-2.1.4/jwe/internal/content_crypt/BUILD.bazel000066400000000000000000000007321476711647200265070ustar00rootroot00000000000000load("@io_bazel_rules_go//go:def.bzl", "go_library") go_library( name = "content_crypt", srcs = [ "content_crypt.go", "interface.go", ], importpath = "github.com/lestrrat-go/jwx/v2/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-2.1.4/jwe/internal/content_crypt/content_crypt.go000066400000000000000000000017521476711647200300560ustar00rootroot00000000000000package content_crypt //nolint:golint import ( "fmt" "github.com/lestrrat-go/jwx/v2/jwa" "github.com/lestrrat-go/jwx/v2/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) 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-2.1.4/jwe/internal/content_crypt/interface.go000066400000000000000000000007321476711647200271200ustar00rootroot00000000000000package content_crypt //nolint:golint import ( "github.com/lestrrat-go/jwx/v2/jwa" "github.com/lestrrat-go/jwx/v2/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-2.1.4/jwe/internal/keyenc/000077500000000000000000000000001476711647200232125ustar00rootroot00000000000000golang-github-lestrrat-go-jwx-2.1.4/jwe/internal/keyenc/BUILD.bazel000066400000000000000000000015001476711647200250640ustar00rootroot00000000000000load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") go_library( name = "keyenc", srcs = [ "interface.go", "keyenc.go", ], importpath = "github.com/lestrrat-go/jwx/v2/jwe/internal/keyenc", visibility = ["//:__subpackages__"], deps = [ "//internal/ecutil", "//jwa", "//jwe/internal/cipher", "//jwe/internal/concatkdf", "//jwe/internal/keygen", "//x25519", "@org_golang_x_crypto//curve25519", "@org_golang_x_crypto//pbkdf2", ], ) go_test( name = "keyenc_test", srcs = ["keyenc_test.go"], deps = [ ":keyenc", "//jwk", "@com_github_stretchr_testify//assert", ], ) alias( name = "go_default_library", actual = ":keyenc", visibility = ["//jwe:__subpackages__"], ) golang-github-lestrrat-go-jwx-2.1.4/jwe/internal/keyenc/interface.go000066400000000000000000000045101476711647200255010ustar00rootroot00000000000000package keyenc import ( "crypto/rsa" "hash" "github.com/lestrrat-go/jwx/v2/jwa" "github.com/lestrrat-go/jwx/v2/jwe/internal/keygen" ) // Encrypter is an interface for things that can encrypt keys type Encrypter interface { Algorithm() jwa.KeyEncryptionAlgorithm EncryptKey([]byte) (keygen.ByteSource, error) } // Decrypter is an interface for things that can decrypt keys type Decrypter interface { Algorithm() jwa.KeyEncryptionAlgorithm Decrypt([]byte) ([]byte, error) } type Noop struct { alg jwa.KeyEncryptionAlgorithm keyID string sharedkey []byte } // AES encrypts content encryption keys using AES key wrap. // Contrary to what the name implies, it also decrypt encrypted keys type AES struct { alg jwa.KeyEncryptionAlgorithm keyID string sharedkey []byte } // AESGCM encrypts content encryption keys using AES-GCM key wrap. type AESGCMEncrypt struct { algorithm jwa.KeyEncryptionAlgorithm keyID string sharedkey []byte } // ECDHESEncrypt encrypts content encryption keys using ECDH-ES. type ECDHESEncrypt struct { algorithm jwa.KeyEncryptionAlgorithm keyID string generator keygen.Generator } // ECDHESDecrypt decrypts keys using ECDH-ES. type ECDHESDecrypt struct { keyalg jwa.KeyEncryptionAlgorithm contentalg jwa.ContentEncryptionAlgorithm apu []byte apv []byte privkey interface{} pubkey interface{} } // RSAOAEPEncrypt encrypts keys using RSA OAEP algorithm type RSAOAEPEncrypt struct { alg jwa.KeyEncryptionAlgorithm pubkey *rsa.PublicKey keyID string } // RSAOAEPDecrypt decrypts keys using RSA OAEP algorithm type RSAOAEPDecrypt struct { alg jwa.KeyEncryptionAlgorithm privkey *rsa.PrivateKey } // RSAPKCS15Decrypt decrypts keys using RSA PKCS1v15 algorithm type RSAPKCS15Decrypt struct { alg jwa.KeyEncryptionAlgorithm privkey *rsa.PrivateKey generator keygen.Generator } // RSAPKCSEncrypt encrypts keys using RSA PKCS1v15 algorithm type RSAPKCSEncrypt struct { alg jwa.KeyEncryptionAlgorithm pubkey *rsa.PublicKey keyID string } // DirectDecrypt does no encryption (Note: Unimplemented) type DirectDecrypt struct { Key []byte } // PBES2Encrypt encrypts keys with PBES2 / PBKDF2 password type PBES2Encrypt struct { algorithm jwa.KeyEncryptionAlgorithm hashFunc func() hash.Hash keylen int keyID string password []byte } golang-github-lestrrat-go-jwx-2.1.4/jwe/internal/keyenc/keyenc.go000066400000000000000000000443371476711647200250320ustar00rootroot00000000000000package keyenc import ( "crypto" "crypto/aes" "crypto/cipher" "crypto/ecdsa" "crypto/rand" "crypto/rsa" "crypto/sha1" "crypto/sha256" "crypto/sha512" "crypto/subtle" "encoding/binary" "fmt" "hash" "io" "golang.org/x/crypto/curve25519" "golang.org/x/crypto/pbkdf2" "github.com/lestrrat-go/jwx/v2/internal/ecutil" "github.com/lestrrat-go/jwx/v2/jwa" contentcipher "github.com/lestrrat-go/jwx/v2/jwe/internal/cipher" "github.com/lestrrat-go/jwx/v2/jwe/internal/concatkdf" "github.com/lestrrat-go/jwx/v2/jwe/internal/keygen" "github.com/lestrrat-go/jwx/v2/x25519" ) func NewNoop(alg jwa.KeyEncryptionAlgorithm, sharedkey []byte) (*Noop, error) { return &Noop{ alg: alg, sharedkey: sharedkey, }, nil } func (kw *Noop) Algorithm() jwa.KeyEncryptionAlgorithm { return kw.alg } func (kw *Noop) SetKeyID(v string) { kw.keyID = v } func (kw *Noop) KeyID() string { return kw.keyID } func (kw *Noop) EncryptKey(_ []byte) (keygen.ByteSource, error) { return keygen.ByteKey(kw.sharedkey), nil } // NewAES creates a key-wrap encrypter using AES. // Although the name suggests otherwise, this does the decryption as well. func NewAES(alg jwa.KeyEncryptionAlgorithm, sharedkey []byte) (*AES, error) { return &AES{ alg: alg, sharedkey: sharedkey, }, nil } // Algorithm returns the key encryption algorithm being used func (kw *AES) Algorithm() jwa.KeyEncryptionAlgorithm { return kw.alg } func (kw *AES) SetKeyID(v string) { kw.keyID = v } // KeyID returns the key ID associated with this encrypter func (kw *AES) KeyID() string { return kw.keyID } // Decrypt decrypts the encrypted key using AES key unwrap func (kw *AES) Decrypt(enckey []byte) ([]byte, error) { block, err := aes.NewCipher(kw.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 } // KeyEncrypt encrypts the given content encryption key func (kw *AES) EncryptKey(cek []byte) (keygen.ByteSource, error) { block, err := aes.NewCipher(kw.sharedkey) if err != nil { return nil, fmt.Errorf(`failed to create cipher from shared key: %w`, err) } encrypted, err := Wrap(block, cek) if err != nil { return nil, fmt.Errorf(`keywrap: failed to wrap key: %w`, err) } return keygen.ByteKey(encrypted), nil } func NewAESGCMEncrypt(alg jwa.KeyEncryptionAlgorithm, sharedkey []byte) (*AESGCMEncrypt, error) { return &AESGCMEncrypt{ algorithm: alg, sharedkey: sharedkey, }, nil } func (kw AESGCMEncrypt) Algorithm() jwa.KeyEncryptionAlgorithm { return kw.algorithm } func (kw *AESGCMEncrypt) SetKeyID(v string) { kw.keyID = v } func (kw AESGCMEncrypt) KeyID() string { return kw.keyID } func (kw AESGCMEncrypt) EncryptKey(cek []byte) (keygen.ByteSource, error) { block, err := aes.NewCipher(kw.sharedkey) if err != nil { return nil, fmt.Errorf(`failed to create cipher from shared key: %w`, err) } aesgcm, err := cipher.NewGCM(block) if err != nil { return nil, fmt.Errorf(`failed to create gcm from cipher: %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 } func NewPBES2Encrypt(alg jwa.KeyEncryptionAlgorithm, password []byte) (*PBES2Encrypt, error) { var hashFunc func() hash.Hash var keylen int switch alg { case jwa.PBES2_HS256_A128KW: hashFunc = sha256.New keylen = 16 case jwa.PBES2_HS384_A192KW: hashFunc = sha512.New384 keylen = 24 case jwa.PBES2_HS512_A256KW: hashFunc = sha512.New keylen = 32 default: return nil, fmt.Errorf("unexpected key encryption algorithm %s", alg) } return &PBES2Encrypt{ algorithm: alg, password: password, hashFunc: hashFunc, keylen: keylen, }, nil } func (kw PBES2Encrypt) Algorithm() jwa.KeyEncryptionAlgorithm { return kw.algorithm } func (kw *PBES2Encrypt) SetKeyID(v string) { kw.keyID = v } func (kw PBES2Encrypt) KeyID() string { return kw.keyID } func (kw PBES2Encrypt) EncryptKey(cek []byte) (keygen.ByteSource, error) { count := 10000 salt := make([]byte, kw.keylen) _, err := io.ReadFull(rand.Reader, salt) if err != nil { return nil, fmt.Errorf(`failed to get random salt: %w`, err) } fullsalt := []byte(kw.algorithm) fullsalt = append(fullsalt, byte(0)) fullsalt = append(fullsalt, salt...) sharedkey := pbkdf2.Key(kw.password, fullsalt, count, kw.keylen, kw.hashFunc) 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(`keywrap: failed to wrap key: %w`, err) } return keygen.ByteWithSaltAndCount{ ByteKey: encrypted, Salt: salt, Count: count, }, nil } // NewECDHESEncrypt creates a new key encrypter based on ECDH-ES func NewECDHESEncrypt(alg jwa.KeyEncryptionAlgorithm, enc jwa.ContentEncryptionAlgorithm, keysize int, keyif interface{}, apu, apv []byte) (*ECDHESEncrypt, error) { var generator keygen.Generator var err error switch key := keyif.(type) { case *ecdsa.PublicKey: generator, err = keygen.NewEcdhes(alg, enc, keysize, key, apu, apv) case x25519.PublicKey: generator, err = keygen.NewX25519(alg, enc, keysize, key) default: return nil, fmt.Errorf("unexpected key type %T", keyif) } if err != nil { return nil, fmt.Errorf(`failed to create key generator: %w`, err) } return &ECDHESEncrypt{ algorithm: alg, generator: generator, }, nil } // Algorithm returns the key encryption algorithm being used func (kw ECDHESEncrypt) Algorithm() jwa.KeyEncryptionAlgorithm { return kw.algorithm } func (kw *ECDHESEncrypt) SetKeyID(v string) { kw.keyID = v } // KeyID returns the key ID associated with this encrypter func (kw ECDHESEncrypt) KeyID() string { return kw.keyID } // KeyEncrypt encrypts the content encryption key using ECDH-ES func (kw ECDHESEncrypt) EncryptKey(cek []byte) (keygen.ByteSource, error) { kg, err := kw.generator.Generate() if err != nil { return nil, fmt.Errorf(`failed to create key generator: %w`, err) } bwpk, ok := kg.(keygen.ByteWithECPublicKey) if !ok { return nil, fmt.Errorf(`key generator generated invalid key (expected ByteWithECPrivateKey)`) } if kw.algorithm == jwa.ECDH_ES { return bwpk, nil } 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 } // NewECDHESDecrypt creates a new key decrypter using ECDH-ES func NewECDHESDecrypt(keyalg jwa.KeyEncryptionAlgorithm, contentalg jwa.ContentEncryptionAlgorithm, pubkey interface{}, apu, apv []byte, privkey interface{}) *ECDHESDecrypt { return &ECDHESDecrypt{ keyalg: keyalg, contentalg: contentalg, apu: apu, apv: apv, privkey: privkey, pubkey: pubkey, } } // Algorithm returns the key encryption algorithm being used func (kw ECDHESDecrypt) Algorithm() jwa.KeyEncryptionAlgorithm { return kw.keyalg } func DeriveZ(privkeyif interface{}, pubkeyif interface{}) ([]byte, error) { switch privkeyif.(type) { case x25519.PrivateKey: privkey, ok := privkeyif.(x25519.PrivateKey) if !ok { return nil, fmt.Errorf(`private key must be x25519.PrivateKey, was: %T`, privkeyif) } pubkey, ok := pubkeyif.(x25519.PublicKey) if !ok { return nil, fmt.Errorf(`public key must be x25519.PublicKey, was: %T`, pubkeyif) } return curve25519.X25519(privkey.Seed(), pubkey) default: privkey, ok := privkeyif.(*ecdsa.PrivateKey) if !ok { return nil, fmt.Errorf(`private key must be *ecdsa.PrivateKey, was: %T`, privkeyif) } pubkey, ok := pubkeyif.(*ecdsa.PublicKey) if !ok { return nil, fmt.Errorf(`public key must be *ecdsa.PublicKey, was: %T`, pubkeyif) } if !privkey.PublicKey.Curve.IsOnCurve(pubkey.X, pubkey.Y) { return nil, fmt.Errorf(`public key must be on the same curve as private key`) } z, _ := privkey.PublicKey.Curve.ScalarMult(pubkey.X, pubkey.Y, privkey.D.Bytes()) zBytes := ecutil.AllocECPointBuffer(z, privkey.Curve) defer ecutil.ReleaseECPointBuffer(zBytes) zCopy := make([]byte, len(zBytes)) copy(zCopy, zBytes) return zCopy, nil } } func DeriveECDHES(alg, apu, apv []byte, privkey interface{}, pubkey interface{}, keysize uint32) ([]byte, error) { pubinfo := make([]byte, 4) binary.BigEndian.PutUint32(pubinfo, keysize*8) zBytes, err := DeriveZ(privkey, pubkey) if err != nil { return nil, fmt.Errorf(`unable to determine Z: %w`, err) } kdf := concatkdf.New(crypto.SHA256, alg, zBytes, apu, apv, pubinfo, []byte{}) key := make([]byte, keysize) if _, err := kdf.Read(key); err != nil { return nil, fmt.Errorf(`failed to read kdf: %w`, err) } return key, nil } // Decrypt decrypts the encrypted key using ECDH-ES func (kw ECDHESDecrypt) Decrypt(enckey []byte) ([]byte, error) { var algBytes []byte var keysize uint32 // Use keyalg except for when jwa.ECDH_ES algBytes = []byte(kw.keyalg.String()) switch kw.keyalg { case jwa.ECDH_ES: // Create a content cipher from the content encryption algorithm c, err := contentcipher.NewAES(kw.contentalg) if err != nil { return nil, fmt.Errorf(`failed to create content cipher for %s: %w`, kw.contentalg, err) } keysize = uint32(c.KeySize()) algBytes = []byte(kw.contentalg.String()) case jwa.ECDH_ES_A128KW: keysize = 16 case jwa.ECDH_ES_A192KW: keysize = 24 case jwa.ECDH_ES_A256KW: keysize = 32 default: return nil, fmt.Errorf("invalid ECDH-ES key wrap algorithm (%s)", kw.keyalg) } key, err := DeriveECDHES(algBytes, kw.apu, kw.apv, kw.privkey, kw.pubkey, keysize) if err != nil { return nil, fmt.Errorf(`failed to derive ECDHES encryption key: %w`, err) } // ECDH-ES does not wrap keys if kw.keyalg == jwa.ECDH_ES { return key, nil } 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) } // NewRSAOAEPEncrypt creates a new key encrypter using RSA OAEP func NewRSAOAEPEncrypt(alg jwa.KeyEncryptionAlgorithm, pubkey *rsa.PublicKey) (*RSAOAEPEncrypt, error) { switch alg { case jwa.RSA_OAEP, jwa.RSA_OAEP_256, jwa.RSA_OAEP_384, jwa.RSA_OAEP_512: default: return nil, fmt.Errorf("invalid RSA OAEP encrypt algorithm (%s)", alg) } return &RSAOAEPEncrypt{ alg: alg, pubkey: pubkey, }, nil } // NewRSAPKCSEncrypt creates a new key encrypter using PKCS1v15 func NewRSAPKCSEncrypt(alg jwa.KeyEncryptionAlgorithm, pubkey *rsa.PublicKey) (*RSAPKCSEncrypt, error) { switch alg { case jwa.RSA1_5: default: return nil, fmt.Errorf("invalid RSA PKCS encrypt algorithm (%s)", alg) } return &RSAPKCSEncrypt{ alg: alg, pubkey: pubkey, }, nil } // Algorithm returns the key encryption algorithm being used func (e RSAPKCSEncrypt) Algorithm() jwa.KeyEncryptionAlgorithm { return e.alg } func (e *RSAPKCSEncrypt) SetKeyID(v string) { e.keyID = v } // KeyID returns the key ID associated with this encrypter func (e RSAPKCSEncrypt) KeyID() string { return e.keyID } // Algorithm returns the key encryption algorithm being used func (e RSAOAEPEncrypt) Algorithm() jwa.KeyEncryptionAlgorithm { return e.alg } func (e *RSAOAEPEncrypt) SetKeyID(v string) { e.keyID = v } // KeyID returns the key ID associated with this encrypter func (e RSAOAEPEncrypt) KeyID() string { return e.keyID } // KeyEncrypt encrypts the content encryption key using RSA PKCS1v15 func (e RSAPKCSEncrypt) EncryptKey(cek []byte) (keygen.ByteSource, error) { if e.alg != jwa.RSA1_5 { return nil, fmt.Errorf("invalid RSA PKCS encrypt algorithm (%s)", e.alg) } encrypted, err := rsa.EncryptPKCS1v15(rand.Reader, e.pubkey, cek) if err != nil { return nil, fmt.Errorf(`failed to encrypt using PKCS1v15: %w`, err) } return keygen.ByteKey(encrypted), nil } // KeyEncrypt encrypts the content encryption key using RSA OAEP func (e RSAOAEPEncrypt) EncryptKey(cek []byte) (keygen.ByteSource, error) { var hash hash.Hash switch e.alg { case jwa.RSA_OAEP: hash = sha1.New() case jwa.RSA_OAEP_256: hash = sha256.New() case jwa.RSA_OAEP_384: hash = sha512.New384() case jwa.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, e.pubkey, cek, []byte{}) if err != nil { return nil, fmt.Errorf(`failed to OAEP encrypt: %w`, err) } return keygen.ByteKey(encrypted), nil } // NewRSAPKCS15Decrypt creates a new decrypter using RSA PKCS1v15 func NewRSAPKCS15Decrypt(alg jwa.KeyEncryptionAlgorithm, privkey *rsa.PrivateKey, keysize int) *RSAPKCS15Decrypt { generator := keygen.NewRandom(keysize * 2) return &RSAPKCS15Decrypt{ alg: alg, privkey: privkey, generator: generator, } } // Algorithm returns the key encryption algorithm being used func (d RSAPKCS15Decrypt) Algorithm() jwa.KeyEncryptionAlgorithm { return d.alg } // Decrypt decrypts the encrypted key using RSA PKCS1v1.5 func (d RSAPKCS15Decrypt) Decrypt(enckey []byte) ([]byte, error) { // Hey, these notes and workarounds were stolen from go-jose 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() }() // Perform some input validation. expectedlen := d.privkey.PublicKey.N.BitLen() / 8 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), ) } var err error bk, err := d.generator.Generate() if err != nil { return nil, fmt.Errorf(`failed to generate key`) } cek := bk.Bytes() // 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. err = rsa.DecryptPKCS1v15SessionKey(rand.Reader, d.privkey, enckey, cek) if err != nil { return nil, fmt.Errorf(`failed to decrypt via PKCS1v15: %w`, err) } return cek, nil } // NewRSAOAEPDecrypt creates a new key decrypter using RSA OAEP func NewRSAOAEPDecrypt(alg jwa.KeyEncryptionAlgorithm, privkey *rsa.PrivateKey) (*RSAOAEPDecrypt, error) { switch alg { case jwa.RSA_OAEP, jwa.RSA_OAEP_256, jwa.RSA_OAEP_384, jwa.RSA_OAEP_512: default: return nil, fmt.Errorf("invalid RSA OAEP decrypt algorithm (%s)", alg) } return &RSAOAEPDecrypt{ alg: alg, privkey: privkey, }, nil } // Algorithm returns the key encryption algorithm being used func (d RSAOAEPDecrypt) Algorithm() jwa.KeyEncryptionAlgorithm { return d.alg } // Decrypt decrypts the encrypted key using RSA OAEP func (d RSAOAEPDecrypt) Decrypt(enckey []byte) ([]byte, error) { var hash hash.Hash switch d.alg { case jwa.RSA_OAEP: hash = sha1.New() case jwa.RSA_OAEP_256: hash = sha256.New() case jwa.RSA_OAEP_384: hash = sha512.New384() case jwa.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, d.privkey, enckey, []byte{}) } // Decrypt for DirectDecrypt does not do anything other than // return a copy of the embedded key func (d DirectDecrypt) Decrypt() ([]byte, error) { cek := make([]byte, len(d.Key)) copy(cek, d.Key) return cek, nil } var keywrapDefaultIV = []byte{0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6} const keywrapChunkLen = 8 func Wrap(kek cipher.Block, cek []byte) ([]byte, error) { if len(cek)%8 != 0 { return nil, fmt.Errorf(`keywrap input must be 8 byte blocks`) } n := len(cek) / keywrapChunkLen r := make([][]byte, n) for i := 0; i < n; i++ { r[i] = make([]byte, keywrapChunkLen) copy(r[i], cek[i*keywrapChunkLen:]) } buffer := make([]byte, keywrapChunkLen*2) tBytes := make([]byte, keywrapChunkLen) copy(buffer, keywrapDefaultIV) for t := 0; t < 6*n; t++ { copy(buffer[keywrapChunkLen:], r[t%n]) kek.Encrypt(buffer, buffer) binary.BigEndian.PutUint64(tBytes, uint64(t+1)) for i := 0; i < keywrapChunkLen; i++ { buffer[i] = buffer[i] ^ tBytes[i] } copy(r[t%n], buffer[keywrapChunkLen:]) } out := make([]byte, (n+1)*keywrapChunkLen) copy(out, buffer[:keywrapChunkLen]) for i := range r { copy(out[(i+1)*8:], r[i]) } return out, nil } func Unwrap(block cipher.Block, ciphertxt []byte) ([]byte, error) { if len(ciphertxt)%keywrapChunkLen != 0 { return nil, fmt.Errorf(`keyunwrap input must be %d byte blocks`, keywrapChunkLen) } n := (len(ciphertxt) / keywrapChunkLen) - 1 r := make([][]byte, n) for i := range r { r[i] = make([]byte, keywrapChunkLen) copy(r[i], ciphertxt[(i+1)*keywrapChunkLen:]) } buffer := make([]byte, keywrapChunkLen*2) tBytes := make([]byte, keywrapChunkLen) copy(buffer[:keywrapChunkLen], ciphertxt[:keywrapChunkLen]) for t := 6*n - 1; t >= 0; t-- { binary.BigEndian.PutUint64(tBytes, uint64(t+1)) for i := 0; i < keywrapChunkLen; i++ { buffer[i] = buffer[i] ^ tBytes[i] } copy(buffer[keywrapChunkLen:], r[t%n]) block.Decrypt(buffer, buffer) copy(r[t%n], buffer[keywrapChunkLen:]) } if subtle.ConstantTimeCompare(buffer[:keywrapChunkLen], keywrapDefaultIV) == 0 { return nil, fmt.Errorf(`key unwrap: failed to unwrap key`) } out := make([]byte, n*keywrapChunkLen) for i := range r { copy(out[i*keywrapChunkLen:], r[i]) } return out, nil } golang-github-lestrrat-go-jwx-2.1.4/jwe/internal/keyenc/keyenc_test.go000066400000000000000000000121631476711647200260610ustar00rootroot00000000000000package keyenc_test import ( "bytes" "crypto/aes" "crypto/ecdsa" "encoding/hex" "testing" "github.com/lestrrat-go/jwx/v2/jwe/internal/keyenc" "github.com/lestrrat-go/jwx/v2/jwk" "github.com/stretchr/testify/assert" ) func mustHexDecode(s string) []byte { b, err := hex.DecodeString(s) if err != nil { panic(err) } return b } type vector struct { Kek string Data string Expected string } func TestRFC3394_Wrap(t *testing.T) { vectors := []vector{ { Kek: "000102030405060708090A0B0C0D0E0F", Data: "00112233445566778899AABBCCDDEEFF", Expected: "1FA68B0A8112B447AEF34BD8FB5A7B829D3E862371D2CFE5", }, { Kek: "000102030405060708090A0B0C0D0E0F1011121314151617", Data: "00112233445566778899AABBCCDDEEFF", Expected: "96778B25AE6CA435F92B5B97C050AED2468AB8A17AD84E5D", }, { Kek: "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F", Data: "00112233445566778899AABBCCDDEEFF0001020304050607", Expected: "A8F9BC1612C68B3FF6E6F4FBE30E71E4769C8B80A32CB8958CD5D17D6B254DA1", }, } for _, v := range vectors { 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) if !assert.NoError(t, err, "NewCipher is successful") { return } out, err := keyenc.Wrap(block, data) if !assert.NoError(t, err, "Wrap is successful") { return } if !assert.Equal(t, expected, out, "Wrap generates expected output") { return } unwrapped, err := keyenc.Unwrap(block, out) if !assert.NoError(t, err, "Unwrap is successful") { return } if !assert.Equal(t, data, unwrapped, "Unwrapped data matches") { return } } } func TestDeriveECDHES(t *testing.T) { // stolen from go-jose // Example keys from JWA, Appendix C var aliceKey ecdsa.PrivateKey var bobKey ecdsa.PrivateKey const aliceKeySrc = `{"kty":"EC", "crv":"P-256", "x":"gI0GAILBdu7T53akrFmMyGcsF3n5dO7MmwNBHKW5SV0", "y":"SLW_xSffzlPWrHEVI30DHM_4egVwt3NQqeUD7nMFpps", "d":"0_NxaRPUMQoAJt50Gz8YiTr8gRTwyEaCumd-MToTmIo" }` const bobKeySrc = `{"kty":"EC", "crv":"P-256", "x":"weNJy2HscCSM6AEDTDg04biOvhFhyyWvOHQfeF_PxMQ", "y":"e8lnCO-AlStT-NJVX-crhB7QRYhiix03illJOVAOyck", "d":"VEmDZpDXXK8p8N0Cndsxs924q6nS1RXFASRl6BfUqdw" }` aliceWebKey, err := jwk.ParseKey([]byte(aliceKeySrc)) if !assert.NoError(t, err, `jwk.ParseKey should succeed`) { return } if !assert.NoError(t, aliceWebKey.Raw(&aliceKey), `aliceWebKey.Raw should succeed`) { return } bobWebKey, err := jwk.ParseKey([]byte(bobKeySrc)) if !assert.NoError(t, err, `jwk.ParseKey should succeed`) { return } if !assert.NoError(t, bobWebKey.Raw(&bobKey), `bobWebKey.Raw should succeed`) { return } apuData := []byte("Alice") apvData := []byte("Bob") expected := []byte{86, 170, 141, 234, 248, 35, 109, 32, 92, 34, 40, 205, 113, 167, 16, 26} output, err := keyenc.DeriveECDHES([]byte("A128GCM"), apuData, apvData, &bobKey, &aliceKey.PublicKey, 16) if !assert.NoError(t, err, `keyenc.DeriveECDHES should succeed`) { return } if !assert.Equal(t, output, expected, `result should match`) { return } } func TestKeyWrap(t *testing.T) { // stolen from go-jose // Test vectors from: http://csrc.nist.gov/groups/ST/toolkit/documents/kms/key-wrap.pdf kek0, _ := hex.DecodeString("000102030405060708090A0B0C0D0E0F") cek0, _ := hex.DecodeString("00112233445566778899AABBCCDDEEFF") expected0, _ := hex.DecodeString("1FA68B0A8112B447AEF34BD8FB5A7B829D3E862371D2CFE5") kek1, _ := hex.DecodeString("000102030405060708090A0B0C0D0E0F1011121314151617") cek1, _ := hex.DecodeString("00112233445566778899AABBCCDDEEFF") expected1, _ := hex.DecodeString("96778B25AE6CA435F92B5B97C050AED2468AB8A17AD84E5D") kek2, _ := hex.DecodeString("000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F") cek2, _ := hex.DecodeString("00112233445566778899AABBCCDDEEFF0001020304050607") expected2, _ := hex.DecodeString("A8F9BC1612C68B3FF6E6F4FBE30E71E4769C8B80A32CB8958CD5D17D6B254DA1") block0, _ := aes.NewCipher(kek0) block1, _ := aes.NewCipher(kek1) block2, _ := aes.NewCipher(kek2) out0, _ := keyenc.Wrap(block0, cek0) out1, _ := keyenc.Wrap(block1, cek1) out2, _ := keyenc.Wrap(block2, cek2) if !bytes.Equal(out0, expected0) { t.Error("output 0 not as expected, got", out0, "wanted", expected0) } if !bytes.Equal(out1, expected1) { t.Error("output 1 not as expected, got", out1, "wanted", expected1) } if !bytes.Equal(out2, expected2) { t.Error("output 2 not as expected, got", out2, "wanted", expected2) } unwrap0, _ := keyenc.Unwrap(block0, out0) unwrap1, _ := keyenc.Unwrap(block1, out1) unwrap2, _ := keyenc.Unwrap(block2, out2) if !bytes.Equal(unwrap0, cek0) { t.Error("key unwrap did not return original input, got", unwrap0, "wanted", cek0) } if !bytes.Equal(unwrap1, cek1) { t.Error("key unwrap did not return original input, got", unwrap1, "wanted", cek1) } if !bytes.Equal(unwrap2, cek2) { t.Error("key unwrap did not return original input, got", unwrap2, "wanted", cek2) } } golang-github-lestrrat-go-jwx-2.1.4/jwe/internal/keygen/000077500000000000000000000000001476711647200232165ustar00rootroot00000000000000golang-github-lestrrat-go-jwx-2.1.4/jwe/internal/keygen/BUILD.bazel000066400000000000000000000010571476711647200250770ustar00rootroot00000000000000load("@io_bazel_rules_go//go:def.bzl", "go_library") go_library( name = "keygen", srcs = [ "interface.go", "keygen.go", ], importpath = "github.com/lestrrat-go/jwx/v2/jwe/internal/keygen", visibility = ["//:__subpackages__"], deps = [ "//internal/ecutil", "//jwa", "//jwe/internal/concatkdf", "//jwk", "//x25519", "@org_golang_x_crypto//curve25519", ], ) alias( name = "go_default_library", actual = ":keygen", visibility = ["//jwe:__subpackages__"], ) golang-github-lestrrat-go-jwx-2.1.4/jwe/internal/keygen/interface.go000066400000000000000000000031641476711647200255110ustar00rootroot00000000000000package keygen import ( "crypto/ecdsa" "github.com/lestrrat-go/jwx/v2/jwa" "github.com/lestrrat-go/jwx/v2/x25519" ) type Generator interface { Size() int Generate() (ByteSource, error) } // RandomKeyGenerate generates random keys type Random struct { keysize int } // EcdhesKeyGenerate generates keys using ECDH-ES algorithm / EC-DSA curve type Ecdhes struct { pubkey *ecdsa.PublicKey keysize int algorithm jwa.KeyEncryptionAlgorithm enc jwa.ContentEncryptionAlgorithm apu []byte apv []byte } // X25519KeyGenerate generates keys using ECDH-ES algorithm / X25519 curve type X25519 struct { algorithm jwa.KeyEncryptionAlgorithm enc jwa.ContentEncryptionAlgorithm keysize int pubkey x25519.PublicKey } // 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 interface{} } 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, interface{}) error } golang-github-lestrrat-go-jwx-2.1.4/jwe/internal/keygen/keygen.go000066400000000000000000000116061476711647200250330ustar00rootroot00000000000000package keygen import ( "crypto" "crypto/ecdsa" "crypto/rand" "encoding/binary" "fmt" "io" "golang.org/x/crypto/curve25519" "github.com/lestrrat-go/jwx/v2/internal/ecutil" "github.com/lestrrat-go/jwx/v2/jwa" "github.com/lestrrat-go/jwx/v2/jwe/internal/concatkdf" "github.com/lestrrat-go/jwx/v2/jwk" "github.com/lestrrat-go/jwx/v2/x25519" ) // Bytes returns the byte from this ByteKey func (k ByteKey) Bytes() []byte { return []byte(k) } // NewRandom creates a new Generator that returns // random bytes func NewRandom(n int) Random { return Random{keysize: n} } // Size returns the key size func (g Random) Size() int { return g.keysize } // Generate generates a random new key func (g Random) Generate() (ByteSource, error) { buf := make([]byte, g.keysize) 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 } // NewEcdhes creates a new key generator using ECDH-ES func NewEcdhes(alg jwa.KeyEncryptionAlgorithm, enc jwa.ContentEncryptionAlgorithm, keysize int, pubkey *ecdsa.PublicKey, apu, apv []byte) (*Ecdhes, error) { return &Ecdhes{ algorithm: alg, enc: enc, keysize: keysize, pubkey: pubkey, apu: apu, apv: apv, }, nil } // Size returns the key size associated with this generator func (g Ecdhes) Size() int { return g.keysize } // Generate generates new keys using ECDH-ES func (g Ecdhes) Generate() (ByteSource, error) { priv, err := ecdsa.GenerateKey(g.pubkey.Curve, rand.Reader) if err != nil { return nil, fmt.Errorf(`failed to generate key for ECDH-ES: %w`, err) } var algorithm string if g.algorithm == jwa.ECDH_ES { algorithm = g.enc.String() } else { algorithm = g.algorithm.String() } pubinfo := make([]byte, 4) binary.BigEndian.PutUint32(pubinfo, uint32(g.keysize)*8) if !priv.PublicKey.Curve.IsOnCurve(g.pubkey.X, g.pubkey.Y) { return nil, fmt.Errorf(`public key used does not contain a point (X,Y) on the curve`) } z, _ := priv.PublicKey.Curve.ScalarMult(g.pubkey.X, g.pubkey.Y, priv.D.Bytes()) zBytes := ecutil.AllocECPointBuffer(z, priv.PublicKey.Curve) defer ecutil.ReleaseECPointBuffer(zBytes) kdf := concatkdf.New(crypto.SHA256, []byte(algorithm), zBytes, g.apu, g.apv, pubinfo, []byte{}) kek := make([]byte, g.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 } // NewX25519 creates a new key generator using ECDH-ES func NewX25519(alg jwa.KeyEncryptionAlgorithm, enc jwa.ContentEncryptionAlgorithm, keysize int, pubkey x25519.PublicKey) (*X25519, error) { return &X25519{ algorithm: alg, enc: enc, keysize: keysize, pubkey: pubkey, }, nil } // Size returns the key size associated with this generator func (g X25519) Size() int { return g.keysize } // Generate generates new keys using ECDH-ES func (g X25519) Generate() (ByteSource, error) { pub, priv, err := x25519.GenerateKey(rand.Reader) if err != nil { return nil, fmt.Errorf(`failed to generate key for X25519: %w`, err) } var algorithm string if g.algorithm == jwa.ECDH_ES { algorithm = g.enc.String() } else { algorithm = g.algorithm.String() } pubinfo := make([]byte, 4) binary.BigEndian.PutUint32(pubinfo, uint32(g.keysize)*8) zBytes, err := curve25519.X25519(priv.Seed(), g.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, g.keysize) if _, err := kdf.Read(kek); err != nil { return nil, fmt.Errorf(`failed to read kdf: %w`, err) } return ByteWithECPublicKey{ PublicKey: pub, 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.FromRaw(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-2.1.4/jwe/io.go000066400000000000000000000010251476711647200210540ustar00rootroot00000000000000// Code generated by tools/cmd/genreadfile/main.go. DO NOT EDIT. package jwe import ( "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{}: srcFS = option.Value().(fs.FS) } } f, err := srcFS.Open(path) if err != nil { return nil, err } defer f.Close() return ParseReader(f) } golang-github-lestrrat-go-jwx-2.1.4/jwe/jwe.go000066400000000000000000000700741476711647200212440ustar00rootroot00000000000000//go:generate ../tools/cmd/genjwe.sh // Package jwe implements JWE as described in https://tools.ietf.org/html/rfc7516 package jwe import ( "bytes" "context" "crypto/ecdsa" "crypto/rsa" "fmt" "io" "sync" "github.com/lestrrat-go/blackmagic" "github.com/lestrrat-go/jwx/v2/internal/base64" "github.com/lestrrat-go/jwx/v2/internal/json" "github.com/lestrrat-go/jwx/v2/internal/keyconv" "github.com/lestrrat-go/jwx/v2/jwk" "github.com/lestrrat-go/jwx/v2/jwa" "github.com/lestrrat-go/jwx/v2/jwe/internal/aescbc" "github.com/lestrrat-go/jwx/v2/jwe/internal/content_crypt" "github.com/lestrrat-go/jwx/v2/jwe/internal/keyenc" "github.com/lestrrat-go/jwx/v2/jwe/internal/keygen" "github.com/lestrrat-go/jwx/v2/x25519" ) var muSettings sync.RWMutex var maxPBES2Count = 10000 var maxDecompressBufferSize int64 = 10 * 1024 * 1024 // 10MB func Settings(options ...GlobalOption) { muSettings.Lock() defer muSettings.Unlock() //nolint:forcetypeassert for _, option := range options { switch option.Ident() { case identMaxPBES2Count{}: maxPBES2Count = option.Value().(int) case identMaxDecompressBufferSize{}: maxDecompressBufferSize = option.Value().(int64) case identMaxBufferSize{}: aescbc.SetMaxBufferSize(option.Value().(int64)) } } } const ( fmtInvalid = iota fmtCompact fmtJSON fmtJSONPretty fmtMax ) var _ = fmtInvalid var _ = fmtMax var registry = json.NewRegistry() type keyEncrypterWrapper struct { encrypter KeyEncrypter } func (w *keyEncrypterWrapper) Algorithm() jwa.KeyEncryptionAlgorithm { return w.encrypter.Algorithm() } func (w *keyEncrypterWrapper) EncryptKey(cek []byte) (keygen.ByteSource, error) { encrypted, err := w.encrypter.EncryptKey(cek) if err != nil { return nil, err } return keygen.ByteKey(encrypted), nil } type recipientBuilder struct { alg jwa.KeyEncryptionAlgorithm key interface{} headers Headers } func (b *recipientBuilder) Build(cek []byte, calg jwa.ContentEncryptionAlgorithm, cc *content_crypt.Generic) (Recipient, []byte, error) { var enc keyenc.Encrypter // we need the raw key for later use rawKey := b.key var keyID string if ke, ok := b.key.(KeyEncrypter); ok { enc = &keyEncrypterWrapper{encrypter: ke} if kider, ok := enc.(KeyIDer); ok { keyID = kider.KeyID() } } else if jwkKey, ok := b.key.(jwk.Key); ok { // Meanwhile, grab the kid as well keyID = jwkKey.KeyID() var raw interface{} if err := jwkKey.Raw(&raw); err != nil { return nil, nil, fmt.Errorf(`failed to retrieve raw key out of %T: %w`, b.key, err) } rawKey = raw } if enc == nil { switch b.alg { case jwa.RSA1_5: var pubkey rsa.PublicKey if err := keyconv.RSAPublicKey(&pubkey, rawKey); err != nil { return nil, nil, fmt.Errorf(`failed to generate public key from key (%T): %w`, rawKey, err) } v, err := keyenc.NewRSAPKCSEncrypt(b.alg, &pubkey) if err != nil { return nil, nil, fmt.Errorf(`failed to create RSA PKCS encrypter: %w`, err) } enc = v case jwa.RSA_OAEP, jwa.RSA_OAEP_256, jwa.RSA_OAEP_384, jwa.RSA_OAEP_512: var pubkey rsa.PublicKey if err := keyconv.RSAPublicKey(&pubkey, rawKey); err != nil { return nil, nil, fmt.Errorf(`failed to generate public key from key (%T): %w`, rawKey, err) } v, err := keyenc.NewRSAOAEPEncrypt(b.alg, &pubkey) if err != nil { return nil, nil, fmt.Errorf(`failed to create RSA OAEP encrypter: %w`, err) } enc = v case jwa.A128KW, jwa.A192KW, jwa.A256KW, jwa.A128GCMKW, jwa.A192GCMKW, jwa.A256GCMKW, jwa.PBES2_HS256_A128KW, jwa.PBES2_HS384_A192KW, jwa.PBES2_HS512_A256KW: sharedkey, ok := rawKey.([]byte) if !ok { return nil, nil, fmt.Errorf(`invalid key: []byte required (%T)`, rawKey) } var err error switch b.alg { case jwa.A128KW, jwa.A192KW, jwa.A256KW: enc, err = keyenc.NewAES(b.alg, sharedkey) case jwa.PBES2_HS256_A128KW, jwa.PBES2_HS384_A192KW, jwa.PBES2_HS512_A256KW: enc, err = keyenc.NewPBES2Encrypt(b.alg, sharedkey) default: enc, err = keyenc.NewAESGCMEncrypt(b.alg, sharedkey) } if err != nil { return nil, nil, fmt.Errorf(`failed to create key wrap encrypter: %w`, err) } // NOTE: there was formerly a restriction, introduced // in PR #26, which disallowed certain key/content // algorithm combinations. This seemed bogus, and // interop with the jose tool demonstrates it. case jwa.ECDH_ES, jwa.ECDH_ES_A128KW, jwa.ECDH_ES_A192KW, jwa.ECDH_ES_A256KW: var keysize int switch b.alg { case jwa.ECDH_ES: // https://tools.ietf.org/html/rfc7518#page-15 // In Direct Key Agreement mode, the output of the Concat KDF MUST be a // key of the same length as that used by the "enc" algorithm. keysize = cc.KeySize() case jwa.ECDH_ES_A128KW: keysize = 16 case jwa.ECDH_ES_A192KW: keysize = 24 case jwa.ECDH_ES_A256KW: keysize = 32 } switch key := rawKey.(type) { case x25519.PublicKey: var apu, apv []byte if hdrs := b.headers; hdrs != nil { apu = hdrs.AgreementPartyUInfo() apv = hdrs.AgreementPartyVInfo() } v, err := keyenc.NewECDHESEncrypt(b.alg, calg, keysize, rawKey, apu, apv) if err != nil { return nil, nil, fmt.Errorf(`failed to create ECDHS key wrap encrypter: %w`, err) } enc = v default: var pubkey ecdsa.PublicKey if err := keyconv.ECDSAPublicKey(&pubkey, rawKey); err != nil { return nil, nil, fmt.Errorf(`failed to generate public key from key (%T): %w`, key, err) } var apu, apv []byte if hdrs := b.headers; hdrs != nil { apu = hdrs.AgreementPartyUInfo() apv = hdrs.AgreementPartyVInfo() } v, err := keyenc.NewECDHESEncrypt(b.alg, calg, keysize, &pubkey, apu, apv) if err != nil { return nil, nil, fmt.Errorf(`failed to create ECDHS key wrap encrypter: %w`, err) } enc = v } case jwa.DIRECT: sharedkey, ok := rawKey.([]byte) if !ok { return nil, nil, fmt.Errorf("invalid key: []byte required") } enc, _ = keyenc.NewNoop(b.alg, sharedkey) default: return nil, nil, fmt.Errorf(`invalid key encryption algorithm (%s)`, b.alg) } } r := NewRecipient() if hdrs := b.headers; hdrs != nil { _ = r.SetHeaders(hdrs) } if err := r.Headers().Set(AlgorithmKey, b.alg); err != nil { return nil, nil, fmt.Errorf(`failed to set header: %w`, err) } if keyID != "" { if err := r.Headers().Set(KeyIDKey, keyID); err != nil { return nil, nil, fmt.Errorf(`failed to set header: %w`, err) } } var rawCEK []byte enckey, err := enc.EncryptKey(cek) if err != nil { return nil, nil, fmt.Errorf(`failed to encrypt key: %w`, err) } if enc.Algorithm() == jwa.ECDH_ES || enc.Algorithm() == jwa.DIRECT { rawCEK = enckey.Bytes() } else { if err := r.SetEncryptedKey(enckey.Bytes()); err != nil { return nil, nil, fmt.Errorf(`failed to set encrypted key: %w`, err) } } if hp, ok := enckey.(populater); ok { if err := hp.Populate(r.Headers()); err != nil { return nil, nil, fmt.Errorf(`failed to populate: %w`, err) } } return r, 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. // // 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. func Encrypt(payload []byte, options ...EncryptOption) ([]byte, error) { return encrypt(payload, nil, options...) } // 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, fmt.Errorf(`jwe.EncryptStatic: empty CEK`) } return encrypt(payload, cek, options...) } // encrypt is separate, so it can receive cek from outside. // (but we don't want to receive it in the options slice) func encrypt(payload, cek []byte, options ...EncryptOption) ([]byte, error) { // default content encryption algorithm calg := jwa.A256GCM // default compression is "none" compression := jwa.NoCompress // default format is compact serialization format := fmtCompact // builds each "recipient" with encrypted_key and headers var builders []*recipientBuilder var protected Headers var mergeProtected bool var useRawCEK bool for _, option := range options { //nolint:forcetypeassert switch option.Ident() { case identKey{}: data := option.Value().(*withKey) v, ok := data.alg.(jwa.KeyEncryptionAlgorithm) if !ok { return nil, fmt.Errorf(`jwe.Encrypt: expected alg to be jwa.KeyEncryptionAlgorithm, but got %T`, data.alg) } switch v { case jwa.DIRECT, jwa.ECDH_ES: useRawCEK = true } builders = append(builders, &recipientBuilder{ alg: v, key: data.key, headers: data.headers, }) case identContentEncryptionAlgorithm{}: calg = option.Value().(jwa.ContentEncryptionAlgorithm) case identCompress{}: compression = option.Value().(jwa.CompressionAlgorithm) case identMergeProtectedHeaders{}: mergeProtected = option.Value().(bool) case identProtectedHeaders{}: v := option.Value().(Headers) if !mergeProtected || protected == nil { protected = v } else { ctx := context.TODO() merged, err := protected.Merge(ctx, v) if err != nil { return nil, fmt.Errorf(`jwe.Encrypt: failed to merge headers: %w`, err) } protected = merged } case identSerialization{}: format = option.Value().(int) } } // We need to have at least one builder switch l := len(builders); { case l == 0: return nil, fmt.Errorf(`jwe.Encrypt: missing key encryption builders: use jwe.WithKey() to specify one`) case l > 1: if format == fmtCompact { return nil, fmt.Errorf(`jwe.Encrypt: cannot use compact serialization when multiple recipients exist (check the number of WithKey() argument, or use WithJSON())`) } } if useRawCEK { if len(builders) != 1 { return nil, fmt.Errorf(`jwe.Encrypt: multiple recipients for ECDH-ES/DIRECT mode supported`) } } // There is exactly one content encrypter. contentcrypt, err := content_crypt.NewGeneric(calg) if err != nil { return nil, fmt.Errorf(`jwe.Encrypt: failed to create AES encrypter: %w`, err) } if len(cek) <= 0 { generator := keygen.NewRandom(contentcrypt.KeySize()) bk, err := generator.Generate() if err != nil { return nil, fmt.Errorf(`jwe.Encrypt: failed to generate key: %w`, err) } cek = bk.Bytes() } recipients := make([]Recipient, len(builders)) for i, builder := range builders { // some builders require hint from the contentcrypt object r, rawCEK, err := builder.Build(cek, calg, contentcrypt) if err != nil { return nil, fmt.Errorf(`jwe.Encrypt: failed to create recipient #%d: %w`, i, err) } recipients[i] = r // Kinda feels weird, but if useRawCEK == true, we asserted earlier // that len(builders) == 1, so this is OK if useRawCEK { cek = rawCEK } } if protected == nil { protected = NewHeaders() } if err := protected.Set(ContentEncryptionKey, calg); err != nil { return nil, fmt.Errorf(`jwe.Encrypt: failed to set "enc" in protected header: %w`, err) } if compression != jwa.NoCompress { payload, err = compress(payload) if err != nil { return nil, fmt.Errorf(`jwe.Encrypt: failed to compress payload before encryption: %w`, err) } if err := protected.Set(CompressionKey, compression); err != nil { return nil, fmt.Errorf(`jwe.Encrypt: failed to set "zip" in protected header: %w`, err) } } // If there's only one recipient, you want to include that in the // protected header if len(recipients) == 1 { h, err := protected.Merge(context.TODO(), recipients[0].Headers()) if err != nil { return nil, fmt.Errorf(`jwe.Encrypt: failed to merge protected headers: %w`, err) } protected = h } 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 := NewMessage() 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 format { case fmtCompact: return Compact(msg) case fmtJSON: return json.Marshal(msg) case fmtJSONPretty: return json.MarshalIndent(msg, "", " ") default: return nil, fmt.Errorf(`jwe.Encrypt: invalid serialization`) } } type decryptCtx struct { msg *Message aad []byte cek *[]byte computedAad []byte keyProviders []KeyProvider protectedHeaders Headers maxDecompressBufferSize int64 } // 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) { var keyProviders []KeyProvider var keyUsed interface{} var cek *[]byte var dst *Message perCallMaxDecompressBufferSize := maxDecompressBufferSize //nolint:forcetypeassert for _, option := range options { switch option.Ident() { case identMessage{}: dst = option.Value().(*Message) case identKeyProvider{}: keyProviders = append(keyProviders, option.Value().(KeyProvider)) case identKeyUsed{}: keyUsed = option.Value() case identKey{}: pair := option.Value().(*withKey) alg, ok := pair.alg.(jwa.KeyEncryptionAlgorithm) if !ok { return nil, fmt.Errorf(`WithKey() option must be specified using jwa.KeyEncryptionAlgorithm (got %T)`, pair.alg) } keyProviders = append(keyProviders, &staticKeyProvider{ alg: alg, key: pair.key, }) case identCEK{}: cek = option.Value().(*[]byte) case identMaxDecompressBufferSize{}: perCallMaxDecompressBufferSize = option.Value().(int64) } } if len(keyProviders) < 1 { return nil, fmt.Errorf(`jwe.Decrypt: no key providers have been provided (see jwe.WithKey(), jwe.WithKeySet(), and jwe.WithKeyProvider()`) } 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 ctx := context.TODO() h, err := msg.protectedHeaders.Clone(ctx) if err != nil { return nil, fmt.Errorf(`failed to copy protected headers: %w`, err) } h, err = h.Merge(ctx, 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) } var dctx decryptCtx dctx.aad = aad dctx.computedAad = computedAad dctx.msg = msg dctx.keyProviders = keyProviders dctx.protectedHeaders = h dctx.cek = cek dctx.maxDecompressBufferSize = perCallMaxDecompressBufferSize var lastError error for _, recipient := range recipients { decrypted, err := dctx.try(ctx, recipient, keyUsed) if err != nil { lastError = err continue } if dst != nil { *dst = *msg dst.rawProtectedHeaders = nil dst.storeProtectedHeaders = false } return decrypted, nil } return nil, fmt.Errorf(`jwe.Decrypt: failed to decrypt any of the recipients (last error = %w)`, lastError) } func (dctx *decryptCtx) try(ctx context.Context, recipient Recipient, keyUsed interface{}) ([]byte, error) { var tried int var lastError error for i, kp := range dctx.keyProviders { var sink algKeySink if err := kp.FetchKeys(ctx, &sink, recipient, dctx.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 := dctx.decryptContent(ctx, alg, key, recipient) if err != nil { lastError = err continue } if keyUsed != nil { if err := blackmagic.AssignIfCompatible(keyUsed, key); err != nil { return nil, fmt.Errorf(`failed to assign used key (%T) to %T: %w`, key, 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 (dctx *decryptCtx) decryptContent(ctx context.Context, alg jwa.KeyEncryptionAlgorithm, key interface{}, recipient Recipient) ([]byte, error) { if jwkKey, ok := key.(jwk.Key); ok { var raw interface{} if err := jwkKey.Raw(&raw); err != nil { return nil, fmt.Errorf(`failed to retrieve raw key from %T: %w`, key, err) } key = raw } dec := newDecrypter(alg, dctx.msg.protectedHeaders.ContentEncryption(), key). AuthenticatedData(dctx.aad). ComputedAuthenticatedData(dctx.computedAad). InitializationVector(dctx.msg.initializationVector). Tag(dctx.msg.tag). CEK(dctx.cek) if recipient.Headers().Algorithm() != alg { // algorithms don't match return nil, fmt.Errorf(`jwe.Decrypt: key and recipient algorithms do not match`) } h2, err := dctx.protectedHeaders.Clone(ctx) if err != nil { return nil, fmt.Errorf(`jwe.Decrypt: failed to copy headers (1): %w`, err) } h2, err = h2.Merge(ctx, 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: epkif, ok := h2.Get(EphemeralPublicKeyKey) if !ok { return nil, fmt.Errorf(`failed to get 'epk' field`) } switch epk := epkif.(type) { case jwk.ECDSAPublicKey: var pubkey ecdsa.PublicKey if err := epk.Raw(&pubkey); err != nil { return nil, fmt.Errorf(`failed to get public key: %w`, err) } dec.PublicKey(&pubkey) case jwk.OKPPublicKey: var pubkey interface{} if err := epk.Raw(&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", epkif, alg) } if apu := h2.AgreementPartyUInfo(); len(apu) > 0 { dec.AgreementPartyUInfo(apu) } if apv := h2.AgreementPartyVInfo(); len(apv) > 0 { dec.AgreementPartyVInfo(apv) } case jwa.A128GCMKW, jwa.A192GCMKW, jwa.A256GCMKW: ivB64, ok := h2.Get(InitializationVectorKey) if ok { ivB64Str, ok := ivB64.(string) if !ok { return nil, fmt.Errorf("unexpected type for 'iv': %T", ivB64) } iv, err := base64.DecodeString(ivB64Str) if err != nil { return nil, fmt.Errorf(`failed to b64-decode 'iv': %w`, err) } dec.KeyInitializationVector(iv) } tagB64, ok := h2.Get(TagKey) if ok { tagB64Str, ok := tagB64.(string) if !ok { return nil, fmt.Errorf("unexpected type for 'tag': %T", tagB64) } tag, err := base64.DecodeString(tagB64Str) if err != nil { return nil, fmt.Errorf(`failed to b64-decode 'tag': %w`, err) } dec.KeyTag(tag) } case jwa.PBES2_HS256_A128KW, jwa.PBES2_HS384_A192KW, jwa.PBES2_HS512_A256KW: saltB64, ok := h2.Get(SaltKey) if !ok { return nil, fmt.Errorf(`failed to get 'p2s' field`) } saltB64Str, ok := saltB64.(string) if !ok { return nil, fmt.Errorf("unexpected type for 'p2s': %T", saltB64) } count, ok := h2.Get(CountKey) if !ok { return nil, fmt.Errorf(`failed to get 'p2c' field`) } // check if WithUseNumber is effective, because it will change the // type of the underlying value (#1140) var countFlt float64 if json.UseNumber() { num, ok := count.(json.Number) if !ok { return nil, fmt.Errorf("unexpected type for 'p2c': %T", count) } v, err := num.Float64() if err != nil { return nil, fmt.Errorf("failed to convert 'p2c' to float64: %w", err) } countFlt = v } else { v, ok := count.(float64) if !ok { return nil, fmt.Errorf("unexpected type for 'p2c': %T", count) } countFlt = v } muSettings.RLock() maxCount := maxPBES2Count muSettings.RUnlock() if countFlt > float64(maxCount) { return nil, fmt.Errorf("invalid 'p2c' value") } salt, err := base64.DecodeString(saltB64Str) 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, dctx.msg.cipherText, dctx.msg) if err != nil { return nil, fmt.Errorf(`jwe.Decrypt: decryption failed: %w`, err) } if h2.Compression() == jwa.Deflate { buf, err := uncompress(plaintext, dctx.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 } // 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) } func parseJSONOrCompact(buf []byte, storeProtectedHeaders bool) (*Message, error) { buf = bytes.TrimSpace(buf) if len(buf) == 0 { return nil, fmt.Errorf(`empty buffer`) } if buf[0] == '{' { return parseJSON(buf, storeProtectedHeaders) } return parseCompact(buf, storeProtectedHeaders) } // ParseString is the same as Parse, but takes a string. func ParseString(s string) (*Message, error) { return Parse([]byte(s)) } // 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, fmt.Errorf(`failed to read from io.Reader: %w`, err) } return Parse(buf) } 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) { parts := bytes.Split(buf, []byte{'.'}) if len(parts) != 5 { return nil, fmt.Errorf(`compact JWE format must have five parts (%d)`, len(parts)) } 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 } // 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 // // jwe.RegisterCustomField(`x-birthday`, timeT) // // Then `hdr.Get("x-birthday")` will still return an `interface{}`, // but you can convert its type to `time.Time` // // bdayif, _ := hdr.Get(`x-birthday`) // bday := bdayif.(time.Time) func RegisterCustomField(name string, object interface{}) { registry.Register(name, object) } golang-github-lestrrat-go-jwx-2.1.4/jwe/jwe_test.go000066400000000000000000001141751476711647200223040ustar00rootroot00000000000000package jwe_test import ( "bytes" "context" "crypto" "crypto/ecdsa" "crypto/elliptic" "crypto/rand" "crypto/rsa" "encoding/base64" "fmt" "math" "os" "strings" "testing" "time" "github.com/lestrrat-go/jwx/v2/internal/json" "github.com/lestrrat-go/jwx/v2/internal/jwxtest" "github.com/lestrrat-go/jwx/v2/jwa" "github.com/lestrrat-go/jwx/v2/jwe" "github.com/lestrrat-go/jwx/v2/jwk" "github.com/lestrrat-go/jwx/v2/x25519" "github.com/stretchr/testify/assert" "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 := privkey.Raw(&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, } assert.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)) if !assert.NoError(t, err, "Parsing JWE is successful") { return } if !assert.Len(t, msg.Recipients(), 1, "There is exactly 1 recipient") { return } }) parts := strings.Split(s, ".") t.Run("Missing parts", func(t *testing.T) { s2 := strings.Join(parts[:4], ".") _, err := jwe.Parse([]byte(s2)) if !assert.Error(t, err, `should fail to parse compact format with missing parts`) { return } }) t.Run("Invalid header", func(t *testing.T) { s2 := strings.Join(append(append([]string(nil), "!!invalidheader!!"), parts[1:]...), ".") _, err := jwe.Parse([]byte(s2)) if !assert.Error(t, err, `should fail to parse compact format with invalid header`) { return } }) 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)) if !assert.Error(t, err, `should fail to parse compact format with invalid encrypted key`) { return } }) 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)) if !assert.Error(t, err, `should fail to parse compact format with invalid initialization vector`) { return } }) 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)) if !assert.Error(t, err, `should fail to parse compact format with invalid content`) { return } }) t.Run("Invalid tag", func(t *testing.T) { s2 := strings.Join(append(parts[:4], "!!invalidtag!!"), ".") _, err := jwe.Parse([]byte(s2)) if !assert.Error(t, err, `should fail to parse compact format with invalid tag`) { return } }) }) t.Run("JSON format", func(t *testing.T) { msg, err := jwe.Parse([]byte(s)) if !assert.NoError(t, err, "Parsing JWE is successful") { return } buf, err := json.Marshal(msg) if !assert.NoError(t, err, "Serializing to JSON format should succeed") { return } msg2, err := jwe.Parse(buf) if !assert.NoError(t, err, "Parsing JWE in JSON format should succeed") { return } if !assert.Equal(t, msg, msg2, "messages should match") { return } }) } // 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) if !assert.NoError(t, err, `parsing jwk should succeed`) { return } var rawkey rsa.PrivateKey if !assert.NoError(t, privkey.Raw(&rawkey), `obtaining raw key should succeed`) { return } msg := jwe.NewMessage() plaintext, err := jwe.Decrypt([]byte(serialized), jwe.WithKey(jwa.RSA_OAEP, rawkey), jwe.WithMessage(msg)) if !assert.NoError(t, err, "jwe.Decrypt should be successful") { return } if !assert.Equal(t, 1, len(msg.Recipients()), "message recipients header length is 1") { return } if !assert.Equal(t, payload, string(plaintext), "decrypted value does not match") { return } 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" }`, }, } ntmpl := len(templates) testcases := make([]struct { Name string Options []jwe.EncryptOption Expected string }, ntmpl*2) for i, tmpl := range templates { options := make([]jwe.EncryptOption, len(tmpl.Options)) copy(options, tmpl.Options) for j, compression := range []jwa.CompressionAlgorithm{jwa.NoCompress, jwa.Deflate} { compName := compression.String() if compName == "" { compName = "none" } tc := testcases[i+j] tc.Name = tmpl.Name + " (compression=" + compName + ")" tc.Expected = tmpl.Expected tc.Options = append(options, jwe.WithCompress(compression)) } } for _, tc := range testcases { tc := tc 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...) if !assert.NoError(t, err, "jwe.Encrypt should succeed") { return } t.Logf("%s", encrypted) t.Run("WithKey", func(t *testing.T) { plaintext, err = jwe.Decrypt(encrypted, jwe.WithKey(jwa.RSA_OAEP, rawkey)) if !assert.NoError(t, err, "jwe.Decrypt should succeed") { return } if !assert.Equal(t, payload, string(plaintext), "jwe.Decrypt should produce the same plaintext") { return } }) t.Run("WithKeySet", func(t *testing.T) { pkJwk, err := jwk.FromRaw(rawkey) if !assert.NoError(t, err, `jwk.New should succeed`) { return } // Keys are not going to be selected without an algorithm _ = pkJwk.Set(jwe.AlgorithmKey, jwa.RSA_OAEP) set := jwk.NewSet() set.AddKey(pkJwk) var used interface{} plaintext, err = jwe.Decrypt(encrypted, jwe.WithKeySet(set, jwe.WithRequireKid(false)), jwe.WithKeyUsed(&used)) if !assert.NoError(t, err, "jwe.Decrypt should succeed") { return } if !assert.Equal(t, payload, string(plaintext), "jwe.Decrypt should produce the same plaintext") { return } if !assert.Equal(t, pkJwk, used) { return } }) }) } // Test direct marshaling and unmarshaling t.Run("Marshal/Unmarshal", func(t *testing.T) { buf, err := json.Marshal(msg) if !assert.NoError(t, err, `json.Marshal should succeed`) { return } m2 := jwe.NewMessage() if !assert.NoError(t, json.Unmarshal(buf, m2), `json.Unmarshal should succeed`) { t.Logf("%s", buf) return } if !assert.Equal(t, msg, m2, `messages should be the same after roundtrip`) { return } }) } // 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 i := 0; i < iterations; i++ { encrypted, err := jwe.Encrypt(plaintext, jwe.WithKey(jwa.RSA_OAEP, &rsaPrivKey.PublicKey)) if !assert.NoError(t, err, "Encrypt should succeed") { return } decrypted, err := jwe.Decrypt(encrypted, jwe.WithKey(jwa.RSA_OAEP, rsaPrivKey)) if !assert.NoError(t, err, "Decrypt should succeed") { return } if !assert.Equal(t, plaintext, decrypted, "Decrypted content should match") { return } } } 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 i := 0; i < iterations; i++ { encrypted, err := jwe.Encrypt(plaintext, jwe.WithKey(jwa.RSA1_5, &rsaPrivKey.PublicKey), jwe.WithContentEncryption(jwa.A128CBC_HS256)) if !assert.NoError(t, err, "Encrypt is successful") { return } decrypted, err := jwe.Decrypt(encrypted, jwe.WithKey(jwa.RSA1_5, rsaPrivKey)) if !assert.NoError(t, err, "Decrypt successful") { return } if !assert.Equal(t, plaintext, decrypted, "Decrypted correct plaintext") { return } } } // 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 i := 0; i < iterations; i++ { encrypted, err := jwe.Encrypt(plaintext, jwe.WithKey(jwa.A128KW, sharedkey), jwe.WithContentEncryption(jwa.A128CBC_HS256)) if !assert.NoError(t, err, "Encrypt is successful") { return } decrypted, err := jwe.Decrypt(encrypted, jwe.WithKey(jwa.A128KW, sharedkey)) if !assert.NoError(t, err, "Decrypt successful") { return } if !assert.Equal(t, plaintext, decrypted, "Decrypted correct plaintext") { return } } } //nolint:thelper func testEncodeECDHWithKey(t *testing.T, privkey interface{}, pubkey interface{}) { 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 { alg := alg t.Run(alg.String(), func(t *testing.T) { encrypted, err := jwe.Encrypt(plaintext, jwe.WithKey(alg, pubkey)) if !assert.NoError(t, err, "Encrypt succeeds") { return } _, err = jwe.Parse(encrypted) if !assert.NoError(t, err, `jwe.Parse should succeed`) { return } decrypted, err := jwe.Decrypt(encrypted, jwe.WithKey(alg, privkey)) if !assert.NoError(t, err, "Decrypt succeeds") { return } t.Logf("%s", decrypted) }) } } func TestEncode_ECDH(t *testing.T) { curves := []elliptic.Curve{ elliptic.P256(), elliptic.P384(), elliptic.P521(), } for _, crv := range curves { crv := crv t.Run(crv.Params().Name, func(t *testing.T) { privkey, err := ecdsa.GenerateKey(crv, rand.Reader) if !assert.NoError(t, err, `ecdsa.GenerateKey should succeed`) { return } testEncodeECDHWithKey(t, privkey, &privkey.PublicKey) }) } } func TestEncode_X25519(t *testing.T) { pubkey, privkey, err := x25519.GenerateKey(rand.Reader) if !assert.NoError(t, err, `x25519.GenerateKey should succeed`) { return } testEncodeECDHWithKey(t, privkey, pubkey) } 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 { tc := tc t.Run(tc.Name, func(t *testing.T) { webKey, err := jwk.ParseKey([]byte(tc.Key)) if !assert.NoError(t, err, `jwk.ParseKey should succeed`) { return } thumbprint, err := webKey.Thumbprint(crypto.SHA1) if !assert.NoError(t, err, `jwk.Thumbprint should succeed`) { return } if !assert.Equal(t, base64.RawURLEncoding.EncodeToString(thumbprint), tc.Thumbprint, `thumbprints should match`) { return } var key ecdsa.PrivateKey if !assert.NoError(t, webKey.Raw(&key), `jwk.Raw should succeed`) { return } decrypted, err := jwe.Decrypt([]byte(tc.Data), jwe.WithKeyProvider(jwe.KeyProviderFunc(func(_ context.Context, sink jwe.KeySink, r jwe.Recipient, _ *jwe.Message) error { sink.Key(r.Headers().Algorithm(), &key) return nil }))) if !assert.NoError(t, err, `jwe.Decrypt should succeed`) { return } if !assert.Equal(t, string(decrypted), plaintext, `plaintext should match`) { return } }) } } // tests direct key encryption by encrypting-decrypting a plaintext func TestEncode_Direct(t *testing.T) { var testcases = []struct { Algorithm jwa.ContentEncryptionAlgorithm KeySize int // in bytes }{ {jwa.A128CBC_HS256, 32}, {jwa.A128GCM, 16}, {jwa.A192CBC_HS384, 48}, {jwa.A192GCM, 24}, {jwa.A256CBC_HS512, 64}, {jwa.A256GCM, 32}, } plaintext := []byte("Lorem ipsum") for _, tc := range testcases { tc := tc t.Run(tc.Algorithm.String(), func(t *testing.T) { key := make([]byte, tc.KeySize) /* _, err := rand.Read(key) if !assert.NoError(t, err, "Key generation succeeds") { return }*/ 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 !assert.NoError(t, err, `jwe.Encrypt should succeed`) { return } decrypted, err := jwe.Decrypt(encrypted, jwe.WithKey(jwa.DIRECT, key)) if !assert.NoError(t, err, `jwe.Decrypt should succeed`) { return } assert.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 { tc := tc t.Run(tc.Algorithm.String(), func(t *testing.T) { webKey, err := jwk.ParseKey([]byte(tc.Key)) if !assert.NoError(t, err, `jwk.ParseKey should succeed`) { return } thumbprint, err := webKey.Thumbprint(crypto.SHA1) if !assert.NoError(t, err, `jwk.Thumbprint should succeed`) { return } if !assert.Equal(t, base64.RawURLEncoding.EncodeToString(thumbprint), tc.Thumbprint, `thumbprints should match`) { return } var key []byte if !assert.NoError(t, webKey.Raw(&key), `jwk.Raw should succeed`) { return } decrypted, err := jwe.Decrypt([]byte(tc.Data), jwe.WithKey(jwa.DIRECT, key)) if !assert.NoError(t, err, `jwe.Decrypt should succeed`) { return } if !assert.Equal(t, plaintext, string(decrypted), `plaintext should match`) { return } }) } } 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)) if !assert.NoError(t, err, `jwe.Parse should succeed`) { return } compact, err := jwe.Compact(msg) if !assert.NoError(t, err, `jwe.Compact should succeed`) { return } msg2, err := jwe.Parse(compact) if !assert.NoError(t, err, `jwe.Parse should succeed`) { return } if !assert.Equal(t, msg, msg2, `data -> msg -> compact -> msg2 produces msg == msg2`) { t.Logf("msg -> %#v", msg) t.Logf("msg2 -> %#v", msg2) return } } 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") if !assert.NoError(t, err, `os.CreateTemp should succeed`) { return } defer f.Close() fmt.Fprintf(f, "%s", s) if _, err := jwe.ReadFile(f.Name()); !assert.NoError(t, err, `jwe.ReadFile should succeed`) { return } } func TestCustomField(t *testing.T) { // XXX has global effect!!! jwe.RegisterCustomField(`x-birthday`, time.Time{}) defer jwe.RegisterCustomField(`x-birthday`, nil) expected := time.Date(2015, 11, 4, 5, 12, 52, 0, time.UTC) bdaybytes, _ := expected.MarshalText() // RFC3339 plaintext := []byte("Hello, World!") rsakey, err := jwxtest.GenerateRsaJwk() if !assert.NoError(t, err, `jwxtest.GenerateRsaJwk() should succeed`) { return } pubkey, err := jwk.PublicKeyOf(rsakey) if !assert.NoError(t, err, `jwk.PublicKeyOf() should succeed`) { return } protected := jwe.NewHeaders() protected.Set(`x-birthday`, string(bdaybytes)) encrypted, err := jwe.Encrypt(plaintext, jwe.WithKey(jwa.RSA_OAEP, pubkey), jwe.WithProtectedHeaders(protected)) if !assert.NoError(t, err, `jwe.Encrypt should succeed`) { return } t.Run("jwe.Parse + json.Unmarshal", func(t *testing.T) { msg, err := jwe.Parse(encrypted) if !assert.NoError(t, err, `jwe.Parse should succeed`) { return } v, ok := msg.ProtectedHeaders().Get(`x-birthday`) if !assert.True(t, ok, `msg.ProtectedHeaders().Get("x-birthday") should succeed`) { return } if !assert.Equal(t, expected, v, `values should match`) { return } // Create JSON from jwe.Message buf, err := json.Marshal(msg) if !assert.NoError(t, err, `json.Marshal should succeed`) { return } var msg2 jwe.Message if !assert.NoError(t, json.Unmarshal(buf, &msg2), `json.Unmarshal should succeed`) { return } v, ok = msg2.ProtectedHeaders().Get(`x-birthday`) if !assert.True(t, ok, `msg2.ProtectedHeaders().Get("x-birthday") should succeed`) { return } if !assert.Equal(t, expected, v, `values should match`) { return } }) } func TestGH554(t *testing.T) { const keyID = `very-secret-key` const plaintext = `hello world!` privkey, err := jwxtest.GenerateEcdsaJwk() if !assert.NoError(t, err, `jwxtest.GenerateEcdsaJwk() should succeed`) { return } _ = privkey.Set(jwk.KeyIDKey, keyID) pubkey, err := jwk.PublicKeyOf(privkey) if !assert.NoError(t, err, `jwk.PublicKeyOf() should succeed`) { return } if !assert.Equal(t, keyID, pubkey.KeyID(), `key ID should match`) { return } encrypted, err := jwe.Encrypt([]byte(plaintext), jwe.WithKey(jwa.ECDH_ES, pubkey)) if !assert.NoError(t, err, `jwk.Encrypt() should succeed`) { return } msg, err := jwe.Parse(encrypted) if !assert.NoError(t, err, `jwe.Parse() should succeed`) { return } recipients := msg.Recipients() // The epk must have the same key ID as the original kid := recipients[0].Headers().KeyID() if !assert.Equal(t, keyID, kid, `key ID in epk should match`) { return } } 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`) require.Equal(t, apu, msg.ProtectedHeaders().AgreementPartyUInfo()) require.Equal(t, apv, msg.ProtectedHeaders().AgreementPartyVInfo()) } 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.FromRaw([]byte(`abcdefg`)) require.NoError(t, err, `jwk.FromRaw 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 TestMaxBufferSize(t *testing.T) { // NOTE: This has GLOBAL EFFECT jwe.Settings(jwe.WithMaxBufferSize(1)) defer jwe.Settings(jwe.WithMaxBufferSize(0)) key, err := jwxtest.GenerateRsaJwk() require.NoError(t, err, `jwxtest.GenerateRsaJwk should succeed`) _, err = jwe.Encrypt([]byte("Lorem Ipsum"), jwe.WithContentEncryption(jwa.A128CBC_HS256), jwe.WithKey(jwa.RSA_OAEP, key)) require.Error(t, err, `jwe.Encrypt should fail`) } func TestMaxDecompressBufferSize(t *testing.T) { // This payload size is intentionally set to a small value to avoid // causing problems for regular users and CI/CD systems. If you wish to // verify that root issue is fixed, you may want to try increasing the // payload size to a larger value. const payloadSize = 1 << 16 privkey, err := rsa.GenerateKey(rand.Reader, 2048) require.NoError(t, err, `rsa.GenerateKey should succeed`) pubkey := &privkey.PublicKey wrongPrivkey, err := rsa.GenerateKey(rand.Reader, 2048) require.NoError(t, err, `rsa.GenerateKey should succeed`) wrongPubkey := &wrongPrivkey.PublicKey payload := strings.Repeat("x", payloadSize) testcases := []struct { Name string GlobalMaxSize int64 PublicKey *rsa.PublicKey Error bool ProcessDecryptOptions func([]jwe.DecryptOption) []jwe.DecryptOption }{ // This should work, because we set the MaxSize to be large (==payload size) { Name: "same as payload size", GlobalMaxSize: payloadSize, PublicKey: pubkey, }, // This should fail, because we set the GlobalMaxSize to be smaller than the payload size { Name: "smaller than payload size", GlobalMaxSize: payloadSize - 1, PublicKey: pubkey, Error: true, }, // This should fail, because the public key does not match the // private key used to decrypt the payload. In essence this way // we do NOT trigger the root cause of this issue, but we bail out early { Name: "Wrong PublicKey", GlobalMaxSize: payloadSize, PublicKey: wrongPubkey, Error: true, }, { Name: "global=payloadSize-1, per-call=payloadSize", GlobalMaxSize: payloadSize - 1, PublicKey: pubkey, ProcessDecryptOptions: func(options []jwe.DecryptOption) []jwe.DecryptOption { return append(options, jwe.WithMaxDecompressBufferSize(payloadSize)) }, }, // This should be the last test case to put the value back to default :) { Name: "Default 10MB globally", GlobalMaxSize: 10 * 1024 * 1024, PublicKey: pubkey, }, } for _, tc := range testcases { tc := tc t.Run(tc.Name, func(t *testing.T) { jwe.Settings(jwe.WithMaxDecompressBufferSize(tc.GlobalMaxSize)) encrypted, err := jwe.Encrypt([]byte(payload), jwe.WithKey(jwa.RSA_OAEP, tc.PublicKey), jwe.WithContentEncryption("A128CBC-HS256"), jwe.WithCompress(jwa.Deflate)) require.NoError(t, err, `jwe.Encrypt should succeed`) decryptOptions := []jwe.DecryptOption{jwe.WithKey(jwa.RSA_OAEP, privkey)} if fn := tc.ProcessDecryptOptions; fn != nil { decryptOptions = fn(decryptOptions) } _, err = jwe.Decrypt(encrypted, decryptOptions...) if tc.Error { require.Error(t, err, `jwe.Decrypt should fail`) } else { require.NoError(t, err, `jwe.Decrypt should succeed`) } }) } } golang-github-lestrrat-go-jwx-2.1.4/jwe/key_provider.go000066400000000000000000000112251476711647200231520ustar00rootroot00000000000000package jwe import ( "context" "fmt" "sync" "github.com/lestrrat-go/jwx/v2/jwa" "github.com/lestrrat-go/jwx/v2/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, interface{}) } type algKeyPair struct { alg jwa.KeyAlgorithm key interface{} } type algKeySink struct { mu sync.Mutex list []algKeyPair } func (s *algKeySink) Key(alg jwa.KeyEncryptionAlgorithm, key interface{}) { s.mu.Lock() s.list = append(s.list, algKeyPair{alg, key}) s.mu.Unlock() } type staticKeyProvider struct { alg jwa.KeyEncryptionAlgorithm key interface{} } 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 := key.KeyUsage(); usage != "" && usage != jwk.ForEncryption.String() { return nil } if v := key.Algorithm(); v.String() != "" { var alg jwa.KeyEncryptionAlgorithm if err := alg.Accept(v); err != nil { return fmt.Errorf(`invalid key encryption algorithm %s: %w`, key.Algorithm(), err) } sink.Key(alg, 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 := r.Headers().KeyID() if 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 := 0; i < kp.set.Len(); i++ { 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-2.1.4/jwe/message.go000066400000000000000000000346471476711647200221110ustar00rootroot00000000000000package jwe import ( "context" "fmt" "sort" "strings" "github.com/lestrrat-go/jwx/v2/internal/base64" "github.com/lestrrat-go/jwx/v2/internal/json" "github.com/lestrrat-go/jwx/v2/internal/pool" ) // 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.GetBytesBuffer() defer pool.ReleaseBytesBuffer(buf) buf.WriteString(`{"header":`) hdrbuf, err := r.headers.MarshalJSON() 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 interface{}) 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.GetBytesBuffer() defer pool.ReleaseBytesBuffer(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, '.') 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 { 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() 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(context.TODO()) 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`) } ctx := context.TODO() hcopy, err := m.protectedHeaders.Clone(ctx) if err != nil { return nil, fmt.Errorf(`failed to copy protected header: %w`, err) } hcopy, err = hcopy.Merge(ctx, m.unprotectedHeaders) if err != nil { return nil, fmt.Errorf(`failed to merge unprotected header: %w`, err) } hcopy, err = hcopy.Merge(ctx, 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.GetBytesBuffer() defer pool.ReleaseBytesBuffer(buf) buf.Grow(len(protected) + len(encryptedKey) + len(iv) + len(cipher) + len(tag) + 4) buf.Write(protected) buf.WriteByte('.') buf.Write(encryptedKey) buf.WriteByte('.') buf.Write(iv) buf.WriteByte('.') buf.Write(cipher) buf.WriteByte('.') buf.Write(tag) result := make([]byte, buf.Len()) copy(result, buf.Bytes()) return result, nil } golang-github-lestrrat-go-jwx-2.1.4/jwe/message_test.go000066400000000000000000000012241476711647200231310ustar00rootroot00000000000000package jwe_test import ( "testing" "github.com/lestrrat-go/jwx/v2/internal/json" "github.com/lestrrat-go/jwx/v2/jwe" "github.com/stretchr/testify/assert" ) func TestRecipient(t *testing.T) { t.Run("JSON Marshaling", func(t *testing.T) { const src = `{"header":{"foo":"bar"},"encrypted_key":"Zm9vYmFyYmF6"}` r1 := jwe.NewRecipient() if !assert.NoError(t, json.Unmarshal([]byte(src), r1), `json.Unmarshal should succeed`) { return } buf, err := json.Marshal(r1) if !assert.NoError(t, err, `json.Marshal should succeed`) { return } if !assert.Equal(t, []byte(src), buf, `json representation should match`) { return } }) } golang-github-lestrrat-go-jwx-2.1.4/jwe/options.go000066400000000000000000000057611476711647200221530ustar00rootroot00000000000000package jwe import ( "context" "github.com/lestrrat-go/jwx/v2/jwa" "github.com/lestrrat-go/jwx/v2/jwk" "github.com/lestrrat-go/option" ) // 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(context.Background()) return &encryptOption{option.New(identProtectedHeaders{}, cloned)} } type withKey struct { alg jwa.KeyAlgorithm key interface{} 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 interface{}, options ...WithKeySuboption) EncryptDecryptOption { var hdr Headers for _, option := range options { //nolint:forcetypeassert switch option.Ident() { case identPerRecipientHeaders{}: hdr = option.Value().(Headers) } } 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 { //nolint:forcetypeassert switch option.Ident() { case identRequireKid{}: requireKid = option.Value().(bool) } } 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 { //nolint:forcetypeassert switch option.Ident() { case identPretty{}: pretty = option.Value().(bool) } } format := fmtJSON if pretty { format = fmtJSONPretty } return &encryptOption{option.New(identSerialization{}, format)} } golang-github-lestrrat-go-jwx-2.1.4/jwe/options.yaml000066400000000000000000000145151476711647200225050ustar00rootroot00000000000000package_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: 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: 'interface{}' 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 `interface{}`. 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: MaxBufferSize interface: GlobalOption argument_type: int64 comment: | WithMaxBufferSize specifies the maximum buffer size for internal calculations, such as when AES-CBC is performed. The default value is 256MB. If set to an invalid value, the default value is used. This option has a global effect. Due to historical reasons this option has a vague name, but in future versions it will be appropriately renamed. golang-github-lestrrat-go-jwx-2.1.4/jwe/options_gen.go000066400000000000000000000217341476711647200230020ustar00rootroot00000000000000// Code generated by tools/cmd/genoptions/main.go. DO NOT EDIT. package jwe import ( "io/fs" "github.com/lestrrat-go/jwx/v2/jwa" "github.com/lestrrat-go/option" ) 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 identCEK struct{} type identCompress struct{} type identContentEncryptionAlgorithm struct{} type identFS struct{} type identKey struct{} type identKeyProvider struct{} type identKeyUsed struct{} type identMaxBufferSize 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 (identCEK) String() string { return "WithCEK" } func (identCompress) String() string { return "WithCompress" } func (identContentEncryptionAlgorithm) String() string { return "WithContentEncryption" } 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 (identMaxBufferSize) String() string { return "WithMaxBufferSize" } 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" } // 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)} } // 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 `interface{}`. 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 interface{}) DecryptOption { return &decryptOption{option.New(identKeyUsed{}, v)} } // WithMaxBufferSize specifies the maximum buffer size for internal // calculations, such as when AES-CBC is performed. The default value is 256MB. // If set to an invalid value, the default value is used. // // This option has a global effect. // // Due to historical reasons this option has a vague name, but in future versions // it will be appropriately renamed. func WithMaxBufferSize(v int64) GlobalOption { return &globalOption{option.New(identMaxBufferSize{}, v)} } // WithMaxDecompressBufferSize specifies the maximum buffer size for used when // decompressing the payload of a JWE message. If a compressed JWE payload // exceeds this amount when decompressed, jwe.Decrypt will return an error. // The default value is 10MB. // // This option can be used for `jwe.Settings()`, which changes the behavior // globally, or for `jwe.Decrypt()`, which changes the behavior for that // specific call. func WithMaxDecompressBufferSize(v int64) GlobalDecryptOption { return &globalDecryptOption{option.New(identMaxDecompressBufferSize{}, v)} } // WithMaxPBES2Count specifies the maximum number of PBES2 iterations // to use when decrypting a message. If not specified, the default // value of 10,000 is used. // // 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-2.1.4/jwe/options_gen_test.go000066400000000000000000000024511476711647200240340ustar00rootroot00000000000000// 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, "WithCEK", identCEK{}.String()) require.Equal(t, "WithCompress", identCompress{}.String()) require.Equal(t, "WithContentEncryption", identContentEncryptionAlgorithm{}.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, "WithMaxBufferSize", identMaxBufferSize{}.String()) require.Equal(t, "WithMaxDecompressBufferSize", identMaxDecompressBufferSize{}.String()) require.Equal(t, "WithMaxPBES2Count", identMaxPBES2Count{}.String()) require.Equal(t, "WithMergeProtectedHeaders", identMergeProtectedHeaders{}.String()) require.Equal(t, "WithMessage", identMessage{}.String()) 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-2.1.4/jwe/speed_test.go000066400000000000000000000022541476711647200226110ustar00rootroot00000000000000package jwe import ( "bytes" "testing" ) var s = []byte(`eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkEyNTZHQ00ifQ.OKOawDo13gRp2ojaHV7LFpZcgV7T6DVZKTyKOMTYUmKoTCVJRgckCL9kiMT03JGeipsEdY3mx_etLbbWSrFr05kLzcSr4qKAq7YN7e9jwQRb23nfa6c9d-StnImGyFDbSv04uVuxIp5Zms1gNxKKK2Da14B8S4rzVRltdYwam_lDp5XnZAYpQdb76FdIKLaVmqgfwX7XWRxv2322i-vDxRfqNzo_tETKzpVLzfiwQyeyPGLBIO56YJ7eObdv0je81860ppamavo35UgoRdbYaBcoh9QcfylQr66oc6vFWXRcZ_ZT2LawVCWTIy3brGPi6UklfCpIMfIjf7iGdXKHzg.48V1_ALb6US04U3b.5eym8TW_c8SuK0ltJ3rpYIzOeDQz7TALvtu6UG9oMo4vpzs9tX_EFShS8iB7j6jiSdiwkIr3ajwQzaBtQD_A.XFBoMYUZodetZdvTiFvSkQ`) func BenchmarkSplitLib(b *testing.B) { for i := 0; i < b.N; i++ { SplitLib(s) } } func BenchmarkSplitManual(b *testing.B) { ret := make([][]byte, 5) for i := 0; i < b.N; i++ { SplitManual(ret, s) } } func SplitLib(buf []byte) [][]byte { return bytes.Split(buf, []byte{'.'}) } func SplitManual(parts [][]byte, buf []byte) { bufi := 0 for len(buf) > 0 { i := bytes.IndexByte(buf, '.') 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, '.'); i != -1 { return } parts[4] = buf } golang-github-lestrrat-go-jwx-2.1.4/jwk/000077500000000000000000000000001476711647200201265ustar00rootroot00000000000000golang-github-lestrrat-go-jwx-2.1.4/jwk/BUILD.bazel000066400000000000000000000036621476711647200220130ustar00rootroot00000000000000load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") go_library( name = "jwk", srcs = [ "cache.go", "ecdsa.go", "ecdsa_gen.go", "fetch.go", "interface.go", "interface_gen.go", "io.go", "jwk.go", "key_ops.go", "okp.go", "okp_gen.go", "options.go", "options_gen.go", "rsa.go", "rsa_gen.go", "set.go", "symmetric.go", "symmetric_gen.go", "usage.go", "whitelist.go", ], importpath = "github.com/lestrrat-go/jwx/v2/jwk", visibility = ["//visibility:public"], deps = [ "//cert", "//internal/base64", "//internal/ecutil", "//internal/iter", "//internal/json", "//internal/pool", "//jwa", "//x25519", "//jwk/internal/x509", "@com_github_lestrrat_go_blackmagic//:go_default_library", "@com_github_lestrrat_go_httprc//:go_default_library", "@com_github_lestrrat_go_iter//arrayiter:go_default_library", "@com_github_lestrrat_go_iter//mapiter:go_default_library", "@com_github_lestrrat_go_option//:option", ], ) go_test( name = "jwk_test", srcs = [ "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/ecutil", "//internal/jose", "//internal/json", "//internal/jwxtest", "//jwa", "//jws", "//x25519", "//jwk/internal/x509", "@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-2.1.4/jwk/README.md000066400000000000000000000155411476711647200214130ustar00rootroot00000000000000# JWK [![Go Reference](https://pkg.go.dev/badge/github.com/lestrrat-go/jwx/v2/jwk.svg)](https://pkg.go.dev/github.com/lestrrat-go/jwx/v2/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/v2/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/jwx/v2/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. // // Note that by default refreshes only happen very 15 minutes at the // earliest. If you need to control this, use `jwk.WithRefreshWindow()` c := jwk.NewCache(ctx) // Tell *jwk.Cache that we only want to refresh this JWKS // when it needs to (based on Cache-Control or Expires header from // the HTTP response). If the calculated minimum refresh interval is less // than 15 minutes, don't go refreshing any earlier than 15 minutes. c.Register(googleCerts, jwk.WithMinRefreshInterval(15*time.Minute)) // Refresh the JWKS once before getting into the main loop. // This allows you to check if the JWKS is available before we start // a long-running program _, err := c.Refresh(ctx, googleCerts) if err != nil { fmt.Printf("failed to refresh 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.Get(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 // fetch upon calling `(jwk.Cache).Fetch()`. // 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/v2/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/v2/internal/json" "github.com/lestrrat-go/jwx/v2/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 it := set.Keys(context.Background()); it.Next(context.Background()); { pair := it.Pair() key := pair.Value.(jwk.Key) var rawkey interface{} // This is the raw key, like *rsa.PrivateKey or *ecdsa.PrivateKey if err := key.Raw(&rawkey); err != nil { log.Printf("failed to create public key: %s", err) return } // Use rawkey for jws.Verify() or whatever. _ = rawkey // You can create jwk.Key from a raw key, too fromRawKey, err := jwk.FromRaw(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.FromRaw(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/v2/examples/jwk_example_test.go) golang-github-lestrrat-go-jwx-2.1.4/jwk/cache.go000066400000000000000000000304731476711647200215270ustar00rootroot00000000000000package jwk import ( "context" "fmt" "io" "net/http" "time" "github.com/lestrrat-go/httprc" "github.com/lestrrat-go/iter/arrayiter" "github.com/lestrrat-go/iter/mapiter" ) type Transformer = httprc.Transformer type HTTPClient = httprc.HTTPClient type ErrSink = httprc.ErrSink // Whitelist describes a set of rules that allows users to access // a particular URL. By default all URLs are blocked for security // reasons. You will HAVE to provide some sort of whitelist. See // the documentation for github.com/lestrrat-go/httprc for more details. type Whitelist = httprc.Whitelist // Cache is a container 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) // c.Register(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 { cache *httprc.Cache } // PostFetcher is an interface for objects that want to perform // operations on the `Set` that was fetched. type PostFetcher interface { // PostFetch receives the URL and the JWKS, after a successful // fetch and parse. // // It should return a `Set`, optionally modified, to be stored // in the cache for subsequent use PostFetch(string, Set) (Set, error) } // PostFetchFunc is a PostFetcher based on a function. type PostFetchFunc func(string, Set) (Set, error) func (f PostFetchFunc) PostFetch(u string, set Set) (Set, error) { return f(u, set) } // httprc.Transformer that transforms the response into a JWKS type jwksTransform struct { postFetch PostFetcher parseOptions []ParseOption } // Default transform has no postFetch. This can be shared // by multiple fetchers var defaultTransform = &jwksTransform{} func (t *jwksTransform) Transform(u string, res *http.Response) (interface{}, error) { if res.StatusCode != http.StatusOK { return nil, fmt.Errorf(`failed to process response: non-200 response code %q`, res.Status) } buf, err := io.ReadAll(res.Body) if err != nil { return nil, fmt.Errorf(`failed to read response body status: %w`, err) } set, err := Parse(buf, t.parseOptions...) if err != nil { return nil, fmt.Errorf(`failed to parse JWK set at %q: %w`, u, err) } if pf := t.postFetch; pf != nil { v, err := pf.PostFetch(u, set) if err != nil { return nil, fmt.Errorf(`failed to execute PostFetch: %w`, err) } set = v } return set, nil } // NewCache creates a new `jwk.Cache` object. // // Please refer to the documentation for `httprc.New` for more // details. func NewCache(ctx context.Context, options ...CacheOption) *Cache { var hrcopts []httprc.CacheOption for _, option := range options { //nolint:forcetypeassert switch option.Ident() { case identRefreshWindow{}: hrcopts = append(hrcopts, httprc.WithRefreshWindow(option.Value().(time.Duration))) case identErrSink{}: hrcopts = append(hrcopts, httprc.WithErrSink(option.Value().(ErrSink))) } } return &Cache{ cache: httprc.NewCache(ctx, hrcopts...), } } // Register registers a URL to be managed by the cache. URLs must // be registered before issuing `Get` // // This method is almost identical to `(httprc.Cache).Register`, except // it accepts some extra options. // // Use `jwk.WithParser` to configure how the JWKS should be parsed, // such as passing it extra options. // // Please refer to the documentation for `(httprc.Cache).Register` for more // details. // // Register does not check for the validity of the url being registered. // If you need to make sure that a url is valid before entering your main // loop, call `Refresh` once to make sure the JWKS is available. // // _ = cache.Register(url) // if _, err := cache.Refresh(ctx, url); err != nil { // // url is not a valid JWKS // panic(err) // } func (c *Cache) Register(u string, options ...RegisterOption) error { var hrropts []httprc.RegisterOption var pf PostFetcher var parseOptions []ParseOption // Note: we do NOT accept Transform option for _, option := range options { if parseOpt, ok := option.(ParseOption); ok { parseOptions = append(parseOptions, parseOpt) continue } //nolint:forcetypeassert switch option.Ident() { case identHTTPClient{}: hrropts = append(hrropts, httprc.WithHTTPClient(option.Value().(HTTPClient))) case identRefreshInterval{}: hrropts = append(hrropts, httprc.WithRefreshInterval(option.Value().(time.Duration))) case identMinRefreshInterval{}: hrropts = append(hrropts, httprc.WithMinRefreshInterval(option.Value().(time.Duration))) case identFetchWhitelist{}: hrropts = append(hrropts, httprc.WithWhitelist(option.Value().(httprc.Whitelist))) case identPostFetcher{}: pf = option.Value().(PostFetcher) } } var t *jwksTransform if pf == nil && len(parseOptions) == 0 { t = defaultTransform } else { // User-supplied PostFetcher is attached to the transformer t = &jwksTransform{ postFetch: pf, parseOptions: parseOptions, } } // Set the transformer at the end so that nobody can override it hrropts = append(hrropts, httprc.WithTransformer(t)) return c.cache.Register(u, hrropts...) } // Get returns the stored JWK set (`Set`) from the cache. // // Please refer to the documentation for `(httprc.Cache).Get` for more // details. func (c *Cache) Get(ctx context.Context, u string) (Set, error) { v, err := c.cache.Get(ctx, u) if err != nil { return nil, err } set, ok := v.(Set) if !ok { return nil, fmt.Errorf(`cached object is not a Set (was %T)`, v) } return set, nil } // 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) { v, err := c.cache.Refresh(ctx, u) if err != nil { return nil, err } set, ok := v.(Set) if !ok { return nil, fmt.Errorf(`cached object is not a Set (was %T)`, v) } return set, nil } // IsRegistered returns true if the given URL `u` has already been registered // in the cache. // // Please refer to the documentation for `(httprc.Cache).IsRegistered` for more // details. func (c *Cache) IsRegistered(u string) bool { return c.cache.IsRegistered(u) } // Unregister removes the given URL `u` from the cache. // // Please refer to the documentation for `(httprc.Cache).Unregister` for more // details. func (c *Cache) Unregister(u string) error { return c.cache.Unregister(u) } func (c *Cache) Snapshot() *httprc.Snapshot { return c.cache.Snapshot() } // 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 struct { cache *Cache url string } var _ Set = &CachedSet{} func NewCachedSet(cache *Cache, url string) Set { return &CachedSet{ cache: cache, url: url, } } func (cs *CachedSet) cached() (Set, error) { return cs.cache.Get(context.Background(), cs.url) } // Add 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, _ interface{}) 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) (interface{}, bool) { set, err := cs.cached() if err != nil { return nil, false } return set.Get(name) } // 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(ctx context.Context) KeyIterator { set, err := cs.cached() if err != nil { return arrayiter.New(nil) } return set.Keys(ctx) } func (cs *CachedSet) Iterate(ctx context.Context) HeaderIterator { set, err := cs.cached() if err != nil { return mapiter.New(nil) } return set.Iterate(ctx) } 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-2.1.4/jwk/ecdsa.go000066400000000000000000000152771476711647200215500ustar00rootroot00000000000000package jwk import ( "crypto" "crypto/ecdsa" "crypto/elliptic" "fmt" "math/big" "github.com/lestrrat-go/blackmagic" "github.com/lestrrat-go/jwx/v2/internal/base64" "github.com/lestrrat-go/jwx/v2/internal/ecutil" "github.com/lestrrat-go/jwx/v2/jwa" ) func init() { ecutil.RegisterCurve(elliptic.P256(), jwa.P256) ecutil.RegisterCurve(elliptic.P384(), jwa.P384) ecutil.RegisterCurve(elliptic.P521(), jwa.P521) } func (k *ecdsaPublicKey) FromRaw(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) var crv jwa.EllipticCurveAlgorithm if tmp, ok := ecutil.AlgorithmForCurve(rawKey.Curve); ok { crv = tmp } else { return fmt.Errorf(`invalid elliptic curve %s`, rawKey.Curve) } k.crv = &crv return nil } func (k *ecdsaPrivateKey) FromRaw(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) var crv jwa.EllipticCurveAlgorithm if tmp, ok := ecutil.AlgorithmForCurve(rawKey.Curve); ok { crv = tmp } else { return fmt.Errorf(`invalid elliptic curve %s`, rawKey.Curve) } k.crv = &crv return nil } func buildECDSAPublicKey(alg jwa.EllipticCurveAlgorithm, xbuf, ybuf []byte) (*ecdsa.PublicKey, error) { var crv elliptic.Curve if tmp, ok := ecutil.CurveForAlgorithm(alg); ok { crv = tmp } else { return nil, fmt.Errorf(`invalid curve algorithm %s`, alg) } var x, y big.Int x.SetBytes(xbuf) y.SetBytes(ybuf) return &ecdsa.PublicKey{Curve: crv, X: &x, Y: &y}, nil } // Raw returns the EC-DSA public key represented by this JWK func (k *ecdsaPublicKey) Raw(v interface{}) error { k.mu.RLock() defer k.mu.RUnlock() pubk, err := buildECDSAPublicKey(k.Crv(), k.x, k.y) if err != nil { return fmt.Errorf(`failed to build public key: %w`, err) } return blackmagic.AssignIfCompatible(v, pubk) } func (k *ecdsaPrivateKey) Raw(v interface{}) error { k.mu.RLock() defer k.mu.RUnlock() pubk, err := buildECDSAPublicKey(k.Crv(), k.x, k.y) if err != nil { return fmt.Errorf(`failed to build public key: %w`, err) } var key ecdsa.PrivateKey var d big.Int d.SetBytes(k.d) key.D = &d key.PublicKey = *pubk return blackmagic.AssignIfCompatible(v, &key) } func makeECDSAPublicKey(v interface { makePairs() []*HeaderPair }) (Key, error) { newKey := newECDSAPublicKey() // Iterate and copy everything except for the bits that should not be in the public key for _, pair := range v.makePairs() { switch pair.Key { case ECDSADKey: continue default: //nolint:forcetypeassert key := pair.Key.(string) if err := newKey.Set(key, pair.Value); err != nil { return nil, fmt.Errorf(`failed to set field %q: %w`, key, 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 := k.Raw(&key); err != nil { return nil, fmt.Errorf(`failed to materialize 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 := k.Raw(&key); err != nil { return nil, fmt.Errorf(`failed to materialize 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 X() []byte Y() []byte }, checkPrivate bool) error { crv, ok := ecutil.CurveForAlgorithm(k.Crv()) if !ok { return fmt.Errorf(`invalid curve algorithm %q`, k.Crv()) } keySize := ecutil.CalculateKeySize(crv) if x := k.X(); len(x) != keySize { return fmt.Errorf(`invalid "x" length (%d) for curve %q`, len(x), crv.Params().Name) } if y := k.Y(); len(y) != keySize { return fmt.Errorf(`invalid "y" length (%d) for curve %q`, len(y), crv.Params().Name) } if checkPrivate { if priv, ok := k.(interface{ D() []byte }); ok { if len(priv.D()) != keySize { return fmt.Errorf(`invalid "d" length (%d) for curve %q`, len(priv.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-2.1.4/jwk/ecdsa_gen.go000066400000000000000000000735721476711647200224030ustar00rootroot00000000000000// Code generated by tools/cmd/genjwk/main.go. DO NOT EDIT. package jwk import ( "bytes" "context" "crypto/ecdsa" "fmt" "sort" "sync" "github.com/lestrrat-go/iter/mapiter" "github.com/lestrrat-go/jwx/v2/cert" "github.com/lestrrat-go/jwx/v2/internal/base64" "github.com/lestrrat-go/jwx/v2/internal/iter" "github.com/lestrrat-go/jwx/v2/internal/json" "github.com/lestrrat-go/jwx/v2/internal/pool" "github.com/lestrrat-go/jwx/v2/jwa" ) const ( ECDSACrvKey = "crv" ECDSADKey = "d" ECDSAXKey = "x" ECDSAYKey = "y" ) type ECDSAPublicKey interface { Key FromRaw(*ecdsa.PublicKey) error Crv() jwa.EllipticCurveAlgorithm X() []byte Y() []byte } 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]interface{} mu *sync.RWMutex dc json.DecodeCtx } var _ ECDSAPublicKey = &ecdsaPublicKey{} var _ Key = &ecdsaPublicKey{} func newECDSAPublicKey() *ecdsaPublicKey { return &ecdsaPublicKey{ mu: &sync.RWMutex{}, privateParams: make(map[string]interface{}), } } func (h ecdsaPublicKey) KeyType() jwa.KeyType { return jwa.EC } func (h ecdsaPublicKey) IsPrivate() bool { return false } func (h *ecdsaPublicKey) Algorithm() jwa.KeyAlgorithm { if h.algorithm != nil { return *(h.algorithm) } return jwa.InvalidKeyAlgorithm("") } func (h *ecdsaPublicKey) Crv() jwa.EllipticCurveAlgorithm { if h.crv != nil { return *(h.crv) } return jwa.InvalidEllipticCurve } func (h *ecdsaPublicKey) KeyID() string { if h.keyID != nil { return *(h.keyID) } return "" } func (h *ecdsaPublicKey) KeyOps() KeyOperationList { if h.keyOps != nil { return *(h.keyOps) } return nil } func (h *ecdsaPublicKey) KeyUsage() string { if h.keyUsage != nil { return *(h.keyUsage) } return "" } func (h *ecdsaPublicKey) X() []byte { return h.x } func (h *ecdsaPublicKey) X509CertChain() *cert.Chain { return h.x509CertChain } func (h *ecdsaPublicKey) X509CertThumbprint() string { if h.x509CertThumbprint != nil { return *(h.x509CertThumbprint) } return "" } func (h *ecdsaPublicKey) X509CertThumbprintS256() string { if h.x509CertThumbprintS256 != nil { return *(h.x509CertThumbprintS256) } return "" } func (h *ecdsaPublicKey) X509URL() string { if h.x509URL != nil { return *(h.x509URL) } return "" } func (h *ecdsaPublicKey) Y() []byte { return h.y } func (h *ecdsaPublicKey) makePairs() []*HeaderPair { h.mu.RLock() defer h.mu.RUnlock() var pairs []*HeaderPair pairs = append(pairs, &HeaderPair{Key: "kty", Value: jwa.EC}) if h.algorithm != nil { pairs = append(pairs, &HeaderPair{Key: AlgorithmKey, Value: *(h.algorithm)}) } if h.crv != nil { pairs = append(pairs, &HeaderPair{Key: ECDSACrvKey, Value: *(h.crv)}) } if h.keyID != nil { pairs = append(pairs, &HeaderPair{Key: KeyIDKey, Value: *(h.keyID)}) } if h.keyOps != nil { pairs = append(pairs, &HeaderPair{Key: KeyOpsKey, Value: *(h.keyOps)}) } if h.keyUsage != nil { pairs = append(pairs, &HeaderPair{Key: KeyUsageKey, Value: *(h.keyUsage)}) } if h.x != nil { pairs = append(pairs, &HeaderPair{Key: ECDSAXKey, Value: h.x}) } if h.x509CertChain != nil { pairs = append(pairs, &HeaderPair{Key: X509CertChainKey, Value: h.x509CertChain}) } if h.x509CertThumbprint != nil { pairs = append(pairs, &HeaderPair{Key: X509CertThumbprintKey, Value: *(h.x509CertThumbprint)}) } if h.x509CertThumbprintS256 != nil { pairs = append(pairs, &HeaderPair{Key: X509CertThumbprintS256Key, Value: *(h.x509CertThumbprintS256)}) } if h.x509URL != nil { pairs = append(pairs, &HeaderPair{Key: X509URLKey, Value: *(h.x509URL)}) } if h.y != nil { pairs = append(pairs, &HeaderPair{Key: ECDSAYKey, Value: h.y}) } for k, v := range h.privateParams { pairs = append(pairs, &HeaderPair{Key: k, Value: v}) } return pairs } func (h *ecdsaPublicKey) PrivateParams() map[string]interface{} { return h.privateParams } func (h *ecdsaPublicKey) Get(name string) (interface{}, bool) { h.mu.RLock() defer h.mu.RUnlock() switch name { case KeyTypeKey: return h.KeyType(), true case AlgorithmKey: if h.algorithm == nil { return nil, false } return *(h.algorithm), true case ECDSACrvKey: if h.crv == nil { return nil, false } return *(h.crv), true case KeyIDKey: if h.keyID == nil { return nil, false } return *(h.keyID), true case KeyOpsKey: if h.keyOps == nil { return nil, false } return *(h.keyOps), true case KeyUsageKey: if h.keyUsage == nil { return nil, false } return *(h.keyUsage), true case ECDSAXKey: if h.x == nil { return nil, false } return h.x, true case X509CertChainKey: if h.x509CertChain == nil { return nil, false } return h.x509CertChain, true case X509CertThumbprintKey: if h.x509CertThumbprint == nil { return nil, false } return *(h.x509CertThumbprint), true case X509CertThumbprintS256Key: if h.x509CertThumbprintS256 == nil { return nil, false } return *(h.x509CertThumbprintS256), true case X509URLKey: if h.x509URL == nil { return nil, false } return *(h.x509URL), true case ECDSAYKey: if h.y == nil { return nil, false } return h.y, true default: v, ok := h.privateParams[name] return v, ok } } func (h *ecdsaPublicKey) Set(name string, value interface{}) error { h.mu.Lock() defer h.mu.Unlock() return h.setNoLock(name, value) } func (h *ecdsaPublicKey) setNoLock(name string, value interface{}) error { switch name { case "kty": return nil case AlgorithmKey: switch v := value.(type) { case string, jwa.SignatureAlgorithm, jwa.ContentEncryptionAlgorithm: var tmp = jwa.KeyAlgorithmFrom(v) h.algorithm = &tmp case fmt.Stringer: s := v.String() var tmp = jwa.KeyAlgorithmFrom(s) h.algorithm = &tmp default: return fmt.Errorf(`invalid type for %s 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]interface{}{} } 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) { return cloneKey(k) } 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 '{' or '}' here. if tok == '}' { // End of object break LOOP } else if tok != '{' { return fmt.Errorf(`expected '{', but got '%c'`, 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 := jwa.KeyAlgorithmFrom(s) 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]interface{}) fields := make([]string, 0, 11) for _, pair := range h.makePairs() { fields = append(fields, pair.Key.(string)) data[pair.Key.(string)] = pair.Value } sort.Strings(fields) buf := pool.GetBytesBuffer() defer pool.ReleaseBytesBuffer(buf) buf.WriteByte('{') enc := json.NewEncoder(buf) for i, f := range fields { if i > 0 { buf.WriteRune(',') } buf.WriteRune('"') buf.WriteString(f) buf.WriteString(`":`) v := data[f] switch v := v.(type) { case []byte: buf.WriteRune('"') buf.WriteString(base64.EncodeToString(v)) buf.WriteRune('"') 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('}') ret := make([]byte, buf.Len()) copy(ret, buf.Bytes()) return ret, nil } func (h *ecdsaPublicKey) Iterate(ctx context.Context) HeaderIterator { pairs := h.makePairs() ch := make(chan *HeaderPair, len(pairs)) go func(ctx context.Context, ch chan *HeaderPair, pairs []*HeaderPair) { defer close(ch) for _, pair := range pairs { select { case <-ctx.Done(): return case ch <- pair: } } }(ctx, ch, pairs) return mapiter.New(ch) } func (h *ecdsaPublicKey) Walk(ctx context.Context, visitor HeaderVisitor) error { return iter.WalkMap(ctx, h, visitor) } func (h *ecdsaPublicKey) AsMap(ctx context.Context) (map[string]interface{}, error) { return iter.AsMap(ctx, h) } type ECDSAPrivateKey interface { Key FromRaw(*ecdsa.PrivateKey) error Crv() jwa.EllipticCurveAlgorithm D() []byte X() []byte Y() []byte } 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]interface{} mu *sync.RWMutex dc json.DecodeCtx } var _ ECDSAPrivateKey = &ecdsaPrivateKey{} var _ Key = &ecdsaPrivateKey{} func newECDSAPrivateKey() *ecdsaPrivateKey { return &ecdsaPrivateKey{ mu: &sync.RWMutex{}, privateParams: make(map[string]interface{}), } } func (h ecdsaPrivateKey) KeyType() jwa.KeyType { return jwa.EC } func (h ecdsaPrivateKey) IsPrivate() bool { return true } func (h *ecdsaPrivateKey) Algorithm() jwa.KeyAlgorithm { if h.algorithm != nil { return *(h.algorithm) } return jwa.InvalidKeyAlgorithm("") } func (h *ecdsaPrivateKey) Crv() jwa.EllipticCurveAlgorithm { if h.crv != nil { return *(h.crv) } return jwa.InvalidEllipticCurve } func (h *ecdsaPrivateKey) D() []byte { return h.d } func (h *ecdsaPrivateKey) KeyID() string { if h.keyID != nil { return *(h.keyID) } return "" } func (h *ecdsaPrivateKey) KeyOps() KeyOperationList { if h.keyOps != nil { return *(h.keyOps) } return nil } func (h *ecdsaPrivateKey) KeyUsage() string { if h.keyUsage != nil { return *(h.keyUsage) } return "" } func (h *ecdsaPrivateKey) X() []byte { return h.x } func (h *ecdsaPrivateKey) X509CertChain() *cert.Chain { return h.x509CertChain } func (h *ecdsaPrivateKey) X509CertThumbprint() string { if h.x509CertThumbprint != nil { return *(h.x509CertThumbprint) } return "" } func (h *ecdsaPrivateKey) X509CertThumbprintS256() string { if h.x509CertThumbprintS256 != nil { return *(h.x509CertThumbprintS256) } return "" } func (h *ecdsaPrivateKey) X509URL() string { if h.x509URL != nil { return *(h.x509URL) } return "" } func (h *ecdsaPrivateKey) Y() []byte { return h.y } func (h *ecdsaPrivateKey) makePairs() []*HeaderPair { h.mu.RLock() defer h.mu.RUnlock() var pairs []*HeaderPair pairs = append(pairs, &HeaderPair{Key: "kty", Value: jwa.EC}) if h.algorithm != nil { pairs = append(pairs, &HeaderPair{Key: AlgorithmKey, Value: *(h.algorithm)}) } if h.crv != nil { pairs = append(pairs, &HeaderPair{Key: ECDSACrvKey, Value: *(h.crv)}) } if h.d != nil { pairs = append(pairs, &HeaderPair{Key: ECDSADKey, Value: h.d}) } if h.keyID != nil { pairs = append(pairs, &HeaderPair{Key: KeyIDKey, Value: *(h.keyID)}) } if h.keyOps != nil { pairs = append(pairs, &HeaderPair{Key: KeyOpsKey, Value: *(h.keyOps)}) } if h.keyUsage != nil { pairs = append(pairs, &HeaderPair{Key: KeyUsageKey, Value: *(h.keyUsage)}) } if h.x != nil { pairs = append(pairs, &HeaderPair{Key: ECDSAXKey, Value: h.x}) } if h.x509CertChain != nil { pairs = append(pairs, &HeaderPair{Key: X509CertChainKey, Value: h.x509CertChain}) } if h.x509CertThumbprint != nil { pairs = append(pairs, &HeaderPair{Key: X509CertThumbprintKey, Value: *(h.x509CertThumbprint)}) } if h.x509CertThumbprintS256 != nil { pairs = append(pairs, &HeaderPair{Key: X509CertThumbprintS256Key, Value: *(h.x509CertThumbprintS256)}) } if h.x509URL != nil { pairs = append(pairs, &HeaderPair{Key: X509URLKey, Value: *(h.x509URL)}) } if h.y != nil { pairs = append(pairs, &HeaderPair{Key: ECDSAYKey, Value: h.y}) } for k, v := range h.privateParams { pairs = append(pairs, &HeaderPair{Key: k, Value: v}) } return pairs } func (h *ecdsaPrivateKey) PrivateParams() map[string]interface{} { return h.privateParams } func (h *ecdsaPrivateKey) Get(name string) (interface{}, bool) { h.mu.RLock() defer h.mu.RUnlock() switch name { case KeyTypeKey: return h.KeyType(), true case AlgorithmKey: if h.algorithm == nil { return nil, false } return *(h.algorithm), true case ECDSACrvKey: if h.crv == nil { return nil, false } return *(h.crv), true case ECDSADKey: if h.d == nil { return nil, false } return h.d, true case KeyIDKey: if h.keyID == nil { return nil, false } return *(h.keyID), true case KeyOpsKey: if h.keyOps == nil { return nil, false } return *(h.keyOps), true case KeyUsageKey: if h.keyUsage == nil { return nil, false } return *(h.keyUsage), true case ECDSAXKey: if h.x == nil { return nil, false } return h.x, true case X509CertChainKey: if h.x509CertChain == nil { return nil, false } return h.x509CertChain, true case X509CertThumbprintKey: if h.x509CertThumbprint == nil { return nil, false } return *(h.x509CertThumbprint), true case X509CertThumbprintS256Key: if h.x509CertThumbprintS256 == nil { return nil, false } return *(h.x509CertThumbprintS256), true case X509URLKey: if h.x509URL == nil { return nil, false } return *(h.x509URL), true case ECDSAYKey: if h.y == nil { return nil, false } return h.y, true default: v, ok := h.privateParams[name] return v, ok } } func (h *ecdsaPrivateKey) Set(name string, value interface{}) error { h.mu.Lock() defer h.mu.Unlock() return h.setNoLock(name, value) } func (h *ecdsaPrivateKey) setNoLock(name string, value interface{}) error { switch name { case "kty": return nil case AlgorithmKey: switch v := value.(type) { case string, jwa.SignatureAlgorithm, jwa.ContentEncryptionAlgorithm: var tmp = jwa.KeyAlgorithmFrom(v) h.algorithm = &tmp case fmt.Stringer: s := v.String() var tmp = jwa.KeyAlgorithmFrom(s) h.algorithm = &tmp default: return fmt.Errorf(`invalid type for %s 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]interface{}{} } 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) { return cloneKey(k) } 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 '{' or '}' here. if tok == '}' { // End of object break LOOP } else if tok != '{' { return fmt.Errorf(`expected '{', but got '%c'`, 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 := jwa.KeyAlgorithmFrom(s) 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]interface{}) fields := make([]string, 0, 12) for _, pair := range h.makePairs() { fields = append(fields, pair.Key.(string)) data[pair.Key.(string)] = pair.Value } sort.Strings(fields) buf := pool.GetBytesBuffer() defer pool.ReleaseBytesBuffer(buf) buf.WriteByte('{') enc := json.NewEncoder(buf) for i, f := range fields { if i > 0 { buf.WriteRune(',') } buf.WriteRune('"') buf.WriteString(f) buf.WriteString(`":`) v := data[f] switch v := v.(type) { case []byte: buf.WriteRune('"') buf.WriteString(base64.EncodeToString(v)) buf.WriteRune('"') 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('}') ret := make([]byte, buf.Len()) copy(ret, buf.Bytes()) return ret, nil } func (h *ecdsaPrivateKey) Iterate(ctx context.Context) HeaderIterator { pairs := h.makePairs() ch := make(chan *HeaderPair, len(pairs)) go func(ctx context.Context, ch chan *HeaderPair, pairs []*HeaderPair) { defer close(ch) for _, pair := range pairs { select { case <-ctx.Done(): return case ch <- pair: } } }(ctx, ch, pairs) return mapiter.New(ch) } func (h *ecdsaPrivateKey) Walk(ctx context.Context, visitor HeaderVisitor) error { return iter.WalkMap(ctx, h, visitor) } func (h *ecdsaPrivateKey) AsMap(ctx context.Context) (map[string]interface{}, error) { return iter.AsMap(ctx, h) } golang-github-lestrrat-go-jwx-2.1.4/jwk/es256k.go000066400000000000000000000004171476711647200214760ustar00rootroot00000000000000//go:build jwx_es256k // +build jwx_es256k package jwk import ( "github.com/decred/dcrd/dcrec/secp256k1/v4" "github.com/lestrrat-go/jwx/v2/internal/ecutil" "github.com/lestrrat-go/jwx/v2/jwa" ) func init() { ecutil.RegisterCurve(secp256k1.S256(), jwa.Secp256k1) } golang-github-lestrrat-go-jwx-2.1.4/jwk/es256k_go1.20_test.go000066400000000000000000000024701476711647200235240ustar00rootroot00000000000000//go:build jwx_es256k && jwx_secp256k1_pem && go1.20 package jwk_test import ( "fmt" "testing" "github.com/decred/dcrd/dcrec/secp256k1/v4" "github.com/lestrrat-go/jwx/v2/jwk" "github.com/stretchr/testify/require" ) func TestES256KPem(t *testing.T) { raw, err := secp256k1.GeneratePrivateKey() require.NoError(t, err, `GeneratePrivateKey should succeed`) testcases := []interface{}{raw.ToECDSA(), raw.PubKey().ToECDSA()} for _, tc := range testcases { t.Run(fmt.Sprintf("Marshal %T", tc), func(t *testing.T) { key, err := jwk.FromRaw(tc) require.NoError(t, err, `FromRaw should succeed`) pem, err := jwk.Pem(key) require.NoError(t, err, `Pem should succeed`) require.NotEmpty(t, pem, `Pem should not be empty`) parsed, err := jwk.Parse(pem, jwk.WithPEM(true)) require.NoError(t, err, `Parse should succeed`) _ = parsed }) } t.Run("ParsePKCS8PrivateKey", func(t *testing.T) { const src = `-----BEGIN PRIVATE KEY----- MIGEAgEAMBAGByqGSM49AgEGBSuBBAAKBG0wawIBAQQggS9t6iYyj9JSL+btkMEq pMYitWV4X+/Jg9zu3L8Ob5ShRANCAAT/YrxWHfw3e8lfDncJLLkPRbdby0L4qT95 vyWU5lPpSwRbEAfSFR1E5RD9irkN1mCY8D1ko1PAlmHVB78pNzq4 -----END PRIVATE KEY-----` key, err := jwk.Parse([]byte(src), jwk.WithPEM(true)) require.NoError(t, err, `Parse should succeed`) require.NotNil(t, key, `key should not be nil`) }) } golang-github-lestrrat-go-jwx-2.1.4/jwk/es256k_test.go000066400000000000000000000030141476711647200225310ustar00rootroot00000000000000//go:build jwx_es256k // +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/v2/internal/ecutil" "github.com/lestrrat-go/jwx/v2/jwa" "github.com/lestrrat-go/jwx/v2/jwk" "github.com/stretchr/testify/require" ) func TestES256K(t *testing.T) { require.True(t, ecutil.IsAvailable(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]interface{}{ "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.FromRaw", 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.FromRaw(&raw) if err != nil { panic(err) } _ = key } }) } golang-github-lestrrat-go-jwx-2.1.4/jwk/fetch.go000066400000000000000000000101111476711647200215400ustar00rootroot00000000000000package jwk import ( "context" "fmt" "io" "math" "os" "strconv" "sync" "sync/atomic" "github.com/lestrrat-go/httprc" ) type Fetcher interface { Fetch(context.Context, string, ...FetchOption) (Set, error) } type FetchFunc func(context.Context, string, ...FetchOption) (Set, error) func (f FetchFunc) Fetch(ctx context.Context, u string, options ...FetchOption) (Set, error) { return f(ctx, u, options...) } var globalFetcher httprc.Fetcher var muGlobalFetcher sync.Mutex var fetcherChanged uint32 func init() { atomic.StoreUint32(&fetcherChanged, 1) } func getGlobalFetcher() httprc.Fetcher { if v := atomic.LoadUint32(&fetcherChanged); v == 0 { return globalFetcher } muGlobalFetcher.Lock() defer muGlobalFetcher.Unlock() if globalFetcher == nil { var nworkers int v := os.Getenv(`JWK_FETCHER_WORKER_COUNT`) if c, err := strconv.ParseInt(v, 10, 64); err == nil { if c > math.MaxInt { nworkers = math.MaxInt } else { nworkers = int(c) } } if nworkers < 1 { nworkers = 3 } globalFetcher = httprc.NewFetcher(context.Background(), httprc.WithFetcherWorkerCount(nworkers)) } atomic.StoreUint32(&fetcherChanged, 0) return globalFetcher } // SetGlobalFetcher allows users to specify a custom global fetcher, // which is used by the `Fetch` function. Assigning `nil` forces // the default fetcher to be (re)created when the next call to // `jwk.Fetch` occurs // // You only need to call this function when you want to // either change the fetching behavior (for example, you want to change // how the default whitelist is handled), or when you want to control // the lifetime of the global fetcher, for example for tests // that require a clean shutdown. // // If you do use this function to set a custom fetcher, and you // control its termination, make sure that you call `jwk.SetGlobalFetcher()` // one more time (possibly with `nil`) to assign a valid fetcher. // Otherwise, once the fetcher is invalidated, subsequent calls to `jwk.Fetch` // may hang, causing very hard to debug problems. // // If you are sure you no longer need `jwk.Fetch` after terminating the // fetcher, then you the above caution is not necessary. func SetGlobalFetcher(f httprc.Fetcher) { muGlobalFetcher.Lock() globalFetcher = f muGlobalFetcher.Unlock() atomic.StoreUint32(&fetcherChanged, 1) } // Fetch fetches a JWK resource specified by a URL. The url must be // pointing to a resource that is supported by `net/http`. // // 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. // // Please note that underneath the `jwk.Fetch` function, it uses a global // object that spawns goroutines that are present until the go runtime // exits. Initially this global variable is uninitialized, but upon // calling `jwk.Fetch` once, it is initialized and goroutines are spawned. // If you want to control the lifetime of these goroutines, you can // call `jwk.SetGlobalFetcher` with a custom fetcher which is tied to // a `context.Context` object that you can control. func Fetch(ctx context.Context, u string, options ...FetchOption) (Set, error) { var hrfopts []httprc.FetchOption var parseOptions []ParseOption for _, option := range options { if parseOpt, ok := option.(ParseOption); ok { parseOptions = append(parseOptions, parseOpt) continue } //nolint:forcetypeassert switch option.Ident() { case identHTTPClient{}: hrfopts = append(hrfopts, httprc.WithHTTPClient(option.Value().(HTTPClient))) case identFetchWhitelist{}: hrfopts = append(hrfopts, httprc.WithWhitelist(option.Value().(httprc.Whitelist))) } } res, err := getGlobalFetcher().Fetch(ctx, u, hrfopts...) if err != nil { return nil, fmt.Errorf(`failed to fetch %q: %w`, u, err) } buf, err := io.ReadAll(res.Body) defer res.Body.Close() if err != nil { return nil, fmt.Errorf(`failed to read response body for %q: %w`, u, err) } return Parse(buf, parseOptions...) } golang-github-lestrrat-go-jwx-2.1.4/jwk/headers_test.go000066400000000000000000000062421476711647200231330ustar00rootroot00000000000000package jwk_test import ( "context" "testing" "github.com/lestrrat-go/jwx/v2/jwa" "github.com/lestrrat-go/jwx/v2/jwk" "github.com/stretchr/testify/assert" ) func TestHeader(t *testing.T) { t.Parallel() t.Run("Roundtrip", func(t *testing.T) { t.Parallel() values := map[string]interface{}{ 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.FromRaw([]byte("dummy")) if !assert.NoError(t, err, `jwk.New should succeed`) { return } for k, v := range values { if !assert.NoError(t, h.Set(k, v), "Set works for '%s'", k) { return } got, ok := h.Get(k) if !assert.True(t, ok, "Get works for '%s'", k) { return } if !assert.Equal(t, v, got, "values match '%s'", k) { return } if !assert.NoError(t, h.Set(k, v), "Set works for '%s'", k) { return } } t.Run("Private params", func(t *testing.T) { t.Parallel() pp, err := h.AsMap(context.Background()) if !assert.NoError(t, err, `h.AsMap should succeed`) { return } v, ok := pp["private"] if !assert.True(t, ok, "key 'private' should exists") { return } if !assert.Equal(t, v, "boofoo", "value for 'private' should match") { return } }) }) t.Run("RoundtripError", func(t *testing.T) { t.Parallel() type dummyStruct struct { dummy1 int dummy2 float64 } dummy := &dummyStruct{1, 3.4} values := map[string]interface{}{ 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.FromRaw([]byte("dummy")) if !assert.NoError(t, err, `jwk.New should succeed`) { return } for k, v := range values { err := h.Set(k, v) if err == nil { t.Fatalf("Setting %s value should have failed", k) } } if !assert.NoError(t, h.Set("Default", dummy), `Setting "Default" should succeed`) { return } if !assert.Empty(t, h.Algorithm().String(), "Algorithm should be empty string") { return } if h.KeyID() != "" { t.Fatalf("KeyID should be empty string") } if h.KeyUsage() != "" { t.Fatalf("KeyUsage should be empty string") } if h.KeyOps() != nil { t.Fatalf("KeyOps should be empty string") } }) t.Run("Algorithm", func(t *testing.T) { t.Parallel() h, err := jwk.FromRaw([]byte("dummy")) if !assert.NoError(t, err, `jwk.New should succeed`) { return } for _, value := range []interface{}{jwa.RS256, jwa.RSA1_5} { if !assert.NoError(t, h.Set(jwk.AlgorithmKey, value), "Set for alg should succeed") { return } got, ok := h.Get("alg") if !assert.True(t, ok, "Get for alg should succeed") { return } if !assert.Equal(t, value, got, "values match") { return } } }) } golang-github-lestrrat-go-jwx-2.1.4/jwk/interface.go000066400000000000000000000120771476711647200224240ustar00rootroot00000000000000package jwk import ( "context" "sync" "github.com/lestrrat-go/iter/arrayiter" "github.com/lestrrat-go/iter/mapiter" "github.com/lestrrat-go/jwx/v2/internal/iter" "github.com/lestrrat-go/jwx/v2/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) (interface{}, bool) // Set sets the value of a single field. // // This method, which takes an `interface{}`, 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, interface{}) 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, use `Iterate()` 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 creates an iterator to iterate through all keys in the set. Keys(context.Context) KeyIterator // Iterate creates an iterator to iterate through all fields other than the keys Iterate(context.Context) HeaderIterator // 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]interface{} } type HeaderVisitor = iter.MapVisitor type HeaderVisitorFunc = iter.MapVisitorFunc type HeaderPair = mapiter.Pair type HeaderIterator = mapiter.Iterator type KeyPair = arrayiter.Pair type KeyIterator = arrayiter.Iterator 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 } golang-github-lestrrat-go-jwx-2.1.4/jwk/interface_gen.go000066400000000000000000000120721476711647200232500ustar00rootroot00000000000000// Code generated by tools/cmd/genjwk/main.go. DO NOT EDIT. package jwk import ( "context" "crypto" "github.com/lestrrat-go/jwx/v2/cert" "github.com/lestrrat-go/jwx/v2/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 types, so you should use type assertions // to perform more specific tasks with each key type Key interface { // Get returns the value of a single field. The second boolean return value // will be false if the field is not stored in the source // // This method, which returns an `interface{}`, 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 Get(string) (interface{}, bool) // 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 `interface{}`, 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, interface{}) 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 // Raw creates the corresponding raw key. For example, // EC types would create *ecdsa.PublicKey or *ecdsa.PrivateKey, // and OctetSeq types create a []byte 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 first 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 (e.g. &rsa.PrivateKey) // for efficiency. Raw(interface{}) error // Thumbprint returns the JWK thumbprint using the indicated // hashing algorithm, according to RFC 7638 Thumbprint(crypto.Hash) ([]byte, error) // Iterate returns an iterator that returns all keys and values. // See github.com/lestrrat-go/iter for a description of the iterator. Iterate(ctx context.Context) HeaderIterator // Walk is a utility tool that allows a visitor to iterate all keys and values Walk(context.Context, HeaderVisitor) error // AsMap is a utility tool that returns a new map that contains the same fields as the source AsMap(context.Context) (map[string]interface{}, error) // PrivateParams returns the non-standard elements in the source structure // WARNING: DO NOT USE PrivateParams() IF YOU HAVE CONCURRENT CODE ACCESSING THEM. // Use `AsMap()` to get a copy of the entire header, or use `Iterate()` instead PrivateParams() map[string]interface{} // 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 // KeyOps returns `key_ops` of a JWK KeyOps() KeyOperationList // Algorithm returns `alg` of a JWK // Algorithm returns the value of the `alg` field // // This field may contain either `jwk.SignatureAlgorithm` or `jwk.KeyEncryptionAlgorithm`. // This is why there exists a `jwa.KeyAlgorithm` type that encompasses both types. Algorithm() jwa.KeyAlgorithm // KeyID returns `kid` of a JWK KeyID() string // X509URL returns `x5u` of a JWK X509URL() string // X509CertChain returns `x5c` of a JWK X509CertChain() *cert.Chain // X509CertThumbprint returns `x5t` of a JWK X509CertThumbprint() string // X509CertThumbprintS256 returns `x5t#S256` of a JWK X509CertThumbprintS256() string makePairs() []*HeaderPair } golang-github-lestrrat-go-jwx-2.1.4/jwk/internal/000077500000000000000000000000001476711647200217425ustar00rootroot00000000000000golang-github-lestrrat-go-jwx-2.1.4/jwk/internal/x509/000077500000000000000000000000001476711647200224475ustar00rootroot00000000000000golang-github-lestrrat-go-jwx-2.1.4/jwk/internal/x509/BUILD.bazel000066400000000000000000000006021476711647200243230ustar00rootroot00000000000000load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") go_library( name = "x509", srcs = [ "x509.go", "x509_nosecp256k1.go", "x509_sepc256k1.go" ], importpath = "github.com/lestrrat-go/jwx/v2/jwk/internal/x509", visibility = ["//:__subpackages__"], ) alias( name = "go_default_library", actual = ":x509", visibility = ["//jwe:__subpackages__"], ) golang-github-lestrrat-go-jwx-2.1.4/jwk/internal/x509/x509.go000066400000000000000000000014611476711647200235050ustar00rootroot00000000000000package x509 import ( "crypto/rsa" "crypto/x509" ) // In this x509 package we provide a proxy for crypto/x509 methods, // so that we can easily swap out the ParseECPrivateKey method with // our version of it that recognizes the secp256k1 curve... // _IF_ the jwx_es256k build tag is set. func MarshalPKCS1PrivateKey(priv *rsa.PrivateKey) []byte { return x509.MarshalPKCS1PrivateKey(priv) } func MarshalPKCS8PrivateKey(priv interface{}) ([]byte, error) { return x509.MarshalPKCS8PrivateKey(priv) } func ParsePKCS1PrivateKey(der []byte) (*rsa.PrivateKey, error) { return x509.ParsePKCS1PrivateKey(der) } func ParsePKCS1PublicKey(der []byte) (*rsa.PublicKey, error) { return x509.ParsePKCS1PublicKey(der) } func ParseCertificate(der []byte) (*x509.Certificate, error) { return x509.ParseCertificate(der) } golang-github-lestrrat-go-jwx-2.1.4/jwk/internal/x509/x509_nosecp256k1.go000066400000000000000000000011461476711647200255450ustar00rootroot00000000000000//go:build !jwx_es256k || !jwx_secp256k1_pem || !go1.20 package x509 import ( "crypto/ecdsa" "crypto/x509" ) func MarshalECPrivateKey(priv *ecdsa.PrivateKey) ([]byte, error) { return x509.MarshalECPrivateKey(priv) } func ParseECPrivateKey(der []byte) (*ecdsa.PrivateKey, error) { return x509.ParseECPrivateKey(der) } func MarshalPKIXPublicKey(pub any) ([]byte, error) { return x509.MarshalPKIXPublicKey(pub) } func ParsePKIXPublicKey(der []byte) (any, error) { return x509.ParsePKIXPublicKey(der) } func ParsePKCS8PrivateKey(der []byte) (interface{}, error) { return x509.ParsePKCS8PrivateKey(der) } golang-github-lestrrat-go-jwx-2.1.4/jwk/internal/x509/x509_sepc256k1.go000066400000000000000000000354251476711647200252170ustar00rootroot00000000000000//go:build jwx_es256k && jwx_secp256k1_pem && go1.20 package x509 import ( "bytes" "crypto/dsa" "crypto/ecdh" "crypto/ecdsa" "crypto/ed25519" "crypto/elliptic" "crypto/rsa" "crypto/x509/pkix" "encoding/asn1" "errors" "fmt" "math/big" "github.com/decred/dcrd/dcrec/secp256k1/v4" "golang.org/x/crypto/cryptobyte" cryptobyte_asn1 "golang.org/x/crypto/cryptobyte/asn1" ) // See src/crypto/x509/sec1.go for original code const ecPrivKeyVersion = 1 // See src/crypto/x509/x509.go for original code type publicKeyInfo struct { Raw asn1.RawContent Algorithm pkix.AlgorithmIdentifier PublicKey asn1.BitString } // See src/crypto/x509/pkcs1.go for original code type pkcs1PrivateKey struct { Version int N *big.Int E int D *big.Int P *big.Int Q *big.Int // We ignore these values, if present, because rsa will calculate them. Dp *big.Int `asn1:"optional"` Dq *big.Int `asn1:"optional"` Qinv *big.Int `asn1:"optional"` AdditionalPrimes []pkcs1AdditionalRSAPrime `asn1:"optional,omitempty"` } // See src/crypto/x509/pkcs1.go for original code type pkcs1AdditionalRSAPrime struct { Prime *big.Int // We ignore these values because rsa will calculate them. Exp *big.Int Coeff *big.Int } // See src/crypto/x509/pkcs1.go for original code type pkcs1PublicKey struct { N *big.Int E int } // See src/crypto/x509/pkcs8.go for original code type pkcs8 struct { Version int Algo pkix.AlgorithmIdentifier PrivateKey []byte // optional attributes omitted. } // See src/crypto/x509/x509.go for original code type pkixPublicKey struct { Algo pkix.AlgorithmIdentifier BitString asn1.BitString } type ecPrivateKey struct { Version int PrivateKey []byte NamedCurveOID asn1.ObjectIdentifier `asn1:"optional,explicit,tag:0"` PublicKey asn1.BitString `asn1:"optional,explicit,tag:1"` } var ( // See src/crypto/x509/x509.go for original code oidNamedCurveP224 = asn1.ObjectIdentifier{1, 3, 132, 0, 33} oidNamedCurveP256 = asn1.ObjectIdentifier{1, 2, 840, 10045, 3, 1, 7} oidNamedCurveP384 = asn1.ObjectIdentifier{1, 3, 132, 0, 34} oidNamedCurveP521 = asn1.ObjectIdentifier{1, 3, 132, 0, 35} oidPublicKeyRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 1} oidPublicKeyDSA = asn1.ObjectIdentifier{1, 2, 840, 10040, 4, 1} oidPublicKeyECDSA = asn1.ObjectIdentifier{1, 2, 840, 10045, 2, 1} oidPublicKeyX25519 = asn1.ObjectIdentifier{1, 3, 101, 110} oidPublicKeyEd25519 = asn1.ObjectIdentifier{1, 3, 101, 112} // Added for this package oidNamedCurveSecp256K1 = asn1.ObjectIdentifier{1, 3, 132, 0, 10} ) // See src/crypto/x509/x509.go for original code func namedCurveFromOID(oid asn1.ObjectIdentifier) elliptic.Curve { switch { case oid.Equal(oidNamedCurveP224): return elliptic.P224() case oid.Equal(oidNamedCurveP256): return elliptic.P256() case oid.Equal(oidNamedCurveP384): return elliptic.P384() case oid.Equal(oidNamedCurveP521): return elliptic.P521() case oid.Equal(oidNamedCurveSecp256K1): // Added for this package return secp256k1.S256() } return nil } // See src/crypto/x509/x509.go for original code func oidFromNamedCurve(curve elliptic.Curve) (asn1.ObjectIdentifier, bool) { switch curve { case elliptic.P224(): return oidNamedCurveP224, true case elliptic.P256(): return oidNamedCurveP256, true case elliptic.P384(): return oidNamedCurveP384, true case elliptic.P521(): return oidNamedCurveP521, true case secp256k1.S256(): return oidNamedCurveSecp256K1, true } return nil, false } // See crypto/x509/x509.go for original code func oidFromECDHCurve(curve ecdh.Curve) (asn1.ObjectIdentifier, bool) { switch curve { case ecdh.X25519(): return oidPublicKeyX25519, true case ecdh.P256(): return oidNamedCurveP256, true case ecdh.P384(): return oidNamedCurveP384, true case ecdh.P521(): return oidNamedCurveP521, true } return nil, false } func MarshalECPrivateKey(key *ecdsa.PrivateKey) ([]byte, error) { // See src/crypto/x509/sec1.go for original code oid, ok := oidFromNamedCurve(key.Curve) if !ok { return nil, errors.New("x509: unknown elliptic curve") } privateKey := make([]byte, (key.Curve.Params().N.BitLen()+7)/8) return asn1.Marshal(ecPrivateKey{ Version: 1, PrivateKey: key.D.FillBytes(privateKey), NamedCurveOID: oid, PublicKey: asn1.BitString{Bytes: elliptic.Marshal(key.Curve, key.X, key.Y)}, }) } func ParseECPrivateKey(der []byte) (*ecdsa.PrivateKey, error) { return parseECPrivateKey(nil, der) } func parseECPrivateKey(namedCurveOID *asn1.ObjectIdentifier, der []byte) (*ecdsa.PrivateKey, error) { var privKey ecPrivateKey if _, err := asn1.Unmarshal(der, &privKey); err != nil { if _, err := asn1.Unmarshal(der, &pkcs8{}); err == nil { return nil, errors.New("x509: failed to parse private key (use ParsePKCS8PrivateKey instead for this key format)") } if _, err := asn1.Unmarshal(der, &pkcs1PrivateKey{}); err == nil { return nil, errors.New("x509: failed to parse private key (use ParsePKCS1PrivateKey instead for this key format)") } return nil, errors.New("x509: failed to parse EC private key: " + err.Error()) } if privKey.Version != ecPrivKeyVersion { return nil, fmt.Errorf("x509: unknown EC private key version %d", privKey.Version) } var curve elliptic.Curve if namedCurveOID != nil { curve = namedCurveFromOID(*namedCurveOID) } else { curve = namedCurveFromOID(privKey.NamedCurveOID) } if curve == nil { return nil, errors.New("x509: unknown elliptic curve") } k := new(big.Int).SetBytes(privKey.PrivateKey) curveOrder := curve.Params().N if k.Cmp(curveOrder) >= 0 { return nil, errors.New("x509: invalid elliptic curve private key value") } priv := new(ecdsa.PrivateKey) priv.Curve = curve priv.D = k privateKey := make([]byte, (curveOrder.BitLen()+7)/8) // Some private keys have leading zero padding. This is invalid // according to [SEC1], but this code will ignore it. for len(privKey.PrivateKey) > len(privateKey) { if privKey.PrivateKey[0] != 0 { return nil, errors.New("x509: invalid private key length") } privKey.PrivateKey = privKey.PrivateKey[1:] } // Some private keys remove all leading zeros, this is also invalid // according to [SEC1] but since OpenSSL used to do this, we ignore // this too. copy(privateKey[len(privateKey)-len(privKey.PrivateKey):], privKey.PrivateKey) priv.X, priv.Y = curve.ScalarBaseMult(privateKey) return priv, nil } // See src/crypto/x509/x509.go for original code func marshalPublicKey(pub any) (publicKeyBytes []byte, publicKeyAlgorithm pkix.AlgorithmIdentifier, err error) { switch pub := pub.(type) { case *rsa.PublicKey: publicKeyBytes, err = asn1.Marshal(pkcs1PublicKey{ N: pub.N, E: pub.E, }) if err != nil { return nil, pkix.AlgorithmIdentifier{}, err } publicKeyAlgorithm.Algorithm = oidPublicKeyRSA // This is a NULL parameters value which is required by // RFC 3279, Section 2.3.1. publicKeyAlgorithm.Parameters = asn1.NullRawValue case *ecdsa.PublicKey: oid, ok := oidFromNamedCurve(pub.Curve) if !ok { return nil, pkix.AlgorithmIdentifier{}, errors.New("x509: unsupported elliptic curve") } if !pub.Curve.IsOnCurve(pub.X, pub.Y) { return nil, pkix.AlgorithmIdentifier{}, errors.New("x509: invalid elliptic curve public key") } publicKeyBytes = elliptic.Marshal(pub.Curve, pub.X, pub.Y) publicKeyAlgorithm.Algorithm = oidPublicKeyECDSA var paramBytes []byte paramBytes, err = asn1.Marshal(oid) if err != nil { return } publicKeyAlgorithm.Parameters.FullBytes = paramBytes case ed25519.PublicKey: publicKeyBytes = pub publicKeyAlgorithm.Algorithm = oidPublicKeyEd25519 case *ecdh.PublicKey: publicKeyBytes = pub.Bytes() if pub.Curve() == ecdh.X25519() { publicKeyAlgorithm.Algorithm = oidPublicKeyX25519 } else { oid, ok := oidFromECDHCurve(pub.Curve()) if !ok { return nil, pkix.AlgorithmIdentifier{}, errors.New("x509: unsupported elliptic curve") } publicKeyAlgorithm.Algorithm = oidPublicKeyECDSA var paramBytes []byte paramBytes, err = asn1.Marshal(oid) if err != nil { return } publicKeyAlgorithm.Parameters.FullBytes = paramBytes } default: return nil, pkix.AlgorithmIdentifier{}, fmt.Errorf("x509: unsupported public key type: %T", pub) } return publicKeyBytes, publicKeyAlgorithm, nil } // See src/crypto/x509/x509.go for original code func MarshalPKIXPublicKey(pub any) ([]byte, error) { var publicKeyBytes []byte var publicKeyAlgorithm pkix.AlgorithmIdentifier var err error if publicKeyBytes, publicKeyAlgorithm, err = marshalPublicKey(pub); err != nil { return nil, err } pkix := pkixPublicKey{ Algo: publicKeyAlgorithm, BitString: asn1.BitString{ Bytes: publicKeyBytes, BitLength: 8 * len(publicKeyBytes), }, } ret, _ := asn1.Marshal(pkix) return ret, nil } func ParsePKIXPublicKey(derBytes []byte) (pub any, err error) { var pki publicKeyInfo if rest, err := asn1.Unmarshal(derBytes, &pki); err != nil { if _, err := asn1.Unmarshal(derBytes, &pkcs1PublicKey{}); err == nil { return nil, errors.New("x509: failed to parse public key (use ParsePKCS1PublicKey instead for this key format)") } return nil, err } else if len(rest) != 0 { return nil, errors.New("x509: trailing data after ASN.1 of public-key") } return parsePublicKey(&pki) } func parsePublicKey(keyData *publicKeyInfo) (any, error) { oid := keyData.Algorithm.Algorithm params := keyData.Algorithm.Parameters der := cryptobyte.String(keyData.PublicKey.RightAlign()) switch { case oid.Equal(oidPublicKeyRSA): // RSA public keys must have a NULL in the parameters. // See RFC 3279, Section 2.3.1. if !bytes.Equal(params.FullBytes, asn1.NullBytes) { return nil, errors.New("x509: RSA key missing NULL parameters") } p := &pkcs1PublicKey{N: new(big.Int)} if !der.ReadASN1(&der, cryptobyte_asn1.SEQUENCE) { return nil, errors.New("x509: invalid RSA public key") } if !der.ReadASN1Integer(p.N) { return nil, errors.New("x509: invalid RSA modulus") } if !der.ReadASN1Integer(&p.E) { return nil, errors.New("x509: invalid RSA public exponent") } if p.N.Sign() <= 0 { return nil, errors.New("x509: RSA modulus is not a positive number") } if p.E <= 0 { return nil, errors.New("x509: RSA public exponent is not a positive number") } pub := &rsa.PublicKey{ E: p.E, N: p.N, } return pub, nil case oid.Equal(oidPublicKeyECDSA): paramsDer := cryptobyte.String(params.FullBytes) namedCurveOID := new(asn1.ObjectIdentifier) if !paramsDer.ReadASN1ObjectIdentifier(namedCurveOID) { return nil, errors.New("x509: invalid ECDSA parameters") } namedCurve := namedCurveFromOID(*namedCurveOID) if namedCurve == nil { return nil, errors.New("x509: unsupported elliptic curve") } x, y := elliptic.Unmarshal(namedCurve, der) if x == nil { return nil, errors.New("x509: failed to unmarshal elliptic curve point") } pub := &ecdsa.PublicKey{ Curve: namedCurve, X: x, Y: y, } return pub, nil case oid.Equal(oidPublicKeyEd25519): // RFC 8410, Section 3 // > For all of the OIDs, the parameters MUST be absent. if len(params.FullBytes) != 0 { return nil, errors.New("x509: Ed25519 key encoded with illegal parameters") } if len(der) != ed25519.PublicKeySize { return nil, errors.New("x509: wrong Ed25519 public key size") } return ed25519.PublicKey(der), nil case oid.Equal(oidPublicKeyX25519): // RFC 8410, Section 3 // > For all of the OIDs, the parameters MUST be absent. if len(params.FullBytes) != 0 { return nil, errors.New("x509: X25519 key encoded with illegal parameters") } return ecdh.X25519().NewPublicKey(der) case oid.Equal(oidPublicKeyDSA): y := new(big.Int) if !der.ReadASN1Integer(y) { return nil, errors.New("x509: invalid DSA public key") } pub := &dsa.PublicKey{ Y: y, Parameters: dsa.Parameters{ P: new(big.Int), Q: new(big.Int), G: new(big.Int), }, } paramsDer := cryptobyte.String(params.FullBytes) if !paramsDer.ReadASN1(¶msDer, cryptobyte_asn1.SEQUENCE) || !paramsDer.ReadASN1Integer(pub.Parameters.P) || !paramsDer.ReadASN1Integer(pub.Parameters.Q) || !paramsDer.ReadASN1Integer(pub.Parameters.G) { return nil, errors.New("x509: invalid DSA parameters") } if pub.Y.Sign() <= 0 || pub.Parameters.P.Sign() <= 0 || pub.Parameters.Q.Sign() <= 0 || pub.Parameters.G.Sign() <= 0 { return nil, errors.New("x509: zero or negative DSA parameter") } return pub, nil default: return nil, errors.New("x509: unknown public key algorithm") } } // See src/crypto/x509/pkcs8.go for original code func ParsePKCS8PrivateKey(der []byte) (key any, err error) { var privKey pkcs8 if _, err := asn1.Unmarshal(der, &privKey); err != nil { if _, err := asn1.Unmarshal(der, &ecPrivateKey{}); err == nil { return nil, errors.New("x509: failed to parse private key (use ParseECPrivateKey instead for this key format)") } if _, err := asn1.Unmarshal(der, &pkcs1PrivateKey{}); err == nil { return nil, errors.New("x509: failed to parse private key (use ParsePKCS1PrivateKey instead for this key format)") } return nil, err } switch { case privKey.Algo.Algorithm.Equal(oidPublicKeyRSA): key, err = ParsePKCS1PrivateKey(privKey.PrivateKey) if err != nil { return nil, errors.New("x509: failed to parse RSA private key embedded in PKCS#8: " + err.Error()) } return key, nil case privKey.Algo.Algorithm.Equal(oidPublicKeyECDSA): bytes := privKey.Algo.Parameters.FullBytes namedCurveOID := new(asn1.ObjectIdentifier) if _, err := asn1.Unmarshal(bytes, namedCurveOID); err != nil { namedCurveOID = nil } key, err = parseECPrivateKey(namedCurveOID, privKey.PrivateKey) if err != nil { return nil, errors.New("x509: failed to parse EC private key embedded in PKCS#8: " + err.Error()) } return key, nil case privKey.Algo.Algorithm.Equal(oidPublicKeyEd25519): if l := len(privKey.Algo.Parameters.FullBytes); l != 0 { return nil, errors.New("x509: invalid Ed25519 private key parameters") } var curvePrivateKey []byte if _, err := asn1.Unmarshal(privKey.PrivateKey, &curvePrivateKey); err != nil { return nil, fmt.Errorf("x509: invalid Ed25519 private key: %v", err) } if l := len(curvePrivateKey); l != ed25519.SeedSize { return nil, fmt.Errorf("x509: invalid Ed25519 private key length: %d", l) } return ed25519.NewKeyFromSeed(curvePrivateKey), nil case privKey.Algo.Algorithm.Equal(oidPublicKeyX25519): if l := len(privKey.Algo.Parameters.FullBytes); l != 0 { return nil, errors.New("x509: invalid X25519 private key parameters") } var curvePrivateKey []byte if _, err := asn1.Unmarshal(privKey.PrivateKey, &curvePrivateKey); err != nil { return nil, fmt.Errorf("x509: invalid X25519 private key: %v", err) } return ecdh.X25519().NewPrivateKey(curvePrivateKey) default: return nil, fmt.Errorf("x509: PKCS#8 wrapping contained private key with unknown algorithm: %v", privKey.Algo.Algorithm) } } golang-github-lestrrat-go-jwx-2.1.4/jwk/io.go000066400000000000000000000012771476711647200210730ustar00rootroot00000000000000// Code generated by tools/cmd/genreadfile/main.go. DO NOT EDIT. package jwk import ( "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{}: srcFS = option.Value().(fs.FS) } } f, err := srcFS.Open(path) if err != nil { return nil, err } defer f.Close() return ParseReader(f, parseOptions...) } golang-github-lestrrat-go-jwx-2.1.4/jwk/jwk.go000066400000000000000000000554471476711647200212670ustar00rootroot00000000000000//go:generate ../tools/cmd/genjwk.sh // Package jwk implements JWK as described in https://tools.ietf.org/html/rfc7517 package jwk import ( "bytes" "crypto" "crypto/ecdsa" "crypto/ed25519" "crypto/elliptic" "crypto/rsa" "encoding/pem" "errors" "fmt" "io" "math/big" "github.com/lestrrat-go/jwx/v2/internal/base64" "github.com/lestrrat-go/jwx/v2/internal/ecutil" "github.com/lestrrat-go/jwx/v2/internal/json" "github.com/lestrrat-go/jwx/v2/jwa" "github.com/lestrrat-go/jwx/v2/jwk/internal/x509" "github.com/lestrrat-go/jwx/v2/x25519" ) 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 } // FromRaw 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 // - []byte creates a symmetric key func FromRaw(key interface{}) (Key, error) { if key == nil { return nil, fmt.Errorf(`jwk.FromRaw requires a non-nil key`) } var ptr interface{} switch v := key.(type) { case rsa.PrivateKey: ptr = &v case rsa.PublicKey: ptr = &v case ecdsa.PrivateKey: ptr = &v case ecdsa.PublicKey: ptr = &v default: ptr = v } switch rawKey := ptr.(type) { case *rsa.PrivateKey: k := newRSAPrivateKey() if err := k.FromRaw(rawKey); err != nil { return nil, fmt.Errorf(`failed to initialize %T from %T: %w`, k, rawKey, err) } return k, nil case *rsa.PublicKey: k := newRSAPublicKey() if err := k.FromRaw(rawKey); err != nil { return nil, fmt.Errorf(`failed to initialize %T from %T: %w`, k, rawKey, err) } return k, nil case *ecdsa.PrivateKey: k := newECDSAPrivateKey() if err := k.FromRaw(rawKey); err != nil { return nil, fmt.Errorf(`failed to initialize %T from %T: %w`, k, rawKey, err) } return k, nil case *ecdsa.PublicKey: k := newECDSAPublicKey() if err := k.FromRaw(rawKey); err != nil { return nil, fmt.Errorf(`failed to initialize %T from %T: %w`, k, rawKey, err) } return k, nil case ed25519.PrivateKey: k := newOKPPrivateKey() if err := k.FromRaw(rawKey); err != nil { return nil, fmt.Errorf(`failed to initialize %T from %T: %w`, k, rawKey, err) } return k, nil case ed25519.PublicKey: k := newOKPPublicKey() if err := k.FromRaw(rawKey); err != nil { return nil, fmt.Errorf(`failed to initialize %T from %T: %w`, k, rawKey, err) } return k, nil case x25519.PrivateKey: k := newOKPPrivateKey() if err := k.FromRaw(rawKey); err != nil { return nil, fmt.Errorf(`failed to initialize %T from %T: %w`, k, rawKey, err) } return k, nil case x25519.PublicKey: k := newOKPPublicKey() if err := k.FromRaw(rawKey); err != nil { return nil, fmt.Errorf(`failed to initialize %T from %T: %w`, k, rawKey, err) } return k, nil case []byte: k := newSymmetricKey() if err := k.FromRaw(rawKey); err != nil { return nil, fmt.Errorf(`failed to initialize %T from %T: %w`, k, rawKey, err) } return k, nil default: return nil, fmt.Errorf(`invalid key type '%T' for jwk.New`, key) } } // 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 := 0; i < n; i++ { 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 interface{}) (Key, error) { // This should catch all jwk.Key instances if pk, ok := v.(PublicKeyer); ok { return pk.PublicKey() } jk, err := FromRaw(v) if err != nil { return nil, fmt.Errorf(`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. func PublicRawKeyOf(v interface{}) (interface{}, error) { if pk, ok := v.(PublicKeyer); ok { pubk, err := pk.PublicKey() if err != nil { return nil, fmt.Errorf(`failed to obtain public key from %T: %w`, v, err) } var raw interface{} if err := pubk.Raw(&raw); err != nil { return nil, fmt.Errorf(`failed to obtain raw key from %T: %w`, pubk, err) } return raw, nil } // This may be a silly idea, but if the user gave us a non-pointer value... var ptr interface{} switch v := v.(type) { case rsa.PrivateKey: ptr = &v case rsa.PublicKey: ptr = &v case ecdsa.PrivateKey: ptr = &v case ecdsa.PublicKey: ptr = &v default: ptr = v } switch x := ptr.(type) { case *rsa.PrivateKey: return &x.PublicKey, nil case *rsa.PublicKey: return x, nil case *ecdsa.PrivateKey: return &x.PublicKey, nil case *ecdsa.PublicKey: return x, nil case ed25519.PrivateKey: return x.Public(), nil case ed25519.PublicKey: return x, nil case x25519.PrivateKey: return x.Public(), nil case x25519.PublicKey: return x, nil case []byte: return x, nil default: return nil, fmt.Errorf(`invalid key type passed to PublicKeyOf (%T)`, v) } } const ( pmPrivateKey = `PRIVATE KEY` pmPublicKey = `PUBLIC KEY` pmECPrivateKey = `EC PRIVATE KEY` pmRSAPublicKey = `RSA PUBLIC KEY` pmRSAPrivateKey = `RSA PRIVATE KEY` ) // EncodeX509 encodes the key into a byte sequence in ASN.1 DER format // suitable for to be PEM encoded. The key can be a jwk.Key or a raw key // instance, but it must be one of the types supported by `x509` package. // // This function will try to do the right thing depending on the key type // (i.e. switch between `x509.MarshalPKCS1PrivateKey` and `x509.MarshalECPrivateKey`), // but for public keys, it will always use `x509.MarshalPKIXPublicKey`. // Please manually perform the encoding if you need more fine-grained control // // The first return value is the name that can be used for `(pem.Block).Type`. // The second return value is the encoded byte sequence. func EncodeX509(v interface{}) (string, []byte, error) { // we can't import jwk, so just use the interface if key, ok := v.(interface{ Raw(interface{}) error }); ok { var raw interface{} if err := key.Raw(&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 interface{}) ([]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 } // DecodePEM decodes a key in PEM encoded ASN.1 DER format. // and returns a raw key func DecodePEM(src []byte) (interface{}, []byte, error) { block, rest := pem.Decode(src) if block == nil { return nil, nil, fmt.Errorf(`failed to decode PEM data`) } switch block.Type { // Handle the semi-obvious cases case pmRSAPrivateKey: key, err := x509.ParsePKCS1PrivateKey(block.Bytes) if err != nil { return nil, nil, fmt.Errorf(`failed to parse PKCS1 private key: %w`, err) } return key, rest, nil case pmRSAPublicKey: key, err := x509.ParsePKCS1PublicKey(block.Bytes) if err != nil { return nil, nil, fmt.Errorf(`failed to parse PKCS1 public key: %w`, err) } return key, rest, nil case pmECPrivateKey: key, err := x509.ParseECPrivateKey(block.Bytes) if err != nil { return nil, nil, fmt.Errorf(`failed to parse EC private key: %w`, err) } return key, rest, nil case pmPublicKey: // XXX *could* return dsa.PublicKey key, err := x509.ParsePKIXPublicKey(block.Bytes) if err != nil { return nil, nil, fmt.Errorf(`failed to parse PKIX public key: %w`, err) } return key, rest, nil case pmPrivateKey: key, err := x509.ParsePKCS8PrivateKey(block.Bytes) if err != nil { return nil, nil, fmt.Errorf(`failed to parse PKCS8 private key: %w`, err) } return key, rest, nil case "CERTIFICATE": cert, err := x509.ParseCertificate(block.Bytes) if err != nil { return nil, nil, fmt.Errorf(`failed to parse certificate: %w`, err) } return cert.PublicKey, rest, nil default: return nil, nil, fmt.Errorf(`invalid PEM block type %s`, block.Type) } } // 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 interface{}) error { key, err := ParseKey(data) if err != nil { return fmt.Errorf(`failed to parse key: %w`, err) } if err := key.Raw(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 for _, option := range options { //nolint:forcetypeassert switch option.Ident() { case identPEM{}: parsePEM = option.Value().(bool) case identLocalRegistry{}: // in reality you can only pass either withLocalRegistry or // WithTypedField, but since withLocalRegistry is used only by us, // we skip checking localReg = option.Value().(*json.Registry) case identTypedField{}: pair := option.Value().(typedFieldPair) 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 { raw, _, err := DecodePEM(data) if err != nil { return nil, fmt.Errorf(`failed to parse PEM encoded key: %w`, err) } return FromRaw(raw) } var hint struct { Kty string `json:"kty"` D json.RawMessage `json:"d"` } if err := json.Unmarshal(data, &hint); err != nil { return nil, fmt.Errorf(`failed to unmarshal JSON into key hint: %w`, err) } var key Key switch jwa.KeyType(hint.Kty) { case jwa.RSA: if len(hint.D) > 0 { key = newRSAPrivateKey() } else { key = newRSAPublicKey() } case jwa.EC: if len(hint.D) > 0 { key = newECDSAPrivateKey() } else { key = newECDSAPublicKey() } case jwa.OctetSeq: key = newSymmetricKey() case jwa.OKP: if len(hint.D) > 0 { key = newOKPPrivateKey() } else { key = newOKPPublicKey() } default: return nil, fmt.Errorf(`invalid key type from JSON (%s)`, hint.Kty) } if localReg != nil { dcKey, ok := key.(json.DecodeCtxContainer) if !ok { return nil, fmt.Errorf(`typed field was requested, but the key (%T) does not support DecodeCtx`, key) } dc := json.NewDecodeCtx(localReg) dcKey.SetDecodeCtx(dc) defer func() { dcKey.SetDecodeCtx(nil) }() } if err := json.Unmarshal(data, key); err != nil { return nil, fmt.Errorf(`failed to unmarshal JSON into key (%T): %w`, key, err) } return key, nil } // 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 localReg *json.Registry var ignoreParseError bool for _, option := range options { //nolint:forcetypeassert switch option.Ident() { case identPEM{}: parsePEM = option.Value().(bool) case identIgnoreParseError{}: ignoreParseError = option.Value().(bool) case identTypedField{}: pair := option.Value().(typedFieldPair) if localReg == nil { localReg = json.NewRegistry() } localReg.Register(pair.Name, pair.Value) } } s := NewSet() if parsePEM { src = bytes.TrimSpace(src) for len(src) > 0 { raw, rest, err := DecodePEM(src) if err != nil { return nil, fmt.Errorf(`failed to parse PEM encoded key: %w`, err) } key, err := FromRaw(raw) if err != nil { return nil, fmt.Errorf(`failed to create jwk.Key from %T: %w`, raw, err) } if err := s.AddKey(key); err != nil { return nil, fmt.Errorf(`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, fmt.Errorf(`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, fmt.Errorf(`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, fmt.Errorf(`failed to read from io.Reader: %w`, err) } return Parse(buf, options...) } // ParseString parses a JWK set from the incoming string. func ParseString(s string, options ...ParseOption) (Set, error) { return Parse([]byte(s), options...) } // 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 _, ok := key.Get(KeyIDKey); ok { return nil } hash := crypto.SHA256 for _, option := range options { //nolint:forcetypeassert switch option.Ident() { case identThumbprintHash{}: hash = option.Value().(crypto.Hash) } } 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 } 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(`unknown key type %T`, src) } for _, pair := range src.makePairs() { //nolint:forcetypeassert key := pair.Key.(string) if err := dst.Set(key, pair.Value); err != nil { return nil, fmt.Errorf(`failed to set %q: %w`, key, 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 interface{}) ([]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 := 0; i < set.Len(); i++ { 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 := key.Raw(&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 interface{} if err := key.Raw(&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 interface{} if err := key.Raw(&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) } } // 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 // // jwk.RegisterCustomField(`x-birthday`, timeT) // // Then `key.Get("x-birthday")` will still return an `interface{}`, // but you can convert its type to `time.Time` // // bdayif, _ := key.Get(`x-birthday`) // bday := bdayif.(time.Time) func RegisterCustomField(name string, object interface{}) { registry.Register(name, object) } func AvailableCurves() []elliptic.Curve { return ecutil.AvailableCurves() } func CurveForAlgorithm(alg jwa.EllipticCurveAlgorithm) (elliptic.Curve, bool) { return ecutil.CurveForAlgorithm(alg) } // 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) } golang-github-lestrrat-go-jwx-2.1.4/jwk/jwk_internal_test.go000066400000000000000000000261741476711647200242150ustar00rootroot00000000000000package jwk import ( "context" "fmt" "testing" "github.com/lestrrat-go/jwx/v2/cert" "github.com/lestrrat-go/jwx/v2/internal/base64" "github.com/lestrrat-go/jwx/v2/internal/json" "github.com/lestrrat-go/jwx/v2/jwa" "github.com/stretchr/testify/assert" ) 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) { if !assert.NoError(t, key.Set(X509CertChainKey, &c), "Set for x5c should succeed") { return } v, ok := key.Get(X509CertChainKey) if !assert.True(t, ok, "Get for x5c should succeed") { return } gotcerts := v.(*cert.Chain) if !assert.Equal(t, gotcerts.Len(), 3, `should have 3 cert`) { return } }) } } func TestIterator(t *testing.T) { commonValues := map[string]interface{}{ 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]interface{}) { t.Helper() t.Run("Iterate", func(t *testing.T) { seen := make(map[string]interface{}) for iter := v.Iterate(context.TODO()); iter.Next(context.TODO()); { pair := iter.Pair() seen[pair.Key.(string)] = pair.Value getV, ok := v.Get(pair.Key.(string)) if !assert.True(t, ok, `v.Get should succeed for key %#v`, pair.Key) { return } if !assert.Equal(t, pair.Value, getV, `pair.Value should match value from v.Get()`) { return } } if !assert.Equal(t, expected, seen, `values should match`) { return } }) t.Run("Walk", func(t *testing.T) { seen := make(map[string]interface{}) v.Walk(context.TODO(), HeaderVisitorFunc(func(key string, value interface{}) error { seen[key] = value return nil })) if !assert.Equal(t, expected, seen, `values should match`) { return } }) t.Run("AsMap", func(t *testing.T) { seen, err := v.AsMap(context.TODO()) if !assert.NoError(t, err, `v.AsMap should succeed`) { return } if !assert.Equal(t, expected, seen, `values should match`) { return } }) } type iterTestCase struct { Extras map[string]interface{} Func func() Key } testcases := []iterTestCase{ { Extras: map[string]interface{}{ 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]interface{}{ 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]interface{}{ 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]interface{}{ 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]interface{}{ SymmetricOctetsKey: []byte("abcd"), }, Func: func() Key { return newSymmetricKey() }, }, } for _, test := range testcases { key := test.Func() key2 := test.Func() expected := make(map[string]interface{}) expected[KeyTypeKey] = key.KeyType() for k, v := range commonValues { if !assert.NoError(t, key.Set(k, v), `key.Set %#v should succeed`, k) { return } expected[k] = v } for k, v := range test.Extras { if !assert.NoError(t, key.Set(k, v), `key.Set %#v should succeed`, k) { return } 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) if !assert.NoError(t, err, `json.Marshal should succeed`) { return } if !assert.NoError(t, json.Unmarshal(buf, key2), `json.Unmarshal should succeed`) { t.Logf("%s", buf) return } verifyIterators(t, key2, expected) }) } } golang-github-lestrrat-go-jwx-2.1.4/jwk/jwk_test.go000066400000000000000000002205411476711647200223130ustar00rootroot00000000000000package jwk_test import ( "bytes" "context" "crypto" "crypto/ecdsa" "crypto/ed25519" "crypto/rsa" "fmt" "io" "math/big" "net/http" "net/http/httptest" "reflect" "regexp" "strconv" "strings" "testing" "time" "github.com/lestrrat-go/jwx/v2/cert" "github.com/lestrrat-go/jwx/v2/internal/ecutil" "github.com/lestrrat-go/jwx/v2/internal/jose" "github.com/lestrrat-go/jwx/v2/internal/json" "github.com/lestrrat-go/jwx/v2/internal/jwxtest" "github.com/lestrrat-go/jwx/v2/internal/base64" "github.com/lestrrat-go/jwx/v2/jwa" "github.com/lestrrat-go/jwx/v2/jwk" "github.com/lestrrat-go/jwx/v2/x25519" "github.com/stretchr/testify/assert" "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 interface{} Value interface{} Method string } var commonDef map[string]keyDef func init() { certChain = &cert.Chain{} for _, src := range certChainSrc { _ = certChain.AddString(src) } commonDef = map[string]keyDef{ jwk.AlgorithmKey: { Method: "Algorithm", Value: jwa.KeyAlgorithmFrom("random-algorithm"), }, 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/v2", 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]interface{}{} 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) interface{} { 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: switch key.Crv() { case jwa.Ed25519: return ed25519.PrivateKey(nil) case jwa.X25519: return x25519.PrivateKey(nil) default: panic("unknown curve type for OKPPrivateKey:" + key.Crv()) } case jwk.OKPPublicKey: switch key.Crv() { case jwa.Ed25519: return ed25519.PublicKey(nil) case jwa.X25519: return x25519.PublicKey(nil) default: panic("unknown curve type for OKPPublicKey:" + key.Crv()) } default: panic("unknown key type:" + reflect.TypeOf(key).String()) } } func VerifyKey(t *testing.T, def map[string]keyDef) { t.Helper() def = complimentDef(def) key, err := jwk.ParseKey(makeKeyJSON(def)) if !assert.NoError(t, err, `jwk.ParseKey should succeed`) { return } t.Run("Fields", func(t *testing.T) { for k, kdef := range def { k := k kdef := kdef t.Run(k, func(t *testing.T) { getval, ok := key.Get(k) if !assert.True(t, ok, `key.Get(%s) should succeed`, k) { return } expected := kdef.Expected if expected == nil { expected = kdef.Value } if !assert.Equal(t, expected, getval) { return } if mname := kdef.Method; mname != "" { method := reflect.ValueOf(key).MethodByName(mname) if !assert.NotEqual(t, zeroval, method, `method should not be a zero value`) { return } retvals := method.Call(nil) if !assert.Len(t, retvals, 1, `there should be 1 return value`) { return } if !assert.Equal(t, expected, retvals[0].Interface()) { return } } }) } }) 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) if !assert.NoError(t, err, `jwk.EncodePEM should succeed`) { return } buf = pem } else { jsonbuf, err := json.Marshal(key) if !assert.NoError(t, err, `json.Marshal should succeed`) { return } buf = jsonbuf } newkey, err := jwk.ParseKey(buf, jwk.WithPEM(usePEM)) if !assert.NoError(t, err, `jwk.ParseKey should succeed`) { return } m1, err := key.AsMap(context.TODO()) if !assert.NoError(t, err, `key.AsMap should succeed`) { return } m2, err := newkey.AsMap(context.TODO()) if !assert.NoError(t, err, `key.AsMap should succeed`) { return } // PEM does not preserve these keys if usePEM { delete(m1, `private`) delete(m1, jwk.AlgorithmKey) delete(m1, jwk.KeyIDKey) delete(m1, jwk.KeyOpsKey) delete(m1, jwk.KeyUsageKey) delete(m1, jwk.X509CertChainKey) delete(m1, jwk.X509CertThumbprintKey) delete(m1, jwk.X509CertThumbprintS256Key) delete(m1, jwk.X509URLKey) } if !assert.Equal(t, m1, m2, `keys should match`) { return } }) } }) t.Run("Raw", func(t *testing.T) { typ := expectedRawKeyType(key) var rawkey interface{} if !assert.NoError(t, key.Raw(&rawkey), `Raw() should succeed`) { return } if !assert.IsType(t, rawkey, typ, `raw key should be of this type`) { return } }) t.Run("PublicKey", func(t *testing.T) { _, err := jwk.PublicKeyOf(key) if !assert.NoError(t, err, `jwk.PublicKeyOf should succeed`) { return } }) t.Run("IsPrivate", func(t *testing.T) { _, err := jwk.IsPrivateKey(key) if _, ok := key.(jwk.SymmetricKey); ok { if !assert.Error(t, err, `jwk.IsPrivateKey should fail`) { return } } else { if !assert.NoError(t, err, `jwk.IsPrivateKey should succeed`) { return } } }) t.Run("Set/Remove", func(t *testing.T) { ctx := context.TODO() newkey, err := key.Clone() if !assert.NoError(t, err, `key.Clone should succeed`) { return } for iter := key.Iterate(ctx); iter.Next(ctx); { pair := iter.Pair() newkey.Remove(pair.Key.(string)) } m, err := newkey.AsMap(ctx) if !assert.NoError(t, err, `key.AsMap should succeed`) { return } if !assert.Len(t, m, 1, `keys should have 1 key (kty remains)`) { return } for iter := key.Iterate(ctx); iter.Next(ctx); { pair := iter.Pair() if !assert.NoError(t, newkey.Set(pair.Key.(string), pair.Value), `newkey.Set should succeed`) { return } } }) } func TestNew(t *testing.T) { t.Parallel() k, err := jwk.FromRaw(nil) if !assert.Nil(t, k, "key should be nil") { return } if !assert.Error(t, err, "nil key should cause an error") { return } } 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() if err := json.Unmarshal([]byte(src), set); !assert.NoError(t, err, `json.Unmarshal should succeed`) { return } if !assert.True(t, set.Len() > 0, "set.Keys should be greater than 0") { return } ctx, cancel := context.WithCancel(context.Background()) defer cancel() for iter := set.Keys(ctx); iter.Next(ctx); { pair := iter.Pair() if !assert.True(t, reflect.TypeOf(pair.Value).AssignableTo(expected), "key should be a %s", expected) { return } } }) t.Run("jwk.Parse", func(t *testing.T) { t.Helper() set, err := jwk.Parse([]byte(`{"keys":[` + src + `]}`)) if !assert.NoError(t, err, `jwk.Parse should succeed`) { return } if !assert.True(t, set.Len() > 0, "set.Len should be greater than 0") { return } for iter := set.Keys(context.TODO()); iter.Next(context.TODO()); { pair := iter.Pair() key := pair.Value.(jwk.Key) switch key := key.(type) { case jwk.RSAPrivateKey, jwk.ECDSAPrivateKey, jwk.OKPPrivateKey, jwk.RSAPublicKey, jwk.ECDSAPublicKey, jwk.OKPPublicKey, jwk.SymmetricKey: default: assert.Fail(t, fmt.Sprintf("invalid type: %T", key)) } } }) t.Run("jwk.ParseKey", func(t *testing.T) { t.Helper() key, err := jwk.ParseKey([]byte(src)) if !assert.NoError(t, err, `jwk.ParseKey should succeed`) { return } t.Run("Raw", func(t *testing.T) { t.Helper() var irawkey interface{} if !assert.NoError(t, key.Raw(&irawkey), `key.Raw(&interface) should ucceed`) { return } isPrivate, err := jwk.IsPrivateKey(key) if !assert.NoError(t, err, "jwk.IsPrivateKey(%T) should succeed", key) { return } var crawkey interface{} switch k := key.(type) { case jwk.RSAPrivateKey: if !assert.True(t, isPrivate, `jwk.IsPrivateKey(&rsa.PrivateKey) should be true`) { return } var rawkey rsa.PrivateKey if !assert.NoError(t, key.Raw(&rawkey), `key.Raw(&rsa.PrivateKey) should succeed`) { return } crawkey = &rawkey case jwk.RSAPublicKey: if !assert.False(t, isPrivate, `jwk.IsPrivateKey(&rsa.PublicKey) should be false`) { return } var rawkey rsa.PublicKey if !assert.NoError(t, key.Raw(&rawkey), `key.Raw(&rsa.PublicKey) should succeed`) { return } crawkey = &rawkey case jwk.ECDSAPrivateKey: if !assert.True(t, isPrivate, `jwk.IsPrivateKey(&ecdsa.PrivateKey) should be true`) { return } var rawkey ecdsa.PrivateKey if !assert.NoError(t, key.Raw(&rawkey), `key.Raw(&ecdsa.PrivateKey) should succeed`) { return } crawkey = &rawkey case jwk.OKPPrivateKey: if !assert.True(t, isPrivate, `jwk.IsPrivateKey(&ed25519.PrivateKey) should be true`) { return } switch k.Crv() { case jwa.Ed25519: var rawkey ed25519.PrivateKey if !assert.NoError(t, key.Raw(&rawkey), `key.Raw(&ed25519.PrivateKey) should succeed`) { return } crawkey = rawkey case jwa.X25519: var rawkey x25519.PrivateKey if !assert.NoError(t, key.Raw(&rawkey), `key.Raw(&x25519.PrivateKey) should succeed`) { return } crawkey = rawkey default: t.Errorf(`invalid curve %s`, k.Crv()) } // NOTE: Has to come after private // key, since it's a subset of the // private key variant. case jwk.OKPPublicKey: if !assert.False(t, isPrivate, `jwk.IsPrivateKey(&ed25519.PublicKey) should be false`) { return } switch k.Crv() { case jwa.Ed25519: var rawkey ed25519.PublicKey if !assert.NoError(t, key.Raw(&rawkey), `key.Raw(&ed25519.PublicKey) should succeed`) { return } crawkey = rawkey case jwa.X25519: var rawkey x25519.PublicKey if !assert.NoError(t, key.Raw(&rawkey), `key.Raw(&x25519.PublicKey) should succeed`) { return } crawkey = rawkey default: t.Errorf(`invalid curve %s`, k.Crv()) } default: t.Errorf(`invalid key type %T`, key) return } if !assert.IsType(t, crawkey, irawkey, `key types should match`) { return } }) }) t.Run("ParseRawKey", func(t *testing.T) { var v interface{} if !assert.NoError(t, jwk.ParseRawKey([]byte(src), &v), `jwk.ParseRawKey should succeed`) { return } }) } 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.TypeOf((*jwk.RSAPublicKey)(nil)).Elem()) }) 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.TypeOf((*jwk.RSAPrivateKey)(nil)).Elem()) }) 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.TypeOf((*jwk.ECDSAPrivateKey)(nil)).Elem()) }) 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) if !assert.Error(t, err, `jwk.ParseString should fail`) { return } }) 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.TypeOf((*jwk.OKPPublicKey)(nil)).Elem()) }) 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.TypeOf((*jwk.OKPPrivateKey)(nil)).Elem()) }) 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.TypeOf((*jwk.OKPPublicKey)(nil)).Elem()) }) 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.TypeOf((*jwk.OKPPrivateKey)(nil)).Elem()) }) } 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) if !assert.NoError(t, err, `tc.generate should succeed`) { return } if !assert.NoError(t, ks1.AddKey(key), `ks1.Add should succeed`) { return } } buf, err := json.MarshalIndent(ks1, "", " ") if !assert.NoError(t, err, "JSON marshal succeeded") { return } ks2, err := jwk.Parse(buf) if !assert.NoError(t, err, "JSON unmarshal succeeded") { t.Logf("%s", buf) return } for _, tc := range tests { key1, ok := ks2.LookupKeyID(tc.keyID) if !assert.True(t, ok, "ks2.LookupKeyID should succeed") { return } key2, ok := ks1.LookupKeyID(tc.keyID) if !assert.True(t, ok, "ks1.LookupKeyID should succeed") { return } pk1json, _ := json.Marshal(key1) pk2json, _ := json.Marshal(key2) if !assert.Equal(t, pk1json, pk2json, "Keys should match (kid = %s)", tc.keyID) { return } } } func TestAccept(t *testing.T) { t.Parallel() t.Run("KeyOperation", func(t *testing.T) { t.Parallel() testcases := []struct { Args interface{} 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: []interface{}{"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 { if !assert.Error(t, ops.Accept(test.Args), `KeyOperationList.Accept should fail`) { return } } else { if !assert.NoError(t, ops.Accept(test.Args), `KeyOperationList.Accept should succeed`) { return } } } }) t.Run("KeyUsage", func(t *testing.T) { t.Parallel() testcases := []struct { Args interface{} 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 { if !assert.Error(t, usage.Accept(test.Args), `KeyUsage.Accept should fail`) { return } } else { if !assert.NoError(t, usage.Accept(test.Args), `KeyUsage.Accept should succeed`) { return } } } }) } 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() if !assert.NoError(t, err, `jwk generation should be successful`) { return } if !assert.Empty(t, k.KeyID(), `k.KeyID should be non-empty`) { return } if !assert.NoError(t, jwk.AssignKeyID(k), `AssignKeyID shuld be successful`) { return } if !assert.NotEmpty(t, k.KeyID(), `k.KeyID should be non-empty`) { return } } } func TestPublicKeyOf(t *testing.T) { t.Parallel() rsakey, err := jwxtest.GenerateRsaKey() if !assert.NoError(t, err, `generating raw RSA key should succeed`) { return } ecdsakey, err := jwxtest.GenerateEcdsaKey(jwa.P521) if !assert.NoError(t, err, `generating raw ECDSA key should succeed`) { return } octets := jwxtest.GenerateSymmetricKey() ed25519key, err := jwxtest.GenerateEd25519Key() if !assert.NoError(t, err, `generating raw Ed25519 key should succeed`) { return } x25519key, err := jwxtest.GenerateX25519Key() if !assert.NoError(t, err, `generating raw X25519 key should succeed`) { return } keys := []struct { Key interface{} PublicKeyType reflect.Type }{ { Key: rsakey, PublicKeyType: reflect.PointerTo(reflect.TypeOf(rsakey.PublicKey)), }, { Key: *rsakey, PublicKeyType: reflect.PointerTo(reflect.TypeOf(rsakey.PublicKey)), }, { Key: rsakey.PublicKey, PublicKeyType: reflect.PointerTo(reflect.TypeOf(rsakey.PublicKey)), }, { Key: &rsakey.PublicKey, PublicKeyType: reflect.PointerTo(reflect.TypeOf(rsakey.PublicKey)), }, { Key: ecdsakey, PublicKeyType: reflect.PointerTo(reflect.TypeOf(ecdsakey.PublicKey)), }, { Key: *ecdsakey, PublicKeyType: reflect.PointerTo(reflect.TypeOf(ecdsakey.PublicKey)), }, { Key: ecdsakey.PublicKey, PublicKeyType: reflect.PointerTo(reflect.TypeOf(ecdsakey.PublicKey)), }, { Key: &ecdsakey.PublicKey, PublicKeyType: reflect.PointerTo(reflect.TypeOf(ecdsakey.PublicKey)), }, { Key: octets, PublicKeyType: reflect.TypeOf(octets), }, { Key: ed25519key, PublicKeyType: reflect.TypeOf(ed25519key.Public()), }, { Key: ed25519key.Public(), PublicKeyType: reflect.TypeOf(ed25519key.Public()), }, { Key: x25519key, PublicKeyType: reflect.TypeOf(x25519key.Public()), }, { Key: x25519key.Public(), PublicKeyType: reflect.TypeOf(x25519key.Public()), }, } for _, key := range keys { key := key t.Run(fmt.Sprintf("%T", key.Key), func(t *testing.T) { t.Parallel() pubkey, err := jwk.PublicRawKeyOf(key.Key) if !assert.NoError(t, err, `jwk.PublicKeyOf(%T) should succeed`, key.Key) { return } if !assert.Equal(t, key.PublicKeyType, reflect.TypeOf(pubkey), `public key types should match (got %T)`, pubkey) { return } // Go through jwk.FromRaw jwkKey, err := jwk.FromRaw(key.Key) if !assert.NoError(t, err, `jwk.FromRaw should succeed`) { return } pubJwkKey, err := jwk.PublicKeyOf(jwkKey) if !assert.NoError(t, err, `jwk.PublicKeyOf(%T) should succeed`, jwkKey) { return } // Get the raw key to compare var rawKey interface{} if !assert.NoError(t, pubJwkKey.Raw(&rawKey), `pubJwkKey.Raw should succeed`) { return } if !assert.Equal(t, key.PublicKeyType, reflect.TypeOf(rawKey), `public key types should match (got %T)`, rawKey) { return } }) } 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.FromRaw(key.Key) if !assert.NoError(t, err, `jwk.FromRaw should succeed`) { return } 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) if !assert.NoError(t, err, `jwk.PublicKeyOf(jwk.Set) should succeed`) { return } for i, key := range setKeys { setKey, ok := newSet.Key(i) if !assert.True(t, ok, `element %d should be present`, i) { return } if !assert.Equal(t, fmt.Sprintf("key%d", i), setKey.KeyID(), `KeyID() should match for %T`, setKey) { return } // Get the raw key to compare var rawKey interface{} if !assert.NoError(t, setKey.Raw(&rawKey), `pubJwkKey.Raw should succeed`) { return } if !assert.Equal(t, key.PublicKeyType, reflect.TypeOf(rawKey), `public key types should match (got %T)`, rawKey) { return } } }) } 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 i := 0; i < 10; i++ { k, err := jwk.ParseKey([]byte(src)) if !assert.NoError(t, err, `jwk.ParseKey should succeed`) { return } thumb, err := k.Thumbprint(crypto.SHA1) if !assert.NoError(t, err, `k.Thumbprint should succeed`) { return } if !assert.Equal(t, `2Mc_43O_BOrOJTNrGX7uJ6JsIYE`, base64.EncodeToString(thumb), `thumbprints should match`) { return } } } 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)) if !assert.NoError(t, err, `jwk.ParseKey should succeed`) { return } for _, usage := range []string{"sig", "enc"} { if !assert.NoError(t, k.Set(jwk.KeyUsageKey, usage)) { return } if !assert.NoError(t, k.Set(jwk.KeyUsageKey, jwk.KeyUsageType(usage))) { return } } } func TestReadFile(t *testing.T) { t.Parallel() if !jose.Available() { t.SkipNow() } ctx, cancel := context.WithCancel(context.Background()) defer cancel() fn, clean, err := jose.GenerateJwk(ctx, t, `{"alg": "RS256"}`) if !assert.NoError(t, err, `jose.GenerateJwk`) { return } defer clean() if _, err := jwk.ReadFile(fn); !assert.NoError(t, err, `jwk.ReadFile should succeed`) { return } } 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.FromRaw(raw) if !assert.Error(t, err, `jwk.FromRaw should fail for invalid key`) { return } } }) }) 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.FromRaw(raw) if !assert.Error(t, err, `jwk.FromRaw should fail for empty key`) { return } } }) }) 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)) if !assert.NoError(t, err, `jwk.ParseKey should succeed`) { return } tp, err := key.Thumbprint(crypto.SHA256) if !assert.NoError(t, err, "Thumbprint should succeed") { return } if !assert.Equal(t, expected, tp, "Thumbprint should match") { return } }) } 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.FromRaw(raw) if !assert.Error(t, err, `jwk.FromRaw should fail for invalid key`) { return } } }) 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.FromRaw(raw) if !assert.Error(t, err, `jwk.FromRaw should fail for invalid key`) { return } } }) 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) { for _, alg := range ecutil.AvailableAlgorithms() { alg := alg t.Run(alg.String(), func(t *testing.T) { key, err := jwxtest.GenerateEcdsaKey(alg) if !assert.NoError(t, err, `jwxtest.GenerateEcdsaKey should succeed`) { return } privkey, err := jwk.FromRaw(key) if !assert.NoError(t, err, `jwk.FromRaw should succeed`) { return } pubkey, err := jwk.FromRaw(key) if !assert.NoError(t, err, `jwk.FromRaw should succeed`) { return } privtp, err := privkey.Thumbprint(crypto.SHA512) if !assert.NoError(t, err, `privkey.Thumbprint should succeed`) { return } pubtp, err := pubkey.Thumbprint(crypto.SHA512) if !assert.NoError(t, err, `pubkey.Thumbprint should succeed`) { return } if !assert.Equal(t, privtp, pubtp, `Thumbprints should match`) { return } }) } }) } 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() t.Run("Ed25519", func(t *testing.T) { t.Parallel() t.Run("PrivateKey", func(t *testing.T) { t.Parallel() VerifyKey(t, map[string]keyDef{ jwk.KeyTypeKey: { Method: "KeyType", Value: jwa.OKP, }, jwk.OKPDKey: expectBase64(keyDef{ Method: "D", Value: "nWGxne_9WmC6hEr0kuwsxERJxWl7MmkZcDusAxyuf2A", }), jwk.OKPXKey: expectBase64(keyDef{ Method: "X", Value: "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo", }), jwk.OKPCrvKey: { Method: "Crv", Value: jwa.Ed25519, }, }) }) t.Run("PublicKey", func(t *testing.T) { t.Parallel() VerifyKey(t, map[string]keyDef{ jwk.KeyTypeKey: { Method: "KeyType", Value: jwa.OKP, }, jwk.OKPXKey: expectBase64(keyDef{ Method: "X", Value: "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo", }), jwk.OKPCrvKey: { Method: "Crv", Value: jwa.Ed25519, }, }) }) }) t.Run("X25519", func(t *testing.T) { t.Parallel() t.Run("PublicKey", func(t *testing.T) { t.Parallel() VerifyKey(t, map[string]keyDef{ jwk.KeyTypeKey: { Method: "KeyType", Value: jwa.OKP, }, jwk.OKPXKey: expectBase64(keyDef{ Method: "X", Value: "3p7bfXt9wbTTW2HC7OQ1Nz-DQ8hbeGdNrfx-FG-IK08", }), jwk.OKPCrvKey: { Method: "Crv", Value: jwa.X25519, }, }) }) }) } func TestCustomField(t *testing.T) { // XXX has global effect!!! jwk.RegisterCustomField(`x-birthday`, time.Time{}) defer jwk.RegisterCustomField(`x-birthday`, nil) expected := time.Date(2015, 11, 4, 5, 12, 52, 0, time.UTC) bdaybytes, _ := expected.MarshalText() // RFC3339 var b strings.Builder b.WriteString(`{"e":"AQAB", "kty":"RSA", "n":"0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw","x-birthday":"`) b.Write(bdaybytes) b.WriteString(`"}`) src := b.String() t.Run("jwk.ParseKey", func(t *testing.T) { key, err := jwk.ParseKey([]byte(src)) if !assert.NoError(t, err, `jwk.ParseKey should succeed`) { return } v, ok := key.Get(`x-birthday`) if !assert.True(t, ok, `key.Get("x-birthday") should succeed`) { return } if !assert.Equal(t, expected, v, `values should match`) { return } }) } 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)) if !assert.NoError(t, err, `jwk.ParseKey should succeed`) { return } if !assert.Equal(t, jwa.RSA, key.KeyType(), `key type should be RSA`) { return } var pubkey rsa.PublicKey if !assert.NoError(t, key.Raw(&pubkey), `key.Raw should succeed`) { return } N := &big.Int{} N, _ = N.SetString(`779390807991489150242580488277564408218067197694419403671246387831173881192316375931050469298375090533614189460270485948672580508192398132571230359681952349714254730569052029178325305344289615160181016909374016900403698428293142159695593998453788610098596363011884623801134548926432366560975619087466760747503535615491182090094278093592303467050094984372887804234341012289019841973178427045121609424191835554013017436743418746919496835541323790719629313070434897002108079086472354410640690933161025543816362962891190753195691593288890628966181309776957070655619665306995097798188588453327627252794498823229009195585001242181503742627414517186199717150645163224325403559815442522031412813762764879089624715721999552786759649849125487587658121901233329199571710176245013452847516179837767710027433169340850618643815395642568876192931279303797384539146396956216244189819533317558165234451499206045369678277987397913889177569796721689284116762473340601498426367267765652880247655009239893325078809797979771964770948333084772104541394544131668212262901583064272659565503500144472388676955404823979083054620299811247635425415371418720649368570747531327436083928369741631909855731133100553629456091216238379430154237251461586878393695925917`, 10) if !assert.Equal(t, N, pubkey.N, `value for N should match`) { return } if !assert.Equal(t, 65537, pubkey.E, `value for E should amtch`) { return } } type typedField struct { Foo string Bar int64 } func TestTypedFields(t *testing.T) { expected := &typedField{Foo: "Foo", Bar: 0xdeadbeef} var keys []jwk.Key { k1, _ := jwxtest.GenerateRsaJwk() k2, _ := jwxtest.GenerateEcdsaJwk() k3, _ := jwxtest.GenerateSymmetricJwk() k4, _ := jwxtest.GenerateEd25519Jwk() 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, interface{}) (*typedField, error) }{ { Name: "Basic", Options: []jwk.ParseOption{jwk.WithTypedField("typed-field", typedField{})}, PostProcess: func(t *testing.T, field interface{}) (*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 interface{}) (*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 { key := key serialized, err := json.Marshal(key) if !assert.NoError(t, err, `json.Marshal should succeed`) { return } t.Run(fmt.Sprintf("%T", key), func(t *testing.T) { for _, tc := range testcases { tc := tc t.Run(tc.Name, func(t *testing.T) { got, err := jwk.ParseKey(serialized, tc.Options...) if !assert.NoError(t, err, `jwk.Parse should succeed`) { return } v, ok := got.Get("typed-field") if !assert.True(t, ok, `got.Get() should succeed`) { return } field, err := tc.PostProcess(t, v) if !assert.NoError(t, err, `tc.PostProcess should succeed`) { return } if !assert.Equal(t, field, expected, `field should match expected value`) { return } }) } }) } t.Run("Set", func(t *testing.T) { s := jwk.NewSet() for _, key := range keys { s.AddKey(key) } serialized, err := json.Marshal(s) if !assert.NoError(t, err, `json.Marshal should succeed`) { return } for _, tc := range testcases { tc := tc t.Run(tc.Name, func(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() got, err := jwk.Parse(serialized, tc.Options...) if !assert.NoError(t, err, `jwk.Parse should succeed`) { return } for iter := got.Keys(ctx); iter.Next(ctx); { pair := iter.Pair() key, _ := pair.Value.(jwk.Key) v, ok := key.Get("typed-field") if !assert.True(t, ok, `key.Get() should succeed`) { return } field, err := tc.PostProcess(t, v) if !assert.NoError(t, err, `tc.PostProcess should succeed`) { return } if !assert.Equal(t, field, expected, `field should match expected value`) { return } } }) } }) } func TestGH412(t *testing.T) { base := jwk.NewSet() const iterations = 5 kids := make(map[string]struct{}) for i := 0; i < iterations; i++ { k, err := jwxtest.GenerateRsaJwk() if !assert.NoError(t, err, `jwxttest.GenerateRsaJwk() should succeed`) { return } kid := "key-" + strconv.Itoa(i) k.Set(jwk.KeyIDKey, kid) base.AddKey(k) kids[kid] = struct{}{} } for i := 0; i < iterations; i++ { idx := i currentKid := "key-" + strconv.Itoa(i) t.Run(fmt.Sprintf("Remove at position %d", i), func(t *testing.T) { set, err := base.Clone() if !assert.NoError(t, err, `base.Clone() should succeed`) { return } if !assert.Equal(t, iterations, set.Len(), `set.Len should be %d`, iterations) { return } k, ok := set.Key(idx) if !assert.True(t, ok, `set.Get should succeed`) { return } if !assert.NoError(t, set.RemoveKey(k), `set.Remove should succeed`) { return } t.Logf("deleted key %s", k.KeyID()) if !assert.Equal(t, iterations-1, set.Len(), `set.Len should be %d`, iterations-1) { return } expected := make(map[string]struct{}) for k := range kids { if k == currentKid { continue } expected[k] = struct{}{} } ctx := context.Background() for iter := set.Keys(ctx); iter.Next(ctx); { pair := iter.Pair() key := pair.Value.(jwk.Key) if !assert.NotEqual(t, k.KeyID(), key.KeyID(), `key id should not match`) { return } t.Logf("%s found", key.KeyID()) delete(expected, key.KeyID()) } if !assert.Len(t, expected, 0, `expected map should be empty`) { return } }) } } 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)) if !assert.NoError(t, err, `jwk.Parse should succeed`) { return } // there should be 2 keys , get the first key k, _ := keys.Key(0) ops := k.KeyOps() if !assert.Equal(t, jwk.KeyOperationList{jwk.KeyOpDeriveKey}, ops, `k.KeyOps should match`) { return } } func TestSetWithPrivateParams(t *testing.T) { k1, err := jwxtest.GenerateRsaJwk() if !assert.NoError(t, err, `jwxtest.GenerateRsaJwk should succeed`) { return } k2, err := jwxtest.GenerateEcdsaJwk() if !assert.NoError(t, err, `jwxtest.GenerateEcdsaJwk should succeed`) { return } k3, err := jwxtest.GenerateSymmetricJwk() if !assert.NoError(t, err, `jwxtest.GenerateSymmetricJwk should succeed`) { return } 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) if !assert.NoError(t, err, `jwk.Parse should succeed`) { return } if !assert.Equal(t, 1, set.Len(), `set.Len() should be 1`) { return } v, ok := set.Get(`renewal_kid`) if !assert.True(t, ok, `set.Get("renewal_kid") should return ok = true`) { return } if !assert.Equal(t, `foo`, v, `set.Get("renewal_kid") should return "foo"`) { return } key, ok := set.Key(0) if !assert.True(t, ok, `set.Key(0) should return ok = true`) { return } v, ok = key.Get(`renewal_kid`) if !assert.True(t, ok, `key.Get("renewal_kid") should return ok = true`) { return } if !assert.Equal(t, `foo`, v, `key.Get("renewal_kid") should return "foo"`) { return } } 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()) if !assert.NoError(t, err, `jwk.Parse should succeed`) { return } js, err := json.MarshalIndent(set, "", " ") if !assert.NoError(t, err, `json.MarshalIndent should succeed`) { return } 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(',') _ = enc.Encode(k2) buf.WriteByte(',') _ = enc.Encode(k3) buf.WriteString(`]}`) var check = func(t *testing.T, buf []byte) { set, err := jwk.Parse(buf) if !assert.NoError(t, err, `jwk.Parse should succeed`) { return } if !assert.Equal(t, 3, set.Len(), `set.Len() should be 3`) { return } v, ok := set.Get(`renewal_kid`) if !assert.True(t, ok, `set.Get("renewal_kid") should return ok = true`) { return } if !assert.Equal(t, `foo`, v, `set.Get("renewal_kid") should return "foo"`) { return } } 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()) if !assert.NoError(t, err, `jwk.Parse should succeed`) { return } js, err := json.MarshalIndent(set, "", " ") if !assert.NoError(t, err, `json.MarshalIndent should succeed`) { return } check(t, js) }) }) t.Run("Set private parameters", func(t *testing.T) { set := jwk.NewSet() if !assert.NoError(t, set.Set(`renewal_kid`, `foo`), `set.Set should succeed`) { return } v, ok := set.Get(`renewal_kid`) if !assert.True(t, ok, `set.Get("renewal_kid") should succeed`) { return } if !assert.Equal(t, `foo`, v, `set.Get("renewal_kid") should return "foo"`) { return } if !assert.Error(t, set.Set(`keys`, []string{"foo"}), `set.Set should fail`) { return } k, err := jwk.FromRaw([]byte("foobar")) if !assert.NoError(t, err, `jwk.FromRaw should succeed`) { return } keys := []jwk.Key{k} if !assert.NoError(t, set.Set(`keys`, keys), `set.Set should succeed`) { return } if !assert.Equal(t, set.Len(), 1, `set should have 1 key`) { return } }) } func TestFetch(t *testing.T) { k1, err := jwxtest.GenerateRsaJwk() if !assert.NoError(t, err, `jwxtest.GenerateRsaJwk should succeed`) { return } k2, err := jwxtest.GenerateEcdsaJwk() if !assert.NoError(t, err, `jwxtest.GenerateEcdsaJwk should succeed`) { return } k3, err := jwxtest.GenerateSymmetricJwk() if !assert.NoError(t, err, `jwxtest.GenerateSymmetricJwk should succeed`) { return } set := jwk.NewSet() set.AddKey(k1) set.AddKey(k2) set.AddKey(k3) expected, err := json.MarshalIndent(set, "", " ") if !assert.NoError(t, err, `json.MarshalIndent should succeed`) { return } srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusOK) w.Write(expected) })) defer srv.Close() 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 { tc := tc t.Run(tc.Name, func(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() wl := tc.Whitelist() _, err = jwk.Fetch(ctx, `https://github.com/lestrrat-go/jwx/`, jwk.WithFetchWhitelist(wl)) if tc.Error { if !assert.Error(t, err, `jwk.Fetch should fail`) { return } if !assert.True(t, strings.Contains(err.Error(), `rejected by whitelist`), `error should be whitelist error`) { t.Logf("error was %q", err.Error()) return } } fetched, err := jwk.Fetch(ctx, srv.URL, jwk.WithFetchWhitelist(wl)) if !assert.NoError(t, err, `jwk.Fetch should succeed`) { return } got, err := json.MarshalIndent(fetched, "", " ") if !assert.NoError(t, err, `json.MarshalIndent should succeed`) { return } if !assert.Equal(t, expected, got, `data should match`) { return } }) } } 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} { ignoreParseError := ignoreParseError t.Run(fmt.Sprintf(`Parse with ignoreParseError=%t`, ignoreParseError), func(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() c := jwk.NewCache(ctx) c.Register(srv.URL, jwk.WithIgnoreParseError(ignoreParseError)) set, err := c.Get(ctx, srv.URL) if ignoreParseError { if !assert.NoError(t, err, `ar.Fetch should succeed`) { return } if !assert.Equal(t, set.Len(), 2, `JWKS should contain two keys`) { return } } else { if !assert.Error(t, err, `ar.Fetch should fail`) { return } } }) } // 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() if !assert.NoError(t, err, `jwxtest.GenerateRsaJwk() should succeed`) { return } buf, err := json.Marshal(key) if !assert.NoError(t, err, `json.Marshal should succeed`) { return } _, err = jwk.ParseKey(buf) if !assert.NoError(t, err, `jwk.ParseKey (no WithIgnoreParseError) should succeed`) { return } _, err = jwk.ParseKey(buf, jwk.WithIgnoreParseError(true)) if !assert.Error(t, err, `jwk.ParseKey (no WithIgnoreParseError) should fail`) { return } }) } func TestAvailableCurves(_ *testing.T) { // Not much to test here, but this silences the linters _ = jwk.AvailableCurves() } func TestCurveForAlgorithm(_ *testing.T) { // Not much to test here, but this silences the linters _, _ = jwk.CurveForAlgorithm(jwa.P521) } // 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() if !assert.NoError(t, err, `jwxtext.GenerateRsaKey() should succeed`) { return } // first, test a stupid case where Primes > 2 privkey.Primes = append(privkey.Primes, &big.Int{}) _, err = jwk.FromRaw(privkey) if !assert.Error(t, err, `jwk.FromRaw should fail`) { return } privkey.Primes = privkey.Primes[:2] // nuke p and q, dp, dq, qi for i := 0; i < 3; i++ { i := i 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.FromRaw(privkey) if !assert.NoError(t, err, `jwk.FromRaw should succeed`) { return } buf, _ := json.MarshalIndent(jwkPrivkey, "", " ") parsed, err := jwk.ParseKey(buf) if !assert.NoError(t, err, `jwk.ParseKey should succeed`) { return } payload := []byte(`hello , world!`) signed, err := jws.Sign(payload, jws.WithKey(jwa.RS256, parsed)) if !assert.NoError(t, err, `jws.Sign should succeed`) { return } verified, err := jws.Verify(signed, jws.WithKey(jwa.RS256, privkey.PublicKey)) if !assert.NoError(t, err, `jws.Verify should succeed`) { return } if !assert.Equal(t, payload, verified, `verified content should match`) { return } }) } } */ func TestGH730(t *testing.T) { key, err := jwk.FromRaw([]byte(`abracadabra`)) require.NoError(t, err, `jwk.FromRaw 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) } } // This test is commented out because we did not want to include `go.uber.org/goleak` // in our dependency. We agree that it's important to check for goroutine leaks, // but 1) this is sort of expected in this library, and 2) we don't believe that // forcing all of our users to use `go.uber.org/goleak` is prudent. // // However, we are leaving this test here so that users can learn how this function // can be used. This is meant to show you the boilerplate code for when you want to make // absolutely sure that no goroutine is left when you finish your program. // // For example, if you are writing an app, you can follow the pattern in the // test below and stop the goroutines that are started by httprc.NewFetcher // when your app terminates: // // func AppMain() { // ctx, cancel := context.WithCancel(context.Background()) // defer cancel() // // jwk.SetGlobalFetcher(http.NewFetcher(ctx)) // // your app code goes here // } // // Then, you can be sure that no goroutines are left when `AppMain()` is done. // You can also verify this in your test: // // func TestApp(t *testing.T) { // AppMain() // goleak.VerifyNone(t) // } /* func TestGH928(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) jwk.SetGlobalFetcher(httprc.NewFetcher(ctx)) // If you are using a custom fetcher like this in your test and you // still have more tests to run, you probably want to include the // following line to restore the default behavior after this test. // Otherwise, calls to `jwk.Fetch` may hang. defer jwk.SetGlobalFetcher(nil) // This must be included to restore default behavior cancel() // stop fetcher goroutines // At this point, not goroutines from httprc.Fetcher should be running goleak.VerifyNone(t) } */ 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, k.Raw(&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 := key.(jwk.ECDSAPrivateKey).X() 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 := key.(jwk.OKPPrivateKey).X() 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`) } } golang-github-lestrrat-go-jwx-2.1.4/jwk/key_ops.go000066400000000000000000000024351476711647200221320ustar00rootroot00000000000000package jwk import "fmt" func (ops *KeyOperationList) Get() KeyOperationList { if ops == nil { return nil } return *ops } func (ops *KeyOperationList) Accept(v interface{}) error { switch x := v.(type) { case string: return ops.Accept([]string{x}) case []interface{}: 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-2.1.4/jwk/okp.go000066400000000000000000000123301476711647200212450ustar00rootroot00000000000000package jwk import ( "bytes" "crypto" "crypto/ed25519" "fmt" "github.com/lestrrat-go/blackmagic" "github.com/lestrrat-go/jwx/v2/internal/base64" "github.com/lestrrat-go/jwx/v2/jwa" "github.com/lestrrat-go/jwx/v2/x25519" ) func (k *okpPublicKey) FromRaw(rawKeyIf interface{}) 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 x25519.PublicKey: k.x = rawKey crv = jwa.X25519 k.crv = &crv default: return fmt.Errorf(`unknown key type %T`, rawKeyIf) } return nil } func (k *okpPrivateKey) FromRaw(rawKeyIf interface{}) 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 x25519.PrivateKey: k.d = rawKey.Seed() k.x = rawKey.Public().(x25519.PublicKey) //nolint:forcetypeassert crv = jwa.X25519 k.crv = &crv default: return fmt.Errorf(`unknown key type %T`, rawKeyIf) } return nil } func buildOKPPublicKey(alg jwa.EllipticCurveAlgorithm, xbuf []byte) (interface{}, error) { switch alg { case jwa.Ed25519: return ed25519.PublicKey(xbuf), nil case jwa.X25519: return x25519.PublicKey(xbuf), 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 interface{}) error { k.mu.RLock() defer k.mu.RUnlock() pubk, err := buildOKPPublicKey(k.Crv(), k.x) if err != nil { return fmt.Errorf(`failed to build public key: %w`, err) } return blackmagic.AssignIfCompatible(v, pubk) } func buildOKPPrivateKey(alg jwa.EllipticCurveAlgorithm, xbuf []byte, dbuf []byte) (interface{}, 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(`wrong private key size`) } ret := ed25519.NewKeyFromSeed(dbuf) //nolint:forcetypeassert if !bytes.Equal(xbuf, ret.Public().(ed25519.PublicKey)) { return nil, fmt.Errorf(`invalid x value given d value`) } return ret, nil case jwa.X25519: ret, err := x25519.NewKeyFromSeed(dbuf) if err != nil { return nil, fmt.Errorf(`unable to construct x25519 private key from seed: %w`, err) } //nolint:forcetypeassert if !bytes.Equal(xbuf, ret.Public().(x25519.PublicKey)) { return nil, fmt.Errorf(`invalid x value given d value`) } return ret, nil default: return nil, fmt.Errorf(`invalid curve algorithm %s`, alg) } } func (k *okpPrivateKey) Raw(v interface{}) error { k.mu.RLock() defer k.mu.RUnlock() privk, err := buildOKPPrivateKey(k.Crv(), k.x, k.d) if err != nil { return fmt.Errorf(`failed to build public key: %w`, err) } return blackmagic.AssignIfCompatible(v, privk) } func makeOKPPublicKey(v interface { makePairs() []*HeaderPair }) (Key, error) { newKey := newOKPPublicKey() // Iterate and copy everything except for the bits that should not be in the public key for _, pair := range v.makePairs() { switch pair.Key { case OKPDKey: continue default: //nolint:forcetypeassert key := pair.Key.(string) if err := newKey.Set(key, pair.Value); err != nil { return nil, fmt.Errorf(`failed to set field %q: %w`, key, 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() return okpThumbprint( hash, k.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() return okpThumbprint( hash, k.Crv().String(), base64.EncodeToString(k.x), ), nil } func validateOKPKey(key interface { Crv() jwa.EllipticCurveAlgorithm X() []byte }) error { if key.Crv() == jwa.InvalidEllipticCurve { return fmt.Errorf(`invalid curve algorithm`) } if len(key.X()) == 0 { return fmt.Errorf(`missing "x" field`) } if priv, ok := key.(interface{ D() []byte }); ok { if len(priv.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-2.1.4/jwk/okp_gen.go000066400000000000000000000704231476711647200221050ustar00rootroot00000000000000// Code generated by tools/cmd/genjwk/main.go. DO NOT EDIT. package jwk import ( "bytes" "context" "fmt" "sort" "sync" "github.com/lestrrat-go/iter/mapiter" "github.com/lestrrat-go/jwx/v2/cert" "github.com/lestrrat-go/jwx/v2/internal/base64" "github.com/lestrrat-go/jwx/v2/internal/iter" "github.com/lestrrat-go/jwx/v2/internal/json" "github.com/lestrrat-go/jwx/v2/internal/pool" "github.com/lestrrat-go/jwx/v2/jwa" ) const ( OKPCrvKey = "crv" OKPDKey = "d" OKPXKey = "x" ) type OKPPublicKey interface { Key FromRaw(interface{}) error Crv() jwa.EllipticCurveAlgorithm X() []byte } 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]interface{} mu *sync.RWMutex dc json.DecodeCtx } var _ OKPPublicKey = &okpPublicKey{} var _ Key = &okpPublicKey{} func newOKPPublicKey() *okpPublicKey { return &okpPublicKey{ mu: &sync.RWMutex{}, privateParams: make(map[string]interface{}), } } func (h okpPublicKey) KeyType() jwa.KeyType { return jwa.OKP } func (h okpPublicKey) IsPrivate() bool { return false } func (h *okpPublicKey) Algorithm() jwa.KeyAlgorithm { if h.algorithm != nil { return *(h.algorithm) } return jwa.InvalidKeyAlgorithm("") } func (h *okpPublicKey) Crv() jwa.EllipticCurveAlgorithm { if h.crv != nil { return *(h.crv) } return jwa.InvalidEllipticCurve } func (h *okpPublicKey) KeyID() string { if h.keyID != nil { return *(h.keyID) } return "" } func (h *okpPublicKey) KeyOps() KeyOperationList { if h.keyOps != nil { return *(h.keyOps) } return nil } func (h *okpPublicKey) KeyUsage() string { if h.keyUsage != nil { return *(h.keyUsage) } return "" } func (h *okpPublicKey) X() []byte { return h.x } func (h *okpPublicKey) X509CertChain() *cert.Chain { return h.x509CertChain } func (h *okpPublicKey) X509CertThumbprint() string { if h.x509CertThumbprint != nil { return *(h.x509CertThumbprint) } return "" } func (h *okpPublicKey) X509CertThumbprintS256() string { if h.x509CertThumbprintS256 != nil { return *(h.x509CertThumbprintS256) } return "" } func (h *okpPublicKey) X509URL() string { if h.x509URL != nil { return *(h.x509URL) } return "" } func (h *okpPublicKey) makePairs() []*HeaderPair { h.mu.RLock() defer h.mu.RUnlock() var pairs []*HeaderPair pairs = append(pairs, &HeaderPair{Key: "kty", Value: jwa.OKP}) if h.algorithm != nil { pairs = append(pairs, &HeaderPair{Key: AlgorithmKey, Value: *(h.algorithm)}) } if h.crv != nil { pairs = append(pairs, &HeaderPair{Key: OKPCrvKey, Value: *(h.crv)}) } if h.keyID != nil { pairs = append(pairs, &HeaderPair{Key: KeyIDKey, Value: *(h.keyID)}) } if h.keyOps != nil { pairs = append(pairs, &HeaderPair{Key: KeyOpsKey, Value: *(h.keyOps)}) } if h.keyUsage != nil { pairs = append(pairs, &HeaderPair{Key: KeyUsageKey, Value: *(h.keyUsage)}) } if h.x != nil { pairs = append(pairs, &HeaderPair{Key: OKPXKey, Value: h.x}) } if h.x509CertChain != nil { pairs = append(pairs, &HeaderPair{Key: X509CertChainKey, Value: h.x509CertChain}) } if h.x509CertThumbprint != nil { pairs = append(pairs, &HeaderPair{Key: X509CertThumbprintKey, Value: *(h.x509CertThumbprint)}) } if h.x509CertThumbprintS256 != nil { pairs = append(pairs, &HeaderPair{Key: X509CertThumbprintS256Key, Value: *(h.x509CertThumbprintS256)}) } if h.x509URL != nil { pairs = append(pairs, &HeaderPair{Key: X509URLKey, Value: *(h.x509URL)}) } for k, v := range h.privateParams { pairs = append(pairs, &HeaderPair{Key: k, Value: v}) } return pairs } func (h *okpPublicKey) PrivateParams() map[string]interface{} { return h.privateParams } func (h *okpPublicKey) Get(name string) (interface{}, bool) { h.mu.RLock() defer h.mu.RUnlock() switch name { case KeyTypeKey: return h.KeyType(), true case AlgorithmKey: if h.algorithm == nil { return nil, false } return *(h.algorithm), true case OKPCrvKey: if h.crv == nil { return nil, false } return *(h.crv), true case KeyIDKey: if h.keyID == nil { return nil, false } return *(h.keyID), true case KeyOpsKey: if h.keyOps == nil { return nil, false } return *(h.keyOps), true case KeyUsageKey: if h.keyUsage == nil { return nil, false } return *(h.keyUsage), true case OKPXKey: if h.x == nil { return nil, false } return h.x, true case X509CertChainKey: if h.x509CertChain == nil { return nil, false } return h.x509CertChain, true case X509CertThumbprintKey: if h.x509CertThumbprint == nil { return nil, false } return *(h.x509CertThumbprint), true case X509CertThumbprintS256Key: if h.x509CertThumbprintS256 == nil { return nil, false } return *(h.x509CertThumbprintS256), true case X509URLKey: if h.x509URL == nil { return nil, false } return *(h.x509URL), true default: v, ok := h.privateParams[name] return v, ok } } func (h *okpPublicKey) Set(name string, value interface{}) error { h.mu.Lock() defer h.mu.Unlock() return h.setNoLock(name, value) } func (h *okpPublicKey) setNoLock(name string, value interface{}) error { switch name { case "kty": return nil case AlgorithmKey: switch v := value.(type) { case string, jwa.SignatureAlgorithm, jwa.ContentEncryptionAlgorithm: var tmp = jwa.KeyAlgorithmFrom(v) h.algorithm = &tmp case fmt.Stringer: s := v.String() var tmp = jwa.KeyAlgorithmFrom(s) h.algorithm = &tmp default: return fmt.Errorf(`invalid type for %s 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]interface{}{} } 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) { return cloneKey(k) } 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 '{' or '}' here. if tok == '}' { // End of object break LOOP } else if tok != '{' { return fmt.Errorf(`expected '{', but got '%c'`, 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 := jwa.KeyAlgorithmFrom(s) 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]interface{}) fields := make([]string, 0, 10) for _, pair := range h.makePairs() { fields = append(fields, pair.Key.(string)) data[pair.Key.(string)] = pair.Value } sort.Strings(fields) buf := pool.GetBytesBuffer() defer pool.ReleaseBytesBuffer(buf) buf.WriteByte('{') enc := json.NewEncoder(buf) for i, f := range fields { if i > 0 { buf.WriteRune(',') } buf.WriteRune('"') buf.WriteString(f) buf.WriteString(`":`) v := data[f] switch v := v.(type) { case []byte: buf.WriteRune('"') buf.WriteString(base64.EncodeToString(v)) buf.WriteRune('"') 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('}') ret := make([]byte, buf.Len()) copy(ret, buf.Bytes()) return ret, nil } func (h *okpPublicKey) Iterate(ctx context.Context) HeaderIterator { pairs := h.makePairs() ch := make(chan *HeaderPair, len(pairs)) go func(ctx context.Context, ch chan *HeaderPair, pairs []*HeaderPair) { defer close(ch) for _, pair := range pairs { select { case <-ctx.Done(): return case ch <- pair: } } }(ctx, ch, pairs) return mapiter.New(ch) } func (h *okpPublicKey) Walk(ctx context.Context, visitor HeaderVisitor) error { return iter.WalkMap(ctx, h, visitor) } func (h *okpPublicKey) AsMap(ctx context.Context) (map[string]interface{}, error) { return iter.AsMap(ctx, h) } type OKPPrivateKey interface { Key FromRaw(interface{}) error Crv() jwa.EllipticCurveAlgorithm D() []byte X() []byte } 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]interface{} mu *sync.RWMutex dc json.DecodeCtx } var _ OKPPrivateKey = &okpPrivateKey{} var _ Key = &okpPrivateKey{} func newOKPPrivateKey() *okpPrivateKey { return &okpPrivateKey{ mu: &sync.RWMutex{}, privateParams: make(map[string]interface{}), } } func (h okpPrivateKey) KeyType() jwa.KeyType { return jwa.OKP } func (h okpPrivateKey) IsPrivate() bool { return true } func (h *okpPrivateKey) Algorithm() jwa.KeyAlgorithm { if h.algorithm != nil { return *(h.algorithm) } return jwa.InvalidKeyAlgorithm("") } func (h *okpPrivateKey) Crv() jwa.EllipticCurveAlgorithm { if h.crv != nil { return *(h.crv) } return jwa.InvalidEllipticCurve } func (h *okpPrivateKey) D() []byte { return h.d } func (h *okpPrivateKey) KeyID() string { if h.keyID != nil { return *(h.keyID) } return "" } func (h *okpPrivateKey) KeyOps() KeyOperationList { if h.keyOps != nil { return *(h.keyOps) } return nil } func (h *okpPrivateKey) KeyUsage() string { if h.keyUsage != nil { return *(h.keyUsage) } return "" } func (h *okpPrivateKey) X() []byte { return h.x } func (h *okpPrivateKey) X509CertChain() *cert.Chain { return h.x509CertChain } func (h *okpPrivateKey) X509CertThumbprint() string { if h.x509CertThumbprint != nil { return *(h.x509CertThumbprint) } return "" } func (h *okpPrivateKey) X509CertThumbprintS256() string { if h.x509CertThumbprintS256 != nil { return *(h.x509CertThumbprintS256) } return "" } func (h *okpPrivateKey) X509URL() string { if h.x509URL != nil { return *(h.x509URL) } return "" } func (h *okpPrivateKey) makePairs() []*HeaderPair { h.mu.RLock() defer h.mu.RUnlock() var pairs []*HeaderPair pairs = append(pairs, &HeaderPair{Key: "kty", Value: jwa.OKP}) if h.algorithm != nil { pairs = append(pairs, &HeaderPair{Key: AlgorithmKey, Value: *(h.algorithm)}) } if h.crv != nil { pairs = append(pairs, &HeaderPair{Key: OKPCrvKey, Value: *(h.crv)}) } if h.d != nil { pairs = append(pairs, &HeaderPair{Key: OKPDKey, Value: h.d}) } if h.keyID != nil { pairs = append(pairs, &HeaderPair{Key: KeyIDKey, Value: *(h.keyID)}) } if h.keyOps != nil { pairs = append(pairs, &HeaderPair{Key: KeyOpsKey, Value: *(h.keyOps)}) } if h.keyUsage != nil { pairs = append(pairs, &HeaderPair{Key: KeyUsageKey, Value: *(h.keyUsage)}) } if h.x != nil { pairs = append(pairs, &HeaderPair{Key: OKPXKey, Value: h.x}) } if h.x509CertChain != nil { pairs = append(pairs, &HeaderPair{Key: X509CertChainKey, Value: h.x509CertChain}) } if h.x509CertThumbprint != nil { pairs = append(pairs, &HeaderPair{Key: X509CertThumbprintKey, Value: *(h.x509CertThumbprint)}) } if h.x509CertThumbprintS256 != nil { pairs = append(pairs, &HeaderPair{Key: X509CertThumbprintS256Key, Value: *(h.x509CertThumbprintS256)}) } if h.x509URL != nil { pairs = append(pairs, &HeaderPair{Key: X509URLKey, Value: *(h.x509URL)}) } for k, v := range h.privateParams { pairs = append(pairs, &HeaderPair{Key: k, Value: v}) } return pairs } func (h *okpPrivateKey) PrivateParams() map[string]interface{} { return h.privateParams } func (h *okpPrivateKey) Get(name string) (interface{}, bool) { h.mu.RLock() defer h.mu.RUnlock() switch name { case KeyTypeKey: return h.KeyType(), true case AlgorithmKey: if h.algorithm == nil { return nil, false } return *(h.algorithm), true case OKPCrvKey: if h.crv == nil { return nil, false } return *(h.crv), true case OKPDKey: if h.d == nil { return nil, false } return h.d, true case KeyIDKey: if h.keyID == nil { return nil, false } return *(h.keyID), true case KeyOpsKey: if h.keyOps == nil { return nil, false } return *(h.keyOps), true case KeyUsageKey: if h.keyUsage == nil { return nil, false } return *(h.keyUsage), true case OKPXKey: if h.x == nil { return nil, false } return h.x, true case X509CertChainKey: if h.x509CertChain == nil { return nil, false } return h.x509CertChain, true case X509CertThumbprintKey: if h.x509CertThumbprint == nil { return nil, false } return *(h.x509CertThumbprint), true case X509CertThumbprintS256Key: if h.x509CertThumbprintS256 == nil { return nil, false } return *(h.x509CertThumbprintS256), true case X509URLKey: if h.x509URL == nil { return nil, false } return *(h.x509URL), true default: v, ok := h.privateParams[name] return v, ok } } func (h *okpPrivateKey) Set(name string, value interface{}) error { h.mu.Lock() defer h.mu.Unlock() return h.setNoLock(name, value) } func (h *okpPrivateKey) setNoLock(name string, value interface{}) error { switch name { case "kty": return nil case AlgorithmKey: switch v := value.(type) { case string, jwa.SignatureAlgorithm, jwa.ContentEncryptionAlgorithm: var tmp = jwa.KeyAlgorithmFrom(v) h.algorithm = &tmp case fmt.Stringer: s := v.String() var tmp = jwa.KeyAlgorithmFrom(s) h.algorithm = &tmp default: return fmt.Errorf(`invalid type for %s 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]interface{}{} } 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) { return cloneKey(k) } 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 '{' or '}' here. if tok == '}' { // End of object break LOOP } else if tok != '{' { return fmt.Errorf(`expected '{', but got '%c'`, 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 := jwa.KeyAlgorithmFrom(s) 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]interface{}) fields := make([]string, 0, 11) for _, pair := range h.makePairs() { fields = append(fields, pair.Key.(string)) data[pair.Key.(string)] = pair.Value } sort.Strings(fields) buf := pool.GetBytesBuffer() defer pool.ReleaseBytesBuffer(buf) buf.WriteByte('{') enc := json.NewEncoder(buf) for i, f := range fields { if i > 0 { buf.WriteRune(',') } buf.WriteRune('"') buf.WriteString(f) buf.WriteString(`":`) v := data[f] switch v := v.(type) { case []byte: buf.WriteRune('"') buf.WriteString(base64.EncodeToString(v)) buf.WriteRune('"') 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('}') ret := make([]byte, buf.Len()) copy(ret, buf.Bytes()) return ret, nil } func (h *okpPrivateKey) Iterate(ctx context.Context) HeaderIterator { pairs := h.makePairs() ch := make(chan *HeaderPair, len(pairs)) go func(ctx context.Context, ch chan *HeaderPair, pairs []*HeaderPair) { defer close(ch) for _, pair := range pairs { select { case <-ctx.Done(): return case ch <- pair: } } }(ctx, ch, pairs) return mapiter.New(ch) } func (h *okpPrivateKey) Walk(ctx context.Context, visitor HeaderVisitor) error { return iter.WalkMap(ctx, h, visitor) } func (h *okpPrivateKey) AsMap(ctx context.Context) (map[string]interface{}, error) { return iter.AsMap(ctx, h) } golang-github-lestrrat-go-jwx-2.1.4/jwk/options.go000066400000000000000000000027161476711647200221560ustar00rootroot00000000000000package jwk import ( "github.com/lestrrat-go/option" ) type identTypedField struct{} type typedFieldPair struct { Name string Value interface{} } // 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 interface{}) ParseOption { return &parseOption{ option.New(identTypedField{}, typedFieldPair{Name: name, Value: object}, ), } } golang-github-lestrrat-go-jwx-2.1.4/jwk/options.yaml000066400000000000000000000132771476711647200225170ustar00rootroot00000000000000package_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: 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 `CacheOption`, 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()` options: - ident: HTTPClient interface: FetchOption 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: RefreshInterval interface: RegisterOption argument_type: time.Duration comment: | WithRefreshInterval specifies the static interval between refreshes of jwk.Set objects controlled by jwk.Cache. Providing this option overrides the adaptive token refreshing based on Cache-Control/Expires header (and jwk.WithMinRefreshInterval), and refreshes will *always* happen in this interval. - ident: MinRefreshInterval interface: RegisterOption argument_type: time.Duration comment: | WithMinRefreshInterval specifies the minimum refresh interval to be used when using `jwk.Cache`. This value is ONLY used if you did not specify a user-supplied static refresh interval via `WithRefreshInterval`. This value is used as a fallback value when tokens are refreshed. When we fetch the key from a remote URL, we first look at the max-age directive from Cache-Control response header. If this value is present, we compare the max-age value and the value specified by this option and take the larger one. Next we check for the Expires header, and similarly if the header is present, we compare it against the value specified by this option, and take the larger one. Finally, if neither of the above headers are present, we use the value specified by this option as the next refresh timing If unspecified, the minimum refresh interval is 1 hour - 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. - 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()`, `jwk.NewCache()`, and `(*jwk.Cache).Configure()` - 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: PostFetcher interface: RegisterOption argument_type: PostFetcher comment: | WithPostFetcher specifies the PostFetcher object to be used on the jwk.Set object obtained in `jwk.Cache`. This option can be used to, for example, modify the jwk.Set to give it key IDs or algorithm names after it has been fetched and parsed, but before it is cached. - ident: RefreshWindow interface: CacheOption argument_type: time.Duration comment: | WithRefreshWindow specifies the interval between checks for refreshes. See the documentation in `httprc.WithRefreshWindow` for more details. - ident: ErrSink interface: CacheOption argument_type: ErrSink comment: | WithErrSink specifies the `httprc.ErrSink` object that handles errors that occurred during the cache's execution. See the documentation in `httprc.WithErrSink` for more details. golang-github-lestrrat-go-jwx-2.1.4/jwk/options_gen.go000066400000000000000000000173001476711647200230020ustar00rootroot00000000000000// Code generated by tools/cmd/genoptions/main.go. DO NOT EDIT. package jwk import ( "crypto" "io/fs" "time" "github.com/lestrrat-go/jwx/v2/internal/json" "github.com/lestrrat-go/option" ) 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() {} // ParseOption is a type of Option that can be passed to `jwk.Parse()` // ParseOption also implements the `ReadFileOption` and `CacheOption`, // 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() {} // RegisterOption describes options that can be passed to `(jwk.Cache).Register()` type RegisterOption interface { Option registerOption() } type registerOption struct { Option } func (*registerOption) registerOption() {} type identErrSink struct{} type identFS struct{} type identFetchWhitelist struct{} type identHTTPClient struct{} type identIgnoreParseError struct{} type identLocalRegistry struct{} type identMinRefreshInterval struct{} type identPEM struct{} type identPostFetcher struct{} type identRefreshInterval struct{} type identRefreshWindow struct{} type identThumbprintHash struct{} func (identErrSink) String() string { return "WithErrSink" } 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 (identMinRefreshInterval) String() string { return "WithMinRefreshInterval" } func (identPEM) String() string { return "WithPEM" } func (identPostFetcher) String() string { return "WithPostFetcher" } func (identRefreshInterval) String() string { return "WithRefreshInterval" } func (identRefreshWindow) String() string { return "WithRefreshWindow" } func (identThumbprintHash) String() string { return "WithThumbprintHash" } // WithErrSink specifies the `httprc.ErrSink` object that handles errors // that occurred during the cache's execution. // // See the documentation in `httprc.WithErrSink` for more details. func WithErrSink(v ErrSink) CacheOption { return &cacheOption{option.New(identErrSink{}, 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)} } // WithFetchWhitelist specifies the Whitelist object to use when // fetching JWKs from a remote source. This option can be passed // to both `jwk.Fetch()`, `jwk.NewCache()`, and `(*jwk.Cache).Configure()` 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) FetchOption { return &fetchOption{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)} } // WithMinRefreshInterval specifies the minimum refresh interval to be used // when using `jwk.Cache`. This value is ONLY used if you did not specify // a user-supplied static refresh interval via `WithRefreshInterval`. // // This value is used as a fallback value when tokens are refreshed. // // When we fetch the key from a remote URL, we first look at the max-age // directive from Cache-Control response header. If this value is present, // we compare the max-age value and the value specified by this option // and take the larger one. // // Next we check for the Expires header, and similarly if the header is // present, we compare it against the value specified by this option, // and take the larger one. // // Finally, if neither of the above headers are present, we use the // value specified by this option as the next refresh timing // // If unspecified, the minimum refresh interval is 1 hour func WithMinRefreshInterval(v time.Duration) RegisterOption { return ®isterOption{option.New(identMinRefreshInterval{}, v)} } // WithPEM specifies that the input to `Parse()` is a PEM encoded key. func WithPEM(v bool) ParseOption { return &parseOption{option.New(identPEM{}, v)} } // WithPostFetcher specifies the PostFetcher object to be used on the // jwk.Set object obtained in `jwk.Cache`. This option can be used // to, for example, modify the jwk.Set to give it key IDs or algorithm // names after it has been fetched and parsed, but before it is cached. func WithPostFetcher(v PostFetcher) RegisterOption { return ®isterOption{option.New(identPostFetcher{}, v)} } // WithRefreshInterval specifies the static interval between refreshes // of jwk.Set objects controlled by jwk.Cache. // // Providing this option overrides the adaptive token refreshing based // on Cache-Control/Expires header (and jwk.WithMinRefreshInterval), // and refreshes will *always* happen in this interval. func WithRefreshInterval(v time.Duration) RegisterOption { return ®isterOption{option.New(identRefreshInterval{}, v)} } // WithRefreshWindow specifies the interval between checks for refreshes. // // See the documentation in `httprc.WithRefreshWindow` for more details. func WithRefreshWindow(v time.Duration) CacheOption { return &cacheOption{option.New(identRefreshWindow{}, v)} } func WithThumbprintHash(v crypto.Hash) AssignKeyIDOption { return &assignKeyIDOption{option.New(identThumbprintHash{}, v)} } golang-github-lestrrat-go-jwx-2.1.4/jwk/options_gen_test.go000066400000000000000000000017241476711647200240440ustar00rootroot00000000000000// 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, "WithErrSink", identErrSink{}.String()) 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, "WithMinRefreshInterval", identMinRefreshInterval{}.String()) require.Equal(t, "WithPEM", identPEM{}.String()) require.Equal(t, "WithPostFetcher", identPostFetcher{}.String()) require.Equal(t, "WithRefreshInterval", identRefreshInterval{}.String()) require.Equal(t, "WithRefreshWindow", identRefreshWindow{}.String()) require.Equal(t, "WithThumbprintHash", identThumbprintHash{}.String()) } golang-github-lestrrat-go-jwx-2.1.4/jwk/refresh_test.go000066400000000000000000000327261476711647200231640ustar00rootroot00000000000000package jwk_test import ( "bytes" "context" "fmt" "net/http" "net/http/httptest" "sync" "testing" "time" "github.com/lestrrat-go/jwx/v2/internal/json" "github.com/lestrrat-go/jwx/v2/internal/jwxtest" "github.com/lestrrat-go/jwx/v2/jwk" "github.com/stretchr/testify/assert" ) //nolint:revive,golint func checkAccessCount(t *testing.T, ctx context.Context, src jwk.Set, expected ...int) bool { t.Helper() iter := src.Keys(ctx) iter.Next(ctx) key := iter.Pair().Value.(jwk.Key) v, ok := key.Get(`accessCount`) if !assert.True(t, ok, `key.Get("accessCount") should succeed`) { return false } for _, e := range expected { if v == float64(e) { return assert.Equal(t, float64(e), v, `key.Get("accessCount") should be %d`, e) } } 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, "]") return assert.Failf(t, `checking access count failed`, `key.Get("accessCount") should be one of %s (got %f)`, buf.String(), v) } func TestCache(t *testing.T) { t.Parallel() t.Run("CachedSet", func(t *testing.T) { const numKeys = 3 t.Parallel() ctx, cancel := context.WithTimeout(context.Background(), time.Minute) defer cancel() set := jwk.NewSet() for i := 0; i < numKeys; i++ { key, err := jwxtest.GenerateRsaJwk() if !assert.NoError(t, err, `jwxtest.GenerateRsaJwk should succeed`) { return } if !assert.NoError(t, set.AddKey(key), `set.AddKey should succeed`) { return } } 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() af := jwk.NewCache(ctx, jwk.WithRefreshWindow(time.Second)) if !assert.NoError(t, af.Register(srv.URL), `af.Register should succeed`) { return } cached := jwk.NewCachedSet(af, srv.URL) if !assert.Error(t, cached.Set("bogus", nil), `cached.Set should be an error`) { return } if !assert.Error(t, cached.Remove("bogus"), `cached.Remove should be an error`) { return } if !assert.Error(t, cached.AddKey(nil), `cached.AddKey should be an error`) { return } if !assert.Error(t, cached.RemoveKey(nil), `cached.RemoveKey should be an error`) { return } if !assert.Equal(t, set.Len(), cached.Len(), `value of Len() should be the same`) { return } iter := set.Keys(ctx) citer := cached.Keys(ctx) for i := 0; i < numKeys; i++ { k, err := set.Key(i) ck, cerr := cached.Key(i) if !assert.Equal(t, k, ck, `key %d should match`, i) { return } if !assert.Equal(t, err, cerr, `error %d should match`, i) { return } if !assert.Equal(t, iter.Next(ctx), citer.Next(ctx), `iter.Next should match`) { return } if !assert.Equal(t, iter.Pair(), citer.Pair(), `iter.Pair should match`) { return } } }) t.Run("Specify explicit refresh interval", func(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]interface{}{ "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() af := jwk.NewCache(ctx, jwk.WithRefreshWindow(time.Second)) if !assert.NoError(t, af.Register(srv.URL, jwk.WithRefreshInterval(3*time.Second)), `af.Register should succeed`) { return } retries := 5 var wg sync.WaitGroup wg.Add(retries) for i := 0; i < retries; i++ { // Run these in separate goroutines to emulate a possible thundering herd go func() { defer wg.Done() ks, err := af.Get(ctx, srv.URL) if !assert.NoError(t, err, `af.Get should succeed`) { return } if !checkAccessCount(t, ctx, ks, 1) { return } }() } t.Logf("Waiting for fetching goroutines...") wg.Wait() t.Logf("Waiting for the refresh ...") time.Sleep(4 * time.Second) ks, err := af.Get(ctx, srv.URL) if !assert.NoError(t, err, `af.Get should succeed`) { return } if !checkAccessCount(t, ctx, ks, 2) { return } }) t.Run("Calculate next refresh from Cache-Control header", func(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]interface{}{ "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() af := jwk.NewCache(ctx, jwk.WithRefreshWindow(time.Second)) if !assert.NoError(t, af.Register(srv.URL, jwk.WithMinRefreshInterval(time.Second)), `af.Register should succeed`) { return } if !assert.True(t, af.IsRegistered(srv.URL), `af.IsRegistered should be true`) { return } retries := 5 var wg sync.WaitGroup wg.Add(retries) for i := 0; i < retries; i++ { // Run these in separate goroutines to emulate a possible thundering herd go func() { defer wg.Done() ks, err := af.Get(ctx, srv.URL) if !assert.NoError(t, err, `af.Get should succeed`) { return } if !checkAccessCount(t, ctx, ks, 1) { return } }() } t.Logf("Waiting for fetching goroutines...") wg.Wait() t.Logf("Waiting for the refresh ...") time.Sleep(4 * time.Second) ks, err := af.Get(ctx, srv.URL) if !assert.NoError(t, err, `af.Get should succeed`) { return } if !checkAccessCount(t, ctx, ks, 2) { return } }) t.Run("Backoff", func(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++ if accessCount > 1 && accessCount < 4 { http.Error(w, "wait for it....", http.StatusForbidden) return } key := map[string]interface{}{ "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=1`) json.NewEncoder(w).Encode(key) })) defer srv.Close() af := jwk.NewCache(ctx, jwk.WithRefreshWindow(time.Second)) af.Register(srv.URL, jwk.WithMinRefreshInterval(time.Second)) // First fetch should succeed ks, err := af.Get(ctx, srv.URL) if !assert.NoError(t, err, `af.Get (#1) should succeed`) { return } if !checkAccessCount(t, ctx, ks, 1) { return } // enough time for 1 refresh to have occurred time.Sleep(1500 * time.Millisecond) ks, err = af.Get(ctx, srv.URL) if !assert.NoError(t, err, `af.Get (#2) should succeed`) { return } // Should be using the cached version if !checkAccessCount(t, ctx, ks, 1) { return } // enough time for 2 refreshes to have occurred time.Sleep(2500 * time.Millisecond) ks, err = af.Get(ctx, srv.URL) if !assert.NoError(t, err, `af.Get (#3) should succeed`) { return } // should be new if !checkAccessCount(t, ctx, ks, 4, 5) { return } }) } func TestRefreshSnapshot(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), time.Minute) defer cancel() var jwksURLs []string getJwksURL := func(dst *[]string, url string) bool { req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) if err != nil { return false } res, err := http.DefaultClient.Do(req) if err != nil { return false } defer res.Body.Close() var m map[string]interface{} if err := json.NewDecoder(res.Body).Decode(&m); err != nil { return false } jwksURL, ok := m["jwks_uri"] if !ok { return false } *dst = append(*dst, jwksURL.(string)) return true } if !getJwksURL(&jwksURLs, "https://oidc-sample.onelogin.com/oidc/2/.well-known/openid-configuration") { t.SkipNow() } if !getJwksURL(&jwksURLs, "https://accounts.google.com/.well-known/openid-configuration") { t.SkipNow() } ar := jwk.NewCache(ctx, jwk.WithRefreshWindow(time.Second)) for _, url := range jwksURLs { if !assert.NoError(t, ar.Register(url), `ar.Register should succeed`) { return } } for _, url := range jwksURLs { _ = ar.Unregister(url) } for _, target := range ar.Snapshot().Entries { t.Logf("%s last refreshed at %s", target.URL, target.LastFetched) } for _, url := range jwksURLs { ar.Unregister(url) } if !assert.Len(t, ar.Snapshot().Entries, 0, `there should be no URLs`) { return } if !assert.Error(t, ar.Unregister(`dummy`), `removing a non-existing url should be an error`) { return } } type accumulateErrs struct { mu sync.RWMutex errs []error } func (e *accumulateErrs) Error(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() if !assert.NoError(t, err, `jwxtest.GenerateRsaJwk should succeed`) { return } set := jwk.NewSet() _ = set.AddKey(k) testcases := []struct { Name string Options func() []jwk.RegisterOption Handler http.Handler }{ /* { Name: "non-200 response", Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusForbidden) }), }, { Name: "invalid JWK", Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) w.Write([]byte(`{"empty": "nonthingness"}`)) }), }, */ { Name: `rejected by whitelist`, Options: func() []jwk.RegisterOption { return []jwk.RegisterOption{ jwk.WithFetchWhitelist(jwk.WhitelistFunc(func(_ string) bool { return false })), } }, Handler: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(k) }), }, } for _, tc := range testcases { tc := tc 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 ar := jwk.NewCache(ctx, jwk.WithErrSink(&errSink), jwk.WithRefreshWindow(time.Second)) var options []jwk.RegisterOption if f := tc.Options; f != nil { options = f() } options = append(options, jwk.WithRefreshInterval(time.Second)) if !assert.NoError(t, ar.Register(srv.URL, options...), `ar.Register should succeed`) { return } _, _ = ar.Get(ctx, srv.URL) timer := time.NewTimer(6 * time.Second) select { case <-ctx.Done(): t.Errorf(`ctx.Done before timer`) case <-timer.C: } cancel() // forcefully end context, and thus the Cache // timing issues can cause this to be non-deterministic... // we'll say it's okay as long as we're in +/- 1 range l := errSink.Len() if !assert.True(t, l <= 7, "number of errors should be less than or equal to 7 (%d)", l) { return } if !assert.True(t, l >= 5, "number of errors should be greater than or equal to 5 (%d)", l) { return } }) } } func TestPostFetch(t *testing.T) { t.Parallel() set := jwk.NewSet() for i := 0; i < 3; i++ { key, err := jwk.FromRaw([]byte(fmt.Sprintf(`abracadabra-%d`, i))) if !assert.NoError(t, err, `jwk.FromRaw should succeed`) { return } _ = set.AddKey(key) } testcases := []struct { Name string Options []jwk.RegisterOption ExpectKid bool }{ { Name: "No PostFetch", }, { Name: "With PostFetch", Options: []jwk.RegisterOption{jwk.WithPostFetcher(jwk.PostFetchFunc(func(_ string, set jwk.Set) (jwk.Set, error) { for i := 0; i < set.Len(); i++ { key, _ := set.Key(i) key.Set(jwk.KeyIDKey, fmt.Sprintf(`key-%d`, i)) } return set, nil }))}, ExpectKid: true, }, } for _, tc := range testcases { tc := tc t.Run(tc.Name, func(t *testing.T) { t.Parallel() srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(set) })) defer srv.Close() ctx, cancel := context.WithTimeout(context.Background(), time.Minute) defer cancel() ar := jwk.NewCache(ctx) ar.Register(srv.URL, tc.Options...) set, err := ar.Get(ctx, srv.URL) if !assert.NoError(t, err, `ar.Fetch should succeed`) { return } for i := 0; i < set.Len(); i++ { key, _ := set.Key(i) if tc.ExpectKid { if !assert.NotEmpty(t, key.KeyID(), `key.KeyID should not be empty`) { return } } else { if !assert.Empty(t, key.KeyID(), `key.KeyID should be empty`) { return } } } }) } } golang-github-lestrrat-go-jwx-2.1.4/jwk/rsa.go000066400000000000000000000143351476711647200212500ustar00rootroot00000000000000package jwk import ( "crypto" "crypto/rsa" "encoding/binary" "fmt" "math/big" "github.com/lestrrat-go/blackmagic" "github.com/lestrrat-go/jwx/v2/internal/base64" "github.com/lestrrat-go/jwx/v2/internal/pool" ) func (k *rsaPrivateKey) FromRaw(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 := rsaPublicKeyByteValuesFromRaw(&rawKey.PublicKey) if err != nil { return fmt.Errorf(`invalid rsa.PrivateKey: %w`, err) } k.n = n k.e = e return nil } func rsaPublicKeyByteValuesFromRaw(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) FromRaw(rawKey *rsa.PublicKey) error { k.mu.Lock() defer k.mu.Unlock() n, e, err := rsaPublicKeyByteValuesFromRaw(rawKey) if err != nil { return fmt.Errorf(`invalid rsa.PrivateKey: %w`, err) } k.n = n k.e = e return nil } func (k *rsaPrivateKey) Raw(v interface{}) error { k.mu.RLock() defer k.mu.RUnlock() var d, q, p big.Int // note: do not use from sync.Pool d.SetBytes(k.d) q.SetBytes(k.q) p.SetBytes(k.p) // optional fields var dp, dq, qi *big.Int if len(k.dp) > 0 { dp = &big.Int{} // note: do not use from sync.Pool dp.SetBytes(k.dp) } if len(k.dq) > 0 { dq = &big.Int{} // note: do not use from sync.Pool dq.SetBytes(k.dq) } if len(k.qi) > 0 { qi = &big.Int{} // note: do not use from sync.Pool qi.SetBytes(k.qi) } var key rsa.PrivateKey pubk := newRSAPublicKey() pubk.n = k.n pubk.e = k.e if err := pubk.Raw(&key.PublicKey); err != nil { return fmt.Errorf(`failed to materialize RSA public key: %w`, err) } key.D = &d key.Primes = []*big.Int{&p, &q} if dp != nil { key.Precomputed.Dp = dp } if dq != nil { key.Precomputed.Dq = dq } if qi != nil { key.Precomputed.Qinv = qi } key.Precomputed.CRTValues = []rsa.CRTValue{} return blackmagic.AssignIfCompatible(v, &key) } // Raw takes the values stored in the Key object, and creates the // corresponding *rsa.PublicKey object. func (k *rsaPublicKey) Raw(v interface{}) error { k.mu.RLock() defer k.mu.RUnlock() var key rsa.PublicKey n := pool.GetBigInt() e := pool.GetBigInt() defer pool.ReleaseBigInt(e) n.SetBytes(k.n) e.SetBytes(k.e) key.N = n key.E = int(e.Int64()) return blackmagic.AssignIfCompatible(v, &key) } func makeRSAPublicKey(v interface { makePairs() []*HeaderPair }) (Key, error) { newKey := newRSAPublicKey() // Iterate and copy everything except for the bits that should not be in the public key for _, pair := range v.makePairs() { switch pair.Key { case RSADKey, RSADPKey, RSADQKey, RSAPKey, RSAQKey, RSAQIKey: continue default: //nolint:forcetypeassert key := pair.Key.(string) if err := newKey.Set(key, pair.Value); err != nil { return nil, fmt.Errorf(`failed to set field %q: %w`, key, 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 := k.Raw(&key); err != nil { return nil, fmt.Errorf(`failed to materialize 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 := k.Raw(&key); err != nil { return nil, fmt.Errorf(`failed to materialize RSA public key: %w`, err) } return rsaThumbprint(hash, &key) } func rsaThumbprint(hash crypto.Hash, key *rsa.PublicKey) ([]byte, error) { buf := pool.GetBytesBuffer() defer pool.ReleaseBytesBuffer(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 E() []byte }, checkPrivate bool) error { if len(key.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(key.E()) == 0 { return fmt.Errorf(`missing "e" value`) } if checkPrivate { if priv, ok := key.(interface{ D() []byte }); ok { if len(priv.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-2.1.4/jwk/rsa_gen.go000066400000000000000000000756601476711647200221110ustar00rootroot00000000000000// Code generated by tools/cmd/genjwk/main.go. DO NOT EDIT. package jwk import ( "bytes" "context" "crypto/rsa" "fmt" "sort" "sync" "github.com/lestrrat-go/iter/mapiter" "github.com/lestrrat-go/jwx/v2/cert" "github.com/lestrrat-go/jwx/v2/internal/base64" "github.com/lestrrat-go/jwx/v2/internal/iter" "github.com/lestrrat-go/jwx/v2/internal/json" "github.com/lestrrat-go/jwx/v2/internal/pool" "github.com/lestrrat-go/jwx/v2/jwa" ) const ( RSADKey = "d" RSADPKey = "dp" RSADQKey = "dq" RSAEKey = "e" RSANKey = "n" RSAPKey = "p" RSAQIKey = "qi" RSAQKey = "q" ) type RSAPublicKey interface { Key FromRaw(*rsa.PublicKey) error E() []byte N() []byte } 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]interface{} mu *sync.RWMutex dc json.DecodeCtx } var _ RSAPublicKey = &rsaPublicKey{} var _ Key = &rsaPublicKey{} func newRSAPublicKey() *rsaPublicKey { return &rsaPublicKey{ mu: &sync.RWMutex{}, privateParams: make(map[string]interface{}), } } func (h rsaPublicKey) KeyType() jwa.KeyType { return jwa.RSA } func (h rsaPublicKey) IsPrivate() bool { return false } func (h *rsaPublicKey) Algorithm() jwa.KeyAlgorithm { if h.algorithm != nil { return *(h.algorithm) } return jwa.InvalidKeyAlgorithm("") } func (h *rsaPublicKey) E() []byte { return h.e } func (h *rsaPublicKey) KeyID() string { if h.keyID != nil { return *(h.keyID) } return "" } func (h *rsaPublicKey) KeyOps() KeyOperationList { if h.keyOps != nil { return *(h.keyOps) } return nil } func (h *rsaPublicKey) KeyUsage() string { if h.keyUsage != nil { return *(h.keyUsage) } return "" } func (h *rsaPublicKey) N() []byte { return h.n } func (h *rsaPublicKey) X509CertChain() *cert.Chain { return h.x509CertChain } func (h *rsaPublicKey) X509CertThumbprint() string { if h.x509CertThumbprint != nil { return *(h.x509CertThumbprint) } return "" } func (h *rsaPublicKey) X509CertThumbprintS256() string { if h.x509CertThumbprintS256 != nil { return *(h.x509CertThumbprintS256) } return "" } func (h *rsaPublicKey) X509URL() string { if h.x509URL != nil { return *(h.x509URL) } return "" } func (h *rsaPublicKey) makePairs() []*HeaderPair { h.mu.RLock() defer h.mu.RUnlock() var pairs []*HeaderPair pairs = append(pairs, &HeaderPair{Key: "kty", Value: jwa.RSA}) if h.algorithm != nil { pairs = append(pairs, &HeaderPair{Key: AlgorithmKey, Value: *(h.algorithm)}) } if h.e != nil { pairs = append(pairs, &HeaderPair{Key: RSAEKey, Value: h.e}) } if h.keyID != nil { pairs = append(pairs, &HeaderPair{Key: KeyIDKey, Value: *(h.keyID)}) } if h.keyOps != nil { pairs = append(pairs, &HeaderPair{Key: KeyOpsKey, Value: *(h.keyOps)}) } if h.keyUsage != nil { pairs = append(pairs, &HeaderPair{Key: KeyUsageKey, Value: *(h.keyUsage)}) } if h.n != nil { pairs = append(pairs, &HeaderPair{Key: RSANKey, Value: h.n}) } if h.x509CertChain != nil { pairs = append(pairs, &HeaderPair{Key: X509CertChainKey, Value: h.x509CertChain}) } if h.x509CertThumbprint != nil { pairs = append(pairs, &HeaderPair{Key: X509CertThumbprintKey, Value: *(h.x509CertThumbprint)}) } if h.x509CertThumbprintS256 != nil { pairs = append(pairs, &HeaderPair{Key: X509CertThumbprintS256Key, Value: *(h.x509CertThumbprintS256)}) } if h.x509URL != nil { pairs = append(pairs, &HeaderPair{Key: X509URLKey, Value: *(h.x509URL)}) } for k, v := range h.privateParams { pairs = append(pairs, &HeaderPair{Key: k, Value: v}) } return pairs } func (h *rsaPublicKey) PrivateParams() map[string]interface{} { return h.privateParams } func (h *rsaPublicKey) Get(name string) (interface{}, bool) { h.mu.RLock() defer h.mu.RUnlock() switch name { case KeyTypeKey: return h.KeyType(), true case AlgorithmKey: if h.algorithm == nil { return nil, false } return *(h.algorithm), true case RSAEKey: if h.e == nil { return nil, false } return h.e, true case KeyIDKey: if h.keyID == nil { return nil, false } return *(h.keyID), true case KeyOpsKey: if h.keyOps == nil { return nil, false } return *(h.keyOps), true case KeyUsageKey: if h.keyUsage == nil { return nil, false } return *(h.keyUsage), true case RSANKey: if h.n == nil { return nil, false } return h.n, true case X509CertChainKey: if h.x509CertChain == nil { return nil, false } return h.x509CertChain, true case X509CertThumbprintKey: if h.x509CertThumbprint == nil { return nil, false } return *(h.x509CertThumbprint), true case X509CertThumbprintS256Key: if h.x509CertThumbprintS256 == nil { return nil, false } return *(h.x509CertThumbprintS256), true case X509URLKey: if h.x509URL == nil { return nil, false } return *(h.x509URL), true default: v, ok := h.privateParams[name] return v, ok } } func (h *rsaPublicKey) Set(name string, value interface{}) error { h.mu.Lock() defer h.mu.Unlock() return h.setNoLock(name, value) } func (h *rsaPublicKey) setNoLock(name string, value interface{}) error { switch name { case "kty": return nil case AlgorithmKey: switch v := value.(type) { case string, jwa.SignatureAlgorithm, jwa.ContentEncryptionAlgorithm: var tmp = jwa.KeyAlgorithmFrom(v) h.algorithm = &tmp case fmt.Stringer: s := v.String() var tmp = jwa.KeyAlgorithmFrom(s) h.algorithm = &tmp default: return fmt.Errorf(`invalid type for %s 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]interface{}{} } 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) { return cloneKey(k) } 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 '{' or '}' here. if tok == '}' { // End of object break LOOP } else if tok != '{' { return fmt.Errorf(`expected '{', but got '%c'`, 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 := jwa.KeyAlgorithmFrom(s) 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]interface{}) fields := make([]string, 0, 10) for _, pair := range h.makePairs() { fields = append(fields, pair.Key.(string)) data[pair.Key.(string)] = pair.Value } sort.Strings(fields) buf := pool.GetBytesBuffer() defer pool.ReleaseBytesBuffer(buf) buf.WriteByte('{') enc := json.NewEncoder(buf) for i, f := range fields { if i > 0 { buf.WriteRune(',') } buf.WriteRune('"') buf.WriteString(f) buf.WriteString(`":`) v := data[f] switch v := v.(type) { case []byte: buf.WriteRune('"') buf.WriteString(base64.EncodeToString(v)) buf.WriteRune('"') 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('}') ret := make([]byte, buf.Len()) copy(ret, buf.Bytes()) return ret, nil } func (h *rsaPublicKey) Iterate(ctx context.Context) HeaderIterator { pairs := h.makePairs() ch := make(chan *HeaderPair, len(pairs)) go func(ctx context.Context, ch chan *HeaderPair, pairs []*HeaderPair) { defer close(ch) for _, pair := range pairs { select { case <-ctx.Done(): return case ch <- pair: } } }(ctx, ch, pairs) return mapiter.New(ch) } func (h *rsaPublicKey) Walk(ctx context.Context, visitor HeaderVisitor) error { return iter.WalkMap(ctx, h, visitor) } func (h *rsaPublicKey) AsMap(ctx context.Context) (map[string]interface{}, error) { return iter.AsMap(ctx, h) } type RSAPrivateKey interface { Key FromRaw(*rsa.PrivateKey) error D() []byte DP() []byte DQ() []byte E() []byte N() []byte P() []byte Q() []byte QI() []byte } 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]interface{} mu *sync.RWMutex dc json.DecodeCtx } var _ RSAPrivateKey = &rsaPrivateKey{} var _ Key = &rsaPrivateKey{} func newRSAPrivateKey() *rsaPrivateKey { return &rsaPrivateKey{ mu: &sync.RWMutex{}, privateParams: make(map[string]interface{}), } } func (h rsaPrivateKey) KeyType() jwa.KeyType { return jwa.RSA } func (h rsaPrivateKey) IsPrivate() bool { return true } func (h *rsaPrivateKey) Algorithm() jwa.KeyAlgorithm { if h.algorithm != nil { return *(h.algorithm) } return jwa.InvalidKeyAlgorithm("") } func (h *rsaPrivateKey) D() []byte { return h.d } func (h *rsaPrivateKey) DP() []byte { return h.dp } func (h *rsaPrivateKey) DQ() []byte { return h.dq } func (h *rsaPrivateKey) E() []byte { return h.e } func (h *rsaPrivateKey) KeyID() string { if h.keyID != nil { return *(h.keyID) } return "" } func (h *rsaPrivateKey) KeyOps() KeyOperationList { if h.keyOps != nil { return *(h.keyOps) } return nil } func (h *rsaPrivateKey) KeyUsage() string { if h.keyUsage != nil { return *(h.keyUsage) } return "" } func (h *rsaPrivateKey) N() []byte { return h.n } func (h *rsaPrivateKey) P() []byte { return h.p } func (h *rsaPrivateKey) Q() []byte { return h.q } func (h *rsaPrivateKey) QI() []byte { return h.qi } func (h *rsaPrivateKey) X509CertChain() *cert.Chain { return h.x509CertChain } func (h *rsaPrivateKey) X509CertThumbprint() string { if h.x509CertThumbprint != nil { return *(h.x509CertThumbprint) } return "" } func (h *rsaPrivateKey) X509CertThumbprintS256() string { if h.x509CertThumbprintS256 != nil { return *(h.x509CertThumbprintS256) } return "" } func (h *rsaPrivateKey) X509URL() string { if h.x509URL != nil { return *(h.x509URL) } return "" } func (h *rsaPrivateKey) makePairs() []*HeaderPair { h.mu.RLock() defer h.mu.RUnlock() var pairs []*HeaderPair pairs = append(pairs, &HeaderPair{Key: "kty", Value: jwa.RSA}) if h.algorithm != nil { pairs = append(pairs, &HeaderPair{Key: AlgorithmKey, Value: *(h.algorithm)}) } if h.d != nil { pairs = append(pairs, &HeaderPair{Key: RSADKey, Value: h.d}) } if h.dp != nil { pairs = append(pairs, &HeaderPair{Key: RSADPKey, Value: h.dp}) } if h.dq != nil { pairs = append(pairs, &HeaderPair{Key: RSADQKey, Value: h.dq}) } if h.e != nil { pairs = append(pairs, &HeaderPair{Key: RSAEKey, Value: h.e}) } if h.keyID != nil { pairs = append(pairs, &HeaderPair{Key: KeyIDKey, Value: *(h.keyID)}) } if h.keyOps != nil { pairs = append(pairs, &HeaderPair{Key: KeyOpsKey, Value: *(h.keyOps)}) } if h.keyUsage != nil { pairs = append(pairs, &HeaderPair{Key: KeyUsageKey, Value: *(h.keyUsage)}) } if h.n != nil { pairs = append(pairs, &HeaderPair{Key: RSANKey, Value: h.n}) } if h.p != nil { pairs = append(pairs, &HeaderPair{Key: RSAPKey, Value: h.p}) } if h.q != nil { pairs = append(pairs, &HeaderPair{Key: RSAQKey, Value: h.q}) } if h.qi != nil { pairs = append(pairs, &HeaderPair{Key: RSAQIKey, Value: h.qi}) } if h.x509CertChain != nil { pairs = append(pairs, &HeaderPair{Key: X509CertChainKey, Value: h.x509CertChain}) } if h.x509CertThumbprint != nil { pairs = append(pairs, &HeaderPair{Key: X509CertThumbprintKey, Value: *(h.x509CertThumbprint)}) } if h.x509CertThumbprintS256 != nil { pairs = append(pairs, &HeaderPair{Key: X509CertThumbprintS256Key, Value: *(h.x509CertThumbprintS256)}) } if h.x509URL != nil { pairs = append(pairs, &HeaderPair{Key: X509URLKey, Value: *(h.x509URL)}) } for k, v := range h.privateParams { pairs = append(pairs, &HeaderPair{Key: k, Value: v}) } return pairs } func (h *rsaPrivateKey) PrivateParams() map[string]interface{} { return h.privateParams } func (h *rsaPrivateKey) Get(name string) (interface{}, bool) { h.mu.RLock() defer h.mu.RUnlock() switch name { case KeyTypeKey: return h.KeyType(), true case AlgorithmKey: if h.algorithm == nil { return nil, false } return *(h.algorithm), true case RSADKey: if h.d == nil { return nil, false } return h.d, true case RSADPKey: if h.dp == nil { return nil, false } return h.dp, true case RSADQKey: if h.dq == nil { return nil, false } return h.dq, true case RSAEKey: if h.e == nil { return nil, false } return h.e, true case KeyIDKey: if h.keyID == nil { return nil, false } return *(h.keyID), true case KeyOpsKey: if h.keyOps == nil { return nil, false } return *(h.keyOps), true case KeyUsageKey: if h.keyUsage == nil { return nil, false } return *(h.keyUsage), true case RSANKey: if h.n == nil { return nil, false } return h.n, true case RSAPKey: if h.p == nil { return nil, false } return h.p, true case RSAQKey: if h.q == nil { return nil, false } return h.q, true case RSAQIKey: if h.qi == nil { return nil, false } return h.qi, true case X509CertChainKey: if h.x509CertChain == nil { return nil, false } return h.x509CertChain, true case X509CertThumbprintKey: if h.x509CertThumbprint == nil { return nil, false } return *(h.x509CertThumbprint), true case X509CertThumbprintS256Key: if h.x509CertThumbprintS256 == nil { return nil, false } return *(h.x509CertThumbprintS256), true case X509URLKey: if h.x509URL == nil { return nil, false } return *(h.x509URL), true default: v, ok := h.privateParams[name] return v, ok } } func (h *rsaPrivateKey) Set(name string, value interface{}) error { h.mu.Lock() defer h.mu.Unlock() return h.setNoLock(name, value) } func (h *rsaPrivateKey) setNoLock(name string, value interface{}) error { switch name { case "kty": return nil case AlgorithmKey: switch v := value.(type) { case string, jwa.SignatureAlgorithm, jwa.ContentEncryptionAlgorithm: var tmp = jwa.KeyAlgorithmFrom(v) h.algorithm = &tmp case fmt.Stringer: s := v.String() var tmp = jwa.KeyAlgorithmFrom(s) h.algorithm = &tmp default: return fmt.Errorf(`invalid type for %s 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]interface{}{} } 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) { return cloneKey(k) } 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 '{' or '}' here. if tok == '}' { // End of object break LOOP } else if tok != '{' { return fmt.Errorf(`expected '{', but got '%c'`, 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 := jwa.KeyAlgorithmFrom(s) 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]interface{}) fields := make([]string, 0, 16) for _, pair := range h.makePairs() { fields = append(fields, pair.Key.(string)) data[pair.Key.(string)] = pair.Value } sort.Strings(fields) buf := pool.GetBytesBuffer() defer pool.ReleaseBytesBuffer(buf) buf.WriteByte('{') enc := json.NewEncoder(buf) for i, f := range fields { if i > 0 { buf.WriteRune(',') } buf.WriteRune('"') buf.WriteString(f) buf.WriteString(`":`) v := data[f] switch v := v.(type) { case []byte: buf.WriteRune('"') buf.WriteString(base64.EncodeToString(v)) buf.WriteRune('"') 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('}') ret := make([]byte, buf.Len()) copy(ret, buf.Bytes()) return ret, nil } func (h *rsaPrivateKey) Iterate(ctx context.Context) HeaderIterator { pairs := h.makePairs() ch := make(chan *HeaderPair, len(pairs)) go func(ctx context.Context, ch chan *HeaderPair, pairs []*HeaderPair) { defer close(ch) for _, pair := range pairs { select { case <-ctx.Done(): return case ch <- pair: } } }(ctx, ch, pairs) return mapiter.New(ch) } func (h *rsaPrivateKey) Walk(ctx context.Context, visitor HeaderVisitor) error { return iter.WalkMap(ctx, h, visitor) } func (h *rsaPrivateKey) AsMap(ctx context.Context) (map[string]interface{}, error) { return iter.AsMap(ctx, h) } golang-github-lestrrat-go-jwx-2.1.4/jwk/set.go000066400000000000000000000151651476711647200212600ustar00rootroot00000000000000package jwk import ( "bytes" "context" "fmt" "sort" "github.com/lestrrat-go/iter/arrayiter" "github.com/lestrrat-go/iter/mapiter" "github.com/lestrrat-go/jwx/v2/internal/json" "github.com/lestrrat-go/jwx/v2/internal/pool" ) const keysKey = `keys` // appease linter // NewSet creates and empty `jwk.Set` object func NewSet() Set { return &set{ privateParams: make(map[string]interface{}), } } func (s *set) Set(n string, v interface{}) 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(n string) (interface{}, bool) { s.mu.RLock() defer s.mu.RUnlock() v, ok := s.privateParams[n] return v, ok } 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 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]interface{}) return nil } func (s *set) Keys(ctx context.Context) KeyIterator { ch := make(chan *KeyPair, s.Len()) go iterate(ctx, s.keys, ch) return arrayiter.New(ch) } func iterate(ctx context.Context, keys []Key, ch chan *KeyPair) { defer close(ch) for i, key := range keys { pair := &KeyPair{Index: i, Value: key} select { case <-ctx.Done(): return case ch <- pair: } } } func (s *set) MarshalJSON() ([]byte, error) { s.mu.RLock() defer s.mu.RUnlock() buf := pool.GetBytesBuffer() defer pool.ReleaseBytesBuffer(buf) enc := json.NewEncoder(buf) fields := []string{keysKey} for k := range s.privateParams { fields = append(fields, k) } sort.Strings(fields) buf.WriteByte('{') for i, field := range fields { if i > 0 { buf.WriteByte(',') } 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('[') for j, k := range s.keys { if j > 0 { buf.WriteByte(',') } if err := enc.Encode(k); err != nil { return nil, fmt.Errorf(`failed to marshal key #%d: %w`, i, err) } } buf.WriteByte(']') } } buf.WriteByte('}') 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]interface{}) 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 '{' or '}' here. if tok == '}' { // End of object break LOOP } else if tok != '{' { return fmt.Errorf(`expected '{', but got '%c'`, 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 interface{} 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() n := s.Len() for i := 0; i < n; i++ { key, ok := s.Key(i) if !ok { return nil, false } if key.KeyID() == 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 := &set{} s.mu.RLock() defer s.mu.RUnlock() s2.keys = make([]Key, len(s.keys)) copy(s2.keys, s.keys) return s2, nil } func (s *set) makePairs() []*HeaderPair { pairs := make([]*HeaderPair, 0, len(s.privateParams)) for k, v := range s.privateParams { pairs = append(pairs, &HeaderPair{Key: k, Value: v}) } sort.Slice(pairs, func(i, j int) bool { //nolint:forcetypeassert return pairs[i].Key.(string) < pairs[j].Key.(string) }) return pairs } func (s *set) Iterate(ctx context.Context) HeaderIterator { pairs := s.makePairs() ch := make(chan *HeaderPair, len(pairs)) go func(ctx context.Context, ch chan *HeaderPair, pairs []*HeaderPair) { defer close(ch) for _, pair := range pairs { select { case <-ctx.Done(): return case ch <- pair: } } }(ctx, ch, pairs) return mapiter.New(ch) } golang-github-lestrrat-go-jwx-2.1.4/jwk/set_test.go000066400000000000000000000025471476711647200223170ustar00rootroot00000000000000package jwk_test import ( "testing" "github.com/lestrrat-go/jwx/v2/internal/jwxtest" "github.com/lestrrat-go/jwx/v2/jwk" "github.com/stretchr/testify/assert" ) 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() if !assert.NoError(t, err, `key generation should succeed`) { return } if !assert.NoError(t, set.AddKey(k), `set.AddKey should succeed`) { return } keys = append(keys, k) } if !assert.Equal(t, set.Len(), 3, `set.Len should be 3`) { return } for i, k := range keys { if !assert.Equal(t, i, set.Index(k), `set.Index should return %d`, i) { return } } for _, k := range keys { if !assert.NoError(t, set.RemoveKey(k), `set.RemoveKey should succeed`) { return } } if !assert.Equal(t, set.Len(), 0, `set.Len should be 0`) { return } for _, gen := range keygens { k, err := gen() if !assert.NoError(t, err, `key generation should succeed`) { return } if !assert.NoError(t, set.AddKey(k), `set.Add should succeed`) { return } } if !assert.Equal(t, set.Len(), 3, `set.Len should be 3`) { return } set.Clear() if !assert.Equal(t, set.Len(), 0, `set.Len should be 0`) { return } } golang-github-lestrrat-go-jwx-2.1.4/jwk/symmetric.go000066400000000000000000000030351476711647200224720ustar00rootroot00000000000000package jwk import ( "crypto" "fmt" "github.com/lestrrat-go/blackmagic" "github.com/lestrrat-go/jwx/v2/internal/base64" ) func (k *symmetricKey) FromRaw(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 } // Raw returns the octets for this symmetric key. // Since this is a symmetric key, this just calls Octets func (k *symmetricKey) Raw(v interface{}) error { k.mu.RLock() defer k.mu.RUnlock() return blackmagic.AssignIfCompatible(v, k.octets) } // 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 := k.Raw(&octets); err != nil { return nil, fmt.Errorf(`failed to materialize 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 _, pair := range k.makePairs() { //nolint:forcetypeassert key := pair.Key.(string) if err := newKey.Set(key, pair.Value); err != nil { return nil, fmt.Errorf(`failed to set field %q: %w`, key, err) } } return newKey, nil } func (k *symmetricKey) Validate() error { if len(k.Octets()) == 0 { return NewKeyValidationError(fmt.Errorf(`jwk.SymmetricKey: missing "k" field`)) } return nil } golang-github-lestrrat-go-jwx-2.1.4/jwk/symmetric_gen.go000066400000000000000000000323171476711647200233300ustar00rootroot00000000000000// Code generated by tools/cmd/genjwk/main.go. DO NOT EDIT. package jwk import ( "bytes" "context" "fmt" "sort" "sync" "github.com/lestrrat-go/iter/mapiter" "github.com/lestrrat-go/jwx/v2/cert" "github.com/lestrrat-go/jwx/v2/internal/base64" "github.com/lestrrat-go/jwx/v2/internal/iter" "github.com/lestrrat-go/jwx/v2/internal/json" "github.com/lestrrat-go/jwx/v2/internal/pool" "github.com/lestrrat-go/jwx/v2/jwa" ) const ( SymmetricOctetsKey = "k" ) type SymmetricKey interface { Key FromRaw([]byte) error Octets() []byte } 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]interface{} mu *sync.RWMutex dc json.DecodeCtx } var _ SymmetricKey = &symmetricKey{} var _ Key = &symmetricKey{} func newSymmetricKey() *symmetricKey { return &symmetricKey{ mu: &sync.RWMutex{}, privateParams: make(map[string]interface{}), } } func (h symmetricKey) KeyType() jwa.KeyType { return jwa.OctetSeq } func (h *symmetricKey) Algorithm() jwa.KeyAlgorithm { if h.algorithm != nil { return *(h.algorithm) } return jwa.InvalidKeyAlgorithm("") } func (h *symmetricKey) KeyID() string { if h.keyID != nil { return *(h.keyID) } return "" } func (h *symmetricKey) KeyOps() KeyOperationList { if h.keyOps != nil { return *(h.keyOps) } return nil } func (h *symmetricKey) KeyUsage() string { if h.keyUsage != nil { return *(h.keyUsage) } return "" } func (h *symmetricKey) Octets() []byte { return h.octets } func (h *symmetricKey) X509CertChain() *cert.Chain { return h.x509CertChain } func (h *symmetricKey) X509CertThumbprint() string { if h.x509CertThumbprint != nil { return *(h.x509CertThumbprint) } return "" } func (h *symmetricKey) X509CertThumbprintS256() string { if h.x509CertThumbprintS256 != nil { return *(h.x509CertThumbprintS256) } return "" } func (h *symmetricKey) X509URL() string { if h.x509URL != nil { return *(h.x509URL) } return "" } func (h *symmetricKey) makePairs() []*HeaderPair { h.mu.RLock() defer h.mu.RUnlock() var pairs []*HeaderPair pairs = append(pairs, &HeaderPair{Key: "kty", Value: jwa.OctetSeq}) if h.algorithm != nil { pairs = append(pairs, &HeaderPair{Key: AlgorithmKey, Value: *(h.algorithm)}) } if h.keyID != nil { pairs = append(pairs, &HeaderPair{Key: KeyIDKey, Value: *(h.keyID)}) } if h.keyOps != nil { pairs = append(pairs, &HeaderPair{Key: KeyOpsKey, Value: *(h.keyOps)}) } if h.keyUsage != nil { pairs = append(pairs, &HeaderPair{Key: KeyUsageKey, Value: *(h.keyUsage)}) } if h.octets != nil { pairs = append(pairs, &HeaderPair{Key: SymmetricOctetsKey, Value: h.octets}) } if h.x509CertChain != nil { pairs = append(pairs, &HeaderPair{Key: X509CertChainKey, Value: h.x509CertChain}) } if h.x509CertThumbprint != nil { pairs = append(pairs, &HeaderPair{Key: X509CertThumbprintKey, Value: *(h.x509CertThumbprint)}) } if h.x509CertThumbprintS256 != nil { pairs = append(pairs, &HeaderPair{Key: X509CertThumbprintS256Key, Value: *(h.x509CertThumbprintS256)}) } if h.x509URL != nil { pairs = append(pairs, &HeaderPair{Key: X509URLKey, Value: *(h.x509URL)}) } for k, v := range h.privateParams { pairs = append(pairs, &HeaderPair{Key: k, Value: v}) } return pairs } func (h *symmetricKey) PrivateParams() map[string]interface{} { return h.privateParams } func (h *symmetricKey) Get(name string) (interface{}, bool) { h.mu.RLock() defer h.mu.RUnlock() switch name { case KeyTypeKey: return h.KeyType(), true case AlgorithmKey: if h.algorithm == nil { return nil, false } return *(h.algorithm), true case KeyIDKey: if h.keyID == nil { return nil, false } return *(h.keyID), true case KeyOpsKey: if h.keyOps == nil { return nil, false } return *(h.keyOps), true case KeyUsageKey: if h.keyUsage == nil { return nil, false } return *(h.keyUsage), true case SymmetricOctetsKey: if h.octets == nil { return nil, false } return h.octets, true case X509CertChainKey: if h.x509CertChain == nil { return nil, false } return h.x509CertChain, true case X509CertThumbprintKey: if h.x509CertThumbprint == nil { return nil, false } return *(h.x509CertThumbprint), true case X509CertThumbprintS256Key: if h.x509CertThumbprintS256 == nil { return nil, false } return *(h.x509CertThumbprintS256), true case X509URLKey: if h.x509URL == nil { return nil, false } return *(h.x509URL), true default: v, ok := h.privateParams[name] return v, ok } } func (h *symmetricKey) Set(name string, value interface{}) error { h.mu.Lock() defer h.mu.Unlock() return h.setNoLock(name, value) } func (h *symmetricKey) setNoLock(name string, value interface{}) error { switch name { case "kty": return nil case AlgorithmKey: switch v := value.(type) { case string, jwa.SignatureAlgorithm, jwa.ContentEncryptionAlgorithm: var tmp = jwa.KeyAlgorithmFrom(v) h.algorithm = &tmp case fmt.Stringer: s := v.String() var tmp = jwa.KeyAlgorithmFrom(s) h.algorithm = &tmp default: return fmt.Errorf(`invalid type for %s 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]interface{}{} } 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) { return cloneKey(k) } 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 '{' or '}' here. if tok == '}' { // End of object break LOOP } else if tok != '{' { return fmt.Errorf(`expected '{', but got '%c'`, 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 := jwa.KeyAlgorithmFrom(s) 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]interface{}) fields := make([]string, 0, 9) for _, pair := range h.makePairs() { fields = append(fields, pair.Key.(string)) data[pair.Key.(string)] = pair.Value } sort.Strings(fields) buf := pool.GetBytesBuffer() defer pool.ReleaseBytesBuffer(buf) buf.WriteByte('{') enc := json.NewEncoder(buf) for i, f := range fields { if i > 0 { buf.WriteRune(',') } buf.WriteRune('"') buf.WriteString(f) buf.WriteString(`":`) v := data[f] switch v := v.(type) { case []byte: buf.WriteRune('"') buf.WriteString(base64.EncodeToString(v)) buf.WriteRune('"') 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('}') ret := make([]byte, buf.Len()) copy(ret, buf.Bytes()) return ret, nil } func (h *symmetricKey) Iterate(ctx context.Context) HeaderIterator { pairs := h.makePairs() ch := make(chan *HeaderPair, len(pairs)) go func(ctx context.Context, ch chan *HeaderPair, pairs []*HeaderPair) { defer close(ch) for _, pair := range pairs { select { case <-ctx.Done(): return case ch <- pair: } } }(ctx, ch, pairs) return mapiter.New(ch) } func (h *symmetricKey) Walk(ctx context.Context, visitor HeaderVisitor) error { return iter.WalkMap(ctx, h, visitor) } func (h *symmetricKey) AsMap(ctx context.Context) (map[string]interface{}, error) { return iter.AsMap(ctx, h) } golang-github-lestrrat-go-jwx-2.1.4/jwk/testdata/000077500000000000000000000000001476711647200217375ustar00rootroot00000000000000golang-github-lestrrat-go-jwx-2.1.4/jwk/testdata/rs256.jwk000066400000000000000000000031711476711647200233370ustar00rootroot00000000000000{"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-2.1.4/jwk/usage.go000066400000000000000000000011041476711647200215550ustar00rootroot00000000000000package jwk import "fmt" func (k KeyUsageType) String() string { return string(k) } func (k *KeyUsageType) Accept(v interface{}) error { switch v := v.(type) { case KeyUsageType: switch v { case ForSignature, ForEncryption: *k = v return nil default: return fmt.Errorf("invalid key usage type %s", v) } case string: switch v { case ForSignature.String(), ForEncryption.String(): *k = KeyUsageType(v) return nil default: return fmt.Errorf("invalid key usage type %s", v) } } return fmt.Errorf("invalid value for key usage type %s", v) } golang-github-lestrrat-go-jwx-2.1.4/jwk/whitelist.go000066400000000000000000000035341476711647200224760ustar00rootroot00000000000000package jwk import "regexp" // InsecureWhitelist allows any URLs to be fetched. This is the default // behavior of `jwk.Fetch()`, but this exists to allow other libraries // (such as jws, via jws.VerifyAuto) and users to be able to explicitly // state that they intend to not check the URLs that are being fetched type InsecureWhitelist struct{} func (InsecureWhitelist) IsAllowed(string) bool { return true } // RegexpWhitelist is a jwk.Whitelist object comprised of a list of *regexp.Regexp // objects. All entries in the list are tried until one matches. If none of the // *regexp.Regexp objects match, then the URL is deemed unallowed. type RegexpWhitelist struct { patterns []*regexp.Regexp } func NewRegexpWhitelist() *RegexpWhitelist { return &RegexpWhitelist{} } func (w *RegexpWhitelist) Add(pat *regexp.Regexp) *RegexpWhitelist { w.patterns = append(w.patterns, pat) return w } // IsAllowed returns true if any of the patterns in the whitelist // returns true. func (w *RegexpWhitelist) IsAllowed(u string) bool { for _, pat := range w.patterns { if pat.MatchString(u) { return true } } return false } // MapWhitelist is a jwk.Whitelist object comprised of a map of strings. // If the URL exists in the map, then the URL is allowed to be fetched. type MapWhitelist struct { store map[string]struct{} } func NewMapWhitelist() *MapWhitelist { return &MapWhitelist{store: make(map[string]struct{})} } func (w *MapWhitelist) Add(pat string) *MapWhitelist { w.store[pat] = struct{}{} return w } func (w *MapWhitelist) IsAllowed(u string) bool { _, b := w.store[u] return b } // WhitelistFunc is a jwk.Whitelist object based on a function. // You can perform any sort of check against the given URL to determine // if it can be fetched or not. type WhitelistFunc func(string) bool func (w WhitelistFunc) IsAllowed(u string) bool { return w(u) } golang-github-lestrrat-go-jwx-2.1.4/jwk/x5c_test.go000066400000000000000000000123351476711647200222170ustar00rootroot00000000000000package jwk_test import ( "testing" "github.com/lestrrat-go/jwx/v2/cert" "github.com/lestrrat-go/jwx/v2/internal/json" "github.com/stretchr/testify/assert" ) 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) if !assert.NoError(t, err, `json.Marshal should succeed`) { return } // Take the input, and create a json jsonbuf, err := json.Marshal(certs) if !assert.NoError(t, err, `json.Marshal should succeed (for input)`) { return } var c cert.Chain if !assert.NoError(t, json.Unmarshal(jsonbuf, &c), `json.Unmarshal should succeed`) { return } if !assert.Equal(t, c.Len(), 3, `should have three certs`) { return } buf, err := json.Marshal(c) if !assert.NoError(t, err, `json.Marshal should succeed`) { return } if !assert.Equal(t, expected, buf, `json output should match`) { return } }) } golang-github-lestrrat-go-jwx-2.1.4/jws/000077500000000000000000000000001476711647200201365ustar00rootroot00000000000000golang-github-lestrrat-go-jwx-2.1.4/jws/BUILD.bazel000066400000000000000000000032261476711647200220170ustar00rootroot00000000000000load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") go_library( name = "jws", srcs = [ "ecdsa.go", "eddsa.go", "headers.go", "headers_gen.go", "hmac.go", "interface.go", "io.go", "jws.go", "key_provider.go", "message.go", "options.go", "options_gen.go", "rsa.go", "signer.go", "verifier.go", ], importpath = "github.com/lestrrat-go/jwx/v2/jws", visibility = ["//visibility:public"], deps = [ "//cert", "//internal/base64", "//internal/ecutil", "//internal/iter", "//internal/json", "//internal/keyconv", "//internal/pool", "//jwa", "//jwk", "//x25519", "@com_github_lestrrat_go_blackmagic//:go_default_library", "@com_github_lestrrat_go_iter//mapiter:go_default_library", "@com_github_lestrrat_go_option//:option", ], ) go_test( name = "jws_test", srcs = [ "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", "//x25519", "@com_github_lestrrat_go_httprc//:go_default_library", "@com_github_stretchr_testify//assert", "@com_github_stretchr_testify//require", ], ) alias( name = "go_default_library", actual = ":jws", visibility = ["//visibility:public"], ) golang-github-lestrrat-go-jwx-2.1.4/jws/README.md000066400000000000000000000076261476711647200214300ustar00rootroot00000000000000# JWS [![Go Reference](https://pkg.go.dev/badge/github.com/lestrrat-go/jwx/v2/jws.svg)](https://pkg.go.dev/github.com/lestrrat-go/jwx/v2/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/v2/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/v2/jwa" "github.com/lestrrat-go/jwx/v2/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-2.1.4/jws/ecdsa.go000066400000000000000000000122411476711647200215440ustar00rootroot00000000000000package jws import ( "crypto" "crypto/ecdsa" "crypto/rand" "encoding/asn1" "fmt" "math/big" "github.com/lestrrat-go/jwx/v2/internal/ecutil" "github.com/lestrrat-go/jwx/v2/internal/keyconv" "github.com/lestrrat-go/jwx/v2/internal/pool" "github.com/lestrrat-go/jwx/v2/jwa" ) var ecdsaSigners map[jwa.SignatureAlgorithm]*ecdsaSigner var ecdsaVerifiers 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, } ecdsaSigners = make(map[jwa.SignatureAlgorithm]*ecdsaSigner) ecdsaVerifiers = make(map[jwa.SignatureAlgorithm]*ecdsaVerifier) 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 interface{}) ([]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 !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 (it's 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 interface{}) 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.GetBigInt() s := pool.GetBigInt() defer pool.ReleaseBigInt(r) defer pool.ReleaseBigInt(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-2.1.4/jws/eddsa.go000066400000000000000000000037131476711647200215510ustar00rootroot00000000000000package jws import ( "crypto" "crypto/ed25519" "crypto/rand" "fmt" "github.com/lestrrat-go/jwx/v2/internal/keyconv" "github.com/lestrrat-go/jwx/v2/jwa" ) type eddsaSigner struct{} func newEdDSASigner() Signer { return &eddsaSigner{} } func (s eddsaSigner) Algorithm() jwa.SignatureAlgorithm { return jwa.EdDSA } func (s eddsaSigner) Sign(payload []byte, key interface{}) ([]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 !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 interface{}) (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-2.1.4/jws/es256k.go000066400000000000000000000002461476711647200215060ustar00rootroot00000000000000//go:build jwx_es256k // +build jwx_es256k package jws import ( "github.com/lestrrat-go/jwx/v2/jwa" ) func init() { addAlgorithmForKeyType(jwa.EC, jwa.ES256K) } golang-github-lestrrat-go-jwx-2.1.4/jws/es256k_test.go000066400000000000000000000013461476711647200225470ustar00rootroot00000000000000//go:build jwx_es256k // +build jwx_es256k package jws_test import ( "testing" "github.com/lestrrat-go/jwx/v2/internal/jwxtest" "github.com/lestrrat-go/jwx/v2/jwa" "github.com/lestrrat-go/jwx/v2/jwk" "github.com/stretchr/testify/assert" ) func init() { hasES256K = true } func TestES256K(t *testing.T) { payload := []byte("Hello, World!") t.Parallel() key, err := jwxtest.GenerateEcdsaKey(jwa.Secp256k1) if !assert.NoError(t, err, "ECDSA key generated") { return } jwkKey, _ := jwk.FromRaw(key.PublicKey) keys := map[string]interface{}{ "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-2.1.4/jws/headers.go000066400000000000000000000034631476711647200221060ustar00rootroot00000000000000package jws import ( "context" "fmt" "github.com/lestrrat-go/iter/mapiter" "github.com/lestrrat-go/jwx/v2/internal/iter" ) // Iterate returns a channel that successively returns all the // header name and values. func (h *stdHeaders) Iterate(ctx context.Context) Iterator { pairs := h.makePairs() ch := make(chan *HeaderPair, len(pairs)) go func(ctx context.Context, ch chan *HeaderPair, pairs []*HeaderPair) { defer close(ch) for _, pair := range pairs { select { case <-ctx.Done(): return case ch <- pair: } } }(ctx, ch, pairs) return mapiter.New(ch) } func (h *stdHeaders) Walk(ctx context.Context, visitor Visitor) error { return iter.WalkMap(ctx, h, visitor) } func (h *stdHeaders) AsMap(ctx context.Context) (map[string]interface{}, error) { return iter.AsMap(ctx, h) } func (h *stdHeaders) Copy(_ context.Context, dst Headers) error { for _, pair := range h.makePairs() { //nolint:forcetypeassert key := pair.Key.(string) if err := dst.Set(key, pair.Value); err != nil { return fmt.Errorf(`failed to set header %q: %w`, key, 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(ctx context.Context, h1, h2 Headers) (Headers, error) { h3 := NewHeaders() if h1 != nil { if err := h1.Copy(ctx, h3); err != nil { return nil, fmt.Errorf(`failed to copy headers from first Header: %w`, err) } } if h2 != nil { if err := h2.Copy(ctx, h3); err != nil { return nil, fmt.Errorf(`failed to copy headers from second Header: %w`, err) } } return h3, nil } func (h *stdHeaders) Merge(ctx context.Context, h2 Headers) (Headers, error) { return mergeHeaders(ctx, h, h2) } golang-github-lestrrat-go-jwx-2.1.4/jws/headers_gen.go000066400000000000000000000351211476711647200227330ustar00rootroot00000000000000// Code generated by tools/cmd/genjws/main.go. DO NOT EDIT. package jws import ( "bytes" "context" "fmt" "sort" "sync" "github.com/lestrrat-go/jwx/v2/cert" "github.com/lestrrat-go/jwx/v2/internal/base64" "github.com/lestrrat-go/jwx/v2/internal/json" "github.com/lestrrat-go/jwx/v2/internal/pool" "github.com/lestrrat-go/jwx/v2/jwa" "github.com/lestrrat-go/jwx/v2/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 Header set. type Headers interface { json.Marshaler json.Unmarshaler Algorithm() jwa.SignatureAlgorithm ContentType() string Critical() []string JWK() jwk.Key JWKSetURL() string KeyID() string Type() string X509CertChain() *cert.Chain X509CertThumbprint() string X509CertThumbprintS256() string X509URL() string Iterate(ctx context.Context) Iterator Walk(context.Context, Visitor) error AsMap(context.Context) (map[string]interface{}, error) Copy(context.Context, Headers) error Merge(context.Context, Headers) (Headers, error) Get(string) (interface{}, bool) Set(string, interface{}) error Remove(string) error // PrivateParams returns the non-standard elements in the source structure // WARNING: DO NOT USE PrivateParams() IF YOU HAVE CONCURRENT CODE ACCESSING THEM. // Use AsMap() to get a copy of the entire header instead PrivateParams() map[string]interface{} } 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]interface{} 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 { h.mu.RLock() defer h.mu.RUnlock() if h.algorithm == nil { return "" } return *(h.algorithm) } func (h *stdHeaders) ContentType() string { h.mu.RLock() defer h.mu.RUnlock() if h.contentType == nil { return "" } return *(h.contentType) } func (h *stdHeaders) Critical() []string { h.mu.RLock() defer h.mu.RUnlock() return h.critical } func (h *stdHeaders) JWK() jwk.Key { h.mu.RLock() defer h.mu.RUnlock() return h.jwk } func (h *stdHeaders) JWKSetURL() string { h.mu.RLock() defer h.mu.RUnlock() if h.jwkSetURL == nil { return "" } return *(h.jwkSetURL) } func (h *stdHeaders) KeyID() string { h.mu.RLock() defer h.mu.RUnlock() if h.keyID == nil { return "" } return *(h.keyID) } func (h *stdHeaders) Type() string { h.mu.RLock() defer h.mu.RUnlock() if h.typ == nil { return "" } return *(h.typ) } func (h *stdHeaders) X509CertChain() *cert.Chain { h.mu.RLock() defer h.mu.RUnlock() return h.x509CertChain } func (h *stdHeaders) X509CertThumbprint() string { h.mu.RLock() defer h.mu.RUnlock() if h.x509CertThumbprint == nil { return "" } return *(h.x509CertThumbprint) } func (h *stdHeaders) X509CertThumbprintS256() string { h.mu.RLock() defer h.mu.RUnlock() if h.x509CertThumbprintS256 == nil { return "" } return *(h.x509CertThumbprintS256) } func (h *stdHeaders) X509URL() string { h.mu.RLock() defer h.mu.RUnlock() if h.x509URL == nil { return "" } return *(h.x509URL) } 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) makePairs() []*HeaderPair { h.mu.RLock() defer h.mu.RUnlock() var pairs []*HeaderPair if h.algorithm != nil { pairs = append(pairs, &HeaderPair{Key: AlgorithmKey, Value: *(h.algorithm)}) } if h.contentType != nil { pairs = append(pairs, &HeaderPair{Key: ContentTypeKey, Value: *(h.contentType)}) } if h.critical != nil { pairs = append(pairs, &HeaderPair{Key: CriticalKey, Value: h.critical}) } if h.jwk != nil { pairs = append(pairs, &HeaderPair{Key: JWKKey, Value: h.jwk}) } if h.jwkSetURL != nil { pairs = append(pairs, &HeaderPair{Key: JWKSetURLKey, Value: *(h.jwkSetURL)}) } if h.keyID != nil { pairs = append(pairs, &HeaderPair{Key: KeyIDKey, Value: *(h.keyID)}) } if h.typ != nil { pairs = append(pairs, &HeaderPair{Key: TypeKey, Value: *(h.typ)}) } if h.x509CertChain != nil { pairs = append(pairs, &HeaderPair{Key: X509CertChainKey, Value: h.x509CertChain}) } if h.x509CertThumbprint != nil { pairs = append(pairs, &HeaderPair{Key: X509CertThumbprintKey, Value: *(h.x509CertThumbprint)}) } if h.x509CertThumbprintS256 != nil { pairs = append(pairs, &HeaderPair{Key: X509CertThumbprintS256Key, Value: *(h.x509CertThumbprintS256)}) } if h.x509URL != nil { pairs = append(pairs, &HeaderPair{Key: X509URLKey, Value: *(h.x509URL)}) } for k, v := range h.privateParams { pairs = append(pairs, &HeaderPair{Key: k, Value: v}) } sort.Slice(pairs, func(i, j int) bool { return pairs[i].Key.(string) < pairs[j].Key.(string) }) return pairs } func (h *stdHeaders) PrivateParams() map[string]interface{} { h.mu.RLock() defer h.mu.RUnlock() return h.privateParams } func (h *stdHeaders) Get(name string) (interface{}, bool) { h.mu.RLock() defer h.mu.RUnlock() switch name { case AlgorithmKey: if h.algorithm == nil { return nil, false } return *(h.algorithm), true case ContentTypeKey: if h.contentType == nil { return nil, false } return *(h.contentType), true case CriticalKey: if h.critical == nil { return nil, false } return h.critical, true case JWKKey: if h.jwk == nil { return nil, false } return h.jwk, true case JWKSetURLKey: if h.jwkSetURL == nil { return nil, false } return *(h.jwkSetURL), true case KeyIDKey: if h.keyID == nil { return nil, false } return *(h.keyID), true case TypeKey: if h.typ == nil { return nil, false } return *(h.typ), true case X509CertChainKey: if h.x509CertChain == nil { return nil, false } return h.x509CertChain, true case X509CertThumbprintKey: if h.x509CertThumbprint == nil { return nil, false } return *(h.x509CertThumbprint), true case X509CertThumbprintS256Key: if h.x509CertThumbprintS256 == nil { return nil, false } return *(h.x509CertThumbprintS256), true case X509URLKey: if h.x509URL == nil { return nil, false } return *(h.x509URL), true default: v, ok := h.privateParams[name] return v, ok } } func (h *stdHeaders) Set(name string, value interface{}) error { h.mu.Lock() defer h.mu.Unlock() return h.setNoLock(name, value) } func (h *stdHeaders) setNoLock(name string, value interface{}) error { switch name { case AlgorithmKey: var acceptor jwa.SignatureAlgorithm if err := acceptor.Accept(value); err != nil { return fmt.Errorf(`invalid value for %s key: %w`, AlgorithmKey, err) } h.algorithm = &acceptor return nil 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]interface{}{} } 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 '{' or '}' here. if tok == '}' { // End of object break LOOP } else if tok != '{' { return fmt.Errorf(`expected '{', but got '%c'`, 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) MarshalJSON() ([]byte, error) { buf := pool.GetBytesBuffer() defer pool.ReleaseBytesBuffer(buf) buf.WriteByte('{') enc := json.NewEncoder(buf) for i, p := range h.makePairs() { if i > 0 { buf.WriteRune(',') } buf.WriteRune('"') buf.WriteString(p.Key.(string)) buf.WriteString(`":`) v := p.Value switch v := v.(type) { case []byte: buf.WriteRune('"') buf.WriteString(base64.EncodeToString(v)) buf.WriteRune('"') default: if err := enc.Encode(v); err != nil { return nil, fmt.Errorf(`failed to encode value for field %s: %w`, p.Key, err) } buf.Truncate(buf.Len() - 1) } } buf.WriteByte('}') ret := make([]byte, buf.Len()) copy(ret, buf.Bytes()) return ret, nil } golang-github-lestrrat-go-jwx-2.1.4/jws/headers_test.go000066400000000000000000000232171476711647200231440ustar00rootroot00000000000000package jws_test import ( "context" "reflect" "testing" "github.com/lestrrat-go/jwx/v2/cert" "github.com/lestrrat-go/jwx/v2/jwa" "github.com/lestrrat-go/jwx/v2/jwk" "github.com/lestrrat-go/jwx/v2/jws" "github.com/stretchr/testify/assert" ) 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)) if !assert.NoError(t, err, `jwk.ParseKey should succeed`) { return } 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 interface{} Expected interface{} 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 { if !assert.NoError(t, base.Set(tc.Key, tc.Value), "Headers.Set should succeed") { return } } }) t.Run("Set/Get", func(t *testing.T) { h := jws.NewHeaders() ctx := context.Background() for iter := base.Iterate(ctx); iter.Next(ctx); { pair := iter.Pair() if !assert.NoError(t, h.Set(pair.Key.(string), pair.Value), `h.Set should be successful`) { return } } for _, tc := range data { var values []interface{} viaGet, ok := h.Get(tc.Key) if !assert.True(t, ok, "value for %s should exist", tc.Key) { return } values = append(values, viaGet) if method := tc.Method; method != "" { m := reflect.ValueOf(h).MethodByName(method) if !assert.NotEqual(t, m, zeroval, "method %s should be available", method) { return } ret := m.Call(nil) if !assert.Len(t, ret, 1, `should get exactly 1 value as return value`) { return } values = append(values, ret[0].Interface()) } expected := tc.Expected if expected == nil { expected = tc.Value } for i, got := range values { if !assert.Equal(t, expected, got, "value %d should match", i) { return } } } }) t.Run("PrivateParams", func(t *testing.T) { h := base pp, err := h.AsMap(context.Background()) if !assert.NoError(t, err, `h.AsMap should succeed`) { return } v, ok := pp["private"] if !assert.True(t, ok, "key 'private' should exists") { return } if !assert.Equal(t, v, "boofoo", "value for 'private' should match") { return } }) t.Run("Iterator", func(t *testing.T) { expected := map[string]interface{}{} for _, tc := range data { v := tc.Value if expected := tc.Expected; expected != nil { v = expected } expected[tc.Key] = v } v := base t.Run("Iterate", func(t *testing.T) { seen := make(map[string]interface{}) for iter := v.Iterate(context.TODO()); iter.Next(context.TODO()); { pair := iter.Pair() seen[pair.Key.(string)] = pair.Value getV, ok := v.Get(pair.Key.(string)) if !assert.True(t, ok, `v.Get should succeed for key %#v`, pair.Key) { return } if !assert.Equal(t, pair.Value, getV, `pair.Value should match value from v.Get()`) { return } } if !assert.Equal(t, expected, seen, `values should match`) { return } }) t.Run("Walk", func(t *testing.T) { seen := make(map[string]interface{}) v.Walk(context.TODO(), jwk.HeaderVisitorFunc(func(key string, value interface{}) error { seen[key] = value return nil })) if !assert.Equal(t, expected, seen, `values should match`) { return } }) t.Run("AsMap", func(t *testing.T) { m, err := v.AsMap(context.TODO()) if !assert.NoError(t, err, `v.AsMap should succeed`) { return } if !assert.Equal(t, expected, m, `values should match`) { return } }) t.Run("Remove", func(t *testing.T) { h := base for iter := h.Iterate(context.TODO()); iter.Next(context.TODO()); { pair := iter.Pair() h.Remove(pair.Key.(string)) } m, err := h.AsMap(context.TODO()) if !assert.NoError(t, err, `h.AsMap should succeed`) { return } if !assert.Len(t, m, 0, `len should be zero`) { return } }) }) } golang-github-lestrrat-go-jwx-2.1.4/jws/hmac.go000066400000000000000000000034241476711647200214000ustar00rootroot00000000000000package jws import ( "crypto/hmac" "crypto/sha256" "crypto/sha512" "fmt" "hash" "github.com/lestrrat-go/jwx/v2/internal/keyconv" "github.com/lestrrat-go/jwx/v2/jwa" ) var hmacSignFuncs = map[jwa.SignatureAlgorithm]hmacSignFunc{} 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) } } 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 interface{}) ([]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 interface{}) (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-2.1.4/jws/interface.go000066400000000000000000000071271476711647200224340ustar00rootroot00000000000000package jws import ( "github.com/lestrrat-go/iter/mapiter" "github.com/lestrrat-go/jwx/v2/internal/iter" "github.com/lestrrat-go/jwx/v2/jwa" ) 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 { dc DecodeCtx headers Headers // Unprotected Headers protected Headers // Protected Headers signature []byte // Signature detached bool } type Visitor = iter.MapVisitor type VisitorFunc = iter.MapVisitorFunc type HeaderPair = mapiter.Pair type Iterator = mapiter.Iterator // Signer generates the signature for a given payload. 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, interface{}) ([]byte, error) Algorithm() jwa.SignatureAlgorithm } type hmacSignFunc func([]byte, []byte) ([]byte, error) // HMACSigner uses crypto/hmac to sign the payloads. type HMACSigner struct { alg jwa.SignatureAlgorithm sign hmacSignFunc } 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 interface{}) error } type HMACVerifier struct { signer Signer } golang-github-lestrrat-go-jwx-2.1.4/jws/io.go000066400000000000000000000010251476711647200210720ustar00rootroot00000000000000// Code generated by tools/cmd/genreadfile/main.go. DO NOT EDIT. package jws import ( "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{}: srcFS = option.Value().(fs.FS) } } f, err := srcFS.Open(path) if err != nil { return nil, err } defer f.Close() return ParseReader(f) } golang-github-lestrrat-go-jwx-2.1.4/jws/jws.go000066400000000000000000000646241476711647200213040ustar00rootroot00000000000000//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. package jws import ( "bufio" "bytes" "context" "crypto/ecdsa" "crypto/ed25519" "crypto/rsa" "errors" "fmt" "io" "reflect" "strings" "sync" "unicode" "unicode/utf8" "github.com/lestrrat-go/blackmagic" "github.com/lestrrat-go/jwx/v2/internal/base64" "github.com/lestrrat-go/jwx/v2/internal/json" "github.com/lestrrat-go/jwx/v2/internal/pool" "github.com/lestrrat-go/jwx/v2/jwa" "github.com/lestrrat-go/jwx/v2/jwk" "github.com/lestrrat-go/jwx/v2/x25519" ) var registry = json.NewRegistry() type payloadSigner struct { signer Signer key interface{} protected Headers public Headers } func (s *payloadSigner) Sign(payload []byte) ([]byte, error) { return s.signer.Sign(payload, s.key) } func (s *payloadSigner) Algorithm() jwa.SignatureAlgorithm { return s.signer.Algorithm() } func (s *payloadSigner) ProtectedHeader() Headers { return s.protected } func (s *payloadSigner) PublicHeader() Headers { return s.public } var signers = make(map[jwa.SignatureAlgorithm]Signer) var muSigner = &sync.Mutex{} func removeSigner(alg jwa.SignatureAlgorithm) { muSigner.Lock() defer muSigner.Unlock() delete(signers, alg) } func makeSigner(alg jwa.SignatureAlgorithm, key interface{}, public, protected Headers) (*payloadSigner, error) { muSigner.Lock() signer, ok := signers[alg] if !ok { v, err := NewSigner(alg) if err != nil { muSigner.Unlock() return nil, fmt.Errorf(`failed to create payload signer: %w`, err) } signers[alg] = v signer = v } muSigner.Unlock() return &payloadSigner{ signer: signer, key: key, public: public, protected: protected, }, nil } const ( fmtInvalid = 1 << iota fmtCompact fmtJSON fmtJSONPretty fmtMax ) // silence linters var _ = fmtInvalid var _ = fmtMax func validateKeyBeforeUse(key interface{}) error { jwkKey, ok := key.(jwk.Key) if !ok { converted, err := jwk.FromRaw(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. func Sign(payload []byte, options ...SignOption) ([]byte, error) { format := fmtCompact var signers []*payloadSigner var detached bool var noneSignature *payloadSigner var validateKey bool for _, option := range options { //nolint:forcetypeassert switch option.Ident() { case identSerialization{}: format = option.Value().(int) case identInsecureNoSignature{}: data := option.Value().(*withInsecureNoSignature) // only the last one is used (we overwrite previous values) noneSignature = &payloadSigner{ signer: noneSigner{}, protected: data.protected, } case identKey{}: data := option.Value().(*withKey) alg, ok := data.alg.(jwa.SignatureAlgorithm) if !ok { return nil, fmt.Errorf(`jws.Sign: 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 nil, fmt.Errorf(`jws.Sign: "none" (jwa.NoSignature) cannot be used with jws.WithKey`) } signer, err := makeSigner(alg, data.key, data.public, data.protected) if err != nil { return nil, fmt.Errorf(`jws.Sign: failed to create signer: %w`, err) } signers = append(signers, signer) case identDetachedPayload{}: detached = true if payload != nil { return nil, fmt.Errorf(`jws.Sign: payload must be nil when jws.WithDetachedPayload() is specified`) } payload = option.Value().([]byte) case identValidateKey{}: validateKey = option.Value().(bool) } } if noneSignature != nil { signers = append(signers, noneSignature) } lsigner := len(signers) if lsigner == 0 { return nil, fmt.Errorf(`jws.Sign: no signers available. Specify an alogirthm and akey 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 format == fmtCompact && lsigner != 1 { return nil, fmt.Errorf(`jws.Sign: 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 result.payload = payload result.signatures = make([]*Signature, 0, len(signers)) for i, signer := range signers { protected := signer.ProtectedHeader() if protected == nil { protected = NewHeaders() } if err := protected.Set(AlgorithmKey, signer.Algorithm()); err != nil { return nil, fmt.Errorf(`failed to set "alg" header: %w`, err) } if key, ok := signer.key.(jwk.Key); ok { if kid := key.KeyID(); kid != "" { if err := protected.Set(KeyIDKey, kid); err != nil { return nil, fmt.Errorf(`failed to set "kid" header: %w`, err) } } } sig := &Signature{ headers: signer.PublicHeader(), protected: protected, // cheat. FIXXXXXXMEEEEEE detached: detached, } if validateKey { if err := validateKeyBeforeUse(signer.key); err != nil { return nil, fmt.Errorf(`jws.Verify: %w`, err) } } _, _, err := sig.Sign(payload, signer.signer, signer.key) if err != nil { return nil, fmt.Errorf(`failed to generate signature for signer #%d (alg=%s): %w`, i, signer.Algorithm(), err) } result.signatures = append(result.signatures, sig) } switch 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 detached { compactOpts = append(compactOpts, WithDetached(detached)) } return Compact(&result, compactOpts...) default: return nil, fmt.Errorf(`jws.Sign: 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. func Verify(buf []byte, options ...VerifyOption) ([]byte, error) { var parseOptions []ParseOption var dst *Message var detachedPayload []byte var keyProviders []KeyProvider var keyUsed interface{} var validateKey bool ctx := context.Background() //nolint:forcetypeassert for _, option := range options { switch option.Ident() { case identMessage{}: dst = option.Value().(*Message) case identDetachedPayload{}: detachedPayload = option.Value().([]byte) case identKey{}: pair := option.Value().(*withKey) alg, ok := pair.alg.(jwa.SignatureAlgorithm) if !ok { return nil, fmt.Errorf(`WithKey() option must be specified using jwa.SignatureAlgorithm (got %T)`, pair.alg) } keyProviders = append(keyProviders, &staticKeyProvider{ alg: alg, key: pair.key, }) case identKeyProvider{}: keyProviders = append(keyProviders, option.Value().(KeyProvider)) case identKeyUsed{}: keyUsed = option.Value() case identContext{}: //nolint:fatcontext ctx = option.Value().(context.Context) case identValidateKey{}: validateKey = option.Value().(bool) case identSerialization{}: parseOptions = append(parseOptions, option.(ParseOption)) default: return nil, fmt.Errorf(`invalid jws.VerifyOption %q passed`, `With`+strings.TrimPrefix(fmt.Sprintf(`%T`, option.Ident()), `jws.ident`)) } } if len(keyProviders) < 1 { return nil, fmt.Errorf(`jws.Verify: no key providers have been provided (see jws.WithKey(), jws.WithKeySet(), jws.WithVerifyAuto(), and jws.WithKeyProvider()`) } msg, err := Parse(buf, parseOptions...) if err != nil { return nil, fmt.Errorf(`failed to parse jws: %w`, err) } defer msg.clearRaw() if detachedPayload != nil { if len(msg.payload) != 0 { return nil, fmt.Errorf(`can't specify detached payload for JWS with payload`) } msg.payload = detachedPayload } // Pre-compute the base64 encoded version of payload var payload string if msg.b64 { payload = base64.EncodeToString(msg.payload) } else { payload = string(msg.payload) } verifyBuf := pool.GetBytesBuffer() defer pool.ReleaseBytesBuffer(verifyBuf) var errs []error for i, sig := range msg.signatures { verifyBuf.Reset() var encodedProtectedHeader string if rbp, ok := sig.protected.(interface{ rawBuffer() []byte }); ok { if raw := rbp.rawBuffer(); raw != nil { encodedProtectedHeader = base64.EncodeToString(raw) } } if encodedProtectedHeader == "" { protected, err := json.Marshal(sig.protected) if err != nil { return nil, fmt.Errorf(`failed to marshal "protected" for signature #%d: %w`, i+1, err) } encodedProtectedHeader = base64.EncodeToString(protected) } verifyBuf.WriteString(encodedProtectedHeader) verifyBuf.WriteByte('.') verifyBuf.WriteString(payload) for i, kp := range keyProviders { var sink algKeySink if err := kp.FetchKeys(ctx, &sink, sig, msg); err != nil { return nil, fmt.Errorf(`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 validateKey { if err := validateKeyBeforeUse(key); err != nil { return nil, fmt.Errorf(`jws.Verify: %w`, err) } } verifier, err := NewVerifier(alg) if err != nil { return nil, fmt.Errorf(`failed to create verifier for algorithm %q: %w`, alg, err) } if err := verifier.Verify(verifyBuf.Bytes(), sig.signature, key); err != nil { errs = append(errs, err) continue } if keyUsed != nil { if err := blackmagic.AssignIfCompatible(keyUsed, key); err != nil { return nil, fmt.Errorf(`failed to assign used key (%T) to %T: %w`, key, keyUsed, err) } } if dst != nil { *(dst) = *msg } return msg.payload, nil } } } return nil, &verifyError{errs: errs} } type verifyError struct { // Note: when/if we can ditch Go < 1.20, we can change this to a simple // `err error`, where the value is the result of `errors.Join()` // // We also need to implement Unwrap: // func (e *verifyError) Unwrap() error { // return e.err //} // // And finally, As() can go away errs []error } func (e *verifyError) Error() string { return `could not verify message using any of the signatures or keys` } func (e *verifyError) As(target interface{}) bool { for _, err := range e.errs { if errors.As(err, target) { return true } } return false } // IsVerificationError returns true if the error came from the verification part of the // jws.Verify function, allowing you to check if the error is a result of actual // verification failure. // // For example, if the error happened while fetching a key // from a datasource, feeding that error should to this function return false, whereas // a failure to compute the signature for whatever reason would be a verification error // and returns true. func IsVerificationError(err error) bool { var ve *verifyError return errors.As(err, &ve) } // 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 { b64raw, ok := hdr.Get("b64") if !ok { return true // default } b64, ok := b64raw.(bool) // default if !ok { return false } return b64 } // This is an "optimized" io.ReadAll(). It will attempt to read // all of the contents from the reader IF the reader is of a certain // concrete type. func readAll(rdr io.Reader) ([]byte, bool) { switch rdr.(type) { case *bytes.Reader, *bytes.Buffer, *strings.Reader: data, err := io.ReadAll(rdr) if err != nil { return nil, false } return data, true default: return nil, false } } // 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. func Parse(src []byte, options ...ParseOption) (*Message, error) { var formats int for _, option := range options { //nolint:forcetypeassert switch option.Ident() { case identSerialization{}: switch option.Value().(int) { case fmtJSON: formats |= fmtJSON case fmtCompact: formats |= fmtCompact } } } // if format is 0 or both JSON/Compact, auto detect if v := formats & (fmtJSON | fmtCompact); v == 0 || v == fmtJSON|fmtCompact { for i := 0; i < len(src); i++ { r := rune(src[i]) if r >= utf8.RuneSelf { r, _ = utf8.DecodeRune(src) } if !unicode.IsSpace(r) { if r == '{' { return parseJSON(src) } return parseCompact(src) } } } else if formats&fmtCompact == fmtCompact { return parseCompact(src) } else if formats&fmtJSON == fmtJSON { return parseJSON(src) } return nil, fmt.Errorf(`invalid byte sequence`) } // Parse parses contents from the given source and creates a jws.Message // struct. The input can be in either compact or full JSON serialization. func ParseString(src string) (*Message, error) { return Parse([]byte(src)) } // Parse parses contents from the given source and creates a jws.Message // struct. The input can be in either compact or full JSON serialization. func ParseReader(src io.Reader) (*Message, error) { if data, ok := readAll(src); ok { return Parse(data) } rdr := bufio.NewReader(src) var first rune for { r, _, err := rdr.ReadRune() if err != nil { return nil, fmt.Errorf(`failed to read rune: %w`, err) } if !unicode.IsSpace(r) { first = r if err := rdr.UnreadRune(); err != nil { return nil, fmt.Errorf(`failed to unread rune: %w`, err) } break } } var parser func(io.Reader) (*Message, error) if first == '{' { parser = parseJSONReader } else { parser = parseCompactReader } m, err := parser(rdr) if err != nil { return nil, fmt.Errorf(`failed to parse jws message: %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 JWT and returns its three parts // separately: protected headers, payload and signature. func SplitCompact(src []byte) ([]byte, []byte, []byte, error) { parts := bytes.Split(src, []byte(".")) if len(parts) < 3 { return nil, nil, nil, fmt.Errorf(`invalid number of segments`) } return parts[0], parts[1], parts[2], nil } // SplitCompactString splits a JWT and returns its three parts // separately: protected headers, payload and signature. func SplitCompactString(src string) ([]byte, []byte, []byte, error) { parts := strings.Split(src, ".") if len(parts) < 3 { return nil, nil, nil, fmt.Errorf(`invalid number of segments`) } return []byte(parts[0]), []byte(parts[1]), []byte(parts[2]), nil } // SplitCompactReader splits a JWT and returns its three parts // separately: protected headers, payload and signature. func SplitCompactReader(rdr io.Reader) ([]byte, []byte, []byte, error) { if data, ok := readAll(rdr); ok { return SplitCompact(data) } var protected []byte var payload []byte var signature []byte 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, fmt.Errorf(`unexpected end of input: %w`, err) } // append to current buffer sofar = append(sofar, buf[:n]...) // loop to capture multiple '.' in current buffer for loop := true; loop; { var i = bytes.IndexByte(sofar, '.') if i == -1 && err != io.EOF { // no '.' found -> exit and read next bytes (outer loop) loop = false continue } else if i == -1 && err == io.EOF { // no '.' found -> process rest and exit i = len(sofar) loop = false } else { // '.' found periods++ } // Reaching this point means we have found a '.' 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, fmt.Errorf(`invalid number of segments`) } return protected, 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 } // 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 // // jwe.RegisterCustomField(`x-birthday`, timeT) // // Then `hdr.Get("x-birthday")` will still return an `interface{}`, // but you can convert its type to `time.Time` // // bdayif, _ := hdr.Get(`x-birthday`) // bday := bdayif.(time.Time) func RegisterCustomField(name string, object interface{}) { 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.TypeOf([]byte(nil))] = jwa.OctetSeq rawKeyToKeyType[reflect.TypeOf(ed25519.PublicKey(nil))] = jwa.OKP rawKeyToKeyType[reflect.TypeOf(rsa.PublicKey{})] = jwa.RSA rawKeyToKeyType[reflect.TypeOf((*rsa.PublicKey)(nil))] = jwa.RSA rawKeyToKeyType[reflect.TypeOf(ecdsa.PublicKey{})] = jwa.EC rawKeyToKeyType[reflect.TypeOf((*ecdsa.PublicKey)(nil))] = 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 interface{}) ([]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, x25519.PublicKey, x25519.PrivateKey: kty = jwa.OKP case []byte: kty = jwa.OctetSeq default: return nil, fmt.Errorf(`invalid key %T`, key) } algs, ok := keyTypeToAlgorithms[kty] if !ok { return nil, fmt.Errorf(`invalid key type %q`, kty) } return algs, nil } // 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 interface{}) 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 interface{}) 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 interface{}) 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-2.1.4/jws/jws_test.go000066400000000000000000002042141476711647200223320ustar00rootroot00000000000000package jws_test import ( "bufio" "bytes" "context" "crypto" "crypto/ecdsa" "crypto/ed25519" "crypto/rsa" "crypto/sha256" "crypto/sha512" "encoding/asn1" "errors" "fmt" "io" "math/big" "net/http" "net/http/httptest" "os" "sort" "strings" "testing" "time" "github.com/lestrrat-go/httprc" "github.com/lestrrat-go/jwx/v2/internal/base64" "github.com/lestrrat-go/jwx/v2/internal/json" "github.com/lestrrat-go/jwx/v2/internal/jwxtest" "github.com/lestrrat-go/jwx/v2/jwa" "github.com/lestrrat-go/jwx/v2/jwk" "github.com/lestrrat-go/jwx/v2/jws" "github.com/lestrrat-go/jwx/v2/jwt" "github.com/lestrrat-go/jwx/v2/x25519" "github.com/stretchr/testify/assert" "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`) }) } 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 interface{}, keys map[string]interface{}) { jwkKey, err := jwk.FromRaw(signKey) require.NoError(t, err, `jwk.New should succeed`) signKeys := []struct { Name string Key interface{} }{ { Name: "Raw Key", Key: signKey, }, { Name: "JWK Key", Key: jwkKey, }, } if es, ok := signKey.(*ecdsa.PrivateKey); ok { signKeys = append(signKeys, struct { Name string Key interface{} }{ Name: "crypto.Hash", Key: &dummyECDSACryptoSigner{raw: es}, }) } else if cs, ok := signKey.(crypto.Signer); ok { signKeys = append(signKeys, struct { Name string Key interface{} }{ Name: "crypto.Hash", Key: &dummyCryptoSigner{raw: cs}, }) } for _, key := range signKeys { key := key 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 { name := name f := f 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 keys { name := name testKey := testKey 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.FromRaw(sharedkey) keys := map[string]interface{}{ "[]byte": sharedkey, "jwk.Key": jwkKey, } hmacAlgorithms := []jwa.SignatureAlgorithm{jwa.HS256, jwa.HS384, jwa.HS512} for _, alg := range hmacAlgorithms { alg := alg 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.FromRaw(key.PublicKey) keys := map[string]interface{}{ "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} { alg := alg 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.FromRaw(key.PublicKey) keys := map[string]interface{}{ "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} { alg := alg 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.FromRaw(pubkey) keys := map[string]interface{}{ "Verify(ed25519.Public())": pubkey, // Meh, this doesn't work // "Verify(*ed25519.Public())": &pubkey, "Verify(jwk.Key)": jwkKey, } for _, alg := range []jwa.SignatureAlgorithm{jwa.EdDSA} { alg := alg 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 signed []byte t.Run("Sign", func(t *testing.T) { var options = []jws.SignOption{jws.WithJSON()} for _, alg := range hmacAlgorithms { options = append(options, jws.WithKey(alg, sharedkey)) // (signer, sharedkey, nil, nil)) } var err error signed, err = jws.Sign(payload, options...) require.NoError(t, err, `jws.SignMulti should succeed`) }) for _, alg := range hmacAlgorithms { alg := alg t.Run("Verify "+alg.String(), func(t *testing.T) { 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() // HS256Compact tests that https://tools.ietf.org/html/rfc7515#appendix-A.1 works t.Run("HS256Compact", 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, "HMAC base64 decoded successful") hdrbuf := base64.Encode([]byte(hdr)) payload := base64.Encode([]byte(examplePayload)) signingInput := bytes.Join( [][]byte{ hdrbuf, payload, }, []byte{'.'}, ) sign, err := jws.NewSigner(jwa.HS256) require.NoError(t, err, "HMAC signer created successfully") signature, err := sign.Sign(signingInput, hmacKeyDecoded) require.NoError(t, err, "PayloadSign is successful") sigbuf := base64.Encode(signature) encoded := bytes.Join( [][]byte{ signingInput, sigbuf, }, []byte{'.'}, ) require.Equal(t, expected, string(encoded), "generated compact serialization should match") msg, err := jws.ParseReader(bytes.NewReader(encoded)) require.NoError(t, err, "Parsing compact encoded serialization succeeds") signatures := msg.Signatures() require.Len(t, signatures, 1, `there should be exactly one signature`) algorithm := signatures[0].ProtectedHeaders().Algorithm() if algorithm != jwa.HS256 { t.Fatal("Algorithm in header does not match") } v, err := jws.NewVerifier(jwa.HS256) require.NoError(t, err, "HmacVerify created") require.NoError(t, v.Verify(signingInput, signature, hmacKeyDecoded), "Verify succeeds") }) t.Run("ES512Compact", func(t *testing.T) { t.Parallel() // ES256Compact tests that https://tools.ietf.org/html/rfc7515#appendix-A.3 works hdr := []byte{123, 34, 97, 108, 103, 34, 58, 34, 69, 83, 53, 49, 50, 34, 125} const jwksrc = `{ "kty":"EC", "crv":"P-521", "x":"AekpBQ8ST8a8VcfVOTNl353vSrDCLLJXmPk06wTjxrrjcBpXp5EOnYG_NjFZ6OvLFV1jSfS9tsz4qUxcWceqwQGk", "y":"ADSmRA43Z1DSNx_RvcLI87cdL07l6jQyyBXMoxVg_l2Th-x3S1WDhjDly79ajL4Kkd0AZMaZmh9ubmf63e3kyMj2", "d":"AY5pb7A0UFiB3RELSD64fTLOSV_jazdF7fLYyuTw8lOfRhWg6Y6rUrPAxerEzgdRhajnu0ferB0d53vM9mE15j2C" }` // "Payload" jwsPayload := []byte{80, 97, 121, 108, 111, 97, 100} standardHeaders := jws.NewHeaders() require.NoError(t, json.Unmarshal(hdr, standardHeaders), `parsing headers should succeed`) alg := standardHeaders.Algorithm() jwkKey, err := jwk.ParseKey([]byte(jwksrc)) if err != nil { t.Fatal("Failed to parse JWK") } var key interface{} require.NoError(t, jwkKey.Raw(&key), `jwk.Raw should succeed`) var jwsCompact []byte jwsCompact, err = jws.Sign(jwsPayload, jws.WithKey(alg, key)) if err != nil { t.Fatal("Failed to sign message") } // Verify with standard ecdsa library _, _, jwsSignature, err := jws.SplitCompact(jwsCompact) if err != nil { t.Fatal("Failed to split compact JWT") } decodedJwsSignature, err := base64.Decode(jwsSignature) require.NoError(t, err, `base64.Decode should succeed`) r, s := &big.Int{}, &big.Int{} n := len(decodedJwsSignature) / 2 r.SetBytes(decodedJwsSignature[:n]) s.SetBytes(decodedJwsSignature[n:]) signingHdr := base64.Encode(hdr) signingPayload := base64.Encode(jwsPayload) jwsSigningInput := bytes.Join( [][]byte{ signingHdr, signingPayload, }, []byte{'.'}, ) hashed512 := sha512.Sum512(jwsSigningInput) ecdsaPrivateKey := key.(*ecdsa.PrivateKey) require.True(t, ecdsa.Verify(&ecdsaPrivateKey.PublicKey, hashed512[:], r, s), "ecdsa.Verify should succeed") // Verify with API library publicKey, err := jwk.PublicRawKeyOf(key) require.NoError(t, err, `jwk.PublicRawKeyOf should succeed`) verifiedPayload, err := jws.Verify(jwsCompact, jws.WithKey(alg, publicKey)) if err != nil || string(verifiedPayload) != string(jwsPayload) { t.Fatal("Failed to verify message") } }) t.Run("RS256Compact", func(t *testing.T) { t.Parallel() // RS256Compact tests that https://tools.ietf.org/html/rfc7515#appendix-A.2 works const hdr = `{"alg":"RS256"}` const expected = `eyJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.cC4hiUPoj9Eetdgtv3hF80EGrhuB__dzERat0XF9g2VtQgr9PJbu3XOiZj5RZmh7AAuHIm4Bh-0Qc_lF5YKt_O8W2Fp5jujGbds9uJdbF9CUAr7t1dnZcAcQjbKBYNX4BAynRFdiuB--f_nZLgrnbyTyWzO75vRK5h6xBArLIARNPvkSjtQBMHlb1L07Qe7K0GarZRmB_eSN9383LcOLn6_dO--xi12jzDwusC-eOkHWEsqtFZESc6BfI7noOPqvhJ1phCnvWh6IeYI2w9QOYEUipUTI8np6LbgGY9Fs98rqVt5AXLIhWkWywlVmtVrBp0igcN_IoypGlUPQGe77Rw` const jwksrc = `{ "kty":"RSA", "n":"ofgWCuLjybRlzo0tZWJjNiuSfb4p4fAkd_wWJcyQoTbji9k0l8W26mPddxHmfHQp-Vaw-4qPCJrcS2mJPMEzP1Pt0Bm4d4QlL-yRT-SFd2lZS-pCgNMsD1W_YpRPEwOWvG6b32690r2jZ47soMZo9wGzjb_7OMg0LOL-bSf63kpaSHSXndS5z5rexMdbBYUsLA9e-KXBdQOS-UTo7WTBEMa2R2CapHg665xsmtdVMTBQY4uDZlxvb3qCo5ZwKh9kG4LT6_I5IhlJH7aGhyxXFvUK-DWNmoudF8NAco9_h9iaGNj8q2ethFkMLs91kzk2PAcDTW9gb54h4FRWyuXpoQ", "e":"AQAB", "d":"Eq5xpGnNCivDflJsRQBXHx1hdR1k6Ulwe2JZD50LpXyWPEAeP88vLNO97IjlA7_GQ5sLKMgvfTeXZx9SE-7YwVol2NXOoAJe46sui395IW_GO-pWJ1O0BkTGoVEn2bKVRUCgu-GjBVaYLU6f3l9kJfFNS3E0QbVdxzubSu3Mkqzjkn439X0M_V51gfpRLI9JYanrC4D4qAdGcopV_0ZHHzQlBjudU2QvXt4ehNYTCBr6XCLQUShb1juUO1ZdiYoFaFQT5Tw8bGUl_x_jTj3ccPDVZFD9pIuhLhBOneufuBiB4cS98l2SR_RQyGWSeWjnczT0QU91p1DhOVRuOopznQ", "p":"4BzEEOtIpmVdVEZNCqS7baC4crd0pqnRH_5IB3jw3bcxGn6QLvnEtfdUdiYrqBdss1l58BQ3KhooKeQTa9AB0Hw_Py5PJdTJNPY8cQn7ouZ2KKDcmnPGBY5t7yLc1QlQ5xHdwW1VhvKn-nXqhJTBgIPgtldC-KDV5z-y2XDwGUc", "q":"uQPEfgmVtjL0Uyyx88GZFF1fOunH3-7cepKmtH4pxhtCoHqpWmT8YAmZxaewHgHAjLYsp1ZSe7zFYHj7C6ul7TjeLQeZD_YwD66t62wDmpe_HlB-TnBA-njbglfIsRLtXlnDzQkv5dTltRJ11BKBBypeeF6689rjcJIDEz9RWdc", "dp":"BwKfV3Akq5_MFZDFZCnW-wzl-CCo83WoZvnLQwCTeDv8uzluRSnm71I3QCLdhrqE2e9YkxvuxdBfpT_PI7Yz-FOKnu1R6HsJeDCjn12Sk3vmAktV2zb34MCdy7cpdTh_YVr7tss2u6vneTwrA86rZtu5Mbr1C1XsmvkxHQAdYo0", "dq":"h_96-mK1R_7glhsum81dZxjTnYynPbZpHziZjeeHcXYsXaaMwkOlODsWa7I9xXDoRwbKgB719rrmI2oKr6N3Do9U0ajaHF-NKJnwgjMd2w9cjz3_-kyNlxAr2v4IKhGNpmM5iIgOS1VZnOZ68m6_pbLBSp3nssTdlqvd0tIiTHU", "qi":"IYd7DHOhrWvxkwPQsRM2tOgrjbcrfvtQJipd-DlcxyVuuM9sQLdgjVk2oy26F0EmpScGLq2MowX7fhd_QJQ3ydy5cY7YIBi87w93IKLEdfnbJtoOPLUW0ITrJReOgo1cq9SbsxYawBgfp_gh6A5603k2-ZQwVK0JKSHuLFkuQ3U" }` privkey, err := jwk.ParseKey([]byte(jwksrc)) require.NoError(t, err, `parsing jwk should be successful`) var rawkey rsa.PrivateKey require.NoError(t, privkey.Raw(&rawkey), `obtaining raw key should succeed`) sign, err := jws.NewSigner(jwa.RS256) require.NoError(t, err, "RsaSign created successfully") hdrbuf := base64.Encode([]byte(hdr)) payload := base64.Encode([]byte(examplePayload)) signingInput := bytes.Join( [][]byte{ hdrbuf, payload, }, []byte{'.'}, ) signature, err := sign.Sign(signingInput, rawkey) require.NoError(t, err, "PayloadSign is successful") sigbuf := base64.Encode(signature) encoded := bytes.Join( [][]byte{ signingInput, sigbuf, }, []byte{'.'}, ) require.Equal(t, expected, string(encoded), "generated compact serialization should match") msg, err := jws.ParseReader(bytes.NewReader(encoded)) require.NoError(t, err, "Parsing compact encoded serialization succeeds") signatures := msg.Signatures() require.Len(t, signatures, 1, `there should be exactly one signature`) algorithm := signatures[0].ProtectedHeaders().Algorithm() if algorithm != jwa.RS256 { t.Fatal("Algorithm in header does not match") } v, err := jws.NewVerifier(jwa.RS256) require.NoError(t, err, "Verify created") require.NoError(t, v.Verify(signingInput, signature, rawkey.PublicKey), "Verify succeeds") }) t.Run("ES256Compact", func(t *testing.T) { t.Parallel() // ES256Compact tests that https://tools.ietf.org/html/rfc7515#appendix-A.3 works const hdr = `{"alg":"ES256"}` const jwksrc = `{ "kty":"EC", "crv":"P-256", "x":"f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU", "y":"x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0", "d":"jpsQnnGQmL-YBIffH1136cspYG6-0iY7X1fCE9-E9LI" }` privkey, err := jwk.ParseKey([]byte(jwksrc)) require.NoError(t, err, `parsing jwk should succeed`) var rawkey ecdsa.PrivateKey require.NoError(t, privkey.Raw(&rawkey), `obtaining raw key should succeed`) signer, err := jws.NewSigner(jwa.ES256) require.NoError(t, err, "RsaSign created successfully") hdrbuf := base64.Encode([]byte(hdr)) payload := base64.Encode([]byte(examplePayload)) signingInput := bytes.Join( [][]byte{ hdrbuf, payload, }, []byte{'.'}, ) signature, err := signer.Sign(signingInput, &rawkey) require.NoError(t, err, "PayloadSign is successful") sigbuf := base64.Encode(signature) require.NoError(t, err, "base64 encode successful") encoded := bytes.Join( [][]byte{ signingInput, sigbuf, }, []byte{'.'}, ) // The signature contains random factor, so unfortunately we can't match // the output against a fixed expected outcome. We'll wave doing an // exact match, and just try to verify using the signature msg, err := jws.ParseReader(bytes.NewReader(encoded)) require.NoError(t, err, "Parsing compact encoded serialization succeeds") signatures := msg.Signatures() require.Len(t, signatures, 1, `there should be exactly one signature`) algorithm := signatures[0].ProtectedHeaders().Algorithm() if algorithm != jwa.ES256 { t.Fatal("Algorithm in header does not match") } v, err := jws.NewVerifier(jwa.ES256) require.NoError(t, err, "EcdsaVerify created") require.NoError(t, v.Verify(signingInput, signature, rawkey.PublicKey), "Verify succeeds") }) t.Run("EdDSACompact", func(t *testing.T) { t.Parallel() // EdDSACompact tests that https://tools.ietf.org/html/rfc8037#appendix-A.1-5 works const hdr = `{"alg":"EdDSA"}` const jwksrc = `{ "kty":"OKP", "crv":"Ed25519", "d":"nWGxne_9WmC6hEr0kuwsxERJxWl7MmkZcDusAxyuf2A", "x":"11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo" }` const examplePayload = `Example of Ed25519 signing` const expected = `hgyY0il_MGCjP0JzlnLWG1PPOt7-09PGcvMg3AIbQR6dWbhijcNR4ki4iylGjg5BhVsPt9g7sVvpAr_MuM0KAg` expectedDecoded, err := base64.Decode([]byte(expected)) require.NoError(t, err, "Expected Signature decode successful") privkey, err := jwk.ParseKey([]byte(jwksrc)) require.NoError(t, err, `parsing jwk should succeed`) var rawkey ed25519.PrivateKey require.NoError(t, privkey.Raw(&rawkey), `obtaining raw key should succeed`) signer, err := jws.NewSigner(jwa.EdDSA) require.NoError(t, err, "EdDSASign created successfully") hdrbuf := base64.Encode([]byte(hdr)) payload := base64.Encode([]byte(examplePayload)) signingInput := bytes.Join( [][]byte{ hdrbuf, payload, }, []byte{'.'}, ) signature, err := signer.Sign(signingInput, rawkey) require.NoError(t, err, "PayloadSign is successful") sigbuf := base64.Encode(signature) encoded := bytes.Join( [][]byte{ signingInput, sigbuf, }, []byte{'.'}, ) // The signature contains random factor, so unfortunately we can't match // the output against a fixed expected outcome. We'll wave doing an // exact match, and just try to verify using the signature msg, err := jws.ParseReader(bytes.NewReader(encoded)) require.NoError(t, err, "Parsing compact encoded serialization succeeds") signatures := msg.Signatures() require.Len(t, signatures, 1, `there should be exactly one signature`) algorithm := signatures[0].ProtectedHeaders().Algorithm() if algorithm != jwa.EdDSA { t.Fatal("Algorithm in header does not match") } v, err := jws.NewVerifier(jwa.EdDSA) require.NoError(t, err, "EcdsaVerify created") require.NoError(t, v.Verify(signingInput, signature, rawkey.Public()), "Verify succeeds") require.Equal(t, signature, expectedDecoded, "signatures match") }) 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]interface{}{} 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 := signatures[0].ProtectedHeaders().Algorithm() if 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 i := 0; i < size; i++ { payload = append(payload, 'X') } payload = append(payload, '.') for i := 0; i < size; i++ { payload = append(payload, 'Y') } payload = append(payload, '.') for i := 0; i < size; i++ { 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 TestPublicHeaders(t *testing.T) { key, err := jwxtest.GenerateRsaKey() require.NoError(t, err, "GenerateKey should succeed") signer, err := jws.NewSigner(jwa.RS256) require.NoError(t, err, "jws.NewSigner should succeed") _ = signer // TODO pubkey := key.PublicKey pubjwk, err := jwk.FromRaw(&pubkey) require.NoError(t, err, "NewRsaPublicKey should succeed") _ = pubjwk // TODO } func TestDecode_ES384Compact_NoSigTrim(t *testing.T) { incoming := "eyJhbGciOiJFUzM4NCIsInR5cCI6IkpXVCIsImtpZCI6IjE5MzFmZTQ0YmFhMWNhZTkyZWUzNzYzOTQ0MDU1OGMwODdlMTRlNjk5ZWU5NjVhM2Q1OGU1MmU2NGY4MDE0NWIifQ.eyJpc3MiOiJicmt0LWNsaS0xLjAuN3ByZTEiLCJpYXQiOjE0ODQ2OTU1MjAsImp0aSI6IjgxYjczY2Y3In0.DdFi0KmPHSv4PfIMGcWGMSRLmZsfRPQ3muLFW6Ly2HpiLFFQWZ0VEanyrFV263wjlp3udfedgw_vrBLz3XC8CkbvCo_xeHMzaTr_yfhjoheSj8gWRLwB-22rOnUX_M0A" t.Logf("incoming = '%s'", incoming) const jwksrc = `{ "kty":"EC", "crv":"P-384", "x":"YHVZ4gc1RDoqxKm4NzaN_Y1r7R7h3RM3JMteC478apSKUiLVb4UNytqWaLoE6ygH", "y":"CRKSqP-aYTIsqJfg_wZEEYUayUR5JhZaS2m4NLk2t1DfXZgfApAJ2lBO0vWKnUMp" }` pubkey, err := jwk.ParseKey([]byte(jwksrc)) require.NoError(t, err, `parsing jwk should be successful`) var rawkey ecdsa.PublicKey require.NoError(t, pubkey.Raw(&rawkey), `obtaining raw key should succeed`) v, err := jws.NewVerifier(jwa.ES384) require.NoError(t, err, "EcdsaVerify created") protected, payload, signature, err := jws.SplitCompact([]byte(incoming)) require.NoError(t, err, `jws.SplitCompact should succeed`) var buf bytes.Buffer buf.Write(protected) buf.WriteByte('.') buf.Write(payload) decodedSignature, err := base64.Decode(signature) require.NoError(t, err, `decoding signature should succeed`) require.NoError(t, v.Verify(buf.Bytes(), decodedSignature, rawkey), "Verify succeeds") } 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) if _, err := jws.ReadFile(f.Name()); !assert.NoError(t, err, `jws.ReadFile should succeed`) { return } } 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 { tc := tc 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)} { set := set 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, _ := jwk.FromRaw([]byte("abracadabra")) set.AddKey(k1) k2, _ := jwk.FromRaw([]byte("opensesame")) set.AddKey(k2) pubkey, _ := jwk.PublicKeyOf(privkey) pubkey.Set(jwk.AlgorithmKey, jwa.RS256) set.AddKey(pubkey) return set } for _, useJSON := range []bool{true, false} { useJSON := useJSON 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!!! 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`) v, ok := msg.Signatures()[0].ProtectedHeaders().Get(`x-birthday`) require.True(t, ok, `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, ok = msg2.Signatures()[0].ProtectedHeaders().Get(`x-birthday`) require.True(t, ok, `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 { tc := tc 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 { tc := tc 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.False(t, jws.IsVerificationError(err), `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) { 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/v2`) return []jwk.FetchOption{ jwk.WithFetchWhitelist(wl), jwk.WithHTTPClient(srv.Client()), } }, }, { Name: "JWKFetcher", Fetcher: func() jwk.Fetcher { c := jwk.NewCache(context.TODO()) return jwk.FetchFunc(func(ctx context.Context, u string, options ...jwk.FetchOption) (jwk.Set, error) { var cacheopts []jwk.RegisterOption for _, option := range options { cacheopts = append(cacheopts, option) } cacheopts = append(cacheopts, jwk.WithHTTPClient(srv.Client())) cacheopts = append(cacheopts, jwk.WithFetchWhitelist(httprc.InsecureWhitelist{})) c.Register(u, cacheopts...) return c.Get(ctx, u) }) }, }, } for _, tc := range testcases { tc := tc 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 := 0; i < 3; i++ { 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 := 0; i < 2; i++ { 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`) require.Equal(t, pubkey.KeyID(), key.KeyID(), `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/v2`) return []jwk.FetchOption{ jwk.WithFetchWhitelist(wl), } }, }, } for _, tc := range testcases { tc := tc 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 { if !assert.NoError(t, err, `jws.Verify should succeed`) { set, _ := jwk.Fetch(context.Background(), srv.URL, options...) { buf, _ := json.MarshalIndent(set, "", " ") t.Logf("%s", buf) } return } 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 interface{} 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: x25519.PublicKey(nil), Expected: []jwa.SignatureAlgorithm{jwa.EdDSA}, }, } for _, tc := range testcases { tc := tc 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 sig.ProtectedHeaders().Algorithm() != 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{} const sha256Algo jwa.SignatureAlgorithm = "SillyTest256" func (s256SignerVerifier) Algorithm() jwa.SignatureAlgorithm { return sha256Algo } func (s256SignerVerifier) Sign(payload []byte, _ interface{}) ([]byte, error) { h := sha256.Sum256(payload) return h[:], nil } func (s256SignerVerifier) Verify(payload, signature []byte, _ interface{}) error { h := sha256.Sum256(payload) if !bytes.Equal(h[:], signature) { return errors.New("invalid signature") } return nil } func TestGH910(t *testing.T) { // Note: This has global effect. You can't run this in parallel with other tests jws.RegisterSigner(sha256Algo, jws.SignerFactoryFn(func() (jws.Signer, error) { return s256SignerVerifier{}, nil })) defer jws.UnregisterSigner(sha256Algo) jws.RegisterVerifier(sha256Algo, jws.VerifierFactoryFn(func() (jws.Verifier, error) { return s256SignerVerifier{}, nil })) defer jws.UnregisterVerifier(sha256Algo) defer jwa.UnregisterSignatureAlgorithm(sha256Algo) var sa jwa.SignatureAlgorithm require.NoError(t, sa.Accept(sha256Algo.String()), `jwa.SignatureAlgorithm.Accept should succeed`) // Now that we have established that the signature algorithm works, // we can proceed with the test const src = `Lorem Ipsum` signed, err := jws.Sign([]byte(src), jws.WithKey(sha256Algo, nil)) require.NoError(t, err, `jws.Sign should succeed`) verified, err := jws.Verify(signed, jws.WithKey(sha256Algo, nil)) require.NoError(t, err, `jws.Verify should succeed`) require.Equal(t, src, string(verified), `verified payload should match`) 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.FromRaw(rawKey) require.NoError(t, err, `jwk.FromRaw 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, '.') 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 := pubKey.(jwk.RSAPublicKey).N() // 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( []byte(fmt.Sprintf(`{"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('.') buf.Write(payload) buf.WriteRune('.') 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`) } golang-github-lestrrat-go-jwx-2.1.4/jws/key_provider.go000066400000000000000000000202321476711647200231660ustar00rootroot00000000000000package jws import ( "context" "fmt" "net/url" "sync" "github.com/lestrrat-go/jwx/v2/jwa" "github.com/lestrrat-go/jwx/v2/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, interface{}) } type algKeyPair struct { alg jwa.KeyAlgorithm key interface{} } type algKeySink struct { mu sync.Mutex list []algKeyPair } func (s *algKeySink) Key(alg jwa.SignatureAlgorithm, key interface{}) { s.mu.Lock() s.list = append(s.list, algKeyPair{alg, key}) s.mu.Unlock() } type staticKeyProvider struct { alg jwa.SignatureAlgorithm key interface{} } 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 := key.KeyUsage(); usage != "" && usage != jwk.ForSignature.String() { return nil } if v := key.Algorithm(); v.String() != "" { var alg jwa.SignatureAlgorithm if err := alg.Accept(v); err != nil { return fmt.Errorf(`invalid signature algorithm %s: %w`, key.Algorithm(), err) } sink.Key(alg, 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 := sig.ProtectedHeaders().Algorithm(); tokAlg != "" { 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 := sig.ProtectedHeaders().KeyID() if wantedKid == "" { // 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, _ := kp.set.Key(0) 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 var ok bool for i := 0; i < kp.set.Len(); i++ { key, _ := kp.set.Key(i) if key.KeyID() != 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 := 0; i < kp.set.Len(); i++ { key, _ := kp.set.Key(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 { kid := sig.ProtectedHeaders().KeyID() if kid == "" { 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 := sig.ProtectedHeaders().JWKSetURL() if 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 := sig.ProtectedHeaders().Algorithm() for _, alg := range algs { // if we have an "alg" field in the JWS, we can only proceed if // the inferred algorithm matches if hdrAlg != "" && 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-2.1.4/jws/message.go000066400000000000000000000312121476711647200221100ustar00rootroot00000000000000package jws import ( "bytes" "context" "fmt" "github.com/lestrrat-go/jwx/v2/internal/base64" "github.com/lestrrat-go/jwx/v2/internal/json" "github.com/lestrrat-go/jwx/v2/internal/pool" "github.com/lestrrat-go/jwx/v2/jwk" ) 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{'{'}) { 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") func (s *Signature) Sign(payload []byte, signer Signer, key interface{}) ([]byte, []byte, error) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() hdrs, err := mergeHeaders(ctx, s.headers, s.protected) if err != nil { return nil, nil, fmt.Errorf(`failed to merge headers: %w`, err) } if err := hdrs.Set(AlgorithmKey, signer.Algorithm()); err != nil { return nil, nil, fmt.Errorf(`failed to set "alg": %w`, err) } // If the key is a jwk.Key instance, obtain the raw key if jwkKey, ok := key.(jwk.Key); ok { // If we have a key ID specified by this jwk.Key, use that in the header if kid := jwkKey.KeyID(); kid != "" { if err := hdrs.Set(jwk.KeyIDKey, kid); err != nil { return nil, nil, fmt.Errorf(`set key ID from jwk.Key: %w`, err) } } } hdrbuf, err := json.Marshal(hdrs) if err != nil { return nil, nil, fmt.Errorf(`failed to marshal headers: %w`, err) } buf := pool.GetBytesBuffer() defer pool.ReleaseBytesBuffer(buf) buf.WriteString(base64.EncodeToString(hdrbuf)) buf.WriteByte('.') var plen int b64 := getB64Value(hdrs) if b64 { encoded := base64.EncodeToString(payload) plen = len(encoded) buf.WriteString(encoded) } else { if !s.detached { if bytes.Contains(payload, []byte{'.'}) { return nil, nil, fmt.Errorf(`payload must not contain a "."`) } } plen = len(payload) buf.Write(payload) } signature, err := signer.Sign(buf.Bytes(), key) if err != nil { return nil, nil, fmt.Errorf(`failed to sign payload: %w`, err) } s.signature = signature // Detached payload, this should be removed from the end result if s.detached { buf.Truncate(buf.Len() - plen) } buf.WriteByte('.') buf.WriteString(base64.EncodeToString(signature)) ret := make([]byte, buf.Len()) copy(ret, buf.Bytes()) return 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 := hdr.KeyID() if hdrKeyID == kid { sigs = append(sigs, sig) continue } } if hdr := sig.ProtectedHeaders(); hdr != nil { hdrKeyID := hdr.KeyID() if 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.GetBytesBuffer() defer pool.ReleaseBytesBuffer(buf) sig := m.signatures[0] buf.WriteRune('{') var wrote bool if hdr := sig.headers; hdr != nil { hdrjs, err := hdr.MarshalJSON() 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(',') } buf.WriteString(`"payload":"`) buf.WriteString(base64.EncodeToString(m.payload)) buf.WriteRune('"') if protected := sig.protected; protected != nil { protectedbuf, err := protected.MarshalJSON() 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('}') ret := make([]byte, buf.Len()) copy(ret, buf.Bytes()) return ret, nil } func (m Message) marshalFull() ([]byte, error) { buf := pool.GetBytesBuffer() defer pool.ReleaseBytesBuffer(buf) buf.WriteString(`{"payload":"`) buf.WriteString(base64.EncodeToString(m.payload)) buf.WriteString(`","signatures":[`) for i, sig := range m.signatures { if i > 0 { buf.WriteRune(',') } buf.WriteRune('{') var wrote bool if hdr := sig.headers; hdr != nil { hdrbuf, err := hdr.MarshalJSON() 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 := protected.MarshalJSON() if err != nil { return nil, fmt.Errorf(`failed to marshal "protected" for signature #%d: %w`, i+1, err) } if wrote { buf.WriteRune(',') } 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(',') } 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 for _, option := range options { //nolint:forcetypeassert switch option.Ident() { case identDetached{}: detached = option.Value().(bool) } } 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.GetBytesBuffer() defer pool.ReleaseBytesBuffer(buf) buf.WriteString(base64.EncodeToString(hdrbuf)) buf.WriteByte('.') if !detached { if getB64Value(hdrs) { encoded := base64.EncodeToString(msg.payload) buf.WriteString(encoded) } else { if bytes.Contains(msg.payload, []byte{'.'}) { return nil, fmt.Errorf(`jws.Compress: payload must not contain a "."`) } buf.Write(msg.payload) } } buf.WriteByte('.') buf.WriteString(base64.EncodeToString(s.signature)) ret := make([]byte, buf.Len()) copy(ret, buf.Bytes()) return ret, nil } golang-github-lestrrat-go-jwx-2.1.4/jws/message_test.go000066400000000000000000000105061476711647200231520ustar00rootroot00000000000000package jws_test import ( "testing" "github.com/lestrrat-go/jwx/v2/internal/base64" "github.com/lestrrat-go/jwx/v2/internal/json" "github.com/lestrrat-go/jwx/v2/jwa" "github.com/lestrrat-go/jwx/v2/jws" "github.com/stretchr/testify/assert" ) 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 if !assert.NoError(t, json.Unmarshal([]byte(src), &m), `json.Unmarshal should succeed`) { return } buf, err := json.MarshalIndent(m, "", " ") if !assert.NoError(t, err, `json.Marshal should succeed`) { return } if !assert.Equal(t, src, string(buf), `roundtrip should match`) { return } }) 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) if !assert.NoError(t, err, `base64.DecodeString should succeed (payload)`) { return } decodedSig1, err := base64.DecodeString(encodedSig1) if !assert.NoError(t, err, `base64.DecodeString should succeed (sig1)`) { return } decodedSig2, err := base64.DecodeString(encodedSig2) if !assert.NoError(t, err, `base64.DecodeString should succeed (sig2)`) { 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") 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, "", " ") if !assert.NoError(t, err, `json.MarshalIndent should succeed`) { return } if !assert.Equal(t, expected, string(buf), `output should match`) { return } }) } golang-github-lestrrat-go-jwx-2.1.4/jws/options.go000066400000000000000000000176161476711647200221730ustar00rootroot00000000000000package jws import ( "github.com/lestrrat-go/jwx/v2/jwa" "github.com/lestrrat-go/jwx/v2/jwk" "github.com/lestrrat-go/option" ) type identHeaders struct{} type identInsecureNoSignature struct{} // WithHeaders is deprecated. See WithProtectedHeaders to specify // headers to include in the jws signature. // // Using this option has NO EFFECT. func WithHeaders(h Headers) SignOption { return &signOption{option.New(identHeaders{}, h)} } // 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 { //nolint:forcetypeassert switch option.Ident() { case identPretty{}: pretty = option.Value().(bool) } } format := fmtJSON if pretty { format = fmtJSONPretty } return &signVerifyParseOption{option.New(identSerialization{}, format)} } type withKey struct { alg jwa.KeyAlgorithm key interface{} protected Headers public Headers } // This 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 interface{}, 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 { //nolint:forcetypeassert switch option.Ident() { case identProtectedHeaders{}: protected = option.Value().(Headers) case identPublicHeaders{}: public = option.Value().(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 { //nolint:forcetypeassert switch option.Ident() { case identRequireKid{}: requireKid = option.Value().(bool) case identUseDefault{}: useDefault = option.Value().(bool) case identMultipleKeysPerKeyID{}: multipleKeysPerKeyID = option.Value().(bool) case identInferAlgorithmFromKey{}: inferAlgorithm = option.Value().(bool) } } return WithKeyProvider(&keySetProvider{ set: set, requireKid: requireKid, useDefault: useDefault, multipleKeysPerKeyID: multipleKeysPerKeyID, inferAlgorithm: inferAlgorithm, }) } func WithVerifyAuto(f jwk.Fetcher, options ...jwk.FetchOption) VerifyOption { if f == nil { f = jwk.FetchFunc(jwk.Fetch) } // 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 } // This 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 { //nolint:forcetypeassert switch option.Ident() { case identProtectedHeaders{}: protected = option.Value().(Headers) } } return &signOption{ option.New(identInsecureNoSignature{}, &withInsecureNoSignature{ protected: protected, }, ), } } golang-github-lestrrat-go-jwx-2.1.4/jws/options.yaml000066400000000000000000000174571476711647200225330ustar00rootroot00000000000000package_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: 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 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. 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: 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: 'interface{}' 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 `interface{}`. 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.FromRaw()`. 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. (I think this should be removed) - 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. golang-github-lestrrat-go-jwx-2.1.4/jws/options_gen.go000066400000000000000000000251451476711647200230200ustar00rootroot00000000000000// Code generated by tools/cmd/genoptions/main.go. DO NOT EDIT. package jws import ( "context" "io/fs" "github.com/lestrrat-go/option" ) 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() {} // 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() {} // 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 identContext struct{} type identDetached struct{} type identDetachedPayload struct{} type identFS struct{} type identInferAlgorithmFromKey struct{} type identKey struct{} type identKeyProvider struct{} type identKeyUsed 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 (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 (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" } 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. // // 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 `interface{}`. 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 interface{}) VerifyOption { return &verifyOption{option.New(identKeyUsed{}, v)} } // 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. // (I think this should be removed) 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.FromRaw()`. 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-2.1.4/jws/options_gen_test.go000066400000000000000000000024101476711647200240450ustar00rootroot00000000000000// 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, "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, "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-2.1.4/jws/rsa.go000066400000000000000000000062621476711647200212600ustar00rootroot00000000000000package jws import ( "crypto" "crypto/rand" "crypto/rsa" "fmt" "github.com/lestrrat-go/jwx/v2/internal/keyconv" "github.com/lestrrat-go/jwx/v2/jwa" ) var rsaSigners map[jwa.SignatureAlgorithm]*rsaSigner var rsaVerifiers map[jwa.SignatureAlgorithm]*rsaVerifier func init() { algs := 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, }, } rsaSigners = make(map[jwa.SignatureAlgorithm]*rsaSigner) rsaVerifiers = make(map[jwa.SignatureAlgorithm]*rsaVerifier) for alg, item := range algs { 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 interface{}) ([]byte, error) { if key == nil { return nil, fmt.Errorf(`missing private key while signing payload`) } signer, ok := key.(crypto.Signer) if ok { if !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 interface{}) 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-2.1.4/jws/signer.go000066400000000000000000000063521476711647200217620ustar00rootroot00000000000000package jws import ( "fmt" "sync" "github.com/lestrrat-go/jwx/v2/jwa" ) type SignerFactory interface { Create() (Signer, error) } type SignerFactoryFn func() (Signer, error) func (fn SignerFactoryFn) Create() (Signer, error) { return fn() } var muSignerDB sync.RWMutex var signerDB map[jwa.SignatureAlgorithm]SignerFactory // RegisterSigner is used to register a factory object that creates // Signer objects based on the given algorithm. Previous object instantiated // by the factory is discarded. // // For example, if you would like to provide a custom signer for // jwa.EdDSA, use this function to register a `SignerFactory` // (probably in your `init()`) // // Unlike the `UnregisterSigner` function, this function automatically // calls `jwa.RegisterSignatureAlgorithm` to register the algorithm // in this module's algorithm database. func RegisterSigner(alg jwa.SignatureAlgorithm, f SignerFactory) { jwa.RegisterSignatureAlgorithm(alg) muSignerDB.Lock() signerDB[alg] = f muSignerDB.Unlock() // Remove previous signer, if there was one removeSigner(alg) } // 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) { muSignerDB.Lock() delete(signerDB, alg) muSignerDB.Unlock() // Remove previous signer removeSigner(alg) } func init() { signerDB = make(map[jwa.SignatureAlgorithm]SignerFactory) for _, alg := range []jwa.SignatureAlgorithm{jwa.RS256, jwa.RS384, jwa.RS512, jwa.PS256, jwa.PS384, jwa.PS512} { RegisterSigner(alg, func(alg jwa.SignatureAlgorithm) SignerFactory { return SignerFactoryFn(func() (Signer, error) { return newRSASigner(alg), nil }) }(alg)) } for _, alg := range []jwa.SignatureAlgorithm{jwa.ES256, jwa.ES384, jwa.ES512, jwa.ES256K} { RegisterSigner(alg, func(alg jwa.SignatureAlgorithm) SignerFactory { return SignerFactoryFn(func() (Signer, error) { return newECDSASigner(alg), nil }) }(alg)) } for _, alg := range []jwa.SignatureAlgorithm{jwa.HS256, jwa.HS384, jwa.HS512} { RegisterSigner(alg, func(alg jwa.SignatureAlgorithm) SignerFactory { return SignerFactoryFn(func() (Signer, error) { return newHMACSigner(alg), nil }) }(alg)) } RegisterSigner(jwa.EdDSA, SignerFactoryFn(func() (Signer, error) { return newEdDSASigner(), nil })) } // NewSigner creates a signer that signs payloads using the given signature algorithm. func NewSigner(alg jwa.SignatureAlgorithm) (Signer, error) { muSignerDB.RLock() f, ok := signerDB[alg] muSignerDB.RUnlock() if ok { return f.Create() } return nil, fmt.Errorf(`unsupported signature algorithm "%s"`, alg) } type noneSigner struct{} func (noneSigner) Algorithm() jwa.SignatureAlgorithm { return jwa.NoSignature } func (noneSigner) Sign([]byte, interface{}) ([]byte, error) { return nil, nil } golang-github-lestrrat-go-jwx-2.1.4/jws/signer_test.go000066400000000000000000000046301476711647200230160ustar00rootroot00000000000000package jws_test import ( "strings" "testing" "github.com/lestrrat-go/jwx/v2/internal/jwxtest" "github.com/lestrrat-go/jwx/v2/jwa" "github.com/lestrrat-go/jwx/v2/jws" "github.com/stretchr/testify/assert" ) 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.SignatureAlgorithm("FooBar"), nil)) if !assert.Error(t, err, "Unknown algorithm should return error") { return } }) t.Run("No private key", func(t *testing.T) { t.Parallel() _, err := jws.Sign([]byte{'a', 'b', 'c'}, jws.WithKey(jwa.RS256, nil)) if !assert.Error(t, err, "Sign with no private key should return error") { return } }) t.Run("RSA verify with no public key", func(t *testing.T) { t.Parallel() _, err := jws.Verify([]byte(nil), jws.WithKey(jwa.RS256, nil)) if !assert.Error(t, err, "Verify with no private key should return error") { return } }) t.Run("RSA roundtrip", func(t *testing.T) { t.Parallel() rsakey, err := jwxtest.GenerateRsaKey() if !assert.NoError(t, err, "RSA key generated") { return } signer, err := jws.NewSigner(jwa.RS256) if !assert.NoError(t, err, `creating a signer should succeed`) { return } payload := []byte("Hello, world") signed, err := signer.Sign(payload, rsakey) if !assert.NoError(t, err, "Payload signed") { return } verifier, err := jws.NewVerifier(jwa.RS256) if !assert.NoError(t, err, "creating a verifier should succeed") { return } if !assert.NoError(t, verifier.Verify(payload, signed, &rsakey.PublicKey), "Payload verified") { return } }) } func TestSignMulti(t *testing.T) { rsakey, err := jwxtest.GenerateRsaKey() if !assert.NoError(t, err, "RSA key generated") { return } dsakey, err := jwxtest.GenerateEcdsaKey(jwa.P521) if !assert.NoError(t, err, "ECDSA key generated") { return } 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)), ) if !assert.NoError(t, err, "jws.SignMulti should succeed") { return } t.Logf("%s", m) } golang-github-lestrrat-go-jwx-2.1.4/jws/verifier.go000066400000000000000000000056511476711647200223070ustar00rootroot00000000000000package jws import ( "fmt" "sync" "github.com/lestrrat-go/jwx/v2/jwa" ) 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 map[jwa.SignatureAlgorithm]VerifierFactory // RegisterVerifier is used to register a factory object that creates // Verifier objects based on the given algorithm. // // For example, if you would like to provide a custom verifier for // jwa.EdDSA, use this function to register a `VerifierFactory` // (probably in your `init()`) // // Unlike the `UnregisterVerifier` function, this function automatically // calls `jwa.RegisterSignatureAlgorithm` to register the algorithm // in this module's algorithm database. func RegisterVerifier(alg jwa.SignatureAlgorithm, f VerifierFactory) { jwa.RegisterSignatureAlgorithm(alg) muVerifierDB.Lock() verifierDB[alg] = f muVerifierDB.Unlock() } // UnregisterVerifier removes the signer factory associated with // the given algorithm. // // Note that when you call this function, the algorithm itself is // not automatically unregistered from 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) { muVerifierDB.Lock() delete(verifierDB, alg) muVerifierDB.Unlock() } func init() { verifierDB = make(map[jwa.SignatureAlgorithm]VerifierFactory) for _, alg := range []jwa.SignatureAlgorithm{jwa.RS256, jwa.RS384, jwa.RS512, jwa.PS256, jwa.PS384, jwa.PS512} { RegisterVerifier(alg, func(alg jwa.SignatureAlgorithm) VerifierFactory { return VerifierFactoryFn(func() (Verifier, error) { return newRSAVerifier(alg), nil }) }(alg)) } for _, alg := range []jwa.SignatureAlgorithm{jwa.ES256, jwa.ES384, jwa.ES512, jwa.ES256K} { RegisterVerifier(alg, func(alg jwa.SignatureAlgorithm) VerifierFactory { return VerifierFactoryFn(func() (Verifier, error) { return newECDSAVerifier(alg), nil }) }(alg)) } for _, alg := range []jwa.SignatureAlgorithm{jwa.HS256, jwa.HS384, jwa.HS512} { RegisterVerifier(alg, func(alg jwa.SignatureAlgorithm) VerifierFactory { return VerifierFactoryFn(func() (Verifier, error) { return newHMACVerifier(alg), nil }) }(alg)) } RegisterVerifier(jwa.EdDSA, VerifierFactoryFn(func() (Verifier, error) { return newEdDSAVerifier(), nil })) } // 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(`unsupported signature algorithm "%s"`, alg) } golang-github-lestrrat-go-jwx-2.1.4/jwt/000077500000000000000000000000001476711647200201375ustar00rootroot00000000000000golang-github-lestrrat-go-jwx-2.1.4/jwt/BUILD.bazel000066400000000000000000000027211476711647200220170ustar00rootroot00000000000000load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") go_library( name = "jwt", srcs = [ "builder_gen.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/v2/jwt", visibility = ["//visibility:public"], deps = [ "//:jwx", "//internal/base64", "//internal/iter", "//internal/json", "//internal/pool", "//jwa", "//jwe", "//jwk", "//jws", "//jwt/internal/types", "@com_github_lestrrat_go_iter//mapiter:go_default_library", "@com_github_lestrrat_go_option//:option", ], ) go_test( name = "jwt_test", srcs = [ "jwt_test.go", "options_gen_test.go", "token_options_test.go", "token_test.go", "validate_test.go", ], embed = [":jwt"], deps = [ "//internal/ecutil", "//internal/json", "//internal/jwxtest", "//jwa", "//jwe", "//jwk", "//jws", "//jwt/internal/types", "@com_github_stretchr_testify//assert", "@com_github_stretchr_testify//require", ], ) alias( name = "go_default_library", actual = ":jwt", visibility = ["//visibility:public"], ) golang-github-lestrrat-go-jwx-2.1.4/jwt/README.md000066400000000000000000000167301476711647200214250ustar00rootroot00000000000000# JWT [![Go Reference](https://pkg.go.dev/badge/github.com/lestrrat-go/jwx/v2/jwt.svg)](https://pkg.go.dev/github.com/lestrrat-go/jwx/v2/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/v2/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/v2/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/v2/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-2.1.4/jwt/builder_gen.go000066400000000000000000000034401476711647200227460ustar00rootroot00000000000000// Code generated by tools/cmd/genjwt/main.go. DO NOT EDIT. package jwt import ( "fmt" "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 { claims []*ClaimPair } func NewBuilder() *Builder { return &Builder{} } func (b *Builder) Claim(name string, value interface{}) *Builder { b.claims = append(b.claims, &ClaimPair{Key: name, Value: 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 func (b *Builder) Build() (Token, error) { tok := New() for _, claim := range b.claims { if err := tok.Set(claim.Key.(string), claim.Value); err != nil { return nil, fmt.Errorf(`failed to set claim %q: %w`, claim.Key.(string), err) } } return tok, nil } golang-github-lestrrat-go-jwx-2.1.4/jwt/http.go000066400000000000000000000160451476711647200214530ustar00rootroot00000000000000package jwt import ( "fmt" "net/http" "net/url" "strconv" "strings" "github.com/lestrrat-go/jwx/v2/internal/pool" ) // 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 //nolint:forcetypeassert for _, option := range options { switch option.Ident() { case identCookie{}: dst = option.Value().(**http.Cookie) } } cookie, err := req.Cookie(name) if err != nil { return nil, err } tok, err := ParseString(cookie.Value, options...) if err != nil { return nil, fmt.Errorf(`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")) } return ParseString(v, options...) } // 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 { //nolint:forcetypeassert switch option.Ident() { case identHeaderKey{}: hdrkeys = append(hdrkeys, option.Value().(string)) case identFormKey{}: formkeys = append(formkeys, option.Value().(string)) case identCookieKey{}: cookiekeys = append(cookiekeys, option.Value().(string)) default: parseOptions = append(parseOptions, option) } } if len(hdrkeys) == 0 && len(formkeys) == 0 && len(cookiekeys) == 0 { hdrkeys = append(hdrkeys, "Authorization") } mhdrs := pool.GetKeyToErrorMap() defer pool.ReleaseKeyToErrorMap(mhdrs) mfrms := pool.GetKeyToErrorMap() defer pool.ReleaseKeyToErrorMap(mfrms) mcookies := pool.GetKeyToErrorMap() defer pool.ReleaseKeyToErrorMap(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(']') } if triedForms.Len() > 0 { if b.Len() > olen { b.WriteString(", ") } b.WriteString("form keys: [") b.WriteString(triedForms.String()) b.WriteByte(']') } if triedCookies.Len() > 0 { if b.Len() > olen { b.WriteString(", ") } b.WriteString("cookie keys: [") b.WriteString(triedCookies.String()) b.WriteByte(']') } b.WriteByte(')') lmhdrs := len(mhdrs) lmfrms := len(mfrms) lmcookies := len(mcookies) var errors []interface{} if lmhdrs > 0 || lmfrms > 0 || lmcookies > 0 { b.WriteString(". Additionally, errors were encountered during attempts to parse") 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-2.1.4/jwt/interface.go000066400000000000000000000005651476711647200224340ustar00rootroot00000000000000package jwt import ( "github.com/lestrrat-go/iter/mapiter" "github.com/lestrrat-go/jwx/v2/internal/iter" "github.com/lestrrat-go/jwx/v2/internal/json" ) type ClaimPair = mapiter.Pair type Iterator = mapiter.Iterator type Visitor = iter.MapVisitor type VisitorFunc = iter.MapVisitorFunc type DecodeCtx = json.DecodeCtx type TokenWithDecodeCtx = json.DecodeCtxContainer golang-github-lestrrat-go-jwx-2.1.4/jwt/internal/000077500000000000000000000000001476711647200217535ustar00rootroot00000000000000golang-github-lestrrat-go-jwx-2.1.4/jwt/internal/types/000077500000000000000000000000001476711647200231175ustar00rootroot00000000000000golang-github-lestrrat-go-jwx-2.1.4/jwt/internal/types/BUILD.bazel000066400000000000000000000012131476711647200247720ustar00rootroot00000000000000load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") go_library( name = "types", srcs = [ "date.go", "string.go", ], importpath = "github.com/lestrrat-go/jwx/v2/jwt/internal/types", visibility = ["//jwt:__subpackages__"], deps = ["//internal/json"], ) go_test( name = "types_test", srcs = [ "date_test.go", "string_test.go", ], deps = [ ":types", "//internal/json", "//jwt", "@com_github_stretchr_testify//assert", ], ) alias( name = "go_default_library", actual = ":types", visibility = ["//jwt:__subpackages__"], ) golang-github-lestrrat-go-jwx-2.1.4/jwt/internal/types/date.go000066400000000000000000000104731476711647200243700ustar00rootroot00000000000000package types import ( "fmt" "strconv" "strings" "time" "github.com/lestrrat-go/jwx/v2/internal/json" ) 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 interface{}, 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 = '.' 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, '.'); i > 0 { if ParsePrecision > 0 && len(x) > i+1 { fractional = x[i+1:] // everything after the '.' 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 interface{}) 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] == '.' { 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 interface{} 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-2.1.4/jwt/internal/types/date_test.go000066400000000000000000000050721476711647200254260ustar00rootroot00000000000000package types_test import ( "fmt" "testing" "time" "github.com/lestrrat-go/jwx/v2/internal/json" "github.com/lestrrat-go/jwx/v2/jwt" "github.com/lestrrat-go/jwx/v2/jwt/internal/types" "github.com/stretchr/testify/assert" ) func TestDate(t *testing.T) { t.Run("Get from a nil NumericDate", func(t *testing.T) { var n *types.NumericDate if !assert.Equal(t, time.Time{}, n.Get()) { return } }) t.Run("MarshalJSON with a zero value", func(t *testing.T) { var n *types.NumericDate buf, err := json.Marshal(n) if !assert.NoError(t, err, `json.Marshal against a zero value should succeed`) { return } if !assert.Equal(t, []byte(`null`), buf, `result should be null`) { return } }) // 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 interface{} 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 { tc := tc 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) if !assert.NoError(t, err) { return } v, ok := t1.Get(jwt.IssuedAtKey) if !assert.True(t, ok) { return } realized := v.(time.Time) if !assert.Equal(t, tc.Expected, realized) { return } }) } }) } golang-github-lestrrat-go-jwx-2.1.4/jwt/internal/types/string.go000066400000000000000000000014651476711647200247620ustar00rootroot00000000000000package types import ( "fmt" "github.com/lestrrat-go/jwx/v2/internal/json" ) type StringList []string func (l StringList) Get() []string { return []string(l) } func (l *StringList) Accept(v interface{}) error { switch x := v.(type) { case string: *l = StringList([]string{x}) case []string: *l = StringList(x) case []interface{}: 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 interface{} 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-2.1.4/jwt/internal/types/string_test.go000066400000000000000000000007251476711647200260170ustar00rootroot00000000000000package types_test import ( "testing" "github.com/lestrrat-go/jwx/v2/jwt/internal/types" "github.com/stretchr/testify/assert" ) func TestStringList_Accept(t *testing.T) { t.Parallel() var x types.StringList interfaceList := make([]interface{}, 0) interfaceList = append(interfaceList, "first") interfaceList = append(interfaceList, "second") if !assert.NoError(t, x.Accept(interfaceList), "failed to convert []interface{} into StringList") { return } } golang-github-lestrrat-go-jwx-2.1.4/jwt/io.go000066400000000000000000000013011476711647200210700ustar00rootroot00000000000000// Code generated by tools/cmd/genreadfile/main.go. DO NOT EDIT. package jwt import ( "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{}: srcFS = option.Value().(fs.FS) } } f, err := srcFS.Open(path) if err != nil { return nil, err } defer f.Close() return ParseReader(f, parseOptions...) } golang-github-lestrrat-go-jwx-2.1.4/jwt/jwt.go000066400000000000000000000347511476711647200213040ustar00rootroot00000000000000//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" "errors" "fmt" "io" "sync/atomic" "github.com/lestrrat-go/jwx/v2" "github.com/lestrrat-go/jwx/v2/internal/json" "github.com/lestrrat-go/jwx/v2/jws" "github.com/lestrrat-go/jwx/v2/jwt/internal/types" ) var compactOnly uint32 var errInvalidJWT = errors.New(`invalid JWT`) // ErrInvalidJWT returns the opaque error value that is returned when // `jwt.Parse` fails due to not being able to deduce the format of // the incoming buffer func ErrInvalidJWT() error { return errInvalidJWT } // Settings controls global settings that are specific to JWTs. func Settings(options ...GlobalOption) { var flattenAudience bool var compactOnlyBool bool var parsePedantic bool var parsePrecision = types.MaxPrecision + 1 // illegal value, so we can detect nothing was set var formatPrecision = types.MaxPrecision + 1 // illegal value, so we can detect nothing was set //nolint:forcetypeassert for _, option := range options { switch option.Ident() { case identFlattenAudience{}: flattenAudience = option.Value().(bool) case identCompactOnly{}: compactOnlyBool = option.Value().(bool) case identNumericDateParsePedantic{}: parsePedantic = option.Value().(bool) case identNumericDateParsePrecision{}: v := option.Value().(int) // only accept this value if it's in our desired range if v >= 0 && v <= int(types.MaxPrecision) { parsePrecision = uint32(v) } case identNumericDateFormatPrecision{}: v := option.Value().(int) // 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) } } { v := atomic.LoadUint32(&compactOnly) if (v == 1) != compactOnlyBool { var newVal uint32 if compactOnlyBool { newVal = 1 } atomic.CompareAndSwapUint32(&compactOnly, v, newVal) } } { defaultOptionsMu.Lock() if flattenAudience { defaultOptions.Enable(FlattenAudience) } else { defaultOptions.Disable(FlattenAudience) } defaultOptionsMu.Unlock() } } var registry = json.NewRegistry() // ParseString calls Parse against a string func ParseString(s string, options ...ParseOption) (Token, error) { return parseBytes([]byte(s), options...) } // Parse parses the JWT token payload and creates a new `jwt.Token` object. // The token must be encoded in either JSON format or compact format. // // This function can only work with either raw JWT (JSON) and JWS (Compact or JSON). // If you need JWE support on top of it, 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) { return parseBytes(s, options...) } // 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, fmt.Errorf(`jwt.ParseInsecure: jwt.WithVerify() and jwt.WithValidate() may not be specified`) } } options = append(options, WithVerify(false), WithValidate(false)) return Parse(s, options...) } // 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, fmt.Errorf(`failed to read from token data source: %w`, err) } return parseBytes(data, options...) } type parseCtx struct { token Token validateOpts []ValidateOption verifyOpts []jws.VerifyOption localReg *json.Registry pedantic bool skipVerification bool validate bool } 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) continue } //nolint:forcetypeassert switch o.Ident() { case identKey{}, identKeySet{}, identVerifyAuto{}, identKeyProvider{}: verifyOpts = append(verifyOpts, o) case identToken{}: token, ok := o.Value().(Token) if !ok { return nil, fmt.Errorf(`invalid token passed via WithToken() option (%T)`, o.Value()) } ctx.token = token case identPedantic{}: ctx.pedantic = o.Value().(bool) case identValidate{}: ctx.validate = o.Value().(bool) case identVerify{}: verification = o.Value().(bool) case identTypedClaim{}: pair := o.Value().(claimPair) 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) { if len(ctx.verifyOpts) == 0 { return nil, _JwsVerifySkipped, nil } verifyOpts := ctx.verifyOpts if atomic.LoadUint32(&compactOnly) == 1 { verifyOpts = append(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 := 0; i < maxDecodeLevels; i++ { 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, ErrInvalidJWT() 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. var parseOptions []jws.ParseOption if atomic.LoadUint32(&compactOnly) == 1 { parseOptions = append(parseOptions, jws.WithCompact()) } m, err := jws.Parse(data, parseOptions...) if err != nil { return nil, fmt.Errorf(`invalid jws message: %w`, err) } 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. // // 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) { 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 _, pair := range t.makePairs() { //nolint:forcetypeassert key := pair.Key.(string) if err := dst.Set(key, pair.Value); err != nil { return nil, fmt.Errorf(`failed to set %s: %w`, key, 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 `interface{}`, // but you can convert its type to `time.Time` // // bdayif, _ := token.Get(`x-birthday`) // bday := bdayif.(time.Time) func RegisterCustomField(name string, object interface{}) { registry.Register(name, object) } golang-github-lestrrat-go-jwx-2.1.4/jwt/jwt_test.go000066400000000000000000001502511476711647200223350ustar00rootroot00000000000000package 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/jwx/v2/internal/ecutil" "github.com/lestrrat-go/jwx/v2/internal/json" "github.com/lestrrat-go/jwx/v2/internal/jwxtest" "github.com/lestrrat-go/jwx/v2/jwe" "github.com/lestrrat-go/jwx/v2/jwt/internal/types" "github.com/lestrrat-go/jwx/v2/jwa" "github.com/lestrrat-go/jwx/v2/jwk" "github.com/lestrrat-go/jwx/v2/jws" "github.com/lestrrat-go/jwx/v2/jwt" "github.com/stretchr/testify/assert" "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 TestJWTParse(t *testing.T) { t.Parallel() alg := jwa.RS256 key, err := jwxtest.GenerateRsaKey() if !assert.NoError(t, err, `jwxtest.GenerateRsaKey should succeed`) { return } t1 := jwt.New() signed, err := jwt.Sign(t1, jwt.WithKey(alg, key)) if !assert.NoError(t, err, `jwt.Sign should succeed`) { return } t.Logf("%s", signed) t.Run("Parse (no signature verification)", func(t *testing.T) { t.Parallel() t2, err := jwt.ParseInsecure(signed) if !assert.NoError(t, err, `jwt.Parse should succeed`) { return } if !assert.True(t, jwt.Equal(t1, t2), `t1 == t2`) { return } }) t.Run("ParseString (no signature verification)", func(t *testing.T) { t.Parallel() t2, err := jwt.ParseString(string(signed), jwt.WithVerify(false), jwt.WithValidate(false)) if !assert.NoError(t, err, `jwt.ParseString should succeed`) { return } if !assert.True(t, jwt.Equal(t1, t2), `t1 == t2`) { return } }) 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)) if !assert.NoError(t, err, `jwt.ParseReader should succeed`) { return } if !assert.True(t, jwt.Equal(t1, t2), `t1 == t2`) { return } }) t.Run("Parse (correct signature key)", func(t *testing.T) { t.Parallel() t2, err := jwt.Parse(signed, jwt.WithKey(alg, &key.PublicKey)) if !assert.NoError(t, err, `jwt.Parse should succeed`) { return } if !assert.True(t, jwt.Equal(t1, t2), `t1 == t2`) { return } }) t.Run("parse (wrong signature algorithm)", func(t *testing.T) { t.Parallel() _, err := jwt.Parse(signed, jwt.WithKey(jwa.RS512, &key.PublicKey)) if !assert.Error(t, err, `jwt.Parse should fail`) { return } }) 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)) if !assert.Error(t, err, `jwt.Parse should fail`) { return } }) } func TestJWTParseVerify(t *testing.T) { t.Parallel() keys := make([]interface{}, 0, 6) keys = append(keys, []byte("abracadabra")) rsaPrivKey, err := jwxtest.GenerateRsaKey() if !assert.NoError(t, err, "RSA key generated") { return } keys = append(keys, rsaPrivKey) for _, alg := range []jwa.EllipticCurveAlgorithm{jwa.P256, jwa.P384, jwa.P521} { ecdsaPrivKey, err := jwxtest.GenerateEcdsaKey(alg) if !assert.NoError(t, err, "jwxtest.GenerateEcdsaKey should succeed for %s", alg) { return } keys = append(keys, ecdsaPrivKey) } ed25519PrivKey, err := jwxtest.GenerateEd25519Key() if !assert.NoError(t, err, `jwxtest.GenerateEd25519Key should succeed`) { return } keys = append(keys, ed25519PrivKey) for _, key := range keys { key := key t.Run(fmt.Sprintf("Key=%T", key), func(t *testing.T) { t.Parallel() algs, err := jws.AlgorithmsForKey(key) if !assert.NoError(t, err, `jwas.AlgorithmsForKey should succeed`) { return } var dummyRawKey interface{} switch pk := key.(type) { case *rsa.PrivateKey: dummyRawKey, err = jwxtest.GenerateRsaKey() if !assert.NoError(t, err, `jwxtest.GenerateRsaKey should succeed`) { return } case *ecdsa.PrivateKey: curveAlg, ok := ecutil.AlgorithmForCurve(pk.Curve) if !assert.True(t, ok, `ecutil.AlgorithmForCurve should succeed`) { return } dummyRawKey, err = jwxtest.GenerateEcdsaKey(curveAlg) if !assert.NoError(t, err, `jwxtest.GenerateEcdsaKey should succeed`) { return } case ed25519.PrivateKey: dummyRawKey, err = jwxtest.GenerateEd25519Key() if !assert.NoError(t, err, `jwxtest.GenerateEd25519Key should succeed`) { return } case []byte: dummyRawKey = jwxtest.GenerateSymmetricKey() default: assert.Fail(t, fmt.Sprintf("Unhandled key type %T", key)) return } 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 { alg := alg for _, tc := range testcases { tc := tc 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))) if !assert.NoError(t, err, "token.Sign should succeed") { return } pubkey, err := jwk.PublicKeyOf(key) if !assert.NoError(t, err, `jwk.PublicKeyOf should succeed`) { return } if tc.SetAlgorithm { pubkey.Set(jwk.AlgorithmKey, alg) } dummyKey, err := jwk.PublicKeyOf(dummyRawKey) if !assert.NoError(t, err, `jwk.PublicKeyOf should succeed`) { return } 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 := 0; i < 6; i++ { 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 { assert.Error(t, err, `jwt.Parse should fail`) return } if !assert.NoError(t, err, `jwt.Parse should succeed`) { return } if !assert.True(t, jwt.Equal(t1, t2), `t1 == t2`) { return } }) } }) } } }) } t.Run("Miscellaneous", func(t *testing.T) { key, err := jwxtest.GenerateRsaKey() if !assert.NoError(t, err, "RSA key generated") { return } const 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))) if !assert.NoError(t, err, "token.Sign should succeed") { return } t.Run("Alg does not match", func(t *testing.T) { t.Parallel() pubkey, err := jwk.PublicKeyOf(key) if !assert.NoError(t, err) { return } pubkey.Set(jwk.AlgorithmKey, jwa.HS256) pubkey.Set(jwk.KeyIDKey, kid) set := jwk.NewSet() set.AddKey(pubkey) _, err = jwt.Parse(signed, jwt.WithKeySet(set, jws.WithInferAlgorithmFromKey(true), jws.WithUseDefault(true))) if !assert.Error(t, err, `jwt.Parse should fail`) { return } }) t.Run("UseDefault with a key set with 1 key", func(t *testing.T) { t.Parallel() pubkey, err := jwk.PublicKeyOf(key) if !assert.NoError(t, err) { return } 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))) if !assert.NoError(t, err, `jwt.Parse with key set should succeed`) { return } if !assert.True(t, jwt.Equal(t1, t2), `t1 == t2`) { return } }) t.Run("UseDefault with multiple keys should fail", func(t *testing.T) { t.Parallel() pubkey1, err := jwk.FromRaw(&key.PublicKey) if !assert.NoError(t, err) { return } pubkey2, err := jwk.FromRaw(&key.PublicKey) if !assert.NoError(t, err) { return } 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))) if !assert.Error(t, err, `jwt.Parse should fail`) { return } }) // 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) if !assert.NoError(t, err, `jws.SplitCompact should succeed`) { return } dummyHeader := jws.NewHeaders() ctx, cancel := context.WithCancel(context.Background()) defer cancel() for iter := hdrs.Iterate(ctx); iter.Next(ctx); { pair := iter.Pair() dummyHeader.Set(pair.Key.(string), pair.Value) } dummyHeader.Set(jws.AlgorithmKey, jwa.NoSignature) dummyMarshaled, err := json.Marshal(dummyHeader) if !assert.NoError(t, err, `json.Marshal should succeed`) { return } dummyEncoded := make([]byte, base64.RawURLEncoding.EncodedLen(len(dummyMarshaled))) base64.RawURLEncoding.Encode(dummyEncoded, dummyMarshaled) signedButNot := bytes.Join([][]byte{dummyEncoded, payload, signature}, []byte{'.'}) pubkey, err := jwk.FromRaw(&key.PublicKey) if !assert.NoError(t, err) { return } pubkey.Set(jwk.KeyIDKey, kid) set := jwk.NewSet() set.AddKey(pubkey) _, err = jwt.Parse(signedButNot, jwt.WithKeySet(set)) // This should fail if !assert.Error(t, err, `jwt.Parse with key set + alg=none should fail`) { return } }) }) } 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), } if !assert.NoError(t, jwt.Validate(token, args...), "token.Validate should validate tokens in the same second they are created") { if now.Equal(token.IssuedAt()) { t.Errorf("iat claim failed: iat == now") } return } }) } 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 { tc := tc t.Run(tc.Title, func(t *testing.T) { t.Parallel() token := jwt.New() if !assert.NoError(t, json.Unmarshal([]byte(tc.Source), &token), `json.Unmarshal should succeed`) { return } if !assert.Equal(t, tc.Expected(), token, `token should match expected value`) { return } var buf bytes.Buffer if !assert.NoError(t, json.NewEncoder(&buf).Encode(token), `json.Marshal should succeed`) { return } if !assert.Equal(t, tc.ExpectedJSON, strings.TrimSpace(buf.String()), `json should match`) { return } }) } } func TestGH52(t *testing.T) { if testing.Short() { t.SkipNow() } t.Parallel() priv, err := jwxtest.GenerateEcdsaKey(jwa.P521) if !assert.NoError(t, err) { return } pub := &priv.PublicKey if !assert.NoError(t, err) { return } const iterations = 100 var wg sync.WaitGroup wg.Add(iterations) for i := 0; i < iterations; i++ { // 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)) if !assert.NoError(t, err) { return } if _, err = jws.Verify(s, jws.WithKey(jwa.ES256, pub)); !assert.NoError(t, err, `test should pass (run %d)`, i) { return } }(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() if !assert.NoError(t, json.Unmarshal([]byte(`{"aud":["foo", "bar", "baz"]}`), &t1), `jwt.Parse should succeed`) { return } aud, ok := t1.Get(jwt.AudienceKey) if !assert.True(t, ok, `jwt.Get(jwt.AudienceKey) should succeed`) { t.Logf("%#v", t1) return } if !assert.Equal(t, aud.([]string), []string{"foo", "bar", "baz"}, "audience should match. got %v", aud) { return } }) } func TestSignErrors(t *testing.T) { t.Parallel() priv, err := jwxtest.GenerateEcdsaKey(jwa.P521) if !assert.NoError(t, err, `jwxtest.GenerateEcdsaKey should succeed`) { return } tok := jwt.New() _, err = jwt.Sign(tok, jwt.WithKey(jwa.SignatureAlgorithm("BOGUS"), priv)) if !assert.Error(t, err) { return } if !assert.Contains(t, err.Error(), `unsupported signature algorithm "BOGUS"`) { return } _, err = jwt.Sign(tok, jwt.WithKey(jwa.ES256, nil)) if !assert.Error(t, err) { return } if !assert.Contains(t, err.Error(), "missing private key") { return } } func TestSignJWK(t *testing.T) { t.Parallel() priv, err := jwxtest.GenerateRsaKey() assert.Nil(t, err) key, err := jwk.FromRaw(priv) assert.Nil(t, err) key.Set(jwk.KeyIDKey, "test") key.Set(jwk.AlgorithmKey, jwa.RS256) tok := jwt.New() signed, err := jwt.Sign(tok, jwt.WithKey(key.Algorithm(), key)) assert.Nil(t, err) header, err := jws.ParseString(string(signed)) assert.Nil(t, err) signatures := header.LookupSignature("test") assert.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() if !assert.NoError(t, err) { return } 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)) if !assert.NoError(t, err) { return } got, err := getJWTHeaders(signed) if !assert.NoError(t, err) { return } if !assert.Equal(t, `JWT`, got.Type(), `"typ" header parameter should be set to JWT`) { return } }) 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))) if !assert.NoError(t, err) { return } got, err := getJWTHeaders(signed) if !assert.NoError(t, err) { return } if !assert.Equal(t, `custom-typ`, got.Type(), `"typ" header parameter should be set to the custom value`) { return } }) } func TestReadFile(t *testing.T) { t.Parallel() f, err := os.CreateTemp(t.TempDir(), "test-read-file-*.jwt") if !assert.NoError(t, err, `os.CreateTemp should succeed`) { return } defer f.Close() token := jwt.New() token.Set(jwt.IssuerKey, `lestrrat`) if !assert.NoError(t, json.NewEncoder(f).Encode(token), `json.NewEncoder.Encode should succeed`) { return } if _, err := jwt.ReadFile(f.Name(), jwt.WithVerify(false), jwt.WithValidate(true), jwt.WithIssuer("lestrrat")); !assert.NoError(t, err, `jwt.ReadFile should succeed`) { return } if _, err := jwt.ReadFile(f.Name(), jwt.WithVerify(false), jwt.WithValidate(true), jwt.WithIssuer("lestrrrrrat")); !assert.Error(t, err, `jwt.ReadFile should fail`) { return } } func TestCustomField(t *testing.T) { // XXX has global effect!!! jwt.RegisterCustomField(`x-birthday`, time.Time{}) defer jwt.RegisterCustomField(`x-birthday`, nil) expected := time.Date(2015, 11, 4, 5, 12, 52, 0, time.UTC) bdaybytes, _ := expected.MarshalText() // RFC3339 var b strings.Builder b.WriteString(`{"iss": "github.com/lesstrrat-go/jwx", "x-birthday": "`) b.Write(bdaybytes) b.WriteString(`"}`) src := b.String() t.Run("jwt.Parse", func(t *testing.T) { token, err := jwt.ParseInsecure([]byte(src)) if !assert.NoError(t, err, `jwt.Parse should succeed`) { t.Logf("%q", src) return } v, ok := token.Get(`x-birthday`) if !assert.True(t, ok, `token.Get("x-birthday") should succeed`) { return } if !assert.Equal(t, expected, v, `values should match`) { return } }) t.Run("json.Unmarshal", func(t *testing.T) { token := jwt.New() if !assert.NoError(t, json.Unmarshal([]byte(src), token), `json.Unmarshal should succeed`) { return } v, ok := token.Get(`x-birthday`) if !assert.True(t, ok, `token.Get("x-birthday") should succeed`) { return } if !assert.Equal(t, expected, v, `values should match`) { return } }) } func TestParseRequest(t *testing.T) { const u = "https://github.com/lestrrat-gow/jwx/jwt" const xauth = "X-Authorization" privkey, _ := jwxtest.GenerateEcdsaJwk() privkey.Set(jwk.AlgorithmKey, jwa.ES256) privkey.Set(jwk.KeyIDKey, `my-awesome-key`) pubkey, _ := jwk.PublicKeyOf(privkey) pubkey.Set(jwk.AlgorithmKey, jwa.ES256) 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() set.AddKey(pubkey) 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 { tc := tc t.Run(tc.Name, func(t *testing.T) { got, err := tc.Parse(tc.Request()) if tc.Error { t.Logf("%s", err) assert.Error(t, err, `tc.Parse should fail`) return } if !assert.NoError(t, err, `tc.Parse should succeed`) { return } if !assert.True(t, jwt.Equal(tok, got), `tokens should match`) { { buf, _ := json.MarshalIndent(tok, "", " ") t.Logf("expected: %s", buf) } { buf, _ := json.MarshalIndent(got, "", " ") t.Logf("got: %s", buf) } return } }) } // 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} { globalFlatten := globalFlatten for _, perObjectFlatten := range []bool{true, false} { perObjectFlatten := perObjectFlatten // 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, "", " ") if !assert.NoError(t, err, `json.MarshalIndent should succeed`) { return } var expected string if perObjectFlatten { expected = `{ "aud": "hello" }` } else { expected = `{ "aud": [ "hello" ] }` } if !assert.Equal(t, expected, string(buf), `output should match`) { return } }) } } }) for _, flatten := range []bool{true, false} { flatten := flatten 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, "", " ") if !assert.NoError(t, err, `json.MarshalIndent should succeed`) { return } var expected string if flatten { expected = `{ "aud": "hello" }` } else { expected = `{ "aud": [ "hello" ] }` } if !assert.Equal(t, expected, string(buf), `output should match`) { return } }) t.Run("Multiple Keys", func(t *testing.T) { tok, err := jwt.NewBuilder(). Audience([]string{"hello", "world"}). Build() if !assert.NoError(t, err, `jwt.Builder should succeed`) { return } buf, err := json.MarshalIndent(tok, "", " ") if !assert.NoError(t, err, `json.MarshalIndent should succeed`) { return } const expected = `{ "aud": [ "hello", "world" ] }` if !assert.Equal(t, expected, string(buf), `output should match`) { return } }) }) } } func TestGH375(t *testing.T) { key, err := jwxtest.GenerateRsaJwk() if !assert.NoError(t, err, `jwxtest.GenerateRsaJwk should succeed`) { return } key.Set(jwk.KeyIDKey, `test`) token, err := jwt.NewBuilder(). Issuer(`foobar`). Build() if !assert.NoError(t, err, `jwt.Builder should succeed`) { return } signAlg := jwa.RS512 signed, err := jwt.Sign(token, jwt.WithKey(signAlg, key)) if !assert.NoError(t, err, `jwt.Sign should succeed`) { return } verifyKey, err := jwk.PublicKeyOf(key) if !assert.NoError(t, err, `jwk.PublicKeyOf should succeed`) { return } 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)) if !assert.Error(t, err, `jwt.Parse should fail`) { return } } type Claim struct { Foo string Bar int64 } func TestJWTParseWithTypedClaim(t *testing.T) { testcases := []struct { Name string Options []jwt.ParseOption PostProcess func(*testing.T, interface{}) (*Claim, error) }{ { Name: "Basic", Options: []jwt.ParseOption{jwt.WithTypedClaim("typed-claim", Claim{})}, PostProcess: func(t *testing.T, claim interface{}) (*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 interface{}) (*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() if !assert.NoError(t, err, `jwxtest.GenerateRsaKey should succeed`) { return } var signed []byte { token := jwt.New() if !assert.NoError(t, token.Set("typed-claim", expected), `expected.Set should succeed`) { return } v, err := jwt.Sign(token, jwt.WithKey(jwa.RS256, key)) if !assert.NoError(t, err, `jwt.Sign should succeed`) { return } signed = v } for _, tc := range testcases { tc := tc t.Run(tc.Name, func(t *testing.T) { options := append(tc.Options, jwt.WithVerify(false)) got, err := jwt.Parse(signed, options...) if !assert.NoError(t, err, `jwt.Parse should succeed`) { return } v, ok := got.Get("typed-claim") if !assert.True(t, ok, `got.Get() should succeed`) { return } claim, err := tc.PostProcess(t, v) if !assert.NoError(t, err, `tc.PostProcess should succeed`) { return } if !assert.Equal(t, claim, expected, `claim should match expected value`) { return } }) } } func TestGH393(t *testing.T) { t.Run("Non-existent required claims", func(t *testing.T) { tok := jwt.New() if !assert.Error(t, jwt.Validate(tok, jwt.WithRequiredClaim(jwt.IssuedAtKey)), `jwt.Validate should fail`) { return } }) 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() if !assert.NoError(t, err, `jwt.Builder should succeed`) { return } if !assert.Error(t, jwt.Validate(tok, jwt.WithMaxDelta(2*time.Second, jwt.ExpirationKey, jwt.IssuedAtKey)), `jwt.Validate should fail`) { return } if !assert.NoError(t, jwt.Validate(tok, jwt.WithMaxDelta(10*time.Second, jwt.ExpirationKey, jwt.IssuedAtKey)), `jwt.Validate should succeed`) { return } }) 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() if !assert.NoError(t, err, `jwt.Builder should succeed`) { return } if !assert.Error(t, jwt.Validate(tok, jwt.WithMinDelta(10*time.Second, jwt.ExpirationKey, jwt.IssuedAtKey)), `jwt.Validate should fail`) { return } }) 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() if !assert.NoError(t, err, `jwt.Builder should succeed`) { return } if !assert.NoError(t, jwt.Validate(tok, jwt.WithMinDelta(10*time.Second, jwt.ExpirationKey, jwt.IssuedAtKey), jwt.WithAcceptableSkew(5*time.Second)), `jwt.Validate should succeed`) { return } }) t.Run("now - iat < WithMaxDelta(10 secs)", func(t *testing.T) { now := time.Now() tok, err := jwt.NewBuilder(). IssuedAt(now). Build() if !assert.NoError(t, err, `jwt.Builder should succeed`) { return } if !assert.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`) { return } }) 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() if !assert.NoError(t, err, `jwt.Builder should succeed`) { return } if !assert.Error(t, jwt.Validate(tok, jwt.WithMinDelta(10*time.Second, jwt.ExpirationKey, "foo"), jwt.WithAcceptableSkew(5*time.Second)), `jwt.Validate should fail`) { return } }) 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() if !assert.NoError(t, err, `jwt.Builder should succeed`) { return } if !assert.Error(t, jwt.Validate(tok, jwt.WithMinDelta(10*time.Second, "foo", jwt.IssuedAtKey), jwt.WithAcceptableSkew(5*time.Second)), `jwt.Validate should fail`) { return } }) // 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() if !assert.Error(t, jwt.Validate(tok, jwt.WithRequiredClaim("foo")), `jwt.Validate should fail`) { return } }) t.Run(`WithRequiredClaim succeeds for existing claim`, func(t *testing.T) { tok, err := jwt.NewBuilder(). Claim(`foo`, 1). Build() if !assert.NoError(t, err, `jwt.Builder should succeed`) { return } if !assert.NoError(t, jwt.Validate(tok, jwt.WithRequiredClaim("foo")), `jwt.Validate should fail`) { return } }) } func TestGH430(t *testing.T) { t1 := jwt.New() err := t1.Set("payload", map[string]interface{}{ "name": "someone", }) if !assert.NoError(t, err, `t1.Set should succeed`) { return } key := []byte("secret") signed, err := jwt.Sign(t1, jwt.WithKey(jwa.HS256, key)) if !assert.NoError(t, err, `jwt.Sign should succeed`) { return } if _, err = jwt.Parse(signed, jwt.WithKey(jwa.HS256, key)); !assert.NoError(t, err, `jwt.Parse should succeed`) { return } } func TestGH706(t *testing.T) { err := jwt.Validate(jwt.New(), jwt.WithRequiredClaim("foo")) if !assert.True(t, jwt.IsValidationError(err), `error should be a validation error`) { return } if !assert.ErrorIs(t, err, jwt.ErrRequiredClaim(), `jwt.Validate should fail`) { return } } 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{ []byte(fmt.Sprintf(tmpl, `"aud": ["test"],`, exp)), []byte(fmt.Sprintf(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) if !assert.Error(t, err, `jwt.Parse should fail`) { return } } } } func TestVerifyAuto(t *testing.T) { key, err := jwxtest.GenerateRsaJwk() if !assert.NoError(t, err, `jwxtest.GenerateRsaJwk should succeed`) { return } key.Set(jwk.KeyIDKey, `my-awesome-key`) pubkey, err := jwk.PublicKeyOf(key) if !assert.NoError(t, err, `jwk.PublicKeyOf should succeed`) { return } 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/v2`). Claim(jwt.SubjectKey, `jku-test`). Build() if !assert.NoError(t, err, `jwt.NewBuilder.Build() should succeed`) { return } hdrs := jws.NewHeaders() hdrs.Set(jws.JWKSetURLKey, srv.URL) signed, err := jwt.Sign(tok, jwt.WithKey(jwa.RS256, key, jws.WithProtectedHeaders(hdrs))) if !assert.NoError(t, err, `jwt.Sign() should succeed`) { return } wl := jwk.NewMapWhitelist(). Add(srv.URL) parsed, err := jwt.Parse(signed, jwt.WithVerifyAuto(nil, jwk.WithFetchWhitelist(wl), jwk.WithHTTPClient(srv.Client()))) if !assert.NoError(t, err, `jwt.Parse should succeed`) { return } if !assert.True(t, jwt.Equal(tok, parsed), `tokens should be equal`) { return } _, err = jwt.Parse(signed, jwt.WithVerifyAuto(nil)) if !assert.Error(t, err, `jwt.Parse should fail`) { return } wl = jwk.NewMapWhitelist(). Add(`https://github.com/lestrrat-go/jwx/v2`) _, err = jwt.Parse(signed, jwt.WithVerifyAuto(nil, jwk.WithFetchWhitelist(wl))) if !assert.Error(t, err, `jwt.Parse should fail`) { return } // now with Cache c := jwk.NewCache(context.TODO()) 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(u, registeropts...) return c.Get(ctx, u) }), jwk.WithHTTPClient(srv.Client()), jwk.WithFetchWhitelist(jwk.InsecureWhitelist{}), ), ) if !assert.NoError(t, err, `jwt.Parse should succeed`) { return } if !assert.True(t, jwt.Equal(tok, parsed), `tokens should be equal`) { return } } 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()) if !assert.Error(t, err, `Serialize() should fail`) { return } }) t.Run(`Invalid SignatureAglrotihm`, func(t *testing.T) { _, err := jwt.NewSerializer(). Encrypt(jwt.WithKey(jwa.A256KW, []byte("abracadabra"))). Serialize(jwt.New()) if !assert.Error(t, err, `Serialize() should succeedl`) { return } }) 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()) if !assert.Error(t, err, `Serialize() should fail`) { return } }) t.Run(`Invalid KeyEncryptionAglrotihm`, func(t *testing.T) { _, err := jwt.NewSerializer(). Encrypt(jwt.WithKey(jwa.HS256, []byte("abracadabra"))). Serialize(jwt.New()) if !assert.Error(t, err, `Serialize() should succeedl`) { return } }) } 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 { tc := tc 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 { tc := tc 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( []byte(fmt.Sprintf(template, tc.Input)), jwt.WithVerify(false), jwt.WithValidate(false), ) require.NoError(t, err, `jwt.Parse should succeed`) require.Equal(t, tc.Expected, tok.IssuedAt(), `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.ErrInvalidJWT())) } 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{'.'})[3]), len(bytes.Split(serialized2, []byte{'.'})[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: jwt.Settings has global effect! defer jwt.Settings(jwt.WithCompactOnly(false)) for _, compactOnly := range []bool{true, false} { t.Run("compactOnly="+strconv.FormatBool(compactOnly), func(t *testing.T) { jwt.Settings(jwt.WithCompactOnly(compactOnly)) privKey, err := jwxtest.GenerateRsaJwk() require.NoError(t, err, `jwxtest.GenerateRsaJwk should succeed`) signedJSON, err := jws.Sign([]byte(`{}`), jws.WithKey(jwa.RS256, privKey), jws.WithValidateKey(true), jws.WithJSON()) require.NoError(t, err, `jws.Sign should succeed`) // jws.Verify should succeed _, err = jws.Verify(signedJSON, jws.WithKey(jwa.RS256, privKey)) require.NoError(t, err, `jws.Parse should succeed`) if compactOnly { // jwt.Parse should fail _, err = jwt.Parse(signedJSON, jwt.WithKey(jwa.RS256, privKey)) require.Error(t, err, `jws.Parse should fail`) } else { // for backward compatibility, this should succeed _, err = jwt.Parse(signedJSON, jwt.WithKey(jwa.RS256, privKey)) require.NoError(t, err, `jws.Parse should succeed`) } }) } } 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.ErrTokenExpired(), `jwt.ParseRequest should fail with jwt.ErrTokenExpired`) } golang-github-lestrrat-go-jwx-2.1.4/jwt/openid/000077500000000000000000000000001476711647200214155ustar00rootroot00000000000000golang-github-lestrrat-go-jwx-2.1.4/jwt/openid/BUILD.bazel000066400000000000000000000020301476711647200232660ustar00rootroot00000000000000load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") go_library( name = "openid", srcs = [ "address.go", "birthdate.go", "builder_gen.go", "interface.go", "openid.go", "token_gen.go", ], importpath = "github.com/lestrrat-go/jwx/v2/jwt/openid", visibility = ["//visibility:public"], deps = [ "//internal/base64", "//internal/iter", "//internal/json", "//internal/pool", "//jwt", "//jwt/internal/types", "@com_github_lestrrat_go_iter//mapiter:go_default_library", ], ) go_test( name = "openid_test", srcs = ["openid_test.go"], deps = [ ":openid", "//internal/json", "//internal/jwxtest", "//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-2.1.4/jwt/openid/address.go000066400000000000000000000171711476711647200234000ustar00rootroot00000000000000package openid import ( "fmt" "strconv" "github.com/lestrrat-go/jwx/v2/internal/json" "github.com/lestrrat-go/jwx/v2/internal/pool" ) 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) (interface{}, 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 interface{}) 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 interface{}) error { switch v := v.(type) { case AddressClaim: *t = v return nil case *AddressClaim: *t = *v return nil case map[string]interface{}: 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.GetBytesBuffer() defer pool.ReleaseBytesBuffer(buf) buf.WriteByte('{') 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(',') } prev = buf.Len() buf.WriteString(`"formatted":`) buf.WriteString(strconv.Quote(*v)) } if v := t.locality; v != nil { if buf.Len() > prev { buf.WriteByte(',') } prev = buf.Len() buf.WriteString(`"locality":`) buf.WriteString(strconv.Quote(*v)) } if v := t.postalCode; v != nil { if buf.Len() > prev { buf.WriteByte(',') } prev = buf.Len() buf.WriteString(`"postal_code":`) buf.WriteString(strconv.Quote(*v)) } if v := t.region; v != nil { if buf.Len() > prev { buf.WriteByte(',') } prev = buf.Len() buf.WriteString(`"region":`) buf.WriteString(strconv.Quote(*v)) } if v := t.streetAddress; v != nil { if buf.Len() > prev { buf.WriteByte(',') } buf.WriteString(`"street_address":`) buf.WriteString(strconv.Quote(*v)) } buf.WriteByte('}') 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-2.1.4/jwt/openid/birthdate.go000066400000000000000000000071031476711647200237130ustar00rootroot00000000000000package openid import ( "bytes" "fmt" "io" "math" "regexp" "strconv" "github.com/lestrrat-go/jwx/v2/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})$`) // 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 interface{}) 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-2.1.4/jwt/openid/builder_gen.go000066400000000000000000000066031476711647200242300ustar00rootroot00000000000000// Code generated by tools/cmd/genjwt/main.go. DO NOT EDIT. package openid import ( "fmt" "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 { claims []*ClaimPair } func NewBuilder() *Builder { return &Builder{} } func (b *Builder) Claim(name string, value interface{}) *Builder { b.claims = append(b.claims, &ClaimPair{Key: name, Value: 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 func (b *Builder) Build() (Token, error) { tok := New() for _, claim := range b.claims { if err := tok.Set(claim.Key.(string), claim.Value); err != nil { return nil, fmt.Errorf(`failed to set claim %q: %w`, claim.Key.(string), err) } } return tok, nil } golang-github-lestrrat-go-jwx-2.1.4/jwt/openid/interface.go000066400000000000000000000005701476711647200237060ustar00rootroot00000000000000package openid import ( "github.com/lestrrat-go/iter/mapiter" "github.com/lestrrat-go/jwx/v2/internal/iter" "github.com/lestrrat-go/jwx/v2/internal/json" ) type ClaimPair = mapiter.Pair type Iterator = mapiter.Iterator type Visitor = iter.MapVisitor type VisitorFunc = iter.MapVisitorFunc type DecodeCtx = json.DecodeCtx type TokenWithDecodeCtx = json.DecodeCtxContainer golang-github-lestrrat-go-jwx-2.1.4/jwt/openid/openid.go000066400000000000000000000026431476711647200232270ustar00rootroot00000000000000// 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/v2/internal/json" "github.com/lestrrat-go/jwx/v2/jwt" ) var registry = json.NewRegistry() func (t *stdToken) Clone() (jwt.Token, error) { var dst jwt.Token = New() for _, pair := range t.makePairs() { //nolint:forcetypeassert key := pair.Key.(string) if err := dst.Set(key, pair.Value); err != nil { return nil, fmt.Errorf(`failed to set %s: %w`, key, 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 `interface{}`, // but you can convert its type to `time.Time` // // bdayif, _ := token.Get(`x-birthday`) // bday := bdayif.(time.Time) func RegisterCustomField(name string, object interface{}) { registry.Register(name, object) } golang-github-lestrrat-go-jwx-2.1.4/jwt/openid/openid_test.go000066400000000000000000000426351476711647200242730ustar00rootroot00000000000000package openid_test import ( "context" "fmt" "strconv" "testing" "time" "github.com/lestrrat-go/jwx/v2/internal/json" "github.com/lestrrat-go/jwx/v2/internal/jwxtest" "github.com/lestrrat-go/jwx/v2/jwa" "github.com/lestrrat-go/jwx/v2/jwt" "github.com/lestrrat-go/jwx/v2/jwt/internal/types" "github.com/lestrrat-go/jwx/v2/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() if !assert.NotNil(t, x) { return } 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 { tc := tc t.Run(tc.KeyName, func(t *testing.T) { t.Run("Accessor", func(t *testing.T) { if !assert.Equal(t, tc.Value, tc.Accessor(), "values should match") { return } }) t.Run("Get", func(t *testing.T) { v, ok := x.Get(tc.KeyName) if !assert.True(t, ok, `x.Get should succeed`) { return } if !assert.Equal(t, tc.Value, v, `values should match`) { return } }) }) } } func TestAdressClaim(t *testing.T) { const src = `{ "formatted": "〒105-0011 æąäēŦéƒŊ港åŒē芝å…Ŧ園īŧ”ä¸į›Žīŧ’−īŧ˜", "street_address": "芝å…Ŧ園īŧ”ä¸į›Žīŧ’−īŧ˜", "locality": "港åŒē", "region": "æąäēŦéƒŊ", "postal_code": "105-0011", "country": "æ—ĨæœŦ" }` var address openid.AddressClaim if !assert.NoError(t, json.Unmarshal([]byte(src), &address), "json.Unmarshal should succeed") { return } var roundtrip openid.AddressClaim buf, err := json.Marshal(address) if !assert.NoError(t, err, `json.Marshal(address) should succeed`) { return } if !assert.NoError(t, json.Unmarshal(buf, &roundtrip), "json.Unmarshal should succeed") { return } for _, x := range []*openid.AddressClaim{&address, &roundtrip} { testStockAddressClaim(t, x) } } func TestOpenIDClaims(t *testing.T) { getVerify := func(token openid.Token, key string, expected interface{}) bool { v, ok := token.Get(key) if !assert.True(t, ok, `token.Get %#v should succeed`, key) { return false } return assert.Equal(t, v, expected) } var base = []struct { Value interface{} Expected func(interface{}) interface{} Check func(openid.Token) Key string }{ { Key: openid.AudienceKey, Value: []string{"developers", "secops", "tac"}, Check: func(token openid.Token) { assert.Equal(t, token.Audience(), []string{"developers", "secops", "tac"}) }, }, { Key: openid.ExpirationKey, Value: tokenTime, Expected: func(v interface{}) interface{} { var n types.NumericDate if err := n.Accept(v); err != nil { panic(err) } return n.Get() }, Check: func(token openid.Token) { assert.Equal(t, token.Expiration(), expectedTokenTime) }, }, { Key: openid.IssuedAtKey, Value: tokenTime, Expected: func(v interface{}) interface{} { var n types.NumericDate if err := n.Accept(v); err != nil { panic(err) } return n.Get() }, Check: func(token openid.Token) { assert.Equal(t, token.Expiration(), expectedTokenTime) }, }, { Key: openid.IssuerKey, Value: "http://www.example.com", Check: func(token openid.Token) { assert.Equal(t, token.Issuer(), "http://www.example.com") }, }, { Key: openid.JwtIDKey, Value: "e9bc097a-ce51-4036-9562-d2ade882db0d", Check: func(token openid.Token) { assert.Equal(t, token.JwtID(), "e9bc097a-ce51-4036-9562-d2ade882db0d") }, }, { Key: openid.NotBeforeKey, Value: tokenTime, Expected: func(v interface{}) interface{} { var n types.NumericDate if err := n.Accept(v); err != nil { panic(err) } return n.Get() }, Check: func(token openid.Token) { assert.Equal(t, token.NotBefore(), expectedTokenTime) }, }, { Key: openid.SubjectKey, Value: "unit test", Check: func(token openid.Token) { assert.Equal(t, token.Subject(), "unit test") }, }, { Value: "jwx", Key: openid.NameKey, Check: func(token openid.Token) { assert.Equal(t, token.Name(), "jwx") }, }, { Value: "jay", Key: openid.GivenNameKey, Check: func(token openid.Token) { assert.Equal(t, token.GivenName(), "jay") }, }, { Value: "weee", Key: openid.MiddleNameKey, Check: func(token openid.Token) { assert.Equal(t, token.MiddleName(), "weee") }, }, { Value: "xi", Key: openid.FamilyNameKey, Check: func(token openid.Token) { assert.Equal(t, token.FamilyName(), "xi") }, }, { Value: "jayweexi", Key: openid.NicknameKey, Check: func(token openid.Token) { assert.Equal(t, token.Nickname(), "jayweexi") }, }, { Value: "jwx", Key: openid.PreferredUsernameKey, Check: func(token openid.Token) { assert.Equal(t, token.PreferredUsername(), "jwx") }, }, { Value: "https://github.com/lestrrat-go/jwx/v2", Key: openid.ProfileKey, Check: func(token openid.Token) { assert.Equal(t, token.Profile(), "https://github.com/lestrrat-go/jwx/v2") }, }, { Value: "https://avatars1.githubusercontent.com/u/36653903?s=400&v=4", Key: openid.PictureKey, Check: func(token openid.Token) { assert.Equal(t, token.Picture(), "https://avatars1.githubusercontent.com/u/36653903?s=400&v=4") }, }, { Value: "https://github.com/lestrrat-go/jwx/v2", Key: openid.WebsiteKey, Check: func(token openid.Token) { assert.Equal(t, token.Website(), "https://github.com/lestrrat-go/jwx/v2") }, }, { Value: "lestrrat+github@gmail.com", Key: openid.EmailKey, Check: func(token openid.Token) { assert.Equal(t, token.Email(), "lestrrat+github@gmail.com") }, }, { Value: true, Key: openid.EmailVerifiedKey, Check: func(token openid.Token) { assert.True(t, token.EmailVerified()) }, }, { Value: "n/a", Key: openid.GenderKey, Check: func(token openid.Token) { assert.Equal(t, token.Gender(), "n/a") }, }, { Value: "2015-11-04", Key: openid.BirthdateKey, Expected: func(v interface{}) interface{} { 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") assert.Equal(t, token.Birthdate(), &b) }, }, { Value: "Asia/Tokyo", Key: openid.ZoneinfoKey, Check: func(token openid.Token) { assert.Equal(t, token.Zoneinfo(), "Asia/Tokyo") }, }, { Value: "ja_JP", Key: openid.LocaleKey, Check: func(token openid.Token) { assert.Equal(t, token.Locale(), "ja_JP") }, }, { Value: "819012345678", Key: openid.PhoneNumberKey, Check: func(token openid.Token) { assert.Equal(t, token.PhoneNumber(), "819012345678") }, }, { Value: true, Key: openid.PhoneNumberVerifiedKey, Check: func(token openid.Token) { assert.True(t, token.PhoneNumberVerified()) }, }, { Value: map[string]interface{}{ "formatted": "〒105-0011 æąäēŦéƒŊ港åŒē芝å…Ŧ園īŧ”ä¸į›Žīŧ’−īŧ˜", "street_address": "芝å…Ŧ園īŧ”ä¸į›Žīŧ’−īŧ˜", "locality": "港åŒē", "region": "æąäēŦéƒŊ", "country": "æ—ĨæœŦ", "postal_code": "105-0011", }, Key: openid.AddressKey, Expected: func(v interface{}) interface{} { address := openid.NewAddress() m, ok := v.(map[string]interface{}) if !ok { panic(fmt.Sprintf("expected map[string]interface{}, got %T", v)) } for name, val := range m { if !assert.NoError(t, address.Set(name, val), `address.Set should succeed`) { return nil } } return address }, Check: func(token openid.Token) { testStockAddressClaim(t, token.Address()) }, }, { Value: aLongLongTimeAgoString, Key: openid.UpdatedAtKey, Expected: func(v interface{}) interface{} { var n types.NumericDate if err := n.Accept(v); err != nil { panic(err) } return n.Get() }, Check: func(token openid.Token) { assert.Equal(t, time.Unix(aLongLongTimeAgo, 0).UTC(), token.UpdatedAt()) }, }, { Value: `dummy`, Key: `dummy`, Check: func(token openid.Token) { v, ok := token.Get(`dummy`) if !assert.True(t, ok, `token.Get should return valid value`) { return } if !assert.Equal(t, `dummy`, v, `values should match`) { return } }, }, } var data = map[string]interface{}{} var expected = map[string]interface{}{} 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() if !assert.NoError(t, err, `b.Build() should succeed`) { return } tokens = append(tokens, openidTokTestCase{Name: `token constructed by calling Set()`, Token: token}) } { // two with json.Marshal / json.Unmarshal src, err := json.MarshalIndent(data, "", " ") if !assert.NoError(t, err, `failed to marshal base map`) { return } t.Logf("Using source JSON: %s", src) token := openid.New() if !assert.NoError(t, json.Unmarshal(src, &token), `json.Unmarshal should succeed`) { return } 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) if !assert.NoError(t, err, `json.Marshal should succeed`) { return } token2 := openid.New() if !assert.NoError(t, json.Unmarshal(buf, &token2), `json.Unmarshal should succeed`) { return } 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() if !assert.NoError(t, err, `rsa.GeneraKey should succeed`) { return } signed, err := jwt.Sign(token, jwt.WithKey(alg, key)) if !assert.NoError(t, err, `jwt.Sign should succeed`) { return } tokenTmp, err := jwt.Parse(signed, jwt.WithToken(openid.New()), jwt.WithKey(alg, &key.PublicKey), jwt.WithValidate(false)) if !assert.NoError(t, err, `parsing the token via jwt.Parse should succeed`) { return } // 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 { token := token t.Run(token.Name, func(t *testing.T) { for _, value := range base { value := value 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) { v := tokens[0].Token t.Run("Iterate", func(t *testing.T) { seen := make(map[string]interface{}) for iter := v.Iterate(context.TODO()); iter.Next(context.TODO()); { pair := iter.Pair() seen[pair.Key.(string)] = pair.Value getV, ok := v.Get(pair.Key.(string)) if !assert.True(t, ok, `v.Get should succeed for key %#v`, pair.Key) { return } if !assert.Equal(t, pair.Value, getV, `pair.Value should match value from v.Get()`) { return } } if !assert.Equal(t, expected, seen, `values should match`) { return } }) t.Run("Walk", func(t *testing.T) { seen := make(map[string]interface{}) v.Walk(context.TODO(), openid.VisitorFunc(func(key string, value interface{}) error { seen[key] = value return nil })) if !assert.Equal(t, expected, seen, `values should match`) { return } }) t.Run("AsMap", func(t *testing.T) { seen, err := v.AsMap(context.TODO()) if !assert.NoError(t, err, `v.AsMap should succeed`) { return } if !assert.Equal(t, expected, seen, `values should match`) { return } }) t.Run("Clone", func(t *testing.T) { cloned, err := v.Clone() if !assert.NoError(t, err, `v.Clone should succeed`) { return } if !assert.True(t, jwt.Equal(v, cloned), `values should match`) { return } }) }) } 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 { tc := tc 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 } if !assert.NoError(t, json.Unmarshal([]byte(tc.Source), &b), `json.Unmarshal should succeed`) { return } if !assert.Equal(t, b.Year(), tc.Year, "year should match") { return } if !assert.Equal(t, b.Month(), tc.Month, "month should match") { return } if !assert.Equal(t, b.Day(), tc.Day, "day should match") { return } serialized, err := json.Marshal(b) if !assert.NoError(t, err, `json.Marshal should succeed`) { return } if !assert.Equal(t, string(serialized), tc.Source, `serialized format should be the same`) { return } stringified := b.String() expectedString, _ := strconv.Unquote(tc.Source) if !assert.Equal(t, stringified, expectedString, `stringified format should be the same`) { return } }) } }) t.Run("empty date", func(t *testing.T) { t.Parallel() var b openid.BirthdateClaim if !assert.Equal(t, b.Year(), 0, "year should match") { return } if !assert.Equal(t, b.Month(), 0, "month should match") { return } if !assert.Equal(t, b.Day(), 0, "day should match") { return } }) t.Run("invalid accept", func(t *testing.T) { t.Parallel() var b openid.BirthdateClaim if !assert.Error(t, b.Accept(nil)) { return } }) } 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`) require.Equal(t, expected, tok.UpdatedAt(), `updated_at should match`) } }) } jwt.Settings(jwt.WithNumericDateParsePedantic(false)) } golang-github-lestrrat-go-jwx-2.1.4/jwt/openid/token_gen.go000066400000000000000000000766101476711647200237270ustar00rootroot00000000000000// Code generated by tools/cmd/genjwt/main.go. DO NOT EDIT. package openid import ( "bytes" "context" "fmt" "sort" "sync" "time" "github.com/lestrrat-go/iter/mapiter" "github.com/lestrrat-go/jwx/v2/internal/base64" "github.com/lestrrat-go/jwx/v2/internal/iter" "github.com/lestrrat-go/jwx/v2/internal/json" "github.com/lestrrat-go/jwx/v2/internal/pool" "github.com/lestrrat-go/jwx/v2/jwt" "github.com/lestrrat-go/jwx/v2/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" ) type Token interface { // Address returns the value for "address" field of the token Address() *AddressClaim // Audience returns the value for "aud" field of the token Audience() []string // Birthdate returns the value for "birthdate" field of the token Birthdate() *BirthdateClaim // Email returns the value for "email" field of the token Email() string // EmailVerified returns the value for "email_verified" field of the token EmailVerified() bool // Expiration returns the value for "exp" field of the token Expiration() time.Time // FamilyName returns the value for "family_name" field of the token FamilyName() string // Gender returns the value for "gender" field of the token Gender() string // GivenName returns the value for "given_name" field of the token GivenName() string // IssuedAt returns the value for "iat" field of the token IssuedAt() time.Time // Issuer returns the value for "iss" field of the token Issuer() string // JwtID returns the value for "jti" field of the token JwtID() string // Locale returns the value for "locale" field of the token Locale() string // MiddleName returns the value for "middle_name" field of the token MiddleName() string // Name returns the value for "name" field of the token Name() string // Nickname returns the value for "nickname" field of the token Nickname() string // NotBefore returns the value for "nbf" field of the token NotBefore() time.Time // PhoneNumber returns the value for "phone_number" field of the token PhoneNumber() string // PhoneNumberVerified returns the value for "phone_number_verified" field of the token PhoneNumberVerified() bool // Picture returns the value for "picture" field of the token Picture() string // PreferredUsername returns the value for "preferred_username" field of the token PreferredUsername() string // Profile returns the value for "profile" field of the token Profile() string // Subject returns the value for "sub" field of the token Subject() string // UpdatedAt returns the value for "updated_at" field of the token UpdatedAt() time.Time // Website returns the value for "website" field of the token Website() string // Zoneinfo returns the value for "zoneinfo" field of the token Zoneinfo() string // PrivateClaims return the entire set of fields (claims) in the token // *other* than the pre-defined fields such as `iss`, `nbf`, `iat`, etc. PrivateClaims() map[string]interface{} // Get returns the value of the corresponding field in the token, such as // `nbf`, `exp`, `iat`, and other user-defined fields. If the field does not // exist in the token, the second return value will be `false` // // If you need to access fields like `alg`, `kid`, `jku`, etc, you need // to access the corresponding fields in the JWS/JWE message. For this, // you will need to access them by directly parsing the payload using // `jws.Parse` and `jwe.Parse` Get(string) (interface{}, bool) // 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, interface{}) error 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) Iterate(context.Context) Iterator Walk(context.Context, Visitor) error AsMap(context.Context) (map[string]interface{}, error) } 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]interface{} } // 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]interface{}), options: jwt.DefaultOptionSet(), } } func (t *stdToken) Options() *jwt.TokenOptionSet { return &t.options } func (t *stdToken) Get(name string) (interface{}, bool) { t.mu.RLock() defer t.mu.RUnlock() switch name { case AddressKey: if t.address == nil { return nil, false } v := t.address return v, true case AudienceKey: if t.audience == nil { return nil, false } v := t.audience.Get() return v, true case BirthdateKey: if t.birthdate == nil { return nil, false } v := t.birthdate return v, true case EmailKey: if t.email == nil { return nil, false } v := *(t.email) return v, true case EmailVerifiedKey: if t.emailVerified == nil { return nil, false } v := *(t.emailVerified) return v, true case ExpirationKey: if t.expiration == nil { return nil, false } v := t.expiration.Get() return v, true case FamilyNameKey: if t.familyName == nil { return nil, false } v := *(t.familyName) return v, true case GenderKey: if t.gender == nil { return nil, false } v := *(t.gender) return v, true case GivenNameKey: if t.givenName == nil { return nil, false } v := *(t.givenName) return v, true case IssuedAtKey: if t.issuedAt == nil { return nil, false } v := t.issuedAt.Get() return v, true case IssuerKey: if t.issuer == nil { return nil, false } v := *(t.issuer) return v, true case JwtIDKey: if t.jwtID == nil { return nil, false } v := *(t.jwtID) return v, true case LocaleKey: if t.locale == nil { return nil, false } v := *(t.locale) return v, true case MiddleNameKey: if t.middleName == nil { return nil, false } v := *(t.middleName) return v, true case NameKey: if t.name == nil { return nil, false } v := *(t.name) return v, true case NicknameKey: if t.nickname == nil { return nil, false } v := *(t.nickname) return v, true case NotBeforeKey: if t.notBefore == nil { return nil, false } v := t.notBefore.Get() return v, true case PhoneNumberKey: if t.phoneNumber == nil { return nil, false } v := *(t.phoneNumber) return v, true case PhoneNumberVerifiedKey: if t.phoneNumberVerified == nil { return nil, false } v := *(t.phoneNumberVerified) return v, true case PictureKey: if t.picture == nil { return nil, false } v := *(t.picture) return v, true case PreferredUsernameKey: if t.preferredUsername == nil { return nil, false } v := *(t.preferredUsername) return v, true case ProfileKey: if t.profile == nil { return nil, false } v := *(t.profile) return v, true case SubjectKey: if t.subject == nil { return nil, false } v := *(t.subject) return v, true case UpdatedAtKey: if t.updatedAt == nil { return nil, false } v := t.updatedAt.Get() return v, true case WebsiteKey: if t.website == nil { return nil, false } v := *(t.website) return v, true case ZoneinfoKey: if t.zoneinfo == nil { return nil, false } v := *(t.zoneinfo) return v, true default: v, ok := t.privateClaims[name] return v, ok } } 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 interface{}) 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 interface{}) 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]interface{}{} } t.privateClaims[name] = value } return nil } func (t *stdToken) Address() *AddressClaim { t.mu.RLock() defer t.mu.RUnlock() return t.address } func (t *stdToken) Audience() []string { t.mu.RLock() defer t.mu.RUnlock() if t.audience != nil { return t.audience.Get() } return nil } func (t *stdToken) Birthdate() *BirthdateClaim { t.mu.RLock() defer t.mu.RUnlock() return t.birthdate } func (t *stdToken) Email() string { t.mu.RLock() defer t.mu.RUnlock() if t.email != nil { return *(t.email) } return "" } func (t *stdToken) EmailVerified() bool { t.mu.RLock() defer t.mu.RUnlock() if t.emailVerified != nil { return *(t.emailVerified) } return false } func (t *stdToken) Expiration() time.Time { t.mu.RLock() defer t.mu.RUnlock() if t.expiration != nil { return t.expiration.Get() } return time.Time{} } func (t *stdToken) FamilyName() string { t.mu.RLock() defer t.mu.RUnlock() if t.familyName != nil { return *(t.familyName) } return "" } func (t *stdToken) Gender() string { t.mu.RLock() defer t.mu.RUnlock() if t.gender != nil { return *(t.gender) } return "" } func (t *stdToken) GivenName() string { t.mu.RLock() defer t.mu.RUnlock() if t.givenName != nil { return *(t.givenName) } return "" } func (t *stdToken) IssuedAt() time.Time { t.mu.RLock() defer t.mu.RUnlock() if t.issuedAt != nil { return t.issuedAt.Get() } return time.Time{} } func (t *stdToken) Issuer() string { t.mu.RLock() defer t.mu.RUnlock() if t.issuer != nil { return *(t.issuer) } return "" } func (t *stdToken) JwtID() string { t.mu.RLock() defer t.mu.RUnlock() if t.jwtID != nil { return *(t.jwtID) } return "" } func (t *stdToken) Locale() string { t.mu.RLock() defer t.mu.RUnlock() if t.locale != nil { return *(t.locale) } return "" } func (t *stdToken) MiddleName() string { t.mu.RLock() defer t.mu.RUnlock() if t.middleName != nil { return *(t.middleName) } return "" } func (t *stdToken) Name() string { t.mu.RLock() defer t.mu.RUnlock() if t.name != nil { return *(t.name) } return "" } func (t *stdToken) Nickname() string { t.mu.RLock() defer t.mu.RUnlock() if t.nickname != nil { return *(t.nickname) } return "" } func (t *stdToken) NotBefore() time.Time { t.mu.RLock() defer t.mu.RUnlock() if t.notBefore != nil { return t.notBefore.Get() } return time.Time{} } func (t *stdToken) PhoneNumber() string { t.mu.RLock() defer t.mu.RUnlock() if t.phoneNumber != nil { return *(t.phoneNumber) } return "" } func (t *stdToken) PhoneNumberVerified() bool { t.mu.RLock() defer t.mu.RUnlock() if t.phoneNumberVerified != nil { return *(t.phoneNumberVerified) } return false } func (t *stdToken) Picture() string { t.mu.RLock() defer t.mu.RUnlock() if t.picture != nil { return *(t.picture) } return "" } func (t *stdToken) PreferredUsername() string { t.mu.RLock() defer t.mu.RUnlock() if t.preferredUsername != nil { return *(t.preferredUsername) } return "" } func (t *stdToken) Profile() string { t.mu.RLock() defer t.mu.RUnlock() if t.profile != nil { return *(t.profile) } return "" } func (t *stdToken) Subject() string { t.mu.RLock() defer t.mu.RUnlock() if t.subject != nil { return *(t.subject) } return "" } func (t *stdToken) UpdatedAt() time.Time { t.mu.RLock() defer t.mu.RUnlock() if t.updatedAt != nil { return t.updatedAt.Get() } return time.Time{} } func (t *stdToken) Website() string { t.mu.RLock() defer t.mu.RUnlock() if t.website != nil { return *(t.website) } return "" } func (t *stdToken) Zoneinfo() string { t.mu.RLock() defer t.mu.RUnlock() if t.zoneinfo != nil { return *(t.zoneinfo) } return "" } func (t *stdToken) PrivateClaims() map[string]interface{} { t.mu.RLock() defer t.mu.RUnlock() return t.privateClaims } func (t *stdToken) makePairs() []*ClaimPair { t.mu.RLock() defer t.mu.RUnlock() pairs := make([]*ClaimPair, 0, 26) if t.address != nil { v := t.address pairs = append(pairs, &ClaimPair{Key: AddressKey, Value: v}) } if t.audience != nil { v := t.audience.Get() pairs = append(pairs, &ClaimPair{Key: AudienceKey, Value: v}) } if t.birthdate != nil { v := t.birthdate pairs = append(pairs, &ClaimPair{Key: BirthdateKey, Value: v}) } if t.email != nil { v := *(t.email) pairs = append(pairs, &ClaimPair{Key: EmailKey, Value: v}) } if t.emailVerified != nil { v := *(t.emailVerified) pairs = append(pairs, &ClaimPair{Key: EmailVerifiedKey, Value: v}) } if t.expiration != nil { v := t.expiration.Get() pairs = append(pairs, &ClaimPair{Key: ExpirationKey, Value: v}) } if t.familyName != nil { v := *(t.familyName) pairs = append(pairs, &ClaimPair{Key: FamilyNameKey, Value: v}) } if t.gender != nil { v := *(t.gender) pairs = append(pairs, &ClaimPair{Key: GenderKey, Value: v}) } if t.givenName != nil { v := *(t.givenName) pairs = append(pairs, &ClaimPair{Key: GivenNameKey, Value: v}) } if t.issuedAt != nil { v := t.issuedAt.Get() pairs = append(pairs, &ClaimPair{Key: IssuedAtKey, Value: v}) } if t.issuer != nil { v := *(t.issuer) pairs = append(pairs, &ClaimPair{Key: IssuerKey, Value: v}) } if t.jwtID != nil { v := *(t.jwtID) pairs = append(pairs, &ClaimPair{Key: JwtIDKey, Value: v}) } if t.locale != nil { v := *(t.locale) pairs = append(pairs, &ClaimPair{Key: LocaleKey, Value: v}) } if t.middleName != nil { v := *(t.middleName) pairs = append(pairs, &ClaimPair{Key: MiddleNameKey, Value: v}) } if t.name != nil { v := *(t.name) pairs = append(pairs, &ClaimPair{Key: NameKey, Value: v}) } if t.nickname != nil { v := *(t.nickname) pairs = append(pairs, &ClaimPair{Key: NicknameKey, Value: v}) } if t.notBefore != nil { v := t.notBefore.Get() pairs = append(pairs, &ClaimPair{Key: NotBeforeKey, Value: v}) } if t.phoneNumber != nil { v := *(t.phoneNumber) pairs = append(pairs, &ClaimPair{Key: PhoneNumberKey, Value: v}) } if t.phoneNumberVerified != nil { v := *(t.phoneNumberVerified) pairs = append(pairs, &ClaimPair{Key: PhoneNumberVerifiedKey, Value: v}) } if t.picture != nil { v := *(t.picture) pairs = append(pairs, &ClaimPair{Key: PictureKey, Value: v}) } if t.preferredUsername != nil { v := *(t.preferredUsername) pairs = append(pairs, &ClaimPair{Key: PreferredUsernameKey, Value: v}) } if t.profile != nil { v := *(t.profile) pairs = append(pairs, &ClaimPair{Key: ProfileKey, Value: v}) } if t.subject != nil { v := *(t.subject) pairs = append(pairs, &ClaimPair{Key: SubjectKey, Value: v}) } if t.updatedAt != nil { v := t.updatedAt.Get() pairs = append(pairs, &ClaimPair{Key: UpdatedAtKey, Value: v}) } if t.website != nil { v := *(t.website) pairs = append(pairs, &ClaimPair{Key: WebsiteKey, Value: v}) } if t.zoneinfo != nil { v := *(t.zoneinfo) pairs = append(pairs, &ClaimPair{Key: ZoneinfoKey, Value: v}) } for k, v := range t.privateClaims { pairs = append(pairs, &ClaimPair{Key: k, Value: v}) } sort.Slice(pairs, func(i, j int) bool { return pairs[i].Key.(string) < pairs[j].Key.(string) }) return pairs } 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 '{' or '}' here. if tok == '}' { // End of object break LOOP } else if tok != '{' { return fmt.Errorf(`expected '{', but got '%c'`, 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) MarshalJSON() ([]byte, error) { buf := pool.GetBytesBuffer() defer pool.ReleaseBytesBuffer(buf) buf.WriteByte('{') enc := json.NewEncoder(buf) for i, pair := range t.makePairs() { f := pair.Key.(string) if i > 0 { buf.WriteByte(',') } buf.WriteRune('"') buf.WriteString(f) buf.WriteString(`":`) switch f { case AudienceKey: if err := json.EncodeAudience(enc, pair.Value.([]string), t.options.IsEnabled(jwt.FlattenAudience)); err != nil { return nil, fmt.Errorf(`failed to encode "aud": %w`, err) } continue case ExpirationKey, IssuedAtKey, NotBeforeKey, UpdatedAtKey: enc.Encode(pair.Value.(time.Time).Unix()) continue } switch v := pair.Value.(type) { case []byte: buf.WriteRune('"') buf.WriteString(base64.EncodeToString(v)) buf.WriteRune('"') default: if err := enc.Encode(v); err != nil { return nil, fmt.Errorf(`failed to marshal field %s: %w`, f, err) } buf.Truncate(buf.Len() - 1) } } buf.WriteByte('}') ret := make([]byte, buf.Len()) copy(ret, buf.Bytes()) return ret, nil } func (t *stdToken) Iterate(ctx context.Context) Iterator { pairs := t.makePairs() ch := make(chan *ClaimPair, len(pairs)) go func(ctx context.Context, ch chan *ClaimPair, pairs []*ClaimPair) { defer close(ch) for _, pair := range pairs { select { case <-ctx.Done(): return case ch <- pair: } } }(ctx, ch, pairs) return mapiter.New(ch) } func (t *stdToken) Walk(ctx context.Context, visitor Visitor) error { return iter.WalkMap(ctx, t, visitor) } func (t *stdToken) AsMap(ctx context.Context) (map[string]interface{}, error) { return iter.AsMap(ctx, t) } golang-github-lestrrat-go-jwx-2.1.4/jwt/options.go000066400000000000000000000304311476711647200221620ustar00rootroot00000000000000package jwt import ( "fmt" "time" "github.com/lestrrat-go/jwx/v2/jwa" "github.com/lestrrat-go/jwx/v2/jwe" "github.com/lestrrat-go/jwx/v2/jwk" "github.com/lestrrat-go/jwx/v2/jws" "github.com/lestrrat-go/option" ) 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 { //nolint:forcetypeassert switch option.Ident() { case identInsecureNoSignature{}: soptions = append(soptions, jws.WithInsecureNoSignature()) case identKey{}: wk := option.Value().(*withKey) // this always succeeds 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{}: sigOpt := option.Value().(jws.SignOption) // this always succeeds soptions = append(soptions, sigOpt) } } return soptions, nil } func toEncryptOptions(options ...Option) ([]jwe.EncryptOption, error) { soptions := make([]jwe.EncryptOption, 0, len(options)) for _, option := range options { //nolint:forcetypeassert switch option.Ident() { case identKey{}: wk := option.Value().(*withKey) // this always succeeds 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{}: encOpt := option.Value().(jwe.EncryptOption) // this always succeeds 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 { //nolint:forcetypeassert switch option.Ident() { case identKey{}: wk := option.Value().(*withKey) // this always succeeds 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{}: wks := option.Value().(*withKeySet) // this always succeeds 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{}: // this one doesn't need conversion. just get the stored option voptions = append(voptions, option.Value().(jws.VerifyOption)) case identKeyProvider{}: kp, ok := option.Value().(jws.KeyProvider) if !ok { return nil, fmt.Errorf(`expected jws.KeyProvider, got %T`, option.Value()) } voptions = append(voptions, jws.WithKeyProvider(kp)) } } return voptions, nil } type withKey struct { alg jwa.KeyAlgorithm key interface{} 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 interface{}, suboptions ...Option) SignEncryptParseOption { return &signEncryptParseOption{option.New(identKey{}, &withKey{ alg: alg, key: key, options: suboptions, })} } type withKeySet struct { set jwk.Set options []interface{} } // 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 ...interface{}) 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 interface{}) ValidateOption { return WithValidator(ClaimValueIs(name, v)) } type claimPair struct { Name string Value interface{} } // 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 interface{}) 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. // // The first argument should either be `nil`, or your custom jwk.Fetcher // object, which tells how the JWKS should be fetched. Leaving it to // `nil` is equivalent to specifying that `jwk.Fetch` should be used. // // You can further pass options to customize the fetching behavior. // // One notable difference in the option available via the `jwt` // package and the `jws.Verify()` or `jwk.Fetch()` functions is that // by default all fetching is disabled unless you explicitly whitelist urls. // Therefore, when you use this option you WILL have to specify at least // the `jwk.WithFetchWhitelist()` suboption: as: // // jwt.Parse(data, jwt.WithVerifyAuto(nil, jwk.WithFetchWhitelist(...))) // // See the list of available options that you can pass to `jwk.Fetch()` // in the `jwk` package 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{}, nil)} } golang-github-lestrrat-go-jwx-2.1.4/jwt/options.yaml000066400000000000000000000247161476711647200225300ustar00rootroot00000000000000package_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: 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` 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: ValidateOption argument_type: time.Duration comment: | WithTruncation specifies the amount that should be used when truncating time values used during time-based validation routines. By default time values are truncated down to second accuracy. If you want to use sub-second accuracy, you will need to set this value to 0. - 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: CompactOnly interface: GlobalOption argument_type: bool comment: | WithCompactOnly option controls whether jwt.Parse should accept only tokens that are in compact serialization format. RFC7519 specifies that JWTs should be serialized in JWS compact form only, but historically this library allowed for deserialization of JWTs in JWS's JSON serialization format. Specifying this option will disable this behavior, and will report errors if the token is not in compact serialization format. - 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 golang-github-lestrrat-go-jwx-2.1.4/jwt/options_gen.go000066400000000000000000000333601476711647200230170ustar00rootroot00000000000000// 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/v2/jwe" "github.com/lestrrat-go/jwx/v2/jws" "github.com/lestrrat-go/option" ) 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() {} // 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() {} // 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 identClock struct{} type identCompactOnly 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 (identClock) String() string { return "WithClock" } func (identCompactOnly) String() string { return "WithCompactOnly" } 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)} } // 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)} } // WithCompactOnly option controls whether jwt.Parse should accept only tokens // that are in compact serialization format. RFC7519 specifies that JWTs // should be serialized in JWS compact form only, but historically this library // allowed for deserialization of JWTs in JWS's JSON serialization format. // Specifying this option will disable this behavior, and will report // errors if the token is not in compact serialization format. func WithCompactOnly(v bool) GlobalOption { return &globalOption{option.New(identCompactOnly{}, v)} } // WithContext allows you to specify a context.Context object to be used // with `jwt.Validate()` option. // // 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. // By default time values are truncated down to second accuracy. // If you want to use sub-second accuracy, you will need to set // this value to 0. func WithTruncation(v time.Duration) ValidateOption { return &validateOption{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-2.1.4/jwt/options_gen_test.go000066400000000000000000000032461476711647200240560ustar00rootroot00000000000000// 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, "WithClock", identClock{}.String()) require.Equal(t, "WithCompactOnly", identCompactOnly{}.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-2.1.4/jwt/serialize.go000066400000000000000000000156021476711647200224610ustar00rootroot00000000000000package jwt import ( "fmt" "github.com/lestrrat-go/jwx/v2/internal/json" "github.com/lestrrat-go/jwx/v2/jwe" "github.com/lestrrat-go/jwx/v2/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, interface{}) (interface{}, 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, _ interface{}) (interface{}, 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(jwa.RSA_OAEP, key.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 interface{}) (interface{}, 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`) } return buf, nil } type genericHeader interface { Get(string) (interface{}, bool) Set(string, interface{}) error } 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 _, ok := hdrs.Get(typKey); !ok { 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 interface{}) (interface{}, error) { payload, ok := v.([]byte) if !ok { return nil, fmt.Errorf(`expected []byte as input`) } for _, option := range s.options { pc, ok := option.Value().(interface{ Protected(jws.Headers) jws.Headers }) if !ok { 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 if v, ok := hdrs.Get("b64"); ok { if bval, bok := v.(bool); bok { if !bval { // 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 interface{}) (interface{}, 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 interface{} = 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-2.1.4/jwt/token_gen.go000066400000000000000000000337151476711647200224500ustar00rootroot00000000000000// Code generated by tools/cmd/genjwt/main.go. DO NOT EDIT. package jwt import ( "bytes" "context" "fmt" "sort" "sync" "time" "github.com/lestrrat-go/iter/mapiter" "github.com/lestrrat-go/jwx/v2/internal/base64" "github.com/lestrrat-go/jwx/v2/internal/iter" "github.com/lestrrat-go/jwx/v2/internal/json" "github.com/lestrrat-go/jwx/v2/internal/pool" "github.com/lestrrat-go/jwx/v2/jwt/internal/types" ) const ( AudienceKey = "aud" ExpirationKey = "exp" IssuedAtKey = "iat" IssuerKey = "iss" JwtIDKey = "jti" NotBeforeKey = "nbf" SubjectKey = "sub" ) // 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 // Expiration returns the value for "exp" field of the token Expiration() time.Time // IssuedAt returns the value for "iat" field of the token IssuedAt() time.Time // Issuer returns the value for "iss" field of the token Issuer() string // JwtID returns the value for "jti" field of the token JwtID() string // NotBefore returns the value for "nbf" field of the token NotBefore() time.Time // Subject returns the value for "sub" field of the token Subject() string // PrivateClaims return the entire set of fields (claims) in the token // *other* than the pre-defined fields such as `iss`, `nbf`, `iat`, etc. PrivateClaims() map[string]interface{} // Get returns the value of the corresponding field in the token, such as // `nbf`, `exp`, `iat`, and other user-defined fields. If the field does not // exist in the token, the second return value will be `false` // // If you need to access fields like `alg`, `kid`, `jku`, etc, you need // to access the corresponding fields in the JWS/JWE message. For this, // you will need to access them by directly parsing the payload using // `jws.Parse` and `jwe.Parse` Get(string) (interface{}, bool) // 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, interface{}) error 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) Iterate(context.Context) Iterator Walk(context.Context, Visitor) error AsMap(context.Context) (map[string]interface{}, error) } 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]interface{} } // 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]interface{}), options: DefaultOptionSet(), } } func (t *stdToken) Options() *TokenOptionSet { return &t.options } func (t *stdToken) Get(name string) (interface{}, bool) { t.mu.RLock() defer t.mu.RUnlock() switch name { case AudienceKey: if t.audience == nil { return nil, false } v := t.audience.Get() return v, true case ExpirationKey: if t.expiration == nil { return nil, false } v := t.expiration.Get() return v, true case IssuedAtKey: if t.issuedAt == nil { return nil, false } v := t.issuedAt.Get() return v, true case IssuerKey: if t.issuer == nil { return nil, false } v := *(t.issuer) return v, true case JwtIDKey: if t.jwtID == nil { return nil, false } v := *(t.jwtID) return v, true case NotBeforeKey: if t.notBefore == nil { return nil, false } v := t.notBefore.Get() return v, true case SubjectKey: if t.subject == nil { return nil, false } v := *(t.subject) return v, true default: v, ok := t.privateClaims[name] return v, ok } } 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 interface{}) 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 interface{}) 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]interface{}{} } t.privateClaims[name] = value } return nil } func (t *stdToken) Audience() []string { t.mu.RLock() defer t.mu.RUnlock() if t.audience != nil { return t.audience.Get() } return nil } func (t *stdToken) Expiration() time.Time { t.mu.RLock() defer t.mu.RUnlock() if t.expiration != nil { return t.expiration.Get() } return time.Time{} } func (t *stdToken) IssuedAt() time.Time { t.mu.RLock() defer t.mu.RUnlock() if t.issuedAt != nil { return t.issuedAt.Get() } return time.Time{} } func (t *stdToken) Issuer() string { t.mu.RLock() defer t.mu.RUnlock() if t.issuer != nil { return *(t.issuer) } return "" } func (t *stdToken) JwtID() string { t.mu.RLock() defer t.mu.RUnlock() if t.jwtID != nil { return *(t.jwtID) } return "" } func (t *stdToken) NotBefore() time.Time { t.mu.RLock() defer t.mu.RUnlock() if t.notBefore != nil { return t.notBefore.Get() } return time.Time{} } func (t *stdToken) Subject() string { t.mu.RLock() defer t.mu.RUnlock() if t.subject != nil { return *(t.subject) } return "" } func (t *stdToken) PrivateClaims() map[string]interface{} { t.mu.RLock() defer t.mu.RUnlock() return t.privateClaims } func (t *stdToken) makePairs() []*ClaimPair { t.mu.RLock() defer t.mu.RUnlock() pairs := make([]*ClaimPair, 0, 7) if t.audience != nil { v := t.audience.Get() pairs = append(pairs, &ClaimPair{Key: AudienceKey, Value: v}) } if t.expiration != nil { v := t.expiration.Get() pairs = append(pairs, &ClaimPair{Key: ExpirationKey, Value: v}) } if t.issuedAt != nil { v := t.issuedAt.Get() pairs = append(pairs, &ClaimPair{Key: IssuedAtKey, Value: v}) } if t.issuer != nil { v := *(t.issuer) pairs = append(pairs, &ClaimPair{Key: IssuerKey, Value: v}) } if t.jwtID != nil { v := *(t.jwtID) pairs = append(pairs, &ClaimPair{Key: JwtIDKey, Value: v}) } if t.notBefore != nil { v := t.notBefore.Get() pairs = append(pairs, &ClaimPair{Key: NotBeforeKey, Value: v}) } if t.subject != nil { v := *(t.subject) pairs = append(pairs, &ClaimPair{Key: SubjectKey, Value: v}) } for k, v := range t.privateClaims { pairs = append(pairs, &ClaimPair{Key: k, Value: v}) } sort.Slice(pairs, func(i, j int) bool { return pairs[i].Key.(string) < pairs[j].Key.(string) }) return pairs } 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 '{' or '}' here. if tok == '}' { // End of object break LOOP } else if tok != '{' { return fmt.Errorf(`expected '{', but got '%c'`, 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) MarshalJSON() ([]byte, error) { buf := pool.GetBytesBuffer() defer pool.ReleaseBytesBuffer(buf) buf.WriteByte('{') enc := json.NewEncoder(buf) for i, pair := range t.makePairs() { f := pair.Key.(string) if i > 0 { buf.WriteByte(',') } buf.WriteRune('"') buf.WriteString(f) buf.WriteString(`":`) switch f { case AudienceKey: if err := json.EncodeAudience(enc, pair.Value.([]string), t.options.IsEnabled(FlattenAudience)); err != nil { return nil, fmt.Errorf(`failed to encode "aud": %w`, err) } continue case ExpirationKey, IssuedAtKey, NotBeforeKey: enc.Encode(pair.Value.(time.Time).Unix()) continue } switch v := pair.Value.(type) { case []byte: buf.WriteRune('"') buf.WriteString(base64.EncodeToString(v)) buf.WriteRune('"') default: if err := enc.Encode(v); err != nil { return nil, fmt.Errorf(`failed to marshal field %s: %w`, f, err) } buf.Truncate(buf.Len() - 1) } } buf.WriteByte('}') ret := make([]byte, buf.Len()) copy(ret, buf.Bytes()) return ret, nil } func (t *stdToken) Iterate(ctx context.Context) Iterator { pairs := t.makePairs() ch := make(chan *ClaimPair, len(pairs)) go func(ctx context.Context, ch chan *ClaimPair, pairs []*ClaimPair) { defer close(ch) for _, pair := range pairs { select { case <-ctx.Done(): return case ch <- pair: } } }(ctx, ch, pairs) return mapiter.New(ch) } func (t *stdToken) Walk(ctx context.Context, visitor Visitor) error { return iter.WalkMap(ctx, t, visitor) } func (t *stdToken) AsMap(ctx context.Context) (map[string]interface{}, error) { return iter.AsMap(ctx, t) } golang-github-lestrrat-go-jwx-2.1.4/jwt/token_options.go000066400000000000000000000050101476711647200233550ustar00rootroot00000000000000package 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)) } // Enable 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-2.1.4/jwt/token_options_gen.go000066400000000000000000000013101476711647200242050ustar00rootroot00000000000000// 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 { i -= 1 if i >= TokenOption(len(_TokenOption_index)-1) { return "TokenOption(" + strconv.FormatInt(int64(i+1), 10) + ")" } return _TokenOption_name[_TokenOption_index[i]:_TokenOption_index[i+1]] } golang-github-lestrrat-go-jwx-2.1.4/jwt/token_options_test.go000066400000000000000000000025661476711647200244310ustar00rootroot00000000000000package jwt_test import ( "testing" "github.com/lestrrat-go/jwx/v2/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-2.1.4/jwt/token_test.go000066400000000000000000000147771476711647200226650ustar00rootroot00000000000000package jwt_test import ( "context" "reflect" "testing" "time" "github.com/lestrrat-go/jwx/v2/internal/json" "github.com/lestrrat-go/jwx/v2/jwt" "github.com/stretchr/testify/assert" ) const ( tokenTime = 233431200 ) var zeroval reflect.Value var expectedTokenTime = time.Unix(tokenTime, 0).UTC() func TestHeader(t *testing.T) { t.Parallel() values := map[string]interface{}{ 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 { if !assert.NoError(t, h.Set(k, v), `h.Set should succeed for key %#v`, k) { return } got, ok := h.Get(k) if !assert.True(t, ok, `h.Get should succeed for key %#v`, k) { return } 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]interface{}{ 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") } for k := range values { _, ok := h.Get(k) if ok { t.Fatalf("Getting %s value should have failed", k) } } _, ok := h.Get("default") if !ok { t.Fatal("Failed to get default value") } }) t.Run("GetError", func(t *testing.T) { t.Parallel() h := jwt.New() issuer := h.Issuer() if issuer != "" { t.Fatalf("Get Issuer should return empty string") } jwtID := h.JwtID() if jwtID != "" { t.Fatalf("Get JWT Id should return empty string") } }) } 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() if !assert.NoError(t, json.Unmarshal(jsonbuf1, t2), `json.Unmarshal should succeed`) { return } if !assert.Equal(t, t1, t2, "tokens should match") { return } _, err = json.MarshalIndent(t2, "", " ") if err != nil { t.Fatalf("JSON marshal error: %s", err.Error()) } } func TestToken(t *testing.T) { tok := jwt.New() def := map[string]struct { Value interface{} 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 { if !assert.NoError(t, tok.Set(k, kdef.Value), `tok.Set(%s) should succeed`, k) { return } } }) t.Run("Get", func(t *testing.T) { rv := reflect.ValueOf(tok) for k, kdef := range def { getval, ok := tok.Get(k) if !assert.True(t, ok, `tok.Get(%s) should succeed`, k) { return } if mname := kdef.Method; mname != "" { method := rv.MethodByName(mname) if !assert.NotEqual(t, zeroval, method, `method %s should not be zero value`, mname) { return } retvals := method.Call(nil) if !assert.Len(t, retvals, 1, `should have exactly one return value`) { return } if !assert.Equal(t, getval, retvals[0].Interface(), `values should match`) { return } } } }) t.Run("Roundtrip", func(t *testing.T) { buf, err := json.Marshal(tok) if !assert.NoError(t, err, `json.Marshal should succeed`) { return } newtok, err := jwt.ParseInsecure(buf) if !assert.NoError(t, err, `jwt.Parse should succeed`) { return } m1, err := tok.AsMap(context.TODO()) if !assert.NoError(t, err, `tok.AsMap should succeed`) { return } m2, err := newtok.AsMap(context.TODO()) if !assert.NoError(t, err, `tok.AsMap should succeed`) { return } if !assert.Equal(t, m1, m2, `tokens should match`) { return } }) t.Run("Set/Remove", func(t *testing.T) { ctx := context.TODO() newtok, err := tok.Clone() if !assert.NoError(t, err, `tok.Clone should succeed`) { return } for iter := tok.Iterate(ctx); iter.Next(ctx); { pair := iter.Pair() newtok.Remove(pair.Key.(string)) } m, err := newtok.AsMap(ctx) if !assert.NoError(t, err, `tok.AsMap should succeed`) { return } if !assert.Len(t, m, 0, `toks should have 0 tok`) { return } for iter := tok.Iterate(ctx); iter.Next(ctx); { pair := iter.Pair() if !assert.NoError(t, newtok.Set(pair.Key.(string), pair.Value), `newtok.Set should succeed`) { return } } }) } golang-github-lestrrat-go-jwx-2.1.4/jwt/validate.go000066400000000000000000000371111476711647200222620ustar00rootroot00000000000000package jwt import ( "context" "fmt" "strconv" "time" ) 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 NewValidationError(fmt.Errorf(`unsupported time claim %s`, strconv.Quote(c))) } func timeClaim(t Token, clock Clock, c string) time.Time { switch c { case ExpirationKey: return t.Expiration() case IssuedAtKey: return t.IssuedAt() case NotBeforeKey: return t.NotBefore() 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 := time.Second 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 { //nolint:forcetypeassert switch o.Ident() { case identClock{}: clock = o.Value().(Clock) case identAcceptableSkew{}: skew = o.Value().(time.Duration) case identTruncation{}: trunc = o.Value().(time.Duration) case identContext{}: //nolint:fatcontext ctx = o.Value().(context.Context) case identResetValidators{}: resetValidators = o.Value().(bool) case identValidator{}: v := o.Value().(Validator) 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 fmt.Errorf(`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 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) ValidationError { 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 NewValidationError(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 NewValidationError(fmt.Errorf(`iitr between %s and %s is less than %s (skew %s)`, iitr.c1, iitr.c2, iitr.dur, skew)) } } return nil } type ValidationError interface { error isValidationError() Unwrap() error } func NewValidationError(err error) ValidationError { return &validationError{error: err} } // This is a generic validation error. type validationError struct { error } func (validationError) isValidationError() {} func (err *validationError) Unwrap() error { return err.error } type missingRequiredClaimError struct { claim string } func (err *missingRequiredClaimError) Error() string { return fmt.Sprintf("%q not satisfied: required claim not found", err.claim) } func (err *missingRequiredClaimError) Is(target error) bool { _, ok := target.(*missingRequiredClaimError) return ok } func (err *missingRequiredClaimError) isValidationError() {} func (*missingRequiredClaimError) Unwrap() error { return nil } type invalidAudienceError struct { error } func (err *invalidAudienceError) Is(target error) bool { _, ok := target.(*invalidAudienceError) return ok } func (err *invalidAudienceError) isValidationError() {} func (err *invalidAudienceError) Unwrap() error { return err.error } func (err *invalidAudienceError) Error() string { if err.error == nil { return `"aud" not satisfied` } return err.error.Error() } type invalidIssuerError struct { error } func (err *invalidIssuerError) Is(target error) bool { _, ok := target.(*invalidIssuerError) return ok } func (err *invalidIssuerError) isValidationError() {} func (err *invalidIssuerError) Unwrap() error { return err.error } func (err *invalidIssuerError) Error() string { if err.error == nil { return `"iss" not satisfied` } return err.error.Error() } var errTokenExpired = NewValidationError(fmt.Errorf(`"exp" not satisfied`)) var errInvalidIssuedAt = NewValidationError(fmt.Errorf(`"iat" not satisfied`)) var errTokenNotYetValid = NewValidationError(fmt.Errorf(`"nbf" not satisfied`)) var errInvalidAudience = &invalidAudienceError{} var errInvalidIssuer = &invalidIssuerError{} var errRequiredClaim = &missingRequiredClaimError{} // ErrTokenExpired returns the immutable error used when `exp` claim // is not satisfied. // // The return value should only be used for comparison using `errors.Is()` func ErrTokenExpired() ValidationError { return errTokenExpired } // ErrInvalidIssuedAt returns the immutable error used when `iat` claim // is not satisfied // // The return value should only be used for comparison using `errors.Is()` func ErrInvalidIssuedAt() ValidationError { return errInvalidIssuedAt } // ErrTokenNotYetValid returns the immutable error used when `nbf` claim // is not satisfied // // The return value should only be used for comparison using `errors.Is()` func ErrTokenNotYetValid() ValidationError { return errTokenNotYetValid } // ErrInvalidAudience returns the immutable error used when `aud` claim // is not satisfied // // The return value should only be used for comparison using `errors.Is()` func ErrInvalidAudience() ValidationError { return errInvalidAudience } // ErrInvalidIssuer returns the immutable error used when `iss` claim // is not satisfied // // The return value should only be used for comparison using `errors.Is()` func ErrInvalidIssuer() ValidationError { return errInvalidIssuer } // ErrMissingRequiredClaim should not have been exported, and will be // removed in a future release. Use `ErrRequiredClaim()` instead to get // an error to be used in `errors.Is()` // // This function should not have been implemented as a constructor. // but rather a means to retrieve an opaque and immutable error value // that could be passed to `errors.Is()`. func ErrMissingRequiredClaim(name string) ValidationError { return &missingRequiredClaimError{claim: name} } // ErrRequiredClaim returns the immutable error used when the claim // specified by `jwt.IsRequired()` is not present. // // The return value should only be used for comparison using `errors.Is()` func ErrRequiredClaim() ValidationError { return errRequiredClaim } // 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) ValidationError } // ValidatorFunc is a type of Validator that does not have any // state, that is implemented as a function type ValidatorFunc func(context.Context, Token) ValidationError func (vf ValidatorFunc) Validate(ctx context.Context, tok Token) ValidationError { 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) ValidationError { tv := t.Expiration() if tv.IsZero() || tv.Unix() == 0 { 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 ErrTokenExpired() } 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) ValidationError { tv := t.IssuedAt() if tv.IsZero() || tv.Unix() == 0 { 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 ErrInvalidIssuedAt() } 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) ValidationError { tv := t.NotBefore() if tv.IsZero() || tv.Unix() == 0 { 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 ErrTokenNotYetValid() } return nil } type claimContainsString struct { name string value string makeErr func(error) ValidationError } // 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: NewValidationError, } } // IsValidationError returns true if the error is a validation error func IsValidationError(err error) bool { switch err { case errTokenExpired, errTokenNotYetValid, errInvalidIssuedAt: return true default: switch err.(type) { case *validationError, *invalidAudienceError, *invalidIssuerError, *missingRequiredClaimError: return true default: return false } } } func (ccs claimContainsString) Validate(_ context.Context, t Token) ValidationError { v, ok := t.Get(ccs.name) if !ok { return ccs.makeErr(fmt.Errorf(`claim %q not found`, ccs.name)) } list, ok := v.([]string) if !ok { return ccs.makeErr(fmt.Errorf(`claim %q must be a []string (got %T)`, ccs.name, v)) } for _, v := range list { if v == ccs.value { return nil } } return ccs.makeErr(fmt.Errorf(`%q not satisfied`, ccs.name)) } func makeInvalidAudienceError(err error) ValidationError { return &invalidAudienceError{error: err} } // 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: makeInvalidAudienceError, } } type claimValueIs struct { name string value interface{} makeErr func(error) ValidationError } // 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 interface{}) Validator { return &claimValueIs{ name: name, value: value, makeErr: NewValidationError, } } func (cv *claimValueIs) Validate(_ context.Context, t Token) ValidationError { v, ok := t.Get(cv.name) if !ok { return cv.makeErr(fmt.Errorf(`%q not satisfied: claim %q does not exist`, cv.name, cv.name)) } if v != cv.value { return cv.makeErr(fmt.Errorf(`%q not satisfied: values do not match`, cv.name)) } return nil } func makeIssuerClaimError(err error) ValidationError { return &invalidIssuerError{error: err} } // issuerClaimValueIs creates a Validator that checks if the issuer claim // matches `value`. func issuerClaimValueIs(value string) Validator { return &claimValueIs{ name: IssuerKey, value: value, makeErr: makeIssuerClaimError, } } // 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) ValidationError { name := string(ir) _, ok := t.Get(name) if !ok { return &missingRequiredClaimError{claim: name} } return nil } golang-github-lestrrat-go-jwx-2.1.4/jwt/validate_test.go000066400000000000000000000370671476711647200233330ustar00rootroot00000000000000package jwt_test import ( "context" "errors" "log" "testing" "time" "github.com/lestrrat-go/jwx/v2/internal/json" "github.com/lestrrat-go/jwx/v2/jwt" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestGHIssue10(t *testing.T) { t.Parallel() // 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 { tc := tc t.Run(tc.ClaimName, func(t *testing.T) { t.Parallel() t1, err := tc.BuildFunc(tc.ClaimValue) if !assert.NoError(t, err, `jwt.NewBuilder should succeed`) { return } // This should succeed, because validation option (tc.OptionFunc) // is not provided in the optional parameters if !assert.NoError(t, jwt.Validate(t1), "t1.Validate should succeed") { return } // This should succeed, because the option is provided with same value if !assert.NoError(t, jwt.Validate(t1, tc.OptionFunc(tc.ClaimValue)), "t1.Validate should succeed") { return } if !assert.Error(t, jwt.Validate(t1, jwt.WithIssuer("poop")), "t1.Validate should fail") { return } }) } t.Run(jwt.IssuerKey, func(t *testing.T) { t.Parallel() t1, err := jwt.NewBuilder(). Issuer("github.com/lestrrat-go/jwx/v2"). Build() if !assert.NoError(t, err, `jwt.NewBuilder should succeed`) { return } // This should succeed, because WithIssuer is not provided in the // optional parameters if !assert.NoError(t, jwt.Validate(t1), "jwt.Validate should succeed") { return } // This should succeed, because WithIssuer is provided with same value if !assert.NoError(t, jwt.Validate(t1, jwt.WithIssuer(t1.Issuer())), "jwt.Validate should succeed") { return } err = jwt.Validate(t1, jwt.WithIssuer("poop")) if !assert.Error(t, err, "jwt.Validate should fail") { return } if !assert.ErrorIs(t, err, jwt.ErrInvalidIssuer(), "error should be jwt.ErrInvalidIssuer") { return } if !assert.True(t, jwt.IsValidationError(err), "error should be a validation error") { return } }) t.Run(jwt.IssuedAtKey, func(t *testing.T) { t.Parallel() tm := time.Now() t1, err := jwt.NewBuilder(). Claim(jwt.IssuedAtKey, tm). Build() if !assert.NoError(t, err, `jwt.NewBuilder should succeed`) { return } 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 { tc := tc t.Run(tc.Name, func(t *testing.T) { log.Printf("%s", tc.Name) err := jwt.Validate(t1, tc.Options...) if !tc.Error { assert.NoError(t, err, `jwt.Validate should succeed`) return } if !assert.Error(t, err, `jwt.Validate should fail`) { return } if !assert.True(t, errors.Is(err, jwt.ErrInvalidIssuedAt()), `error should be jwt.ErrInvalidIssuedAt`) { return } if !assert.False(t, errors.Is(err, jwt.ErrTokenNotYetValid()), `error should be not ErrNotYetValid`) { return } if !assert.True(t, jwt.IsValidationError(err), `error should be a validation error`) { return } }) } }) t.Run(jwt.AudienceKey, func(t *testing.T) { t.Parallel() t1, err := jwt.NewBuilder(). Claim(jwt.AudienceKey, []string{"foo", "bar", "baz"}). Build() if !assert.NoError(t, err, `jwt.NewBuilder should succeed`) { return } // This should succeed, because WithAudience is not provided in the // optional parameters t.Run("`aud` check disabled", func(t *testing.T) { t.Parallel() if !assert.NoError(t, jwt.Validate(t1), `jwt.Validate should succeed`) { return } }) // 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() if !assert.NoError(t, jwt.Validate(t1, jwt.WithAudience("baz")), "jwt.Validate should succeed") { return } }) t.Run("check `aud` contains `poop`", func(t *testing.T) { t.Parallel() err := jwt.Validate(t1, jwt.WithAudience("poop")) if !assert.Error(t, err, "token.Validate should fail") { return } if !assert.ErrorIs(t, err, jwt.ErrInvalidAudience(), `error should be ErrInvalidAudience`) { return } if !assert.True(t, jwt.IsValidationError(err), `error should be a validation error`) { return } }) }) t.Run(jwt.SubjectKey, func(t *testing.T) { t.Parallel() t1, err := jwt.NewBuilder(). Claim(jwt.SubjectKey, "github.com/lestrrat-go/jwx/v2"). Build() if !assert.NoError(t, err, `jwt.NewBuilder should succeed`) { return } // This should succeed, because WithSubject is not provided in the // optional parameters if !assert.NoError(t, jwt.Validate(t1), "token.Validate should succeed") { return } // This should succeed, because WithSubject is provided with same value if !assert.NoError(t, jwt.Validate(t1, jwt.WithSubject(t1.Subject())), "token.Validate should succeed") { return } if !assert.Error(t, jwt.Validate(t1, jwt.WithSubject("poop")), "token.Validate should fail") { return } }) 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() if !assert.NoError(t, err, `jwt.NewBuilder should succeed`) { return } 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 { tc := tc t.Run(tc.Name, func(t *testing.T) { err := jwt.Validate(t1, tc.Options...) if !tc.Error { assert.NoError(t, err, "token.Validate should succeed") return } if !assert.Error(t, err, "token.Validate should fail") { return } if !assert.True(t, errors.Is(err, jwt.ErrTokenNotYetValid()), `error should be ErrTokenNotYetValid`) { return } if !assert.False(t, errors.Is(err, jwt.ErrTokenExpired()), `error should not be ErrTokenExpired`) { return } if !assert.True(t, jwt.IsValidationError(err), `error should be a validation error`) { return } }) } }) 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() if !assert.NoError(t, err, `jwt.NewBuilder should succeed`) { return } 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 { tc := tc t.Run(tc.Name, func(t *testing.T) { err := jwt.Validate(t1, tc.Options...) if !tc.Error { assert.NoError(t, err, `jwt.Validate should succeed`) return } require.Error(t, err, `jwt.Validate should fail`) if !assert.False(t, errors.Is(err, jwt.ErrTokenNotYetValid()), `error should not be ErrTokenNotYetValid`) { return } if !assert.True(t, errors.Is(err, jwt.ErrTokenExpired()), `error should be ErrTokenExpired`) { return } if !assert.True(t, jwt.IsValidationError(err), `error should be a validation error`) { return } }) } }) t.Run("Unix zero times", func(t *testing.T) { t.Parallel() tm := time.Unix(0, 0) t1, err := jwt.NewBuilder(). Claim(jwt.NotBeforeKey, tm). Claim(jwt.IssuedAtKey, tm). Claim(jwt.ExpirationKey, tm). Build() if !assert.NoError(t, err, `jwt.NewBuilder should succeed`) { return } // This should pass because the unix zero times should be ignored if assert.NoError(t, jwt.Validate(t1), "token.Validate should pass") { return } }) t.Run("Go zero times", func(t *testing.T) { t.Parallel() tm := time.Time{} t1, err := jwt.NewBuilder(). Claim(jwt.NotBeforeKey, tm). Claim(jwt.IssuedAtKey, tm). Claim(jwt.ExpirationKey, tm). Build() if !assert.NoError(t, err, `jwt.NewBuilder should succeed`) { return } // This should pass because the go zero times should be ignored if assert.NoError(t, jwt.Validate(t1), "token.Validate should pass") { return } }) 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() if !assert.NoError(t, err, `jwt.NewBuilder should succeed`) { return } buf, err := json.Marshal(t1) if !assert.NoError(t, err, `json.Marshal should succeed`) { return } _, err = jwt.Parse(buf, jwt.WithVerify(false), jwt.WithValidate(true)) // This should fail, because exp is set in the past if !assert.Error(t, err, "jwt.Parse should fail") { return } _, 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 if !assert.NoError(t, err, "jwt.Parse should succeed (1)") { return } // 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)) if !assert.NoError(t, err, "jwt.Parse should succeed (2)") { return } }) t.Run("any claim value", func(t *testing.T) { t.Parallel() t1, err := jwt.NewBuilder(). Claim("email", "email@example.com"). Build() if !assert.NoError(t, err, `jwt.NewBuilder should succeed`) { return } // This should succeed, because WithClaimValue("email", "xxx") is not provided in the // optional parameters if !assert.NoError(t, jwt.Validate(t1), "t1.Validate should succeed") { return } // This should succeed, because WithClaimValue is provided with same value if !assert.NoError(t, jwt.Validate(t1, jwt.WithClaimValue("email", "email@example.com")), "t1.Validate should succeed") { return } if !assert.Error(t, jwt.Validate(t1, jwt.WithClaimValue("email", "poop")), "t1.Validate should fail") { return } if !assert.Error(t, jwt.Validate(t1, jwt.WithClaimValue("xxxx", "email@example.com")), "t1.Validate should fail") { return } if !assert.Error(t, jwt.Validate(t1, jwt.WithClaimValue("xxxx", "")), "t1.Validate should fail") { return } }) } 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) jwt.ValidationError { _, ok := tok.Get(myClaim) if !ok { return jwt.NewValidationError(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]interface{}{"k": "v"}) return t1 }, }, { Name: "Target claim does not exist", MakeToken: func() jwt.Token { t1 := jwt.New() _ = t1.Set("other-claim", map[string]interface{}{"k": "v"}) return t1 }, Error: err0, }, } for _, tc := range testcases { tc := tc t.Run(tc.Name, func(t *testing.T) { t.Parallel() t1 := tc.MakeToken() if err := tc.Error; err != nil { if !assert.ErrorIs(t, jwt.Validate(t1, jwt.WithValidator(v)), err) { return } return } if !assert.NoError(t, jwt.Validate(t1, jwt.WithValidator(v))) { return } }) } } golang-github-lestrrat-go-jwx-2.1.4/jwx.go000066400000000000000000000031201476711647200204660ustar00rootroot00000000000000//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/v2/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/v2/docs) package jwx import ( "github.com/lestrrat-go/jwx/v2/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 { //nolint:forcetypeassert switch option.Ident() { case identUseNumber{}: useNumber = option.Value().(bool) } } json.DecoderSettings(useNumber) } golang-github-lestrrat-go-jwx-2.1.4/jwx_test.go000066400000000000000000000476721476711647200215510ustar00rootroot00000000000000package jwx_test import ( "context" "crypto/ecdsa" "crypto/rsa" "fmt" "strings" "testing" "github.com/lestrrat-go/jwx/v2" "github.com/lestrrat-go/jwx/v2/internal/ecutil" "github.com/lestrrat-go/jwx/v2/internal/jose" "github.com/lestrrat-go/jwx/v2/internal/json" "github.com/lestrrat-go/jwx/v2/internal/jwxtest" "github.com/lestrrat-go/jwx/v2/jwa" "github.com/lestrrat-go/jwx/v2/jwe" "github.com/lestrrat-go/jwx/v2/jwk" "github.com/lestrrat-go/jwx/v2/jws" "github.com/stretchr/testify/assert" "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 ecutil.AvailableAlgorithms() { t.Logf(" %s", alg) } } type jsonUnmarshalWrapper struct { buf []byte } func (w jsonUnmarshalWrapper) Decode(v interface{}) 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} { useNumber := useNumber 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(interface{}) error } }{ {Name: "Decoder", Decoder: json.NewDecoder(strings.NewReader(src))}, {Name: "Unmarshal", Decoder: jsonUnmarshalWrapper{buf: []byte(src)}}, } for _, tc := range decoders { tc := tc t.Run(tc.Name, func(t *testing.T) { var m map[string]interface{} if !assert.NoError(t, tc.Decoder.Decode(&m), `Decode should succeed`) { return } v, ok := m["foo"] if !assert.True(t, ok, `m["foo"] should exist`) { return } if useNumber { if !assert.Equal(t, json.Number("1"), v, `v should be a json.Number object`) { return } } else { if !assert.Equal(t, float64(1), v, `v should be a float64`) { return } } }) } }) } } // 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 } // latchset/jose uses a larger p2c count than we allow jwe.Settings(jwe.WithMaxPBES2Count(32768)) t.Cleanup(func() { jwe.Settings(jwe.WithMaxPBES2Count(10000)) }) t.Run("jwk", func(t *testing.T) { testcases := []struct { Name string Raw interface{} Template string VerifyKey func(context.Context, *testing.T, jwk.Key) bool }{ { 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) bool { v, ok := key.Get(`x-jwx`) require.True(t, ok, `key.Get should succeed`) require.Equal(t, float64(1234), v, `private parameters should match`) return true }, }, } for _, tc := range testcases { tc := tc t.Run(tc.Name, func(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() keyfile, cleanup, err := jose.GenerateJwk(ctx, t, tc.Template) if !assert.NoError(t, err, `jose.GenerateJwk should succeed`) { return } defer cleanup() webkey, err := jwxtest.ParseJwkFile(ctx, keyfile) if !assert.NoError(t, err, `ParseJwkFile should succeed`) { return } if vk := tc.VerifyKey; vk != nil { if !vk(ctx, t, webkey) { return } } if !assert.NoError(t, webkey.Raw(&tc.Raw), `jwk.Raw should succeed`) { return } }) } }) 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, cancel := context.WithCancel(context.Background()) defer cancel() 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 { test := test t.Run(fmt.Sprintf("%s-%s", test.alg, test.enc), func(t *testing.T) { t.Parallel() ctx, cancel := context.WithCancel(context.Background()) defer cancel() 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 { test := test t.Run(test.String(), func(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() 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)) if !assert.NoError(t, err, `jose.GenerateJwk should succeed`) { return } defer joseJwkCleanup() // Load the JWK generated by jose jwxJwk, err := jwxtest.ParseJwkFile(ctx, joseJwkFile) if !assert.NoError(t, err, `jwxtest.ParseJwkFile should succeed`) { return } 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 if !assert.NoError(t, jwxJwk.Raw(&rawkey), `jwk.Raw should succeed`) { return } case jwa.ECDH_ES, jwa.ECDH_ES_A128KW, jwa.ECDH_ES_A192KW, jwa.ECDH_ES_A256KW: var rawkey ecdsa.PrivateKey if !assert.NoError(t, jwxJwk.Raw(&rawkey), `jwk.Raw should succeed`) { return } default: var rawkey []byte if !assert.NoError(t, jwxJwk.Raw(&rawkey), `jwk.Raw should succeed`) { return } } }) 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) if !assert.NoError(t, err, `jose.EncryptJwe should succeed`) { return } defer joseCryptCleanup() jwxtest.DumpFile(t, joseCryptFile) // let jwx decrypt the jose crypted file payload, err := jwxtest.DecryptJweFile(ctx, joseCryptFile, spec.alg, joseJwkFile) if !assert.NoError(t, err, `decryptFile.DecryptJwe should succeed`) { return } if !assert.Equal(t, expected, payload, `decrypted payloads should match`) { return } }) 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) if !assert.NoError(t, err, `jwxtest.EncryptJweFile should succeed`) { return } defer jwxCryptCleanup() payload, err := jose.DecryptJwe(ctx, t, jwxCryptFile, joseJwkFile) if !assert.NoError(t, err, `jose.DecryptJwe should succeed`) { return } if !assert.Equal(t, expected, payload, `decrypted payloads should match`) { return } }) } 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)) if !assert.NoError(t, err, `jose.GenerateJwk should succeed`) { return } defer joseJwkCleanup() // Load the JWK generated by jose _, err = jwxtest.ParseJwkFile(ctx, joseJwkFile) if !assert.NoError(t, err, `jwxtest.ParseJwkFile should succeed`) { return } 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) if !assert.NoError(t, err, `jose.SignJws should succeed`) { return } defer joseCryptCleanup() jwxtest.DumpFile(t, joseCryptFile) // let jwx decrypt the jose crypted file payload, err := jwxtest.VerifyJwsFile(ctx, joseCryptFile, alg, joseJwkFile) if !assert.NoError(t, err, `jwxtest.VerifyJwsFile should succeed`) { return } if !assert.Equal(t, expected, payload, `decrypted payloads should match`) { return } }) t.Run("Sign with jwx, Verify with jose", func(t *testing.T) { jwxCryptFile, jwxCryptCleanup, err := jwxtest.SignJwsFile(ctx, t.TempDir(), expected, alg, joseJwkFile) if !assert.NoError(t, err, `jwxtest.SignJwsFile should succeed`) { return } defer jwxCryptCleanup() payload, err := jose.VerifyJws(ctx, t, jwxCryptFile, joseJwkFile) if !assert.NoError(t, err, `jose.VerifyJws should succeed`) { return } if !assert.Equal(t, expected, payload, `decrypted payloads should match`) { return } }) } func TestGHIssue230(t *testing.T) { t.Parallel() if !jose.Available() { t.SkipNow() } data := "eyJhbGciOiJFQ0RILUVTIiwiY2xldmlzIjp7InBpbiI6InRhbmciLCJ0YW5nIjp7ImFkdiI6eyJrZXlzIjpbeyJhbGciOiJFQ01SIiwiY3J2IjoiUC01MjEiLCJrZXlfb3BzIjpbImRlcml2ZUtleSJdLCJrdHkiOiJFQyIsIngiOiJBZm5tR2xHRTFHRUZ5NEpUT2tGWmo5ZEhEUmdpVE5IeFBST3hpZDZLdm0xVGRFQkZ3bElsSVB6TG5lTjlnb3h6OUVGYmJLM3BoN0tWZS05aVF4MmxhOVNFIiwieSI6IkFmZGFaTVYzVzk1NE14elQxeXF3MWVaRU9xTFFZZnBXSGczMlJvekhyQjBEYmoxWWV3OVFvTDg1M2Y2aUw2REIyRC1nbEcxSFFsb3czdGRNdFhjN1pSY0IifSx7ImFsZyI6IkVTNTEyIiwiY3J2IjoiUC01MjEiLCJrZXlfb3BzIjpbInZlcmlmeSJdLCJrdHkiOiJFQyIsIngiOiJBR0drcXRPZzZqel9pZnhmVnVWQ01CalVySFhCTGtfS2hIb3lKRkU5NmJucTZKZVVHNFNMZnRrZ2FIYk5WT0U4Q3Mwd0JqR0ZkSWxDbnBmak94RGJfbFBoIiwieSI6IkFLU0laT0JYY1Jfa3RkWjZ6T3F3TGI5SEJzai0yYmRMUmw5dFZVbnVlV2N3aXg5X3NiekliSWx0SE9YUGhBTW9yaUlYMWVyNzc4Unh6Vkg5d0FtaUhGa1kifV19LCJ1cmwiOiJodHRwOi8vbG9jYWxob3N0OjM5NDIxIn19LCJlbmMiOiJBMjU2R0NNIiwiZXBrIjp7ImNydiI6IlAtNTIxIiwia3R5IjoiRUMiLCJ4IjoiQUJMUm9sQWotZFdVdzZLSjg2T3J6d1F6RjlGT09URFZBZnNWNkh0OU0zREhyQ045Q0N6dVJ1b3cwbWp6M3BjZnVCaFpYREpfN0dkdzE0LXdneV9fTFNrYyIsInkiOiJBT3NRMzlKZmFQVGhjc2FZTjhSMVBHXzIwYXZxRU1NRl9fM2RHQmI3c1BqNmktNEJORDVMdkZ3cVpJT1l4SS1kVWlvNzkyOWY1YnE0eEdJY0lGWWtlbllxIn0sImtpZCI6ImhlZmVpNzVqMkp4Sko3REZnSDAxUWlOVmlGayJ9..GH3-8v7wfxEsRnki.wns--EIYTRjM3Tb0HyA.EGn2Gq7PnSVvPaMN0oRi5A" compactMsg, err := jwe.ParseString(data) if !assert.NoError(t, err, `jwe.ParseString should succeed`) { return } formatted, err := jose.FmtJwe(context.TODO(), t, []byte(data)) if !assert.NoError(t, err, `jose.FmtJwe should succeed`) { return } jsonMsg, err := jwe.Parse(formatted) if !assert.NoError(t, err, `jwe.Parse should succeed`) { return } if !assert.Equal(t, compactMsg, jsonMsg, `messages should match`) { return } } 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/v2"}`), }, } for _, tc := range testcases { tc := tc t.Run(tc.Name, func(t *testing.T) { got := jwx.GuessFormat(tc.Source) if !assert.Equal(t, got, tc.Expected, `value of jwx.GuessFormat should match (%s != %s)`, got, tc.Expected) { return } }) } } 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 { tc := tc t.Run(tc.Expected, func(t *testing.T) { if !assert.Equal(t, tc.Expected, tc.Value.String(), `stringification should match`) { return } }) } } 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 []interface{} InvalidSigningKeys []interface{} ValidVerificationKeys []interface{} InvalidVerificationKeys []interface{} }{ { Name: `ECDSA`, Algorithm: jwa.ES256, ValidSigningKeys: []interface{}{ecdsaKey}, InvalidSigningKeys: []interface{}{rsaKey, okpKey, symmetricKey}, ValidVerificationKeys: []interface{}{ecdsaKey.PublicKey}, InvalidVerificationKeys: []interface{}{rsaKey.PublicKey, okpKey.Public(), symmetricKey}, }, { Name: `RSA`, Algorithm: jwa.RS256, ValidSigningKeys: []interface{}{rsaKey}, InvalidSigningKeys: []interface{}{ecdsaKey, okpKey, symmetricKey}, ValidVerificationKeys: []interface{}{rsaKey.PublicKey}, InvalidVerificationKeys: []interface{}{ecdsaKey.PublicKey, okpKey.Public(), symmetricKey}, }, { Name: `OKP`, Algorithm: jwa.EdDSA, ValidSigningKeys: []interface{}{okpKey}, InvalidSigningKeys: []interface{}{ecdsaKey, rsaKey, symmetricKey}, ValidVerificationKeys: []interface{}{okpKey.Public()}, InvalidVerificationKeys: []interface{}{ecdsaKey.PublicKey, rsaKey.PublicKey, symmetricKey}, }, } for _, tc := range testcases { tc := tc t.Run(tc.Name, func(t *testing.T) { for _, valid := range tc.ValidSigningKeys { valid := valid 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 { invalid := invalid 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 { valid := valid 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 { invalid := invalid 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.FromRaw([]byte("secure-key")) require.NoError(t, err, `jwk.FromRaw 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`) } golang-github-lestrrat-go-jwx-2.1.4/options.go000066400000000000000000000011241476711647200213530ustar00rootroot00000000000000package jwx import "github.com/lestrrat-go/option" type identUseNumber struct{} type Option = option.Interface type JSONOption interface { Option isJSONOption() } type jsonOption struct { Option } func (o *jsonOption) isJSONOption() {} func newJSONOption(n interface{}, v interface{}) 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-2.1.4/scripts/000077500000000000000000000000001476711647200210225ustar00rootroot00000000000000golang-github-lestrrat-go-jwx-2.1.4/scripts/benchcmp.sh000077500000000000000000000022061476711647200231400ustar00rootroot00000000000000#!/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-2.1.4/scripts/check-diff.sh000077500000000000000000000010741476711647200233460ustar00rootroot00000000000000#!/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-2.1.4/scripts/tidy.sh000077500000000000000000000001661476711647200223350ustar00rootroot00000000000000#!/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-2.1.4/scripts/update-mods.sh000077500000000000000000000013531476711647200236050ustar00rootroot00000000000000#!/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/v2@"$TAG" go mod tidy popd > /dev/null done golang-github-lestrrat-go-jwx-2.1.4/tools/000077500000000000000000000000001476711647200204735ustar00rootroot00000000000000golang-github-lestrrat-go-jwx-2.1.4/tools/autodoc.pl000066400000000000000000000053611476711647200224730ustar00rootroot00000000000000#!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-2.1.4/tools/cmd/000077500000000000000000000000001476711647200212365ustar00rootroot00000000000000golang-github-lestrrat-go-jwx-2.1.4/tools/cmd/genjwa.sh000077500000000000000000000006401476711647200230500ustar00rootroot00000000000000#!/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" echo "✔ done!" rm "$EXE" golang-github-lestrrat-go-jwx-2.1.4/tools/cmd/genjwa/000077500000000000000000000000001476711647200225115ustar00rootroot00000000000000golang-github-lestrrat-go-jwx-2.1.4/tools/cmd/genjwa/go.mod000066400000000000000000000005161476711647200236210ustar00rootroot00000000000000module github.com/lestrrat-go/jwx/v2/tools/cmd/genalgs go 1.16 require ( github.com/lestrrat-go/codegen v1.0.4 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/tools v0.16.1 // indirect ) golang-github-lestrrat-go-jwx-2.1.4/tools/cmd/genjwa/go.sum000066400000000000000000000203371476711647200236510ustar00rootroot00000000000000github.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= 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-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 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/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 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/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/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= 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/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/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 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-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.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200918232735-d647fc253266/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= 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/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= 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-2.1.4/tools/cmd/genjwa/main.go000066400000000000000000000574511476711647200240000ustar00rootroot00000000000000package main import ( "bytes" "fmt" "log" "os" "sort" "strconv" "strings" "github.com/lestrrat-go/codegen" ) func main() { if err := _main(); err != nil { log.Printf("%s", err) os.Exit(1) } } func _main() error { typs := []typ{ { 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: []element{ { name: `NoCompress`, value: ``, comment: `No compression`, }, { name: `Deflate`, value: `DEF`, comment: `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: []element{ { name: `A128CBC_HS256`, value: `A128CBC-HS256`, comment: `AES-CBC + HMAC-SHA256 (128)`, }, { name: `A192CBC_HS384`, value: `A192CBC-HS384`, comment: `AES-CBC + HMAC-SHA384 (192)`, }, { name: `A256CBC_HS512`, value: `A256CBC-HS512`, comment: `AES-CBC + HMAC-SHA512 (256)`, }, { name: `A128GCM`, value: `A128GCM`, comment: `AES-GCM (128)`, }, { name: `A192GCM`, value: `A192GCM`, comment: `AES-GCM (192)`, }, { name: `A256GCM`, value: `A256GCM`, comment: `AES-GCM (256)`, }, }, }, { name: `KeyType`, comment: `KeyType represents the key type ("kty") that are supported`, filename: "key_type_gen.go", elements: []element{ { name: `InvalidKeyType`, value: ``, 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: []element{ { name: `InvalidEllipticCurve`, value: `P-invalid`, invalid: true, }, { name: `P256`, value: `P-256`, }, { name: `P384`, value: `P-384`, }, { name: `P521`, value: `P-521`, }, { name: `Ed25519`, value: `Ed25519`, }, { name: `Ed448`, value: `Ed448`, }, { name: `X25519`, value: `X25519`, }, { name: `X448`, value: `X448`, }, }, }, { 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: []element{ { name: `NoSignature`, value: "none", }, { name: `HS256`, value: "HS256", comment: `HMAC using SHA-256`, sym: true, }, { name: `HS384`, value: `HS384`, comment: `HMAC using SHA-384`, sym: true, }, { name: `HS512`, value: "HS512", comment: `HMAC using SHA-512`, sym: true, }, { name: `RS256`, value: `RS256`, comment: `RSASSA-PKCS-v1.5 using SHA-256`, }, { name: `RS384`, value: `RS384`, comment: `RSASSA-PKCS-v1.5 using SHA-384`, }, { name: `RS512`, value: `RS512`, comment: `RSASSA-PKCS-v1.5 using SHA-512`, }, { name: `ES256`, value: `ES256`, comment: `ECDSA using P-256 and SHA-256`, }, { name: `ES384`, value: `ES384`, comment: `ECDSA using P-384 and SHA-384`, }, { name: `ES512`, value: "ES512", comment: `ECDSA using P-521 and SHA-512`, }, { name: `ES256K`, value: "ES256K", comment: `ECDSA using secp256k1 and SHA-256`, }, { name: `EdDSA`, value: `EdDSA`, comment: `EdDSA signature algorithms`, }, { name: `PS256`, value: `PS256`, comment: `RSASSA-PSS using SHA256 and MGF1-SHA256`, }, { name: `PS384`, value: `PS384`, comment: `RSASSA-PSS using SHA384 and MGF1-SHA384`, }, { name: `PS512`, value: `PS512`, comment: `RSASSA-PSS using SHA512 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: []element{ { name: `RSA1_5`, value: "RSA1_5", comment: `RSA-PKCS1v1.5`, }, { name: `RSA_OAEP`, value: "RSA-OAEP", comment: `RSA-OAEP-SHA1`, }, { name: `RSA_OAEP_256`, value: "RSA-OAEP-256", comment: `RSA-OAEP-SHA256`, }, { name: `RSA_OAEP_384`, value: "RSA-OAEP-384", comment: `RSA-OAEP-SHA384`, }, { name: `RSA_OAEP_512`, value: "RSA-OAEP-512", comment: `RSA-OAEP-SHA512`, }, { name: `A128KW`, value: "A128KW", comment: `AES key wrap (128)`, sym: true, }, { name: `A192KW`, value: "A192KW", comment: `AES key wrap (192)`, sym: true, }, { name: `A256KW`, value: "A256KW", comment: `AES key wrap (256)`, sym: true, }, { name: `DIRECT`, value: "dir", comment: `Direct encryption`, sym: true, }, { name: `ECDH_ES`, value: "ECDH-ES", comment: `ECDH-ES`, }, { name: `ECDH_ES_A128KW`, value: "ECDH-ES+A128KW", comment: `ECDH-ES + AES key wrap (128)`, }, { name: `ECDH_ES_A192KW`, value: "ECDH-ES+A192KW", comment: `ECDH-ES + AES key wrap (192)`, }, { name: `ECDH_ES_A256KW`, value: "ECDH-ES+A256KW", comment: `ECDH-ES + AES key wrap (256)`, }, { name: `A128GCMKW`, value: "A128GCMKW", comment: `AES-GCM key wrap (128)`, sym: true, }, { name: `A192GCMKW`, value: "A192GCMKW", comment: `AES-GCM key wrap (192)`, sym: true, }, { name: `A256GCMKW`, value: "A256GCMKW", comment: `AES-GCM key wrap (256)`, sym: true, }, { name: `PBES2_HS256_A128KW`, value: "PBES2-HS256+A128KW", comment: `PBES2 + HMAC-SHA256 + AES key wrap (128)`, sym: true, }, { name: `PBES2_HS384_A192KW`, value: "PBES2-HS384+A192KW", comment: `PBES2 + HMAC-SHA384 + AES key wrap (192)`, sym: true, }, { name: `PBES2_HS512_A256KW`, value: "PBES2-HS512+A256KW", comment: `PBES2 + HMAC-SHA512 + AES key wrap (256)`, sym: true, }, }, }, } sort.Slice(typs, func(i, j int) bool { return typs[i].name < typs[j].name }) for _, t := range typs { t := t sort.Slice(t.elements, func(i, j int) bool { return t.elements[i].name < t.elements[j].name }) if err := t.Generate(); err != nil { return fmt.Errorf(`failed to generate file: %w`, err) } if err := t.GenerateTest(); err != nil { return fmt.Errorf(`failed to generate test file: %w`, err) } } return nil } type typ struct { name string comment string filename string elements []element symmetric bool } type element struct { name string value string comment string invalid bool sym bool } func (t typ) Generate() error { var buf bytes.Buffer 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{ "fmt", "sort", "sync", "strings", } for _, pkg := range pkgs { o.L("%s", strconv.Quote(pkg)) } o.L(")") o.LL("// %s", t.comment) o.L("type %s string", t.name) o.LL("// Supported values for %s", t.name) o.L("const (") for _, e := range t.elements { o.L("%s %s = %s", e.name, t.name, strconv.Quote(e.value)) if len(e.comment) > 0 { o.R(" // %s", e.comment) } } o.L(")") // end const // Register and related tools are provided so users can register their own types. // This triggers some re-building of data structures that are otherwise // reused for efficiency o.LL("var mu%[1]ss sync.RWMutex", t.name) o.L("var all%[1]ss map[%[1]s]struct{}", t.name) o.L("var list%[1]s []%[1]s", t.name) if t.symmetric { o.L("var symmetric%[1]ss map[%[1]s]struct{}", t.name) } o.LL("func init() {") o.L("mu%[1]ss.Lock()", t.name) o.L("defer mu%[1]ss.Unlock()", t.name) o.L("all%[1]ss = make(map[%[1]s]struct{})", t.name) for _, e := range t.elements { if !e.invalid { o.L("all%[1]ss[%[2]s] = struct{}{}", t.name, e.name) } } if t.symmetric { o.L("symmetric%[1]ss = make(map[%[1]s]struct{})", t.name) for _, e := range t.elements { if !e.invalid && e.sym { o.L("symmetric%[1]ss[%[2]s] = struct{}{}", t.name, e.name) } } } o.L("rebuild%[1]s()", t.name) o.L("}") if !t.symmetric { o.LL("// Register%[1]s registers a new %[1]s so that the jwx can properly handle the new value.", t.name) o.L("// Duplicates will silently be ignored") o.L("func Register%[1]s(v %[1]s) {", t.name) o.L("mu%[1]ss.Lock()", t.name) o.L("defer mu%[1]ss.Unlock()", t.name) o.L("if _, ok := all%[1]ss[v]; !ok {", t.name) o.L("all%[1]ss[v] = struct{}{}", t.name) o.L("rebuild%[1]s()", t.name) o.L("}") o.L("}") } else { o.LL("// Register%[1]s registers a new %[1]s so that the jwx can properly handle the new value.", t.name) o.L("// Duplicates will silently be ignored") o.L("func Register%[1]s(v %[1]s) {", t.name) o.L("Register%[1]sWithOptions(v)", t.name) o.L("}") o.LL("// Register%[1]sWithOptions is the same as Register%[1]s when used without options,", t.name) o.L("// but allows its behavior to change based on the provided options.") o.L("// This is an experimental AND stopgap function which will most likely be merged in Register%[1]s, and subsequently removed in the future. As such it should not be considered part of the stable API -- it is still subject to change.", t.name) o.L("//") o.L("// You can pass `WithSymmetricAlgorithm(true)` to let the library know that it's a symmetric algorithm. This library makes no attempt to verify if the algorithm is indeed symmetric or not.") o.L("func Register%[1]sWithOptions(v %[1]s, options ...RegisterAlgorithmOption) {", t.name) o.L("var symmetric bool") o.L("//nolint:forcetypeassert") o.L("for _, option := range options {") o.L("switch option.Ident() {") o.L("case identSymmetricAlgorithm{}:") o.L("symmetric = option.Value().(bool)") o.L("}") o.L("}") o.L("mu%[1]ss.Lock()", t.name) o.L("defer mu%[1]ss.Unlock()", t.name) o.L("if _, ok := all%[1]ss[v]; !ok {", t.name) o.L("all%[1]ss[v] = struct{}{}", t.name) o.L("if symmetric {") o.L("symmetric%[1]ss[v] = struct{}{}", t.name) o.L("}") o.L("rebuild%[1]s()", t.name) o.L("}") o.L("}") } o.LL("// Unregister%[1]s unregisters a %[1]s from its known database.", t.name) o.L("// Non-existent entries will silently be ignored") o.L("func Unregister%[1]s(v %[1]s) {", t.name) o.L("mu%[1]ss.Lock()", t.name) o.L("defer mu%[1]ss.Unlock()", t.name) o.L("if _, ok := all%[1]ss[v]; ok {", t.name) o.L("delete(all%[1]ss, v)", t.name) if t.symmetric { o.L("if _, ok := symmetric%[1]ss[v]; ok {", t.name) o.L("delete(symmetric%[1]ss, v)", t.name) o.L("}") } o.L("rebuild%[1]s()", t.name) o.L("}") o.L("}") o.LL("func rebuild%[1]s() {", t.name) o.L("list%[1]s = make([]%[1]s, 0, len(all%[1]ss))", t.name) o.L("for v := range all%ss {", t.name) o.L("list%[1]s = append(list%[1]s, v)", t.name) o.L("}") o.L("sort.Slice(list%s, func(i, j int) bool {", t.name) o.L("return string(list%[1]s[i]) < string(list%[1]s[j])", t.name) o.L("})") o.L("}") o.LL("// %[1]ss returns a list of all available values for %[1]s", t.name) o.L("func %[1]ss() []%[1]s {", t.name) o.L("mu%[1]ss.RLock()", t.name) o.L("defer mu%[1]ss.RUnlock()", t.name) o.L("return list%s", t.name) o.L("}") o.LL("// Accept is used when conversion from values given by") o.L("// outside sources (such as JSON payloads) is required") o.L("func (v *%s) Accept(value interface{}) error {", t.name) o.L("var tmp %s", t.name) o.L("if x, ok := value.(%s); ok {", t.name) o.L("tmp = x") o.L("} else {") o.L("var s string") o.L("switch x := value.(type) {") o.L("case fmt.Stringer:") o.L("s = x.String()") o.L("case string:") o.L("s = x") o.L("default:") o.L("return fmt.Errorf(`invalid type for jwa.%s: %%T`, value)", t.name) o.L("}") o.L("tmp = %s(s)", t.name) o.L("}") o.L("if _, ok := all%ss[tmp]; !ok {", t.name) o.L("return fmt.Errorf(`invalid jwa.%s value`)", t.name) o.L("}") o.LL("*v = tmp") o.L("return nil") o.L("}") // func (v *%s) Accept(v interface{}) o.LL("// String returns the string representation of a %s", t.name) o.L("func (v %s) String() string {", t.name) o.L("return string(v)") o.L("}") if t.symmetric { o.LL("// IsSymmetric returns true if the algorithm is a symmetric type.") if t.name == "SignatureAlgorithm" { o.L("// Keep in mind that the NoSignature algorithm is neither a symmetric nor an asymmetric algorithm.") } o.L("func (v %s) IsSymmetric() bool {", t.name) o.L("_, ok := symmetric%[1]ss[v]", t.name) o.L("return ok") 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 (t typ) GenerateTest() 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{ "testing", "github.com/lestrrat-go/jwx/v2/jwa", "github.com/stretchr/testify/assert", } 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(`accept jwa constant %s`, func(t *testing.T) {", e.name) o.L("t.Parallel()") o.L("var dst jwa.%s", t.name) o.L("if !assert.NoError(t, dst.Accept(jwa.%s), `accept is successful`) {", e.name) o.L("return") o.L("}") o.L("if !assert.Equal(t, jwa.%s, dst, `accepted value should be equal to constant`) {", e.name) o.L("return") o.L("}") o.L("})") o.L("t.Run(`accept the string %s`, func(t *testing.T) {", e.value) o.L("t.Parallel()") o.L("var dst jwa.%s", t.name) o.L("if !assert.NoError(t, dst.Accept(%#v), `accept is successful`) {", e.value) o.L("return") o.L("}") o.L("if !assert.Equal(t, jwa.%s, dst, `accepted value should be equal to constant`) {", e.name) o.L("return") o.L("}") o.L("})") o.L("t.Run(`accept fmt.Stringer for %s`, func(t *testing.T) {", e.value) o.L("t.Parallel()") o.L("var dst jwa.%s", t.name) o.L("if !assert.NoError(t, dst.Accept(stringer{ src: %#v }), `accept is successful`) {", e.value) o.L("return") o.L("}") o.L("if !assert.Equal(t, jwa.%s, dst, `accepted value should be equal to constant`) {", e.name) o.L("return") o.L("}") o.L("})") o.L("t.Run(`stringification for %s`, func(t *testing.T) {", e.value) o.L("t.Parallel()") o.L("if !assert.Equal(t, %#v, jwa.%s.String(), `stringified value matches`) {", e.value, e.name) o.L("return") o.L("}") o.L("})") } for _, e := range invalids { o.L("t.Run(`do not accept invalid constant %s`, func(t *testing.T) {", e.name) o.L("t.Parallel()") o.L("var dst jwa.%s", t.name) o.L("if !assert.Error(t, dst.Accept(jwa.%s), `accept should fail`) {", e.name) o.L("return") o.L("}") o.L("})") } o.L("t.Run(`bail out on random integer value`, func(t *testing.T) {") o.L("t.Parallel()") o.L("var dst jwa.%s", t.name) o.L("if !assert.Error(t, dst.Accept(1), `accept should fail`) {") o.L("return") o.L("}") o.L("})") o.L("t.Run(`do not accept invalid (totally made up) string value`, func(t *testing.T) {") o.L("t.Parallel()") o.L("var dst jwa.%s", t.name) o.L("if !assert.Error(t, dst.Accept(`totallyInvalidValue`), `accept should fail`) {") o.L("return") o.L("}") 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("assert.True(t, jwa.%[1]s.IsSymmetric(), `jwa.%[1]s should be symmetric`)", e.name) } else { o.L("assert.False(t, jwa.%[1]s.IsSymmetric(), `jwa.%[1]s should NOT be symmetric`)", 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("if _, ok := expected[v]; !assert.True(t, ok, `%%s should be in the expected list`, v) {") o.L("return") o.L("}") o.L("delete(expected, v)") o.L("}") o.L("if !assert.Len(t, expected, 0) {") o.L("return") o.L("}") 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(`customAlgorithm := jwa.%[1]s("custom-algorithm")`, t.name) 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(`accept variable used to register custom algorithm`, func(t *testing.T) {") o.L("t.Parallel()") o.L("var dst jwa.%[1]s", t.name) o.L("if !assert.NoError(t, dst.Accept(customAlgorithm), `accept is successful`) {") o.L("return") o.L("}") o.L("assert.Equal(t, customAlgorithm, dst, `accepted value should be equal to variable`)") o.L("})") o.L("t.Run(`accept the string custom-algorithm`, func(t *testing.T) {") o.L("t.Parallel()") o.L("var dst jwa.%[1]s", t.name) o.L("if !assert.NoError(t, dst.Accept(`custom-algorithm`), `accept is successful`) {") o.L("return") o.L("}") o.L("assert.Equal(t, customAlgorithm, dst, `accepted value should be equal to variable`)") o.L("})") o.L("t.Run(`accept fmt.Stringer for custom-algorithm`, func(t *testing.T) {") o.L("t.Parallel()") o.L("var dst jwa.%[1]s", t.name) o.L("if !assert.NoError(t, dst.Accept(stringer{src: `custom-algorithm`}), `accept is successful`) {") o.L("return") o.L("}") o.L("assert.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("assert.False(t, customAlgorithm.IsSymmetric(), `custom algorithm should NOT be symmetric`)") 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(`reject variable used to register custom algorithm`, func(t *testing.T) {") o.L("t.Parallel()") o.L("var dst jwa.%[1]s", t.name) o.L("assert.Error(t, dst.Accept(customAlgorithm), `accept failed`)") o.L("})") o.L("t.Run(`reject the string custom-algorithm`, func(t *testing.T) {") o.L("t.Parallel()") o.L("var dst jwa.%[1]s", t.name) o.L("assert.Error(t, dst.Accept(`custom-algorithm`), `accept failed`)") o.L("})") o.L("t.Run(`reject fmt.Stringer for custom-algorithm`, func(t *testing.T) {") o.L("t.Parallel()") o.L("var dst jwa.%[1]s", t.name) o.L("assert.Error(t, dst.Accept(stringer{src: `custom-algorithm`}), `accept failed`)") o.L("})") if t.symmetric { o.L("t.Run(`check symmetric`, func(t *testing.T) {") o.L("t.Parallel()") o.L("assert.False(t, customAlgorithm.IsSymmetric(), `custom algorithm should NOT be symmetric`)") o.L("})") } o.L("})") if t.symmetric { for _, value := range []bool{false, true} { o.LL("t.Run(`with custom algorithm registered with WithSymmetricAlgorithm(%t)`, func(t *testing.T) {", value) o.L("jwa.Register%[1]sWithOptions(customAlgorithm, jwa.WithSymmetricAlgorithm(%[2]t))", t.name, value) o.L("t.Run(`accept variable used to register custom algorithm`, func(t *testing.T) {") o.L("t.Parallel()") o.L("var dst jwa.%[1]s", t.name) o.L("if !assert.NoError(t, dst.Accept(customAlgorithm), `accept is successful`) {") o.L("return") o.L("}") o.L("assert.Equal(t, customAlgorithm, dst, `accepted value should be equal to variable`)") o.L("})") o.L("t.Run(`accept the string custom-algorithm`, func(t *testing.T) {") o.L("t.Parallel()") o.L("var dst jwa.%[1]s", t.name) o.L("if !assert.NoError(t, dst.Accept(`custom-algorithm`), `accept is successful`) {") o.L("return") o.L("}") o.L("assert.Equal(t, customAlgorithm, dst, `accepted value should be equal to variable`)") o.L("})") o.L("t.Run(`accept fmt.Stringer for custom-algorithm`, func(t *testing.T) {") o.L("t.Parallel()") o.L("var dst jwa.%[1]s", t.name) o.L("if !assert.NoError(t, dst.Accept(stringer{src: `custom-algorithm`}), `accept is successful`) {") o.L("return") o.L("}") o.L("assert.Equal(t, customAlgorithm, dst, `accepted value should be equal to variable`)") o.L("})") o.L("t.Run(`check symmetric`, func(t *testing.T) {") o.L("t.Parallel()") if value { o.L("assert.True(t, customAlgorithm.IsSymmetric(), `custom algorithm should be symmetric`)") } else { o.L("assert.False(t, customAlgorithm.IsSymmetric(), `custom algorithm should NOT be symmetric`)") } o.L("})") o.L("})") o.L("t.Run(`with custom algorithm deregistered (was WithSymmetricAlgorithm(%t))`, func(t *testing.T) {", value) o.L("jwa.Unregister%[1]s(customAlgorithm)", t.name) o.L("t.Run(`reject variable used to register custom algorithm`, func(t *testing.T) {") o.L("t.Parallel()") o.L("var dst jwa.%[1]s", t.name) o.L("assert.Error(t, dst.Accept(customAlgorithm), `accept failed`)") o.L("})") o.L("t.Run(`reject the string custom-algorithm`, func(t *testing.T) {") o.L("t.Parallel()") o.L("var dst jwa.%[1]s", t.name) o.L("assert.Error(t, dst.Accept(`custom-algorithm`), `accept failed`)") o.L("})") o.L("t.Run(`reject fmt.Stringer for custom-algorithm`, func(t *testing.T) {") o.L("t.Parallel()") o.L("var dst jwa.%[1]s", t.name) o.L("assert.Error(t, dst.Accept(stringer{src: `custom-algorithm`}), `accept failed`)") o.L("})") o.L("t.Run(`check symmetric`, func(t *testing.T) {") o.L("t.Parallel()") o.L("assert.False(t, customAlgorithm.IsSymmetric(), `custom algorithm should NOT be symmetric`)") o.L("})") o.L("})") } } 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-2.1.4/tools/cmd/genjwe.sh000077500000000000000000000006731476711647200230620ustar00rootroot00000000000000#!/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-2.1.4/tools/cmd/genjwe/000077500000000000000000000000001476711647200225155ustar00rootroot00000000000000golang-github-lestrrat-go-jwx-2.1.4/tools/cmd/genjwe/go.mod000066400000000000000000000007521476711647200236270ustar00rootroot00000000000000module github.com/lestrrat-go/jwx/v2/jwe/internal/cmd/genheader go 1.16 require ( github.com/fatih/color v1.16.0 // indirect github.com/goccy/go-yaml v1.11.2 github.com/lestrrat-go/codegen v1.0.4 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/tools v0.16.1 // indirect golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect ) golang-github-lestrrat-go-jwx-2.1.4/tools/cmd/genjwe/go.sum000066400000000000000000000271731476711647200236620ustar00rootroot00000000000000github.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.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= 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/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= 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.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= 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.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= 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.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 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= 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-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY= golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 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/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 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/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= 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/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/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 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-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 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.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200918232735-d647fc253266/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= 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/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= 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.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 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-2.1.4/tools/cmd/genjwe/main.go000066400000000000000000000272641476711647200240030ustar00rootroot00000000000000package 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`, `""`) codegen.RegisterZeroVal(`jwa.CompressionAlgorithm`, `jwa.NoCompress`) codegen.RegisterZeroVal(`jwa.ContentEncryptionAlgorithm`, `""`) 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 interface{} 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.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 Header set.") o.L("type Headers interface {") o.L("json.Marshaler") o.L("json.Unmarshaler") // These are the basic values that most jws have for _, f := range obj.Fields() { o.L("%s() %s", f.GetterMethod(true), f.Type()) //PointerElem()) } // These are used to iterate through all keys in a header o.L("Iterate(ctx context.Context) Iterator") o.L("Walk(ctx context.Context, v Visitor) error") o.L("AsMap(ctx context.Context) (map[string]interface{}, error)") // These are used to access a single element by key name o.L("Get(string) (interface{}, bool)") o.L("Set(string, interface{}) error") o.L("Remove(string) error") // These are used to deal with encoded headers o.L("Encode() ([]byte, error)") o.L("Decode([]byte) error") // Access private parameters o.L("// PrivateParams returns the map containing the non-standard ('private') parameters") o.L("// in the associated header. WARNING: DO NOT USE PrivateParams()") o.L("// IF YOU HAVE CONCURRENT CODE ACCESSING THEM. Use AsMap() to") o.L("// get a copy of the entire header instead") o.L("PrivateParams() map[string]interface{}") o.L("Clone(context.Context) (Headers, error)") o.L("Copy(context.Context, Headers) error") o.L("Merge(context.Context, Headers) (Headers, error)") o.L("}") 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]interface{}") 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]interface{}{},") o.L("}") o.L("}") for _, f := range obj.Fields() { o.LL("func (h *stdHeaders) %s() %s{", f.GetterMethod(true), f.Type()) o.L("h.mu.RLock()") o.L("defer h.mu.RUnlock()") if !fieldStorageTypeIsIndirect(f.Type()) { o.L("return h.%s", f.Name(false)) } else { o.L("if h.%s == nil {", f.Name(false)) o.L("return %s", codegen.ZeroVal(f.Type())) o.L("}") o.L("return *(h.%s)", f.Name(false)) } o.L("}") // func (h *stdHeaders) %s() %s } // Generate a function that iterates through all of the keys // in this header. o.LL("func (h *stdHeaders) makePairs() []*HeaderPair {") o.L("h.mu.RLock()") o.L("defer h.mu.RUnlock()") // NOTE: building up an array is *slow*? o.L("var pairs []*HeaderPair") for _, f := range obj.Fields() { o.L("if h.%s != nil {", f.Name(false)) if fieldStorageTypeIsIndirect(f.Type()) { o.L("pairs = append(pairs, &HeaderPair{Key: %sKey, Value: *(h.%s)})", f.Name(true), f.Name(false)) } else { o.L("pairs = append(pairs, &HeaderPair{Key: %sKey, Value: h.%s})", f.Name(true), f.Name(false)) } o.L("}") } o.L("for k, v := range h.privateParams {") o.L("pairs = append(pairs, &HeaderPair{Key: k, Value: v})") o.L("}") o.L("return pairs") o.L("}") // end of (h *stdHeaders) iterate(...) o.LL("func (h *stdHeaders) PrivateParams() map[string]interface{} {") o.L("h.mu.RLock()") o.L("defer h.mu.RUnlock()") o.L("return h.privateParams") o.L("}") o.LL("func (h *stdHeaders) Get(name string) (interface{}, 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("if h.%s == nil {", f.Name(false)) o.L("return nil, false") o.L("}") if fieldStorageTypeIsIndirect(f.Type()) { o.L("return *(h.%s), true", f.Name(false)) } else { o.L("return h.%s, true", f.Name(false)) } } o.L("default:") o.L("v, ok := h.privateParams[name]") o.L("return v, ok") o.L("}") // end switch name o.L("}") // func (h *stdHeaders) Get(name string) (interface{}, bool) o.LL("func (h *stdHeaders) Set(name string, value interface{}) 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 interface{}) 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 == \"\" {") 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]interface{}{}") 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 interface{}) 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 '{' or '}' here.") o.L("if tok == '}' { // End of object") o.L("break LOOP") o.L("} else if tok != '{' {") o.L("return fmt.Errorf(`expected '{', but got '%%c'`, 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) MarshalJSON() ([]byte, error) {") o.L("data := make(map[string]interface{})") o.L("fields := make([]string, 0, %d)", len(obj.Fields())) o.L("for _, pair := range h.makePairs() {") o.L("fields = append(fields, pair.Key.(string))") o.L("data[pair.Key.(string)] = pair.Value") o.L("}") o.LL("sort.Strings(fields)") o.L("buf := pool.GetBytesBuffer()") o.L("defer pool.ReleaseBytesBuffer(buf)") o.L("buf.WriteByte('{')") o.L("enc := json.NewEncoder(buf)") o.L("for i, f := range fields {") o.L("if i > 0 {") o.L("buf.WriteRune(',')") o.L("}") o.L("buf.WriteRune('\"')") 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('\"')") o.L("buf.WriteString(base64.EncodeToString(v))") o.L("buf.WriteRune('\"')") 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`, f)") o.L("}") o.L("buf.Truncate(buf.Len()-1)") o.L("}") o.L("}") o.L("buf.WriteByte('}')") 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-2.1.4/tools/cmd/genjwe/objects.yml000066400000000000000000000024711476711647200246750ustar00rootroot00000000000000fields: - 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-2.1.4/tools/cmd/genjwk.sh000077500000000000000000000006731476711647200230700ustar00rootroot00000000000000#!/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-2.1.4/tools/cmd/genjwk/000077500000000000000000000000001476711647200225235ustar00rootroot00000000000000golang-github-lestrrat-go-jwx-2.1.4/tools/cmd/genjwk/go.mod000066400000000000000000000007461476711647200236400ustar00rootroot00000000000000module gitub.com/lestrrat-go/jwx/jwk/internal/cmd/genheader go 1.16 require ( github.com/fatih/color v1.16.0 // indirect github.com/goccy/go-yaml v1.11.2 github.com/lestrrat-go/codegen v1.0.4 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/tools v0.16.1 // indirect golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect ) golang-github-lestrrat-go-jwx-2.1.4/tools/cmd/genjwk/go.sum000066400000000000000000000271731476711647200236700ustar00rootroot00000000000000github.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.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= 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/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= 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.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= 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.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= 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.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 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= 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-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY= golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 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/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 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/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= 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/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/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 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-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 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.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200918232735-d647fc253266/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= 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/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= 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.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 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-2.1.4/tools/cmd/genjwk/main.go000066400000000000000000000544141476711647200240060ustar00rootroot00000000000000package 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 interface{} 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`, `jwa.InvalidKeyAlgorithm("")`) 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); err != nil { return err } for _, kt := range def.KeyTypes { if err := generateKeyType(kt); 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) 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) } } 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 } 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") o.L("FromRaw(%s) error", obj.MustString(`raw_key_type`)) for _, f := range obj.Fields() { if f.Bool(`is_std`) { continue } o.L("%s() %s", 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]interface{}") 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]interface{}),") o.L("}") o.L("}") o.LL("func (h %s) KeyType() jwa.KeyType {", structName) o.L("return %s", kt.KeyType) 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(" {") if f.Bool(`hasGet`) { o.L("if h.%s != nil {", f.Name(false)) o.L("return h.%s.Get()", f.Name(false)) o.L("}") o.L("return %s", 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)", f.Name(false)) o.L("}") o.L("return %s", codegen.ZeroVal(PointerElem(f))) } else { o.L("return h.%s", f.Name(false)) } } else { o.L(`return h.%s`, f.Name(false)) } o.L("}") // func (h *stdHeaders) %s() %s } o.LL("func (h *%s) makePairs() []*HeaderPair {", structName) o.L("h.mu.RLock()") o.L("defer h.mu.RUnlock()") // NOTE: building up an array is *slow*? o.LL("var pairs []*HeaderPair") o.L("pairs = append(pairs, &HeaderPair{Key: \"kty\", Value: %s})", kt.KeyType) 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("pairs = append(pairs, &HeaderPair{Key: %s, Value: *(h.%s)})", keyName, f.Name(false)) } else { o.L("pairs = append(pairs, &HeaderPair{Key: %s, Value: h.%s})", keyName, f.Name(false)) } o.L("}") } o.L("for k, v := range h.privateParams {") o.L("pairs = append(pairs, &HeaderPair{Key: k, Value: v})") o.L("}") o.L("return pairs") o.L("}") // end of (h *stdHeaders) makePairs(...) o.LL("func (h *%s) PrivateParams() map[string]interface{} {", structName) o.L("return h.privateParams") o.L("}") o.LL("func (h *%s) Get(name string) (interface{}, bool) {", structName) o.L("h.mu.RLock()") o.L("defer h.mu.RUnlock()") o.L("switch name {") o.L("case KeyTypeKey:") o.L("return h.KeyType(), true") 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 nil, false") o.L("}") if f.Bool(`hasGet`) { o.L("return h.%s.Get(), true", f.Name(false)) } else if fieldStorageTypeIsIndirect(f.Type()) { o.L("return *(h.%s), true", f.Name(false)) } else { o.L("return h.%s, true", f.Name(false)) } } o.L("default:") o.L("v, ok := h.privateParams[name]") o.L("return v, ok") o.L("}") // end switch name o.L("}") // func (h *%s) Get(name string) (interface{}, bool) o.LL("func (h *%s) Set(name string, value interface{}) 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 interface{}) 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.ContentEncryptionAlgorithm:") o.L("var tmp = jwa.KeyAlgorithmFrom(v)") o.L("h.algorithm = &tmp") o.L("case fmt.Stringer:") o.L("s := v.String()") o.L("var tmp = jwa.KeyAlgorithmFrom(s)") o.L("h.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.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]interface{}{}") 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 interface{}) 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("return cloneKey(k)") 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 '{' or '}' here.") o.L("if tok == '}' { // End of object") o.L("break LOOP") o.L("} else if tok != '{' {") o.L("return fmt.Errorf(`expected '{', but got '%%c'`, 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 := jwa.KeyAlgorithmFrom(s)") 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]interface{})") o.L("fields := make([]string, 0, %d)", len(obj.Fields())) o.L("for _, pair := range h.makePairs() {") o.L("fields = append(fields, pair.Key.(string))") o.L("data[pair.Key.(string)] = pair.Value") o.L("}") o.LL("sort.Strings(fields)") o.L("buf := pool.GetBytesBuffer()") o.L("defer pool.ReleaseBytesBuffer(buf)") o.L("buf.WriteByte('{')") o.L("enc := json.NewEncoder(buf)") o.L("for i, f := range fields {") o.L("if i > 0 {") o.L("buf.WriteRune(',')") o.L("}") o.L("buf.WriteRune('\"')") 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('\"')") o.L("buf.WriteString(base64.EncodeToString(v))") o.L("buf.WriteRune('\"')") 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('}')") 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) Iterate(ctx context.Context) HeaderIterator {", structName) o.L("pairs := h.makePairs()") o.L("ch := make(chan *HeaderPair, len(pairs))") o.L("go func(ctx context.Context, ch chan *HeaderPair, pairs []*HeaderPair) {") o.L("defer close(ch)") o.L("for _, pair := range pairs {") o.L("select {") o.L("case <-ctx.Done():") o.L("return") o.L("case ch<-pair:") o.L("}") o.L("}") o.L("}(ctx, ch, pairs)") o.L("return mapiter.New(ch)") o.L("}") o.LL("func (h *%s) Walk(ctx context.Context, visitor HeaderVisitor) error {", structName) o.L("return iter.WalkMap(ctx, h, visitor)") o.L("}") o.LL("func (h *%s) AsMap(ctx context.Context) (map[string]interface{}, error) {", structName) o.L("return iter.AsMap(ctx, h)") o.L("}") return nil } func generateGenericHeaders(fields 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") o.LL("import (") pkgs := []string{ "crypto/x509", "fmt", "github.com/lestrrat-go/jwx/v2/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 types, so you should use type assertions") o.L("// to perform more specific tasks with each key") o.L("type Key interface {") o.L("// Get returns the value of a single field. The second boolean return value") o.L("// will be false if the field is not stored in the source") o.L("//\n// This method, which returns an `interface{}`, 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("Get(string) (interface{}, bool)") 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 `interface{}`, 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, interface{}) 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("// Raw creates the corresponding raw key. For example,") o.L("// EC types would create *ecdsa.PublicKey or *ecdsa.PrivateKey,") o.L("// and OctetSeq types create a []byte key.") o.L("//\n// If you do not know the exact type of a jwk.Key before attempting") o.L("// to obtain the raw key, you can simply pass a pointer to an") o.L("// empty interface as the first argument.") o.L("//\n// If you already know the exact type, it is recommended that you") o.L("// pass a pointer to the zero value of the actual key type (e.g. &rsa.PrivateKey)") o.L("// for efficiency.") o.L("Raw(interface{}) 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("// Iterate returns an iterator that returns all keys and values.") o.L("// See github.com/lestrrat-go/iter for a description of the iterator.") o.L("Iterate(ctx context.Context) HeaderIterator") o.LL("// Walk is a utility tool that allows a visitor to iterate all keys and values") o.L("Walk(context.Context, HeaderVisitor) error") o.LL("// AsMap is a utility tool that returns a new map that contains the same fields as the source") o.L("AsMap(context.Context) (map[string]interface{}, error)") o.LL("// PrivateParams returns the non-standard elements in the source structure") o.L("// WARNING: DO NOT USE PrivateParams() IF YOU HAVE CONCURRENT CODE ACCESSING THEM.") o.L("// Use `AsMap()` to get a copy of the entire header, or use `Iterate()` instead") o.L("PrivateParams() map[string]interface{}") 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` or `jwk.KeyEncryptionAlgorithm`.") 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.LL("makePairs() []*HeaderPair") 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-2.1.4/tools/cmd/genjwk/objects.yml000066400000000000000000000100711476711647200246760ustar00rootroot00000000000000std_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-2.1.4/tools/cmd/genjws.sh000077500000000000000000000006731476711647200231000ustar00rootroot00000000000000#!/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-2.1.4/tools/cmd/genjws/000077500000000000000000000000001476711647200225335ustar00rootroot00000000000000golang-github-lestrrat-go-jwx-2.1.4/tools/cmd/genjws/go.mod000066400000000000000000000007521476711647200236450ustar00rootroot00000000000000module github.com/lestrrat-go/jwx/v2/jws/internal/cmd/genheader go 1.16 require ( github.com/fatih/color v1.16.0 // indirect github.com/goccy/go-yaml v1.11.2 github.com/lestrrat-go/codegen v1.0.4 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/tools v0.16.1 // indirect golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect ) golang-github-lestrrat-go-jwx-2.1.4/tools/cmd/genjws/go.sum000066400000000000000000000271731476711647200237000ustar00rootroot00000000000000github.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.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= 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/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= 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.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= 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.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= 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.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 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= 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-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY= golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 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/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 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/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= 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/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/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 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-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 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.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200918232735-d647fc253266/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= 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/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= 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.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 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-2.1.4/tools/cmd/genjws/main.go000066400000000000000000000274211476711647200240140ustar00rootroot00000000000000package 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`, `""`) 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 interface{} 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 Header set.") o.L("type Headers interface {") o.L("json.Marshaler") o.L("json.Unmarshaler") // These are the basic values that most jws have for _, f := range obj.Fields() { if f.Bool(`noDeref`) { o.L("%s() %s", f.GetterMethod(true), f.Type()) } else { o.L("%s() %s", f.GetterMethod(true), PointerElem(f)) } } // These are used to iterate through all keys in a header o.L("Iterate(ctx context.Context) Iterator") o.L("Walk(context.Context, Visitor) error") o.L("AsMap(context.Context) (map[string]interface{}, error)") o.L("Copy(context.Context, Headers) error") o.L("Merge(context.Context, Headers) (Headers, error)") // These are used to access a single element by key name o.L("Get(string) (interface{}, bool)") o.L("Set(string, interface{}) error") o.L("Remove(string) error") o.LL("// PrivateParams returns the non-standard elements in the source structure") o.L("// WARNING: DO NOT USE PrivateParams() IF YOU HAVE CONCURRENT CODE ACCESSING THEM.") o.L("// Use AsMap() to get a copy of the entire header instead") o.L("PrivateParams() map[string]interface{}") o.L("}") 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]interface{}") 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{", 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", codegen.ZeroVal(f.Type())) o.L("}") o.L("return *(h.%s)", f.Name(false)) } else { o.L("return h.%s", 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("}") // Generate a function that iterates through all of the keys // in this header. o.LL("func (h *stdHeaders) makePairs() []*HeaderPair {") o.L("h.mu.RLock()") o.L("defer h.mu.RUnlock()") // NOTE: building up an array is *slow*? o.L("var pairs []*HeaderPair") for _, f := range obj.Fields() { o.L("if h.%s != nil {", f.Name(false)) if fieldStorageTypeIsIndirect(f.Type()) { o.L("pairs = append(pairs, &HeaderPair{Key: %sKey, Value: *(h.%s)})", f.Name(true), f.Name(false)) } else { o.L("pairs = append(pairs, &HeaderPair{Key: %sKey, Value: h.%s})", f.Name(true), f.Name(false)) } o.L("}") } o.L("for k, v := range h.privateParams {") o.L("pairs = append(pairs, &HeaderPair{Key: k, Value: v})") o.L("}") o.L("sort.Slice(pairs, func(i, j int) bool {") o.L("return pairs[i].Key.(string) < pairs[j].Key.(string)") o.L("})") o.L("return pairs") o.L("}") // end of (h *stdHeaders) iterate(...) o.LL("func (h *stdHeaders) PrivateParams() map[string]interface{} {") o.L("h.mu.RLock()") o.L("defer h.mu.RUnlock()") o.L("return h.privateParams") o.L("}") o.LL("func (h *stdHeaders) Get(name string) (interface{}, 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("if h.%s == nil {", f.Name(false)) o.L("return nil, false") o.L("}") if fieldStorageTypeIsIndirect(f.Type()) { o.L("return *(h.%s), true", f.Name(false)) } else { o.L("return h.%s, true", f.Name(false)) } } o.L("default:") o.L("v, ok := h.privateParams[name]") o.L("return v, ok") o.L("}") // end switch name o.L("}") // func (h *stdHeaders) Get(name string) (interface{}, bool) o.LL("func (h *stdHeaders) Set(name string, value interface{}) 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 interface{}) 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 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]interface{}{}") 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 '{' or '}' here.") o.L("if tok == '}' { // End of object") o.L("break LOOP") o.L("} else if tok != '{' {") o.L("return fmt.Errorf(`expected '{', but got '%%c'`, 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) MarshalJSON() ([]byte, error) {") o.L("buf := pool.GetBytesBuffer()") o.L("defer pool.ReleaseBytesBuffer(buf)") o.L("buf.WriteByte('{')") o.L("enc := json.NewEncoder(buf)") o.L("for i, p := range h.makePairs() {") o.L("if i > 0 {") o.L("buf.WriteRune(',')") o.L("}") o.L("buf.WriteRune('\"')") o.L("buf.WriteString(p.Key.(string))") o.L("buf.WriteString(`\":`)") o.L("v := p.Value") o.L("switch v := v.(type) {") o.L("case []byte:") o.L("buf.WriteRune('\"')") o.L("buf.WriteString(base64.EncodeToString(v))") o.L("buf.WriteRune('\"')") 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`, p.Key, err)") o.L("}") o.L("buf.Truncate(buf.Len()-1)") o.L("}") o.L("}") o.L("buf.WriteByte('}')") 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-2.1.4/tools/cmd/genjws/objects.yml000066400000000000000000000032751476711647200247160ustar00rootroot00000000000000fields: - 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-2.1.4/tools/cmd/genjwt.sh000077500000000000000000000006731476711647200231010ustar00rootroot00000000000000#!/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-2.1.4/tools/cmd/genjwt/000077500000000000000000000000001476711647200225345ustar00rootroot00000000000000golang-github-lestrrat-go-jwx-2.1.4/tools/cmd/genjwt/go.mod000066400000000000000000000010131476711647200236350ustar00rootroot00000000000000module github.com/lestrrat-go/jwx/v2/jwt/internal/cmd/gentoken go 1.16 require ( github.com/fatih/color v1.16.0 // indirect github.com/goccy/go-json v0.10.2 github.com/goccy/go-yaml v1.11.2 github.com/lestrrat-go/codegen v1.0.4 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/tools v0.16.1 // indirect golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect ) golang-github-lestrrat-go-jwx-2.1.4/tools/cmd/genjwt/go.sum000066400000000000000000000274441476711647200237020ustar00rootroot00000000000000github.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.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= 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/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= 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.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= 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.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= 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.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= 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.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 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= 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-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY= golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 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/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 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/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= 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/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/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 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-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 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.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200918232735-d647fc253266/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= 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/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= 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.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 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-2.1.4/tools/cmd/genjwt/main.go000066400000000000000000000461511476711647200240160ustar00rootroot00000000000000package 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 interface{} 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`)) 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 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 _, field := range fields { o.LL("// %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", field.GetterMethod(true), rv) } o.LL("// PrivateClaims return the entire set of fields (claims) in the token") o.L("// *other* than the pre-defined fields such as `iss`, `nbf`, `iat`, etc.") o.L("PrivateClaims() map[string]interface{}") o.LL("// Get returns the value of the corresponding field in the token, such as") o.L("// `nbf`, `exp`, `iat`, and other user-defined fields. If the field does not") o.L("// exist in the token, the second return value will be `false`") o.L("//") o.L("// If you need to access fields like `alg`, `kid`, `jku`, etc, you need") o.L("// to access the corresponding fields in the JWS/JWE message. For this,") o.L("// you will need to access them by directly parsing the payload using") o.L("// `jws.Parse` and `jwe.Parse`") o.L("Get(string) (interface{}, bool)") 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, interface{}) error") o.L("Remove(string) error") var pkgPrefix string if obj.String(`package`) != `jwt` { pkgPrefix = `jwt.` } 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("Iterate(context.Context) Iterator") o.L("Walk(context.Context, Visitor) error") o.L("AsMap(context.Context) (map[string]interface{}, error)") 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]interface{}") 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]interface{}),") 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) Get(name string) (interface{}, bool) {", 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 nil, false") o.L("}") if f.Bool(`hasGet`) { o.L("v := t.%s.Get()", f.Name(false)) } else { if fieldStorageTypeIsIndirect(f.Type()) { o.L("v := *(t.%s)", f.Name(false)) } else { o.L("v := t.%s", f.Name(false)) } } o.L("return v, true") } o.L("default:") o.L("v, ok := t.privateClaims[name]") o.L("return v, ok") 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 interface{}) 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 interface{}) 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]interface{}{}") 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 interface{}) 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(" {") 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()", f.Name(false)) o.L("}") o.L("return %s", 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)", f.Name(false)) o.L("}") o.L("return %s", codegen.ZeroVal(rv)) } else { o.L("return t.%s", f.Name(false)) } } else { o.L("return t.%s", f.Name(false)) } o.L("}") // func (h *stdHeaders) %s() %s } o.LL("func (t *%s) PrivateClaims() map[string]interface{} {", obj.Name(false)) o.L("t.mu.RLock()") o.L("defer t.mu.RUnlock()") o.L("return t.privateClaims") o.L("}") // Generate a function that iterates through all of the keys // in this header. o.LL("func (t *%s) makePairs() []*ClaimPair {", obj.Name(false)) o.L("t.mu.RLock()") o.L("defer t.mu.RUnlock()") // NOTE: building up an array is *slow*? o.LL("pairs := make([]*ClaimPair, 0, %d)", len(fields)) for _, f := range fields { keyName := f.Name(true) + "Key" o.L("if t.%s != nil {", f.Name(false)) if f.Bool(`hasGet`) { o.L("v := t.%s.Get()", f.Name(false)) } else { if fieldStorageTypeIsIndirect(f.Type()) { o.L("v := *(t.%s)", f.Name(false)) } else { o.L("v := t.%s", f.Name(false)) } } o.L("pairs = append(pairs, &ClaimPair{Key: %s, Value: v})", keyName) o.L("}") } o.L("for k, v := range t.privateClaims {") o.L("pairs = append(pairs, &ClaimPair{Key: k, Value: v})") o.L("}") o.L("sort.Slice(pairs, func(i, j int) bool {") o.L("return pairs[i].Key.(string) < pairs[j].Key.(string)") o.L("})") o.L("return pairs") o.L("}") // end of (h *stdHeaders) iterate(...) 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 '{' or '}' here.") o.L("if tok == '}' { // End of object") o.L("break LOOP") o.L("} else if tok != '{' {") o.L("return fmt.Errorf(`expected '{', but got '%%c'`, 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("}") var numericDateFields []codegen.Field for _, field := range fields { if field.Type() == "types.NumericDate" { numericDateFields = append(numericDateFields, field) } } o.LL("func (t %s) MarshalJSON() ([]byte, error) {", obj.Name(false)) o.L("buf := pool.GetBytesBuffer()") o.L("defer pool.ReleaseBytesBuffer(buf)") o.L("buf.WriteByte('{')") o.L("enc := json.NewEncoder(buf)") o.L("for i, pair := range t.makePairs() {") o.L("f := pair.Key.(string)") o.L("if i > 0 {") o.L("buf.WriteByte(',')") o.L("}") o.L("buf.WriteRune('\"')") o.L("buf.WriteString(f)") o.L("buf.WriteString(`\":`)") // Handle cases that need specialized handling o.L("switch f {") o.L("case AudienceKey:") o.L("if err := json.EncodeAudience(enc, pair.Value.([]string), t.options.IsEnabled(%sFlattenAudience)); err != nil {", pkgPrefix) o.L("return nil, fmt.Errorf(`failed to encode \"aud\": %%w`, err)") o.L("}") o.L("continue") if lndf := len(numericDateFields); lndf > 0 { o.L("case ") for i, ndf := range numericDateFields { o.R("%sKey", ndf.Name(true)) if i < lndf-1 { o.R(",") } } o.R(":") o.L("enc.Encode(pair.Value.(time.Time).Unix())") o.L("continue") } o.L("}") o.L("switch v := pair.Value.(type) {") o.L("case []byte:") o.L("buf.WriteRune('\"')") o.L("buf.WriteString(base64.EncodeToString(v))") o.L("buf.WriteRune('\"')") o.L("default:") o.L("if err := enc.Encode(v); err != nil {") o.L("return nil, fmt.Errorf(`failed to marshal field %%s: %%w`, f, err)") o.L("}") o.L("buf.Truncate(buf.Len()-1)") o.L("}") o.L("}") o.L("buf.WriteByte('}')") o.L("ret := make([]byte, buf.Len())") o.L("copy(ret, buf.Bytes())") o.L("return ret, nil") o.L("}") o.LL("func (t *%s) Iterate(ctx context.Context) Iterator {", obj.Name(false)) o.L("pairs := t.makePairs()") o.L("ch := make(chan *ClaimPair, len(pairs))") o.L("go func(ctx context.Context, ch chan *ClaimPair, pairs []*ClaimPair) {") o.L("defer close(ch)") o.L("for _, pair := range pairs {") o.L("select {") o.L("case <-ctx.Done():") o.L("return") o.L("case ch<-pair:") o.L("}") o.L("}") o.L("}(ctx, ch, pairs)") o.L("return mapiter.New(ch)") o.L("}") o.LL("func (t *%s) Walk(ctx context.Context, visitor Visitor) error {", obj.Name(false)) o.L("return iter.WalkMap(ctx, t, visitor)") o.L("}") o.LL("func (t *%s) AsMap(ctx context.Context) (map[string]interface{}, error) {", obj.Name(false)) o.L("return iter.AsMap(ctx, t)") 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("claims []*ClaimPair") o.L("}") o.LL("func NewBuilder() *Builder {") o.L("return &Builder{}") o.L("}") o.LL("func (b *Builder) Claim(name string, value interface{}) *Builder {") o.L("b.claims = append(b.claims, &ClaimPair{Key: name, Value: 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("func (b *Builder) Build() (Token, error) {") o.L("tok := New()") o.L("for _, claim := range b.claims {") o.L("if err := tok.Set(claim.Key.(string), claim.Value); err != nil {") o.L("return nil, fmt.Errorf(`failed to set claim %%q: %%w`, claim.Key.(string), 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-2.1.4/tools/cmd/genjwt/objects.yml000066400000000000000000000044051476711647200247130ustar00rootroot00000000000000common_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-2.1.4/tools/cmd/genoptions.sh000077500000000000000000000010431476711647200237600ustar00rootroot00000000000000#!/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 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-2.1.4/tools/cmd/genoptions/000077500000000000000000000000001476711647200234235ustar00rootroot00000000000000golang-github-lestrrat-go-jwx-2.1.4/tools/cmd/genoptions/.gitignore000066400000000000000000000000141476711647200254060ustar00rootroot00000000000000.genoptions golang-github-lestrrat-go-jwx-2.1.4/tools/cmd/genoptions/go.mod000066400000000000000000000012301476711647200245250ustar00rootroot00000000000000module github.com/lestrrat-go/jwx/v2/jwe/tools/cmd/genoptions go 1.19 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.8.2 // indirect golang.org/x/mod v0.14.0 // indirect golang.org/x/sys v0.15.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-2.1.4/tools/cmd/genoptions/go.sum000066400000000000000000000147461476711647200245720ustar00rootroot00000000000000github.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.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= golang.org/x/sys v0.15.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-2.1.4/tools/cmd/genoptions/main.go000066400000000000000000000150511476711647200247000ustar00rootroot00000000000000package 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`, objects.Output) } 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 different package `github.com/lestrrat-go/jwx/v2/jwa`, `github.com/lestrrat-go/jwx/v2/jwe`, `github.com/lestrrat-go/jwx/v2/jwk`, `github.com/lestrrat-go/jwx/v2/jws`, `github.com/lestrrat-go/jwx/v2/jwt`, }...) // 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-2.1.4/tools/cmd/genreadfile.sh000077500000000000000000000006111476711647200240400ustar00rootroot00000000000000#!/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-2.1.4/tools/cmd/genreadfile/000077500000000000000000000000001476711647200235035ustar00rootroot00000000000000golang-github-lestrrat-go-jwx-2.1.4/tools/cmd/genreadfile/.gitignore000066400000000000000000000000151476711647200254670ustar00rootroot00000000000000.genreadfile golang-github-lestrrat-go-jwx-2.1.4/tools/cmd/genreadfile/go.mod000066400000000000000000000005221476711647200246100ustar00rootroot00000000000000module github.com/lestrrat-go/jwx/v2/tools/cmd/genreadfile go 1.16 require ( github.com/lestrrat-go/codegen v1.0.4 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/tools v0.16.1 // indirect ) golang-github-lestrrat-go-jwx-2.1.4/tools/cmd/genreadfile/go.sum000066400000000000000000000203371476711647200246430ustar00rootroot00000000000000github.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= 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-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 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/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 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/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/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= 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/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/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 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-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.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200918232735-d647fc253266/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= 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/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= 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-2.1.4/tools/cmd/genreadfile/main.go000066400000000000000000000043021476711647200247550ustar00rootroot00000000000000package 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") 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(`srcFS = option.Value().(fs.FS)`) 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-2.1.4/tools/cmd/gofmt.sh000077500000000000000000000000651476711647200227120ustar00rootroot00000000000000#!/bin/bash find . -name '*.go' | xargs gofmt -w -s golang-github-lestrrat-go-jwx-2.1.4/tools/cmd/install-jwx.sh000077500000000000000000000005331476711647200240520ustar00rootroot00000000000000#!/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-2.1.4/tools/test.sh000077500000000000000000000016061476711647200220140ustar00rootroot00000000000000#!/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 -json ${testopts[@]} ./... > $testout if [[ "$?" != "0" ]]; then failures=$((failures+1)) fi tparse -file="$testout" rm "$testout" 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-2.1.4/x25519/000077500000000000000000000000001476711647200202105ustar00rootroot00000000000000golang-github-lestrrat-go-jwx-2.1.4/x25519/BUILD.bazel000066400000000000000000000010331476711647200220630ustar00rootroot00000000000000load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") go_library( name = "x25519", srcs = ["x25519.go"], importpath = "github.com/lestrrat-go/jwx/v2/x25519", visibility = ["//visibility:public"], deps = ["@org_golang_x_crypto//curve25519"], ) go_test( name = "x25519_test", srcs = ["x25519_test.go"], deps = [ ":x25519", "@com_github_stretchr_testify//assert", ], ) alias( name = "go_default_library", actual = ":x25519", visibility = ["//visibility:public"], ) golang-github-lestrrat-go-jwx-2.1.4/x25519/x25519.go000066400000000000000000000064431476711647200214230ustar00rootroot00000000000000package x25519 import ( "bytes" "crypto" cryptorand "crypto/rand" "fmt" "io" "golang.org/x/crypto/curve25519" ) // This mirrors ed25519's structure for private/public "keys". jwx // requires dedicated types for these as they drive // serialization/deserialization logic, as well as encryption types. // // Note that with the x25519 scheme, the private key is a sequence of // 32 bytes, while the public key is the result of X25519(private, // basepoint). // // Portions of this file are from Go's ed25519.go, which is // Copyright 2016 The Go Authors. All rights reserved. const ( // PublicKeySize is the size, in bytes, of public keys as used in this package. PublicKeySize = 32 // PrivateKeySize is the size, in bytes, of private keys as used in this package. PrivateKeySize = 64 // SeedSize is the size, in bytes, of private key seeds. These are the private key representations used by RFC 8032. SeedSize = 32 ) // PublicKey is the type of X25519 public keys type PublicKey []byte // Any methods implemented on PublicKey might need to also be implemented on // PrivateKey, as the latter embeds the former and will expose its methods. // Equal reports whether pub and x have the same value. func (pub PublicKey) Equal(x crypto.PublicKey) bool { xx, ok := x.(PublicKey) if !ok { return false } return bytes.Equal(pub, xx) } // PrivateKey is the type of X25519 private key type PrivateKey []byte // Public returns the PublicKey corresponding to priv. func (priv PrivateKey) Public() crypto.PublicKey { publicKey := make([]byte, PublicKeySize) copy(publicKey, priv[SeedSize:]) return PublicKey(publicKey) } // Equal reports whether priv and x have the same value. func (priv PrivateKey) Equal(x crypto.PrivateKey) bool { xx, ok := x.(PrivateKey) if !ok { return false } return bytes.Equal(priv, xx) } // Seed returns the private key seed corresponding to priv. It is provided for // interoperability with RFC 7748. RFC 7748's private keys correspond to seeds // in this package. func (priv PrivateKey) Seed() []byte { seed := make([]byte, SeedSize) copy(seed, priv[:SeedSize]) return seed } // NewKeyFromSeed calculates a private key from a seed. It will return // an error if len(seed) is not SeedSize. This function is provided // for interoperability with RFC 7748. RFC 7748's private keys // correspond to seeds in this package. func NewKeyFromSeed(seed []byte) (PrivateKey, error) { privateKey := make([]byte, PrivateKeySize) if len(seed) != SeedSize { return nil, fmt.Errorf("unexpected seed size: %d", len(seed)) } copy(privateKey, seed) public, err := curve25519.X25519(seed, curve25519.Basepoint) if err != nil { return nil, fmt.Errorf(`failed to compute public key: %w`, err) } copy(privateKey[SeedSize:], public) return privateKey, nil } // GenerateKey generates a public/private key pair using entropy from rand. // If rand is nil, crypto/rand.Reader will be used. func GenerateKey(rand io.Reader) (PublicKey, PrivateKey, error) { if rand == nil { rand = cryptorand.Reader } seed := make([]byte, SeedSize) if _, err := io.ReadFull(rand, seed); err != nil { return nil, nil, err } privateKey, err := NewKeyFromSeed(seed) if err != nil { return nil, nil, err } publicKey := make([]byte, PublicKeySize) copy(publicKey, privateKey[SeedSize:]) return publicKey, privateKey, nil } golang-github-lestrrat-go-jwx-2.1.4/x25519/x25519_test.go000066400000000000000000000042101476711647200224500ustar00rootroot00000000000000package x25519_test import ( "encoding/hex" "testing" "github.com/lestrrat-go/jwx/v2/x25519" "github.com/stretchr/testify/assert" ) func TestGenerateKey(t *testing.T) { t.Run("x25519.GenerateKey(nil)", func(t *testing.T) { _, _, err := x25519.GenerateKey(nil) if !assert.NoError(t, err, `x25519.GenerateKey should work even if argument is nil`) { return } }) t.Run("x25519.NewKeyFromSeed(wrongSeedLength)", func(t *testing.T) { dummy := make([]byte, x25519.SeedSize-1) _, err := x25519.NewKeyFromSeed(dummy) if !assert.Error(t, err, `wrong seed size should result in error`) { return } }) } func TestNewKeyFromSeed(t *testing.T) { // These test vectors are from RFC7748 Section 6.1 const alicePrivHex = `77076d0a7318a57d3c16c17251b26645df4c2f87ebc0992ab177fba51db92c2a` const alicePubHex = `8520f0098930a754748b7ddcb43ef75a0dbf3a0d26381af4eba4a98eaa9b4e6a` const bobPrivHex = `5dab087e624a8a4b79e17f8b83800ee66f3bb1292618b6fd1c2f8b27ff88e0eb` const bobPubHex = `de9edb7d7b7dc1b4d35b61c2ece435373f8343c85b78674dadfc7e146f882b4f` alicePrivSeed, err := hex.DecodeString(alicePrivHex) if !assert.NoError(t, err, `alice seed decoded`) { return } alicePriv, err := x25519.NewKeyFromSeed(alicePrivSeed) if !assert.NoError(t, err, `alice private key`) { return } alicePub := alicePriv.Public().(x25519.PublicKey) if !assert.Equal(t, hex.EncodeToString(alicePub), alicePubHex, `alice public key`) { return } bobPrivSeed, err := hex.DecodeString(bobPrivHex) if !assert.NoError(t, err, `bob seed decoded`) { return } bobPriv, err := x25519.NewKeyFromSeed(bobPrivSeed) if !assert.NoError(t, err, `bob private key`) { return } bobPub := bobPriv.Public().(x25519.PublicKey) if !assert.Equal(t, hex.EncodeToString(bobPub), bobPubHex, `bob public key`) { return } if !assert.True(t, bobPriv.Equal(bobPriv), `bobPriv should equal bobPriv`) { return } if !assert.True(t, bobPub.Equal(bobPub), `bobPub should equal bobPub`) { return } if !assert.False(t, bobPriv.Equal(bobPub), `bobPriv should NOT equal bobPub`) { return } if !assert.False(t, bobPub.Equal(bobPriv), `bobPub should NOT equal bobPriv`) { return } }