diff --git a/.aspect/bazelrc/convenience.bazelrc b/.aspect/bazelrc/convenience.bazelrc index 3122d3273..cfa8d5a1d 100644 --- a/.aspect/bazelrc/convenience.bazelrc +++ b/.aspect/bazelrc/convenience.bazelrc @@ -4,7 +4,7 @@ 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 noiser in exchange for reducing the time-to-feedback on test failures for +# 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 diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 481cb36b7..2124c7c9c 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -10,7 +10,7 @@ The following is a set of guidelines that we ask you to follow when you contribu * [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](#reviewer-reviewee-guidelines) +* [Reviewer/Reviewee Guidelines](#reviewerreviewee-guidelines) * [Brown M&M Clause](#brown-mm-clause) * [Pull Requests](#pull-requests) * [Branches](#branches) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c4034b598..c56f59867 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,7 +11,7 @@ jobs: strategy: matrix: go_tags: [ 'stdlib', 'goccy', 'es256k', 'secp256k1-pem', 'asmbase64', 'alltags'] - go: [ '1.21', '1.20', '1.19' ] + go: [ '1.22', '1.21', '1.20' ] name: "Test [ Go ${{ matrix.go }} / Tags ${{ matrix.go_tags }} ]" steps: - name: Checkout repository diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 702a4c5d7..5278cad4f 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -8,11 +8,11 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-go@v5 with: - go-version: 1.19 + go-version: "1.20" check-latest: true - uses: golangci/golangci-lint-action@v6 with: - version: v1.54.2 + version: v1.59 - name: Run go vet run: | go vet ./... diff --git a/.github/workflows/smoke.yml b/.github/workflows/smoke.yml index 624ed102d..c5892cbf1 100644 --- a/.github/workflows/smoke.yml +++ b/.github/workflows/smoke.yml @@ -14,7 +14,7 @@ jobs: strategy: matrix: go_tags: [ 'stdlib', 'goccy', 'es256k', 'alltags' ] - go: [ '1.21', '1.20', '1.19' ] + go: [ '1.22', '1.21', '1.20' ] name: "Smoke [ Go ${{ matrix.go }} / Tags ${{ matrix.go_tags }} ]" steps: - name: Checkout repository diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index c69612c14..f2bee296c 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -12,7 +12,7 @@ jobs: 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 chnages that you would like to include. ' + 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 diff --git a/.golangci.yml b/.golangci.yml index 0ec7162a7..f7f07deeb 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -11,12 +11,11 @@ linters: enable-all: true disable: - cyclop - - deadcode # deprecated - depguard - dupl - exhaustive - - exhaustivestruct - errorlint + - err113 - funlen - gci - gochecknoglobals @@ -26,31 +25,26 @@ linters: - gocyclo - godot - godox - - goerr113 - gofumpt - - golint #deprecated - gomnd - gosec - govet - - interfacer # deprecated - - ifshort + - inamedparam # oh, sod off - ireturn # No, I _LIKE_ returning interfaces - lll - maintidx # Do this in code review - - maligned # deprecated - makezero + - mnd - nakedret - nestif - nlreturn - nonamedreturns # visit this back later - - nosnakecase - paralleltest - - scopelint # deprecated - - structcheck # deprecated + - perfsprint - tagliatelle + - testifylint # TODO: revisit when we have the chance - testpackage - thelper # Tests are fine - - varcheck # deprecated - varnamelen # Short names are ok - wrapcheck - wsl @@ -92,6 +86,10 @@ issues: - path: cmd/jwx/jwx.go linters: - forbidigo + - path: /*_test.go + text: "var-naming: " + litners: + - revive # Maximum issues count per one linter. Set to 0 to disable. Default is 50. max-issues-per-linter: 0 diff --git a/Changes b/Changes index 0c7be2dea..d0ca6c847 100644 --- a/Changes +++ b/Changes @@ -4,6 +4,15 @@ Changes v2 has many incompatibilities with v1. To see the full list of differences between v1 and v2, please read the Changes-v2.md file (https://github.com/lestrrat-go/jwx/blob/develop/v2/Changes-v2.md) +v2.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 @@ -19,13 +28,13 @@ v2.1.0 18 Jun 2024 # previously jwt.ParseRequest(req) // looks under Authorization - jwt.ParseReuqest(req, jwt.WithFormKey("foo")) // looks under foo AND Authorization - jwt.ParseReuqest(req, jwt.WithHeaderKey("Authorization"), jwt.WithFormKey("foo")) // looks under foo AND 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.ParseReuqest(req, jwt.WithHeaderKey("Authorization"), jwt.WithFormKey("foo")) // looks under foo AND Authorization + 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 @@ -43,13 +52,13 @@ v2.1.0 18 Jun 2024 `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 suppliying the `WithPEM(true)` option, `jwk.Parse()` is now + 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 - accomodate secp256k1 keys in PEM/ASN.1 DER format we need to "patch" the stdlib. + 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. @@ -106,7 +115,7 @@ v2.0.20 20 Feb 2024 JWS message. * [jwt] Add `jwt.WithCompactOnly()` to specify that only compact serialization can be used for `jwt.Parse()`. Previously, by virtue of `jws.Parse()` allowing either - JSON or Compact serialization format, `jwt.Parse()` also alloed JSON serialization + 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))` @@ -125,7 +134,7 @@ v2.0.19 09 Jan 2024 [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 possiblity of a DoS. + 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 @@ -219,7 +228,7 @@ v2.0.14 17 Oct 2023 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 softwares may flag. + 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 @@ -261,7 +270,7 @@ v2.0.10 - 12 Jun 2023 This feature is labeled experimental because the API for the above interfaces have not been battle tested, and may need to changed yet. Please be aware that until the API - is deemed stable, you may have to adapat our code to these possible changes, + is deemed stable, you may have to adapt your code to these possible changes, _even_ during minor version upgrades of this library. [Bug fixes] @@ -283,7 +292,7 @@ v2.0.10 - 12 Jun 2023 If you care about this performance improvement, you should probably enable `goccy` JSON parser as well, by specifying `jwx_goccy,jwx_asmbase64` in your build call. * Slightly changed the way global variables underneath `jwk.Fetch` are initialized and - configured. `jwk.Fetch` creates an object that spawns wokers to fetch JWKS when it's + 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. @@ -304,7 +313,7 @@ v2.0.9 - 21 Mar 2023 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 witout verification. + messages without verification. `jwt` also allows you to sign using alg="none", but there's no symmetrical way to verify such messages. @@ -373,7 +382,7 @@ v2.0.7 - 15 Nov 2022 [Miscellaneous] * WithCompact's stringification should have been that of the - internal indentity struct ("WithSerialization"), but it was + 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 @@ -410,7 +419,7 @@ v2.0.5 - 11 Aug 2022 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 re-fetched into its queue. + 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. @@ -419,20 +428,20 @@ v2.0.4 - 19 Jul 2022 * [jwk] Fix doc buglet in `KeyType()` method [New Features] - * [jws] Add `jws.WithMultipleKeysPerKeyID()` sub-option to allow non-unique + * [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-subsecond portion + 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, truncatation will still happen because we do not want to + 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 @@ -450,14 +459,14 @@ v2.0.2 - 23 May 2022 [New Features] * [jwt] RFC3339 timestamps are also accepted for Numeric Date types in JWT tokens. - This allows users to parse servers that errnously use RFC3339 timestamps in + 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 erronously been documented as not returning an error + * [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 diff --git a/Changes-v2.md b/Changes-v2.md index 1395c39a6..af146ed33 100644 --- a/Changes-v2.md +++ b/Changes-v2.md @@ -38,7 +38,7 @@ 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 -// (previosly you needed to type assert) +// (previously you needed to type assert) jws.Sign(payload, jws.WithKey(key.Algorithm(), key)) // If you need the specific type, type assert @@ -73,7 +73,7 @@ jws.Verify(signed, jws.WithKeySet(jwks), jws.WithKeyUsed(&keyUsed)) ```go // basic -jwe.Encrypt(payload, jwe.WithKey(alg, key)) // other defaults are infered +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)) @@ -92,7 +92,7 @@ jwe.Verify(signed, jwe.WithKeySet(jwks), jwe.WithKeyUsed(&keyUsed)) * Module now requires go 1.16 -* Use of github.com/pkg/errors is no more. If you were relying on bevaior +* 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 @@ -243,8 +243,8 @@ jws.Verify(signed, jws.WithKeySet(cachedSet)) 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 to - not clog your fetch queue, and therefore you should be writing custom code that + 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 @@ -306,7 +306,7 @@ jws.Parse(serialized, The rest of the arguments are treated as options passed to the `(jwk.Fetcher).Fetch()` function. -* Remove `jws.WithPayloadSigner()`. This should be completely repleceable +* Remove `jws.WithPayloadSigner()`. This should be completely replaceable using `jws.WithKey()` * jws.WithKeyProvider() has been added to specify arbitrary diff --git a/README.md b/README.md index 448e32b94..59c47e5c0 100644 --- a/README.md +++ b/README.md @@ -240,7 +240,7 @@ Please try [discussions](https://github.com/lestrrat-go/jwx/tree/v2/discussions) # Related Modules -* [github.com/lestrrat-go/echo-middileware-jwx](https://github.com/lestrrat-go/echo-middleware-jwx) - Sample Echo middleware +* [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) diff --git a/WORKSPACE b/WORKSPACE index 6a0cbe84b..c3db68424 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -29,7 +29,7 @@ go_dependencies() go_rules_dependencies() -go_register_toolchains(version = "1.19.5") +go_register_toolchains(version = "1.20.14") gazelle_dependencies() diff --git a/cmd/jwx/jwe.go b/cmd/jwx/jwe.go index b1ef49401..e714b0e39 100644 --- a/cmd/jwx/jwe.go +++ b/cmd/jwx/jwe.go @@ -39,7 +39,7 @@ func keyEncryptionFlag(required bool) cli.Flag { func makeJweEncryptCmd() *cli.Command { var cmd cli.Command cmd.Name = "encrypt" - cmd.Usage = "Encrypt payload to generage JWE message" + 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 diff --git a/cmd/jwx/jws.go b/cmd/jwx/jws.go index 3f21846e4..c4a00c652 100644 --- a/cmd/jwx/jws.go +++ b/cmd/jwx/jws.go @@ -41,7 +41,7 @@ func makeJwsCmd() *cli.Command { func makeJwsParseCmd() *cli.Command { var cmd cli.Command cmd.Name = "parse" - cmd.Usage = "Parse JWS mesage" + cmd.Usage = "Parse JWS message" cmd.UsageText = `jwx jws parse [command options] FILE Parse FILE and display information about a JWS message. @@ -203,7 +203,7 @@ func makeJwsSignCmd() *cli.Command { var cmd cli.Command cmd.Name = "sign" cmd.Aliases = []string{"sig"} - cmd.Usage = "Verify JWS mesage" + 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. @@ -258,16 +258,18 @@ func makeJwsSignCmd() *cli.Command { return fmt.Errorf(`invalid alg %s`, givenalg) } - var options []jws.SignOption + // 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) } - options = append(options, jws.WithHeaders(h)) + suboptions = append(suboptions, jws.WithProtectedHeaders(h)) } - options = append(options, jws.WithKey(alg, key)) + 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) diff --git a/deps.bzl b/deps.bzl index 5b6754095..260ca129d 100644 --- a/deps.bzl +++ b/deps.bzl @@ -47,8 +47,8 @@ def go_dependencies(): name = "com_github_lestrrat_go_httprc", build_file_proto_mode = "disable_global", importpath = "github.com/lestrrat-go/httprc", - sum = "h1:bsTfiH8xaKOJPrg1R+E3iE/AWZr/x0Phj9PBTG/OLUk=", - version = "v1.0.5", + sum = "h1:qgmgIRhpvBqexMJjA/PmwSvhNk679oqD1RbovdCGW8k=", + version = "v1.0.6", ) go_repository( name = "com_github_lestrrat_go_iter", @@ -115,8 +115,8 @@ def go_dependencies(): name = "org_golang_x_crypto", build_file_proto_mode = "disable_global", importpath = "golang.org/x/crypto", - sum = "h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI=", - version = "v0.24.0", + sum = "h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=", + version = "v0.25.0", ) go_repository( @@ -131,15 +131,15 @@ def go_dependencies(): name = "org_golang_x_sys", build_file_proto_mode = "disable_global", importpath = "golang.org/x/sys", - sum = "h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=", - version = "v0.21.0", + sum = "h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=", + version = "v0.22.0", ) go_repository( name = "org_golang_x_term", build_file_proto_mode = "disable_global", importpath = "golang.org/x/term", - sum = "h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA=", - version = "v0.21.0", + sum = "h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk=", + version = "v0.22.0", ) go_repository( diff --git a/docs/00-anatomy.md b/docs/00-anatomy.md index a799de437..aae3354cc 100644 --- a/docs/00-anatomy.md +++ b/docs/00-anatomy.md @@ -6,7 +6,7 @@ In this article we will briefly go over what JOSE is, and how each JWX libraries # JOSE Overview -Javascript Object Signing and Encryption is mainly comprised of specifications that span over 5 RFCs: namely RFC7515, RFC7516, RFC7517, RFC7518, and RFC7519. +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) @@ -32,7 +32,7 @@ Most of the times this information must be integrity checked using signatures, s 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 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. +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` @@ -56,7 +56,7 @@ Please note that a JWS message may take three forms: compact, full JSON, and fla 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 --> |"does NOT have 'signature'"| FullJSON[Full JSON Serialization] JSON --> |"has 'signature'"| Flat[Flattened JSON Serialization] ``` @@ -86,7 +86,7 @@ JWE is implemented in github.com/lestrrat-go/jwe package. This package provides # 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 a key. This is where RFC757 which describes JWK comes into play. +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.  @@ -110,6 +110,6 @@ JWK is implemented in github.com/lestrrat-go/jwx/v2/jwk package. This package pr # JWA - RFC7518 -And finally, RFC exists to define commonly used algorithm names in JWT, JWS, JWE, and JWK. +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. diff --git a/docs/01-jwt.md b/docs/01-jwt.md index d9de411be..f89ac45b8 100644 --- a/docs/01-jwt.md +++ b/docs/01-jwt.md @@ -10,22 +10,22 @@ In this document we describe how to work with JWT using `github.com/lestrrat-go/ * [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-jwt-new) + * [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 (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) + * [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 claims](#validate-for-specific-claims) + * [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 string](#serialize-aud-field-as-a-string) -* [Working with JWT](#working-with-jwt) + * [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) @@ -44,17 +44,17 @@ We use the terms "validate" and "validation" to describe the process of checking # Parsing -Parsing a (possibly) JWT comprises 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): +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 is comprised of three data segments encoded in `base64`, concatenated with a `.`. Each part reads as follows: +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 signinig key, the headers, and the payload. +* **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. @@ -94,12 +94,12 @@ func ExampleJWT_Parse() { 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 andno validation of the JWT token itself. +Note that the above form performs only signature verification and no validation of the JWT token itself. In order to perform validation, please use `Validate()`. ## Parse a JWT from file -To parsea JWT stored in a file, use [`jwt.ReadFile()`](https://pkg.go.dev/github.com/lestrrat-go/jwx/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). +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 @@ -123,9 +123,9 @@ func ExampleJWT_ReadFile() { fmt.Fprintf(f, exampleJWTSignedHMAC) f.Close() - // Note: this JWT has NOT been verified because we have not - // passed jwt.WithKey() et al. You need to pass these values - // if you want the token to be parsed and verified in one go + // 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) @@ -347,7 +347,7 @@ source: [examples/jwt_parse_with_key_example_test.go](https://github.com/lestrra 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") +## 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. @@ -406,8 +406,7 @@ func ExampleJWT_ParseWithKeySet() { // 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 - // all of the public keys + // We can use the jwk.PublicSetOf() utility to get a JWKS of the public keys { privset := jwk.NewSet() privset.AddKey(realKey) @@ -466,11 +465,11 @@ 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 as the JWS protecte headers. +keys if they have matching `kid` fields in the JWS protected headers. -You may override this behavior if you explicitly specify to to turn this off using +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 before hand, it is recommended that you parse the `jwk.Set` +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. @@ -643,7 +642,7 @@ func ExampleJWT_ParseWithKeyProvider() { 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") +## 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 @@ -722,7 +721,7 @@ func ExampleJWT_ParseWithJKU() { // We need to pass jwk.WithHTTPClient because we are using HTTPS, // and we need the certificates setup - // We also need to explicitly setup the whitelist, this is required + // 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) @@ -918,7 +917,7 @@ func ExampleJWT_ValidateDetectErrorType() { } { - // Case 1: Parsing error. We're not showing verification failure + // 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)) @@ -1067,7 +1066,7 @@ source: [examples/jwt_serialize_jws_example_test.go](https://github.com/lestrrat 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 buil-tin `(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. +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: @@ -1131,11 +1130,11 @@ func ExampleJWT_SerializeJWEJWS() { 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 the `aud` field as a single string +## 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 shoud 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)). +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)). ``` { @@ -1151,7 +1150,7 @@ The examples below shoud both be valid, but apparently there are systems that do } ``` -To workaround 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()`. +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 @@ -1303,7 +1302,7 @@ func ExampleJWTPlainStruct() { 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 panalties, and you get exactly the type of field that you want. +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 @@ -1318,7 +1317,7 @@ Please [look at the JWS documentation for it](./02-jws.md#parse-a-jws-message-an ## Get/Set fields -Any field in the token can be accessed in an uniform away using `(jwt.Token).Get()` +Any field in the token can be accessed in a uniform away using `(jwt.Token).Get()` ```go v, ok := token.Get(name) diff --git a/docs/02-jws.md b/docs/02-jws.md index c4cb36ec1..aa8f9f072 100644 --- a/docs/02-jws.md +++ b/docs/02-jws.md @@ -17,7 +17,7 @@ In this document we describe how to work with JWS using [`github.com/lestrrat-go * [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-customg-signingverification-algorithm) +* [Using a custom signing/verification algorithm](#using-a-custom-signingverification-algorithm) * [Enabling ES256K](#enabling-es256k) # Parsing @@ -111,7 +111,7 @@ source: [examples/jws_readfile_example_test.go](https://github.com/lestrrat-go/j 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 treat 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 witha `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). +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: @@ -157,7 +157,7 @@ func ExampleJWS_UseJWSHeader() { } // While JWT enveloped with JWS in compact format only has 1 signature, - // a generic JWS message may have multiple signatures. Therefore we + // 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: @@ -392,11 +392,11 @@ source: [examples/jws_verify_with_key_example_test.go](https://github.com/lestrr 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 `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 explictly disabled by specifying `jws.WithRequireKid(false)` suboption when using the `jws.WithKeySet()` option (i.e.: `jws.WithKeySet(keyset, jws.WithRequireKid(false))`) +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) +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 diff --git a/docs/03-jwe.md b/docs/03-jwe.md index 5c2a84acc..383473404 100644 --- a/docs/03-jwe.md +++ b/docs/03-jwe.md @@ -8,15 +8,14 @@ In this document we describe how to work with JWK using `github.com/lestrrat-go/ * [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) - * [Generating a JWE message with detached payload](#generating-a-jwe-message-with-detached-payload) * [Including arbitrary headers](#including-arbitrary-headers) -* [Decrypting](#decryptingG) +* [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 as when you use `jwe.Decrypt()` to decrypt the message. +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`. @@ -275,7 +274,6 @@ For global protected headers, you can use the `jwe.WithProtectedHeaders()` optio 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 @@ -377,12 +375,12 @@ source: [examples/jwe_decrypt_with_key_example_test.go](https://github.com/lestr 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 `jwe.Decrypt`?](99-faq.md#why-dont-you-automatically-infer-the-algorithm-for-jwsdecrypt-)" +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-decrypt-a-jwt-with-a-key-set-matching-kid) +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 diff --git a/docs/04-jwk.md b/docs/04-jwk.md index 3d3098151..e3ec82d14 100644 --- a/docs/04-jwk.md +++ b/docs/04-jwk.md @@ -14,8 +14,6 @@ In this document we describe how to work with JWK using `github.com/lestrrat-go/ * [Parse a key as a struct field](#parse-a-key-as-a-struct-field) * [Construction](#construction) * [Using jwk.FromRaw()](#using-jwkfromraw) - * [Construct a specific key type from scratch](#construct-a-specific-key-type-from-scratch) - * [Construct a specific key type from a raw key](#construct-a-specific-key-type-from-a-raw-key) * [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) @@ -45,7 +43,7 @@ and [`jwk.ReadFile()`](https://pkg.go.dev/github.com/lestrrat-go/jwx/v2/jwk#Read 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 +and so forth. --- @@ -63,7 +61,7 @@ If given anything else, `jwk.FromRaw` will return an error. ## 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) +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 @@ -110,7 +108,7 @@ source: [examples/jwk_parse_jwks_example_test.go](https://github.com/lestrrat-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) +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 @@ -334,7 +332,7 @@ source: [examples/jwk_readfile_with_pem_example_test.go](https://github.com/lest ## Parse a key as a struct field -As `jwk.Key` is an interface, it can't directly be used as an argument in `json.Unmarsshal`. +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 @@ -532,9 +530,9 @@ source: [examples/jwk_from_raw_example_test.go](https://github.com/lestrrat-go/j ## Parse a key from a remote resource -To parse keys stored in a remote location pointed by a HTTP(s) URL, use [`jwk.Fetch()`](https://pkg.go.dev/github.com/lestrrat-go/jwx/v2/jwk#Fetch) +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. +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 @@ -593,8 +591,8 @@ source: [examples/jwk_fetch_example_test.go](https://github.com/lestrrat-go/jwx/ ## Auto-refreshing remote keys -Sometimes you need to fetch a remote JWK, and use it mltiple times in a long-running process. -For example, you may act as an itermediary to some other service, and you may need to verify incoming JWT tokens against the tokens in said other service. +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. @@ -661,7 +659,7 @@ MAIN: // 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 re-fetching the keyset fails, a cached version will be returned from the previous successful + // 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 @@ -669,7 +667,7 @@ MAIN: time.Sleep(time.Second) // Because we're a dummy program, we just cancel the loop now. - // If this were a real program, you prosumably loop forever + // If this were a real program, you presumably loop forever cancel() } // OUTPUT: @@ -716,7 +714,7 @@ func ExampleJWK_CachedSet() { // // jwk.Fetch(ctx, googleCerts) // - // But you are instead using a cached (and periodically refreshed) + // But you are instead using a cached (and periodically refreshed) set // for each operation. _ = jws.WithKeySet(cached) } @@ -726,13 +724,13 @@ source: [examples/jwk_cached_set_example_test.go](https://github.com/lestrrat-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, +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 allo all URLs. -If you would like to implement something more complex, you can provide a function via `jwk.WhitelistFunc` or implement you own type of `jwk.Whitelist`. +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 @@ -885,11 +883,11 @@ Using [`jwk.FromRaw()`](https://pkg.go.dev/github.com/lestrrat-go/jwx/v2/jwk#Fro 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 not. +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 againt the wall after finding out that you have a typo. +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`) @@ -898,7 +896,7 @@ 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) as "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. +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) @@ -912,7 +910,7 @@ 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) +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{} diff --git a/docs/20-global-settings.md b/docs/20-global-settings.md index b006222f3..f70c8a5d6 100644 --- a/docs/20-global-settings.md +++ b/docs/20-global-settings.md @@ -13,7 +13,7 @@ If you do not provide these tags, the program will still compile, but it will re ## Switching to a faster JSON library -By default we use the standard library's `encoding/json` for all of our JSON needs. +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 @@ -21,25 +21,25 @@ However, if performance for parsing/serializing JSON is really important to you, ``` [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 see any problems, and I would say that it is mostly stable. +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. +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 +Currently, you can enable [github.com/segmentio/asm/base64](https://github.com/segmentio/asm/tree/main/base64) by specifying the `jwx_asmbase64` build tag ```shell % go build -tags jwx_goccy ... ``` -In our limited testing, this does not seem to improve performance significantly: presumably the other bottlenecks are more dominant. If you care enough to use this option, you probably wantt o enable `jwx_goccy` build tag as well. +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 @@ -59,7 +59,7 @@ within `jwx` *will* use your settings. 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 -wharever `"encoding/json".Unmarshal` deems appropriate. +whatever `"encoding/json".Unmarshal` deems appropriate. For example, JSON objects are converted to `map[string]interface{}`, JSON arrays into `[]interface{}`, and so on. diff --git a/docs/21-frameworks.md b/docs/21-frameworks.md index 58f47c64d..077fd7265 100644 --- a/docs/21-frameworks.md +++ b/docs/21-frameworks.md @@ -10,7 +10,7 @@ type Server struct { } ``` -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 tha there are some [optional algorithms](./global-settings.md#enabling-pptional-signature-methods). +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 @@ -52,7 +52,7 @@ s.alg = jwa.RS256 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 it as additional options. Please read the manual for `jwt.ParseRequest` for more details. +`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. diff --git a/docs/99-faq.md b/docs/99-faq.md index d77fd2c7f..f0ba7200d 100644 --- a/docs/99-faq.md +++ b/docs/99-faq.md @@ -20,7 +20,7 @@ Please read https://auth0.com/blog/critical-vulnerabilities-in-json-web-token-li ## 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" +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. @@ -28,7 +28,7 @@ Maybe we used the wrong terminology. Maybe we made public something that should 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 idempodent builds via Go modules. +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. @@ -70,7 +70,7 @@ So, for example, a service like Google will sign their JWTs using their private 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 implemention. +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. @@ -122,7 +122,7 @@ func MyOption(obj *Object) { 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. +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: @@ -143,7 +143,7 @@ func MyOption(obj *Object, cfg *MethodConfig) { But this means that you will have to have a state/config object for _each_ method call that takes options, and we have a lot of methods. -And finally, as you have seen above, with a callback based option object you will have to change its signature for each usecase. It's just a lot of hassle to remember which option uses which signature. +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. diff --git a/examples/go.sum b/examples/go.sum index 6be8ddd05..ba53a1f10 100644 --- a/examples/go.sum +++ b/examples/go.sum @@ -14,8 +14,8 @@ github.com/lestrrat-go/blackmagic v1.0.2 h1:Cg2gVSc9h7sz9NOByczrbUvLopQmXrfFx//N github.com/lestrrat-go/blackmagic v1.0.2/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU= github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE= github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E= -github.com/lestrrat-go/httprc v1.0.5 h1:bsTfiH8xaKOJPrg1R+E3iE/AWZr/x0Phj9PBTG/OLUk= -github.com/lestrrat-go/httprc v1.0.5/go.mod h1:mwwz3JMTPBjHUkkDv/IGJ39aALInZLrhBp0X7KGUZlo= +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= @@ -42,8 +42,8 @@ golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliY 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.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= -golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= +golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= +golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= 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= @@ -75,8 +75,8 @@ 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.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= -golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= +golang.org/x/sys v0.22.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= @@ -86,7 +86,7 @@ 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.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0= +golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= diff --git a/examples/jwk_cache_example_test.go b/examples/jwk_cache_example_test.go index e91a41479..06ce8b3a6 100644 --- a/examples/jwk_cache_example_test.go +++ b/examples/jwk_cache_example_test.go @@ -55,7 +55,7 @@ MAIN: // 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 re-fetching the keyset fails, a cached version will be returned from the previous successful + // 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 @@ -63,7 +63,7 @@ MAIN: time.Sleep(time.Second) // Because we're a dummy program, we just cancel the loop now. - // If this were a real program, you prosumably loop forever + // If this were a real program, you presumably loop forever cancel() } // OUTPUT: diff --git a/examples/jwk_cached_set_example_test.go b/examples/jwk_cached_set_example_test.go index 6dc0cd5e4..6709b3991 100644 --- a/examples/jwk_cached_set_example_test.go +++ b/examples/jwk_cached_set_example_test.go @@ -34,7 +34,7 @@ func ExampleJWK_CachedSet() { // // jwk.Fetch(ctx, googleCerts) // - // But you are instead using a cached (and periodically refreshed) + // But you are instead using a cached (and periodically refreshed) set // for each operation. _ = jws.WithKeySet(cached) } diff --git a/examples/jws_use_jws_header_test.go b/examples/jws_use_jws_header_test.go index 35db6d553..eeb7d0014 100644 --- a/examples/jws_use_jws_header_test.go +++ b/examples/jws_use_jws_header_test.go @@ -38,7 +38,7 @@ func ExampleJWS_UseJWSHeader() { } // While JWT enveloped with JWS in compact format only has 1 signature, - // a generic JWS message may have multiple signatures. Therefore we + // 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: diff --git a/examples/jwt_parse_with_jku_example_test.go b/examples/jwt_parse_with_jku_example_test.go index df573d804..9c075e906 100644 --- a/examples/jwt_parse_with_jku_example_test.go +++ b/examples/jwt_parse_with_jku_example_test.go @@ -69,7 +69,7 @@ func ExampleJWT_ParseWithJKU() { // We need to pass jwk.WithHTTPClient because we are using HTTPS, // and we need the certificates setup - // We also need to explicitly setup the whitelist, this is required + // 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) diff --git a/examples/jwt_parse_with_keyset_example_test.go b/examples/jwt_parse_with_keyset_example_test.go index 9c880872e..3b51e7bff 100644 --- a/examples/jwt_parse_with_keyset_example_test.go +++ b/examples/jwt_parse_with_keyset_example_test.go @@ -47,8 +47,7 @@ func ExampleJWT_ParseWithKeySet() { // 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 - // all of the public keys + // We can use the jwk.PublicSetOf() utility to get a JWKS of the public keys { privset := jwk.NewSet() privset.AddKey(realKey) diff --git a/examples/jwt_readfile_example_test.go b/examples/jwt_readfile_example_test.go index ab7ccabfa..cc604f3f3 100644 --- a/examples/jwt_readfile_example_test.go +++ b/examples/jwt_readfile_example_test.go @@ -18,9 +18,9 @@ func ExampleJWT_ReadFile() { fmt.Fprintf(f, exampleJWTSignedHMAC) f.Close() - // Note: this JWT has NOT been verified because we have not - // passed jwt.WithKey() et al. You need to pass these values - // if you want the token to be parsed and verified in one go + // 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) diff --git a/examples/jwt_validate_detect_error_type_example_test.go b/examples/jwt_validate_detect_error_type_example_test.go index 40bca774f..b5b83aced 100644 --- a/examples/jwt_validate_detect_error_type_example_test.go +++ b/examples/jwt_validate_detect_error_type_example_test.go @@ -26,7 +26,7 @@ func ExampleJWT_ValidateDetectErrorType() { } { - // Case 1: Parsing error. We're not showing verification failure + // 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)) diff --git a/format.go b/format.go index ba721acac..40854b180 100644 --- a/format.go +++ b/format.go @@ -43,7 +43,7 @@ type formatHint struct { // 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 reuturn JWS, not JWT. +// 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. diff --git a/go.mod b/go.mod index f572e38cc..9a996a32a 100644 --- a/go.mod +++ b/go.mod @@ -1,23 +1,23 @@ module github.com/lestrrat-go/jwx/v2 -go 1.18 +go 1.20 require ( github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 github.com/goccy/go-json v0.10.3 github.com/lestrrat-go/blackmagic v1.0.2 - github.com/lestrrat-go/httprc v1.0.5 + 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.9.0 - golang.org/x/crypto v0.24.0 + golang.org/x/crypto v0.25.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.21.0 // indirect + golang.org/x/sys v0.22.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 948ab8262..8844858fc 100644 --- a/go.sum +++ b/go.sum @@ -9,8 +9,8 @@ github.com/lestrrat-go/blackmagic v1.0.2 h1:Cg2gVSc9h7sz9NOByczrbUvLopQmXrfFx//N github.com/lestrrat-go/blackmagic v1.0.2/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU= github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE= github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E= -github.com/lestrrat-go/httprc v1.0.5 h1:bsTfiH8xaKOJPrg1R+E3iE/AWZr/x0Phj9PBTG/OLUk= -github.com/lestrrat-go/httprc v1.0.5/go.mod h1:mwwz3JMTPBjHUkkDv/IGJ39aALInZLrhBp0X7KGUZlo= +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= @@ -24,10 +24,10 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= -golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= -golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= -golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= +golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= +golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= +golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/jwxtest/jwxtest.go b/internal/jwxtest/jwxtest.go index cd9e8af2e..9b5e2514b 100644 --- a/internal/jwxtest/jwxtest.go +++ b/internal/jwxtest/jwxtest.go @@ -286,7 +286,7 @@ func EncryptJweFile(ctx context.Context, payload []byte, keyalg jwa.KeyEncryptio var keyif interface{} switch keyalg { - case jwa.RSA1_5, jwa.RSA_OAEP, jwa.RSA_OAEP_256: + 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) diff --git a/jwa/BUILD.bazel b/jwa/BUILD.bazel index 63fffcf90..aa05d4b3a 100644 --- a/jwa/BUILD.bazel +++ b/jwa/BUILD.bazel @@ -9,10 +9,14 @@ go_library( "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( @@ -29,6 +33,7 @@ go_test( deps = [ ":jwa", "@com_github_stretchr_testify//assert", + "@com_github_lestrrat_go_option//:option", ], ) diff --git a/jwa/compression_gen.go b/jwa/compression_gen.go index 9fb65220d..b2a7af2f4 100644 --- a/jwa/compression_gen.go +++ b/jwa/compression_gen.go @@ -42,7 +42,7 @@ func RegisterCompressionAlgorithm(v CompressionAlgorithm) { } // UnregisterCompressionAlgorithm unregisters a CompressionAlgorithm from its known database. -// Non-existentn entries will silently be ignored +// Non-existent entries will silently be ignored func UnregisterCompressionAlgorithm(v CompressionAlgorithm) { muCompressionAlgorithms.Lock() defer muCompressionAlgorithms.Unlock() diff --git a/jwa/compression_gen_test.go b/jwa/compression_gen_test.go index 016e6093e..e5abc5ff4 100644 --- a/jwa/compression_gen_test.go +++ b/jwa/compression_gen_test.go @@ -93,7 +93,7 @@ func TestCompressionAlgorithm(t *testing.T) { 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(`totallyInvfalidValue`), `accept should fail`) { + if !assert.Error(t, dst.Accept(`totallyInvalidValue`), `accept should fail`) { return } }) @@ -114,3 +114,58 @@ func TestCompressionAlgorithm(t *testing.T) { } }) } + +// 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`) + }) + }) +} diff --git a/jwa/content_encryption_gen.go b/jwa/content_encryption_gen.go index 115fa18e0..ddea22858 100644 --- a/jwa/content_encryption_gen.go +++ b/jwa/content_encryption_gen.go @@ -50,7 +50,7 @@ func RegisterContentEncryptionAlgorithm(v ContentEncryptionAlgorithm) { } // UnregisterContentEncryptionAlgorithm unregisters a ContentEncryptionAlgorithm from its known database. -// Non-existentn entries will silently be ignored +// Non-existent entries will silently be ignored func UnregisterContentEncryptionAlgorithm(v ContentEncryptionAlgorithm) { muContentEncryptionAlgorithms.Lock() defer muContentEncryptionAlgorithms.Unlock() diff --git a/jwa/content_encryption_gen_test.go b/jwa/content_encryption_gen_test.go index 7eec98652..44f5fd97d 100644 --- a/jwa/content_encryption_gen_test.go +++ b/jwa/content_encryption_gen_test.go @@ -237,7 +237,7 @@ func TestContentEncryptionAlgorithm(t *testing.T) { 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(`totallyInvfalidValue`), `accept should fail`) { + if !assert.Error(t, dst.Accept(`totallyInvalidValue`), `accept should fail`) { return } }) @@ -262,3 +262,58 @@ func TestContentEncryptionAlgorithm(t *testing.T) { } }) } + +// 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`) + }) + }) +} diff --git a/jwa/elliptic_gen.go b/jwa/elliptic_gen.go index fbfe466aa..bc4da72bd 100644 --- a/jwa/elliptic_gen.go +++ b/jwa/elliptic_gen.go @@ -53,7 +53,7 @@ func RegisterEllipticCurveAlgorithm(v EllipticCurveAlgorithm) { } // UnregisterEllipticCurveAlgorithm unregisters a EllipticCurveAlgorithm from its known database. -// Non-existentn entries will silently be ignored +// Non-existent entries will silently be ignored func UnregisterEllipticCurveAlgorithm(v EllipticCurveAlgorithm) { muEllipticCurveAlgorithms.Lock() defer muEllipticCurveAlgorithms.Unlock() diff --git a/jwa/elliptic_gen_test.go b/jwa/elliptic_gen_test.go index badec268d..71446f0f2 100644 --- a/jwa/elliptic_gen_test.go +++ b/jwa/elliptic_gen_test.go @@ -280,7 +280,7 @@ func TestEllipticCurveAlgorithm(t *testing.T) { 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(`totallyInvfalidValue`), `accept should fail`) { + if !assert.Error(t, dst.Accept(`totallyInvalidValue`), `accept should fail`) { return } }) @@ -311,3 +311,58 @@ func TestEllipticCurveAlgorithm(t *testing.T) { } }) } + +// 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`) + }) + }) +} diff --git a/jwa/jwa.go b/jwa/jwa.go index f9ce38e04..64aef89a8 100644 --- a/jwa/jwa.go +++ b/jwa/jwa.go @@ -13,7 +13,7 @@ import "fmt" // like other fields. // // Ideally we would like to keep track of Signature Algorithms and -// Content Encryption Algorithms separately, and force the APIs to +// 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 { diff --git a/jwa/key_encryption_gen.go b/jwa/key_encryption_gen.go index 49ed1f678..5b28fd962 100644 --- a/jwa/key_encryption_gen.go +++ b/jwa/key_encryption_gen.go @@ -30,11 +30,14 @@ const ( 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() @@ -57,27 +60,63 @@ func init() { 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-existentn entries will silently be ignored +// 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() } } @@ -130,11 +169,8 @@ func (v KeyEncryptionAlgorithm) String() string { return string(v) } -// IsSymmetric returns true if the algorithm is a symmetric type +// IsSymmetric returns true if the algorithm is a symmetric type. func (v KeyEncryptionAlgorithm) IsSymmetric() bool { - switch v { - case A128GCMKW, A128KW, A192GCMKW, A192KW, A256GCMKW, A256KW, DIRECT, PBES2_HS256_A128KW, PBES2_HS384_A192KW, PBES2_HS512_A256KW: - return true - } - return false + _, ok := symmetricKeyEncryptionAlgorithms[v] + return ok } diff --git a/jwa/key_encryption_gen_test.go b/jwa/key_encryption_gen_test.go index 26250b584..6f731dff3 100644 --- a/jwa/key_encryption_gen_test.go +++ b/jwa/key_encryption_gen_test.go @@ -623,6 +623,78 @@ func TestKeyEncryptionAlgorithm(t *testing.T) { 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 @@ -633,7 +705,7 @@ func TestKeyEncryptionAlgorithm(t *testing.T) { 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(`totallyInvfalidValue`), `accept should fail`) { + if !assert.Error(t, dst.Accept(`totallyInvalidValue`), `accept should fail`) { return } }) @@ -690,6 +762,12 @@ func TestKeyEncryptionAlgorithm(t *testing.T) { 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() @@ -711,6 +789,8 @@ func TestKeyEncryptionAlgorithm(t *testing.T) { 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) { @@ -723,3 +803,174 @@ func TestKeyEncryptionAlgorithm(t *testing.T) { } }) } + +// 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`) + }) + }) +} diff --git a/jwa/key_type_gen.go b/jwa/key_type_gen.go index e1f9e3896..dc13146bf 100644 --- a/jwa/key_type_gen.go +++ b/jwa/key_type_gen.go @@ -47,7 +47,7 @@ func RegisterKeyType(v KeyType) { } // UnregisterKeyType unregisters a KeyType from its known database. -// Non-existentn entries will silently be ignored +// Non-existent entries will silently be ignored func UnregisterKeyType(v KeyType) { muKeyTypes.Lock() defer muKeyTypes.Unlock() diff --git a/jwa/key_type_gen_test.go b/jwa/key_type_gen_test.go index 56df50642..3e2b5439b 100644 --- a/jwa/key_type_gen_test.go +++ b/jwa/key_type_gen_test.go @@ -172,7 +172,7 @@ func TestKeyType(t *testing.T) { 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(`totallyInvfalidValue`), `accept should fail`) { + if !assert.Error(t, dst.Accept(`totallyInvalidValue`), `accept should fail`) { return } }) @@ -195,3 +195,58 @@ func TestKeyType(t *testing.T) { } }) } + +// 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`) + }) + }) +} diff --git a/jwa/options.yaml b/jwa/options.yaml new file mode 100644 index 000000000..1b5596abf --- /dev/null +++ b/jwa/options.yaml @@ -0,0 +1,15 @@ +package_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. diff --git a/jwa/options_gen.go b/jwa/options_gen.go new file mode 100644 index 000000000..3a4122f06 --- /dev/null +++ b/jwa/options_gen.go @@ -0,0 +1,33 @@ +// 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)} +} diff --git a/jwa/options_gen_test.go b/jwa/options_gen_test.go new file mode 100644 index 000000000..fefe5aec4 --- /dev/null +++ b/jwa/options_gen_test.go @@ -0,0 +1,13 @@ +// 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()) +} diff --git a/jwa/signature_gen.go b/jwa/signature_gen.go index eaa2f8662..9cff7f048 100644 --- a/jwa/signature_gen.go +++ b/jwa/signature_gen.go @@ -33,6 +33,7 @@ const ( var muSignatureAlgorithms sync.RWMutex var allSignatureAlgorithms map[SignatureAlgorithm]struct{} var listSignatureAlgorithm []SignatureAlgorithm +var symmetricSignatureAlgorithms map[SignatureAlgorithm]struct{} func init() { muSignatureAlgorithms.Lock() @@ -53,27 +54,54 @@ func init() { 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-existentn entries will silently be ignored +// 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() } } @@ -125,3 +153,10 @@ func (v *SignatureAlgorithm) Accept(value interface{}) error { 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 +} diff --git a/jwa/signature_gen_test.go b/jwa/signature_gen_test.go index 647111fd6..f6feb7c5c 100644 --- a/jwa/signature_gen_test.go +++ b/jwa/signature_gen_test.go @@ -561,9 +561,57 @@ func TestSignatureAlgorithm(t *testing.T) { 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(`totallyInvfalidValue`), `accept should fail`) { - return - } + 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() @@ -595,3 +643,174 @@ func TestSignatureAlgorithm(t *testing.T) { } }) } + +// 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`) + }) + }) +} diff --git a/jwe/compress.go b/jwe/compress.go index 45b666132..bc55841bb 100644 --- a/jwe/compress.go +++ b/jwe/compress.go @@ -34,7 +34,6 @@ func uncompress(src []byte, maxBufferSize int64) ([]byte, error) { if readErr != nil { // if it got here, then readErr == io.EOF, we're done - //nolint:nilerr return dst.Bytes(), nil } } diff --git a/jwe/decrypt.go b/jwe/decrypt.go index 8729e4e53..0b26a3c84 100644 --- a/jwe/decrypt.go +++ b/jwe/decrypt.go @@ -278,7 +278,7 @@ func (d *decrypter) BuildKeyDecrypter() (keyenc.Decrypter, error) { } return keyenc.NewRSAPKCS15Decrypt(alg, &privkey, cipher.KeySize()/2), nil - case jwa.RSA_OAEP, jwa.RSA_OAEP_256: + 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) diff --git a/jwe/interface.go b/jwe/interface.go index 828412f67..fa71f991d 100644 --- a/jwe/interface.go +++ b/jwe/interface.go @@ -96,7 +96,7 @@ type stdRecipient struct { // 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 +// 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. // @@ -132,7 +132,7 @@ type Message struct { // These Header Parameter values are not integrity protected. // // JWX note: This field is NOT mutually exclusive with per-recipient - // headers within the implmentation because... it's too much work. + // 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. diff --git a/jwe/internal/aescbc/aescbc.go b/jwe/internal/aescbc/aescbc.go index 057953e77..48d2e6482 100644 --- a/jwe/internal/aescbc/aescbc.go +++ b/jwe/internal/aescbc/aescbc.go @@ -19,21 +19,17 @@ const ( const defaultBufSize int64 = 256 * 1024 * 1024 -// Grr, we would like to use atomic.Int64, but that's only available -// from Go 1.19. Yes, we will cut support for Go 1.19 at some point, -// but not today (probably going to up the minimum required Go version -// some time after 1.22 is released) -var maxBufSize int64 +var maxBufSize atomic.Int64 func init() { - atomic.StoreInt64(&maxBufSize, defaultBufSize) + SetMaxBufferSize(defaultBufSize) } func SetMaxBufferSize(siz int64) { if siz <= 0 { siz = defaultBufSize } - atomic.StoreInt64(&maxBufSize, siz) + maxBufSize.Store(siz) } func pad(buf []byte, n int) []byte { @@ -43,7 +39,7 @@ func pad(buf []byte, n int) []byte { } bufsiz := len(buf) + rem - mbs := atomic.LoadInt64(&maxBufSize) + mbs := maxBufSize.Load() if int64(bufsiz) > mbs { panic(fmt.Errorf("failed to allocate buffer")) } @@ -200,7 +196,8 @@ func ensureSize(dst []byte, n int) []byte { func (c Hmac) Seal(dst, nonce, plaintext, data []byte) []byte { ctlen := len(plaintext) bufsiz := ctlen + c.Overhead() - mbs := atomic.LoadInt64(&maxBufSize) + mbs := maxBufSize.Load() + if int64(bufsiz) > mbs { panic(fmt.Errorf("failed to allocate buffer")) } diff --git a/jwe/internal/keyenc/keyenc.go b/jwe/internal/keyenc/keyenc.go index 2f57a316b..52acb0faa 100644 --- a/jwe/internal/keyenc/keyenc.go +++ b/jwe/internal/keyenc/keyenc.go @@ -390,7 +390,7 @@ func (kw ECDHESDecrypt) Decrypt(enckey []byte) ([]byte, error) { // 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: + 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) } @@ -462,8 +462,12 @@ func (e RSAOAEPEncrypt) EncryptKey(cek []byte) (keygen.ByteSource, error) { 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 required`) + 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 { @@ -536,7 +540,7 @@ func (d RSAPKCS15Decrypt) Decrypt(enckey []byte) ([]byte, error) { // 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: + 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) } @@ -560,8 +564,12 @@ func (d RSAOAEPDecrypt) Decrypt(enckey []byte) ([]byte, error) { 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 required`) + 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{}) } diff --git a/jwe/jwe.go b/jwe/jwe.go index 1e6ecda5a..81ed86cea 100644 --- a/jwe/jwe.go +++ b/jwe/jwe.go @@ -118,7 +118,7 @@ func (b *recipientBuilder) Build(cek []byte, calg jwa.ContentEncryptionAlgorithm return nil, nil, fmt.Errorf(`failed to create RSA PKCS encrypter: %w`, err) } enc = v - case jwa.RSA_OAEP, jwa.RSA_OAEP_256: + 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) @@ -272,7 +272,7 @@ func Encrypt(payload []byte, options ...EncryptOption) ([]byte, error) { return encrypt(payload, nil, options...) } -// Encryptstatic is exactly like Encrypt, except it accepts a static +// 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. @@ -289,7 +289,7 @@ func EncryptStatic(payload, cek []byte, options ...EncryptOption) ([]byte, error return encrypt(payload, cek, options...) } -// encrypt is separate so it can receive cek from outside. +// 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 @@ -624,7 +624,7 @@ func (dctx *decryptCtx) try(ctx context.Context, recipient Recipient, keyUsed in 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.KeyAlgorithm` and `alg jwa.SignatureAlgorithm` + // structs for `alg jwa.KeyEncryptionAlgorithm` and `alg jwa.SignatureAlgorithm` //nolint:forcetypeassert alg := pair.alg.(jwa.KeyEncryptionAlgorithm) key := pair.key diff --git a/jwe/key_provider.go b/jwe/key_provider.go index 746980fca..5d272d6a9 100644 --- a/jwe/key_provider.go +++ b/jwe/key_provider.go @@ -13,7 +13,7 @@ import ( // 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.Derypt()` can accept `jwe.WithKey()`, `jwe.WithKeySet()`, +// while `jwe.Decrypt()` can accept `jwe.WithKey()`, `jwe.WithKeySet()`, // and `jwe.WithKeyProvider()`. // // Understanding how this works is crucial to learn how this package works. @@ -34,11 +34,11 @@ import ( // // 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 signature: +// the key(s) to use on this CEK: // // for r in msg.Recipients { // for kp in keyProviders { -// kp.FetcKeys(ctx, sink, r, msg) +// kp.FetchKeys(ctx, sink, r, msg) // ... // } // } @@ -56,7 +56,7 @@ import ( // // sink.Key(alg, key) // -// These keys are then retrieved and tried for each signature, until +// These keys are then retrieved and tried for each recipient, until // a match is found: // // keys := sink.Keys() diff --git a/jwe/options.go b/jwe/options.go index f31c63518..180d3ab44 100644 --- a/jwe/options.go +++ b/jwe/options.go @@ -44,9 +44,9 @@ func WithPerRecipientHeaders(hdr Headers) WithKeySuboption { // 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.SignatureAlgorithm` +// 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.ContentEncryptionAlgorithm`, +// 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 diff --git a/jwe/options.yaml b/jwe/options.yaml index f465ab2ed..2ab19847c 100644 --- a/jwe/options.yaml +++ b/jwe/options.yaml @@ -81,7 +81,7 @@ options: interface: DecryptOption argument_type: '*Message' comment: | - WithMessage provides a message object to be populated by `jwe.Decrpt` + 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. @@ -93,7 +93,7 @@ options: interface: WithKeySetSuboption argument_type: bool comment: | - WithrequiredKid specifies whether the keys in the jwk.Set should + 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 @@ -122,7 +122,7 @@ options: 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 - signature. + 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 @@ -170,4 +170,4 @@ options: This option has a global effect. Due to historical reasons this option has a vague name, but in future versions - it will be appropriately renamed. \ No newline at end of file + it will be appropriately renamed. diff --git a/jwe/options_gen.go b/jwe/options_gen.go index 6118fb4b7..39a56e390 100644 --- a/jwe/options_gen.go +++ b/jwe/options_gen.go @@ -260,7 +260,7 @@ func WithKeyProvider(v KeyProvider) DecryptOption { // 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 -// signature. +// 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 @@ -310,7 +310,7 @@ func WithMergeProtectedHeaders(v bool) EncryptOption { return &encryptOption{option.New(identMergeProtectedHeaders{}, v)} } -// WithMessage provides a message object to be populated by `jwe.Decrpt` +// 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. // @@ -328,7 +328,7 @@ func WithPretty(v bool) WithJSONSuboption { return &withJSONSuboption{option.New(identPretty{}, v)} } -// WithrequiredKid specifies whether the keys in the jwk.Set should +// 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 { diff --git a/jwk/README.md b/jwk/README.md index 611585787..cdaa3ec81 100644 --- a/jwk/README.md +++ b/jwk/README.md @@ -6,7 +6,7 @@ If you are looking to use JWT wit JWKs, look no further than [github.com/lestrra * 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.AutoRefersh +* Ability to keep a JWKS fresh using *jwk.AutoRefresh ## Supported key types: @@ -26,7 +26,7 @@ If you are looking to use JWT wit JWKs, look no further than [github.com/lestrra 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 +# Auto-Refresh a key during a long-running process ```go @@ -87,7 +87,7 @@ MAIN: // 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 re-fetching the keyset fails, a cached version will be returned from the previous successful + // 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 @@ -95,7 +95,7 @@ MAIN: time.Sleep(time.Second) // Because we're a dummy program, we just cancel the loop now. - // If this were a real program, you prosumably loop forever + // If this were a real program, you presumably loop forever cancel() } // OUTPUT: diff --git a/jwk/cache.go b/jwk/cache.go index 5d5b6b90b..56a7d1d75 100644 --- a/jwk/cache.go +++ b/jwk/cache.go @@ -50,7 +50,7 @@ type Whitelist = httprc.Whitelist // 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 setup a more robust way to both cache +// 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. @@ -72,7 +72,7 @@ type Whitelist = httprc.Whitelist // // 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 perodically refresh the JWKS thereby keeping them +// 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 @@ -87,7 +87,7 @@ type Cache struct { // PostFetcher is an interface for objects that want to perform // operations on the `Set` that was fetched. type PostFetcher interface { - // PostFetch revceives the URL and the JWKS, after a successful + // PostFetch receives the URL and the JWKS, after a successful // fetch and parse. // // It should return a `Set`, optionally modified, to be stored @@ -95,14 +95,14 @@ type PostFetcher interface { PostFetch(string, Set) (Set, error) } -// PostFetchFunc is a PostFetcher based on a functon. +// 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.Transofmer that transforms the response into a JWKS +// httprc.Transformer that transforms the response into a JWKS type jwksTransform struct { postFetch PostFetcher parseOptions []ParseOption @@ -217,7 +217,7 @@ func (c *Cache) Register(u string, options ...RegisterOption) error { } } - // Set the transfomer at the end so that nobody can override it + // Set the transformer at the end so that nobody can override it hrropts = append(hrropts, httprc.WithTransformer(t)) return c.cache.Register(u, hrropts...) } @@ -278,7 +278,7 @@ func (c *Cache) Snapshot() *httprc.Snapshot { return c.cache.Snapshot() } -// CachedSet is a thin shim over jwk.Cache that allows the user to cloack +// 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. // diff --git a/jwk/fetch.go b/jwk/fetch.go index a4315a562..f80d49dfd 100644 --- a/jwk/fetch.go +++ b/jwk/fetch.go @@ -70,7 +70,7 @@ func getGlobalFetcher() httprc.Fetcher { // 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 +// 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` diff --git a/jwk/interface.go b/jwk/interface.go index fa0cff023..7d4f72ed5 100644 --- a/jwk/interface.go +++ b/jwk/interface.go @@ -10,7 +10,7 @@ import ( "github.com/lestrrat-go/jwx/v2/internal/json" ) -// AsymmetricKey describes a Key that represents an key in an asymmetric key pair, +// 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 { diff --git a/jwk/interface_gen.go b/jwk/interface_gen.go index 9ee50516c..090f06764 100644 --- a/jwk/interface_gen.go +++ b/jwk/interface_gen.go @@ -113,7 +113,7 @@ type Key interface { // 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 encompases both types. + // This is why there exists a `jwa.KeyAlgorithm` type that encompasses both types. Algorithm() jwa.KeyAlgorithm // KeyID returns `kid` of a JWK KeyID() string diff --git a/jwk/jwk.go b/jwk/jwk.go index bc14bf7e1..32f378c62 100644 --- a/jwk/jwk.go +++ b/jwk/jwk.go @@ -247,9 +247,9 @@ const ( // 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`), +// (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 +// 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. diff --git a/jwk/jwk_test.go b/jwk/jwk_test.go index 9ed60d370..1365a3204 100644 --- a/jwk/jwk_test.go +++ b/jwk/jwk_test.go @@ -1503,7 +1503,7 @@ c4wOvhbalcX0FqTM3mXCgMFRbibquhwdxbU= type typedField struct { Foo string - Bar int + Bar int64 } func TestTypedFields(t *testing.T) { @@ -1890,7 +1890,7 @@ func TestFetch(t *testing.T) { return } - srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusOK) w.Write(expected) })) @@ -2011,7 +2011,7 @@ func TestGH567(t *testing.T) { ] }` - srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.Header().Set(`Content-Type`, `application/json`) w.WriteHeader(http.StatusOK) diff --git a/jwk/options.yaml b/jwk/options.yaml index 6dfbded42..7687be34a 100644 --- a/jwk/options.yaml +++ b/jwk/options.yaml @@ -22,14 +22,14 @@ interfaces: - readFileOption comment: | ParseOption is a type of Option that can be passed to `jwk.Parse()` - ParseOption also implmentsthe `ReadFileOption` and `CacheOption`, + 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 desribes options that can be passed to `(jwk.Cache).Register()` + RegisterOption describes options that can be passed to `(jwk.Cache).Register()` options: - ident: HTTPClient interface: FetchOption diff --git a/jwk/options_gen.go b/jwk/options_gen.go index 2aac86fc6..dbc428b9d 100644 --- a/jwk/options_gen.go +++ b/jwk/options_gen.go @@ -58,7 +58,7 @@ func (*fetchOption) parseOption() {} func (*fetchOption) registerOption() {} // ParseOption is a type of Option that can be passed to `jwk.Parse()` -// ParseOption also implmentsthe `ReadFileOption` and `CacheOption`, +// ParseOption also implements the `ReadFileOption` and `CacheOption`, // and thus safely be passed to `jwk.ReadFile` and `(*jwk.Cache).Configure()` type ParseOption interface { Option @@ -89,7 +89,7 @@ type readFileOption struct { func (*readFileOption) readFileOption() {} -// RegisterOption desribes options that can be passed to `(jwk.Cache).Register()` +// RegisterOption describes options that can be passed to `(jwk.Cache).Register()` type RegisterOption interface { Option registerOption() diff --git a/jwk/refresh_test.go b/jwk/refresh_test.go index f6c56273c..f7eba22cc 100644 --- a/jwk/refresh_test.go +++ b/jwk/refresh_test.go @@ -67,7 +67,7 @@ func TestCache(t *testing.T) { } } - srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + 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`) @@ -125,7 +125,7 @@ func TestCache(t *testing.T) { defer cancel() var accessCount int - srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { accessCount++ key := map[string]interface{}{ @@ -184,7 +184,7 @@ func TestCache(t *testing.T) { defer cancel() var accessCount int - srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { accessCount++ key := map[string]interface{}{ @@ -248,7 +248,7 @@ func TestCache(t *testing.T) { defer cancel() var accessCount int - srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + 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) @@ -426,7 +426,7 @@ func TestErrorSink(t *testing.T) { })), } }, - Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + Handler: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(k) }), @@ -469,10 +469,10 @@ func TestErrorSink(t *testing.T) { // 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 shold be less than or equal to 7 (%d)", l) { + 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 shold be greather than or equal to 5 (%d)", l) { + if !assert.True(t, l >= 5, "number of errors should be greater than or equal to 5 (%d)", l) { return } }) diff --git a/jwk/whitelist.go b/jwk/whitelist.go index 6b0180d30..9b144dc4e 100644 --- a/jwk/whitelist.go +++ b/jwk/whitelist.go @@ -28,7 +28,7 @@ func (w *RegexpWhitelist) Add(pat *regexp.Regexp) *RegexpWhitelist { return w } -// IsAlloed returns true if any of the patterns in the whitelist +// 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 { diff --git a/jws/README.md b/jws/README.md index 470842ef3..ee2ccd4e2 100644 --- a/jws/README.md +++ b/jws/README.md @@ -74,7 +74,7 @@ func main() { } ``` -## Programatically manipulate `jws.Message` +## Programmatically manipulate `jws.Message` ```go func ExampleMessage() { diff --git a/jws/jws.go b/jws/jws.go index 096e4f22d..3c7e13fcf 100644 --- a/jws/jws.go +++ b/jws/jws.go @@ -221,8 +221,8 @@ func Sign(payload []byte, options ...SignOption) ([]byte, error) { // 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 expcted + // 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 @@ -309,8 +309,8 @@ var allowNoneWhitelist = jwk.WhitelistFunc(func(string) bool { // // 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 counter intuitive when the code says -// you _verified_ something when in fact it did no such thing). If you want to +// 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) { @@ -417,7 +417,7 @@ func Verify(buf []byte, options ...VerifyOption) ([]byte, error) { 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.KeyAlgorithm` and `alg jwa.SignatureAlgorithm` + // structs for `alg jwa.KeyEncryptionAlgorithm` and `alg jwa.SignatureAlgorithm` //nolint:forcetypeassert alg := pair.alg.(jwa.SignatureAlgorithm) key := pair.key diff --git a/jws/jws_test.go b/jws/jws_test.go index ecf54085c..cca85f602 100644 --- a/jws/jws_test.go +++ b/jws/jws_test.go @@ -354,7 +354,7 @@ func TestSignMulti2(t *testing.T) { require.NoError(t, err, "Verify succeeded") require.Equal(t, payload, verified, "verified payload matches") - // XXX This actally doesn't really test much, but if there was anything + // 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") }) @@ -1261,7 +1261,7 @@ func TestJKU(t *testing.T) { require.NoError(t, err, `jwk.PublicKeyOf should succeed`) set := jwk.NewSet() set.AddKey(pubkey) - srv := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + srv := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(set) })) @@ -1384,7 +1384,7 @@ func TestJKU(t *testing.T) { require.Equal(t, pubkey.KeyID(), key.KeyID(), `key ID should be populated`) set.AddKey(pubkey) } - srv := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + srv := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(set) })) @@ -1453,7 +1453,7 @@ func TestJKU(t *testing.T) { return } require.Equal(t, payload, decoded, `decoded payload should match`) - // XXX This actally doesn't really test much, but if there was anything + // 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") } @@ -1629,7 +1629,7 @@ func TestGH888(t *testing.T) { _, 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 signaures with JSON serialization + // 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`) diff --git a/jws/key_provider.go b/jws/key_provider.go index 7d7518af1..a682641a2 100644 --- a/jws/key_provider.go +++ b/jws/key_provider.go @@ -37,7 +37,7 @@ import ( // // for sig in msg.Signatures { // for kp in keyProviders { -// kp.FetcKeys(ctx, sink, sig, msg) +// kp.FetchKeys(ctx, sink, sig, msg) // ... // } // } @@ -48,7 +48,7 @@ import ( // // 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()` fetchs a JWK from the `jku` URL, +// `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 @@ -61,7 +61,7 @@ import ( // // keys := sink.Keys() // for key in keys { -// if givenSignature == makeSignatre(key, payload, ...)) { +// if givenSignature == makeSignature(key, payload, ...)) { // return OK // } // } @@ -221,7 +221,7 @@ func (kp jkuProvider) FetchKeys(ctx context.Context, sink KeySink, sig *Signatur return fmt.Errorf(`use of "jku" requires that the payload contain a "kid" field in the protected header`) } - // errors here can't be reliablly passed to the consumers. + // 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() @@ -254,7 +254,7 @@ func (kp jkuProvider) FetchKeys(ctx context.Context, sink KeySink, sig *Signatur hdrAlg := sig.ProtectedHeaders().Algorithm() for _, alg := range algs { - // if we have a "alg" field in the JWS, we can only proceed if + // if we have an "alg" field in the JWS, we can only proceed if // the inferred algorithm matches if hdrAlg != "" && hdrAlg != alg { continue diff --git a/jws/options.go b/jws/options.go index 55578e6db..55d806224 100644 --- a/jws/options.go +++ b/jws/options.go @@ -9,8 +9,10 @@ import ( type identHeaders struct{} type identInsecureNoSignature struct{} -// WithHeaders allows you to specify extra header values to include in the -// final JWS message +// 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)} } @@ -44,7 +46,7 @@ type withKey struct { public Headers } -// This exist as escape hatches to modify the header values after the fact +// 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 @@ -57,17 +59,17 @@ func (w *withKey) Protected(v Headers) Headers { // 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.ContentEncryptionAlgorithm`, +// 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. +// 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 followin is accepted for the `key` parameter: +// 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 @@ -91,7 +93,7 @@ func (w *withKey) Protected(v Headers) Headers { // 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 furher accept suboptions to change signing behavior +// `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. // @@ -189,7 +191,7 @@ type withInsecureNoSignature struct { protected Headers } -// This exist as escape hatches to modify the header values after the fact +// 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 @@ -205,7 +207,7 @@ func (w *withInsecureNoSignature) Protected(v Headers) Headers { // results in an error when `jws.Sign()` is called -- we do not allow using // "none" by accident) // -// TODO: create specific sub-option set for this option +// TODO: create specific suboption set for this option func WithInsecureNoSignature(options ...WithKeySuboption) SignOption { var protected Headers for _, option := range options { diff --git a/jws/options.yaml b/jws/options.yaml index 75a2de821..a175c1c1f 100644 --- a/jws/options.yaml +++ b/jws/options.yaml @@ -23,7 +23,7 @@ interfaces: - name: WithJSONSuboption concrete_type: withJSONSuboption comment: | - JSONSuboption describes suboptions that can be passed to `jws.WithJSON()` option + 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()` @@ -147,7 +147,7 @@ options: argument_type: bool comment: | WithUseDefault specifies that if and only if a jwk.Key contains - exactly one jwk.Key, that tkey should be used. + exactly one jwk.Key, that key should be used. (I think this should be removed) - ident: RequireKid interface: WithKeySetSuboption diff --git a/jws/options_gen.go b/jws/options_gen.go index fbef1ef3f..689b7ada2 100644 --- a/jws/options_gen.go +++ b/jws/options_gen.go @@ -112,7 +112,7 @@ func (*verifyOption) verifyOption() {} func (*verifyOption) parseOption() {} -// JSONSuboption describes suboptions that can be passed to `jws.WithJSON()` option +// JSONSuboption describes suboptions that can be passed to the `jws.WithJSON()` option. type WithJSONSuboption interface { Option withJSONSuboption() @@ -360,7 +360,7 @@ func WithCompact() SignVerifyParseOption { } // WithUseDefault specifies that if and only if a jwk.Key contains -// exactly one jwk.Key, that tkey should be used. +// 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)} diff --git a/jws/signer.go b/jws/signer.go index 434d51bc2..3dacc36f4 100644 --- a/jws/signer.go +++ b/jws/signer.go @@ -29,7 +29,7 @@ var signerDB map[jwa.SignatureAlgorithm]SignerFactory // // Unlike the `UnregisterSigner` function, this function automatically // calls `jwa.RegisterSignatureAlgorithm` to register the algorithm -// in the known algorithms database. +// in this module's algorithm database. func RegisterSigner(alg jwa.SignatureAlgorithm, f SignerFactory) { jwa.RegisterSignatureAlgorithm(alg) muSignerDB.Lock() @@ -45,7 +45,7 @@ func RegisterSigner(alg jwa.SignatureAlgorithm, f SignerFactory) { // by the factory. // // Note that when you call this function, the algorithm itself is -// not automatically unregistered from the known algorithms database. +// 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 diff --git a/jws/verifier.go b/jws/verifier.go index 2dd29c848..eaccd77f9 100644 --- a/jws/verifier.go +++ b/jws/verifier.go @@ -28,7 +28,7 @@ var verifierDB map[jwa.SignatureAlgorithm]VerifierFactory // // Unlike the `UnregisterVerifier` function, this function automatically // calls `jwa.RegisterSignatureAlgorithm` to register the algorithm -// in the known algorithms database. +// in this module's algorithm database. func RegisterVerifier(alg jwa.SignatureAlgorithm, f VerifierFactory) { jwa.RegisterSignatureAlgorithm(alg) muVerifierDB.Lock() @@ -40,7 +40,7 @@ func RegisterVerifier(alg jwa.SignatureAlgorithm, f VerifierFactory) { // the given algorithm. // // Note that when you call this function, the algorithm itself is -// not automatically unregistered from the known algorithms database. +// 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 diff --git a/jwt/http.go b/jwt/http.go index 706d8350d..149136f15 100644 --- a/jwt/http.go +++ b/jwt/http.go @@ -169,7 +169,7 @@ func ParseRequest(req *http.Request, options ...ParseOption) (Token, error) { return tok, nil } - // Everything below is a preulde to error reporting. + // Everything below is a prelude to error reporting. var triedHdrs strings.Builder for i, hdrkey := range hdrkeys { if i > 0 { diff --git a/jwt/internal/types/date.go b/jwt/internal/types/date.go index 0878397f6..8dd40389f 100644 --- a/jwt/internal/types/date.go +++ b/jwt/internal/types/date.go @@ -149,8 +149,8 @@ func (n NumericDate) String() string { 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 balue + // 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) { diff --git a/jwt/jwt.go b/jwt/jwt.go index c09ea8bf4..74544ca79 100644 --- a/jwt/jwt.go +++ b/jwt/jwt.go @@ -120,14 +120,14 @@ func ParseString(s string, options ...ParseOption) (Token, error) { // 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, +// 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 workaround this by modifying +// 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 @@ -409,7 +409,7 @@ OUTER: // // 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 jwt.WithHeaders option. +// 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 { diff --git a/jwt/jwt_test.go b/jwt/jwt_test.go index 04379c306..5fd0984e7 100644 --- a/jwt/jwt_test.go +++ b/jwt/jwt_test.go @@ -808,6 +808,7 @@ func TestCustomField(t *testing.T) { 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) @@ -835,7 +836,7 @@ func TestParseRequest(t *testing.T) { Parse: func(req *http.Request) (jwt.Token, error) { return jwt.ParseRequest(req, jwt.WithHeaderKey("Authorization"), - jwt.WithHeaderKey("x-authorization"), + jwt.WithHeaderKey(xauth), jwt.WithFormKey("access_token"), jwt.WithFormKey("token"), jwt.WithCookieKey("cookie"), @@ -885,30 +886,30 @@ func TestParseRequest(t *testing.T) { return req }, Parse: func(req *http.Request) (jwt.Token, error) { - return jwt.ParseRequest(req, jwt.WithHeaderKey("x-authorization"), jwt.WithKey(jwa.ES256, pubkey)) + return jwt.ParseRequest(req, jwt.WithHeaderKey(xauth), jwt.WithKey(jwa.ES256, pubkey)) }, Error: true, }, { - Name: "Token in x-authorization header (w/ option)", + Name: fmt.Sprintf("Token in %s header (w/ option)", xauth), Request: func() *http.Request { req := httptest.NewRequest(http.MethodGet, u, nil) - req.Header.Add("x-authorization", string(signed)) + req.Header.Add(xauth, string(signed)) return req }, Parse: func(req *http.Request) (jwt.Token, error) { - return jwt.ParseRequest(req, jwt.WithHeaderKey("x-authorization"), jwt.WithKey(jwa.ES256, pubkey)) + return jwt.ParseRequest(req, jwt.WithHeaderKey(xauth), jwt.WithKey(jwa.ES256, pubkey)) }, }, { - Name: "Invalid token in x-authorization header", + Name: fmt.Sprintf("Invalid token in %s header", xauth), Request: func() *http.Request { req := httptest.NewRequest(http.MethodGet, u, nil) - req.Header.Add("x-authorization", string(signed)+"foobarbaz") + req.Header.Add(xauth, string(signed)+"foobarbaz") return req }, Parse: func(req *http.Request) (jwt.Token, error) { - return jwt.ParseRequest(req, jwt.WithHeaderKey("x-authorization"), jwt.WithKey(jwa.ES256, pubkey)) + return jwt.ParseRequest(req, jwt.WithHeaderKey(xauth), jwt.WithKey(jwa.ES256, pubkey)) }, Error: true, }, @@ -1166,7 +1167,7 @@ func TestGH375(t *testing.T) { type Claim struct { Foo string - Bar int + Bar int64 } func TestJWTParseWithTypedClaim(t *testing.T) { diff --git a/jwt/openid/openid_test.go b/jwt/openid/openid_test.go index 15aba8e20..7d9a60096 100644 --- a/jwt/openid/openid_test.go +++ b/jwt/openid/openid_test.go @@ -478,10 +478,10 @@ func TestOpenIDClaims(t *testing.T) { t.Run(token.Name, func(t *testing.T) { for _, value := range base { value := value - t.Run(value.Key, func(t *testing.T) { + t.Run(value.Key, func(_ *testing.T) { value.Check(token.Token) }) - t.Run(value.Key+" via Get()", func(t *testing.T) { + t.Run(value.Key+" via Get()", func(_ *testing.T) { expected := value.Value if expf := value.Expected; expf != nil { expected = expf(value.Value) diff --git a/jwt/options.go b/jwt/options.go index 0403e0df9..73162e8b2 100644 --- a/jwt/options.go +++ b/jwt/options.go @@ -118,7 +118,7 @@ type withKey struct { options []Option } -// WithKey is a multi-purpose option. It can be used for either jwt.Sign, jwt.Parse (and +// 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`. @@ -131,7 +131,7 @@ type withKey struct { // // 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 it will be an error. +// detected, and an error will occur. func WithKey(alg jwa.KeyAlgorithm, key interface{}, suboptions ...Option) SignEncryptParseOption { return &signEncryptParseOption{option.New(identKey{}, &withKey{ alg: alg, diff --git a/jwt/options.yaml b/jwt/options.yaml index 7b2241031..bd59bb08d 100644 --- a/jwt/options.yaml +++ b/jwt/options.yaml @@ -51,7 +51,7 @@ options: interface: ValidateOption argument_type: time.Duration comment: | - WithTruncation speficies the amount that should be used when + 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 @@ -113,7 +113,7 @@ options: should be serialized in JWS compact form only, but historically this library allowed for deserialization of JWTs in JWS's JSON serialization format. Specifying this option will disable this behavior, and will report - errots if the token is not in compact serialization format. + errors if the token is not in compact serialization format. - ident: FormKey interface: ParseOption argument_type: string @@ -152,8 +152,8 @@ options: interface: ParseOption argument_type: Token comment: | - WithToken specifies the token instance where the result JWT is stored - when parsing JWT tokensthat is used when parsing + WithToken specifies the token instance in which the resulting JWT is stored + when parsing JWT tokens - ident: Validate interface: ParseOption argument_type: bool @@ -199,14 +199,14 @@ options: argument_type: jwe.EncryptOption comment: | WithEncryptOption provides an escape hatch for cases where extra options to - `(jws.Serializer).Encrypt()` must be specified when usng `jwt.Sign()`. Normally you do not + `(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 usng `jwt.Sign()`. Normally you do not + `jws.Sign()` must be specified when using `jwt.Sign()`. Normally you do not need to use this. - ident: Validator interface: ValidateOption @@ -234,14 +234,14 @@ options: 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 fractionals), max is 9 (nanosecond) + 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 fractionals), max is 9 (nanosecond) + fields. Default is 0 (second, no fractions), max is 9 (nanosecond) - ident: NumericDateParsePedantic interface: GlobalOption argument_type: bool @@ -251,7 +251,7 @@ options: 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-comforming servers. + 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 diff --git a/jwt/options_gen.go b/jwt/options_gen.go index 9a545f866..5486b3ef3 100644 --- a/jwt/options_gen.go +++ b/jwt/options_gen.go @@ -256,7 +256,7 @@ func WithClock(v Clock) ValidateOption { // should be serialized in JWS compact form only, but historically this library // allowed for deserialization of JWTs in JWS's JSON serialization format. // Specifying this option will disable this behavior, and will report -// errots if the token is not in compact serialization format. +// errors if the token is not in compact serialization format. func WithCompactOnly(v bool) GlobalOption { return &globalOption{option.New(identCompactOnly{}, v)} } @@ -290,7 +290,7 @@ func WithCookieKey(v string) ParseOption { } // WithEncryptOption provides an escape hatch for cases where extra options to -// `(jws.Serializer).Encrypt()` must be specified when usng `jwt.Sign()`. Normally you do not +// `(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)} @@ -336,7 +336,7 @@ func WithKeyProvider(v jws.KeyProvider) ParseOption { // 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 fractionals), max is 9 (nanosecond) +// fields. Default is 0 (second, no fractions), max is 9 (nanosecond) func WithNumericDateFormatPrecision(v int) GlobalOption { return &globalOption{option.New(identNumericDateFormatPrecision{}, v)} } @@ -346,7 +346,7 @@ func WithNumericDateFormatPrecision(v int) GlobalOption { // 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-comforming servers. +// 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 @@ -356,7 +356,7 @@ func WithNumericDateParsePedantic(v bool) GlobalOption { // 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 fractionals), max is 9 (nanosecond) +// fields. Default is 0 (second, no fractions), max is 9 (nanosecond) func WithNumericDateParsePrecision(v int) GlobalOption { return &globalOption{option.New(identNumericDateParsePrecision{}, v)} } @@ -390,19 +390,19 @@ func WithResetValidators(v bool) ValidateOption { } // WithSignOption provides an escape hatch for cases where extra options to -// `jws.Sign()` must be specified when usng `jwt.Sign()`. Normally you do not +// `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 where the result JWT is stored -// when parsing JWT tokensthat is used when parsing +// 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 speficies the amount that should be used when +// 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 diff --git a/jwt/serialize.go b/jwt/serialize.go index 1a5e467d0..8291ba8b7 100644 --- a/jwt/serialize.go +++ b/jwt/serialize.go @@ -40,18 +40,18 @@ func (e errStep) Serialize(_ SerializeCtx, _ interface{}) (interface{}, error) { return nil, e.err } -// Serializer is a generic serializer for JWTs. Whereas other conveinience +// 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. +// 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.NewSerialer(). +// serialized, err := jwt.NewSerializer(). // Sign(jwa.RS256, key). // Encrypt(jwa.RSA_OAEP, key.PublicKey). // Serialize(token) diff --git a/jwt/validate.go b/jwt/validate.go index 3638b5f33..b11634857 100644 --- a/jwt/validate.go +++ b/jwt/validate.go @@ -453,8 +453,8 @@ type claimContainsString struct { } // 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. +// 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, diff --git a/jwt/validate_test.go b/jwt/validate_test.go index 40d2893c5..fad9c5613 100644 --- a/jwt/validate_test.go +++ b/jwt/validate_test.go @@ -278,7 +278,7 @@ func TestGHIssue10(t *testing.T) { }, }, { - Name: `clock is set to some subseconds before nbf`, + 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) })), @@ -286,13 +286,13 @@ func TestGHIssue10(t *testing.T) { }, }, { - Name: `clock is set to some subseconds before nbf (but truncation = default)`, + 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 subseconds after nbf`, + 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), @@ -314,7 +314,7 @@ func TestGHIssue10(t *testing.T) { 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 ErrTokenExpierd`) { + 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`) { @@ -328,7 +328,7 @@ func TestGHIssue10(t *testing.T) { tm := time.Now() t1, err := jwt.NewBuilder(). - // issuedat = 1 Hr before current time + // 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). @@ -362,7 +362,7 @@ func TestGHIssue10(t *testing.T) { }, }, { - Name: `clock is set to some subseconds after exp`, + 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) })), @@ -370,14 +370,14 @@ func TestGHIssue10(t *testing.T) { }, }, { - Name: `clock is set to some subseconds after exp (but truncation = default)`, + 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 subseconds before exp`, + 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), @@ -398,7 +398,7 @@ func TestGHIssue10(t *testing.T) { 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 ErrTokenExpierd`) { + 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`) { @@ -445,7 +445,7 @@ func TestGHIssue10(t *testing.T) { t.Parallel() tm := time.Now() t1, err := jwt.NewBuilder(). - // issuedat = 1 Hr before current time + // 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)). diff --git a/jwx_test.go b/jwx_test.go index b21e15186..7145ee45d 100644 --- a/jwx_test.go +++ b/jwx_test.go @@ -188,7 +188,7 @@ func TestJoseCompatibility(t *testing.T) { var tests []interopTest - for _, keyenc := range []jwa.KeyEncryptionAlgorithm{jwa.RSA1_5, jwa.RSA_OAEP, jwa.RSA_OAEP_256} { + 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 @@ -297,7 +297,7 @@ func joseInteropTest(ctx context.Context, spec interopTest, t *testing.T) { t.Run("Parse JWK via jwx", func(t *testing.T) { switch spec.alg { - case jwa.RSA1_5, jwa.RSA_OAEP, jwa.RSA_OAEP_256: + 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 diff --git a/tools/cmd/genjwa/main.go b/tools/cmd/genjwa/main.go index b7f8a7f66..00fb74291 100644 --- a/tools/cmd/genjwa/main.go +++ b/tools/cmd/genjwa/main.go @@ -149,9 +149,10 @@ func _main() error { }, }, { - 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`, + 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`, @@ -161,16 +162,19 @@ func _main() error { 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`, @@ -230,9 +234,10 @@ func _main() error { }, }, { - 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`, + 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`, @@ -249,25 +254,39 @@ func _main() error { 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`, @@ -293,31 +312,37 @@ func _main() error { 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, }, }, }, @@ -343,10 +368,11 @@ func _main() error { } type typ struct { - name string - comment string - filename string - elements []element + name string + comment string + filename string + elements []element + symmetric bool } type element struct { @@ -354,20 +380,7 @@ type element struct { value string comment string invalid bool -} - -var isSymmetricKeyEncryption = map[string]struct{}{ - `A128KW`: {}, - `A192KW`: {}, - `A256KW`: {}, - `DIRECT`: {}, - `A128GCMKW`: {}, - `A192GCMKW`: {}, - `A256GCMKW`: {}, - - `PBES2_HS256_A128KW`: {}, - `PBES2_HS384_A192KW`: {}, - `PBES2_HS512_A256KW`: {}, + sym bool } func (t typ) Generate() error { @@ -381,6 +394,9 @@ func (t typ) Generate() error { o.LL("import (") pkgs := []string{ "fmt", + "sort", + "sync", + "strings", } for _, pkg := range pkgs { o.L("%s", strconv.Quote(pkg)) @@ -400,12 +416,15 @@ func (t typ) Generate() error { } o.L(")") // end const - // Register%s and related tools are provided so users can register their own types. + // 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) @@ -416,27 +435,73 @@ func (t typ) Generate() error { 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("}") - 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("}") + 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-existentn entries will silently be ignored") + 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("}") @@ -490,27 +555,14 @@ func (t typ) Generate() error { o.L("return string(v)") o.L("}") - if t.name == "KeyEncryptionAlgorithm" { - o.LL("// IsSymmetric returns true if the algorithm is a symmetric type") - o.L("func (v %s) IsSymmetric() bool {", t.name) - o.L("switch v {") - o.L("case ") - var count int - for _, e := range t.elements { - if _, ok := isSymmetricKeyEncryption[e.name]; !ok { - continue - } - if count == 0 { - o.R("%s", e.name) - } else { - o.R(",%s", e.name) - } - count++ + 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.R(":") - o.L("return true") - o.L("}") - o.L("return false") + o.L("func (v %s) IsSymmetric() bool {", t.name) + o.L("_, ok := symmetric%[1]ss[v]", t.name) + o.L("return ok") o.L("}") } @@ -616,17 +668,17 @@ func (t typ) GenerateTest() error { 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(`totallyInvfalidValue`), `accept should fail`) {") + o.L("if !assert.Error(t, dst.Accept(`totallyInvalidValue`), `accept should fail`) {") o.L("return") o.L("}") o.L("})") - if t.name == "KeyEncryptionAlgorithm" { + 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 _, ok := isSymmetricKeyEncryption[e.name]; ok { + 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) @@ -662,7 +714,135 @@ func (t typ) GenerateTest() error { 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) diff --git a/tools/cmd/genjwk/main.go b/tools/cmd/genjwk/main.go index 0ddad9117..335a3201b 100644 --- a/tools/cmd/genjwk/main.go +++ b/tools/cmd/genjwk/main.go @@ -698,7 +698,7 @@ func generateGenericHeaders(fields codegen.FieldList) error { 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 encompases both types.") + 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 != "" { diff --git a/tools/cmd/genoptions/main.go b/tools/cmd/genoptions/main.go index 52232062e..f2727c993 100644 --- a/tools/cmd/genoptions/main.go +++ b/tools/cmd/genoptions/main.go @@ -139,7 +139,7 @@ func genOptions(objects *Objects) error { o.LL(`package %s`, objects.PackageName) imports := append(objects.Imports, []string{ - `io/fs`, // for some reason without this the goimports in my environment tries to import a differnet package + `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`,